dart install and dart remote run with descriptors

Bug: https://github.com/dart-lang/sdk/issues/62123
Change-Id: I16e4fc0c20b9728e8357b6f67540aec73b2804ce
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/477560
Reviewed-by: Daco Harkes <dacoharkes@google.com>
Reviewed-by: Jonas Jensen <jonasfj@google.com>
Commit-Queue: Sigurd Meldgaard <sigurdm@google.com>
This commit is contained in:
Sigurd Meldgaard
2026-02-10 05:50:12 -08:00
committed by Commit Queue
parent 71250eddc2
commit 528d4faff6
8 changed files with 361 additions and 477 deletions
+168 -124
View File
@@ -2,6 +2,7 @@
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'dart:convert';
import 'dart:io';
import 'package:dartdev/src/commands/build.dart';
@@ -12,6 +13,7 @@ import 'package:front_end/src/api_prototype/compiler_options.dart'
import 'package:path/path.dart' as p;
import 'package:pub/pub.dart';
import 'package:pub_formats/pub_formats.dart';
import 'package:yaml/yaml.dart';
import '../core.dart';
@@ -27,11 +29,20 @@ executables.
If the same package has been previously installed, it will be overwritten.
You can specify three different values for the <package> argument:
1. A package name. This will install the package from pub.dev. (hosted)
The [version-constraint] argument can only be passed to 'hosted'.
2. A git url. This will install the package from a git repository. (git)
3. A path on your machine. This will install the package from that path. (path)''';
You can specify a package to install from pub.dev, a git repository, or a
local path using the `<package>[@<descriptor>]` syntax.
The `@<descriptor>` can be a version constraint (for hosted packages) or a
pub descriptor (consistent with pubspec.yaml).
Examples:
dart install <pkg>
dart install <pkg>@^3.0.0
dart install '<pkg>@{hosted: https://pub.dev, version: ^3.0.0}'
dart install '<pkg>@{git: {url: https://github.com/<owner>/<repo>, path: <path>}}'
dart install '<pkg>@{path: /path/to/<pkg>}'
See https://dart.dev/go/pub-descriptors for more details.''';
static const int genericErrorExitCode = 255;
static const gitRefOption = 'git-ref';
@@ -40,7 +51,7 @@ You can specify three different values for the <package> argument:
@override
String get invocation {
final superNoArguments = super.invocation.replaceAll(' [arguments]', '');
return '$superNoArguments <package> [version-constraint]';
return '$superNoArguments <package>[@<descriptor>]';
}
@override
@@ -53,6 +64,7 @@ You can specify three different values for the <package> argument:
help:
'Path of git package in repository. '
'Only applies when using a git url for <package>.',
hide: true,
);
argParser.addOption(
@@ -60,6 +72,7 @@ You can specify three different values for the <package> argument:
help:
'Git branch or commit to be retrieved. '
'Only applies when using a git url for <package>.',
hide: true,
);
argParser.addFlag(
@@ -74,6 +87,7 @@ You can specify three different values for the <package> argument:
help:
'A custom pub server URL for the package. '
'Only applies when using a package name for <package>.',
hide: true,
);
}
@@ -95,71 +109,99 @@ You can specify three different values for the <package> argument:
return arg;
}
final argument = readArg('No package source given.');
final sourceKind = _soureKindFromArgument(argument);
final firstArgument = readArg('Specify a package to install.');
final gitPath = argResults.option(gitPathOption);
var gitRef = argResults.option(gitRefOption);
if (sourceKind != RemoteSourceKind.git &&
(gitPath != null || gitRef != null)) {
usageException(
'Options `--$gitPathOption` and `--$gitRefOption` '
'can only be used with a git source.',
final atIndex = firstArgument.indexOf('@');
if (firstArgument.startsWith('git@') || atIndex == -1) {
final sourceKind = _sourceKindFromArgument(firstArgument);
final versionConstraint = sourceKind == RemoteSourceKind.hosted
? (args.isEmpty ? 'any' : readArg())
: null;
if (sourceKind != RemoteSourceKind.git &&
(gitPath != null || gitRef != null)) {
usageException(
'Options `--$gitPathOption` and `--$gitRefOption` '
'can only be used with a git source.',
);
}
final hostedUrl = argResults.option('hosted-url');
if (sourceKind != RemoteSourceKind.hosted && hostedUrl != null) {
usageException(
'Option `--hosted-url` can only be used with a hosted source.',
);
}
if (args.isNotEmpty) {
usageException(
'Too many arguments, did not expect "${args.join(' ')}"',
);
}
return NonDescriptorInstallCommandParsedArguments(
source: firstArgument,
sourceKind: sourceKind,
versionConstraint: versionConstraint,
gitPath: gitPath,
gitRef: gitRef,
hostedUrl: hostedUrl,
overwrite: overwrite,
);
} else {
if (gitPath != null || gitRef != null) {
usageException(
'Options `--$gitPathOption` and `--$gitRefOption` '
'cannot be used with the @ descriptor syntax.',
);
}
final Object? descriptor;
final packageName = firstArgument.substring(0, atIndex);
final descriptorString = firstArgument.substring(atIndex + 1);
try {
descriptor = loadYaml(descriptorString);
} on FormatException catch (e) {
usageException(
'Could not parse (what comes after @) "$descriptorString": $e',
);
}
return DescriptorInstallCommandParsedArguments(
packageName: packageName,
descriptor: descriptor,
overwrite: overwrite,
);
}
final hostedUrl = argResults.option('hosted-url');
if (sourceKind != RemoteSourceKind.hosted && hostedUrl != null) {
usageException(
'Option `--hosted-url` can only be used with a hosted source.',
);
}
String? versionConstraint;
switch (sourceKind) {
case RemoteSourceKind.git:
case RemoteSourceKind.path:
break;
case RemoteSourceKind.hosted:
versionConstraint = args.isEmpty ? 'any' : readArg();
}
if (args.isNotEmpty) {
usageException('Too many arguments, did not expect "${args.join(' ')}"');
}
return InstallCommandParsedArguments(
source: argument,
sourceKind: sourceKind,
versionConstraint: versionConstraint,
gitPath: gitPath,
gitRef: gitRef,
hostedUrl: hostedUrl,
overwrite: overwrite,
);
}
Future<String> _findPackageName(
InstallCommandParsedArguments parsedArgs,
) async {
switch (parsedArgs.sourceKind) {
case RemoteSourceKind.git:
return await getPackageNameFromGitRepo(
parsedArgs.source,
ref: parsedArgs.gitRef,
path: parsedArgs.gitPath,
relativeTo: Directory.current.path,
tagPattern: null,
);
case RemoteSourceKind.hosted:
return parsedArgs.source;
case RemoteSourceKind.path:
final pubspecFile = File.fromUri(
Directory(parsedArgs.source).absolute.uri.resolve('pubspec.yaml'),
);
if (!await pubspecFile.exists()) {
usageException('No pubspec found in ${pubspecFile.path}.');
switch (parsedArgs) {
case DescriptorInstallCommandParsedArguments _:
return parsedArgs.packageName;
case NonDescriptorInstallCommandParsedArguments _:
switch (parsedArgs.sourceKind) {
case RemoteSourceKind.git:
return await getPackageNameFromGitRepo(
parsedArgs.source,
ref: parsedArgs.gitRef,
path: parsedArgs.gitPath,
relativeTo: Directory.current.path,
tagPattern: null,
);
case RemoteSourceKind.hosted:
return parsedArgs.source;
case RemoteSourceKind.path:
final pubspecFile = File.fromUri(
Directory(parsedArgs.source).absolute.uri.resolve('pubspec.yaml'),
);
if (!await pubspecFile.exists()) {
usageException('No pubspec found in ${pubspecFile.path}.');
}
final pubspecYaml = PubspecYamlFile.loadSync(pubspecFile);
return pubspecYaml.name;
}
final pubspecYaml = PubspecYamlFile.loadSync(pubspecFile);
return pubspecYaml.name;
}
}
@@ -180,34 +222,35 @@ You can specify three different values for the <package> argument:
final tempPubspec = File.fromUri(
helperPackageDir.uri.resolve('pubspec.yaml'),
);
final helperPackagePubspec = PubspecYamlFileSyntax(
name: _helperPackageName,
environment: EnvironmentSyntax(
sdk: '^${Platform.version.split(' ').first}',
),
dependencies: {
packageName: switch (parsedArgs.sourceKind) {
RemoteSourceKind.git => GitDependencySourceSyntax(
git: GitSyntax(
url: parsedArgs.source,
path$: parsedArgs.gitPath,
ref: parsedArgs.gitRef,
),
),
RemoteSourceKind.hosted => HostedDependencySourceSyntax(
hosted: parsedArgs.hostedUrl,
version: parsedArgs.versionConstraint!,
),
RemoteSourceKind.path =>
// Re-resolve dependencies for path activate, behave like it would work
// for users of the package if the activate via hosted or git.
PathDependencySourceSyntax(
path$: Directory(parsedArgs.source).absolute.path,
),
final descriptor = switch (parsedArgs) {
DescriptorInstallCommandParsedArguments _ => parsedArgs.descriptor,
NonDescriptorInstallCommandParsedArguments _ =>
switch (parsedArgs.sourceKind) {
RemoteSourceKind.git => {
'git': {
'url': parsedArgs.source,
if (parsedArgs.gitPath != null) 'path': parsedArgs.gitPath!,
if (parsedArgs.gitRef != null) 'ref': parsedArgs.gitRef!,
},
},
RemoteSourceKind.hosted => {
'hosted': ?parsedArgs.hostedUrl,
'version': parsedArgs.versionConstraint!,
},
RemoteSourceKind.path => {
'path': Directory(parsedArgs.source).absolute.path,
},
},
},
};
final helperPackagePubspec = <String, Object?>{
'name': _helperPackageName,
'environment': {'sdk': '^${Platform.version.split(' ').first}'},
'dependencies': {packageName: descriptor},
};
tempPubspec.writeAsStringSync(
JsonEncoder.withIndent(' ').convert(helperPackagePubspec),
);
helperPackagePubspec.writeSync(tempPubspec);
}
static const _helperPackageName = 'dart_install_helper_package';
@@ -317,42 +360,27 @@ You can specify three different values for the <package> argument:
}
static AppBundleDirectory selectAppBundleDirectory(
InstallCommandParsedArguments parsedArgs,
String packageName,
Directory helperPackageDir,
File helperPackageLockFile,
) {
final AppBundleDirectory outputDir;
switch (parsedArgs.sourceKind) {
case RemoteSourceKind.git:
final resolvedGitRef =
parsedArgs.gitRef ??
GitPackageDescriptionSyntax.fromJson(
PubspecLockFile.loadSync(
helperPackageLockFile,
).packages![packageName]!.description.json,
).resolvedRef;
outputDir = DartInstallDirectory().gitAppBundle(
packageName,
resolvedGitRef,
);
case RemoteSourceKind.hosted:
final packageGraphJson = PackageGraphFile.loadSync(
File.fromUri(
helperPackageDir.uri.resolve('.dart_tool/package_graph.json'),
),
);
final resolvedVersion = packageGraphJson.packages
.firstWhere((e) => e.name == packageName)
.version;
outputDir = DartInstallDirectory().hostedAppBundle(
packageName,
resolvedVersion,
);
case RemoteSourceKind.path:
outputDir = DartInstallDirectory().localAppBundle(packageName);
final lockFile = PubspecLockFile.loadSync(helperPackageLockFile);
final resolvedPackage = lockFile.packages![packageName]!;
final source = resolvedPackage.source;
if (source == PackageSourceSyntax.git) {
final resolvedGitRef = GitPackageDescriptionSyntax.fromJson(
resolvedPackage.description.json,
).resolvedRef;
return DartInstallDirectory().gitAppBundle(packageName, resolvedGitRef);
} else if (source == PackageSourceSyntax.path$) {
return DartInstallDirectory().localAppBundle(packageName);
} else {
return DartInstallDirectory().hostedAppBundle(
packageName,
resolvedPackage.version,
);
}
return outputDir;
}
static Future<void> createAppBundleDirectory(
@@ -572,7 +600,6 @@ You can specify three different values for the <package> argument:
_uniinstallAllPackageVersions(packageName);
AppBundleDirectory appBundleDirectory = selectAppBundleDirectory(
parsedArgs,
packageName,
helperPackageDirectory,
helperPackageLockFile,
@@ -630,7 +657,25 @@ You can specify three different values for the <package> argument:
}
}
final class InstallCommandParsedArguments {
sealed class InstallCommandParsedArguments {
final bool overwrite;
InstallCommandParsedArguments({required this.overwrite});
}
class DescriptorInstallCommandParsedArguments
extends InstallCommandParsedArguments {
final String packageName;
final Object? descriptor;
DescriptorInstallCommandParsedArguments({
required this.packageName,
required this.descriptor,
required super.overwrite,
});
}
class NonDescriptorInstallCommandParsedArguments
extends InstallCommandParsedArguments {
/// Package name, git url, or file path, depending on [sourceKind].
final String source;
final RemoteSourceKind sourceKind;
@@ -638,22 +683,21 @@ final class InstallCommandParsedArguments {
final String? gitPath;
final String? gitRef;
final String? hostedUrl;
final bool overwrite;
InstallCommandParsedArguments({
NonDescriptorInstallCommandParsedArguments({
required this.source,
required this.sourceKind,
required this.versionConstraint,
required this.gitPath,
required this.gitRef,
required this.hostedUrl,
required this.overwrite,
required super.overwrite,
});
}
enum RemoteSourceKind { git, hosted, path }
RemoteSourceKind _soureKindFromArgument(String argument) {
RemoteSourceKind _sourceKindFromArgument(String argument) {
if (_packageNameRegExp.hasMatch(argument)) {
return RemoteSourceKind.hosted;
}
+71 -218
View File
@@ -17,6 +17,7 @@ import 'package:frontend_server/resident_frontend_server_utils.dart'
show invokeReplaceCachedDill;
import 'package:path/path.dart';
import 'package:pub/pub.dart';
import 'package:yaml/yaml.dart';
import '../core.dart';
import '../experiments.dart';
@@ -56,17 +57,42 @@ class RunCommand extends DartdevCommand {
bool verbose = false,
this.nativeAssetsExperimentEnabled = false,
this.dataAssetsExperimentEnabled = false,
}) : super(cmdName, '''Run a Dart program from a file or a local package.
}) : super(cmdName, '''
Run a Dart program from a file or a local or remote package.
Usage: dart [vm-options] run [arguments] <dart-file>|<local-package> [args]
Usage:
Running a local script or package executable:
dart run [vm-options] <dart-file>|<local-package>[:<executable>] args
Running a remote package executable:
dart run <remote-package>[:<executable?]@[<descriptor>]> [args]
<dart-file>
A path to a Dart script (e.g., `bin/main.dart`).
<local-package>
An executable from a local package dependency, in the format <package>[:<executable>].
For example, `test:test` runs the `test` executable from the `test` package.
If the executable is not specified, the package name is used.''', verbose) {
The name of a package in the local package resolution.
<executable>
The name of an executable in the package to execute.
For example, `dart run test:test` runs the `test` executable from the `test` package.
If the executable is not specified, the package name is used.
<descriptor>
A YAML formatted string that describes how to locate the
remote package, the same you could use in a pubspec.
For example, to run the latest stable `pubviz` package from pub.dev:
dart run pubviz@
To specify a version constraint:
dart run pubviz@^4.0.0
To specify a custom package host:
dart run 'pubviz@{hosted: https://my_repository.com, version: ^1.0.0}'
To run from a git package:
dart run 'pubviz@{git: https://github.com/kevmoo/pubviz}'
See https://dart.dev/go/pub-descriptors for more details.''', verbose) {
argParser
..addFlag(
residentOption,
@@ -327,58 +353,12 @@ Usage: dart [vm-options] run [arguments] <dart-file>|<local-package> [args]
'extension development environment.',
)
..addFlag('debug-dds', hide: true)
..addExperimentalFlags(verbose: verbose)
..addFlag(
'enable-experiment-remote-run',
negatable: false,
hide: !verbose,
help: '''
Enables running executables from remote packages.
When running a remote executable, all other command-line flags are disabled,
except for the options for remote executables. `dart run <remote-executable>`
uses `dart install` under the hood and compiles the app into a standalone
executable, preventing passing VM options.
(Syntax is expected to change in the future.)
From a hosted package server:
<hosted-url>/<package>[@<version>][:<executable>]
Downloads the package from a hosted package server and runs the specified
executable.
If a version is provided, the specified version is downloaded.
If an executable is not specified, the package name is used.
For example, `https://pub.dev/dcli@1.0.0:dcli_complete` runs the
`dcli_complete` executable from version 1.0.0 of the `dcli` package.
From a git repository:
<git-url>[:<executable>]
Clones the git repository and runs the specified executable from it.
If an executable is not specified, the package name from the cloned
repository's pubspec.yaml is used.
The git url can be any valid git url.''',
)
..addOption(
hide: !verbose,
gitPathOption,
help:
'Path of git package in repository. '
'Only applies when using a git url for <remote-executable>.',
)
..addOption(
hide: !verbose,
gitRefOption,
help:
'Git branch or commit to be retrieved. '
'Only applies when using a git url for <remote-executable>.',
);
..addExperimentalFlags(verbose: verbose);
}
@override
String get invocation =>
'${super.invocation} [<dart-file|package-target> [args]]';
'dart run [vm-options] <dart-file>|<local-pkg>|<remote-pkg>@<descriptor> <program-args...>';
@override
CommandCategory get commandCategory => CommandCategory.project;
@@ -468,8 +448,8 @@ Enables running executables from remote packages.
runArgs = args.rest.skip(1).toList();
}
if (args.flag('enable-experiment-remote-run') &&
_isRemoteRun(mainCommand)) {
final atIndex = mainCommand.indexOf('@');
if (atIndex != -1) {
return _runRemote(args, mainCommand, runArgs);
}
return _runLocal(args, mainCommand, runArgs);
@@ -498,12 +478,6 @@ Enables running executables from remote packages.
);
return errorExitCode;
}
if (args.wasParsed(gitPathOption) || args.wasParsed(gitRefOption)) {
usageException(
'Options `--$gitPathOption` and `--$gitRefOption` '
'can only be used with a remote executable.',
);
}
String? nativeAssets;
final packageConfigUri = await DartNativeAssetsBuilder.ensurePackageConfig(
@@ -639,140 +613,6 @@ Enables running executables from remote packages.
return 0;
}
static RemoteSourceKind? _remoteSourceKindFromArgument(String argument) {
if (argument.startsWith('git@')) {
return RemoteSourceKind.git;
}
final potentialUri = argument
.split(_colonButNoSlashes)
.first
.split('@')
.first;
final endsWithDotGitRegex = RegExp(r'\.git[/\\]?$');
if (endsWithDotGitRegex.hasMatch(potentialUri)) {
return RemoteSourceKind.git;
}
final parsedUri = Uri.tryParse(potentialUri);
if (parsedUri != null) {
switch (parsedUri.scheme.toLowerCase()) {
case 'git':
return RemoteSourceKind.git;
case 'http':
case 'https':
return RemoteSourceKind.hosted;
}
}
final parsedGitSshUrl = GitSshUrl.tryParse(potentialUri);
if (parsedGitSshUrl != null) {
return RemoteSourceKind.git;
}
// Local execution.
return null;
}
static bool _isRemoteRun(String mainCommand) {
return _remoteSourceKindFromArgument(mainCommand) != null;
}
/// Parse the arguments for remote run.
///
/// Constructs a [InstallCommandParsedArguments] to be able to reuse the
/// [InstallCommand] implementation.
InstallCommandParsedArguments _parseRemoteArguments(String mainCommand) {
final argResults = this.argResults!;
final sourceKind = _remoteSourceKindFromArgument(mainCommand)!;
final gitPath = argResults.option(gitPathOption);
var gitRef = argResults.option(gitRefOption);
if (sourceKind != RemoteSourceKind.git &&
(gitPath != null || gitRef != null)) {
usageException(
'Options `--$gitPathOption` and `--$gitRefOption` '
'can only be used with a git source.',
);
}
for (final option in argResults.options) {
if (argResults.wasParsed(option) &&
option != gitPathOption &&
option != gitRefOption &&
option != verbosityOption &&
option != 'enable-experiment-remote-run') {
usageException(
'Option $option cannot be used in remote runs. '
'`dart run <remote-executable>` uses `dart install` under the hood '
'and compiles the app into a standalone executable.',
);
}
}
String? hostedUrl;
String? versionConstraint;
final String source;
switch (sourceKind) {
case RemoteSourceKind.git:
if (mainCommand.startsWith('git@') && mainCommand.contains('.git')) {
// Valid values might contain a colon for the command or not:
// - git@github.com:org/repo.git
// - git@github.com:org/repo.git:executable
// Drop everything after the 2nd colon for the git repository.
source = mainCommand.split(':').sublist(0, 2).join(':');
} else {
source = mainCommand.split(_colonButNoSlashes).first;
}
case RemoteSourceKind.hosted:
final parsedUri = Uri.parse(
mainCommand.split('@').first.split(_colonButNoSlashes).first,
);
hostedUrl = '${parsedUri.scheme}://${parsedUri.host}';
source = parsedUri.path.replaceFirst('/', '');
versionConstraint =
mainCommand
.split('@')
.lastButNotFirstOrNull
?.split(_colonButNoSlashes)
.first ??
'any';
if (versionConstraint.isEmpty) {
versionConstraint = 'any';
}
case RemoteSourceKind.path:
throw StateError('Unreachable');
}
return InstallCommandParsedArguments(
source: source,
sourceKind: sourceKind,
versionConstraint: versionConstraint,
gitPath: gitPath,
gitRef: gitRef,
hostedUrl: hostedUrl,
overwrite: false,
);
}
Future<String> _findPackageName(
InstallCommandParsedArguments parsedArgs,
) async {
switch (parsedArgs.sourceKind) {
case RemoteSourceKind.git:
return await getPackageNameFromGitRepo(
parsedArgs.source,
ref: parsedArgs.gitRef,
path: parsedArgs.gitPath,
relativeTo: Directory.current.path,
tagPattern: null,
);
case RemoteSourceKind.hosted:
return parsedArgs.source;
case RemoteSourceKind.path:
throw StateError('Unreachable');
}
}
/// Installs (if needed) and runs the remote executable.
///
/// Installs the app bundle at the same location as `dart install` but does
@@ -782,9 +622,38 @@ Enables running executables from remote packages.
String mainCommand,
List<String> runArgs,
) async {
final parsedArgs = _parseRemoteArguments(mainCommand);
final packageName = await _findPackageName(parsedArgs);
for (final option in args.options) {
if (args.wasParsed(option) && option != verbosityOption) {
usageException(
'Option --$option cannot be used in remote runs. '
'`dart run <remote-executable>` uses `dart install` under the hood '
'and compiles the app into a standalone executable.',
);
}
}
final atIndex = mainCommand.indexOf('@');
assert(atIndex != -1);
final command = mainCommand.substring(0, atIndex);
final descriptorString = mainCommand.substring(atIndex + 1);
final Object? descriptor;
try {
descriptor = loadYaml(descriptorString);
} on FormatException catch (e) {
usageException(
'Failed to parse remote executable descriptor "$descriptorString": $e',
);
}
final colonIndex = command.indexOf(':');
final String packageName;
final String executable;
if (colonIndex == -1) {
packageName = command;
executable = command;
} else {
packageName = command.substring(0, colonIndex);
executable = command.substring(colonIndex + 1);
}
return await InstallCommand.inTempDir((tempDirectory) async {
try {
// Create a helper package for running a pub-resolve and pulling in the
@@ -796,7 +665,11 @@ Enables running executables from remote packages.
InstallCommand.createHelperPackagePubspec(
helperPackageDir: helperPackageDirectory,
packageName: packageName,
parsedArgs: parsedArgs,
parsedArgs: DescriptorInstallCommandParsedArguments(
packageName: packageName,
descriptor: descriptor,
overwrite: false,
),
);
await InstallCommand.resolveHelperPackage(helperPackageDirectory);
final helperPackageLockFile = File.fromUri(
@@ -804,7 +677,6 @@ Enables running executables from remote packages.
);
final appBundleDirectory = InstallCommand.selectAppBundleDirectory(
parsedArgs,
packageName,
helperPackageDirectory,
helperPackageLockFile,
@@ -858,14 +730,6 @@ Enables running executables from remote packages.
);
}
final mainCommandRemainder = mainCommand.substring(
parsedArgs.source.length,
);
final executable =
mainCommandRemainder
.split(_colonButNoSlashes)
.lastButNotFirstOrNull ??
packageName;
final executableUri = appBundleDirectory.directory.uri.resolve(
'bundle/bin/$executable',
);
@@ -888,17 +752,6 @@ Enables running executables from remote packages.
}
}
extension<T> on List<T> {
/// Return the last element, but only if there are at least two elements.
T? get lastButNotFirstOrNull {
if (length < 2) return null;
return last;
}
}
/// Does not match the :// in an url scheme or the :\ in a Windows path.
final _colonButNoSlashes = RegExp(r':(?!(//|\\))');
/// Keep in sync with [getExecutableForCommand].
///
/// Returns `null` if root package should be used.
+1 -1
View File
@@ -90,7 +90,7 @@ Project
compile Compile Dart to various formats.
create Create a new Dart project.
pub Work with packages.
run Run a Dart program from a file or a local package.
run Run a Dart program from a file or a local or remote package.
test Run tests for a project.
Source code
+4 -4
View File
@@ -107,13 +107,13 @@ void run() {
expect(
result.stdout,
contains('Run a Dart program from a file or a local package.'),
contains('Run a Dart program from a file or a local or remote package.'),
);
expect(result.stdout, contains('Debugging options:'));
expect(
result.stdout,
contains(
'Usage: dart [vm-options] run [arguments] <dart-file>|<local-package> [args]',
'Usage: dart run [vm-options] <dart-file>|<local-pkg>|<remote-pkg>@<descriptor> <program-args...>',
),
);
expect(result.stderr, isEmpty);
@@ -126,13 +126,13 @@ void run() {
expect(
result.stdout,
contains('Run a Dart program from a file or a local package.'),
contains('Run a Dart program from a file or a local or remote package.'),
);
expect(result.stdout, contains('Debugging options:'));
expect(
result.stdout,
contains(
'Usage: dart [vm-options] run [arguments] <dart-file>|<local-package> [args]',
'dart run [vm-options] <dart-file>|<local-pkg>|<remote-pkg>@<descriptor> <program-args...>',
),
);
expect(result.stderr, isEmpty);
@@ -0,0 +1,3 @@
void main(List<String> args) {
print('Hello from other app ${args.join(' ')}');
}
@@ -5,3 +5,4 @@ environment:
executables:
dart_app:
other_app:
@@ -90,21 +90,26 @@ executables.
If the same package has been previously installed, it will be overwritten.
You can specify three different values for the <package> argument:
1. A package name. This will install the package from pub.dev. (hosted)
The [version-constraint] argument can only be passed to 'hosted'.
2. A git url. This will install the package from a git repository. (git)
3. A path on your machine. This will install the package from that path. (path)
You can specify a package to install from pub.dev, a git repository, or a
local path using the `<package>[@<descriptor>]` syntax.
Usage: dart install <package> [version-constraint]
-h, --help Print this usage information.
--git-path Path of git package in repository. Only applies when using a git url for <package>.
--git-ref Git branch or commit to be retrieved. Only applies when using a git url for <package>.
--overwrite Overwrite executables from other packages with the same name.
-u, --hosted-url A custom pub server URL for the package. Only applies when using a package name for <package>.
The `@<descriptor>` can be a version constraint (for hosted packages) or a
pub descriptor (consistent with pubspec.yaml).
Run "dart help" to see global options.
''',
Examples:
dart install <pkg>
dart install <pkg>@^3.0.0
dart install '<pkg>@{hosted: https://pub.dev, version: ^3.0.0}'
dart install '<pkg>@{git: {url: https://github.com/<owner>/<repo>, path: <path>}}'
dart install '<pkg>@{path: /path/to/<pkg>}'
See https://dart.dev/go/pub-descriptors for more details.
Usage: dart install <package>[@<descriptor>]
-h, --help Print this usage information.
--overwrite Overwrite executables from other packages with the same name.
Run "dart help" to see global options.''',
),
(
'installed',
@@ -152,6 +157,9 @@ Run "dart help" to see global options.
final argumentss = [
(null, [_packageForTest]),
(null, [_packageForTest, _packageVersion]),
(null, ['$_packageForTest@$_packageVersion']),
(null, ['$_packageForTest@{"version": "$_packageVersion"}']),
(null, ['$_packageForTest@{version: $_packageVersion}']),
(
null,
[_packageForTest, _packageVersion, '--hosted-url', 'https://pub.dev/'],
@@ -225,6 +233,7 @@ Run "dart help" to see global options.
final argumentssGit = [
['git'],
['git', '--git-path', '--git-ref'],
['git-descriptor'],
];
for (final testArguments in argumentssGit) {
@@ -234,7 +243,11 @@ Run "dart help" to see global options.
await inTempDir((tempUri) async {
final gitUri = tempUri.resolve('app.git/');
await Directory.fromUri(gitUri.resolve('bin/')).create(recursive: true);
for (final file in ['pubspec.yaml', 'bin/dart_app.dart']) {
for (final file in [
'pubspec.yaml',
'bin/dart_app.dart',
'bin/other_app.dart',
]) {
await File.fromUri(
_package2Dir.uri.resolve(file),
).copy(gitUri.resolve(file).toFilePath());
@@ -261,11 +274,21 @@ Run "dart help" to see global options.
as String)
.trim();
final gitPath = './';
final arguments = [
gitUri.toFilePath(),
if (testArguments.contains('--git-path')) ...['--git-path', gitPath],
if (testArguments.contains('--git-ref')) ...['--git-ref', gitRef],
];
final List<String> arguments;
if (testArguments.contains('git-descriptor')) {
arguments = [
'dart_app@{git: {url: ${gitUri.toFilePath()}, ref: $gitRef}}',
];
} else {
arguments = [
gitUri.toFilePath(),
if (testArguments.contains('--git-path')) ...[
'--git-path',
gitPath,
],
if (testArguments.contains('--git-ref')) ...['--git-ref', gitRef],
];
}
final dartDataHome = tempUri.resolve('dart_home/');
await Directory.fromUri(dartDataHome).create();
@@ -11,10 +11,6 @@ import 'package:test/test.dart';
import '../utils.dart';
import 'helpers.dart';
const _packageForTest = 'vm_snapshot_analysis';
const _packageVersion = '0.7.5';
const _cliToolForTest = 'snapshot_analysis';
final _sdkUri = resolveDartDevUri('.').resolve('../../');
final _package2RelativePath = Uri.directory('pkg/dartdev/test/data/dart_app/');
@@ -23,8 +19,6 @@ final _package2Dir = Directory.fromUri(
_sdkUri.resolveUri(_package2RelativePath),
);
const _gitPackageForTest = 'dart_app';
final _pathEnvVarSeparator = Platform.isWindows ? ';' : ':';
const String _dartDirectoryEnvKey = 'DART_DATA_HOME';
@@ -48,52 +42,59 @@ void main() async {
final result = await _runDartdev(
fromDartdevSource,
'run',
['--help', '-v'],
['--help'],
null,
{},
);
print(result.stdout);
printOnFailure('stdout:\n${result.stdout}');
expect(
result.stdout,
contains(
'''
--enable-experiment-remote-run Enables running executables from remote packages.
When running a remote executable, all other command-line flags are disabled,
except for the options for remote executables. `dart run <remote-executable>`
uses `dart install` under the hood and compiles the app into a standalone
executable, preventing passing VM options.
(Syntax is expected to change in the future.)
From a hosted package server:
<hosted-url>/<package>[@<version>][:<executable>]
Downloads the package from a hosted package server and runs the specified
executable.
If a version is provided, the specified version is downloaded.
If an executable is not specified, the package name is used.
For example, `https://pub.dev/dcli@1.0.0:dcli_complete` runs the
`dcli_complete` executable from version 1.0.0 of the `dcli` package.
From a git repository:
<git-url>[:<executable>]
Clones the git repository and runs the specified executable from it.
If an executable is not specified, the package name from the cloned
repository's pubspec.yaml is used.
The git url can be any valid git url.
--git-path Path of git package in repository. Only applies when using a git url for <remote-executable>.
--git-ref Git branch or commit to be retrieved. Only applies when using a git url for <remote-executable>.''',
),
contains('''
Run a Dart program from a file or a local or remote package.
Usage:
Running a local script or package executable:
dart run [vm-options] <dart-file>|<local-package>[:<executable>] args
Running a remote package executable:
dart run <remote-package>[:<executable?]@[<descriptor>]> [args]
<dart-file>
A path to a Dart script (e.g., `bin/main.dart`).
<local-package>
The name of a package in the local package resolution.
<executable>
The name of an executable in the package to execute.
For example, `dart run test:test` runs the `test` executable from the `test` package.
If the executable is not specified, the package name is used.
<descriptor>
A YAML formatted string that describes how to locate the
remote package, the same you could use in a pubspec.
For example, to run the latest stable `pubviz` package from pub.dev:
dart run pubviz@
To specify a version constraint:
dart run pubviz@^4.0.0
To specify a custom package host:
dart run 'pubviz@{hosted: https://my_repository.com, version: ^1.0.0}'
To run from a git package:
dart run 'pubviz@{git: https://github.com/kevmoo/pubviz}'
See https://dart.dev/go/pub-descriptors for more details.'''),
);
});
for (final version in ['', '@$_packageVersion', '@']) {
final testName = version == ''
? 'no version'
: (version == '@' ? 'empty version' : 'with version');
test('dart run hosted package $testName', timeout: longTimeout, () async {
for (final argument in [
'vm_snapshot_analysis:snapshot_analysis@0.7.5',
'vm_snapshot_analysis:snapshot_analysis@^0.7.5',
'vm_snapshot_analysis:snapshot_analysis@', // Resolves to latest stable version.
'vm_snapshot_analysis:snapshot_analysis@{hosted: https://pub.dev}',
]) {
test('dart run $argument', timeout: longTimeout, () async {
await inTempDir((tempUri) async {
final dartDataHome = tempUri.resolve('dart_home/');
await Directory.fromUri(dartDataHome).create();
@@ -109,8 +110,7 @@ void main() async {
fromDartdevSource,
'run',
[
'--enable-experiment-remote-run',
'https://pub.dev/$_packageForTest$version:$_cliToolForTest',
argument,
// Make sure to pass arguments that influence stdout.
'compare',
'--help',
@@ -129,23 +129,18 @@ void main() async {
});
}
final argumentssGit = [
['git'],
['git', '--git-path', '--git-ref'],
];
for (final testArguments in argumentssGit) {
var testName = testArguments.join(' ');
test('dart run from $testName', timeout: longTimeout, () async {
await inTempDir((tempUri) async {
final (gitUri, gitRef) = await _setupSimpleGitRepo(tempUri);
final gitPath = './';
test('dart run remote from git', timeout: longTimeout, () async {
await inTempDir((tempUri) async {
final (gitUri, gitRef) = await _setupSimpleGitRepo(tempUri);
for (final (argument, expectedResult) in [
('dart_app@{git: ${gitUri.toString()}}', 'Hello Alice and Bob'),
(
'dart_app:other_app@{git: ${gitUri.toString()}}',
'Hello from other app Alice and Bob',
),
]) {
final arguments = [
'--enable-experiment-remote-run',
if (testArguments.contains('--git-path')) ...['--git-path', gitPath],
if (testArguments.contains('--git-ref')) ...['--git-ref', gitRef],
'${gitUri.toFilePath()}:$_gitPackageForTest',
argument,
// Make sure to pass arguments that influence stdout.
'Alice',
'and',
@@ -170,48 +165,24 @@ void main() async {
environment,
);
expect(
runResult.stdout,
stringContainsInOrder(['Hello Alice and Bob']),
);
expect(runResult.stdout, contains(expectedResult));
expect(runResult.exitCode, 0);
});
}
});
}
});
final errorArgumentss = [
(
[
'--enable-experiment-remote-run',
'https://pub.dev/this_package_does_not_exist_12345',
],
['this_package_does_not_exist_12345@'],
'could not find package this_package_does_not_exist_12345 at',
errorExitCode,
),
(
[
'--enable-experiment-remote-run',
'--git-path',
'foo/',
'https://pub.dev/vm_snapshot_analysis',
],
'git-path',
usageExitCode,
),
(
[
'--enable-experiment-remote-run',
'--enable-asserts',
'https://pub.dev/vm_snapshot_analysis',
],
'enable-asserts',
usageExitCode,
),
(
['--enable-experiment-remote-run', '--git-path', 'foo/'],
'git-path',
['--enable-asserts', 'vm_snapshot_analysis@'],
'--enable-asserts cannot be used in remote runs',
usageExitCode,
),
(['my_package@{,bad,descriptor,}'], '{,bad,descriptor,}', usageExitCode),
];
for (final (errorArguments, error, exitCode) in errorArgumentss) {
test('dart run ${errorArguments.join(' ')}', timeout: longTimeout, () async {
@@ -284,10 +255,7 @@ void main(List<String> args) async {
final runResult = await _runDartdev(
fromDartdevSource,
'run',
[
'--enable-experiment-remote-run',
'${gitUri.toFilePath()}:$packageName',
],
['test_app_with_failing_hook@{git: ${gitUri.toFilePath()}}'],
null,
environment,
expectedExitCode: errorExitCode,
@@ -309,13 +277,7 @@ void main(List<String> args) async {
await inTempDir((tempUri) async {
final (gitUri, gitRef) = await _setupSimpleGitRepo(tempUri);
final arguments = [
'--enable-experiment-remote-run',
'--git-ref',
gitRef,
'${gitUri.toFilePath()}:$_gitPackageForTest',
'World',
];
final arguments = ['dart_app@{git: ${gitUri.toFilePath()}}', 'World'];
// 2. Setup environment
final dartDataHome = tempUri.resolve('dart_home/');
@@ -372,11 +334,8 @@ void main(List<String> args) async {
);
final arguments = [
'--enable-experiment-remote-run',
if (verbosityError) '--verbosity=error',
'--git-ref',
gitRef,
'${gitUri.toFilePath()}:$packageName',
'test_app_with_hook@{git: {url: ${gitUri.toFilePath()}, ref: $gitRef}}',
'ignored',
'arguments',
];
@@ -391,8 +350,6 @@ void main(List<String> args) async {
'${binDir.path}$_pathEnvVarSeparator${Platform.environment['PATH']!}',
};
print(environment);
print('dart run ${arguments.join(' ')}');
final runResult = await _runDartdev(
fromDartdevSource,
'run',
@@ -407,7 +364,7 @@ void main(List<String> args) async {
expect(runResult.stdout, isNot(contains('Running build hooks')));
expect(runResult.stdout, isNot(contains('Running link hooks')));
expect(runResult.stdout, isNot(contains('Generated: ')));
// Should have no other output then the program.
// Should have no other output than the program.
expect(runResult.stdout.trim(), equals('Hello World'));
} else {
expect(runResult.stdout, contains('Running build hooks'));
@@ -513,6 +470,9 @@ Future<(Uri gitUri, String gitRef)> _setupSimpleGitRepo(Uri tempUri) async {
'bin/dart_app.dart': await File.fromUri(
_package2Dir.uri.resolve('bin/dart_app.dart'),
).readAsString(),
'bin/other_app.dart': await File.fromUri(
_package2Dir.uri.resolve('bin/other_app.dart'),
).readAsString(),
},
);
}