| // Copyright 2014 The Flutter Authors. 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:io' as io show Platform, exit, stderr, stdout; | |
| import 'package:args/args.dart'; | |
| import 'package:file/file.dart'; | |
| import 'package:file/local.dart'; | |
| import 'package:yaml/yaml.dart'; | |
| import 'package:yaml_edit/yaml_edit.dart'; | |
| /// The entry point for the version constraint bumping tool. | |
| void main(List<String> arguments) { | |
| const FileSystem fileSystem = LocalFileSystem(); | |
| final File script = fileSystem.file(io.Platform.script).absolute; | |
| final Directory flutterRoot = script.parent.parent.parent.parent; | |
| run( | |
| arguments, | |
| fileSystem: fileSystem, | |
| flutterRoot: flutterRoot, | |
| stdout: io.stdout, | |
| stderr: io.stderr, | |
| exit: io.exit, | |
| ); | |
| } | |
| /// Runs the version constraint bumping tool with the given [arguments]. | |
| /// | |
| /// This is separated from [main] to allow for hermetic testing. | |
| void run( | |
| List<String> arguments, { | |
| required FileSystem fileSystem, | |
| required Directory flutterRoot, | |
| required StringSink stdout, | |
| required StringSink stderr, | |
| required void Function(int) exit, | |
| }) { | |
| final parser = ArgParser() | |
| ..addFlag('help', abbr: 'h', negatable: false, help: 'Print this usage information.'); | |
| late final ArgResults options; | |
| try { | |
| options = parser.parse(arguments); | |
| } on FormatException catch (e) { | |
| stderr.writeln('ERROR: $e'); | |
| _usage(parser, stderr, exit); | |
| return; | |
| } | |
| if (options['help'] as bool) { | |
| _usage(parser, stdout, exit, exitCode: 0); | |
| return; | |
| } | |
| if (options.rest.length != 2) { | |
| stderr.writeln( | |
| 'ERROR: Expected exactly two arguments specifying the old SDK constraint and the new SDK constraint.', | |
| ); | |
| _usage(parser, stderr, exit); | |
| return; | |
| } | |
| final String oldVersion = options.rest[0]; | |
| final String newVersion = options.rest[1]; | |
| var updatedCount = 0; | |
| var errorCount = 0; | |
| final (List<File> pubspecs, bool hasTraversalError) = _findPubspecs(flutterRoot, stderr); | |
| if (hasTraversalError) { | |
| errorCount++; | |
| } | |
| for (final file in pubspecs) { | |
| final String relativePath = fileSystem.path.relative(file.path, from: flutterRoot.path); | |
| try { | |
| final String content = file.readAsStringSync(); | |
| final yamlEditor = YamlEditor(content); | |
| final YamlNode doc = loadYamlNode(content); | |
| if (doc is! YamlMap) { | |
| stderr.writeln('Error: $relativePath is not a valid YAML map.'); | |
| errorCount++; | |
| continue; | |
| } | |
| var fileUpdated = false; | |
| if (doc.containsKey('environment')) { | |
| final Object? env = doc['environment']; | |
| if (env is YamlMap && env.containsKey('sdk')) { | |
| final Object? currentSdk = env['sdk']; | |
| if (currentSdk == newVersion) { | |
| // Already updated. | |
| } else if (currentSdk == oldVersion) { | |
| yamlEditor.update(<String>['environment', 'sdk'], newVersion); | |
| fileUpdated = true; | |
| } else { | |
| stdout.writeln( | |
| 'Skipping $relativePath: SDK constraint "$currentSdk" does not match expected "$oldVersion".', | |
| ); | |
| } | |
| } | |
| } | |
| if (fileUpdated) { | |
| var newContent = yamlEditor.toString(); | |
| if (!newContent.endsWith('\n')) { | |
| newContent += '\n'; | |
| } | |
| file.writeAsStringSync(newContent); | |
| stdout.writeln('Updated $relativePath'); | |
| updatedCount++; | |
| } | |
| } catch (e) { | |
| stderr.writeln('Error updating $relativePath: $e'); | |
| errorCount++; | |
| } | |
| } | |
| stdout.writeln('Done. Updated $updatedCount pubspec.yaml file${updatedCount == 1 ? '' : 's'}.'); | |
| if (updatedCount == 0) { | |
| stderr.writeln('Error: No pubspec.yaml files were updated.'); | |
| errorCount++; | |
| } | |
| if (errorCount > 0) { | |
| exit(1); | |
| } | |
| } | |
| (List<File>, bool) _findPubspecs(Directory dir, StringSink stderr) { | |
| final FileSystem fileSystem = dir.fileSystem; | |
| final result = <File>[]; | |
| var hasError = false; | |
| void search(Directory currentDir) { | |
| try { | |
| for (final FileSystemEntity entity in currentDir.listSync(followLinks: false)) { | |
| if (entity is Directory) { | |
| final String name = fileSystem.path.basename(entity.path); | |
| if (name.startsWith('.') || | |
| name == 'build' || | |
| (name == 'cache' && fileSystem.path.basename(currentDir.path) == 'bin')) { | |
| continue; | |
| } | |
| search(entity); | |
| } else if (entity is File) { | |
| if (fileSystem.path.basename(entity.path) == 'pubspec.yaml') { | |
| result.add(entity); | |
| } | |
| } | |
| } | |
| } catch (e) { | |
| stderr.writeln('Error traversing ${currentDir.path}: $e'); | |
| hasError = true; | |
| } | |
| } | |
| search(dir); | |
| return (result, hasError); | |
| } | |
| void _usage(ArgParser parser, StringSink out, void Function(int) exit, {int exitCode = 1}) { | |
| out.writeln( | |
| 'Usage: dart dev/tools/bin/bump_version_constraints.dart <old_sdk_constraint> <new_sdk_constraint>', | |
| ); | |
| out.writeln('Example: dart dev/tools/bin/bump_version_constraints.dart ^3.10.0-0 ^3.11.0-0'); | |
| if (parser.options.isNotEmpty) { | |
| out.writeln(); | |
| out.writeln('Options:'); | |
| out.writeln(parser.usage); | |
| } | |
| exit(exitCode); | |
| } |