blob: b20b5d3b60dfa79de16b454e1d9d96964a981ec5 [file] [log] [blame]
// This file is part of ICU4X. For terms of use, please see the file
// called LICENSE at the top level of the ICU4X source tree
// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).
import 'dart:io';
import 'package:args/args.dart';
import 'package:code_assets/code_assets.dart';
import 'package:path/path.dart' as path;
const crateName = 'icu_capi';
Future<void> main(List<String> args) async {
const fileKey = 'file';
const osKey = 'os';
const architectureKey = 'architecture';
const simulatorKey = 'simulator';
const compileTypeKey = 'compile_type';
const cargoFeaturesKey = 'cargo_features';
final argParser =
ArgParser()
..addOption(fileKey, mandatory: true)
..addOption(
compileTypeKey,
allowed: ['static', 'dynamic'],
mandatory: true,
)
..addFlag(simulatorKey, defaultsTo: false)
..addOption(osKey, mandatory: true)
..addOption(architectureKey, mandatory: true)
..addMultiOption(
cargoFeaturesKey,
defaultsTo: ['default_components', 'compiled_data'],
);
ArgResults parsed;
try {
parsed = argParser.parse(args);
} catch (e) {
print('Error parsing $args');
print(argParser.usage);
exit(1);
}
final lib = await buildLib(
OS.values.firstWhere((o) => o.name == parsed.option(osKey)!),
Architecture.values.firstWhere(
(o) => o.name == parsed.option(architectureKey)!,
),
parsed.option(compileTypeKey)! == 'static',
parsed.flag(simulatorKey),
File.fromUri(Platform.script).parent,
parsed.multiOption(cargoFeaturesKey),
);
await lib.copy(
Uri.file(parsed.option(fileKey)!).toFilePath(windows: Platform.isWindows),
);
}
// Copied from Dart's package:intl4x build.dart, see
// https://github.com/dart-lang/i18n/blob/main/pkgs/intl4x/hook/build.dart
Future<File> buildLib(
OS targetOS,
Architecture targetArchitecture,
bool buildStatic,
bool isSimulator,
Directory startingPoint,
List<String> cargoFeatures,
) async {
// We assume that the first folder to contain a cargo.toml above the
// output directory is the directory containing the ICU4X code.
var workingDirectory = startingPoint;
while (!File.fromUri(
workingDirectory.uri.resolve('Cargo.toml'),
).existsSync()) {
workingDirectory = workingDirectory.parent;
}
final isNoStd = _isNoStdTarget((targetOS, targetArchitecture));
final target = _asRustTarget(targetOS, targetArchitecture, isSimulator);
if (!isNoStd) {
final rustArguments = ['target', 'add', target];
await runProcess(
'rustup',
rustArguments,
workingDirectory: workingDirectory,
);
}
await runProcess('cargo', [
if (buildStatic || isNoStd) '+nightly',
'rustc',
'--manifest-path=ffi/capi/Cargo.toml',
'--crate-type=${buildStatic ? 'staticlib' : 'cdylib'}',
'--release',
'--config=profile.release.panic="abort"',
'--config=profile.release.codegen-units=1',
'--no-default-features',
'--features=${{
...cargoFeatures,
...(isNoStd ? ['libc_alloc', 'panic_handler'] : ['logging', 'simple_logger']),
}.join(',')}',
if (isNoStd) '-Zbuild-std=core,alloc',
if (buildStatic || isNoStd) ...[
'-Zbuild-std=std,panic_abort',
'-Zbuild-std-features=panic_immediate_abort',
],
'--target=$target',
], workingDirectory: workingDirectory);
final file = File(
path.join(
workingDirectory.path,
'target',
target,
'release',
(buildStatic ? targetOS.staticlibFileName : targetOS.dylibFileName)(
crateName.replaceAll('-', '_'),
),
),
);
if (!(await file.exists())) {
throw FileSystemException('Building the dylib failed', file.path);
}
return file;
}
String _asRustTarget(OS os, Architecture? architecture, bool isSimulator) {
if (os == OS.iOS && architecture == Architecture.arm64 && isSimulator) {
return 'aarch64-apple-ios-sim';
}
return switch ((os, architecture)) {
(OS.android, Architecture.arm) => 'armv7-linux-androideabi',
(OS.android, Architecture.arm64) => 'aarch64-linux-android',
(OS.android, Architecture.ia32) => 'i686-linux-android',
(OS.android, Architecture.riscv64) => 'riscv64-linux-android',
(OS.android, Architecture.x64) => 'x86_64-linux-android',
(OS.fuchsia, Architecture.arm64) => 'aarch64-unknown-fuchsia',
(OS.fuchsia, Architecture.x64) => 'x86_64-unknown-fuchsia',
(OS.iOS, Architecture.arm64) => 'aarch64-apple-ios',
(OS.iOS, Architecture.x64) => 'x86_64-apple-ios',
(OS.linux, Architecture.arm) => 'armv7-unknown-linux-gnueabihf',
(OS.linux, Architecture.arm64) => 'aarch64-unknown-linux-gnu',
(OS.linux, Architecture.ia32) => 'i686-unknown-linux-gnu',
(OS.linux, Architecture.riscv32) => 'riscv32gc-unknown-linux-gnu',
(OS.linux, Architecture.riscv64) => 'riscv64gc-unknown-linux-gnu',
(OS.linux, Architecture.x64) => 'x86_64-unknown-linux-gnu',
(OS.macOS, Architecture.arm64) => 'aarch64-apple-darwin',
(OS.macOS, Architecture.x64) => 'x86_64-apple-darwin',
(OS.windows, Architecture.arm64) => 'aarch64-pc-windows-msvc',
(OS.windows, Architecture.ia32) => 'i686-pc-windows-msvc',
(OS.windows, Architecture.x64) => 'x86_64-pc-windows-msvc',
(_, _) =>
throw UnimplementedError(
'Target ${(os, architecture)} not available for rust',
),
};
}
bool _isNoStdTarget((OS os, Architecture? architecture) arg) => [
(OS.android, Architecture.riscv64),
(OS.linux, Architecture.riscv64),
].contains(arg);
Future<void> runProcess(
String executable,
List<String> arguments, {
Directory? workingDirectory,
bool dryRun = false,
}) async {
print('----------');
print('Running `$executable $arguments` in $workingDirectory');
if (!dryRun) {
final processResult = await Process.run(
executable,
arguments,
workingDirectory: workingDirectory?.path,
);
print('stdout:');
print(processResult.stdout);
if ((processResult.stderr as String).isNotEmpty) {
print('stderr:');
print(processResult.stderr);
}
if (processResult.exitCode != 0) {
throw ProcessException(executable, arguments, '', processResult.exitCode);
}
} else {
print('Not running, as --dry-run is set.');
}
print('==========');
}