diff --git a/.github/workflows/build-toolchain.yml b/.github/workflows/build-toolchain.yml index e7f185a078..9bab21bc77 100644 --- a/.github/workflows/build-toolchain.yml +++ b/.github/workflows/build-toolchain.yml @@ -25,6 +25,22 @@ on: default: false type: boolean description: "Build extras" + release-name: + required: false + type: string + description: "Release name for the build, used in versioning" + build_user: + required: false + type: string + description: "Override VERSION_BUILDUSER" + build_loc: + required: false + type: string + description: "Override VERSION_BUILDLOC" + build_num: + required: false + type: string + description: "Override VERSION_BUILDNUM" jobs: build: @@ -41,10 +57,10 @@ jobs: uses: actions/cache@v4 with: path: C:\VC6 - key: vc6-permanent-cache-v2 + key: vc6-permanent-cache-v3 - name: Cache CMake Dependencies - id: cache-cmake-deps + id: cache-cmake-deps-v2 uses: actions/cache@v4 with: path: build\${{ inputs.preset }}\_deps @@ -110,6 +126,22 @@ jobs: "-DRTS_BUILD_GENERALS=${{ inputs.game == 'Generals' && 'ON' || 'OFF' }}" ) + if ("${{ inputs.release-name }}") { + $buildFlags += "-DRELEASE_NAME:STRING=${{ inputs.release-name }}" + } + + if ("${{ inputs.build_user }}") { + $buildFlags += "-DVERSION_BUILDUSER:STRING=${{ inputs.build_user }}" + } + + if ("${{ inputs.build_loc }}") { + $buildFlags += "-DVERSION_BUILDLOC:STRING=${{ inputs.build_loc }}" + } + + if ("${{ inputs.build_num }}") { + $buildFlags += "-DVERSION_BUILDNUM:INT=${{ inputs.build_num }}" + } + $gamePrefix = "${{ inputs.game == 'Generals' && 'GENERALS' || 'ZEROHOUR' }}" $buildFlags += "-DRTS_BUILD_CORE_TOOLS=${{ inputs.tools && 'ON' || 'OFF' }}" $buildFlags += "-DRTS_BUILD_${gamePrefix}_TOOLS=${{ inputs.tools && 'ON' || 'OFF' }}" diff --git a/.github/workflows/weekly-release.yml b/.github/workflows/weekly-release.yml new file mode 100644 index 0000000000..7fcb70a8fa --- /dev/null +++ b/.github/workflows/weekly-release.yml @@ -0,0 +1,235 @@ +name: Weekly Release + +permissions: + contents: write + pull-requests: write + +on: + workflow_dispatch: + inputs: + build_notes: + description: 'Build notes (optional)' + required: false + default: '' + type: string + known_issues: + description: 'Known issues (optional)' + required: false + default: '' + type: string + force_changed: + description: 'Force build' + required: false + default: 'false' + type: choice + options: + - 'false' + - 'true' + pre-release: + description: 'Mark release as pre-release' + required: false + default: 'false' + type: choice + options: + - 'false' + - 'true' + build_user: + description: 'Override BUILDUSER (optional)' + required: false + type: string + build_loc: + description: 'override BUILDLOC (optional)' + required: false + type: string + + schedule: + - cron: '0 8 * * 5' + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + get-date: + runs-on: ubuntu-latest + outputs: + date: ${{ steps.date.outputs.date }} + steps: + - name: Get current date + id: date + run: echo "date=$(date +'%Y-%m-%d')" >> $GITHUB_OUTPUT + + detect-scm-changes: + needs: [get-date] + runs-on: ubuntu-latest + outputs: + changed: ${{ steps.check.outputs.changed }} + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + fetch-tags: true + - id: check + run: | + if [ "${{ github.event.inputs.force_changed }}" = "true" ]; then + echo "changed=true" >> $GITHUB_OUTPUT + exit 0 + fi + + echo LAST TAG: + git describe --tags --abbrev=0 2>/dev/null || echo "" + + LAST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "") + if [ -z "$LAST_TAG" ]; then + echo "changed=true" >> $GITHUB_OUTPUT + exit 0 + fi + CHANGED=$(git diff --name-only $LAST_TAG..HEAD | grep -v '.github/workflows/' | wc -l) + if [ "$CHANGED" -eq "0" ]; then + echo "changed=false" >> $GITHUB_OUTPUT + else + echo "changed=true" >> $GITHUB_OUTPUT + fi + + build-generals: + needs: [detect-scm-changes, get-date] + if: needs.detect-scm-changes.outputs.changed == 'true' + name: Build Generals${{ matrix.preset && '' }} + strategy: + matrix: + include: + - preset: "vc6" + tools: true + extras: false + release: true + fail-fast: false + uses: ./.github/workflows/build-toolchain.yml + with: + game: "Generals" + preset: ${{ matrix.preset }} + tools: ${{ matrix.tools }} + extras: ${{ matrix.extras }} + release-name: ${{ needs.get-date.outputs.date }} + build_user: ${{ github.event.inputs.build_user || 'TheSuperHackers' }} + build_loc: ${{ github.event.inputs.build_loc || 'https://github.com/TheSuperHackers/GeneralsGameCode/' }} + build_num: ${{ needs.calculate-version.outputs.tag_count }} + secrets: inherit + + build-generalsmd: + needs: [detect-scm-changes, get-date] + if: needs.detect-scm-changes.outputs.changed == 'true' + name: Build GeneralsMD${{ matrix.preset && '' }} + strategy: + matrix: + include: + - preset: "vc6" + tools: true + extras: false + release: true + fail-fast: false + uses: ./.github/workflows/build-toolchain.yml + with: + game: "GeneralsMD" + preset: ${{ matrix.preset }} + tools: ${{ matrix.tools }} + extras: ${{ matrix.extras }} + release-name: ${{ needs.get-date.outputs.date }} + build_user: ${{ github.event.inputs.build_user || 'TheSuperHackers' }} + build_loc: ${{ github.event.inputs.build_loc || 'https://github.com/TheSuperHackers/GeneralsGameCode/' }} + build_num: ${{ needs.calculate-version.outputs.tag_count }} + secrets: inherit + + create-release: + name: Create Release + needs: [build-generals, build-generalsmd, get-date] + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + fetch-tags: true + + - name: Collect commits since last release + id: changelog + run: | + LAST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "") + if [ -z "$LAST_TAG" ]; then + CHANGELOG_COMMITS=$(git log --pretty="format:- %s" --no-merges HEAD | head -n 10 || true) + else + CHANGELOG_COMMITS=$(git log --pretty="format:- %s" --no-merges "$LAST_TAG"..HEAD || true) + fi + if [ -z "$CHANGELOG_COMMITS" ]; then + CHANGELOG_COMMITS="- No relevant changes detected since the last release." + fi + { + echo 'commits<> "$GITHUB_OUTPUT" + + # Generals vc6 + - name: Download Generals VC6 Artifacts + uses: actions/download-artifact@v4 + with: + name: Generals-vc6+t + path: generals-vc6-artifacts + + - name: Prepare and Zip Generals VC6 + run: | + mkdir generals-vc6-release + cp generals-vc6-artifacts/generalsv.exe generals-vc6-release/GeneralsV.exe + cp generals-vc6-artifacts/W3DViewV.exe generals-vc6-release/W3DViewV.exe + cp generals-vc6-artifacts/WorldBuilderV.exe generals-vc6-release/WorldBuilderV.exe + zip -j generals-preview-${{ needs.get-date.outputs.date }}.zip generals-vc6-release/* + + # GeneralsMD vc6 + - name: Download GeneralsMD VC6 Artifacts + uses: actions/download-artifact@v4 + with: + name: GeneralsMD-vc6+t + path: generalsmd-vc6-artifacts + + - name: Prepare and Zip GeneralsMD VC6 + run: | + mkdir generalsmd-vc6-release + cp generalsmd-vc6-artifacts/generalszh.exe generalsmd-vc6-release/GeneralsZHv.exe + cp generalsmd-vc6-artifacts/W3DViewZH.exe generalsmd-vc6-release/W3DViewZHv.exe + cp generalsmd-vc6-artifacts/WorldBuilderZH.exe generalsmd-vc6-release/WorldBuilderZHv.exe + zip -j generalszh-preview-${{ needs.get-date.outputs.date }}.zip generalsmd-vc6-release/* + + - name: Generate release notes + id: release_body + run: | + BODY="" + if [ "${{ github.event.inputs.build_notes }}" != "" ]; then + BODY="${BODY}### Build notes\n${{ github.event.inputs.build_notes }}\n" + fi + if [ "${{ github.event.inputs.known_issues }}" != "" ]; then + BODY="${BODY}### Known issues\n${{ github.event.inputs.known_issues }}\n" + fi + BODY="${BODY}### Changelog\n${{ steps.changelog.outputs.commits }}" + echo "body<> $GITHUB_OUTPUT + echo -e "$BODY" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + + - name: Create GitHub Release + uses: softprops/action-gh-release@v2 + with: + tag_name: preview-${{ needs.get-date.outputs.date }} + name: preview-${{ needs.get-date.outputs.date }} + prerelease: ${{ github.event.inputs.pre-release == 'true' }} + body: ${{ steps.release_body.outputs.body }} + files: | + generals-preview-${{ needs.get-date.outputs.date }}.zip + generalszh-preview-${{ needs.get-date.outputs.date }}.zip + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Clean up release folders + if: always() + run: | + rm -rf generals-vc6-release generalsmd-vc6-release + rm -rf generals-vc6-artifacts generalsmd-vc6-artifacts + rm -f generals-preview-${{ needs.get-date.outputs.date }}.zip + rm -f generalszh-preview-${{ needs.get-date.outputs.date }}.zip diff --git a/Generals/Code/GameEngine/Source/Common/version.cpp b/Generals/Code/GameEngine/Source/Common/version.cpp index d6721bc255..3e79cffd10 100644 --- a/Generals/Code/GameEngine/Source/Common/version.cpp +++ b/Generals/Code/GameEngine/Source/Common/version.cpp @@ -329,22 +329,32 @@ UnicodeString Version::getUnicodeProductString() const if (!productTitle.isEmpty()) { - UnicodeString productVersion = TheGameText->FETCH_OR_SUBSTITUTE("Version:ProductVersion", getUnicodeProductVersion().str()); - UnicodeString productAuthor = TheGameText->FETCH_OR_SUBSTITUTE("Version:ProductAuthor", getUnicodeProductAuthor().str()); - - str.concat(productTitle); - - if (!productVersion.isEmpty()) + // Get build user or author name + AsciiString asciiUser = getAsciiBuildUserOrGitCommitAuthorName(); + UnicodeString user; + if (!asciiUser.isEmpty()) { - str.concat(L" "); - str.concat(productVersion); + user.translate(asciiUser); + } + else + { + user = L"Unknown"; } - if (!productAuthor.isEmpty()) + // Get release name from gitinfo + UnicodeString releaseName; + if (ReleaseName[0] != '\0') + { + releaseName.translate(ReleaseName); + } + else { - str.concat(L" "); - str.concat(productAuthor); + releaseName = getUnicodeGitTagOrHash(); } + + str.concat(productTitle); + str.concat(L" by "); + str.concat(user); } return str; @@ -356,15 +366,26 @@ UnicodeString Version::getUnicodeProductVersionHashString() const UnicodeString productString = getUnicodeProductString(); UnicodeString gameVersion = getUnicodeVersion(); UnicodeString gameHash; - gameHash.format(L"exe:%08X ini:%08X", TheGlobalData->m_exeCRC, TheGlobalData->m_iniCRC); + + if (ReleaseName[0]) + { + AsciiString asciiRel; + asciiRel.concat(ReleaseName); + UnicodeString uRel; + uRel.translate(asciiRel); + gameHash.format(L" release %s", uRel.str()); + } + else + { + gameHash.format(L" | exe:%08X ini:%08X", TheGlobalData->m_exeCRC, TheGlobalData->m_iniCRC); + } if (!productString.isEmpty()) { str.concat(productString); - str.concat(L" | "); } str.concat(gameHash); - str.concat(L" "); + str.concat(L" - "); str.concat(gameVersion); return str; diff --git a/Generals/Code/Main/CMakeLists.txt b/Generals/Code/Main/CMakeLists.txt index 51ca97d5af..62216caec6 100644 --- a/Generals/Code/Main/CMakeLists.txt +++ b/Generals/Code/Main/CMakeLists.txt @@ -24,13 +24,53 @@ target_link_libraries(g_generals PRIVATE winmm ) -# TODO Originally referred to build host and user, replace with git info perhaps? +# Set build information variables with default values that can be overridden +if(NOT DEFINED VERSION_BUILDUSER) + set(VERSION_BUILDUSER "\"The Super Hackers\"") +endif() + +if(NOT DEFINED VERSION_BUILDLOC) + set(VERSION_BUILDLOC "\"https://github.com/TheSuperHackers/GeneralsGameCode\"") +endif() + +# Normalize to ensure they are C string literals even when provided via -D on the CLI +set(_VERSION_BUILDUSER ${VERSION_BUILDUSER}) +if(NOT _VERSION_BUILDUSER MATCHES "^\".*\"$") + set(_VERSION_BUILDUSER "\"${VERSION_BUILDUSER}\"") +endif() + +set(_VERSION_BUILDLOC ${VERSION_BUILDLOC}) +if(NOT _VERSION_BUILDLOC MATCHES "^\".*\"$") + set(_VERSION_BUILDLOC "\"${VERSION_BUILDLOC}\"") +endif() + +# Use git revision count as build number if available, otherwise use default or override +if(NOT DEFINED VERSION_BUILDNUM) + if(DEFINED GIT_REV_LIST_COUNT) + set(VERSION_BUILDNUM ${GIT_REV_LIST_COUNT}) + else() + set(VERSION_BUILDNUM 601) + endif() +endif() + file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/GeneratedVersion.h "#pragma once +#ifndef VERSION_BUILDNUM +#define VERSION_BUILDNUM ${VERSION_BUILDNUM} +#endif + +#ifndef VERSION_LOCALBUILDNUM #define VERSION_LOCALBUILDNUM 0 -#define VERSION_BUILDUSER \"\" -#define VERSION_BUILDLOC \"\" +#endif + +#ifndef VERSION_BUILDUSER +#define VERSION_BUILDUSER ${_VERSION_BUILDUSER} +#endif + +#ifndef VERSION_BUILDLOC +#define VERSION_BUILDLOC ${_VERSION_BUILDLOC} +#endif " ) @@ -41,7 +81,6 @@ file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/BuildVersion.h #define VERSION_MAJOR 1 #define VERSION_MINOR 7 -#define VERSION_BUILDNUM 601 " ) else() @@ -50,7 +89,6 @@ file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/BuildVersion.h #define VERSION_MAJOR 1 #define VERSION_MINOR 8 -#define VERSION_BUILDNUM 601 " ) endif() diff --git a/GeneralsMD/Code/GameEngine/Source/Common/version.cpp b/GeneralsMD/Code/GameEngine/Source/Common/version.cpp index 865cf13c9d..d309d459f3 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/version.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/version.cpp @@ -329,22 +329,32 @@ UnicodeString Version::getUnicodeProductString() const if (!productTitle.isEmpty()) { - UnicodeString productVersion = TheGameText->FETCH_OR_SUBSTITUTE("Version:ProductVersion", getUnicodeProductVersion().str()); - UnicodeString productAuthor = TheGameText->FETCH_OR_SUBSTITUTE("Version:ProductAuthor", getUnicodeProductAuthor().str()); - - str.concat(productTitle); - - if (!productVersion.isEmpty()) + // Get build user or author name + AsciiString asciiUser = getAsciiBuildUserOrGitCommitAuthorName(); + UnicodeString user; + if (!asciiUser.isEmpty()) { - str.concat(L" "); - str.concat(productVersion); + user.translate(asciiUser); + } + else + { + user = L"Unknown"; } - if (!productAuthor.isEmpty()) + // Get release name from gitinfo + UnicodeString releaseName; + if (ReleaseName[0] != '\0') + { + releaseName.translate(ReleaseName); + } + else { - str.concat(L" "); - str.concat(productAuthor); + releaseName = getUnicodeGitTagOrHash(); } + + str.concat(productTitle); + str.concat(L" by "); + str.concat(user); } return str; @@ -356,15 +366,26 @@ UnicodeString Version::getUnicodeProductVersionHashString() const UnicodeString productString = getUnicodeProductString(); UnicodeString gameVersion = getUnicodeVersion(); UnicodeString gameHash; - gameHash.format(L"exe:%08X ini:%08X", TheGlobalData->m_exeCRC, TheGlobalData->m_iniCRC); + + if (ReleaseName[0]) + { + AsciiString asciiRel; + asciiRel.concat(ReleaseName); + UnicodeString uRel; + uRel.translate(asciiRel); + gameHash.format(L" release %s", uRel.str()); + } + else + { + gameHash.format(L" | exe:%08X ini:%08X", TheGlobalData->m_exeCRC, TheGlobalData->m_iniCRC); + } if (!productString.isEmpty()) { str.concat(productString); - str.concat(L" | "); } str.concat(gameHash); - str.concat(L" "); + str.concat(L" - "); str.concat(gameVersion); return str; diff --git a/GeneralsMD/Code/Main/CMakeLists.txt b/GeneralsMD/Code/Main/CMakeLists.txt index fa73f03c13..c7283a7464 100644 --- a/GeneralsMD/Code/Main/CMakeLists.txt +++ b/GeneralsMD/Code/Main/CMakeLists.txt @@ -26,13 +26,53 @@ target_link_libraries(z_generals PRIVATE zi_always ) -# TODO Originally referred to build host and user, replace with git info perhaps? +# Set build information variables with default values that can be overridden +if(NOT DEFINED VERSION_BUILDUSER) + set(VERSION_BUILDUSER "\"The Super Hackers\"") +endif() + +if(NOT DEFINED VERSION_BUILDLOC) + set(VERSION_BUILDLOC "\"https://github.com/TheSuperHackers/GeneralsGameCode\"") +endif() + +# Normalize to ensure they are C string literals even when provided via -D on the CLI +set(_VERSION_BUILDUSER ${VERSION_BUILDUSER}) +if(NOT _VERSION_BUILDUSER MATCHES "^\".*\"$") + set(_VERSION_BUILDUSER "\"${VERSION_BUILDUSER}\"") +endif() + +set(_VERSION_BUILDLOC ${VERSION_BUILDLOC}) +if(NOT _VERSION_BUILDLOC MATCHES "^\".*\"$") + set(_VERSION_BUILDLOC "\"${VERSION_BUILDLOC}\"") +endif() + +# Use git revision count as build number if available, otherwise use default or override +if(NOT DEFINED VERSION_BUILDNUM) + if(DEFINED GIT_REV_LIST_COUNT) + set(VERSION_BUILDNUM ${GIT_REV_LIST_COUNT}) + else() + set(VERSION_BUILDNUM 601) + endif() +endif() + file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/GeneratedVersion.h "#pragma once +#ifndef VERSION_BUILDNUM +#define VERSION_BUILDNUM ${VERSION_BUILDNUM} +#endif + +#ifndef VERSION_LOCALBUILDNUM #define VERSION_LOCALBUILDNUM 0 -#define VERSION_BUILDUSER \"\" -#define VERSION_BUILDLOC \"\" +#endif + +#ifndef VERSION_BUILDUSER +#define VERSION_BUILDUSER ${_VERSION_BUILDUSER} +#endif + +#ifndef VERSION_BUILDLOC +#define VERSION_BUILDLOC ${_VERSION_BUILDLOC} +#endif " ) @@ -42,7 +82,6 @@ file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/BuildVersion.h #define VERSION_MAJOR 1 #define VERSION_MINOR 4 -#define VERSION_BUILDNUM 601 " ) diff --git a/resources/CMakeLists.txt b/resources/CMakeLists.txt index d8d613ca9b..a5682e2f65 100644 --- a/resources/CMakeLists.txt +++ b/resources/CMakeLists.txt @@ -2,7 +2,30 @@ set(GIT_PRE_CONFIGURE_FILE "${CMAKE_CURRENT_SOURCE_DIR}/gitinfo/gitinfo.cpp.in") set(GIT_POST_CONFIGURE_FILE "${CMAKE_CURRENT_BINARY_DIR}/gitinfo.cpp") -include(gitinfo/git_watcher.cmake) +# Ensure RELEASE_NAME is always defined for configure_file substitutions +if(NOT DEFINED RELEASE_NAME) + set(RELEASE_NAME "") +endif() + +# Override git variables when RELEASE_NAME is defined +if(DEFINED RELEASE_NAME AND NOT "${RELEASE_NAME}" STREQUAL "") + # Set git variables to use RELEASE_NAME + set(GIT_HEAD_SHA1 "${RELEASE_NAME}") + set(GIT_HEAD_SHORT_SHA1 "${RELEASE_NAME}") + set(GIT_TAG "${RELEASE_NAME}") + set(GIT_COMMIT_DATE_ISO8601 "") + set(GIT_AUTHOR_NAME "") + set(GIT_COMMIT_TSTAMP 0) + set(GIT_IS_DIRTY false) + set(GIT_RETRIEVED_STATE true) + set(GIT_REV_LIST_COUNT 1) + + # Configure the file directly with our variables + configure_file("${GIT_PRE_CONFIGURE_FILE}" "${GIT_POST_CONFIGURE_FILE}" @ONLY) +else() + # Use the normal git watcher for development builds + include(gitinfo/git_watcher.cmake) +endif() # Create resources library add_library(resources STATIC) diff --git a/resources/gitinfo/gitinfo.cpp.in b/resources/gitinfo/gitinfo.cpp.in index 6ab0802d01..138a571001 100644 --- a/resources/gitinfo/gitinfo.cpp.in +++ b/resources/gitinfo/gitinfo.cpp.in @@ -22,6 +22,7 @@ const char GitShortSHA1[] = "@GIT_HEAD_SHORT_SHA1@"; const char GitCommitDate[] = "@GIT_COMMIT_DATE_ISO8601@"; const char GitCommitAuthorName[] = "@GIT_AUTHOR_NAME@"; const char GitTag[] = "@GIT_TAG@"; +const char ReleaseName[] = "@RELEASE_NAME@"; time_t GitCommitTimeStamp = @GIT_COMMIT_TSTAMP@; bool GitUncommittedChanges = @GIT_IS_DIRTY@; bool GitHaveInfo = @GIT_RETRIEVED_STATE@; diff --git a/resources/gitinfo/gitinfo.h b/resources/gitinfo/gitinfo.h index 6fbd55fffe..667b8e990e 100644 --- a/resources/gitinfo/gitinfo.h +++ b/resources/gitinfo/gitinfo.h @@ -30,6 +30,7 @@ extern const char GitShortSHA1[]; extern const char GitCommitDate[]; extern const char GitCommitAuthorName[]; extern const char GitTag[]; +extern const char ReleaseName[]; extern time_t GitCommitTimeStamp; extern bool GitUncommittedChanges; extern bool GitHaveInfo;