From 3bbea40446de4ee51debeaf97f39515f94270367 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADtor=20Vasconcellos?= Date: Tue, 17 Sep 2024 02:27:42 +0000 Subject: [PATCH] [ENG-1840, ENG-1842] Add native dependencies for iOS (#2693) * Implement dowload of mobile native deps - Add a spinner animation to `pnpm prep` - Change abandoned dependency @iarna/toml with smol-toml - Validate cargo config.toml after generating it from mustache template - Disabled HTTP2 downloads with udici, it is very broken * Initial ios native deps xcframework logic * Remove logic for handling dynamic iOS libs, using static libs now * Fix PATH in build-rust.sh - Remove app.json * Restore crates/images/src/pdf.rs * minor fix .editorconfig * Finally ios successfully compiles with ffmpeg enabled - Change SDCore.podspec to add extra libraries required by ffmpeg - Fix heif linking for ios in config.toml template - Add symlink logic for extra libraries required to compile ios in build-rust.sh --- .cargo/config.toml.mustache | 23 +++ .editorconfig | 6 + .gitignore | 1 + .prettierrc.js | 4 +- .vscode/settings.json | 2 - apps/mobile/modules/sd-core/core/Cargo.toml | 12 +- apps/mobile/modules/sd-core/core/src/lib.rs | 2 + .../mobile/modules/sd-core/ios/SDCore.podspec | 67 ++++---- apps/mobile/modules/sd-core/ios/build-rust.sh | 52 ++++-- package.json | 2 +- pnpm-lock.yaml | 30 ++-- scripts/package.json | 5 +- scripts/preprep.mjs | 153 +++++++++++++----- scripts/tauri.mjs | 4 +- scripts/utils/consts.mjs | 9 ++ scripts/utils/fetch.mjs | 5 +- scripts/utils/rustup.mjs | 12 ++ scripts/utils/spinner.mjs | 44 +++++ 18 files changed, 324 insertions(+), 109 deletions(-) create mode 100644 scripts/utils/rustup.mjs create mode 100644 scripts/utils/spinner.mjs diff --git a/.cargo/config.toml.mustache b/.cargo/config.toml.mustache index 8236e42c8f33..7119b15ef3b8 100644 --- a/.cargo/config.toml.mustache +++ b/.cargo/config.toml.mustache @@ -24,6 +24,29 @@ rustflags = ["-L", "{{{nativeDeps}}}/lib", "-Csplit-debuginfo=unpacked"] [target.aarch64-apple-darwin.heif] rustc-link-search = ["{{{nativeDeps}}}/lib"] rustc-link-lib = ["heif"] + +{{#hasiOS}} +[target.aarch64-apple-ios] +rustflags = ["-L", "{{{mobileNativeDeps}}}/aarch64-apple-ios/lib", "-Csplit-debuginfo=unpacked"] + +[target.aarch64-apple-ios.heif] +rustc-link-search = ["{{{mobileNativeDeps}}}/aarch64-apple-ios/lib"] +rustc-link-lib = ["static:+bundle=heif"] + +[target.aarch64-apple-ios-sim] +rustflags = ["-L", "{{{mobileNativeDeps}}}/aarch64-apple-ios-sim/lib", "-Csplit-debuginfo=unpacked"] + +[target.aarch64-apple-ios-sim.heif] +rustc-link-search = ["{{{mobileNativeDeps}}}/aarch64-apple-ios-sim/lib"] +rustc-link-lib = ["static:+bundle=heif"] + +[target.x86_64-apple-ios] +rustflags = ["-L", "{{{mobileNativeDeps}}}/x86_64-apple-ios/lib", "-Csplit-debuginfo=unpacked"] + +[target.x86_64-apple-ios.heif] +rustc-link-search = ["{{{mobileNativeDeps}}}/x86_64-apple-ios/lib"] +rustc-link-lib = ["static:+bundle=heif"] +{{/hasiOS}} {{/isMacOS}} {{#isWin}} diff --git a/.editorconfig b/.editorconfig index 34150bb03fc7..a647be8769c5 100644 --- a/.editorconfig +++ b/.editorconfig @@ -77,6 +77,12 @@ indent_style = space indent_size = 4 indent_style = space +# Ruby +# http://www.caliban.org/ruby/rubyguide.shtml#indentation +[*.{rb,podspec}] +indent_size = 2 +indent_style = space + # YAML # http://yaml.org/spec/1.2/2009-07-21/spec.html#id2576668 [*.{yaml,yml}] diff --git a/.gitignore b/.gitignore index a3341ae15b75..ee85b90abaa3 100644 --- a/.gitignore +++ b/.gitignore @@ -298,6 +298,7 @@ packages/turbo-server/data/ packages/turbo-server/uploads/ apps/*/stats.html apps/.deps +apps/mobile/.deps apps/releases/.vscode apps/desktop/src-tauri/tauri.conf.patch.json apps/desktop/src-tauri/*.dll diff --git a/.prettierrc.js b/.prettierrc.js index 56224606caae..f57e12019fe2 100644 --- a/.prettierrc.js +++ b/.prettierrc.js @@ -1,3 +1,5 @@ +const path = require('node:path'); + /** * {@type require('prettier').Config} */ @@ -24,6 +26,6 @@ module.exports = { ], importOrderParserPlugins: ['typescript', 'jsx', 'decorators'], importOrderTypeScriptVersion: '5.0.0', - tailwindConfig: './packages/ui/tailwind.config.js', + tailwindConfig: path.resolve(path.join(__dirname, 'packages/ui/tailwind.config.js')), plugins: ['@ianvs/prettier-plugin-sort-imports', 'prettier-plugin-tailwindcss'] }; diff --git a/.vscode/settings.json b/.vscode/settings.json index 625b44315cf2..5f8fde47a476 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -106,7 +106,5 @@ "i18n-ally.keystyle": "flat", // You need to add this to your locale settings file "i18n-ally.translate.google.apiKey": "xxx" "i18n-ally.translate.engines": ["google"], - "prettier.configPath": ".prettierrc.js", - "prettier.prettierPath": "./node_modules/prettier", "evenBetterToml.taplo.configFile.path": ".taplo.toml" } diff --git a/apps/mobile/modules/sd-core/core/Cargo.toml b/apps/mobile/modules/sd-core/core/Cargo.toml index de8c145030c0..7165278deca7 100644 --- a/apps/mobile/modules/sd-core/core/Cargo.toml +++ b/apps/mobile/modules/sd-core/core/Cargo.toml @@ -7,9 +7,15 @@ license.workspace = true repository.workspace = true rust-version = "1.64" -[dependencies] -# Spacedrive Sub-crates -sd-core = { path = "../../../../../core", features = ["mobile"], default-features = false } +[target.'cfg(target_os = "android")'.dependencies] +sd-core = { default-features = false, features = ["mobile"], path = "../../../../../core" } + +[target.'cfg(target_os = "ios")'.dependencies] +sd-core = { default-features = false, features = [ + "ffmpeg", + "heif", + "mobile" +], path = "../../../../../core" } # Workspace dependencies futures = { workspace = true } diff --git a/apps/mobile/modules/sd-core/core/src/lib.rs b/apps/mobile/modules/sd-core/core/src/lib.rs index 575447ca4556..e5cc08988486 100644 --- a/apps/mobile/modules/sd-core/core/src/lib.rs +++ b/apps/mobile/modules/sd-core/core/src/lib.rs @@ -1,3 +1,5 @@ +#![cfg(any(target_os = "android", target_os = "ios"))] + use futures::{future::join_all, StreamExt}; use futures_channel::mpsc; use once_cell::sync::{Lazy, OnceCell}; diff --git a/apps/mobile/modules/sd-core/ios/SDCore.podspec b/apps/mobile/modules/sd-core/ios/SDCore.podspec index 02dc90a6269a..4061fb0a106f 100644 --- a/apps/mobile/modules/sd-core/ios/SDCore.podspec +++ b/apps/mobile/modules/sd-core/ios/SDCore.podspec @@ -1,40 +1,51 @@ -# -# You will probs wanna add `use_frameworks! :linkage => :static` into your `ios/Podfile` as well. -# - -require 'json' +require "json" Pod::Spec.new do |s| - s.name = 'SDCore' - s.version = '0.0.0' - s.summary = 'Spacedrive core for React Native' - s.description = 'Spacedrive core for React Native' - s.author = 'Oscar Beaumont' - s.license = 'APGL-3.0' - s.platform = :ios, '14.0' - s.source = { git: 'https://github.com/spacedriveapp/spacedrive' } - s.homepage = 'https://www.spacedrive.com' + s.name = "SDCore" + s.version = "0.0.0" + s.summary = "Spacedrive core for React Native" + s.description = "Spacedrive core for React Native" + s.author = "Spacedrive Technology Inc" + s.license = "AGPL-3.0" + s.platform = :ios, "14.0" + s.source = { git: "https://github.com/spacedriveapp/spacedrive" } + s.homepage = "https://www.spacedrive.com" s.static_framework = true - s.dependency 'ExpoModulesCore' + s.dependency "ExpoModulesCore" s.pod_target_xcconfig = { - 'DEFINES_MODULE' => 'YES', - 'SWIFT_COMPILATION_MODE' => 'wholemodule' + "DEFINES_MODULE" => "YES", + "SWIFT_COMPILATION_MODE" => "wholemodule", + } + + s.script_phase = { + :name => "Build Spacedrive Core!", + :script => "exec \"${PODS_TARGET_SRCROOT}/build-rust.sh\"", + :execution_position => :before_compile, } - s.script_phase = { - :name => 'Build Spacedrive Core!', - :script => 'env -i SPACEDRIVE_CI=$SPACEDRIVE_CI CONFIGURATION=$CONFIGURATION PLATFORM_NAME=$PLATFORM_NAME ${PODS_TARGET_SRCROOT}/build-rust.sh', - :execution_position => :before_compile - } + # Add libraries + ffmpeg_libraries = [ + "-lmp3lame", "-lsoxr", "-ltheora", "-lopus", "-lvorbisenc", "-lx265", + "-lpostproc", "-ltheoraenc", "-ltheoradec", "-lde265", "-lvorbisfile", + "-logg", "-lSvtAv1Enc", "-lvpx", "-lhdr10plus", "-lx264", "-lvorbis", + "-lzimg", "-lsoxr-lsr", "-liconv", "-lbz2", "-llzma" + ].join(' ') - s.xcconfig = { - 'LIBRARY_SEARCH_PATHS' => '"' + JSON.parse(`cargo metadata`)["target_directory"].to_s + '"', - 'OTHER_LDFLAGS[sdk=iphoneos*]' => '$(inherited) -lsd_mobile_ios', - 'OTHER_LDFLAGS[sdk=iphonesimulator*]' => '$(inherited) -lsd_mobile_iossim' - } + # Add frameworks + ffmpeg_frameworks = [ + "-framework AudioToolbox", + "-framework VideoToolbox", + "-framework AVFoundation" + ].join(' ') + + s.xcconfig = { + "LIBRARY_SEARCH_PATHS" => '"' + JSON.parse(`cargo metadata`)["target_directory"].to_s + '"', + "OTHER_LDFLAGS[sdk=iphoneos*]" => "$(inherited) -lsd_mobile_ios #{ffmpeg_libraries} #{ffmpeg_frameworks}", + "OTHER_LDFLAGS[sdk=iphonesimulator*]" => "$(inherited) -lsd_mobile_iossim #{ffmpeg_libraries} #{ffmpeg_frameworks}", + } s.source_files = "**/*.{h,m,mm,swift,hpp,cpp}" - s.module_map = "#{s.name}.modulemap" + s.module_map = "#{s.name}.modulemap" end diff --git a/apps/mobile/modules/sd-core/ios/build-rust.sh b/apps/mobile/modules/sd-core/ios/build-rust.sh index a6a5510653e0..e76842270958 100755 --- a/apps/mobile/modules/sd-core/ios/build-rust.sh +++ b/apps/mobile/modules/sd-core/ios/build-rust.sh @@ -13,6 +13,22 @@ err() { exit 1 } +symlink_libs() { + if [ $# -ne 2 ]; then + err "Invalid number of arguments. Usage: symlink_libs " + fi + + if [ ! -d "$1" ]; then + err "Directory '$1' does not exist." + fi + + if [ ! -d "$2" ]; then + err "Directory '$2' does not exist." + fi + + find "$1" -type f -name '*.a' -exec ln -sf "{}" "$2" \; +} + if [ -z "${HOME:-}" ]; then HOME="$(CDPATH='' cd -- "$(osascript -e 'set output to (POSIX path of (path to home folder))')" && pwd -P)" export HOME @@ -21,35 +37,47 @@ fi echo "Building 'sd-mobile-ios' library..." __dirname="$(CDPATH='' cd -- "$(dirname -- "$0")" && pwd -P)" +DEPS="${__dirname}/../../../.deps/" +DEPS="$(CDPATH='' cd -- "$DEPS" && pwd -P)" +CARGO_CONFIG="${__dirname}/../../../../../.cargo" +CARGO_CONFIG="$(CDPATH='' cd -- "$CARGO_CONFIG" && pwd -P)/config.toml" # Ensure target dir exists TARGET_DIRECTORY="${__dirname}/../../../../../target" mkdir -p "$TARGET_DIRECTORY" TARGET_DIRECTORY="$(CDPATH='' cd -- "$TARGET_DIRECTORY" && pwd -P)" -# if [ "${CONFIGURATION:-}" != "Debug" ]; then -# CARGO_FLAGS=--release -# export CARGO_FLAGS -# fi +TARGET_CONFIG=debug +if [ "${CONFIGURATION:-}" = "Release" ]; then + set -- --release + TARGET_CONFIG=release +fi -# Required for CI and for everyone I guess? -export PATH="${CARGO_HOME:-"${HOME}/.cargo"}/bin:$PATH" +trap 'if [ -e "${CARGO_CONFIG}.bak" ]; then mv "${CARGO_CONFIG}.bak" "$CARGO_CONFIG"; fi' EXIT +# Required for `cargo` to correctly compile the library +RUST_PATH="${CARGO_HOME:-"${HOME}/.cargo"}/bin:$(brew --prefix)/bin:$(env -i /bin/bash --noprofile --norc -c 'echo $PATH')" if [ "${PLATFORM_NAME:-}" = "iphonesimulator" ]; then case "$(uname -m)" in "arm64" | "aarch64") # M series - cargo build -p sd-mobile-ios --target aarch64-apple-ios-sim - lipo -create -output "$TARGET_DIRECTORY"/libsd_mobile_iossim.a "$TARGET_DIRECTORY"/aarch64-apple-ios-sim/debug/libsd_mobile_ios.a + sed -i.bak "s|FFMPEG_DIR = { force = true, value = \".*\" }|FFMPEG_DIR = { force = true, value = \"${DEPS}/aarch64-apple-ios-sim\" }|" "$CARGO_CONFIG" + env CARGO_FEATURE_STATIC=1 PATH="$RUST_PATH" cargo build -p sd-mobile-ios --target aarch64-apple-ios-sim "$@" + lipo -create -output "$TARGET_DIRECTORY"/libsd_mobile_iossim.a "${TARGET_DIRECTORY}/aarch64-apple-ios-sim/${TARGET_CONFIG}/libsd_mobile_ios.a" + symlink_libs "${DEPS}/aarch64-apple-ios-sim/lib" "$TARGET_DIRECTORY" ;; "x86_64") # Intel - cargo build -p sd-mobile-ios --target x86_64-apple-ios - lipo -create -output "$TARGET_DIRECTORY"/libsd_mobile_iossim.a "$TARGET_DIRECTORY"/x86_64-apple-ios/debug/libsd_mobile_ios.a + sed -i.bak "s|FFMPEG_DIR = { force = true, value = \".*\" }|FFMPEG_DIR = { force = true, value = \"${DEPS}/x86_64-apple-ios\" }|" "$CARGO_CONFIG" + env CARGO_FEATURE_STATIC=1 PATH="$RUST_PATH" cargo build -p sd-mobile-ios --target x86_64-apple-ios "$@" + lipo -create -output "$TARGET_DIRECTORY"/libsd_mobile_iossim.a "${TARGET_DIRECTORY}/x86_64-apple-ios/${TARGET_CONFIG}/libsd_mobile_ios.a" + symlink_libs "${DEPS}/x86_64-apple-ios/lib" "$TARGET_DIRECTORY" ;; *) err 'Unsupported architecture.' ;; esac else - cargo build -p sd-mobile-ios --target aarch64-apple-ios --release - lipo -create -output "$TARGET_DIRECTORY"/libsd_mobile_ios.a "$TARGET_DIRECTORY"/aarch64-apple-ios/release/libsd_mobile_ios.a + sed -i.bak "s|FFMPEG_DIR = { force = true, value = \".*\" }|FFMPEG_DIR = { force = true, value = \"${DEPS}/aarch64-apple-ios\" }|" "$CARGO_CONFIG" + env CARGO_FEATURE_STATIC=1 PATH="$RUST_PATH" cargo build -p sd-mobile-ios --target aarch64-apple-ios "$@" + lipo -create -output "$TARGET_DIRECTORY"/libsd_mobile_ios.a "${TARGET_DIRECTORY}/aarch64-apple-ios/${TARGET_CONFIG}/libsd_mobile_ios.a" + symlink_libs "${DEPS}/aarch64-apple-ios/lib" "$TARGET_DIRECTORY" fi diff --git a/package.json b/package.json index c8e4eb4ba652..00f6dfcf9c48 100644 --- a/package.json +++ b/package.json @@ -74,5 +74,5 @@ "eslintConfig": { "root": true }, - "packageManager": "pnpm@9.7.0" + "packageManager": "pnpm@9.9.0" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 04cae94f1d1e..df32664bb9da 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1169,9 +1169,6 @@ importers: scripts: dependencies: - '@iarna/toml': - specifier: ^3.0.0 - version: 3.0.0 archive-wasm: specifier: ^1.7.0 version: 1.7.0 @@ -1181,12 +1178,18 @@ importers: os-proxy-config: specifier: ^1.1.1 version: 1.1.1 + plist: + specifier: ^3.1.0 + version: 3.1.0 semver: specifier: ^7.6.3 version: 7.6.3 + smol-toml: + specifier: ^1.3.0 + version: 1.3.0 undici: - specifier: ^6.19.7 - version: 6.19.7 + specifier: ^6.19.8 + version: 6.19.8 devDependencies: '@babel/core': specifier: ^7.24.0 @@ -2991,9 +2994,6 @@ packages: '@vue/compiler-sfc': optional: true - '@iarna/toml@3.0.0': - resolution: {integrity: sha512-td6ZUkz2oS3VeleBcN+m//Q6HlCFCPrnI0FZhrt/h4XqLEdOyYp2u21nd8MdsR+WJy5r9PTDaHTDDfhf4H4l6Q==} - '@icons-pack/react-simple-icons@9.4.0': resolution: {integrity: sha512-fZtC4Zv53hE+IQE2dJlFt3EB6UOifwTrUNMuEu4hSXemtqMahd05Dpvj2K0j2ewVc+j/ibavud3xjfaMB2Nj7g==} peerDependencies: @@ -12270,6 +12270,10 @@ packages: resolution: {integrity: sha512-h+z7HKHYXj6wJU+AnS/+IH8Uh9fdcX1Lrhg1/VMdf9PwoBQXFcXiAdsy2tSK0P6gKwJLXp02r90ahUCqHk9rrw==} engines: {node: '>=8.0.0'} + smol-toml@1.3.0: + resolution: {integrity: sha512-tWpi2TsODPScmi48b/OQZGi2lgUmBCHy6SZrhi/FdnnHiU1GwebbCfuQuxsC3nHaLwtYeJGPrDZDIeodDOc4pA==} + engines: {node: '>= 18'} + snake-case@3.0.4: resolution: {integrity: sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==} @@ -13064,8 +13068,8 @@ packages: resolution: {integrity: sha512-3ItfzbrhDlINjaP0duwnNsKpDQk3acHI3gVJ1z4fmwMK31k5G9OVIAMLSIaP6w4FaGkaAkN6zaQO9LUvZ1t7VA==} engines: {node: '>=14.0'} - undici@6.19.7: - resolution: {integrity: sha512-HR3W/bMGPSr90i8AAp2C4DM3wChFdJPLrWYpIS++LxS8K+W535qftjt+4MyjNYHeWabMj1nvtmLIi7l++iq91A==} + undici@6.19.8: + resolution: {integrity: sha512-U8uCCl2x9TK3WANvmBavymRzxbfFYG+tAu+fgx3zxQy3qdagQqBLwJVrdyO1TBfUXvfKveMKJZhpvUYoOjM+4g==} engines: {node: '>=18.17'} unicode-canonical-property-names-ecmascript@2.0.0: @@ -15987,8 +15991,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@iarna/toml@3.0.0': {} - '@icons-pack/react-simple-icons@9.4.0(react@18.2.0)': dependencies: react: 18.2.0 @@ -28240,6 +28242,8 @@ snapshots: slugify@1.6.6: {} + smol-toml@1.3.0: {} + snake-case@3.0.4: dependencies: dot-case: 3.0.4 @@ -29078,7 +29082,7 @@ snapshots: dependencies: '@fastify/busboy': 2.1.1 - undici@6.19.7: {} + undici@6.19.8: {} unicode-canonical-property-names-ecmascript@2.0.0: {} diff --git a/scripts/package.json b/scripts/package.json index ae2d8c6d2a29..50e277273d05 100644 --- a/scripts/package.json +++ b/scripts/package.json @@ -18,12 +18,13 @@ "trailingComma": "es5" }, "dependencies": { - "@iarna/toml": "^3.0.0", + "smol-toml": "^1.3.0", "archive-wasm": "^1.7.0", "mustache": "^4.2.0", "os-proxy-config": "^1.1.1", + "plist": "^3.1.0", "semver": "^7.6.3", - "undici": "^6.19.7" + "undici": "^6.19.8" }, "devDependencies": { "@babel/core": "^7.24.0", diff --git a/scripts/preprep.mjs b/scripts/preprep.mjs index cc253c6d4e15..2bec4a0a566b 100755 --- a/scripts/preprep.mjs +++ b/scripts/preprep.mjs @@ -7,11 +7,14 @@ import { fileURLToPath } from 'node:url' import { extractTo } from 'archive-wasm/src/fs.mjs' import * as _mustache from 'mustache' +import { parse as parseTOML } from 'smol-toml' import { getConst, NATIVE_DEPS_URL, NATIVE_DEPS_ASSETS } from './utils/consts.mjs' import { get } from './utils/fetch.mjs' import { getMachineId } from './utils/machineId.mjs' +import { getRustTargetList } from './utils/rustup.mjs' import { symlinkSharedLibsMacOS, symlinkSharedLibsLinux } from './utils/shared.mjs' +import { spinTask } from './utils/spinner.mjs' import { which } from './utils/which.mjs' if (/^(msys|mingw|cygwin)$/i.test(env.OSTYPE ?? '')) { @@ -55,24 +58,78 @@ packages/scripts/${machineId[0] === 'Windows_NT' ? 'setup.ps1' : 'setup.sh'} // Directory where the native deps will be downloaded const nativeDeps = path.join(__root, 'apps', '.deps') +const mobileNativeDeps = path.join(__root, 'apps', 'mobile', '.deps') await fs.rm(nativeDeps, { force: true, recursive: true }) await fs.mkdir(nativeDeps, { mode: 0o750, recursive: true }) +// Native deps for desktop app try { - console.log('Downloading Native dependencies...') + console.log('Downloading desktop native dependencies...') const assetName = getConst(NATIVE_DEPS_ASSETS, machineId) if (assetName == null) throw new Error('NO_ASSET') - const archiveData = await get(`${NATIVE_DEPS_URL}/${assetName}`) + const archiveData = await spinTask( + get((__debug && env.NATIVE_DEPS_URL) || `${NATIVE_DEPS_URL}/${assetName}`) + ) - await extractTo(archiveData, nativeDeps, { - chmod: 0o600, - recursive: true, - overwrite: true, - }) + console.log(`Extracting native dependencies...`) + await spinTask( + extractTo(archiveData, nativeDeps, { + chmod: 0o600, + recursive: true, + overwrite: true, + }) + ) } catch (e) { - console.error(`Failed to download native dependencies. ${bugWarn}`) + console.error(`Failed to download native dependencies.\n${bugWarn}`) + if (__debug) console.error(e) + exit(1) +} + +const rustTargets = await getRustTargetList() +const iosTargets = { + 'aarch64-apple-ios': NATIVE_DEPS_ASSETS.IOS.ios.aarch64, + 'aarch64-apple-ios-sim': NATIVE_DEPS_ASSETS.IOS.iossim.aarch64, + 'x86_64-apple-ios': NATIVE_DEPS_ASSETS.IOS.iossim.x86_64, +} + +// Native deps for mobile +try { + const mobileTargets = /** @type {Record} */ {} + + if (machineId[0] === 'Darwin') + // iOS is only supported on macOS + Object.assign(mobileTargets, iosTargets) + + for (const [rustTarget, nativeTarget] of Object.entries(mobileTargets)) { + if (!rustTargets.has(rustTarget)) continue + console.log(`Downloading mobile native dependencies for ${nativeTarget}...`) + + const specificMobileNativeDeps = path.join(mobileNativeDeps, rustTarget) + await fs.rm(specificMobileNativeDeps, { force: true, recursive: true }) + await fs.mkdir(specificMobileNativeDeps, { mode: 0o750, recursive: true }) + + const archiveData = await spinTask( + get( + (__debug && + env[`NATIVE_DEPS_${rustTarget.replaceAll('-', '_').toUpperCase()}_URL`]) || + `${NATIVE_DEPS_URL}/${nativeTarget}` + ) + ) + + console.log(`Extracting native dependencies...`) + await spinTask( + extractTo(archiveData, specificMobileNativeDeps, { + chmod: 0o600, + sizeLimit: 256n * 1024n * 1024n, + recursive: true, + overwrite: true, + }) + ) + } +} catch (e) { + console.error(`Failed to download native dependencies for mobile.\n${bugWarn}`) if (__debug) console.error(e) exit(1) } @@ -81,17 +138,21 @@ try { try { if (machineId[0] === 'Linux') { console.log(`Symlink shared libs...`) - symlinkSharedLibsLinux(__root, nativeDeps).catch(e => { - console.error(`Failed to symlink shared libs. ${bugWarn}`) - throw e - }) + await spinTask( + symlinkSharedLibsLinux(__root, nativeDeps).catch(e => { + console.error(`Failed to symlink shared libs.\n${bugWarn}`) + throw e + }) + ) } else if (machineId[0] === 'Darwin') { // This is still required due to how ffmpeg-sys-next builds script works console.log(`Symlink shared libs...`) - await symlinkSharedLibsMacOS(__root, nativeDeps).catch(e => { - console.error(`Failed to symlink shared libs. ${bugWarn}`) - throw e - }) + await spinTask( + symlinkSharedLibsMacOS(__root, nativeDeps).catch(e => { + console.error(`Failed to symlink shared libs.\n${bugWarn}`) + throw e + }) + ) } } catch (error) { if (__debug) console.error(error) @@ -126,34 +187,40 @@ try { break } - await fs.writeFile( - path.join(__root, '.cargo', 'config.toml'), - mustache - .render( - await fs.readFile(path.join(__root, '.cargo', 'config.toml.mustache'), { - encoding: 'utf8', - }), - { - isWin, - isMacOS, - isLinux, - // Escape windows path separator to be compatible with TOML parsing - protoc: path - .join( - nativeDeps, - 'bin', - machineId[0] === 'Windows_NT' ? 'protoc.exe' : 'protoc' - ) - .replaceAll('\\', '\\\\'), - nativeDeps: nativeDeps.replaceAll('\\', '\\\\'), - hasLLD, - } - ) - .replace(/\n\n+/g, '\n'), - { mode: 0o751, flag: 'w+' } - ) + const configData = mustache + .render( + await fs.readFile(path.join(__root, '.cargo', 'config.toml.mustache'), { + encoding: 'utf8', + }), + { + isWin, + hasiOS: Object.keys(iosTargets).some(target => rustTargets.has(target)), + isMacOS, + isLinux, + // Escape windows path separator to be compatible with TOML parsing + protoc: path + .join( + nativeDeps, + 'bin', + machineId[0] === 'Windows_NT' ? 'protoc.exe' : 'protoc' + ) + .replaceAll('\\', '\\\\'), + nativeDeps: nativeDeps.replaceAll('\\', '\\\\'), + mobileNativeDeps: mobileNativeDeps.replaceAll('\\', '\\\\'), + hasLLD, + } + ) + .replace(/\n\n+/g, '\n') + + // Validate generated TOML + parseTOML(configData) + + await fs.writeFile(path.join(__root, '.cargo', 'config.toml'), configData, { + mode: 0o751, + flag: 'w+', + }) } catch (error) { - console.error(`Failed to generate .cargo/config.toml. ${bugWarn}`) + console.error(`Failed to generate .cargo/config.toml.\n${bugWarn}`) if (__debug) console.error(error) exit(1) } diff --git a/scripts/tauri.mjs b/scripts/tauri.mjs index 152079e72269..0eed0d73f040 100755 --- a/scripts/tauri.mjs +++ b/scripts/tauri.mjs @@ -6,7 +6,7 @@ import { env, exit, umask, platform } from 'node:process' import { setTimeout } from 'node:timers/promises' import { fileURLToPath } from 'node:url' -import * as toml from '@iarna/toml' +import { parse as parseTOML } from 'smol-toml' import { waitLockUnlock } from './utils/flock.mjs' import { patchTauri } from './utils/patchTauri.mjs' @@ -44,7 +44,7 @@ process.on('SIGINT', cleanUp) // Export environment variables defined in cargo.toml const cargoConfig = await fs .readFile(path.resolve(__root, '.cargo', 'config.toml'), { encoding: 'binary' }) - .then(toml.parse) + .then(parseTOML) if (cargoConfig.env && typeof cargoConfig.env === 'object') for (const [name, value] of Object.entries(cargoConfig.env)) if (!env[name]) env[name] = value diff --git a/scripts/utils/consts.mjs b/scripts/utils/consts.mjs index ef36be962026..420e20a3bac1 100644 --- a/scripts/utils/consts.mjs +++ b/scripts/utils/consts.mjs @@ -20,6 +20,15 @@ export const NATIVE_DEPS_ASSETS = { x86_64: 'native-deps-x86_64-windows-gnu.tar.xz ', aarch64: 'native-deps-aarch64-windows-gnu.tar.xz', }, + IOS: { + iossim: { + x86_64: 'native-deps-x86_64-iossim-apple.tar.xz', + aarch64: 'native-deps-aarch64-iossim-apple.tar.xz', + }, + ios: { + aarch64: 'native-deps-aarch64-ios-apple.tar.xz', + }, + }, } /** diff --git a/scripts/utils/fetch.mjs b/scripts/utils/fetch.mjs index 778148fad1c1..cf8cd6c41ffe 100644 --- a/scripts/utils/fetch.mjs +++ b/scripts/utils/fetch.mjs @@ -15,7 +15,7 @@ const cacheDir = joinPath(__dirname, '.tmp') /** @type {Agent.Options} */ const agentOpts = { - allowH2: true, + allowH2: !!env.HTTP2, connect: { timeout: CONNECT_TIMEOUT }, connectTimeout: CONNECT_TIMEOUT, autoSelectFamily: true, @@ -45,7 +45,7 @@ async function getCache(resource, headers) { let header // Don't cache in CI - if (env.CI === 'true') return null + if (env.CI === 'true' || env.NO_CACHE === 'true') return null if (headers) resource += Array.from(headers.entries()) @@ -145,6 +145,7 @@ export async function get(resource, headers, preferCache) { if (cache?.header) headers.append(...cache.header) + if (__debug) console.log(`Downloading ${resource} ${cache?.data ? ' (cached)' : ''}...`) const response = await fetch(resource, { dispatcher, headers }) if (!response.ok) { diff --git a/scripts/utils/rustup.mjs b/scripts/utils/rustup.mjs new file mode 100644 index 000000000000..2dfca571ebc3 --- /dev/null +++ b/scripts/utils/rustup.mjs @@ -0,0 +1,12 @@ +import { exec as execCb } from 'node:child_process' +import { promisify } from 'node:util' + +const exec = promisify(execCb) + +/** + * Get the list of rust targets + * @returns {Promise>} + */ +export async function getRustTargetList() { + return new Set((await exec('rustup target list --installed')).stdout.split('\n')) +} diff --git a/scripts/utils/spinner.mjs b/scripts/utils/spinner.mjs new file mode 100644 index 000000000000..fccb3a9202c7 --- /dev/null +++ b/scripts/utils/spinner.mjs @@ -0,0 +1,44 @@ +/** + * Simple function that implements a spinner animation in the terminal. + * It receives an AbortController as argument and return a promise that resolves when the AbortController is signaled. + * @param {AbortController} abortController + * @returns {Promise} + */ +export function spinnerAnimation(abortController) { + const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'] + let frameIndex = 0 + + return new Promise(resolve => { + const intervalId = setInterval(() => { + process.stdout.write(`\r${frames[frameIndex++]}`) + frameIndex %= frames.length + }, 100) + + const onAbort = () => { + clearInterval(intervalId) + process.stdout.write('\r \r') // Clear spinner + resolve() + } + + if (abortController.signal.aborted) { + onAbort() + } else { + abortController.signal.addEventListener('abort', onAbort) + } + }) +} + +/** + * Wrap a long running task with a spinner animation. + * @template T + * @param {Promise} promise + * @returns {Promise} + */ +export async function spinTask(promise) { + const spinnerControl = new AbortController() + const [, result] = await Promise.all([ + spinnerAnimation(spinnerControl), + promise.finally(() => spinnerControl.abort('Task is over')), + ]) + return result +}