| // Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| import 'dart:async'; |
| import 'dart:io'; |
| |
| import 'package:code_assets/code_assets.dart'; |
| import 'package:dartdev/src/native_assets_bundling.dart'; |
| import 'package:dartdev/src/sdk.dart'; |
| import 'package:dartdev/src/utils.dart'; |
| import 'package:data_assets/data_assets.dart'; |
| import 'package:file/local.dart'; |
| import 'package:hooks/hooks.dart'; |
| import 'package:hooks_runner/hooks_runner.dart'; |
| import 'package:logging/logging.dart'; |
| import 'package:package_config/package_config.dart' as package_config; |
| import 'package:yaml/yaml.dart' show loadYaml; |
| |
| import 'core.dart'; |
| |
| class DartNativeAssetsBuilder { |
| final Uri? pubspecUri; |
| final Uri packageConfigUri; |
| final package_config.PackageConfig packageConfig; |
| final String runPackageName; |
| final bool includeDevDependencies; |
| final bool verbose; |
| |
| static const _fileSystem = LocalFileSystem(); |
| |
| late final Future<PackageLayout> _packageLayout = () async { |
| return PackageLayout.fromPackageConfig( |
| _fileSystem, |
| packageConfig, |
| packageConfigUri, |
| runPackageName, |
| includeDevDependencies: includeDevDependencies, |
| ); |
| }(); |
| |
| late final _logger = Logger('') |
| ..onRecord.listen((LogRecord record) { |
| final levelValue = record.level.value; |
| if (levelValue >= Level.SEVERE.value) { |
| log.stderr(record.message); |
| } else if (levelValue >= Level.WARNING.value || |
| verbose && levelValue >= Level.INFO.value) { |
| log.stdout(record.message); |
| } else { |
| // Note, this is ignored by default. |
| log.trace(record.message); |
| } |
| }); |
| |
| late final Future<NativeAssetsBuildRunner> _nativeAssetsBuildRunner = |
| () async { |
| return NativeAssetsBuildRunner( |
| // This always runs in JIT mode. |
| dartExecutable: Uri.file(sdk.dart), |
| logger: _logger, |
| fileSystem: const LocalFileSystem(), |
| packageLayout: await _packageLayout, |
| userDefines: UserDefines(workspacePubspec: pubspecUri), |
| ); |
| }(); |
| |
| DartNativeAssetsBuilder({ |
| this.pubspecUri, |
| required this.packageConfigUri, |
| required this.packageConfig, |
| required this.runPackageName, |
| required this.includeDevDependencies, |
| required this.verbose, |
| Target? target, |
| }) : target = target ?? Target.current; |
| |
| /// Compiles all native assets for host OS in JIT mode. |
| /// |
| /// If provided, only native assets of all transitive dependencies of |
| /// [runPackageName] are built. |
| Future<List<EncodedAsset>?> compileNativeAssetsJit() async { |
| final buildResult = await _buildNativeAssetsShared(linkingEnabled: false); |
| if (buildResult == null) return null; |
| return buildResult.encodedAssets; |
| } |
| |
| /// Compiles all native assets for host OS in JIT mode, and creates the |
| /// native assets yaml file. |
| /// |
| /// If provided, only native assets of all transitive dependencies of |
| /// [runPackageName] are built. |
| /// |
| /// Used in `dart run` and `dart test`. |
| Future<Uri?> compileNativeAssetsJitYamlFile() async { |
| final assets = await compileNativeAssetsJit(); |
| if (assets == null) return null; |
| |
| final dartToolUri = Directory.current.uri.resolve('.dart_tool/'); |
| final outputUri = dartToolUri.resolve('native_assets/'); |
| await Directory.fromUri(outputUri).create(recursive: true); |
| |
| final kernelAssets = await bundleNativeAssets( |
| assets, |
| target, |
| outputUri, |
| relocatable: false, |
| ); |
| |
| return await writeNativeAssetsYaml( |
| kernelAssets, |
| dartToolUri, |
| header: '''# Native assets mapping for host OS in JIT mode. |
| # Generated by dartdev and package:hooks_runner. |
| ''', |
| ); |
| } |
| |
| Future<bool> warnOnNativeAssets() async { |
| final builder = await _nativeAssetsBuildRunner; |
| final packageNames = await builder.packagesWithBuildHooks(); |
| if (packageNames.isEmpty) return false; |
| log.stderr( |
| 'Package(s) $packageNames require the native assets feature to be enabled. ' |
| 'Enable native assets with `--enable-experiment=native-assets`.', |
| ); |
| return true; |
| } |
| |
| late final _extensions = [ |
| CodeAssetExtension( |
| targetOS: target.os, |
| linkModePreference: LinkModePreference.dynamic, |
| targetArchitecture: target.architecture, |
| macOS: _macOSConfig, |
| cCompiler: _cCompilerConfig, |
| ), |
| // TODO(dacoharkes,mosum): This should be gated behind a data-assets |
| // experiment flag. |
| DataAssetsExtension(), |
| ]; |
| |
| Future<BuildResult?> _buildNativeAssetsShared({ |
| required bool linkingEnabled, |
| }) async { |
| final builder = await _nativeAssetsBuildRunner; |
| final buildResult = await builder.build( |
| extensions: _extensions, |
| linkingEnabled: linkingEnabled, |
| ); |
| if (buildResult.isFailure) return null; |
| return buildResult.success; |
| } |
| |
| Future<BuildResult?> buildNativeAssetsAOT() { |
| return _buildNativeAssetsShared(linkingEnabled: true); |
| } |
| |
| Future<LinkResult?> linkNativeAssetsAOT({ |
| required String? recordedUsagesPath, |
| required BuildResult buildResult, |
| }) async { |
| final builder = await _nativeAssetsBuildRunner; |
| final linkResult = await builder.link( |
| extensions: _extensions, |
| resourceIdentifiers: |
| recordedUsagesPath != null ? Uri.file(recordedUsagesPath) : null, |
| buildResult: buildResult, |
| ); |
| if (linkResult.isFailure) return null; |
| return linkResult.success; |
| } |
| |
| final Target target; |
| |
| late final _macOSConfig = target.os == OS.macOS |
| ? MacOSCodeConfig(targetVersion: minimumSupportedMacOSVersion) |
| : null; |
| |
| late final _cCompilerConfig = _getCCompilerConfig(target.os); |
| |
| CCompilerConfig? _getCCompilerConfig(OS targetOS) { |
| // Specifically for running our tests on Dart CI with the test runner, we |
| // recognize specific variables to setup the C Compiler configuration. |
| final env = Platform.environment; |
| final cc = env['DART_HOOK_TESTING_C_COMPILER__CC']; |
| final ar = env['DART_HOOK_TESTING_C_COMPILER__AR']; |
| final ld = env['DART_HOOK_TESTING_C_COMPILER__LD']; |
| final envScript = env['DART_HOOK_TESTING_C_COMPILER__ENV_SCRIPT']; |
| final envScriptArgs = |
| env['DART_HOOK_TESTING_C_COMPILER__ENV_SCRIPT_ARGUMENTS'] |
| ?.split(' ') |
| .map((arg) => arg.trim()) |
| .where((arg) => arg.isNotEmpty) |
| .toList(); |
| if (cc != null && ar != null && ld != null) { |
| return CCompilerConfig( |
| archiver: Uri.file(ar), |
| compiler: Uri.file(cc), |
| linker: Uri.file(ld), |
| windows: targetOS == OS.windows |
| ? WindowsCCompilerConfig( |
| developerCommandPrompt: envScript == null |
| ? null |
| : DeveloperCommandPrompt( |
| script: Uri.file(envScript), |
| arguments: envScriptArgs ?? [], |
| ), |
| ) |
| : null, |
| ); |
| } |
| return null; |
| } |
| |
| /// Runs `pub get` if no package config can be found. |
| /// |
| /// Returns `null` if no package config can be found, even after pub get. |
| static Future<Uri?> ensurePackageConfig(Uri uri) async { |
| var packageConfig = await _findPackageConfigUri(uri); |
| // TODO(https://github.com/dart-lang/package_config/issues/126): Use |
| // package config resolution from package:package_config. |
| if (packageConfig == null) { |
| final pubspecMaybe = await findPubspec(uri); |
| if (pubspecMaybe != null) { |
| // Silently run `pub get`, this is what would happen in |
| // `getExecutableForCommand` later. |
| final result = await Process.run(sdk.dart, ['pub', 'get']); |
| if (result.exitCode != 0) { |
| return null; |
| } |
| packageConfig = await _findPackageConfigUri(uri); |
| } else { |
| return null; |
| } |
| } |
| return packageConfig; |
| } |
| |
| /// Tries to load the package config. |
| /// |
| /// Returns null and writes to stderr if the package config is malformed. |
| static Future<package_config.PackageConfig?> loadPackageConfig( |
| Uri packageConfigUri) async { |
| try { |
| return await package_config.loadPackageConfigUri(packageConfigUri); |
| } on FormatException catch (e) { |
| // This can be thrown if the package_config.json is malformed or has |
| // duplicate entries. |
| log.stderr( |
| 'Error encountered while parsing ' |
| '${packageConfigUri.toFilePath()}: ${e.message}.', |
| ); |
| return null; |
| } |
| } |
| |
| /// Finds the package config uri. |
| /// |
| /// Returns `null` if no package config can be found. |
| // TODO(https://github.com/dart-lang/package_config/issues/126): Expose this |
| // logic in package:package_config. |
| static Future<Uri?> _findPackageConfigUri(Uri uri) async { |
| while (true) { |
| final packageConfig = |
| File.fromUri(uri.resolve('.dart_tool/package_config.json')); |
| final packageGraph = |
| File.fromUri(uri.resolve('.dart_tool/package_graph.json')); |
| if (await packageConfig.exists() && await packageGraph.exists()) { |
| return packageConfig.uri; |
| } |
| final parent = uri.resolve('..'); |
| if (parent == uri) { |
| return null; |
| } |
| uri = parent; |
| } |
| } |
| |
| static Future<Uri?> findPubspec(Uri uri) async { |
| while (true) { |
| final candidate = uri.resolve('pubspec.yaml'); |
| if (await File.fromUri(candidate).exists()) { |
| return candidate; |
| } |
| final parent = uri.resolve('..'); |
| if (parent == uri) { |
| return null; |
| } |
| uri = parent; |
| } |
| } |
| |
| static Future<Uri?> findWorkspacePubspec(Uri? workspacePackageConfig) async { |
| if (workspacePackageConfig == null) { |
| return null; |
| } |
| final candidate = workspacePackageConfig.resolve('../pubspec.yaml'); |
| if (File.fromUri(candidate).existsSync()) { |
| return candidate; |
| } |
| return null; |
| } |
| |
| /// Tries to find the package name that [uri] is in. |
| /// |
| /// Returns `null` if package cannnot be determined. |
| static Future<String?> findRootPackageName(Uri uri) async { |
| final pubspecUri = await findPubspec(uri); |
| if (pubspecUri == null) { |
| return null; |
| } |
| final pubspecFile = File.fromUri(pubspecUri); |
| final contents = await pubspecFile.readAsString(); |
| final pubspec = loadYaml(contents); |
| return pubspec['name']; |
| } |
| } |