[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
This commit is contained in:
Vítor Vasconcellos 2024-09-17 02:27:42 +00:00 committed by GitHub
parent 9c9fde2245
commit b4d1a295ff
18 changed files with 324 additions and 109 deletions

View File

@ -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}}

View File

@ -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}]

1
.gitignore vendored
View File

@ -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

View File

@ -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']
};

View File

@ -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"
}

View File

@ -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 }

View File

@ -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};

View File

@ -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 => 'env -i SPACEDRIVE_CI=$SPACEDRIVE_CI CONFIGURATION=$CONFIGURATION PLATFORM_NAME=$PLATFORM_NAME ${PODS_TARGET_SRCROOT}/build-rust.sh',
:execution_position => :before_compile
}
s.script_phase = {
:name => "Build Spacedrive Core!",
:script => "exec \"${PODS_TARGET_SRCROOT}/build-rust.sh\"",
:execution_position => :before_compile,
}
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 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(' ')
# 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

View File

@ -13,6 +13,22 @@ err() {
exit 1
}
symlink_libs() {
if [ $# -ne 2 ]; then
err "Invalid number of arguments. Usage: symlink_libs <dir1> <dir2>"
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

View File

@ -74,5 +74,5 @@
"eslintConfig": {
"root": true
},
"packageManager": "pnpm@9.7.0"
"packageManager": "pnpm@9.9.0"
}

30
pnpm-lock.yaml generated
View File

@ -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: {}

View File

@ -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",

View File

@ -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<string, string>} */ {}
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)
}

View File

@ -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

View File

@ -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',
},
},
}
/**

View File

@ -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) {

12
scripts/utils/rustup.mjs Normal file
View File

@ -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<Set<string>>}
*/
export async function getRustTargetList() {
return new Set((await exec('rustup target list --installed')).stdout.split('\n'))
}

44
scripts/utils/spinner.mjs Normal file
View File

@ -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<void>}
*/
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<T>} promise
* @returns {Promise<T>}
*/
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
}