Skip to content

Commit b18d32f

Browse files
authored
Add native_dynamic_linking to test_data (#1437)
1 parent e022fea commit b18d32f

File tree

18 files changed

+459
-3
lines changed

18 files changed

+459
-3
lines changed

.github/workflows/native.yaml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,9 @@ jobs:
9696
- run: dart pub get -C test_data/treeshaking_native_libs/
9797
if: ${{ matrix.package == 'native_assets_builder' }}
9898

99+
- run: dart pub get -C test_data/native_dynamic_linking/
100+
if: ${{ matrix.package == 'native_assets_builder' }}
101+
99102
- run: dart pub get -C example/build/native_dynamic_linking/
100103
if: ${{ matrix.package == 'native_assets_cli' }}
101104

@@ -127,7 +130,7 @@ jobs:
127130

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

132135
- run: dart --enable-experiment=native-assets test
133136
working-directory: pkgs/${{ matrix.package }}/example/build/native_add_app/

pkgs/native_assets_builder/test/helpers.dart

Lines changed: 68 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,22 @@ import 'dart:io';
88
import 'package:logging/logging.dart';
99
import 'package:native_assets_builder/src/utils/run_process.dart'
1010
as run_process;
11+
import 'package:native_assets_cli/native_assets_cli.dart';
12+
import 'package:native_assets_cli/native_assets_cli_internal.dart' as internal;
1113
import 'package:test/test.dart';
1214
import 'package:yaml/yaml.dart';
1315

1416
extension UriExtension on Uri {
17+
String get name => pathSegments.where((e) => e != '').last;
18+
1519
Uri get parent => File(toFilePath()).parent.uri;
20+
21+
FileSystemEntity get fileSystemEntity {
22+
if (path.endsWith(Platform.pathSeparator) || path.endsWith('/')) {
23+
return Directory.fromUri(this);
24+
}
25+
return File.fromUri(this);
26+
}
1627
}
1728

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

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

115-
extension on Uri {
116-
String get name => pathSegments.where((e) => e != '').last;
126+
String unparseKey(String key) => key.replaceAll('.', '__').toUpperCase();
127+
128+
/// Archiver provided by the environment.
129+
///
130+
/// Provided on Dart CI.
131+
final Uri? ar = Platform
132+
.environment[unparseKey(internal.CCompilerConfigImpl.arConfigKeyFull)]
133+
?.asFileUri();
134+
135+
/// Compiler provided by the environment.
136+
///
137+
/// Provided on Dart CI.
138+
final Uri? cc = Platform
139+
.environment[unparseKey(internal.CCompilerConfigImpl.ccConfigKeyFull)]
140+
?.asFileUri();
141+
142+
/// Linker provided by the environment.
143+
///
144+
/// Provided on Dart CI.
145+
final Uri? ld = Platform
146+
.environment[unparseKey(internal.CCompilerConfigImpl.ldConfigKeyFull)]
147+
?.asFileUri();
148+
149+
/// Path to script that sets environment variables for [cc], [ld], and [ar].
150+
///
151+
/// Provided on Dart CI.
152+
final Uri? envScript = Platform.environment[
153+
unparseKey(internal.CCompilerConfigImpl.envScriptConfigKeyFull)]
154+
?.asFileUri();
155+
156+
/// Arguments for [envScript] provided by environment.
157+
///
158+
/// Provided on Dart CI.
159+
final List<String>? envScriptArgs = Platform.environment[
160+
unparseKey(internal.CCompilerConfigImpl.envScriptArgsConfigKeyFull)]
161+
?.split(' ');
162+
163+
extension on String {
164+
Uri asFileUri() => Uri.file(this);
165+
}
166+
167+
extension AssetIterable on Iterable<Asset> {
168+
Future<bool> allExist() async {
169+
final allResults = await Future.wait(map((e) => e.exists()));
170+
final missing = allResults.contains(false);
171+
return !missing;
172+
}
173+
}
174+
175+
extension on Asset {
176+
Future<bool> exists() async {
177+
final path_ = file;
178+
return switch (path_) {
179+
null => true,
180+
_ => await path_.fileSystemEntity.exists(),
181+
};
182+
}
117183
}
118184

119185
Future<void> copyTestProjects({
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import 'dart:ffi';
6+
7+
void main(List<String> arguments) {
8+
final addLibraryPath = arguments[0];
9+
final a = int.parse(arguments[1]);
10+
final b = int.parse(arguments[2]);
11+
final addLibrary = DynamicLibrary.open(addLibraryPath);
12+
final add = addLibrary.lookupFunction<Int32 Function(Int32, Int32),
13+
int Function(int, int)>('add');
14+
print(add(a, b));
15+
}
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
@OnPlatform({
6+
'mac-os': Timeout.factor(2),
7+
'windows': Timeout.factor(10),
8+
})
9+
library;
10+
11+
import 'dart:convert';
12+
import 'dart:io';
13+
14+
import 'package:native_assets_cli/native_assets_cli_internal.dart';
15+
import 'package:test/test.dart';
16+
17+
import '../helpers.dart';
18+
19+
void main() async {
20+
late Uri tempUri;
21+
const name = 'native_dynamic_linking';
22+
23+
setUp(() async {
24+
tempUri = (await Directory.systemTemp.createTemp()).uri;
25+
});
26+
27+
tearDown(() async {
28+
await Directory.fromUri(tempUri).delete(recursive: true);
29+
});
30+
31+
for (final dryRun in [true, false]) {
32+
final testSuffix = dryRun ? ' dry_run' : '';
33+
test('native_dynamic_linking build$testSuffix', () async {
34+
final testTempUri = tempUri.resolve('test1/');
35+
await Directory.fromUri(testTempUri).create();
36+
final testPackageUri = testDataUri.resolve('$name/');
37+
final dartUri = Uri.file(Platform.resolvedExecutable);
38+
39+
final config = BuildConfigImpl(
40+
outputDirectory: tempUri,
41+
packageName: name,
42+
packageRoot: testPackageUri,
43+
targetOS: OSImpl.current,
44+
version: HookConfigImpl.latestVersion,
45+
linkModePreference: LinkModePreferenceImpl.dynamic,
46+
dryRun: dryRun,
47+
linkingEnabled: false,
48+
targetArchitecture: dryRun ? null : ArchitectureImpl.current,
49+
buildMode: dryRun ? null : BuildModeImpl.debug,
50+
cCompiler: dryRun
51+
? null
52+
: CCompilerConfigImpl(
53+
compiler: cc,
54+
envScript: envScript,
55+
envScriptArgs: envScriptArgs,
56+
),
57+
);
58+
59+
final buildConfigUri = testTempUri.resolve('build_config.json');
60+
File.fromUri(buildConfigUri)
61+
.writeAsStringSync(jsonEncode(config.toJson()));
62+
63+
final processResult = await Process.run(
64+
dartUri.toFilePath(),
65+
[
66+
'hook/build.dart',
67+
'--config=${buildConfigUri.toFilePath()}',
68+
],
69+
workingDirectory: testPackageUri.toFilePath(),
70+
);
71+
if (processResult.exitCode != 0) {
72+
print(processResult.stdout);
73+
print(processResult.stderr);
74+
print(processResult.exitCode);
75+
}
76+
expect(processResult.exitCode, 0);
77+
78+
final buildOutputUri = tempUri.resolve('build_output.json');
79+
final buildOutput = HookOutputImpl.fromJsonString(
80+
await File.fromUri(buildOutputUri).readAsString());
81+
final assets = buildOutput.assets;
82+
final dependencies = buildOutput.dependencies;
83+
if (dryRun) {
84+
expect(assets.length, greaterThanOrEqualTo(3));
85+
expect(dependencies, <Uri>[]);
86+
} else {
87+
expect(assets.length, 3);
88+
expect(await assets.allExist(), true);
89+
expect(
90+
dependencies,
91+
[
92+
testPackageUri.resolve('src/debug.c'),
93+
testPackageUri.resolve('src/math.c'),
94+
testPackageUri.resolve('src/add.c'),
95+
],
96+
);
97+
98+
final addLibraryPath = assets
99+
.firstWhere((asset) => asset.id.endsWith('add.dart'))
100+
.file!
101+
.toFilePath();
102+
final addResult = await runProcess(
103+
executable: dartExecutable,
104+
arguments: [
105+
'run',
106+
pkgNativeAssetsBuilderUri
107+
.resolve('test/test_data/native_dynamic_linking_add.dart')
108+
.toFilePath(),
109+
addLibraryPath,
110+
'1',
111+
'2',
112+
],
113+
environment: {
114+
// Add the directory containing the linked dynamic libraries to the
115+
// PATH so that the dynamic linker can find them.
116+
if (Platform.isWindows)
117+
'PATH': '${tempUri.toFilePath()};${Platform.environment['PATH']}',
118+
},
119+
throwOnUnexpectedExitCode: true,
120+
logger: logger,
121+
);
122+
expect(addResult.stdout, 'Adding 1 and 2.\n3\n');
123+
}
124+
});
125+
}
126+
}

pkgs/native_assets_builder/test_data/manifest.yaml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,3 +115,16 @@
115115
- wrong_linker/pubspec.yaml
116116
- wrong_namespace_asset/hook/build.dart
117117
- wrong_namespace_asset/pubspec.yaml
118+
- native_dynamic_linking/bin/native_dynamic_linking.dart
119+
- native_dynamic_linking/hook/build.dart
120+
- native_dynamic_linking/lib/add.dart
121+
- native_dynamic_linking/src/add.c
122+
- native_dynamic_linking/src/add.h
123+
- native_dynamic_linking/src/debug.c
124+
- native_dynamic_linking/src/debug.h
125+
- native_dynamic_linking/src/math.c
126+
- native_dynamic_linking/src/math.h
127+
- native_dynamic_linking/test/add_test.dart
128+
- native_dynamic_linking/ffigen.yaml
129+
- native_dynamic_linking/pubspec.yaml
130+
- native_dynamic_linking/README.md
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
An example library that builds 3 native libraries, 2 of which are dynamically
2+
linked to each other.
3+
4+
## Usage
5+
6+
Run tests with `dart --enable-experiment=native-assets test`.
7+
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import 'package:native_dynamic_linking/add.dart';
2+
3+
void main() => print('${add(24, 18)}');
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Run with `flutter pub run ffigen --config ffigen.yaml`.
2+
name: AddBindings
3+
description: |
4+
Bindings for `src/add.h`.
5+
6+
Regenerate bindings with `flutter pub run ffigen --config ffigen.yaml`.
7+
output: 'lib/add.dart'
8+
headers:
9+
entry-points:
10+
- 'src/add.h'
11+
include-directives:
12+
- 'src/add.h'
13+
preamble: |
14+
// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file
15+
// for details. All rights reserved. Use of this source code is governed by a
16+
// BSD-style license that can be found in the LICENSE file.
17+
comments:
18+
style: any
19+
length: full
20+
ffi-native:
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import 'package:logging/logging.dart';
6+
import 'package:native_assets_cli/native_assets_cli.dart';
7+
import 'package:native_toolchain_c/native_toolchain_c.dart';
8+
9+
void main(List<String> args) async {
10+
await build(args, (config, output) async {
11+
final logger = Logger('')
12+
..level = Level.ALL
13+
..onRecord.listen((record) => print(record.message));
14+
15+
final builders = [
16+
CBuilder.library(
17+
name: 'debug',
18+
assetName: 'debug',
19+
sources: [
20+
'src/debug.c',
21+
],
22+
),
23+
CBuilder.library(
24+
name: 'math',
25+
assetName: 'math',
26+
sources: [
27+
'src/math.c',
28+
],
29+
// TODO(https://github.com/dart-lang/native/issues/190): Use specific
30+
// API for linking once available.
31+
flags: config.dynamicLinkingFlags('debug'),
32+
),
33+
CBuilder.library(
34+
name: 'add',
35+
assetName: 'add.dart',
36+
sources: [
37+
'src/add.c',
38+
],
39+
// TODO(https://github.com/dart-lang/native/issues/190): Use specific
40+
// API for linking once available.
41+
flags: config.dynamicLinkingFlags('math'),
42+
)
43+
];
44+
45+
// Note: This builders need to be run sequentially because they depend on
46+
// each others output.
47+
for (final builder in builders) {
48+
await builder.run(
49+
config: config,
50+
output: output,
51+
logger: logger,
52+
);
53+
}
54+
});
55+
}
56+
57+
extension on BuildConfig {
58+
List<String> dynamicLinkingFlags(String libraryName) => switch (targetOS) {
59+
OS.macOS => [
60+
'-L${outputDirectory.toFilePath()}',
61+
'-l$libraryName',
62+
],
63+
OS.linux => [
64+
r'-Wl,-rpath=$ORIGIN',
65+
'-L${outputDirectory.toFilePath()}',
66+
'-l$libraryName',
67+
],
68+
OS.windows => [
69+
outputDirectory.resolve('$libraryName.dll').toFilePath(),
70+
],
71+
_ => throw UnimplementedError('Unsupported OS: $targetOS'),
72+
};
73+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
// AUTO GENERATED FILE, DO NOT EDIT.
6+
//
7+
// Generated by `package:ffigen`.
8+
// ignore_for_file: type=lint
9+
import 'dart:ffi' as ffi;
10+
11+
@ffi.Native<ffi.Int32 Function(ffi.Int32, ffi.Int32)>(symbol: 'add')
12+
external int add(
13+
int a,
14+
int b,
15+
);

0 commit comments

Comments
 (0)