Skip to content

Windows

Windows #322

Workflow file for this run

name: Windows
on:
push:
# Match all pushes
pull_request:
# Match all pull requests
schedule:
- cron: "5 10 * * 1"
workflow_dispatch:
jobs:
windows-build:
strategy:
matrix:
compiler: ["vc2022"]
architecture: ["x86", "x64", "arm64"]
fail-fast: false
env:
APPCONFIG_MSVC_VER: ${{ matrix.compiler }}
APPCONFIG_TARGET_ARCH: ${{ matrix.architecture }}
name: '${{ matrix.architecture }} [${{ matrix.compiler }}]'
runs-on: windows-2022
if: "!contains(github.event.head_commit.message, '[ci skip]')"
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
path: 'src'
- name: Generate Settings for Run [${{ matrix.compiler }}:${{ matrix.architecture }}]
id: settings
run: |
# Basic variable setup
# --------------------
$APPCONFIG_VC_TARGET_PLATFORMNAME = "$env:APPCONFIG_TARGET_ARCH"
if ($env:APPCONFIG_TARGET_ARCH -eq "x86") {
$APPCONFIG_VC_TARGET_PLATFORMNAME = "Win32" # special case, map "x86" -> "Win32"
}
# ------------------------------
# MSVC version / generator info
$APPCONFIG_VISUAL_STUDIO_INSTALL_PATH = ""
if ($env:APPCONFIG_MSVC_VER -eq "vc2022") {
$APPCONFIG_VC_GENERATOR = "Visual Studio 17 2022"
#$env:APPCONFIG_VC_TOOLCHAIN = "v143"
$APPCONFIG_FILEVERSION_TO_CHECK = "vcruntime140.dll"
# Get the installation path for a specific version of MSVC (2022)
$APPCONFIG_VISUAL_STUDIO_INSTALL_PATH = & "vswhere.exe" -latest -property installationPath -version "[17.0,18.0)"
if ($LastExitCode -ne 0)
{
echo "::warning ::Could not find MSVC 2022"
$APPCONFIG_VISUAL_STUDIO_INSTALL_PATH = ""
}
}
elseif ($env:APPCONFIG_MSVC_VER -eq "vc2019") {
$APPCONFIG_VC_GENERATOR = "Visual Studio 16 2019"
#$env:APPCONFIG_VC_TOOLCHAIN = "v142"
$APPCONFIG_FILEVERSION_TO_CHECK = "vcruntime140.dll"
# Get the installation path for a specific version of MSVC (2019)
$APPCONFIG_VISUAL_STUDIO_INSTALL_PATH = & "vswhere.exe" -latest -property installationPath -version "[16.0,17.0)"
if ($LastExitCode -ne 0)
{
echo "::warning ::Could not find MSVC 2019"
$APPCONFIG_VISUAL_STUDIO_INSTALL_PATH = ""
}
}
elseif ($env:APPCONFIG_MSVC_VER -eq "vc2017") {
$APPCONFIG_VC_GENERATOR = "Visual Studio 15 2017"
#$env:APPCONFIG_VC_TOOLCHAIN = "v141"
$APPCONFIG_FILEVERSION_TO_CHECK = "vcruntime140.dll"
# Get the installation path for a specific version of MSVC (2017)
$APPCONFIG_VISUAL_STUDIO_INSTALL_PATH = & "vswhere.exe" -latest -property installationPath -version "[15.0,16.0)"
if ($LastExitCode -ne 0)
{
echo "::warning ::Could not find MSVC 2017"
$APPCONFIG_VISUAL_STUDIO_INSTALL_PATH = ""
}
}
if ([string]::IsNullOrWhitespace(${APPCONFIG_VISUAL_STUDIO_INSTALL_PATH}))
{
$APPCONFIG_VISUAL_STUDIO_INSTALL_PATH = & "vswhere.exe" -latest -property installationPath
echo "::warning ::Default to 'latest' MSVC: `"${APPCONFIG_VISUAL_STUDIO_INSTALL_PATH}`""
}
# ----------------
# Export Variables
# Export everything important to environment variables (for future steps)
echo "APPCONFIG_REPO_PATH=${{ github.workspace }}\src" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
echo "APPCONFIG_VC_TARGET_PLATFORMNAME=${APPCONFIG_VC_TARGET_PLATFORMNAME}" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
echo "APPCONFIG_VC_GENERATOR=${APPCONFIG_VC_GENERATOR}" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
#echo "APPCONFIG_VC_TOOLCHAIN=${APPCONFIG_VC_TOOLCHAIN}" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
echo "APPCONFIG_FILEVERSION_TO_CHECK=${APPCONFIG_FILEVERSION_TO_CHECK}" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
echo "APPCONFIG_VISUAL_STUDIO_INSTALL_PATH=${APPCONFIG_VISUAL_STUDIO_INSTALL_PATH}" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
# echo "APPCONFIG_DEPLOY_RELEASE=${APPCONFIG_DEPLOY_RELEASE}" | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append
#####################################################
- name: Create directories
run: |
New-Item -ItemType Directory -Force -Path "${{ github.workspace }}\build"
New-Item -ItemType Directory -Force -Path "${{ github.workspace }}\install"
New-Item -ItemType Directory -Force -Path "${{ github.workspace }}\output"
- name: CMake Configure
working-directory: '${{ github.workspace }}\build'
run: |
cmake -DCMAKE_GENERATOR_INSTANCE="${env:APPCONFIG_VISUAL_STUDIO_INSTALL_PATH}" -G "${env:APPCONFIG_VC_GENERATOR}" -A "${env:APPCONFIG_VC_TARGET_PLATFORMNAME}" -DCMAKE_INSTALL_PREFIX:PATH="${{ github.workspace }}\install" "${env:APPCONFIG_REPO_PATH}"
- name: CMake Build
id: build
run: |
& cmake --build build --config Release --target install
# Package the install output into a zip
# Name the zip file based on the platform-name & built product info:
# applocalconfig-<APPCONFIG_MSVC_VER>-<APPCONFIG_TARGET_ARCH>-<RUNTIME_VERSION>.zip
$VersionInfo = (Get-Item "${{ github.workspace }}\install\$($env:APPCONFIG_FILEVERSION_TO_CHECK)").VersionInfo
$FileVersion = ("{0}.{1}.{2}.{3}" -f $VersionInfo.FileMajorPart, $VersionInfo.FileMinorPart,$VersionInfo.FileBuildPart, $VersionInfo.FilePrivatePart)
$OutputFilename = "applocalconfig-$($env:APPCONFIG_MSVC_VER)-$($env:APPCONFIG_TARGET_ARCH)-$($FileVersion).zip"
$OutputFile = "${{ github.workspace }}\output\${OutputFilename}"
cmd /c 7z a "$($OutputFile)" "${{ github.workspace }}\install\*"
echo "APPCONFIG_FILEVERSION=${FileVersion}" | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append
echo "APPCONFIG_OUTPUT_FILE=${OutputFile}" | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append
echo "APPCONFIG_OUTPUT_FILENAME=${OutputFilename}" | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append
- name: Log Build Output Hash
working-directory: '${{ github.workspace }}\output'
run: |
# Log hash of the built .zip
# $outputFileName = Split-Path "${{ steps.build.outputs.APPCONFIG_OUTPUT_FILE }}" -leaf
Write-Host "SHA512 Hashes:"
Write-Host "`n${{ steps.build.outputs.APPCONFIG_OUTPUT_FILENAME }}`n`t-> SHA512: $((Get-FileHash -LiteralPath "${{ steps.build.outputs.APPCONFIG_OUTPUT_FILE }}" -Algorithm SHA512).Hash)`n`t`-> Size (bytes): $((Get-Item -LiteralPath "${{ steps.build.outputs.APPCONFIG_OUTPUT_FILE }}").Length)"
Write-Host ""
#####################################################
# Upload build artifact
#####################################################
- name: 'Upload Artifact'
uses: actions/upload-artifact@v4
if: success()
with:
name: '${{ steps.build.outputs.APPCONFIG_OUTPUT_FILENAME }}'
path: '${{ steps.build.outputs.APPCONFIG_OUTPUT_FILE }}'
if-no-files-found: 'error'
create-release:
if: github.event_name == 'workflow_dispatch' || github.event_name == 'schedule'
needs: windows-build
name: 'Auto-Release'
runs-on: ubuntu-latest
steps:
- name: 'Download All Artifacts'
uses: actions/download-artifact@v4
with:
path: dl/artifacts
- name: 'Get Latest Release Info'
id: 'latest-release-info'
env:
APPCONFIG_VERSIONCHECK_ARCH: x64
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
run: |
gh release view --json "id,tagName,body" > "latest.json"
latest_release_id="$(cat "latest.json" | jq -r '.id')"
echo "latest_release_id=${latest_release_id}"
latest_release_tagname="$(cat "latest.json" | jq -r '.tagName')"
echo "latest_release_tagname=${latest_release_tagname}"
# Remove "v/" or "v" prefix (as in "v3.2.2"), if present
latest_version="$(echo "${latest_release_tagname}" | sed -e 's:^v/::' -e 's:^v::')"
echo "latest_version=${latest_version}"
latest_release_runtime_version=$(cat "latest.json" | jq -r '.body' | sed -n "s/^\s*MSVC [0-9][0-9]* [|] ${APPCONFIG_VERSIONCHECK_ARCH} [|] \([0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\)/\1/p")
echo "latest_release_runtime_version=${latest_release_runtime_version}"
echo "RELEASE_ID=${latest_release_id}" >> $GITHUB_OUTPUT
echo "RELEASE_TAGNAME=${latest_release_tagname}" >> $GITHUB_OUTPUT
echo "RELEASE_SEMVER=${latest_version}" >> $GITHUB_OUTPUT
echo "RUNTIME_VERSION=${latest_release_runtime_version}" >> $GITHUB_OUTPUT
- name: 'Calculate Next SemVer'
id: 'next-semver'
run: |
# Reference: https://stackoverflow.com/a/17364637
# Accepts a version string and prints it incremented by one.
# Usage: increment_version <version> [<position>] [<leftmost>]
increment_version() {
local usage=" USAGE: $FUNCNAME [-l] [-t] <version> [<position>] [<leftmost>]
-l : remove leading zeros
-t : drop trailing zeros
<version> : The version string.
<position> : Optional. The position (starting with one) of the number
within <version> to increment. If the position does not
exist, it will be created. Defaults to last position.
<leftmost> : The leftmost position that can be incremented. If does not
exist, position will be created. This right-padding will
occur even to right of <position>, unless passed the -t flag."
# Get flags.
local flag_remove_leading_zeros=0
local flag_drop_trailing_zeros=0
while [ "${1:0:1}" == "-" ]; do
if [ "$1" == "--" ]; then shift; break
elif [ "$1" == "-l" ]; then flag_remove_leading_zeros=1
elif [ "$1" == "-t" ]; then flag_drop_trailing_zeros=1
else echo -e "Invalid flag: ${1}\n$usage"; return 1; fi
shift; done
# Get arguments.
if [ ${#@} -lt 1 ]; then echo "$usage"; return 1; fi
local v="${1}" # version string
local targetPos=${2-last} # target position
local minPos=${3-${2-0}} # minimum position
# Split version string into array using its periods.
local IFSbak; IFSbak=IFS; IFS='.' # IFS restored at end of func to
read -ra v <<< "$v" # avoid breaking other scripts.
# Determine target position.
if [ "${targetPos}" == "last" ]; then
if [ "${minPos}" == "last" ]; then minPos=0; fi
targetPos=$((${#v[@]}>${minPos}?${#v[@]}:$minPos)); fi
if [[ ! ${targetPos} -gt 0 ]]; then
echo -e "Invalid position: '$targetPos'\n$usage"; return 1; fi
(( targetPos-- )) || true # offset to match array index
# Make sure minPosition exists.
while [ ${#v[@]} -lt ${minPos} ]; do v+=("0"); done;
# Increment target position.
v[$targetPos]=`printf %0${#v[$targetPos]}d $((10#${v[$targetPos]}+1))`;
# Remove leading zeros, if -l flag passed.
if [ $flag_remove_leading_zeros == 1 ]; then
for (( pos=0; $pos<${#v[@]}; pos++ )); do
v[$pos]=$((${v[$pos]}*1)); done; fi
# If targetPosition was not at end of array, reset following positions to
# zero (or remove them if -t flag was passed).
if [[ ${flag_drop_trailing_zeros} -eq "1" ]]; then
for (( p=$((${#v[@]}-1)); $p>$targetPos; p-- )); do unset v[$p]; done
else for (( p=$((${#v[@]}-1)); $p>$targetPos; p-- )); do v[$p]=0; done; fi
echo "${v[*]}"
IFS=IFSbak
return 0
}
next_release_semver="$(increment_version ${{ steps.latest-release-info.outputs.RELEASE_SEMVER }} last 3)"
echo "next_release_semver=${next_release_semver}"
echo "NEXT_SEMVER=${next_release_semver}" >> $GITHUB_OUTPUT
- name: 'Determine whether to Auto-Release'
id: 'config'
working-directory: 'dl/artifacts'
run: |
echo "Parsing artifacts for version information"
echo "v${{ steps.next-semver.outputs.NEXT_SEMVER }}" > "BUILD_INFO.md"
echo "Built on: $(date '+%Y-%m-%d')" >> "BUILD_INFO.md"
echo "" >> "BUILD_INFO.md"
echo "Compiler | Architecture | Local Runtime Version" >> "BUILD_INFO.md"
echo "--------- | --------- | -------------" >> "BUILD_INFO.md"
NEEDS_RELEASE=0
for d in */ ; do
artifact_filename="${d%/}"
echo "Found artifact: ${artifact_filename}"
# Extract the runtime version number (etc) from the artifact filename
# Expected format of artifact folder is: applocalconfig-vc<MSVCVER>-<ARCH>-XX.YY.ZZZZZ.B.zip/
artifact_msvc_version="$(echo "${artifact_filename}" | sed -n "s/^applocalconfig-vc\([0-9]*\)-[a-z0-9A-Z]*-[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*.zip$/\1/p")"
artifact_architecture="$(echo "${artifact_filename}" | sed -n "s/^applocalconfig-vc[0-9]*-\([a-z0-9A-Z]*\)-[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*.zip$/\1/p")"
artifact_runtime_version="$(echo "${artifact_filename}" | sed -n "s/^applocalconfig-vc[0-9]*-[a-z0-9A-Z]*-\([0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\).zip$/\1/p")"
echo "MSVC ${artifact_msvc_version} | ${artifact_architecture} | ${artifact_runtime_version}"
echo "MSVC ${artifact_msvc_version} | ${artifact_architecture} | ${artifact_runtime_version}" >> "BUILD_INFO.md"
# Compare versus the runtime version number included in the latest release
if [ "$artifact_runtime_version" != "${{ steps.latest-release-info.outputs.RUNTIME_VERSION }}" ]; then
NEEDS_RELEASE=1
fi
# Rename the actual .zip to no longer include the runtime version
# Expected output filename is: applocalconfig-vc<MSVCVER>-<ARCH>.zip
mv "${d}${artifact_filename}" "${d}applocalconfig-vc${artifact_msvc_version}-${artifact_architecture}.zip"
done
if [ $NEEDS_RELEASE -eq 1 ]; then
echo "AUTO_RELEASE=true" >> $GITHUB_OUTPUT
else
echo "AUTO_RELEASE=false" >> $GITHUB_OUTPUT
fi
- name: 'Auto-Release'
if: success() && (steps.config.outputs.AUTO_RELEASE == 'true')
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
next_semver: '${{ steps.next-semver.outputs.NEXT_SEMVER }}'
run: |
gh release create "v${next_semver}" --title "${next_semver}" -F "./dl/artifacts/BUILD_INFO.md" ./dl/artifacts/*/*.zip ./dl/artifacts/BUILD_INFO.md
# **********************************************
# Mirror to SourceForge
# **********************************************
- name: 'Set up SourceForge Env'
if: success() && (steps.config.outputs.AUTO_RELEASE == 'true')
run: |
echo "SF_PROJECT_NAME=applocalconfig" >> $GITHUB_ENV
echo "SF_BASE_PATH=releases" >> $GITHUB_ENV
- name: 'Prepare SourceForge Config'
if: success() && (steps.config.outputs.AUTO_RELEASE == 'true')
id: sf-settings
working-directory: '${{ github.workspace }}'
run: |
RELEASE_TAG="v${{ steps.next-semver.outputs.NEXT_SEMVER }}"
echo "GITHUB_REPOSITORY is: ${GITHUB_REPOSITORY}"
echo "Release tag is: ${RELEASE_TAG}"
if [[ $RELEASE_TAG == *..* ]]; then
# Reject any release tags with two ".." in a row
echo "::error ::Invalid RELEASE_TAG value - aborting."
exit 1
fi
if [ -z "${RELEASE_TAG}" ]; then
echo "::error ::RELEASE_TAG variable is empty. Aborting."
exit 1
fi
SF_FILE_PATH=""
if [ ! -z "${SF_BASE_PATH}" ]; then
SF_FILE_PATH="${SF_BASE_PATH}/"
fi
SF_FILE_PATH="${SF_FILE_PATH}${RELEASE_TAG}"
echo "SF_FILE_PATH=${SF_FILE_PATH}"
echo "SF_FILE_PATH=${SF_FILE_PATH}" >> $GITHUB_OUTPUT
# Prepare release_assets directory
mkdir -p "release_assets/${SF_FILE_PATH}"
- name: 'Copy All Release Assets To SF Upload Dir'
if: success() && (steps.config.outputs.AUTO_RELEASE == 'true')
id: sf-stage-upload-dir
env:
SF_FILE_PATH: ${{ steps.sf-settings.outputs.SF_FILE_PATH }}
working-directory: 'dl/artifacts'
run: |
# Copy all release assets to upload dir
UPLOAD_DIR="${{ github.workspace }}/release_assets/${SF_FILE_PATH}"
for artifact in */*.zip ; do
echo "Found artifact: ${artifact}"
cp "${artifact}" "${UPLOAD_DIR}/$(basename -- "${artifact}")"
done
cp "BUILD_INFO.md" "${UPLOAD_DIR}/BUILD_INFO.md"
# Output list of staged files
cd "${UPLOAD_DIR}"
ls -al
echo "Done."
- name: Set up SourceForge known_hosts
if: success() && (steps.config.outputs.AUTO_RELEASE == 'true')
run: |
mkdir -p ~/.ssh/
# May need updating on occasion. See: https://sourceforge.net/p/forge/documentation/SSH%20Key%20Fingerprints/
# Output of: `ssh-keyscan frs.sourceforge.net >> ~/.ssh/known_hosts`
cat >> ~/.ssh/known_hosts << 'EOF'
frs.sourceforge.net ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOQD35Ujalhh+JJkPvMckDlhu4dS7WH6NsOJ15iGCJLC
frs.sourceforge.net ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA2uifHZbNexw6cXbyg1JnzDitL5VhYs0E65Hk/tLAPmcmm5GuiGeUoI/B0eUSNFsbqzwgwrttjnzKMKiGLN5CWVmlN1IXGGAfLYsQwK6wAu7kYFzkqP4jcwc5Jr9UPRpJdYIK733tSEmzab4qc5Oq8izKQKIaxXNe7FgmL15HjSpatFt9w/ot/CHS78FUAr3j3RwekHCm/jhPeqhlMAgC+jUgNJbFt3DlhDaRMa0NYamVzmX8D47rtmBbEDU3ld6AezWBPUR5Lh7ODOwlfVI58NAf/aYNlmvl2TZiauBCTa7OPYSyXJnIPbQXg6YQlDknNCr0K769EjeIlAfY87Z4tw==
frs.sourceforge.net ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBCwsY6sZT4MTTkHfpRzYjxG7mnXrGL74RCT2cO/NFvRrZVNB5XNwKNn7G5fHbYLdJ6UzpURDRae1eMg92JG0+yo=
EOF
- name: Set up SSH Agent
if: success() && (steps.config.outputs.AUTO_RELEASE == 'true')
env:
SF_KEY: ${{ secrets.SF_SSH_KEY }}
run: |
eval "$(ssh-agent -s)"
set +x
# Add the private key to SSH agent
mkdir -p ~/.ssh/
echo ${SF_KEY} | base64 --decode > ~/.ssh/id_ed25519
chmod 600 ~/.ssh/id_ed25519
ssh-add ~/.ssh/id_ed25519
# Create a public key file
ssh-keygen -y -f ~/.ssh/id_ed25519 > ~/.ssh/id_ed25519.pub
- name: 'Mirror Release Assets to SourceForge'
if: success() && (steps.config.outputs.AUTO_RELEASE == 'true')
id: sf-upload
env:
SF_FRS_USERNAME: ${{ secrets.SF_FRS_USERNAME }}
SF_FILE_PATH: ${{ steps.sf-settings.outputs.SF_FILE_PATH }}
working-directory: '${{ github.workspace }}/release_assets'
run: |
if [ -z "${SF_FILE_PATH}" ]; then
echo "::error ::SF_FILE_PATH variable is empty. Aborting this step."
exit 1
fi
# Upload all release assets
rsync -e ssh -rvh --relative "${SF_FILE_PATH}/" "${SF_FRS_USERNAME}@frs.sourceforge.net:/home/frs/project/${SF_PROJECT_NAME}/" --delete
echo "Done."
- name: Set Default SourceForge Downloads
if: success() && (steps.config.outputs.AUTO_RELEASE == 'true')
env:
SF_RELEASES_API_KEY: ${{ secrets.SF_RELEASES_API_KEY }}
SF_FILE_PATH: ${{ steps.sf-settings.outputs.SF_FILE_PATH }}
working-directory: '${{ github.workspace }}/release_assets'
run: |
echo "The provided tagged release is, in fact, the latest release. Updating default downloads:"
if [ -z "${SF_RELEASES_API_KEY}" ]; then
echo "::error ::SF_RELEASES_API_KEY secret was not set. Aborting this step."
exit 1
fi
# User-defined function
set_sf_default_if_present(){
local filename="$1"
local sf_default_string="$2"
if [ -f "${SF_FILE_PATH}/$filename" ]; then
echo "Setting: \"${SF_FILE_PATH}/${filename}\" as: ${sf_default_string}"
curl -H "Accept: application/json" -X PUT -d "${sf_default_string}" -d "api_key=${SF_RELEASES_API_KEY}" -s "https://sourceforge.net/projects/${SF_PROJECT_NAME}/files/${SF_FILE_PATH}/${filename}"
else
echo "::warning ::Did not find file: ${filename}"
fi
}
# Set default platform downloads for SourceForge
set_sf_default_if_present "applocalconfig-vc2019-x86.zip" "default=windows"
echo "Done."