diff --git a/.github/workflows/native.yaml b/.github/workflows/native.yaml index f4dd54ad7..1aa5e0e96 100644 --- a/.github/workflows/native.yaml +++ b/.github/workflows/native.yaml @@ -80,13 +80,34 @@ jobs: - run: dart pub get -C test_data/package_with_metadata/ if: ${{ matrix.package == 'native_assets_builder' }} - - run: dart pub get -C example/native_add_app/ + - run: dart pub get -C test_data/simple_link/ + if: ${{ matrix.package == 'native_assets_builder' }} + + - run: dart pub get -C test_data/complex_link/ + if: ${{ matrix.package == 'native_assets_builder' }} + + - run: dart pub get -C test_data/complex_link_helper/ + if: ${{ matrix.package == 'native_assets_builder' }} + + - run: dart pub get -C test_data/drop_dylib_link/ + if: ${{ matrix.package == 'native_assets_builder' }} + + - run: dart pub get -C test_data/add_asset_link/ + if: ${{ matrix.package == 'native_assets_builder' }} + + - run: dart pub get -C example/build/native_add_app/ + if: ${{ matrix.package == 'native_assets_cli' }} + + - run: dart pub get -C example/build/native_add_library/ + if: ${{ matrix.package == 'native_assets_cli' }} + + - run: dart pub get -C example/build/use_dart_api/ if: ${{ matrix.package == 'native_assets_cli' }} - - run: dart pub get -C example/native_add_library/ + - run: dart pub get -C example/link/package_with_assets/ if: ${{ matrix.package == 'native_assets_cli' }} - - run: dart pub get -C example/use_dart_api/ + - run: dart pub get -C example/link/app_with_asset_treeshaking/ if: ${{ matrix.package == 'native_assets_cli' }} - run: dart analyze --fatal-infos @@ -103,23 +124,23 @@ jobs: if: ${{ matrix.sdk == 'stable' }} - run: dart --enable-experiment=native-assets test - working-directory: pkgs/${{ matrix.package }}/example/native_add_app/ + working-directory: pkgs/${{ matrix.package }}/example/build/native_add_app/ if: ${{ matrix.package == 'native_assets_cli' && matrix.sdk == 'dev' && !matrix.breaking-change }} - run: dart --enable-experiment=native-assets run - working-directory: pkgs/${{ matrix.package }}/example/native_add_app/ + working-directory: pkgs/${{ matrix.package }}/example/build/native_add_app/ if: ${{ matrix.package == 'native_assets_cli' && matrix.sdk == 'dev' && !matrix.breaking-change }} - run: dart --enable-experiment=native-assets build bin/native_add_app.dart - working-directory: pkgs/${{ matrix.package }}/example/native_add_app/ + working-directory: pkgs/${{ matrix.package }}/example/build/native_add_app/ if: ${{ matrix.package == 'native_assets_cli' && matrix.sdk == 'dev' && !matrix.breaking-change }} - run: ./native_add_app.exe - working-directory: pkgs/${{ matrix.package }}/example/native_add_app/bin/native_add_app/ + working-directory: pkgs/${{ matrix.package }}/example/build/native_add_app/bin/native_add_app/ if: ${{ matrix.package == 'native_assets_cli' && matrix.sdk == 'dev' && !matrix.breaking-change }} - run: dart --enable-experiment=native-assets test - working-directory: pkgs/${{ matrix.package }}/example/use_dart_api/ + working-directory: pkgs/${{ matrix.package }}/example/build/use_dart_api/ if: ${{ matrix.package == 'native_assets_cli' && matrix.sdk == 'dev' && !matrix.breaking-change }} - name: Install coverage diff --git a/pkgs/native_assets_builder/CHANGELOG.md b/pkgs/native_assets_builder/CHANGELOG.md index d79e1a4a4..5e00b7615 100644 --- a/pkgs/native_assets_builder/CHANGELOG.md +++ b/pkgs/native_assets_builder/CHANGELOG.md @@ -1,6 +1,7 @@ -## 0.6.1 +## 0.7.0-wip - Fix test. +- Add support for `hook/link.dart`. ## 0.6.0 diff --git a/pkgs/native_assets_builder/lib/native_assets_builder.dart b/pkgs/native_assets_builder/lib/native_assets_builder.dart index 9dfa400af..d3d92db0b 100644 --- a/pkgs/native_assets_builder/lib/native_assets_builder.dart +++ b/pkgs/native_assets_builder/lib/native_assets_builder.dart @@ -3,5 +3,8 @@ // BSD-style license that can be found in the LICENSE file. export 'package:native_assets_builder/src/build_runner/build_runner.dart'; +export 'package:native_assets_builder/src/model/build_result.dart'; +export 'package:native_assets_builder/src/model/dry_run_result.dart'; export 'package:native_assets_builder/src/model/kernel_assets.dart'; +export 'package:native_assets_builder/src/model/link_result.dart'; export 'package:native_assets_builder/src/package_layout/package_layout.dart'; diff --git a/pkgs/native_assets_builder/lib/src/build_runner/build_planner.dart b/pkgs/native_assets_builder/lib/src/build_runner/build_planner.dart index 0df0cbff3..e6e129518 100644 --- a/pkgs/native_assets_builder/lib/src/build_runner/build_planner.dart +++ b/pkgs/native_assets_builder/lib/src/build_runner/build_planner.dart @@ -83,6 +83,10 @@ class NativeAssetsBuildPlanner { } } +/// A graph of package dependencies. +/// +/// This is encoded as a mapping from package name to list of package +/// dependencies. class PackageGraph { final Map> map; diff --git a/pkgs/native_assets_builder/lib/src/build_runner/build_runner.dart b/pkgs/native_assets_builder/lib/src/build_runner/build_runner.dart index 8de8c5566..9ed7b51c8 100644 --- a/pkgs/native_assets_builder/lib/src/build_runner/build_runner.dart +++ b/pkgs/native_assets_builder/lib/src/build_runner/build_runner.dart @@ -9,6 +9,10 @@ import 'package:logging/logging.dart'; import 'package:native_assets_cli/native_assets_cli_internal.dart'; import 'package:package_config/package_config.dart'; +import '../model/build_result.dart'; +import '../model/dry_run_result.dart'; +import '../model/hook_result.dart'; +import '../model/link_result.dart'; import '../package_layout/package_layout.dart'; import '../utils/run_process.dart'; import 'build_planner.dart'; @@ -33,7 +37,7 @@ class NativeAssetsBuildRunner { /// This method is invoked by launchers such as dartdev (for `dart run`) and /// flutter_tools (for `flutter run` and `flutter build`). /// - /// If provided, only native assets of all transitive dependencies of + /// If provided, only assets of all transitive dependencies of /// [runPackageName] are built. Future build({ required LinkModePreferenceImpl linkModePreference, @@ -47,39 +51,90 @@ class NativeAssetsBuildRunner { PackageLayout? packageLayout, String? runPackageName, Iterable? supportedAssetTypes, - }) async { - packageLayout ??= await PackageLayout.fromRootPackageRoot(workingDirectory); - final packagesWithNativeAssets = - await packageLayout.packagesWithNativeAssets; - final List buildPlan; - final PackageGraph packageGraph; - if (packagesWithNativeAssets.length <= 1 && runPackageName == null) { - buildPlan = packagesWithNativeAssets; - packageGraph = PackageGraph({ - for (final p in packagesWithNativeAssets) p.name: [], - }); - } else { - final planner = await NativeAssetsBuildPlanner.fromRootPackageRoot( - rootPackageRoot: packageLayout.rootPackageRoot, - packagesWithNativeAssets: packagesWithNativeAssets, - dartExecutable: Uri.file(Platform.resolvedExecutable), - logger: logger, + }) async => + _run( + hook: Hook.build, + linkModePreference: linkModePreference, + target: target, + workingDirectory: workingDirectory, + buildMode: buildMode, + cCompilerConfig: cCompilerConfig, + targetIOSSdk: targetIOSSdk, + targetAndroidNdkApi: targetAndroidNdkApi, + includeParentEnvironment: includeParentEnvironment, + packageLayout: packageLayout, + runPackageName: runPackageName, + supportedAssetTypes: supportedAssetTypes, ); - final (plan, planSuccess) = planner.plan( + + /// [workingDirectory] is expected to contain `.dart_tool`. + /// + /// This method is invoked by launchers such as dartdev (for `dart run`) and + /// flutter_tools (for `flutter run` and `flutter build`). + /// + /// If provided, only assets of all transitive dependencies of + /// [runPackageName] are linked. + Future link({ + required Target target, + required Uri workingDirectory, + required BuildModeImpl buildMode, + CCompilerConfigImpl? cCompilerConfig, + IOSSdkImpl? targetIOSSdk, + int? targetAndroidNdkApi, + required bool includeParentEnvironment, + PackageLayout? packageLayout, + Uri? resourceIdentifiers, + String? runPackageName, + Iterable? supportedAssetTypes, + required BuildResult buildResult, + }) async => + _run( + hook: Hook.link, + linkModePreference: LinkModePreferenceImpl.dynamic, + target: target, + workingDirectory: workingDirectory, + buildMode: buildMode, + cCompilerConfig: cCompilerConfig, + targetIOSSdk: targetIOSSdk, + targetAndroidNdkApi: targetAndroidNdkApi, + includeParentEnvironment: includeParentEnvironment, + packageLayout: packageLayout, runPackageName: runPackageName, + resourceIdentifiers: resourceIdentifiers, + supportedAssetTypes: supportedAssetTypes, + buildResult: buildResult, ); - if (!planSuccess) { - return _BuildResultImpl( - assets: [], - dependencies: [], - success: false, - ); - } - buildPlan = plan; - packageGraph = planner.packageGraph; + + /// The common method for running building or linking of assets. + Future _run({ + required Hook hook, + required LinkModePreferenceImpl linkModePreference, + required Target target, + required Uri workingDirectory, + required BuildModeImpl buildMode, + CCompilerConfigImpl? cCompilerConfig, + IOSSdkImpl? targetIOSSdk, + int? targetAndroidNdkApi, + required bool includeParentEnvironment, + PackageLayout? packageLayout, + Uri? resourceIdentifiers, + String? runPackageName, + Iterable? supportedAssetTypes, + BuildResult? buildResult, + }) async { + assert(hook == Hook.link || buildResult == null); + + packageLayout ??= await PackageLayout.fromRootPackageRoot(workingDirectory); + final packagesWithAssets = await packageLayout.packagesWithAssets(hook); + final (buildPlan, packageGraph, planSuccess) = await _plannedPackages( + packagesWithAssets, + packageLayout, + runPackageName, + ); + final hookResult = HookResult.failure(); + if (!planSuccess) { + return hookResult; } - final assets = []; - final dependencies = []; final metadata = {}; var success = true; for (final package in buildPlan) { @@ -89,41 +144,106 @@ class NativeAssetsBuildRunner { targetMetadata: metadata, ); final config = await _cliConfig( - packageName: package.name, - packageRoot: packageLayout.packageRoot(package.name), - target: target, - buildMode: buildMode, - linkMode: linkModePreference, - buildParentDir: packageLayout.dartToolNativeAssetsBuilder, - dependencyMetadata: dependencyMetadata, - cCompilerConfig: cCompilerConfig, - targetIOSSdk: targetIOSSdk, - targetAndroidNdkApi: targetAndroidNdkApi, - supportedAssetTypes: supportedAssetTypes, + package, + packageLayout, + target, + buildMode, + linkModePreference, + dependencyMetadata, + cCompilerConfig, + targetIOSSdk, + targetAndroidNdkApi, + supportedAssetTypes, + hook, + resourceIdentifiers, + buildResult, ); - final ( - packageAssets, - packageDependencies, - packageMetadata, - packageSuccess, - ) = await _buildPackageCached( + + final (buildOutput, packageSuccess) = await _runHookForPackageCached( + hook, config, packageLayout.packageConfigUri, workingDirectory, includeParentEnvironment, + resourceIdentifiers, ); - assets.addAll(packageAssets); - dependencies.addAll(packageDependencies); + hookResult.add(buildOutput); success &= packageSuccess; - if (packageMetadata != null) { - metadata[config.packageName] = packageMetadata; - } + + metadata[config.packageName] = buildOutput.metadata; } - return _BuildResultImpl( - assets: assets, - dependencies: dependencies..sort(_uriCompare), - success: success, + + return hookResult.withSuccess(success); + } + + Future _cliConfig( + Package package, + PackageLayout packageLayout, + Target target, + BuildModeImpl buildMode, + LinkModePreferenceImpl linkModePreference, + DependencyMetadata? dependencyMetadata, + CCompilerConfigImpl? cCompilerConfig, + IOSSdkImpl? targetIOSSdk, + int? targetAndroidNdkApi, + Iterable? supportedAssetTypes, + Hook hook, + Uri? resourceIdentifiers, + BuildResult? buildResult, + ) async { + final buildDirName = HookConfigImpl.checksum( + packageName: package.name, + packageRoot: package.root, + targetOS: target.os, + targetArchitecture: target.architecture, + buildMode: buildMode, + linkModePreference: linkModePreference, + targetIOSSdk: targetIOSSdk, + cCompiler: cCompilerConfig, + dependencyMetadata: dependencyMetadata, + targetAndroidNdkApi: targetAndroidNdkApi, + supportedAssetTypes: supportedAssetTypes, + hook: hook, ); + final outDirUri = + packageLayout.dartToolNativeAssetsBuilder.resolve('$buildDirName/out/'); + final outDir = Directory.fromUri(outDirUri); + if (!await outDir.exists()) { + // TODO(https://dartbug.com/50565): Purge old or unused folders. + await outDir.create(recursive: true); + } + + if (hook == Hook.link) { + return LinkConfigImpl( + outputDirectory: outDirUri, + packageName: package.name, + packageRoot: package.root, + targetOS: target.os, + targetArchitecture: target.architecture, + buildMode: buildMode, + targetIOSSdk: targetIOSSdk, + cCompiler: cCompilerConfig, + targetAndroidNdkApi: targetAndroidNdkApi, + resourceIdentifierUri: resourceIdentifiers, + assets: buildResult!.assetsForLinking[package.name] ?? [], + supportedAssetTypes: supportedAssetTypes, + linkModePreference: LinkModePreferenceImpl.preferStatic, + ); + } else { + return BuildConfigImpl( + outputDirectory: outDirUri, + packageName: package.name, + packageRoot: package.root, + targetOS: target.os, + targetArchitecture: target.architecture, + buildMode: buildMode, + linkModePreference: linkModePreference, + targetIOSSdk: targetIOSSdk, + cCompiler: cCompilerConfig, + dependencyMetadata: dependencyMetadata, + targetAndroidNdkApi: targetAndroidNdkApi, + ); + } } /// [workingDirectory] is expected to contain `.dart_tool`. @@ -143,30 +263,17 @@ class NativeAssetsBuildRunner { Iterable? supportedAssetTypes, }) async { packageLayout ??= await PackageLayout.fromRootPackageRoot(workingDirectory); - final packagesWithNativeAssets = - await packageLayout.packagesWithNativeAssets; - final List buildPlan; - if (packagesWithNativeAssets.length <= 1 && runPackageName == null) { - buildPlan = packagesWithNativeAssets; - } else { - final planner = await NativeAssetsBuildPlanner.fromRootPackageRoot( - rootPackageRoot: packageLayout.rootPackageRoot, - packagesWithNativeAssets: packagesWithNativeAssets, - dartExecutable: Uri.file(Platform.resolvedExecutable), - logger: logger, - ); - final (plan, planSuccess) = planner.plan( - runPackageName: runPackageName, - ); - if (!planSuccess) { - return _DryRunResultImpl( - assets: [], - success: false, - ); - } - buildPlan = plan; + final packagesWithBuild = + await packageLayout.packagesWithAssets(Hook.build); + final (buildPlan, _, planSuccess) = await _plannedPackages( + packagesWithBuild, + packageLayout, + runPackageName, + ); + final hookResult = HookResult.failure(); + if (!planSuccess) { + return hookResult; } - final assets = []; var success = true; for (final package in buildPlan) { final config = await _cliConfigDryRun( @@ -177,95 +284,89 @@ class NativeAssetsBuildRunner { buildParentDir: packageLayout.dartToolNativeAssetsBuilder, supportedAssetTypes: supportedAssetTypes, ); - final (packageAssets, _, _, packageSuccess) = await _buildPackage( + final (buildOutput, packageSuccess) = await _runHookForPackage( + Hook.build, config, packageLayout.packageConfigUri, workingDirectory, includeParentEnvironment, - dryRun: true, + null, ); - for (final asset in packageAssets) { + for (final asset in buildOutput.assets) { switch (asset) { case NativeCodeAssetImpl _: if (asset.architecture != null) { // Backwards compatibility, if an architecture is provided use it. - assets.add(asset); + hookResult.assets.add(asset); } else { // Dry run does not report architecture. Dart VM branches on OS // and Target when looking up assets, so populate assets for all // architectures. for (final architecture in asset.os.architectures) { - assets.add(asset.copyWith( - architecture: architecture, - )); + hookResult.assets + .add(asset.copyWith(architecture: architecture)); } } case DataAssetImpl _: - assets.add(asset); + hookResult.assets.add(asset); } } success &= packageSuccess; } - return _DryRunResultImpl( - assets: assets, - success: success, - ); + return hookResult.withSuccess(success); } - Future<_PackageBuildRecord> _buildPackageCached( - BuildConfigImpl config, + Future<_PackageBuildRecord> _runHookForPackageCached( + Hook hook, + HookConfigImpl config, Uri packageConfigUri, Uri workingDirectory, bool includeParentEnvironment, + Uri? resources, ) async { - final packageName = config.packageName; final outDir = config.outputDirectory; if (!await Directory.fromUri(outDir).exists()) { await Directory.fromUri(outDir).create(recursive: true); } - final buildOutput = await BuildOutputImpl.readFromFile(outDir: outDir); - final lastBuilt = buildOutput?.timestamp.roundDownToSeconds() ?? - DateTime.fromMillisecondsSinceEpoch(0); - final dependencies = buildOutput?.dependenciesModel; - final lastChange = await dependencies?.lastModified() ?? DateTime.now(); - - if (lastBuilt.isAfter(lastChange)) { - logger.info('Skipping build for $packageName in $outDir. ' - 'Last build on $lastBuilt, last input change on $lastChange.'); - // All build flags go into [outDir]. Therefore we do not have to check - // here whether the config is equal. - final assets = buildOutput!.assets; - final dependencies = buildOutput.dependencies; - final metadata = buildOutput.metadataModel; - return (assets, dependencies, metadata, true); + final hookOutput = HookOutputImpl.readFromFile(file: config.outputFile); + if (hookOutput != null) { + final lastBuilt = hookOutput.timestamp.roundDownToSeconds(); + final lastChange = await hookOutput.dependenciesModel.lastModified(); + + if (lastBuilt.isAfter(lastChange)) { + logger + .info('Skipping ${hook.name} for ${config.packageName} in $outDir. ' + 'Last build on $lastBuilt, last input change on $lastChange.'); + // All build flags go into [outDir]. Therefore we do not have to check + // here whether the config is equal. + return (hookOutput, true); + } } - return await _buildPackage( + return await _runHookForPackage( + hook, config, packageConfigUri, workingDirectory, includeParentEnvironment, - dryRun: false, + resources, ); } - Future<_PackageBuildRecord> _buildPackage( - BuildConfigImpl config, + Future<_PackageBuildRecord> _runHookForPackage( + Hook hook, + HookConfigImpl config, Uri packageConfigUri, Uri workingDirectory, - bool includeParentEnvironment, { - required bool dryRun, - }) async { - final outDir = config.outputDirectory; - final configFile = outDir.resolve('../config.json'); - final buildHook = config.packageRoot.resolve('hook/').resolve('build.dart'); - final buildHookLegacy = config.packageRoot.resolve('build.dart'); + bool includeParentEnvironment, + Uri? resources, + ) async { + final configFile = config.configFile; final configFileContents = config.toJsonString(); logger.info('config.json contents: $configFileContents'); await File.fromUri(configFile).writeAsString(configFileContents); - final buildOutputFile = - File.fromUri(outDir.resolve(BuildOutputImpl.fileNameV1_1_0)); + final buildOutputFile = File.fromUri(config.outputFile); if (await buildOutputFile.exists()) { // Ensure we'll never read outdated build results. await buildOutputFile.delete(); @@ -273,11 +374,9 @@ class NativeAssetsBuildRunner { final arguments = [ '--packages=${packageConfigUri.toFilePath()}', - if (await File.fromUri(buildHook).exists()) - buildHook.toFilePath() - else - buildHookLegacy.toFilePath(), + config.script.toFilePath(), '--config=${configFile.toFilePath()}', + if (resources != null) resources.toFilePath(), ]; final result = await runProcess( workingDirectory: workingDirectory, @@ -298,7 +397,7 @@ class NativeAssetsBuildRunner { logger.severe( ''' Building native assets for package:${config.packageName} failed. -build.dart returned with exit code: ${result.exitCode}. +${config.script} returned with exit code: ${result.exitCode}. To reproduce run: $commandString stderr: @@ -311,20 +410,27 @@ ${result.stdout} } try { - final buildOutput = await BuildOutputImpl.readFromFile(outDir: outDir); - final assets = buildOutput?.assets ?? []; - success &= validateAssetsPackage(assets, config.packageName); - final dependencies = buildOutput?.dependencies ?? []; - final metadata = dryRun ? null : buildOutput?.metadataModel; - return (assets, dependencies, metadata, success); + final buildOutput = + HookOutputImpl.readFromFile(file: config.outputFile) ?? + HookOutputImpl(); + //As a link.dart can pipe through assets from other packages. + if (hook == Hook.build) { + success &= validateAssetsPackage( + buildOutput.assets, + config.packageName, + ); + } + return (buildOutput, success); } on FormatException catch (e) { logger.severe(''' Building native assets for package:${config.packageName} failed. -build_output.json contained a format error. +${config.outputName} contained a format error. + +Contents: ${File.fromUri(config.outputFile).readAsStringSync()}. ${e.message} '''); success = false; - return ([], [], const Metadata({}), false); + return (HookOutputImpl(), false); // TODO(https://github.com/dart-lang/native/issues/109): Stop throwing // type errors in native_assets_cli, release a new version of that package // and then remove this. @@ -332,14 +438,14 @@ ${e.message} } on TypeError { logger.severe(''' Building native assets for package:${config.packageName} failed. -build_output.json contained a format error. +${config.outputName} contained a type error. + +Contents: ${File.fromUri(config.outputFile).readAsStringSync()}. '''); success = false; - return ([], [], const Metadata({}), false); + return (HookOutputImpl(), false); } finally { if (!success) { - final buildOutputFile = - File.fromUri(outDir.resolve(BuildOutputImpl.fileNameV1_1_0)); if (await buildOutputFile.exists()) { await buildOutputFile.delete(); } @@ -347,53 +453,6 @@ build_output.json contained a format error. } } - static Future _cliConfig({ - required String packageName, - required Uri packageRoot, - required Target target, - IOSSdkImpl? targetIOSSdk, - int? targetAndroidNdkApi, - required BuildModeImpl buildMode, - required LinkModePreferenceImpl linkMode, - required Uri buildParentDir, - CCompilerConfigImpl? cCompilerConfig, - DependencyMetadata? dependencyMetadata, - Iterable? supportedAssetTypes, - }) async { - final buildDirName = BuildConfigImpl.checksum( - packageName: packageName, - packageRoot: packageRoot, - targetOS: target.os, - targetArchitecture: target.architecture, - buildMode: buildMode, - linkModePreference: linkMode, - targetIOSSdk: targetIOSSdk, - cCompiler: cCompilerConfig, - dependencyMetadata: dependencyMetadata, - targetAndroidNdkApi: targetAndroidNdkApi, - supportedAssetTypes: supportedAssetTypes, - ); - final outDirUri = buildParentDir.resolve('$buildDirName/out/'); - final outDir = Directory.fromUri(outDirUri); - if (!await outDir.exists()) { - // TODO(https://dartbug.com/50565): Purge old or unused folders. - await outDir.create(recursive: true); - } - return BuildConfigImpl( - outDir: outDirUri, - packageName: packageName, - packageRoot: packageRoot, - targetOS: target.os, - targetArchitecture: target.architecture, - buildMode: buildMode, - linkModePreference: linkMode, - targetIOSSdk: targetIOSSdk, - cCompiler: cCompilerConfig, - dependencyMetadata: dependencyMetadata, - targetAndroidNdkApi: targetAndroidNdkApi, - ); - } - static Future _cliConfigDryRun({ required String packageName, required Uri packageRoot, @@ -409,7 +468,7 @@ build_output.json contained a format error. await outDir.create(recursive: true); } return BuildConfigImpl.dryRun( - outDir: outDirUri, + outputDirectory: outDirUri, packageName: packageName, packageRoot: packageRoot, targetOS: targetOS, @@ -436,84 +495,51 @@ build_output.json contained a format error. bool validateAssetsPackage(Iterable assets, String packageName) { final invalidAssetIds = assets .map((a) => a.id) - .where((n) => !n.startsWith('package:$packageName/')) + .where((id) => !id.startsWith('package:$packageName/')) .toSet() .toList() ..sort(); - final success = invalidAssetIds.isEmpty; - if (!success) { + if (invalidAssetIds.isNotEmpty) { logger.severe( '`package:$packageName` declares the following assets which do not ' 'start with `package:$packageName/`: ${invalidAssetIds.join(', ')}.', ); + return false; + } else { + return true; } - return success; } -} - -typedef _PackageBuildRecord = ( - Iterable, - Iterable dependencies, - Metadata?, - bool success, -); - -/// The result from a [NativeAssetsBuildRunner.dryRun]. -abstract interface class DryRunResult { - /// The native assets for all [Target]s for the build or dry run. - List get assets; - /// Whether all builds completed without errors. - /// - /// All error messages are streamed to [NativeAssetsBuildRunner.logger]. - bool get success; -} - -final class _DryRunResultImpl implements DryRunResult { - @override - final List assets; - - @override - final bool success; - - _DryRunResultImpl({ - required this.assets, - required this.success, - }); -} - -/// The result from a [NativeAssetsBuildRunner.build]. -abstract class BuildResult implements DryRunResult { - /// All the files used for building the native assets of all packages. - /// - /// This aggregated list can be used to determine whether the - /// [NativeAssetsBuildRunner] needs to be invoked again. The - /// [NativeAssetsBuildRunner] determines per package with native assets - /// if it needs to run the build again. - List get dependencies; + Future<(List plan, PackageGraph dependencyGraph, bool success)> + _plannedPackages( + List packagesWithNativeAssets, + PackageLayout packageLayout, + String? runPackageName, + ) async { + if (packagesWithNativeAssets.length <= 1 && runPackageName == null) { + final dependencyGraph = PackageGraph({ + for (final p in packagesWithNativeAssets) p.name: [], + }); + return (packagesWithNativeAssets, dependencyGraph, true); + } else { + final planner = await NativeAssetsBuildPlanner.fromRootPackageRoot( + rootPackageRoot: packageLayout.rootPackageRoot, + packagesWithNativeAssets: packagesWithNativeAssets, + dartExecutable: Uri.file(Platform.resolvedExecutable), + logger: logger, + ); + final (plan, planSuccess) = planner.plan( + runPackageName: runPackageName, + ); + return (plan, planner.packageGraph, planSuccess); + } + } } -final class _BuildResultImpl implements BuildResult { - @override - final List assets; - - @override - final List dependencies; - - @override - final bool success; - - _BuildResultImpl({ - required this.assets, - required this.dependencies, - required this.success, - }); -} +typedef _PackageBuildRecord = (HookOutputImpl, bool success); extension on DateTime { DateTime roundDownToSeconds() => DateTime.fromMillisecondsSinceEpoch(millisecondsSinceEpoch - millisecondsSinceEpoch % const Duration(seconds: 1).inMilliseconds); } - -int _uriCompare(Uri u1, Uri u2) => u1.toString().compareTo(u2.toString()); diff --git a/pkgs/native_assets_builder/lib/src/model/build_result.dart b/pkgs/native_assets_builder/lib/src/model/build_result.dart new file mode 100644 index 000000000..9857989ad --- /dev/null +++ b/pkgs/native_assets_builder/lib/src/model/build_result.dart @@ -0,0 +1,17 @@ +// Copyright (c) 2024, 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 'package:native_assets_cli/native_assets_cli_internal.dart'; + +/// The result of a build, executing the build.dart hooks from all packages in +/// the dependency tree of the entry point application. +abstract class BuildResult { + List get dependencies; + + bool get success; + + List get assets; + + Map> get assetsForLinking; +} diff --git a/pkgs/native_assets_builder/lib/src/model/dry_run_result.dart b/pkgs/native_assets_builder/lib/src/model/dry_run_result.dart new file mode 100644 index 000000000..e451d44e8 --- /dev/null +++ b/pkgs/native_assets_builder/lib/src/model/dry_run_result.dart @@ -0,0 +1,19 @@ +// Copyright (c) 2024, 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 'package:native_assets_cli/native_assets_cli_internal.dart'; + +import '../../native_assets_builder.dart'; + +/// Similar to a [BuildResult], but for the case of a dry run, where not assets +/// are actually produced. +abstract interface class DryRunResult { + /// The native assets for all [Target]s for the dry run. + List get assets; + + /// Whether all builds completed without errors. + /// + /// All error messages are streamed to [NativeAssetsBuildRunner.logger]. + bool get success; +} diff --git a/pkgs/native_assets_builder/lib/src/model/hook_result.dart b/pkgs/native_assets_builder/lib/src/model/hook_result.dart new file mode 100644 index 000000000..1aed91f2b --- /dev/null +++ b/pkgs/native_assets_builder/lib/src/model/hook_result.dart @@ -0,0 +1,71 @@ +// Copyright (c) 2024, 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 'package:collection/collection.dart'; +import 'package:native_assets_cli/native_assets_cli_internal.dart'; + +import '../../native_assets_builder.dart'; + +/// The result from a [NativeAssetsBuildRunner.build] or +/// [NativeAssetsBuildRunner.link]. +final class HookResult implements BuildResult, DryRunResult, LinkResult { + @override + final List assets; + + @override + final Map> assetsForLinking; + + @override + final List dependencies; + + @override + final bool success; + + HookResult._({ + required this.assets, + required this.assetsForLinking, + required this.dependencies, + required this.success, + }); + + HookResult.failure() + : this._( + assets: [], + assetsForLinking: {}, + dependencies: [], + success: false, + ); + + void add(HookOutputImpl buildOutput) { + assets.addAll(buildOutput.assets); + final mergedMaps = mergeMaps( + assetsForLinking, + buildOutput.assetsForLinking, + value: (assets1, assets2) { + final twoInOne = assets1.where((asset) => assets2.contains(asset)); + final oneInTwo = assets2.where((asset) => assets1.contains(asset)); + if (twoInOne.isNotEmpty || oneInTwo.isNotEmpty) { + throw ArgumentError( + 'Found assets with same IDs, ${[...oneInTwo, ...twoInOne]}'); + } + return [ + ...assets1, + ...assets2, + ]; + }, + ); + assetsForLinking.addAll(mergedMaps); + dependencies.addAll(buildOutput.dependencies); + dependencies.sort(_uriCompare); + } + + HookResult withSuccess(bool success) => HookResult._( + assets: assets, + assetsForLinking: assetsForLinking, + dependencies: dependencies, + success: success, + ); +} + +int _uriCompare(Uri u1, Uri u2) => u1.toString().compareTo(u2.toString()); diff --git a/pkgs/native_assets_builder/lib/src/model/link_result.dart b/pkgs/native_assets_builder/lib/src/model/link_result.dart new file mode 100644 index 000000000..90f3c580b --- /dev/null +++ b/pkgs/native_assets_builder/lib/src/model/link_result.dart @@ -0,0 +1,17 @@ +// Copyright (c) 2024, 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 'package:native_assets_cli/native_assets_cli_internal.dart'; + +import 'build_result.dart'; + +/// Similar to a [BuildResult], but for `link.dart` hooks instead of +/// `build.dart` hooks. +abstract interface class LinkResult { + List get assets; + + List get dependencies; + + bool get success; +} diff --git a/pkgs/native_assets_builder/lib/src/package_layout/package_layout.dart b/pkgs/native_assets_builder/lib/src/package_layout/package_layout.dart index d190d397f..85cbf1ccf 100644 --- a/pkgs/native_assets_builder/lib/src/package_layout/package_layout.dart +++ b/pkgs/native_assets_builder/lib/src/package_layout/package_layout.dart @@ -4,6 +4,7 @@ import 'dart:io'; +import 'package:native_assets_cli/native_assets_cli_internal.dart'; import 'package:package_config/package_config.dart'; /// Directory layout for dealing with native assets. @@ -86,27 +87,36 @@ class PackageLayout { /// All packages in [packageConfig] with native assets. /// /// Whether a package has native assets is defined by whether it contains - /// a `hook/build.dart`. + /// a `hook/build.dart` or `hook/link.dart`. /// /// For backwards compatibility, a toplevel `build.dart` is also supported. // TODO(https://github.com/dart-lang/native/issues/823): Remove fallback when // everyone has migrated. (Probably once we stop backwards compatibility of // the protocol version pre 1.2.0 on some future version.) - late final Future> packagesWithNativeAssets = () async { + Future> packagesWithAssets(Hook hook) async => switch (hook) { + Hook.build => _packagesWithBuildAssets ??= + await _packagesWithHook(hook), + Hook.link => _packagesWithLinkAssets ??= await _packagesWithHook(hook), + }; + + List? _packagesWithBuildAssets; + List? _packagesWithLinkAssets; + + Future> _packagesWithHook(Hook hook) async { final result = []; for (final package in packageConfig.packages) { final packageRoot = package.root; if (packageRoot.scheme == 'file') { if (await File.fromUri( - packageRoot.resolve('hook/').resolve('build.dart'), + packageRoot.resolve('hook/').resolve(hook.scriptName), ).exists() || await File.fromUri( - packageRoot.resolve('build.dart'), + packageRoot.resolve(hook.scriptName), ).exists()) { result.add(package); } } } return result; - }(); + } } diff --git a/pkgs/native_assets_builder/pubspec.yaml b/pkgs/native_assets_builder/pubspec.yaml index 341d035b2..7dd3ec07d 100644 --- a/pkgs/native_assets_builder/pubspec.yaml +++ b/pkgs/native_assets_builder/pubspec.yaml @@ -1,18 +1,21 @@ name: native_assets_builder description: >- - This package is the backend that invokes `build.dart` hooks. -version: 0.6.1 + This package is the backend that invokes build hooks. +version: 0.7.0-wip repository: https://github.com/dart-lang/native/tree/main/pkgs/native_assets_builder +publish_to: none + environment: sdk: '>=3.3.0 <4.0.0' dependencies: + collection: ^1.18.0 graphs: ^2.3.1 logging: ^1.2.0 - native_assets_cli: ^0.5.0 - # native_assets_cli: - # path: ../native_assets_cli/ + # native_assets_cli: ^0.5.0 + native_assets_cli: + path: ../native_assets_cli/ package_config: ^2.1.0 yaml: ^3.1.2 yaml_edit: ^2.1.0 diff --git a/pkgs/native_assets_builder/test/build_runner/build_planner_test.dart b/pkgs/native_assets_builder/test/build_runner/build_planner_test.dart index 9669c17da..b4a03a30d 100644 --- a/pkgs/native_assets_builder/test/build_runner/build_planner_test.dart +++ b/pkgs/native_assets_builder/test/build_runner/build_planner_test.dart @@ -6,6 +6,7 @@ import 'dart:io'; import 'package:native_assets_builder/native_assets_builder.dart'; import 'package:native_assets_builder/src/build_runner/build_planner.dart'; +import 'package:native_assets_cli/native_assets_cli_internal.dart'; import 'package:test/test.dart'; import '../helpers.dart'; @@ -37,7 +38,7 @@ void main() async { final packageLayout = await PackageLayout.fromRootPackageRoot(nativeAddUri); final packagesWithNativeAssets = - await packageLayout.packagesWithNativeAssets; + await packageLayout.packagesWithAssets(Hook.build); final planner = NativeAssetsBuildPlanner( packageGraph: graph, @@ -62,7 +63,7 @@ void main() async { final packageLayout = await PackageLayout.fromRootPackageRoot(nativeAddUri); final packagesWithNativeAssets = - await packageLayout.packagesWithNativeAssets; + await packageLayout.packagesWithAssets(Hook.build); final nativeAssetsBuildPlanner = await NativeAssetsBuildPlanner.fromRootPackageRoot( rootPackageRoot: nativeAddUri, @@ -89,7 +90,7 @@ void main() async { final packageLayout = await PackageLayout.fromRootPackageRoot(nativeAddUri); final packagesWithNativeAssets = - await packageLayout.packagesWithNativeAssets; + await packageLayout.packagesWithAssets(Hook.build); final nativeAssetsBuildPlanner = await NativeAssetsBuildPlanner.fromRootPackageRoot( rootPackageRoot: nativeAddUri, diff --git a/pkgs/native_assets_builder/test/build_runner/helpers.dart b/pkgs/native_assets_builder/test/build_runner/helpers.dart index 6dab3951e..f541840c8 100644 --- a/pkgs/native_assets_builder/test/build_runner/helpers.dart +++ b/pkgs/native_assets_builder/test/build_runner/helpers.dart @@ -40,38 +40,30 @@ Future build( List? capturedLogs, PackageLayout? packageLayout, String? runPackageName, -}) async { - StreamSubscription? subscription; - if (capturedLogs != null) { - subscription = - logger.onRecord.listen((event) => capturedLogs.add(event.message)); - } +}) async => + await runWithLog(capturedLogs, () async { + final result = await NativeAssetsBuildRunner( + logger: logger, + dartExecutable: dartExecutable, + ).build( + buildMode: BuildModeImpl.release, + linkModePreference: linkModePreference, + target: Target.current, + workingDirectory: packageUri, + cCompilerConfig: cCompilerConfig, + includeParentEnvironment: includeParentEnvironment, + packageLayout: packageLayout, + runPackageName: runPackageName, + ); - final result = await NativeAssetsBuildRunner( - logger: logger, - dartExecutable: dartExecutable, - ).build( - buildMode: BuildModeImpl.release, - linkModePreference: linkModePreference, - target: Target.current, - workingDirectory: packageUri, - cCompilerConfig: cCompilerConfig, - includeParentEnvironment: includeParentEnvironment, - packageLayout: packageLayout, - runPackageName: runPackageName, - ); - if (result.success) { - await expectAssetsExist(result.assets.cast()); - } + if (result.success) { + await expectAssetsExist(result.assets); + } - if (subscription != null) { - await subscription.cancel(); - } - - return result; -} + return result; + }); -Future dryRun( +Future link( Uri packageUri, Logger logger, Uri dartExecutable, { @@ -80,23 +72,40 @@ Future dryRun( bool includeParentEnvironment = true, List? capturedLogs, PackageLayout? packageLayout, -}) async { + required BuildResult buildResult, +}) async => + await runWithLog(capturedLogs, () async { + final result = await NativeAssetsBuildRunner( + logger: logger, + dartExecutable: dartExecutable, + ).link( + buildMode: BuildModeImpl.release, + target: Target.current, + workingDirectory: packageUri, + cCompilerConfig: cCompilerConfig, + includeParentEnvironment: includeParentEnvironment, + packageLayout: packageLayout, + buildResult: buildResult, + ); + + if (result.success) { + await expectAssetsExist(result.assets); + } + + return result; + }); + +Future runWithLog( + List? capturedLogs, + Future Function() f, +) async { StreamSubscription? subscription; if (capturedLogs != null) { subscription = logger.onRecord.listen((event) => capturedLogs.add(event.message)); } - final result = await NativeAssetsBuildRunner( - logger: logger, - dartExecutable: dartExecutable, - ).dryRun( - linkModePreference: linkModePreference, - targetOS: Target.current.os, - workingDirectory: packageUri, - includeParentEnvironment: includeParentEnvironment, - packageLayout: packageLayout, - ); + final result = await f(); if (subscription != null) { await subscription.cancel(); @@ -105,15 +114,33 @@ Future dryRun( return result; } -Future expectAssetsExist(List assets) async { +Future dryRun( + Uri packageUri, + Logger logger, + Uri dartExecutable, { + LinkModePreferenceImpl linkModePreference = LinkModePreferenceImpl.dynamic, + CCompilerConfigImpl? cCompilerConfig, + bool includeParentEnvironment = true, + List? capturedLogs, + PackageLayout? packageLayout, +}) async => + runWithLog(capturedLogs, () async { + final result = await NativeAssetsBuildRunner( + logger: logger, + dartExecutable: dartExecutable, + ).dryRun( + linkModePreference: linkModePreference, + targetOS: Target.current.os, + workingDirectory: packageUri, + includeParentEnvironment: includeParentEnvironment, + packageLayout: packageLayout, + ); + return result; + }); + +Future expectAssetsExist(List assets) async { for (final asset in assets) { - final uri = asset.file!; - expect( - uri.toFilePath(), - contains('${Platform.pathSeparator}.dart_tool${Platform.pathSeparator}' - 'native_assets_builder${Platform.pathSeparator}')); - final file = File.fromUri(uri); - expect(file, exists); + expect(File.fromUri(asset.file!), exists); } } diff --git a/pkgs/native_assets_builder/test/build_runner/link_test.dart b/pkgs/native_assets_builder/test/build_runner/link_test.dart new file mode 100644 index 000000000..fdd8e1aa7 --- /dev/null +++ b/pkgs/native_assets_builder/test/build_runner/link_test.dart @@ -0,0 +1,96 @@ +// Copyright (c) 2024, 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 'package:native_assets_cli/native_assets_cli.dart' as cli; +import 'package:native_assets_cli/src/api/asset.dart'; +import 'package:test/test.dart'; + +import '../helpers.dart'; +import 'helpers.dart'; + +const Timeout longTimeout = Timeout(Duration(minutes: 5)); + +void main() async { + test( + 'simple_link linking', + timeout: longTimeout, + () async { + await inTempDir((tempUri) async { + await copyTestProjects(targetUri: tempUri); + final packageUri = tempUri.resolve('simple_link/'); + + // First, run `pub get`, we need pub to resolve our dependencies. + await runPubGet( + workingDirectory: packageUri, + logger: logger, + ); + + final buildResult = await build( + packageUri, + logger, + dartExecutable, + ); + expect(buildResult.assets.length, 0); + + final linkResult = await link( + packageUri, + logger, + dartExecutable, + buildResult: buildResult, + ); + expect(linkResult.assets.length, 2); + }); + }, + ); + + test( + 'complex_link linking', + timeout: longTimeout, + () async { + await inTempDir((tempUri) async { + // From package:complex_link_helper + const builtHelperAssets = ['data_helper_0', 'data_helper_1']; + const helperAssetsForLinking = ['data_helper_2', 'data_helper_3']; + // From package:complex_link + const mainAssetsForLinking = ['data_0', 'data_1']; + final assetsForLinking = [ + ...helperAssetsForLinking, + ...mainAssetsForLinking, + ]; + final linkedAssets = assetsForLinking.skip(1); + + await copyTestProjects(targetUri: tempUri); + final packageUri = tempUri.resolve('complex_link/'); + + // First, run `pub get`, we need pub to resolve our dependencies. + await runPubGet(workingDirectory: packageUri, logger: logger); + + final buildResult = await build( + packageUri, + logger, + dartExecutable, + ); + expect(buildResult.success, true); + expect(_getNames(buildResult.assets), orderedEquals(builtHelperAssets)); + expect( + _getNames(buildResult.assetsForLinking['complex_link']!), + orderedEquals(assetsForLinking), + ); + + final linkResult = await link( + packageUri, + logger, + dartExecutable, + buildResult: buildResult, + ); + expect(linkResult.success, true); + + expect(_getNames(linkResult.assets), orderedEquals(linkedAssets)); + }); + }, + ); +} + +Iterable _getNames(List assets) => + assets.whereType().map((asset) => asset.name); diff --git a/pkgs/native_assets_builder/test/helpers.dart b/pkgs/native_assets_builder/test/helpers.dart index 51d42897f..5fdc18502 100644 --- a/pkgs/native_assets_builder/test/helpers.dart +++ b/pkgs/native_assets_builder/test/helpers.dart @@ -154,13 +154,19 @@ Future copyTestProjects({ final sourceString = await sourceFile.readAsString(); final modifiedString = sourceString.replaceAll( 'path: ../../', - 'path: ${pkgNativeAssetsBuilderUri.toFilePath().replaceAll('\\', '/')}', + 'path: ${pkgNativeAssetsBuilderUri.toFilePath().unescape()}', ); await File.fromUri(targetFileUri) .writeAsString(modifiedString, flush: true); } } +extension UnescapePath on String { + /// Remove double encoding of slashes on windows, for string comparison with + /// Unix-style encoded strings. + String unescape() => replaceAll('\\', '/'); +} + /// Logger that outputs the full trace when a test fails. Logger get logger => _logger ??= () { // A new logger is lazily created for each test so that the messages diff --git a/pkgs/native_assets_builder/test_data/README.md b/pkgs/native_assets_builder/test_data/README.md index 311839a71..33f86c30e 100644 --- a/pkgs/native_assets_builder/test_data/README.md +++ b/pkgs/native_assets_builder/test_data/README.md @@ -2,5 +2,5 @@ The `dart_app` depends on multiple packages with native assets. `manifest.yaml` contains a list of all the files of the test projects. This is used to copy the test projects to temporary folders when running tests, -to prevent tests accidentally using temporary files or interferring with each +to prevent tests accidentally using temporary files or interfering with each other. diff --git a/pkgs/native_assets_builder/test_data/add_asset_link/.gitignore b/pkgs/native_assets_builder/test_data/add_asset_link/.gitignore new file mode 100644 index 000000000..f7ad401ea --- /dev/null +++ b/pkgs/native_assets_builder/test_data/add_asset_link/.gitignore @@ -0,0 +1,4 @@ +# https://dart.dev/guides/libraries/private-files +# Created by `dart pub` +.dart_tool/ +bin/add_asset_link/ diff --git a/pkgs/native_assets_builder/test_data/add_asset_link/README.md b/pkgs/native_assets_builder/test_data/add_asset_link/README.md new file mode 100644 index 000000000..8b3ebd965 --- /dev/null +++ b/pkgs/native_assets_builder/test_data/add_asset_link/README.md @@ -0,0 +1 @@ +This sample adds a native library in the link step. \ No newline at end of file diff --git a/pkgs/native_assets_builder/test_data/add_asset_link/bin/add_asset_link.dart b/pkgs/native_assets_builder/test_data/add_asset_link/bin/add_asset_link.dart new file mode 100644 index 000000000..c56edc742 --- /dev/null +++ b/pkgs/native_assets_builder/test_data/add_asset_link/bin/add_asset_link.dart @@ -0,0 +1,9 @@ +// Copyright (c) 2024, 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 'package:add_asset_link/add_asset_link.dart'; + +void main(List arguments) { + print('Hello world: ${MyMath.add(3, 4)}!'); +} diff --git a/pkgs/native_assets_builder/test_data/add_asset_link/hook/build.dart b/pkgs/native_assets_builder/test_data/add_asset_link/hook/build.dart new file mode 100644 index 000000000..74908a340 --- /dev/null +++ b/pkgs/native_assets_builder/test_data/add_asset_link/hook/build.dart @@ -0,0 +1,31 @@ +// Copyright (c) 2024, 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 'package:logging/logging.dart'; +import 'package:native_assets_cli/native_assets_cli.dart'; +import 'package:native_toolchain_c/native_toolchain_c.dart'; + +void main(List arguments) async { + await build(arguments, (config, output) async { + final logger = Logger('') + ..level = Level.ALL + ..onRecord.listen((record) { + print('${record.level.name}: ${record.time}: ${record.message}'); + }); + await CBuilder.library( + name: 'add', + assetName: 'dylib_add_build', + sources: [ + 'src/native_add.c', + ], + dartBuildFiles: ['hook/build.dart'], + linkModePreference: LinkModePreference.dynamic, + ).run( + buildConfig: config, + buildOutput: output, + logger: logger, + linkInPackage: 'add_asset_link', + ); + }); +} diff --git a/pkgs/native_assets_builder/test_data/add_asset_link/hook/link.dart b/pkgs/native_assets_builder/test_data/add_asset_link/hook/link.dart new file mode 100644 index 000000000..5cea3b1cd --- /dev/null +++ b/pkgs/native_assets_builder/test_data/add_asset_link/hook/link.dart @@ -0,0 +1,23 @@ +// Copyright (c) 2024, 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 'package:native_assets_cli/native_assets_cli.dart'; + +void main(List arguments) async { + await link(arguments, (config, output) async { + final builtDylib = config.assets.first as NativeCodeAsset; + output + ..addAsset( + NativeCodeAsset( + package: 'add_asset_link', + name: 'dylib_add_link', + linkMode: builtDylib.linkMode, + os: builtDylib.os, + architecture: builtDylib.architecture, + file: builtDylib.file, + ), + ) + ..addDependency(config.packageRoot.resolve('hook/link.dart')); + }); +} diff --git a/pkgs/native_assets_builder/test_data/add_asset_link/lib/add_asset_link.dart b/pkgs/native_assets_builder/test_data/add_asset_link/lib/add_asset_link.dart new file mode 100644 index 000000000..8c0d3965a --- /dev/null +++ b/pkgs/native_assets_builder/test_data/add_asset_link/lib/add_asset_link.dart @@ -0,0 +1,5 @@ +// Copyright (c) 2024, 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. + +export 'src/add_asset_link.dart'; diff --git a/pkgs/native_assets_builder/test_data/add_asset_link/lib/src/add_asset_link.dart b/pkgs/native_assets_builder/test_data/add_asset_link/lib/src/add_asset_link.dart new file mode 100644 index 000000000..71651a7b4 --- /dev/null +++ b/pkgs/native_assets_builder/test_data/add_asset_link/lib/src/add_asset_link.dart @@ -0,0 +1,9 @@ +// Copyright (c) 2024, 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 'add_asset_link_bindings.dart' as bindings; + +class MyMath { + static int add(int a, int b) => bindings.add(a, b); +} diff --git a/pkgs/native_assets_builder/test_data/add_asset_link/lib/src/add_asset_link_bindings.dart b/pkgs/native_assets_builder/test_data/add_asset_link/lib/src/add_asset_link_bindings.dart new file mode 100644 index 000000000..1d1d71837 --- /dev/null +++ b/pkgs/native_assets_builder/test_data/add_asset_link/lib/src/add_asset_link_bindings.dart @@ -0,0 +1,12 @@ +// Copyright (c) 2024, 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:ffi' as ffi; + +@ffi.Native( + assetId: 'package:add_asset_link/dylib_add_link') +external int add( + int a, + int b, +); diff --git a/pkgs/native_assets_builder/test_data/add_asset_link/pubspec.yaml b/pkgs/native_assets_builder/test_data/add_asset_link/pubspec.yaml new file mode 100644 index 000000000..be1090386 --- /dev/null +++ b/pkgs/native_assets_builder/test_data/add_asset_link/pubspec.yaml @@ -0,0 +1,22 @@ +name: add_asset_link +description: Add a dylib in the link step. +version: 1.0.0 + +publish_to: none + +environment: + sdk: ^3.0.0 + +dependencies: + logging: ^1.1.1 + # native_assets_cli: ^0.5.0 + meta: ^1.12.0 + native_assets_cli: + path: ../../../native_assets_cli/ + # native_toolchain_c: ^0.4.0 + native_toolchain_c: + path: ../../../native_toolchain_c/ + +dev_dependencies: + lints: ^3.0.0 + test: ^1.24.0 diff --git a/pkgs/native_assets_builder/test_data/add_asset_link/src/native_add.c b/pkgs/native_assets_builder/test_data/add_asset_link/src/native_add.c new file mode 100644 index 000000000..570e754fe --- /dev/null +++ b/pkgs/native_assets_builder/test_data/add_asset_link/src/native_add.c @@ -0,0 +1,9 @@ +// Copyright (c) 2024, 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. + +#include "native_add.h" + +MYLIB_EXPORT int32_t add(int32_t a, int32_t b) { + return a + b; +} diff --git a/pkgs/native_assets_builder/test_data/add_asset_link/src/native_add.h b/pkgs/native_assets_builder/test_data/add_asset_link/src/native_add.h new file mode 100644 index 000000000..275878fe8 --- /dev/null +++ b/pkgs/native_assets_builder/test_data/add_asset_link/src/native_add.h @@ -0,0 +1,13 @@ +// Copyright (c) 2024, 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. + +#include + +#if _WIN32 +#define MYLIB_EXPORT __declspec(dllexport) +#else +#define MYLIB_EXPORT +#endif + +MYLIB_EXPORT int32_t add(int32_t a, int32_t b); diff --git a/pkgs/native_assets_builder/test_data/complex_link/.gitignore b/pkgs/native_assets_builder/test_data/complex_link/.gitignore new file mode 100644 index 000000000..a051860e1 --- /dev/null +++ b/pkgs/native_assets_builder/test_data/complex_link/.gitignore @@ -0,0 +1,4 @@ +# https://dart.dev/guides/libraries/private-files +# Created by `dart pub` +.dart_tool/ +bin/complex_link/ diff --git a/pkgs/native_assets_builder/test_data/complex_link/assets/data_0.json b/pkgs/native_assets_builder/test_data/complex_link/assets/data_0.json new file mode 100644 index 000000000..e69de29bb diff --git a/pkgs/native_assets_builder/test_data/complex_link/assets/data_1.json b/pkgs/native_assets_builder/test_data/complex_link/assets/data_1.json new file mode 100644 index 000000000..e69de29bb diff --git a/pkgs/native_assets_builder/test_data/complex_link/bin/complex_link.dart b/pkgs/native_assets_builder/test_data/complex_link/bin/complex_link.dart new file mode 100644 index 000000000..03fdefaba --- /dev/null +++ b/pkgs/native_assets_builder/test_data/complex_link/bin/complex_link.dart @@ -0,0 +1,9 @@ +// Copyright (c) 2024, 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 'package:complex_link_helper/complex_link_helper.dart'; + +void main() { + someMethod(); +} diff --git a/pkgs/native_assets_builder/test_data/complex_link/hook/build.dart b/pkgs/native_assets_builder/test_data/complex_link/hook/build.dart new file mode 100644 index 000000000..6c693ec5f --- /dev/null +++ b/pkgs/native_assets_builder/test_data/complex_link/hook/build.dart @@ -0,0 +1,22 @@ +// Copyright (c) 2024, 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 'package:native_assets_cli/native_assets_cli.dart'; + +const packageName = 'complex_link'; + +void main(List args) async => build(args, (config, output) async { + output.addAssets( + List.generate( + 2, + (index) => DataAsset( + name: 'data_$index', + // TODO(mosuem): Simplify specifying files/file paths + file: config.packageRoot.resolve('assets/data_$index.json'), + package: packageName, + ), + ), + linkInPackage: 'complex_link', + ); + }); diff --git a/pkgs/native_assets_builder/test_data/complex_link/hook/link.dart b/pkgs/native_assets_builder/test_data/complex_link/hook/link.dart new file mode 100644 index 000000000..501438d21 --- /dev/null +++ b/pkgs/native_assets_builder/test_data/complex_link/hook/link.dart @@ -0,0 +1,15 @@ +// Copyright (c) 2024, 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 'package:native_assets_cli/native_assets_cli.dart'; + +void main(List args) async { + await link( + args, + (config, output) async => output.addAssets(treeshake(config.assets)), + ); +} + +Iterable treeshake(Iterable assets) => + assets.where((asset) => !asset.id.endsWith('data_helper_2')); diff --git a/pkgs/native_assets_builder/test_data/complex_link/pubspec.yaml b/pkgs/native_assets_builder/test_data/complex_link/pubspec.yaml new file mode 100644 index 000000000..d5cf1197e --- /dev/null +++ b/pkgs/native_assets_builder/test_data/complex_link/pubspec.yaml @@ -0,0 +1,21 @@ +name: complex_link +description: Prints "Tada!", but packages some assets while tree-shaking others +version: 0.1.0 + +publish_to: none + +environment: + sdk: '>=3.0.0 <4.0.0' + +dependencies: + cli_config: ^0.2.0 + complex_link_helper: + path: ../complex_link_helper/ + logging: ^1.1.1 + native_assets_cli: + path: ../../../native_assets_cli/ + +dev_dependencies: + lints: ^3.0.0 + path: ^1.9.0 + test: ^1.23.1 diff --git a/pkgs/native_assets_builder/test_data/complex_link_helper/assets/data_helper_0.json b/pkgs/native_assets_builder/test_data/complex_link_helper/assets/data_helper_0.json new file mode 100644 index 000000000..e69de29bb diff --git a/pkgs/native_assets_builder/test_data/complex_link_helper/assets/data_helper_1.json b/pkgs/native_assets_builder/test_data/complex_link_helper/assets/data_helper_1.json new file mode 100644 index 000000000..e69de29bb diff --git a/pkgs/native_assets_builder/test_data/complex_link_helper/assets/data_helper_2.json b/pkgs/native_assets_builder/test_data/complex_link_helper/assets/data_helper_2.json new file mode 100644 index 000000000..e69de29bb diff --git a/pkgs/native_assets_builder/test_data/complex_link_helper/assets/data_helper_3.json b/pkgs/native_assets_builder/test_data/complex_link_helper/assets/data_helper_3.json new file mode 100644 index 000000000..e69de29bb diff --git a/pkgs/native_assets_builder/test_data/complex_link_helper/hook/build.dart b/pkgs/native_assets_builder/test_data/complex_link_helper/hook/build.dart new file mode 100644 index 000000000..95ba7e0fa --- /dev/null +++ b/pkgs/native_assets_builder/test_data/complex_link_helper/hook/build.dart @@ -0,0 +1,34 @@ +// Copyright (c) 2024, 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 'package:native_assets_cli/native_assets_cli.dart'; + +const packageName = 'complex_link_helper'; + +void main(List args) async => build(args, (config, output) async { + output.addAssets( + List.generate( + 2, + (index) => DataAsset( + name: 'data_helper_$index', + // TODO(mosuem): Simplify specifying files/file paths + file: config.packageRoot.resolve('assets/data_helper_$index.json'), + package: packageName, + ), + ), + ); + output.addAssets( + List.generate( + 2, + (index) => DataAsset( + name: 'data_helper_${index + 2}', + // TODO(mosuem): Simplify specifying files/file paths + file: config.packageRoot + .resolve('assets/data_helper_${index + 2}.json'), + package: packageName, + ), + ), + linkInPackage: 'complex_link', + ); + }); diff --git a/pkgs/native_assets_builder/test_data/complex_link_helper/lib/complex_link_helper.dart b/pkgs/native_assets_builder/test_data/complex_link_helper/lib/complex_link_helper.dart new file mode 100644 index 000000000..826bbf169 --- /dev/null +++ b/pkgs/native_assets_builder/test_data/complex_link_helper/lib/complex_link_helper.dart @@ -0,0 +1,7 @@ +// Copyright (c) 2024, 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. + +void someMethod() { + print('Tada!'); +} diff --git a/pkgs/native_assets_builder/test_data/complex_link_helper/pubspec.yaml b/pkgs/native_assets_builder/test_data/complex_link_helper/pubspec.yaml new file mode 100644 index 000000000..1521b53b1 --- /dev/null +++ b/pkgs/native_assets_builder/test_data/complex_link_helper/pubspec.yaml @@ -0,0 +1,19 @@ +name: complex_link_helper +description: Adds two json files to the built assets, and two more for linking. +version: 0.1.0 + +publish_to: none + +environment: + sdk: '>=3.0.0 <4.0.0' + +dependencies: + cli_config: ^0.2.0 + logging: ^1.1.1 + native_assets_cli: + path: ../../../native_assets_cli/ + +dev_dependencies: + lints: ^3.0.0 + path: ^1.9.0 + test: ^1.23.1 diff --git a/pkgs/native_assets_builder/test_data/cyclic_package_1/pubspec.yaml b/pkgs/native_assets_builder/test_data/cyclic_package_1/pubspec.yaml index 5455d7ef5..83c4aeb03 100644 --- a/pkgs/native_assets_builder/test_data/cyclic_package_1/pubspec.yaml +++ b/pkgs/native_assets_builder/test_data/cyclic_package_1/pubspec.yaml @@ -10,9 +10,9 @@ environment: dependencies: cyclic_package_2: path: ../cyclic_package_2 - native_assets_cli: ^0.5.0 - # native_assets_cli: - # path: ../../../native_assets_cli/ + # native_assets_cli: ^0.5.0 + native_assets_cli: + path: ../../../native_assets_cli/ yaml: ^3.1.1 yaml_edit: ^2.1.0 diff --git a/pkgs/native_assets_builder/test_data/cyclic_package_2/pubspec.yaml b/pkgs/native_assets_builder/test_data/cyclic_package_2/pubspec.yaml index ef9c9ff7e..a44e9f50c 100644 --- a/pkgs/native_assets_builder/test_data/cyclic_package_2/pubspec.yaml +++ b/pkgs/native_assets_builder/test_data/cyclic_package_2/pubspec.yaml @@ -10,9 +10,9 @@ environment: dependencies: cyclic_package_1: path: ../cyclic_package_1 - native_assets_cli: ^0.5.0 - # native_assets_cli: - # path: ../../../native_assets_cli/ + # native_assets_cli: ^0.5.0 + native_assets_cli: + path: ../../../native_assets_cli/ yaml: ^3.1.1 yaml_edit: ^2.1.0 diff --git a/pkgs/native_assets_builder/test_data/drop_dylib_link/.gitignore b/pkgs/native_assets_builder/test_data/drop_dylib_link/.gitignore new file mode 100644 index 000000000..eb803bf91 --- /dev/null +++ b/pkgs/native_assets_builder/test_data/drop_dylib_link/.gitignore @@ -0,0 +1,4 @@ +# https://dart.dev/guides/libraries/private-files +# Created by `dart pub` +.dart_tool/ +bin/drop_dylib_link/ diff --git a/pkgs/native_assets_builder/test_data/drop_dylib_link/README.md b/pkgs/native_assets_builder/test_data/drop_dylib_link/README.md new file mode 100644 index 000000000..11147bc19 --- /dev/null +++ b/pkgs/native_assets_builder/test_data/drop_dylib_link/README.md @@ -0,0 +1 @@ +This sample builds a native library for adding and multiplying, but then only keeps the one for adding in the linking step. \ No newline at end of file diff --git a/pkgs/native_assets_builder/test_data/drop_dylib_link/bin/drop_dylib_link.dart b/pkgs/native_assets_builder/test_data/drop_dylib_link/bin/drop_dylib_link.dart new file mode 100644 index 000000000..be71a0d4f --- /dev/null +++ b/pkgs/native_assets_builder/test_data/drop_dylib_link/bin/drop_dylib_link.dart @@ -0,0 +1,15 @@ +// Copyright (c) 2024, 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 'package:drop_dylib_link/drop_dylib_link.dart'; + +void main(List arguments) { + if (arguments.first == 'add') { + print('Hello world: ${MyMath.add(3, 4)}!'); + } else if (arguments.first == 'multiply') { + print('Hello world: ${MyMath.multiply(3, 4)}!'); + } else { + throw ArgumentError('Must pass either "add" or "multiply"'); + } +} diff --git a/pkgs/native_assets_builder/test_data/drop_dylib_link/hook/build.dart b/pkgs/native_assets_builder/test_data/drop_dylib_link/hook/build.dart new file mode 100644 index 000000000..b7e430c37 --- /dev/null +++ b/pkgs/native_assets_builder/test_data/drop_dylib_link/hook/build.dart @@ -0,0 +1,48 @@ +// Copyright (c) 2024, 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 'package:logging/logging.dart'; +import 'package:native_assets_cli/native_assets_cli.dart'; +import 'package:native_toolchain_c/native_toolchain_c.dart'; + +const packageName = 'drop_dylib_link'; + +void main(List arguments) async { + await build(arguments, (config, output) async { + final logger = Logger('') + ..level = Level.ALL + ..onRecord.listen((record) { + print('${record.level.name}: ${record.time}: ${record.message}'); + }); + await CBuilder.library( + name: 'add', + assetName: 'dylib_add', + sources: [ + 'src/native_add.c', + ], + dartBuildFiles: ['hook/build.dart'], + linkModePreference: LinkModePreference.dynamic, + ).run( + buildConfig: config, + buildOutput: output, + logger: logger, + linkInPackage: packageName, + ); + + await CBuilder.library( + name: 'multiply', + assetName: 'dylib_multiply', + sources: [ + 'src/native_multiply.c', + ], + dartBuildFiles: ['hook/build.dart'], + linkModePreference: LinkModePreference.dynamic, + ).run( + buildConfig: config, + buildOutput: output, + logger: logger, + linkInPackage: packageName, + ); + }); +} diff --git a/pkgs/native_assets_builder/test_data/drop_dylib_link/hook/link.dart b/pkgs/native_assets_builder/test_data/drop_dylib_link/hook/link.dart new file mode 100644 index 000000000..b2f15c0d9 --- /dev/null +++ b/pkgs/native_assets_builder/test_data/drop_dylib_link/hook/link.dart @@ -0,0 +1,18 @@ +// Copyright (c) 2024, 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 'package:native_assets_cli/native_assets_cli.dart'; + +void main(List arguments) async { + await link(arguments, (config, output) async { + print(''' +Received ${config.assets.length} assets: ${config.assets.map((e) => e.id)}. +'''); + output.addAssets(config.assets.where((asset) => asset.id.endsWith('add'))); + print(''' +Keeping only ${output.assets.map((e) => e.id)}. +'''); + output.addDependency(config.packageRoot.resolve('hook/link.dart')); + }); +} diff --git a/pkgs/native_assets_builder/test_data/drop_dylib_link/lib/drop_dylib_link.dart b/pkgs/native_assets_builder/test_data/drop_dylib_link/lib/drop_dylib_link.dart new file mode 100644 index 000000000..3e0c2b6b7 --- /dev/null +++ b/pkgs/native_assets_builder/test_data/drop_dylib_link/lib/drop_dylib_link.dart @@ -0,0 +1,5 @@ +// Copyright (c) 2024, 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. + +export 'src/drop_dylib_link.dart'; diff --git a/pkgs/native_assets_builder/test_data/drop_dylib_link/lib/src/drop_dylib_link.dart b/pkgs/native_assets_builder/test_data/drop_dylib_link/lib/src/drop_dylib_link.dart new file mode 100644 index 000000000..c71715c97 --- /dev/null +++ b/pkgs/native_assets_builder/test_data/drop_dylib_link/lib/src/drop_dylib_link.dart @@ -0,0 +1,15 @@ +// Copyright (c) 2024, 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 'package:meta/meta.dart'; + +import 'drop_dylib_link_bindings.dart' as bindings; + +class MyMath { + @ResourceIdentifier('add') + static int add(int a, int b) => bindings.add(a, b); + + @ResourceIdentifier('multiply') + static int multiply(int a, int b) => bindings.multiply(a, b); +} diff --git a/pkgs/native_assets_builder/test_data/drop_dylib_link/lib/src/drop_dylib_link_bindings.dart b/pkgs/native_assets_builder/test_data/drop_dylib_link/lib/src/drop_dylib_link_bindings.dart new file mode 100644 index 000000000..231a1a58e --- /dev/null +++ b/pkgs/native_assets_builder/test_data/drop_dylib_link/lib/src/drop_dylib_link_bindings.dart @@ -0,0 +1,19 @@ +// Copyright (c) 2024, 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:ffi' as ffi; + +@ffi.Native( + assetId: 'package:drop_dylib_link/dylib_add') +external int add( + int a, + int b, +); + +@ffi.Native( + assetId: 'package:drop_dylib_link/dylib_multiply') +external int multiply( + int a, + int b, +); diff --git a/pkgs/native_assets_builder/test_data/drop_dylib_link/pubspec.yaml b/pkgs/native_assets_builder/test_data/drop_dylib_link/pubspec.yaml new file mode 100644 index 000000000..527e637de --- /dev/null +++ b/pkgs/native_assets_builder/test_data/drop_dylib_link/pubspec.yaml @@ -0,0 +1,22 @@ +name: drop_dylib_link +description: Generate two dylibs, remove one in linking. +version: 1.0.0 + +publish_to: none + +environment: + sdk: ^3.0.0 + +dependencies: + logging: ^1.1.1 + # native_assets_cli: ^0.5.0 + meta: ^1.12.0 + native_assets_cli: + path: ../../../native_assets_cli/ + # native_toolchain_c: ^0.4.0 + native_toolchain_c: + path: ../../../native_toolchain_c/ + +dev_dependencies: + lints: ^3.0.0 + test: ^1.24.0 diff --git a/pkgs/native_assets_builder/test_data/drop_dylib_link/src/native_add.c b/pkgs/native_assets_builder/test_data/drop_dylib_link/src/native_add.c new file mode 100644 index 000000000..570e754fe --- /dev/null +++ b/pkgs/native_assets_builder/test_data/drop_dylib_link/src/native_add.c @@ -0,0 +1,9 @@ +// Copyright (c) 2024, 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. + +#include "native_add.h" + +MYLIB_EXPORT int32_t add(int32_t a, int32_t b) { + return a + b; +} diff --git a/pkgs/native_assets_builder/test_data/drop_dylib_link/src/native_add.h b/pkgs/native_assets_builder/test_data/drop_dylib_link/src/native_add.h new file mode 100644 index 000000000..275878fe8 --- /dev/null +++ b/pkgs/native_assets_builder/test_data/drop_dylib_link/src/native_add.h @@ -0,0 +1,13 @@ +// Copyright (c) 2024, 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. + +#include + +#if _WIN32 +#define MYLIB_EXPORT __declspec(dllexport) +#else +#define MYLIB_EXPORT +#endif + +MYLIB_EXPORT int32_t add(int32_t a, int32_t b); diff --git a/pkgs/native_assets_builder/test_data/drop_dylib_link/src/native_multiply.c b/pkgs/native_assets_builder/test_data/drop_dylib_link/src/native_multiply.c new file mode 100644 index 000000000..37ad92677 --- /dev/null +++ b/pkgs/native_assets_builder/test_data/drop_dylib_link/src/native_multiply.c @@ -0,0 +1,9 @@ +// Copyright (c) 2024, 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. + +#include "native_multiply.h" + +MYLIB_EXPORT intptr_t multiply(intptr_t a, intptr_t b) { + return a * b; +} diff --git a/pkgs/native_assets_builder/test_data/drop_dylib_link/src/native_multiply.h b/pkgs/native_assets_builder/test_data/drop_dylib_link/src/native_multiply.h new file mode 100644 index 000000000..1c326f4ca --- /dev/null +++ b/pkgs/native_assets_builder/test_data/drop_dylib_link/src/native_multiply.h @@ -0,0 +1,13 @@ +// Copyright (c) 2024, 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. + +#include + +#if _WIN32 +#define MYLIB_EXPORT __declspec(dllexport) +#else +#define MYLIB_EXPORT +#endif + +MYLIB_EXPORT intptr_t multiply(intptr_t a, intptr_t b); diff --git a/pkgs/native_assets_builder/test_data/manifest.yaml b/pkgs/native_assets_builder/test_data/manifest.yaml index 5637dae40..60b4a0d85 100644 --- a/pkgs/native_assets_builder/test_data/manifest.yaml +++ b/pkgs/native_assets_builder/test_data/manifest.yaml @@ -27,6 +27,13 @@ - package_reading_metadata/pubspec.yaml - package_with_metadata/hook/build.dart - package_with_metadata/pubspec.yaml +- simple_link/pubspec.yaml +- simple_link/hook/build.dart +- simple_link/hook/link.dart +- simple_link/assets/data_0.json +- simple_link/assets/data_1.json +- simple_link/assets/data_2.json +- simple_link/assets/data_3.json - some_dev_dep/bin/some_dev_dep.dart - some_dev_dep/pubspec.yaml - wrong_build_output_2/hook/build.dart @@ -37,3 +44,36 @@ - wrong_build_output/pubspec.yaml - wrong_namespace_asset/hook/build.dart - wrong_namespace_asset/pubspec.yaml +- complex_link/pubspec.yaml +- complex_link/bin/complex_link.dart +- complex_link/hook/build.dart +- complex_link/hook/link.dart +- complex_link/assets/data_0.json +- complex_link/assets/data_1.json +- complex_link_helper/pubspec.yaml +- complex_link_helper/lib/complex_link_helper.dart +- complex_link_helper/hook/build.dart +- complex_link_helper/assets/data_helper_0.json +- complex_link_helper/assets/data_helper_1.json +- complex_link_helper/assets/data_helper_2.json +- complex_link_helper/assets/data_helper_3.json +- drop_dylib_link/pubspec.yaml +- drop_dylib_link/lib/drop_dylib_link.dart +- drop_dylib_link/lib/src/drop_dylib_link.dart +- drop_dylib_link/lib/src/drop_dylib_link_bindings.dart +- drop_dylib_link/src/native_add.h +- drop_dylib_link/src/native_multiply.h +- drop_dylib_link/src/native_add.c +- drop_dylib_link/src/native_multiply.c +- drop_dylib_link/hook/link.dart +- drop_dylib_link/hook/build.dart +- drop_dylib_link/bin/drop_dylib_link.dart +- add_asset_link/pubspec.yaml +- add_asset_link/hook/link.dart +- add_asset_link/bin/add_asset_link.dart +- add_asset_link/lib/add_asset_link.dart +- add_asset_link/lib/src/add_asset_link.dart +- add_asset_link/lib/src/add_asset_link_bindings.dart +- add_asset_link/src/native_add.h +- add_asset_link/src/native_add.c +- add_asset_link/hook/build.dart \ No newline at end of file diff --git a/pkgs/native_assets_builder/test_data/native_add/pubspec.yaml b/pkgs/native_assets_builder/test_data/native_add/pubspec.yaml index 47c71fb8f..b7df5d1a1 100644 --- a/pkgs/native_assets_builder/test_data/native_add/pubspec.yaml +++ b/pkgs/native_assets_builder/test_data/native_add/pubspec.yaml @@ -9,12 +9,12 @@ environment: dependencies: logging: ^1.1.1 - native_assets_cli: ^0.5.0 - # native_assets_cli: - # path: ../../../native_assets_cli/ - native_toolchain_c: ^0.4.0 - # native_toolchain_c: - # path: ../../../native_toolchain_c/ + # native_assets_cli: ^0.5.0 + native_assets_cli: + path: ../../../native_assets_cli/ + # native_toolchain_c: ^0.4.0 + native_toolchain_c: + path: ../../../native_toolchain_c/ dev_dependencies: ffigen: ^8.0.2 diff --git a/pkgs/native_assets_builder/test_data/native_add_add_source/pubspec.yaml b/pkgs/native_assets_builder/test_data/native_add_add_source/pubspec.yaml index bded3a0b1..b23647db0 100644 --- a/pkgs/native_assets_builder/test_data/native_add_add_source/pubspec.yaml +++ b/pkgs/native_assets_builder/test_data/native_add_add_source/pubspec.yaml @@ -9,12 +9,12 @@ environment: dependencies: logging: ^1.1.1 - native_assets_cli: ^0.5.0 - # native_assets_cli: - # path: ../../../native_assets_cli/ - native_toolchain_c: ^0.4.0 - # native_toolchain_c: - # path: ../../../native_toolchain_c/ + # native_assets_cli: ^0.5.0 + native_assets_cli: + path: ../../../native_assets_cli/ + # native_toolchain_c: ^0.4.0 + native_toolchain_c: + path: ../../../native_toolchain_c/ dev_dependencies: ffigen: ^8.0.2 diff --git a/pkgs/native_assets_builder/test_data/native_subtract/pubspec.yaml b/pkgs/native_assets_builder/test_data/native_subtract/pubspec.yaml index 7cb8f482a..e6ee4c5b1 100644 --- a/pkgs/native_assets_builder/test_data/native_subtract/pubspec.yaml +++ b/pkgs/native_assets_builder/test_data/native_subtract/pubspec.yaml @@ -9,12 +9,12 @@ environment: dependencies: logging: ^1.1.1 - native_assets_cli: ^0.5.0 - # native_assets_cli: - # path: ../../../native_assets_cli/ - native_toolchain_c: ^0.4.0 - # native_toolchain_c: - # path: ../../../native_toolchain_c/ + # native_assets_cli: ^0.5.0 + native_assets_cli: + path: ../../../native_assets_cli/ + # native_toolchain_c: ^0.4.0 + native_toolchain_c: + path: ../../../native_toolchain_c/ dev_dependencies: ffigen: ^8.0.2 diff --git a/pkgs/native_assets_builder/test_data/package_reading_metadata/pubspec.yaml b/pkgs/native_assets_builder/test_data/package_reading_metadata/pubspec.yaml index 26fe6d0d9..ce1016ec3 100644 --- a/pkgs/native_assets_builder/test_data/package_reading_metadata/pubspec.yaml +++ b/pkgs/native_assets_builder/test_data/package_reading_metadata/pubspec.yaml @@ -8,9 +8,9 @@ environment: sdk: '>=3.3.0 <4.0.0' dependencies: - native_assets_cli: ^0.5.0 - # native_assets_cli: - # path: ../../../native_assets_cli/ + # native_assets_cli: ^0.5.0 + native_assets_cli: + path: ../../../native_assets_cli/ package_with_metadata: path: ../package_with_metadata/ yaml: ^3.1.1 diff --git a/pkgs/native_assets_builder/test_data/package_with_metadata/pubspec.yaml b/pkgs/native_assets_builder/test_data/package_with_metadata/pubspec.yaml index 9c277aed5..998a373d3 100644 --- a/pkgs/native_assets_builder/test_data/package_with_metadata/pubspec.yaml +++ b/pkgs/native_assets_builder/test_data/package_with_metadata/pubspec.yaml @@ -8,9 +8,9 @@ environment: sdk: '>=3.3.0 <4.0.0' dependencies: - native_assets_cli: ^0.5.0 - # native_assets_cli: - # path: ../../../native_assets_cli/ + # native_assets_cli: ^0.5.0 + native_assets_cli: + path: ../../../native_assets_cli/ yaml: ^3.1.1 yaml_edit: ^2.1.0 diff --git a/pkgs/native_assets_builder/test_data/simple_link/assets/data_0.json b/pkgs/native_assets_builder/test_data/simple_link/assets/data_0.json new file mode 100644 index 000000000..e69de29bb diff --git a/pkgs/native_assets_builder/test_data/simple_link/assets/data_1.json b/pkgs/native_assets_builder/test_data/simple_link/assets/data_1.json new file mode 100644 index 000000000..e69de29bb diff --git a/pkgs/native_assets_builder/test_data/simple_link/assets/data_2.json b/pkgs/native_assets_builder/test_data/simple_link/assets/data_2.json new file mode 100644 index 000000000..e69de29bb diff --git a/pkgs/native_assets_builder/test_data/simple_link/assets/data_3.json b/pkgs/native_assets_builder/test_data/simple_link/assets/data_3.json new file mode 100644 index 000000000..e69de29bb diff --git a/pkgs/native_assets_builder/test_data/simple_link/bin/simple_link.dart b/pkgs/native_assets_builder/test_data/simple_link/bin/simple_link.dart new file mode 100644 index 000000000..9f5b97af6 --- /dev/null +++ b/pkgs/native_assets_builder/test_data/simple_link/bin/simple_link.dart @@ -0,0 +1,7 @@ +// Copyright (c) 2024, 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. + +void main() { + print('Tada!'); +} diff --git a/pkgs/native_assets_builder/test_data/simple_link/hook/build.dart b/pkgs/native_assets_builder/test_data/simple_link/hook/build.dart new file mode 100644 index 000000000..aa25818d3 --- /dev/null +++ b/pkgs/native_assets_builder/test_data/simple_link/hook/build.dart @@ -0,0 +1,22 @@ +// Copyright (c) 2024, 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 'package:native_assets_cli/native_assets_cli.dart'; + +const packageName = 'simple_link'; + +void main(List args) async => build(args, (config, output) async { + output.addAssets( + List.generate( + 4, + (index) => DataAsset( + name: 'data_$index', + // TODO(mosuem): Simplify specifying files/file paths + file: config.packageRoot.resolve('assets/data_$index.json'), + package: packageName, + ), + ), + linkInPackage: 'simple_link', + ); + }); diff --git a/pkgs/native_assets_builder/test_data/simple_link/hook/link.dart b/pkgs/native_assets_builder/test_data/simple_link/hook/link.dart new file mode 100644 index 000000000..1c019fcee --- /dev/null +++ b/pkgs/native_assets_builder/test_data/simple_link/hook/link.dart @@ -0,0 +1,14 @@ +// Copyright (c) 2024, 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 'package:native_assets_cli/native_assets_cli.dart'; + +void main(List args) async { + await link( + args, + (config, output) async => output.addAssets(shake(config.assets)), + ); +} + +Iterable shake(Iterable assets) => assets.skip(2); diff --git a/pkgs/native_assets_builder/test_data/simple_link/pubspec.yaml b/pkgs/native_assets_builder/test_data/simple_link/pubspec.yaml new file mode 100644 index 000000000..7bece8b09 --- /dev/null +++ b/pkgs/native_assets_builder/test_data/simple_link/pubspec.yaml @@ -0,0 +1,19 @@ +name: simple_link +description: Build 4 assets for linking, only keep two in link.dart. +version: 0.1.0 + +publish_to: none + +environment: + sdk: '>=3.0.0 <4.0.0' + +dependencies: + cli_config: ^0.2.0 + logging: ^1.1.1 + native_assets_cli: + path: ../../../native_assets_cli/ + +dev_dependencies: + lints: ^3.0.0 + path: ^1.9.0 + test: ^1.23.1 diff --git a/pkgs/native_assets_builder/test_data/wrong_build_output/hook/build.dart b/pkgs/native_assets_builder/test_data/wrong_build_output/hook/build.dart index 77785e79c..969f76335 100644 --- a/pkgs/native_assets_builder/test_data/wrong_build_output/hook/build.dart +++ b/pkgs/native_assets_builder/test_data/wrong_build_output/hook/build.dart @@ -8,9 +8,7 @@ import 'package:native_assets_cli/native_assets_cli_internal.dart'; void main(List args) async { final buildConfig = BuildConfigImpl.fromArguments(args); - await File.fromUri( - buildConfig.outputDirectory.resolve(BuildOutputImpl.fileNameV1_1_0)) - .writeAsString(_wrongContents); + await File.fromUri(buildConfig.outputFile).writeAsString(_wrongContents); } const _wrongContents = ''' diff --git a/pkgs/native_assets_builder/test_data/wrong_build_output/pubspec.yaml b/pkgs/native_assets_builder/test_data/wrong_build_output/pubspec.yaml index 12698c1d1..8d24057f3 100644 --- a/pkgs/native_assets_builder/test_data/wrong_build_output/pubspec.yaml +++ b/pkgs/native_assets_builder/test_data/wrong_build_output/pubspec.yaml @@ -8,9 +8,9 @@ environment: sdk: '>=3.3.0 <4.0.0' dependencies: - native_assets_cli: ^0.5.0 - # native_assets_cli: - # path: ../../../native_assets_cli/ + # native_assets_cli: ^0.5.0 + native_assets_cli: + path: ../../../native_assets_cli/ yaml: ^3.1.1 yaml_edit: ^2.1.0 diff --git a/pkgs/native_assets_builder/test_data/wrong_build_output_2/hook/build.dart b/pkgs/native_assets_builder/test_data/wrong_build_output_2/hook/build.dart index c8412752e..dfb5b5e20 100644 --- a/pkgs/native_assets_builder/test_data/wrong_build_output_2/hook/build.dart +++ b/pkgs/native_assets_builder/test_data/wrong_build_output_2/hook/build.dart @@ -8,9 +8,7 @@ import 'package:native_assets_cli/native_assets_cli_internal.dart'; void main(List args) async { final buildConfig = BuildConfigImpl.fromArguments(args); - await File.fromUri( - buildConfig.outputDirectory.resolve(BuildOutputImpl.fileNameV1_1_0)) - .writeAsString(_wrongContents); + await File.fromUri(buildConfig.outputFile).writeAsString(_wrongContents); } const _wrongContents = ''' diff --git a/pkgs/native_assets_builder/test_data/wrong_build_output_2/pubspec.yaml b/pkgs/native_assets_builder/test_data/wrong_build_output_2/pubspec.yaml index 8b7ec8702..1a5144cba 100644 --- a/pkgs/native_assets_builder/test_data/wrong_build_output_2/pubspec.yaml +++ b/pkgs/native_assets_builder/test_data/wrong_build_output_2/pubspec.yaml @@ -8,9 +8,9 @@ environment: sdk: '>=3.3.0 <4.0.0' dependencies: - native_assets_cli: ^0.5.0 - # native_assets_cli: - # path: ../../../native_assets_cli/ + # native_assets_cli: ^0.5.0 + native_assets_cli: + path: ../../../native_assets_cli/ yaml: ^3.1.1 yaml_edit: ^2.1.0 diff --git a/pkgs/native_assets_builder/test_data/wrong_build_output_3/hook/build.dart b/pkgs/native_assets_builder/test_data/wrong_build_output_3/hook/build.dart index bc1fbca2e..c0c95d799 100644 --- a/pkgs/native_assets_builder/test_data/wrong_build_output_3/hook/build.dart +++ b/pkgs/native_assets_builder/test_data/wrong_build_output_3/hook/build.dart @@ -8,9 +8,7 @@ import 'package:native_assets_cli/native_assets_cli_internal.dart'; void main(List args) async { final buildConfig = BuildConfigImpl.fromArguments(args); - await File.fromUri( - buildConfig.outputDirectory.resolve(BuildOutputImpl.fileNameV1_1_0)) - .writeAsString(_rightContents); + await File.fromUri(buildConfig.outputFile).writeAsString(_rightContents); exit(1); } diff --git a/pkgs/native_assets_builder/test_data/wrong_build_output_3/pubspec.yaml b/pkgs/native_assets_builder/test_data/wrong_build_output_3/pubspec.yaml index 3739c2638..65f185a8a 100644 --- a/pkgs/native_assets_builder/test_data/wrong_build_output_3/pubspec.yaml +++ b/pkgs/native_assets_builder/test_data/wrong_build_output_3/pubspec.yaml @@ -8,9 +8,9 @@ environment: sdk: '>=3.3.0 <4.0.0' dependencies: - native_assets_cli: ^0.5.0 - # native_assets_cli: - # path: ../../../native_assets_cli/ + # native_assets_cli: ^0.5.0 + native_assets_cli: + path: ../../../native_assets_cli/ yaml: ^3.1.1 yaml_edit: ^2.1.0 diff --git a/pkgs/native_assets_builder/test_data/wrong_namespace_asset/pubspec.yaml b/pkgs/native_assets_builder/test_data/wrong_namespace_asset/pubspec.yaml index f07a745b8..cd7008b47 100644 --- a/pkgs/native_assets_builder/test_data/wrong_namespace_asset/pubspec.yaml +++ b/pkgs/native_assets_builder/test_data/wrong_namespace_asset/pubspec.yaml @@ -8,9 +8,9 @@ environment: sdk: '>=3.3.0 <4.0.0' dependencies: - native_assets_cli: ^0.5.0 - # native_assets_cli: - # path: ../../../native_assets_cli/ + # native_assets_cli: ^0.5.0 + native_assets_cli: + path: ../../../native_assets_cli/ yaml: ^3.1.1 yaml_edit: ^2.1.0 diff --git a/pkgs/native_assets_cli/CHANGELOG.md b/pkgs/native_assets_cli/CHANGELOG.md index 7cb4bce9a..cfe7e3348 100644 --- a/pkgs/native_assets_cli/CHANGELOG.md +++ b/pkgs/native_assets_cli/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.6.0-wip + +- Add support for `hook/link.dart`. + ## 0.5.4 - Update documentation about providing `NativeCodeAsset.file` in dry runs. diff --git a/pkgs/native_assets_cli/README.md b/pkgs/native_assets_cli/README.md index 3f1eb41da..eefd10643 100644 --- a/pkgs/native_assets_cli/README.md +++ b/pkgs/native_assets_cli/README.md @@ -34,7 +34,7 @@ A typical layout of a package with native code is: to build/bundle with the Dart/Flutter SDK. This file uses the protocol specified in this package. -An example can be found in [example/](example/). +Example can be found in [example/build/](example/build/). ## Usage diff --git a/pkgs/native_assets_cli/example/README.md b/pkgs/native_assets_cli/example/README.md index 1be681f6c..b24ec948e 100644 --- a/pkgs/native_assets_cli/example/README.md +++ b/pkgs/native_assets_cli/example/README.md @@ -1,9 +1,6 @@ -The examples in this folder illustrate how native code is built and bundled -in Dart and Flutter apps. +These are examples for using the hooks defined in `package:native_assets_cli`. -* [native_add_app/](native_add_app/) has a dependency with C code. - This app should declare nothing special. Dart and Flutter should check - all dependencies for native code. -* [native_add_library/](native_add_library/) contains a library with C code. - When Dart code in this library or dependent on this library is invoked, the - C code must be built and bundled so that it can be used by the Dart code. +* [build/](build/) contains examples on how to use `hook/build.dart` to build + and bundle code assets, such as C libraries, into Dart applications. +* [link/](link/) contains examples on how to treeshake unused assets from a Dart + application using the `hook/link.dart` script. \ No newline at end of file diff --git a/pkgs/native_assets_cli/example/build/README.md b/pkgs/native_assets_cli/example/build/README.md new file mode 100644 index 000000000..1be681f6c --- /dev/null +++ b/pkgs/native_assets_cli/example/build/README.md @@ -0,0 +1,9 @@ +The examples in this folder illustrate how native code is built and bundled +in Dart and Flutter apps. + +* [native_add_app/](native_add_app/) has a dependency with C code. + This app should declare nothing special. Dart and Flutter should check + all dependencies for native code. +* [native_add_library/](native_add_library/) contains a library with C code. + When Dart code in this library or dependent on this library is invoked, the + C code must be built and bundled so that it can be used by the Dart code. diff --git a/pkgs/native_assets_cli/example/local_asset/README.md b/pkgs/native_assets_cli/example/build/local_asset/README.md similarity index 100% rename from pkgs/native_assets_cli/example/local_asset/README.md rename to pkgs/native_assets_cli/example/build/local_asset/README.md diff --git a/pkgs/native_assets_cli/example/local_asset/data/asset.txt b/pkgs/native_assets_cli/example/build/local_asset/data/asset.txt similarity index 100% rename from pkgs/native_assets_cli/example/local_asset/data/asset.txt rename to pkgs/native_assets_cli/example/build/local_asset/data/asset.txt diff --git a/pkgs/native_assets_cli/example/local_asset/hook/build.dart b/pkgs/native_assets_cli/example/build/local_asset/hook/build.dart similarity index 100% rename from pkgs/native_assets_cli/example/local_asset/hook/build.dart rename to pkgs/native_assets_cli/example/build/local_asset/hook/build.dart diff --git a/pkgs/native_assets_cli/example/local_asset/pubspec.yaml b/pkgs/native_assets_cli/example/build/local_asset/pubspec.yaml similarity index 68% rename from pkgs/native_assets_cli/example/local_asset/pubspec.yaml rename to pkgs/native_assets_cli/example/build/local_asset/pubspec.yaml index ad2f491df..39cbaced4 100644 --- a/pkgs/native_assets_cli/example/local_asset/pubspec.yaml +++ b/pkgs/native_assets_cli/example/build/local_asset/pubspec.yaml @@ -3,16 +3,16 @@ publish_to: none name: local_asset description: Uses an asset local to the package. version: 0.1.0 -repository: https://github.com/dart-lang/native/tree/main/pkgs/native_assets_cli/example/native_add_library +repository: https://github.com/dart-lang/native/tree/main/pkgs/native_assets_cli/example/build/native_add_library environment: sdk: '>=3.3.0 <4.0.0' dependencies: logging: ^1.1.1 - native_assets_cli: ^0.5.0 -# native_assets_cli: -# path: ../../../native_assets_cli/ + # native_assets_cli: ^0.5.0 + native_assets_cli: + path: ../../../../native_assets_cli/ dev_dependencies: ffigen: ^8.0.2 diff --git a/pkgs/native_assets_cli/example/native_add_app/README.md b/pkgs/native_assets_cli/example/build/native_add_app/README.md similarity index 100% rename from pkgs/native_assets_cli/example/native_add_app/README.md rename to pkgs/native_assets_cli/example/build/native_add_app/README.md diff --git a/pkgs/native_assets_cli/example/native_add_app/bin/native_add_app.dart b/pkgs/native_assets_cli/example/build/native_add_app/bin/native_add_app.dart similarity index 100% rename from pkgs/native_assets_cli/example/native_add_app/bin/native_add_app.dart rename to pkgs/native_assets_cli/example/build/native_add_app/bin/native_add_app.dart diff --git a/pkgs/native_assets_cli/example/native_add_app/pubspec.yaml b/pkgs/native_assets_cli/example/build/native_add_app/pubspec.yaml similarity index 87% rename from pkgs/native_assets_cli/example/native_add_app/pubspec.yaml rename to pkgs/native_assets_cli/example/build/native_add_app/pubspec.yaml index f6b2adc52..86e03409f 100644 --- a/pkgs/native_assets_cli/example/native_add_app/pubspec.yaml +++ b/pkgs/native_assets_cli/example/build/native_add_app/pubspec.yaml @@ -3,7 +3,7 @@ publish_to: none name: native_add_app description: Invokes a package with native assets. version: 0.1.0 -repository: https://github.com/dart-lang/native/tree/main/pkgs/native_assets_cli/example/native_add_app +repository: https://github.com/dart-lang/native/tree/main/pkgs/native_assets_cli/example/build/native_add_app environment: sdk: '>=3.3.0 <4.0.0' diff --git a/pkgs/native_assets_cli/example/native_add_app/test/native_add_library_test.dart b/pkgs/native_assets_cli/example/build/native_add_app/test/native_add_library_test.dart similarity index 100% rename from pkgs/native_assets_cli/example/native_add_app/test/native_add_library_test.dart rename to pkgs/native_assets_cli/example/build/native_add_app/test/native_add_library_test.dart diff --git a/pkgs/native_assets_cli/example/native_add_library/README.md b/pkgs/native_assets_cli/example/build/native_add_library/README.md similarity index 100% rename from pkgs/native_assets_cli/example/native_add_library/README.md rename to pkgs/native_assets_cli/example/build/native_add_library/README.md diff --git a/pkgs/native_assets_cli/example/native_add_library/ffigen.yaml b/pkgs/native_assets_cli/example/build/native_add_library/ffigen.yaml similarity index 100% rename from pkgs/native_assets_cli/example/native_add_library/ffigen.yaml rename to pkgs/native_assets_cli/example/build/native_add_library/ffigen.yaml diff --git a/pkgs/native_assets_cli/example/native_add_library/hook/build.dart b/pkgs/native_assets_cli/example/build/native_add_library/hook/build.dart similarity index 100% rename from pkgs/native_assets_cli/example/native_add_library/hook/build.dart rename to pkgs/native_assets_cli/example/build/native_add_library/hook/build.dart diff --git a/pkgs/native_assets_cli/example/native_add_library/lib/native_add_library.dart b/pkgs/native_assets_cli/example/build/native_add_library/lib/native_add_library.dart similarity index 100% rename from pkgs/native_assets_cli/example/native_add_library/lib/native_add_library.dart rename to pkgs/native_assets_cli/example/build/native_add_library/lib/native_add_library.dart diff --git a/pkgs/native_assets_cli/example/native_add_library/pubspec.yaml b/pkgs/native_assets_cli/example/build/native_add_library/pubspec.yaml similarity index 56% rename from pkgs/native_assets_cli/example/native_add_library/pubspec.yaml rename to pkgs/native_assets_cli/example/build/native_add_library/pubspec.yaml index fb790cfa1..ca8fd001f 100644 --- a/pkgs/native_assets_cli/example/native_add_library/pubspec.yaml +++ b/pkgs/native_assets_cli/example/build/native_add_library/pubspec.yaml @@ -3,19 +3,19 @@ publish_to: none name: native_add_library description: Sums two numbers with native code. version: 0.1.0 -repository: https://github.com/dart-lang/native/tree/main/pkgs/native_assets_cli/example/native_add_library +repository: https://github.com/dart-lang/native/tree/main/pkgs/native_assets_cli/example/build/native_add_library environment: sdk: '>=3.3.0 <4.0.0' dependencies: logging: ^1.1.1 - native_assets_cli: ^0.5.0 - # native_assets_cli: - # path: ../../../native_assets_cli/ - native_toolchain_c: ^0.4.0 - # native_toolchain_c: - # path: ../../../native_toolchain_c/ + # native_assets_cli: ^0.5.0 + native_assets_cli: + path: ../../../../native_assets_cli/ + # native_toolchain_c: ^0.4.0 + native_toolchain_c: + path: ../../../../native_toolchain_c/ dev_dependencies: ffigen: ^8.0.2 diff --git a/pkgs/native_assets_cli/example/native_add_library/src/native_add_library.c b/pkgs/native_assets_cli/example/build/native_add_library/src/native_add_library.c similarity index 100% rename from pkgs/native_assets_cli/example/native_add_library/src/native_add_library.c rename to pkgs/native_assets_cli/example/build/native_add_library/src/native_add_library.c diff --git a/pkgs/native_assets_cli/example/native_add_library/src/native_add_library.h b/pkgs/native_assets_cli/example/build/native_add_library/src/native_add_library.h similarity index 100% rename from pkgs/native_assets_cli/example/native_add_library/src/native_add_library.h rename to pkgs/native_assets_cli/example/build/native_add_library/src/native_add_library.h diff --git a/pkgs/native_assets_cli/example/native_add_library/test/native_add_library_test.dart b/pkgs/native_assets_cli/example/build/native_add_library/test/native_add_library_test.dart similarity index 100% rename from pkgs/native_assets_cli/example/native_add_library/test/native_add_library_test.dart rename to pkgs/native_assets_cli/example/build/native_add_library/test/native_add_library_test.dart diff --git a/pkgs/native_assets_cli/example/use_dart_api/README.md b/pkgs/native_assets_cli/example/build/use_dart_api/README.md similarity index 100% rename from pkgs/native_assets_cli/example/use_dart_api/README.md rename to pkgs/native_assets_cli/example/build/use_dart_api/README.md diff --git a/pkgs/native_assets_cli/example/use_dart_api/ffigen.yaml b/pkgs/native_assets_cli/example/build/use_dart_api/ffigen.yaml similarity index 100% rename from pkgs/native_assets_cli/example/use_dart_api/ffigen.yaml rename to pkgs/native_assets_cli/example/build/use_dart_api/ffigen.yaml diff --git a/pkgs/native_assets_cli/example/use_dart_api/hook/build.dart b/pkgs/native_assets_cli/example/build/use_dart_api/hook/build.dart similarity index 100% rename from pkgs/native_assets_cli/example/use_dart_api/hook/build.dart rename to pkgs/native_assets_cli/example/build/use_dart_api/hook/build.dart diff --git a/pkgs/native_assets_cli/example/use_dart_api/lib/src/use_dart_api_bindings_generated.dart b/pkgs/native_assets_cli/example/build/use_dart_api/lib/src/use_dart_api_bindings_generated.dart similarity index 100% rename from pkgs/native_assets_cli/example/use_dart_api/lib/src/use_dart_api_bindings_generated.dart rename to pkgs/native_assets_cli/example/build/use_dart_api/lib/src/use_dart_api_bindings_generated.dart diff --git a/pkgs/native_assets_cli/example/use_dart_api/lib/use_dart_api.dart b/pkgs/native_assets_cli/example/build/use_dart_api/lib/use_dart_api.dart similarity index 100% rename from pkgs/native_assets_cli/example/use_dart_api/lib/use_dart_api.dart rename to pkgs/native_assets_cli/example/build/use_dart_api/lib/use_dart_api.dart diff --git a/pkgs/native_assets_cli/example/use_dart_api/manifest.yaml b/pkgs/native_assets_cli/example/build/use_dart_api/manifest.yaml similarity index 100% rename from pkgs/native_assets_cli/example/use_dart_api/manifest.yaml rename to pkgs/native_assets_cli/example/build/use_dart_api/manifest.yaml diff --git a/pkgs/native_assets_cli/example/use_dart_api/pubspec.yaml b/pkgs/native_assets_cli/example/build/use_dart_api/pubspec.yaml similarity index 56% rename from pkgs/native_assets_cli/example/use_dart_api/pubspec.yaml rename to pkgs/native_assets_cli/example/build/use_dart_api/pubspec.yaml index a53e696dd..bfef2670c 100644 --- a/pkgs/native_assets_cli/example/use_dart_api/pubspec.yaml +++ b/pkgs/native_assets_cli/example/build/use_dart_api/pubspec.yaml @@ -9,12 +9,12 @@ environment: dependencies: logging: ^1.1.1 - native_assets_cli: ^0.5.0 - # native_assets_cli: - # path: ../../../native_assets_cli/ - native_toolchain_c: ^0.4.0 - # native_toolchain_c: - # path: ../../../native_toolchain_c/ + # native_assets_cli: ^0.5.0 + native_assets_cli: + path: ../../../../native_assets_cli/ + # native_toolchain_c: ^0.4.0 + native_toolchain_c: + path: ../../../../native_toolchain_c/ dev_dependencies: ffigen: ^10.0.0 diff --git a/pkgs/native_assets_cli/example/use_dart_api/src/dart_api.h b/pkgs/native_assets_cli/example/build/use_dart_api/src/dart_api.h similarity index 100% rename from pkgs/native_assets_cli/example/use_dart_api/src/dart_api.h rename to pkgs/native_assets_cli/example/build/use_dart_api/src/dart_api.h diff --git a/pkgs/native_assets_cli/example/use_dart_api/src/dart_api_dl.c b/pkgs/native_assets_cli/example/build/use_dart_api/src/dart_api_dl.c similarity index 100% rename from pkgs/native_assets_cli/example/use_dart_api/src/dart_api_dl.c rename to pkgs/native_assets_cli/example/build/use_dart_api/src/dart_api_dl.c diff --git a/pkgs/native_assets_cli/example/use_dart_api/src/dart_api_dl.h b/pkgs/native_assets_cli/example/build/use_dart_api/src/dart_api_dl.h similarity index 100% rename from pkgs/native_assets_cli/example/use_dart_api/src/dart_api_dl.h rename to pkgs/native_assets_cli/example/build/use_dart_api/src/dart_api_dl.h diff --git a/pkgs/native_assets_cli/example/use_dart_api/src/dart_embedder_api.h b/pkgs/native_assets_cli/example/build/use_dart_api/src/dart_embedder_api.h similarity index 100% rename from pkgs/native_assets_cli/example/use_dart_api/src/dart_embedder_api.h rename to pkgs/native_assets_cli/example/build/use_dart_api/src/dart_embedder_api.h diff --git a/pkgs/native_assets_cli/example/use_dart_api/src/dart_native_api.h b/pkgs/native_assets_cli/example/build/use_dart_api/src/dart_native_api.h similarity index 100% rename from pkgs/native_assets_cli/example/use_dart_api/src/dart_native_api.h rename to pkgs/native_assets_cli/example/build/use_dart_api/src/dart_native_api.h diff --git a/pkgs/native_assets_cli/example/use_dart_api/src/dart_tools_api.h b/pkgs/native_assets_cli/example/build/use_dart_api/src/dart_tools_api.h similarity index 100% rename from pkgs/native_assets_cli/example/use_dart_api/src/dart_tools_api.h rename to pkgs/native_assets_cli/example/build/use_dart_api/src/dart_tools_api.h diff --git a/pkgs/native_assets_cli/example/use_dart_api/src/dart_version.h b/pkgs/native_assets_cli/example/build/use_dart_api/src/dart_version.h similarity index 100% rename from pkgs/native_assets_cli/example/use_dart_api/src/dart_version.h rename to pkgs/native_assets_cli/example/build/use_dart_api/src/dart_version.h diff --git a/pkgs/native_assets_cli/example/use_dart_api/src/internal/dart_api_dl_impl.h b/pkgs/native_assets_cli/example/build/use_dart_api/src/internal/dart_api_dl_impl.h similarity index 100% rename from pkgs/native_assets_cli/example/use_dart_api/src/internal/dart_api_dl_impl.h rename to pkgs/native_assets_cli/example/build/use_dart_api/src/internal/dart_api_dl_impl.h diff --git a/pkgs/native_assets_cli/example/use_dart_api/src/use_dart_api.c b/pkgs/native_assets_cli/example/build/use_dart_api/src/use_dart_api.c similarity index 100% rename from pkgs/native_assets_cli/example/use_dart_api/src/use_dart_api.c rename to pkgs/native_assets_cli/example/build/use_dart_api/src/use_dart_api.c diff --git a/pkgs/native_assets_cli/example/use_dart_api/src/use_dart_api.h b/pkgs/native_assets_cli/example/build/use_dart_api/src/use_dart_api.h similarity index 100% rename from pkgs/native_assets_cli/example/use_dart_api/src/use_dart_api.h rename to pkgs/native_assets_cli/example/build/use_dart_api/src/use_dart_api.h diff --git a/pkgs/native_assets_cli/example/use_dart_api/test/use_dart_api_test.dart b/pkgs/native_assets_cli/example/build/use_dart_api/test/use_dart_api_test.dart similarity index 100% rename from pkgs/native_assets_cli/example/use_dart_api/test/use_dart_api_test.dart rename to pkgs/native_assets_cli/example/build/use_dart_api/test/use_dart_api_test.dart diff --git a/pkgs/native_assets_cli/example/link/README.md b/pkgs/native_assets_cli/example/link/README.md new file mode 100644 index 000000000..dfaf7d413 --- /dev/null +++ b/pkgs/native_assets_cli/example/link/README.md @@ -0,0 +1,9 @@ +The examples in this folder illustrate how to use information from the Dart +treeshaking to "link" assets. + +* [app_with_asset_treeshaking/](app_with_asset_treeshaking/) calls some methods + from `package_with_assets`. +* [package_with_assets/](package_with_assets/) contains a library with two + assets and code which uses these assets. It declares these assets in a + `build.dart` hook, and treeshakes them based on the "resources" collected + during the kernel compilation. diff --git a/pkgs/native_assets_cli/example/link/app_with_asset_treeshaking/.gitignore b/pkgs/native_assets_cli/example/link/app_with_asset_treeshaking/.gitignore new file mode 100644 index 000000000..3a8579040 --- /dev/null +++ b/pkgs/native_assets_cli/example/link/app_with_asset_treeshaking/.gitignore @@ -0,0 +1,3 @@ +# https://dart.dev/guides/libraries/private-files +# Created by `dart pub` +.dart_tool/ diff --git a/pkgs/native_assets_cli/example/link/app_with_asset_treeshaking/CHANGELOG.md b/pkgs/native_assets_cli/example/link/app_with_asset_treeshaking/CHANGELOG.md new file mode 100644 index 000000000..effe43c82 --- /dev/null +++ b/pkgs/native_assets_cli/example/link/app_with_asset_treeshaking/CHANGELOG.md @@ -0,0 +1,3 @@ +## 1.0.0 + +- Initial version. diff --git a/pkgs/native_assets_cli/example/link/app_with_asset_treeshaking/analysis_options.yaml b/pkgs/native_assets_cli/example/link/app_with_asset_treeshaking/analysis_options.yaml new file mode 100644 index 000000000..dee8927aa --- /dev/null +++ b/pkgs/native_assets_cli/example/link/app_with_asset_treeshaking/analysis_options.yaml @@ -0,0 +1,30 @@ +# This file configures the static analysis results for your project (errors, +# warnings, and lints). +# +# This enables the 'recommended' set of lints from `package:lints`. +# This set helps identify many issues that may lead to problems when running +# or consuming Dart code, and enforces writing Dart using a single, idiomatic +# style and format. +# +# If you want a smaller set of lints you can change this to specify +# 'package:lints/core.yaml'. These are just the most critical lints +# (the recommended set includes the core lints). +# The core lints are also what is used by pub.dev for scoring packages. + +include: package:lints/recommended.yaml + +# Uncomment the following section to specify additional rules. + +# linter: +# rules: +# - camel_case_types + +# analyzer: +# exclude: +# - path/to/excluded/files/** + +# For more information about the core and recommended set of lints, see +# https://dart.dev/go/core-lints + +# For additional information about configuring this file, see +# https://dart.dev/guides/language/analysis-options diff --git a/pkgs/native_assets_cli/example/link/app_with_asset_treeshaking/bin/app_with_asset_treeshaking.dart b/pkgs/native_assets_cli/example/link/app_with_asset_treeshaking/bin/app_with_asset_treeshaking.dart new file mode 100644 index 000000000..b315f0c22 --- /dev/null +++ b/pkgs/native_assets_cli/example/link/app_with_asset_treeshaking/bin/app_with_asset_treeshaking.dart @@ -0,0 +1,10 @@ +// Copyright (c) 2024, 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 'package:app_with_asset_treeshaking/app_with_asset_treeshaking.dart' + as app_with_asset_treeshaking; + +void main(List arguments) { + print('Hello world: ${app_with_asset_treeshaking.callOther()}!'); +} diff --git a/pkgs/native_assets_cli/example/link/app_with_asset_treeshaking/lib/app_with_asset_treeshaking.dart b/pkgs/native_assets_cli/example/link/app_with_asset_treeshaking/lib/app_with_asset_treeshaking.dart new file mode 100644 index 000000000..19ebd67d8 --- /dev/null +++ b/pkgs/native_assets_cli/example/link/app_with_asset_treeshaking/lib/app_with_asset_treeshaking.dart @@ -0,0 +1,9 @@ +// Copyright (c) 2024, 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 'package:package_with_assets/package_with_assets.dart'; + +String callOther() { + return someMethod(); +} diff --git a/pkgs/native_assets_cli/example/link/app_with_asset_treeshaking/pubspec.yaml b/pkgs/native_assets_cli/example/link/app_with_asset_treeshaking/pubspec.yaml new file mode 100644 index 000000000..0fc93b81d --- /dev/null +++ b/pkgs/native_assets_cli/example/link/app_with_asset_treeshaking/pubspec.yaml @@ -0,0 +1,17 @@ +publish_to: none + +name: app_with_asset_treeshaking +description: A sample command-line application. +version: 1.0.0 + +environment: + sdk: ^3.0.0 + +dependencies: + logging: ^1.1.1 + package_with_assets: + path: ../package_with_assets/ + +dev_dependencies: + lints: ^3.0.0 + test: ^1.24.0 diff --git a/pkgs/native_assets_cli/example/link/package_with_assets/.gitignore b/pkgs/native_assets_cli/example/link/package_with_assets/.gitignore new file mode 100644 index 000000000..3cceda557 --- /dev/null +++ b/pkgs/native_assets_cli/example/link/package_with_assets/.gitignore @@ -0,0 +1,7 @@ +# https://dart.dev/guides/libraries/private-files +# Created by `dart pub` +.dart_tool/ + +# Avoid committing pubspec.lock for library packages; see +# https://dart.dev/guides/libraries/private-files#pubspeclock. +pubspec.lock diff --git a/pkgs/native_assets_cli/example/link/package_with_assets/CHANGELOG.md b/pkgs/native_assets_cli/example/link/package_with_assets/CHANGELOG.md new file mode 100644 index 000000000..effe43c82 --- /dev/null +++ b/pkgs/native_assets_cli/example/link/package_with_assets/CHANGELOG.md @@ -0,0 +1,3 @@ +## 1.0.0 + +- Initial version. diff --git a/pkgs/native_assets_cli/example/link/package_with_assets/assets/unused_asset.json b/pkgs/native_assets_cli/example/link/package_with_assets/assets/unused_asset.json new file mode 100644 index 000000000..1a7d134dd --- /dev/null +++ b/pkgs/native_assets_cli/example/link/package_with_assets/assets/unused_asset.json @@ -0,0 +1,3 @@ +{ + "freddy": "mercury" +} diff --git a/pkgs/native_assets_cli/example/link/package_with_assets/assets/used_asset.json b/pkgs/native_assets_cli/example/link/package_with_assets/assets/used_asset.json new file mode 100644 index 000000000..697721f13 --- /dev/null +++ b/pkgs/native_assets_cli/example/link/package_with_assets/assets/used_asset.json @@ -0,0 +1,3 @@ +{ + "leroy": "jenkins" +} diff --git a/pkgs/native_assets_cli/example/link/package_with_assets/hook/build.dart b/pkgs/native_assets_cli/example/link/package_with_assets/hook/build.dart new file mode 100644 index 000000000..ed19aa01a --- /dev/null +++ b/pkgs/native_assets_cli/example/link/package_with_assets/hook/build.dart @@ -0,0 +1,24 @@ +// Copyright (c) 2024, 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 'package:native_assets_cli/native_assets_cli.dart'; + +void main(List args) async { + await build(args, (config, output) async { + final packageName = config.packageName; + final allAssets = [ + DataAsset( + package: packageName, + name: 'unused_asset', + file: config.packageRoot.resolve('assets/unused_asset.json'), + ), + DataAsset( + package: packageName, + name: 'used_asset', + file: config.packageRoot.resolve('assets/used_asset.json'), + ) + ]; + output.addAssets(allAssets, linkInPackage: packageName); + }); +} diff --git a/pkgs/native_assets_cli/example/link/package_with_assets/hook/link.dart b/pkgs/native_assets_cli/example/link/package_with_assets/hook/link.dart new file mode 100644 index 000000000..7c81e64d1 --- /dev/null +++ b/pkgs/native_assets_cli/example/link/package_with_assets/hook/link.dart @@ -0,0 +1,13 @@ +// Copyright (c) 2024, 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 'package:native_assets_cli/native_assets_cli.dart'; + +void main(List args) async { + await link(args, (config, output) async { + //TODO: Add tree shaking by reading the resources.json produced by the SDK. + final dataAssets = config.assets.whereType(); + output.addAssets(dataAssets); + }); +} diff --git a/pkgs/native_assets_cli/example/link/package_with_assets/lib/package_with_assets.dart b/pkgs/native_assets_cli/example/link/package_with_assets/lib/package_with_assets.dart new file mode 100644 index 000000000..4317fb29d --- /dev/null +++ b/pkgs/native_assets_cli/example/link/package_with_assets/lib/package_with_assets.dart @@ -0,0 +1,13 @@ +// Copyright (c) 2024, 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 'package:meta/meta.dart'; + +//TODO: Actually use the assets, needs the AssetBundle interface for Dart. See +//also https://github.com/dart-lang/sdk/issues/54003. +@ResourceIdentifier('used_asset') +String someMethod() => 'Using used_asset'; + +@ResourceIdentifier('unused_asset') +String someOtherMethod() => 'Using unused_asset'; diff --git a/pkgs/native_assets_cli/example/link/package_with_assets/pubspec.yaml b/pkgs/native_assets_cli/example/link/package_with_assets/pubspec.yaml new file mode 100644 index 000000000..77dccdba1 --- /dev/null +++ b/pkgs/native_assets_cli/example/link/package_with_assets/pubspec.yaml @@ -0,0 +1,20 @@ +publish_to: none + +name: package_with_assets +description: A starting point for Dart libraries or applications. +version: 1.0.0 +# repository: https://github.com/my_org/my_repo + +environment: + sdk: ^3.0.0 + +dependencies: + logging: ^1.1.1 + meta: ^1.12.0 + # native_assets_cli: ^0.5.0 + native_assets_cli: + path: ../../../../native_assets_cli/ + +dev_dependencies: + lints: ^3.0.0 + test: ^1.24.0 diff --git a/pkgs/native_assets_cli/lib/native_assets_cli.dart b/pkgs/native_assets_cli/lib/native_assets_cli.dart index 1ddebe1a9..46a8a13d3 100644 --- a/pkgs/native_assets_cli/lib/native_assets_cli.dart +++ b/pkgs/native_assets_cli/lib/native_assets_cli.dart @@ -10,6 +10,7 @@ export 'src/api/architecture.dart' show Architecture; export 'src/api/asset.dart' show Asset, + DataAsset, DynamicLoadingBundled, DynamicLoadingSystem, LinkMode, @@ -20,7 +21,10 @@ export 'src/api/asset.dart' export 'src/api/build.dart'; export 'src/api/build_config.dart' show BuildConfig, CCompilerConfig; export 'src/api/build_mode.dart' show BuildMode; -export 'src/api/build_output.dart' show BuildOutput; +export 'src/api/build_output.dart' show BuildOutput, LinkOutput; +export 'src/api/hook_config.dart' show HookConfig; export 'src/api/ios_sdk.dart' show IOSSdk; +export 'src/api/link.dart'; +export 'src/api/link_config.dart' show LinkConfig; export 'src/api/link_mode_preference.dart' show LinkModePreference; export 'src/api/os.dart' show OS; diff --git a/pkgs/native_assets_cli/lib/native_assets_cli_internal.dart b/pkgs/native_assets_cli/lib/native_assets_cli_internal.dart index c96f2a2b2..80e764180 100644 --- a/pkgs/native_assets_cli/lib/native_assets_cli_internal.dart +++ b/pkgs/native_assets_cli/lib/native_assets_cli_internal.dart @@ -30,10 +30,14 @@ export 'src/api/asset.dart' StaticLinkingImpl; export 'src/api/build_config.dart' show BuildConfigImpl, CCompilerConfigImpl; export 'src/api/build_mode.dart' show BuildModeImpl; -export 'src/api/build_output.dart' show BuildOutputImpl; +export 'src/api/build_output.dart' show HookOutputImpl; +export 'src/api/hook_config.dart' show HookConfigImpl; export 'src/api/ios_sdk.dart' show IOSSdkImpl; +export 'src/api/link_config.dart' show LinkConfigImpl; export 'src/api/link_mode_preference.dart' show LinkModePreferenceImpl; export 'src/api/os.dart' show OSImpl; export 'src/model/dependencies.dart'; +export 'src/model/hook.dart'; export 'src/model/metadata.dart'; +export 'src/model/resource_identifiers.dart'; export 'src/model/target.dart'; diff --git a/pkgs/native_assets_cli/lib/src/api/build.dart b/pkgs/native_assets_cli/lib/src/api/build.dart index 0cd4bb5f5..7b173d402 100644 --- a/pkgs/native_assets_cli/lib/src/api/build.dart +++ b/pkgs/native_assets_cli/lib/src/api/build.dart @@ -89,8 +89,8 @@ Future build( List arguments, Future Function(BuildConfig config, BuildOutput output) builder, ) async { - final config = BuildConfig(arguments); - final output = BuildOutputImpl(); + final config = BuildConfigImpl.fromArguments(arguments); + final output = HookOutputImpl(); await builder(config, output); await output.writeToFile(config: config); } diff --git a/pkgs/native_assets_cli/lib/src/api/build_config.dart b/pkgs/native_assets_cli/lib/src/api/build_config.dart index b27f6b628..3d1fa965b 100644 --- a/pkgs/native_assets_cli/lib/src/api/build_config.dart +++ b/pkgs/native_assets_cli/lib/src/api/build_config.dart @@ -2,22 +2,21 @@ // 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:convert'; import 'dart:io'; import 'package:cli_config/cli_config.dart'; import 'package:collection/collection.dart'; -import 'package:crypto/crypto.dart'; import 'package:pub_semver/pub_semver.dart'; +import '../model/hook.dart'; import '../model/metadata.dart'; -import '../model/target.dart'; import '../utils/json.dart'; import '../utils/map.dart'; import 'architecture.dart'; import 'asset.dart'; import 'build.dart'; import 'build_mode.dart'; +import 'hook_config.dart'; import 'ios_sdk.dart'; import 'link_mode_preference.dart'; import 'os.dart'; @@ -32,53 +31,7 @@ part 'c_compiler_config.dart'; /// be automatically run, by the Flutter and Dart SDK tools. The hook will be /// run with specific commandline arguments, which [BuildConfig] can parse and /// provide more convenient access to. -abstract final class BuildConfig { - /// The directory in which all output and intermediate artifacts should be - /// placed. - Uri get outputDirectory; - - /// The name of the package the native assets are built for. - String get packageName; - - /// The root of the package the native assets are built for. - /// - /// Often a package's native assets are built because a package is a - /// dependency of another. For this it is convenient to know the packageRoot. - Uri get packageRoot; - - /// The architecture being compiled for. - /// - /// Not specified (`null`) during a [dryRun]. - Architecture? get targetArchitecture; - - /// The operating system being compiled for. - OS get targetOS; - - /// When compiling for iOS, whether to target device or simulator. - /// - /// Not available if [targetOS] is [OS.iOS]. Will throw a [StateError] if - /// accessed during a [dryRun]. - /// - /// Not available during a [dryRun]. Will throw a [StateError] if accessed - /// during a [dryRun]. - IOSSdk get targetIOSSdk; - - /// When compiling for Android, the minimum Android SDK API version to that - /// the compiled code will be compatible with. - /// - /// Required when [targetOS] equals [OS.android]. - /// - /// Not available during a [dryRun]. Will throw a [StateError] if accessed - /// during a [dryRun]. - /// - /// For more information about the Android API version, refer to - /// [`minSdkVersion`](https://developer.android.com/ndk/guides/sdk-versions#minsdkversion) - /// in the Android documentation. - int? get targetAndroidNdkApi; - - /// The preferred [LinkMode] method for [NativeCodeAsset]s. - LinkModePreference get linkModePreference; - +abstract final class BuildConfig implements HookConfig { /// Metadata from a direct dependency. /// /// The [packageName] of is the package name of the direct dependency. @@ -89,33 +42,6 @@ abstract final class BuildConfig { /// during a [dryRun]. Object? metadatum(String packageName, String key); - /// The configuration for invoking the C compiler. - /// - /// Not available during a [dryRun]. Will throw a [StateError] if accessed - /// during a [dryRun]. - CCompilerConfig get cCompiler; - - /// Whether this run is a dry-run, which doesn't build anything. - /// - /// A dry-run only reports information about which assets a build would - /// create, but doesn't actually create files. - bool get dryRun; - - /// The [BuildMode] that the code should be compiled in. - /// - /// Currently [BuildMode.debug] and [BuildMode.release] are the only modes. - /// - /// Not available during a [dryRun]. Will throw a [StateError] if accessed - /// during a [dryRun]. - BuildMode get buildMode; - - /// The asset types that the invoker of this build supports. - /// - /// Currently known values: - /// * [NativeCodeAsset.type] - /// * [DataAsset.type] - Iterable get supportedAssetTypes; - /// The version of [BuildConfig]. /// /// The build config is used in the protocol between the Dart and Flutter SDKs @@ -124,7 +50,7 @@ abstract final class BuildConfig { /// We're trying to avoid breaking changes. However, in the case that we have /// to, the major version mismatch between the Dart or Flutter SDK and build /// hook (`hook/build.dart`) will lead to a nice error message. - static Version get latestVersion => BuildConfigImpl.latestVersion; + static Version get latestVersion => HookConfigImpl.latestVersion; /// Constructs a config by parsing CLI arguments and loading the config file. /// @@ -177,7 +103,7 @@ abstract final class BuildConfig { Iterable? supportedAssetTypes, }) => BuildConfigImpl( - outDir: outputDirectory, + outputDirectory: outputDirectory, packageName: packageName, packageRoot: packageRoot, buildMode: buildMode as BuildModeImpl, @@ -192,7 +118,7 @@ abstract final class BuildConfig { for (final entry in dependencyMetadata.entries) entry.key: Metadata(entry.value.cast()) } - : {}, + : null, supportedAssetTypes: supportedAssetTypes, ); @@ -204,7 +130,7 @@ abstract final class BuildConfig { /// /// For the documentation of the parameters, see the equally named fields. factory BuildConfig.dryRun({ - required Uri outDir, + required Uri outputDirectory, required String packageName, required Uri packageRoot, required OS targetOS, @@ -212,7 +138,7 @@ abstract final class BuildConfig { Iterable? supportedAssetTypes, }) => BuildConfigImpl.dryRun( - outDir: outDir, + outputDirectory: outputDirectory, packageName: packageName, packageRoot: packageRoot, targetOS: targetOS as OSImpl, diff --git a/pkgs/native_assets_cli/lib/src/api/build_output.dart b/pkgs/native_assets_cli/lib/src/api/build_output.dart index 86387e596..20aab1a8f 100644 --- a/pkgs/native_assets_cli/lib/src/api/build_output.dart +++ b/pkgs/native_assets_cli/lib/src/api/build_output.dart @@ -18,9 +18,11 @@ import '../utils/map.dart'; import 'architecture.dart'; import 'asset.dart'; import 'build_config.dart'; +import 'hook_config.dart'; import 'os.dart'; -part '../model/build_output.dart'; +part '../model/hook_output.dart'; +part 'link_output.dart'; /// The output of a build hook (`hook/build.dart`) invocation. /// @@ -42,6 +44,16 @@ abstract final class BuildOutput { /// the dry run must be provided. Iterable get assets; + /// The assets produced by this build which should be linked. + /// + /// Every key in the map is a package name. These assets in the values are not + /// bundled with the application, but are sent to the link hook of the package + /// specified in the key, which can decide if they are bundled or not. + /// + /// In dry runs, the assets for all [Architecture]s for the [OS] specified in + /// the dry run must be provided. + Map> get assetsForLinking; + /// The files used by this build. /// /// If any of the files in [dependencies] are modified after [timestamp], the @@ -77,7 +89,7 @@ abstract final class BuildOutput { Iterable? dependencies, Map? metadata, }) => - BuildOutputImpl( + HookOutputImpl( timestamp: timestamp, assets: assets?.cast().toList(), dependencies: Dependencies([...?dependencies]), @@ -85,10 +97,19 @@ abstract final class BuildOutput { ); /// Adds [Asset]s produced by this build or dry run. - void addAsset(Asset asset); + /// + /// If the [linkInPackage] argument is specified, the asset will not be + /// bundled during the build step, but sent as input to the link hook of the + /// specified package, where it can be further processed and possibly bundled. + void addAsset(Asset asset, {String? linkInPackage}); /// Adds [Asset]s produced by this build or dry run. - void addAssets(Iterable assets); + /// + /// If the [linkInPackage] argument is specified, the assets will not be + /// bundled during the build step, but sent as input to the link hook of the + /// specified package, where they can be further processed and possibly + /// bundled. + void addAssets(Iterable assets, {String? linkInPackage}); /// Adds file used by this build. /// @@ -114,5 +135,5 @@ abstract final class BuildOutput { /// /// The build output is used in the protocol between the Dart and Flutter SDKs /// and packages through build hook invocations. - static Version get latestVersion => BuildOutputImpl.latestVersion; + static Version get latestVersion => HookOutputImpl.latestVersion; } diff --git a/pkgs/native_assets_cli/lib/src/api/data_asset.dart b/pkgs/native_assets_cli/lib/src/api/data_asset.dart index 6726cf583..4c9459d43 100644 --- a/pkgs/native_assets_cli/lib/src/api/data_asset.dart +++ b/pkgs/native_assets_cli/lib/src/api/data_asset.dart @@ -15,17 +15,24 @@ part of 'asset.dart'; abstract final class DataAsset implements Asset { /// Constructs a data asset. /// - /// The [id] of this asset is a uri `package:/` from [package] - /// and [name]. + /// The unique [id] of this asset is a uri `package:/` from + /// [package] and [name]. factory DataAsset({ required String package, required String name, required Uri file, }) => DataAssetImpl( - id: 'package:$package/$name', + name: name, + package: package, file: file, ); + /// The package which contains this asset. + String get package; + + /// The name of this asset, which must be unique for the package. + String get name; + static const String type = 'data'; } diff --git a/pkgs/native_assets_cli/lib/src/api/hook_config.dart b/pkgs/native_assets_cli/lib/src/api/hook_config.dart new file mode 100644 index 000000000..4935b19e0 --- /dev/null +++ b/pkgs/native_assets_cli/lib/src/api/hook_config.dart @@ -0,0 +1,104 @@ +// Copyright (c) 2024, 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:convert'; + +import 'package:cli_config/cli_config.dart'; +import 'package:collection/collection.dart'; +import 'package:crypto/crypto.dart'; +import 'package:pub_semver/pub_semver.dart'; + +import '../model/hook.dart'; +import '../model/metadata.dart'; +import '../model/target.dart'; +import '../utils/map.dart'; +import 'architecture.dart'; +import 'asset.dart'; +import 'build_config.dart'; +import 'build_mode.dart'; +import 'ios_sdk.dart'; +import 'link_config.dart'; +import 'link_mode_preference.dart'; +import 'os.dart'; + +part '../model/hook_config.dart'; + +/// The shared properties of a [LinkConfig] and a [BuildConfig]. +/// +/// This abstraction makes it easier to design APIs intended for both kinds of +/// build hooks, building and linking. +abstract class HookConfig { + /// The directory in which all output and intermediate artifacts should be + /// placed. + Uri get outputDirectory; + + /// The name of the package the assets are built for. + String get packageName; + + /// The root of the package the assets are built for. + /// + /// Often a package's assets are built because a package is a dependency of + /// another. For this it is convenient to know the packageRoot. + Uri get packageRoot; + + /// The architecture being compiled for. + /// + /// Not specified (`null`) during a [dryRun]. + Architecture? get targetArchitecture; + + /// The operating system being compiled for. + OS get targetOS; + + /// When compiling for iOS, whether to target device or simulator. + /// + /// Not available if [targetOS] is [OS.iOS]. Will throw a [StateError] if + /// accessed during a [dryRun]. + /// + /// Not available during a [dryRun]. Will throw a [StateError] if accessed + /// during a [dryRun]. + IOSSdk? get targetIOSSdk; + + /// When compiling for Android, the minimum Android SDK API version to that + /// the compiled code will be compatible with. + /// + /// Required when [targetOS] equals [OS.android]. + /// + /// Not available during a [dryRun]. Will throw a [StateError] if accessed + /// during a [dryRun]. + /// + /// For more information about the Android API version, refer to + /// [`minSdkVersion`](https://developer.android.com/ndk/guides/sdk-versions#minsdkversion) + /// in the Android documentation. + int? get targetAndroidNdkApi; + + /// The configuration for invoking the C compiler. + /// + /// Not available during a [dryRun]. Will throw a [StateError] if accessed + /// during a [dryRun]. + CCompilerConfig get cCompiler; + + /// Whether this run is a dry-run, which doesn't build anything. + /// + /// A dry-run only reports information about which assets a build would + /// create, but doesn't actually create files. + bool get dryRun; + + /// The [BuildMode] that the code should be compiled in. + /// + /// Currently [BuildMode.debug] and [BuildMode.release] are the only modes. + /// + /// Not available during a [dryRun]. Will throw a [StateError] if accessed + /// during a [dryRun]. + BuildMode get buildMode; + + /// The asset types that the invoker of this hook supports. + /// + /// Currently known values: + /// * [NativeCodeAsset.type] + /// * [DataAsset.type] + Iterable get supportedAssetTypes; + + /// The preferred [LinkMode] method for [NativeCodeAsset]s. + LinkModePreference get linkModePreference; +} diff --git a/pkgs/native_assets_cli/lib/src/api/link.dart b/pkgs/native_assets_cli/lib/src/api/link.dart new file mode 100644 index 000000000..c86894a7b --- /dev/null +++ b/pkgs/native_assets_cli/lib/src/api/link.dart @@ -0,0 +1,45 @@ +// Copyright (c) 2024, 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 '../../native_assets_cli_internal.dart'; +import 'build_output.dart'; +import 'link_config.dart'; + +/// Runs a native assets link. +/// +/// Can link native assets which are not already available, or expose existing +/// files. Each individual asset is assigned a unique asset ID. +/// +/// The linking script may receive assets from build scripts, which are accessed +/// through [LinkConfig.assets]. They will only be bundled with the final +/// application if included in the [LinkOutput]. +/// +/// +/// ```dart +/// import 'package:native_assets_cli/native_assets_cli.dart'; +/// +/// void main(List args) async { +/// await link(args, (config, output) async { +/// final dataAssets = config.assets +/// .whereType(); +/// output.addAssets(dataAssets); +/// }); +/// } +/// ``` +Future link( + List arguments, + Future Function(LinkConfig config, LinkOutput output) linker, +) async { + final config = LinkConfig.fromArguments(arguments) as LinkConfigImpl; + + // The built assets are dependencies of linking, as the linking should be + // rerun if they change. + final builtAssetsFiles = + config.assets.map((asset) => asset.file).whereType().toList(); + final linkOutput = HookOutputImpl( + dependencies: Dependencies(builtAssetsFiles), + ); + await linker(config, linkOutput); + await linkOutput.writeToFile(config: config); +} diff --git a/pkgs/native_assets_cli/lib/src/api/link_config.dart b/pkgs/native_assets_cli/lib/src/api/link_config.dart new file mode 100644 index 000000000..39fabc519 --- /dev/null +++ b/pkgs/native_assets_cli/lib/src/api/link_config.dart @@ -0,0 +1,101 @@ +// Copyright (c) 2024, 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:convert'; +import 'dart:io'; + +import 'package:args/args.dart'; +import 'package:cli_config/cli_config.dart'; +import 'package:collection/collection.dart'; +import 'package:pub_semver/pub_semver.dart'; + +import '../model/hook.dart'; +import '../utils/map.dart'; +import 'architecture.dart'; +import 'asset.dart'; +import 'build_config.dart'; +import 'build_mode.dart'; +import 'hook_config.dart'; +import 'ios_sdk.dart'; +import 'link_mode_preference.dart'; +import 'os.dart'; + +part '../model/link_config.dart'; + +/// The configuration for a link hook (`hook/link.dart`) invocation. +/// +/// It consists of a subset of the fields from the [BuildConfig] already passed +/// to the build hook and the [assets] from the build step. +abstract class LinkConfig implements HookConfig { + /// The list of assets to be linked. These are the assets generated by a + /// `build.dart` script destined for this packages `link.dart`. + Iterable get assets; + + /// Generate the [LinkConfig] from the input arguments to the linking script. + factory LinkConfig.fromArguments(List arguments) => + LinkConfigImpl.fromArguments(arguments); + + factory LinkConfig.build({ + required Uri outputDirectory, + required String packageName, + required Uri packageRoot, + Architecture? targetArchitecture, + required OS targetOS, + IOSSdk? targetIOSSdk, + CCompilerConfig? cCompiler, + BuildMode? buildMode, + List? supportedAssetTypes, + int? targetAndroidNdkApi, + required Iterable assets, + required LinkModePreference linkModePreference, + bool? dryRun, + Version? version, + }) => + LinkConfigImpl( + assets: assets.cast(), + outputDirectory: outputDirectory, + packageName: packageName, + packageRoot: packageRoot, + buildMode: buildMode as BuildModeImpl, + cCompiler: cCompiler as CCompilerConfigImpl?, + targetAndroidNdkApi: targetAndroidNdkApi, + targetArchitecture: targetArchitecture as ArchitectureImpl?, + targetIOSSdk: targetIOSSdk as IOSSdkImpl?, + targetOS: targetOS as OSImpl, + dryRun: dryRun, + linkModePreference: linkModePreference as LinkModePreferenceImpl, + supportedAssetTypes: supportedAssetTypes, + version: version, + ); + + factory LinkConfig.dryRun({ + required Uri outputDirectory, + required String packageName, + required Uri packageRoot, + required OS targetOS, + List? supportedAssetTypes, + required Iterable assets, + required LinkModePreference linkModePreference, + Version? version, + }) => + LinkConfigImpl.dryRun( + assets: assets.cast(), + outputDirectory: outputDirectory, + packageName: packageName, + packageRoot: packageRoot, + targetOS: targetOS as OSImpl, + supportedAssetTypes: supportedAssetTypes, + linkModePreference: linkModePreference as LinkModePreferenceImpl, + version: version, + ); + + /// The version of [BuildConfig]. + /// + /// The build config is used in the protocol between the Dart and Flutter SDKs + /// and packages through build hook invocations. + /// + /// We're trying to avoid breaking changes. However, in the case that we have + /// to, the major version mismatch between the Dart or Flutter SDK and build + /// hook (`hook/build.dart`) will lead to a nice error message. + static Version get latestVersion => HookConfigImpl.latestVersion; +} diff --git a/pkgs/native_assets_cli/lib/src/api/link_output.dart b/pkgs/native_assets_cli/lib/src/api/link_output.dart new file mode 100644 index 000000000..6e7bd996d --- /dev/null +++ b/pkgs/native_assets_cli/lib/src/api/link_output.dart @@ -0,0 +1,61 @@ +// Copyright (c) 2024, 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. + +part of 'build_output.dart'; + +abstract final class LinkOutput { + /// Start time for the link of this output. + /// + /// The [timestamp] is rounded down to whole seconds, because + /// [File.lastModified] is rounded to whole seconds and caching logic compares + /// these timestamps. + DateTime get timestamp; + + /// The assets produced by this link. + /// + /// In dry runs, the assets for all [Architecture]s for the [OS] specified in + /// the dry run must be provided. + Iterable get assets; + + /// The files used by this link. + /// + /// If any of the files in [dependencies] are modified after [timestamp], the + /// link will be re-run. + Iterable get dependencies; + + /// Adds file used by this link. + /// + /// If any of the files are modified after [timestamp], the link will be + /// re-run. + void addDependency(Uri dependency); + + /// Adds files used by this link. + /// + /// If any of the files are modified after [timestamp], the link will be + /// re-run. + void addDependencies(Iterable dependencies); + + /// Adds [Asset]s produced by this link or dry run. + void addAsset(Asset asset); + + /// Adds [Asset]s produced by this link or dry run. + void addAssets(Iterable assets); + + factory LinkOutput({ + List? assets, + Dependencies? dependencies, + DateTime? timestamp, + }) => + HookOutputImpl( + assets: assets, + dependencies: dependencies, + timestamp: timestamp, + ); + + /// The version of [LinkOutput]. + /// + /// The link output is used in the protocol between the Dart and Flutter SDKs + /// and packages through build hook invocations. + static Version get latestVersion => HookOutputImpl.latestVersion; +} diff --git a/pkgs/native_assets_cli/lib/src/model/asset.dart b/pkgs/native_assets_cli/lib/src/model/asset.dart index eec072e5d..0c98f3b07 100644 --- a/pkgs/native_assets_cli/lib/src/model/asset.dart +++ b/pkgs/native_assets_cli/lib/src/model/asset.dart @@ -7,8 +7,9 @@ part of '../api/asset.dart'; abstract final class AssetImpl implements Asset { Map toJson(Version version); - static List listFromJsonList(List list) { + static List listFromJson(List? list) { final assets = []; + if (list == null) return assets; for (final jsonElement in list) { final jsonMap = as>(jsonElement); final type = jsonMap[NativeCodeAssetImpl.typeKey]; @@ -24,4 +25,10 @@ abstract final class AssetImpl implements Asset { } return assets; } + + static List> listToJson( + Iterable assets, Version version) => + [ + for (final asset in assets) asset.toJson(version), + ]; } diff --git a/pkgs/native_assets_cli/lib/src/model/build_config.dart b/pkgs/native_assets_cli/lib/src/model/build_config.dart index 53a6bc170..bc052240c 100644 --- a/pkgs/native_assets_cli/lib/src/model/build_config.dart +++ b/pkgs/native_assets_cli/lib/src/model/build_config.dart @@ -4,243 +4,78 @@ part of '../api/build_config.dart'; -final class BuildConfigImpl implements BuildConfig { +final class BuildConfigImpl extends HookConfigImpl implements BuildConfig { @override - Uri get outputDirectory => _outDir; - late final Uri _outDir; + Hook get hook => Hook.build; @override - String get packageName => _packageName; - late final String _packageName; - - @override - Uri get packageRoot => _packageRoot; - late final Uri _packageRoot; - - @override - ArchitectureImpl? get targetArchitecture => _targetArchitecture; - late final ArchitectureImpl? _targetArchitecture; - - @override - OSImpl get targetOS => _targetOS; - late final OSImpl _targetOS; - - @override - IOSSdkImpl get targetIOSSdk { - _ensureNotDryRun(); - if (_targetOS != OS.iOS) { - throw StateError( - 'This field is not available in if targetOS is not OS.iOS.', - ); + //TODO: Should be removed once migration to `hook/` is complete. + Uri get script { + final hookScript = packageRoot.resolve('hook/').resolve(hook.scriptName); + if (File.fromUri(hookScript).existsSync()) { + return hookScript; + } else { + return packageRoot.resolve(hook.scriptName); } - return _targetIOSSdk!; - } - - late final IOSSdkImpl? _targetIOSSdk; - - @override - int? get targetAndroidNdkApi { - _ensureNotDryRun(); - return _targetAndroidNdkApi; } - late final int? _targetAndroidNdkApi; - @override - LinkModePreferenceImpl get linkModePreference => _linkModePreference; - late final LinkModePreferenceImpl _linkModePreference; + String get outputName => + version > Version(1, 1, 0) ? 'build_output.json' : 'build_output.yaml'; @override Object? metadatum(String packageName, String key) { - _ensureNotDryRun(); + HookConfigImpl.ensureNotDryRun(dryRun); return _dependencyMetadata?[packageName]?.metadata[key]; } - late final Map? _dependencyMetadata; - - @override - CCompilerConfigImpl get cCompiler { - _ensureNotDryRun(); - return _cCompiler; - } - - late final CCompilerConfigImpl _cCompiler; - - @override - bool get dryRun => _dryRun ?? false; - late final bool? _dryRun; - - @override - BuildModeImpl get buildMode { - _ensureNotDryRun(); - return _buildMode; - } - - late final BuildModeImpl _buildMode; - - @override - Iterable get supportedAssetTypes => _supportedAssetTypes; - - late final List _supportedAssetTypes; + final Map? _dependencyMetadata; - Version get version => _version; - - late final Version _version; - - Config get config => _config; - late final Config _config; - - factory BuildConfigImpl({ - required Uri outDir, - required String packageName, - required Uri packageRoot, - required BuildModeImpl buildMode, - required ArchitectureImpl targetArchitecture, - required OSImpl targetOS, - IOSSdkImpl? targetIOSSdk, - int? targetAndroidNdkApi, - CCompilerConfigImpl? cCompiler, - required LinkModePreferenceImpl linkModePreference, - Map? dependencyMetadata, + static List _supportedAssetTypesBackwardsCompatibility( Iterable? supportedAssetTypes, - Version? version, - }) { - final nonValidated = BuildConfigImpl._() - .._version = version ?? latestVersion - .._outDir = outDir - .._packageName = packageName - .._packageRoot = packageRoot - .._buildMode = buildMode - .._targetArchitecture = targetArchitecture - .._targetOS = targetOS - .._targetIOSSdk = targetIOSSdk - .._targetAndroidNdkApi = targetAndroidNdkApi - .._cCompiler = cCompiler ?? CCompilerConfigImpl() - .._linkModePreference = linkModePreference - .._dependencyMetadata = dependencyMetadata - .._dryRun = false - .._supportedAssetTypes = - _supportedAssetTypesBackwardsCompatibility(supportedAssetTypes); - final parsedConfigFile = nonValidated.toJson(); - final config = Config(fileParsed: parsedConfigFile); - return BuildConfigImpl.fromConfig(config); - } + ) => + supportedAssetTypes?.toList() ?? [NativeCodeAsset.type]; - factory BuildConfigImpl.dryRun({ - required Uri outDir, - required String packageName, - required Uri packageRoot, - required OSImpl targetOS, - required LinkModePreferenceImpl linkModePreference, + BuildConfigImpl({ + required super.outputDirectory, + required super.packageName, + required super.packageRoot, + Version? version, + super.buildMode, + super.cCompiler, Iterable? supportedAssetTypes, - }) { - final nonValidated = BuildConfigImpl._() - .._version = latestVersion - .._outDir = outDir - .._packageName = packageName - .._packageRoot = packageRoot - .._targetOS = targetOS - .._targetArchitecture = null - .._linkModePreference = linkModePreference - .._cCompiler = CCompilerConfigImpl() - .._dryRun = true - .._supportedAssetTypes = - _supportedAssetTypesBackwardsCompatibility(supportedAssetTypes); - final parsedConfigFile = nonValidated.toJson(); - final config = Config(fileParsed: parsedConfigFile); - return BuildConfigImpl.fromConfig(config); - } - - /// Constructs a checksum for a [BuildConfigImpl] based on the fields of a - /// buildconfig that influence the build. - /// - /// This can be used for an [outputDirectory], but should not be used for - /// dry-runs. - /// - /// In particular, it only takes the package name from [packageRoot], so that - /// the hash is equal across checkouts and ignores [outputDirectory] itself. - static String checksum({ - required String packageName, - required Uri packageRoot, - required ArchitectureImpl targetArchitecture, - required OSImpl targetOS, - required BuildModeImpl buildMode, - IOSSdkImpl? targetIOSSdk, - int? targetAndroidNdkApi, - CCompilerConfigImpl? cCompiler, - required LinkModePreferenceImpl linkModePreference, + super.targetAndroidNdkApi, + required super.targetArchitecture, + super.targetIOSSdk, + required super.targetOS, + required super.linkModePreference, Map? dependencyMetadata, - Iterable? supportedAssetTypes, - Version? version, - }) { - final input = [ - version ?? latestVersion, - packageName, - targetArchitecture.toString(), - targetOS.toString(), - targetIOSSdk.toString(), - targetAndroidNdkApi.toString(), - buildMode.toString(), - linkModePreference.toString(), - cCompiler?.archiver.toString(), - cCompiler?.compiler.toString(), - cCompiler?.envScript.toString(), - cCompiler?.envScriptArgs.toString(), - cCompiler?.linker.toString(), - if (dependencyMetadata != null) - for (final entry in dependencyMetadata.entries) ...[ - entry.key, - json.encode(entry.value.toJson()), - ], - ..._supportedAssetTypesBackwardsCompatibility(supportedAssetTypes), - ].join('###'); - final sha256String = sha256.convert(utf8.encode(input)).toString(); - // 256 bit hashes lead to 64 hex character strings. - // To avoid overflowing file paths limits, only use 32. - // Using 16 hex characters would also be unlikely to have collisions. - const nameLength = 32; - return sha256String.substring(0, nameLength); - } + super.dryRun, + }) : _dependencyMetadata = dependencyMetadata, + super( + hook: Hook.build, + version: version ?? HookConfigImpl.latestVersion, + supportedAssetTypes: + _supportedAssetTypesBackwardsCompatibility(supportedAssetTypes), + ); - static List _supportedAssetTypesBackwardsCompatibility( + BuildConfigImpl.dryRun({ + required super.outputDirectory, + required super.packageName, + required super.packageRoot, + required super.targetOS, + required super.linkModePreference, Iterable? supportedAssetTypes, - ) => - [ - ...?supportedAssetTypes, - if (supportedAssetTypes == null) NativeCodeAsset.type, - ]; - - BuildConfigImpl._(); - - /// The version of [BuildConfigImpl]. - /// - /// This class is used in the protocol between the Dart and Flutter SDKs - /// and packages through build hook invocations. - /// - /// If we ever were to make breaking changes, it would be useful to give - /// proper error messages rather than just fail to parse the JSON - /// representation in the protocol. - static Version latestVersion = Version(1, 2, 0); - - factory BuildConfigImpl.fromConfig(Config config) { - final result = BuildConfigImpl._().._cCompiler = CCompilerConfigImpl._(); - final configExceptions = []; - for (final f in result._readFieldsFromConfig()) { - try { - f(config); - } on FormatException catch (e, st) { - configExceptions.add(e); - configExceptions.add(st); - } - } - - if (configExceptions.isNotEmpty) { - throw FormatException('Configuration is not in the right format. ' - 'FormatExceptions: $configExceptions'); - } + }) : _dependencyMetadata = null, + super.dryRun( + hook: Hook.build, + version: HookConfigImpl.latestVersion, + supportedAssetTypes: + _supportedAssetTypesBackwardsCompatibility(supportedAssetTypes), + ); - return result; - } + factory BuildConfigImpl._fromConfig(Config config) => + _readFieldsFromConfig(config); static BuildConfigImpl fromArguments( List args, { @@ -255,306 +90,103 @@ final class BuildConfigImpl implements BuildConfig { environment: environment, workingDirectory: workingDirectory, ); - return BuildConfigImpl.fromConfig(config); + return BuildConfigImpl._fromConfig(config); } - static const outDirConfigKey = 'out_dir'; - static const packageNameConfigKey = 'package_name'; - static const packageRootConfigKey = 'package_root'; static const dependencyMetadataConfigKey = 'dependency_metadata'; - static const _versionKey = 'version'; - static const targetAndroidNdkApiConfigKey = 'target_android_ndk_api'; - static const dryRunConfigKey = 'dry_run'; - static const supportedAssetTypesKey = 'supported_asset_types'; - List _readFieldsFromConfig() { - var osSet = false; - var ccSet = false; - return [ - (config) { - final version = Version.parse(config.string('version')); - if (version.major > latestVersion.major) { - throw FormatException( - 'The config version $version is newer than this ' - 'package:native_assets_cli config version $latestVersion, ' - 'please update native_assets_cli.', - ); - } - if (version.major < latestVersion.major) { - throw FormatException( - 'The config version $version is newer than this ' - 'package:native_assets_cli config version $latestVersion, ' - 'please update the Dart or Flutter SDK.', - ); - } - _version = version; - }, - (config) => _config = config, - (config) => _dryRun = config.optionalBool(dryRunConfigKey), - (config) => _outDir = config.path(outDirConfigKey, mustExist: true), - (config) => _packageName = config.string(packageNameConfigKey), - (config) => - _packageRoot = config.path(packageRootConfigKey, mustExist: true), - (config) { - if (dryRun) { - _throwIfNotNullInDryRun(BuildModeImpl.configKey); - } else { - _buildMode = BuildModeImpl.fromString( - config.string( - BuildModeImpl.configKey, - validValues: BuildModeImpl.values.map((e) => '$e'), - ), - ); - } - }, - (config) { - _targetOS = OSImpl.fromString( - config.string( - OSImpl.configKey, - validValues: OSImpl.values.map((e) => '$e'), - ), - ); - osSet = true; - }, - (config) { - if (dryRun) { - _throwIfNotNullInDryRun(ArchitectureImpl.configKey); - _targetArchitecture = null; - } else { - final validArchitectures = [ - if (!osSet) - ...ArchitectureImpl.values - else - for (final target in Target.values) - if (target.os == _targetOS) target.architecture - ]; - _targetArchitecture = ArchitectureImpl.fromString( - config.string( - ArchitectureImpl.configKey, - validValues: validArchitectures.map((e) => '$e'), - ), - ); - } - }, - (config) { - if (dryRun) { - _throwIfNotNullInDryRun(IOSSdkImpl.configKey); - } else { - _targetIOSSdk = (osSet && _targetOS == OSImpl.iOS) - ? IOSSdkImpl.fromString( - config.string( - IOSSdkImpl.configKey, - validValues: IOSSdkImpl.values.map((e) => '$e'), - ), - ) - : null; - } - }, - (config) { - if (dryRun) { - _throwIfNotNullInDryRun(targetAndroidNdkApiConfigKey); - } else { - _targetAndroidNdkApi = (osSet && _targetOS == OSImpl.android) - ? config.int(targetAndroidNdkApiConfigKey) - : null; - } - }, - (config) { - if (dryRun) { - _throwIfNotNullInDryRun(CCompilerConfigImpl.arConfigKeyFull); - } else { - cCompiler._archiver = config.optionalPath( - CCompilerConfigImpl.arConfigKeyFull, - mustExist: true, - ); - } - }, - (config) { - if (dryRun) { - _throwIfNotNullInDryRun(CCompilerConfigImpl.ccConfigKeyFull); - } else { - cCompiler._compiler = config.optionalPath( - CCompilerConfigImpl.ccConfigKeyFull, - mustExist: true, - ); - ccSet = true; - } - }, - (config) { - if (dryRun) { - _throwIfNotNullInDryRun(CCompilerConfigImpl.ccConfigKeyFull); - } else { - cCompiler._linker = config.optionalPath( - CCompilerConfigImpl.ldConfigKeyFull, - mustExist: true, - ); - } - }, - (config) { - if (dryRun) { - _throwIfNotNullInDryRun(CCompilerConfigImpl.ccConfigKeyFull); - } else { - cCompiler._envScript = (ccSet && - cCompiler.compiler != null && - cCompiler.compiler!.toFilePath().endsWith('cl.exe')) - ? config.path(CCompilerConfigImpl.envScriptConfigKeyFull, - mustExist: true) - : null; - } - }, - (config) { - if (dryRun) { - _throwIfNotNullInDryRun(CCompilerConfigImpl.ccConfigKeyFull); - } else { - cCompiler._envScriptArgs = config.optionalStringList( - CCompilerConfigImpl.envScriptArgsConfigKeyFull, - splitEnvironmentPattern: ' ', - ); - } - }, - (config) { - _linkModePreference = LinkModePreferenceImpl.fromString( - config.string( - LinkModePreferenceImpl.configKey, - validValues: LinkModePreferenceImpl.values.map((e) => '$e'), - ), - ); - }, - (config) { - _dependencyMetadata = _readDependencyMetadataFromConfig(config); - }, - (config) => _supportedAssetTypes = - config.optionalStringList(supportedAssetTypesKey) ?? - [NativeCodeAsset.type], - ]; + static BuildConfigImpl _readFieldsFromConfig(Config config) { + final dryRun = HookConfigImpl.parseDryRun(config) ?? false; + final targetOS = HookConfigImpl.parseTargetOS(config); + return BuildConfigImpl( + outputDirectory: HookConfigImpl.parseOutDir(config), + packageName: HookConfigImpl.parsePackageName(config), + packageRoot: HookConfigImpl.parsePackageRoot(config), + buildMode: HookConfigImpl.parseBuildMode(config, dryRun), + targetOS: targetOS, + targetArchitecture: + HookConfigImpl.parseTargetArchitecture(config, dryRun, targetOS), + linkModePreference: HookConfigImpl.parseLinkModePreference(config), + dependencyMetadata: parseDependencyMetadata(config), + version: HookConfigImpl.parseVersion(config), + cCompiler: HookConfigImpl.parseCCompiler(config, dryRun), + supportedAssetTypes: HookConfigImpl.parseSupportedAssetTypes(config), + targetAndroidNdkApi: + HookConfigImpl.parseTargetAndroidNdkApi(config, dryRun, targetOS), + targetIOSSdk: HookConfigImpl.parseTargetIOSSdk(config, dryRun, targetOS), + dryRun: dryRun, + ); } - Map? _readDependencyMetadataFromConfig(Config config) { + static Map? parseDependencyMetadata(Config config) => + _readDependencyMetadataFromConfig(config); + + static Map? _readDependencyMetadataFromConfig( + Config config) { final fileValue = config.valueOf?>(dependencyMetadataConfigKey); if (fileValue == null) { return null; } - final result = {}; - for (final entry in fileValue.entries) { - final packageName = as(entry.key); - final defines = entry.value; - if (defines is! Map) { - throw FormatException("Unexpected value '$defines' for key " - "'$dependencyMetadataConfigKey.$packageName' in config file. " - 'Expected a Map.'); - } - final packageResult = {}; - for (final entry2 in defines.entries) { - final key = as(entry2.key); - final value = as(entry2.value); - packageResult[key] = value; - } - result[packageName] = Metadata(packageResult.sortOnKey()); - } - return result.sortOnKey(); - } - - Map toJson() { - late Map cCompilerJson; - if (!dryRun) { - cCompilerJson = _cCompiler.toJson(); - } - - return { - outDirConfigKey: _outDir.toFilePath(), - packageNameConfigKey: _packageName, - packageRootConfigKey: _packageRoot.toFilePath(), - OSImpl.configKey: _targetOS.toString(), - LinkModePreferenceImpl.configKey: _linkModePreference.toString(), - supportedAssetTypesKey: _supportedAssetTypes, - _versionKey: version.toString(), - if (dryRun) dryRunConfigKey: dryRun, - if (!dryRun) ...{ - BuildModeImpl.configKey: _buildMode.toString(), - ArchitectureImpl.configKey: _targetArchitecture.toString(), - if (_targetIOSSdk != null) - IOSSdkImpl.configKey: _targetIOSSdk.toString(), - if (_targetAndroidNdkApi != null) - targetAndroidNdkApiConfigKey: _targetAndroidNdkApi, - if (cCompilerJson.isNotEmpty) - CCompilerConfigImpl.configKey: cCompilerJson, - if (_dependencyMetadata != null && _dependencyMetadata.isNotEmpty) - dependencyMetadataConfigKey: { - for (final entry in _dependencyMetadata.entries) - entry.key: entry.value.toJson(), - }, + return fileValue + .map((key, defines) => MapEntry(as(key), defines)) + .map( + (packageName, defines) { + if (defines is! Map) { + throw FormatException("Unexpected value '$defines' for key " + "'$dependencyMetadataConfigKey.$packageName' in config file. " + 'Expected a Map.'); + } + return MapEntry( + packageName, + Metadata(defines + .map((key, value) => MapEntry(as(key), as(value))) + .sortOnKey()), + ); }, - }.sortOnKey(); + ).sortOnKey(); } - String toJsonString() => const JsonEncoder.withIndent(' ').convert(toJson()); + static BuildConfigImpl fromJson(Map buildConfigJson) => + BuildConfigImpl._fromConfig(Config(fileParsed: buildConfigJson)); + + @override + Map toJson() => { + ...hookToJson(), + if (!dryRun) ...{ + if (_dependencyMetadata != null && _dependencyMetadata.isNotEmpty) + dependencyMetadataConfigKey: _dependencyMetadata.map( + (packageName, metadata) => + MapEntry(packageName, metadata.toJson()), + ), + }, + }.sortOnKey(); @override bool operator ==(Object other) { + if (super != other) { + return false; + } if (other is! BuildConfigImpl) { return false; } - if (other.outputDirectory != outputDirectory) return false; - if (other.packageName != packageName) return false; - if (other.packageRoot != packageRoot) return false; - if (other.dryRun != dryRun) return false; - if (other.targetOS != targetOS) return false; - if (other.linkModePreference != linkModePreference) return false; - if (!const DeepCollectionEquality() - .equals(other._supportedAssetTypes, _supportedAssetTypes)) return false; - if (!dryRun) { - if (other.buildMode != buildMode) return false; - if (other.targetArchitecture != targetArchitecture) return false; - if (targetOS == OS.iOS && other.targetIOSSdk != targetIOSSdk) { - return false; - } - if (other.targetAndroidNdkApi != targetAndroidNdkApi) return false; - if (other.cCompiler != cCompiler) return false; - if (!const DeepCollectionEquality() - .equals(other._dependencyMetadata, _dependencyMetadata)) return false; + if (!dryRun && + !const DeepCollectionEquality() + .equals(other._dependencyMetadata, _dependencyMetadata)) { + return false; } return true; } @override int get hashCode => Object.hashAll([ - outputDirectory, - packageName, - packageRoot, - targetOS, + super.hashCode, linkModePreference, - dryRun, - const DeepCollectionEquality().hash(_supportedAssetTypes), if (!dryRun) ...[ - buildMode, const DeepCollectionEquality().hash(_dependencyMetadata), - targetArchitecture, - if (targetOS == OS.iOS) targetIOSSdk, - targetAndroidNdkApi, - cCompiler, ], ]); @override - String toString() => 'BuildConfig.build(${toJson()})'; - - void _ensureNotDryRun() { - if (dryRun) { - throw StateError('''This field is not available in dry runs. -In Flutter projects, native builds are generated per OS which target multiple -architectures, build modes, etc. Therefore, the list of native assets produced -can _only_ depend on OS.'''); - } - } - - void _throwIfNotNullInDryRun(String key) { - final object = config.valueOf(key); - if (object != null) { - throw const FormatException('''This field is not available in dry runs. -In Flutter projects, native builds are generated per OS which target multiple -architectures, build modes, etc. Therefore, the list of native assets produced -can _only_ depend on OS.'''); - } - } + String toString() => 'BuildConfig(${toJson()})'; } diff --git a/pkgs/native_assets_cli/lib/src/model/build_config_CHANGELOG.md b/pkgs/native_assets_cli/lib/src/model/build_config_CHANGELOG.md index 3f11f16d7..6d4c5b415 100644 --- a/pkgs/native_assets_cli/lib/src/model/build_config_CHANGELOG.md +++ b/pkgs/native_assets_cli/lib/src/model/build_config_CHANGELOG.md @@ -1,3 +1,9 @@ +## 1.3.0 + +- Rev version to know whether the Dart/Flutter SDK can consume + `BuildOutput.assetsForLinking`. In earlier versions the key will not be read + and the list of assets to be linked will be empty. + ## 1.2.0 - Changed default encoding to JSON. diff --git a/pkgs/native_assets_cli/lib/src/model/build_output_CHANGELOG.md b/pkgs/native_assets_cli/lib/src/model/build_output_CHANGELOG.md index 17cbf88c8..9e58c5c8d 100644 --- a/pkgs/native_assets_cli/lib/src/model/build_output_CHANGELOG.md +++ b/pkgs/native_assets_cli/lib/src/model/build_output_CHANGELOG.md @@ -1,3 +1,9 @@ +## 1.3.0 + +- Add key for assets to be linked. + Backwards compatibility older build hooks: The key can be omitted. + Backwards compatibility older SDKs: These are ignored on older SDKs. + ## 1.2.0 - Changed default encoding to JSON. diff --git a/pkgs/native_assets_cli/lib/src/model/data_asset.dart b/pkgs/native_assets_cli/lib/src/model/data_asset.dart index 3de286b8f..9a05e7c97 100644 --- a/pkgs/native_assets_cli/lib/src/model/data_asset.dart +++ b/pkgs/native_assets_cli/lib/src/model/data_asset.dart @@ -9,17 +9,25 @@ final class DataAssetImpl implements DataAsset, AssetImpl { final Uri file; @override - final String id; + final String name; + + @override + final String package; + + @override + String get id => 'package:$package/$name'; DataAssetImpl({ required this.file, - required this.id, + required this.name, + required this.package, }); factory DataAssetImpl.fromJson(Map jsonMap) => DataAssetImpl( - id: as(jsonMap[_idKey]), - file: Uri(path: as(jsonMap[_fileKey])), + name: get(jsonMap, _nameKey), + package: get(jsonMap, _packageKey), + file: Uri(path: get(jsonMap, _fileKey)), ); @override @@ -27,24 +35,27 @@ final class DataAssetImpl implements DataAsset, AssetImpl { if (other is! DataAssetImpl) { return false; } - return other.id == id && other.file == file; + return other.package == package && other.file == file && other.name == name; } @override int get hashCode => Object.hash( - id, + package, + name, file, ); @override Map toJson(Version version) => { - _idKey: id, + _nameKey: name, + _packageKey: package, _fileKey: file.toFilePath(), typeKey: DataAsset.type, }..sortOnKey(); static const typeKey = 'type'; - static const _idKey = 'id'; + static const _nameKey = 'name'; + static const _packageKey = 'package'; static const _fileKey = 'file'; @override diff --git a/pkgs/native_assets_cli/lib/src/model/dependencies.dart b/pkgs/native_assets_cli/lib/src/model/dependencies.dart index 42dec07a4..51d29c0fb 100644 --- a/pkgs/native_assets_cli/lib/src/model/dependencies.dart +++ b/pkgs/native_assets_cli/lib/src/model/dependencies.dart @@ -22,7 +22,9 @@ class Dependencies { fileSystemPathToUri(as(dependency)), ]); - List toJson() => [ + List toJson() => toJsonList(dependencies); + + static List toJsonList(List dependencies) => [ for (final dependency in dependencies) dependency.toFilePath(), ]; diff --git a/pkgs/native_assets_cli/lib/src/model/hook.dart b/pkgs/native_assets_cli/lib/src/model/hook.dart new file mode 100644 index 000000000..28859e8b3 --- /dev/null +++ b/pkgs/native_assets_cli/lib/src/model/hook.dart @@ -0,0 +1,20 @@ +// Copyright (c) 2024, 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. + +/// The two types of scripts which are hooked into the compilation process. +/// +/// The `build.dart` hook runs before, and the `link.dart` hook after +/// compilation. This enum holds static information about these hooks. +enum Hook { + link('link', 'link_config'), + build('build', 'config'); + + final String _scriptName; + final String _configName; + + String get scriptName => '$_scriptName.dart'; + String get configName => '$_configName.json'; + + const Hook(this._scriptName, this._configName); +} diff --git a/pkgs/native_assets_cli/lib/src/model/hook_config.dart b/pkgs/native_assets_cli/lib/src/model/hook_config.dart new file mode 100644 index 000000000..84493b676 --- /dev/null +++ b/pkgs/native_assets_cli/lib/src/model/hook_config.dart @@ -0,0 +1,477 @@ +// Copyright (c) 2024, 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. + +part of '../api/hook_config.dart'; + +abstract class HookConfigImpl implements HookConfig { + final Hook hook; + + /// The folder in which all output and intermediate artifacts should be + /// placed. + @override + final Uri outputDirectory; + + @override + final String packageName; + + @override + final Uri packageRoot; + + final Version version; + + final BuildModeImpl? _buildMode; + + @override + BuildModeImpl get buildMode { + ensureNotDryRun(dryRun); + return _buildMode!; + } + + final CCompilerConfigImpl _cCompiler; + + @override + CCompilerConfigImpl get cCompiler { + ensureNotDryRun(dryRun); + return _cCompiler; + } + + @override + final bool dryRun; + + @override + final Iterable supportedAssetTypes; + + final int? _targetAndroidNdkApi; + + @override + final LinkModePreferenceImpl linkModePreference; + + @override + int? get targetAndroidNdkApi { + ensureNotDryRun(dryRun); + return _targetAndroidNdkApi; + } + + @override + final ArchitectureImpl? targetArchitecture; + + final IOSSdkImpl? _targetIOSSdk; + + @override + IOSSdkImpl? get targetIOSSdk { + ensureNotDryRun(dryRun); + if (targetOS != OS.iOS) { + throw StateError( + 'This field is not available in if targetOS is not OS.iOS.', + ); + } + return _targetIOSSdk; + } + + @override + final OSImpl targetOS; + + String get outputName; + + HookConfigImpl({ + required this.hook, + required this.outputDirectory, + required this.packageName, + required this.packageRoot, + required this.version, + required BuildModeImpl? buildMode, + required CCompilerConfigImpl? cCompiler, + required this.supportedAssetTypes, + required int? targetAndroidNdkApi, + required this.targetArchitecture, + required IOSSdkImpl? targetIOSSdk, + required this.linkModePreference, + required this.targetOS, + bool? dryRun, + }) : _targetAndroidNdkApi = targetAndroidNdkApi, + _targetIOSSdk = targetIOSSdk, + _buildMode = buildMode, + _cCompiler = cCompiler ?? CCompilerConfigImpl(), + dryRun = dryRun ?? false; + + HookConfigImpl.dryRun({ + required this.hook, + required this.outputDirectory, + required this.packageName, + required this.packageRoot, + required this.version, + required this.supportedAssetTypes, + required this.linkModePreference, + required this.targetOS, + }) : _cCompiler = CCompilerConfigImpl(), + dryRun = true, + targetArchitecture = null, + _buildMode = null, + _targetAndroidNdkApi = null, + _targetIOSSdk = null; + + Uri get configFile => outputDirectory.resolve('../${hook.configName}'); + + Uri get outputFile => outputDirectory.resolve(outputName); + + // This is currently overriden by [BuildConfig], do account for older versions + // still using a top-level build.dart. + Uri get script => packageRoot.resolve('hook/').resolve(hook.scriptName); + + String toJsonString() => const JsonEncoder.withIndent(' ').convert(toJson()); + + static const outDirConfigKey = 'out_dir'; + static const packageNameConfigKey = 'package_name'; + static const packageRootConfigKey = 'package_root'; + static const _versionKey = 'version'; + static const targetAndroidNdkApiConfigKey = 'target_android_ndk_api'; + static const dryRunConfigKey = 'dry_run'; + static const supportedAssetTypesKey = 'supported_asset_types'; + + Map toJson(); + + Map hookToJson() { + late Map cCompilerJson; + if (!dryRun) { + cCompilerJson = cCompiler.toJson(); + } + + return { + outDirConfigKey: outputDirectory.toFilePath(), + packageNameConfigKey: packageName, + packageRootConfigKey: packageRoot.toFilePath(), + OSImpl.configKey: targetOS.toString(), + if (supportedAssetTypes.isNotEmpty) + supportedAssetTypesKey: supportedAssetTypes, + _versionKey: version.toString(), + if (dryRun) dryRunConfigKey: dryRun, + if (!dryRun) ...{ + BuildModeImpl.configKey: buildMode.toString(), + ArchitectureImpl.configKey: targetArchitecture.toString(), + if (targetOS == OS.iOS && targetIOSSdk != null) + IOSSdkImpl.configKey: targetIOSSdk.toString(), + if (targetAndroidNdkApi != null) + targetAndroidNdkApiConfigKey: targetAndroidNdkApi!, + if (cCompilerJson.isNotEmpty) + CCompilerConfigImpl.configKey: cCompilerJson, + }, + LinkModePreferenceImpl.configKey: linkModePreference.toString(), + }.sortOnKey(); + } + + static Version parseVersion(Config config) { + final version = Version.parse(config.string('version')); + if (version.major > latestVersion.major) { + throw FormatException( + 'The config version $version is newer than this ' + 'package:native_assets_cli config version $latestVersion, ' + 'please update native_assets_cli.', + ); + } + if (version.major < latestVersion.major) { + throw FormatException( + 'The config version $version is newer than this ' + 'package:native_assets_cli config version $latestVersion, ' + 'please update the Dart or Flutter SDK.', + ); + } + return version; + } + + static bool? parseDryRun(Config config) => + config.optionalBool(dryRunConfigKey); + + static Uri parseOutDir(Config config) => + config.path(outDirConfigKey, mustExist: true); + + static String parsePackageName(Config config) => + config.string(packageNameConfigKey); + + static Uri parsePackageRoot(Config config) => + config.path(packageRootConfigKey, mustExist: true); + + static BuildModeImpl? parseBuildMode(Config config, bool dryRun) { + if (dryRun) { + _throwIfNotNullInDryRun(config, BuildModeImpl.configKey); + return null; + } else { + return BuildModeImpl.fromString( + config.string( + BuildModeImpl.configKey, + validValues: BuildModeImpl.values.map((e) => '$e'), + ), + ); + } + } + + static LinkModePreferenceImpl parseLinkModePreference(Config config) => + LinkModePreferenceImpl.fromString( + config.string( + LinkModePreferenceImpl.configKey, + validValues: LinkModePreferenceImpl.values.map((e) => e.toString()), + ), + ); + + static OSImpl parseTargetOS(Config config) => OSImpl.fromString( + config.string( + OSImpl.configKey, + validValues: OSImpl.values.map((e) => '$e'), + ), + ); + static ArchitectureImpl? parseTargetArchitecture( + Config config, + bool dryRun, + OSImpl? targetOS, + ) { + if (dryRun) { + _throwIfNotNullInDryRun(config, ArchitectureImpl.configKey); + return null; + } else { + final validArchitectures = [ + if (targetOS == null) + ...ArchitectureImpl.values + else + for (final target in Target.values) + if (target.os == targetOS) target.architecture + ]; + return ArchitectureImpl.fromString( + config.string( + ArchitectureImpl.configKey, + validValues: validArchitectures.map((e) => '$e'), + ), + ); + } + } + + static IOSSdkImpl? parseTargetIOSSdk( + Config config, bool dryRun, OSImpl? targetOS) { + if (dryRun) { + _throwIfNotNullInDryRun(config, IOSSdkImpl.configKey); + return null; + } else { + return targetOS == OSImpl.iOS + ? IOSSdkImpl.fromString( + config.string( + IOSSdkImpl.configKey, + validValues: IOSSdkImpl.values.map((e) => '$e'), + ), + ) + : null; + } + } + + static int? parseTargetAndroidNdkApi( + Config config, + bool dryRun, + OSImpl? targetOS, + ) { + if (dryRun) { + _throwIfNotNullInDryRun(config, targetAndroidNdkApiConfigKey); + return null; + } else { + return (targetOS == OSImpl.android) + ? config.int(targetAndroidNdkApiConfigKey) + : null; + } + } + + static Uri? _parseArchiver(Config config, bool dryRun) { + if (dryRun) { + _throwIfNotNullInDryRun(config, CCompilerConfigImpl.arConfigKeyFull); + return null; + } else { + return config.optionalPath( + CCompilerConfigImpl.arConfigKeyFull, + mustExist: true, + ); + } + } + + static Uri? _parseCompiler(Config config, bool dryRun) { + if (dryRun) { + _throwIfNotNullInDryRun(config, CCompilerConfigImpl.ccConfigKeyFull); + return null; + } else { + return config.optionalPath( + CCompilerConfigImpl.ccConfigKeyFull, + mustExist: true, + ); + } + } + + static Uri? _parseLinker(Config config, bool dryRun) { + if (dryRun) { + _throwIfNotNullInDryRun(config, CCompilerConfigImpl.ccConfigKeyFull); + return null; + } else { + return config.optionalPath( + CCompilerConfigImpl.ldConfigKeyFull, + mustExist: true, + ); + } + } + + static Uri? _parseEnvScript(Config config, bool dryRun, Uri? compiler) { + if (dryRun) { + _throwIfNotNullInDryRun(config, CCompilerConfigImpl.ccConfigKeyFull); + return null; + } else { + return (compiler != null && compiler.toFilePath().endsWith('cl.exe')) + ? config.path(CCompilerConfigImpl.envScriptConfigKeyFull, + mustExist: true) + : null; + } + } + + static List? _parseEnvScriptArgs(Config config, bool dryRun) { + if (dryRun) { + _throwIfNotNullInDryRun(config, CCompilerConfigImpl.ccConfigKeyFull); + return null; + } else { + return config.optionalStringList( + CCompilerConfigImpl.envScriptArgsConfigKeyFull, + splitEnvironmentPattern: ' ', + ); + } + } + + static List parseSupportedAssetTypes(Config config) => + config.optionalStringList(supportedAssetTypesKey) ?? + [NativeCodeAsset.type]; + + static CCompilerConfigImpl parseCCompiler(Config config, bool dryRun) { + final parseCompiler = _parseCompiler(config, dryRun); + final cCompiler = CCompilerConfigImpl( + archiver: _parseArchiver(config, dryRun), + compiler: parseCompiler, + envScript: _parseEnvScript(config, dryRun, parseCompiler), + envScriptArgs: _parseEnvScriptArgs(config, dryRun), + linker: _parseLinker(config, dryRun), + ); + return cCompiler; + } + + static void _throwIfNotNullInDryRun(Config config, String key) { + final object = config.valueOf(key); + if (object != null) { + throw const FormatException('''This field is not available in dry runs. +In Flutter projects, native builds are generated per OS which target multiple +architectures, build modes, etc. Therefore, the list of native assets produced +can _only_ depend on OS.'''); + } + } + + static void ensureNotDryRun(bool dryRun) { + if (dryRun) { + throw StateError('''This field is not available in dry runs. +In Flutter projects, native builds are generated per OS which target multiple +architectures, build modes, etc. Therefore, the list of native assets produced +can _only_ depend on OS.'''); + } + } + + @override + bool operator ==(Object other) { + if (other is! HookConfigImpl) { + return false; + } + if (other.outputDirectory != outputDirectory) return false; + if (other.packageName != packageName) return false; + if (other.packageRoot != packageRoot) return false; + if (other.dryRun != dryRun) return false; + if (other.targetOS != targetOS) return false; + if (other.linkModePreference != linkModePreference) return false; + if (!const DeepCollectionEquality() + .equals(other.supportedAssetTypes, supportedAssetTypes)) return false; + if (!dryRun) { + if (other.buildMode != buildMode) return false; + if (other.targetArchitecture != targetArchitecture) return false; + if (targetOS == OS.iOS && other.targetIOSSdk != targetIOSSdk) { + return false; + } + if (other.targetAndroidNdkApi != targetAndroidNdkApi) return false; + if (other.cCompiler != cCompiler) return false; + } + return true; + } + + @override + int get hashCode => Object.hashAll([ + outputDirectory, + packageName, + packageRoot, + targetOS, + dryRun, + if (!dryRun) ...[ + buildMode, + targetArchitecture, + if (targetOS == OS.iOS) targetIOSSdk, + targetAndroidNdkApi, + cCompiler, + ], + ]); + + /// Constructs a checksum for a [BuildConfigImpl] based on the fields of a + /// buildconfig that influence the build. + /// + /// This can be used for an [outputDirectory], but should not be used for + /// dry-runs. + /// + /// In particular, it only takes the package name from [packageRoot], so that + /// the hash is equal across checkouts and ignores [outputDirectory] itself. + static String checksum({ + required String packageName, + required Uri packageRoot, + required ArchitectureImpl targetArchitecture, + required OSImpl targetOS, + required BuildModeImpl buildMode, + IOSSdkImpl? targetIOSSdk, + int? targetAndroidNdkApi, + CCompilerConfigImpl? cCompiler, + required LinkModePreferenceImpl linkModePreference, + Map? dependencyMetadata, + Iterable? supportedAssetTypes, + Version? version, + required Hook hook, + }) { + final input = [ + version ?? latestVersion, + packageName, + targetArchitecture.toString(), + targetOS.toString(), + targetIOSSdk.toString(), + targetAndroidNdkApi.toString(), + buildMode.toString(), + linkModePreference.toString(), + cCompiler?.archiver.toString(), + cCompiler?.compiler.toString(), + cCompiler?.envScript.toString(), + cCompiler?.envScriptArgs.toString(), + cCompiler?.linker.toString(), + if (dependencyMetadata != null) + for (final entry in dependencyMetadata.entries) ...[ + entry.key, + json.encode(entry.value.toJson()), + ], + ...supportedAssetTypes ?? [NativeCodeAsset.type], + hook.name, + ].join('###'); + final sha256String = sha256.convert(utf8.encode(input)).toString(); + // 256 bit hashes lead to 64 hex character strings. + // To avoid overflowing file paths limits, only use 32. + // Using 16 hex characters would also be unlikely to have collisions. + const nameLength = 32; + return sha256String.substring(0, nameLength); + } + + /// The version of [HookConfigImpl]. + /// + /// This class is used in the protocol between the Dart and Flutter SDKs + /// and packages through build hook invocations. + /// + /// If we ever were to make breaking changes, it would be useful to give + /// proper error messages rather than just fail to parse the JSON + /// representation in the protocol. + static Version latestVersion = Version(1, 3, 0); +} diff --git a/pkgs/native_assets_cli/lib/src/model/build_output.dart b/pkgs/native_assets_cli/lib/src/model/hook_output.dart similarity index 58% rename from pkgs/native_assets_cli/lib/src/model/build_output.dart rename to pkgs/native_assets_cli/lib/src/model/hook_output.dart index 0da94f94a..c8ddfe3c2 100644 --- a/pkgs/native_assets_cli/lib/src/model/build_output.dart +++ b/pkgs/native_assets_cli/lib/src/model/hook_output.dart @@ -4,7 +4,7 @@ part of '../api/build_output.dart'; -final class BuildOutputImpl implements BuildOutput { +final class HookOutputImpl implements BuildOutput, LinkOutput { @override final DateTime timestamp; @@ -13,6 +13,11 @@ final class BuildOutputImpl implements BuildOutput { @override Iterable get assets => _assets; + final Map> _assetsForLinking; + + @override + Map> get assetsForLinking => _assetsForLinking; + final Dependencies _dependencies; Dependencies get dependenciesModel => _dependencies; @@ -20,19 +25,21 @@ final class BuildOutputImpl implements BuildOutput { @override Iterable get dependencies => _dependencies.dependencies; - final Metadata _metadata; + final Metadata metadata; - BuildOutputImpl({ + HookOutputImpl({ DateTime? timestamp, List? assets, + Map>? assetsForLinking, Dependencies? dependencies, Metadata? metadata, }) : timestamp = (timestamp ?? DateTime.now()).roundDownToSeconds(), _assets = assets ?? [], + _assetsForLinking = assetsForLinking ?? {}, // ignore: prefer_const_constructors _dependencies = dependencies ?? Dependencies([]), // ignore: prefer_const_constructors - _metadata = metadata ?? Metadata({}); + metadata = metadata ?? Metadata({}); @override void addDependency(Uri dependency) => @@ -43,12 +50,13 @@ final class BuildOutputImpl implements BuildOutput { _dependencies.dependencies.addAll(dependencies); static const _assetsKey = 'assets'; + static const _assetsForLinkingKey = 'assetsForLinking'; static const _dependenciesKey = 'dependencies'; static const _metadataKey = 'metadata'; static const _timestampKey = 'timestamp'; static const _versionKey = 'version'; - factory BuildOutputImpl.fromJsonString(String jsonString) { + factory HookOutputImpl.fromJsonString(String jsonString) { final Object? json; if (jsonString.startsWith('{')) { json = jsonDecode(jsonString); @@ -57,11 +65,11 @@ final class BuildOutputImpl implements BuildOutput { // remove the YAML fallback. json = loadYaml(jsonString); } - return BuildOutputImpl.fromJson(as>(json)); + return HookOutputImpl.fromJson(as>(json)); } - factory BuildOutputImpl.fromJson(Map jsonMap) { - final outputVersion = Version.parse(as(jsonMap['version'])); + factory HookOutputImpl.fromJson(Map jsonMap) { + final outputVersion = Version.parse(get(jsonMap, 'version')); if (outputVersion.major > latestVersion.major) { throw FormatException( 'The output version $outputVersion is newer than the ' @@ -76,17 +84,17 @@ final class BuildOutputImpl implements BuildOutput { 'Flutter, please update native_assets_cli.', ); } - - final assets = - AssetImpl.listFromJsonList(as>(jsonMap[_assetsKey])); - - return BuildOutputImpl( - timestamp: DateTime.parse(as(jsonMap[_timestampKey])), - assets: assets, + return HookOutputImpl( + timestamp: DateTime.parse(get(jsonMap, _timestampKey)), + assets: AssetImpl.listFromJson(get?>(jsonMap, _assetsKey)), + assetsForLinking: get?>( + jsonMap, _assetsForLinkingKey) + ?.map((packageName, assets) => MapEntry( + packageName, AssetImpl.listFromJson(as>(assets)))), dependencies: - Dependencies.fromJson(as?>(jsonMap[_dependenciesKey])), + Dependencies.fromJson(get?>(jsonMap, _dependenciesKey)), metadata: - Metadata.fromJson(as?>(jsonMap[_metadataKey])), + Metadata.fromJson(get?>(jsonMap, _metadataKey)), ); } @@ -111,14 +119,24 @@ final class BuildOutputImpl implements BuildOutput { assets.add(asset); } } + final linkMinVersion = Version(1, 3, 0); + if (_assetsForLinking.isNotEmpty && version < linkMinVersion) { + throw UnsupportedError('Please update your Dart or Flutter SDK to link ' + 'assets in `link.dart` scripts. Your current version is $version, ' + 'but this feature requires $linkMinVersion'); + } return { _timestampKey: timestamp.toString(), - _assetsKey: [ - for (final asset in assets) asset.toJson(version), - ], + if (assets.isNotEmpty) _assetsKey: AssetImpl.listToJson(assets, version), + if (version >= linkMinVersion && _assetsForLinking.isNotEmpty) + _assetsForLinkingKey: + _assetsForLinking.map((packageName, assets) => MapEntry( + packageName, + AssetImpl.listToJson(assets, version), + )), if (_dependencies.dependencies.isNotEmpty) _dependenciesKey: _dependencies.toJson(), - _metadataKey: _metadata.toJson(), + if (metadata.metadata.isNotEmpty) _metadataKey: metadata.toJson(), _versionKey: version.toString(), }..sortOnKey(); } @@ -126,7 +144,7 @@ final class BuildOutputImpl implements BuildOutput { String toJsonString(Version version) => const JsonEncoder.withIndent(' ').convert(toJson(version)); - /// The version of [BuildOutputImpl]. + /// The version of [HookOutputImpl]. /// /// This class is used in the protocol between the Dart and Flutter SDKs and /// packages through build hook invocations. @@ -140,56 +158,38 @@ final class BuildOutputImpl implements BuildOutput { /// version of the Dart or Flutter SDK. When there is a need to split the /// versions of BuildConfig and BuildOutput, the BuildConfig should start /// passing the highest supported version of BuildOutput. - static Version latestVersion = BuildConfigImpl.latestVersion; - - static const fileName = 'build_output.json'; - static const fileNameV1_1_0 = 'build_output.yaml'; - - /// Reads the JSON file from [outDir]/[fileName]. - static Future readFromFile({required Uri outDir}) async { - final buildOutputUri = outDir.resolve(fileName); - final buildOutputFile = File.fromUri(buildOutputUri); - if (await buildOutputFile.exists()) { - return BuildOutputImpl.fromJsonString( - await buildOutputFile.readAsString()); - } + static Version latestVersion = HookConfigImpl.latestVersion; - final buildOutputUriV1_1_0 = outDir.resolve(fileNameV1_1_0); - final buildOutputFileV1_1_0 = File.fromUri(buildOutputUriV1_1_0); - if (await buildOutputFileV1_1_0.exists()) { - return BuildOutputImpl.fromJsonString( - await buildOutputFileV1_1_0.readAsString()); + /// Writes the JSON file from [file]. + static HookOutputImpl? readFromFile({required Uri file}) { + final buildOutputFile = File.fromUri(file); + if (buildOutputFile.existsSync()) { + return HookOutputImpl.fromJsonString(buildOutputFile.readAsStringSync()); } return null; } - /// Writes the [toJsonString] to [BuildConfig.outputDirectory]/[fileName]. - Future writeToFile({required BuildConfig config}) async { - final configVersion = (config as BuildConfigImpl).version; - final outDir = config.outputDirectory; - final Uri buildOutputUri; - if (configVersion <= Version(1, 1, 0)) { - buildOutputUri = outDir.resolve(fileNameV1_1_0); - } else { - buildOutputUri = outDir.resolve(fileName); - } + /// Writes the [toJsonString] to the output file specified in the [config]. + Future writeToFile({required HookConfigImpl config}) async { + final configVersion = config.version; final jsonString = toJsonString(configVersion); - await File.fromUri(buildOutputUri).writeAsStringCreateDirectory(jsonString); + await File.fromUri(config.outputFile) + .writeAsStringCreateDirectory(jsonString); } @override - String toString() => toJsonString(BuildConfigImpl.latestVersion); + String toString() => toJsonString(HookConfigImpl.latestVersion); @override bool operator ==(Object other) { - if (other is! BuildOutputImpl) { + if (other is! HookOutputImpl) { return false; } return other.timestamp == timestamp && const ListEquality().equals(other._assets, _assets) && other._dependencies == _dependencies && - other._metadata == _metadata; + other.metadata == metadata; } @override @@ -197,28 +197,32 @@ final class BuildOutputImpl implements BuildOutput { timestamp.hashCode, const ListEquality().hash(_assets), _dependencies, - _metadata, + metadata, ); @override void addMetadatum(String key, Object value) { - _metadata.metadata[key] = value; + metadata.metadata[key] = value; } @override void addMetadata(Map metadata) { - _metadata.metadata.addAll(metadata); + this.metadata.metadata.addAll(metadata); } - Metadata get metadataModel => _metadata; + Metadata get metadataModel => metadata; @override - void addAsset(Asset asset) { - _assets.add(asset as AssetImpl); + void addAsset(Asset asset, {String? linkInPackage}) { + _getAssetList(linkInPackage).add(asset as AssetImpl); } @override - void addAssets(Iterable assets) { - _assets.addAll(assets.cast()); + void addAssets(Iterable assets, {String? linkInPackage}) { + _getAssetList(linkInPackage).addAll(assets.cast()); } + + List _getAssetList(String? linkInPackage) => linkInPackage == null + ? _assets + : (_assetsForLinking[linkInPackage] ??= []); } diff --git a/pkgs/native_assets_cli/lib/src/model/link_config.dart b/pkgs/native_assets_cli/lib/src/model/link_config.dart new file mode 100644 index 000000000..20051fffa --- /dev/null +++ b/pkgs/native_assets_cli/lib/src/model/link_config.dart @@ -0,0 +1,146 @@ +// Copyright (c) 2024, 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. + +part of '../api/link_config.dart'; + +/// The input to the linking script. +/// +/// It consists of the fields inherited from the [HookConfig] and the [assets] +/// from the build step. +class LinkConfigImpl extends HookConfigImpl implements LinkConfig { + static const resourceIdentifierKey = 'resource_identifiers'; + + static const assetsKey = 'assets'; + + @override + final Iterable assets; + + // TODO: Placeholder for the resources.json file URL. We don't want to change + // native_assets_builder when implementing the parsing. + final Uri? _resourceIdentifierUri; + + LinkConfigImpl({ + required this.assets, + Uri? resourceIdentifierUri, + required super.outputDirectory, + required super.packageName, + required super.packageRoot, + Version? version, + required super.buildMode, + super.cCompiler, + Iterable? supportedAssetTypes, + super.targetAndroidNdkApi, + super.targetArchitecture, + super.targetIOSSdk, + required super.targetOS, + required super.linkModePreference, + super.dryRun, + }) : _resourceIdentifierUri = resourceIdentifierUri, + super( + hook: Hook.link, + version: version ?? HookConfigImpl.latestVersion, + supportedAssetTypes: supportedAssetTypes ?? [NativeCodeAsset.type], + ); + + LinkConfigImpl.dryRun({ + required this.assets, + Uri? resourceIdentifierUri, + required super.outputDirectory, + required super.packageName, + required super.packageRoot, + Version? version, + Iterable? supportedAssetTypes, + required super.linkModePreference, + required super.targetOS, + }) : _resourceIdentifierUri = resourceIdentifierUri, + super.dryRun( + hook: Hook.link, + version: version ?? HookConfigImpl.latestVersion, + supportedAssetTypes: supportedAssetTypes ?? [NativeCodeAsset.type], + ); + + @override + Hook get hook => Hook.link; + @override + String get outputName => 'link_output.json'; + + @override + Map toJson() => { + ...hookToJson(), + if (_resourceIdentifierUri != null) + resourceIdentifierKey: _resourceIdentifierUri.toFilePath(), + assetsKey: AssetImpl.listToJson(assets, version), + }.sortOnKey(); + + static LinkConfig fromArguments(List arguments) { + final argParser = ArgParser()..addOption('config'); + + final results = argParser.parse(arguments); + final linkConfigContents = + File(results['config'] as String).readAsStringSync(); + final linkConfigJson = + jsonDecode(linkConfigContents) as Map; + + return fromJson(linkConfigJson); + } + + static LinkConfigImpl fromJson(Map linkConfigJson) { + final config = + Config.fromConfigFileContents(fileContents: jsonEncode(linkConfigJson)); + final dryRun = HookConfigImpl.parseDryRun(config) ?? false; + final targetOS = HookConfigImpl.parseTargetOS(config); + return LinkConfigImpl( + outputDirectory: HookConfigImpl.parseOutDir(config), + packageName: HookConfigImpl.parsePackageName(config), + packageRoot: HookConfigImpl.parsePackageRoot(config), + buildMode: HookConfigImpl.parseBuildMode(config, dryRun), + targetOS: targetOS, + targetArchitecture: + HookConfigImpl.parseTargetArchitecture(config, dryRun, targetOS), + linkModePreference: HookConfigImpl.parseLinkModePreference(config), + version: HookConfigImpl.parseVersion(config), + cCompiler: HookConfigImpl.parseCCompiler(config, dryRun), + supportedAssetTypes: HookConfigImpl.parseSupportedAssetTypes(config), + targetAndroidNdkApi: + HookConfigImpl.parseTargetAndroidNdkApi(config, dryRun, targetOS), + targetIOSSdk: HookConfigImpl.parseTargetIOSSdk(config, dryRun, targetOS), + assets: parseAssets(config), + resourceIdentifierUri: parseResourceIdentifier(config), + dryRun: dryRun, + ); + } + + static Uri? parseResourceIdentifier(Config config) => + config.optionalPath(resourceIdentifierKey); + + static List parseAssets(Config config) => + AssetImpl.listFromJson(config.valueOf(assetsKey)); + + @override + bool operator ==(Object other) { + if (super != other) { + return false; + } + if (other is! LinkConfigImpl) { + return false; + } + if (other._resourceIdentifierUri != _resourceIdentifierUri) { + return false; + } + if (!const DeepCollectionEquality().equals(other.assets, assets)) { + return false; + } + return true; + } + + @override + int get hashCode => Object.hashAll([ + super.hashCode, + _resourceIdentifierUri, + const DeepCollectionEquality().hash(assets), + ]); + + @override + String toString() => 'LinkConfig(${toJson()})'; +} diff --git a/pkgs/native_assets_cli/lib/src/model/native_code_asset.dart b/pkgs/native_assets_cli/lib/src/model/native_code_asset.dart index 3073a5855..9863303ba 100644 --- a/pkgs/native_assets_cli/lib/src/model/native_code_asset.dart +++ b/pkgs/native_assets_cli/lib/src/model/native_code_asset.dart @@ -49,8 +49,8 @@ abstract final class LinkModeImpl implements LinkMode { /// v1.1.0 and newer. factory LinkModeImpl.fromJson(Map jsonMap) { - final type = as(jsonMap[_typeKey]); - final uriString = as(jsonMap[_uriKey]); + final type = get(jsonMap, _typeKey); + final uriString = get(jsonMap, _uriKey); final uri = uriString != null ? Uri(path: uriString) : null; return LinkModeImpl(type, uri); } @@ -244,7 +244,7 @@ final class NativeCodeAssetImpl implements NativeCodeAsset, AssetImpl { linkMode = StaticLinkingImpl(); } else { assert(linkModeJson == DynamicLoadingImpl._typeValueV1_0_0); - final pathJson = as>(jsonMap[_pathKey]); + final pathJson = get>(jsonMap, _pathKey); final type = as(pathJson[DynamicLoadingImpl._pathTypeKeyV1_0_0]); final uriString = as(pathJson[DynamicLoadingImpl._uriKey]); @@ -256,7 +256,7 @@ final class NativeCodeAssetImpl implements NativeCodeAsset, AssetImpl { linkMode = LinkModeImpl.fromJson(as>(linkModeJson)); } - final fileString = as(jsonMap[_fileKey]); + final fileString = get(jsonMap, _fileKey); final Uri? file; if (fileString != null) { file = Uri(path: fileString); @@ -270,7 +270,7 @@ final class NativeCodeAssetImpl implements NativeCodeAsset, AssetImpl { } else { file = null; } - final targetString = as(jsonMap[_targetKey]); + final targetString = get(jsonMap, _targetKey); final ArchitectureImpl? architecture; final OSImpl os; if (targetString != null) { @@ -279,8 +279,8 @@ final class NativeCodeAssetImpl implements NativeCodeAsset, AssetImpl { os = target.os; architecture = target.architecture; } else { - os = OSImpl.fromString(as(jsonMap[_osKey])); - final architectureString = as(jsonMap[_architectureKey]); + os = OSImpl.fromString(get(jsonMap, _osKey)); + final architectureString = get(jsonMap, _architectureKey); if (architectureString != null) { architecture = ArchitectureImpl.fromString(architectureString); } else { @@ -289,7 +289,7 @@ final class NativeCodeAssetImpl implements NativeCodeAsset, AssetImpl { } return NativeCodeAssetImpl( - id: as(jsonMap[_idKey]), + id: get(jsonMap, _idKey), os: os, architecture: architecture, linkMode: linkMode, @@ -363,5 +363,5 @@ final class NativeCodeAssetImpl implements NativeCodeAsset, AssetImpl { @override String toString() => - 'NativeCodeAsset(${toJson(BuildOutputImpl.latestVersion)})'; + 'NativeCodeAsset(${toJson(HookOutputImpl.latestVersion)})'; } diff --git a/pkgs/native_assets_cli/lib/src/model/resource_identifiers.dart b/pkgs/native_assets_cli/lib/src/model/resource_identifiers.dart new file mode 100644 index 000000000..0ed06d619 --- /dev/null +++ b/pkgs/native_assets_cli/lib/src/model/resource_identifiers.dart @@ -0,0 +1,202 @@ +// Copyright (c) 2024, 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:convert'; +import 'dart:io'; + +import 'package:collection/collection.dart'; + +class ResourceIdentifiers { + final List identifiers; + + ResourceIdentifiers({required this.identifiers}); + + /// Read resources from a resources.json file + factory ResourceIdentifiers.fromFile(String path) => + ResourceIdentifiers.fromFileContents(File(path).readAsStringSync()); + + /// Read resources from the contents of a resources.json file + factory ResourceIdentifiers.fromFileContents(String fileContents) { + final fileJson = (jsonDecode(fileContents) as Map)['identifiers'] as List; + return ResourceIdentifiers( + identifiers: fileJson + .map((e) => e as Map) + .map(Identifier.fromJson) + .toList()); + } + + factory ResourceIdentifiers.fromJson(Map map) => + ResourceIdentifiers( + identifiers: List.from((map['identifiers'] as List?) + ?.whereType>() + .map(Identifier.fromJson) ?? + []), + ); + + Map toJson() => { + 'identifiers': identifiers.map((x) => x.toJson()).toList(), + }; + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + final listEquals = const DeepCollectionEquality().equals; + + return other is ResourceIdentifiers && + listEquals(other.identifiers, identifiers); + } + + @override + int get hashCode => identifiers.hashCode; + + @override + String toString() => 'ResourceIdentifiers(identifiers: $identifiers)'; +} + +class Identifier { + final String name; + final String id; + final Uri uri; + final bool nonConstant; + final List files; + + Identifier({ + required this.name, + required this.id, + required this.uri, + required this.nonConstant, + required this.files, + }); + + Map toJson() => { + 'name': name, + 'id': id, + 'uri': uri.toFilePath(), + 'nonConstant': nonConstant, + 'files': files.map((x) => x.toJson()).toList(), + }; + + @override + String toString() => + '''Identifier(name: $name, id: $id, uri: $uri, nonConstant: $nonConstant, files: $files)'''; + + factory Identifier.fromJson(Map map) => Identifier( + name: map['name'] as String, + id: map['id'] as String, + uri: Uri.file(map['uri'] as String), + nonConstant: map['nonConstant'] as bool, + files: List.from((map['files'] as List) + .map((e) => e as Map) + .map(ResourceFile.fromJson)), + ); + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + final listEquals = const DeepCollectionEquality().equals; + + return other is Identifier && + other.name == name && + other.id == id && + other.uri == uri && + other.nonConstant == nonConstant && + listEquals(other.files, files); + } + + @override + int get hashCode => + name.hashCode ^ + id.hashCode ^ + uri.hashCode ^ + nonConstant.hashCode ^ + files.hashCode; +} + +class ResourceFile { + final int part; + final List references; + + ResourceFile({required this.part, required this.references}); + + Map toJson() => { + 'part': part, + 'references': references.map((x) => x.toJson()).toList(), + }; + + factory ResourceFile.fromJson(Map map) => ResourceFile( + part: map['part'] as int, + references: List.from((map['references'] as List) + .map((e) => e as Map) + .map(ResourceReference.fromJson)), + ); + + @override + String toString() => 'ResourceFile(part: $part, references: $references)'; + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + final listEquals = const DeepCollectionEquality().equals; + + return other is ResourceFile && + other.part == part && + listEquals(other.references, references); + } + + @override + int get hashCode => 95911 ^ part.hashCode ^ references.hashCode; +} + +class ResourceReference { + final Uri uri; + final int line; + final int column; + final Map arguments; + + ResourceReference({ + required this.uri, + required this.line, + required this.column, + required this.arguments, + }); + + Map toJson() => { + '@': { + 'uri': uri.toFilePath(), + 'line': line, + 'column': column, + }, + ...arguments, + }; + + @override + String toString() => + '''ResourceReference(uri: $uri, line: $line, column: $column, arguments: $arguments)'''; + + factory ResourceReference.fromJson(Map map) { + final submap = map['@'] as Map; + return ResourceReference( + uri: Uri.file(submap['uri'] as String), + line: submap['line'] as int, + column: submap['column'] as int, + arguments: Map.from(map)..remove('@'), + ); + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + final mapEquals = const DeepCollectionEquality().equals; + + return other is ResourceReference && + other.uri == uri && + other.line == line && + other.column == column && + mapEquals(other.arguments, arguments); + } + + @override + int get hashCode => + uri.hashCode ^ line.hashCode ^ column.hashCode ^ arguments.hashCode; +} diff --git a/pkgs/native_assets_cli/lib/src/utils/json.dart b/pkgs/native_assets_cli/lib/src/utils/json.dart index bd18d97b8..096c7edf9 100644 --- a/pkgs/native_assets_cli/lib/src/utils/json.dart +++ b/pkgs/native_assets_cli/lib/src/utils/json.dart @@ -7,7 +7,19 @@ T as(Object? object) { return object; } throw FormatException( - "Unexpected value '$object' in JSON. Expected a $T.", + "Unexpected value '$object' of type ${object.runtimeType} in JSON. Expected" + ' a $T.', + ); +} + +T get(Map map, String key) { + final object = map[key]; + if (object is T) { + return object; + } + throw FormatException( + "Unexpected value '$object' of type ${object.runtimeType} in JSON for key " + '$key. Expected a $T.', ); } diff --git a/pkgs/native_assets_cli/pubspec.yaml b/pkgs/native_assets_cli/pubspec.yaml index e567de627..bba4a830e 100644 --- a/pkgs/native_assets_cli/pubspec.yaml +++ b/pkgs/native_assets_cli/pubspec.yaml @@ -4,7 +4,7 @@ description: >- native assets CLI. # Note: Bump BuildConfig.version and BuildOutput.version on breaking changes! -version: 0.5.4 +version: 0.6.0-wip repository: https://github.com/dart-lang/native/tree/main/pkgs/native_assets_cli topics: @@ -16,6 +16,7 @@ environment: sdk: '>=3.3.0 <4.0.0' dependencies: + args: ^2.4.2 cli_config: ^0.2.0 collection: ^1.17.1 crypto: ^3.0.3 diff --git a/pkgs/native_assets_cli/test/api/asset_test.dart b/pkgs/native_assets_cli/test/api/asset_test.dart index 606d775a5..c01772a9d 100644 --- a/pkgs/native_assets_cli/test/api/asset_test.dart +++ b/pkgs/native_assets_cli/test/api/asset_test.dart @@ -3,7 +3,6 @@ // BSD-style license that can be found in the LICENSE file. import 'package:native_assets_cli/native_assets_cli.dart'; -import 'package:native_assets_cli/src/api/asset.dart'; import 'package:test/test.dart'; void main() { diff --git a/pkgs/native_assets_cli/test/api/build_config_test.dart b/pkgs/native_assets_cli/test/api/build_config_test.dart index ec58b0ecd..88f5724fe 100644 --- a/pkgs/native_assets_cli/test/api/build_config_test.dart +++ b/pkgs/native_assets_cli/test/api/build_config_test.dart @@ -4,7 +4,6 @@ import 'dart:io'; -import 'package:cli_config/cli_config.dart'; import 'package:native_assets_cli/native_assets_cli.dart'; import 'package:native_assets_cli/src/api/build_config.dart'; import 'package:test/test.dart'; @@ -107,7 +106,7 @@ void main() async { linkModePreference: LinkModePreference.preferStatic, ); - final config = Config(fileParsed: { + final config = { 'build_mode': 'release', 'dry_run': false, 'link_mode_preference': 'prefer-static', @@ -118,15 +117,15 @@ void main() async { 'target_architecture': 'arm64', 'target_os': 'android', 'version': BuildOutput.latestVersion.toString(), - }); + }; - final fromConfig = BuildConfigImpl.fromConfig(config); + final fromConfig = BuildConfigImpl.fromJson(config); expect(fromConfig, equals(buildConfig2)); }); test('BuildConfig.dryRun', () { final buildConfig2 = BuildConfig.dryRun( - outDir: outDirUri, + outputDirectory: outDirUri, packageName: packageName, packageRoot: packageRootUri, targetOS: OS.android, @@ -134,7 +133,7 @@ void main() async { supportedAssetTypes: [NativeCodeAsset.type], ); - final config = Config(fileParsed: { + final config = { 'dry_run': true, 'link_mode_preference': 'prefer-static', 'out_dir': outDirUri.toFilePath(), @@ -142,9 +141,9 @@ void main() async { 'package_root': packageRootUri.toFilePath(), 'target_os': 'android', 'version': BuildOutput.latestVersion.toString(), - }); + }; - final fromConfig = BuildConfigImpl.fromConfig(config); + final fromConfig = BuildConfigImpl.fromJson(config); expect(fromConfig, equals(buildConfig2)); }); diff --git a/pkgs/native_assets_cli/test/api/build_test.dart b/pkgs/native_assets_cli/test/api/build_test.dart index f9c4e5874..23c662d24 100644 --- a/pkgs/native_assets_cli/test/api/build_test.dart +++ b/pkgs/native_assets_cli/test/api/build_test.dart @@ -7,7 +7,6 @@ import 'dart:io'; import 'package:file_testing/file_testing.dart'; import 'package:native_assets_cli/native_assets_cli.dart'; import 'package:native_assets_cli/src/api/build_config.dart'; -import 'package:native_assets_cli/src/api/build_output.dart'; import 'package:test/test.dart'; void main() async { @@ -21,6 +20,7 @@ void main() async { late Uri fakeCl; late Uri fakeVcVars; late Uri buildConfigUri; + late BuildConfig config1; setUp(() async { tempUri = (await Directory.systemTemp.createTemp()).uri; @@ -40,7 +40,7 @@ void main() async { fakeVcVars = tempUri.resolve('vcvarsall.bat'); await File.fromUri(fakeVcVars).create(); - final config1 = BuildConfig.build( + config1 = BuildConfig.build( outputDirectory: outDirUri, packageName: packageName, packageRoot: tempUri, @@ -65,7 +65,8 @@ void main() async { (config, output) async { output.addDependency(packageRootUri.resolve('foo')); }); - final buildOutputUri = outDirUri.resolve(BuildOutputImpl.fileName); + final buildOutputUri = + outDirUri.resolve((config1 as BuildConfigImpl).outputName); expect(File.fromUri(buildOutputUri), exists); }); } diff --git a/pkgs/native_assets_cli/test/api/link_config_test.dart b/pkgs/native_assets_cli/test/api/link_config_test.dart new file mode 100644 index 000000000..831278f75 --- /dev/null +++ b/pkgs/native_assets_cli/test/api/link_config_test.dart @@ -0,0 +1,179 @@ +// 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:io'; + +import 'package:native_assets_cli/native_assets_cli.dart'; +import 'package:native_assets_cli/src/api/link_config.dart'; +import 'package:test/test.dart'; + +void main() async { + late Uri tempUri; + late Uri outDirUri; + late Uri outDir2Uri; + late String packageName; + late Uri packageRootUri; + late Uri fakeClang; + late Uri fakeLd; + late Uri fakeAr; + late Uri fakeCl; + late Uri fakeVcVars; + + setUp(() async { + tempUri = (await Directory.systemTemp.createTemp()).uri; + outDirUri = tempUri.resolve('out1/'); + await Directory.fromUri(outDirUri).create(); + outDir2Uri = tempUri.resolve('out2/'); + packageName = 'my_package'; + await Directory.fromUri(outDir2Uri).create(); + packageRootUri = tempUri.resolve('$packageName/'); + await Directory.fromUri(packageRootUri).create(); + fakeClang = tempUri.resolve('fake_clang'); + await File.fromUri(fakeClang).create(); + fakeLd = tempUri.resolve('fake_ld'); + await File.fromUri(fakeLd).create(); + fakeAr = tempUri.resolve('fake_ar'); + await File.fromUri(fakeAr).create(); + fakeCl = tempUri.resolve('cl.exe'); + await File.fromUri(fakeCl).create(); + fakeVcVars = tempUri.resolve('vcvarsall.bat'); + await File.fromUri(fakeVcVars).create(); + }); + + tearDown(() async { + await Directory.fromUri(tempUri).delete(recursive: true); + }); + + test('LinkConfig ==', () { + final config1 = LinkConfig.build( + outputDirectory: outDirUri, + packageName: packageName, + packageRoot: tempUri, + targetArchitecture: Architecture.arm64, + targetOS: OS.iOS, + targetIOSSdk: IOSSdk.iPhoneOS, + cCompiler: CCompilerConfig( + compiler: fakeClang, + linker: fakeLd, + archiver: fakeAr, + ), + buildMode: BuildMode.release, + supportedAssetTypes: [NativeCodeAsset.type], + assets: [], + linkModePreference: LinkModePreference.preferStatic, + ); + + final config2 = LinkConfig.build( + outputDirectory: outDir2Uri, + packageName: packageName, + packageRoot: tempUri, + targetArchitecture: Architecture.arm64, + targetOS: OS.android, + targetAndroidNdkApi: 30, + buildMode: BuildMode.release, + supportedAssetTypes: [NativeCodeAsset.type], + assets: [], + linkModePreference: LinkModePreference.preferStatic, + ); + + expect(config1, equals(config1)); + expect(config1 == config2, false); + expect(config1.outputDirectory != config2.outputDirectory, true); + expect(config1.packageRoot, config2.packageRoot); + expect(config1.targetArchitecture == config2.targetArchitecture, true); + expect(config1.targetOS != config2.targetOS, true); + expect(config1.targetIOSSdk, IOSSdk.iPhoneOS); + expect(() => config2.targetIOSSdk, throwsStateError); + expect(config1.cCompiler.compiler != config2.cCompiler.compiler, true); + expect(config1.cCompiler.linker != config2.cCompiler.linker, true); + expect(config1.cCompiler.archiver != config2.cCompiler.archiver, true); + expect(config1.cCompiler.envScript == config2.cCompiler.envScript, true); + expect(config1.cCompiler.envScriptArgs == config2.cCompiler.envScriptArgs, + true); + expect(config1.cCompiler != config2.cCompiler, true); + expect(config1.supportedAssetTypes, config2.supportedAssetTypes); + }); + + test('LinkConfig fromConfig', () { + final linkConfig2 = LinkConfig.build( + outputDirectory: outDirUri, + packageName: packageName, + packageRoot: packageRootUri, + targetArchitecture: Architecture.arm64, + targetOS: OS.android, + targetAndroidNdkApi: 30, + buildMode: BuildMode.release, + assets: [], + linkModePreference: LinkModePreference.preferStatic, + ); + + final config = { + 'build_mode': 'release', + 'dry_run': false, + 'link_mode_preference': 'prefer-static', + 'out_dir': outDirUri.toFilePath(), + 'package_name': packageName, + 'package_root': packageRootUri.toFilePath(), + 'target_android_ndk_api': 30, + 'target_architecture': 'arm64', + 'target_os': 'android', + 'version': BuildOutput.latestVersion.toString(), + 'assets': [], + }; + + final fromConfig = LinkConfigImpl.fromJson(config); + expect(fromConfig, equals(linkConfig2)); + }); + + test('LinkConfig.dryRun', () { + final linkConfig2 = LinkConfig.dryRun( + outputDirectory: outDirUri, + packageName: packageName, + packageRoot: packageRootUri, + targetOS: OS.android, + supportedAssetTypes: [NativeCodeAsset.type], + assets: [], + linkModePreference: LinkModePreference.preferStatic, + ); + + final config = { + 'dry_run': true, + 'link_mode_preference': 'prefer-static', + 'out_dir': outDirUri.toFilePath(), + 'package_name': packageName, + 'package_root': packageRootUri.toFilePath(), + 'target_os': 'android', + 'version': BuildOutput.latestVersion.toString(), + 'assets': [], + }; + + final fromConfig = LinkConfigImpl.fromJson(config); + expect(fromConfig, equals(linkConfig2)); + }); + + test('LinkConfig fromArgs', () async { + final linkConfig = LinkConfig.build( + outputDirectory: outDirUri, + packageName: packageName, + packageRoot: tempUri, + targetArchitecture: Architecture.arm64, + targetOS: OS.android, + targetAndroidNdkApi: 30, + buildMode: BuildMode.release, + assets: [], + linkModePreference: LinkModePreference.preferStatic, + ); + final configFileContents = (linkConfig as LinkConfigImpl).toJsonString(); + final configUri = tempUri.resolve('config.json'); + final configFile = File.fromUri(configUri); + await configFile.writeAsString(configFileContents); + final linkConfigFromArgs = + LinkConfig.fromArguments(['--config', configUri.toFilePath()]); + expect(linkConfigFromArgs, linkConfig); + }); + + test('LinkConfig.version', () { + LinkConfig.latestVersion.toString(); + }); +} diff --git a/pkgs/native_assets_cli/test/example/local_asset_test.dart b/pkgs/native_assets_cli/test/example/local_asset_test.dart index 51e6e980c..8f07c0387 100644 --- a/pkgs/native_assets_cli/test/example/local_asset_test.dart +++ b/pkgs/native_assets_cli/test/example/local_asset_test.dart @@ -32,7 +32,7 @@ void main() async { test('local_asset build$testSuffix', () async { final testTempUri = tempUri.resolve('test1/'); await Directory.fromUri(testTempUri).create(); - final testPackageUri = packageUri.resolve('example/$name/'); + final testPackageUri = packageUri.resolve('example/build/$name/'); final dartUri = Uri.file(Platform.resolvedExecutable); final processResult = await Process.run( @@ -43,7 +43,7 @@ void main() async { '-Dpackage_name=$name', '-Dpackage_root=${testPackageUri.toFilePath()}', '-Dtarget_os=${OSImpl.current}', - '-Dversion=${BuildConfigImpl.latestVersion}', + '-Dversion=${HookConfigImpl.latestVersion}', '-Dlink_mode_preference=dynamic', '-Ddry_run=$dryRun', if (!dryRun) ...[ @@ -68,7 +68,7 @@ void main() async { expect(processResult.exitCode, 0); final buildOutputUri = tempUri.resolve('build_output.json'); - final buildOutput = BuildOutputImpl.fromJsonString( + final buildOutput = HookOutputImpl.fromJsonString( await File.fromUri(buildOutputUri).readAsString()); final assets = buildOutput.assets; final dependencies = buildOutput.dependencies; diff --git a/pkgs/native_assets_cli/test/example/native_add_library_test.dart b/pkgs/native_assets_cli/test/example/native_add_library_test.dart index c462e0360..263872d5f 100644 --- a/pkgs/native_assets_cli/test/example/native_add_library_test.dart +++ b/pkgs/native_assets_cli/test/example/native_add_library_test.dart @@ -32,7 +32,7 @@ void main() async { test('native_add build$testSuffix', () async { final testTempUri = tempUri.resolve('test1/'); await Directory.fromUri(testTempUri).create(); - final testPackageUri = packageUri.resolve('example/$name/'); + final testPackageUri = packageUri.resolve('example/build/$name/'); final dartUri = Uri.file(Platform.resolvedExecutable); final processResult = await Process.run( @@ -43,7 +43,7 @@ void main() async { '-Dpackage_name=$name', '-Dpackage_root=${testPackageUri.toFilePath()}', '-Dtarget_os=${OSImpl.current}', - '-Dversion=${BuildConfigImpl.latestVersion}', + '-Dversion=${HookConfigImpl.latestVersion}', '-Dlink_mode_preference=dynamic', '-Ddry_run=$dryRun', if (!dryRun) ...[ @@ -68,7 +68,7 @@ void main() async { expect(processResult.exitCode, 0); final buildOutputUri = tempUri.resolve('build_output.json'); - final buildOutput = BuildOutputImpl.fromJsonString( + final buildOutput = HookOutputImpl.fromJsonString( await File.fromUri(buildOutputUri).readAsString()); final assets = buildOutput.assets; final dependencies = buildOutput.dependencies; diff --git a/pkgs/native_assets_cli/test/helpers.dart b/pkgs/native_assets_cli/test/helpers.dart index ae34a37d1..830ed9d37 100644 --- a/pkgs/native_assets_cli/test/helpers.dart +++ b/pkgs/native_assets_cli/test/helpers.dart @@ -133,6 +133,10 @@ extension on Asset { } } +extension UnescapePath on String { + String unescape() => replaceAll('\\', '/'); +} + extension UriExtension on Uri { FileSystemEntity get fileSystemEntity { if (path.endsWith(Platform.pathSeparator) || path.endsWith('/')) { @@ -153,3 +157,22 @@ String yamlEncode(Object yamlEncoding) { ); return editor.toString(); } + +dynamic yamlDecode(String yaml) { + final value = loadYaml(yaml); + return yamlToDart(value); +} + +dynamic yamlToDart(dynamic value) { + if (value is YamlMap) { + final entries = >[]; + for (final key in value.keys) { + entries.add(MapEntry(key as String, yamlToDart(value[key]))); + } + return Map.fromEntries(entries); + } else if (value is YamlList) { + return List.from(value.map(yamlToDart)); + } else { + return value; + } +} diff --git a/pkgs/native_assets_cli/test/model/asset_test.dart b/pkgs/native_assets_cli/test/model/asset_test.dart index d6f5e2013..77a5e1b45 100644 --- a/pkgs/native_assets_cli/test/model/asset_test.dart +++ b/pkgs/native_assets_cli/test/model/asset_test.dart @@ -7,8 +7,6 @@ import 'package:native_assets_cli/native_assets_cli_internal.dart'; import 'package:test/test.dart'; import 'package:yaml/yaml.dart'; -import '../helpers.dart'; - void main() { final fooUri = Uri.file('path/to/libfoo.so'); final foo3Uri = Uri(path: 'libfoo3.so'); @@ -59,11 +57,13 @@ void main() { ]; final dataAssets = [ DataAssetImpl( - id: 'package:my_package/my_data_asset', + name: 'my_data_asset', + package: 'my_package', file: dataUri, ), DataAssetImpl( - id: 'package:my_package/my_data_asset2', + name: 'my_data_asset2', + package: 'my_package', file: data2Uri, ), ]; @@ -107,64 +107,80 @@ void main() { uri: ${blaUri.toFilePath()} target: windows_x64'''; - final assetsYamlEncoding = '''- architecture: x64 - file: ${fooUri.toFilePath()} - id: package:my_package/foo - link_mode: - type: dynamic_loading_bundle - os: android - type: native_code -- architecture: x64 - id: package:my_package/foo3 - link_mode: - type: dynamic_loading_system - uri: ${foo3Uri.toFilePath()} - os: android - type: native_code -- architecture: x64 - id: package:my_package/foo4 - link_mode: - type: dynamic_loading_executable - os: android - type: native_code -- architecture: x64 - id: package:my_package/foo5 - link_mode: - type: dynamic_loading_process - os: android - type: native_code -- architecture: arm64 - file: ${barUri.toFilePath()} - id: package:my_package/bar - link_mode: - type: static - os: linux - type: native_code -- architecture: x64 - file: ${blaUri.toFilePath()} - id: package:my_package/bla - link_mode: - type: dynamic_loading_bundle - os: windows - type: native_code -- id: package:my_package/my_data_asset - file: ${dataUri.toFilePath()} - type: data -- id: package:my_package/my_data_asset2 - file: ${data2Uri.toFilePath()} - type: data'''; + final assetsJsonEncoding = [ + { + 'architecture': 'x64', + 'file': fooUri.toFilePath(), + 'id': 'package:my_package/foo', + 'link_mode': {'type': 'dynamic_loading_bundle'}, + 'os': 'android', + 'type': 'native_code' + }, + { + 'architecture': 'x64', + 'id': 'package:my_package/foo3', + 'link_mode': { + 'type': 'dynamic_loading_system', + 'uri': foo3Uri.toFilePath() + }, + 'os': 'android', + 'type': 'native_code' + }, + { + 'architecture': 'x64', + 'id': 'package:my_package/foo4', + 'link_mode': {'type': 'dynamic_loading_executable'}, + 'os': 'android', + 'type': 'native_code' + }, + { + 'architecture': 'x64', + 'id': 'package:my_package/foo5', + 'link_mode': {'type': 'dynamic_loading_process'}, + 'os': 'android', + 'type': 'native_code' + }, + { + 'architecture': 'arm64', + 'file': barUri.toFilePath(), + 'id': 'package:my_package/bar', + 'link_mode': {'type': 'static'}, + 'os': 'linux', + 'type': 'native_code' + }, + { + 'architecture': 'x64', + 'file': blaUri.toFilePath(), + 'id': 'package:my_package/bla', + 'link_mode': {'type': 'dynamic_loading_bundle'}, + 'os': 'windows', + 'type': 'native_code' + }, + { + 'name': 'my_data_asset', + 'package': 'my_package', + 'file': Uri.file('path/to/data.txt').toFilePath(), + 'type': 'data' + }, + { + 'name': 'my_data_asset2', + 'package': 'my_package', + 'file': Uri.file('path/to/data.json').toFilePath(), + 'type': 'data' + } + ]; test('asset yaml', () { - final yaml = yamlEncode([ - for (final item in assets) item.toJson(BuildOutputImpl.latestVersion) - ]); - expect(yaml, assetsYamlEncoding); - final assets2 = AssetImpl.listFromJsonList(loadYaml(yaml) as List); + final json = [ + for (final item in assets) item.toJson(HookOutputImpl.latestVersion) + ]; + expect(json, assetsJsonEncoding); + final assets2 = AssetImpl.listFromJson(json); expect(assets, assets2); }); test('build_output protocol v1.0.0 keeps working', () { - final assets2 = AssetImpl.listFromJsonList( + final assets2 = AssetImpl.listFromJson( loadYaml(assetsYamlEncodingV1_0_0) as List); expect(nativeCodeAssets, assets2); }); diff --git a/pkgs/native_assets_cli/test/model/build_config_test.dart b/pkgs/native_assets_cli/test/model/build_config_test.dart index 0c906f7d0..4d82529e0 100644 --- a/pkgs/native_assets_cli/test/model/build_config_test.dart +++ b/pkgs/native_assets_cli/test/model/build_config_test.dart @@ -4,7 +4,6 @@ import 'dart:io'; -import 'package:cli_config/cli_config.dart'; import 'package:native_assets_cli/native_assets_cli_internal.dart'; import 'package:native_assets_cli/src/api/asset.dart'; import 'package:test/test.dart'; @@ -12,10 +11,10 @@ import 'package:test/test.dart'; import '../helpers.dart'; void main() async { + const packageName = 'my_package'; late Uri tempUri; late Uri outDirUri; late Uri outDir2Uri; - late String packageName; late Uri packageRootUri; late Uri fakeClang; late Uri fakeLd; @@ -28,7 +27,6 @@ void main() async { outDirUri = tempUri.resolve('out1/'); await Directory.fromUri(outDirUri).create(); outDir2Uri = tempUri.resolve('out2/'); - packageName = 'my_package'; await Directory.fromUri(outDir2Uri).create(); packageRootUri = tempUri.resolve('$packageName/'); await Directory.fromUri(packageRootUri).create(); @@ -50,7 +48,7 @@ void main() async { test('BuildConfig ==', () { final config1 = BuildConfigImpl( - outDir: outDirUri, + outputDirectory: outDirUri, packageName: packageName, packageRoot: tempUri, targetArchitecture: ArchitectureImpl.arm64, @@ -66,7 +64,7 @@ void main() async { ); final config2 = BuildConfigImpl( - outDir: outDir2Uri, + outputDirectory: outDir2Uri, packageName: packageName, packageRoot: tempUri, targetArchitecture: ArchitectureImpl.arm64, @@ -96,7 +94,7 @@ void main() async { test('BuildConfig fromConfig', () { final buildConfig2 = BuildConfigImpl( - outDir: outDirUri, + outputDirectory: outDirUri, packageName: packageName, packageRoot: packageRootUri, targetArchitecture: ArchitectureImpl.arm64, @@ -106,7 +104,7 @@ void main() async { linkModePreference: LinkModePreferenceImpl.preferStatic, ); - final config = Config(fileParsed: { + final config = { 'build_mode': 'release', 'dry_run': false, 'link_mode_preference': 'prefer-static', @@ -116,39 +114,39 @@ void main() async { 'target_android_ndk_api': 30, 'target_architecture': 'arm64', 'target_os': 'android', - 'version': BuildOutputImpl.latestVersion.toString(), - }); + 'version': HookOutputImpl.latestVersion.toString(), + }; - final fromConfig = BuildConfigImpl.fromConfig(config); + final fromConfig = BuildConfigImpl.fromJson(config); expect(fromConfig, equals(buildConfig2)); }); test('BuildConfig.dryRun', () { final buildConfig2 = BuildConfigImpl.dryRun( - outDir: outDirUri, + outputDirectory: outDirUri, packageName: packageName, packageRoot: packageRootUri, targetOS: OSImpl.android, linkModePreference: LinkModePreferenceImpl.preferStatic, ); - final config = Config(fileParsed: { + final config = { 'dry_run': true, 'link_mode_preference': 'prefer-static', 'out_dir': outDirUri.toFilePath(), 'package_name': packageName, 'package_root': packageRootUri.toFilePath(), 'target_os': 'android', - 'version': BuildOutputImpl.latestVersion.toString(), - }); + 'version': HookOutputImpl.latestVersion.toString(), + }; - final fromConfig = BuildConfigImpl.fromConfig(config); + final fromConfig = BuildConfigImpl.fromJson(config); expect(fromConfig, equals(buildConfig2)); }); test('BuildConfig toJson fromConfig', () { final buildConfig1 = BuildConfigImpl( - outDir: outDirUri, + outputDirectory: outDirUri, packageName: packageName, packageRoot: packageRootUri, targetArchitecture: ArchitectureImpl.arm64, @@ -163,14 +161,13 @@ void main() async { ); final configFile = buildConfig1.toJson(); - final config = Config(fileParsed: configFile); - final fromConfig = BuildConfigImpl.fromConfig(config); + final fromConfig = BuildConfigImpl.fromJson(configFile); expect(fromConfig, equals(buildConfig1)); }); test('BuildConfig == dependency metadata', () { final buildConfig1 = BuildConfigImpl( - outDir: outDirUri, + outputDirectory: outDirUri, packageName: packageName, packageRoot: tempUri, targetArchitecture: ArchitectureImpl.arm64, @@ -190,7 +187,7 @@ void main() async { ); final buildConfig2 = BuildConfigImpl( - outDir: outDirUri, + outputDirectory: outDirUri, packageName: packageName, packageRoot: tempUri, targetArchitecture: ArchitectureImpl.arm64, @@ -216,7 +213,7 @@ void main() async { test('BuildConfig toJson fromJson', () { final outDir = outDirUri; final buildConfig1 = BuildConfigImpl( - outDir: outDir, + outputDirectory: outDir, packageName: packageName, packageRoot: tempUri, targetArchitecture: ArchitectureImpl.arm64, @@ -240,72 +237,33 @@ void main() async { }, ); - final yamlString = yamlEncode(buildConfig1.toJson()); - final expectedYamlString = '''build_mode: release -c_compiler: - cc: ${fakeClang.toFilePath()} - ld: ${fakeLd.toFilePath()} -dependency_metadata: - bar: - key: value - foo: - a: 321 - z: - - z - - a -link_mode_preference: prefer-static -out_dir: ${outDir.toFilePath()} -package_name: $packageName -package_root: ${tempUri.toFilePath()} -supported_asset_types: - - ${NativeCodeAsset.type} -target_architecture: arm64 -target_ios_sdk: iphoneos -target_os: ios -version: ${BuildConfigImpl.latestVersion}'''; - expect(yamlString, equals(expectedYamlString)); - - final jsonString = buildConfig1.toJsonString(); - final expectedJsonString = '''{ - "build_mode": "release", - "c_compiler": { - "cc": "${fakeClang.toFilePath()}", - "ld": "${fakeLd.toFilePath()}" - }, - "dependency_metadata": { - "bar": { - "key": "value" - }, - "foo": { - "a": 321, - "z": [ - "z", - "a" - ] - } - }, - "link_mode_preference": "prefer-static", - "out_dir": "${outDir.toFilePath()}", - "package_name": "$packageName", - "package_root": "${tempUri.toFilePath()}", - "supported_asset_types": [ - "${NativeCodeAsset.type}" - ], - "target_architecture": "arm64", - "target_ios_sdk": "iphoneos", - "target_os": "ios", - "version": "${BuildConfigImpl.latestVersion}" -}'''; + final jsonObject = buildConfig1.toJson(); + final expectedJson = { + 'build_mode': 'release', + 'c_compiler': {'cc': fakeClang.toFilePath(), 'ld': fakeLd.toFilePath()}, + 'dependency_metadata': { + 'bar': {'key': 'value'}, + 'foo': { + 'a': 321, + 'z': ['z', 'a'] + } + }, + 'link_mode_preference': 'prefer-static', + 'out_dir': outDir.toFilePath(), + 'package_name': packageName, + 'package_root': tempUri.toFilePath(), + 'supported_asset_types': [NativeCodeAsset.type], + 'target_architecture': 'arm64', + 'target_ios_sdk': 'iphoneos', + 'target_os': 'ios', + 'version': '${HookConfigImpl.latestVersion}' + }; expect( - jsonString.replaceAll('\\\\', '/'), - equals(expectedJsonString.replaceAll('\\', '/')), + jsonObject, + equals(expectedJson), ); - final buildConfig2 = BuildConfigImpl.fromConfig( - Config.fromConfigFileContents( - fileContents: yamlString, - ), - ); + final buildConfig2 = BuildConfigImpl.fromJson(jsonObject); expect(buildConfig2, buildConfig1); }); @@ -332,7 +290,7 @@ target_ios_sdk: iphoneos target_os: ios version: 1.0.0'''; final buildConfig1 = BuildConfigImpl( - outDir: outDir, + outputDirectory: outDir, packageName: packageName, packageRoot: tempUri, targetArchitecture: ArchitectureImpl.arm64, @@ -356,35 +314,32 @@ version: 1.0.0'''; }, ); - final buildConfig2 = BuildConfigImpl.fromConfig( - Config.fromConfigFileContents( - fileContents: yamlString, - ), - ); + final buildConfig2 = BuildConfigImpl.fromJson( + yamlDecode(yamlString) as Map); expect(buildConfig2, buildConfig1); }); test('BuildConfig FormatExceptions', () { expect( - () => BuildConfigImpl.fromConfig(Config(fileParsed: {})), + () => BuildConfigImpl.fromJson({}), throwsA(predicate( (e) => e is FormatException && e.message.contains( - 'No value was provided for required key: build_mode', + 'No value was provided for required key: target_os', ), )), ); expect( - () => BuildConfigImpl.fromConfig(Config(fileParsed: { - 'version': BuildConfigImpl.latestVersion.toString(), + () => BuildConfigImpl.fromJson({ + 'version': HookConfigImpl.latestVersion.toString(), 'package_name': packageName, 'package_root': packageRootUri.toFilePath(), 'target_architecture': 'arm64', 'target_os': 'android', 'target_android_ndk_api': 30, 'link_mode_preference': 'prefer-static', - })), + }), throwsA(predicate( (e) => e is FormatException && @@ -394,8 +349,8 @@ version: 1.0.0'''; )), ); expect( - () => BuildConfigImpl.fromConfig(Config(fileParsed: { - 'version': BuildConfigImpl.latestVersion.toString(), + () => BuildConfigImpl.fromJson({ + 'version': HookConfigImpl.latestVersion.toString(), 'out_dir': outDirUri.toFilePath(), 'package_name': packageName, 'package_root': packageRootUri.toFilePath(), @@ -403,11 +358,12 @@ version: 1.0.0'''; 'target_os': 'android', 'target_android_ndk_api': 30, 'link_mode_preference': 'prefer-static', + 'build_mode': BuildModeImpl.release.name, 'dependency_metadata': { 'bar': {'key': 'value'}, 'foo': [], }, - })), + }), throwsA(predicate( (e) => e is FormatException && @@ -418,15 +374,16 @@ version: 1.0.0'''; )), ); expect( - () => BuildConfigImpl.fromConfig(Config(fileParsed: { + () => BuildConfigImpl.fromJson({ 'out_dir': outDirUri.toFilePath(), - 'version': BuildConfigImpl.latestVersion.toString(), + 'version': HookConfigImpl.latestVersion.toString(), 'package_name': packageName, 'package_root': packageRootUri.toFilePath(), 'target_architecture': 'arm64', 'target_os': 'android', 'link_mode_preference': 'prefer-static', - })), + 'build_mode': BuildModeImpl.release.name, + }), throwsA(predicate( (e) => e is FormatException && @@ -437,22 +394,9 @@ version: 1.0.0'''; ); }); - test('FormatExceptions contain full stack trace of wrapped exception', () { - try { - BuildConfigImpl.fromConfig(Config(fileParsed: { - 'out_dir': outDirUri.toFilePath(), - 'package_root': packageRootUri.toFilePath(), - 'target': [1, 2, 3, 4, 5], - 'link_mode_preference': 'prefer-static', - })); - } on FormatException catch (e) { - expect(e.toString(), stringContainsInOrder(['Config.string'])); - } - }); - test('BuildConfig toString', () { final config = BuildConfigImpl( - outDir: outDirUri, + outputDirectory: outDirUri, packageName: packageName, packageRoot: tempUri, targetArchitecture: ArchitectureImpl.arm64, @@ -470,7 +414,7 @@ version: 1.0.0'''; test('BuildConfig fromArgs', () async { final buildConfig = BuildConfigImpl( - outDir: outDirUri, + outputDirectory: outDirUri, packageName: packageName, packageRoot: tempUri, targetArchitecture: ArchitectureImpl.arm64, @@ -490,37 +434,9 @@ version: 1.0.0'''; expect(buildConfig2, buildConfig); }); - test('dependency metadata via config accessor', () { - final buildConfig1 = BuildConfigImpl( - outDir: outDirUri, - packageName: packageName, - packageRoot: tempUri, - targetArchitecture: ArchitectureImpl.arm64, - targetOS: OSImpl.android, - targetAndroidNdkApi: 30, - buildMode: BuildModeImpl.release, - linkModePreference: LinkModePreferenceImpl.preferStatic, - dependencyMetadata: { - 'bar': const Metadata({ - 'key': {'key2': 'value'}, - }), - }, - ); - // Useful for doing `path(..., exists: true)`. - expect( - buildConfig1.config.string([ - BuildConfigImpl.dependencyMetadataConfigKey, - 'bar', - 'key', - 'key2' - ].join('.')), - 'value', - ); - }); - test('envScript', () { final buildConfig1 = BuildConfigImpl( - outDir: outDirUri, + outputDirectory: outDirUri, packageName: packageName, packageRoot: packageRootUri, targetArchitecture: ArchitectureImpl.x64, @@ -535,87 +451,37 @@ version: 1.0.0'''; ); final configFile = buildConfig1.toJson(); - final config = Config(fileParsed: configFile); - final fromConfig = BuildConfigImpl.fromConfig(config); + final fromConfig = BuildConfigImpl.fromJson(configFile); expect(fromConfig, equals(buildConfig1)); }); for (final version in ['9001.0.0', '0.0.1']) { test('BuildConfig version $version', () { final outDir = outDirUri; - final config = Config(fileParsed: { + final config = { 'link_mode_preference': 'prefer-static', 'out_dir': outDir.toFilePath(), 'package_root': tempUri.toFilePath(), 'target_os': 'linux', - 'target_architecture': 'x64', 'version': version, - }); + 'package_name': packageName, + 'dry_run': true, + }; expect( - () => BuildConfigImpl.fromConfig(config), + () => BuildConfigImpl.fromJson(config), throwsA(predicate( (e) => e is FormatException && e.message.contains(version) && - e.message.contains(BuildConfigImpl.latestVersion.toString()), + e.message.contains(HookConfigImpl.latestVersion.toString()), )), ); }); } - test('checksum', () async { - await inTempDir((tempUri) async { - final nativeAddUri = tempUri.resolve('native_add/'); - final fakeClangUri = tempUri.resolve('fake_clang'); - await File.fromUri(fakeClangUri).create(); - - final name1 = BuildConfigImpl.checksum( - packageName: packageName, - packageRoot: nativeAddUri, - targetArchitecture: ArchitectureImpl.x64, - targetOS: OSImpl.linux, - buildMode: BuildModeImpl.release, - linkModePreference: LinkModePreferenceImpl.dynamic, - supportedAssetTypes: [NativeCodeAsset.type], - ); - - // Using the checksum for a build folder should be stable. - expect(name1, '6723f3af2ba4cd70660494965ac55c2a'); - - // Build folder different due to metadata. - final name2 = BuildConfigImpl.checksum( - packageName: packageName, - packageRoot: nativeAddUri, - targetArchitecture: ArchitectureImpl.x64, - targetOS: OSImpl.linux, - buildMode: BuildModeImpl.release, - linkModePreference: LinkModePreferenceImpl.dynamic, - dependencyMetadata: { - 'foo': const Metadata({'key': 'value'}) - }, - ); - printOnFailure([name1, name2].toString()); - expect(name1 != name2, true); - - // Build folder different due to cc. - final name3 = BuildConfigImpl.checksum( - packageName: packageName, - packageRoot: nativeAddUri, - targetArchitecture: ArchitectureImpl.x64, - targetOS: OSImpl.linux, - buildMode: BuildModeImpl.release, - linkModePreference: LinkModePreferenceImpl.dynamic, - cCompiler: CCompilerConfigImpl( - compiler: fakeClangUri, - )); - printOnFailure([name1, name3].toString()); - expect(name1 != name3, true); - }); - }); - test('BuildConfig invalid target os architecture combination', () { final outDir = outDirUri; - final config = Config(fileParsed: { + final config = { 'link_mode_preference': 'prefer-static', 'out_dir': outDir.toFilePath(), 'package_name': packageName, @@ -623,10 +489,10 @@ version: 1.0.0'''; 'target_os': 'windows', 'target_architecture': 'arm', 'build_mode': 'debug', - 'version': BuildConfigImpl.latestVersion.toString(), - }); + 'version': HookConfigImpl.latestVersion.toString(), + }; expect( - () => BuildConfigImpl.fromConfig(config), + () => BuildConfigImpl.fromJson(config), throwsA(predicate( (e) => e is FormatException && e.message.contains('arm'), )), @@ -635,7 +501,7 @@ version: 1.0.0'''; test('BuildConfig dry_run access invalid args', () { final outDir = outDirUri; - final config = Config(fileParsed: { + final config = { 'link_mode_preference': 'prefer-static', 'out_dir': outDir.toFilePath(), 'package_name': packageName, @@ -644,10 +510,10 @@ version: 1.0.0'''; 'target_architecture': 'arm64', 'build_mode': 'debug', 'dry_run': true, - 'version': BuildConfigImpl.latestVersion.toString(), - }); + 'version': HookConfigImpl.latestVersion.toString(), + }; expect( - () => BuildConfigImpl.fromConfig(config), + () => BuildConfigImpl.fromJson(config), throwsA(predicate( (e) => e is FormatException && e.message.contains('In Flutter projects'), @@ -657,16 +523,16 @@ version: 1.0.0'''; test('BuildConfig dry_run access invalid args', () { final outDir = outDirUri; - final config = Config(fileParsed: { + final config = { 'link_mode_preference': 'prefer-static', 'out_dir': outDir.toFilePath(), 'package_name': packageName, 'package_root': tempUri.toFilePath(), 'target_os': 'android', 'dry_run': true, - 'version': BuildConfigImpl.latestVersion.toString(), - }); - final buildConfig = BuildConfigImpl.fromConfig(config); + 'version': HookConfigImpl.latestVersion.toString(), + }; + final buildConfig = BuildConfigImpl.fromJson(config); expect( () => buildConfig.targetAndroidNdkApi, throwsA(predicate( @@ -677,23 +543,23 @@ version: 1.0.0'''; test('BuildConfig dry_run target arch', () { final outDir = outDirUri; - final config = Config(fileParsed: { + final config = { 'link_mode_preference': 'prefer-static', 'out_dir': outDir.toFilePath(), 'package_name': packageName, 'package_root': tempUri.toFilePath(), 'target_os': 'windows', 'dry_run': true, - 'version': BuildConfigImpl.latestVersion.toString(), - }); - final buildConfig = BuildConfigImpl.fromConfig(config); + 'version': HookConfigImpl.latestVersion.toString(), + }; + final buildConfig = BuildConfigImpl.fromJson(config); expect(buildConfig.targetArchitecture, isNull); }); test('BuildConfig dry_run toString', () { final buildConfig = BuildConfigImpl.dryRun( packageName: packageName, - outDir: outDirUri, + outputDirectory: outDirUri, packageRoot: tempUri, targetOS: OSImpl.windows, linkModePreference: LinkModePreferenceImpl.dynamic, @@ -703,7 +569,7 @@ version: 1.0.0'''; }); test('invalid architecture', () { - final config = Config(fileParsed: { + final config = { 'build_mode': 'release', 'dry_run': false, 'link_mode_preference': 'prefer-static', @@ -713,10 +579,10 @@ version: 1.0.0'''; 'target_android_ndk_api': 30, 'target_architecture': 'invalid_architecture', 'target_os': 'android', - 'version': BuildOutputImpl.latestVersion.toString(), - }); + 'version': HookOutputImpl.latestVersion.toString(), + }; expect( - () => BuildConfigImpl.fromConfig(config), + () => BuildConfigImpl.fromJson(config), throwsFormatException, ); }); diff --git a/pkgs/native_assets_cli/test/model/build_output_test.dart b/pkgs/native_assets_cli/test/model/build_output_test.dart index 0472bf039..0a2aa3089 100644 --- a/pkgs/native_assets_cli/test/model/build_output_test.dart +++ b/pkgs/native_assets_cli/test/model/build_output_test.dart @@ -7,7 +7,6 @@ import 'dart:io'; import 'package:native_assets_cli/native_assets_cli_internal.dart'; import 'package:pub_semver/pub_semver.dart'; import 'package:test/test.dart'; - import '../helpers.dart'; void main() { @@ -21,44 +20,7 @@ void main() { await Directory.fromUri(tempUri).delete(recursive: true); }); - final buildOutput = BuildOutputImpl( - timestamp: DateTime.parse('2022-11-10 13:25:01.000'), - assets: [ - NativeCodeAssetImpl( - id: 'package:my_package/foo', - file: Uri(path: 'path/to/libfoo.so'), - linkMode: DynamicLoadingBundledImpl(), - os: OSImpl.android, - architecture: ArchitectureImpl.x64, - ), - NativeCodeAssetImpl( - id: 'package:my_package/foo2', - linkMode: DynamicLoadingSystemImpl(Uri(path: 'path/to/libfoo2.so')), - os: OSImpl.android, - architecture: ArchitectureImpl.x64, - ), - NativeCodeAssetImpl( - id: 'package:my_package/foo3', - linkMode: LookupInProcessImpl(), - os: OSImpl.android, - architecture: ArchitectureImpl.x64, - ), - NativeCodeAssetImpl( - id: 'package:my_package/foo4', - linkMode: LookupInExecutableImpl(), - os: OSImpl.android, - architecture: ArchitectureImpl.x64, - ), - ], - dependencies: Dependencies([ - Uri.file('path/to/file.ext'), - ]), - metadata: const Metadata({ - 'key': 'value', - }), - ); - - final dryRunOutput = BuildOutputImpl( + final dryRunOutput = HookOutputImpl( timestamp: DateTime.parse('2022-11-10 13:25:01.000'), assets: [ NativeCodeAssetImpl( @@ -132,134 +94,107 @@ assets: path_type: absolute uri: path/to/libfoo.so target: android_riscv64 -metadata: {} version: 1.0.0'''; - final yamlEncoding = '''timestamp: 2022-11-10 13:25:01.000 -assets: - - architecture: x64 - file: path/to/libfoo.so - id: package:my_package/foo - link_mode: - type: dynamic_loading_bundle - os: android - type: native_code - - architecture: x64 - id: package:my_package/foo2 - link_mode: - type: dynamic_loading_system - uri: path/to/libfoo2.so - os: android - type: native_code - - architecture: x64 - id: package:my_package/foo3 - link_mode: - type: dynamic_loading_process - os: android - type: native_code - - architecture: x64 - id: package:my_package/foo4 - link_mode: - type: dynamic_loading_executable - os: android - type: native_code -dependencies: - - path/to/file.ext -metadata: - key: value -version: ${BuildOutputImpl.latestVersion}'''; - - final jsonEncoding = '''{ - "timestamp": "2022-11-10 13:25:01.000", - "assets": [ - { - "architecture": "x64", - "file": "path/to/libfoo.so", - "id": "package:my_package/foo", - "link_mode": { - "type": "dynamic_loading_bundle" + final jsonEncoding = { + 'timestamp': '2022-11-10 13:25:01.000', + 'assets': [ + { + 'architecture': 'x64', + 'file': Uri.file('path/to/libfoo.so').toFilePath(), + 'id': 'package:my_package/foo', + 'link_mode': {'type': 'dynamic_loading_bundle'}, + 'os': 'android', + 'type': 'native_code' }, - "os": "android", - "type": "native_code" - }, - { - "architecture": "x64", - "id": "package:my_package/foo2", - "link_mode": { - "type": "dynamic_loading_system", - "uri": "path/to/libfoo2.so" + { + 'architecture': 'x64', + 'id': 'package:my_package/foo2', + 'link_mode': { + 'type': 'dynamic_loading_system', + 'uri': Uri.file('path/to/libfoo2.so').toFilePath(), + }, + 'os': 'android', + 'type': 'native_code' }, - "os": "android", - "type": "native_code" - }, - { - "architecture": "x64", - "id": "package:my_package/foo3", - "link_mode": { - "type": "dynamic_loading_process" + { + 'architecture': 'x64', + 'id': 'package:my_package/foo3', + 'link_mode': {'type': 'dynamic_loading_process'}, + 'os': 'android', + 'type': 'native_code' }, - "os": "android", - "type": "native_code" + { + 'architecture': 'x64', + 'id': 'package:my_package/foo4', + 'link_mode': {'type': 'dynamic_loading_executable'}, + 'os': 'android', + 'type': 'native_code' + } + ], + 'assetsForLinking': { + 'my_package': [ + { + 'name': 'data', + 'package': 'my_package', + 'file': Uri.file('path/to/data').toFilePath(), + 'type': 'data' + } + ], + 'my_package_2': [ + { + 'name': 'data', + 'package': 'my_package', + 'file': Uri.file('path/to/data2').toFilePath(), + 'type': 'data' + } + ] }, - { - "architecture": "x64", - "id": "package:my_package/foo4", - "link_mode": { - "type": "dynamic_loading_executable" - }, - "os": "android", - "type": "native_code" - } - ], - "dependencies": [ - "path/to/file.ext" - ], - "metadata": { - "key": "value" - }, - "version": "${BuildOutputImpl.latestVersion}" -}'''; - - test('built info yaml', () { - final yaml = yamlEncode(buildOutput.toJson(BuildOutputImpl.latestVersion)) - .replaceAll('\\', '/'); - expect(yaml, yamlEncoding); + 'dependencies': [ + Uri.file('path/to/file.ext').toFilePath(), + ], + 'metadata': {'key': 'value'}, + 'version': '${HookOutputImpl.latestVersion}' + }; - final json = buildOutput - .toJsonString(BuildOutputImpl.latestVersion) - .replaceAll('\\\\', '/'); + test('built info json', () { + final buildOutput = getBuildOutput(); + final json = buildOutput.toJson(HookOutputImpl.latestVersion); expect(json, jsonEncoding); - final buildOutput2 = BuildOutputImpl.fromJsonString(yaml); + final buildOutput2 = HookOutputImpl.fromJson(json); expect(buildOutput.hashCode, buildOutput2.hashCode); expect(buildOutput, buildOutput2); }); test('built info yaml v1.0.0 parsing keeps working', () { - final buildOutput2 = BuildOutputImpl.fromJsonString(yamlEncodingV1_0_0); + final buildOutput = getBuildOutput(withLinkedAssets: false); + final buildOutput2 = HookOutputImpl.fromJsonString(yamlEncodingV1_0_0); expect(buildOutput.hashCode, buildOutput2.hashCode); expect(buildOutput, buildOutput2); }); test('built info yaml v1.0.0 serialization keeps working', () { + final buildOutput = getBuildOutput(withLinkedAssets: false); final yamlEncoding = - yamlEncode(buildOutput.toJson(Version(1, 0, 0))).replaceAll('\\', '/'); + yamlEncode(buildOutput.toJson(Version(1, 0, 0))).unescape(); expect(yamlEncoding, yamlEncodingV1_0_0); }); test('built info yaml v1.0.0 serialization keeps working dry run', () { final yamlEncoding = - yamlEncode(dryRunOutput.toJson(Version(1, 0, 0))).replaceAll('\\', '/'); + yamlEncode(dryRunOutput.toJson(Version(1, 0, 0))).unescape(); expect(yamlEncoding, yamlEncodingV1_0_0dryRun); }); - test('BuildOutput.toString', buildOutput.toString); + test('BuildOutput.toString', getBuildOutput().toString); test('BuildOutput.hashCode', () { - final buildOutput2 = BuildOutputImpl.fromJsonString(yamlEncoding); + final buildOutput = getBuildOutput(); + final buildOutput2 = HookOutputImpl.fromJson(jsonEncoding); expect(buildOutput.hashCode, buildOutput2.hashCode); - final buildOutput3 = BuildOutputImpl( + final buildOutput3 = HookOutputImpl( timestamp: DateTime.parse('2022-11-10 13:25:01.000'), ); expect(buildOutput.hashCode != buildOutput3.hashCode, true); @@ -271,7 +206,7 @@ version: ${BuildOutputImpl.latestVersion}'''; await Directory.fromUri(outDir).create(); await Directory.fromUri(packageRoot).create(); final config = BuildConfigImpl( - outDir: outDir, + outputDirectory: outDir, packageName: 'dontcare', packageRoot: packageRoot, buildMode: BuildModeImpl.debug, @@ -279,8 +214,9 @@ version: ${BuildOutputImpl.latestVersion}'''; targetOS: OSImpl.macOS, linkModePreference: LinkModePreferenceImpl.dynamic, ); + final buildOutput = getBuildOutput(); await buildOutput.writeToFile(config: config); - final buildOutput2 = await BuildOutputImpl.readFromFile(outDir: outDir); + final buildOutput2 = HookOutputImpl.readFromFile(file: config.outputFile); expect(buildOutput2, buildOutput); }); @@ -290,7 +226,7 @@ version: ${BuildOutputImpl.latestVersion}'''; await Directory.fromUri(outDir).create(); await Directory.fromUri(packageRoot).create(); final config = BuildConfigImpl( - outDir: outDir, + outputDirectory: outDir, packageName: 'dontcare', packageRoot: packageRoot, buildMode: BuildModeImpl.debug, @@ -299,13 +235,14 @@ version: ${BuildOutputImpl.latestVersion}'''; linkModePreference: LinkModePreferenceImpl.dynamic, version: Version(1, 1, 0), ); + final buildOutput = getBuildOutput(withLinkedAssets: false); await buildOutput.writeToFile(config: config); - final buildOutput2 = await BuildOutputImpl.readFromFile(outDir: outDir); + final buildOutput2 = HookOutputImpl.readFromFile(file: config.outputFile); expect(buildOutput2, buildOutput); }); test('Round timestamp', () { - final buildOutput3 = BuildOutputImpl( + final buildOutput3 = HookOutputImpl( timestamp: DateTime.parse('2022-11-10 13:25:01.372257'), ); expect(buildOutput3.timestamp, DateTime.parse('2022-11-10 13:25:01.000')); @@ -314,12 +251,12 @@ version: ${BuildOutputImpl.latestVersion}'''; for (final version in ['9001.0.0', '0.0.1']) { test('BuildOutput version $version', () { expect( - () => BuildOutputImpl.fromJsonString('version: $version'), + () => HookOutputImpl.fromJsonString('version: $version'), throwsA(predicate( (e) => e is FormatException && e.message.contains(version) && - e.message.contains(BuildOutputImpl.latestVersion.toString()), + e.message.contains(HookOutputImpl.latestVersion.toString()), )), ); }); @@ -327,7 +264,7 @@ version: ${BuildOutputImpl.latestVersion}'''; test('format exception', () { expect( - () => BuildOutputImpl.fromJsonString('''timestamp: 2022-11-10 13:25:01.000 + () => HookOutputImpl.fromJsonString('''timestamp: 2022-11-10 13:25:01.000 assets: - name: foo link_mode: dynamic @@ -343,7 +280,7 @@ version: 1.0.0'''), throwsFormatException, ); expect( - () => BuildOutputImpl.fromJsonString('''timestamp: 2022-11-10 13:25:01.000 + () => HookOutputImpl.fromJsonString('''timestamp: 2022-11-10 13:25:01.000 assets: - name: foo link_mode: dynamic @@ -359,7 +296,7 @@ version: 1.0.0'''), throwsFormatException, ); expect( - () => BuildOutputImpl.fromJsonString('''timestamp: 2022-11-10 13:25:01.000 + () => HookOutputImpl.fromJsonString('''timestamp: 2022-11-10 13:25:01.000 assets: - name: foo link_mode: dynamic @@ -376,7 +313,7 @@ version: 1.0.0'''), }); test('BuildOutput dependencies can be modified', () { - final buildOutput = BuildOutputImpl(); + final buildOutput = HookOutputImpl(); expect( () => buildOutput.addDependencies([Uri.file('path/to/file.ext')]), returnsNormally, @@ -384,7 +321,7 @@ version: 1.0.0'''), }); test('BuildOutput setters', () { - final buildOutput = BuildOutputImpl( + final buildOutput = HookOutputImpl( timestamp: DateTime.parse('2022-11-10 13:25:01.000'), assets: [ NativeCodeAssetImpl( @@ -411,7 +348,7 @@ version: 1.0.0'''), }), ); - final buildOutput2 = BuildOutputImpl( + final buildOutput2 = HookOutputImpl( timestamp: DateTime.parse('2022-11-10 13:25:01.000'), ); buildOutput2.addAsset( @@ -448,3 +385,58 @@ version: 1.0.0'''), expect(buildOutput2.metadataModel, equals(buildOutput.metadataModel)); }); } + +HookOutputImpl getBuildOutput({bool withLinkedAssets = true}) => HookOutputImpl( + timestamp: DateTime.parse('2022-11-10 13:25:01.000'), + assets: [ + NativeCodeAssetImpl( + id: 'package:my_package/foo', + file: Uri(path: 'path/to/libfoo.so'), + linkMode: DynamicLoadingBundledImpl(), + os: OSImpl.android, + architecture: ArchitectureImpl.x64, + ), + NativeCodeAssetImpl( + id: 'package:my_package/foo2', + linkMode: DynamicLoadingSystemImpl(Uri(path: 'path/to/libfoo2.so')), + os: OSImpl.android, + architecture: ArchitectureImpl.x64, + ), + NativeCodeAssetImpl( + id: 'package:my_package/foo3', + linkMode: LookupInProcessImpl(), + os: OSImpl.android, + architecture: ArchitectureImpl.x64, + ), + NativeCodeAssetImpl( + id: 'package:my_package/foo4', + linkMode: LookupInExecutableImpl(), + os: OSImpl.android, + architecture: ArchitectureImpl.x64, + ), + ], + assetsForLinking: withLinkedAssets + ? { + 'my_package': [ + DataAssetImpl( + file: Uri.file('path/to/data'), + name: 'data', + package: 'my_package', + ) + ], + 'my_package_2': [ + DataAssetImpl( + file: Uri.file('path/to/data2'), + name: 'data', + package: 'my_package', + ) + ] + } + : null, + dependencies: Dependencies([ + Uri.file('path/to/file.ext'), + ]), + metadata: const Metadata({ + 'key': 'value', + }), + ); diff --git a/pkgs/native_assets_cli/test/model/checksum_test.dart b/pkgs/native_assets_cli/test/model/checksum_test.dart new file mode 100644 index 000000000..0aa92f235 --- /dev/null +++ b/pkgs/native_assets_cli/test/model/checksum_test.dart @@ -0,0 +1,84 @@ +import 'dart:io'; + +import 'package:native_assets_cli/native_assets_cli_internal.dart'; +import 'package:native_assets_cli/src/api/asset.dart'; +import 'package:test/test.dart'; + +import '../helpers.dart'; + +void main() { + const packageName = 'my_package'; + test('checksum', () async { + await inTempDir((tempUri) async { + final nativeAddUri = tempUri.resolve('native_add/'); + final fakeClangUri = tempUri.resolve('fake_clang'); + await File.fromUri(fakeClangUri).create(); + + final name1 = HookConfigImpl.checksum( + packageName: packageName, + packageRoot: nativeAddUri, + targetArchitecture: ArchitectureImpl.x64, + targetOS: OSImpl.linux, + buildMode: BuildModeImpl.release, + linkModePreference: LinkModePreferenceImpl.dynamic, + supportedAssetTypes: [NativeCodeAsset.type], + hook: Hook.build, + version: HookConfigImpl.latestVersion, + ); + + // Using the checksum for a build folder should be stable. + expect(name1, 'b6170f6f00000d3766b01cea7637b607'); + + // Build folder different due to metadata. + final name2 = HookConfigImpl.checksum( + packageName: packageName, + packageRoot: nativeAddUri, + targetArchitecture: ArchitectureImpl.x64, + targetOS: OSImpl.linux, + buildMode: BuildModeImpl.release, + linkModePreference: LinkModePreferenceImpl.dynamic, + dependencyMetadata: { + 'foo': const Metadata({'key': 'value'}) + }, + hook: Hook.build, + version: HookConfigImpl.latestVersion, + ); + printOnFailure([name1, name2].toString()); + expect(name1 != name2, true); + + // Build folder different due to cc. + final name3 = HookConfigImpl.checksum( + packageName: packageName, + packageRoot: nativeAddUri, + targetArchitecture: ArchitectureImpl.x64, + targetOS: OSImpl.linux, + buildMode: BuildModeImpl.release, + linkModePreference: LinkModePreferenceImpl.dynamic, + cCompiler: CCompilerConfigImpl( + compiler: fakeClangUri, + ), + hook: Hook.build, + version: HookConfigImpl.latestVersion, + ); + printOnFailure([name1, name3].toString()); + expect(name1 != name3, true); + + // Build folder different due to hook. + final name4 = HookConfigImpl.checksum( + packageName: packageName, + packageRoot: nativeAddUri, + targetArchitecture: ArchitectureImpl.x64, + targetOS: OSImpl.linux, + buildMode: BuildModeImpl.release, + linkModePreference: LinkModePreferenceImpl.dynamic, + cCompiler: CCompilerConfigImpl( + compiler: fakeClangUri, + ), + hook: Hook.link, + version: HookConfigImpl.latestVersion, + ); + printOnFailure([name1, name4].toString()); + expect(name1 != name4, true); + }); + }); +} diff --git a/pkgs/native_assets_cli/test/model/link_config_test.dart b/pkgs/native_assets_cli/test/model/link_config_test.dart new file mode 100644 index 000000000..e11b0e592 --- /dev/null +++ b/pkgs/native_assets_cli/test/model/link_config_test.dart @@ -0,0 +1,476 @@ +// 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:io'; + +import 'package:native_assets_cli/native_assets_cli.dart'; +import 'package:native_assets_cli/native_assets_cli_internal.dart'; +import 'package:native_assets_cli/src/api/asset.dart'; +import 'package:test/test.dart'; + +void main() async { + late Uri tempUri; + late Uri outDirUri; + late Uri outDir2Uri; + const packageName = 'my_package'; + late Uri packageRootUri; + late Uri fakeClang; + late Uri fakeLd; + late Uri fakeAr; + late Uri fakeCl; + late Uri fakeVcVars; + late Uri resources; + final assets = [ + DataAsset( + package: packageName, + name: 'name', + file: Uri.file('nonexistent'), + ), + NativeCodeAsset( + package: packageName, + name: 'name2', + linkMode: DynamicLoadingBundled(), + os: OS.android, + file: Uri.file('not there'), + architecture: Architecture.riscv64, + ) + ].cast(); + + setUp(() async { + tempUri = (await Directory.systemTemp.createTemp()).uri; + outDirUri = tempUri.resolve('out1/'); + await Directory.fromUri(outDirUri).create(); + outDir2Uri = tempUri.resolve('out2/'); + await Directory.fromUri(outDir2Uri).create(); + packageRootUri = tempUri.resolve('$packageName/'); + await Directory.fromUri(packageRootUri).create(); + fakeClang = tempUri.resolve('fake_clang'); + await File.fromUri(fakeClang).create(); + fakeLd = tempUri.resolve('fake_ld'); + await File.fromUri(fakeLd).create(); + fakeAr = tempUri.resolve('fake_ar'); + await File.fromUri(fakeAr).create(); + fakeCl = tempUri.resolve('cl.exe'); + await File.fromUri(fakeCl).create(); + fakeVcVars = tempUri.resolve('vcvarsall.bat'); + await File.fromUri(fakeVcVars).create(); + resources = tempUri.resolve('resources.json'); + File.fromUri(resources).createSync(); + }); + + tearDown(() async { + await Directory.fromUri(tempUri).delete(recursive: true); + }); + + test('LinkConfig ==', () { + final config1 = LinkConfigImpl( + outputDirectory: outDirUri, + packageName: packageName, + packageRoot: tempUri, + targetArchitecture: ArchitectureImpl.arm64, + targetOS: OSImpl.iOS, + targetIOSSdk: IOSSdkImpl.iPhoneOS, + cCompiler: CCompilerConfigImpl( + compiler: fakeClang, + linker: fakeLd, + archiver: fakeAr, + ), + buildMode: BuildModeImpl.release, + assets: assets, + resourceIdentifierUri: resources, + linkModePreference: LinkModePreferenceImpl.preferStatic, + ); + + final config2 = LinkConfigImpl( + outputDirectory: outDir2Uri, + packageName: packageName, + packageRoot: tempUri, + targetArchitecture: ArchitectureImpl.arm64, + targetOS: OSImpl.android, + targetAndroidNdkApi: 30, + buildMode: BuildModeImpl.release, + assets: [], + resourceIdentifierUri: null, + linkModePreference: LinkModePreferenceImpl.preferStatic, + ); + + expect(config1, equals(config1)); + expect(config1 == config2, false); + expect(config1.outputDirectory != config2.outputDirectory, true); + expect(config1.packageRoot, config2.packageRoot); + expect(config1.targetArchitecture == config2.targetArchitecture, true); + expect(config1.targetOS != config2.targetOS, true); + expect(config1.targetIOSSdk, IOSSdkImpl.iPhoneOS); + expect(() => config2.targetIOSSdk, throwsStateError); + expect(config1.cCompiler.compiler != config2.cCompiler.compiler, true); + expect(config1.cCompiler.linker != config2.cCompiler.linker, true); + expect(config1.cCompiler.archiver != config2.cCompiler.archiver, true); + expect(config1.cCompiler.envScript == config2.cCompiler.envScript, true); + expect(config1.cCompiler.envScriptArgs == config2.cCompiler.envScriptArgs, + true); + expect(config1.cCompiler != config2.cCompiler, true); + expect(config1.assets != config2.assets, true); + }); + + test('LinkConfig fromConfig', () { + final buildConfig2 = LinkConfigImpl( + outputDirectory: outDirUri, + packageName: packageName, + packageRoot: packageRootUri, + targetArchitecture: ArchitectureImpl.arm64, + targetOS: OSImpl.android, + targetAndroidNdkApi: 30, + buildMode: BuildModeImpl.release, + assets: assets, + linkModePreference: LinkModePreferenceImpl.preferStatic, + ); + + final config = { + 'build_mode': 'release', + 'dry_run': false, + 'link_mode_preference': 'prefer-static', + 'out_dir': outDirUri.toFilePath(), + 'package_name': packageName, + 'package_root': packageRootUri.toFilePath(), + 'target_android_ndk_api': 30, + 'target_architecture': 'arm64', + 'target_os': 'android', + 'version': HookOutputImpl.latestVersion.toString(), + 'assets': AssetImpl.listToJson(assets, HookOutputImpl.latestVersion), + }; + + final fromConfig = LinkConfigImpl.fromJson(config); + expect(fromConfig, equals(buildConfig2)); + }); + + test('LinkConfig.dryRun', () { + final buildConfig2 = LinkConfigImpl.dryRun( + outputDirectory: outDirUri, + packageName: packageName, + packageRoot: packageRootUri, + targetOS: OSImpl.android, + assets: [], + linkModePreference: LinkModePreferenceImpl.preferStatic, + ); + + final config = { + 'dry_run': true, + 'link_mode_preference': 'prefer-static', + 'out_dir': outDirUri.toFilePath(), + 'package_name': packageName, + 'package_root': packageRootUri.toFilePath(), + 'target_os': 'android', + 'version': HookOutputImpl.latestVersion.toString(), + 'assets': [], + }; + + final fromConfig = LinkConfigImpl.fromJson(config); + expect(fromConfig, equals(buildConfig2)); + }); + + test('LinkConfig toJson fromConfig', () { + final buildConfig1 = LinkConfigImpl( + outputDirectory: outDirUri, + packageName: packageName, + packageRoot: packageRootUri, + targetArchitecture: ArchitectureImpl.arm64, + targetOS: OSImpl.iOS, + targetIOSSdk: IOSSdkImpl.iPhoneOS, + cCompiler: CCompilerConfigImpl( + compiler: fakeClang, + linker: fakeLd, + ), + buildMode: BuildModeImpl.release, + assets: assets, + linkModePreference: LinkModePreferenceImpl.preferStatic, + ); + + final configFile = buildConfig1.toJson(); + final fromConfig = LinkConfigImpl.fromJson(configFile); + expect(fromConfig, equals(buildConfig1)); + }); + + test('LinkConfig toJson fromJson', () { + final outDir = outDirUri; + final buildConfig1 = LinkConfigImpl( + outputDirectory: outDir, + packageName: packageName, + packageRoot: tempUri, + targetArchitecture: ArchitectureImpl.arm64, + targetOS: OSImpl.iOS, + targetIOSSdk: IOSSdkImpl.iPhoneOS, + cCompiler: CCompilerConfigImpl( + compiler: fakeClang, + linker: fakeLd, + ), + buildMode: BuildModeImpl.release, + assets: assets, + linkModePreference: LinkModePreferenceImpl.preferStatic, + ); + + final jsonObject = buildConfig1.toJson(); + final expectedJson = { + 'assets': AssetImpl.listToJson(assets, HookConfigImpl.latestVersion), + 'build_mode': 'release', + 'c_compiler': {'cc': fakeClang.toFilePath(), 'ld': fakeLd.toFilePath()}, + 'out_dir': outDir.toFilePath(), + 'package_name': packageName, + 'package_root': tempUri.toFilePath(), + 'supported_asset_types': [NativeCodeAsset.type], + 'target_architecture': 'arm64', + 'target_ios_sdk': 'iphoneos', + 'target_os': 'ios', + 'version': '${HookConfigImpl.latestVersion}', + 'link_mode_preference': 'prefer-static', + }; + expect(jsonObject, equals(expectedJson)); + + final buildConfig2 = LinkConfigImpl.fromJson(jsonObject); + expect(buildConfig2, buildConfig1); + }); + + test('LinkConfig FormatExceptions', () { + expect( + () => LinkConfigImpl.fromJson({}), + throwsA(predicate( + (e) => + e is FormatException && + e.message + .contains('No value was provided for required key: target_os'), + )), + ); + expect( + () => LinkConfigImpl.fromJson({ + 'version': HookConfigImpl.latestVersion.toString(), + 'package_name': packageName, + 'package_root': packageRootUri.toFilePath(), + 'target_architecture': 'arm64', + 'target_os': 'android', + 'target_android_ndk_api': 30, + 'assets': [], + }), + throwsA(predicate( + (e) => + e is FormatException && + e.message.contains( + 'No value was provided for required key: out_dir', + ), + )), + ); + expect( + () => LinkConfigImpl.fromJson({ + 'version': HookConfigImpl.latestVersion.toString(), + 'out_dir': outDirUri.toFilePath(), + 'package_name': packageName, + 'package_root': packageRootUri.toFilePath(), + 'target_architecture': 'arm64', + 'target_os': 'android', + 'target_android_ndk_api': 30, + 'build_mode': BuildModeImpl.release.name, + 'assets': 'astring', + 'link_mode_preference': LinkModePreferenceImpl.preferStatic.name, + }), + throwsA(predicate( + (e) => + e is FormatException && + e.message.contains( + "Unexpected value 'astring' for key '.assets' in config file. " + 'Expected a List?.', + ), + )), + ); + expect( + () => LinkConfigImpl.fromJson({ + 'out_dir': outDirUri.toFilePath(), + 'version': HookConfigImpl.latestVersion.toString(), + 'package_name': packageName, + 'package_root': packageRootUri.toFilePath(), + 'target_architecture': 'arm64', + 'target_os': 'android', + 'build_mode': BuildModeImpl.release.name, + 'link_mode_preference': LinkModePreferenceImpl.preferStatic.name, + }), + throwsA(predicate( + (e) => + e is FormatException && + e.message.contains( + 'No value was provided for required key: target_android_ndk_api', + ), + )), + ); + }); + + test('LinkConfig toString', () { + final config = LinkConfigImpl( + outputDirectory: outDirUri, + packageName: packageName, + packageRoot: tempUri, + targetArchitecture: ArchitectureImpl.arm64, + targetOS: OSImpl.iOS, + targetIOSSdk: IOSSdkImpl.iPhoneOS, + cCompiler: CCompilerConfigImpl( + compiler: fakeClang, + linker: fakeLd, + ), + buildMode: BuildModeImpl.release, + assets: assets, + linkModePreference: LinkModePreferenceImpl.preferStatic, + ); + expect(config.toString(), isNotEmpty); + }); + + test('LinkConfig fromArgs', () async { + final buildConfig = LinkConfigImpl( + outputDirectory: outDirUri, + packageName: packageName, + packageRoot: tempUri, + targetArchitecture: ArchitectureImpl.arm64, + targetOS: OSImpl.android, + targetAndroidNdkApi: 30, + buildMode: BuildModeImpl.release, + assets: assets, + resourceIdentifierUri: resources, + linkModePreference: LinkModePreferenceImpl.preferStatic, + ); + final configFileContents = buildConfig.toJsonString(); + final configUri = tempUri.resolve('link_config.json'); + final configFile = File.fromUri(configUri); + await configFile.writeAsString(configFileContents); + final buildConfig2 = + LinkConfigImpl.fromArguments(['--config', configUri.toFilePath()]); + expect(buildConfig2, buildConfig); + }); + + for (final version in ['9001.0.0', '0.0.1']) { + test('LinkConfig version $version', () { + final outDir = outDirUri; + final config = { + 'link_mode_preference': 'prefer-static', + 'out_dir': outDir.toFilePath(), + 'package_root': tempUri.toFilePath(), + 'target_os': 'linux', + 'version': version, + 'package_name': packageName, + 'dry_run': true, + }; + expect( + () => LinkConfigImpl.fromJson(config), + throwsA(predicate( + (e) => + e is FormatException && + e.message.contains(version) && + e.message.contains(HookConfigImpl.latestVersion.toString()), + )), + ); + }); + } + + test('LinkConfig invalid target os architecture combination', () { + final outDir = outDirUri; + final config = { + 'link_mode_preference': 'prefer-static', + 'out_dir': outDir.toFilePath(), + 'package_name': packageName, + 'package_root': tempUri.toFilePath(), + 'target_os': 'windows', + 'target_architecture': 'arm', + 'build_mode': 'debug', + 'version': HookConfigImpl.latestVersion.toString(), + }; + expect( + () => LinkConfigImpl.fromJson(config), + throwsA(predicate( + (e) => e is FormatException && e.message.contains('arm'), + )), + ); + }); + + test('LinkConfig dry_run access invalid args', () { + final outDir = outDirUri; + final config = { + 'link_mode_preference': 'prefer-static', + 'out_dir': outDir.toFilePath(), + 'package_name': packageName, + 'package_root': tempUri.toFilePath(), + 'target_os': 'windows', + 'target_architecture': 'arm64', + 'build_mode': 'debug', + 'dry_run': true, + 'version': HookConfigImpl.latestVersion.toString(), + }; + expect( + () => LinkConfigImpl.fromJson(config), + throwsA(predicate( + (e) => + e is FormatException && e.message.contains('In Flutter projects'), + )), + ); + }); + + test('LinkConfig dry_run access invalid args', () { + final outDir = outDirUri; + final config = { + 'link_mode_preference': 'prefer-static', + 'out_dir': outDir.toFilePath(), + 'package_name': packageName, + 'package_root': tempUri.toFilePath(), + 'target_os': 'android', + 'dry_run': true, + 'version': HookConfigImpl.latestVersion.toString(), + }; + final buildConfig = LinkConfigImpl.fromJson(config); + expect( + () => buildConfig.targetAndroidNdkApi, + throwsA(predicate( + (e) => e is StateError && e.message.contains('In Flutter projects'), + )), + ); + }); + + test('LinkConfig dry_run target arch', () { + final outDir = outDirUri; + final config = { + 'link_mode_preference': 'prefer-static', + 'out_dir': outDir.toFilePath(), + 'package_name': packageName, + 'package_root': tempUri.toFilePath(), + 'target_os': 'windows', + 'dry_run': true, + 'version': HookConfigImpl.latestVersion.toString(), + }; + final buildConfig = LinkConfigImpl.fromJson(config); + expect(buildConfig.targetArchitecture, isNull); + }); + + test('LinkConfig dry_run toString', () { + final buildConfig = LinkConfigImpl.dryRun( + packageName: packageName, + outputDirectory: outDirUri, + packageRoot: tempUri, + targetOS: OSImpl.windows, + assets: assets, + linkModePreference: LinkModePreferenceImpl.preferStatic, + ); + expect(buildConfig.toJsonString(), isNotEmpty); + }); + + test('invalid architecture', () { + final config = { + 'build_mode': 'release', + 'dry_run': false, + 'link_mode_preference': 'prefer-static', + 'out_dir': outDirUri.toFilePath(), + 'package_name': packageName, + 'package_root': packageRootUri.toFilePath(), + 'target_android_ndk_api': 30, + 'target_architecture': 'invalid_architecture', + 'target_os': 'android', + 'version': HookOutputImpl.latestVersion.toString(), + }; + expect( + () => LinkConfigImpl.fromJson(config), + throwsFormatException, + ); + }); +} diff --git a/pkgs/native_assets_cli/test/model/resource_data.dart b/pkgs/native_assets_cli/test/model/resource_data.dart new file mode 100644 index 000000000..b83351e19 --- /dev/null +++ b/pkgs/native_assets_cli/test/model/resource_data.dart @@ -0,0 +1,123 @@ +// Copyright (c) 2024, 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 'package:native_assets_cli/src/model/resource_identifiers.dart'; + +const resourceFile = '''{ + "_comment": "Resources referenced by annotated resource identifiers", + "AppTag": "TBD", + "environment": { + "dart.tool.dart2js": false + }, + "identifiers": [] +}'''; + +final resourceIdentifiers = ResourceIdentifiers(identifiers: [ + Identifier( + name: 'methodName1', + id: 'someMetadata', + uri: Uri.file('path/to/file'), + nonConstant: true, + files: [ + ResourceFile(part: 1, references: [ + ResourceReference( + uri: Uri.file('path/to/reference'), + line: 2, + column: 4, + arguments: { + '1': 'Some positional argument', + }, + ), + ]), + ], + ), + Identifier( + name: 'methodName2', + id: 'someOtherMetadata', + uri: Uri.file('path/to/other/file'), + nonConstant: false, + files: [ + ResourceFile(part: 1, references: [ + ResourceReference( + uri: Uri.file('path/to/reference'), + line: 15, + column: 3, + arguments: { + 'namedIntParam': 1, + }, + ), + ]), + ResourceFile(part: 2, references: [ + ResourceReference( + uri: Uri.file('path/to/reference'), + line: 15, + column: 3, + arguments: { + 'namedIntParam': 2, + }, + ), + ]), + ], + ), +]); + +final resourceIdentifiersJson = { + 'identifiers': [ + { + 'name': 'methodName1', + 'id': 'someMetadata', + 'uri': Uri.file('path/to/file').toFilePath(), + 'nonConstant': true, + 'files': [ + { + 'part': 1, + 'references': [ + { + '@': { + 'uri': Uri.file('path/to/reference').toFilePath(), + 'line': 2, + 'column': 4 + }, + '1': 'Some positional argument' + } + ] + } + ] + }, + { + 'name': 'methodName2', + 'id': 'someOtherMetadata', + 'uri': Uri.file('path/to/other/file').toFilePath(), + 'nonConstant': false, + 'files': [ + { + 'part': 1, + 'references': [ + { + '@': { + 'uri': Uri.file('path/to/reference').toFilePath(), + 'line': 15, + 'column': 3 + }, + 'namedIntParam': 1 + } + ] + }, + { + 'part': 2, + 'references': [ + { + '@': { + 'uri': Uri.file('path/to/reference').toFilePath(), + 'line': 15, + 'column': 3 + }, + 'namedIntParam': 2 + } + ] + } + ] + } + ] +}; diff --git a/pkgs/native_assets_cli/test/model/resource_identifiers_test.dart b/pkgs/native_assets_cli/test/model/resource_identifiers_test.dart new file mode 100644 index 000000000..acd207e56 --- /dev/null +++ b/pkgs/native_assets_cli/test/model/resource_identifiers_test.dart @@ -0,0 +1,31 @@ +// Copyright (c) 2024, 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:convert'; + +import 'package:native_assets_cli/src/model/resource_identifiers.dart'; +import 'package:test/test.dart'; + +import 'resource_data.dart'; + +void main() { + test('empty resources parsing', () { + final resourceIdentifiers = + ResourceIdentifiers.fromFileContents(resourceFile); + expect(resourceIdentifiers.identifiers, isEmpty); + }); + test('Serialize to JSON', () { + const jsonEncoder = JsonEncoder.withIndent(' '); + expect( + jsonEncoder.convert(resourceIdentifiers), + jsonEncoder.convert(resourceIdentifiersJson), + ); + }); + test('Deserialize from JSON', () { + expect( + ResourceIdentifiers.fromJson(resourceIdentifiersJson), + resourceIdentifiers, + ); + }); +} diff --git a/pkgs/native_toolchain_c/README.md b/pkgs/native_toolchain_c/README.md index 01b1448a5..c9dc8b6d5 100644 --- a/pkgs/native_toolchain_c/README.md +++ b/pkgs/native_toolchain_c/README.md @@ -23,4 +23,5 @@ For bugs, please file an issue in the ## Example -An example can be found in [../native_assets_cli/example/](../native_assets_cli/example/). +An example can be found in [../native_assets_cli/example/build/]( +../native_assets_cli/example/build/). diff --git a/pkgs/native_toolchain_c/lib/src/cbuilder/cbuilder.dart b/pkgs/native_toolchain_c/lib/src/cbuilder/cbuilder.dart index 9e0683b1b..5bffb92f7 100644 --- a/pkgs/native_toolchain_c/lib/src/cbuilder/cbuilder.dart +++ b/pkgs/native_toolchain_c/lib/src/cbuilder/cbuilder.dart @@ -152,6 +152,12 @@ class CBuilder implements Builder { /// | Fuchsia | `c++` | final String? cppLinkStdLib; + /// If the code asset should be a dynamic or static library. + /// + /// This determines whether to produce a dynamic or static library. If null, + /// the value is instead retrieved from the [BuildConfig]. + final LinkModePreference? linkModePreference; + CBuilder.library({ required this.name, required this.assetName, @@ -167,6 +173,7 @@ class CBuilder implements Builder { this.std, this.language = Language.c, this.cppLinkStdLib, + this.linkModePreference, }) : _type = _CBuilderType.library; CBuilder.executable({ @@ -185,7 +192,8 @@ class CBuilder implements Builder { }) : _type = _CBuilderType.executable, assetName = null, installName = null, - pic = pie; + pic = pie, + linkModePreference = null; /// Runs the C Compiler with on this C build spec. /// @@ -195,11 +203,13 @@ class CBuilder implements Builder { required BuildConfig buildConfig, required BuildOutput buildOutput, required Logger? logger, + String? linkInPackage, }) async { final outDir = buildConfig.outputDirectory; final packageRoot = buildConfig.packageRoot; await Directory.fromUri(outDir).create(recursive: true); - final linkMode = _linkMode(buildConfig.linkModePreference); + final linkMode = + _linkMode(linkModePreference ?? buildConfig.linkModePreference); final libUri = outDir.resolve(buildConfig.targetOS.libraryFileName(name, linkMode)); final exeUri = @@ -247,17 +257,20 @@ class CBuilder implements Builder { } if (assetName != null) { - buildOutput.addAssets([ - NativeCodeAsset( - package: buildConfig.packageName, - name: assetName!, - file: libUri, - linkMode: linkMode, - os: buildConfig.targetOS, - architecture: - buildConfig.dryRun ? null : buildConfig.targetArchitecture, - ) - ]); + buildOutput.addAssets( + [ + NativeCodeAsset( + package: buildConfig.packageName, + name: assetName!, + file: libUri, + linkMode: linkMode, + os: buildConfig.targetOS, + architecture: + buildConfig.dryRun ? null : buildConfig.targetArchitecture, + ) + ], + linkInPackage: linkInPackage, + ); } if (!buildConfig.dryRun) { final includeFiles = await Stream.fromIterable(includes) diff --git a/pkgs/native_toolchain_c/lib/src/cbuilder/run_cbuilder.dart b/pkgs/native_toolchain_c/lib/src/cbuilder/run_cbuilder.dart index 6f5fdbbad..3ff4560ef 100644 --- a/pkgs/native_toolchain_c/lib/src/cbuilder/run_cbuilder.dart +++ b/pkgs/native_toolchain_c/lib/src/cbuilder/run_cbuilder.dart @@ -121,30 +121,79 @@ class RunCBuilder { archiver_ = await archiver(); } - late final IOSSdk targetIosSdk; + final IOSSdk? targetIosSdk; if (buildConfig.targetOS == OS.iOS) { targetIosSdk = buildConfig.targetIOSSdk; + } else { + targetIosSdk = null; } // The Android Gradle plugin does not honor API level 19 and 20 when // invoking clang. Mimic that behavior here. // See https://github.com/dart-lang/native/issues/171. - late final int targetAndroidNdkApi; + final int? targetAndroidNdkApi; if (buildConfig.targetOS == OS.android) { final minimumApi = buildConfig.targetArchitecture == Architecture.riscv64 ? 35 : 21; targetAndroidNdkApi = max(buildConfig.targetAndroidNdkApi!, minimumApi); + } else { + targetAndroidNdkApi = null; } final architecture = buildConfig.targetArchitecture; + final sourceFiles = sources.map((e) => e.toFilePath()).toList(); + final objectFiles = []; + if (staticLibrary != null) { + for (var i = 0; i < sourceFiles.length; i++) { + final objectFile = outDir.resolve('out$i.o'); + await _compile( + compiler, + architecture, + targetAndroidNdkApi, + targetIosSdk, + [sourceFiles[i]], + objectFile, + ); + objectFiles.add(objectFile); + } + await runProcess( + executable: archiver_!, + arguments: [ + 'rc', + outDir.resolveUri(staticLibrary!).toFilePath(), + ...objectFiles.map((objectFile) => objectFile.toFilePath()), + ], + logger: logger, + captureOutput: false, + throwOnUnexpectedExitCode: true, + ); + } else { + await _compile( + compiler, + architecture, + targetAndroidNdkApi, + targetIosSdk, + sourceFiles, + dynamicLibrary != null ? outDir.resolveUri(dynamicLibrary!) : null, + ); + } + } + Future _compile( + ToolInstance compiler, + Architecture? architecture, + int? targetAndroidNdkApi, + IOSSdk? targetIosSdk, + Iterable sourceFiles, + Uri? outFile, + ) async { await runProcess( executable: compiler.uri, arguments: [ if (buildConfig.targetOS == OS.android) ...[ '--target=' '${androidNdkClangTargetFlags[architecture]!}' - '$targetAndroidNdkApi', + '${targetAndroidNdkApi!}', '--sysroot=${androidSysroot(compiler).toFilePath()}', ], if (buildConfig.targetOS == OS.macOS) @@ -153,7 +202,7 @@ class RunCBuilder { '--target=${appleClangIosTargetFlags[architecture]![targetIosSdk]!}', if (buildConfig.targetOS == OS.iOS) ...[ '-isysroot', - (await iosSdk(targetIosSdk, logger: logger)).toFilePath(), + (await iosSdk(targetIosSdk!, logger: logger)).toFilePath(), ], if (buildConfig.targetOS == OS.macOS) ...[ '-isysroot', @@ -196,7 +245,7 @@ class RunCBuilder { for (final MapEntry(key: name, :value) in defines.entries) if (value == null) '-D$name' else '-D$name=$value', for (final include in includes) '-I${include.toFilePath()}', - ...sources.map((e) => e.toFilePath()), + ...sourceFiles, if (executable != null) ...[ '-o', outDir.resolveUri(executable!).toFilePath(), @@ -204,30 +253,17 @@ class RunCBuilder { if (dynamicLibrary != null) ...[ '--shared', '-o', - outDir.resolveUri(dynamicLibrary!).toFilePath(), + outFile!.toFilePath(), ] else if (staticLibrary != null) ...[ '-c', '-o', - outDir.resolve('out.o').toFilePath(), + outFile!.toFilePath(), ], ], logger: logger, captureOutput: false, throwOnUnexpectedExitCode: true, ); - if (staticLibrary != null) { - await runProcess( - executable: archiver_!, - arguments: [ - 'rc', - outDir.resolveUri(staticLibrary!).toFilePath(), - outDir.resolve('out.o').toFilePath(), - ], - logger: logger, - captureOutput: false, - throwOnUnexpectedExitCode: true, - ); - } } Future runCl({required ToolInstance compiler}) async { diff --git a/pkgs/native_toolchain_c/lib/src/native_toolchain/recognizer.dart b/pkgs/native_toolchain_c/lib/src/native_toolchain/recognizer.dart index cc5bfaf58..4080f5e93 100644 --- a/pkgs/native_toolchain_c/lib/src/native_toolchain/recognizer.dart +++ b/pkgs/native_toolchain_c/lib/src/native_toolchain/recognizer.dart @@ -11,7 +11,7 @@ import '../tool/tool_resolver.dart'; import 'apple_clang.dart'; import 'clang.dart'; import 'gcc.dart'; -import 'msvc.dart'; +import 'msvc.dart' as msvc; class CompilerRecognizer implements ToolResolver { final Uri uri; @@ -35,7 +35,7 @@ class CompilerRecognizer implements ToolResolver { tool = clang; } } else if (filePath.endsWith('cl.exe')) { - tool = cl; + tool = msvc.cl; } if (tool != null) { @@ -46,7 +46,7 @@ class CompilerRecognizer implements ToolResolver { toolInstance, logger: logger, arguments: [ - if (tool != cl) '--version', + if (tool != msvc.cl) '--version', ], ), ]; @@ -75,7 +75,7 @@ class LinkerRecognizer implements ToolResolver { } else if (filePath.endsWith(os.executableFileName('ld'))) { tool = appleLd; } else if (filePath.endsWith('link.exe')) { - tool = link; + tool = msvc.link; } if (tool != null) { @@ -89,7 +89,7 @@ class LinkerRecognizer implements ToolResolver { ), ]; } - if (tool == link) { + if (tool == msvc.link) { return [ await CliVersionResolver.lookupVersion( toolInstance, @@ -125,7 +125,7 @@ class ArchiverRecognizer implements ToolResolver { } else if (filePath.endsWith(os.executableFileName('ar'))) { tool = appleAr; } else if (filePath.endsWith('lib.exe')) { - tool = lib; + tool = msvc.lib; } if (tool != null) { diff --git a/pkgs/native_toolchain_c/pubspec.yaml b/pkgs/native_toolchain_c/pubspec.yaml index c25e84733..d6bd424a8 100644 --- a/pkgs/native_toolchain_c/pubspec.yaml +++ b/pkgs/native_toolchain_c/pubspec.yaml @@ -4,6 +4,8 @@ description: >- version: 0.4.1 repository: https://github.com/dart-lang/native/tree/main/pkgs/native_toolchain_c +publish_to: none + topics: - compiler - ffi @@ -18,9 +20,9 @@ dependencies: glob: ^2.1.1 logging: ^1.1.1 meta: ^1.9.1 - native_assets_cli: ^0.5.0 - # native_assets_cli: - # path: ../native_assets_cli/ + # native_assets_cli: ^0.5.0 + native_assets_cli: + path: ../native_assets_cli/ pub_semver: ^2.1.3 dev_dependencies: diff --git a/pkgs/native_toolchain_c/test/cbuilder/cbuilder_test.dart b/pkgs/native_toolchain_c/test/cbuilder/cbuilder_test.dart index 3d0e15d5c..b40e73a2a 100644 --- a/pkgs/native_toolchain_c/test/cbuilder/cbuilder_test.dart +++ b/pkgs/native_toolchain_c/test/cbuilder/cbuilder_test.dart @@ -117,7 +117,7 @@ void main() { final buildConfig = dryRun ? BuildConfig.dryRun( - outDir: tempUri, + outputDirectory: tempUri, packageName: name, packageRoot: tempUri, targetOS: OS.current, diff --git a/pkgs/native_toolchain_c/test/cbuilder/compiler_resolver_test.dart b/pkgs/native_toolchain_c/test/cbuilder/compiler_resolver_test.dart index 328412f72..1a2d40860 100644 --- a/pkgs/native_toolchain_c/test/cbuilder/compiler_resolver_test.dart +++ b/pkgs/native_toolchain_c/test/cbuilder/compiler_resolver_test.dart @@ -13,7 +13,7 @@ import 'package:native_assets_cli/native_assets_cli.dart'; import 'package:native_toolchain_c/src/cbuilder/compiler_resolver.dart'; import 'package:native_toolchain_c/src/native_toolchain/apple_clang.dart'; import 'package:native_toolchain_c/src/native_toolchain/clang.dart'; -import 'package:native_toolchain_c/src/native_toolchain/msvc.dart'; +import 'package:native_toolchain_c/src/native_toolchain/msvc.dart' as msvc; import 'package:native_toolchain_c/src/tool/tool_error.dart'; import 'package:test/test.dart'; @@ -24,21 +24,21 @@ void main() { final tempUri = await tempDirForTest(); final ar = [ ...await appleAr.defaultResolver!.resolve(logger: logger), - ...await lib.defaultResolver!.resolve(logger: logger), + ...await msvc.lib.defaultResolver!.resolve(logger: logger), ...await llvmAr.defaultResolver!.resolve(logger: logger), ].first.uri; final cc = [ ...await appleClang.defaultResolver!.resolve(logger: logger), - ...await cl.defaultResolver!.resolve(logger: logger), + ...await msvc.cl.defaultResolver!.resolve(logger: logger), ...await clang.defaultResolver!.resolve(logger: logger), ].first.uri; final ld = [ ...await appleLd.defaultResolver!.resolve(logger: logger), - ...await link.defaultResolver!.resolve(logger: logger), + ...await msvc.link.defaultResolver!.resolve(logger: logger), ...await lld.defaultResolver!.resolve(logger: logger), ].first.uri; final envScript = [ - ...await vcvars64.defaultResolver!.resolve(logger: logger) + ...await msvc.vcvars64.defaultResolver!.resolve(logger: logger) ].firstOrNull?.uri; final buildConfig = BuildConfig.build( outputDirectory: tempUri, diff --git a/pkgs/native_toolchain_c/test/helpers.dart b/pkgs/native_toolchain_c/test/helpers.dart index 4baa41276..612c2f087 100644 --- a/pkgs/native_toolchain_c/test/helpers.dart +++ b/pkgs/native_toolchain_c/test/helpers.dart @@ -180,3 +180,7 @@ DynamicLibrary openDynamicLibraryForTest(String path) { addTearDown(library.close); return library; } + +extension UnescapePath on String { + String unescape() => replaceAll('\\', '/'); +} diff --git a/pkgs/native_toolchain_c/test/tool/tool_resolver_test.dart b/pkgs/native_toolchain_c/test/tool/tool_resolver_test.dart index 18b1a83d9..85abb1996 100644 --- a/pkgs/native_toolchain_c/test/tool/tool_resolver_test.dart +++ b/pkgs/native_toolchain_c/test/tool/tool_resolver_test.dart @@ -68,7 +68,7 @@ void main() { expect(await File.fromUri(bazExeUri).exists(), true); final barResolver = InstallLocationResolver( toolName: 'bar', - paths: [barExeUri.toFilePath().replaceAll('\\', '/')], + paths: [barExeUri.toFilePath().unescape()], ); final bazResolver = RelativeToolResolver( toolName: 'baz', @@ -94,9 +94,9 @@ void main() { final bazExeUri = tempUri.resolve(bazExeName); await File.fromUri(barExeUri).writeAsString('dummy'); final barResolver = InstallLocationResolver( - toolName: 'bar', paths: [barExeUri.toFilePath().replaceAll('\\', '/')]); + toolName: 'bar', paths: [barExeUri.toFilePath().unescape()]); final bazResolver = InstallLocationResolver( - toolName: 'baz', paths: [bazExeUri.toFilePath().replaceAll('\\', '/')]); + toolName: 'baz', paths: [bazExeUri.toFilePath().unescape()]); final barLogs = []; final bazLogs = []; await barResolver.resolve(logger: createCapturingLogger(barLogs));