
Cross Platform Build Expert
- 189 installs
- 45 repo stars
- Updated December 6, 2025
- martinholovsky/claude-skills-generator
Set up cross-platform build matrices for mobile, CLI, and browser extensions: native toolchains, conditional compilation, artifact packaging, and CI runners per OS/arch target.
About
Cross-platform build expert for assembling reliable compile-and-package pipelines targeting mobile, CLI, and extension distributions. Covers toolchain matrices, platform-specific flags, reproducible CI jobs, signing workflows, and integration of native SDKs into a single automated build system.
- Multi-OS and multi-arch CI build matrices
- Native toolchain and SDK version pinning
- Conditional compilation per platform target
- Unified artifact packaging and code signing
- Reproducible builds across developer and CI environments
Cross Platform Build Expert by the numbers
- 189 all-time installs (skills.sh)
- +2 installs in the week ending Aug 2, 2026 (Skillselion tracking)
- Ranked #416 of 1,435 DevOps & CI/CD skills by installs in the Skillselion catalog
- Data as of Aug 2, 2026 (Skillselion catalog sync)
npx skills add https://github.com/martinholovsky/claude-skills-generator --skill cross-platform-build-expertAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 189 |
|---|---|
| repo stars | ★ 45 |
| Last updated | December 6, 2025 |
| Repository | martinholovsky/claude-skills-generator ↗ |
What it does
Set up cross-platform build matrices for mobile, CLI, and browser extensions: native toolchains, conditional compilation, artifact packaging, and CI runners per OS/arch target.
Files
Cross-Platform Build Expert
0. Mandatory Reading Protocol
CRITICAL: Before implementing ANY platform-specific build configuration, you MUST read the relevant reference files:
Trigger Conditions for Reference Files
Read `references/advanced-patterns.md` WHEN:
- Configuring platform-specific build matrices
- Setting up conditional compilation
- Implementing platform-specific features
- Optimizing build sizes and performance
Read `references/security-examples.md` WHEN:
- Setting up code signing certificates
- Configuring notarization for macOS
- Implementing secure build environments
- Managing signing credentials
---
1. Overview
Risk Level: MEDIUM
Justification: Cross-platform builds involve code signing credentials, platform-specific security configurations, and distribution through various app stores. Improper signing leads to security warnings, failed installations, or rejected submissions. Build configurations can also leak sensitive information or create platform-specific vulnerabilities.
You are an expert in cross-platform desktop application builds, specializing in:
- Platform-specific configurations for Windows, macOS, and Linux
- Code signing and notarization procedures
- Distribution requirements for each platform
- Build optimization for size and performance
- Tauri configuration for multi-platform builds
Primary Use Cases
- Building Tauri applications for all desktop platforms
- Setting up code signing for trusted distribution
- Configuring CI/CD for multi-platform builds
- Optimizing application bundles
- Meeting platform distribution requirements
---
2. Core Principles
1. TDD First - Write build configuration tests before implementing 2. Performance Aware - Optimize build times, bundle sizes, and startup 3. Test on all target platforms - Don't assume cross-platform compatibility 4. Use platform abstractions - Rust std, Tauri APIs for platform differences 5. Handle path differences - Forward vs backward slashes, case sensitivity 6. Respect platform conventions - File locations, UI guidelines 7. Sign all releases - Users trust signed applications 8. Protect signing credentials - Never commit certificates 9. Verify signatures - Check before distribution 10. Use timestamping - Signatures remain valid after certificate expiry
---
3. Technical Foundation
3.1 Platform Build Targets
| Platform | Rust Target | Tauri Bundle |
|---|---|---|
| Windows x64 | x86_64-pc-windows-msvc | msi, nsis |
| Windows ARM | aarch64-pc-windows-msvc | msi, nsis |
| macOS Intel | x86_64-apple-darwin | dmg, app |
| macOS Apple Silicon | aarch64-apple-darwin | dmg, app |
| Linux x64 | x86_64-unknown-linux-gnu | deb, appimage |
| Linux ARM | aarch64-unknown-linux-gnu | deb, appimage |
3.2 Build Dependencies
Windows:
- Visual Studio Build Tools
- Windows SDK
- WebView2 Runtime (bundled by Tauri)
macOS:
- Xcode Command Line Tools
- Apple Developer Certificate
- App-specific password for notarization
Linux:
- GTK3 development libraries
- WebKitGTK
- AppIndicator (for system tray)
---
4. Implementation Patterns
4.1 Tauri Configuration
// tauri.conf.json
{
"build": {
"beforeBuildCommand": "npm run build",
"beforeDevCommand": "npm run dev",
"devPath": "http://localhost:3000",
"distDir": "../dist"
},
"package": {
"productName": "MyApp",
"version": "1.0.0"
},
"tauri": {
"bundle": {
"active": true,
"identifier": "com.company.myapp",
"icon": [
"icons/32x32.png",
"icons/128x128.png",
"icons/128x128@2x.png",
"icons/icon.icns",
"icons/icon.ico"
],
"targets": "all",
"windows": {
"certificateThumbprint": null,
"digestAlgorithm": "sha256",
"timestampUrl": "http://timestamp.digicert.com",
"wix": {
"language": "en-US"
}
},
"macOS": {
"entitlements": "./entitlements.plist",
"exceptionDomain": "",
"frameworks": [],
"minimumSystemVersion": "10.15",
"signingIdentity": null
},
"linux": {
"deb": {
"depends": ["libgtk-3-0", "libwebkit2gtk-4.0-37"]
},
"appimage": {
"bundleMediaFramework": true
}
}
},
"security": {
"csp": "default-src 'self'; script-src 'self'"
}
}
}4.2 Platform-Specific Code
// src-tauri/src/main.rs
#[cfg(target_os = "windows")]
fn platform_init() {
// Windows-specific initialization
use windows::Win32::System::Console::SetConsoleOutputCP;
unsafe { SetConsoleOutputCP(65001); } // UTF-8 support
}
#[cfg(target_os = "macos")]
fn platform_init() {
// macOS-specific initialization
// Handle Dock, menu bar, etc.
}
#[cfg(target_os = "linux")]
fn platform_init() {
// Linux-specific initialization
// Handle DBus, system tray, etc.
}
fn main() {
platform_init();
tauri::Builder::default()
.run(tauri::generate_context!())
.expect("error while running tauri application");
}4.3 GitHub Actions Build Matrix
name: Build
on:
push:
tags:
- 'v*'
jobs:
build:
strategy:
fail-fast: false
matrix:
include:
- platform: windows-latest
args: ''
target: x86_64-pc-windows-msvc
- platform: macos-latest
args: '--target x86_64-apple-darwin'
target: x86_64-apple-darwin
- platform: macos-latest
args: '--target aarch64-apple-darwin'
target: aarch64-apple-darwin
- platform: ubuntu-22.04
args: ''
target: x86_64-unknown-linux-gnu
runs-on: ${{ matrix.platform }}
steps:
- uses: actions/checkout@v4
- name: Setup Rust
uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.target }}
- name: Install Linux Dependencies
if: matrix.platform == 'ubuntu-22.04'
run: |
sudo apt-get update
sudo apt-get install -y \
libgtk-3-dev \
libwebkit2gtk-4.0-dev \
libappindicator3-dev \
librsvg2-dev \
patchelf
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install Dependencies
run: npm ci
- name: Build
run: npm run tauri build -- ${{ matrix.args }}
- name: Upload Artifacts
uses: actions/upload-artifact@v3
with:
name: ${{ matrix.target }}
path: |
src-tauri/target/${{ matrix.target }}/release/bundle/4.4 Code Signing Configuration
Windows (tauri.conf.json):
{
"tauri": {
"bundle": {
"windows": {
"certificateThumbprint": "YOUR_CERT_THUMBPRINT",
"digestAlgorithm": "sha256",
"timestampUrl": "http://timestamp.digicert.com"
}
}
}
}macOS (tauri.conf.json):
{
"tauri": {
"bundle": {
"macOS": {
"signingIdentity": "Developer ID Application: Company Name (TEAM_ID)",
"entitlements": "./entitlements.plist"
}
}
}
}macOS Entitlements (entitlements.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>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>
<key>com.apple.security.cs.disable-library-validation</key>
<true/>
</dict>
</plist>---
5. Security Standards
5.1 Code Signing Requirements
| Platform | Certificate Type | Purpose |
|---|---|---|
| Windows | EV Code Signing | Immediate SmartScreen trust |
| Windows | Standard Code Signing | Trust after reputation |
| macOS | Developer ID Application | Distribution outside App Store |
| macOS | Developer ID Installer | Signed PKG installers |
| Linux | GPG Key | Package signing |
5.2 Signing Best Practices
# Windows: Verify signature
signtool verify /pa /v MyApp.exe
# macOS: Verify signature
codesign --verify --deep --strict MyApp.app
spctl --assess --type execute MyApp.app
# macOS: Check notarization
xcrun stapler validate MyApp.app5.3 Build Security
- [ ] Certificates stored in CI/CD secrets, not repository
- [ ] Signing happens only on tagged releases
- [ ] Build environment is clean/ephemeral
- [ ] Dependencies pinned and verified
- [ ] Artifacts checksummed after signing
---
6. Implementation Workflow (TDD)
Step 1: Write Failing Test First
// tests/build_config_test.rs
#[cfg(test)]
mod tests {
use std::path::Path;
use std::process::Command;
#[test]
fn test_tauri_config_exists() {
assert!(Path::new("src-tauri/tauri.conf.json").exists());
}
#[test]
fn test_icons_all_platforms() {
let required_icons = vec![
"icons/icon.ico", // Windows
"icons/icon.icns", // macOS
"icons/icon.png", // Linux
];
for icon in required_icons {
assert!(Path::new(&format!("src-tauri/{}", icon)).exists(),
"Missing icon: {}", icon);
}
}
#[test]
fn test_bundle_identifier_format() {
let config: serde_json::Value = serde_json::from_str(
&std::fs::read_to_string("src-tauri/tauri.conf.json").unwrap()
).unwrap();
let identifier = config["tauri"]["bundle"]["identifier"].as_str().unwrap();
assert!(identifier.contains('.'), "Bundle ID must use reverse domain");
}
#[test]
fn test_frontend_builds_successfully() {
let output = Command::new("npm")
.args(["run", "build"])
.output()
.expect("Failed to run build");
assert!(output.status.success(), "Frontend build failed");
}
}Step 2: Implement Minimum to Pass
// Create minimal tauri.conf.json
{
"package": { "productName": "MyApp", "version": "0.1.0" },
"tauri": {
"bundle": {
"identifier": "com.company.myapp",
"icon": ["icons/icon.ico", "icons/icon.icns", "icons/icon.png"]
}
}
}Step 3: Refactor and Expand
Add platform-specific tests as you expand configuration:
#[test]
fn test_windows_signing_config() {
let config: serde_json::Value = serde_json::from_str(
&std::fs::read_to_string("src-tauri/tauri.conf.json").unwrap()
).unwrap();
let windows = &config["tauri"]["bundle"]["windows"];
assert!(windows["timestampUrl"].as_str().is_some());
}
#[test]
fn test_macos_minimum_version() {
let config: serde_json::Value = serde_json::from_str(
&std::fs::read_to_string("src-tauri/tauri.conf.json").unwrap()
).unwrap();
let min_ver = config["tauri"]["bundle"]["macOS"]["minimumSystemVersion"]
.as_str().unwrap();
assert!(min_ver >= "10.15", "Must support macOS 10.15+");
}Step 4: Run Full Verification
# Run all build tests
cargo test --manifest-path src-tauri/Cargo.toml
# Verify builds on all platforms (CI)
npm run tauri build -- --target x86_64-pc-windows-msvc
npm run tauri build -- --target x86_64-apple-darwin
npm run tauri build -- --target x86_64-unknown-linux-gnu
# Verify signatures
signtool verify /pa target/release/bundle/msi/*.msi
codesign --verify --deep target/release/bundle/macos/*.app---
7. Performance Patterns
7.1 Incremental Builds
# Cargo.toml - Enable incremental compilation
[profile.dev]
incremental = true
[profile.release]
incremental = true
lto = "thin" # Faster than "fat" LTOGood: Incremental builds reuse compiled artifacts
# First build: 2-3 minutes
cargo build --release
# Subsequent builds: 10-30 seconds
cargo build --releaseBad: Clean builds every time
cargo clean && cargo build --release # Always slow7.2 Build Caching
Good: Cache Rust dependencies in CI
- name: Cache Cargo
uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}Bad: No caching - downloads dependencies every build
- name: Build
run: cargo build --release # Downloads everything7.3 Parallel Compilation
Good: Maximize parallel jobs
# .cargo/config.toml
[build]
jobs = 8 # Match CPU cores
[target.x86_64-unknown-linux-gnu]
rustflags = ["-C", "link-arg=-fuse-ld=mold"] # Fast linkerBad: Single-threaded compilation
cargo build -j 1 # Extremely slow7.4 Tree-Shaking and Dead Code Elimination
Good: Enable LTO for smaller binaries
[profile.release]
lto = true
codegen-units = 1
panic = "abort"
strip = trueBad: Debug symbols in release
[profile.release]
debug = true # Bloats binary size7.5 Code Splitting (Frontend)
Good: Lazy load routes
// nuxt.config.ts
export default defineNuxtConfig({
experimental: {
treeshakeClientOnly: true
},
vite: {
build: {
rollupOptions: {
output: {
manualChunks: {
'vendor': ['vue', 'pinia'],
'three': ['three', '@tresjs/core']
}
}
}
}
}
})Bad: Bundle everything together
// Single massive bundle
import * as everything from './all-modules'7.6 Build Size Optimization
Good: Analyze and optimize bundle
# Analyze Rust binary
cargo bloat --release --crates
# Analyze frontend bundle
npx nuxi analyzeBad: Ignore bundle size
npm run build # Never check what's included---
8. Common Mistakes & Anti-Patterns
8.1 Hardcoded Paths
// WRONG: Windows-style path
let config = std::fs::read("C:\\Users\\app\\config.json")?;
// WRONG: Unix-style absolute path
let config = std::fs::read("/home/user/.config/app/config.json")?;
// CORRECT: Platform-appropriate paths
use directories::ProjectDirs;
let dirs = ProjectDirs::from("com", "company", "app")
.expect("Failed to get project directories");
let config_path = dirs.config_dir().join("config.json");
let config = std::fs::read(config_path)?;8.2 Missing Platform Dependencies
# WRONG: Missing Linux dependencies
- name: Build
run: npm run tauri build # Fails on Linux!
# CORRECT: Install platform dependencies
- name: Install Dependencies (Linux)
if: matrix.platform == 'ubuntu-22.04'
run: |
sudo apt-get update
sudo apt-get install -y \
libgtk-3-dev \
libwebkit2gtk-4.0-dev \
libappindicator3-dev8.3 Universal Binary Issues
# WRONG: Build universal without both targets
- name: Build macOS Universal
run: npm run tauri build -- --target universal-apple-darwin
# Fails if x86_64 or aarch64 not available!
# CORRECT: Build each architecture separately
- name: Build macOS Intel
run: npm run tauri build -- --target x86_64-apple-darwin
- name: Build macOS ARM
run: npm run tauri build -- --target aarch64-apple-darwin
- name: Create Universal Binary
run: |
lipo -create \
target/x86_64-apple-darwin/release/myapp \
target/aarch64-apple-darwin/release/myapp \
-output target/universal/myapp8.4 Missing Notarization
# WRONG: Sign without notarization
codesign --sign "Developer ID" MyApp.app
# Users get Gatekeeper warnings!
# CORRECT: Sign and notarize
codesign --sign "Developer ID" --options runtime MyApp.app
xcrun notarytool submit MyApp.zip --apple-id "$APPLE_ID" --password "$APP_PASSWORD" --team-id "$TEAM_ID" --wait
xcrun stapler staple MyApp.app---
13. Pre-Implementation Checklist
Phase 1: Before Writing Code
- [ ] Read all platform-specific requirements
- [ ] Identify target platforms and architectures
- [ ] Write tests for build configuration validation
- [ ] Set up CI/CD matrix for all targets
- [ ] Acquire code signing certificates
- [ ] Configure secrets in CI environment
Phase 2: During Implementation
- [ ] Run tests after each configuration change
- [ ] Verify incremental builds are working
- [ ] Test platform-specific code with conditional compilation
- [ ] Check bundle sizes after adding dependencies
- [ ] Validate icons exist for all platforms
- [ ] Test on actual target platforms (not just CI)
Phase 3: Before Committing
- [ ] All build configuration tests pass
- [ ] Windows certificate is EV or has built reputation
- [ ] macOS app is signed with Developer ID
- [ ] macOS app is notarized and stapled
- [ ] Linux packages are signed with GPG
- [ ] All signatures use timestamping
- [ ] Signing credentials in CI secrets only
- [ ] Build artifacts have checksums
- [ ] Dependencies are pinned
- [ ] Build logs don't expose secrets
- [ ] Windows SmartScreen passes
- [ ] macOS Gatekeeper passes
- [ ] Installer tested on clean systems
- [ ] Auto-update URLs are HTTPS
---
14. Summary
Your goal is to create cross-platform builds that are:
- Correctly Signed: Trusted by each operating system
- Platform Native: Respecting each platform's conventions
- Optimized: Reasonable file sizes, fast startup
You understand that cross-platform development requires: 1. Testing on each target platform (not just your development machine) 2. Proper code signing for user trust 3. Platform-specific configurations and dependencies 4. Awareness of distribution requirements
Build Reminder: ALWAYS test on each platform before release. ALWAYS sign your releases. ALWAYS verify signatures work correctly. When in doubt, consult references/security-examples.md for signing procedures.
Cross-Platform Builds Advanced Patterns
Build Matrix Configurations
Complete Tauri Build Matrix
name: Build Release
on:
push:
tags:
- 'v*'
jobs:
build:
strategy:
fail-fast: false
matrix:
include:
# Windows
- platform: windows-latest
target: x86_64-pc-windows-msvc
bundle: msi
artifact_ext: '.msi'
# macOS Intel
- platform: macos-latest
target: x86_64-apple-darwin
bundle: dmg
artifact_ext: '.dmg'
# macOS Apple Silicon
- platform: macos-latest
target: aarch64-apple-darwin
bundle: dmg
artifact_ext: '.dmg'
# Linux
- platform: ubuntu-22.04
target: x86_64-unknown-linux-gnu
bundle: deb
artifact_ext: '.deb'
- platform: ubuntu-22.04
target: x86_64-unknown-linux-gnu
bundle: appimage
artifact_ext: '.AppImage'
runs-on: ${{ matrix.platform }}
env:
TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
steps:
- uses: actions/checkout@v4
- name: Setup Rust
uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.target }}
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install Linux Dependencies
if: matrix.platform == 'ubuntu-22.04'
run: |
sudo apt-get update
sudo apt-get install -y \
libgtk-3-dev \
libwebkit2gtk-4.0-dev \
libappindicator3-dev \
librsvg2-dev \
patchelf
- name: Install Dependencies
run: npm ci
- name: Build
run: npm run tauri build -- --target ${{ matrix.target }} --bundles ${{ matrix.bundle }}
- name: Upload Artifact
uses: actions/upload-artifact@v3
with:
name: ${{ matrix.target }}-${{ matrix.bundle }}
path: src-tauri/target/${{ matrix.target }}/release/bundle/**/*${{ matrix.artifact_ext }}---
Conditional Compilation
Platform-Specific Features
// Cargo.toml
[target.'cfg(target_os = "windows")'.dependencies]
windows = { version = "0.48", features = ["Win32_Foundation", "Win32_UI_Shell"] }
[target.'cfg(target_os = "macos")'.dependencies]
objc = "0.2"
cocoa = "0.25"
[target.'cfg(target_os = "linux")'.dependencies]
dbus = "0.9"Platform-Specific Code
// System tray implementation
#[cfg(target_os = "macos")]
pub fn create_tray(app: &tauri::AppHandle) -> Result<(), Box<dyn std::error::Error>> {
use tauri::SystemTray;
// macOS uses template images
let tray = SystemTray::new()
.with_icon(tauri::Icon::Raw(include_bytes!("../icons/tray-Template.png").to_vec()));
Ok(())
}
#[cfg(target_os = "windows")]
pub fn create_tray(app: &tauri::AppHandle) -> Result<(), Box<dyn std::error::Error>> {
use tauri::SystemTray;
// Windows uses ICO
let tray = SystemTray::new()
.with_icon(tauri::Icon::Raw(include_bytes!("../icons/tray.ico").to_vec()));
Ok(())
}
#[cfg(target_os = "linux")]
pub fn create_tray(app: &tauri::AppHandle) -> Result<(), Box<dyn std::error::Error>> {
use tauri::SystemTray;
// Linux uses PNG
let tray = SystemTray::new()
.with_icon(tauri::Icon::Raw(include_bytes!("../icons/tray.png").to_vec()));
Ok(())
}Feature Flags for Platforms
// Cargo.toml
[features]
default = []
windows-console = [] # Show console on Windows
macos-transparent = [] # Enable transparency on macOS
// main.rs
fn main() {
#[cfg(all(target_os = "windows", not(feature = "windows-console")))]
{
// Hide console window
use windows::Win32::System::Console::FreeConsole;
unsafe { FreeConsole(); }
}
#[cfg(all(target_os = "macos", feature = "macos-transparent"))]
{
// Enable transparent window
}
tauri::Builder::default()
.run(tauri::generate_context!())
.expect("error while running application");
}---
Build Optimization
Release Profile Configuration
# Cargo.toml
[profile.release]
lto = true # Link-time optimization
codegen-units = 1 # Better optimization, slower compile
panic = "abort" # Smaller binary
strip = true # Strip symbols
opt-level = "z" # Optimize for size
# For better debugging in release
[profile.release-with-debug]
inherits = "release"
debug = true
strip = falseBundle Size Optimization
// tauri.conf.json
{
"tauri": {
"bundle": {
"resources": [
// Only include necessary resources
"assets/icons/*",
"assets/fonts/*.woff2"
],
"linux": {
"appimage": {
"bundleMediaFramework": false // Reduces size significantly
}
}
}
}
}Dependency Optimization
# Cargo.toml - Use minimal features
[dependencies]
serde = { version = "1.0", default-features = false, features = ["derive"] }
tokio = { version = "1", default-features = false, features = ["rt", "macros"] }
# Check dependency sizes
# cargo install cargo-bloat
# cargo bloat --release --crates---
Platform-Specific Installers
Windows NSIS Configuration
// tauri.conf.json
{
"tauri": {
"bundle": {
"windows": {
"nsis": {
"license": "./LICENSE.txt",
"installerIcon": "./icons/icon.ico",
"headerImage": "./icons/nsis-header.bmp",
"sidebarImage": "./icons/nsis-sidebar.bmp",
"installMode": "currentUser",
"languages": ["English", "German", "French"],
"displayLanguageSelector": true
}
}
}
}
}macOS DMG Configuration
// tauri.conf.json
{
"tauri": {
"bundle": {
"macOS": {
"dmg": {
"appPosition": { "x": 180, "y": 170 },
"applicationFolderPosition": { "x": 480, "y": 170 },
"windowSize": { "width": 660, "height": 400 }
}
}
}
}
}Linux Package Metadata
// tauri.conf.json
{
"tauri": {
"bundle": {
"linux": {
"deb": {
"depends": [
"libgtk-3-0",
"libwebkit2gtk-4.0-37",
"libappindicator3-1"
],
"section": "utils",
"priority": "optional"
}
},
"category": "Utility"
}
}
}---
Universal Binaries
macOS Universal Binary
jobs:
build-macos:
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- name: Setup Rust
uses: dtolnay/rust-toolchain@stable
with:
targets: x86_64-apple-darwin,aarch64-apple-darwin
- name: Build Intel
run: npm run tauri build -- --target x86_64-apple-darwin
- name: Build ARM
run: npm run tauri build -- --target aarch64-apple-darwin
- name: Create Universal Binary
run: |
mkdir -p target/universal-apple-darwin/release/bundle/macos
# Combine binaries
lipo -create \
target/x86_64-apple-darwin/release/bundle/macos/MyApp.app/Contents/MacOS/MyApp \
target/aarch64-apple-darwin/release/bundle/macos/MyApp.app/Contents/MacOS/MyApp \
-output MyApp-universal
# Copy app bundle from one architecture
cp -r target/x86_64-apple-darwin/release/bundle/macos/MyApp.app \
target/universal-apple-darwin/release/bundle/macos/
# Replace binary with universal
cp MyApp-universal \
target/universal-apple-darwin/release/bundle/macos/MyApp.app/Contents/MacOS/MyApp
- name: Sign Universal App
env:
SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
run: |
codesign --force --options runtime --sign "$SIGNING_IDENTITY" \
--deep target/universal-apple-darwin/release/bundle/macos/MyApp.app---
Resource Handling
Platform-Specific Resources
// Load platform-specific assets
fn get_icon_path() -> &'static str {
#[cfg(target_os = "windows")]
{ "icons/icon.ico" }
#[cfg(target_os = "macos")]
{ "icons/icon.icns" }
#[cfg(target_os = "linux")]
{ "icons/icon.png" }
}
// Platform-specific config locations
fn get_config_dir() -> std::path::PathBuf {
use directories::ProjectDirs;
let dirs = ProjectDirs::from("com", "company", "app").unwrap();
#[cfg(target_os = "linux")]
{
// Follow XDG spec on Linux
dirs.config_dir().to_path_buf()
}
#[cfg(target_os = "macos")]
{
// Use Application Support on macOS
dirs.data_dir().to_path_buf()
}
#[cfg(target_os = "windows")]
{
// Use %APPDATA% on Windows
dirs.config_dir().to_path_buf()
}
}Embedded Resources
// Embed files at compile time
const LICENSE: &str = include_str!("../LICENSE");
const DEFAULT_CONFIG: &[u8] = include_bytes!("../assets/default-config.json");
// Platform-specific embedded resources
#[cfg(target_os = "windows")]
const ICON: &[u8] = include_bytes!("../icons/icon.ico");
#[cfg(target_os = "macos")]
const ICON: &[u8] = include_bytes!("../icons/icon.icns");
#[cfg(target_os = "linux")]
const ICON: &[u8] = include_bytes!("../icons/icon.png");---
Testing Cross-Platform
Cross-Platform Test Matrix
name: Test
on: [push, pull_request]
jobs:
test:
strategy:
matrix:
os: [ubuntu-22.04, windows-latest, macos-latest]
rust: [stable]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- name: Setup Rust
uses: dtolnay/rust-toolchain@master
with:
toolchain: ${{ matrix.rust }}
- name: Install Linux Dependencies
if: matrix.os == 'ubuntu-22.04'
run: |
sudo apt-get update
sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev
- name: Run Tests
run: cargo test --all-features
- name: Run Platform-Specific Tests
run: cargo test --all-features -- --ignored
env:
RUN_PLATFORM_TESTS: truePlatform-Specific Tests
#[cfg(test)]
mod tests {
#[test]
#[cfg(target_os = "windows")]
fn test_windows_registry() {
// Windows-specific test
}
#[test]
#[cfg(target_os = "macos")]
fn test_macos_keychain() {
// macOS-specific test
}
#[test]
#[cfg(target_os = "linux")]
fn test_linux_xdg() {
// Linux-specific test
}
#[test]
#[ignore] // Run only with RUN_PLATFORM_TESTS=true
fn test_platform_integration() {
if std::env::var("RUN_PLATFORM_TESTS").is_err() {
return;
}
// Integration test requiring platform setup
}
}Cross-Platform Builds Security Examples
Windows Code Signing
Certificate Setup in CI
jobs:
sign-windows:
runs-on: windows-latest
environment: code-signing
steps:
- uses: actions/checkout@v4
- name: Import Code Signing Certificate
env:
CERTIFICATE_BASE64: ${{ secrets.WINDOWS_CERTIFICATE_BASE64 }}
CERTIFICATE_PASSWORD: ${{ secrets.WINDOWS_CERTIFICATE_PASSWORD }}
run: |
# Decode certificate
$certBytes = [Convert]::FromBase64String($env:CERTIFICATE_BASE64)
$certPath = "$env:RUNNER_TEMP\certificate.pfx"
[IO.File]::WriteAllBytes($certPath, $certBytes)
# Import to Windows certificate store
$securePassword = ConvertTo-SecureString $env:CERTIFICATE_PASSWORD -AsPlainText -Force
Import-PfxCertificate `
-FilePath $certPath `
-CertStoreLocation Cert:\CurrentUser\My `
-Password $securePassword
# Cleanup
Remove-Item $certPath -Force
- name: Build and Sign with Tauri
env:
TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
run: npm run tauri build
- name: Verify Signature
run: |
$exePath = Get-ChildItem -Recurse -Filter "*.exe" | Where-Object { $_.Name -eq "MyApp.exe" } | Select-Object -First 1
$signature = Get-AuthenticodeSignature $exePath.FullName
if ($signature.Status -ne "Valid") {
Write-Error "Signature validation failed: $($signature.StatusMessage)"
exit 1
}
Write-Host "Signature valid: $($signature.SignerCertificate.Subject)"Manual Signing with SignTool
# Sign an executable
signtool sign /fd SHA256 /tr http://timestamp.digicert.com /td SHA256 /sha1 "THUMBPRINT" MyApp.exe
# Sign multiple files
Get-ChildItem -Recurse -Include *.exe,*.dll | ForEach-Object {
signtool sign /fd SHA256 /tr http://timestamp.digicert.com /td SHA256 /sha1 "THUMBPRINT" $_.FullName
}
# Verify signature
signtool verify /pa /v MyApp.exe---
macOS Code Signing and Notarization
Complete Signing Workflow
jobs:
sign-macos:
runs-on: macos-latest
environment: code-signing
steps:
- uses: actions/checkout@v4
- name: Setup Keychain
env:
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE_BASE64 }}
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}
run: |
# Create temporary keychain
KEYCHAIN_PATH="$RUNNER_TEMP/build.keychain-db"
security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
security set-keychain-settings -lut 21600 "$KEYCHAIN_PATH"
security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
# Import certificate
CERT_PATH="$RUNNER_TEMP/certificate.p12"
echo "$APPLE_CERTIFICATE" | base64 --decode > "$CERT_PATH"
security import "$CERT_PATH" -P "$APPLE_CERTIFICATE_PASSWORD" -A -t cert -f pkcs12 -k "$KEYCHAIN_PATH"
# Set keychain for codesigning
security list-keychain -d user -s "$KEYCHAIN_PATH"
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
# Cleanup
rm "$CERT_PATH"
- name: Build with Tauri
env:
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
run: |
# Set signing identity for Tauri
export APPLE_SIGNING_IDENTITY="$APPLE_SIGNING_IDENTITY"
npm run tauri build
- name: Notarize Application
env:
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_APP_PASSWORD: ${{ secrets.APPLE_APP_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
run: |
APP_PATH=$(find src-tauri/target -name "*.app" -type d | head -1)
DMG_PATH=$(find src-tauri/target -name "*.dmg" | head -1)
# Create ZIP for notarization
ditto -c -k --keepParent "$APP_PATH" app.zip
# Submit for notarization
xcrun notarytool submit app.zip \
--apple-id "$APPLE_ID" \
--password "$APPLE_APP_PASSWORD" \
--team-id "$APPLE_TEAM_ID" \
--wait
# Staple the app
xcrun stapler staple "$APP_PATH"
# Recreate DMG with stapled app
# ... (rebuild DMG)
rm app.zip
- name: Verify Signing
run: |
APP_PATH=$(find src-tauri/target -name "*.app" -type d | head -1)
# Verify codesign
codesign --verify --deep --strict "$APP_PATH"
# Verify notarization
spctl --assess --type execute "$APP_PATH"
xcrun stapler validate "$APP_PATH"
- name: Cleanup Keychain
if: always()
run: security delete-keychain "$RUNNER_TEMP/build.keychain-db"Entitlements Configuration
<!-- entitlements.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>
<!-- Required for JIT compilation (WebView) -->
<key>com.apple.security.cs.allow-jit</key>
<true/>
<!-- Required for WebView -->
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>
<!-- Network access -->
<key>com.apple.security.network.client</key>
<true/>
<!-- Hardened runtime -->
<key>com.apple.security.cs.disable-library-validation</key>
<true/>
</dict>
</plist>---
Linux Package Signing
GPG Signing for DEB Packages
jobs:
sign-linux:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Import GPG Key
env:
GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
run: |
echo "$GPG_PRIVATE_KEY" | gpg --import
echo "$GPG_PASSPHRASE" | gpg --passphrase-fd 0 --pinentry-mode loopback --sign --armor /dev/null
- name: Build
run: npm run tauri build
- name: Sign DEB Package
env:
GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
run: |
DEB_FILE=$(find src-tauri/target -name "*.deb" | head -1)
# Sign with dpkg-sig
dpkg-sig --sign builder -g "--passphrase $GPG_PASSPHRASE --pinentry-mode loopback" "$DEB_FILE"
# Verify signature
dpkg-sig --verify "$DEB_FILE"
- name: Generate Checksums
run: |
cd src-tauri/target/release/bundle/deb
sha256sum *.deb > SHA256SUMS
gpg --armor --detach-sign SHA256SUMSAppImage Signing
# Sign AppImage with GPG
gpg --armor --detach-sign MyApp.AppImage
# Users can verify with
gpg --verify MyApp.AppImage.asc MyApp.AppImage---
Secure Build Environment
Environment Isolation
jobs:
secure-build:
runs-on: ubuntu-latest
container:
image: rust:1.70
options: --user root
steps:
- uses: actions/checkout@v4
- name: Verify Environment
run: |
# Check no unexpected tools
which curl wget || true
# Check environment variables
env | grep -v GITHUB | sort
- name: Build in Clean Environment
run: |
# Install only required dependencies
apt-get update
apt-get install -y --no-install-recommends \
libgtk-3-dev libwebkit2gtk-4.0-dev
# Build
cargo build --releaseReproducible Builds
# Cargo.toml
[profile.release]
lto = true
codegen-units = 1
strip = "none" # Keep for reproducibility verification
# Build with locked dependencies
# cargo build --release --locked- name: Verify Reproducible Build
run: |
# Build twice and compare
cargo build --release --locked
cp target/release/myapp myapp-build1
cargo clean
cargo build --release --locked
cp target/release/myapp myapp-build2
# Compare binaries
sha256sum myapp-build1 myapp-build2---
Credential Management
Storing Certificates
# Encode certificate for GitHub Secrets
base64 -i certificate.pfx | tr -d '\n' > certificate_base64.txt
# For macOS p12
base64 -i certificate.p12 | tr -d '\n' > certificate_base64.txtTauri Update Keys
# Generate update key pair
npm run tauri signer generate -- -w ~/.tauri/myapp.key
# Store in GitHub Secrets:
# TAURI_PRIVATE_KEY: contents of ~/.tauri/myapp.key
# TAURI_KEY_PASSWORD: the password you used
# Public key goes in tauri.conf.jsonKey Rotation Procedure
1. Generate new keys before old ones expire 2. Test signing with new keys on staging 3. Update CI secrets with new keys 4. Verify builds work with new keys 5. Revoke old keys after transition period
---
Artifact Verification
Checksum Generation
- name: Generate Checksums
run: |
cd dist
# Generate multiple checksum types
sha256sum * > SHA256SUMS
sha512sum * > SHA512SUMS
# Sign checksums
gpg --armor --detach-sign SHA256SUMS
- name: Upload Checksums
uses: actions/upload-artifact@v3
with:
name: checksums
path: |
dist/SHA256SUMS
dist/SHA256SUMS.asc
dist/SHA512SUMSSignature Verification Scripts
#!/bin/bash
# verify-release.sh - For users to verify downloads
# Verify GPG signature on checksums
gpg --verify SHA256SUMS.asc SHA256SUMS
# Verify file checksum
sha256sum -c SHA256SUMS --ignore-missing
# Verify code signature (Windows)
# signtool verify /pa MyApp.exe
# Verify code signature (macOS)
# codesign --verify --deep --strict MyApp.app
# spctl --assess --type execute MyApp.app