| // 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); |
| } |