diff --git a/.github/workflows/build-desktop.yaml b/.github/workflows/build-desktop.yaml index 624c4790b..6e749da4a 100644 --- a/.github/workflows/build-desktop.yaml +++ b/.github/workflows/build-desktop.yaml @@ -22,7 +22,7 @@ on: default: "" type: string skip_targets: - description: Comma-separated platforms or targets to skip, such as windows, macos-arm64, linux-x64. + description: Comma-separated platforms or targets to skip, such as windows, macos, linux-x64. required: false default: "" type: string @@ -241,7 +241,7 @@ jobs: uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 with: toolchain: "1.93.0" - targets: ${{ matrix.platform == 'macos' && (matrix.arch == 'arm64' && 'aarch64-apple-darwin' || 'x86_64-apple-darwin') || (matrix.arch == 'arm64' && 'aarch64-unknown-linux-gnu' || 'x86_64-unknown-linux-gnu') }} + targets: ${{ matrix.platform == 'macos' && 'aarch64-apple-darwin,x86_64-apple-darwin' || (matrix.arch == 'arm64' && 'aarch64-unknown-linux-gnu' || 'x86_64-unknown-linux-gnu') }} - name: Install MSVC ARM64 build tools if: matrix.platform == 'windows' && matrix.arch == 'arm64' @@ -468,8 +468,8 @@ jobs: cargo run --locked --quiet --manifest-path ${{ github.workspace }}/_ci/tools/ci/Cargo.toml -- build-desktop --step prepare_artifacts_unix - - name: Normalize updater YAML (arm64) - if: matrix.arch == 'arm64' + - name: Normalize updater YAML (macOS) + if: matrix.platform == 'macos' run: >- cargo run --locked --quiet --manifest-path ${{ github.workspace }}/_ci/tools/ci/Cargo.toml -- build-desktop --step normalise_updater_yaml diff --git a/fluxer_api/src/api/download/DownloadService.ts b/fluxer_api/src/api/download/DownloadService.ts index 5fe875d6c..638c3d603 100644 --- a/fluxer_api/src/api/download/DownloadService.ts +++ b/fluxer_api/src/api/download/DownloadService.ts @@ -82,8 +82,8 @@ type FormatMapping = { const FORMAT_MAPPINGS: Record>> = { setup: {win32: {ext: '.exe', arch: {x64: 'x64', arm64: 'arm64'}}}, - dmg: {darwin: {ext: '.dmg', arch: {x64: 'x64', arm64: 'arm64'}}}, - zip: {darwin: {ext: '.zip', arch: {x64: 'x64', arm64: 'arm64'}}}, + dmg: {darwin: {ext: '.dmg', arch: {x64: ['universal', 'x64'], arm64: ['universal', 'arm64']}}}, + zip: {darwin: {ext: '.zip', arch: {x64: ['universal', 'x64'], arm64: ['universal', 'arm64']}}}, appimage: {linux: {ext: '.AppImage', arch: {x64: 'x86_64', arm64: ['aarch64', 'arm64']}}}, deb: {linux: {ext: '.deb', arch: {x64: 'amd64', arm64: 'arm64'}}}, rpm: {linux: {ext: '.rpm', arch: {x64: 'x86_64', arm64: 'aarch64'}}}, diff --git a/fluxer_desktop/electron-builder.config.cjs b/fluxer_desktop/electron-builder.config.cjs index 7acca26f8..586f196d4 100644 --- a/fluxer_desktop/electron-builder.config.cjs +++ b/fluxer_desktop/electron-builder.config.cjs @@ -31,15 +31,22 @@ const provisioningProfile = isCanary ? 'build_resources/profiles/Fluxer_Canary.provisionprofile' : 'build_resources/profiles/Fluxer.provisionprofile'; const supportedTargetArchs = ['x64', 'arm64']; +const supportedMacTargetArchs = [...supportedTargetArchs, 'universal']; const electronArch = process.env.ELECTRON_ARCH; -const cliTargetArch = supportedTargetArchs.find((arch) => process.argv.includes(`--${arch}`)) || null; +const cliTargetArch = supportedMacTargetArchs.find((arch) => process.argv.includes(`--${arch}`)) || null; const targetNativeArch = electronArch || cliTargetArch; -if (electronArch && !supportedTargetArchs.includes(electronArch)) { +if (electronArch && !supportedMacTargetArchs.includes(electronArch)) { throw new Error(`Unsupported ELECTRON_ARCH: ${electronArch}`); } -const targetArchs = electronArch ? [electronArch] : supportedTargetArchs; +if (targetNativeArch === 'universal' && targetPlatform !== 'darwin') { + throw new Error(`ELECTRON_ARCH=universal is only supported for macOS builds, received platform ${targetPlatform}`); +} + +const targetArchs = + electronArch && electronArch !== 'universal' ? [electronArch] : supportedTargetArchs; +const macTargetArchs = targetNativeArch ? [targetNativeArch] : supportedTargetArchs; const winTargets = [ { target: 'dir', @@ -1190,11 +1197,11 @@ module.exports = { target: [ { target: 'dmg', - arch: targetArchs, + arch: macTargetArchs, }, { target: 'zip', - arch: targetArchs, + arch: macTargetArchs, }, ], extendInfo: { diff --git a/tools/ci/src/desktop.rs b/tools/ci/src/desktop.rs index 22ed44d28..9cb3e43ba 100644 --- a/tools/ci/src/desktop.rs +++ b/tools/ci/src/desktop.rs @@ -32,6 +32,7 @@ const PUBLIC_DL_BASE: &str = "https://api.fluxer.app/dl"; const PNPM_VERSION: &str = "10.29.3"; const RUST_TOOLCHAIN: &str = "1.93.0"; const DEFAULT_DESKTOP_VARIANT: &str = "default"; +pub(crate) const MACOS_UNIVERSAL_ARCH: &str = "universal"; const WINDOWS_GAME_CAPTURE_DESKTOP_VARIANT: &str = "windows-game-capture"; #[derive(Debug, Args, Clone)] @@ -53,10 +54,6 @@ pub struct BuildDesktopArgs { #[arg(long)] skip_macos: Option, #[arg(long)] - skip_macos_x64: Option, - #[arg(long)] - skip_macos_arm64: Option, - #[arg(long)] skip_linux: Option, #[arg(long)] skip_linux_x64: Option, @@ -136,17 +133,10 @@ const PLATFORMS: &[Platform] = &[ }, Platform { platform: "macos", - arch: "x64", + arch: MACOS_UNIVERSAL_ARCH, desktop_variant: DEFAULT_DESKTOP_VARIANT, os: "fluxer-desktop-macos-arm64", - electron_arch: "x64", - }, - Platform { - platform: "macos", - arch: "arm64", - desktop_variant: DEFAULT_DESKTOP_VARIANT, - os: "fluxer-desktop-macos-arm64", - electron_arch: "arm64", + electron_arch: MACOS_UNIVERSAL_ARCH, }, Platform { platform: "linux", @@ -381,8 +371,7 @@ fn skip_target_set(args: &BuildDesktopArgs) -> Result> { "windows-x64", "windows-arm64", "macos", - "macos-x64", - "macos-arm64", + "macos-universal", "linux", "linux-x64", "linux-arm64", @@ -424,11 +413,7 @@ fn skip_platform( || (platform.arch == "arm64" && flag(&args.skip_windows_arm64, "SKIP_WINDOWS_ARM64")) } - "macos" => { - flag(&args.skip_macos, "SKIP_MACOS") - || (platform.arch == "x64" && flag(&args.skip_macos_x64, "SKIP_MACOS_X64")) - || (platform.arch == "arm64" && flag(&args.skip_macos_arm64, "SKIP_MACOS_ARM64")) - } + "macos" => flag(&args.skip_macos, "SKIP_MACOS"), "linux" => { flag(&args.skip_linux, "SKIP_LINUX") || (platform.arch == "x64" && flag(&args.skip_linux_x64, "SKIP_LINUX_X64")) @@ -2618,7 +2603,7 @@ fn is_unix_upload_artifact(name: &str) -> bool { fn normalise_updater_yaml_step() -> Result<()> { if env::var("PLATFORM").unwrap_or_default() == "macos" - && env::var("ARCH").unwrap_or_default() == "arm64" + && env::var("ARCH").unwrap_or_default() == MACOS_UNIVERSAL_ARCH { let source = Path::new("upload_staging/latest-mac.yml"); let target = Path::new("upload_staging/latest-mac-arm64.yml"); @@ -2768,30 +2753,32 @@ fn build_payload_step() -> Result<()> { continue; } }; - let mut dest = payload_root - .join(&channel) - .join(platform) - .join(&identity.arch); - if let Some(segment) = desktop_variant_path_segment(&identity.desktop_variant) { - dest = dest.join(segment); + for published_arch in published_arches(platform, &identity.arch) { + let mut dest = payload_root + .join(&channel) + .join(platform) + .join(published_arch); + if let Some(segment) = desktop_variant_path_segment(&identity.desktop_variant) { + dest = dest.join(segment); + } + fs::create_dir_all(&dest)?; + copy_dir_contents(&dir, &dest)?; + let manifest = build_desktop_manifest( + &dest, + &PayloadManifestInput { + channel: channel.clone(), + platform: platform.to_string(), + arch: published_arch.to_string(), + desktop_variant: identity.desktop_variant.clone(), + version: version.clone(), + pub_date: pub_date.clone(), + }, + )?; + if platform == "darwin" { + write_macos_releases(&dest, &s3_prefix, &channel, &manifest)?; + } + write_json_pretty(&dest.join("manifest.json"), &manifest)?; } - fs::create_dir_all(&dest)?; - copy_dir_contents(&dir, &dest)?; - let manifest = build_desktop_manifest( - &dest, - &PayloadManifestInput { - channel: channel.clone(), - platform: platform.to_string(), - arch: identity.arch.clone(), - desktop_variant: identity.desktop_variant.clone(), - version: version.clone(), - pub_date: pub_date.clone(), - }, - )?; - if platform == "darwin" { - write_macos_releases(&dest, &s3_prefix, &channel, &manifest)?; - } - write_json_pretty(&dest.join("manifest.json"), &manifest)?; } println!("Payload tree:"); @@ -3003,6 +2990,17 @@ fn manifest_file_entry(kind: &str, file: &Path) -> Result { } } +fn published_arches(platform: &str, arch: &str) -> Vec<&'static str> { + if platform == "darwin" && arch == MACOS_UNIVERSAL_ARCH { + return vec!["x64", "arm64"]; + } + match arch { + "x64" => vec!["x64"], + "arm64" => vec!["arm64"], + other => panic!("Unsupported desktop arch: {other}"), + } +} + fn write_macos_releases( dest: &Path, s3_prefix: &str, @@ -3332,8 +3330,6 @@ mod tests { skip_windows_x64: Some("false".to_string()), skip_windows_arm64: Some("false".to_string()), skip_macos: Some("false".to_string()), - skip_macos_x64: Some("false".to_string()), - skip_macos_arm64: Some("false".to_string()), skip_linux: Some("false".to_string()), skip_linux_x64: Some("false".to_string()), skip_linux_arm64: Some("false".to_string()), @@ -3414,7 +3410,7 @@ mod tests { fn matrix_selects_one_row_per_platform_arch_by_default() { let selected = selected_platforms(&matrix_args()).unwrap(); - assert_eq!(selected.len(), 6); + assert_eq!(selected.len(), 5); assert_eq!( selected .iter() @@ -3462,7 +3458,7 @@ mod tests { .iter() .all(|platform| platform.platform != "windows") ); - assert_eq!(selected.len(), 4); + assert_eq!(selected.len(), 3); } #[test] diff --git a/tools/ci/src/desktop_native.rs b/tools/ci/src/desktop_native.rs index 9066b647f..3faddd543 100644 --- a/tools/ci/src/desktop_native.rs +++ b/tools/ci/src/desktop_native.rs @@ -3,6 +3,7 @@ use crate::common::{ CommandSpec, command_succeeds, output_text, remove_file_if_exists, run_command, }; +use crate::desktop::MACOS_UNIVERSAL_ARCH; use anyhow::{Context, Result, anyhow, bail, ensure}; use clap::Args; use sha2::{Digest, Sha256}; @@ -358,9 +359,23 @@ fn ensure_pkg_config(requirement: &PkgConfigRequirement) -> Result<()> { fn build_rust_node_addon(addon_root: &Path, addon: &DesktopNativeAddon) -> Result { let platform = current_platform(); let arch = electron_arch(); - let tag = platform_tag(&platform, &arch)?; - let target = rust_target_for_platform(&platform, &arch)?; - let target_root = cargo_target_root_for_build(addon_root, &platform)?; + if platform == "darwin" && arch == MACOS_UNIVERSAL_ARCH { + let arm64 = build_rust_node_addon_for_arch(addon_root, addon, &platform, "arm64")?; + build_rust_node_addon_for_arch(addon_root, addon, &platform, "x64")?; + return Ok(arm64); + } + build_rust_node_addon_for_arch(addon_root, addon, &platform, &arch) +} + +fn build_rust_node_addon_for_arch( + addon_root: &Path, + addon: &DesktopNativeAddon, + platform: &str, + arch: &str, +) -> Result { + let tag = platform_tag(platform, arch)?; + let target = rust_target_for_platform(platform, arch)?; + let target_root = cargo_target_root_for_build(addon_root, platform)?; let mut args = vec![ OsString::from("build"), OsString::from("--release"), @@ -396,10 +411,7 @@ fn build_rust_node_addon(addon_root: &Path, addon: &DesktopNativeAddon) -> Resul let source = target_root .join(&target) .join("release") - .join(cargo_dynamic_library_file_name( - addon.crate_name, - &platform, - )?); + .join(cargo_dynamic_library_file_name(addon.crate_name, platform)?); let out_file = addon_root.join(format!("{}.{}.node", addon.node_file_stem, tag)); ensure!( source.exists(), @@ -418,8 +430,8 @@ fn build_rust_node_addon(addon_root: &Path, addon: &DesktopNativeAddon) -> Resul "expected {} to exist after copy", out_file.display() ); - sign_macos_node_addon(&out_file, &platform)?; - assert_no_redistributable_runtime_imports(&out_file, &platform)?; + sign_macos_node_addon(&out_file, platform)?; + assert_no_redistributable_runtime_imports(&out_file, platform)?; Ok(BuiltNodeAddon { out_file, source,