1
0
mirror of https://github.com/dart-lang/sdk synced 2024-07-05 17:30:16 +00:00

[pkg][vm] Native Assets builder

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 without flutter_tools having to import dartdev.

Some design decisions:

* We don't yet have `build_dependencies`, so we use the ordinary
  dependency graph for ordering of native assets builds. (If there is
  a cycle we refuse to run.)
  Bug: https://github.com/dart-lang/pub/issues/3794
* Builds are cached based on all the configuration provided by the
  caller. Environment variables are ignored in caching. This CL also
  contains a unit test that invokes the build by not passing through
  environment variables. However, for Windows we need to pass through
  at least `SYSTEMROOT` for MSVC to run correctly. So we might need
  to further explore if we can/want to lock env variables down.
  Bug: https://github.com/dart-lang/native/issues/32
  Bug: https://github.com/dart-lang/native/issues/33

Run tests:
```
dart tools/generate_package_config.dart && \
tools/test.py -n unittest-asserts-release-linux pkg/native_assets_builder
```

Bug: https://github.com/dart-lang/sdk/issues/50565
Change-Id: I133052d7195373e87d20924d61e1e96e3d34ce8f
Cq-Include-Trybots: luci.dart.try:pkg-linux-debug-try,pkg-linux-release-try,pkg-mac-release-arm64-try,pkg-mac-release-try,pkg-win-release-try
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/300203
Reviewed-by: Liam Appelbe <liama@google.com>
Commit-Queue: Daco Harkes <dacoharkes@google.com>
Reviewed-by: Martin Kustermann <kustermann@google.com>
Reviewed-by: Hossein Yousefi <yousefi@google.com>
This commit is contained in:
Daco Harkes 2023-05-15 13:49:30 +00:00 committed by Commit Queue
parent 05e77a934e
commit d360edf2f3
69 changed files with 2080 additions and 1 deletions

3
.gitignore vendored
View File

@ -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

2
DEPS
View File

@ -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",

View File

@ -858,6 +858,9 @@ const AllowedExperimentalFlags defaultAllowedExperimentalFlags =
"meta": {
ExperimentalFlag.nonNullable,
},
"native_assets_builder": {
ExperimentalFlag.nonNullable,
},
"native_stack_traces": {
ExperimentalFlag.nonNullable,
},

View File

@ -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

48
pkg/native_assets_builder/.gitignore vendored Normal file
View File

@ -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

View File

@ -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 <email address>
Google LLC

View File

@ -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.

View File

@ -0,0 +1 @@
file:/tools/OWNERS_VM

View File

@ -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.

View File

@ -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

View File

@ -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';

View File

@ -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<Package> packagesWithNativeAssets;
final Uri dartExecutable;
NativeAssetsBuildPlanner({
required this.packageGraph,
required this.packagesWithNativeAssets,
required this.dartExecutable,
});
static Future<NativeAssetsBuildPlanner> fromRootPackageRoot({
required Uri rootPackageRoot,
required List<Package> 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<Package> plan() {
final packageMap = {
for (final package in packagesWithNativeAssets) package.name: package
};
final packagesToBuild = packageMap.keys.toSet();
final stronglyConnectedComponents = packageGraph.computeStrongComponents();
final result = <Package>[];
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<String> {
final Map<String, List<String>> 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<dynamic, dynamic>);
/// Construct a graph from the JSON produced by `dart pub deps --json`.
factory PackageGraph.fromPubDepsJson(Map<dynamic, dynamic> map) {
final result = <String, List<String>>{};
final packages = map['packages'] as List<dynamic>;
for (final package in packages) {
final package_ = package as Map<dynamic, dynamic>;
final name = package_['name'] as String;
final dependencies = (package_['dependencies'] as List<dynamic>)
.whereType<String>()
.toList();
result[name] = dependencies;
}
return PackageGraph(result);
}
@override
Iterable<String> neighborsOf(String vertex) => map[vertex] ?? [];
@override
Iterable<String> get vertices => map.keys;
List<List<String>> computeStrongComponents() =>
graph.computeStrongComponents(this);
}

View File

@ -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<String, Metadata>;
/// 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 = <Target, DependencyMetadata>{};
/// [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<List<Asset>> 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 = <Asset>[];
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<List<Asset>> _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<List<Asset>> _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<BuildConfig> _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);
}

View File

@ -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<PackageLayout> 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<List<Package>> packagesWithNativeAssets = () async {
final result = <Package>[];
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;
}();
}

View File

@ -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<File> 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<DateTime> 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<FileSystemEntity> {
Future<DateTime> 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<DateTime> 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;
}
}

View File

@ -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<RunProcessResult> runProcess({
required String executable,
required List<String> arguments,
Uri? workingDirectory,
Map<String, String>? 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 = <String>[];
final stderrBuffer = <String>[];
final stdoutCompleter = Completer<Object?>();
final stderrCompleter = Completer<Object?>();
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;
}

View File

@ -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

View File

@ -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<String> 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 = <String>[];
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);
}
});
});
}

View File

@ -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');
});
});
}

View File

@ -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<String> 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 = <String>[];
await build(packageUri, logger, dartExecutable,
capturedLogs: logMessages);
expect(
logMessages.join('\n'),
stringContainsInOrder(
['native_add${Platform.pathSeparator}build.dart']));
}
{
final logMessages = <String>[];
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']);
}
});
});
}

View File

@ -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<String> 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, <Asset>[]);
}
});
});
}

View File

@ -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<String> 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);
}

View File

@ -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<String> 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 = <String>[];
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 = <String>[];
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);
}
});
});
}

View File

@ -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<void> 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<List<Asset>> build(
Uri packageUri,
Logger logger,
Uri dartExecutable, {
LinkModePreference linkModePreference = LinkModePreference.dynamic,
CCompilerConfig? cCompilerConfig,
bool includeParentEnvironment = true,
List<String>? capturedLogs,
}) async {
StreamSubscription<LogRecord>? 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<void> expectAssetsExist(List<Asset> 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<void> expectSymbols({
required Asset asset,
required List<String> 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),
);
}
}

View File

@ -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<String> 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 = <String>[];
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}',
]));
}
});
});
}

View File

@ -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<String> 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);
});
});
}

View File

@ -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<void> inTempDir(
Future<void> 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<RunProcessResult> runProcess({
required String executable,
required List<String> arguments,
Uri? workingDirectory,
Map<String, String>? 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 = <String>[];
final stderrBuffer = <String>[];
final stdoutCompleter = Completer<Object?>();
final stderrCompleter = Completer<Object?>();
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<void> 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;

View File

@ -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.

View File

@ -0,0 +1,2 @@
.dart_tool
bin/dart_app/

View File

@ -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');
}

View File

@ -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/

View File

@ -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

View File

@ -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<String> 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);
}

View File

@ -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:

View File

@ -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';

View File

@ -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);

View File

@ -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<ffi.Int32 Function(ffi.Int32, ffi.Int32)>('add')
external int add(
int a,
int b,
);

View File

@ -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/

View File

@ -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;
}

View File

@ -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 <stdint.h>
#if _WIN32
#define MYLIB_EXPORT __declspec(dllexport)
#else
#define MYLIB_EXPORT
#endif
MYLIB_EXPORT int32_t add(int32_t a, int32_t b);

View File

@ -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));
});
}

View File

@ -0,0 +1 @@
Used in automated testing to modify the native_add project.

View File

@ -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<String> 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);
}

View File

@ -0,0 +1,3 @@
- build.dart
- src/native_multiply.c
- src/native_multiply.h

View File

@ -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;
}

View File

@ -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 <stdint.h>
#if _WIN32
#define MYLIB_EXPORT __declspec(dllexport)
#else
#define MYLIB_EXPORT
#endif
MYLIB_EXPORT intptr_t multiply(intptr_t a, intptr_t b);

View File

@ -0,0 +1 @@
Used in automated testing to modify the native_add project.

View File

@ -0,0 +1,2 @@
- src/native_add.c
- src/native_add.h

View File

@ -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;
}

View File

@ -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 <stdint.h>
#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);

View File

@ -0,0 +1 @@
Used in automated testing to modify the native_add project.

View File

@ -0,0 +1 @@
- src/native_add.c

View File

@ -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;
}

View File

@ -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<String> 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);
}

View File

@ -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:

View File

@ -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';

View File

@ -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);

View File

@ -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<ffi.Int32 Function(ffi.Int32, ffi.Int32)>('subtract')
external int subtract(
int a,
int b,
);

View File

@ -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/

View File

@ -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;
}

View File

@ -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 <stdint.h>
#if _WIN32
#define MYLIB_EXPORT __declspec(dllexport)
#else
#define MYLIB_EXPORT
#endif
MYLIB_EXPORT int32_t subtract(int32_t a, int32_t b);

View File

@ -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<String> 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);
}

View File

@ -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

View File

@ -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<String> 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);
}

View File

@ -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

View File

@ -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

View File

@ -105,6 +105,9 @@
"meta": {
"experimentSet": "nullSafety"
},
"native_assets_builder": {
"experimentSet": "nullSafety"
},
"native_stack_traces": {
"experimentSet": "nullSafety"
},

View File

@ -276,6 +276,7 @@
"pkg/kernel/",
"pkg/meta/",
"pkg/native_stack_traces/",
"pkg/native_assets_builder/",
"pkg/pkg.status",
"pkg/smith/",
"pkg/status_file/",

View File

@ -353,6 +353,11 @@ class Package implements Comparable<Package> {
continue;
}
// Skip 'pkg/native_assets_builder/test/test_projects/'.
if (name == 'test_projects') {
continue;
}
if (!name.startsWith('.')) {
_collectDartFiles(entity, files);
}