Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add native_dynamic_linking to test_data #1437

Merged
merged 4 commits into from
Sep 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .github/workflows/native.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,9 @@ jobs:
- run: dart pub get -C test_data/treeshaking_native_libs/
if: ${{ matrix.package == 'native_assets_builder' }}

- run: dart pub get -C test_data/native_dynamic_linking/
if: ${{ matrix.package == 'native_assets_builder' }}

- run: dart pub get -C example/build/native_dynamic_linking/
if: ${{ matrix.package == 'native_assets_cli' }}

Expand Down Expand Up @@ -141,7 +144,7 @@ jobs:

- run: dart --enable-experiment=native-assets test
working-directory: pkgs/${{ matrix.package }}/example/build/native_dynamic_linking/
if: ${{ matrix.package == 'native_assets_cli' && matrix.sdk == 'dev' && !matrix.breaking-chang && matrix.os != 'windows' }}
if: ${{ matrix.package == 'native_assets_cli' && matrix.sdk == 'dev' && !matrix.breaking-change && matrix.os != 'windows' }}

- run: dart --enable-experiment=native-assets test
working-directory: pkgs/${{ matrix.package }}/example/build/native_add_app/
Expand Down
70 changes: 68 additions & 2 deletions pkgs/native_assets_builder/test/helpers.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,22 @@ import 'dart:io';
import 'package:logging/logging.dart';
import 'package:native_assets_builder/src/utils/run_process.dart'
as run_process;
import 'package:native_assets_cli/native_assets_cli.dart';
import 'package:native_assets_cli/native_assets_cli_internal.dart' as internal;
import 'package:test/test.dart';
import 'package:yaml/yaml.dart';

extension UriExtension on Uri {
String get name => pathSegments.where((e) => e != '').last;

Uri get parent => File(toFilePath()).parent.uri;

FileSystemEntity get fileSystemEntity {
if (path.endsWith(Platform.pathSeparator) || path.endsWith('/')) {
return Directory.fromUri(this);
}
return File.fromUri(this);
}
}

const keepTempKey = 'KEEP_TEMPORARY_DIRECTORIES';
Expand Down Expand Up @@ -112,8 +123,63 @@ final pkgNativeAssetsBuilderUri = findPackageRoot('native_assets_builder');

final testDataUri = pkgNativeAssetsBuilderUri.resolve('test_data/');

extension on Uri {
String get name => pathSegments.where((e) => e != '').last;
String unparseKey(String key) => key.replaceAll('.', '__').toUpperCase();

/// Archiver provided by the environment.
///
/// Provided on Dart CI.
final Uri? ar = Platform
.environment[unparseKey(internal.CCompilerConfigImpl.arConfigKeyFull)]
?.asFileUri();

/// Compiler provided by the environment.
///
/// Provided on Dart CI.
final Uri? cc = Platform
.environment[unparseKey(internal.CCompilerConfigImpl.ccConfigKeyFull)]
?.asFileUri();

/// Linker provided by the environment.
///
/// Provided on Dart CI.
final Uri? ld = Platform
.environment[unparseKey(internal.CCompilerConfigImpl.ldConfigKeyFull)]
?.asFileUri();

/// Path to script that sets environment variables for [cc], [ld], and [ar].
///
/// Provided on Dart CI.
final Uri? envScript = Platform.environment[
unparseKey(internal.CCompilerConfigImpl.envScriptConfigKeyFull)]
?.asFileUri();

/// Arguments for [envScript] provided by environment.
///
/// Provided on Dart CI.
final List<String>? envScriptArgs = Platform.environment[
unparseKey(internal.CCompilerConfigImpl.envScriptArgsConfigKeyFull)]
?.split(' ');

extension on String {
Uri asFileUri() => Uri.file(this);
}

extension AssetIterable on Iterable<Asset> {
Future<bool> allExist() async {
final allResults = await Future.wait(map((e) => e.exists()));
final missing = allResults.contains(false);
return !missing;
}
}

extension on Asset {
Future<bool> exists() async {
final path_ = file;
return switch (path_) {
null => true,
_ => await path_.fileSystemEntity.exists(),
};
}
}

Future<void> copyTestProjects({
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import 'dart:ffi';

void main(List<String> arguments) {
final addLibraryPath = arguments[0];
final a = int.parse(arguments[1]);
final b = int.parse(arguments[2]);
final addLibrary = DynamicLibrary.open(addLibraryPath);
final add = addLibrary.lookupFunction<Int32 Function(Int32, Int32),
int Function(int, int)>('add');
print(add(a, b));
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

@OnPlatform({
'mac-os': Timeout.factor(2),
'windows': Timeout.factor(10),
})
library;

import 'dart:convert';
import 'dart:io';

import 'package:native_assets_cli/native_assets_cli_internal.dart';
import 'package:test/test.dart';

import '../helpers.dart';

void main() async {
late Uri tempUri;
const name = 'native_dynamic_linking';

setUp(() async {
tempUri = (await Directory.systemTemp.createTemp()).uri;
});

tearDown(() async {
await Directory.fromUri(tempUri).delete(recursive: true);
});

for (final dryRun in [true, false]) {
final testSuffix = dryRun ? ' dry_run' : '';
test('native_dynamic_linking build$testSuffix', () async {
final testTempUri = tempUri.resolve('test1/');
await Directory.fromUri(testTempUri).create();
final testPackageUri = testDataUri.resolve('$name/');
final dartUri = Uri.file(Platform.resolvedExecutable);

final config = BuildConfigImpl(
outputDirectory: tempUri,
packageName: name,
packageRoot: testPackageUri,
targetOS: OSImpl.current,
version: HookConfigImpl.latestVersion,
linkModePreference: LinkModePreferenceImpl.dynamic,
dryRun: dryRun,
linkingEnabled: false,
targetArchitecture: dryRun ? null : ArchitectureImpl.current,
buildMode: dryRun ? null : BuildModeImpl.debug,
cCompiler: dryRun
? null
: CCompilerConfigImpl(
compiler: cc,
envScript: envScript,
envScriptArgs: envScriptArgs,
),
);

final buildConfigUri = testTempUri.resolve('build_config.json');
File.fromUri(buildConfigUri)
.writeAsStringSync(jsonEncode(config.toJson()));

final processResult = await Process.run(
dartUri.toFilePath(),
[
'hook/build.dart',
'--config=${buildConfigUri.toFilePath()}',
],
workingDirectory: testPackageUri.toFilePath(),
);
if (processResult.exitCode != 0) {
print(processResult.stdout);
print(processResult.stderr);
print(processResult.exitCode);
}
expect(processResult.exitCode, 0);

final buildOutputUri = tempUri.resolve('build_output.json');
final buildOutput = HookOutputImpl.fromJsonString(
await File.fromUri(buildOutputUri).readAsString());
final assets = buildOutput.assets;
final dependencies = buildOutput.dependencies;
if (dryRun) {
expect(assets.length, greaterThanOrEqualTo(3));
expect(dependencies, <Uri>[]);
} else {
expect(assets.length, 3);
expect(await assets.allExist(), true);
expect(
dependencies,
[
testPackageUri.resolve('src/debug.c'),
testPackageUri.resolve('src/math.c'),
testPackageUri.resolve('src/add.c'),
],
);

final addLibraryPath = assets
.firstWhere((asset) => asset.id.endsWith('add.dart'))
.file!
.toFilePath();
final addResult = await runProcess(
executable: dartExecutable,
arguments: [
'run',
pkgNativeAssetsBuilderUri
.resolve('test/test_data/native_dynamic_linking_add.dart')
.toFilePath(),
addLibraryPath,
'1',
'2',
],
environment: {
// Add the directory containing the linked dynamic libraries to the
// PATH so that the dynamic linker can find them.
if (Platform.isWindows)
'PATH': '${tempUri.toFilePath()};${Platform.environment['PATH']}',
},
throwOnUnexpectedExitCode: true,
logger: logger,
);
expect(addResult.stdout, 'Adding 1 and 2.\n3\n');
}
});
}
}
13 changes: 13 additions & 0 deletions pkgs/native_assets_builder/test_data/manifest.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -113,3 +113,16 @@
- wrong_build_output/pubspec.yaml
- wrong_namespace_asset/hook/build.dart
- wrong_namespace_asset/pubspec.yaml
- native_dynamic_linking/bin/native_dynamic_linking.dart
- native_dynamic_linking/hook/build.dart
- native_dynamic_linking/lib/add.dart
- native_dynamic_linking/src/add.c
- native_dynamic_linking/src/add.h
- native_dynamic_linking/src/debug.c
- native_dynamic_linking/src/debug.h
- native_dynamic_linking/src/math.c
- native_dynamic_linking/src/math.h
- native_dynamic_linking/test/add_test.dart
- native_dynamic_linking/ffigen.yaml
- native_dynamic_linking/pubspec.yaml
- native_dynamic_linking/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
An example library that builds 3 native libraries, 2 of which are dynamically
linked to each other.

## Usage

Run tests with `dart --enable-experiment=native-assets test`.

Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import 'package:native_dynamic_linking/add.dart';

void main() => print('${add(24, 18)}');
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Run with `flutter pub run ffigen --config ffigen.yaml`.
name: AddBindings
description: |
Bindings for `src/add.h`.

Regenerate bindings with `flutter pub run ffigen --config ffigen.yaml`.
output: 'lib/add.dart'
headers:
entry-points:
- 'src/add.h'
include-directives:
- 'src/add.h'
preamble: |
// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
comments:
style: any
length: full
ffi-native:
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import 'package:logging/logging.dart';
import 'package:native_assets_cli/native_assets_cli.dart';
import 'package:native_toolchain_c/native_toolchain_c.dart';

void main(List<String> args) async {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please exercise the test data in a new test in pkgs/native_assets_builder/test/

  • Invoke the hook
  • Then try to dlopen the dylib. (This will most likely require starting a new Dart process to open the dylib in so that PATH can be set correctly for that Dart process.)

await build(args, (config, output) async {
final logger = Logger('')
..level = Level.ALL
..onRecord.listen((record) => print(record.message));

final builders = [
CBuilder.library(
name: 'debug',
assetName: 'debug',
sources: [
'src/debug.c',
],
),
CBuilder.library(
name: 'math',
assetName: 'math',
sources: [
'src/math.c',
],
// TODO(https://github.com/dart-lang/native/issues/190): Use specific
// API for linking once available.
flags: config.dynamicLinkingFlags('debug'),
),
CBuilder.library(
name: 'add',
assetName: 'add.dart',
sources: [
'src/add.c',
],
// TODO(https://github.com/dart-lang/native/issues/190): Use specific
// API for linking once available.
flags: config.dynamicLinkingFlags('math'),
)
];

// Note: This builders need to be run sequentially because they depend on
// each others output.
for (final builder in builders) {
await builder.run(
config: config,
output: output,
logger: logger,
);
}
});
}

extension on BuildConfig {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code like this, switching over OSs, seems like something which should actually live in a helper package. Or is it specific to this example?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or is this covered by #190?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've started working on an option for CBuilder that adds the necessary flags (#1423) but have held off until both embedders have landed the changes necessary for dynamic linking to work (standalone Dart: https://dart-review.googlesource.com/c/sdk/+/381580, Flutter: flutter/flutter#153054).

List<String> dynamicLinkingFlags(String libraryName) => switch (targetOS) {
OS.macOS => [
'-L${outputDirectory.toFilePath()}',
'-l$libraryName',
],
OS.linux => [
r'-Wl,-rpath=$ORIGIN',
'-L${outputDirectory.toFilePath()}',
'-l$libraryName',
],
OS.windows => [
outputDirectory.resolve('$libraryName.dll').toFilePath(),
],
_ => throw UnimplementedError('Unsupported OS: $targetOS'),
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

// AUTO GENERATED FILE, DO NOT EDIT.
//
// Generated by `package:ffigen`.
// ignore_for_file: type=lint
import 'dart:ffi' as ffi;

@ffi.Native<ffi.Int32 Function(ffi.Int32, ffi.Int32)>(symbol: 'add')
external int add(
int a,
int b,
);
Loading