Skip to content

Commit 2bc4081

Browse files
dcharkesCommit Queue
authored andcommitted
[dartdev] dart build cli (Removes dart build)
This PR changes `dart build` to have subcommands, so that we can have different bundle formats in the future. Moreover, it changes the following: * `exe` to `cli`. This mirrors `dart create --template cli` * The output executable no longer has `.exe` on MacOS and Linux. * The output bundle now contains a `bin/` dir with the executable. * It removes the `--target-os` option, it would simply error on cross compilation. (And we don't want API parity with `dart compile exe`.) * The default output directory is somewhere in `build/`. This aligns the behavior with Flutter. (`dart compile` puts the file next to the Dart file, which is a source directory.) The executables and dylibs are signed with the ad hoc code signing `"-"`. Users will need to resign in order to distribute. New CLI interface: ``` $ dart build cli --help Build a Dart application for the command line. The CLI app bundle is structured in the following manner: bundle/ bin/ <executable> lib/ <dynamic libraries> Usage: dart build cli [arguments] -h, --help Print this usage information. -o, --output=<path> Write the output to <output>/bundle/. This can be an absolute or relative path. (defaults to "build/cli/macos-arm64/") -t, --target=<path> The main entry-point file of the command-line application. Must be a Dart file in the bin/ directory. If the "--target" option is omitted, and there is a single Dart file in bin/, then that is used instead. (defaults to "bin/native_add_app.dart") --verbosity=<level> Sets the verbosity level of the compilation. [error] Show only error messages [warning] Show only error and warning messages [info] Show error, warning, and info messages [all] (default) Show all messages ``` This PR removes support for `dart build -f exe <target>`, as `package:args` does not support having subcommands and not having subcommands: `Could not find a subcommand named "bin/native_add_app.dart" for "dart build".` Bug: #60730 Change-Id: I2b527754f3186ec6d0809d7ac45e05984d5c0a02 Cq-Include-Trybots: luci.dart.try:pkg-linux-debug-try,pkg-linux-release-arm64-try,pkg-linux-release-try,pkg-mac-release-arm64-try,pkg-win-release-arm64-try,pkg-mac-release-try,pkg-win-release-try Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/433160 Reviewed-by: Michael Goderbauer <[email protected]> Reviewed-by: Ben Konyi <[email protected]> Commit-Queue: Daco Harkes <[email protected]>
1 parent 5e3c017 commit 2bc4081

File tree

6 files changed

+227
-155
lines changed

6 files changed

+227
-155
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,11 @@ constraint][language version] lower bound to 3.9 or greater (`sdk: '^3.9.0'`).
2323
[`switch_on_type`]: http://dart.dev/lints/switch_on_type
2424
[`unnecessary_unawaited`]: http://dart.dev/lints/unnecessary_unawaited
2525

26+
#### Dart build
27+
28+
- Breaking change of feature in preview: `dart build -f exe <target>` is now
29+
`dart build cli --target=<target>`. See `dart build cli --help` for more info.
30+
2631
#### Dart Development Compiler (dartdevc)
2732

2833
- Outstanding async code now checks and cancels itself after a hot restart if

pkg/dartdev/lib/src/commands/build.dart

Lines changed: 88 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,11 @@ import 'package:dart2native/generate.dart';
1010
import 'package:dartdev/src/commands/compile.dart';
1111
import 'package:dartdev/src/experiments.dart';
1212
import 'package:dartdev/src/native_assets_bundling.dart';
13+
import 'package:dartdev/src/native_assets_macos.dart';
1314
import 'package:dartdev/src/sdk.dart';
1415
import 'package:front_end/src/api_prototype/compiler_options.dart'
1516
show Verbosity;
1617
import 'package:path/path.dart' as path;
17-
import 'package:vm/target_os.dart'; // For possible --target-os values.
1818

1919
import '../core.dart';
2020
import '../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.
124161
See 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
}

pkg/dartdev/lib/src/native_assets_bundling.dart

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import 'package:data_assets/data_assets.dart';
1010
import 'package:hooks/hooks.dart';
1111
import 'package:hooks_runner/hooks_runner.dart';
1212

13+
final libOutputDirectoryUriFromBin =
14+
Uri.file('../').resolveUri(libOutputDirectoryUri);
1315
final libOutputDirectoryUri = Uri.file('lib/');
1416
final dataOutputDirectoryUri = Uri.file('assets/');
1517

@@ -130,7 +132,7 @@ extension on CodeAsset {
130132
LookupInProcess() => KernelAssetInProcess(),
131133
DynamicLoadingBundled() => () {
132134
final relativeUri =
133-
libOutputDirectoryUri.resolve(file!.pathSegments.last);
135+
libOutputDirectoryUriFromBin.resolve(file!.pathSegments.last);
134136
return relocatable
135137
? KernelAssetRelativePath(relativeUri)
136138
: KernelAssetAbsolutePath(outputUri.resolveUri(relativeUri));

pkg/dartdev/lib/src/native_assets_macos.dart

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,3 +158,45 @@ Map<Architecture?, List<String>> parseOtoolArchitectureSections(String output) {
158158

159159
return architectureSections;
160160
}
161+
162+
/// Rewrites the include path for an executable on MacOS.
163+
///
164+
/// The directory structure for `dart build cli` is
165+
///
166+
/// ```
167+
/// bundle/
168+
/// bin/
169+
/// <executable>
170+
/// lib/
171+
/// <dylibs>
172+
/// ```
173+
///
174+
/// So, the `bundle/` directory must be added to the include path to allow for
175+
/// loading dylibs.
176+
Future<void> rewriteInstallPath(Uri executable) async {
177+
final result = await Process.run(
178+
'install_name_tool',
179+
['-add_rpath', '@executable_path/..', executable.toFilePath()],
180+
);
181+
if (result.exitCode != 0) {
182+
throw Exception(
183+
'Failed to add rpath: ${result.stderr}',
184+
);
185+
}
186+
187+
// Resign after modifying.
188+
final codesignResult = await Process.run(
189+
'codesign',
190+
[
191+
'--force',
192+
'--sign',
193+
'-',
194+
executable.toFilePath(),
195+
],
196+
);
197+
if (codesignResult.exitCode != 0) {
198+
throw Exception(
199+
'Failed to codesign dylib $executable: ${codesignResult.stderr}',
200+
);
201+
}
202+
}

0 commit comments

Comments
 (0)