@@ -14,6 +14,7 @@ import 'package:dartdev/src/native_assets_macos.dart';
1414import 'package:dartdev/src/sdk.dart' ;
1515import 'package:front_end/src/api_prototype/compiler_options.dart'
1616 show Verbosity;
17+ import 'package:hooks_runner/hooks_runner.dart' ;
1718import 'package:path/path.dart' as path;
1819
1920import '../core.dart' ;
@@ -124,15 +125,21 @@ then that is used instead.''',
124125 // AOT compilation isn't supported on ia32. Currently, generating an
125126 // executable only supports AOT runtimes, so these commands are disabled.
126127 if (Platform .version.contains ('ia32' )) {
127- stderr.write ("'dart build' is not supported on x86 architectures" );
128+ stderr.write ("'dart build' is not supported on x86 architectures. " );
128129 return 64 ;
129130 }
130131 final args = argResults! ;
131132
133+ var target = args.option ('target' );
134+ if (target == null ) {
135+ stderr.write (
136+ 'There are multiple possible targets in the `bin/` directory, '
137+ "and the 'target' argument wasn't specified." ,
138+ );
139+ return 255 ;
140+ }
132141 final sourceUri =
133- File .fromUri (Uri .file (args.option ('target' )! ).normalizePath ())
134- .absolute
135- .uri;
142+ File .fromUri (Uri .file (target).normalizePath ()).absolute.uri;
136143 if (! checkFile (sourceUri.toFilePath ())) {
137144 return genericErrorExitCode;
138145 }
@@ -146,38 +153,84 @@ then that is used instead.''',
146153 stderr.writeln ('Requested output directory: ${outputUri .toFilePath ()}' );
147154 return 128 ;
148155 }
156+ final verbosity = args.option ('verbosity' )! ;
157+ final enabledExperiments = args.enabledExperiments;
158+
159+ stdout.writeln ('''The `dart build cli` command is in preview at the moment.
160+ See documentation on https://dart.dev/interop/c-interop#native-assets.
161+ ''' );
162+ final packageConfigUri = await DartNativeAssetsBuilder .ensurePackageConfig (
163+ sourceUri,
164+ );
165+ final pubspecUri =
166+ await DartNativeAssetsBuilder .findWorkspacePubspec (packageConfigUri);
167+ final executableName = path.basenameWithoutExtension (sourceUri.path);
168+
169+ return await doBuild (
170+ executables: [(name: executableName, sourceEntryPoint: sourceUri)],
171+ enabledExperiments: enabledExperiments,
172+ outputUri: outputUri,
173+ packageConfigUri: packageConfigUri! ,
174+ pubspecUri: pubspecUri,
175+ recordUseEnabled: recordUseEnabled,
176+ verbose: verbose,
177+ verbosity: verbosity,
178+ );
179+ }
180+
181+ static Future <int > doBuild ({
182+ required DartBuildExecutables executables,
183+ required Uri outputUri,
184+ required Uri packageConfigUri,
185+ required Uri ? pubspecUri,
186+ required bool recordUseEnabled,
187+ required List <String > enabledExperiments,
188+ required bool verbose,
189+ required String verbosity,
190+ }) async {
191+ if (executables.length >= 2 && recordUseEnabled) {
192+ // Multiple entry points can lead to multiple different tree-shakings.
193+ // We either need to generate a new entry point that combines all entry
194+ // points and combine that into a single executable and have wrappers
195+ // around that executable. Or, we need to merge the recorded uses for the
196+ // various entrypoints. The former will lead to smaller bundle-size
197+ // overall.
198+ stderr.writeln (
199+ 'Multiple executables together with record use is not yet supported.' ,
200+ );
201+ return 255 ;
202+ }
149203 final outputDir = Directory .fromUri (outputUri);
150204 if (await outputDir.exists ()) {
151205 stdout.writeln ('Deleting output directory: ${outputUri .toFilePath ()}.' );
152- await outputDir.delete (recursive: true );
206+ try {
207+ await outputDir.delete (recursive: true );
208+ } on PathAccessException {
209+ stderr.writeln (
210+ 'Failed to delete: ${outputUri .toFilePath ()}. '
211+ 'The application might be in use.' ,
212+ );
213+ return 255 ;
214+ }
153215 }
216+
217+ // Place the bundle in a subdir so that we can potentially put debug symbols
218+ // next to it.
154219 final bundleDirectory = Directory .fromUri (outputUri.resolve ('bundle/' ));
155220 final binDirectory = Directory .fromUri (bundleDirectory.uri.resolve ('bin/' ));
156221 await binDirectory.create (recursive: true );
157222
158- final outputExeUri = binDirectory.uri.resolve (
159- targetOS.executableFileName (
160- path.basenameWithoutExtension (sourceUri.path),
161- ),
162- );
163-
164- stdout.writeln ('''The `dart build cli` command is in preview at the moment.
165- See documentation on https://dart.dev/interop/c-interop#native-assets.
166- ''' );
167-
168223 stdout.writeln ('Building native assets.' );
169- final packageConfigUri = await DartNativeAssetsBuilder .ensurePackageConfig (
170- sourceUri,
171- );
224+
172225 final packageConfig =
173- await DartNativeAssetsBuilder .loadPackageConfig (packageConfigUri! );
226+ await DartNativeAssetsBuilder .loadPackageConfig (packageConfigUri);
174227 if (packageConfig == null ) {
175228 return compileErrorExitCode;
176229 }
177230 final runPackageName = await DartNativeAssetsBuilder .findRootPackageName (
178- sourceUri ,
231+ executables.first.sourceEntryPoint ,
179232 );
180- final pubspecUri =
233+ pubspecUri ?? =
181234 await DartNativeAssetsBuilder .findWorkspacePubspec (packageConfigUri);
182235 final builder = DartNativeAssetsBuilder (
183236 pubspecUri: pubspecUri,
@@ -195,75 +248,89 @@ See documentation on https://dart.dev/interop/c-interop#native-assets.
195248
196249 final tempDir = Directory .systemTemp.createTempSync ();
197250 try {
198- String ? recordedUsagesPath;
199- if (recordUseEnabled) {
200- recordedUsagesPath = path.join (tempDir.path, 'recorded_usages.json' );
201- }
202- final generator = KernelGenerator (
203- genSnapshot: sdk.genSnapshot,
204- targetDartAotRuntime: sdk.dartAotRuntime,
205- kind: Kind .exe,
206- sourceFile: sourceUri.toFilePath (),
207- outputFile: outputExeUri.toFilePath (),
208- verbose: verbose,
209- verbosity: args.option ('verbosity' )! ,
210- defines: [],
211- packages: packageConfigUri.toFilePath (),
212- targetOS: targetOS,
213- enableExperiment: args.enabledExperiments.join (',' ),
214- tempDir: tempDir,
215- );
251+ var first = true ;
252+ Uri ? nativeAssetsYamlUri;
253+ LinkResult ? linkResult;
254+ for (final e in executables) {
255+ String ? recordedUsagesPath;
256+ if (recordUseEnabled) {
257+ recordedUsagesPath = path.join (tempDir.path, 'recorded_usages.json' );
258+ }
259+ final outputExeUri = binDirectory.uri.resolve (
260+ targetOS.executableFileName (e.name),
261+ );
262+ final generator = KernelGenerator (
263+ genSnapshot: sdk.genSnapshot,
264+ targetDartAotRuntime: sdk.dartAotRuntime,
265+ kind: Kind .exe,
266+ sourceFile: e.sourceEntryPoint.toFilePath (),
267+ outputFile: outputExeUri.toFilePath (),
268+ verbose: verbose,
269+ verbosity: verbosity,
270+ defines: [],
271+ packages: packageConfigUri.toFilePath (),
272+ targetOS: targetOS,
273+ enableExperiment: enabledExperiments.join (',' ),
274+ tempDir: tempDir,
275+ );
216276
217- final snapshotGenerator = await generator.generate (
218- recordedUsagesFile: recordedUsagesPath,
219- );
277+ final snapshotGenerator = await generator.generate (
278+ recordedUsagesFile: recordedUsagesPath,
279+ );
220280
221- final linkResult = await builder.linkNativeAssetsAOT (
222- recordedUsagesPath: recordedUsagesPath,
223- buildResult: buildResult,
224- );
225- if (linkResult == null ) {
226- stderr.writeln ('Native assets link failed.' );
227- return 255 ;
228- }
281+ if (first) {
282+ // Multiple executables are only supported with recorded uses
283+ // disabled, so don't re-invoke link hooks.
284+ linkResult = await builder.linkNativeAssetsAOT (
285+ recordedUsagesPath: recordedUsagesPath,
286+ buildResult: buildResult,
287+ );
288+ }
289+ if (linkResult == null ) {
290+ stderr.writeln ('Native assets link failed.' );
291+ return 255 ;
292+ }
293+
294+ final allAssets = [
295+ ...buildResult.encodedAssets,
296+ ...linkResult.encodedAssets
297+ ];
229298
230- final allAssets = [
231- ...buildResult.encodedAssets,
232- ...linkResult.encodedAssets
233- ];
234-
235- final staticAssets = allAssets
236- .where ((e) => e.isCodeAsset)
237- .map (CodeAsset .fromEncoded)
238- .where ((e) => e.linkMode == StaticLinking ());
239- if (staticAssets.isNotEmpty) {
240- stderr.write (
241- """'dart build' does not yet support CodeAssets with static linking.
299+ final staticAssets = allAssets
300+ .where ((e) => e.isCodeAsset)
301+ .map (CodeAsset .fromEncoded)
302+ .where ((e) => e.linkMode == StaticLinking ());
303+ if (staticAssets.isNotEmpty) {
304+ stderr.write (
305+ """'dart build' does not yet support CodeAssets with static linking.
242306Use linkMode as dynamic library instead.""" );
243- return 255 ;
244- }
307+ return 255 ;
308+ }
245309
246- Uri ? nativeAssetsYamlUri;
247- if (allAssets.isNotEmpty) {
248- final kernelAssets = await bundleNativeAssets (
249- allAssets,
250- builder.target,
251- binDirectory.uri,
252- relocatable: true ,
253- verbose: true ,
254- );
255- nativeAssetsYamlUri =
256- await writeNativeAssetsYaml (kernelAssets, tempDir.uri);
257- }
310+ if (allAssets.isNotEmpty && first) {
311+ // Without tree-shaking, the assets after linking must be identical
312+ // for all entry points.
313+ final kernelAssets = await bundleNativeAssets (
314+ allAssets,
315+ builder.target,
316+ binDirectory.uri,
317+ relocatable: true ,
318+ verbose: true ,
319+ );
320+ nativeAssetsYamlUri =
321+ await writeNativeAssetsYaml (kernelAssets, tempDir.uri);
322+ }
258323
259- await snapshotGenerator.generate (
260- nativeAssets: nativeAssetsYamlUri? .toFilePath (),
261- );
324+ await snapshotGenerator.generate (
325+ nativeAssets: nativeAssetsYamlUri? .toFilePath (),
326+ );
262327
263- if (targetOS == OS .macOS) {
264- // The dylibs are opened with a relative path to the executable.
265- // MacOS prevents opening dylibs that are not on the include path.
266- await rewriteInstallPath (outputExeUri);
328+ if (targetOS == OS .macOS) {
329+ // The dylibs are opened with a relative path to the executable.
330+ // MacOS prevents opening dylibs that are not on the include path.
331+ await rewriteInstallPath (outputExeUri);
332+ }
333+ first = false ;
267334 }
268335 } finally {
269336 await tempDir.delete (recursive: true );
@@ -277,3 +344,13 @@ extension on String {
277344 String makeFolder () => endsWith ('\\ ' ) || endsWith ('/' ) ? this : '$this /' ;
278345 String removeDotDart () => replaceFirst (RegExp (r'\.dart$' ), '' );
279346}
347+
348+ /// The executables to build in a `dart build cli` app bundle.
349+ ///
350+ /// All entry points must be in the same package.
351+ ///
352+ /// The names are typically taken from the `executables` section of the
353+ /// `pubspec.yaml` file.
354+ ///
355+ /// Recorded usages and multiple executables are not supported yet.
356+ typedef DartBuildExecutables = List <({String name, Uri sourceEntryPoint})>;
0 commit comments