Release pipeline after PhotoCraft's: AppImage/deb/rpm/Flatpak/tar.gz for Linux x86_64 and aarch64, FreeBSD, a macOS dmg and CLI zip, Windows x64/x86/arm64 MSI and portable zip (plus the plain x64 exe), web zip, SHA256SUMS.txt; version 0.1.2
Adapted from storytold/photocraft's release.yml and packaging/ (Apache-2.0; ATTRIBUTION.md and NOTICE say so). Packages install the licence files, THIRD-PARTY-LICENSES.txt included. The app icon is drawn by `solvecraft-cli icon` (a blue block with a bore, rendered on a transparent background; .ico too), so no image is committed. Notes: a short header plus GitHub's generated What's Changed. A dry run (Run workflow, publish off) keeps the files as the release-assets artifact. packaging-lint.yml checks the workflows, scripts and metadata. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
90fde44c32
commit
7e0618d64b
29 files changed
+1593
-267
No files matched your search
+21
-69
@@ -1,73 +1,25 @@
|
||||
SolveCraft is an open-source parametric 3D CAD application in pure Rust: sketch on a plane,
|
||||
constrain and dimension the sketch, turn profiles into solids on a feature timeline, and change a
|
||||
parameter to watch the timeline rebuild. It is early, and we'd like your bug reports.
|
||||
SolveCraft is open-source parametric 3D CAD in Rust: sketch, constrain, extrude, and change a
|
||||
parameter to watch the timeline rebuild. [Documentation](https://bherbruck.github.io/solvecraft/)
|
||||
· [try it in the browser](https://bherbruck.github.io/solvecraft/app/) ·
|
||||
[contributing](https://github.com/bherbruck/solvecraft/blob/main/CONTRIBUTING.md) ·
|
||||
[issues](https://github.com/bherbruck/solvecraft/issues). AI agents can model with it over MCP:
|
||||
`solvecraft-cli mcp` ([how](https://bherbruck.github.io/solvecraft/guide/mcp.html)).
|
||||
|
||||
## Links
|
||||
**Which file?**
|
||||
- **Windows:** `SolveCraft-{version}-windows-x64.exe` runs as is. Or install the `.msi`, or unzip the
|
||||
`-portable.zip` (x64, x86 and arm64).
|
||||
- **macOS 11+** (Apple silicon and Intel): open the `.dmg` and drag SolveCraft onto Applications.
|
||||
The CLI is in `solvecraft-cli-{version}-macos-universal.zip`.
|
||||
- **Linux** (x86_64 and aarch64, glibc 2.35+): the `.AppImage` (`chmod +x`, then run it), the
|
||||
`.deb` or `.rpm`, the `.flatpak` (`flatpak install --user <file>`), or the `.tar.gz`.
|
||||
- **FreeBSD:** `tar -xzf solvecraft-{version}-freebsd-x86_64.tar.gz --strip-components 1 -C /usr/local`.
|
||||
- **Web:** `solvecraft-web-{version}.zip` is a static site to host yourself.
|
||||
- `SHA256SUMS.txt`: `sha256sum -c SHA256SUMS.txt --ignore-missing`.
|
||||
|
||||
- **Documentation:** https://bherbruck.github.io/solvecraft/
|
||||
- **Try it in the browser:** https://bherbruck.github.io/solvecraft/app/ (`?sample` opens a sample part)
|
||||
- **Contributing:** [CONTRIBUTING.md](https://github.com/bherbruck/solvecraft/blob/main/CONTRIBUTING.md)
|
||||
- **Bugs and ideas:** [issues](https://github.com/bherbruck/solvecraft/issues)
|
||||
|
||||
## What works
|
||||
|
||||
- Sketches with SolveCraft's own constraint solver: lines, rectangles, circles, arcs, polygons,
|
||||
slots, splines, text; constraints and parameter-driven dimensions; automatic profiles.
|
||||
- Solid features on a parametric timeline that rebuilds incrementally: extrude, revolve, sweep,
|
||||
loft, fillet and chamfer, shell, draft, holes and threads, patterns, mirror, combine, split,
|
||||
primitives. Faces and edges are referenced by persistent names, so upstream edits don't
|
||||
scramble downstream features.
|
||||
- Parameters with unit-aware expressions, and configurations.
|
||||
- Components, joints, motion studies, sheet metal with flat patterns, and a plastic-enclosure
|
||||
subset (bosses, lips, snap fits).
|
||||
- Files: `.solvecraft` designs; STEP (AP203/214/242) and IGES import and export; 3MF, STL and OBJ;
|
||||
DXF. Atomic saves, autosave and crash recovery.
|
||||
- 76 of 76 reference parts built in Fusion rebuild in SolveCraft with the same volume, area and
|
||||
topology.
|
||||
|
||||
## Built for AI agents
|
||||
|
||||
Everything the UI does is a command with JSON parameters, so an agent can do it too.
|
||||
`solvecraft-cli mcp` is a Model Context Protocol server: headless, or bridged to the running app
|
||||
(`solvecraft --control 7878`, then `solvecraft-cli mcp --connect 127.0.0.1:7878`) so you can
|
||||
watch the agent model. In Claude Code: `claude mcp add solvecraft -- solvecraft-cli mcp`. The
|
||||
demo at the top of the [README](https://github.com/bherbruck/solvecraft#readme) shows an agent
|
||||
building an enclosure through the UI and MCP.
|
||||
|
||||
## Downloads
|
||||
|
||||
| File | What |
|
||||
|---|---|
|
||||
| `SolveCraft-{tag}-windows-x86_64.exe` | Windows 10/11: the app, ready to run |
|
||||
| `solvecraft-cli-{tag}-windows-x86_64.exe` | Windows: the command-line tool and MCP server |
|
||||
| `SolveCraft-{tag}-macos-universal.dmg` | macOS 11 or newer, Apple silicon and Intel: open it and drag SolveCraft onto Applications |
|
||||
| `solvecraft-cli-{tag}-macos-universal` | macOS: the command-line tool and MCP server |
|
||||
| `SolveCraft-{tag}-x86_64.AppImage` | Linux x86_64 (glibc 2.35 or newer): the app as one file |
|
||||
| `solvecraft-{tag}-linux-x86_64` | Linux: the app as a plain binary |
|
||||
| `solvecraft-cli-{tag}-linux-x86_64` | Linux: the command-line tool and MCP server |
|
||||
| `solvecraft-{tag}-web.zip` | the web app, to host yourself (any static server; HTTPS or localhost for saving files) |
|
||||
| `SHA256SUMS` | checksums: `sha256sum -c SHA256SUMS` |
|
||||
|
||||
On Linux and macOS a downloaded program isn't executable yet: `chmod +x` it first
|
||||
(`chmod +x SolveCraft-{tag}-x86_64.AppImage && ./SolveCraft-{tag}-x86_64.AppImage`). The licences
|
||||
of SolveCraft and everything it is built from are inside each program: Help ▸ About ▸ Licences,
|
||||
or `solvecraft-cli licences`.
|
||||
|
||||
### The binaries are not signed
|
||||
|
||||
- **macOS:** the first time, right-click (or Control-click) `SolveCraft.app` in Applications and
|
||||
choose **Open**, then **Open** again in the dialog. If macOS says the app is damaged, run
|
||||
`xattr -dr com.apple.quarantine /Applications/SolveCraft.app`. For the CLI:
|
||||
`xattr -d com.apple.quarantine solvecraft-cli-{tag}-macos-universal`.
|
||||
- **Windows:** SmartScreen may say "Windows protected your PC". Click **More info**, then
|
||||
**Run anyway**.
|
||||
|
||||
## Known limitations
|
||||
|
||||
SolveCraft is early. Some fillet, chamfer and boolean configurations still fail (with an error,
|
||||
never a crash); surfaces, drawings, CAM, mesh tools and simulation are out of scope for now; the
|
||||
model is Z-up only, so STEP files from Y-up tools open lying on their backs. The full list is in
|
||||
The builds aren't signed yet. On macOS, right-click SolveCraft in Applications and choose
|
||||
**Open** the first time (or run `xattr -dr com.apple.quarantine /Applications/SolveCraft.app`).
|
||||
On Windows, SmartScreen may say "Windows protected your PC": **More info**, then **Run anyway**.
|
||||
Licences are built in (Help ▸ About ▸ Licences, `solvecraft-cli licences`); the packages also
|
||||
install `THIRD-PARTY-LICENSES.txt`. Known limitations:
|
||||
[ROADMAP.md](https://github.com/bherbruck/solvecraft/blob/main/ROADMAP.md#known-limitations).
|
||||
|
||||
SolveCraft is MIT OR Apache-2.0. It is a clean-room implementation and is not affiliated with
|
||||
Autodesk; Fusion is a trademark of Autodesk, Inc.
|
||||
@@ -1,38 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# Package the Linux release build: the app and the CLI as plain binaries, and an AppImage of the
|
||||
# app. Usage: .github/release/package-linux.sh <tag> (from the repository root, after
|
||||
# `cargo build --release -p solvecraft -p solvecraft-cli`). Writes out/.
|
||||
set -euo pipefail
|
||||
tag=${1:?usage: package-linux.sh <tag>}
|
||||
bin=${CARGO_TARGET_DIR:-target}/release
|
||||
rm -rf stage out && mkdir -p stage out
|
||||
cp "$bin/solvecraft" "out/solvecraft-$tag-linux-x86_64"
|
||||
cp "$bin/solvecraft-cli" "out/solvecraft-cli-$tag-linux-x86_64"
|
||||
|
||||
# AppImage. The icon is rendered by SolveCraft itself from the bracket example, so no image file
|
||||
# is committed.
|
||||
app=stage/SolveCraft.AppDir
|
||||
mkdir -p "$app/usr/bin"
|
||||
cp "$bin/solvecraft" "$bin/solvecraft-cli" "$app/usr/bin/"
|
||||
"$bin/solvecraft-cli" snapshot examples/bracket.json --out "$app/solvecraft.png" --width 256 --height 256 >/dev/null
|
||||
cat > "$app/solvecraft.desktop" <<'DESKTOP'
|
||||
[Desktop Entry]
|
||||
Type=Application
|
||||
Name=SolveCraft
|
||||
Comment=Parametric 3D CAD
|
||||
Exec=solvecraft %f
|
||||
Icon=solvecraft
|
||||
Categories=Graphics;Engineering;
|
||||
MimeType=model/step;
|
||||
DESKTOP
|
||||
cat > "$app/AppRun" <<'APPRUN'
|
||||
#!/bin/sh
|
||||
here="$(dirname "$(readlink -f "$0")")"
|
||||
exec "$here/usr/bin/solvecraft" "$@"
|
||||
APPRUN
|
||||
chmod +x "$app/AppRun"
|
||||
tool=stage/appimagetool
|
||||
curl -sSfL -o "$tool" https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-x86_64.AppImage
|
||||
chmod +x "$tool"
|
||||
APPIMAGE_EXTRACT_AND_RUN=1 ARCH=x86_64 "$tool" --no-appstream "$app" "out/SolveCraft-$tag-x86_64.AppImage"
|
||||
ls -l out
|
||||
@@ -1,44 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# Package the macOS release build: a universal (arm64 + x86_64) SolveCraft.app in a .dmg (with an
|
||||
# Applications link to drag it onto), and the universal CLI as a plain binary.
|
||||
# Usage: .github/release/package-macos.sh <tag> (from the repository root, after building both
|
||||
# aarch64-apple-darwin and x86_64-apple-darwin). Writes out/. Neither is signed.
|
||||
set -euo pipefail
|
||||
tag=${1:?usage: package-macos.sh <tag>}
|
||||
version=${tag#v}
|
||||
t=${CARGO_TARGET_DIR:-target}
|
||||
rm -rf stage out && mkdir -p stage/dmg out
|
||||
app="stage/dmg/SolveCraft.app"
|
||||
mkdir -p "$app/Contents/MacOS"
|
||||
lipo -create -output "$app/Contents/MacOS/solvecraft" "$t/aarch64-apple-darwin/release/solvecraft" "$t/x86_64-apple-darwin/release/solvecraft"
|
||||
lipo -create -output "out/solvecraft-cli-$tag-macos-universal" "$t/aarch64-apple-darwin/release/solvecraft-cli" "$t/x86_64-apple-darwin/release/solvecraft-cli"
|
||||
cat > "$app/Contents/Info.plist" <<PLIST
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleName</key><string>SolveCraft</string>
|
||||
<key>CFBundleDisplayName</key><string>SolveCraft</string>
|
||||
<key>CFBundleIdentifier</key><string>io.github.bherbruck.solvecraft</string>
|
||||
<key>CFBundleExecutable</key><string>solvecraft</string>
|
||||
<key>CFBundlePackageType</key><string>APPL</string>
|
||||
<key>CFBundleVersion</key><string>$version</string>
|
||||
<key>CFBundleShortVersionString</key><string>$version</string>
|
||||
<key>LSMinimumSystemVersion</key><string>11.0</string>
|
||||
<key>NSHighResolutionCapable</key><true/>
|
||||
</dict>
|
||||
</plist>
|
||||
PLIST
|
||||
lipo -info "$app/Contents/MacOS/solvecraft"
|
||||
ln -s /Applications stage/dmg/Applications
|
||||
hdiutil create -volname "SolveCraft $tag" -srcfolder stage/dmg -ov -format UDZO "out/SolveCraft-$tag-macos-universal.dmg"
|
||||
# Check the image: mount it and look at what a user will see.
|
||||
mnt=$(mktemp -d)
|
||||
hdiutil attach -readonly -nobrowse -mountpoint "$mnt" "out/SolveCraft-$tag-macos-universal.dmg"
|
||||
ls -la "$mnt" "$mnt/SolveCraft.app/Contents" "$mnt/SolveCraft.app/Contents/MacOS"
|
||||
test -L "$mnt/Applications" && test "$(readlink "$mnt/Applications")" = /Applications
|
||||
plutil -lint "$mnt/SolveCraft.app/Contents/Info.plist"
|
||||
lipo "$mnt/SolveCraft.app/Contents/MacOS/solvecraft" -verify_arch arm64 x86_64
|
||||
"$mnt/SolveCraft.app/Contents/MacOS/solvecraft" --version
|
||||
hdiutil detach "$mnt"
|
||||
ls -l out
|
||||
@@ -0,0 +1,53 @@
|
||||
# Quick static checks for the release pipeline (seconds, no Rust build): workflow syntax, the
|
||||
# packaging shell and PowerShell scripts, and the Linux desktop/AppStream/Flatpak metadata.
|
||||
# Adapted from PhotoCraft's (storytold/photocraft, Apache-2.0; see ATTRIBUTION.md).
|
||||
name: Packaging lint
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths: [".github/workflows/**", "packaging/**"]
|
||||
pull_request:
|
||||
paths: [".github/workflows/**", "packaging/**"]
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: packaging-lint-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
ACTIONLINT_VERSION: 1.7.12
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install tools
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y --no-install-recommends shellcheck desktop-file-utils appstream libxml2-utils python3-yaml
|
||||
curl -fsSL "https://github.com/rhysd/actionlint/releases/download/v${ACTIONLINT_VERSION}/actionlint_${ACTIONLINT_VERSION}_linux_amd64.tar.gz" \
|
||||
| tar -xz -C "$RUNNER_TEMP" actionlint
|
||||
- name: actionlint
|
||||
run: '"$RUNNER_TEMP/actionlint" -color'
|
||||
- name: shellcheck
|
||||
run: shellcheck packaging/*.sh packaging/*/*.sh packaging/linux/AppRun
|
||||
- name: PowerShell
|
||||
shell: pwsh
|
||||
run: |
|
||||
foreach ($f in Get-ChildItem packaging/windows/*.ps1) {
|
||||
$errors = $null
|
||||
[System.Management.Automation.Language.Parser]::ParseFile($f.FullName, [ref]$null, [ref]$errors) | Out-Null
|
||||
if ($errors) { $errors | ForEach-Object { Write-Output "::error file=$($f.Name)::$_" }; exit 1 }
|
||||
Write-Output "ok $($f.Name)"
|
||||
}
|
||||
- name: XML, desktop entry, AppStream, Flatpak manifest
|
||||
run: |
|
||||
xmllint --noout packaging/windows/solvecraft.wxs packaging/windows/solvecraft.en-us.wxl packaging/macos/*.plist* packaging/linux/*.xml*
|
||||
desktop-file-validate packaging/linux/ai.storyteller.solvecraft.desktop
|
||||
sed -e 's/@VERSION@/0.0.0/' -e 's/@DATE@/2026-01-01/' packaging/linux/ai.storyteller.solvecraft.metainfo.xml.in >"$RUNNER_TEMP/ai.storyteller.solvecraft.metainfo.xml"
|
||||
appstreamcli validate --no-net --explain "$RUNNER_TEMP/ai.storyteller.solvecraft.metainfo.xml"
|
||||
python3 -c "import yaml; m = yaml.safe_load(open('packaging/linux/flatpak/ai.storyteller.solvecraft.bundle.yml')); assert m['id'] == 'ai.storyteller.solvecraft' and m['command'] == 'solvecraft'"
|
||||
+288
-100
@@ -1,10 +1,18 @@
|
||||
# Release builds: push a tag `v*` (or run it by hand with a tag name) and this builds the desktop
|
||||
# app and the CLI for Linux, Windows and macOS plus the web app, and attaches them to a GitHub
|
||||
# release with SHA256SUMS (a pre-release when the tag has a suffix, such as v0.2.0-rc1). Every
|
||||
# download is a plain program (Windows .exe, Linux binary and AppImage, a macOS .dmg); the licences
|
||||
# are built into the programs (Help ▸ About ▸ Licences, `solvecraft-cli licences`). Only the web
|
||||
# app, being many files, is a zip. Notes: .github/release-notes.md, with {tag} replaced. A dry run:
|
||||
# Actions ▸ Release ▸ Run workflow with "publish" off keeps the assets as the release-assets artifact.
|
||||
# Release pipeline, after PhotoCraft's (storytold/photocraft, Apache-2.0; see ATTRIBUTION.md).
|
||||
#
|
||||
# Push a tag `v<version>` (matching [workspace.package] version in Cargo.toml) and this builds and
|
||||
# packages SolveCraft for every platform and publishes the GitHub release "SolveCraft v<version>"
|
||||
# with SHA256SUMS.txt, .github/release-notes.md and GitHub's generated "What's Changed". A
|
||||
# version with a suffix (0.2.0-rc.1) is a pre-release. Nothing is signed yet.
|
||||
#
|
||||
# Linux x86_64, aarch64 AppImage (+ .zsync), .deb, .rpm, .flatpak, .tar.gz packaging/linux
|
||||
# FreeBSD x86_64 .tar.gz (built in a FreeBSD VM) packaging/freebsd
|
||||
# macOS universal .dmg, and the CLI as a .zip packaging/macos
|
||||
# Windows x64, x86, arm64 .msi and a portable .zip; x64 also the plain .exe packaging/windows
|
||||
# Web a .zip of the static site packaging/web
|
||||
#
|
||||
# A dry run: Actions > Release > Run workflow (publish off) builds everything and keeps it as
|
||||
# the release-assets artifact instead of releasing.
|
||||
name: Release
|
||||
|
||||
on:
|
||||
@@ -12,144 +20,324 @@ on:
|
||||
tags: ["v*"]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: "Tag to release (must exist), e.g. v0.1.0; empty: the branch this runs on"
|
||||
required: false
|
||||
publish:
|
||||
description: "Create the GitHub release (off: a dry run that keeps everything as the release-assets artifact)"
|
||||
description: "Publish the release (off: a dry run; the files become the release-assets artifact)"
|
||||
type: boolean
|
||||
default: true
|
||||
default: false
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: release-${{ github.ref }}
|
||||
cancel-in-progress: false
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
TAG: ${{ inputs.tag || github.ref_name }}
|
||||
TRUNK_VERSION: v0.21.14
|
||||
CARGO_INCREMENTAL: 0
|
||||
|
||||
jobs:
|
||||
linux:
|
||||
runs-on: ubuntu-22.04 # an older glibc, so the binaries run on older distributions
|
||||
timeout-minutes: 60
|
||||
version:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
version: ${{ steps.v.outputs.version }}
|
||||
date: ${{ steps.v.outputs.date }}
|
||||
prerelease: ${{ steps.v.outputs.prerelease }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ env.TAG }}
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- run: cargo build --release --locked -p solvecraft -p solvecraft-cli
|
||||
- name: Package
|
||||
run: .github/release/package-linux.sh "$TAG"
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: linux
|
||||
path: out/*
|
||||
|
||||
windows:
|
||||
runs-on: windows-latest
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ env.TAG }}
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- run: cargo build --release --locked -p solvecraft -p solvecraft-cli
|
||||
- name: Package
|
||||
shell: pwsh
|
||||
- id: v
|
||||
name: Resolve version
|
||||
run: |
|
||||
New-Item -ItemType Directory -Force -Path out | Out-Null
|
||||
Copy-Item target/release/solvecraft.exe "out/SolveCraft-$env:TAG-windows-x86_64.exe"
|
||||
Copy-Item target/release/solvecraft-cli.exe "out/solvecraft-cli-$env:TAG-windows-x86_64.exe"
|
||||
. packaging/env.sh
|
||||
if [ "$GITHUB_REF_TYPE" = tag ] && [ "${GITHUB_REF_NAME#v}" != "$VERSION" ]; then
|
||||
echo "::error::tag $GITHUB_REF_NAME doesn't match the Cargo.toml version $VERSION"
|
||||
exit 1
|
||||
fi
|
||||
case "$VERSION" in *-*) pre=true ;; *) pre=false ;; esac
|
||||
{ echo "version=$VERSION"; echo "date=$(date -u +%Y-%m-%d)"; echo "prerelease=$pre"; } >>"$GITHUB_OUTPUT"
|
||||
echo "### SolveCraft v$VERSION (\`${GITHUB_SHA::9}\`)" >>"$GITHUB_STEP_SUMMARY"
|
||||
|
||||
# The app icon in every size, drawn by solvecraft-cli (no image files are committed), for the
|
||||
# Windows jobs: the ARM64 build is cross-compiled and can't run there to draw its own.
|
||||
icons:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
key: release-icons
|
||||
- run: cargo build --release --locked -p solvecraft-cli
|
||||
- run: |
|
||||
. packaging/env.sh
|
||||
make_icons target/release/solvecraft-cli icons
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: windows
|
||||
path: out/*
|
||||
name: icons
|
||||
path: icons
|
||||
if-no-files-found: error
|
||||
|
||||
linux:
|
||||
name: Linux ${{ matrix.arch }}
|
||||
needs: version
|
||||
# The oldest GitHub-hosted image, so the binaries only need glibc >= 2.35.
|
||||
runs-on: ${{ matrix.runner }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- { arch: x86_64, runner: ubuntu-22.04, deb: amd64 }
|
||||
- { arch: aarch64, runner: ubuntu-22.04-arm, deb: arm64 }
|
||||
env:
|
||||
SOLVECRAFT_VERSION: ${{ needs.version.outputs.version }}
|
||||
SOLVECRAFT_BUILD_DATE: ${{ needs.version.outputs.date }}
|
||||
NFPM_VERSION: 2.47.0
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install packaging tools
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y --no-install-recommends desktop-file-utils appstream zsync rpm
|
||||
curl -fsSL -o "$RUNNER_TEMP/nfpm.deb" \
|
||||
"https://github.com/goreleaser/nfpm/releases/download/v${NFPM_VERSION}/nfpm_${NFPM_VERSION}_${{ matrix.deb }}.deb"
|
||||
sudo dpkg -i "$RUNNER_TEMP/nfpm.deb"
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
key: release-linux-${{ matrix.arch }}
|
||||
- name: Build and package (AppImage, deb, rpm, tar.gz)
|
||||
run: packaging/linux/package.sh
|
||||
- name: Check the packages
|
||||
run: |
|
||||
dpkg-deb --info dist/release/*.deb
|
||||
dpkg-deb --contents dist/release/*.deb | grep -E 'usr/bin/solvecraft$|usr/bin/solvecraft-cli$|metainfo|applications|THIRD-PARTY-LICENSES.txt'
|
||||
rpm -qip dist/release/*.rpm
|
||||
rpm -qlp dist/release/*.rpm | grep -E '/usr/bin/solvecraft$|THIRD-PARTY-LICENSES.txt'
|
||||
tar -tzf dist/release/*.tar.gz | grep -E '/bin/solvecraft$|THIRD-PARTY-LICENSES.txt'
|
||||
ldd target/release/solvecraft
|
||||
for appimage in dist/release/*.AppImage; do
|
||||
chmod +x "$appimage"
|
||||
APPIMAGE_EXTRACT_AND_RUN=1 "$appimage" --version
|
||||
"$appimage" --appimage-updateinformation | grep -F 'gh-releases-zsync|'
|
||||
test -s "$appimage.zsync"
|
||||
done
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: release-linux-${{ matrix.arch }}
|
||||
path: dist/release/*
|
||||
if-no-files-found: error
|
||||
|
||||
flatpak:
|
||||
name: Flatpak ${{ matrix.arch }}
|
||||
needs: [version, linux]
|
||||
# Repackages the Linux job's tarball (the same binaries as the AppImage/deb/rpm); no Rust
|
||||
# build here. 24.04 for a newer flatpak-builder than 22.04 ships.
|
||||
runs-on: ${{ matrix.runner }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- { arch: x86_64, runner: ubuntu-24.04 }
|
||||
- { arch: aarch64, runner: ubuntu-24.04-arm }
|
||||
env:
|
||||
SOLVECRAFT_VERSION: ${{ needs.version.outputs.version }}
|
||||
SOLVECRAFT_BUILD_DATE: ${{ needs.version.outputs.date }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: release-linux-${{ matrix.arch }}
|
||||
path: dist/linux
|
||||
- name: Install flatpak-builder
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y --no-install-recommends flatpak flatpak-builder librsvg2-common
|
||||
# Ubuntu 24.04's AppArmor blocks the unprivileged user namespaces bubblewrap needs.
|
||||
sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0
|
||||
- name: Build the Flatpak bundle
|
||||
run: packaging/linux/flatpak-bundle.sh "dist/linux/solvecraft-$SOLVECRAFT_VERSION-linux-${{ matrix.arch }}.tar.gz"
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: release-flatpak-${{ matrix.arch }}
|
||||
path: dist/release/*.flatpak
|
||||
if-no-files-found: error
|
||||
|
||||
freebsd:
|
||||
name: FreeBSD x86_64
|
||||
needs: version
|
||||
# GitHub has no FreeBSD runners: build in a FreeBSD 14.3 VM.
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 180
|
||||
env:
|
||||
SOLVECRAFT_VERSION: ${{ needs.version.outputs.version }}
|
||||
SOLVECRAFT_BUILD_DATE: ${{ needs.version.outputs.date }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Build and package in a FreeBSD VM (tar.gz)
|
||||
# vmactions/freebsd-vm v1.5.9, pinned by commit (it runs our build).
|
||||
uses: vmactions/freebsd-vm@c46abacb49f09938ca4e1702d15d836285d694cc
|
||||
with:
|
||||
release: "14.3"
|
||||
usesh: true
|
||||
cpu: 4
|
||||
mem: 12288
|
||||
# Passed into the VM by name; never templated into the script below.
|
||||
envs: "SOLVECRAFT_VERSION SOLVECRAFT_BUILD_DATE"
|
||||
prepare: |
|
||||
pkg install -y bash git curl pkgconf libxkbcommon wayland libX11 libXcursor libXrandr libXi libxcb mesa-libs vulkan-loader
|
||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal --default-toolchain stable
|
||||
run: |
|
||||
set -e
|
||||
. "$HOME/.cargo/env"
|
||||
git config --global --add safe.directory "$PWD"
|
||||
# Build outside the checkout so only dist/ is copied back out of the VM. 4 jobs: more
|
||||
# runs the VM out of memory.
|
||||
export CARGO_TARGET_DIR=/var/tmp/solvecraft-target CARGO_BUILD_JOBS=4
|
||||
bash packaging/freebsd/package.sh
|
||||
tar -tzf dist/release/solvecraft-*-freebsd-x86_64.tar.gz \
|
||||
| grep -E '/bin/solvecraft$|/bin/solvecraft-cli$|/share/applications/ai\.storyteller\.solvecraft\.desktop$|/share/doc/solvecraft/THIRD-PARTY-LICENSES\.txt$'
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: release-freebsd-x86_64
|
||||
path: dist/release/*
|
||||
if-no-files-found: error
|
||||
|
||||
macos:
|
||||
# One universal build: arm64 natively, x86_64 cross-compiled on the same runner, joined by lipo.
|
||||
name: macOS universal
|
||||
needs: version
|
||||
runs-on: macos-14
|
||||
timeout-minutes: 90
|
||||
env:
|
||||
SOLVECRAFT_VERSION: ${{ needs.version.outputs.version }}
|
||||
SOLVECRAFT_BUILD_DATE: ${{ needs.version.outputs.date }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ env.TAG }}
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
targets: aarch64-apple-darwin, x86_64-apple-darwin
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- run: |
|
||||
cargo build --release --locked -p solvecraft -p solvecraft-cli --target aarch64-apple-darwin
|
||||
cargo build --release --locked -p solvecraft -p solvecraft-cli --target x86_64-apple-darwin
|
||||
- name: Package
|
||||
run: .github/release/package-macos.sh "$TAG"
|
||||
with:
|
||||
key: release-macos-universal
|
||||
- name: Build and package (dmg, CLI zip; the dmg is mounted and checked)
|
||||
run: packaging/macos/package.sh --arch universal
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: macos
|
||||
path: out/*
|
||||
name: release-macos
|
||||
path: dist/release/*
|
||||
if-no-files-found: error
|
||||
|
||||
web:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 60
|
||||
windows:
|
||||
name: Windows ${{ matrix.arch }}
|
||||
needs: [version, icons]
|
||||
runs-on: windows-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- { arch: x64, target: x86_64-pc-windows-msvc }
|
||||
- { arch: x86, target: i686-pc-windows-msvc }
|
||||
# Windows on ARM: cross-compiled on the x64 runner.
|
||||
- { arch: arm64, target: aarch64-pc-windows-msvc }
|
||||
env:
|
||||
SOLVECRAFT_VERSION: ${{ needs.version.outputs.version }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
ref: ${{ env.TAG }}
|
||||
name: icons
|
||||
path: icons
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
targets: ${{ matrix.target }}
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
key: release-windows-${{ matrix.arch }}
|
||||
- name: Install WiX
|
||||
shell: pwsh
|
||||
run: |
|
||||
dotnet tool install --global wix --version 5.0.2
|
||||
wix extension add -g WixToolset.UI.wixext/5.0.2 WixToolset.Util.wixext/5.0.2
|
||||
- name: Build and package (msi, portable zip)
|
||||
shell: pwsh
|
||||
env:
|
||||
SOLVECRAFT_ICONS: ${{ github.workspace }}/icons
|
||||
run: ./packaging/windows/package.ps1 -Arch ${{ matrix.arch }}
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: release-windows-${{ matrix.arch }}
|
||||
path: dist/release/*
|
||||
if-no-files-found: error
|
||||
|
||||
web:
|
||||
name: Web (wasm)
|
||||
needs: version
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
SOLVECRAFT_VERSION: ${{ needs.version.outputs.version }}
|
||||
TRUNK_VERSION: 0.21.14
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
targets: wasm32-unknown-unknown
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
key: release-web
|
||||
- name: Install trunk
|
||||
run: |
|
||||
mkdir -p "$HOME/.local/bin"
|
||||
curl -sSfL "https://github.com/trunk-rs/trunk/releases/download/${TRUNK_VERSION}/trunk-x86_64-unknown-linux-gnu.tar.gz" | tar -xz -C "$HOME/.local/bin"
|
||||
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
|
||||
- name: Build (relative public URL, so it can be served from any path)
|
||||
working-directory: apps/solvecraft-web
|
||||
run: trunk build --release
|
||||
- name: Package
|
||||
run: |
|
||||
name="solvecraft-$TAG-web"
|
||||
mkdir -p out && cp -r dist/web "$name"
|
||||
cp LICENSE-MIT LICENSE-APACHE NOTICE THIRD-PARTY-LICENSES.txt "$name/"
|
||||
zip -qr "out/$name.zip" "$name"
|
||||
curl -fsSL "https://github.com/trunk-rs/trunk/releases/download/v${TRUNK_VERSION}/trunk-x86_64-unknown-linux-gnu.tar.gz" \
|
||||
| tar -xz -C "$HOME/.cargo/bin" trunk
|
||||
- name: Build and zip
|
||||
run: packaging/web/package.sh
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: web
|
||||
path: out/*
|
||||
name: release-web
|
||||
path: dist/release/*
|
||||
if-no-files-found: error
|
||||
|
||||
release:
|
||||
needs: [linux, windows, macos, web]
|
||||
name: GitHub Release
|
||||
needs: [version, linux, flatpak, freebsd, macos, windows, web]
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
env:
|
||||
VERSION: ${{ needs.version.outputs.version }}
|
||||
PRERELEASE: ${{ needs.version.outputs.prerelease }}
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
GH_REPO: ${{ github.repository }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ env.TAG }}
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: assets
|
||||
pattern: release-*
|
||||
path: artifacts
|
||||
merge-multiple: true
|
||||
- name: Checksums
|
||||
working-directory: assets
|
||||
run: sha256sum * > SHA256SUMS && cat SHA256SUMS
|
||||
- name: Notes
|
||||
run: sed "s/{tag}/$TAG/g" .github/release-notes.md > notes.md
|
||||
- if: ${{ github.event_name == 'push' || inputs.publish }}
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
tag_name: ${{ env.TAG }}
|
||||
name: SolveCraft ${{ env.TAG }}
|
||||
body_path: notes.md
|
||||
generate_release_notes: true
|
||||
# A tag with a suffix (v0.2.0-rc1) is a pre-release; v1.2.3 is a normal release.
|
||||
prerelease: ${{ contains(env.TAG, '-') }}
|
||||
files: assets/*
|
||||
# A dry run (workflow_dispatch with publish off): the assets and notes as an artifact.
|
||||
- if: ${{ github.event_name == 'workflow_dispatch' && !inputs.publish }}
|
||||
run: cp notes.md assets/release-notes.md
|
||||
- if: ${{ github.event_name == 'workflow_dispatch' && !inputs.publish }}
|
||||
- name: Checksums and notes
|
||||
run: |
|
||||
(cd artifacts && sha256sum -- * | tee SHA256SUMS.txt)
|
||||
sed "s/{version}/$VERSION/g" .github/release-notes.md >notes.md
|
||||
- name: Publish the release
|
||||
if: github.event_name == 'push' || inputs.publish
|
||||
working-directory: artifacts
|
||||
run: |
|
||||
tag="v$VERSION"
|
||||
pre=()
|
||||
if [ "$PRERELEASE" = true ]; then pre=(--prerelease); else pre=(--latest); fi
|
||||
if gh release view "$tag" >/dev/null 2>&1; then
|
||||
echo "::error::Release $tag already exists. Bump the version before tagging."
|
||||
exit 1
|
||||
fi
|
||||
gh release create "$tag" --title "SolveCraft v$VERSION" --verify-tag --notes-file ../notes.md --generate-notes "${pre[@]}" -- *
|
||||
{
|
||||
echo "Published **SolveCraft v$VERSION** at \`${GITHUB_SHA::9}\`:"
|
||||
echo
|
||||
sed 's/^/ /' SHA256SUMS.txt
|
||||
} >>"$GITHUB_STEP_SUMMARY"
|
||||
- name: Keep the files (dry run)
|
||||
if: github.event_name == 'workflow_dispatch' && !inputs.publish
|
||||
run: cp notes.md artifacts/release-notes.md
|
||||
- if: github.event_name == 'workflow_dispatch' && !inputs.publish
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: release-assets
|
||||
path: assets/*
|
||||
path: artifacts/*
|
||||
@@ -40,6 +40,7 @@ Generated-in-code assets are original and have no file to list:
|
||||
| crates/io/src/threemf.rs (bounded zip part reading and writing) | GridCraft `crates/xlsx/src/package.rs` | MIT OR Apache-2.0 |
|
||||
| crates/ui-egui/src/titlebar.rs (title bar drag, caption buttons, resize zones, their tests) and the window setup in apps/solvecraft/src/main.rs | VectorCraft `crates/ui-egui/src/titlebar.rs`, `apps/vectorcraft/src/main.rs` | MIT OR Apache-2.0 |
|
||||
| LICENSE-MIT, LICENSE-APACHE (licence text) | CADCraft | — |
|
||||
| .github/workflows/release.yml, .github/workflows/packaging-lint.yml and packaging/ (release pipeline: Linux AppImage/deb/rpm/Flatpak/tar.gz, FreeBSD, macOS dmg, Windows MSI/portable zip, web zip; AppRun, nfpm, Flatpak, WiX and Info.plist templates) | PhotoCraft (https://github.com/storytold/photocraft) `.github/workflows/{release,packaging-lint}.yml`, `packaging/` | Apache-2.0 |
|
||||
|
||||
## Vendored third-party code
|
||||
|
||||
|
||||
Generated
+13
-13
@@ -3059,7 +3059,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "solvecraft"
|
||||
version = "0.1.1"
|
||||
version = "0.1.2"
|
||||
dependencies = [
|
||||
"eframe",
|
||||
"egui",
|
||||
@@ -3072,7 +3072,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "solvecraft-cli"
|
||||
version = "0.1.1"
|
||||
version = "0.1.2"
|
||||
dependencies = [
|
||||
"serde_json",
|
||||
"solvecraft-engine",
|
||||
@@ -3081,7 +3081,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "solvecraft-doc"
|
||||
version = "0.1.1"
|
||||
version = "0.1.2"
|
||||
dependencies = [
|
||||
"log",
|
||||
"serde",
|
||||
@@ -3095,7 +3095,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "solvecraft-engine"
|
||||
version = "0.1.1"
|
||||
version = "0.1.2"
|
||||
dependencies = [
|
||||
"log",
|
||||
"serde",
|
||||
@@ -3111,7 +3111,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "solvecraft-geom"
|
||||
version = "0.1.1"
|
||||
version = "0.1.2"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -3119,7 +3119,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "solvecraft-io"
|
||||
version = "0.1.1"
|
||||
version = "0.1.2"
|
||||
dependencies = [
|
||||
"quick-xml 0.38.4",
|
||||
"solvecraft-doc",
|
||||
@@ -3131,7 +3131,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "solvecraft-kernel"
|
||||
version = "0.1.1"
|
||||
version = "0.1.2"
|
||||
dependencies = [
|
||||
"log",
|
||||
"serde",
|
||||
@@ -3146,7 +3146,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "solvecraft-mcp"
|
||||
version = "0.1.1"
|
||||
version = "0.1.2"
|
||||
dependencies = [
|
||||
"serde_json",
|
||||
"solvecraft-engine",
|
||||
@@ -3154,7 +3154,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "solvecraft-render"
|
||||
version = "0.1.1"
|
||||
version = "0.1.2"
|
||||
dependencies = [
|
||||
"image",
|
||||
"serde",
|
||||
@@ -3163,7 +3163,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "solvecraft-sketch"
|
||||
version = "0.1.1"
|
||||
version = "0.1.2"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -3174,7 +3174,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "solvecraft-ui-egui"
|
||||
version = "0.1.1"
|
||||
version = "0.1.2"
|
||||
dependencies = [
|
||||
"egui",
|
||||
"egui-wgpu",
|
||||
@@ -3188,7 +3188,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "solvecraft-web"
|
||||
version = "0.1.1"
|
||||
version = "0.1.2"
|
||||
dependencies = [
|
||||
"eframe",
|
||||
"egui",
|
||||
@@ -4487,7 +4487,7 @@ checksum = "e450f9b2ed1dff33c94c12589a87338689467b9c4f5d8a5710bd09a847d2c8a7"
|
||||
|
||||
[[package]]
|
||||
name = "xtask"
|
||||
version = "0.1.1"
|
||||
version = "0.1.2"
|
||||
dependencies = [
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@ members = ["crates/*", "apps/*", "xtask"]
|
||||
exclude = ["vendor"]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.1.1"
|
||||
version = "0.1.2"
|
||||
edition = "2024"
|
||||
license = "MIT OR Apache-2.0"
|
||||
rust-version = "1.90"
|
||||
|
||||
@@ -16,6 +16,8 @@ vendor/truck-shapeops/LICENSE. Copyright (c) RICOS Co. Ltd.
|
||||
|
||||
Some code is adapted from sibling projects in the storytold family (CADCraft, GridCraft), which are
|
||||
licensed MIT OR Apache-2.0; ATTRIBUTION.md lists the files.
|
||||
The release packaging (.github/workflows/release.yml, packaging/) is adapted from PhotoCraft
|
||||
(https://github.com/storytold/photocraft), licensed under the Apache License 2.0.
|
||||
|
||||
None of the bundled assets come from third parties: every icon is drawn in code. The per-file
|
||||
list is in ATTRIBUTION.md.
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
//! solvecraft-cli step-corpus <case-dir>... import part.step and compare with measure.json
|
||||
//! solvecraft-cli mcp [--in design|script] [--connect HOST:PORT] MCP server on stdio (docs/mcp.md)
|
||||
//! solvecraft-cli licences SolveCraft's licence and the third-party licences
|
||||
//! solvecraft-cli icon --out F.png|F.ico [--size N] the app icon (packaging; drawn, not a file)
|
||||
//! ```
|
||||
#![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable)]
|
||||
#![forbid(unsafe_code)]
|
||||
@@ -37,6 +38,7 @@ const USAGE: &str = "usage:
|
||||
solvecraft-cli step-corpus <case-dir>... [--json]
|
||||
solvecraft-cli mcp [--in design.solvecraft|script.json] [--connect 127.0.0.1:PORT]
|
||||
solvecraft-cli licences
|
||||
solvecraft-cli icon --out icon.png [--size 256] | --out icon.ico
|
||||
";
|
||||
|
||||
fn main() -> ExitCode {
|
||||
@@ -57,6 +59,7 @@ fn main() -> ExitCode {
|
||||
Some("oracle") => oracle::run(&args[1..]),
|
||||
Some("step-corpus") => oracle::step_corpus(&args[1..]),
|
||||
Some("mcp") => cmd_mcp(&args[1..]),
|
||||
Some("icon") => cmd_icon(&args[1..]),
|
||||
Some("licences" | "licenses") => {
|
||||
// Written, not print!ed: a closed pipe (`| head`) is not a crash.
|
||||
use std::io::Write as _;
|
||||
@@ -204,6 +207,69 @@ fn cmd_snapshot(args: &[String]) -> Result<(), String> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// The app icon: a blue block with a bore, drawn by the CPU renderer on a transparent background
|
||||
/// (so packages need no committed image). `.ico` holds the Windows sizes as PNGs.
|
||||
fn cmd_icon(args: &[String]) -> Result<(), String> {
|
||||
let out = flag(args, "--out").ok_or("icon needs --out FILE.png or FILE.ico")?;
|
||||
let size: usize = flag(args, "--size").and_then(|x| x.parse().ok()).unwrap_or(256).clamp(8, 2048);
|
||||
let bytes = if out.to_ascii_lowercase().ends_with(".ico") {
|
||||
let pngs: Vec<(usize, Vec<u8>)> =
|
||||
[16, 20, 24, 32, 40, 48, 64, 128, 256].into_iter().map(|s| icon_png(s).map(|p| (s, p))).collect::<Result<_, _>>()?;
|
||||
ico(&pngs)?
|
||||
} else {
|
||||
icon_png(size)?
|
||||
};
|
||||
std::fs::write(out, &bytes).map_err(|e| format!("{out}: {e}"))?;
|
||||
println!("{}", pretty(&json!({"path": out, "bytes": bytes.len()})));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn icon_png(size: usize) -> Result<Vec<u8>, String> {
|
||||
use solvecraft_engine::render::{Rgb, Scene, SceneLine, SceneMesh, png, render_transparent};
|
||||
let mut s = Session::default();
|
||||
for (cmd, p) in [
|
||||
("solid.box", json!({"length": 20, "width": 20, "height": 20, "center": [0, 0, 0]})),
|
||||
("solid.cylinder", json!({"diameter": 9, "height": 30, "base": [0, 0, -15], "operation": "cut"})),
|
||||
] {
|
||||
s.execute(cmd, &p).map_err(|e| format!("icon: {e}"))?;
|
||||
}
|
||||
let mut cam = solvecraft_engine::view::home_camera(&s);
|
||||
cam.distance *= 0.95;
|
||||
let mut scene = Scene { radius: 60.0, ..Default::default() };
|
||||
let px = size as f64 / 256.0;
|
||||
for body in &s.world_state().bodies {
|
||||
let mesh = body.mesh();
|
||||
scene.meshes.push(SceneMesh { mesh: std::sync::Arc::clone(&mesh), color: Rgb(90, 160, 240) });
|
||||
for (i, e) in mesh.edges.iter().enumerate() {
|
||||
if !mesh.seams.get(i).copied().unwrap_or(false) {
|
||||
scene.lines.push(SceneLine { points: e.clone(), color: Rgb(24, 52, 96), width: (3.0 * px).max(0.8), on_top: false });
|
||||
}
|
||||
}
|
||||
}
|
||||
let c = render_transparent(&mut scene, &cam, size, size);
|
||||
png(c.w, c.h, c.rgba).ok_or_else(|| "icon: PNG encoding failed".to_string())
|
||||
}
|
||||
|
||||
/// A Windows icon file holding PNG images (Vista and later read these).
|
||||
fn ico(images: &[(usize, Vec<u8>)]) -> Result<Vec<u8>, String> {
|
||||
let n = u16::try_from(images.len()).map_err(|_| "too many icon sizes")?;
|
||||
let mut out = Vec::new();
|
||||
out.extend_from_slice(&[0, 0, 1, 0]);
|
||||
out.extend_from_slice(&n.to_le_bytes());
|
||||
let mut offset = 6 + 16 * images.len();
|
||||
for (size, data) in images {
|
||||
let dim = if *size >= 256 { 0 } else { *size as u8 };
|
||||
out.extend_from_slice(&[dim, dim, 0, 0, 1, 0, 32, 0]);
|
||||
out.extend_from_slice(&u32::try_from(data.len()).map_err(|_| "icon too large")?.to_le_bytes());
|
||||
out.extend_from_slice(&u32::try_from(offset).map_err(|_| "icon too large")?.to_le_bytes());
|
||||
offset += data.len();
|
||||
}
|
||||
for (_, data) in images {
|
||||
out.extend_from_slice(data);
|
||||
}
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
fn cmd_exec(args: &[String]) -> Result<(), String> {
|
||||
let id = args.first().ok_or_else(|| USAGE.to_string())?;
|
||||
let p: Value = match args.get(1) {
|
||||
|
||||
@@ -8,4 +8,4 @@ mod camera;
|
||||
mod raster;
|
||||
|
||||
pub use camera::{Camera, CameraAnim, Mat4, Quat, StandardView, ease_in_out};
|
||||
pub use raster::{Canvas, Rgb, Scene, SceneLine, SceneMesh, render, render_png};
|
||||
pub use raster::{Canvas, Rgb, Scene, SceneLine, SceneMesh, png, render, render_png, render_transparent};
|
||||
@@ -200,12 +200,38 @@ pub fn render(scene: &Scene, cam: &Camera, w: usize, h: usize) -> Canvas {
|
||||
/// Render to PNG bytes.
|
||||
pub fn render_png(scene: &Scene, cam: &Camera, w: usize, h: usize) -> Option<Vec<u8>> {
|
||||
let c = render(scene, cam, w, h);
|
||||
let img = image::RgbaImage::from_raw(c.w as u32, c.h as u32, c.rgba)?;
|
||||
png(c.w, c.h, c.rgba)
|
||||
}
|
||||
|
||||
/// RGBA pixels (`w × h`) to PNG bytes.
|
||||
pub fn png(w: usize, h: usize, rgba: Vec<u8>) -> Option<Vec<u8>> {
|
||||
let img = image::RgbaImage::from_raw(u32::try_from(w).ok()?, u32::try_from(h).ok()?, rgba)?;
|
||||
let mut buf = std::io::Cursor::new(Vec::new());
|
||||
img.write_to(&mut buf, image::ImageFormat::Png).ok()?;
|
||||
Some(buf.into_inner())
|
||||
}
|
||||
|
||||
/// Render on a transparent background: the scene drawn over black and over white, and the
|
||||
/// alpha recovered from the difference (exact for the anti-aliased edges).
|
||||
pub fn render_transparent(scene: &mut Scene, cam: &Camera, w: usize, h: usize) -> Canvas {
|
||||
let keep = scene.background;
|
||||
scene.background = Some((Rgb(0, 0, 0), Rgb(0, 0, 0)));
|
||||
let black = render(scene, cam, w, h);
|
||||
scene.background = Some((Rgb(255, 255, 255), Rgb(255, 255, 255)));
|
||||
let mut out = render(scene, cam, w, h);
|
||||
scene.background = keep;
|
||||
for (o, b) in out.rgba.as_chunks_mut::<4>().0.iter_mut().zip(black.rgba.as_chunks::<4>().0) {
|
||||
// white - black = 255 (1 - alpha) in every channel; average them.
|
||||
let d: u32 = (0..3).map(|k| u32::from(o[k].saturating_sub(b[k]))).sum();
|
||||
let a = 255.0 - d as f64 / 3.0;
|
||||
for k in 0..3 {
|
||||
o[k] = if a > 0.5 { (f64::from(b[k]) * 255.0 / a).round().clamp(0.0, 255.0) as u8 } else { 0 };
|
||||
}
|
||||
o[3] = a.round().clamp(0.0, 255.0) as u8;
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
# shellcheck shell=bash
|
||||
# Shared setup for the packaging scripts. Source it: `. "$(dirname "$0")/../env.sh"`.
|
||||
# Adapted from PhotoCraft's packaging (storytold/photocraft, Apache-2.0; see ATTRIBUTION.md).
|
||||
#
|
||||
# Exports:
|
||||
# ROOT workspace root
|
||||
# VERSION [workspace.package] version from Cargo.toml (override: SOLVECRAFT_VERSION)
|
||||
# DIST output directory for release artifacts (default: $ROOT/dist/release)
|
||||
# SOLVECRAFT_BUILD_DATE UTC build date, YYYY-MM-DD
|
||||
# CARGO_TARGET_DIR cargo's target dir (default: $ROOT/target)
|
||||
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
export ROOT
|
||||
|
||||
# The version lives in one place: `[workspace.package] version` in the root Cargo.toml.
|
||||
workspace_version() {
|
||||
awk '
|
||||
/^\[/ { in_pkg = ($0 == "[workspace.package]") ; next }
|
||||
in_pkg && $1 == "version" { gsub(/[" ]/, "", $3); print $3; exit }
|
||||
' "$ROOT/Cargo.toml"
|
||||
}
|
||||
|
||||
VERSION="${SOLVECRAFT_VERSION:-$(workspace_version)}"
|
||||
if [ -z "$VERSION" ]; then
|
||||
echo "error: could not read [workspace.package] version from $ROOT/Cargo.toml" >&2
|
||||
exit 1
|
||||
fi
|
||||
export VERSION
|
||||
|
||||
DIST="${DIST:-$ROOT/dist/release}"
|
||||
mkdir -p "$DIST"
|
||||
DIST="$(cd "$DIST" && pwd)"
|
||||
export DIST
|
||||
|
||||
export SOLVECRAFT_BUILD_DATE="${SOLVECRAFT_BUILD_DATE:-$(date -u +%Y-%m-%d)}"
|
||||
export CARGO_TARGET_DIR="${CARGO_TARGET_DIR:-$ROOT/target}"
|
||||
APP_ID=ai.storyteller.solvecraft
|
||||
export APP_ID
|
||||
|
||||
# Emit a GitHub Actions warning (plain stderr outside Actions).
|
||||
warn() {
|
||||
if [ -n "${GITHUB_ACTIONS:-}" ]; then echo "::warning::$*"; else echo "warning: $*" >&2; fi
|
||||
}
|
||||
|
||||
# Copy the licence, notice and readme files into a package directory. The programs also carry
|
||||
# them (Help > About > Licences, `solvecraft-cli licences`).
|
||||
copy_docs() {
|
||||
local dest="$1" f
|
||||
for f in README.md LICENSE-MIT LICENSE-APACHE NOTICE THIRD-PARTY-LICENSES.txt; do
|
||||
if [ -f "$ROOT/$f" ]; then cp "$ROOT/$f" "$dest/"; fi
|
||||
done
|
||||
}
|
||||
|
||||
# The app icon in every size a package needs, drawn by the CLI (no image files are committed):
|
||||
# $1 = the solvecraft-cli to run, $2 = output dir. Writes hicolor/<n>x<n>/apps/$APP_ID.png,
|
||||
# solvecraft.ico and solvecraft-1024.png.
|
||||
make_icons() {
|
||||
local cli="$1" out="$2" s
|
||||
for s in 16 24 32 48 64 128 256 512; do
|
||||
mkdir -p "$out/hicolor/${s}x${s}/apps"
|
||||
"$cli" icon --size "$s" --out "$out/hicolor/${s}x${s}/apps/$APP_ID.png" >/dev/null
|
||||
done
|
||||
"$cli" icon --out "$out/solvecraft.ico" >/dev/null
|
||||
"$cli" icon --size 1024 --out "$out/solvecraft-1024.png" >/dev/null
|
||||
}
|
||||
Executable
+70
@@ -0,0 +1,70 @@
|
||||
#!/usr/bin/env bash
|
||||
# Build and package SolveCraft for FreeBSD:
|
||||
#
|
||||
# $DIST/solvecraft-<version>-freebsd-x86_64.tar.gz a /usr/local-style tree:
|
||||
# solvecraft-<version>-freebsd-x86_64/{bin, share/applications, share/icons, share/mime,
|
||||
# share/metainfo, share/doc/solvecraft}
|
||||
#
|
||||
# Install by copying the tree's contents into /usr/local:
|
||||
# tar -xzf solvecraft-<version>-freebsd-x86_64.tar.gz --strip-components 1 -C /usr/local
|
||||
#
|
||||
# Usage: packaging/freebsd/package.sh [--skip-build]
|
||||
# Needs: bash, cargo, and the packages installed by release.yml's FreeBSD job.
|
||||
# Adapted from PhotoCraft's packaging (storytold/photocraft, Apache-2.0; see ATTRIBUTION.md).
|
||||
set -euo pipefail
|
||||
# shellcheck source=../env.sh
|
||||
. "$(dirname "${BASH_SOURCE[0]}")/../env.sh"
|
||||
LINUX="$ROOT/packaging/linux"
|
||||
|
||||
SKIP_BUILD=0
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--skip-build) SKIP_BUILD=1; shift ;;
|
||||
-h | --help) sed -n '2,13p' "$0"; exit 0 ;;
|
||||
*) echo "unknown argument: $1" >&2; exit 2 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# FreeBSD's uname says amd64; release file names use x86_64 like the Linux ones.
|
||||
case "$(uname -m)" in
|
||||
amd64 | x86_64) ARCH=x86_64 ;;
|
||||
arm64 | aarch64) ARCH=aarch64 ;;
|
||||
*) echo "unsupported architecture $(uname -m)" >&2; exit 2 ;;
|
||||
esac
|
||||
BASENAME="solvecraft-$VERSION-freebsd-$ARCH"
|
||||
echo "==> SolveCraft $VERSION for FreeBSD $ARCH"
|
||||
|
||||
# The release VM has 12 GB; full parallelism on the biggest crates runs it out of memory.
|
||||
export CARGO_BUILD_JOBS="${CARGO_BUILD_JOBS:-4}"
|
||||
if [ "$SKIP_BUILD" = 0 ]; then
|
||||
(cd "$ROOT" && cargo build --release --locked -p solvecraft -p solvecraft-cli)
|
||||
fi
|
||||
WORK="$CARGO_TARGET_DIR/freebsd-package"
|
||||
STAGE="$WORK/$BASENAME"
|
||||
BIN="$CARGO_TARGET_DIR/release"
|
||||
rm -rf "$WORK"
|
||||
|
||||
# FreeBSD's install(1) has no -D: create the directories first.
|
||||
mkdir -p "$STAGE/bin" "$STAGE/share/applications" "$STAGE/share/mime/packages" \
|
||||
"$STAGE/share/metainfo" "$STAGE/share/icons" "$STAGE/share/doc/solvecraft"
|
||||
install -m 755 "$BIN/solvecraft" "$BIN/solvecraft-cli" "$STAGE/bin/"
|
||||
strip "$STAGE/bin/solvecraft" "$STAGE/bin/solvecraft-cli" 2>/dev/null || true
|
||||
# The desktop entry, MIME type, metainfo and icons are the freedesktop files Linux ships.
|
||||
install -m 644 "$LINUX/$APP_ID.desktop" "$STAGE/share/applications/$APP_ID.desktop"
|
||||
install -m 644 "$LINUX/$APP_ID.mime.xml" "$STAGE/share/mime/packages/$APP_ID.xml"
|
||||
sed -e "s/@VERSION@/$VERSION/g" -e "s/@DATE@/$SOLVECRAFT_BUILD_DATE/g" \
|
||||
"$LINUX/$APP_ID.metainfo.xml.in" >"$STAGE/share/metainfo/$APP_ID.metainfo.xml"
|
||||
make_icons "$STAGE/bin/solvecraft-cli" "$WORK/icons"
|
||||
cp -R "$WORK/icons/hicolor" "$STAGE/share/icons/"
|
||||
copy_docs "$STAGE/share/doc/solvecraft"
|
||||
|
||||
for f in bin/solvecraft bin/solvecraft-cli "share/applications/$APP_ID.desktop" \
|
||||
"share/icons/hicolor/256x256/apps/$APP_ID.png" share/doc/solvecraft/LICENSE-MIT \
|
||||
share/doc/solvecraft/LICENSE-APACHE share/doc/solvecraft/THIRD-PARTY-LICENSES.txt; do
|
||||
if [ ! -e "$STAGE/$f" ]; then echo "error: $f is missing from the package" >&2; exit 1; fi
|
||||
done
|
||||
|
||||
tar -C "$WORK" -czf "$DIST/$BASENAME.tar.gz" "$BASENAME"
|
||||
echo "wrote $DIST/$BASENAME.tar.gz"
|
||||
"$STAGE/bin/solvecraft-cli" --version
|
||||
echo "==> done"
|
||||
Executable
+73
@@ -0,0 +1,73 @@
|
||||
#!/bin/sh
|
||||
# SolveCraft AppImage entry point (packaging/linux/AppRun; installed by package.sh).
|
||||
# Adapted from PhotoCraft's AppRun (storytold/photocraft, Apache-2.0; see ATTRIBUTION.md).
|
||||
#
|
||||
# On Wayland the compositor resolves a window's dock icon by taking the window's
|
||||
# app_id (ai.storyteller.solvecraft) and looking up an installed .desktop file.
|
||||
# An AppImage installs nothing, so every AppImage run would otherwise show the
|
||||
# generic "Wayland" icon. Best-effort desktop integration: on first run
|
||||
# (and whenever the AppImage's path changes) install the .desktop entry and the
|
||||
# hicolor icons into $XDG_DATA_HOME, then launch. Every failure here must leave
|
||||
# the launch untouched. Opt out with SOLVECRAFT_NO_DESKTOP_INTEGRATION=1.
|
||||
|
||||
set -u
|
||||
|
||||
APPDIR="$(dirname "$(readlink -f "$0")")"
|
||||
DESK_ID=ai.storyteller.solvecraft
|
||||
|
||||
integrate() {
|
||||
case "${SOLVECRAFT_NO_DESKTOP_INTEGRATION:-}" in
|
||||
1 | true | yes) return 0 ;;
|
||||
esac
|
||||
# The AppImage runtime exports $APPIMAGE; without it (an extracted AppDir) there
|
||||
# is no path to register and nothing to do.
|
||||
appimage="${APPIMAGE:-}"
|
||||
[ -n "$appimage" ] && [ -f "$appimage" ] || return 0
|
||||
appimage="$(readlink -f "$appimage")" || return 0
|
||||
# Exec= is parsed by the desktop: a path holding a quote, backquote, dollar, backslash,
|
||||
# percent or a line break would need escaping (or could add lines), so skip integration.
|
||||
case "$appimage" in
|
||||
*[\"\`\$\\%]* | *'
|
||||
'*) return 0 ;;
|
||||
esac
|
||||
home="${HOME:-}"
|
||||
[ -n "$home" ] || return 0
|
||||
data="${XDG_DATA_HOME:-$home/.local/share}"
|
||||
src_desktop="$APPDIR/usr/share/applications/$DESK_ID.desktop"
|
||||
[ -f "$src_desktop" ] || return 0
|
||||
|
||||
# Rewrite Exec to this AppImage (the binary is not on the host PATH) and drop
|
||||
# TryExec, which would hide the entry; everything else is shipped as packaged.
|
||||
apps="$data/applications"
|
||||
mkdir -p "$apps" 2>/dev/null || return 0
|
||||
tmp="$apps/.$DESK_ID.tmp.$$"
|
||||
esc="$(printf '%s' "$appimage" | sed 's/[\\&|]/\\&/g')"
|
||||
if sed -e "s|^Exec=.*|Exec=\"$esc\" %F|" -e '/^TryExec=/d' "$src_desktop" 2>/dev/null >"$tmp"; then
|
||||
if cmp -s "$tmp" "$apps/$DESK_ID.desktop"; then
|
||||
rm -f "$tmp"
|
||||
else
|
||||
if mv -f "$tmp" "$apps/$DESK_ID.desktop" 2>/dev/null; then
|
||||
command -v update-desktop-database >/dev/null 2>&1 && update-desktop-database "$apps" >/dev/null 2>&1
|
||||
else
|
||||
rm -f "$tmp"
|
||||
fi
|
||||
fi
|
||||
else
|
||||
rm -f "$tmp"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Icons: per-file compare so an unchanged install costs a handful of cmp calls.
|
||||
src_icons="$APPDIR/usr/share/icons/hicolor"
|
||||
[ -d "$src_icons" ] || return 0
|
||||
icons="$data/icons/hicolor"
|
||||
(cd "$src_icons" && find . -type f) | while IFS= read -r rel; do
|
||||
mkdir -p "$icons/$(dirname "$rel")" 2>/dev/null || continue
|
||||
if ! cmp -s "$src_icons/$rel" "$icons/$rel"; then
|
||||
cp "$src_icons/$rel" "$icons/$rel" 2>/dev/null || true
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
integrate
|
||||
exec "$APPDIR/usr/bin/solvecraft" "$@"
|
||||
@@ -0,0 +1,14 @@
|
||||
[Desktop Entry]
|
||||
Type=Application
|
||||
Name=SolveCraft
|
||||
GenericName=3D CAD
|
||||
Comment=Parametric 3D CAD: sketch, constrain, extrude, and change a parameter to rebuild
|
||||
Exec=solvecraft %f
|
||||
TryExec=solvecraft
|
||||
Icon=ai.storyteller.solvecraft
|
||||
Terminal=false
|
||||
StartupNotify=true
|
||||
StartupWMClass=solvecraft
|
||||
Categories=Graphics;3DGraphics;Engineering;
|
||||
Keywords=cad;3d;parametric;sketch;step;stl;modelling;modeling;
|
||||
MimeType=application/x-solvecraft;model/step;model/iges;model/stl;model/3mf;
|
||||
@@ -0,0 +1,64 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- AppStream metadata (software centres, Flathub). Template: package.sh substitutes @VERSION@
|
||||
and @DATE@ from Cargo.toml and the build date. -->
|
||||
<component type="desktop-application">
|
||||
<id>ai.storyteller.solvecraft</id>
|
||||
<metadata_license>CC0-1.0</metadata_license>
|
||||
<project_license>MIT OR Apache-2.0</project_license>
|
||||
<name>SolveCraft</name>
|
||||
<summary>Parametric 3D CAD in the style of Fusion</summary>
|
||||
<developer id="io.github.bherbruck">
|
||||
<name>SolveCraft contributors</name>
|
||||
</developer>
|
||||
<description>
|
||||
<p>
|
||||
SolveCraft is an open-source parametric 3D CAD application written in Rust. Sketch on a
|
||||
plane, constrain and dimension the sketch, turn profiles into solids on a feature
|
||||
timeline, and change a parameter to watch the timeline rebuild.
|
||||
</p>
|
||||
<ul>
|
||||
<li>Sketches with a constraint solver and parameter-driven dimensions</li>
|
||||
<li>Extrude, revolve, sweep, loft, fillet, chamfer, shell, holes and patterns</li>
|
||||
<li>Components and joints, sheet metal, and a plastic-enclosure subset</li>
|
||||
<li>STEP and IGES import and export; 3MF, STL and OBJ</li>
|
||||
<li>Every action is a command, scriptable from a CLI, a JSON control channel and an MCP server</li>
|
||||
</ul>
|
||||
</description>
|
||||
<launchable type="desktop-id">ai.storyteller.solvecraft.desktop</launchable>
|
||||
<icon type="stock">ai.storyteller.solvecraft</icon>
|
||||
<url type="homepage">https://bherbruck.github.io/solvecraft/</url>
|
||||
<url type="bugtracker">https://github.com/bherbruck/solvecraft/issues</url>
|
||||
<url type="help">https://bherbruck.github.io/solvecraft/</url>
|
||||
<categories>
|
||||
<category>Graphics</category>
|
||||
<category>3DGraphics</category>
|
||||
<category>Engineering</category>
|
||||
</categories>
|
||||
<keywords>
|
||||
<keyword>cad</keyword>
|
||||
<keyword>parametric</keyword>
|
||||
<keyword>step</keyword>
|
||||
</keywords>
|
||||
<screenshots>
|
||||
<screenshot type="default">
|
||||
<caption>A sample part with its sketches and feature timeline</caption>
|
||||
<image>https://raw.githubusercontent.com/bherbruck/solvecraft/main/docs/screenshots/sample-plate.png</image>
|
||||
</screenshot>
|
||||
</screenshots>
|
||||
<requires>
|
||||
<display_length compare="ge">760</display_length>
|
||||
</requires>
|
||||
<supports>
|
||||
<control>keyboard</control>
|
||||
<control>pointing</control>
|
||||
</supports>
|
||||
<provides>
|
||||
<binary>solvecraft</binary>
|
||||
<binary>solvecraft-cli</binary>
|
||||
<mediatype>application/x-solvecraft</mediatype>
|
||||
</provides>
|
||||
<content_rating type="oars-1.1"/>
|
||||
<releases>
|
||||
<release version="@VERSION@" date="@DATE@"/>
|
||||
</releases>
|
||||
</component>
|
||||
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- shared-mime-info definitions for types the system database may not have.
|
||||
Installed to /usr/share/mime/packages/ (update-mime-database runs on install). -->
|
||||
<mime-info xmlns="http://www.freedesktop.org/standards/shared-mime-info">
|
||||
<mime-type type="application/x-solvecraft">
|
||||
<comment>SolveCraft design</comment>
|
||||
<icon name="ai.storyteller.solvecraft"/>
|
||||
<sub-class-of type="application/json"/>
|
||||
<glob pattern="*.solvecraft"/>
|
||||
</mime-type>
|
||||
</mime-info>
|
||||
Executable
+72
@@ -0,0 +1,72 @@
|
||||
#!/usr/bin/env bash
|
||||
# Repackage a SolveCraft Linux tarball as a single-file Flatpak bundle:
|
||||
#
|
||||
# $DIST/solvecraft-<version>-linux-<arch>.flatpak
|
||||
#
|
||||
# Usage: packaging/linux/flatpak-bundle.sh [--no-test] [TARBALL]
|
||||
#
|
||||
# TARBALL defaults to $DIST/solvecraft-<version>-linux-<arch>.tar.gz from package.sh. The bundle
|
||||
# is built for the host architecture (x86_64 or aarch64), which must match the tarball's.
|
||||
# Needs flatpak, flatpak-builder and the SVG pixbuf loader (librsvg2-common) for the host's
|
||||
# `appstreamcli compose`; the freedesktop runtime and SDK named in the manifest are
|
||||
# installed per-user from Flathub. Unless --no-test, the bundle is then installed per-user and
|
||||
# `solvecraft-cli --version` is run inside the sandbox as a smoke test.
|
||||
# Manifest: packaging/linux/flatpak/ai.storyteller.solvecraft.bundle.yml.
|
||||
# Adapted from PhotoCraft's (storytold/photocraft, Apache-2.0; see ATTRIBUTION.md).
|
||||
set -euo pipefail
|
||||
# shellcheck source=../env.sh
|
||||
. "$(dirname "${BASH_SOURCE[0]}")/../env.sh"
|
||||
HERE="$ROOT/packaging/linux"
|
||||
FLATHUB=https://dl.flathub.org/repo/flathub.flatpakrepo
|
||||
|
||||
TEST=1
|
||||
TARBALL=""
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--no-test) TEST=0; shift ;;
|
||||
-h | --help) sed -n '2,14p' "$0"; exit 0 ;;
|
||||
-*) echo "unknown argument: $1" >&2; exit 2 ;;
|
||||
*) TARBALL="$1"; shift ;;
|
||||
esac
|
||||
done
|
||||
|
||||
ARCH="$(uname -m)"
|
||||
case "$ARCH" in
|
||||
x86_64) ;;
|
||||
aarch64 | arm64) ARCH=aarch64 ;;
|
||||
*) echo "unsupported architecture $ARCH" >&2; exit 2 ;;
|
||||
esac
|
||||
BASENAME="solvecraft-$VERSION-linux-$ARCH"
|
||||
TARBALL="${TARBALL:-$DIST/$BASENAME.tar.gz}"
|
||||
[ -f "$TARBALL" ] || { echo "error: $TARBALL not found (run packaging/linux/package.sh --formats tar first)" >&2; exit 1; }
|
||||
for tool in flatpak flatpak-builder; do
|
||||
command -v "$tool" >/dev/null || { echo "error: $tool not found (apt install flatpak flatpak-builder librsvg2-common)" >&2; exit 1; }
|
||||
done
|
||||
|
||||
echo "==> SolveCraft $VERSION Flatpak bundle for $ARCH from $(basename "$TARBALL")"
|
||||
|
||||
WORK="$CARGO_TARGET_DIR/flatpak-bundle"
|
||||
rm -rf "$WORK"
|
||||
mkdir -p "$WORK/stage"
|
||||
tar -xzf "$TARBALL" -C "$WORK/stage" --strip-components=1
|
||||
[ -x "$WORK/stage/bin/solvecraft" ] || { echo "error: $TARBALL has no bin/solvecraft" >&2; exit 1; }
|
||||
cp "$HERE/flatpak/$APP_ID.bundle.yml" "$WORK/$APP_ID.yml"
|
||||
|
||||
flatpak remote-add --user --if-not-exists flathub "$FLATHUB"
|
||||
# --disable-rofiles-fuse: no FUSE in containers and CI runners.
|
||||
flatpak-builder --user --install-deps-from=flathub --disable-rofiles-fuse --force-clean \
|
||||
--default-branch=stable --repo="$WORK/repo" --state-dir="$WORK/state" "$WORK/build" "$WORK/$APP_ID.yml"
|
||||
|
||||
OUT="$DIST/$BASENAME.flatpak"
|
||||
# --runtime-repo lets `flatpak install` fetch the freedesktop runtime from Flathub if missing.
|
||||
flatpak build-bundle --runtime-repo="$FLATHUB" "$WORK/repo" "$OUT" "$APP_ID" stable
|
||||
echo "wrote $OUT"
|
||||
|
||||
if [ "$TEST" = 1 ]; then
|
||||
flatpak install --user -y --noninteractive --reinstall "$OUT"
|
||||
flatpak info --user "$APP_ID"
|
||||
flatpak run --command=solvecraft-cli "$APP_ID" --version
|
||||
flatpak run --command=sh "$APP_ID" -c 'ls /app/share/applications /app/share/metainfo /app/share/mime/packages /app/share/icons/hicolor/256x256/apps /app/share/doc/solvecraft'
|
||||
fi
|
||||
echo "==> done"
|
||||
ls -lh "$OUT"
|
||||
@@ -0,0 +1,43 @@
|
||||
# Flatpak manifest for the single-file bundle attached to every GitHub release
|
||||
# (solvecraft-<version>-linux-<arch>.flatpak). Build it with packaging/linux/flatpak-bundle.sh.
|
||||
# Adapted from PhotoCraft's (storytold/photocraft, Apache-2.0; see ATTRIBUTION.md).
|
||||
#
|
||||
# It repackages the release's own Linux tarball (solvecraft-<version>-linux-<arch>.tar.gz, built
|
||||
# on Ubuntu 22.04 by package.sh) instead of compiling Rust inside flatpak-builder: no network
|
||||
# during the build, and the same binaries as the AppImage/deb/rpm. The binaries need glibc >= 2.35
|
||||
# and load X11, Wayland, xkbcommon and Vulkan at runtime; the freedesktop runtime has all of
|
||||
# them, and the GPU driver comes from its org.freedesktop.Platform.GL extension.
|
||||
id: ai.storyteller.solvecraft
|
||||
runtime: org.freedesktop.Platform
|
||||
runtime-version: '26.08'
|
||||
sdk: org.freedesktop.Sdk
|
||||
command: solvecraft
|
||||
finish-args:
|
||||
# Display: Wayland first; X11 only when no Wayland compositor is running.
|
||||
- --socket=wayland
|
||||
- --socket=fallback-x11
|
||||
# X11 shared memory (MIT-SHM). Without it the X11 fallback is slow or fails.
|
||||
- --share=ipc
|
||||
# GPU access for the wgpu viewport (Vulkan, or GL as a fallback).
|
||||
- --device=dri
|
||||
# Open/Save dialogs go through the file-chooser portal (rfd's xdg-portal backend), so picked
|
||||
# files work anywhere without broad filesystem access. Documents is granted so files passed by
|
||||
# path (the command line, recent designs) work in the usual place.
|
||||
- --filesystem=xdg-documents
|
||||
# Deliberately not granted: network (the optional --control server listens on 127.0.0.1 in
|
||||
# the sandbox; `flatpak override --user --share=network ai.storyteller.solvecraft` exposes it to
|
||||
# host agents) and home/host filesystem access.
|
||||
modules:
|
||||
- name: solvecraft
|
||||
buildsystem: simple
|
||||
build-commands:
|
||||
- install -Dm755 bin/solvecraft /app/bin/solvecraft
|
||||
- install -Dm755 bin/solvecraft-cli /app/bin/solvecraft-cli
|
||||
- install -Dm644 share/applications/ai.storyteller.solvecraft.desktop /app/share/applications/ai.storyteller.solvecraft.desktop
|
||||
- install -Dm644 share/mime/packages/ai.storyteller.solvecraft.xml /app/share/mime/packages/ai.storyteller.solvecraft.xml
|
||||
# Already has the release's version and date substituted by package.sh.
|
||||
- install -Dm644 share/metainfo/ai.storyteller.solvecraft.metainfo.xml /app/share/metainfo/ai.storyteller.solvecraft.metainfo.xml
|
||||
- mkdir -p /app/share/icons /app/share/doc && cp -R share/icons/hicolor /app/share/icons/ && cp -R share/doc/solvecraft /app/share/doc/
|
||||
sources:
|
||||
- type: dir
|
||||
path: stage
|
||||
@@ -0,0 +1,74 @@
|
||||
# .deb and .rpm packages (nfpm: https://nfpm.goreleaser.com). Run by package.sh, which stages
|
||||
# the file tree, substitutes its path for ${STAGE} (nfpm doesn't expand env vars in `src`) and
|
||||
# exports VERSION and NFPM_ARCH. Adapted from PhotoCraft's (storytold/photocraft, Apache-2.0).
|
||||
name: solvecraft
|
||||
arch: ${NFPM_ARCH}
|
||||
platform: linux
|
||||
version: ${VERSION}
|
||||
# semver: 0.2.0-rc.1 becomes 0.2.0~rc.1 in deb/rpm, so pre-releases sort before the release.
|
||||
version_schema: semver
|
||||
release: 1
|
||||
section: graphics
|
||||
priority: optional
|
||||
maintainer: ${SOLVECRAFT_MAINTAINER}
|
||||
vendor: SolveCraft contributors
|
||||
homepage: https://bherbruck.github.io/solvecraft/
|
||||
license: MIT OR Apache-2.0
|
||||
description: |-
|
||||
Open-source parametric 3D CAD in the style of Fusion
|
||||
Sketches with a constraint solver, a parametric feature timeline, components and
|
||||
joints, sheet metal; STEP, IGES, 3MF, STL and OBJ. Includes solvecraft-cli, a
|
||||
headless command runner and MCP server for AI agents.
|
||||
|
||||
# The binaries link only glibc and libgcc_s directly. Windowing (X11/Wayland, xkbcommon) and the
|
||||
# GPU (Vulkan, EGL/GL) are loaded at runtime with dlopen, so they aren't ELF NEEDED entries and
|
||||
# the package tools can't find them: list them here.
|
||||
overrides:
|
||||
deb:
|
||||
depends:
|
||||
- libc6 (>= 2.35)
|
||||
- libgcc-s1
|
||||
- libxkbcommon0
|
||||
- libxkbcommon-x11-0
|
||||
- libwayland-client0
|
||||
- libx11-6
|
||||
- libx11-xcb1
|
||||
- libxcb1
|
||||
- libxcursor1
|
||||
- libxi6
|
||||
- libvulkan1 | libegl1
|
||||
recommends:
|
||||
- mesa-vulkan-drivers
|
||||
- libegl-mesa0
|
||||
- xdg-desktop-portal
|
||||
rpm:
|
||||
# Soname provides rather than package names, so the same requirements resolve on Fedora/RHEL
|
||||
# and openSUSE.
|
||||
depends:
|
||||
- libxkbcommon.so.0()(64bit)
|
||||
- libxkbcommon-x11.so.0()(64bit)
|
||||
- libwayland-client.so.0()(64bit)
|
||||
- libX11.so.6()(64bit)
|
||||
- libX11-xcb.so.1()(64bit)
|
||||
- libxcb.so.1()(64bit)
|
||||
- libXcursor.so.1()(64bit)
|
||||
- libXi.so.6()(64bit)
|
||||
- (libvulkan.so.1()(64bit) or libEGL.so.1()(64bit))
|
||||
recommends:
|
||||
- mesa-vulkan-drivers
|
||||
- xdg-desktop-portal
|
||||
|
||||
contents:
|
||||
- src: ${STAGE}/usr/bin/solvecraft
|
||||
dst: /usr/bin/solvecraft
|
||||
file_info: { mode: 0755 }
|
||||
- src: ${STAGE}/usr/bin/solvecraft-cli
|
||||
dst: /usr/bin/solvecraft-cli
|
||||
file_info: { mode: 0755 }
|
||||
- src: ${STAGE}/usr/share/
|
||||
dst: /usr/share/
|
||||
type: tree
|
||||
|
||||
scripts:
|
||||
postinstall: packaging/linux/postinst.sh
|
||||
postremove: packaging/linux/postinst.sh
|
||||
Executable
+139
@@ -0,0 +1,139 @@
|
||||
#!/usr/bin/env bash
|
||||
# Build and package SolveCraft for Linux (<arch> is x86_64 or aarch64):
|
||||
#
|
||||
# $DIST/solvecraft-<version>-linux-<arch>.AppImage any distro with glibc >= the build host's
|
||||
# $DIST/solvecraft-<version>-linux-<arch>.AppImage.zsync delta updates (needs zsyncmake)
|
||||
# $DIST/solvecraft-<version>-linux-<arch>.deb Debian, Ubuntu, Mint, Pop!_OS, ...
|
||||
# $DIST/solvecraft-<version>-linux-<arch>.rpm Fedora, openSUSE, RHEL, ...
|
||||
# $DIST/solvecraft-<version>-linux-<arch>.tar.gz plain FHS-style tree (bin/, share/)
|
||||
#
|
||||
# Usage: packaging/linux/package.sh [--skip-build] [--formats "appimage deb rpm tar"]
|
||||
#
|
||||
# Needs: cargo; nfpm for deb/rpm (https://nfpm.goreleaser.com); appimagetool for the AppImage
|
||||
# (downloaded into $CARGO_TARGET_DIR if missing). Build on an old distro (CI: Ubuntu 22.04,
|
||||
# glibc 2.35) so the binaries run on newer ones. Optional: desktop-file-validate, appstreamcli,
|
||||
# zsyncmake (the zsync package) for the AppImage's .zsync.
|
||||
# Adapted from PhotoCraft's packaging (storytold/photocraft, Apache-2.0; see ATTRIBUTION.md).
|
||||
set -euo pipefail
|
||||
# shellcheck source=../env.sh
|
||||
. "$(dirname "${BASH_SOURCE[0]}")/../env.sh"
|
||||
HERE="$ROOT/packaging/linux"
|
||||
|
||||
SKIP_BUILD=0
|
||||
FORMATS="appimage deb rpm tar"
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--skip-build) SKIP_BUILD=1; shift ;;
|
||||
--formats) FORMATS="$2"; shift 2 ;;
|
||||
-h | --help) sed -n '2,16p' "$0"; exit 0 ;;
|
||||
*) echo "unknown argument: $1" >&2; exit 2 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
ARCH="$(uname -m)"
|
||||
case "$ARCH" in
|
||||
x86_64) DEB_ARCH=amd64 ;;
|
||||
aarch64 | arm64) ARCH=aarch64; DEB_ARCH=arm64 ;;
|
||||
*) echo "unsupported architecture $ARCH" >&2; exit 2 ;;
|
||||
esac
|
||||
export SOLVECRAFT_MAINTAINER="${SOLVECRAFT_MAINTAINER:-SolveCraft contributors <https://github.com/bherbruck/solvecraft>}"
|
||||
BASENAME="solvecraft-$VERSION-linux-$ARCH"
|
||||
|
||||
echo "==> SolveCraft $VERSION for Linux $ARCH ($FORMATS)"
|
||||
|
||||
if [ "$SKIP_BUILD" = 0 ]; then
|
||||
(cd "$ROOT" && cargo build --release --locked -p solvecraft -p solvecraft-cli)
|
||||
fi
|
||||
BIN="$CARGO_TARGET_DIR/release"
|
||||
WORK="$CARGO_TARGET_DIR/linux-package"
|
||||
STAGE="$WORK/root"
|
||||
rm -rf "$WORK"
|
||||
|
||||
# ---- stage an FHS tree (shared by every format) -------------------------------------------------
|
||||
install -Dm755 "$BIN/solvecraft" "$STAGE/usr/bin/solvecraft"
|
||||
install -Dm755 "$BIN/solvecraft-cli" "$STAGE/usr/bin/solvecraft-cli"
|
||||
strip "$STAGE/usr/bin/solvecraft" "$STAGE/usr/bin/solvecraft-cli" 2>/dev/null || true
|
||||
install -Dm644 "$HERE/$APP_ID.desktop" "$STAGE/usr/share/applications/$APP_ID.desktop"
|
||||
install -Dm644 "$HERE/$APP_ID.mime.xml" "$STAGE/usr/share/mime/packages/$APP_ID.xml"
|
||||
mkdir -p "$STAGE/usr/share/metainfo"
|
||||
sed -e "s/@VERSION@/$VERSION/g" -e "s/@DATE@/$SOLVECRAFT_BUILD_DATE/g" \
|
||||
"$HERE/$APP_ID.metainfo.xml.in" >"$STAGE/usr/share/metainfo/$APP_ID.metainfo.xml"
|
||||
make_icons "$STAGE/usr/bin/solvecraft-cli" "$WORK/icons"
|
||||
mkdir -p "$STAGE/usr/share/icons"
|
||||
cp -R "$WORK/icons/hicolor" "$STAGE/usr/share/icons/"
|
||||
mkdir -p "$STAGE/usr/share/doc/solvecraft"
|
||||
copy_docs "$STAGE/usr/share/doc/solvecraft"
|
||||
|
||||
if command -v desktop-file-validate >/dev/null; then
|
||||
desktop-file-validate "$STAGE/usr/share/applications/$APP_ID.desktop"
|
||||
fi
|
||||
if command -v appstreamcli >/dev/null; then
|
||||
appstreamcli validate --no-net --explain "$STAGE/usr/share/metainfo/$APP_ID.metainfo.xml"
|
||||
fi
|
||||
|
||||
has() { case " $FORMATS " in *" $1 "*) return 0 ;; *) return 1 ;; esac; }
|
||||
|
||||
# ---- .tar.gz ------------------------------------------------------------------------------------
|
||||
if has tar; then
|
||||
mkdir -p "$WORK/tar"
|
||||
cp -R "$STAGE/usr" "$WORK/tar/$BASENAME"
|
||||
tar -C "$WORK/tar" -czf "$DIST/$BASENAME.tar.gz" "$BASENAME"
|
||||
echo "wrote $DIST/$BASENAME.tar.gz"
|
||||
fi
|
||||
|
||||
# ---- .deb / .rpm --------------------------------------------------------------------------------
|
||||
if has deb || has rpm; then
|
||||
command -v nfpm >/dev/null || { echo "error: nfpm not found (https://nfpm.goreleaser.com/install/)" >&2; exit 1; }
|
||||
export VERSION
|
||||
export NFPM_ARCH="$DEB_ARCH"
|
||||
# nfpm expands env vars in fields like `version` and `arch`, but not in `contents[].src`.
|
||||
sed "s|\${STAGE}|$STAGE|g" "$HERE/nfpm.yaml" >"$WORK/nfpm.yaml"
|
||||
for fmt in deb rpm; do
|
||||
if has "$fmt"; then (cd "$ROOT" && nfpm package -f "$WORK/nfpm.yaml" -p "$fmt" -t "$DIST/$BASENAME.$fmt"); fi
|
||||
done
|
||||
fi
|
||||
|
||||
# ---- AppImage -----------------------------------------------------------------------------------
|
||||
if has appimage; then
|
||||
APPDIR="$WORK/SolveCraft.AppDir"
|
||||
cp -R "$STAGE" "$APPDIR"
|
||||
mv "$APPDIR/usr/share/doc" "$WORK/doc-unused"
|
||||
# AppRun is a script, not a symlink: it installs the .desktop entry and icons into
|
||||
# $XDG_DATA_HOME before launching, so Wayland compositors can resolve the window's app_id to
|
||||
# our icon.
|
||||
install -Dm755 "$HERE/AppRun" "$APPDIR/AppRun"
|
||||
cp "$HERE/$APP_ID.desktop" "$APPDIR/$APP_ID.desktop"
|
||||
cp "$WORK/icons/hicolor/256x256/apps/$APP_ID.png" "$APPDIR/$APP_ID.png"
|
||||
ln -s "$APP_ID.png" "$APPDIR/.DirIcon"
|
||||
|
||||
TOOL="${APPIMAGETOOL:-$(command -v appimagetool || true)}"
|
||||
if [ -z "$TOOL" ]; then
|
||||
TOOL="$CARGO_TARGET_DIR/appimagetool-$ARCH.AppImage"
|
||||
if [ ! -x "$TOOL" ]; then
|
||||
curl -fsSL -o "$TOOL" "https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-$ARCH.AppImage"
|
||||
chmod +x "$TOOL"
|
||||
fi
|
||||
fi
|
||||
OUT="$DIST/$BASENAME.AppImage"
|
||||
APPDIR="$(cd "$APPDIR" && pwd)"
|
||||
TOOL="$(cd "$(dirname "$TOOL")" && pwd)/$(basename "$TOOL")"
|
||||
rm -f "$OUT.zsync"
|
||||
# Update information: AppImageUpdate and the like fetch only the changed blocks of a newer
|
||||
# release through the .zsync published next to each AppImage. `latest` is the newest published
|
||||
# release that is not a pre-release.
|
||||
REPO="${GITHUB_REPOSITORY:-bherbruck/solvecraft}"
|
||||
UPDATE_INFO="gh-releases-zsync|${REPO%%/*}|${REPO#*/}|latest|solvecraft-*-linux-$ARCH.AppImage.zsync"
|
||||
# Extract-and-run: works without FUSE (containers, CI). With zsyncmake on the host appimagetool
|
||||
# also writes the .zsync, into its working directory, hence the cd.
|
||||
(cd "$DIST" && ARCH="$ARCH" APPIMAGE_EXTRACT_AND_RUN=1 "$TOOL" --no-appstream -u "$UPDATE_INFO" "$APPDIR" "$OUT")
|
||||
echo "wrote $OUT"
|
||||
if [ -s "$OUT.zsync" ]; then
|
||||
echo "wrote $OUT.zsync"
|
||||
else
|
||||
warn "zsyncmake not found, so $OUT.zsync was not written; AppImage delta updates need it"
|
||||
fi
|
||||
fi
|
||||
|
||||
"$STAGE/usr/bin/solvecraft-cli" --version
|
||||
echo "==> done"
|
||||
ls -lh "$DIST"
|
||||
Executable
+13
@@ -0,0 +1,13 @@
|
||||
#!/bin/sh
|
||||
# deb/rpm post-install and post-remove: refresh the desktop, MIME and icon caches.
|
||||
# Every tool is optional; missing ones are skipped.
|
||||
if command -v update-desktop-database >/dev/null 2>&1; then
|
||||
update-desktop-database -q /usr/share/applications || true
|
||||
fi
|
||||
if command -v update-mime-database >/dev/null 2>&1; then
|
||||
update-mime-database /usr/share/mime || true
|
||||
fi
|
||||
if command -v gtk-update-icon-cache >/dev/null 2>&1; then
|
||||
gtk-update-icon-cache -q -t -f /usr/share/icons/hicolor || true
|
||||
fi
|
||||
exit 0
|
||||
@@ -0,0 +1,64 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<!-- Template: packaging/macos/package.sh substitutes @VERSION@ (full, e.g. 0.2.0-rc.1) and
|
||||
@SHORT_VERSION@ (numeric X.Y.Z). Adapted from PhotoCraft's (storytold/photocraft, Apache-2.0). -->
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>SolveCraft</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>SolveCraft</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>SolveCraft</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>ai.storyteller.solvecraft</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>@SHORT_VERSION@</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>@SHORT_VERSION@</string>
|
||||
<key>SolveCraftVersion</key>
|
||||
<string>@VERSION@</string>
|
||||
<key>CFBundleIconFile</key>
|
||||
<string>SolveCraft</string>
|
||||
<key>LSApplicationCategoryType</key>
|
||||
<string>public.app-category.graphics-design</string>
|
||||
<!-- Must match MACOSX_DEPLOYMENT_TARGET in package.sh. -->
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>11.0</string>
|
||||
<key>NSHighResolutionCapable</key>
|
||||
<true/>
|
||||
<key>NSSupportsAutomaticGraphicsSwitching</key>
|
||||
<true/>
|
||||
<key>CFBundleDocumentTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>SolveCraft design</string>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array><string>solvecraft</string></array>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Editor</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>STEP, IGES and mesh files</string>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array><string>step</string><string>stp</string><string>igs</string><string>iges</string><string>3mf</string><string>stl</string></array>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Alternate</string>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
Executable
+116
@@ -0,0 +1,116 @@
|
||||
#!/usr/bin/env bash
|
||||
# Build and package SolveCraft for macOS:
|
||||
#
|
||||
# $DIST/solvecraft-<version>-macos-<arch>.dmg SolveCraft.app on a drag-to-Applications DMG
|
||||
# $DIST/solvecraft-cli-<version>-macos-<arch>.zip the headless CLI with the licence files
|
||||
#
|
||||
# Usage: packaging/macos/package.sh [--arch universal|aarch64|x86_64] [--skip-build]
|
||||
#
|
||||
# The app is ad-hoc signed (not notarized): the first launch needs right-click > Open.
|
||||
# Adapted from PhotoCraft's packaging (storytold/photocraft, Apache-2.0; see ATTRIBUTION.md).
|
||||
set -euo pipefail
|
||||
# shellcheck source=../env.sh
|
||||
. "$(dirname "${BASH_SOURCE[0]}")/../env.sh"
|
||||
HERE="$ROOT/packaging/macos"
|
||||
|
||||
ARCH=universal
|
||||
SKIP_BUILD=0
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--arch) ARCH="$2"; shift 2 ;;
|
||||
--skip-build) SKIP_BUILD=1; shift ;;
|
||||
-h | --help) sed -n '2,10p' "$0"; exit 0 ;;
|
||||
*) echo "unknown argument: $1" >&2; exit 2 ;;
|
||||
esac
|
||||
done
|
||||
case "$ARCH" in
|
||||
universal) TARGETS=(aarch64-apple-darwin x86_64-apple-darwin) ;;
|
||||
aarch64) TARGETS=(aarch64-apple-darwin) ;;
|
||||
x86_64) TARGETS=(x86_64-apple-darwin) ;;
|
||||
*) echo "unknown --arch $ARCH" >&2; exit 2 ;;
|
||||
esac
|
||||
|
||||
# Keep in sync with LSMinimumSystemVersion in Info.plist.in.
|
||||
export MACOSX_DEPLOYMENT_TARGET=11.0
|
||||
SHORT_VERSION="${VERSION%%-*}"
|
||||
WORK="$CARGO_TARGET_DIR/macos-package"
|
||||
APP="$WORK/SolveCraft.app"
|
||||
DMG="$DIST/solvecraft-$VERSION-macos-$ARCH.dmg"
|
||||
CLI_ZIP="$DIST/solvecraft-cli-$VERSION-macos-$ARCH.zip"
|
||||
|
||||
echo "==> SolveCraft $VERSION for macOS ($ARCH)"
|
||||
if [ "$SKIP_BUILD" = 0 ]; then
|
||||
args=()
|
||||
for t in "${TARGETS[@]}"; do args+=(--target "$t"); done
|
||||
(cd "$ROOT" && cargo build --release --locked -p solvecraft -p solvecraft-cli "${args[@]}")
|
||||
fi
|
||||
|
||||
rm -rf "$WORK"
|
||||
mkdir -p "$WORK/bin"
|
||||
for bin in solvecraft solvecraft-cli; do
|
||||
inputs=()
|
||||
for t in "${TARGETS[@]}"; do inputs+=("$CARGO_TARGET_DIR/$t/release/$bin"); done
|
||||
lipo -create -output "$WORK/bin/$bin" "${inputs[@]}"
|
||||
lipo -info "$WORK/bin/$bin"
|
||||
done
|
||||
|
||||
# ---- SolveCraft.app ----------------------------------------------------------------------------
|
||||
echo "==> assembling $APP"
|
||||
mkdir -p "$APP/Contents/MacOS" "$APP/Contents/Resources"
|
||||
cp "$WORK/bin/solvecraft" "$APP/Contents/MacOS/SolveCraft"
|
||||
# The icon, drawn by the CLI (no image files are committed).
|
||||
SET="$WORK/SolveCraft.iconset"
|
||||
mkdir -p "$SET"
|
||||
for s in 16 32 128 256 512; do
|
||||
"$WORK/bin/solvecraft-cli" icon --size "$s" --out "$SET/icon_${s}x${s}.png" >/dev/null
|
||||
"$WORK/bin/solvecraft-cli" icon --size $((s * 2)) --out "$SET/icon_${s}x${s}@2x.png" >/dev/null
|
||||
done
|
||||
iconutil -c icns -o "$APP/Contents/Resources/SolveCraft.icns" "$SET"
|
||||
mkdir -p "$APP/Contents/Resources/Licenses"
|
||||
copy_docs "$APP/Contents/Resources/Licenses"
|
||||
sed -e "s/@VERSION@/$VERSION/g" -e "s/@SHORT_VERSION@/$SHORT_VERSION/g" "$HERE/Info.plist.in" >"$APP/Contents/Info.plist"
|
||||
plutil -lint "$APP/Contents/Info.plist"
|
||||
printf 'APPL????' >"$APP/Contents/PkgInfo"
|
||||
# Ad-hoc signature over the whole bundle (Apple silicon refuses unsigned code).
|
||||
codesign --force --sign - --timestamp=none "$APP"
|
||||
codesign --verify --strict --deep --verbose=2 "$APP"
|
||||
|
||||
# ---- DMG ---------------------------------------------------------------------------------------
|
||||
echo "==> building $DMG"
|
||||
STAGE="$WORK/dmg"
|
||||
mkdir -p "$STAGE"
|
||||
ditto "$APP" "$STAGE/SolveCraft.app"
|
||||
ln -s /Applications "$STAGE/Applications"
|
||||
rm -f "$DMG" "$WORK/raw.dmg"
|
||||
# makehybrid + convert builds the image without attaching a device, unlike `create -srcfolder`,
|
||||
# which is flaky on CI runners ("Resource busy").
|
||||
hdiutil makehybrid -hfs -hfs-volume-name "SolveCraft" -hfs-openfolder "$STAGE" -o "$WORK/raw.dmg" "$STAGE"
|
||||
hdiutil convert "$WORK/raw.dmg" -format UDZO -imagekey zlib-level=9 -o "$DMG"
|
||||
rm -f "$WORK/raw.dmg"
|
||||
|
||||
# Check the image as a user gets it: mount it and look inside.
|
||||
MNT="$(mktemp -d)"
|
||||
hdiutil attach -readonly -nobrowse -mountpoint "$MNT" "$DMG"
|
||||
ls -la "$MNT" "$MNT/SolveCraft.app/Contents" "$MNT/SolveCraft.app/Contents/MacOS" "$MNT/SolveCraft.app/Contents/Resources"
|
||||
test -L "$MNT/Applications" && test "$(readlink "$MNT/Applications")" = /Applications
|
||||
plutil -lint "$MNT/SolveCraft.app/Contents/Info.plist"
|
||||
case "$ARCH" in universal) WANT=(arm64 x86_64) ;; aarch64) WANT=(arm64) ;; *) WANT=(x86_64) ;; esac
|
||||
lipo "$MNT/SolveCraft.app/Contents/MacOS/SolveCraft" -verify_arch "${WANT[@]}"
|
||||
test -s "$MNT/SolveCraft.app/Contents/Resources/Licenses/THIRD-PARTY-LICENSES.txt"
|
||||
codesign --verify --strict --deep "$MNT/SolveCraft.app"
|
||||
"$MNT/SolveCraft.app/Contents/MacOS/SolveCraft" --version
|
||||
hdiutil detach "$MNT"
|
||||
|
||||
# ---- CLI ---------------------------------------------------------------------------------------
|
||||
echo "==> building $CLI_ZIP"
|
||||
CLI_DIR="$WORK/solvecraft-cli-$VERSION-macos-$ARCH"
|
||||
mkdir -p "$CLI_DIR"
|
||||
cp "$WORK/bin/solvecraft-cli" "$CLI_DIR/"
|
||||
codesign --force --sign - --timestamp=none --identifier ai.storyteller.solvecraft-cli "$CLI_DIR/solvecraft-cli"
|
||||
copy_docs "$CLI_DIR"
|
||||
rm -f "$CLI_ZIP"
|
||||
ditto -c -k --keepParent "$CLI_DIR" "$CLI_ZIP"
|
||||
|
||||
"$WORK/bin/solvecraft-cli" --version
|
||||
echo "==> done"
|
||||
ls -lh "$DMG" "$CLI_ZIP"
|
||||
Executable
+36
@@ -0,0 +1,36 @@
|
||||
#!/usr/bin/env bash
|
||||
# Build the browser version and zip it: $DIST/solvecraft-web-<version>.zip
|
||||
#
|
||||
# Usage: packaging/web/package.sh [--skip-build]
|
||||
#
|
||||
# Needs: trunk and the wasm32-unknown-unknown target. The zip holds a self-contained static site
|
||||
# in solvecraft-web-<version>/ that works from any URL path (public_url = "./" in Trunk.toml).
|
||||
# Saving designs needs HTTPS or localhost (the browser's private file storage).
|
||||
# Adapted from PhotoCraft's packaging (storytold/photocraft, Apache-2.0; see ATTRIBUTION.md).
|
||||
set -euo pipefail
|
||||
# shellcheck source=../env.sh
|
||||
. "$(dirname "${BASH_SOURCE[0]}")/../env.sh"
|
||||
|
||||
if [ "${1:-}" != "--skip-build" ]; then
|
||||
command -v trunk >/dev/null || { echo "error: trunk not found (cargo install trunk --locked)" >&2; exit 1; }
|
||||
(cd "$ROOT/apps/solvecraft-web" && trunk build --release)
|
||||
fi
|
||||
|
||||
SITE="$ROOT/dist/web"
|
||||
[ -f "$SITE/index.html" ] || { echo "error: $SITE/index.html missing; run without --skip-build" >&2; exit 1; }
|
||||
# Paths must be relative so the site works under any prefix.
|
||||
if grep -Eq '(src|href)="/[^/]' "$SITE/index.html"; then
|
||||
echo "error: $SITE/index.html has root-absolute URLs; it would break when served from a sub-path" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
NAME="solvecraft-web-$VERSION"
|
||||
WORK="$CARGO_TARGET_DIR/web-package"
|
||||
rm -rf "$WORK"
|
||||
mkdir -p "$WORK/$NAME"
|
||||
cp -R "$SITE/." "$WORK/$NAME/"
|
||||
copy_docs "$WORK/$NAME"
|
||||
rm -f "$DIST/$NAME.zip"
|
||||
(cd "$WORK" && zip -qr9 "$DIST/$NAME.zip" "$NAME")
|
||||
echo "wrote $DIST/$NAME.zip"
|
||||
ls -lh "$DIST/$NAME.zip"
|
||||
@@ -0,0 +1,133 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Build and package SolveCraft for Windows.
|
||||
|
||||
.DESCRIPTION
|
||||
Produces, in $env:DIST (default: dist/release):
|
||||
solvecraft-<version>-windows-<arch>.msi per-machine installer (WiX v5)
|
||||
solvecraft-<version>-windows-<arch>-portable.zip solvecraft.exe + solvecraft-cli.exe + licences
|
||||
SolveCraft-<version>-windows-<arch>.exe the app alone (x64 only: a plain download)
|
||||
|
||||
The binaries link the C runtime statically (+crt-static), so nothing else needs installing.
|
||||
The icon comes from $env:SOLVECRAFT_ICONS (solvecraft.ico, made by `solvecraft-cli icon`), or
|
||||
is drawn by the CLI just built when this machine can run it. Nothing is signed.
|
||||
|
||||
Needs: Rust (MSVC toolchain + the target) and WiX v5:
|
||||
dotnet tool install --global wix --version 5.0.2
|
||||
wix extension add -g WixToolset.UI.wixext/5.0.2 WixToolset.Util.wixext/5.0.2
|
||||
|
||||
Adapted from PhotoCraft's packaging (storytold/photocraft, Apache-2.0; see ATTRIBUTION.md).
|
||||
|
||||
.EXAMPLE
|
||||
pwsh packaging/windows/package.ps1 -Arch x64
|
||||
pwsh packaging/windows/package.ps1 -Arch arm64 # cross-compiled; needs the MSVC ARM64 build tools
|
||||
#>
|
||||
param(
|
||||
[ValidateSet('x64', 'x86', 'arm64')] [string] $Arch = 'x64',
|
||||
[switch] $SkipBuild
|
||||
)
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$Root = (Resolve-Path (Join-Path $PSScriptRoot '..\..')).Path
|
||||
|
||||
function Invoke-Native([string] $What, [scriptblock] $Block) {
|
||||
Write-Output "==> $What"
|
||||
& $Block
|
||||
if ($LASTEXITCODE -ne 0) { throw "$What failed with exit code $LASTEXITCODE" }
|
||||
}
|
||||
|
||||
# The version lives in one place: [workspace.package] version in the root Cargo.toml.
|
||||
$Version = $env:SOLVECRAFT_VERSION
|
||||
if (-not $Version) {
|
||||
$inPkg = $false
|
||||
foreach ($line in Get-Content (Join-Path $Root 'Cargo.toml')) {
|
||||
if ($line -match '^\s*\[') { $inPkg = ($line.Trim() -eq '[workspace.package]'); continue }
|
||||
if ($inPkg -and $line -match '^\s*version\s*=\s*"([^"]+)"') { $Version = $Matches[1]; break }
|
||||
}
|
||||
}
|
||||
if (-not $Version) { throw 'could not read [workspace.package] version from Cargo.toml' }
|
||||
# MSI ProductVersion is numeric (major.minor.build); pre-release tags are dropped there.
|
||||
$MsiVersion = ($Version -split '-')[0]
|
||||
|
||||
$Target = switch ($Arch) { 'x64' { 'x86_64-pc-windows-msvc' } 'x86' { 'i686-pc-windows-msvc' } 'arm64' { 'aarch64-pc-windows-msvc' } }
|
||||
$Dist = if ($env:DIST) { $env:DIST } else { Join-Path $Root 'dist\release' }
|
||||
$TargetDir = if ($env:CARGO_TARGET_DIR) { $env:CARGO_TARGET_DIR } else { Join-Path $Root 'target' }
|
||||
New-Item -ItemType Directory -Force -Path $Dist | Out-Null
|
||||
|
||||
Write-Output "SolveCraft $Version for Windows $Arch ($Target)"
|
||||
|
||||
if (-not $SkipBuild) {
|
||||
# Static CRT: no VC++ redistributable needed. Scoped to the target so host build scripts and
|
||||
# proc-macros are unaffected.
|
||||
$flagVar = 'CARGO_TARGET_' + ($Target.ToUpper() -replace '-', '_') + '_RUSTFLAGS'
|
||||
[Environment]::SetEnvironmentVariable($flagVar, '-C target-feature=+crt-static')
|
||||
Invoke-Native "cargo build ($Target)" { cargo build --release --locked -p solvecraft -p solvecraft-cli --target $Target }
|
||||
}
|
||||
$Bin = Join-Path $TargetDir "$Target\release"
|
||||
|
||||
# Check both binaries' PE headers: Machine must match -Arch; Subsystem 2 = GUI (the app opens no
|
||||
# console window), 3 = console (the CLI's output reaches the terminal).
|
||||
function Get-PeHeader([string] $Path) {
|
||||
$bytes = [System.IO.File]::ReadAllBytes($Path)
|
||||
$pe = [BitConverter]::ToInt32($bytes, 0x3C)
|
||||
return @{ Machine = [BitConverter]::ToUInt16($bytes, $pe + 4); Subsystem = [BitConverter]::ToUInt16($bytes, $pe + 0x5C) }
|
||||
}
|
||||
$Machine = switch ($Arch) { 'x64' { 0x8664 } 'x86' { 0x14C } 'arm64' { 0xAA64 } }
|
||||
foreach ($check in @(@('solvecraft.exe', 2), @('solvecraft-cli.exe', 3))) {
|
||||
$h = Get-PeHeader (Join-Path $Bin $check[0])
|
||||
if ($h.Machine -ne $Machine) { throw "$($check[0]) is for machine 0x$('{0:X}' -f $h.Machine), expected 0x$('{0:X}' -f $Machine) ($Arch)" }
|
||||
if ($h.Subsystem -ne $check[1]) { throw "$($check[0]) has PE subsystem $($h.Subsystem), expected $($check[1])" }
|
||||
Write-Output "ok $($check[0]): $Arch, PE subsystem $($h.Subsystem)"
|
||||
}
|
||||
$Stage = Join-Path $TargetDir "windows-package\$Arch"
|
||||
Remove-Item -Recurse -Force $Stage -ErrorAction SilentlyContinue
|
||||
New-Item -ItemType Directory -Force -Path $Stage | Out-Null
|
||||
Copy-Item (Join-Path $Bin 'solvecraft.exe'), (Join-Path $Bin 'solvecraft-cli.exe') $Stage
|
||||
foreach ($f in 'LICENSE-MIT', 'LICENSE-APACHE', 'NOTICE', 'THIRD-PARTY-LICENSES.txt') { Copy-Item (Join-Path $Root $f) $Stage }
|
||||
|
||||
$HostArch = [System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture.ToString().ToLowerInvariant()
|
||||
$CanRun = ($Arch -ne 'arm64' -or $HostArch -eq 'arm64')
|
||||
$Icon = if ($env:SOLVECRAFT_ICONS) { Join-Path $env:SOLVECRAFT_ICONS 'solvecraft.ico' } else { Join-Path $Stage 'solvecraft.ico' }
|
||||
if (-not (Test-Path $Icon)) {
|
||||
if (-not $CanRun) { throw "no icon: set SOLVECRAFT_ICONS to a folder with solvecraft.ico (an $Arch CLI can't run on this $HostArch machine)" }
|
||||
Invoke-Native 'draw the icon' { & (Join-Path $Stage 'solvecraft-cli.exe') icon --out $Icon }
|
||||
}
|
||||
|
||||
# ---- MSI ---------------------------------------------------------------------------------------
|
||||
$Msi = Join-Path $Dist "solvecraft-$Version-windows-$Arch.msi"
|
||||
Invoke-Native 'wix build' {
|
||||
wix build (Join-Path $PSScriptRoot 'solvecraft.wxs') -arch $Arch `
|
||||
-ext WixToolset.UI.wixext -ext WixToolset.Util.wixext `
|
||||
-culture en-US -loc (Join-Path $PSScriptRoot 'solvecraft.en-us.wxl') `
|
||||
-d "Version=$MsiVersion" -d "DisplayVersion=$Version" -d "BinDir=$Stage" -d "IconPath=$Icon" `
|
||||
-o $Msi
|
||||
}
|
||||
Invoke-Native 'MSI shortcut icon validation (ICE50)' {
|
||||
wix msi validate $Msi -ice ICE50 -intermediateFolder (Join-Path $Stage 'msi-validation')
|
||||
}
|
||||
# wix writes its debug symbols (.wixpdb) next to the MSI; keep them out of the release assets.
|
||||
Remove-Item -Force -ErrorAction SilentlyContinue ([IO.Path]::ChangeExtension($Msi, '.wixpdb'))
|
||||
|
||||
# ---- portable zip ------------------------------------------------------------------------------
|
||||
$Portable = Join-Path $TargetDir "windows-package\solvecraft-$Version-windows-$Arch-portable"
|
||||
Remove-Item -Recurse -Force $Portable -ErrorAction SilentlyContinue
|
||||
New-Item -ItemType Directory -Force -Path $Portable | Out-Null
|
||||
Copy-Item (Join-Path $Stage '*.exe') $Portable
|
||||
foreach ($f in 'README.md', 'LICENSE-MIT', 'LICENSE-APACHE', 'NOTICE', 'THIRD-PARTY-LICENSES.txt') { Copy-Item (Join-Path $Root $f) $Portable }
|
||||
$Zip = Join-Path $Dist "solvecraft-$Version-windows-$Arch-portable.zip"
|
||||
Remove-Item -Force $Zip -ErrorAction SilentlyContinue
|
||||
Compress-Archive -Path $Portable -DestinationPath $Zip
|
||||
|
||||
# ---- the app as a plain download (x64) ---------------------------------------------------------
|
||||
$Outputs = @($Msi, $Zip)
|
||||
if ($Arch -eq 'x64') {
|
||||
$Exe = Join-Path $Dist "SolveCraft-$Version-windows-$Arch.exe"
|
||||
Copy-Item (Join-Path $Stage 'solvecraft.exe') $Exe
|
||||
$Outputs += $Exe
|
||||
}
|
||||
|
||||
if ($CanRun) {
|
||||
Invoke-Native 'solvecraft-cli --version' { & (Join-Path $Stage 'solvecraft-cli.exe') --version }
|
||||
} else {
|
||||
Write-Output "skipping solvecraft-cli --version: an $Arch build doesn't run on this $HostArch machine"
|
||||
}
|
||||
Get-Item $Outputs | Format-Table Name, Length
|
||||
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Overrides for the setup wizard's WixUI strings, so its pages name the version being installed
|
||||
([SolvecraftVersion] is the full version, pre-release tag included, set in solvecraft.wxs).
|
||||
Adapted from PhotoCraft's (storytold/photocraft, Apache-2.0; see ATTRIBUTION.md).
|
||||
-->
|
||||
<WixLocalization Culture="en-US" xmlns="http://wixtoolset.org/schemas/v4/wxl">
|
||||
<String Id="WelcomeDlgTitle" Value="{\WixUI_Font_Bigger}Welcome to the [ProductName] [SolvecraftVersion] Setup Wizard" />
|
||||
<String Id="WelcomeDlgDescription" Value="The Setup Wizard will install [ProductName] version [SolvecraftVersion] on your computer. Click Next to continue or Cancel to exit the Setup Wizard." />
|
||||
<String Id="VerifyReadyDlgInstallTitle" Value="{\WixUI_Font_Title}Ready to install [ProductName] [SolvecraftVersion]" />
|
||||
<String Id="ExitDialogTitle" Value="{\WixUI_Font_Bigger}Completed the [ProductName] [SolvecraftVersion] Setup Wizard" />
|
||||
</WixLocalization>
|
||||
@@ -0,0 +1,121 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
SolveCraft MSI (WiX Toolset v5, plus the UI and Util extensions). Built by package.ps1:
|
||||
|
||||
wix build solvecraft.wxs -arch x64 -ext WixToolset.UI.wixext -ext WixToolset.Util.wixext
|
||||
-culture en-US -loc solvecraft.en-us.wxl -d Version=1.2.3 -d DisplayVersion=1.2.3-rc.1
|
||||
-d BinDir=... -d IconPath=... -o out.msi
|
||||
|
||||
Per-machine install with a setup wizard: Welcome, install folder (Program Files\SolveCraft by
|
||||
default, remembered for upgrades), Ready, progress, and a Finish page with a ticked "Launch
|
||||
SolveCraft" box. Installs a Start Menu shortcut, solvecraft-cli, the licence files (including
|
||||
THIRD-PARTY-LICENSES.txt), makes SolveCraft the owner of `.solvecraft` and offers it under
|
||||
"Open with" for STEP, IGES, 3MF and STL. `msiexec /qn` installs silently.
|
||||
Adapted from PhotoCraft's (storytold/photocraft, Apache-2.0; see ATTRIBUTION.md).
|
||||
-->
|
||||
<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"
|
||||
xmlns:ui="http://wixtoolset.org/schemas/v4/wxs/ui">
|
||||
<Package
|
||||
Name="SolveCraft"
|
||||
Manufacturer="SolveCraft contributors"
|
||||
Version="$(var.Version)"
|
||||
UpgradeCode="6A145FD6-D285-428D-AE84-A69570CDF4DB"
|
||||
Scope="perMachine"
|
||||
Compressed="yes"
|
||||
Language="1033">
|
||||
<SummaryInformation Description="SolveCraft parametric 3D CAD" Manufacturer="SolveCraft contributors" />
|
||||
<!-- Same-version upgrades let successive pre-releases of one X.Y.Z replace each other. -->
|
||||
<MajorUpgrade AllowSameVersionUpgrades="yes"
|
||||
DowngradeErrorMessage="A newer version of SolveCraft is already installed." />
|
||||
<MediaTemplate EmbedCab="yes" CompressionLevel="high" />
|
||||
|
||||
<!-- Advertised shortcuts to an .exe need an .exe icon identifier (ICE50). -->
|
||||
<Icon Id="SolvecraftIcon.exe" SourceFile="$(var.IconPath)" />
|
||||
<Property Id="ARPPRODUCTICON" Value="SolvecraftIcon.exe" />
|
||||
<Property Id="ARPURLINFOABOUT" Value="https://bherbruck.github.io/solvecraft/" />
|
||||
<Property Id="ARPURLUPDATEINFO" Value="https://github.com/bherbruck/solvecraft/releases" />
|
||||
<Property Id="ARPHELPLINK" Value="https://github.com/bherbruck/solvecraft/issues" />
|
||||
|
||||
<StandardDirectory Id="ProgramFiles6432Folder">
|
||||
<Directory Id="INSTALLFOLDER" Name="SolveCraft" />
|
||||
</StandardDirectory>
|
||||
<StandardDirectory Id="ProgramMenuFolder" />
|
||||
|
||||
<!-- An upgrade installs where the user put the previous version (written by SolvecraftApp). -->
|
||||
<Property Id="INSTALLFOLDER">
|
||||
<RegistrySearch Id="PreviousInstallFolder" Root="HKLM" Key="Software\SolveCraft" Name="InstallDir" Type="raw" />
|
||||
</Property>
|
||||
|
||||
<Feature Id="Main" Title="SolveCraft" Level="1">
|
||||
<ComponentGroupRef Id="SolvecraftFiles" />
|
||||
</Feature>
|
||||
|
||||
<!-- WixUI_InstallDir without its licence page (MIT OR Apache-2.0: nothing to accept). -->
|
||||
<ui:WixUI Id="WixUI_InstallDir" InstallDirectory="INSTALLFOLDER" />
|
||||
<UI>
|
||||
<Publish Dialog="WelcomeDlg" Control="Next" Event="NewDialog" Value="InstallDirDlg" Order="2" Condition="NOT Installed" />
|
||||
<Publish Dialog="InstallDirDlg" Control="Back" Event="NewDialog" Value="WelcomeDlg" Order="2" />
|
||||
<Publish Dialog="ExitDialog" Control="Finish" Event="DoAction" Value="LaunchSolvecraft"
|
||||
Condition="WIXUI_EXITDIALOGOPTIONALCHECKBOX = 1 AND NOT Installed" />
|
||||
</UI>
|
||||
|
||||
<Property Id="SolvecraftVersion" Value="$(var.DisplayVersion)" />
|
||||
<Property Id="WIXUI_EXITDIALOGOPTIONALTEXT" Value="SolveCraft $(var.DisplayVersion) is installed. You can start it any time from the Start menu." />
|
||||
<Property Id="WIXUI_EXITDIALOGOPTIONALCHECKBOXTEXT" Value="Launch SolveCraft" />
|
||||
<Property Id="WIXUI_EXITDIALOGOPTIONALCHECKBOX" Value="1" />
|
||||
<Property Id="WixShellExecTarget" Value="[#SolvecraftExe]" />
|
||||
<!-- Runs in the wizard's own (unelevated) process, so the app starts as the user. -->
|
||||
<CustomAction Id="LaunchSolvecraft" BinaryRef="Wix4UtilCA_$(sys.BUILDARCHSHORT)" DllEntry="WixShellExec" Impersonate="yes" Return="ignore" />
|
||||
</Package>
|
||||
|
||||
<Fragment>
|
||||
<ComponentGroup Id="SolvecraftFiles" Directory="INSTALLFOLDER">
|
||||
<Component Id="SolvecraftApp" Guid="290BEF27-C200-4069-AA43-7DE6A39CDB0A">
|
||||
<File Id="SolvecraftExe" Source="$(var.BinDir)\solvecraft.exe" KeyPath="yes">
|
||||
<Shortcut Id="StartMenuShortcut" Directory="ProgramMenuFolder" Name="SolveCraft"
|
||||
Description="SolveCraft parametric 3D CAD" WorkingDirectory="INSTALLFOLDER" Advertise="yes"
|
||||
Icon="SolvecraftIcon.exe" IconIndex="0" />
|
||||
</File>
|
||||
|
||||
<!-- .solvecraft: SolveCraft's own format; SolveCraft is its default app. -->
|
||||
<ProgId Id="Solvecraft.Design" Description="SolveCraft Design" Icon="SolvecraftExe" IconIndex="0">
|
||||
<Extension Id="solvecraft" ContentType="application/x-solvecraft">
|
||||
<Verb Id="open" Command="Open" TargetFile="SolvecraftExe" Argument=""%1"" />
|
||||
</Extension>
|
||||
</ProgId>
|
||||
<!-- STEP, IGES and meshes: offered under "Open with", never forced as the default. -->
|
||||
<ProgId Id="Solvecraft.Model" Description="3D model" Icon="SolvecraftExe" IconIndex="0" />
|
||||
<RegistryValue Root="HKLM" Key="Software\Classes\Solvecraft.Model\shell\open\command" Value=""[#SolvecraftExe]" "%1"" Type="string" />
|
||||
<RegistryValue Root="HKLM" Key="Software\Classes\.step\OpenWithProgids" Name="Solvecraft.Model" Value="" Type="string" />
|
||||
<RegistryValue Root="HKLM" Key="Software\Classes\.stp\OpenWithProgids" Name="Solvecraft.Model" Value="" Type="string" />
|
||||
<RegistryValue Root="HKLM" Key="Software\Classes\.igs\OpenWithProgids" Name="Solvecraft.Model" Value="" Type="string" />
|
||||
<RegistryValue Root="HKLM" Key="Software\Classes\.iges\OpenWithProgids" Name="Solvecraft.Model" Value="" Type="string" />
|
||||
<RegistryValue Root="HKLM" Key="Software\Classes\.3mf\OpenWithProgids" Name="Solvecraft.Model" Value="" Type="string" />
|
||||
<RegistryValue Root="HKLM" Key="Software\Classes\.stl\OpenWithProgids" Name="Solvecraft.Model" Value="" Type="string" />
|
||||
|
||||
<!-- "Open with" app list and the Run dialog (Win+R "solvecraft"). -->
|
||||
<RegistryValue Root="HKLM" Key="Software\Classes\Applications\solvecraft.exe" Name="FriendlyAppName" Value="SolveCraft" Type="string" />
|
||||
<RegistryValue Root="HKLM" Key="Software\Classes\Applications\solvecraft.exe\shell\open\command" Value=""[#SolvecraftExe]" "%1"" Type="string" />
|
||||
<RegistryValue Root="HKLM" Key="Software\Classes\Applications\solvecraft.exe\SupportedTypes" Name=".solvecraft" Value="" Type="string" />
|
||||
<RegistryValue Root="HKLM" Key="Software\Classes\Applications\solvecraft.exe\SupportedTypes" Name=".step" Value="" Type="string" />
|
||||
<RegistryValue Root="HKLM" Key="Software\Classes\Applications\solvecraft.exe\SupportedTypes" Name=".stp" Value="" Type="string" />
|
||||
<RegistryValue Root="HKLM" Key="Software\Classes\Applications\solvecraft.exe\SupportedTypes" Name=".igs" Value="" Type="string" />
|
||||
<RegistryValue Root="HKLM" Key="Software\Classes\Applications\solvecraft.exe\SupportedTypes" Name=".iges" Value="" Type="string" />
|
||||
<RegistryValue Root="HKLM" Key="Software\Classes\Applications\solvecraft.exe\SupportedTypes" Name=".3mf" Value="" Type="string" />
|
||||
<RegistryValue Root="HKLM" Key="Software\Classes\Applications\solvecraft.exe\SupportedTypes" Name=".stl" Value="" Type="string" />
|
||||
<RegistryValue Root="HKLM" Key="Software\Microsoft\Windows\CurrentVersion\App Paths\solvecraft.exe" Value="[#SolvecraftExe]" Type="string" />
|
||||
<RegistryValue Root="HKLM" Key="Software\SolveCraft" Name="InstallDir" Value="[INSTALLFOLDER]" Type="string" />
|
||||
</Component>
|
||||
|
||||
<Component Id="SolvecraftCli">
|
||||
<File Id="SolvecraftCliExe" Source="$(var.BinDir)\solvecraft-cli.exe" KeyPath="yes" />
|
||||
</Component>
|
||||
<Component Id="SolvecraftLicences">
|
||||
<File Id="LicenseMit" Source="$(var.BinDir)\LICENSE-MIT" KeyPath="yes" />
|
||||
<File Id="LicenseApache" Source="$(var.BinDir)\LICENSE-APACHE" />
|
||||
<File Id="Notice" Source="$(var.BinDir)\NOTICE" />
|
||||
<File Id="ThirdParty" Source="$(var.BinDir)\THIRD-PARTY-LICENSES.txt" />
|
||||
</Component>
|
||||
</ComponentGroup>
|
||||
</Fragment>
|
||||
</Wix>
|
||||
Reference in new issue
Block a user