diff --git a/.github/workflows/build_release.yml b/.github/workflows/build_release.yml index 86add33ba2..f6de5e921c 100644 --- a/.github/workflows/build_release.yml +++ b/.github/workflows/build_release.yml @@ -9,6 +9,10 @@ on: permissions: contents: write +concurrency: + group: build-release-${{ github.event_name }} + cancel-in-progress: true + jobs: upload_src: runs-on: ubuntu-latest @@ -20,15 +24,15 @@ jobs: with: egress-policy: audit - - name: Checkout Source - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + - name: Checkout source + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: ref: ${{ github.sha }} fetch-depth: 2 fetch-tags: true submodules: 'recursive' - - name: get tag and create release if weekly + - name: Get tag and create release if weekly id: get_tag shell: bash -l {0} env: @@ -38,7 +42,10 @@ jobs: export BUILD_TAG="${{ github.event.release.tag_name }}" else export BUILD_TAG=weekly-$(date "+%Y.%m.%d") - gh release create ${BUILD_TAG} --title "Development Build ${BUILD_TAG}" -F .github/workflows/weekly-build-notes.md --prerelease || true + gh release create ${BUILD_TAG} --title "Development Build ${BUILD_TAG}" \ + -F .github/workflows/weekly-build-notes.md \ + --prerelease || \ + gh release view ${BUILD_TAG} > /dev/null # fail if it doesn't exist fi echo "BUILD_TAG=${BUILD_TAG}" >> "$GITHUB_ENV" echo "build_tag=${BUILD_TAG}" >> "$GITHUB_OUTPUT" @@ -92,7 +99,7 @@ jobs: remove-android: 'true' # (frees ~9 GB) remove-cached-tools: 'true' # (frees ~8.3 GB) - - name: Set Platform Environment Variables + - name: Set platform environment variables shell: bash -l {0} env: OPERATING_SYSTEM: ${{ runner.os }} @@ -102,8 +109,8 @@ jobs: echo 'RATTLER_CACHE_DIR=D:\rattler' >> "$GITHUB_ENV" fi - - name: Checkout Source - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + - name: Checkout source + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: ref: ${{ github.sha }} fetch-depth: 2 @@ -116,7 +123,7 @@ jobs: cache: false - name: Install the Apple certificate and provisioning profile - id: get_cert + id: macos_get_cert if: runner.os == 'macOS' env: APP_SPECIFIC_PASSWORD: ${{ secrets.APP_SPECIFIC_PASSWORD }} @@ -124,7 +131,6 @@ jobs: BUILD_CERTIFICATE_BASE64: ${{ secrets.BUILD_CERTIFICATE_BASE64 }} BUILD_PROVISION_PROFILE_BASE64: ${{ secrets.BUILD_PROVISION_PROFILE_BASE64 }} DEVELOPER_TEAM_ID: ${{ secrets.DEVELOPER_TEAM_ID }} - KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }} P12_PASSWORD: ${{ secrets.P12_PASSWORD }} run: | if [ -z "$BUILD_CERTIFICATE_BASE64" ]; then @@ -160,20 +166,81 @@ jobs: xcrun notarytool store-credentials "FreeCAD" --keychain "$KEYCHAIN_PATH" --apple-id "${APPLE_ID}" --password "${APP_SPECIFIC_PASSWORD}" --team-id "${DEVELOPER_TEAM_ID}" - - name: Build and Release Packages + - name: Setup .NET 10 SDK + if: runner.os == 'Windows' + uses: actions/setup-dotnet@baa11fbfe1d6520db94683bd5c7a3818018e4309 # v5.1.0 + with: + dotnet-version: '10.0.103' + + - name: Install sign tool + if: runner.os == 'Windows' + run: | + # NOTE: The sign tool is ONLY available as a prerelease package, they never issue real releases. + dotnet tool install --global --prerelease sign --version 0.9.1-beta.26127.1 + echo "$env:USERPROFILE\.dotnet\tools" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append + dotnet --info + sign --version + + - name: Build packages + shell: bash + env: + GH_TOKEN: ${{ github.token }} + TARGET_PLATFORM: ${{ matrix.target }} + BUILD_TAG: ${{ needs.upload_src.outputs.build_tag }} + run: | + set -euo pipefail + if [[ "${{ runner.os }}" == "macOS" ]]; then + export MACOS_DEPLOYMENT_TARGET="${{ matrix.deploy_target }}" + fi + python3 package/scripts/write_version_info.py ../freecad_version.txt + cd package/rattler-build + pixi install + + - name: Azure login for Windows build code signing + id: azure_login + if: runner.os == 'Windows' + continue-on-error: true + uses: azure/login@a457da9ea143d694b1b9c7c869ebb04ebe844ef5 # v2.3.0 + with: + creds: '{"clientId":"${{ secrets.AZURE_CLIENT_ID }}","clientSecret":"${{ secrets.AZURE_CLIENT_SECRET }}","subscriptionId":"${{ secrets.AZURE_SUBSCRIPTION_ID }}","tenantId":"${{ secrets.AZURE_TENANT_ID }}"}' + + - name: Release packages with optional code-signing on Windows and macOS shell: bash env: GH_TOKEN: ${{ github.token }} - SIGNING_KEY_ID: ${{ secrets.SIGNING_KEY_ID }} - SIGN_RELEASE: ${{ steps.get_cert.outputs.has_cert }} TARGET_PLATFORM: ${{ matrix.target }} MAKE_INSTALLER: "true" UPLOAD_RELEASE: "true" BUILD_TAG: ${{ needs.upload_src.outputs.build_tag }} run: | - python3 package/scripts/write_version_info.py ../freecad_version.txt + set -euo pipefail + + export MACOS_SIGN_RELEASE=0 + export WINDOWS_SIGN_RELEASE=0 + + case "${RUNNER_OS}" in + Windows) + export WINDOWS_SIGN_RELEASE="${{ steps.azure_login.outcome }}" + export WINDOWS_AZURE_ENDPOINT="${{ vars.AZURE_TRUSTED_SIGNING_ENDPOINT }}" + export WINDOWS_AZURE_CERTIFICATE_PROFILE="${{ vars.AZURE_TRUSTED_SIGNING_CERTIFICATE_PROFILE }}" + export WINDOWS_AZURE_SIGNING_ACCOUNT="${{ vars.AZURE_TRUSTED_SIGNING_ACCOUNT }}" + + # For good measure, normalize the azure_login result to 1 or 0 + if [[ "${WINDOWS_SIGN_RELEASE:-}" == "success" ]]; then + export WINDOWS_SIGN_RELEASE=1 + else + export WINDOWS_SIGN_RELEASE=0 + fi + ;; + + macOS) + export MACOS_SIGN_RELEASE="${{ steps.macos_get_cert.outputs.has_cert }}" + export MACOS_SIGNING_KEY_ID="${{ secrets.SIGNING_KEY_ID }}" + export MACOS_DEPLOYMENT_TARGET="${{ matrix.deploy_target }}" + ;; + esac + cd package/rattler-build - pixi install pixi run -e package create_bundle ## Needed if running on a self-hosted runner: diff --git a/package/rattler-build/osx/create_bundle.sh b/package/rattler-build/osx/create_bundle.sh index 7872126c24..f184339df0 100644 --- a/package/rattler-build/osx/create_bundle.sh +++ b/package/rattler-build/osx/create_bundle.sh @@ -66,9 +66,9 @@ sed -i '1s/.*/\nLIST OF PACKAGES:/' FreeCAD.app/Contents/packages.txt cp -a ${conda_env}/Library ${conda_env}/.. rm -rf ${conda_env}/Library -if [[ "${SIGN_RELEASE}" == "true" ]]; then +if [[ "${MACOS_SIGN_RELEASE}" == "true" ]]; then # create the signed dmg - ../../scripts/macos_sign_and_notarize.zsh -p "FreeCAD" -k ${SIGNING_KEY_ID} -o "${version_name}.dmg" + ../../scripts/macos_sign_and_notarize.zsh -p "FreeCAD" -k ${MACOS_SIGNING_KEY_ID} -o "${version_name}.dmg" else # create the dmg dmgbuild -s dmg_settings.py "FreeCAD" "${version_name}.dmg" diff --git a/package/rattler-build/windows/create_bundle.sh b/package/rattler-build/windows/create_bundle.sh index ddb69e87bd..7a5508a6a8 100644 --- a/package/rattler-build/windows/create_bundle.sh +++ b/package/rattler-build/windows/create_bundle.sh @@ -44,6 +44,9 @@ find ${copy_dir} -name \*arm\*.exe -delete # arm binaries that fail to extract u mv ${copy_dir}/bin/Lib/ssl.py .ssl-orig.py cp ssl-patch.py ${copy_dir}/bin/Lib/ssl.py +# Turn off the echo before we start actually calling "echo" +set +x + echo '[Paths]' >> ${copy_dir}/bin/qt6.conf echo 'Prefix = ../lib/qt6' >> ${copy_dir}/bin/qt6.conf @@ -67,6 +70,64 @@ sed -i '1s/.*/\nLIST OF PACKAGES:/' ${copy_dir}/packages.txt mv ${copy_dir} ${version_name} + +# Sign the EXE, DLL, and PYD files (if we can access the Azure account for signing): +set -euo pipefail +SIGN_DIR="${version_name}" + + +if [[ "${WINDOWS_SIGN_RELEASE:-0}" == "1" ]]; then + TENANT="$(az account show --query tenantId -o tsv)" + export AZURE_IDENTITY_DISABLE_WORKLOAD_IDENTITY=true + export AZURE_IDENTITY_DISABLE_MANAGED_IDENTITY=true + unset AZURE_IDENTITY_LOGGING_ENABLED + + if az account get-access-token \ + --tenant "$TENANT" \ + --scope "https://codesigning.azure.net/.default" \ + >/dev/null 2>&1; + then + echo "Azure Artifact Signing access confirmed. Beginning signing process..." + + shopt -s nullglob + + FILES=( + "$SIGN_DIR"/*.exe + "$SIGN_DIR"/bin/*.exe + "$SIGN_DIR"/bin/*.dll + "$SIGN_DIR"/bin/*.pyd + ) + + count=0 + total=${#FILES[@]} + echo "Signing $total files" + for f in "${FILES[@]}"; do + ((count+=1)) + echo "Signing [$count/$total]: $f" + sign code artifact-signing \ + --artifact-signing-endpoint "${WINDOWS_AZURE_ENDPOINT}" \ + --artifact-signing-certificate-profile "${WINDOWS_AZURE_CERTIFICATE_PROFILE}" \ + --artifact-signing-account "${WINDOWS_AZURE_SIGNING_ACCOUNT}" \ + --timestamp-url https://timestamp.acs.microsoft.com \ + --timestamp-digest sha256 \ + "$f" >/dev/null 2>&1 + + # Output was redirected to /dev/null because Azure authentication is absurdly noisy, with constant misleading + # "failure" messages about Managed Identity authentication failing. We don't use, or want to use, that + # authentication, and the fact that it fails is not a problem as long as the real authentication succeeds. + done + + # Manually check the important one! + signtool verify -pa "$SIGN_DIR/bin/FreeCAD.exe" + + echo "Signing completed." + else + echo "Signing requested, but no Azure Artifact Signing available -- skipping signing." + fi +else + echo "Not logged into Azure -- skipping signing." +fi + 7z a -t7z -mx9 -mmt=${NUMBER_OF_PROCESSORS} ${version_name}.7z ${version_name} -bb # create hash sha256sum ${version_name}.7z > ${version_name}.7z-SHA256.txt @@ -88,6 +149,28 @@ if [ "${MAKE_INSTALLER}" == "true" ]; then -X'SetCompressor /FINAL lzma' \ ../../WindowsInstaller/FreeCAD-installer.nsi mv ../../WindowsInstaller/${version_name}-installer.exe . + echo "Created installer ${version_name}-installer.exe" + + # See if we can sign the installer exe as well: + if [[ "${WINDOWS_SIGN_RELEASE:-0}" == "1" ]] && \ + az account get-access-token \ + --tenant "$TENANT" \ + --scope "https://codesigning.azure.net/.default" \ + >/dev/null 2>&1; + then + echo "Signing the installer..." + sign code artifact-signing \ + --artifact-signing-endpoint "${WINDOWS_AZURE_ENDPOINT}" \ + --artifact-signing-certificate-profile "${WINDOWS_AZURE_CERTIFICATE_PROFILE}" \ + --artifact-signing-account "${WINDOWS_AZURE_SIGNING_ACCOUNT}" \ + --timestamp-url https://timestamp.acs.microsoft.com \ + --timestamp-digest sha256 \ + ${version_name}-installer.exe >/dev/null 2>&1 \ + || { echo "Signing the installer failed!"; exit 1; } + else + echo "No code signing available, leaving the installer unsigned" + fi + sha256sum ${version_name}-installer.exe > ${version_name}-installer.exe-SHA256.txt else echo "Error: Failed to get NsProcess plugin. Aborting installer creation..." @@ -96,8 +179,10 @@ if [ "${MAKE_INSTALLER}" == "true" ]; then fi if [ "${UPLOAD_RELEASE}" == "true" ]; then + echo "Uploading the release..." gh release upload --clobber ${BUILD_TAG} "${version_name}.7z" "${version_name}.7z-SHA256.txt" if [ "${MAKE_INSTALLER}" == "true" ]; then gh release upload --clobber ${BUILD_TAG} "${version_name}-installer.exe" "${version_name}-installer.exe-SHA256.txt" fi + echo "Done uploading" fi