Unverified Commit 8c5a70f3 authored by Ian Hickson's avatar Ian Hickson Committed by GitHub

flutter update-packages --cherry-pick-package (#128917)

Fixes https://github.com/flutter/flutter/issues/101525
parent d3e771c8
...@@ -51,6 +51,14 @@ class UpdatePackagesCommand extends FlutterCommand { ...@@ -51,6 +51,14 @@ class UpdatePackagesCommand extends FlutterCommand {
'This will actually modify the pubspec.yaml files in your checkout.', 'This will actually modify the pubspec.yaml files in your checkout.',
negatable: false, negatable: false,
) )
..addOption(
'cherry-pick-package',
help: 'Attempt to update only the specified package. The "-cherry-pick-version" version must be specified also.',
)
..addOption(
'cherry-pick-version',
help: 'Attempt to update the package to the specified version. The "--cherry-pick-package" option must be specified also.',
)
..addFlag( ..addFlag(
'paths', 'paths',
help: 'Finds paths in the dependency chain leading from package specified ' help: 'Finds paths in the dependency chain leading from package specified '
...@@ -182,7 +190,8 @@ class UpdatePackagesCommand extends FlutterCommand { ...@@ -182,7 +190,8 @@ class UpdatePackagesCommand extends FlutterCommand {
final bool isVerifyOnly = boolArg('verify-only'); final bool isVerifyOnly = boolArg('verify-only');
final bool isConsumerOnly = boolArg('consumer-only'); final bool isConsumerOnly = boolArg('consumer-only');
final bool offline = boolArg('offline'); final bool offline = boolArg('offline');
final bool doUpgrade = forceUpgrade || isPrintPaths || isPrintTransitiveClosure; final String? cherryPickPackage = stringArg('cherry-pick-package');
final String? cherryPickVersion = stringArg('cherry-pick-version');
if (boolArg('crash')) { if (boolArg('crash')) {
throw StateError('test crash please ignore.'); throw StateError('test crash please ignore.');
...@@ -194,6 +203,54 @@ class UpdatePackagesCommand extends FlutterCommand { ...@@ -194,6 +203,54 @@ class UpdatePackagesCommand extends FlutterCommand {
); );
} }
if (forceUpgrade && cherryPickPackage != null) {
throwToolExit(
'--force-upgrade cannot be used with the --cherry-pick-package flag'
);
}
if (forceUpgrade && isPrintPaths) {
throwToolExit(
'--force-upgrade cannot be used with the --paths flag'
);
}
if (forceUpgrade && isPrintTransitiveClosure) {
throwToolExit(
'--force-upgrade cannot be used with the --transitive-closure flag'
);
}
if (cherryPickPackage != null && offline) {
throwToolExit(
'--cherry-pick-package cannot be used with the --offline flag'
);
}
if (cherryPickPackage != null && cherryPickVersion == null) {
throwToolExit(
'--cherry-pick-version is required when using --cherry-pick-package flag'
);
}
if (isPrintPaths && (stringArg('from') == null || stringArg('to') == null)) {
throwToolExit(
'The --from and --to flags are required when using the --paths flag'
);
}
if (!isPrintPaths && (stringArg('from') != null || stringArg('to') != null)) {
throwToolExit(
'The --from and --to flags are only allowed when using the --paths flag'
);
}
if (isPrintTransitiveClosure && isPrintPaths) {
throwToolExit(
'The --transitive-closure flag cannot be used with the --paths flag'
);
}
// "consumer" packages are those that constitute our public API (e.g. flutter, flutter_test, flutter_driver, flutter_localizations, integration_test). // "consumer" packages are those that constitute our public API (e.g. flutter, flutter_test, flutter_driver, flutter_localizations, integration_test).
if (isConsumerOnly) { if (isConsumerOnly) {
if (!isPrintTransitiveClosure) { if (!isPrintTransitiveClosure) {
...@@ -216,7 +273,7 @@ class UpdatePackagesCommand extends FlutterCommand { ...@@ -216,7 +273,7 @@ class UpdatePackagesCommand extends FlutterCommand {
return FlutterCommandResult.success(); return FlutterCommandResult.success();
} }
if (doUpgrade) { if (forceUpgrade) {
// This feature attempts to collect all the packages used across all the // This feature attempts to collect all the packages used across all the
// pubspec.yamls in the repo (including via transitive dependencies), and // pubspec.yamls in the repo (including via transitive dependencies), and
// find the latest version of each that can be used while keeping each // find the latest version of each that can be used while keeping each
...@@ -235,9 +292,38 @@ class UpdatePackagesCommand extends FlutterCommand { ...@@ -235,9 +292,38 @@ class UpdatePackagesCommand extends FlutterCommand {
explicitDependencies: explicitDependencies, explicitDependencies: explicitDependencies,
allDependencies: allDependencies, allDependencies: allDependencies,
specialDependencies: specialDependencies, specialDependencies: specialDependencies,
doUpgrade: doUpgrade, printPaths: forceUpgrade || isPrintPaths || isPrintTransitiveClosure || cherryPickPackage != null,
); );
final Iterable<PubspecDependency> baseDependencies;
if (cherryPickPackage != null) {
if (!allDependencies.containsKey(cherryPickPackage)) {
throwToolExit(
'Package "$cherryPickPackage" is not currently a dependency, and therefore cannot be upgraded.'
);
}
if (cherryPickVersion != null) {
globals.printStatus('Pinning package "$cherryPickPackage" to version "$cherryPickVersion"...');
} else {
globals.printStatus('Upgrading package "$cherryPickPackage"...');
}
final List<PubspecDependency> adjustedDependencies = <PubspecDependency>[];
for (final String package in allDependencies.keys) {
if (package == cherryPickPackage) {
assert(cherryPickVersion != null);
final PubspecDependency pubspec = allDependencies[cherryPickPackage]!;
adjustedDependencies.add(pubspec.copyWith(version: cherryPickVersion));
} else {
adjustedDependencies.add(allDependencies[package]!);
}
}
baseDependencies = adjustedDependencies;
} else if (forceUpgrade) {
baseDependencies = explicitDependencies.values;
} else {
baseDependencies = allDependencies.values;
}
// Now that we have all the dependencies we care about, we are going to // Now that we have all the dependencies we care about, we are going to
// create a fake package and then run either "pub upgrade", if requested, // create a fake package and then run either "pub upgrade", if requested,
// followed by "pub get" on it. If upgrading, the pub tool will attempt to // followed by "pub get" on it. If upgrading, the pub tool will attempt to
...@@ -246,12 +332,14 @@ class UpdatePackagesCommand extends FlutterCommand { ...@@ -246,12 +332,14 @@ class UpdatePackagesCommand extends FlutterCommand {
// attempt to download any necessary package versions to the pub cache to // attempt to download any necessary package versions to the pub cache to
// warm the cache. // warm the cache.
final PubDependencyTree tree = PubDependencyTree(); // object to collect results final PubDependencyTree tree = PubDependencyTree(); // object to collect results
await _generateFakePackage( await _pubGetAllDependencies(
tempDir: _syntheticPackageDir, tempDir: _syntheticPackageDir,
dependencies: doUpgrade ? explicitDependencies.values : allDependencies.values, dependencies: baseDependencies,
pubspecs: pubspecs, pubspecs: pubspecs,
tree: tree, tree: tree,
doUpgrade: doUpgrade, doUpgrade: forceUpgrade,
isolateEnvironment: forceUpgrade || isPrintPaths || isPrintTransitiveClosure || cherryPickPackage != null,
reportDependenciesToTree: forceUpgrade || isPrintPaths || isPrintTransitiveClosure || cherryPickPackage != null,
); );
// Only delete the synthetic package if it was done in a temp directory // Only delete the synthetic package if it was done in a temp directory
...@@ -259,18 +347,31 @@ class UpdatePackagesCommand extends FlutterCommand { ...@@ -259,18 +347,31 @@ class UpdatePackagesCommand extends FlutterCommand {
_syntheticPackageDir.deleteSync(recursive: true); _syntheticPackageDir.deleteSync(recursive: true);
} }
if (doUpgrade) { if (forceUpgrade || isPrintTransitiveClosure || isPrintPaths || cherryPickPackage != null) {
final bool done = _upgradePubspecs( _processPubspecs(
tree: tree, tree: tree,
pubspecs: pubspecs, pubspecs: pubspecs,
explicitDependencies: explicitDependencies,
specialDependencies: specialDependencies, specialDependencies: specialDependencies,
); );
if (done) { if (isPrintTransitiveClosure) {
// Complete early if we were just printing data. tree._dependencyTree.forEach((String from, Set<String> to) {
globals.printStatus('$from -> $to');
});
return FlutterCommandResult.success();
}
if (isPrintPaths) {
showDependencyPaths(from: stringArg('from')!, to: stringArg('to')!, tree: tree);
return FlutterCommandResult.success(); return FlutterCommandResult.success();
} }
globals.printStatus('Updating workspace...');
_updatePubspecs(
tree: tree,
pubspecs: pubspecs,
specialDependencies: specialDependencies,
);
} }
await _runPubGetOnPackages(packages); await _runPubGetOnPackages(packages);
...@@ -306,8 +407,9 @@ class UpdatePackagesCommand extends FlutterCommand { ...@@ -306,8 +407,9 @@ class UpdatePackagesCommand extends FlutterCommand {
// we need to run update-packages to recapture the transitive deps. // we need to run update-packages to recapture the transitive deps.
globals.printWarning( globals.printWarning(
'Warning: pubspec in ${directory.path} has updated or new dependencies. ' 'Warning: pubspec in ${directory.path} has updated or new dependencies. '
'Please run "flutter update-packages --force-upgrade" to update them correctly ' 'Please run "flutter update-packages --force-upgrade" to update them correctly.'
'(checksum ${pubspec.checksum.value} != $checksum).' // DO NOT PRINT THE CHECKSUM HERE.
// It causes people to ignore the requirement to actually run the script.
); );
needsUpdate = true; needsUpdate = true;
} else { } else {
...@@ -331,11 +433,11 @@ class UpdatePackagesCommand extends FlutterCommand { ...@@ -331,11 +433,11 @@ class UpdatePackagesCommand extends FlutterCommand {
required Set<String> specialDependencies, required Set<String> specialDependencies,
required Map<String, PubspecDependency> explicitDependencies, required Map<String, PubspecDependency> explicitDependencies,
required Map<String, PubspecDependency> allDependencies, required Map<String, PubspecDependency> allDependencies,
required bool doUpgrade, required bool printPaths,
}) { }) {
// Visit all the directories with pubspec.yamls we care about. // Visit all the directories with pubspec.yamls we care about.
for (final Directory directory in packages) { for (final Directory directory in packages) {
if (doUpgrade) { if (printPaths) {
globals.printTrace('Reading pubspec.yaml from: ${directory.path}'); globals.printTrace('Reading pubspec.yaml from: ${directory.path}');
} }
final PubspecYaml pubspec = PubspecYaml(directory); // this parses the pubspec.yaml final PubspecYaml pubspec = PubspecYaml(directory); // this parses the pubspec.yaml
...@@ -404,12 +506,14 @@ class UpdatePackagesCommand extends FlutterCommand { ...@@ -404,12 +506,14 @@ class UpdatePackagesCommand extends FlutterCommand {
} }
} }
Future<void> _generateFakePackage({ Future<void> _pubGetAllDependencies({
required Directory tempDir, required Directory tempDir,
required Iterable<PubspecDependency> dependencies, required Iterable<PubspecDependency> dependencies,
required List<PubspecYaml> pubspecs, required List<PubspecYaml> pubspecs,
required PubDependencyTree tree, required PubDependencyTree tree,
required bool doUpgrade, required bool doUpgrade,
required bool isolateEnvironment,
required bool reportDependenciesToTree,
}) async { }) async {
Directory? temporaryFlutterSdk; Directory? temporaryFlutterSdk;
final Directory syntheticPackageDir = tempDir.childDirectory('synthetic_package'); final Directory syntheticPackageDir = tempDir.childDirectory('synthetic_package');
...@@ -421,9 +525,10 @@ class UpdatePackagesCommand extends FlutterCommand { ...@@ -421,9 +525,10 @@ class UpdatePackagesCommand extends FlutterCommand {
doUpgrade: doUpgrade, doUpgrade: doUpgrade,
), ),
); );
// Create a synthetic flutter SDK so that transitive flutter SDK
// constraints are not affected by this upgrade. if (isolateEnvironment) {
if (doUpgrade) { // Create a synthetic flutter SDK so that transitive flutter SDK
// constraints are not affected by this upgrade.
temporaryFlutterSdk = createTemporaryFlutterSdk( temporaryFlutterSdk = createTemporaryFlutterSdk(
globals.logger, globals.logger,
globals.fs, globals.fs,
...@@ -433,8 +538,10 @@ class UpdatePackagesCommand extends FlutterCommand { ...@@ -433,8 +538,10 @@ class UpdatePackagesCommand extends FlutterCommand {
); );
} }
// Next we run "pub get" on it in order to force the download of any // Run "pub get" on it in order to force the download of any
// needed packages to the pub cache, upgrading if requested. // needed packages to the pub cache, upgrading if requested.
// TODO(ianh): If this fails, the tool exits silently.
// It can fail, e.g., if --cherry-pick-version is invalid.
await pub.get( await pub.get(
context: PubContext.updatePackages, context: PubContext.updatePackages,
project: FlutterProject.fromDirectory(syntheticPackageDir), project: FlutterProject.fromDirectory(syntheticPackageDir),
...@@ -444,9 +551,9 @@ class UpdatePackagesCommand extends FlutterCommand { ...@@ -444,9 +551,9 @@ class UpdatePackagesCommand extends FlutterCommand {
outputMode: PubOutputMode.none, outputMode: PubOutputMode.none,
); );
if (doUpgrade) { if (reportDependenciesToTree) {
// If upgrading, we run "pub deps --style=compact" on the result. We // Run "pub deps --style=compact" on the result.
// pipe all the output to tree.fill(), which parses it so that it can // We pipe all the output to tree.fill(), which parses it so that it can
// create a graph of all the dependencies so that we can figure out the // create a graph of all the dependencies so that we can figure out the
// transitive dependencies later. It also remembers which version was // transitive dependencies later. It also remembers which version was
// selected for each package. // selected for each package.
...@@ -459,40 +566,29 @@ class UpdatePackagesCommand extends FlutterCommand { ...@@ -459,40 +566,29 @@ class UpdatePackagesCommand extends FlutterCommand {
} }
} }
bool _upgradePubspecs({ void _processPubspecs({
required PubDependencyTree tree, required PubDependencyTree tree,
required List<PubspecYaml> pubspecs, required List<PubspecYaml> pubspecs,
required Set<String> specialDependencies, required Set<String> specialDependencies,
required Map<String, PubspecDependency> explicitDependencies,
}) { }) {
// The transitive dependency tree for the fake package does not contain
// dependencies between Flutter SDK packages and pub packages. We add them
// here.
for (final PubspecYaml pubspec in pubspecs) { for (final PubspecYaml pubspec in pubspecs) {
final String package = pubspec.name; final String package = pubspec.name;
specialDependencies.add(package); specialDependencies.add(package);
tree._versions[package] = pubspec.version; tree._versions[package] = pubspec.version;
assert(!tree._dependencyTree.containsKey(package));
tree._dependencyTree[package] = <String>{};
for (final PubspecDependency dependency in pubspec.dependencies) { for (final PubspecDependency dependency in pubspec.dependencies) {
if (dependency.kind == DependencyKind.normal) { if (dependency.kind == DependencyKind.normal) {
tree._dependencyTree[package] ??= <String>{};
tree._dependencyTree[package]!.add(dependency.name); tree._dependencyTree[package]!.add(dependency.name);
} }
} }
} }
}
if (boolArg('transitive-closure')) { bool _updatePubspecs({
tree._dependencyTree.forEach((String from, Set<String> to) { required PubDependencyTree tree,
globals.printStatus('$from -> $to'); required List<PubspecYaml> pubspecs,
}); required Set<String> specialDependencies,
return true; }) {
}
if (boolArg('paths')) {
showDependencyPaths(from: stringArg('from')!, to: stringArg('to')!, tree: tree);
return true;
}
// Now that we have collected all the data, we can apply our dependency // Now that we have collected all the data, we can apply our dependency
// versions to each pubspec.yaml that we collected. This mutates the // versions to each pubspec.yaml that we collected. This mutates the
// pubspec.yaml files. // pubspec.yaml files.
...@@ -662,7 +758,6 @@ enum DependencyKind { ...@@ -662,7 +758,6 @@ enum DependencyKind {
/// pubspec.yaml file. /// pubspec.yaml file.
const String kTransitiveMagicString= '# THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"'; const String kTransitiveMagicString= '# THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"';
/// This is the string output before a checksum of the packages used. /// This is the string output before a checksum of the packages used.
const String kDependencyChecksum = '# PUBSPEC CHECKSUM: '; const String kDependencyChecksum = '# PUBSPEC CHECKSUM: ';
...@@ -1288,6 +1383,28 @@ class PubspecDependency extends PubspecLine { ...@@ -1288,6 +1383,28 @@ class PubspecDependency extends PubspecLine {
static const String _sdkPrefix = ' sdk: '; static const String _sdkPrefix = ' sdk: ';
static const String _gitPrefix = ' git:'; static const String _gitPrefix = ' git:';
PubspecDependency copyWith({
String? line,
String? name,
String? suffix,
bool? isTransitive,
DependencyKind? kind,
String? version,
String? sourcePath,
bool? isDevDependency,
}) {
return PubspecDependency(
line ?? this.line,
name ?? this.name,
suffix ?? this.suffix,
isTransitive: isTransitive ?? this.isTransitive,
kind: kind ?? this.kind,
version: version ?? this.version,
sourcePath: sourcePath ?? this.sourcePath,
isDevDependency: isDevDependency ?? this.isDevDependency,
);
}
/// Whether the dependency points to a package in the Flutter SDK. /// Whether the dependency points to a package in the Flutter SDK.
/// ///
/// There are two ways one can point to a Flutter package: /// There are two ways one can point to a Flutter package:
...@@ -1353,16 +1470,16 @@ class PubspecDependency extends PubspecLine { ...@@ -1353,16 +1470,16 @@ class PubspecDependency extends PubspecLine {
/// This generates the entry for this dependency for the pubspec.yaml for the /// This generates the entry for this dependency for the pubspec.yaml for the
/// fake package that we'll use to get the version numbers figured out. /// fake package that we'll use to get the version numbers figured out.
/// ///
/// When called with [doUpgrade] as [true], the version constrains will be set /// When called with [allowUpgrade] as [true], the version constrains will be set
/// to >= whatever the previous version was. If [doUpgrade] is [false], then /// to >= whatever the previous version was. If [allowUpgrade] is [false], then
/// the previous version is used again as an exact pin. /// the previous version is used again as an exact pin.
void describeForFakePubspec(StringBuffer dependencies, StringBuffer overrides, { bool doUpgrade = true }) { void describeForFakePubspec(StringBuffer dependencies, StringBuffer overrides, { bool allowUpgrade = true }) {
final String versionToUse; final String versionToUse;
// This should only happen when manually adding new dependencies; otherwise // This should only happen when manually adding new dependencies; otherwise
// versions should always be pinned exactly // versions should always be pinned exactly
if (version.isEmpty || version == 'any') { if (version.isEmpty || version == 'any') {
versionToUse = 'any'; versionToUse = 'any';
} else if (doUpgrade) { } else if (allowUpgrade) {
// Must wrap in quotes for Yaml parsing // Must wrap in quotes for Yaml parsing
versionToUse = "'>= $version'"; versionToUse = "'>= $version'";
} else { } else {
...@@ -1445,7 +1562,7 @@ String generateFakePubspec( ...@@ -1445,7 +1562,7 @@ String generateFakePubspec(
// Don't add pinned dependency if it is not in the set of all transitive dependencies. // Don't add pinned dependency if it is not in the set of all transitive dependencies.
if (!allTransitive.contains(package)) { if (!allTransitive.contains(package)) {
if (verbose) { if (verbose) {
globals.printStatus('Skipping $package because it was not transitive'); globals.printStatus(' - $package: $version (skipped because it was not a transitive dependency)');
} }
return; return;
} }
...@@ -1457,7 +1574,7 @@ String generateFakePubspec( ...@@ -1457,7 +1574,7 @@ String generateFakePubspec(
} }
for (final PubspecDependency dependency in dependencies) { for (final PubspecDependency dependency in dependencies) {
if (!dependency.pointsToSdk) { if (!dependency.pointsToSdk) {
dependency.describeForFakePubspec(result, overrides, doUpgrade: doUpgrade); dependency.describeForFakePubspec(result, overrides, allowUpgrade: doUpgrade);
} }
} }
result.write(overrides.toString()); result.write(overrides.toString());
......
...@@ -197,6 +197,73 @@ void main() { ...@@ -197,6 +197,73 @@ void main() {
Logger: () => logger, Logger: () => logger,
}); });
testUsingContext('--cherry-pick-package', () async {
final UpdatePackagesCommand command = UpdatePackagesCommand();
await createTestCommandRunner(command).run(<String>[
'update-packages',
'--cherry-pick-package=vector_math',
'--cherry-pick-version=2.0.9',
]);
expect(pub.pubGetDirectories, equals(<String>[
'/.tmp_rand0/flutter_update_packages.rand0/synthetic_package',
'/flutter/examples',
'/flutter/packages/flutter',
]));
expect(pub.pubBatchDirectories, equals(<String>[
'/.tmp_rand0/flutter_update_packages.rand0/synthetic_package',
]));
expect(pub.pubspecYamls, hasLength(3));
final String output = pub.pubspecYamls.first;
expect(output, isNotNull);
expect(output, contains('collection: 1.14.11\n'));
expect(output, contains('meta: 1.1.8\n'));
expect(output, contains('typed_data: 1.1.6\n'));
expect(output, contains('vector_math: 2.0.9\n'));
expect(output, isNot(contains('vector_math: 2.0.8')));
expect(output, isNot(contains('vector_math: ">= 2.0.8"')));
expect(output, isNot(contains("vector_math: '>= 2.0.8'")));
}, overrides: <Type, Generator>{
Pub: () => pub,
FileSystem: () => fileSystem,
ProcessManager: () => processManager,
Cache: () => Cache.test(
processManager: processManager,
),
Logger: () => logger,
});
testUsingContext('--force-upgrade', () async {
final UpdatePackagesCommand command = UpdatePackagesCommand();
await createTestCommandRunner(command).run(<String>[
'update-packages',
'--force-upgrade',
]);
expect(pub.pubGetDirectories, equals(<String>[
'/.tmp_rand0/flutter_update_packages.rand0/synthetic_package',
'/flutter/examples',
'/flutter/packages/flutter',
]));
expect(pub.pubBatchDirectories, equals(<String>[
'/.tmp_rand0/flutter_update_packages.rand0/synthetic_package',
]));
expect(pub.pubspecYamls, hasLength(3));
final String output = pub.pubspecYamls.first;
expect(output, isNotNull);
expect(output, contains("collection: '>= 1.14.11'\n"));
expect(output, contains("meta: '>= 1.1.8'\n"));
expect(output, contains("typed_data: '>= 1.1.6'\n"));
expect(output, contains("vector_math: '>= 2.0.8'\n"));
expect(output, isNot(contains('vector_math: 2.0.8')));
}, overrides: <Type, Generator>{
Pub: () => pub,
FileSystem: () => fileSystem,
ProcessManager: () => processManager,
Cache: () => Cache.test(
processManager: processManager,
),
Logger: () => logger,
});
testUsingContext('force updates packages --synthetic-package-path', () async { testUsingContext('force updates packages --synthetic-package-path', () async {
final UpdatePackagesCommand command = UpdatePackagesCommand(); final UpdatePackagesCommand command = UpdatePackagesCommand();
const String dir = '/path/to/synthetic/package'; const String dir = '/path/to/synthetic/package';
...@@ -272,6 +339,7 @@ class FakePub extends Fake implements Pub { ...@@ -272,6 +339,7 @@ class FakePub extends Fake implements Pub {
final FileSystem fileSystem; final FileSystem fileSystem;
final List<String> pubGetDirectories = <String>[]; final List<String> pubGetDirectories = <String>[];
final List<String> pubBatchDirectories = <String>[]; final List<String> pubBatchDirectories = <String>[];
final List<String> pubspecYamls = <String>[];
@override @override
Future<void> get({ Future<void> get({
...@@ -287,6 +355,7 @@ class FakePub extends Fake implements Pub { ...@@ -287,6 +355,7 @@ class FakePub extends Fake implements Pub {
PubOutputMode outputMode = PubOutputMode.all, PubOutputMode outputMode = PubOutputMode.all,
}) async { }) async {
pubGetDirectories.add(project.directory.path); pubGetDirectories.add(project.directory.path);
pubspecYamls.add(project.directory.childFile('pubspec.yaml').readAsStringSync());
project.directory.childFile('pubspec.lock') project.directory.childFile('pubspec.lock')
..createSync(recursive: true) ..createSync(recursive: true)
..writeAsStringSync(''' ..writeAsStringSync('''
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment