Skip to content

Commit 94894b1

Browse files
dcharkesCommit Queue
authored andcommitted
[dartdev] dart install
This CL adds three new commands to `dart`: ``` Global install Install a Dart CLI tool for global use. installed List globally installed Dart CLI tools. uninstall Remove a globally installed Dart CLI tool. ``` These commands are intended to replace `dart pub global` subcommands while adding support for `hook/build.dart` and building packages in AOT instead of running them with JIT. Internal design doc: http://go/dart-install-cli. Implementation details: * The source of truth is the state of the file system. These commands write and read directories, files, and symlinks. * App bundles and symlinks are placed in `DART_DATA_HOME` as per http://go/dart-data-home. * On Unix systems we use symlinks and on Windows batchfiles to place executables in the bin directory that point to an application bundle. (These OS differences have been encapsulated in a single class.) * On Windows, when an application is running, trying to re-install it will fail. Test coverage: * Installing from hosted, git, and local paths. * Installing a package with hooks. * Installing a package with hooks and user-defines. * Surfacing build hook failures during install. * Installing packages with conflicting executables names. This tests `--overwrite` flag behavior. * Installing a new or the same version, this should simply succeed. * A warning is shown if the bin directory is not on the `PATH`. * Running an installed app reports the correct exit code on exit. * Listing all installed versions, including the versions not on the`PATH`. * Uninstalling, which uninstalls all versions. * Re-installing while it is running. * Uninstalling while it is running. Out of scope for initial version: * Saving the SDK version (to display in `dart installed`). * Short-circuiting if re-installing an exactly installed version. Bug: #60889 Change-Id: I8f3a60d26e013957ce6fd7f52e564bcaaff30509 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-mac-release-try,pkg-win-release-arm64-try,pkg-win-release-try Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/441581 Reviewed-by: Sigurd Meldgaard <[email protected]> Commit-Queue: Daco Harkes <[email protected]> Reviewed-by: Michael Goderbauer <[email protected]>
1 parent c92417b commit 94894b1

File tree

14 files changed

+2320
-84
lines changed

14 files changed

+2320
-84
lines changed

pkg/dartdev/lib/dartdev.dart

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,13 @@ import 'src/commands/devtools.dart';
2727
import 'src/commands/doc.dart';
2828
import 'src/commands/fix.dart';
2929
import 'src/commands/info.dart';
30+
import 'src/commands/install.dart';
31+
import 'src/commands/installed.dart';
3032
import 'src/commands/language_server.dart';
3133
import 'src/commands/run.dart';
3234
import 'src/commands/test.dart';
3335
import 'src/commands/tooling_daemon.dart';
36+
import 'src/commands/uninstall.dart';
3437
import 'src/core.dart';
3538
import 'src/experiments.dart';
3639
import 'src/unified_analytics.dart';
@@ -135,6 +138,11 @@ class DartdevRunner extends CommandRunner<int> {
135138
nativeAssetsExperimentEnabled: nativeAssetsExperimentEnabled,
136139
));
137140
addCommand(ToolingDaemonCommand(verbose: verbose));
141+
if (nativeAssetsExperimentEnabled) {
142+
addCommand(InstallCommand(verbose: verbose));
143+
addCommand(InstalledCommand(verbose: verbose));
144+
addCommand(UninstallCommand(verbose: verbose));
145+
}
138146
}
139147

140148
@visibleForTesting

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

Lines changed: 160 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import 'package:dartdev/src/native_assets_macos.dart';
1414
import 'package:dartdev/src/sdk.dart';
1515
import 'package:front_end/src/api_prototype/compiler_options.dart'
1616
show Verbosity;
17+
import 'package:hooks_runner/hooks_runner.dart';
1718
import 'package:path/path.dart' as path;
1819

1920
import '../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.
242306
Use 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

Comments
 (0)