Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 77 additions & 37 deletions .github/workflows/build_release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ permissions:
contents: write
actions: write

concurrency:
group: build-release-${{ github.event_name }}
cancel-in-progress: true

jobs:
upload_src:
runs-on: ubuntu-latest
Expand All @@ -21,15 +25,15 @@ jobs:
with:
egress-policy: audit

- name: Checkout Source
- 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:
Expand All @@ -39,36 +43,15 @@ 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"

- name: Trigger notes updater workflow (only for weekly)
if: startsWith(steps.get_tag.outputs.build_tag, 'weekly-')
uses: actions/github-script@v7
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
- name: Upload source
id: upload_source
shell: bash -l {0}
env:
Expand Down Expand Up @@ -118,7 +101,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 }}
Expand All @@ -128,7 +111,7 @@ jobs:
echo 'RATTLER_CACHE_DIR=D:\rattler' >> "$GITHUB_ENV"
fi

- name: Checkout Source
- name: Checkout source
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
ref: ${{ github.sha }}
Expand All @@ -142,15 +125,14 @@ 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 }}
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 }}
KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}
P12_PASSWORD: ${{ secrets.P12_PASSWORD }}
run: |
if [ -z "$BUILD_CERTIFICATE_BASE64" ]; then
Expand Down Expand Up @@ -186,23 +168,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.x'
dotnet-quality: 'preview'

- name: Install sign tool
if: runner.os == 'Windows'
run: |
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 }}
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: |
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:
Expand Down
4 changes: 2 additions & 2 deletions package/rattler-build/osx/create_bundle.sh
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,9 @@ if [ -d "${conda_env}/PlugIns" ]; then
mv ${conda_env}/PlugIns ${conda_env}/..
fi

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
# Ad-hoc sign for local builds (required for QuickLook extensions to register)
if [ -d "FreeCAD.app/Contents/PlugIns" ]; then
Expand Down
84 changes: 84 additions & 0 deletions package/rattler-build/windows/create_bundle.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -67,6 +70,63 @@ 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}"

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 [[ "${WINDOWS_SIGN_RELEASE:-0}" == "1" ]] && \
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. But,
# we better check the return value to be sure things are working...
rc=$?
echo "'sign code artifact-signing ...' exit code: $rc"
done

# Manually check the important one!
signtool verify -pa "$SIGN_DIR/bin/FreeCAD.exe"

echo "Signing completed."
else
echo "No Azure Artifact Signing available -- 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
Expand All @@ -88,6 +148,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 http://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..."
Expand All @@ -96,8 +178,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