From 9a24d55d8c679291da90f771c5a580823f8f3beb Mon Sep 17 00:00:00 2001 From: Marcin Kurczewski Date: Sat, 13 Apr 2024 21:28:12 +0200 Subject: [PATCH] build: change tooling to match TR1X --- .github/workflows/build_docker.yml | 22 +-- .github/workflows/build_game_win.yml | 66 -------- .github/workflows/lint.yml | 54 +++++-- .github/workflows/prerelease.yml | 24 ++- .github/workflows/release.yml | 144 ++++++++++++------ CONTRIBUTING.md | 86 ++++++++--- {bin => data/ship}/Tomb2.exe | Bin docker/game-win/entrypoint.sh | 17 --- justfile | 66 ++++++++ meson.build | 58 ++++--- meson.options | 1 + src/decomp/decomp.c | 2 +- src/game/los.c | 4 +- {docker => tools/docker}/game-win/Dockerfile | 3 +- tools/docker/game-win/entrypoint.sh | 28 ++++ .../docker}/game-win/meson_linux_mingw32.txt | 0 tools/{generate_version => get_version} | 13 +- tools/output_current_changelog | 28 ++++ tools/shared/__init__.py | 0 tools/shared/common.py | 6 + tools/shared/docker.py | 91 +++++++++++ tools/shared/packaging.py | 15 ++ tools/shared/versioning.py | 27 ++++ 23 files changed, 529 insertions(+), 226 deletions(-) delete mode 100644 .github/workflows/build_game_win.yml rename {bin => data/ship}/Tomb2.exe (100%) delete mode 100755 docker/game-win/entrypoint.sh create mode 100644 justfile create mode 100644 meson.options rename {docker => tools/docker}/game-win/Dockerfile (92%) create mode 100755 tools/docker/game-win/entrypoint.sh rename {docker => tools/docker}/game-win/meson_linux_mingw32.txt (100%) rename tools/{generate_version => get_version} (63%) mode change 100644 => 100755 create mode 100755 tools/output_current_changelog create mode 100644 tools/shared/__init__.py create mode 100644 tools/shared/common.py create mode 100644 tools/shared/docker.py create mode 100644 tools/shared/packaging.py create mode 100644 tools/shared/versioning.py diff --git a/.github/workflows/build_docker.yml b/.github/workflows/build_docker.yml index 6b19b1c7..08be2bdd 100644 --- a/.github/workflows/build_docker.yml +++ b/.github/workflows/build_docker.yml @@ -7,20 +7,24 @@ jobs: publish_docker_image: name: Build Docker toolchain runs-on: ubuntu-latest + strategy: + matrix: + include: + - platform: win steps: - - name: 'Login to Docker Hub' + - name: Login to Docker Hub uses: docker/login-action@v1 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_PASSWORD }} - - name: 'Checkout code' - uses: actions/checkout@v2 - with: - path: . - fetch-tags: true + - name: Install dependencies + uses: taiki-e/install-action@just + + - name: Checkout code + uses: actions/checkout@v4 - - name: 'Build Docker image' + - name: Build Docker image (${{ matrix.platform }}) run: | - docker build -t "rrdash/tr2x:latest" . -f docker/game-win/Dockerfile - docker push "rrdash/tr2x:latest" + just image-${{ matrix.platform }} + just push-image-${{ matrix.platform }} diff --git a/.github/workflows/build_game_win.yml b/.github/workflows/build_game_win.yml deleted file mode 100644 index b36859ea..00000000 --- a/.github/workflows/build_game_win.yml +++ /dev/null @@ -1,66 +0,0 @@ -on: - - workflow_call - -jobs: - build_game_win: - name: 'Build' - runs-on: ubuntu-latest - steps: - - name: 'Checkout code' - uses: actions/checkout@v3 - with: - path: . - fetch-tags: true - - - name: 'Install dependencies' - run: | - echo "$GITHUB_CONTEXT" - sudo apt-get update - sudo apt-get install -y make moby-engine moby-cli - shell: sh - - - name: 'Build the game' - run: | - make clean release - mkdir -p out/ - cp build/win/*.exe out/ - cp build/win/*.dll out/ - cp -r bin/* out/ - shell: sh - - - name: 'Upload the artifact' - uses: actions/upload-artifact@v1 - with: - name: game_win - path: out/ - - package_game_win: - name: 'Package' - runs-on: ubuntu-latest - needs: - - build_game_win - steps: - - name: 'Download built assets' - uses: actions/download-artifact@v1 - with: - name: game_win - path: artifacts/ - - - name: 'Install dependencies' - run: | - sudo apt-get update - sudo apt-get install -y p7zip-full - shell: sh - - - name: 'Package the game' - run: | - mkdir -p out - cd artifacts - 7z a ../out/game-win.zip * - shell: sh - - - name: 'Upload the artifact' - uses: actions/upload-artifact@v1 - with: - name: game_win_zip - path: out/game-win.zip diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index d3a4aeca..c309580d 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -13,26 +13,50 @@ jobs: uses: actions/checkout@v2 with: path: . - fetch-tags: true + fetch-depth: 0 - - name: 'Install dependencies' + - name: Check JSON files validity + shell: python + run: | + import json + from pathlib import Path + errors = False + for path in Path('.').rglob('**/*.json'): + try: + json.loads(path.read_text()) + except json.JSONDecodeError as ex: + print(f'Malformed JSON in {path}: {ex}') + errors = True + if errors: + exit(1) + + - name: Install dependencies run: | wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key|sudo apt-key add - - echo 'deb http://apt.llvm.org/focal/ llvm-toolchain-focal-12 main' | sudo tee -a /etc/apt/sources.list - echo 'deb-src http://apt.llvm.org/focal/ llvm-toolchain-focal-12 main' | sudo tee -a /etc/apt/sources.list + echo 'deb http://apt.llvm.org/jammy/ llvm-toolchain-jammy-18 main' | sudo tee -a /etc/apt/sources.list + echo 'deb-src http://apt.llvm.org/jammy/ llvm-toolchain-jammy-18 main' | sudo tee -a /etc/apt/sources.list sudo apt update - sudo apt-get install -y clang-format-12 iwyu - sudo ln -s /usr/bin/clang-format-12 /usr/local/bin/clang-format - sudo apt-get install -y make python3-pip - sudo python3 -m pip install pyjson5 + sudo apt-get install -y clang-format-18 iwyu + sudo snap install --edge --classic just + sudo ln -s /usr/bin/clang-format-18 /usr/local/bin/clang-format + sudo apt-get install -y python3-pip + sudo python3 -m pip install pyjson5 pre-commit - - name: 'Check imports' + - name: Check formatted code differences run: | - git add -A - python3 tools/sort_imports - git diff --exit-code || ( echo 'Please run `make imports` and commit the changes.'; exit 1 ) + just lint-format || /bin/true + git diff --exit-code || ( + clang-format --version + echo 'Please run `just lint` and commit the changes.' + exit 1 + ) - - name: 'Check formatted code differences' + - name: Check imports run: | - make lint - git diff --exit-code || ( echo 'Please run `make lint` and commit the changes.'; exit 1 ) + git add -A + just lint-imports + git diff --exit-code || ( + include-what-you-use --version + echo 'Please run `just lint` and commit the changes.' + exit 1 + ) diff --git a/.github/workflows/prerelease.yml b/.github/workflows/prerelease.yml index 25749182..eb2d939d 100644 --- a/.github/workflows/prerelease.yml +++ b/.github/workflows/prerelease.yml @@ -1,30 +1,24 @@ -name: Publish a prerelease +name: Publish a pre-release permissions: contents: write on: push: - branch: develop + branches: + - develop jobs: - tag_latest: - name: 'Tag the repository' - runs-on: ubuntu-latest - steps: - - name: 'Checkout code' - uses: actions/checkout@v3 - - - name: 'Update the tag' - uses: EndBug/latest-tag@latest - publish_prerelease: name: 'Create a prerelease' - needs: - - tag_latest uses: ./.github/workflows/release.yml + if: | + github.ref == 'refs/heads/develop' && + vars.RELEASE_ENABLE == 'true' with: release_name: 'Development snapshot' draft: false prerelease: true - tag_name: latest + tag_name: 'latest' + let_mac_fail: true + secrets: inherit diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index bf1187d2..3543f021 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -7,82 +7,138 @@ on: push: branch: stable tags: - - 'v?[0-9]*' + - "v?[0-9]*" + workflow_call: inputs: - release_name: - description: 'Release name' + draft: + type: boolean + description: "Draft" required: true - default: 'Release ${{ github.ref_name }}' + default: false + prerelease: + type: boolean + description: "Prerelease" + required: true + default: false + release_name: type: string + description: "Release name" + required: true + default: "Release ${{ github.ref_name }}" tag_name: - description: 'Tag name' - required: false - default: '${{ github.ref }}' type: string + description: "Tag name" + required: false + default: github.ref_name + + workflow_dispatch: + inputs: draft: - description: 'Draft' + description: "Draft" required: true - default: false type: boolean + default: false prerelease: - description: 'Prerelease' + description: "Prerelease" required: true type: boolean + default: false + release_name: + description: "Release name" + required: true + type: string + default: "Release name" + tag_name: + description: "Tag name" + required: false + type: string + default: github.ref_Name jobs: + package_multiplatform: + name: Build release assets + runs-on: ubuntu-latest + if: vars.RELEASE_ENABLE == 'true' + strategy: + matrix: + include: + - platform: win + just_target: package-win + steps: + - name: Install dependencies + uses: taiki-e/install-action@just + + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Package asset (${{ matrix.platform }}) + run: just ${{ matrix.just_target }} + + - name: Upload the artifact + uses: actions/upload-artifact@v4 + with: + name: packaged_asset-${{ matrix.platform }} + path: | + *.zip + *.exe + publish_release: - name: 'Create a GitHub release' + if: vars.RELEASE_ENABLE == 'true' + name: Create a GitHub release runs-on: ubuntu-latest needs: - - package_game_win + - package_multiplatform + steps: - - name: 'Checkout code' - uses: actions/checkout@v3 - with: - path: . - fetch-tags: true + - name: "Install dependencies" + uses: taiki-e/install-action@just - - name: 'Download built assets' - uses: actions/download-artifact@v1 + - name: "Checkout code" + uses: actions/checkout@v4 + + - name: "Download built game assets" + uses: actions/download-artifact@v4 with: - name: game_win_zip path: artifacts/ + merge-multiple: true - - name: 'Prepare the changelog' - id: prepare_changelog - uses: ./.github/actions/prepare_changelog + - name: "Generate changelog" + run: | + just output-current-changelog > _changes.txt - - name: 'Get version' - id: get_version - uses: ./.github/actions/get_version + - name: "Get information on the latest pre-release" + if: ${{ inputs.prerelease == true || inputs.prerelease == 'true' }} + id: last_release + uses: InsonusK/get-latest-release@v1.0.1 + with: + myToken: ${{ github.token }} + exclude_types: "draft|release" - - name: 'Prepare for the release' - run: | - echo "${{steps.prepare_changelog.outputs.changelog }}" >artifacts/changes.txt - mv artifacts/game-win.zip artifacts/TR2X-${{ steps.get_version.outputs.version }}-Windows.zip + - name: 'Mark the pre-release as latest' + if: ${{ inputs.prerelease == true || inputs.prerelease == 'true' }} + uses: EndBug/latest-tag@latest - - name: 'Delete old release assets' + - name: "Delete old pre-release assets" + if: ${{ inputs.prerelease == true || inputs.prerelease == 'true' }} uses: mknejp/delete-release-assets@v1 continue-on-error: true with: token: ${{ github.token }} - tag: ${{ inputs.tag_name }} - assets: '*.*' + tag: ${{ steps.last_release.outputs.tag_name }} + assets: "*.*" - - name: 'Publish the release' - uses: softprops/action-gh-release@v1 + - name: "Publish a release" + uses: softprops/action-gh-release@v2 with: token: ${{ secrets.GITHUB_TOKEN }} name: ${{ inputs.release_name }} - body_path: artifacts/changes.txt - draft: ${{ inputs.draft }} - prerelease: ${{ inputs.prerelease }} - fail_on_unmatched_files: true tag_name: ${{ inputs.tag_name }} + body_path: _changes.txt + draft: ${{ inputs.draft == true || inputs.draft == 'true' }} + prerelease: ${{ inputs.prerelease == true || inputs.prerelease == 'true' }} + fail_on_unmatched_files: true files: | - artifacts/TR2X-${{ steps.get_version.outputs.VERSION }}-Windows.zip - - package_game_win: - name: 'Package the game (Windows)' - uses: ./.github/workflows/build_game_win.yml + artifacts/* diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0d8aefd0..c6d3f7fa 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -6,21 +6,25 @@ - Copy all .dll and .exe files from `build/` to your game directory - Copy the contents of `bin/` to your game directory + + ## Compiling ### Compiling on Ubuntu - **With Docker**: - Make sure to install Docker and make, then run `make debug`. - The binaries should appear in the `build/` directory. + Make sure to install Docker and [just](https://github.com/casey/just), then + run `just`. The binaries should appear in the `build/` directory. + To see list of possible build targets, run `just -l`. - **Without Docker**: This scenario is not officially supported, but you can see how it's done by - examining the `docker` configuration files for the external dependencies, - and `meson.build` for the local files, then tailoring your system to match - the process. + examining the files in the `tools/docker/` directory for the external + dependencies and `meson.build` for the local files, then tailoring your + system to match the process. + ### Compiling on Windows @@ -36,13 +40,52 @@ Run WSL and continue with the instructions from the `Compiling on Ubuntu` section. + ### Supported compilers Please be advised that any build systems that are not the one we use for automating releases (= mingw-w64) come at user's own risk. They might crash or even refuse to compile. -## Coding conventions + + +## Working with the project + +### Top values + +- Compatibility with the original game's look and feel +- Maintainability +- Automation where possible +- Documentation (git history and GitHub issues are great for this purpose) + +### Automatic code formatting + +This project uses [pre-commit](https://pre-commit.com/) to make sure the code +is formatted the right way. This tool has additional external dependencies: +`clang-format` for automatic code formatting and `include-what-you-use` to +remove unused `#include`s. +To install pre-commit: + +``` +python3 -m pip install --user pre-commit +pre-commit install +``` + +To install required external dependencies on Ubuntu: + +``` +apt-get install -y iwyu clang-format-18 +``` + +After this, each time you make a commit a hook should trigger to automatically +format your changes. Additionally, in order to trigger this process manually, +you can run `just lint-format`. This doesn't include the slowest checks that +would hinder productivity – to run the full process, you can run `just lint`. +If for any reason you can't install the above software on your machine, our CI +pipeline will still show what needs to be changed in case of mistakes. + + +### Coding convention While the existing source code does not conform to the rules because it uses the original Core Design's naming, new code should adhere to the following @@ -85,14 +128,8 @@ Other things: If the expressions are extraordinarily complex, we refactor these into smaller conditions or functions. -### Code formatting -This project uses `clang-format` to take care of automatic code formatting. To -ensure your code conforms to the standard, please run `make lint` after each -commit. If for some reason you can't run it, don't worry, our CI pipeline will -show you what need to be changed in case of mistakes. - -## Submitting changes +### Submitting changes We commit via pull requests and avoid committing directly to `develop`, which is a protected branch. Each pull request gets peer-reviewed and should have at @@ -104,13 +141,15 @@ Otherwise we don't mark the discussions as resolved and give a chance for the reviewer to reply. Once all change requests are addressed, we should re-request a review from the interested parties. -## Changelog + +### Changelog We keep a changelog in `CHANGELOG.md`. Anything other than an internal change or refactor needs an entry there. Furthermore, new features and OG bugfixes should be documented in README as well. -## Commit scope + +### Commit scope Either you can make a lot of throwaway commits such as 'test' 'continue testing' 'fix 1' 'fix 2' 'fix of the fix' and then squash your pull request as @@ -120,7 +159,8 @@ general we prefer the latter approach. As a principle, refactors should made in separate commits. Code review changes are best made incrementally and then squashed prior to merging, for easing the review process. -## Commit messages + +### Commit messages **The most important thing to remember:** bug fixes and feature implementations should always include the phrase `Resolves #123`. If there's no ticket and the @@ -196,7 +236,8 @@ When merging via squash, it's OK to have GitHub append the pull request number, but pay special attention to the body field which often gets filled with garbage. -## Branching model + +### Branching model We have two branches: `develop` and `stable`. `develop` is where all changes about to be published in the next release land. `stable` is the latest release. @@ -206,13 +247,22 @@ released ahead of unpublished work in `develop` are merged directly to `stable`, and `develop` needs to be then rebased on top of the now-patched `stable`. -## Releasing a new version + +### Tooling + +We try to code all of our internal tools in a reasonably recent version of +Python. Avoid bash, shell and other similar languages. + + +### Releasing a new version New version releases happen automatically whenever a new tag is pushed to the `stable` branch with the help of GitHub actions. In general this is accompanied with a special commit `docs: release X.Y.Z` that assigns unreleased changes to a specific version. See git history for details. + + ## Glossary - OG: original game diff --git a/bin/Tomb2.exe b/data/ship/Tomb2.exe similarity index 100% rename from bin/Tomb2.exe rename to data/ship/Tomb2.exe diff --git a/docker/game-win/entrypoint.sh b/docker/game-win/entrypoint.sh deleted file mode 100755 index 519ba694..00000000 --- a/docker/game-win/entrypoint.sh +++ /dev/null @@ -1,17 +0,0 @@ -#!/bin/sh -set -x -set -e - -export CFLAGS=-DDOCKER_BUILD - -if [ ! -f /app/build/win/build.ninja ]; then - meson --buildtype "$TARGET" /app/build/win/ --cross /app/docker/game-win/meson_linux_mingw32.txt --pkg-config-path=$PKG_CONFIG_PATH -fi - -cd /app/build/win; meson compile - -if [ "$TARGET" = release ]; then - for file in *.exe; do - upx -t "$file" || ( i686-w64-mingw32-strip "$file" && upx "$file" ) - done -fi diff --git a/justfile b/justfile new file mode 100644 index 00000000..601941c1 --- /dev/null +++ b/justfile @@ -0,0 +1,66 @@ +CWD := `pwd` +HOST_USER_UID := `id -u` +HOST_USER_GID := `id -g` + +default: (build-win "debug") + +_docker_push tag: + docker push {{tag}} + +_docker_build dockerfile tag force="0": + #!/usr/bin/env sh + if [ "{{force}}" = "0" ]; then + docker images --format '{''{.Repository}}' | grep '^{{tag}}$' + if [ $? -eq 0 ]; then + echo "Docker image {{tag}} found" + exit 0 + fi + echo "Docker image {{tag}} not found, trying to download from DockerHub" + if docker pull {{tag}}; then + echo "Docker image {{tag}} downloaded from DockerHub" + exit 0 + fi + echo "Docker image {{tag}} not found, trying to build" + fi + + echo "Building Docker image: {{dockerfile}} → {{tag}}" + docker build \ + --progress plain \ + . \ + -f {{dockerfile}} \ + -t {{tag}} + +_docker_run *args: + @echo "Running docker image: {{args}}" + docker run \ + --rm \ + --user \ + {{HOST_USER_UID}}:{{HOST_USER_GID}} \ + --network host \ + -v {{CWD}}:/app/ \ + {{args}} + + +image-win force="1": (_docker_build "tools/docker/game-win/Dockerfile" "rrdash/tr2x" force) + +push-image-win: (image-win "0") (_docker_push "rrdash/tr2x") + +build-win target='debug': (image-win "0") (_docker_run "-e" "TARGET="+target "rrdash/tr2x") + +package-win: (build-win "release") (_docker_run "rrdash/tr2x" "package") + +output-current-changelog: + tools/output_current_changelog + +clean: + -find build/ -type f -delete + -find tools/ -type f \( -ipath '*/out/*' -or -ipath '*/bin/*' -or -ipath '*/obj/*' \) -delete + -find . -mindepth 1 -empty -type d -delete + +lint-imports: + tools/sort_imports + +lint-format: + pre-commit run -a + +lint: (lint-imports) (lint-format) diff --git a/meson.build b/meson.build index 56bd1e28..b77dee3e 100644 --- a/meson.build +++ b/meson.build @@ -5,67 +5,57 @@ project('TR2X', ['c'], ], ) -warning_level = 3 - c_compiler = meson.get_compiler('c') -is_mingw = c_compiler.get_id() == 'gcc' and host_machine.system() == 'windows' -if is_mingw - add_project_link_arguments([], language: 'c') -endif - build_opts = [ '-Wno-unused', - '-D_GNU_SOURCE', -] -c_opts = [ + '-DMESON_BUILD', '-ffile-prefix-map=../../src/=', ] -add_project_arguments(build_opts + c_opts, language: 'c') + +add_project_arguments(build_opts, language: 'c') + +staticdeps = get_option('staticdeps') + +null_dep = dependency('', required: false) +dep_mathlibrary = c_compiler.find_library('m', static: staticdeps, required : false) # autogenerated files resources = [] python3 = find_program('python3', required: true) git = find_program('git', required: true) -version = custom_target('version', - output: ['version.txt'], - command: [python3, meson.source_root() + '/tools/generate_version', '-o', '@OUTPUT0@'], - build_by_default: true, - build_always_stale: true -) - init = custom_target( 'fake_init', - depends: [version], - input: [version[0]], output: ['init.c'], - command: [python3, meson.source_root() + '/tools/generate_init', '--version-file', '@INPUT@', '-o', '@OUTPUT0@'], - build_by_default: true, + command: [python3, meson.source_root() + '/tools/generate_init', '-o', meson.current_build_dir() / '@OUTPUT0@'], + build_always_stale: true, ) + version_rc = custom_target( 'fake_version', - depends: [version], - input: [version[0]], output: ['version.rc'], - command: [python3, meson.source_root() + '/tools/generate_rcfile', '--version-file', '@INPUT@', '-o', '@OUTPUT0@'], - build_by_default: true, + command: [python3, meson.source_root() + '/tools/generate_rcfile', '-o', '@OUTPUT0@'], + build_always_stale: true, ) + icon_rc = custom_target( 'fake_icon', - input: [version[0]], output: ['icon.rc'], - command: [python3, meson.source_root() + '/tools/generate_rcfile', '--version-file', '@INPUT@', '-o', '@OUTPUT0@'], + command: [python3, meson.source_root() + '/tools/generate_rcfile', '-o', '@OUTPUT0@'], ) + +link_args = [] + if host_machine.system() == 'windows' windows = import('windows') + resources = [ windows.compile_resources(version_rc), windows.compile_resources(icon_rc), ] - link_args = ['-static'] -else - link_args = [] + + link_args += ['-static'] endif exe_sources = [ @@ -111,6 +101,10 @@ dll_sources = [ 'src/specific/s_music_pauld.c', ] +dependencies = [ + dep_mathlibrary, +] + executable( 'TR2X', exe_sources, @@ -119,10 +113,12 @@ executable( link_args: link_args, gui_app: true, ) + library( 'TR2X', dll_sources, name_prefix: '', include_directories: ['src/'], + dependencies: dependencies, link_args: link_args, ) diff --git a/meson.options b/meson.options new file mode 100644 index 00000000..5577c457 --- /dev/null +++ b/meson.options @@ -0,0 +1 @@ +option('staticdeps', type: 'boolean', value: true, description: 'Try to build against static dependencies. default: true') diff --git a/src/decomp/decomp.c b/src/decomp/decomp.c index 653fb484..3de1dac8 100644 --- a/src/decomp/decomp.c +++ b/src/decomp/decomp.c @@ -275,7 +275,7 @@ void __cdecl ScreenshotTGA(IDirectDrawSurface3 *screen, int32_t bpp) FILE *handle = fopen(file_name, "wb"); if (!handle) { - goto cleanup; + return; } const TGA_HEADER header = { diff --git a/src/game/los.c b/src/game/los.c index 1e443371..13ac98e9 100644 --- a/src/game/los.c +++ b/src/game/los.c @@ -308,7 +308,7 @@ int32_t __cdecl LOS_CheckSmashable( const enum DIRECTION direction = Math_GetDirection(item->pos.y_rot); const int16_t *const bounds = Item_GetBoundsAccurate(item); const int16_t *x_extent; - const int16_t *z_extent; + const int16_t *z_extent = NULL; switch (direction) { case DIR_EAST: case DIR_WEST: @@ -321,9 +321,9 @@ int32_t __cdecl LOS_CheckSmashable( z_extent = &bounds[FBBOX_MIN_Z]; break; default: - assert(false); break; } + assert(z_extent != NULL); int32_t failure = 0; if (ABS(dz) > ABS(dx)) { diff --git a/docker/game-win/Dockerfile b/tools/docker/game-win/Dockerfile similarity index 92% rename from docker/game-win/Dockerfile rename to tools/docker/game-win/Dockerfile index e80e1a10..fd6f7eb4 100644 --- a/docker/game-win/Dockerfile +++ b/tools/docker/game-win/Dockerfile @@ -41,4 +41,5 @@ RUN apt-get install -y \ meson \ ninja -ENTRYPOINT ["/app/docker/game-win/entrypoint.sh"] +ENV PYTHONPATH=/app/tools/ +ENTRYPOINT ["/app/tools/docker/game-win/entrypoint.sh"] diff --git a/tools/docker/game-win/entrypoint.sh b/tools/docker/game-win/entrypoint.sh new file mode 100755 index 00000000..f4927ade --- /dev/null +++ b/tools/docker/game-win/entrypoint.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env python3 +from pathlib import Path + +from shared.docker import BaseGameEntrypoint + + +class WindowsEntrypoint(BaseGameEntrypoint): + BUILD_ROOT = Path("/app/build/win/") + COMPILE_ARGS = [ + "--cross", + "/app/tools/docker/game-win/meson_linux_mingw32.txt", + ] + RELEASE_ZIP_SUFFIX = "Windows" + RELEASE_ZIP_FILES = [ + (BUILD_ROOT / "TR2X.exe", "TR2X.exe"), + (BUILD_ROOT / "TR2X.dll", "TR2X.dll"), + ] + + def post_compile(self) -> None: + if self.target == "release": + for path in self.BUILD_ROOT.glob("*.exe"): + self.compress_exe(path) + for path in self.BUILD_ROOT.glob("*.dll"): + self.compress_exe(path) + + +if __name__ == "__main__": + WindowsEntrypoint().run() diff --git a/docker/game-win/meson_linux_mingw32.txt b/tools/docker/game-win/meson_linux_mingw32.txt similarity index 100% rename from docker/game-win/meson_linux_mingw32.txt rename to tools/docker/game-win/meson_linux_mingw32.txt diff --git a/tools/generate_version b/tools/get_version old mode 100644 new mode 100755 similarity index 63% rename from tools/generate_version rename to tools/get_version index 2e65f30e..46952b4e --- a/tools/generate_version +++ b/tools/get_version @@ -3,6 +3,8 @@ import argparse from pathlib import Path from subprocess import run +from shared.versioning import generate_version + def parse_args() -> argparse.Namespace: parser = argparse.ArgumentParser() @@ -10,16 +12,13 @@ def parse_args() -> argparse.Namespace: return parser.parse_args() -def generate_version() -> str: - cmd = ["git", "describe", "--abbrev=7", "--tags"] - version = run(cmd, capture_output=True, text=True).stdout - return f'TR2X {version or "?"}' - - def main() -> None: args = parse_args() version = generate_version() - args.output.write_text(version) + if args.output: + args.output.write_text(version) + else: + print(version, end="") if __name__ == "__main__": diff --git a/tools/output_current_changelog b/tools/output_current_changelog new file mode 100755 index 00000000..2828d287 --- /dev/null +++ b/tools/output_current_changelog @@ -0,0 +1,28 @@ +#!/usr/bin/env python3 +import re +from pathlib import Path + +TOOLS_DIR = Path(__file__).parent +ROOT_DIR = TOOLS_DIR.parent +CHANGELOG_PATH = ROOT_DIR / "CHANGELOG.md" + + +def get_current_changelog() -> str: + sections = [ + section + for section in CHANGELOG_PATH.read_text().split("\n\n") + if re.search(r"- \w", section) + ] + if sections: + section = sections[0] + return "\n".join( + line for line in section.splitlines() if not line.startswith("#") + ) + + +def main() -> None: + print(get_current_changelog()) + + +if __name__ == "__main__": + main() diff --git a/tools/shared/__init__.py b/tools/shared/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tools/shared/common.py b/tools/shared/common.py new file mode 100644 index 00000000..8448451c --- /dev/null +++ b/tools/shared/common.py @@ -0,0 +1,6 @@ +from pathlib import Path + +TOOLS_DIR = Path(__file__).parent.parent +REPO_DIR = TOOLS_DIR.parent +DATA_DIR = REPO_DIR / "data" +SRC_DIR = REPO_DIR / "src" diff --git a/tools/shared/docker.py b/tools/shared/docker.py new file mode 100644 index 00000000..2838e5b7 --- /dev/null +++ b/tools/shared/docker.py @@ -0,0 +1,91 @@ +import argparse +import os +from pathlib import Path +from subprocess import check_call, run + +from shared.common import DATA_DIR +from shared.packaging import create_zip +from shared.versioning import generate_version + +SHIP_DIR = DATA_DIR / "ship" + + +class BaseGameEntrypoint: + BUILD_ROOT: Path = ... + COMPILE_ARGS: list[str] = ... + STRIP_TOOL = "strip" + UPX_TOOL = "upx" + RELEASE_ZIP_SUFFIX: str = ... + RELEASE_ZIP_FILES: list[tuple[Path, str]] = ... + + def __init__(self) -> None: + self.target = os.environ.get("TARGET", "debug") + + def run(self) -> None: + args = self.parse_args() + args.func(args) + + def parse_args(self) -> argparse.Namespace: + parser = argparse.ArgumentParser(description="Docker entrypoint") + subparsers = parser.add_subparsers(dest="action", help="Subcommands") + + compile_parser = subparsers.add_parser( + "compile", help="Compile action" + ) + compile_parser.set_defaults(func=self.compile) + + package_parser = subparsers.add_parser( + "package", help="Package action" + ) + package_parser.add_argument("-o", "--output", type=Path) + package_parser.set_defaults(func=self.package) + + args = parser.parse_args() + if not hasattr(args, "func"): + args.action = "compile" + args.func = self.compile + return args + + def compile(self, args: argparse.Namespace) -> None: + pkg_config_path = os.environ.get("PKG_CONFIG_PATH") + + if not Path("/app/build/linux/build.jinja").exists(): + command = [ + "meson", + "--buildtype", + self.target, + *self.COMPILE_ARGS, + self.BUILD_ROOT, + ] + if pkg_config_path: + command.extend(["--pkg-config-path", pkg_config_path]) + check_call(command) + + check_call(["meson", "compile"], cwd=self.BUILD_ROOT) + + self.post_compile() + + def post_compile(self) -> None: + pass + + def compress_exe(self, path: Path) -> None: + if run([self.UPX_TOOL, "-t", str(path)]).returncode != 0: + check_call([self.STRIP_TOOL, str(path)]) + check_call([self.UPX_TOOL, str(path)]) + + def package(self, args: argparse.Namespace) -> None: + if args.output: + zip_path = args.output + else: + version = generate_version() + zip_path = Path(f"{version}-{self.RELEASE_ZIP_SUFFIX}.zip") + source_files = [ + *[ + (path, path.relative_to(SHIP_DIR)) + for path in SHIP_DIR.rglob("*") + if path.is_file() + ], + *self.RELEASE_ZIP_FILES, + ] + create_zip(zip_path, source_files) + print(f"Created {zip_path}") diff --git a/tools/shared/packaging.py b/tools/shared/packaging.py new file mode 100644 index 00000000..bafac16f --- /dev/null +++ b/tools/shared/packaging.py @@ -0,0 +1,15 @@ +import sys +import zipfile +from collections.abc import Iterable +from pathlib import Path + + +def create_zip( + output_path: Path, source_files: Iterable[tuple[Path, str]] +) -> None: + with zipfile.ZipFile(output_path, "w") as handle: + for source_path, archive_name in source_files: + if not source_path.exists(): + print(f"WARNING: {source_path} does not exist", file=sys.stderr) + continue + handle.write(source_path, archive_name) diff --git a/tools/shared/versioning.py b/tools/shared/versioning.py new file mode 100644 index 00000000..4d92bedd --- /dev/null +++ b/tools/shared/versioning.py @@ -0,0 +1,27 @@ +from subprocess import run + +from shared.common import SRC_DIR + + +def get_branch_version(branch: str | None) -> str: + return run( + [ + "git", + "describe", + *([branch] if branch else ["--dirty"]), + "--always", + "--abbrev=7", + "--tags", + "--exclude", + "latest", + ], + cwd=SRC_DIR, + text=True, + capture_output=True, + check=False, + ).stdout.strip() + + +def generate_version() -> str: + version = get_branch_version(None) + return f'TR2X {version or "?"}'