Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@ jobs:
run: flutter analyze --fatal-infos --fatal-warnings
- name: Dart format
run: dart format --set-exit-if-changed .
# - name: Dart tests
# run: melos run test
- name: Dart tests
run: melos run test

# macos_integration_test:
# runs-on: macos-latest
Expand Down
12 changes: 9 additions & 3 deletions lib/native_toolchain_rs.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ import 'package:code_assets/code_assets.dart';
import 'package:hooks/hooks.dart';
import 'package:logging/logging.dart';
import 'package:meta/meta.dart';
import 'package:native_toolchain_rs/src/build_environment.dart';
import 'package:native_toolchain_rs/src/build_runner.dart';
import 'package:native_toolchain_rs/src/crate_info_validator.dart';
import 'package:native_toolchain_rs/src/crate_resolver.dart';
import 'package:native_toolchain_rs/src/process_runner.dart';
import 'package:native_toolchain_rs/src/toml_parsing.dart';
Expand Down Expand Up @@ -97,15 +99,19 @@ final class RustBuilder {
logger,
tomlDocumentWrapperFactory,
);
const buildEnvironmentFactory = BuildEnvironmentFactory();
final crateInfoValidator = CrateInfoValidator(
toolchainTomlParser: toolchainTomlParser,
cargoManifestParser: cargoManifestParser,
);

return RustBuildRunner(
config: this,
logger: logger,
processRunner: processRunner,
crateDirectoryResolver: crateDirectoryResolver,
tomlDocumentWrapperFactory: tomlDocumentWrapperFactory,
cargoManifestParser: cargoManifestParser,
toolchainTomlParser: toolchainTomlParser,
buildEnvironmentFactory: buildEnvironmentFactory,
crateInfoValidator: crateInfoValidator,
).run(input: input, output: output, assetRouting: assetRouting);
}
}
65 changes: 65 additions & 0 deletions lib/src/build_environment.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import 'dart:io';

import 'package:code_assets/code_assets.dart';
import 'package:meta/meta.dart';
import 'package:native_toolchain_rs/src/config_mapping.dart';
import 'package:native_toolchain_rs/src/exception.dart';
import 'package:path/path.dart' as path;

@internal
interface class BuildEnvironmentFactory {
const BuildEnvironmentFactory();

Map<String, String> createBuildEnvVars(CodeConfig codeConfig) {
final CodeConfig(:targetOS, :cCompiler) = codeConfig;
final targetTriple = codeConfig.targetTriple;
final targetTripleEnvVar = targetTriple.replaceAll('-', '_');

String getBinary(String binaryName) {
if (cCompiler == null) {
throw UnsupportedError(
'CCompilerConfig was not provided but is required for $targetTriple',
);
}

final binaryPath = path.join(
path.dirname(path.fromUri(cCompiler.compiler)),
OS.current.executableFileName(binaryName),
);

if (!File(binaryPath).existsSync()) {
throw RustValidationException([
'Binary $binaryPath not found; is your installed compiler too old?',
]);
}

return binaryPath;
}

return {
// NOTE: XCode makes some injections into PATH that break host build
// for crates with a build.rs
// See also: https://github.com/irondash/native_toolchain_rust/issues/17
if (Platform.isMacOS) ...{
'PATH': Platform.environment['PATH']!
.split(':')
.where((e) => !e.contains('Contents/Developer/'))
.join(':'),
},

// NOTE: we need to point to NDK >=27 vended LLVM for Android.
// The `${targetTriple}35-clang`s were introduced in NDK 27,
// so using these binaries:
// 1. Ensures we are using a compatible NDK
// 2. Also fixes build issues when just using the `clang`s directly
if (targetOS == OS.android) ...{
'AR_$targetTripleEnvVar': getBinary('llvm-ar'),
'CC_$targetTripleEnvVar': getBinary('${targetTriple}35-clang'),
'CXX_$targetTripleEnvVar': getBinary('${targetTriple}35-clang++'),
'CARGO_TARGET_${targetTripleEnvVar.toUpperCase()}_LINKER': getBinary(
'${targetTriple}35-clang',
),
},
};
}
}
185 changes: 17 additions & 168 deletions lib/src/build_runner.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,33 +3,32 @@ import 'dart:io';
import 'package:code_assets/code_assets.dart';
import 'package:hooks/hooks.dart';
import 'package:logging/logging.dart';
import 'package:meta/meta.dart';
import 'package:native_toolchain_rs/native_toolchain_rs.dart';
import 'package:native_toolchain_rs/src/build_environment.dart';
import 'package:native_toolchain_rs/src/config_mapping.dart';
import 'package:native_toolchain_rs/src/crate_info_validator.dart';
import 'package:native_toolchain_rs/src/crate_resolver.dart';
import 'package:native_toolchain_rs/src/process_runner.dart';
import 'package:native_toolchain_rs/src/toml_parsing.dart';
import 'package:path/path.dart' as path;

// NOTE: this is an internal implementation detail
// ignore_for_file: public_member_api_docs

final class RustBuildRunner {
@internal
interface class RustBuildRunner {
const RustBuildRunner({
required this.config,
required this.logger,
required this.crateDirectoryResolver,
required this.tomlDocumentWrapperFactory,
required this.toolchainTomlParser,
required this.cargoManifestParser,
required this.processRunner,
required this.buildEnvironmentFactory,
required this.crateInfoValidator,
});

final RustBuilder config;
final Logger? logger;
final CrateDirectoryResolver crateDirectoryResolver;
final TomlDocumentWrapperFactory tomlDocumentWrapperFactory;
final ToolchainTomlParser toolchainTomlParser;
final CargoManifestParser cargoManifestParser;
final ProcessRunner processRunner;
final BuildEnvironmentFactory buildEnvironmentFactory;
final CrateInfoValidator crateInfoValidator;

Future<void> run({
required BuildInput input,
Expand All @@ -49,7 +48,8 @@ final class RustBuildRunner {
}

logger?.info('Gathering all data required for the build');
final CodeConfig(:targetOS, :targetTriple, :linkMode) = input.config.code;
final codeConfig = input.config.code;
final CodeConfig(:targetOS, :targetTriple, :linkMode) = codeConfig;
final RustBuilder(
:assetName,
:cratePath,
Expand All @@ -65,7 +65,10 @@ final class RustBuildRunner {
);
final outputDir = path.join(path.fromUri(input.outputDirectory), 'target');
final manifestPath = path.join(crateDirectory.path, 'Cargo.toml');
final (:crateName, :toolchainChannel) = fetchAndValidateCrateInfo(
final (
:crateName,
:toolchainChannel,
) = crateInfoValidator.fetchAndValidateCrateInfo(
targetTriple: targetTriple,
manifestPath: manifestPath,
toolchainTomlPath: path.join(crateDirectory.path, 'rust-toolchain.toml'),
Expand Down Expand Up @@ -98,7 +101,7 @@ final class RustBuildRunner {
...extraCargoBuildArgs,
],
environment: {
...createBuildEnvVars(input.config.code),
...buildEnvironmentFactory.createBuildEnvVars(codeConfig),
...extraCargoEnvironmentVariables,
},
);
Expand Down Expand Up @@ -133,158 +136,4 @@ final class RustBuildRunner {
], workingDirectory: crateDirectory),
);
}

({String crateName, String toolchainChannel}) fetchAndValidateCrateInfo({
required String manifestPath,
required String toolchainTomlPath,
required String targetTriple,
}) {
final [
String crateName,
String toolchainChannel,
] = RustValidationException.compose<dynamic>([
() {
final (:crateName, :libCrateTypes) = cargoManifestParser.parseManifest(
manifestPath,
);

const requiredTypes = ['staticlib', 'cdylib'];
if (!requiredTypes.every(libCrateTypes.contains)) {
throw RustValidationException([
'Cargo.toml must specify $requiredTypes under lib.crate-types',
]);
}

return crateName;
},
() {
final (:channel, :targets) = toolchainTomlParser.parseToolchainToml(
toolchainTomlPath,
);

final toolchainIssues = <String>[];

const deniedChannels = {'stable', 'beta', 'nightly'};
if (deniedChannels.contains(channel)) {
toolchainIssues.add(
'Your current channel in rust-toolchain.toml is $channel; '
'this is dangerous and consequently is not allowed! '
'Please specify an exact version to fix this issue.',
);
}

if (!targets.contains(targetTriple)) {
toolchainIssues.add(
'$targetTriple is not one of the supported targets: $targets',
);
}

return channel;
},
]);

return (crateName: crateName, toolchainChannel: toolchainChannel);
}

Map<String, String> createBuildEnvVars(CodeConfig codeConfig) {
final CodeConfig(:targetOS, :targetTriple, :cCompiler) = codeConfig;
final targetTripleEnvVar = targetTriple.replaceAll('-', '_');

String getBinary(String binaryName) {
if (cCompiler == null) {
throw UnsupportedError(
'CCompilerConfig was not provided but is required for $targetTriple',
);
}

final binaryPath = path.join(
path.dirname(path.fromUri(cCompiler.compiler)),
OS.current.executableFileName(binaryName),
);

if (!File(binaryPath).existsSync()) {
throw RustValidationException([
'Binary $binaryPath not found; is your installed compiler too old?',
]);
}

return binaryPath;
}

return {
// NOTE: XCode makes some injections into PATH that break host build
// for crates with a build.rs
// See also: https://github.com/irondash/native_toolchain_rust/issues/17
if (Platform.isMacOS) ...{
'PATH': Platform.environment['PATH']!
.split(':')
.where((e) => !e.contains('Contents/Developer/'))
.join(':'),
},

// NOTE: we need to point to NDK >=27 vended LLVM for Android.
// The `${targetTriple}35-clang`s were introduced in NDK 27,
// so using these binaries:
// 1. Ensures we are using a compatible NDK
// 2. Also fixes build issues when just using the `clang`s directly
if (targetOS == OS.android) ...{
'AR_$targetTripleEnvVar': getBinary('llvm-ar'),
'CC_$targetTripleEnvVar': getBinary('${targetTriple}35-clang'),
'CXX_$targetTripleEnvVar': getBinary('${targetTriple}35-clang++'),
'CARGO_TARGET_${targetTripleEnvVar.toUpperCase()}_LINKER': getBinary(
'${targetTriple}35-clang',
),
},
};
}
}

extension on CodeConfig {
String get targetTriple {
return switch ((targetOS, targetArchitecture)) {
// Android
(OS.android, Architecture.arm64) => 'aarch64-linux-android',
(OS.android, Architecture.arm) => 'armv7-linux-androideabi',
(OS.android, Architecture.x64) => 'x86_64-linux-android',

// iOS
(OS.iOS, Architecture.arm64)
when iOS.targetSdk == IOSSdk.iPhoneSimulator =>
'aarch64-apple-ios-sim',
(OS.iOS, Architecture.arm64) when iOS.targetSdk == IOSSdk.iPhoneOS =>
'aarch64-apple-ios',
(OS.iOS, Architecture.arm64) => throw UnsupportedError(
'Unknown IOSSdk: ${iOS.targetSdk}',
),
(OS.iOS, Architecture.x64) => 'x86_64-apple-ios',

// Windows
(OS.windows, Architecture.arm64) => 'aarch64-pc-windows-msvc',
(OS.windows, Architecture.x64) => 'x86_64-pc-windows-msvc',

// Linux
(OS.linux, Architecture.arm64) => 'aarch64-unknown-linux-gnu',
(OS.linux, Architecture.x64) => 'x86_64-unknown-linux-gnu',

// macOS
(OS.macOS, Architecture.arm64) => 'aarch64-apple-darwin',
(OS.macOS, Architecture.x64) => 'x86_64-apple-darwin',

(_, _) => throw UnsupportedError(
'Unsupported target: $targetOS on $targetArchitecture',
),
};
}

LinkMode get linkMode {
return switch (linkModePreference) {
LinkModePreference.dynamic ||
LinkModePreference.preferDynamic => DynamicLoadingBundled(),
LinkModePreference.static ||
LinkModePreference.preferStatic => StaticLinking(),
_ => throw UnsupportedError(
'Unsupported LinkModePreference: $linkModePreference',
),
};
}
}
53 changes: 53 additions & 0 deletions lib/src/config_mapping.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import 'package:code_assets/code_assets.dart';
import 'package:meta/meta.dart';

@internal
extension CodeConfigMapping on CodeConfig {
String get targetTriple {
return switch ((targetOS, targetArchitecture)) {
// Android
(OS.android, Architecture.arm64) => 'aarch64-linux-android',
(OS.android, Architecture.arm) => 'armv7-linux-androideabi',
(OS.android, Architecture.x64) => 'x86_64-linux-android',

// iOS
(OS.iOS, Architecture.arm64)
when iOS.targetSdk == IOSSdk.iPhoneSimulator =>
'aarch64-apple-ios-sim',
(OS.iOS, Architecture.arm64) when iOS.targetSdk == IOSSdk.iPhoneOS =>
'aarch64-apple-ios',
(OS.iOS, Architecture.arm64) => throw UnsupportedError(
'Unknown IOSSdk: ${iOS.targetSdk}',
),
(OS.iOS, Architecture.x64) => 'x86_64-apple-ios',

// Windows
(OS.windows, Architecture.arm64) => 'aarch64-pc-windows-msvc',
(OS.windows, Architecture.x64) => 'x86_64-pc-windows-msvc',

// Linux
(OS.linux, Architecture.arm64) => 'aarch64-unknown-linux-gnu',
(OS.linux, Architecture.x64) => 'x86_64-unknown-linux-gnu',

// macOS
(OS.macOS, Architecture.arm64) => 'aarch64-apple-darwin',
(OS.macOS, Architecture.x64) => 'x86_64-apple-darwin',

(_, _) => throw UnsupportedError(
'Unsupported target: $targetOS on $targetArchitecture',
),
};
}

LinkMode get linkMode {
return switch (linkModePreference) {
LinkModePreference.dynamic ||
LinkModePreference.preferDynamic => DynamicLoadingBundled(),
LinkModePreference.static ||
LinkModePreference.preferStatic => StaticLinking(),
_ => throw UnsupportedError(
'Unsupported LinkModePreference: $linkModePreference',
),
};
}
}
Loading