name: Build Release on: release: types: [created] schedule: - cron: "0 0 * * 3" workflow_dispatch: permissions: contents: write actions: write concurrency: group: build-release-${{ github.event_name }} cancel-in-progress: true jobs: upload_src: runs-on: ubuntu-latest outputs: build_tag: ${{ steps.get_tag.outputs.build_tag }} steps: - name: Harden the runner (Audit all outbound calls) uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0 with: egress-policy: audit - 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 id: get_tag shell: bash -l {0} env: GH_TOKEN: ${{ github.token }} run: | if [ "${{ github.event_name }}" = "release" ]; then 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 || \ 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" - name: Trigger notes updater workflow (only for weekly) if: startsWith(steps.get_tag.outputs.build_tag, 'weekly-') uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 env: WEEKLY_TAG: ${{ steps.get_tag.outputs.build_tag }} with: script: | const owner = context.repo.owner; const repo = context.repo.repo; // Reusable/dispatchable updater workflow file in .github/workflows/ const workflow_id = 'weekly-compare-link.yml'; // Use the default branch so the workflow file is available const ref = (context.payload?.repository?.default_branch) || 'main'; const current_tag = process.env.WEEKLY_TAG || ''; await github.rest.actions.createWorkflowDispatch({ owner, repo, workflow_id, ref, inputs: { current_tag } }); core.info(`Dispatched ${workflow_id} on ${ref} with current_tag='${current_tag}'.`) - name: Upload Source id: upload_source shell: bash -l {0} env: GH_TOKEN: ${{ github.token }} run: | python3 package/scripts/write_version_info.py ../freecad_version.txt git config user.email '41898282+github-actions[bot]@users.noreply.github.com' git config user.name 'github-actions[bot]' git apply package/disable_git_info.patch git commit -a -m "Disable git info write to Version.h" git archive HEAD -o freecad_source_${BUILD_TAG}.tar git submodule foreach --recursive \ "git archive HEAD --prefix=\$path/ -o \$sha1.tar && \ tar -A -f \$toplevel/freecad_source_${BUILD_TAG}.tar \$sha1.tar && \ rm \$sha1.tar" gzip freecad_source_${BUILD_TAG}.tar sha256sum freecad_source_${BUILD_TAG}.tar.gz > freecad_source_${BUILD_TAG}.tar.gz-SHA256.txt gh release upload --clobber ${BUILD_TAG} "freecad_source_${BUILD_TAG}.tar.gz" "freecad_source_${BUILD_TAG}.tar.gz-SHA256.txt" build: needs: upload_src strategy: matrix: include: - { target: linux-64, os: ubuntu-22.04 } - { target: linux-arm64, os: ubuntu-22.04-arm } - { target: osx-64, os: macos-15-intel, deploy_target: "10.13" } - { target: osx-arm64, os: macos-latest, deploy_target: "11.0" } - { target: osx-arm64, os: macos-latest, deploy_target: "15.0" } - { target: win-64, os: windows-latest } fail-fast: false runs-on: ${{ matrix.os }} environment: weekly-build steps: - name: Harden the runner (Audit all outbound calls) uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0 with: egress-policy: audit # prevent running out of disk space on Ubuntu runners. - name: Maximize build space if: runner.os == 'Linux' uses: AdityaGarg8/remove-unwanted-software@90e01b21170618765a73370fcc3abbd1684a7793 # v5 with: verbose: 'true' remove-android: 'true' # (frees ~9 GB) remove-cached-tools: 'true' # (frees ~8.3 GB) - name: Set platform environment variables shell: bash -l {0} env: OPERATING_SYSTEM: ${{ runner.os }} run: | if [[ $OPERATING_SYSTEM == 'Windows' ]]; then echo 'PIXI_CACHE_DIR=D:\rattler' >> "$GITHUB_ENV" echo 'RATTLER_CACHE_DIR=D:\rattler' >> "$GITHUB_ENV" fi - name: Checkout source uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: ref: ${{ github.sha }} fetch-depth: 2 fetch-tags: true submodules: 'recursive' - uses: prefix-dev/setup-pixi@a0af7a228712d6121d37aba47adf55c1332c9c2e # v0.9.4 with: pixi-version: v0.59.0 cache: false - name: Install the Apple certificate and provisioning profile id: macos_get_cert if: runner.os == 'macOS' env: APP_SPECIFIC_PASSWORD: ${{ secrets.APP_SPECIFIC_PASSWORD }} APPLE_ID: ${{ secrets.APPLE_ID }} BUILD_CERTIFICATE_BASE64: ${{ secrets.BUILD_CERTIFICATE_BASE64 }} BUILD_PROVISION_PROFILE_BASE64: ${{ secrets.BUILD_PROVISION_PROFILE_BASE64 }} DEVELOPER_TEAM_ID: ${{ secrets.DEVELOPER_TEAM_ID }} P12_PASSWORD: ${{ secrets.P12_PASSWORD }} run: | if [ -z "$BUILD_CERTIFICATE_BASE64" ]; then echo "has_cert=false" >> $GITHUB_OUTPUT echo "No certificate avalable... skipping" && exit 0 else echo "has_cert=true" >> $GITHUB_OUTPUT fi # create variables CERTIFICATE_PATH=$RUNNER_TEMP/build_certificate.p12 PP_PATH=$RUNNER_TEMP/FreeCAD_bundle.provisionprofile KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db export KEYCHAIN_PASSWORD=$(openssl rand -base64 8) # import certificate and provisioning profile from secrets echo -n "$BUILD_CERTIFICATE_BASE64" | base64 --decode -o $CERTIFICATE_PATH echo -n "$BUILD_PROVISION_PROFILE_BASE64" | base64 --decode -o $PP_PATH # create temporary keychain security create-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH security set-keychain-settings -lut 21600 $KEYCHAIN_PATH security unlock-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH # import certificate to keychain security import $CERTIFICATE_PATH -P "$P12_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH security set-key-partition-list -S apple-tool:,apple: -k "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH security list-keychain -d user -s $KEYCHAIN_PATH # apply provisioning profile mkdir -p ~/Library/Provisioning\ Profiles cp $PP_PATH ~/Library/Provisioning\ Profiles xcrun notarytool store-credentials "FreeCAD" --keychain "$KEYCHAIN_PATH" --apple-id "${APPLE_ID}" --password "${APP_SPECIFIC_PASSWORD}" --team-id "${DEVELOPER_TEAM_ID}" - name: Setup .NET 10 SDK if: runner.os == 'Windows' uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5.2.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 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 }} TARGET_PLATFORM: ${{ matrix.target }} MAKE_INSTALLER: "true" UPLOAD_RELEASE: "true" BUILD_TAG: ${{ needs.upload_src.outputs.build_tag }} run: | 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 run -e package create_bundle ## Needed if running on a self-hosted runner: # - name: Clean up keychain and provisioning profile # if: ${{ always() }} # run: | # security delete-keychain $RUNNER_TEMP/app-signing.keychain-db # rm ~/Library/MobileDevice/Provisioning\ Profiles/build_pp.mobileprovision