From e7824377e2bff7def505779113939a8e51a771dd Mon Sep 17 00:00:00 2001 From: tmp64 Date: Sun, 3 Aug 2025 13:07:12 +0700 Subject: [PATCH 1/4] Add CMake presets --- .gitignore | 15 ++++++- CMakePresets.json | 41 +++++++++++++++++++ CMakeUserPresets.example.json | 26 ++++++++++++ cmake/CMakeLinuxPresets.json | 15 +++++++ cmake/CMakeWindowsPresets.json | 15 +++++++ .../darwin-appleclang.cmake} | 0 .../include-linux-m32.cmake} | 0 .../linux-clang.cmake} | 2 +- .../linux-gcc-8.cmake} | 2 +- .../linux-gcc-9.cmake} | 2 +- .../linux-gcc.cmake} | 2 +- 11 files changed, 114 insertions(+), 6 deletions(-) create mode 100644 CMakePresets.json create mode 100644 CMakeUserPresets.example.json create mode 100644 cmake/CMakeLinuxPresets.json create mode 100644 cmake/CMakeWindowsPresets.json rename cmake/{ToolchainDarwinAppleClang.cmake => toolchains/darwin-appleclang.cmake} (100%) rename cmake/{Linux32CrossCompile.cmake => toolchains/include-linux-m32.cmake} (100%) rename cmake/{ToolchainLinuxClang.cmake => toolchains/linux-clang.cmake} (75%) rename cmake/{ToolchainLinuxGCC8.cmake => toolchains/linux-gcc-8.cmake} (75%) rename cmake/{ToolchainLinuxGCC9.cmake => toolchains/linux-gcc-9.cmake} (75%) rename cmake/{ToolchainLinuxGCC.cmake => toolchains/linux-gcc.cmake} (74%) diff --git a/.gitignore b/.gitignore index 6884a43..2e6c443 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,13 @@ -/_build* -/.build* +# CMake build directory +/build +/build_* +/_build +/_build_* +CMakeUserPresets.json + +# macOS garbage +.DS_Store + +# Ignore .vscode but allow examples +.vscode/* +!.vscode/**/*.example.* diff --git a/CMakePresets.json b/CMakePresets.json new file mode 100644 index 0000000..7c17eb3 --- /dev/null +++ b/CMakePresets.json @@ -0,0 +1,41 @@ +{ + "version": 9, + "cmakeMinimumRequired": { + "major": 3, + "minor": 30 + }, + "include": [ + "cmake/CMake${hostSystemName}Presets.json" + ], + "configurePresets": [ + { + "name": "base", + "inherits": ["platform-toolchain"], + "binaryDir": "${sourceDir}/_build/${presetName}", + "hidden": true, + "cacheVariables": { + "CMAKE_EXPORT_COMPILE_COMMANDS": true, + + "AUTO_DEPLOY": false, + "GNU_FORCE_COLORED_OUTPUT": true + } + }, + { + "name": "github-actions", + "inherits": ["base"], + "generator": "Ninja Multi-Config", + "cacheVariables": { + "GNU_FORCE_COLORED_OUTPUT": false, + "WARNINGS_ARE_ERRORS": true + } + } + ], + "buildPresets": [ + { + "name": "github-actions", + "configurePreset": "github-actions", + "inheritConfigureEnvironment": true, + "configuration": "RelWithDebInfo" + } + ] +} \ No newline at end of file diff --git a/CMakeUserPresets.example.json b/CMakeUserPresets.example.json new file mode 100644 index 0000000..36ff81e --- /dev/null +++ b/CMakeUserPresets.example.json @@ -0,0 +1,26 @@ +{ + "version": 4, + "configurePresets": [ + { + "name": "user", + "inherits": ["base", "platform-generator"], + "cacheVariables": { + "AUTO_DEPLOY": true + } + } + ], + "buildPresets": [ + { + "name": "user-debug", + "configurePreset": "user", + "inheritConfigureEnvironment": true, + "configuration": "Debug" + }, + { + "name": "user-release", + "configurePreset": "user", + "inheritConfigureEnvironment": true, + "configuration": "RelWithDebInfo" + } + ] +} \ No newline at end of file diff --git a/cmake/CMakeLinuxPresets.json b/cmake/CMakeLinuxPresets.json new file mode 100644 index 0000000..9b78341 --- /dev/null +++ b/cmake/CMakeLinuxPresets.json @@ -0,0 +1,15 @@ +{ + "version": 4, + "configurePresets": [ + { + "name": "platform-toolchain", + "hidden": true, + "toolchainFile": "${sourceDir}/cmake/toolchains/linux-gcc.cmake" + }, + { + "name": "platform-generator", + "hidden": true, + "generator": "Ninja Multi-Config" + } + ] +} \ No newline at end of file diff --git a/cmake/CMakeWindowsPresets.json b/cmake/CMakeWindowsPresets.json new file mode 100644 index 0000000..eb35afb --- /dev/null +++ b/cmake/CMakeWindowsPresets.json @@ -0,0 +1,15 @@ +{ + "version": 4, + "configurePresets": [ + { + "name": "platform-toolchain", + "hidden": true + }, + { + "name": "platform-generator", + "hidden": true, + "generator": "Visual Studio 17 2022", + "architecture": "Win32" + } + ] +} \ No newline at end of file diff --git a/cmake/ToolchainDarwinAppleClang.cmake b/cmake/toolchains/darwin-appleclang.cmake similarity index 100% rename from cmake/ToolchainDarwinAppleClang.cmake rename to cmake/toolchains/darwin-appleclang.cmake diff --git a/cmake/Linux32CrossCompile.cmake b/cmake/toolchains/include-linux-m32.cmake similarity index 100% rename from cmake/Linux32CrossCompile.cmake rename to cmake/toolchains/include-linux-m32.cmake diff --git a/cmake/ToolchainLinuxClang.cmake b/cmake/toolchains/linux-clang.cmake similarity index 75% rename from cmake/ToolchainLinuxClang.cmake rename to cmake/toolchains/linux-clang.cmake index f724ef6..f48adfc 100644 --- a/cmake/ToolchainLinuxClang.cmake +++ b/cmake/toolchains/linux-clang.cmake @@ -1,7 +1,7 @@ # This toolchain adds -m32 to compiler flags # It is required to make CMake link with proper 32-bit libraries -include( Linux32CrossCompile ) +include( ${CMAKE_CURRENT_LIST_DIR}/include-linux-m32.cmake ) set( CMAKE_C_COMPILER clang -m32 ) set( CMAKE_CXX_COMPILER clang++ -m32) diff --git a/cmake/ToolchainLinuxGCC8.cmake b/cmake/toolchains/linux-gcc-8.cmake similarity index 75% rename from cmake/ToolchainLinuxGCC8.cmake rename to cmake/toolchains/linux-gcc-8.cmake index 160e7cf..4cb0356 100644 --- a/cmake/ToolchainLinuxGCC8.cmake +++ b/cmake/toolchains/linux-gcc-8.cmake @@ -1,7 +1,7 @@ # This toolchain adds -m32 to compiler flags # It is required to make CMake link with proper 32-bit libraries -include( Linux32CrossCompile ) +include( ${CMAKE_CURRENT_LIST_DIR}/include-linux-m32.cmake ) set( CMAKE_C_COMPILER gcc-8 -m32 ) set( CMAKE_CXX_COMPILER g++-8 -m32) diff --git a/cmake/ToolchainLinuxGCC9.cmake b/cmake/toolchains/linux-gcc-9.cmake similarity index 75% rename from cmake/ToolchainLinuxGCC9.cmake rename to cmake/toolchains/linux-gcc-9.cmake index 9fe001b..267bd47 100644 --- a/cmake/ToolchainLinuxGCC9.cmake +++ b/cmake/toolchains/linux-gcc-9.cmake @@ -1,7 +1,7 @@ # This toolchain adds -m32 to compiler flags # It is required to make CMake link with proper 32-bit libraries -include( Linux32CrossCompile ) +include( ${CMAKE_CURRENT_LIST_DIR}/include-linux-m32.cmake ) set( CMAKE_C_COMPILER gcc-9 -m32 ) set( CMAKE_CXX_COMPILER g++-9 -m32) diff --git a/cmake/ToolchainLinuxGCC.cmake b/cmake/toolchains/linux-gcc.cmake similarity index 74% rename from cmake/ToolchainLinuxGCC.cmake rename to cmake/toolchains/linux-gcc.cmake index 6a2d28f..a31ad6b 100644 --- a/cmake/ToolchainLinuxGCC.cmake +++ b/cmake/toolchains/linux-gcc.cmake @@ -1,7 +1,7 @@ # This toolchain adds -m32 to compiler flags # It is required to make CMake link with proper 32-bit libraries -include( Linux32CrossCompile ) +include( ${CMAKE_CURRENT_LIST_DIR}/include-linux-m32.cmake ) set( CMAKE_C_COMPILER gcc -m32 ) set( CMAKE_CXX_COMPILER g++ -m32) From 920d6f2f1b06560158c7607a037fd6fb906574ee Mon Sep 17 00:00:00 2001 From: tmp64 Date: Sun, 3 Aug 2025 13:17:22 +0700 Subject: [PATCH 2/4] Use CMake Install for auto-deploy --- .vscode/settings.example.json | 12 ++++ CMakeLists.txt | 27 +++---- cmake/DeployLibs.cmake | 72 +++++++++++++++++++ gamedir/CMakeLists.txt | 59 +++++++++++++++ .../configs/weaponmod/spawnpoints/_my_map.ini | 0 .../amxmodx/configs/weaponmod/weaponmod.ini | 0 .../gamedata/weaponmod.games/master.games.txt | 0 .../valve/offsets-virtual-cbaseplayerammo.txt | 0 .../valve/offsets-virtual-cbaseplayeritem.txt | 0 .../offsets-virtual-cbaseplayerweapon.txt | 0 .../weaponmod.games/valve/settings.txt | 0 .../amxmodx/scripting/include/beams.inc | 0 .../amxmodx/scripting/include/hl_wpnmod.inc | 0 .../scripting/include/hl_wpnmod_compat.inc | 0 .../scripting/include/hl_wpnmod_const.inc | 0 15 files changed, 157 insertions(+), 13 deletions(-) create mode 100644 .vscode/settings.example.json create mode 100644 cmake/DeployLibs.cmake create mode 100644 gamedir/CMakeLists.txt rename gamedir/{ => addons}/amxmodx/configs/weaponmod/spawnpoints/_my_map.ini (100%) rename gamedir/{ => addons}/amxmodx/configs/weaponmod/weaponmod.ini (100%) rename gamedir/{ => addons}/amxmodx/data/gamedata/weaponmod.games/master.games.txt (100%) rename gamedir/{ => addons}/amxmodx/data/gamedata/weaponmod.games/valve/offsets-virtual-cbaseplayerammo.txt (100%) rename gamedir/{ => addons}/amxmodx/data/gamedata/weaponmod.games/valve/offsets-virtual-cbaseplayeritem.txt (100%) rename gamedir/{ => addons}/amxmodx/data/gamedata/weaponmod.games/valve/offsets-virtual-cbaseplayerweapon.txt (100%) rename gamedir/{ => addons}/amxmodx/data/gamedata/weaponmod.games/valve/settings.txt (100%) rename gamedir/{ => addons}/amxmodx/scripting/include/beams.inc (100%) rename gamedir/{ => addons}/amxmodx/scripting/include/hl_wpnmod.inc (100%) rename gamedir/{ => addons}/amxmodx/scripting/include/hl_wpnmod_compat.inc (100%) rename gamedir/{ => addons}/amxmodx/scripting/include/hl_wpnmod_const.inc (100%) diff --git a/.vscode/settings.example.json b/.vscode/settings.example.json new file mode 100644 index 0000000..c4f6429 --- /dev/null +++ b/.vscode/settings.example.json @@ -0,0 +1,12 @@ +{ + "[cmake]": { + "editor.insertSpaces": false, + "editor.tabSize": 4 + }, + "C_Cpp.default.configurationProvider": "ms-vscode.cmake-tools", + "clangd.arguments": [ + "-header-insertion=never", + "--compile-commands-dir=_build/user" + ], + "cmake.useCMakePresets": "always" +} \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 1f2adc2..dc273e6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -242,18 +242,18 @@ include_directories( ${GENERATED_INCLUDE_DIR} ) # Autodeploy macro #----------------------------------------------------------------- if( AUTO_DEPLOY ) - macro( add_auto_deploy TARGET_NAME PUBLISH_PATHS_FILE ) - if ( WIN32 ) - add_custom_command( TARGET ${TARGET_NAME} - POST_BUILD - COMMAND ${CMAKE_SOURCE_DIR}/scripts/deploy_libs.bat "${CMAKE_BINARY_DIR}\\${PUBLISH_PATHS_FILE}" "$" "$" - ) - else() - add_custom_command( TARGET ${TARGET_NAME} - POST_BUILD - COMMAND ${CMAKE_SOURCE_DIR}/scripts/deploy_libs.sh "${CMAKE_BINARY_DIR}/${PUBLISH_PATHS_FILE}" "$" - ) - endif() + macro( add_auto_deploy target_name publish_paths_file ) + add_custom_command( TARGET ${target_name} + POST_BUILD + COMMAND + ${CMAKE_COMMAND} + -P + ${CMAKE_SOURCE_DIR}/cmake/DeployLibs.cmake + -- + ${CMAKE_BINARY_DIR} + "$" + ${CMAKE_BINARY_DIR}/${publish_paths_file} + ) endmacro() else() macro( add_auto_deploy ) @@ -266,5 +266,6 @@ endif() set( THREADS_PREFER_PTHREAD_FLAG ON ) find_package( Threads REQUIRED ) -add_subdirectory( thirdparty ) +add_subdirectory( gamedir ) add_subdirectory( src ) +add_subdirectory( thirdparty ) diff --git a/cmake/DeployLibs.cmake b/cmake/DeployLibs.cmake new file mode 100644 index 0000000..9924e9c --- /dev/null +++ b/cmake/DeployLibs.cmake @@ -0,0 +1,72 @@ +cmake_minimum_required( VERSION 3.25.0 ) + +# In script mode, CMAKE_ARGVx contains ALL arguments, including `-P` and script path +# This is stupid, so we neew to find where `--` is +set( actual_args_start_idx 0 ) +math( EXPR last_arg_idx "${CMAKE_ARGC} - 1" ) + +foreach( i RANGE 0 ${last_arg_idx}) + set( arg "${CMAKE_ARGV${i}}" ) + + if( "${arg}" STREQUAL "--" ) + math( EXPR actual_args_start_idx "${i} + 1") + break() + endif() +endforeach() + +# Copy args to temp vars +math( EXPR arg_binary_dir_idx "${actual_args_start_idx} + 0" ) +set( binary_dir ${CMAKE_ARGV${arg_binary_dir_idx}} ) + + +math( EXPR arg_config_idx "${actual_args_start_idx} + 1" ) +set( config_name ${CMAKE_ARGV${arg_config_idx}} ) + +math( EXPR arg_path_list_idx "${actual_args_start_idx} + 2" ) +set( path_list_file ${CMAKE_ARGV${arg_path_list_idx}} ) + +# Check that args are set +if( NOT binary_dir ) + message( FATAL_ERROR "Binary dir path not specified." ) +endif() + +if( NOT path_list_file ) + message( FATAL_ERROR "Path list file not specified." ) +endif() + +# Check that paths exist +if( NOT EXISTS "${binary_dir}" ) + message( FATAL_ERROR "Binary dir ${FATAL_ERROR} doesn't exist." ) +endif() + +if( NOT EXISTS "${path_list_file}" ) + message( NOTICE "No deployment path specified. Create file ${path_list_file} with folder paths on separate lines for auto deployment." ) + return() +endif() + +# Read the path list file +file( STRINGS ${path_list_file} deploy_paths ) + +# Iterate over all paths in the file +foreach( deploy_path IN LISTS deploy_paths) + cmake_path( IS_ABSOLUTE deploy_path is_path_absolute ) + + if( NOT is_path_absolute ) + message( SEND_ERROR "Path must be absolute: ${deploy_path_full}" ) + continue() + endif() + + if( NOT EXISTS "${deploy_path}" ) + message( SEND_ERROR "Path doesn't exist: ${deploy_path}" ) + continue() + endif() + + # Run cmake --install for the path + execute_process( + COMMAND + ${CMAKE_COMMAND} + --install ${binary_dir} + --config "${config_name}" + --prefix ${deploy_path} + ) +endforeach() diff --git a/gamedir/CMakeLists.txt b/gamedir/CMakeLists.txt new file mode 100644 index 0000000..d03f4f3 --- /dev/null +++ b/gamedir/CMakeLists.txt @@ -0,0 +1,59 @@ +set( SERVER_FILES + addons/amxmodx/configs/weaponmod/spawnpoints/_my_map.ini + addons/amxmodx/configs/weaponmod/weaponmod.ini + addons/amxmodx/data/gamedata/weaponmod.games/master.games.txt + addons/amxmodx/data/gamedata/weaponmod.games/valve/offsets-virtual-cbaseplayerammo.txt + addons/amxmodx/data/gamedata/weaponmod.games/valve/offsets-virtual-cbaseplayeritem.txt + addons/amxmodx/data/gamedata/weaponmod.games/valve/offsets-virtual-cbaseplayerweapon.txt + addons/amxmodx/data/gamedata/weaponmod.games/valve/settings.txt + addons/amxmodx/scripting/include/beams.inc + addons/amxmodx/scripting/include/hl_wpnmod.inc + addons/amxmodx/scripting/include/hl_wpnmod_compat.inc + addons/amxmodx/scripting/include/hl_wpnmod_const.inc +) + +function(bhl_install_files file_list) + foreach( rel_file_path IN LISTS file_list) + cmake_path( HAS_PARENT_PATH rel_file_path has_parent_path ) + + if( has_parent_path) + cmake_path( GET rel_file_path PARENT_PATH parent_path ) + else() + set( parent_path "." ) + endif() + + # Copy the file as-is + set( file_to_copy "${CMAKE_CURRENT_SOURCE_DIR}/${rel_file_path}" ) + + install( + FILES ${file_to_copy} + DESTINATION ${parent_path} + ) + endforeach() + + # Install readme + install( + FILES ${CMAKE_SOURCE_DIR}/README.md + DESTINATION "." + RENAME README_WeaponMod.md + ) +endfunction() + + +function(bhl_install_target target_name install_dir) + install( + FILES $ + DESTINATION ${install_dir} + ) + + if( CMAKE_CXX_LINKER_SUPPORTS_PDB ) + install( + FILES $ + DESTINATION ${install_dir} + OPTIONAL + ) + endif() +endfunction() + +bhl_install_files( "${SERVER_FILES}" ) +bhl_install_target( weaponmod_amxx "addons/amxmodx/modules" ) diff --git a/gamedir/amxmodx/configs/weaponmod/spawnpoints/_my_map.ini b/gamedir/addons/amxmodx/configs/weaponmod/spawnpoints/_my_map.ini similarity index 100% rename from gamedir/amxmodx/configs/weaponmod/spawnpoints/_my_map.ini rename to gamedir/addons/amxmodx/configs/weaponmod/spawnpoints/_my_map.ini diff --git a/gamedir/amxmodx/configs/weaponmod/weaponmod.ini b/gamedir/addons/amxmodx/configs/weaponmod/weaponmod.ini similarity index 100% rename from gamedir/amxmodx/configs/weaponmod/weaponmod.ini rename to gamedir/addons/amxmodx/configs/weaponmod/weaponmod.ini diff --git a/gamedir/amxmodx/data/gamedata/weaponmod.games/master.games.txt b/gamedir/addons/amxmodx/data/gamedata/weaponmod.games/master.games.txt similarity index 100% rename from gamedir/amxmodx/data/gamedata/weaponmod.games/master.games.txt rename to gamedir/addons/amxmodx/data/gamedata/weaponmod.games/master.games.txt diff --git a/gamedir/amxmodx/data/gamedata/weaponmod.games/valve/offsets-virtual-cbaseplayerammo.txt b/gamedir/addons/amxmodx/data/gamedata/weaponmod.games/valve/offsets-virtual-cbaseplayerammo.txt similarity index 100% rename from gamedir/amxmodx/data/gamedata/weaponmod.games/valve/offsets-virtual-cbaseplayerammo.txt rename to gamedir/addons/amxmodx/data/gamedata/weaponmod.games/valve/offsets-virtual-cbaseplayerammo.txt diff --git a/gamedir/amxmodx/data/gamedata/weaponmod.games/valve/offsets-virtual-cbaseplayeritem.txt b/gamedir/addons/amxmodx/data/gamedata/weaponmod.games/valve/offsets-virtual-cbaseplayeritem.txt similarity index 100% rename from gamedir/amxmodx/data/gamedata/weaponmod.games/valve/offsets-virtual-cbaseplayeritem.txt rename to gamedir/addons/amxmodx/data/gamedata/weaponmod.games/valve/offsets-virtual-cbaseplayeritem.txt diff --git a/gamedir/amxmodx/data/gamedata/weaponmod.games/valve/offsets-virtual-cbaseplayerweapon.txt b/gamedir/addons/amxmodx/data/gamedata/weaponmod.games/valve/offsets-virtual-cbaseplayerweapon.txt similarity index 100% rename from gamedir/amxmodx/data/gamedata/weaponmod.games/valve/offsets-virtual-cbaseplayerweapon.txt rename to gamedir/addons/amxmodx/data/gamedata/weaponmod.games/valve/offsets-virtual-cbaseplayerweapon.txt diff --git a/gamedir/amxmodx/data/gamedata/weaponmod.games/valve/settings.txt b/gamedir/addons/amxmodx/data/gamedata/weaponmod.games/valve/settings.txt similarity index 100% rename from gamedir/amxmodx/data/gamedata/weaponmod.games/valve/settings.txt rename to gamedir/addons/amxmodx/data/gamedata/weaponmod.games/valve/settings.txt diff --git a/gamedir/amxmodx/scripting/include/beams.inc b/gamedir/addons/amxmodx/scripting/include/beams.inc similarity index 100% rename from gamedir/amxmodx/scripting/include/beams.inc rename to gamedir/addons/amxmodx/scripting/include/beams.inc diff --git a/gamedir/amxmodx/scripting/include/hl_wpnmod.inc b/gamedir/addons/amxmodx/scripting/include/hl_wpnmod.inc similarity index 100% rename from gamedir/amxmodx/scripting/include/hl_wpnmod.inc rename to gamedir/addons/amxmodx/scripting/include/hl_wpnmod.inc diff --git a/gamedir/amxmodx/scripting/include/hl_wpnmod_compat.inc b/gamedir/addons/amxmodx/scripting/include/hl_wpnmod_compat.inc similarity index 100% rename from gamedir/amxmodx/scripting/include/hl_wpnmod_compat.inc rename to gamedir/addons/amxmodx/scripting/include/hl_wpnmod_compat.inc diff --git a/gamedir/amxmodx/scripting/include/hl_wpnmod_const.inc b/gamedir/addons/amxmodx/scripting/include/hl_wpnmod_const.inc similarity index 100% rename from gamedir/amxmodx/scripting/include/hl_wpnmod_const.inc rename to gamedir/addons/amxmodx/scripting/include/hl_wpnmod_const.inc From c138e9eaadb0964b9d8f731e327f2bf97f93ef58 Mon Sep 17 00:00:00 2001 From: tmp64 Date: Sun, 3 Aug 2025 13:24:44 +0700 Subject: [PATCH 3/4] CI: Update scripts --- .github/workflows/ci.yml | 41 ++- CMakeLists.txt | 5 + CMakePresets.json | 3 +- cmake/GitVersionSemverfier.cmake | 15 +- scripts/build_release.py | 445 ------------------------------- scripts/deploy_libs.bat | 36 --- scripts/deploy_libs.sh | 29 -- scripts/package_target.py | 54 ++++ scripts/wpnmod_appversion.sh | 69 ----- 9 files changed, 97 insertions(+), 600 deletions(-) delete mode 100644 scripts/build_release.py delete mode 100644 scripts/deploy_libs.bat delete mode 100755 scripts/deploy_libs.sh create mode 100644 scripts/package_target.py delete mode 100755 scripts/wpnmod_appversion.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7de28c7..a3fb6c8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,10 +13,14 @@ jobs: build: strategy: matrix: - runs-on: [windows-2019, ubuntu-20.04] + os: + - runs-on: windows-2022 + suffix: ci-windows + - runs-on: ubuntu-22.04 + suffix: ci-linux # The type of runner that the job will run on - runs-on: ${{ matrix.runs-on }} + runs-on: ${{ matrix.os.runs-on }} # Steps represent a sequence of tasks that will be executed as part of the job steps: @@ -24,26 +28,35 @@ jobs: - uses: actions/checkout@v3.3.0 with: submodules: recursive - - - name: Set up Python 3.8 - uses: actions/setup-python@v4.5.0 - with: - python-version: 3.8 - + fetch-depth: 0 # Required for automatic versioning + - name: Install Ubuntu packages - if: matrix.runs-on == 'ubuntu-20.04' + if: matrix.os.runs-on == 'ubuntu-22.04' run: | sudo dpkg --add-architecture i386 sudo apt update || true - sudo apt install -y libc6:i386 ninja-build gcc-9-multilib g++-9-multilib libssl1.1:i386 libssl-dev:i386 zlib1g-dev:i386 + sudo apt install -y libc6:i386 linux-libc-dev:i386 ninja-build gcc-multilib g++-multilib - name: Build release id: build run: | - python ./scripts/build_release.py --build-type release --vs 2019 --toolset v141_xp --linux-compiler gcc-9 --out-dir ./_build_out --cmake-args="-DWARNINGS_ARE_ERRORS=ON" --github-actions + cmake --preset github-actions + cmake --build --preset github-actions + cmake --install ${{github.workspace}}/_build/github-actions --config RelWithDebInfo --prefix ${{github.workspace}}/_build/ci-install + + # Prepare artifact + - name: Prepare artifact + id: prepare_artifact + run: > + python scripts/package_target.py + --build-dir ${{github.workspace}}/_build/github-actions + --install-dir ${{github.workspace}}/_build/ci-install + --artifact-dir ${{github.workspace}}/_build/ci-artifact + --suffix ${{ matrix.os.suffix }} + # Upload result - name: Upload build result - uses: actions/upload-artifact@v4.6.0 + uses: actions/upload-artifact@v4 with: - name: ${{ steps.build.outputs.artifact_name }} - path: ./_build_out/WeaponMod-*.zip + name: ${{ steps.prepare_artifact.outputs.artifact_name }} + path: ${{github.workspace}}/_build/ci-artifact diff --git a/CMakeLists.txt b/CMakeLists.txt index dc273e6..976e64e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,6 +17,11 @@ include(InputFilesList) include(WinXPSupport) include(GitVersionSemverfier) +# This will be used by CI build scripts +file( WRITE ${CMAKE_BINARY_DIR}/version.txt ${GIT_SEM_VERSION} ) + +message( "Version: ${GIT_SEM_VERSION} (from Git)" ) + project( WeaponMod VERSION "${GIT_MAJOR}.${GIT_MINOR}.${GIT_PATCH}" ) include( PlatformInfo ) diff --git a/CMakePresets.json b/CMakePresets.json index 7c17eb3..9c7cbbf 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -22,8 +22,7 @@ }, { "name": "github-actions", - "inherits": ["base"], - "generator": "Ninja Multi-Config", + "inherits": ["platform-generator", "base"], "cacheVariables": { "GNU_FORCE_COLORED_OUTPUT": false, "WARNINGS_ARE_ERRORS": true diff --git a/cmake/GitVersionSemverfier.cmake b/cmake/GitVersionSemverfier.cmake index 5f6d18f..6f1cb50 100644 --- a/cmake/GitVersionSemverfier.cmake +++ b/cmake/GitVersionSemverfier.cmake @@ -42,6 +42,9 @@ if(GIT_FOUND AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/.git") WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} OUTPUT_VARIABLE GIT_HASH OUTPUT_STRIP_TRAILING_WHITESPACE) + # Replace slash in Git branch name + string( REPLACE "/" "-" GIT_BRANCH ${GIT_BRANCH} ) + if(NOT _git_code EQUAL 128) string(REGEX MATCH "(.*)-([0-9]+)-g(.+)" _git_result "${_git_result}") set(GIT_TAG ${CMAKE_MATCH_1}) @@ -65,9 +68,10 @@ if(GIT_FOUND AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/.git") if(GIT_SKIP GREATER 0) math(EXPR GIT_MINOR "${GIT_MINOR} + 1" OUTPUT_FORMAT DECIMAL) string(REGEX REPLACE "(v[0-9]+\.)([0-9]+)(\.[0-9]+)(.*)" "\\1${GIT_MINOR}.0\\4" GIT_TAG "${GIT_TAG}") - set(GIT_SEM_VERSION "${GIT_TAG}${_git_delimiter}dev.${GIT_SKIP}+${GIT_HASH}.${GIT_BRANCH}") + set(GIT_SEM_VERSION "${GIT_TAG}${_git_delimiter}dev.${GIT_SKIP}+${GIT_BRANCH}.${GIT_HASH}") + set(GIT_PATCH 0) else() - set(GIT_SEM_VERSION "${GIT_TAG}+${GIT_HASH}.${GIT_BRANCH}") + set(GIT_SEM_VERSION "${GIT_TAG}+${GIT_BRANCH}.${GIT_HASH}") endif() execute_process(COMMAND "${GIT_EXECUTABLE}" diff-index --quiet HEAD -- @@ -77,6 +81,7 @@ if(GIT_FOUND AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/.git") OUTPUT_STRIP_TRAILING_WHITESPACE) if(_git_dirty EQUAL 1) set(GIT_SEM_VERSION "${GIT_SEM_VERSION}.m") + message("Git working tree is dirty!") endif() #if(CMAKE_SCRIPT_MODE_FILE) @@ -87,13 +92,13 @@ if(GIT_FOUND AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/.git") #endif() string(REGEX REPLACE "v(.*)" "\\1" GIT_SEM_VERSION "${GIT_SEM_VERSION}") + set(GIT_SUCCESS TRUE) else() set(GIT_MAJOR 0) set(GIT_MINOR 0) set(GIT_PATCH 0) set(GIT_SKIP 0) set(GIT_TAG "v0.0.0") - set(GIT_SEM_VERSION "0.0.0+0000000.error") + set(GIT_SEM_VERSION "0.0.0+error.0000000") + set(GIT_SUCCESS FALSE) endif() - -message("Version: ${GIT_SEM_VERSION}") diff --git a/scripts/build_release.py b/scripts/build_release.py deleted file mode 100644 index 0460bb8..0000000 --- a/scripts/build_release.py +++ /dev/null @@ -1,445 +0,0 @@ -#!/usr/bin/env python3 - -import argparse -import datetime -import distutils -import distutils.dir_util -import os -import shutil -import subprocess -import sys -import zipfile - - -# Also needs to be changed in CMakeLists.txt -DEFAULT_VERSION = [1, 10, 0, 'dev', ''] - - -# --------------------------------------------- -# Platform stuff -# --------------------------------------------- -def get_platform_type() -> str: - if sys.platform.startswith('linux'): - return 'linux' - elif sys.platform.startswith('win32'): - return 'windows' - else: - return 'unknown' - - -class PlatformWindows: - script = None - - def __init__(self, scr): - self.script = scr - - def get_cmake_args(self): - args = [] - if self.script.vs_version == '2017': - args.extend(['-G', 'Visual Studio 15 2017']) - elif self.script.vs_version == '2019': - args.extend(['-G', 'Visual Studio 16 2019']) - args.extend(['-A', 'Win32']) - elif self.script.vs_version == '2022': - args.extend(['-G', 'Visual Studio 17 2022']) - args.extend(['-A', 'Win32']) - - args.extend(['-T', self.script.vs_toolset]) - - return args - - def get_cmake_build_args(self): - return ['--', '/p:VcpkgEnabled=false'] - - def need_cmake_build_type_var(self): - return False - - def get_dll_ext(self): - return '.dll' - - def get_out_dir(self, path): - return f'{self.script.paths.build}/{path}/{self.script.build_type}/' - - -class PlatformLinux: - script = None - - def __init__(self, scr): - self.script = scr - - def get_cmake_args(self): - args = [] - args.extend(['-G', 'Ninja']) - - toolchain_file = '' - - if self.script.linux_compiler == 'gcc': - toolchain_file = 'ToolchainLinuxGCC.cmake' - elif self.script.linux_compiler == 'gcc-8': - toolchain_file = 'ToolchainLinuxGCC8.cmake' - elif self.script.linux_compiler == 'gcc-9': - toolchain_file = 'ToolchainLinuxGCC9.cmake' - - if toolchain_file: - args.extend(['-DCMAKE_TOOLCHAIN_FILE={}cmake/{}'.format(self.script.repo_root, toolchain_file)]) - return args - - def get_cmake_build_args(self): - return [] - - def need_cmake_build_type_var(self): - return True - - def get_dll_ext(self): - return '_i386.so' - - def get_out_dir(self, path): - return f'{self.script.paths.build}/{path}/' - - -# --------------------------------------------- -# Targets -# --------------------------------------------- -class FileToCopy: - src = '' - dst = '' - - def __init__(self, _src, _dst): - self.src = _src - self.dst = _dst - - -class TargetWeaponMod: - script = None - - def __init__(self, scr): - self.script = scr - - def get_build_target_names(self): - return ['weaponmod_amxx'] - - def get_file_list(self): - files = [] - out_dir = self.script.platform.get_out_dir('src') - - files.append(FileToCopy('gamedir/amxmodx', 'amxmodx')) - - files.append(FileToCopy(out_dir + 'weaponmod_amxx' + self.script.platform.get_dll_ext(), - 'amxmodx/modules/weaponmod_amxx' + self.script.platform.get_dll_ext())) - - if get_platform_type() == 'windows': - files.append(FileToCopy(out_dir + 'weaponmod_amxx.pdb', - 'amxmodx/modules/weaponmod_amxx.pdb')) - - files.append(FileToCopy('README.md', 'amxmodx/README_WeaponMod.md')) - - return files - - -# --------------------------------------------- -# Build script class -# --------------------------------------------- -class BuildScript: - class Paths: - base = '' - build = '' - archive_root = '' - archive_files = '' - - allowed_build_types = ['debug', 'release'] - - allowed_vs_versions = ['2017', '2019', '2022'] - allowed_vs_toolsets = ['v141', 'v141_xp', 'v142'] - - allowed_linux_compilers = ['gcc', 'gcc-8', 'gcc-9'] - - platform = None - build_target = None - build_type = '' # Values are Debug or RelWithDebInfo - vs_version = '' - vs_toolset = '' - linux_compiler = '' - release_version = '' - release_version_array = [] - - cmake_binary = '' - cmake_args = [] - - repo_root = '' - git_hash = '' - date_code = '' - out_dir_name = '' - paths = Paths() - - def run(self): - if get_platform_type() == 'unknown': - print("Unknown platform. This script only supports Windows and Linux.") - exit(1) - elif get_platform_type() == 'windows': - self.platform = PlatformWindows(self) - elif get_platform_type() == 'linux': - self.platform = PlatformLinux(self) - - try: - self.repo_root = \ - subprocess.Popen(['git', 'rev-parse', '--show-toplevel'], stdout=subprocess.PIPE).communicate()[ - 0].rstrip().decode('utf-8').replace('\\', '/') + '/' - self.git_hash = \ - subprocess.Popen(['git', 'rev-parse', '--short', 'HEAD'], stdout=subprocess.PIPE).communicate()[ - 0].rstrip().decode('utf-8') - except Exception as e: - print('Failed to get info from Git repo: ' + str(e)) - exit(1) - - self.date_code = datetime.datetime.now().strftime('%Y%m%d') - - self.parse_arguments() - - self.run_cmake() - self.build_targets() - self.copy_files() - self.create_zip() - - def parse_arguments(self): - parser = argparse.ArgumentParser(description='Builds production build of WeaponMod, ready for release on ' - 'GitHub.') - - parser.add_argument('--build-type', action='store', required=True, choices=self.allowed_build_types, - help='(required) build type') - parser.add_argument('--vs', action='store', required=(get_platform_type() == 'windows'), - choices=self.allowed_vs_versions, help='(windows only, required) Visual Studio version') - parser.add_argument('--toolset', action='store', required=(get_platform_type() == 'windows'), - choices=self.allowed_vs_toolsets, help='(windows only, required) Visual Studio toolset') - parser.add_argument('--linux-compiler', action='store', default='gcc', - choices=self.allowed_linux_compilers, help='(linux only) CMake toolchain file to use') - # parser.add_argument('--updater', action='store_true', - # help='enable updater') - parser.add_argument('--out-dir', action='store', - help='output directory (if not set, uses /WeaponMod-)') - parser.add_argument('--cmake-bin', action='store', default=shutil.which("cmake"), - help='path to cmake binary (PATH used instead)') - parser.add_argument('--cmake-args', action='store', - help='additional CMake arguments') - parser.add_argument('--github-actions', dest='ci', action='store_true', - help='build for GitHub Actions') - - # Version override - parser.add_argument('--v-major', action='store', type=int, default=DEFAULT_VERSION[0], - help='sets major version number') - parser.add_argument('--v-minor', action='store', type=int, default=DEFAULT_VERSION[1], - help='sets minor version number') - parser.add_argument('--v-patch', action='store', type=int, default=DEFAULT_VERSION[2], - help='sets patch version number') - parser.add_argument('--v-tag', action='store', default=DEFAULT_VERSION[3], - help='sets version tag') - parser.add_argument('--v-meta', action='store', default=DEFAULT_VERSION[4], - help='sets version metadata') - - # Parse arguments - args = parser.parse_args() - self.vs_version = args.vs - self.vs_toolset = args.toolset - self.linux_compiler = args.linux_compiler - self.cmake_binary = args.cmake_bin - if args.cmake_args: - self.cmake_args = [i.strip() for i in args.cmake_args.split(' ')] - - # Set target - self.build_target = TargetWeaponMod(self) - - # Set build type - if args.build_type == 'debug': - self.build_type = 'Debug' - elif args.build_type == 'release': - self.build_type = 'RelWithDebInfo' - - # Parse version - self.release_version = "{}.{}.{}".format(args.v_major, args.v_minor, args.v_patch) - if args.v_tag: - self.release_version = "{}-{}".format(self.release_version, args.v_tag) - if args.v_meta: - self.release_version = "{}+{}".format(self.release_version, args.v_meta) - - self.release_version_array = [args.v_major, args.v_minor, args.v_patch, args.v_tag] - - # Set output directory - self.out_dir_name = 'WeaponMod-{}-{}-{}-{}'.format( - self.release_version.replace('+', '-'), - get_platform_type(), - self.git_hash, - self.date_code - ) - - # Set artifact name - if args.ci: - with open(os.environ['GITHUB_OUTPUT'], 'a') as f: - f.write('artifact_name=WeaponMod-{}-{}-{}'.format( - self.release_version.replace('+', '-'), - get_platform_type(), - self.git_hash - )) - - out_dir = args.out_dir - if out_dir: - self.paths.base = out_dir - else: - work_dir = os.getcwd() - out_dir = self.out_dir_name - if os.path.exists(os.path.realpath(work_dir + '/' + out_dir)): - count = 1 - while True: - out_dir = '{}-{:03}'.format(self.out_dir_name, count) - if os.path.exists(os.path.realpath(work_dir + '/' + out_dir)): - count += 1 - else: - break - - if count > 999: - print('Failed to create out path. Try setting it manually') - exit(1) - break - self.paths.base = os.path.realpath(work_dir + '/' + out_dir) - - self.paths.base = os.path.realpath(self.paths.base) + '/' - self.paths.build = self.paths.base + 'build' - self.paths.archive_root = self.paths.base + 'archive' + '/' - self.paths.archive_files = self.paths.archive_root + self.out_dir_name + '/' - - try: - os.mkdir(self.paths.base) - os.mkdir(self.paths.build) - os.mkdir(self.paths.archive_root) - os.mkdir(self.paths.archive_files) - except OSError as e: - print("Failed to create out paths: {}. Try setting it manually.".format(str(e))) - exit(1) - - def run_cmake(self): - try: - args = [self.cmake_binary] - args.extend(['-S', self.repo_root]) - args.extend(['-B', self.paths.build]) - args.extend(self.platform.get_cmake_args()) - args.extend(['-DVERSION_MAJOR=' + str(self.release_version_array[0])]) - args.extend(['-DVERSION_MINOR=' + str(self.release_version_array[1])]) - args.extend(['-DVERSION_PATCH=' + str(self.release_version_array[2])]) - - if self.release_version_array[3] == '': - args.extend(['-DVERSION_TAG=no_tag']) - else: - args.extend(['-DVERSION_TAG=' + self.release_version_array[3]]) - - args.extend(self.cmake_args) - - if self.platform.need_cmake_build_type_var(): - args.extend(['-DCMAKE_BUILD_TYPE=' + self.build_type]) - - print("---------------- Running CMake with arguments:") - for i in args: - print('\'', i, '\' ', sep='', end='') - print() - print() - - subprocess.run(args, check=True) - - print() - print() - except subprocess.CalledProcessError: - print('CMake finished with non-zero status code.') - exit(1) - except Exception as e: - print('Failed to run CMake: {}.'.format(str(e))) - exit(1) - - def build_targets(self): - try: - targets = '' - - for target in self.build_target.get_build_target_names(): - targets += target + ' ' - - targets = targets.strip() - - print("---------------- Building targets", targets) - args = [self.cmake_binary] - args.extend(['--build', self.paths.build]) - args.append('--target') - args.extend(self.build_target.get_build_target_names()) - args.extend(['--config', self.build_type]) - args.extend(self.platform.get_cmake_build_args()) - - for i in args: - print('\'', i, '\' ', sep='', end='') - print() - print() - - subprocess.run(args, check=True) - - print() - print() - except subprocess.CalledProcessError: - print('Build finished with non-zero status code.') - exit(1) - except Exception as e: - print('Failed to run build: {}.'.format(str(e))) - exit(1) - - def copy_files(self): - print("---------------- Copying files") - files = self.build_target.get_file_list() - - for i in files: - print(i.src, '->', i.dst) - - if os.path.isabs(i.src): - src = i.src - else: - src = self.repo_root + i.src - dst = self.paths.archive_files + i.dst - - try: - if not os.path.exists(src): - print('Error: file', src, "doesn't exist.") - exit(1) - - if os.path.exists(dst): - print('Error: file', dst, 'already exists.') - exit(1) - - if os.path.isdir(src): - distutils.dir_util.copy_tree(src, dst) - else: - # Create parent dirs - distutils.dir_util.mkpath(os.path.dirname(dst)) - - # Copy file - shutil.copyfile(src, dst) - - except Exception as e: - print('Failed to copy files: {}.'.format(str(e))) - exit(1) - - print() - print() - - def create_zip(self): - print("---------------- Creating ZIP archive") - try: - zipf = zipfile.ZipFile('{}{}.zip'.format(self.paths.base, self.out_dir_name), 'w', - zipfile.ZIP_DEFLATED) - - for root, dirs, files in os.walk(self.paths.archive_root): - for file in files: - path = os.path.join(root, file) - zipf.write(path, os.path.relpath(path, self.paths.archive_root)) - - zipf.close() - except Exception as e: - print('Failed to create ZIP archive: {}.'.format(str(e))) - exit(1) - - -# Script entry point -if __name__ == '__main__': - script = BuildScript() - script.run() diff --git a/scripts/deploy_libs.bat b/scripts/deploy_libs.bat deleted file mode 100644 index daf57db..0000000 --- a/scripts/deploy_libs.bat +++ /dev/null @@ -1,36 +0,0 @@ -@echo OFF -:: Usage: PublishLibs.bat [path list file] [files to copy...] - -SET pathsList=%~1 - -IF NOT EXIST "%pathsList%" ( - ECHO No deployment path specified. Create %pathsList% with paths on separate lines for auto deployment. - exit /B 0 -) - -:TOP -:: Iterate over arguments passed to the script -SHIFT -IF (%1) == () GOTO END - -SET file=%1 -:: Replace forward slashes with back slashes (because Windows) -SET file=%file:/=\% - -:: Iterate over lines in %pathsList% and copy the file from argument -FOR /f "tokens=* delims= usebackq" %%a IN ("%pathsList%") DO ( - IF NOT "%%a" == "" ( - copy /Y "%file%" "%%a" - ) -) - -IF "%%a" == "" ( - ECHO No deployment path specified. - exit /B 0 -) - -GOTO TOP - -:END - -exit /B 0 diff --git a/scripts/deploy_libs.sh b/scripts/deploy_libs.sh deleted file mode 100755 index 1a16d81..0000000 --- a/scripts/deploy_libs.sh +++ /dev/null @@ -1,29 +0,0 @@ -#!/bin/bash -# Usage: PublishLibs.sh [path list file] [files to copy...] - -PATHS=$1 - -if [ ! -f "$PATHS" ]; then - echo "No deployment path specified. Create file ${PATHS} with paths on separate lines for auto deployment." - exit 0 -fi - -while IFS="" read -r p || [ -n "$f" ] -do - echo "Deploying to: ${p}" - - FIRST=0 - mkdir -p "${p}" - - for file in "$@" - do - if [ "$FIRST" -eq 0 ]; then - # Skip first argument which is list file name - # I am bad in bash - FIRST=1 - continue - fi - cp "${file}" "${p}" - done - -done < "$PATHS" diff --git a/scripts/package_target.py b/scripts/package_target.py new file mode 100644 index 0000000..48d2054 --- /dev/null +++ b/scripts/package_target.py @@ -0,0 +1,54 @@ +import argparse +import os +import re +import shutil +from pathlib import Path + +SEMVER_REGEX = re.compile(r'^(?P0|[1-9]\d*)\.(?P0|[1-9]\d*)\.(?P0|[1-9]\d*)(?:-(?P(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+(?P[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$') + +def main(): + parser = argparse.ArgumentParser(description='Prepares CMake install results for zipping') + parser.add_argument('--build-dir', required=True, help='CMake build directory') + parser.add_argument('--install-dir', required=True, help='CMake install directory') + parser.add_argument('--artifact-dir', required=True, help='Output artifact directory. A new directory will be created there') + parser.add_argument('--suffix', required=True, help='Name suffix') + parser.add_argument('--allow-mod', action=argparse.BooleanOptionalAction, help='Allow version to have .m suffix') + + args = parser.parse_args() + build_dir = Path(args.build_dir) + install_dir = Path(args.install_dir) + artifact_dir = Path(args.artifact_dir) + + # Read the version file + with open(build_dir / 'version.txt', 'r', encoding='utf-8') as f: + version = f.read().strip() + + # Check that version is valid + if not SEMVER_REGEX.match(version): + raise Exception(f'Version is not a valid semver 2.0.0 version (version is {version})') + + # Make sure we don't package binaries with '.m' suffix + if version.endswith('.m') and not args.allow_mod: + raise Exception(f'Source tree is modified (version is {version})') + + version_main = version.split("+")[0] + + # Assemble the artifact name + artifact_name = f'WeaponMod-{version_main}-{args.suffix}' + print(f'artifact_name = {artifact_name}') + + # Copy install files to artifact dir + artifact_inner_dir = artifact_dir / artifact_name / 'valve_addon' + artifact_inner_dir.parent.mkdir(parents=True, exist_ok=True) + shutil.copytree(install_dir, artifact_inner_dir) + + # Pass artifact name to GitHub Actions + if 'GITHUB_OUTPUT' in os.environ: + print(f'Passing artifact name to GitHub Actions as artifact_name') + with open(os.environ['GITHUB_OUTPUT'], 'a', encoding='utf-8') as f: + f.write(f'artifact_name={artifact_name}\n') + else: + print(f'Not running on GitHub Actions') + + +main() diff --git a/scripts/wpnmod_appversion.sh b/scripts/wpnmod_appversion.sh deleted file mode 100755 index e0faa5f..0000000 --- a/scripts/wpnmod_appversion.sh +++ /dev/null @@ -1,69 +0,0 @@ -#!/bin/sh - - # Get old version information from appversion.h - if [ -e ./wpnmod_appversion.h ]; then - oldver=$(cat ./wpnmod_appversion.h | grep -i '#define APP_VERSION_STRD' | sed -e 's/#define APP_VERSION_STRD \(.*\)/\1/i') - if [ $? -ne 0 ]; then - oldver="" - fi - oldmod=$(cat ./wpnmod_appversion.h | grep -i '#define APP_VERSION_SPECIALBUILD' | sed -e 's/#define APP_VERSION_SPECIALBUILD \(.*\)/\1/i') - if [ $? -ne 0 ]; then - oldmod="" - fi - fi - - # Get major, minor and maitenance information from version.h - major=$(cat ./include/wpnmod_version.h | grep -i '#define VERSION_MAJOR' | sed -e 's/#define VERSION_MAJOR \(.*\)/\1/i') - if [ $? -ne 0 -o "$major" = "" ]; then - major=0 - fi - minor=$(cat ./include/wpnmod_version.h | grep -i '#define VERSION_MINOR' | sed -e 's/#define VERSION_MINOR \(.*\)/\1/i') - if [ $? -ne 0 -o "$minor" = "" ]; then - minor=0 - fi - maintenance=$(cat ./include/wpnmod_version.h | grep -i '#define VERSION_MAITENANCE' | sed -e 's/#define VERSION_MAITENANCE \(.*\)/\1/i') - if [ $? -ne 0 -o "$maintenance" = "" ]; then - maintenance= - fi - - # Get revision and modification status from local SVN copy - revision=$(svnversion -c -n ./ | sed -e 's/\([0-9]\+:\)\?\([0-9]\+\).*/\2/') - if [ $? -ne 0 -o "$revision" = "" ]; then - revision=0 - fi - modifications=$(svnversion -c -n ./ | sed -e 's/\([0-9]\+:\)\?[0-9]\+.*\(M\).*/\2/') - if [ $? -eq 0 -a "$modifications" = "M" ]; then - modifications='"'modified'"' - else - modifications="" - fi - - # Construct version string - if [ "$maitenance" = "" ]; then - version='"'$major.$minor.$revision'"' - else - version='"'$major.$minor.$maitenance.$revision'"' - fi - - if [ "$version" != "$oldver" -o "$modifications" != "$oldmod" ]; then - echo Old version is $oldver $oldmod and new one is $version $modifications - echo Going to update wpnmod_appversion.h - - echo '#ifndef __APPVERSION_H__' > wpnmod_appversion.h - echo '#define __APPVERSION_H__' >> wpnmod_appversion.h - echo '' >> wpnmod_appversion.h - - echo -n '#define APP_VERSION_STRD ' >> wpnmod_appversion.h - echo $version >> wpnmod_appversion.h - - if [ "$modifications" != "" ]; then - echo -n '#define APP_VERSION_SPECIALBUILD ' >> wpnmod_appversion.h - echo $modifications >> wpnmod_appversion.h - echo '#define APP_VERSION APP_VERSION_STRD " " APP_VERSION_SPECIALBUILD' >> wpnmod_appversion.h - else - echo '#define APP_VERSION APP_VERSION_STRD' >> wpnmod_appversion.h - fi - echo '' >> wpnmod_appversion.h - - echo '#endif //__APPVERSION_H__' >> wpnmod_appversion.h - fi From 8af6f480614fb28f0b6bcc2d551d72c279da1f99 Mon Sep 17 00:00:00 2001 From: tmp64 Date: Sun, 3 Aug 2025 13:41:10 +0700 Subject: [PATCH 4/4] CI: Merge artifacts --- .github/workflows/ci.yml | 40 +++++++++++++++++++ scripts/merge_artifacts.py | 82 ++++++++++++++++++++++++++++++++++++++ scripts/package_target.py | 2 +- 3 files changed, 123 insertions(+), 1 deletion(-) create mode 100644 scripts/merge_artifacts.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a3fb6c8..662ee2c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -60,3 +60,43 @@ jobs: with: name: ${{ steps.prepare_artifact.outputs.artifact_name }} path: ${{github.workspace}}/_build/ci-artifact + + merge-artifacts: + name: "Merge Artifacts" + needs: build + runs-on: ubuntu-latest + + env: + # Must also be updated in src/game/server/CMakeLists.txt + AMXX_OFFSET_GENERATOR_VERSION: "1.0.1" + + steps: + # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it + - uses: actions/checkout@v4 + with: + submodules: false # Only need scripts folder + + # Download artifacts + - uses: actions/download-artifact@v4 + with: + path: ${{github.workspace}}/_build/ci-artifacts + pattern: "*-ci-*" + merge-multiple: true + + # Merge them + - name: Merge artifacts + id: merge_artifacts + run: > + python scripts/merge_artifacts.py + --artifact-dir ${{github.workspace}}/_build/ci-artifacts + --out-dir ${{github.workspace}}/_build/ci-out-artifact + ci-linux + ci-windows + + # Upload merged artifact + - name: Upload merged artifact + uses: actions/upload-artifact@v4 + with: + name: ${{ steps.merge_artifacts.outputs.artifact_name }} + path: ${{github.workspace}}/_build/ci-out-artifact + diff --git a/scripts/merge_artifacts.py b/scripts/merge_artifacts.py new file mode 100644 index 0000000..4db415f --- /dev/null +++ b/scripts/merge_artifacts.py @@ -0,0 +1,82 @@ +import argparse +import json +import os +import re +import shutil +from pathlib import Path + + +def main(): + parser = argparse.ArgumentParser(description='Merges multiple artifacts into one') + parser.add_argument('--artifact-dir', required=True, help='Where artifacts are located') + parser.add_argument('--out-dir', required=True, help='Where to put the merged artifact') + parser.add_argument('suffixes', nargs='+', help='Artifact suffixes to combine. Priority is first to last.') + + args = parser.parse_args() + artifact_dir = Path(args.artifact_dir) + out_dir = Path(args.out_dir) + name_prefix: str | None = None + + # Build file list + files: dict[str, tuple[Path, Path]] = {} # lower_case_path -> rel path, root path + + for suffix in args.suffixes: + # Find the artifact for this suffix + name_suffix = f'-{suffix}' + artifacts_with_suffix = list(artifact_dir.glob(f'*{name_suffix}', case_sensitive=True)) + + if len(artifacts_with_suffix) == 0: + if suffix.startswith('allow-missing'): + continue + else: + raise Exception(f'No artifacts found for suffix = {suffix}') + + if len(artifacts_with_suffix) > 1: + print('Found too many artifacts:') + for i in artifacts_with_suffix: + print(f'- {i}') + raise Exception(f'Too many artifacts found for suffix = {suffix}') + + artifact_with_suffix = artifacts_with_suffix[0] + + this_name_prefix = artifact_with_suffix.name.removesuffix(name_suffix) + + if name_prefix is None: + name_prefix = this_name_prefix + elif name_prefix != this_name_prefix: + raise Exception(f'Name prefix mismatch. Last: "{name_prefix}", this: "{this_name_prefix}"') + + # Build the file list + for dirpath, _, filenames in os.walk(artifact_with_suffix): + for filename in filenames: + full_path = Path(dirpath) / filename + rel_path = full_path.relative_to(artifact_with_suffix) + key = rel_path.as_posix().lower() + + if key not in files: + files[key] = (rel_path, artifact_with_suffix) + print(f'{rel_path} -> {artifact_with_suffix.name}') + + if name_prefix is None: + raise Exception('Prefix not set, bug in script') + + out_artifact_name = name_prefix + + # Pass artifact name to GitHub Actions + if 'GITHUB_OUTPUT' in os.environ: + print(f'Passing artifact name to GitHub Actions as artifact_name') + with open(os.environ['GITHUB_OUTPUT'], 'a', encoding='utf-8') as f: + f.write(f'artifact_name={out_artifact_name}\n') + else: + print(f'Not running on GitHub Actions') + + # Copy the files + artifact_inner_dir = out_dir / out_artifact_name + artifact_inner_dir.mkdir(parents=True, exist_ok=True) + + for rel_path, root_path in files.values(): + abs_path = artifact_inner_dir / rel_path + abs_path.parent.mkdir(parents=True, exist_ok=True) + shutil.copy2(root_path / rel_path, abs_path) + +main() diff --git a/scripts/package_target.py b/scripts/package_target.py index 48d2054..7280da9 100644 --- a/scripts/package_target.py +++ b/scripts/package_target.py @@ -38,7 +38,7 @@ def main(): print(f'artifact_name = {artifact_name}') # Copy install files to artifact dir - artifact_inner_dir = artifact_dir / artifact_name / 'valve_addon' + artifact_inner_dir = artifact_dir / artifact_name artifact_inner_dir.parent.mkdir(parents=True, exist_ok=True) shutil.copytree(install_dir, artifact_inner_dir)