@@ -10,11 +10,11 @@ import 'package:dart2native/generate.dart';
1010import 'package:dartdev/src/commands/compile.dart' ;
1111import 'package:dartdev/src/experiments.dart' ;
1212import 'package:dartdev/src/native_assets_bundling.dart' ;
13+ import 'package:dartdev/src/native_assets_macos.dart' ;
1314import 'package:dartdev/src/sdk.dart' ;
1415import 'package:front_end/src/api_prototype/compiler_options.dart'
1516 show Verbosity;
1617import 'package:path/path.dart' as path;
17- import 'package:vm/target_os.dart' ; // For possible --target-os values.
1818
1919import '../core.dart' ;
2020import '../native_assets.dart' ;
@@ -29,37 +29,87 @@ class BuildCommand extends DartdevCommand {
2929 BuildCommand ({bool verbose = false , required this .recordUseEnabled})
3030 : super (cmdName, 'Build a Dart application including native assets.' ,
3131 verbose) {
32+ addSubcommand (BuildCliSubcommand (
33+ verbose: verbose,
34+ recordUseEnabled: recordUseEnabled,
35+ ));
36+ }
37+ }
38+
39+ /// Subcommand for `dart build cli` .
40+ ///
41+ /// Expects [Directory.current] to contain a Dart project with a bin/ directory.
42+ class BuildCliSubcommand extends CompileSubcommandCommand {
43+ final bool recordUseEnabled;
44+
45+ static const String cmdName = 'cli' ;
46+
47+ static final OS targetOS = OS .current;
48+ late final List <File > entryPoints;
49+
50+ BuildCliSubcommand ({bool verbose = false , required this .recordUseEnabled})
51+ : super (
52+ cmdName,
53+ '''Build a Dart application with a command line interface (CLI).
54+
55+ The resulting CLI app bundle is structured in the following manner:
56+
57+ bundle/
58+ bin/
59+ <executable>
60+ lib/
61+ <dynamic libraries>
62+ ''' ,
63+ verbose) {
64+ final binDirectory =
65+ Directory .fromUri (Directory .current.uri.resolve ('bin/' ));
66+
67+ final outputDirectoryDefault = Directory .fromUri (Directory .current.uri
68+ .resolve ('build/cli/${OS .current }-${Architecture .current }/' ));
69+ entryPoints = binDirectory.existsSync ()
70+ ? binDirectory
71+ .listSync ()
72+ .whereType <File >()
73+ .where ((e) => e.path.endsWith ('dart' ))
74+ .toList ()
75+ : [];
3276 argParser
3377 ..addOption (
34- outputOptionName ,
78+ 'output' ,
3579 abbr: 'o' ,
3680 help: '''
37- Write the output to <folder name> .
81+ Write the output to <output>/bundle/ .
3882 This can be an absolute or relative path.
3983 ''' ,
84+ valueHelp: 'path' ,
85+ defaultsTo: path
86+ .relative (outputDirectoryDefault.path, from: Directory .current.path)
87+ .makeFolder (),
4088 )
4189 ..addOption (
42- formatOptionName,
43- abbr: 'f' ,
44- allowed: ['exe' , 'aot' ],
45- defaultsTo: 'exe' ,
90+ 'target' ,
91+ abbr: 't' ,
92+ help: '''The main entry-point file of the command-line application.
93+ Must be a Dart file in the bin/ directory.
94+ If the "--target" option is omitted, and there is a single Dart file in bin/,
95+ then that is used instead.''' ,
96+ valueHelp: 'path' ,
97+ defaultsTo: entryPoints.length == 1
98+ ? path.relative (entryPoints.single.path,
99+ from: Directory .current.path)
100+ : null ,
46101 )
47- ..addOption ('target-os' ,
48- help: 'Compile to a specific target operating system.' ,
49- allowed: TargetOS .names)
50102 ..addOption (
51103 'verbosity' ,
52104 help: 'Sets the verbosity level of the compilation.' ,
105+ valueHelp: 'level' ,
53106 defaultsTo: Verbosity .defaultValue,
54107 allowed: Verbosity .allowedValues,
55108 allowedHelp: Verbosity .allowedValuesHelp,
56109 )
57110 ..addExperimentalFlags (verbose: verbose);
58111 }
59112
60- @override
61- String get invocation => '${super .invocation } <dart entry point>' ;
62-
63113 @override
64114 Future <int > run () async {
65115 if (! Sdk .checkArtifactExists (genKernel) ||
@@ -75,62 +125,42 @@ class BuildCommand extends DartdevCommand {
75125 }
76126 final args = argResults! ;
77127
78- // We expect a single rest argument; the dart entry point.
79- if (args.rest.length != 1 ) {
80- // This throws.
81- usageException ('Missing Dart entry point.' );
82- }
83-
84- // TODO(https://dartbug.com/52458): Support `dart build <pkg>:<bin-script>`.
85- // Similar to Dart run. Possibly also in `dart compile`.
86- final sourceUri = Uri (path: args.rest[0 ].normalizeCanonicalizePath ());
128+ final sourceUri =
129+ File .fromUri (Uri .file (args.option ('target' )! ).normalizePath ())
130+ .absolute
131+ .uri;
87132 if (! checkFile (sourceUri.toFilePath ())) {
88133 return genericErrorExitCode;
89134 }
90135
91136 final outputUri = Uri .directory (
92- args.option (outputOptionName )? .normalizeCanonicalizePath ().makeFolder () ??
137+ args.option ('output' )? .normalizeCanonicalizePath ().makeFolder () ??
93138 sourceUri.toFilePath ().removeDotDart ().makeFolder (),
94139 );
95140 if (await File .fromUri (outputUri.resolve ('pubspec.yaml' )).exists ()) {
96141 stderr.writeln ("'dart build' refuses to delete your project." );
97142 stderr.writeln ('Requested output directory: ${outputUri .toFilePath ()}' );
98143 return 128 ;
99144 }
145+ final outputDir = Directory .fromUri (outputUri);
146+ if (await outputDir.exists ()) {
147+ stdout.writeln ('Deleting output directory: ${outputUri .toFilePath ()}.' );
148+ await outputDir.delete (recursive: true );
149+ }
150+ final bundleDirectory = Directory .fromUri (outputUri.resolve ('bundle/' ));
151+ final binDirectory = Directory .fromUri (bundleDirectory.uri.resolve ('bin/' ));
152+ await binDirectory.create (recursive: true );
100153
101- final format = Kind .values.byName (args.option (formatOptionName)! );
102- final outputExeUri = outputUri.resolve (
103- format.appendFileExtension (
104- sourceUri.pathSegments.last.split ('.' ).first,
154+ final outputExeUri = binDirectory.uri.resolve (
155+ targetOS.executableFileName (
156+ path.basenameWithoutExtension (sourceUri.path),
105157 ),
106158 );
107- String ? targetOS = args['target-os' ];
108- if (format != Kind .exe) {
109- assert (format == Kind .aot);
110- // If we're generating an AOT snapshot and not an executable, then
111- // targetOS is allowed to be null for a platform-independent snapshot
112- // or a different platform than the host.
113- } else if (targetOS == null ) {
114- targetOS = Platform .operatingSystem;
115- } else if (targetOS != Platform .operatingSystem) {
116- stderr.writeln (
117- "'dart build -f ${format .name }' does not support cross-OS compilation." );
118- stderr.writeln ('Host OS: ${Platform .operatingSystem }' );
119- stderr.writeln ('Target OS: $targetOS ' );
120- return 128 ;
121- }
122159
123- stdout.writeln ('''The `dart build` command is in preview at the moment.
160+ stdout.writeln ('''The `dart build cli ` command is in preview at the moment.
124161See documentation on https://dart.dev/interop/c-interop#native-assets.
125162''' );
126163
127- final outputDir = Directory .fromUri (outputUri);
128- if (await outputDir.exists ()) {
129- stdout.writeln ('Deleting output directory: ${outputUri .toFilePath ()}.' );
130- await outputDir.delete (recursive: true );
131- }
132- await outputDir.create (recursive: true );
133-
134164 stdout.writeln ('Building native assets.' );
135165 final packageConfigUri = await DartNativeAssetsBuilder .ensurePackageConfig (
136166 sourceUri,
@@ -168,14 +198,14 @@ See documentation on https://dart.dev/interop/c-interop#native-assets.
168198 final generator = KernelGenerator (
169199 genSnapshot: genSnapshotHost,
170200 targetDartAotRuntime: hostDartAotRuntime,
171- kind: format ,
201+ kind: Kind .exe ,
172202 sourceFile: sourceUri.toFilePath (),
173203 outputFile: outputExeUri.toFilePath (),
174204 verbose: verbose,
175205 verbosity: args.option ('verbosity' )! ,
176206 defines: [],
177207 packages: packageConfigUri.toFilePath (),
178- targetOS: targetOS == null ? null : OS . fromString (targetOS) ,
208+ targetOS: targetOS,
179209 enableExperiment: args.enabledExperiments.join (',' ),
180210 tempDir: tempDir,
181211 );
@@ -214,7 +244,7 @@ Use linkMode as dynamic library instead.""");
214244 final kernelAssets = await bundleNativeAssets (
215245 allAssets,
216246 builder.target,
217- outputUri ,
247+ binDirectory.uri ,
218248 relocatable: true ,
219249 verbose: true ,
220250 );
@@ -226,7 +256,11 @@ Use linkMode as dynamic library instead.""");
226256 nativeAssets: nativeAssetsYamlUri? .toFilePath (),
227257 );
228258
229- // End linking here.
259+ if (targetOS == OS .macOS) {
260+ // The dylibs are opened with a relative path to the executable.
261+ // MacOS prevents opening dylibs that are not on the include path.
262+ await rewriteInstallPath (outputExeUri);
263+ }
230264 } finally {
231265 await tempDir.delete (recursive: true );
232266 }
0 commit comments