diff --git a/.gitignore b/.gitignore index 5868805e7be..b38fe503f10 100644 --- a/.gitignore +++ b/.gitignore @@ -75,6 +75,9 @@ client/tests/drt packages pubspec.lock +# Files for native assets auto-generated per checkout. +/.dart_tool/native_assets_builder/ + # The top level package file (this is auto-generated per checkout). /.dart_tool/package_config.json diff --git a/DEPS b/DEPS index 078702601fb..4e0b99ca521 100644 --- a/DEPS +++ b/DEPS @@ -152,7 +152,7 @@ vars = { "matcher_rev": "4dfd9adfdcd3de95e6964fc295075a32635e5b37", "mime_rev": "eb9d54b8f6fab9442b2b733e19137dc25fb0b868", "mockito_rev": "51a7728927c505a907384860f4a407f8b7219004", - "native_rev": "64aa5b500fd71b51a34bf4ef0dc05ec24379be00", + "native_rev": "e01aa63e851ce971390bc45fc17d16240b8d164a", "package_config_rev": "f41f92cd4d2c539d910f2ef041ba6f6bd8469e78", "path_rev": "f8d15c2775835fd61ea5e06bb0ab99232f5ec446", "pool_rev": "86b4f4328a32a9e9eb4f1601c8296e22d3e50552", diff --git a/pkg/front_end/lib/src/api_prototype/experimental_flags_generated.dart b/pkg/front_end/lib/src/api_prototype/experimental_flags_generated.dart index bc71f382081..5b660a1e637 100644 --- a/pkg/front_end/lib/src/api_prototype/experimental_flags_generated.dart +++ b/pkg/front_end/lib/src/api_prototype/experimental_flags_generated.dart @@ -858,6 +858,9 @@ const AllowedExperimentalFlags defaultAllowedExperimentalFlags = "meta": { ExperimentalFlag.nonNullable, }, + "native_assets_builder": { + ExperimentalFlag.nonNullable, + }, "native_stack_traces": { ExperimentalFlag.nonNullable, }, diff --git a/pkg/front_end/test/spell_checking_list_common.txt b/pkg/front_end/test/spell_checking_list_common.txt index 671264aaa47..088e17da2fd 100644 --- a/pkg/front_end/test/spell_checking_list_common.txt +++ b/pkg/front_end/test/spell_checking_list_common.txt @@ -204,6 +204,8 @@ asserted assertion assertions asserts +asset +assets assign assignability assignable @@ -3114,6 +3116,7 @@ tokens too took tool +toolchain tools top topologically diff --git a/pkg/native_assets_builder/.gitignore b/pkg/native_assets_builder/.gitignore new file mode 100644 index 00000000000..51dab37f324 --- /dev/null +++ b/pkg/native_assets_builder/.gitignore @@ -0,0 +1,48 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.packages +.pub-cache/ +.pub/ +/build/ + +# Web related +lib/generated_plugin_registrant.dart + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release + +pubspec.lock diff --git a/pkg/native_assets_builder/AUTHORS b/pkg/native_assets_builder/AUTHORS new file mode 100644 index 00000000000..846e4a15687 --- /dev/null +++ b/pkg/native_assets_builder/AUTHORS @@ -0,0 +1,6 @@ +# Below is a list of people and organizations that have contributed +# to the Dart project. Names should be added to the list like so: +# +# Name/Organization + +Google LLC diff --git a/pkg/native_assets_builder/LICENSE b/pkg/native_assets_builder/LICENSE new file mode 100644 index 00000000000..33474aad68b --- /dev/null +++ b/pkg/native_assets_builder/LICENSE @@ -0,0 +1,27 @@ +Copyright 2022, the Dart project authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google LLC nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/pkg/native_assets_builder/OWNERS b/pkg/native_assets_builder/OWNERS new file mode 100644 index 00000000000..dc3a1d02753 --- /dev/null +++ b/pkg/native_assets_builder/OWNERS @@ -0,0 +1 @@ +file:/tools/OWNERS_VM diff --git a/pkg/native_assets_builder/README.md b/pkg/native_assets_builder/README.md new file mode 100644 index 00000000000..830f841f7a4 --- /dev/null +++ b/pkg/native_assets_builder/README.md @@ -0,0 +1,7 @@ +This package contains the logic for building native assets. + +This package is the backend that invokes toplevel `build.dart` scripts. +For more info on these scripts see https://github.com/dart-lang/native. + +This is a separate package so that dartdev and flutter_tools can reuse +the same logic with flutter_tools having to import dartdev. diff --git a/pkg/native_assets_builder/analysis_options.yaml b/pkg/native_assets_builder/analysis_options.yaml new file mode 100644 index 00000000000..52a18b39e31 --- /dev/null +++ b/pkg/native_assets_builder/analysis_options.yaml @@ -0,0 +1,27 @@ +include: package:lints/recommended.yaml + +analyzer: + language: + strict-raw-types: true + strict-inference: true + # The test projects do _not_ have resolved dependencies through + # `dart tools/package_deps/bin/package_deps.dart`. + # So don't analyze them in the IDE or on the CI. + exclude: + - test/test_projects/**.dart + +linter: + rules: + - always_declare_return_types + - avoid_dynamic_calls + - camel_case_types + - depend_on_referenced_packages + - directives_ordering + - prefer_const_declarations + - prefer_expression_function_bodies + - prefer_final_in_for_each + - prefer_final_locals + - prefer_relative_imports + - prefer_single_quotes + - sort_pub_dependencies + - unawaited_futures diff --git a/pkg/native_assets_builder/lib/native_assets_builder.dart b/pkg/native_assets_builder/lib/native_assets_builder.dart new file mode 100644 index 00000000000..3ff29ab0c3e --- /dev/null +++ b/pkg/native_assets_builder/lib/native_assets_builder.dart @@ -0,0 +1,6 @@ +// 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. + +export 'package:native_assets_builder/src/build_runner/build_runner.dart'; +export 'package:native_assets_builder/src/package_layout/package_layout.dart'; diff --git a/pkg/native_assets_builder/lib/src/build_runner/build_planner.dart b/pkg/native_assets_builder/lib/src/build_runner/build_planner.dart new file mode 100644 index 00000000000..8aac2cf8334 --- /dev/null +++ b/pkg/native_assets_builder/lib/src/build_runner/build_planner.dart @@ -0,0 +1,102 @@ +// 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:convert'; +import 'dart:io'; + +import 'package:kernel/util/graph.dart' as graph; +import 'package:package_config/package_config.dart'; + +class NativeAssetsBuildPlanner { + final PackageGraph packageGraph; + final List packagesWithNativeAssets; + final Uri dartExecutable; + + NativeAssetsBuildPlanner({ + required this.packageGraph, + required this.packagesWithNativeAssets, + required this.dartExecutable, + }); + + static Future fromRootPackageRoot({ + required Uri rootPackageRoot, + required List packagesWithNativeAssets, + required Uri dartExecutable, + }) async { + final result = await Process.run( + dartExecutable.toFilePath(), + [ + 'pub', + 'deps', + '--json', + ], + workingDirectory: rootPackageRoot.toFilePath(), + ); + final packageGraph = PackageGraph.fromPubDepsJsonString(result.stdout); + return NativeAssetsBuildPlanner( + packageGraph: packageGraph, + packagesWithNativeAssets: packagesWithNativeAssets, + dartExecutable: dartExecutable, + ); + } + + List plan() { + final packageMap = { + for (final package in packagesWithNativeAssets) package.name: package + }; + final packagesToBuild = packageMap.keys.toSet(); + final stronglyConnectedComponents = packageGraph.computeStrongComponents(); + final result = []; + for (final stronglyConnectedComponent in stronglyConnectedComponents) { + final stronglyConnectedComponentWithNativeAssets = [ + for (final packageName in stronglyConnectedComponent) + if (packagesToBuild.contains(packageName)) packageName + ]; + if (stronglyConnectedComponentWithNativeAssets.length > 1) { + throw Exception( + 'Cyclic dependency for native asset builds in the following ' + 'packages: $stronglyConnectedComponent.', + ); + } else if (stronglyConnectedComponentWithNativeAssets.length == 1) { + result.add( + packageMap[stronglyConnectedComponentWithNativeAssets.single]!); + } + } + return result; + } +} + +class PackageGraph implements graph.Graph { + final Map> map; + + PackageGraph(this.map); + + /// Construct a graph from the JSON produced by `dart pub deps --json`. + factory PackageGraph.fromPubDepsJsonString(String json) => + PackageGraph.fromPubDepsJson(jsonDecode(json) as Map); + + /// Construct a graph from the JSON produced by `dart pub deps --json`. + factory PackageGraph.fromPubDepsJson(Map map) { + final result = >{}; + final packages = map['packages'] as List; + for (final package in packages) { + final package_ = package as Map; + final name = package_['name'] as String; + final dependencies = (package_['dependencies'] as List) + .whereType() + .toList(); + result[name] = dependencies; + } + return PackageGraph(result); + } + + @override + Iterable neighborsOf(String vertex) => map[vertex] ?? []; + + @override + Iterable get vertices => map.keys; + + List> computeStrongComponents() => + graph.computeStrongComponents(this); +} diff --git a/pkg/native_assets_builder/lib/src/build_runner/build_runner.dart b/pkg/native_assets_builder/lib/src/build_runner/build_runner.dart new file mode 100644 index 00000000000..a29894cf0cc --- /dev/null +++ b/pkg/native_assets_builder/lib/src/build_runner/build_runner.dart @@ -0,0 +1,220 @@ +// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:async'; +import 'dart:io'; + +import 'package:logging/logging.dart'; +import 'package:native_assets_cli/native_assets_cli.dart'; + +import '../package_layout/package_layout.dart'; +import '../utils/run_process.dart'; +import 'build_planner.dart'; + +typedef DependencyMetadata = Map; + +/// The programmatic API to be used by Dart launchers to invoke native builds. +/// +/// These methods are invoked by launchers such as dartdev (for `dart run`) +/// and flutter_tools (for `flutter run` and `flutter build`). +class NativeAssetsBuildRunner { + final Logger logger; + final Uri dartExecutable; + + NativeAssetsBuildRunner({ + required this.logger, + required this.dartExecutable, + }); + + final _metadata = {}; + + /// [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`). + /// + /// Completes the future with an error if the build fails. + Future> build({ + required LinkModePreference linkModePreference, + required Target target, + required Uri workingDirectory, + CCompilerConfig? cCompilerConfig, + required bool includeParentEnvironment, + }) async { + assert(_metadata.isEmpty); + final packageLayout = + await PackageLayout.fromRootPackageRoot(workingDirectory); + final packagesWithNativeAssets = + await packageLayout.packagesWithNativeAssets; + final planner = await NativeAssetsBuildPlanner.fromRootPackageRoot( + rootPackageRoot: packageLayout.rootPackageRoot, + packagesWithNativeAssets: packagesWithNativeAssets, + dartExecutable: Uri.file(Platform.resolvedExecutable), + ); + final plan = planner.plan(); + final assetList = []; + for (final package in plan) { + final dependencyMetadata = _metadataForPackage( + packageGraph: planner.packageGraph, + packageName: package.name, + targetMetadata: _metadata[target], + ); + final config = await _cliConfig( + packageName: package.name, + packageRoot: packageLayout.packageRoot(package.name), + target: target, + linkMode: linkModePreference, + buildParentDir: packageLayout.dartToolNativeAssetsBuilder, + dependencyMetadata: dependencyMetadata, + cCompilerConfig: cCompilerConfig, + ); + final assets = await _buildPackageCached( + config, + packageLayout.packageConfigUri, + workingDirectory, + includeParentEnvironment, + ); + assetList.addAll(assets); + } + return assetList; + } + + Future> _buildPackageCached( + BuildConfig config, + Uri packageConfigUri, + Uri workingDirectory, + bool includeParentEnvironment, + ) async { + final packageName = config.packageName; + final outDir = config.outDir; + if (!await Directory.fromUri(outDir).exists()) { + await Directory.fromUri(outDir).create(recursive: true); + } + + final buildOutput = await BuildOutput.readFromFile(outDir: outDir); + final lastBuilt = buildOutput?.timestamp.roundDownToSeconds() ?? + DateTime.fromMillisecondsSinceEpoch(0); + final dependencies = buildOutput?.dependencies; + 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. + + setMetadata(config.target, packageName, buildOutput?.metadata); + return buildOutput!.assets; + } + + return _buildPackage( + config, + packageConfigUri, + workingDirectory, + includeParentEnvironment, + ); + } + + Future> _buildPackage( + BuildConfig config, + Uri packageConfigUri, + Uri workingDirectory, + bool includeParentEnvironment, + ) async { + final outDir = config.outDir; + final configFile = outDir.resolve('config.yaml'); + final buildDotDart = config.packageRoot.resolve('build.dart'); + final configFileContents = config.toYamlString(); + logger.info('config.yaml contents: $configFileContents'); + await File.fromUri(configFile).writeAsString(configFileContents); + final buildOutputFile = File.fromUri(outDir.resolve(BuildOutput.fileName)); + if (await buildOutputFile.exists()) { + // Ensure we'll never read outdated build results. + await buildOutputFile.delete(); + } + await runProcess( + workingDirectory: workingDirectory, + executable: dartExecutable.toFilePath(), + arguments: [ + '--packages=${packageConfigUri.toFilePath()}', + buildDotDart.toFilePath(), + '--config=${configFile.toFilePath()}', + ], + logger: logger, + includeParentEnvironment: includeParentEnvironment, + ); + final buildOutput = await BuildOutput.readFromFile(outDir: outDir); + setMetadata(config.target, config.packageName, buildOutput?.metadata); + return buildOutput?.assets ?? []; + } + + void setMetadata(Target target, String packageName, Metadata? metadata) { + if (metadata == null) { + return; + } + _metadata[target] ??= {}; + _metadata[target]![packageName] = metadata; + } + + static Future _cliConfig({ + required String packageName, + required Uri packageRoot, + required Target target, + IOSSdk? targetIOSSdk, + required LinkModePreference linkMode, + required Uri buildParentDir, + CCompilerConfig? cCompilerConfig, + DependencyMetadata? dependencyMetadata, + }) async { + final buildDirName = BuildConfig.checksum( + packageRoot: packageRoot, + target: target, + linkModePreference: linkMode, + targetIOSSdk: targetIOSSdk, + cCompiler: cCompilerConfig, + dependencyMetadata: dependencyMetadata, + ); + final outDirUri = buildParentDir.resolve('$buildDirName/'); + 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 BuildConfig( + outDir: outDirUri, + packageRoot: packageRoot, + target: target, + linkModePreference: linkMode, + targetIOSSdk: targetIOSSdk, + cCompiler: cCompilerConfig, + dependencyMetadata: dependencyMetadata, + ); + } + + DependencyMetadata? _metadataForPackage({ + required PackageGraph packageGraph, + required String packageName, + DependencyMetadata? targetMetadata, + }) { + if (targetMetadata == null) { + return null; + } + final dependencies = packageGraph.neighborsOf(packageName).toSet(); + return { + for (final entry in targetMetadata.entries) + if (dependencies.contains(entry.key)) entry.key: entry.value, + }; + } +} + +extension on DateTime { + DateTime roundDownToSeconds() => + DateTime.fromMillisecondsSinceEpoch(millisecondsSinceEpoch - + millisecondsSinceEpoch % Duration(seconds: 1).inMilliseconds); +} + +extension on BuildConfig { + String get packageName => + packageRoot.pathSegments.lastWhere((e) => e.isNotEmpty); +} diff --git a/pkg/native_assets_builder/lib/src/package_layout/package_layout.dart b/pkg/native_assets_builder/lib/src/package_layout/package_layout.dart new file mode 100644 index 00000000000..d9f202db457 --- /dev/null +++ b/pkg/native_assets_builder/lib/src/package_layout/package_layout.dart @@ -0,0 +1,94 @@ +// 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:package_config/package_config.dart'; + +/// Directory layout for dealing with native assets. +/// +/// Build scripts for native assets will be run from the context of another +/// root package. +/// +/// The directory layout follows pub's convention for caching: +/// https://dart.dev/tools/pub/package-layout#project-specific-caching-for-tools +class PackageLayout { + /// The root folder of the current dart invocation root package. + /// + /// `$rootPackageRoot`. + final Uri rootPackageRoot; + + /// Package config containing the information of where to foot the root [Uri]s + /// of other packages. + /// + /// Can be `null` to enable quick construction of a + /// [PackageLayout]. + final PackageConfig packageConfig; + + final Uri packageConfigUri; + + PackageLayout._( + this.rootPackageRoot, this.packageConfig, this.packageConfigUri); + + static Future fromRootPackageRoot(Uri rootPackageRoot) async { + rootPackageRoot = rootPackageRoot.normalizePath(); + final packageConfigUri = + rootPackageRoot.resolve('.dart_tool/package_config.json'); + assert(await File.fromUri(packageConfigUri).exists()); + final packageConfig = await loadPackageConfigUri(packageConfigUri); + return PackageLayout._(rootPackageRoot, packageConfig, packageConfigUri); + } + + /// The .dart_tool directory is used to store built artifacts and caches. + /// + /// `$rootPackageRoot/.dart_tool/`. + /// + /// Each package should only modify the subfolder of `.dart_tool/` with its + /// own name. + /// https://dart.dev/tools/pub/package-layout#project-specific-caching-for-tools + late final Uri dartTool = rootPackageRoot.resolve('.dart_tool/'); + + /// The directory where `package:native_assets_builder` stores all persistent + /// information. + /// + /// This folder is owned by `package:native_assets_builder`, no other package + /// should read or modify it. + /// https://dart.dev/tools/pub/package-layout#project-specific-caching-for-tools + /// + /// `$rootPackageRoot/.dart_tool/native_assets_builder/`. + late final Uri dartToolNativeAssetsBuilder = + dartTool.resolve('native_assets_builder/'); + + /// The root of `package:$packageName`. + /// + /// `$packageName/`. + /// + /// This folder is owned by pub, and should _never_ be written to. + Uri packageRoot(String packageName) { + final package = packageConfig[packageName]; + if (package == null) { + throw StateError('Package $packageName not found in packageConfig.'); + } + return package.root; + } + + /// All packages in [packageConfig] with native assets. + /// + /// Whether a package has native assets is defined by whether it contains + /// a `build.dart`. + /// + /// `package:native` itself is excluded. + late final Future> packagesWithNativeAssets = () async { + final result = []; + for (final package in packageConfig.packages) { + final packageRoot = package.root; + if (packageRoot.scheme == 'file') { + if (await File.fromUri(packageRoot.resolve('build.dart')).exists()) { + result.add(package); + } + } + } + return result; + }(); +} diff --git a/pkg/native_assets_builder/lib/src/utils/file.dart b/pkg/native_assets_builder/lib/src/utils/file.dart new file mode 100644 index 00000000000..54e83d04aa9 --- /dev/null +++ b/pkg/native_assets_builder/lib/src/utils/file.dart @@ -0,0 +1,66 @@ +// 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:convert'; +import 'dart:io'; + +extension FileExtension on File { + Future writeAsStringCreateDirectory(String contents, + {FileMode mode = FileMode.write, + Encoding encoding = utf8, + bool flush = false}) async { + if (!await parent.exists()) { + await parent.create(recursive: true); + } + return await writeAsString(contents, + mode: mode, encoding: encoding, flush: flush); + } +} + +extension FileSystemEntityExtension on FileSystemEntity { + Future lastModified() async { + final this_ = this; + if (this_ is Link || await FileSystemEntity.isLink(this_.path)) { + // Don't follow links. + return DateTime.fromMicrosecondsSinceEpoch(0); + } + if (this_ is File) { + if (!await this_.exists()) { + // If the file was deleted, regard it is modified recently. + return DateTime.now(); + } + return await this_.lastModified(); + } + if (this_ is Directory) { + return await this_.lastModified(); + } + throw Exception('Unknown FileSystemEntity $runtimeType'); + } +} + +extension FileSystemEntityIterable on Iterable { + Future lastModified() async { + var last = DateTime.fromMillisecondsSinceEpoch(0); + for (final entity in this) { + final entityTimestamp = await entity.lastModified(); + if (entityTimestamp.isAfter(last)) { + last = entityTimestamp; + } + } + return last; + } +} + +extension DirectoryExtension on Directory { + Future lastModified() async { + var last = DateTime.fromMillisecondsSinceEpoch(0); + await for (final entity in list()) { + final entityTimestamp = await entity.lastModified(); + if (entityTimestamp.isAfter(last)) { + last = entityTimestamp; + } + } + return last; + } +} diff --git a/pkg/native_assets_builder/lib/src/utils/run_process.dart b/pkg/native_assets_builder/lib/src/utils/run_process.dart new file mode 100644 index 00000000000..b557bb51216 --- /dev/null +++ b/pkg/native_assets_builder/lib/src/utils/run_process.dart @@ -0,0 +1,141 @@ +// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; + +import 'package:logging/logging.dart'; + +/// Runs a process async and captures the exit code and standard out. +/// +/// Supports streaming output using a [Logger]. +Future runProcess({ + required String executable, + required List arguments, + Uri? workingDirectory, + Map? environment, + bool includeParentEnvironment = true, + bool throwOnFailure = true, + required Logger logger, +}) async { + if (Platform.isWindows && !includeParentEnvironment) { + const winEnvKeys = [ + 'SYSTEMROOT', + ]; + final newEnvironment = { + ...{ + for (final winEnvKey in winEnvKeys) + winEnvKey: Platform.environment[winEnvKey]!, + }, + if (environment != null) ...environment, + }; + environment = newEnvironment; + } + + final printWorkingDir = + workingDirectory != null && workingDirectory != Directory.current.uri; + final commandString = [ + if (printWorkingDir) '(cd ${workingDirectory.toFilePath()};', + ...?environment?.entries.map((entry) => '${entry.key}=${entry.value}'), + executable, + ...arguments.map((a) => a.contains(' ') ? "'$a'" : a), + if (printWorkingDir) ')', + ].join(' '); + logger.info('Running `$commandString`.'); + + final stdoutBuffer = []; + final stderrBuffer = []; + final stdoutCompleter = Completer(); + final stderrCompleter = Completer(); + final Process process = await Process.start( + executable, + arguments, + workingDirectory: workingDirectory?.toFilePath(), + environment: environment, + includeParentEnvironment: includeParentEnvironment, + runInShell: Platform.isWindows && !includeParentEnvironment, + ); + + process.stdout.transform(utf8.decoder).transform(const LineSplitter()).listen( + (s) { + logger.fine(s); + stdoutBuffer.add(s); + }, + onDone: stdoutCompleter.complete, + ); + process.stderr.transform(utf8.decoder).transform(const LineSplitter()).listen( + (s) { + logger.severe(s); + stderrBuffer.add(s); + }, + onDone: stderrCompleter.complete, + ); + + final int exitCode = await process.exitCode; + await stdoutCompleter.future; + final String stdout = stdoutBuffer.join(); + await stderrCompleter.future; + final String stderr = stderrBuffer.join(); + final result = RunProcessResult( + pid: process.pid, + command: '$executable ${arguments.join(' ')}', + exitCode: exitCode, + stdout: stdout, + stderr: stderr, + ); + if (throwOnFailure && result.exitCode != 0) { + throw ProcessInvocationException(result); + } + return result; +} + +class RunProcessResult extends ProcessResult { + final String command; + + final int _exitCode; + + // For some reason super.exitCode returns 0. + @override + int get exitCode => _exitCode; + + final String _stderrString; + + @override + String get stderr => _stderrString; + + final String _stdoutString; + + @override + String get stdout => _stdoutString; + + RunProcessResult({ + required int pid, + required this.command, + required int exitCode, + required String stderr, + required String stdout, + }) : _exitCode = exitCode, + _stderrString = stderr, + _stdoutString = stdout, + super(pid, exitCode, stdout, stderr); + + @override + String toString() => '''command: $command +exitCode: $exitCode +stdout: $stdout +stderr: $stderr'''; +} + +class ProcessInvocationException implements Exception { + final RunProcessResult runProcessResult; + + ProcessInvocationException(this.runProcessResult); + + String get message => '''A process run failed. +$runProcessResult'''; + + @override + String toString() => message; +} diff --git a/pkg/native_assets_builder/pubspec.yaml b/pkg/native_assets_builder/pubspec.yaml new file mode 100644 index 00000000000..16398d4728d --- /dev/null +++ b/pkg/native_assets_builder/pubspec.yaml @@ -0,0 +1,21 @@ +name: native_assets_builder +version: 0.0.1 + +# This package is not intended for consumption on pub.dev. DO NOT publish. +publish_to: none + +environment: + sdk: ">=2.17.0 <3.0.0" + +# Use 'any' constraints here; we get our versions from the DEPS file. +dependencies: + kernel: any # For Graph topological sort. + logging: any + native_assets_cli: any + package_config: any + +# Use 'any' constraints here; we get our versions from the DEPS file. +dev_dependencies: + lints: any + test: any + yaml: any diff --git a/pkg/native_assets_builder/test/build_runner/build_dependencies_test.dart b/pkg/native_assets_builder/test/build_runner/build_dependencies_test.dart new file mode 100644 index 00000000000..d4a97a3b052 --- /dev/null +++ b/pkg/native_assets_builder/test/build_runner/build_dependencies_test.dart @@ -0,0 +1,41 @@ +// 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:test/test.dart'; + +import '../helpers.dart'; +import 'helpers.dart'; + +const Timeout longTimeout = Timeout(Duration(minutes: 5)); + +void main(List args) async { + test('dart_app build dependencies', timeout: longTimeout, () async { + await inTempDir((tempUri) async { + await copyTestProjects(targetUri: tempUri); + final packageUri = tempUri.resolve('dart_app/'); + + // First, run `pub get`, we need pub to resolve our dependencies. + await runPubGet( + workingDirectory: packageUri, + logger: logger, + ); + + // Trigger a build, should invoke build for libraries with native assets. + { + final logMessages = []; + final assets = await build(packageUri, logger, dartExecutable, + capturedLogs: logMessages); + expect( + logMessages.join('\n'), + stringContainsInOrder([ + 'native_add${Platform.pathSeparator}build.dart', + 'native_subtract${Platform.pathSeparator}build.dart' + ])); + expect(assets.length, 2); + } + }); + }); +} diff --git a/pkg/native_assets_builder/test/build_runner/build_planner_test.dart b/pkg/native_assets_builder/test/build_runner/build_planner_test.dart new file mode 100644 index 00000000000..2af502a6678 --- /dev/null +++ b/pkg/native_assets_builder/test/build_runner/build_planner_test.dart @@ -0,0 +1,74 @@ +// 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_builder/native_assets_builder.dart'; +import 'package:native_assets_builder/src/build_runner/build_planner.dart'; +import 'package:test/test.dart'; + +import '../helpers.dart'; +import 'helpers.dart'; + +void main() async { + test('build dependency graph from pub', () async { + await inTempDir((tempUri) async { + await copyTestProjects(targetUri: tempUri); + final nativeAddUri = tempUri.resolve('native_add/'); + + // First, run `pub get`, we need pub to resolve our dependencies. + await runPubGet(workingDirectory: nativeAddUri, logger: logger); + + final result = await runProcess( + executable: Platform.resolvedExecutable, + arguments: [ + 'pub', + 'deps', + '--json', + ], + workingDirectory: nativeAddUri, + logger: logger, + ); + expect(result.exitCode, 0); + + final graph = PackageGraph.fromPubDepsJsonString(result.stdout); + + final packageLayout = + await PackageLayout.fromRootPackageRoot(nativeAddUri); + final packagesWithNativeAssets = + await packageLayout.packagesWithNativeAssets; + + final planner = NativeAssetsBuildPlanner( + packageGraph: graph, + packagesWithNativeAssets: packagesWithNativeAssets, + dartExecutable: Uri.file(Platform.resolvedExecutable), + ); + final buildPlan = planner.plan(); + expect(buildPlan.length, 1); + expect(buildPlan.single.name, 'native_add'); + }); + }); + test('build dependency graph fromPackageRoot', () async { + await inTempDir((tempUri) async { + await copyTestProjects(targetUri: tempUri); + final nativeAddUri = tempUri.resolve('native_add/'); + + // First, run `pub get`, we need pub to resolve our dependencies. + await runPubGet(workingDirectory: nativeAddUri, logger: logger); + + final packageLayout = + await PackageLayout.fromRootPackageRoot(nativeAddUri); + final packagesWithNativeAssets = + await packageLayout.packagesWithNativeAssets; + final buildPlan = (await NativeAssetsBuildPlanner.fromRootPackageRoot( + rootPackageRoot: nativeAddUri, + packagesWithNativeAssets: packagesWithNativeAssets, + dartExecutable: Uri.file(Platform.resolvedExecutable), + )) + .plan(); + expect(buildPlan.length, 1); + expect(buildPlan.single.name, 'native_add'); + }); + }); +} diff --git a/pkg/native_assets_builder/test/build_runner/build_runner_caching_test.dart b/pkg/native_assets_builder/test/build_runner/build_runner_caching_test.dart new file mode 100644 index 00000000000..467201b6d1e --- /dev/null +++ b/pkg/native_assets_builder/test/build_runner/build_runner_caching_test.dart @@ -0,0 +1,99 @@ +// 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:test/test.dart'; + +import '../helpers.dart'; +import 'helpers.dart'; + +const Timeout longTimeout = Timeout(Duration(minutes: 5)); + +void main(List args) async { + test('cached build', timeout: longTimeout, () async { + await inTempDir((tempUri) async { + await copyTestProjects(targetUri: tempUri); + final packageUri = tempUri.resolve('native_add/'); + + await runPubGet( + workingDirectory: packageUri, + logger: logger, + ); + + { + final logMessages = []; + await build(packageUri, logger, dartExecutable, + capturedLogs: logMessages); + expect( + logMessages.join('\n'), + stringContainsInOrder( + ['native_add${Platform.pathSeparator}build.dart'])); + } + + { + final logMessages = []; + await build(packageUri, logger, dartExecutable, + capturedLogs: logMessages); + expect(logMessages.join('\n'), + stringContainsInOrder(['Skipping build for native_add'])); + expect( + false, + logMessages + .join('\n') + .contains('native_add${Platform.pathSeparator}build.dart')); + } + }); + }); + + test('modify C file', timeout: longTimeout, () async { + await inTempDir((tempUri) async { + await copyTestProjects(targetUri: tempUri); + final packageUri = tempUri.resolve('native_add/'); + + await runPubGet( + workingDirectory: packageUri, + logger: logger, + ); + + { + final assets = await build(packageUri, logger, dartExecutable); + await expectSymbols(asset: assets.single, symbols: ['add']); + } + + await copyTestProjects( + sourceUri: testProjectsUri.resolve('native_add_add_symbol/'), + targetUri: packageUri, + ); + + { + final assets = await build(packageUri, logger, dartExecutable); + await expectSymbols(asset: assets.single, symbols: ['add', 'subtract']); + } + }); + }); + + test('add C file, modify script', timeout: longTimeout, () async { + await inTempDir((tempUri) async { + await copyTestProjects(targetUri: tempUri); + final packageUri = tempUri.resolve('native_add/'); + + await runPubGet(workingDirectory: packageUri, logger: logger); + + { + final assets = await build(packageUri, logger, dartExecutable); + await expectSymbols(asset: assets.single, symbols: ['add']); + } + + await copyTestProjects( + sourceUri: testProjectsUri.resolve('native_add_add_source/'), + targetUri: packageUri); + + { + final assets = await build(packageUri, logger, dartExecutable); + await expectSymbols(asset: assets.single, symbols: ['add', 'multiply']); + } + }); + }); +} diff --git a/pkg/native_assets_builder/test/build_runner/build_runner_failure_test.dart b/pkg/native_assets_builder/test/build_runner/build_runner_failure_test.dart new file mode 100644 index 00000000000..da2e41f3e0e --- /dev/null +++ b/pkg/native_assets_builder/test/build_runner/build_runner_failure_test.dart @@ -0,0 +1,47 @@ +// 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 'package:native_assets_cli/native_assets_cli.dart'; +import 'package:test/test.dart'; + +import '../helpers.dart'; +import 'helpers.dart'; + +const Timeout longTimeout = Timeout(Duration(minutes: 5)); + +void main(List args) async { + test('break build', timeout: longTimeout, () async { + await inTempDir((tempUri) async { + await copyTestProjects(targetUri: tempUri); + final packageUri = tempUri.resolve('native_add/'); + + await runPubGet( + workingDirectory: packageUri, + logger: logger, + ); + + { + final assets = await build(packageUri, logger, dartExecutable); + expect(assets.length, 1); + await expectSymbols(asset: assets.single, symbols: ['add']); + } + + await copyTestProjects( + sourceUri: testProjectsUri.resolve('native_add_break_build/'), + targetUri: packageUri, + ); + + { + bool buildFailed = false; + final assets = await build(packageUri, logger, dartExecutable) + .onError((error, stackTrace) { + buildFailed = true; + return []; + }); + expect(buildFailed, true); + expect(assets, []); + } + }); + }); +} diff --git a/pkg/native_assets_builder/test/build_runner/build_runner_run_in_isolation_test.dart b/pkg/native_assets_builder/test/build_runner/build_runner_run_in_isolation_test.dart new file mode 100644 index 00000000000..8cf97bc3276 --- /dev/null +++ b/pkg/native_assets_builder/test/build_runner/build_runner_run_in_isolation_test.dart @@ -0,0 +1,65 @@ +// 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:test/test.dart'; + +import '../helpers.dart'; +import 'helpers.dart'; + +const Timeout longTimeout = Timeout(Duration(minutes: 5)); + +void main(List args) async { + if (Platform.isMacOS) { + // We don't set any compiler paths on MacOS in + // pkg/test_runner/lib/src/configuration.dart + // nativeCompilerEnvironmentVariables. + return; + } + + test('run in isolation', timeout: longTimeout, () async { + await inTempDir((tempUri) async { + String unparseKey(String key) => key.replaceAll('.', '__').toUpperCase(); + final arKey = unparseKey(CCompilerConfig.arConfigKeyFull); + final ccKey = unparseKey(CCompilerConfig.ccConfigKeyFull); + final ldKey = unparseKey(CCompilerConfig.ldConfigKeyFull); + final envScriptKey = unparseKey(CCompilerConfig.envScriptConfigKeyFull); + final envScriptArgsKey = + unparseKey(CCompilerConfig.envScriptArgsConfigKeyFull); + + await copyTestProjects(targetUri: tempUri); + final packageUri = tempUri.resolve('native_add/'); + + await runPubGet(workingDirectory: packageUri, logger: logger); + + final cc = Platform.environment[ccKey]?.fileUri; + printOnFailure( + 'Platform.environment[ccKey]: ${Platform.environment[ccKey]}'); + printOnFailure('cc: $cc'); + + final assets = await build( + packageUri, + logger, + dartExecutable, + // Manually pass in a compiler. + cCompilerConfig: CCompilerConfig( + ar: Platform.environment[arKey]?.fileUri, + cc: cc, + envScript: Platform.environment[envScriptKey]?.fileUri, + envScriptArgs: Platform.environment[envScriptArgsKey]?.split(' '), + ld: Platform.environment[ldKey]?.fileUri, + ), + // Prevent any other environment variables. + includeParentEnvironment: false, + ); + expect(assets.length, 1); + }); + }); +} + +extension on String { + Uri get fileUri => Uri.file(this); +} diff --git a/pkg/native_assets_builder/test/build_runner/build_runner_test.dart b/pkg/native_assets_builder/test/build_runner/build_runner_test.dart new file mode 100644 index 00000000000..189a9df2814 --- /dev/null +++ b/pkg/native_assets_builder/test/build_runner/build_runner_test.dart @@ -0,0 +1,52 @@ +// 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:test/test.dart'; + +import '../helpers.dart'; +import 'helpers.dart'; + +const Timeout longTimeout = Timeout(Duration(minutes: 5)); + +void main(List args) async { + test('native_add build', timeout: longTimeout, () async { + await inTempDir((tempUri) async { + await copyTestProjects(targetUri: tempUri); + final packageUri = tempUri.resolve('native_add/'); + + // First, run `pub get`, we need pub to resolve our dependencies. + await runPubGet( + workingDirectory: packageUri, + logger: logger, + ); + + // Trigger a build, should invoke build for libraries with native assets. + { + final logMessages = []; + final assets = await build(packageUri, logger, dartExecutable, + capturedLogs: logMessages); + expect( + logMessages.join('\n'), + stringContainsInOrder( + ['native_add${Platform.pathSeparator}build.dart'])); + expect(assets.length, 1); + } + + // Trigger a build, should not invoke anything. + { + final logMessages = []; + final assets = await build(packageUri, logger, dartExecutable, + capturedLogs: logMessages); + expect( + false, + logMessages + .join('\n') + .contains('native_add${Platform.pathSeparator}build.dart')); + expect(assets.length, 1); + } + }); + }); +} diff --git a/pkg/native_assets_builder/test/build_runner/helpers.dart b/pkg/native_assets_builder/test/build_runner/helpers.dart new file mode 100644 index 00000000000..443966ad295 --- /dev/null +++ b/pkg/native_assets_builder/test/build_runner/helpers.dart @@ -0,0 +1,94 @@ +// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:async'; +import 'dart:io'; + +import 'package:logging/logging.dart'; +import 'package:native_assets_builder/native_assets_builder.dart'; +import 'package:native_assets_cli/native_assets_cli.dart'; +import 'package:test/test.dart'; + +import '../helpers.dart'; + +Future runPubGet({ + required Uri workingDirectory, + required Logger logger, +}) async { + final result = await runProcess( + executable: Platform.resolvedExecutable, + arguments: ['pub', 'get'], + workingDirectory: workingDirectory, + logger: logger, + ); + expect(result.exitCode, 0); +} + +Future> build( + Uri packageUri, + Logger logger, + Uri dartExecutable, { + LinkModePreference linkModePreference = LinkModePreference.dynamic, + CCompilerConfig? cCompilerConfig, + bool includeParentEnvironment = true, + List? capturedLogs, +}) async { + StreamSubscription? subscription; + if (capturedLogs != null) { + subscription = + logger.onRecord.listen((event) => capturedLogs.add(event.message)); + } + + final assets = await NativeAssetsBuildRunner( + logger: logger, + dartExecutable: dartExecutable, + ).build( + linkModePreference: linkModePreference, + target: Target.current, + workingDirectory: packageUri, + cCompilerConfig: cCompilerConfig, + includeParentEnvironment: includeParentEnvironment, + ); + await expectAssetsExist(assets); + + if (subscription != null) { + await subscription.cancel(); + } + + return assets; +} + +Future expectAssetsExist(List assets) async { + for (final asset in assets) { + final uri = (asset.path as AssetAbsolutePath).uri; + expect( + uri.toFilePath(), + contains('${Platform.pathSeparator}.dart_tool${Platform.pathSeparator}' + 'native_assets_builder${Platform.pathSeparator}')); + final file = File.fromUri(uri); + expect(await file.exists(), true); + } +} + +Future expectSymbols({ + required Asset asset, + required List symbols, +}) async { + if (Platform.isLinux) { + final assetUri = (asset.path as AssetAbsolutePath).uri; + final nmResult = await runProcess( + executable: 'nm', + arguments: [ + '-D', + assetUri.toFilePath(), + ], + logger: logger, + ); + + expect( + nmResult.stdout, + stringContainsInOrder(symbols), + ); + } +} diff --git a/pkg/native_assets_builder/test/build_runner/metadata_test.dart b/pkg/native_assets_builder/test/build_runner/metadata_test.dart new file mode 100644 index 00000000000..a8337ceb6d4 --- /dev/null +++ b/pkg/native_assets_builder/test/build_runner/metadata_test.dart @@ -0,0 +1,41 @@ +// 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:test/test.dart'; + +import '../helpers.dart'; +import 'helpers.dart'; + +const Timeout longTimeout = Timeout(Duration(minutes: 5)); + +void main(List args) async { + test('get dependency metadata', timeout: longTimeout, () async { + await inTempDir((tempUri) async { + await copyTestProjects(targetUri: tempUri); + final packageUri = tempUri.resolve('package_reading_metadata/'); + + // First, run `pub get`, we need pub to resolve our dependencies. + await runPubGet( + workingDirectory: packageUri, + logger: logger, + ); + + // Trigger a build, should invoke build for libraries with native assets. + { + final logMessages = []; + await build(packageUri, logger, dartExecutable, + capturedLogs: logMessages); + expect( + logMessages.join('\n'), + stringContainsInOrder([ + 'package_with_metadata${Platform.pathSeparator}build.dart', + 'package_reading_metadata${Platform.pathSeparator}build.dart', + '{some_int: 3, some_key: some_value}', + ])); + } + }); + }); +} diff --git a/pkg/native_assets_builder/test/build_runner/packaging_preference_test.dart b/pkg/native_assets_builder/test/build_runner/packaging_preference_test.dart new file mode 100644 index 00000000000..bb13e8fab74 --- /dev/null +++ b/pkg/native_assets_builder/test/build_runner/packaging_preference_test.dart @@ -0,0 +1,60 @@ +// 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 'package:native_assets_cli/native_assets_cli.dart'; +import 'package:test/test.dart'; + +import '../helpers.dart'; +import 'helpers.dart'; + +const Timeout longTimeout = Timeout(Duration(minutes: 5)); + +void main(List args) async { + test('link mode preference', timeout: longTimeout, () async { + await inTempDir((tempUri) async { + await copyTestProjects(targetUri: tempUri); + final packageUri = tempUri.resolve('native_add/'); + + // First, run `pub get`, we need pub to resolve our dependencies. + await runPubGet( + workingDirectory: packageUri, + logger: logger, + ); + + final assetsDynamic = await build( + packageUri, + logger, + dartExecutable, + linkModePreference: LinkModePreference.dynamic, + ); + + final assetsPreferDynamic = await build( + packageUri, + logger, + dartExecutable, + linkModePreference: LinkModePreference.preferDynamic, + ); + + final assetsStatic = await build( + packageUri, + logger, + dartExecutable, + linkModePreference: LinkModePreference.static, + ); + + final assetsPreferStatic = await build( + packageUri, + logger, + dartExecutable, + linkModePreference: LinkModePreference.preferStatic, + ); + + // This package honors preferences. + expect(assetsDynamic.single.linkMode, LinkMode.dynamic); + expect(assetsPreferDynamic.single.linkMode, LinkMode.dynamic); + expect(assetsStatic.single.linkMode, LinkMode.static); + expect(assetsPreferStatic.single.linkMode, LinkMode.static); + }); + }); +} diff --git a/pkg/native_assets_builder/test/helpers.dart b/pkg/native_assets_builder/test/helpers.dart new file mode 100644 index 00000000000..342bc4b9d80 --- /dev/null +++ b/pkg/native_assets_builder/test/helpers.dart @@ -0,0 +1,196 @@ +// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; + +import 'package:logging/logging.dart'; +import 'package:test/test.dart'; +import 'package:yaml/yaml.dart'; + +extension UriExtension on Uri { + Uri get parent => File(toFilePath()).parent.uri; +} + +const keepTempKey = 'KEEP_TEMPORARY_DIRECTORIES'; + +Future inTempDir( + Future Function(Uri tempUri) fun, { + String? prefix, + bool keepTemp = false, +}) async { + final tempDir = await Directory.systemTemp.createTemp(prefix); + // Deal with Windows temp folder aliases. + final tempUri = + Directory(await tempDir.resolveSymbolicLinks()).uri.normalizePath(); + try { + await fun(tempUri); + } finally { + if ((!Platform.environment.containsKey(keepTempKey) || + Platform.environment[keepTempKey]!.isEmpty) && + !keepTemp) { + await tempDir.delete(recursive: true); + } + } +} + +/// Runs a process async and captures the exit code and standard out. +Future runProcess({ + required String executable, + required List arguments, + Uri? workingDirectory, + Map? environment, + bool throwOnFailure = true, + required Logger logger, +}) async { + final printWorkingDir = + workingDirectory != null && workingDirectory != Directory.current.uri; + final commandString = [ + if (printWorkingDir) '(cd ${workingDirectory.toFilePath()};', + ...?environment?.entries.map((entry) => '${entry.key}=${entry.value}'), + executable, + ...arguments.map((a) => a.contains(' ') ? "'$a'" : a), + if (printWorkingDir) ')', + ].join(' '); + + logger.info('Running `$commandString`.'); + + final stdoutBuffer = []; + final stderrBuffer = []; + final stdoutCompleter = Completer(); + final stderrCompleter = Completer(); + final Process process = await Process.start( + executable, + arguments, + workingDirectory: workingDirectory?.toFilePath(), + environment: environment, + ); + + process.stdout.transform(utf8.decoder).listen( + (s) { + logger.fine(' $s'); + stdoutBuffer.add(s); + }, + onDone: stdoutCompleter.complete, + ); + process.stderr.transform(utf8.decoder).listen( + (s) { + logger.shout(' $s'); + stderrBuffer.add(s); + }, + onDone: stderrCompleter.complete, + ); + + final int exitCode = await process.exitCode; + await stdoutCompleter.future; + final String stdout = stdoutBuffer.join(); + await stderrCompleter.future; + final String stderr = stderrBuffer.join(); + final result = RunProcessResult( + pid: process.pid, + command: '$executable ${arguments.join(' ')}', + exitCode: exitCode, + stdout: stdout, + stderr: stderr, + ); + if (throwOnFailure && result.exitCode != 0) { + throw result; + } + return result; +} + +class RunProcessResult extends ProcessResult { + final String command; + + final int _exitCode; + + @override + int get exitCode => _exitCode; + + final String _stderrString; + + @override + String get stderr => _stderrString; + + final String _stdoutString; + + @override + String get stdout => _stdoutString; + + RunProcessResult({ + required int pid, + required this.command, + required int exitCode, + required String stderr, + required String stdout, + }) : _exitCode = exitCode, + _stderrString = stderr, + _stdoutString = stdout, + super(pid, exitCode, stdout, stderr); + + @override + String toString() => '''command: $command +exitCode: $exitCode +stdout: $stdout +stderr: $stderr'''; +} + +final pkgNativeAssetsBuilderUri = Platform.script.resolve('../../'); +final testProjectsUri = + pkgNativeAssetsBuilderUri.resolve('test/test_projects/'); + +Future copyTestProjects({ + Uri? sourceUri, + required Uri targetUri, +}) async { + sourceUri ??= testProjectsUri; + final manifestUri = sourceUri.resolve('manifest.yaml'); + final manifestFile = File.fromUri(manifestUri); + final manifestString = await manifestFile.readAsString(); + final manifestYaml = loadYamlDocument(manifestString); + final manifest = [ + for (final path in manifestYaml.contents as YamlList) Uri(path: path) + ]; + final filesToCopy = + manifest.where((e) => e.pathSegments.last != 'pubspec.yaml').toList(); + final filesToModify = + manifest.where((e) => e.pathSegments.last == 'pubspec.yaml').toList(); + + for (final pathToCopy in filesToCopy) { + final sourceFile = File.fromUri(sourceUri.resolveUri(pathToCopy)); + final targetFileUri = targetUri.resolveUri(pathToCopy); + final targetDirUri = targetFileUri.parent; + final targetDir = Directory.fromUri(targetDirUri); + if (!(await targetDir.exists())) { + await targetDir.create(recursive: true); + } + + // Copying files on MacOS and Windows preserves the source timestamps. + // The builder will use the cached build if the timestamps are equal. + // So just write the file instead. + final targetFile = File.fromUri(targetFileUri); + await targetFile.writeAsBytes(await sourceFile.readAsBytes()); + } + for (final pathToModify in filesToModify) { + final sourceFile = File.fromUri(sourceUri.resolveUri(pathToModify)); + final targetFileUri = targetUri.resolveUri(pathToModify); + final sourceString = await sourceFile.readAsString(); + final modifiedString = sourceString.replaceAll( + 'path: ../../../', + 'path: ${pkgNativeAssetsBuilderUri.toFilePath().replaceAll('\\', '/')}', + ); + await File.fromUri(targetFileUri) + .writeAsString(modifiedString, flush: true); + } +} + +/// Logger that outputs the full trace when a test fails. +final logger = Logger('') + ..level = Level.ALL + ..onRecord.listen((record) { + printOnFailure('${record.level.name}: ${record.time}: ${record.message}'); + }); + +final dartExecutable = File(Platform.resolvedExecutable).uri; diff --git a/pkg/native_assets_builder/test/test_projects/README.md b/pkg/native_assets_builder/test/test_projects/README.md new file mode 100644 index 00000000000..311839a711b --- /dev/null +++ b/pkg/native_assets_builder/test/test_projects/README.md @@ -0,0 +1,6 @@ +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 +other. diff --git a/pkg/native_assets_builder/test/test_projects/dart_app/.gitignore b/pkg/native_assets_builder/test/test_projects/dart_app/.gitignore new file mode 100644 index 00000000000..2a9e6b8706f --- /dev/null +++ b/pkg/native_assets_builder/test/test_projects/dart_app/.gitignore @@ -0,0 +1,2 @@ +.dart_tool +bin/dart_app/ diff --git a/pkg/native_assets_builder/test/test_projects/dart_app/bin/dart_app.dart b/pkg/native_assets_builder/test/test_projects/dart_app/bin/dart_app.dart new file mode 100644 index 00000000000..4e9dc84b80c --- /dev/null +++ b/pkg/native_assets_builder/test/test_projects/dart_app/bin/dart_app.dart @@ -0,0 +1,27 @@ +// 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 'package:native_add/native_add.dart'; +import 'package:native_subtract/native_subtract.dart'; + +void main() { + testNativeAdd(); + testNativeSubtract(); +} + +void testNativeAdd() { + final answer = add(5, 6); + if (answer != 5 + 6) { + throw 'Wrong answer'; + } + print('add(5, 6) = $answer'); +} + +void testNativeSubtract() { + final answer = subtract(5, 6); + if (answer != 5 - 6) { + throw 'Wrong answer'; + } + print('subtract(5, 6) = $answer'); +} diff --git a/pkg/native_assets_builder/test/test_projects/dart_app/pubspec.yaml b/pkg/native_assets_builder/test/test_projects/dart_app/pubspec.yaml new file mode 100644 index 00000000000..c84892e170b --- /dev/null +++ b/pkg/native_assets_builder/test/test_projects/dart_app/pubspec.yaml @@ -0,0 +1,16 @@ +name: dart_app + +publish_to: none + +environment: + sdk: ">=2.18.0 <3.0.0" + +dependencies: + native_add: + path: ../native_add + native_subtract: + path: ../native_subtract + +dependency_overrides: + native_assets_cli: + path: ../../../../../third_party/pkg/native/pkgs/native_assets_cli/ diff --git a/pkg/native_assets_builder/test/test_projects/manifest.yaml b/pkg/native_assets_builder/test/test_projects/manifest.yaml new file mode 100644 index 00000000000..ea2c3a1f8e9 --- /dev/null +++ b/pkg/native_assets_builder/test/test_projects/manifest.yaml @@ -0,0 +1,25 @@ +# The list of files to copy to a temporary folder to ensure running tests from +# a completely clean setup. +- dart_app/bin/dart_app.dart +- dart_app/pubspec.yaml +- native_add/build.dart +- native_add/ffigen.yaml +- native_add/lib/native_add.dart +- native_add/lib/src/native_add_bindings_generated.dart +- native_add/lib/src/native_add.dart +- native_add/pubspec.yaml +- native_add/src/native_add.c +- native_add/src/native_add.h +- native_add/test/native_add_test.dart +- native_subtract/build.dart +- native_subtract/ffigen.yaml +- native_subtract/lib/native_subtract.dart +- native_subtract/lib/src/native_subtract_bindings_generated.dart +- native_subtract/lib/src/native_subtract.dart +- native_subtract/pubspec.yaml +- native_subtract/src/native_subtract.c +- native_subtract/src/native_subtract.h +- package_reading_metadata/build.dart +- package_reading_metadata/pubspec.yaml +- package_with_metadata/build.dart +- package_with_metadata/pubspec.yaml diff --git a/pkg/native_assets_builder/test/test_projects/native_add/build.dart b/pkg/native_assets_builder/test/test_projects/native_add/build.dart new file mode 100644 index 00000000000..2f9f43f2f41 --- /dev/null +++ b/pkg/native_assets_builder/test/test_projects/native_add/build.dart @@ -0,0 +1,32 @@ +// 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 'package:c_compiler/c_compiler.dart'; +import 'package:logging/logging.dart'; +import 'package:native_assets_cli/native_assets_cli.dart'; + +const packageName = 'native_add'; + +void main(List args) async { + final buildConfig = await BuildConfig.fromArgs(args); + final buildOutput = BuildOutput(); + final cbuilder = CBuilder.library( + name: packageName, + assetName: + 'package:$packageName/src/${packageName}_bindings_generated.dart', + sources: [ + 'src/$packageName.c', + ], + ); + await cbuilder.run( + buildConfig: buildConfig, + buildOutput: buildOutput, + logger: Logger('') + ..level = Level.ALL + ..onRecord.listen((record) { + print('${record.level.name}: ${record.time}: ${record.message}'); + }), + ); + await buildOutput.writeToFile(outDir: buildConfig.outDir); +} diff --git a/pkg/native_assets_builder/test/test_projects/native_add/ffigen.yaml b/pkg/native_assets_builder/test/test_projects/native_add/ffigen.yaml new file mode 100644 index 00000000000..b8716bd2d5a --- /dev/null +++ b/pkg/native_assets_builder/test/test_projects/native_add/ffigen.yaml @@ -0,0 +1,20 @@ +# Run with `flutter pub run ffigen --config ffigen.yaml`. +name: NativeAddBindings +description: | + Bindings for `src/native_add.h`. + + Regenerate bindings with `flutter pub run ffigen --config ffigen.yaml`. +output: "lib/src/native_add_bindings_generated.dart" +headers: + entry-points: + - "src/native_add.h" + include-directives: + - "src/native_add.h" +preamble: | + // 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. +comments: + style: any + length: full +ffi-native: diff --git a/pkg/native_assets_builder/test/test_projects/native_add/lib/native_add.dart b/pkg/native_assets_builder/test/test_projects/native_add/lib/native_add.dart new file mode 100644 index 00000000000..9eda681ab4e --- /dev/null +++ b/pkg/native_assets_builder/test/test_projects/native_add/lib/native_add.dart @@ -0,0 +1,5 @@ +// 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. + +export 'src/native_add.dart'; diff --git a/pkg/native_assets_builder/test/test_projects/native_add/lib/src/native_add.dart b/pkg/native_assets_builder/test/test_projects/native_add/lib/src/native_add.dart new file mode 100644 index 00000000000..8d050916830 --- /dev/null +++ b/pkg/native_assets_builder/test/test_projects/native_add/lib/src/native_add.dart @@ -0,0 +1,9 @@ +// 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 'native_add_bindings_generated.dart' as bindings; + +// TODO(dacoharkes): This will fail lookup until the Dart SDK consumes the +// output of `build.dart`. +int add(int a, int b) => bindings.add(a, b); diff --git a/pkg/native_assets_builder/test/test_projects/native_add/lib/src/native_add_bindings_generated.dart b/pkg/native_assets_builder/test/test_projects/native_add/lib/src/native_add_bindings_generated.dart new file mode 100644 index 00000000000..07b6fc5f6be --- /dev/null +++ b/pkg/native_assets_builder/test/test_projects/native_add/lib/src/native_add_bindings_generated.dart @@ -0,0 +1,15 @@ +// 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. + +// AUTO GENERATED FILE, DO NOT EDIT. +// +// Generated by `package:ffigen`. +// ignore_for_file: type=lint +import 'dart:ffi' as ffi; + +@ffi.FfiNative('add') +external int add( + int a, + int b, +); diff --git a/pkg/native_assets_builder/test/test_projects/native_add/pubspec.yaml b/pkg/native_assets_builder/test/test_projects/native_add/pubspec.yaml new file mode 100644 index 00000000000..ad19ab28761 --- /dev/null +++ b/pkg/native_assets_builder/test/test_projects/native_add/pubspec.yaml @@ -0,0 +1,26 @@ +name: native_add +description: Sums two numbers with native code. +version: 0.1.0 + +publish_to: none + +environment: + sdk: ">=2.19.3 <4.0.0" + +dependencies: + c_compiler: + path: ../../../../../third_party/pkg/native/pkgs/c_compiler/ + cli_config: ^0.1.1 + logging: ^1.1.1 + native_assets_cli: + path: ../../../../../third_party/pkg/native/pkgs/native_assets_cli/ + +dev_dependencies: + ffigen: ^7.2.9 + lints: ^2.0.0 + test: ^1.23.1 + +dependency_overrides: + native_assets_cli: + path: ../../../../../third_party/pkg/native/pkgs/native_assets_cli/ + diff --git a/pkg/native_assets_builder/test/test_projects/native_add/src/native_add.c b/pkg/native_assets_builder/test/test_projects/native_add/src/native_add.c new file mode 100644 index 00000000000..cf21076d1dc --- /dev/null +++ b/pkg/native_assets_builder/test/test_projects/native_add/src/native_add.c @@ -0,0 +1,9 @@ +// 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. + +#include "native_add.h" + +int32_t add(int32_t a, int32_t b) { + return a + b; +} diff --git a/pkg/native_assets_builder/test/test_projects/native_add/src/native_add.h b/pkg/native_assets_builder/test/test_projects/native_add/src/native_add.h new file mode 100644 index 00000000000..5824f98a7de --- /dev/null +++ b/pkg/native_assets_builder/test/test_projects/native_add/src/native_add.h @@ -0,0 +1,13 @@ +// 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. + +#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/pkg/native_assets_builder/test/test_projects/native_add/test/native_add_test.dart b/pkg/native_assets_builder/test/test_projects/native_add/test/native_add_test.dart new file mode 100644 index 00000000000..7531a620994 --- /dev/null +++ b/pkg/native_assets_builder/test/test_projects/native_add/test/native_add_test.dart @@ -0,0 +1,13 @@ +// 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 'package:native_add/native_add.dart'; +import 'package:test/test.dart'; + +void main() { + test('native add test', () { + final result = add(4, 6); + expect(result, equals(10)); + }); +} diff --git a/pkg/native_assets_builder/test/test_projects/native_add_add_source/README.md b/pkg/native_assets_builder/test/test_projects/native_add_add_source/README.md new file mode 100644 index 00000000000..5a1b8071ee0 --- /dev/null +++ b/pkg/native_assets_builder/test/test_projects/native_add_add_source/README.md @@ -0,0 +1 @@ +Used in automated testing to modify the native_add project. diff --git a/pkg/native_assets_builder/test/test_projects/native_add_add_source/build.dart b/pkg/native_assets_builder/test/test_projects/native_add_add_source/build.dart new file mode 100644 index 00000000000..6a6b3f2ba26 --- /dev/null +++ b/pkg/native_assets_builder/test/test_projects/native_add_add_source/build.dart @@ -0,0 +1,33 @@ +// 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 'package:c_compiler/c_compiler.dart'; +import 'package:logging/logging.dart'; +import 'package:native_assets_cli/native_assets_cli.dart'; + +const packageName = 'native_add'; + +void main(List args) async { + final buildConfig = await BuildConfig.fromArgs(args); + final buildOutput = BuildOutput(); + final cbuilder = CBuilder.library( + name: packageName, + assetName: + 'package:$packageName/src/${packageName}_bindings_generated.dart', + sources: [ + 'src/$packageName.c', + 'src/native_multiply.c', + ], + ); + await cbuilder.run( + buildConfig: buildConfig, + buildOutput: buildOutput, + logger: Logger('') + ..level = Level.ALL + ..onRecord.listen((record) { + print('${record.level.name}: ${record.time}: ${record.message}'); + }), + ); + await buildOutput.writeToFile(outDir: buildConfig.outDir); +} diff --git a/pkg/native_assets_builder/test/test_projects/native_add_add_source/manifest.yaml b/pkg/native_assets_builder/test/test_projects/native_add_add_source/manifest.yaml new file mode 100644 index 00000000000..6f90bc1ee18 --- /dev/null +++ b/pkg/native_assets_builder/test/test_projects/native_add_add_source/manifest.yaml @@ -0,0 +1,3 @@ +- build.dart +- src/native_multiply.c +- src/native_multiply.h \ No newline at end of file diff --git a/pkg/native_assets_builder/test/test_projects/native_add_add_source/src/native_multiply.c b/pkg/native_assets_builder/test/test_projects/native_add_add_source/src/native_multiply.c new file mode 100644 index 00000000000..7801f65f451 --- /dev/null +++ b/pkg/native_assets_builder/test/test_projects/native_add_add_source/src/native_multiply.c @@ -0,0 +1,9 @@ +// 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. + +#include "native_multiply.h" + +MYLIB_EXPORT intptr_t multiply(intptr_t a, intptr_t b) { + return a * b; +} diff --git a/pkg/native_assets_builder/test/test_projects/native_add_add_source/src/native_multiply.h b/pkg/native_assets_builder/test/test_projects/native_add_add_source/src/native_multiply.h new file mode 100644 index 00000000000..082757175ac --- /dev/null +++ b/pkg/native_assets_builder/test/test_projects/native_add_add_source/src/native_multiply.h @@ -0,0 +1,13 @@ +// 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. + +#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/pkg/native_assets_builder/test/test_projects/native_add_add_symbol/README.md b/pkg/native_assets_builder/test/test_projects/native_add_add_symbol/README.md new file mode 100644 index 00000000000..5a1b8071ee0 --- /dev/null +++ b/pkg/native_assets_builder/test/test_projects/native_add_add_symbol/README.md @@ -0,0 +1 @@ +Used in automated testing to modify the native_add project. diff --git a/pkg/native_assets_builder/test/test_projects/native_add_add_symbol/manifest.yaml b/pkg/native_assets_builder/test/test_projects/native_add_add_symbol/manifest.yaml new file mode 100644 index 00000000000..9a6b5a433c2 --- /dev/null +++ b/pkg/native_assets_builder/test/test_projects/native_add_add_symbol/manifest.yaml @@ -0,0 +1,2 @@ +- src/native_add.c +- src/native_add.h \ No newline at end of file diff --git a/pkg/native_assets_builder/test/test_projects/native_add_add_symbol/src/native_add.c b/pkg/native_assets_builder/test/test_projects/native_add_add_symbol/src/native_add.c new file mode 100644 index 00000000000..a493add382c --- /dev/null +++ b/pkg/native_assets_builder/test/test_projects/native_add_add_symbol/src/native_add.c @@ -0,0 +1,14 @@ +// 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. + +#include "native_add.h" + +MYLIB_EXPORT int32_t add(int32_t a, int32_t b) { + return a + b; +} + + +MYLIB_EXPORT intptr_t subtract(intptr_t a, intptr_t b) { + return a - b; +} diff --git a/pkg/native_assets_builder/test/test_projects/native_add_add_symbol/src/native_add.h b/pkg/native_assets_builder/test/test_projects/native_add_add_symbol/src/native_add.h new file mode 100644 index 00000000000..37d53b6fc0d --- /dev/null +++ b/pkg/native_assets_builder/test/test_projects/native_add_add_symbol/src/native_add.h @@ -0,0 +1,15 @@ +// 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. + +#include + +#if _WIN32 +#define MYLIB_EXPORT __declspec(dllexport) +#else +#define MYLIB_EXPORT +#endif + +MYLIB_EXPORT int32_t add(int32_t a, int32_t b); + +MYLIB_EXPORT intptr_t subtract(intptr_t a, intptr_t b); diff --git a/pkg/native_assets_builder/test/test_projects/native_add_break_build/README.md b/pkg/native_assets_builder/test/test_projects/native_add_break_build/README.md new file mode 100644 index 00000000000..5a1b8071ee0 --- /dev/null +++ b/pkg/native_assets_builder/test/test_projects/native_add_break_build/README.md @@ -0,0 +1 @@ +Used in automated testing to modify the native_add project. diff --git a/pkg/native_assets_builder/test/test_projects/native_add_break_build/manifest.yaml b/pkg/native_assets_builder/test/test_projects/native_add_break_build/manifest.yaml new file mode 100644 index 00000000000..ea69afd5304 --- /dev/null +++ b/pkg/native_assets_builder/test/test_projects/native_add_break_build/manifest.yaml @@ -0,0 +1 @@ +- src/native_add.c \ No newline at end of file diff --git a/pkg/native_assets_builder/test/test_projects/native_add_break_build/src/native_add.c b/pkg/native_assets_builder/test/test_projects/native_add_break_build/src/native_add.c new file mode 100644 index 00000000000..ba06d8bba3c --- /dev/null +++ b/pkg/native_assets_builder/test/test_projects/native_add_break_build/src/native_add.c @@ -0,0 +1,9 @@ +// 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. + +#include "native_add.h" + +MYLIB_EXPORT int32_t add(int32_t different_parameters) { + return a + b; +} \ No newline at end of file diff --git a/pkg/native_assets_builder/test/test_projects/native_subtract/build.dart b/pkg/native_assets_builder/test/test_projects/native_subtract/build.dart new file mode 100644 index 00000000000..2f1b36d63ef --- /dev/null +++ b/pkg/native_assets_builder/test/test_projects/native_subtract/build.dart @@ -0,0 +1,32 @@ +// 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 'package:c_compiler/c_compiler.dart'; +import 'package:logging/logging.dart'; +import 'package:native_assets_cli/native_assets_cli.dart'; + +const packageName = 'native_subtract'; + +void main(List args) async { + final buildConfig = await BuildConfig.fromArgs(args); + final buildOutput = BuildOutput(); + final cbuilder = CBuilder.library( + name: packageName, + assetName: + 'package:$packageName/src/${packageName}_bindings_generated.dart', + sources: [ + 'src/$packageName.c', + ], + ); + await cbuilder.run( + buildConfig: buildConfig, + buildOutput: buildOutput, + logger: Logger('') + ..level = Level.ALL + ..onRecord.listen((record) { + print('${record.level.name}: ${record.time}: ${record.message}'); + }), + ); + await buildOutput.writeToFile(outDir: buildConfig.outDir); +} diff --git a/pkg/native_assets_builder/test/test_projects/native_subtract/ffigen.yaml b/pkg/native_assets_builder/test/test_projects/native_subtract/ffigen.yaml new file mode 100644 index 00000000000..5f79dbe5a58 --- /dev/null +++ b/pkg/native_assets_builder/test/test_projects/native_subtract/ffigen.yaml @@ -0,0 +1,20 @@ +# Run with `flutter pub run ffigen --config ffigen.yaml`. +name: NativeAddBindings +description: | + Bindings for `src/native_subtract.h`. + + Regenerate bindings with `flutter pub run ffigen --config ffigen.yaml`. +output: "lib/src/native_subtract_bindings_generated.dart" +headers: + entry-points: + - "src/native_subtract.h" + include-directives: + - "src/native_subtract.h" +preamble: | + // 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. +comments: + style: any + length: full +ffi-native: diff --git a/pkg/native_assets_builder/test/test_projects/native_subtract/lib/native_subtract.dart b/pkg/native_assets_builder/test/test_projects/native_subtract/lib/native_subtract.dart new file mode 100644 index 00000000000..a2831561e4b --- /dev/null +++ b/pkg/native_assets_builder/test/test_projects/native_subtract/lib/native_subtract.dart @@ -0,0 +1,5 @@ +// 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. + +export 'src/native_subtract.dart'; diff --git a/pkg/native_assets_builder/test/test_projects/native_subtract/lib/src/native_subtract.dart b/pkg/native_assets_builder/test/test_projects/native_subtract/lib/src/native_subtract.dart new file mode 100644 index 00000000000..2068d24e38a --- /dev/null +++ b/pkg/native_assets_builder/test/test_projects/native_subtract/lib/src/native_subtract.dart @@ -0,0 +1,9 @@ +// 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 'native_subtract_bindings_generated.dart' as bindings; + +// TODO(dacoharkes): This will fail lookup until the Dart SDK consumes the +// output of `build.dart`. +int subtract(int a, int b) => bindings.subtract(a, b); diff --git a/pkg/native_assets_builder/test/test_projects/native_subtract/lib/src/native_subtract_bindings_generated.dart b/pkg/native_assets_builder/test/test_projects/native_subtract/lib/src/native_subtract_bindings_generated.dart new file mode 100644 index 00000000000..345fc59b3fe --- /dev/null +++ b/pkg/native_assets_builder/test/test_projects/native_subtract/lib/src/native_subtract_bindings_generated.dart @@ -0,0 +1,15 @@ +// 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. + +// AUTO GENERATED FILE, DO NOT EDIT. +// +// Generated by `package:ffigen`. +// ignore_for_file: type=lint +import 'dart:ffi' as ffi; + +@ffi.FfiNative('subtract') +external int subtract( + int a, + int b, +); diff --git a/pkg/native_assets_builder/test/test_projects/native_subtract/pubspec.yaml b/pkg/native_assets_builder/test/test_projects/native_subtract/pubspec.yaml new file mode 100644 index 00000000000..1fced1c1b47 --- /dev/null +++ b/pkg/native_assets_builder/test/test_projects/native_subtract/pubspec.yaml @@ -0,0 +1,25 @@ +name: native_subtract +description: Subtracts two numbers with native code. +version: 0.1.0 + +publish_to: none + +environment: + sdk: ">=2.19.3 <4.0.0" + +dependencies: + c_compiler: + path: ../../../../../third_party/pkg/native/pkgs/c_compiler/ + cli_config: ^0.1.1 + logging: ^1.1.1 + native_assets_cli: + path: ../../../../../third_party/pkg/native/pkgs/native_assets_cli/ + +dev_dependencies: + ffigen: ^7.2.9 + lints: ^2.0.0 + test: ^1.23.1 + +dependency_overrides: + native_assets_cli: + path: ../../../../../third_party/pkg/native/pkgs/native_assets_cli/ diff --git a/pkg/native_assets_builder/test/test_projects/native_subtract/src/native_subtract.c b/pkg/native_assets_builder/test/test_projects/native_subtract/src/native_subtract.c new file mode 100644 index 00000000000..6562ae6aae2 --- /dev/null +++ b/pkg/native_assets_builder/test/test_projects/native_subtract/src/native_subtract.c @@ -0,0 +1,9 @@ +// 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. + +#include "native_subtract.h" + +int32_t subtract(int32_t a, int32_t b) { + return a - b; +} diff --git a/pkg/native_assets_builder/test/test_projects/native_subtract/src/native_subtract.h b/pkg/native_assets_builder/test/test_projects/native_subtract/src/native_subtract.h new file mode 100644 index 00000000000..407fc5d2ec1 --- /dev/null +++ b/pkg/native_assets_builder/test/test_projects/native_subtract/src/native_subtract.h @@ -0,0 +1,13 @@ +// 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. + +#include + +#if _WIN32 +#define MYLIB_EXPORT __declspec(dllexport) +#else +#define MYLIB_EXPORT +#endif + +MYLIB_EXPORT int32_t subtract(int32_t a, int32_t b); diff --git a/pkg/native_assets_builder/test/test_projects/package_reading_metadata/build.dart b/pkg/native_assets_builder/test/test_projects/package_reading_metadata/build.dart new file mode 100644 index 00000000000..b291c859368 --- /dev/null +++ b/pkg/native_assets_builder/test/test_projects/package_reading_metadata/build.dart @@ -0,0 +1,18 @@ +// 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:cli_config/cli_config.dart'; +import 'package:native_assets_cli/native_assets_cli.dart'; + +void main(List args) async { + final config = await Config.fromArgs(args: args); + final buildConfig = BuildConfig.fromConfig(config); + final metadata = + buildConfig.dependencyMetadata!['package_with_metadata']!.metadata; + final someValue = metadata['some_key']!; + final someInt = metadata['some_int']!; + print(metadata); +} diff --git a/pkg/native_assets_builder/test/test_projects/package_reading_metadata/pubspec.yaml b/pkg/native_assets_builder/test/test_projects/package_reading_metadata/pubspec.yaml new file mode 100644 index 00000000000..d5e1537976c --- /dev/null +++ b/pkg/native_assets_builder/test/test_projects/package_reading_metadata/pubspec.yaml @@ -0,0 +1,21 @@ +name: package_reading_metadata +description: Reads some metadata in its build.dart +version: 0.1.0 + +publish_to: none + +environment: + sdk: ">=2.19.3 <4.0.0" + +dependencies: + cli_config: ^0.1.1 + native_assets_cli: + path: ../../../../../third_party/pkg/native/pkgs/native_assets_cli/ + package_with_metadata: + path: ../package_with_metadata/ + yaml: ^3.1.1 + yaml_edit: ^2.1.0 + +dev_dependencies: + lints: ^2.0.0 + diff --git a/pkg/native_assets_builder/test/test_projects/package_with_metadata/build.dart b/pkg/native_assets_builder/test/test_projects/package_with_metadata/build.dart new file mode 100644 index 00000000000..c30d4b57456 --- /dev/null +++ b/pkg/native_assets_builder/test/test_projects/package_with_metadata/build.dart @@ -0,0 +1,18 @@ +// 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 'package:native_assets_cli/native_assets_cli.dart'; + +const packageName = 'native_add'; + +void main(List args) async { + final buildConfig = await BuildConfig.fromArgs(args); + final buildOutput = BuildOutput( + metadata: Metadata({ + 'some_key': 'some_value', + 'some_int': 3, + }), + ); + await buildOutput.writeToFile(outDir: buildConfig.outDir); +} diff --git a/pkg/native_assets_builder/test/test_projects/package_with_metadata/pubspec.yaml b/pkg/native_assets_builder/test/test_projects/package_with_metadata/pubspec.yaml new file mode 100644 index 00000000000..dbef556ed32 --- /dev/null +++ b/pkg/native_assets_builder/test/test_projects/package_with_metadata/pubspec.yaml @@ -0,0 +1,19 @@ +name: package_with_metadata +description: Sets some metadata in its build.dart +version: 0.1.0 + +publish_to: none + +environment: + sdk: ">=2.19.3 <4.0.0" + +dependencies: + cli_config: ^0.1.1 + native_assets_cli: + path: ../../../../../third_party/pkg/native/pkgs/native_assets_cli/ + yaml: ^3.1.1 + yaml_edit: ^2.1.0 + +dev_dependencies: + lints: ^2.0.0 + diff --git a/pkg/pkg.status b/pkg/pkg.status index 2c3ee710184..438712109a5 100644 --- a/pkg/pkg.status +++ b/pkg/pkg.status @@ -63,6 +63,7 @@ front_end/test/incremental_dart2js_test: Pass, Slow front_end/testcases/*: Skip # These are not tests but input for tests. front_end/tool/incremental_perf_test: Slow, Pass kernel/testcases/*: Skip # These are not tests but input for tests. +native_assets_builder/test/test_projects/*: SkipByDesign # These test projects don't work without running pub in the project itself. vm/test/kernel_front_end_test: Slow, Pass vm/test/transformations/type_flow/transformer_test: Slow, Pass vm/testcases/*: SkipByDesign # These are not tests but input for tests. @@ -89,6 +90,7 @@ front_end/tool/*: SkipByDesign # Only meant to run on vm modular_test/test/memory_pipeline_test: Slow, Pass modular_test/test/validate_pipeline_test: Slow, Pass modular_test/test/validate_suite_test: Slow, Pass +native_assets_builder/test/*: SkipByDesign # Only meant to run on vm nnbd_migration/test/*: SkipByDesign # Uses mirrors nnbd_migration/tool/*: SkipByDesign # Only meant to run on vm smith/test/*: SkipByDesign # Only meant to run on vm diff --git a/sdk/lib/_internal/allowed_experiments.json b/sdk/lib/_internal/allowed_experiments.json index 7b1568e07b3..64b0300fb6d 100644 --- a/sdk/lib/_internal/allowed_experiments.json +++ b/sdk/lib/_internal/allowed_experiments.json @@ -105,6 +105,9 @@ "meta": { "experimentSet": "nullSafety" }, + "native_assets_builder": { + "experimentSet": "nullSafety" + }, "native_stack_traces": { "experimentSet": "nullSafety" }, diff --git a/tools/bots/test_matrix.json b/tools/bots/test_matrix.json index b9514f5cfce..1388cf663b3 100644 --- a/tools/bots/test_matrix.json +++ b/tools/bots/test_matrix.json @@ -276,6 +276,7 @@ "pkg/kernel/", "pkg/meta/", "pkg/native_stack_traces/", + "pkg/native_assets_builder/", "pkg/pkg.status", "pkg/smith/", "pkg/status_file/", diff --git a/tools/package_deps/bin/package_deps.dart b/tools/package_deps/bin/package_deps.dart index c56b6a11d33..229c3ad9e22 100644 --- a/tools/package_deps/bin/package_deps.dart +++ b/tools/package_deps/bin/package_deps.dart @@ -353,6 +353,11 @@ class Package implements Comparable { continue; } + // Skip 'pkg/native_assets_builder/test/test_projects/'. + if (name == 'test_projects') { + continue; + } + if (!name.startsWith('.')) { _collectDartFiles(entity, files); }