From a13b0dce4574e0be62b4d550c483dd0f97ef77f9 Mon Sep 17 00:00:00 2001 From: Tyler Wilding Date: Sat, 3 Aug 2024 02:42:34 -0400 Subject: [PATCH] ci: Add workflow that compares the compiled output between the PR and master (#3626) Fixes #3063 --- .github/workflows/compiler-output-check.yaml | 96 ++++++++++++++++++++ .gitignore | 3 +- common/util/DgoWriter.cpp | 19 +++- out/jak1/fr3/.gitignore | 3 - out/jak1/iso/.gitignore | 3 - out/jak1/obj/.gitignore | 3 - out/jak2/fr3/.gitignore | 3 - out/jak2/iso/.gitignore | 3 - out/jak2/obj/.gitignore | 3 - out/jak3/fr3/.gitignore | 3 - out/jak3/iso/.gitignore | 3 - out/jak3/obj/.gitignore | 3 - scripts/gsrc/compare-compilation-outputs.py | 62 ++++++++++++- scripts/gsrc/jak1-sprite-adjustments.py | 1 - 14 files changed, 170 insertions(+), 38 deletions(-) create mode 100644 .github/workflows/compiler-output-check.yaml delete mode 100644 out/jak1/fr3/.gitignore delete mode 100644 out/jak1/iso/.gitignore delete mode 100644 out/jak1/obj/.gitignore delete mode 100644 out/jak2/fr3/.gitignore delete mode 100644 out/jak2/iso/.gitignore delete mode 100644 out/jak2/obj/.gitignore delete mode 100644 out/jak3/fr3/.gitignore delete mode 100644 out/jak3/iso/.gitignore delete mode 100644 out/jak3/obj/.gitignore diff --git a/.github/workflows/compiler-output-check.yaml b/.github/workflows/compiler-output-check.yaml new file mode 100644 index 0000000000..3cfbb90277 --- /dev/null +++ b/.github/workflows/compiler-output-check.yaml @@ -0,0 +1,96 @@ +name: Compilation Check + +on: + pull_request: + branches: + - master + +jobs: + build: + name: Compare + runs-on: ubuntu-20.04 + timeout-minutes: 60 + + steps: + - name: Checkout Repository + uses: actions/checkout@v4 + with: + ref: master + + - name: Install Package Dependencies + run: | + sudo apt update + sudo apt install build-essential cmake \ + clang gcc g++ lcov make nasm libxrandr-dev \ + libxinerama-dev libxcursor-dev libpulse-dev \ + libxi-dev zip ninja-build libgl1-mesa-dev libssl-dev + + - name: Setup sccache + uses: hendrikmuhs/ccache-action@v1.2.13 + with: + variant: sccache + key: linux-ubuntu-20.04--Release-linux-clang-asan-${{ github.sha }} + restore-keys: linux-ubuntu-20.04--Release-linux-clang-asan + max-size: 1000M + + - name: CMake Generation (master) + env: + CC: clang + CXX: clang++ + run: | + cmake -B build --preset=Release-linux-clang-asan \ + -DCMAKE_C_COMPILER_LAUNCHER=sccache \ + -DCMAKE_CXX_COMPILER_LAUNCHER=sccache + + - name: Build goalc (master) + run: cmake --build build --parallel $((`nproc`)) --target goalc + + - name: Compile and preserve (master) + run: | + ./build/goalc/goalc --game jak1 --cmd "(make-group \"all-code\")" + ./build/goalc/goalc --game jak2 --cmd "(make-group \"all-code\")" + ./build/goalc/goalc --game jak3 --cmd "(make-group \"all-code\")" + mv ./out/jak1/obj ./out/jak1/obj.master + mv ./out/jak2/obj ./out/jak2/obj.master + mv ./out/jak3/obj ./out/jak3/obj.master + + - name: Checkout PR + uses: actions/checkout@v4 + with: + clean: "false" + + - name: CMake Generation (PR) + env: + CC: clang + CXX: clang++ + run: | + cmake -B build --preset=Release-linux-clang-asan \ + -DCMAKE_C_COMPILER_LAUNCHER=sccache \ + -DCMAKE_CXX_COMPILER_LAUNCHER=sccache + + - name: Build goalc (PR) + run: cmake --build build --parallel $((`nproc`)) --target goalc + + - name: Compile and preserve (PR) + run: | + ./build/goalc/goalc --game jak1 --cmd "(make-group \"all-code\")" + ./build/goalc/goalc --game jak2 --cmd "(make-group \"all-code\")" + ./build/goalc/goalc --game jak3 --cmd "(make-group \"all-code\")" + mv ./out/jak1/obj ./out/jak1/obj.pr + mv ./out/jak2/obj ./out/jak2/obj.pr + mv ./out/jak3/obj ./out/jak3/obj.pr + + - name: Compare Results and Produce Report + run: | + ls -l ./out/jak1 + ls -l ./out/jak2 + ls -l ./out/jak3 + set +e + python ./scripts/gsrc/compare-compilation-outputs.py --base "./out/jak1/obj.master,./out/jak2/obj.master,./out/jak3/obj.master" --compare "./out/jak1/obj.pr,./out/jak2/obj.pr,./out/jak3/obj.pr" --markdown + SCRIPT_EXIT_CODE=$? + cat ./comp-diff-report.md >> $GITHUB_STEP_SUMMARY + if [ "$SCRIPT_EXIT_CODE" -ne 0 ]; then + exit 1 + fi + + diff --git a/.gitignore b/.gitignore index 620924397f..47eda51112 100644 --- a/.gitignore +++ b/.gitignore @@ -32,6 +32,7 @@ linux-default/ *.bin *.log *.p2s +out/ savestate-out/ savestate_out/ failures/ @@ -39,6 +40,7 @@ ee-results*.json search-results.json .env /search-results.json +comp-diff-report.md # graphics debug debug_out/ @@ -67,7 +69,6 @@ custom_assets/jak3/merc_replacements/* svnrev.h common/versions/revision.h ci-artifacts/ -out/build/ __pycache__/ # sqlite stuff diff --git a/common/util/DgoWriter.cpp b/common/util/DgoWriter.cpp index 03e7c02b46..6466e3b19c 100644 --- a/common/util/DgoWriter.cpp +++ b/common/util/DgoWriter.cpp @@ -8,6 +8,8 @@ #include "BinaryWriter.h" #include "FileUtil.h" +#include "common/log/log.h" + void build_dgo(const DgoDescription& description, const std::string& output_prefix) { BinaryWriter writer; // dgo header @@ -15,8 +17,14 @@ void build_dgo(const DgoDescription& description, const std::string& output_pref writer.add_cstr_len(description.dgo_name.c_str(), 60); for (auto& obj : description.entries) { - auto obj_data = file_util::read_binary_file(file_util::get_jak_project_dir() / "out" / - output_prefix / "obj" / obj.file_name); + // Ensure the directory exists + const auto file_path = + file_util::get_jak_project_dir() / "out" / output_prefix / "obj" / obj.file_name; + if (!file_util::file_exists(file_path.string())) { + lg::warn("tried to build a DGO with a file that doesn't exist: {}", file_path.string()); + continue; + } + auto obj_data = file_util::read_binary_file(file_path); // size writer.add(obj_data.size()); // name @@ -29,6 +37,9 @@ void build_dgo(const DgoDescription& description, const std::string& output_pref } } - writer.write_to_file(file_util::get_jak_project_dir() / "out" / output_prefix / "iso" / - description.dgo_name); + // Ensure the output directory exists + const auto dgo_path = + file_util::get_jak_project_dir() / "out" / output_prefix / "iso" / description.dgo_name; + file_util::create_dir_if_needed_for_file(dgo_path); + writer.write_to_file(dgo_path); } diff --git a/out/jak1/fr3/.gitignore b/out/jak1/fr3/.gitignore deleted file mode 100644 index 268b9c8dd5..0000000000 --- a/out/jak1/fr3/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -* -!.gitignore -!hash.md5 diff --git a/out/jak1/iso/.gitignore b/out/jak1/iso/.gitignore deleted file mode 100644 index 268b9c8dd5..0000000000 --- a/out/jak1/iso/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -* -!.gitignore -!hash.md5 diff --git a/out/jak1/obj/.gitignore b/out/jak1/obj/.gitignore deleted file mode 100644 index 268b9c8dd5..0000000000 --- a/out/jak1/obj/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -* -!.gitignore -!hash.md5 diff --git a/out/jak2/fr3/.gitignore b/out/jak2/fr3/.gitignore deleted file mode 100644 index 268b9c8dd5..0000000000 --- a/out/jak2/fr3/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -* -!.gitignore -!hash.md5 diff --git a/out/jak2/iso/.gitignore b/out/jak2/iso/.gitignore deleted file mode 100644 index 268b9c8dd5..0000000000 --- a/out/jak2/iso/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -* -!.gitignore -!hash.md5 diff --git a/out/jak2/obj/.gitignore b/out/jak2/obj/.gitignore deleted file mode 100644 index 268b9c8dd5..0000000000 --- a/out/jak2/obj/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -* -!.gitignore -!hash.md5 diff --git a/out/jak3/fr3/.gitignore b/out/jak3/fr3/.gitignore deleted file mode 100644 index 268b9c8dd5..0000000000 --- a/out/jak3/fr3/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -* -!.gitignore -!hash.md5 diff --git a/out/jak3/iso/.gitignore b/out/jak3/iso/.gitignore deleted file mode 100644 index 268b9c8dd5..0000000000 --- a/out/jak3/iso/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -* -!.gitignore -!hash.md5 diff --git a/out/jak3/obj/.gitignore b/out/jak3/obj/.gitignore deleted file mode 100644 index 268b9c8dd5..0000000000 --- a/out/jak3/obj/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -* -!.gitignore -!hash.md5 diff --git a/scripts/gsrc/compare-compilation-outputs.py b/scripts/gsrc/compare-compilation-outputs.py index 714a0507f4..8ad21930a2 100644 --- a/scripts/gsrc/compare-compilation-outputs.py +++ b/scripts/gsrc/compare-compilation-outputs.py @@ -4,6 +4,19 @@ import os import hashlib +import argparse + +parser = argparse.ArgumentParser("compare-compilation-outputs") +parser.add_argument("--base", help="The base branch directories", type=str) +parser.add_argument("--compare", help="The potentially modified directories to compare", type=str) +parser.add_argument("--markdown", help="The format to output results as a markdown file './comp-diff-report.md'", action="store_true") +args = parser.parse_args() + +to_markdown_file = False +if args.markdown: + to_markdown_file = True + +markdown_lines = [] def hash_file(filepath): """Returns the MD5 hash of the file.""" @@ -19,8 +32,13 @@ def compare_directories(base_dir, compare_dir): missing_files = [] # Iterate through files in the base directory + total_files = 0 for root, _, files in os.walk(base_dir): for file in files: + if file == ".gitignore": + continue + total_files = total_files + 1 + base_file_path = os.path.join(root, file) relative_path = os.path.relpath(base_file_path, base_dir) compare_file_path = os.path.join(compare_dir, relative_path) @@ -34,20 +52,54 @@ def compare_directories(base_dir, compare_dir): missing_files.append(relative_path) # Report results + print(f'Comparing {base_dir} with {compare_dir}') + markdown_lines.append(f'### Comparing `{base_dir}` with `{compare_dir}`\n\n') if not mismatched_files and not missing_files: print("All files matched successfully.") + markdown_lines.append(f'All `{total_files}` files matched successfully ✅\n\n') + return 0 else: + markdown_lines.append(f'Found potential problems ❌\n') + markdown_lines.append(f'- {len(mismatched_files)} different file(s)\n') + markdown_lines.append(f'- {len(missing_files)} missing file(s)\n\n') + markdown_lines.append("| file | result |\n") + markdown_lines.append("|------|--------|\n") if mismatched_files: print("Mismatched files:") + markdown_printed_already = 0 for file in mismatched_files: print(f" - {file}") + if markdown_printed_already < 25: + markdown_lines.append(f"| `{file}` | different |\n") + markdown_printed_already = markdown_printed_already + 1 + if len(mismatched_files) > 25: + markdown_lines.append(f"| ...and {len(mismatched_files) - 25} other files | different |\n") if missing_files: print("Missing files:") + markdown_printed_already = 0 for file in missing_files: print(f" - {file}") + if markdown_printed_already < 25: + markdown_lines.append(f"| `{file}` | missing |\n") + markdown_printed_already = markdown_printed_already + 1 + if len(missing_files) > 25: + markdown_lines.append(f"| ...and {len(missing_files) - 25} other files | missing |\n") + markdown_lines.append("\n") + return 1 -# Usage example -base_directory = './out/jak1/obj' -compare_directory = './out/jak1/obj_master' -print(f'Comparing {base_directory} with {compare_directory}') -compare_directories(base_directory, compare_directory) +print(f"base: {args.base}") +print(f"compare: {args.compare}") +base_directory_list = args.base.split(",") +compare_directory_list = args.compare.split(",") +result = 0 +for base_dir, comp_dir in zip(base_directory_list, compare_directory_list): + latest_result = compare_directories(base_dir, comp_dir) + if latest_result != 0: + result = latest_result + +if to_markdown_file: + with open('./comp-diff-report.md', 'w', encoding='utf-8') as md_file: + md_file.writelines(markdown_lines) + print("Wrote results to ./comp-diff-report.md") + +exit(result) \ No newline at end of file diff --git a/scripts/gsrc/jak1-sprite-adjustments.py b/scripts/gsrc/jak1-sprite-adjustments.py index 45f8b49d25..231f822ecd 100644 --- a/scripts/gsrc/jak1-sprite-adjustments.py +++ b/scripts/gsrc/jak1-sprite-adjustments.py @@ -276,4 +276,3 @@ for adjust in adjustments: function_string += f" ((fequal-epsilon? aspect-ratio {aspect_ratio} 0.01) {value:.1f})\n" function_string += f" (else\n (+ {coefficients[0]}\n (* {coefficients[1]} aspect-ratio)\n (* {coefficients[2]} aspect-ratio aspect-ratio)\n (* {coefficients[3]} aspect-ratio aspect-ratio aspect-ratio)))))\n" print(function_string) - \ No newline at end of file