cad-editor/.github/workflows/release.yml
2026-08-16 08:16:09 +03:00

454 lines
19 KiB
YAML

name: Release
on:
release:
types: [published]
workflow_dispatch:
jobs:
build-appimage:
runs-on: ubuntu-22.04
permissions:
contents: write
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
- name: Install dependencies
run: |
sudo apt-get update -q
sudo apt-get install -y \
libgl1-mesa-dev libx11-dev libxcursor-dev libxi-dev libxrandr-dev \
libxkbcommon-dev libwayland-dev libfontconfig1-dev libfreetype6-dev \
librsvg2-bin fuse libfuse2
- name: Build
env:
OCS_PATREON_TOKEN: ${{ secrets.OCS_PATREON_TOKEN }}
run: cargo build --release
- name: Prepare AppDir
run: |
mkdir -p AppDir/usr/bin
mkdir -p AppDir/usr/share/applications
mkdir -p AppDir/usr/share/icons/hicolor/256x256/apps
mkdir -p AppDir/usr/share/metainfo
cp target/release/OpenCADStudio AppDir/usr/bin/OpenCADStudio
cp packaging/OpenCADStudio.desktop AppDir/usr/share/applications/io.github.HakanSeven12.OpenCadStudio.desktop
cp packaging/io.github.HakanSeven12.OpenCadStudio.metainfo.xml AppDir/usr/share/metainfo/
rsvg-convert -w 256 -h 256 assets/logo.svg \
-o AppDir/usr/share/icons/hicolor/256x256/apps/io.github.HakanSeven12.OpenCadStudio.png
- name: Pack shared Linux payload
run: tar -C AppDir -czf linux-payload.tar.gz usr
- name: Upload shared Linux payload
uses: actions/upload-artifact@v4
with:
name: linux-payload
path: linux-payload.tar.gz
if-no-files-found: error
retention-days: 7
- name: Download linuxdeploy
run: |
wget -q https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage
chmod +x linuxdeploy-x86_64.AppImage
- name: Build AppImage
env:
APPIMAGE_EXTRACT_AND_RUN: 1
run: |
./linuxdeploy-x86_64.AppImage \
--appdir AppDir \
--desktop-file AppDir/usr/share/applications/io.github.HakanSeven12.OpenCadStudio.desktop \
--icon-file AppDir/usr/share/icons/hicolor/256x256/apps/io.github.HakanSeven12.OpenCadStudio.png \
--output appimage
mv Open*CAD*Studio*.AppImage OpenCADStudio.AppImage
- name: Upload AppImage to release
if: github.event_name == 'release'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
ASSET=OpenCADStudio-${{ github.ref_name }}-linux-x86_64.AppImage
mv OpenCADStudio.AppImage "$ASSET"
gh release upload ${{ github.ref_name }} "$ASSET" --clobber --repo ${{ github.repository }}
build-snap:
needs: build-appimage
runs-on: ubuntu-22.04
timeout-minutes: 75
concurrency:
group: snap
cancel-in-progress: false
permissions:
actions: read
contents: write
env:
SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAPCRAFT_STORE_CREDENTIALS }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Download shared Linux payload
uses: actions/download-artifact@v4
with:
name: linux-payload
path: .
- name: Set package version
env:
EVENT_NAME: ${{ github.event_name }}
REF_NAME: ${{ github.ref_name }}
run: |
if [ "$EVENT_NAME" = "release" ]; then
VERSION="${REF_NAME#v}"
if [ "v$VERSION" != "$REF_NAME" ]; then
echo "::error::Release tag must be vX.Y.Z"
exit 1
fi
else
VERSION="$(sed -n 's/^version = "\([0-9][0-9.]*\)"/\1/p' Cargo.toml | head -1)"
fi
if ! printf '%s' "$VERSION" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+$'; then
echo "::error::Invalid package version: $VERSION"
exit 1
fi
sed -i "s/^version: .*/version: '$VERSION'/" snap/snapcraft.yaml
- name: Build snap
id: snapcraft
uses: snapcore/action-build@v1
- name: Upload snap to release
if: github.event_name == 'release'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SNAP: ${{ steps.snapcraft.outputs.snap }}
run: gh release upload "${{ github.ref_name }}" "$SNAP" --clobber
- name: Upload snap as workflow artifact
if: github.event_name == 'workflow_dispatch'
uses: actions/upload-artifact@v4
with:
name: open-cad-studio-snap
path: ${{ steps.snapcraft.outputs.snap }}
if-no-files-found: error
- name: Publish snap to edge
if: github.event_name == 'release' && env.SNAPCRAFT_STORE_CREDENTIALS != ''
uses: snapcore/action-publish@v1
with:
snap: ${{ steps.snapcraft.outputs.snap }}
release: edge
build-windows:
runs-on: windows-latest
permissions:
contents: write
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
- name: Convert SVG icon to ICO
shell: pwsh
run: |
# ImageMagick is pre-installed on windows-latest. The
# auto-resize generates every size Explorer / Add-Remove
# Programs / the Start Menu shortcut may request from a
# single source SVG. Must run BEFORE the build so build.rs
# can embed the icon into the .exe (issue #107).
magick assets/logo.svg `
-define icon:auto-resize=16,24,32,48,64,128,256 `
packaging/windows/AppIcon.ico
- name: Convert DWG/DXF file icons to ICO
shell: pwsh
run: |
# DWG/DXF Explorer file icons, from the single-source mimetype SVGs.
magick assets/mimetypes/image-vnd.dwg.svg `
-define icon:auto-resize=16,24,32,48,64,128,256 `
packaging/windows/dwg.ico
magick assets/mimetypes/image-vnd.dxf.svg `
-define icon:auto-resize=16,24,32,48,64,128,256 `
packaging/windows/dxf.ico
- name: Build
env:
OCS_PATREON_TOKEN: ${{ secrets.OCS_PATREON_TOKEN }}
run: |
cargo build --release
# The MSI bundles the DWG thumbnail provider DLL. That cdylib is a
# workspace member, not an app dependency, so a plain `cargo build`
# never produces it — build the crate as an explicit target.
cargo build --release -p dwg-thumbnailer-win
- name: Sign executable (Azure Trusted Signing)
# Sign the bare .exe *before* the MSI is built so the copy packed
# into the installer carries the signature too. AZURE_CLIENT_SECRET
# authenticates the App Registration; the remaining AZURE_* secrets
# select the Trusted Signing account / certificate profile.
uses: azure/artifact-signing-action@v2
with:
azure-tenant-id: ${{ secrets.AZURE_TENANT_ID }}
azure-client-id: ${{ secrets.AZURE_CLIENT_ID }}
azure-client-secret: ${{ secrets.AZURE_CLIENT_SECRET }}
endpoint: ${{ secrets.AZURE_ENDPOINT }}
signing-account-name: ${{ secrets.AZURE_TRUSTED_SIGNING_ACCOUNT_NAME }}
certificate-profile-name: ${{ secrets.AZURE_CERTIFICATE_PROFILE_NAME }}
files: ${{ github.workspace }}\target\release\OpenCADStudio.exe
file-digest: SHA256
timestamp-rfc3161: http://timestamp.acs.microsoft.com
timestamp-digest: SHA256
- name: Build MSI installer
shell: pwsh
run: |
$version = "${{ github.ref_name }}" -replace '^v', ''
# workflow_dispatch runs use the branch name as ref_name, which
# is not a valid MSI ProductVersion — fall back to 0.0.0 so test
# builds still produce an installer.
if ($version -notmatch '^\d+(\.\d+){0,3}$') { $version = "0.0.0" }
# WiX Toolset 3.x is pre-installed on windows-latest; $env:WIX
# points to the install root. candle compiles .wxs → .wixobj
# and light links it into the final .msi.
$candle = Join-Path $env:WIX "bin\candle.exe"
$light = Join-Path $env:WIX "bin\light.exe"
# Resolve to absolute paths and pass each `-d<name>=<value>`
# as one quoted argument. PowerShell's native-command argument
# passing strips unquoted `-d…=…` strings on the `=` sign,
# which would otherwise hand candle an empty Source variable
# and `target\release\OpenCADStudio.exe` as a stray input
# filename.
$exePath = (Resolve-Path target\release\OpenCADStudio.exe).Path
$iconPath = (Resolve-Path packaging\windows\AppIcon.ico).Path
# GPL-3 license RTF shown on the installer's EULA page.
$licensePath = (Resolve-Path packaging\windows\License.rtf).Path
# main.wxs holds the package/feature; ui.wxs holds the installer UI
# and finish-screen launch checkbox. Both compile together.
$mainWxs = (Resolve-Path packaging\windows\main.wxs).Path
$uiWxs = (Resolve-Path packaging\windows\ui.wxs).Path
# candle writes one .wixobj per source into this directory (the
# trailing slash tells it -out is a folder, not a single file).
$objDir = Join-Path $PWD "packaging\windows\"
$msiPath = Join-Path $PWD "OpenCADStudio.msi"
& $candle -arch x64 `
"-dVersion=$version" `
"-dSource=$exePath" `
"-dIcon=$iconPath" `
"-dLicense=$licensePath" `
$mainWxs $uiWxs `
-out $objDir
if ($LASTEXITCODE -ne 0) { throw "candle failed" }
$mainObj = Join-Path $objDir "main.wixobj"
$uiObj = Join-Path $objDir "ui.wixobj"
& $light $mainObj $uiObj -ext WixUIExtension -out $msiPath
if ($LASTEXITCODE -ne 0) { throw "light failed" }
- name: Sign MSI installer (Azure Trusted Signing)
# The installer itself must be signed separately — light produces a
# fresh, unsigned .msi even though the .exe inside is already signed.
uses: azure/artifact-signing-action@v2
with:
azure-tenant-id: ${{ secrets.AZURE_TENANT_ID }}
azure-client-id: ${{ secrets.AZURE_CLIENT_ID }}
azure-client-secret: ${{ secrets.AZURE_CLIENT_SECRET }}
endpoint: ${{ secrets.AZURE_ENDPOINT }}
signing-account-name: ${{ secrets.AZURE_TRUSTED_SIGNING_ACCOUNT_NAME }}
certificate-profile-name: ${{ secrets.AZURE_CERTIFICATE_PROFILE_NAME }}
files: ${{ github.workspace }}\OpenCADStudio.msi
file-digest: SHA256
timestamp-rfc3161: http://timestamp.acs.microsoft.com
timestamp-digest: SHA256
- name: Upload artifacts to release
if: github.event_name == 'release'
shell: pwsh
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
$tag = "${{ github.ref_name }}"
# Ship both the bare .exe (portable, no install) and the
# .msi (installer with Start Menu shortcut + .dwg / .dxf
# file association). The `-portable` / `-installer` suffix
# makes the distinction obvious on the release page.
$exe = "OpenCADStudio-$tag-windows-x86_64-portable.exe"
$msi = "OpenCADStudio-$tag-windows-x86_64-installer.msi"
Move-Item target\release\OpenCADStudio.exe $exe
Move-Item OpenCADStudio.msi $msi
gh release upload $tag `
$exe $msi `
--clobber --repo ${{ github.repository }}
build-macos:
runs-on: macos-14 # Apple Silicon (arm64) only
permissions:
contents: write
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
with:
targets: aarch64-apple-darwin
- name: Install librsvg (for rsvg-convert)
env:
HOMEBREW_NO_AUTO_UPDATE: "1"
run: brew install librsvg
- name: Build
env:
OCS_PATREON_TOKEN: ${{ secrets.OCS_PATREON_TOKEN }}
run: |
cargo build --release --target aarch64-apple-darwin
# The QuickLook extension links libdwg_thumbnailer.a. The staticlib
# crate-type is only emitted when the crate is built as a target (as a
# plain dependency cargo produces just the rlib), so build it here.
cargo build --release --target aarch64-apple-darwin -p dwg-thumbnailer
- name: Build .icns from SVG
run: |
mkdir -p OpenCADStudio.iconset
# Render all the sizes Apple expects in an .icns.
for SIZE in 16 32 64 128 256 512 1024; do
rsvg-convert -w $SIZE -h $SIZE assets/logo.svg -o OpenCADStudio.iconset/icon_${SIZE}x${SIZE}.png
done
# @2x retina variants (half-size base name)
for BASE in 16 32 128 256 512; do
DOUBLE=$((BASE * 2))
cp OpenCADStudio.iconset/icon_${DOUBLE}x${DOUBLE}.png OpenCADStudio.iconset/icon_${BASE}x${BASE}@2x.png
done
iconutil -c icns OpenCADStudio.iconset -o AppIcon.icns
- name: Build DWG/DXF document .icns from the same SVG sources
run: |
# Finder document icons (Info.plist CFBundleTypeIconFile = DWG/DXF),
# generated from the single-source mimetype SVGs — never hand-drawn.
for T in dwg dxf; do
mkdir -p "$T.iconset"
for SIZE in 16 32 64 128 256 512 1024; do
rsvg-convert -w $SIZE -h $SIZE "assets/mimetypes/image-vnd.$T.svg" \
-o "$T.iconset/icon_${SIZE}x${SIZE}.png"
done
for BASE in 16 32 128 256 512; do
cp "$T.iconset/icon_$((BASE*2))x$((BASE*2)).png" \
"$T.iconset/icon_${BASE}x${BASE}@2x.png"
done
iconutil -c icns "$T.iconset" -o "$(echo "$T" | tr a-z A-Z).icns"
done
- name: Build DWG QuickLook thumbnail extension (.appex)
run: |
# A QuickLook thumbnail App Extension, built without an Xcode project:
# swiftc compiles the provider, links the Rust core static lib (its C
# ABI) + the system frameworks, and we hand-assemble the .appex bundle.
# `cargo build` already produced libdwg_thumbnailer.a (workspace member,
# crate-type staticlib). The whole app is ad-hoc signed below, which
# deep-signs the embedded extension too.
set -e
VERSION="${{ github.ref_name }}"; VERSION="${VERSION#v}"; [ -z "$VERSION" ] && VERSION="0.0.0"
EXT=DWGThumbnail.appex
rm -rf "$EXT"; mkdir -p "$EXT/Contents/MacOS"
swiftc \
-sdk "$(xcrun --sdk macosx --show-sdk-path)" \
-target arm64-apple-macos11 \
-O -parse-as-library -application-extension \
-module-name DWGThumbnail \
-import-objc-header crates/dwg-thumbnailer/macos/dwg_thumbnailer.h \
crates/dwg-thumbnailer/macos/ThumbnailProvider.swift \
-L target/aarch64-apple-darwin/release -ldwg_thumbnailer \
-framework QuickLookThumbnailing -framework CoreGraphics \
-framework ImageIO -framework Foundation -framework Security \
-framework SystemConfiguration -liconv \
-Xlinker -e -Xlinker _NSExtensionMain \
-o "$EXT/Contents/MacOS/DWGThumbnail"
sed "s/__VERSION__/$VERSION/g" crates/dwg-thumbnailer/macos/Info.plist > "$EXT/Contents/Info.plist"
- name: Assemble .app bundle
run: |
APP=OpenCADStudio.app
rm -rf "$APP"
mkdir -p "$APP/Contents/MacOS" "$APP/Contents/Resources" "$APP/Contents/PlugIns"
cp target/aarch64-apple-darwin/release/OpenCADStudio "$APP/Contents/MacOS/OpenCADStudio"
chmod +x "$APP/Contents/MacOS/OpenCADStudio"
cp AppIcon.icns "$APP/Contents/Resources/AppIcon.icns"
cp DWG.icns DXF.icns "$APP/Contents/Resources/"
# QuickLook thumbnail extension.
cp -R DWGThumbnail.appex "$APP/Contents/PlugIns/"
# Substitute version into Info.plist.
VERSION="${{ github.ref_name }}"
VERSION="${VERSION#v}"
[ -z "$VERSION" ] && VERSION="0.0.0"
sed "s/__VERSION__/$VERSION/g" packaging/Info.plist > "$APP/Contents/Info.plist"
- name: Ad-hoc code-sign the bundle
run: |
# No paid Apple Developer ID is available, so the app cannot be
# notarised. An *ad-hoc* signature (identity "-") at least gives the
# bundle a valid self-signature: this avoids the harshest Gatekeeper
# verdict ("app is damaged, Move to Trash") on a quarantined download
# and is mandatory anyway for arm64 binaries to execute. Sign the
# whole bundle deeply, then verify.
codesign --force --deep --sign - --timestamp=none OpenCADStudio.app
codesign --verify --strict --verbose=2 OpenCADStudio.app
- name: Create .dmg
run: |
# hdiutil intermittently fails with "Resource busy" on the hosted
# runner (a stale mount or background indexer holding the image).
# Detach any leftover volume and retry a few times before giving up.
set +e
for attempt in 1 2 3 4 5; do
hdiutil detach "/Volumes/Open CAD Studio" >/dev/null 2>&1 || true
rm -f OpenCADStudio.dmg
hdiutil create \
-volname "Open CAD Studio" \
-srcfolder OpenCADStudio.app \
-ov -format UDZO \
OpenCADStudio.dmg && break
echo "hdiutil create failed (attempt $attempt) — retrying in 5s"
sleep 5
done
set -e
test -f OpenCADStudio.dmg
- name: Upload .dmg to release
if: github.event_name == 'release'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
ASSET=OpenCADStudio-${{ github.ref_name }}-macos-arm64.dmg
mv OpenCADStudio.dmg "$ASSET"
gh release upload ${{ github.ref_name }} "$ASSET" --clobber --repo ${{ github.repository }}
- name: Emit Homebrew cask sha256
if: github.event_name == 'release'
run: |
# Print the values needed to bump the Homebrew cask
# (packaging/homebrew/open-cad-studio.rb) for this release, so the
# tap can be updated with a copy-paste instead of computing the
# digest by hand.
VERSION="${{ github.ref_name }}"
VERSION="${VERSION#v}"
SHA=$(shasum -a 256 OpenCADStudio-${{ github.ref_name }}-macos-arm64.dmg | awk '{print $1}')
{
echo "## Homebrew cask bump"
echo "Update \`packaging/homebrew/open-cad-studio.rb\`:"
echo '```ruby'
echo " version \"$VERSION\""
echo " sha256 \"$SHA\""
echo '```'
} >> "$GITHUB_STEP_SUMMARY"