1// Copyright 2015 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5import 'dart:async'; 6import 'dart:collection'; 7 8import 'package:meta/meta.dart'; 9 10import '../base/common.dart'; 11import '../base/file_system.dart'; 12import '../base/logger.dart'; 13import '../base/net.dart'; 14import '../base/platform.dart'; 15import '../cache.dart'; 16import '../dart/pub.dart'; 17import '../globals.dart'; 18import '../runner/flutter_command.dart'; 19 20/// Map from package name to package version, used to artificially pin a pub 21/// package version in cases when upgrading to the latest breaks Flutter. 22const Map<String, String> _kManuallyPinnedDependencies = <String, String>{ 23 // Add pinned packages here. 24 'flutter_gallery_assets': '0.1.9+2', // See //examples/flutter_gallery/pubspec.yaml 25 'mockito': '^4.1.0', // Prevent mockito from downgrading to 4.0.0 26 'test': '1.6.3', // | Tests are timing out at 1.6.4 27 'test_api': '0.2.5', // | 28 'test_core': '0.2.5', // | 29 'vm_service_client': '0.2.6+2', // Final version before being marked deprecated. 30}; 31 32class UpdatePackagesCommand extends FlutterCommand { 33 UpdatePackagesCommand({ this.hidden = false }) { 34 argParser 35 ..addFlag( 36 'force-upgrade', 37 help: 'Attempt to update all the dependencies to their latest versions.\n' 38 'This will actually modify the pubspec.yaml files in your checkout.', 39 defaultsTo: false, 40 negatable: false, 41 ) 42 ..addFlag( 43 'paths', 44 help: 'Finds paths in the dependency chain leading from package specified ' 45 'in --from to package specified in --to.', 46 defaultsTo: false, 47 negatable: false, 48 ) 49 ..addOption( 50 'from', 51 help: 'Used with flag --dependency-path. Specifies the package to begin ' 52 'searching dependency path from.', 53 ) 54 ..addOption( 55 'to', 56 help: 'Used with flag --dependency-path. Specifies the package that the ' 57 'sought after dependency path leads to.', 58 ) 59 ..addFlag( 60 'transitive-closure', 61 help: 'Prints the dependency graph that is the transitive closure of ' 62 'packages the Flutter SDK depends on.', 63 defaultsTo: false, 64 negatable: false, 65 ) 66 ..addFlag( 67 'consumer-only', 68 help: 'Only prints the dependency graph that is the transitive closure' 69 'that a consumer of the Flutter SDK will observe (When combined ' 70 'with transitive-closure)', 71 defaultsTo: false, 72 negatable: false, 73 ) 74 ..addFlag( 75 'verify-only', 76 help: 'verifies the package checksum without changing or updating deps', 77 defaultsTo: false, 78 negatable: false, 79 ); 80 } 81 82 @override 83 final String name = 'update-packages'; 84 85 @override 86 final String description = 'Update the packages inside the Flutter repo.'; 87 88 @override 89 final bool hidden; 90 91 @override 92 Future<Set<DevelopmentArtifact>> get requiredArtifacts async => <DevelopmentArtifact>{ 93 DevelopmentArtifact.universal, 94 }; 95 96 Future<void> _downloadCoverageData() async { 97 final Status status = logger.startProgress( 98 'Downloading lcov data for package:flutter...', 99 timeout: timeoutConfiguration.slowOperation, 100 ); 101 final String urlBase = platform.environment['FLUTTER_STORAGE_BASE_URL'] ?? 'https://storage.googleapis.com'; 102 final List<int> data = await fetchUrl(Uri.parse('$urlBase/flutter_infra/flutter/coverage/lcov.info')); 103 final String coverageDir = fs.path.join(Cache.flutterRoot, 'packages/flutter/coverage'); 104 fs.file(fs.path.join(coverageDir, 'lcov.base.info')) 105 ..createSync(recursive: true) 106 ..writeAsBytesSync(data, flush: true); 107 fs.file(fs.path.join(coverageDir, 'lcov.info')) 108 ..createSync(recursive: true) 109 ..writeAsBytesSync(data, flush: true); 110 status.stop(); 111 } 112 113 @override 114 Future<FlutterCommandResult> runCommand() async { 115 final List<Directory> packages = runner.getRepoPackages(); 116 117 final bool upgrade = argResults['force-upgrade']; 118 final bool isPrintPaths = argResults['paths']; 119 final bool isPrintTransitiveClosure = argResults['transitive-closure']; 120 final bool isVerifyOnly = argResults['verify-only']; 121 final bool isConsumerOnly = argResults['consumer-only']; 122 123 // "consumer" packages are those that constitute our public API (e.g. flutter, flutter_test, flutter_driver, flutter_localizations). 124 if (isConsumerOnly) { 125 if (!isPrintTransitiveClosure) { 126 throwToolExit( 127 '--consumer-only can only be used with the --transitive-closure flag' 128 ); 129 } 130 // Only retain flutter, flutter_test, flutter_driver, and flutter_localizations. 131 const List<String> consumerPackages = <String>['flutter', 'flutter_test', 'flutter_driver', 'flutter_localizations']; 132 // ensure we only get flutter/packages 133 packages.retainWhere((Directory directory) { 134 return consumerPackages.any((String package) { 135 return directory.path.endsWith('packages${fs.path.separator}$package'); 136 }); 137 }); 138 } 139 140 if (isVerifyOnly) { 141 bool needsUpdate = false; 142 printStatus('Verifying pubspecs...'); 143 for (Directory directory in packages) { 144 PubspecYaml pubspec; 145 try { 146 pubspec = PubspecYaml(directory); 147 } on String catch (message) { 148 throwToolExit(message); 149 } 150 printTrace('Reading pubspec.yaml from ${directory.path}'); 151 if (pubspec.checksum.value == null) { 152 // If the checksum is invalid or missing, we can just ask them run to run 153 // upgrade again to compute it. 154 printError( 155 'Warning: pubspec in ${directory.path} has out of date dependencies. ' 156 'Please run "flutter update-packages --force-upgrade" to update them correctly.' 157 ); 158 needsUpdate = true; 159 } 160 // all dependencies in the pubspec sorted lexically. 161 final Map<String, String> checksumDependencies = <String, String>{}; 162 for (PubspecLine data in pubspec.inputData) { 163 if (data is PubspecDependency && data.kind == DependencyKind.normal) 164 checksumDependencies[data.name] = data.version; 165 } 166 final String checksum = _computeChecksum(checksumDependencies.keys, (String name) => checksumDependencies[name]); 167 if (checksum != pubspec.checksum.value) { 168 // If the checksum doesn't match, they may have added or removed some dependencies. 169 // we need to run update-packages to recapture the transitive deps. 170 printError( 171 'Warning: pubspec in ${directory.path} has invalid dependencies. ' 172 'Please run "flutter update-packages --force-upgrade" to update them correctly.' 173 ); 174 needsUpdate = true; 175 } else { 176 // everything is correct in the pubspec. 177 printTrace('pubspec in ${directory.path} is up to date!'); 178 } 179 } 180 if (needsUpdate) { 181 throwToolExit( 182 'Warning: one or more pubspecs have invalid dependencies. ' 183 'Please run "flutter update-packages --force-upgrade" to update them correctly.', 184 exitCode: 1, 185 ); 186 } 187 printStatus('All pubspecs were up to date.'); 188 return null; 189 } 190 191 if (upgrade || isPrintPaths || isPrintTransitiveClosure) { 192 printStatus('Upgrading packages...'); 193 // This feature attempts to collect all the packages used across all the 194 // pubspec.yamls in the repo (including via transitive dependencies), and 195 // find the latest version of each that can be used while keeping each 196 // such package fixed at a single version across all the pubspec.yamls. 197 // 198 // First, collect up the explicit dependencies: 199 final List<PubspecYaml> pubspecs = <PubspecYaml>[]; 200 final Map<String, PubspecDependency> dependencies = <String, PubspecDependency>{}; 201 final Set<String> specialDependencies = <String>{}; 202 for (Directory directory in packages) { // these are all the directories with pubspec.yamls we care about 203 printTrace('Reading pubspec.yaml from: ${directory.path}'); 204 PubspecYaml pubspec; 205 try { 206 pubspec = PubspecYaml(directory); // this parses the pubspec.yaml 207 } on String catch (message) { 208 throwToolExit(message); 209 } 210 pubspecs.add(pubspec); // remember it for later 211 for (PubspecDependency dependency in pubspec.allDependencies) { // this is all the explicit dependencies 212 if (dependencies.containsKey(dependency.name)) { 213 // If we've seen the dependency before, make sure that we are 214 // importing it the same way. There's several ways to import a 215 // dependency. Hosted (from pub via version number), by path (e.g. 216 // pointing at the version of a package we get from the Dart SDK 217 // that we download with Flutter), by SDK (e.g. the "flutter" 218 // package is explicitly from "sdk: flutter"). 219 // 220 // This makes sure that we don't import a package in two different 221 // ways, e.g. by saying "sdk: flutter" in one pubspec.yaml and 222 // saying "path: ../../..." in another. 223 final PubspecDependency previous = dependencies[dependency.name]; 224 if (dependency.kind != previous.kind || dependency.lockTarget != previous.lockTarget) { 225 throwToolExit( 226 'Inconsistent requirements around ${dependency.name}; ' 227 'saw ${dependency.kind} (${dependency.lockTarget}) in "${dependency.sourcePath}" ' 228 'and ${previous.kind} (${previous.lockTarget}) in "${previous.sourcePath}".' 229 ); 230 } 231 } 232 // Remember this dependency by name so we can look it up again. 233 dependencies[dependency.name] = dependency; 234 // Normal dependencies are those we get from pub. The others we 235 // already implicitly pin since we pull down one version of the 236 // Flutter and Dart SDKs, so we track which those are here so that we 237 // can omit them from our list of pinned dependencies later. 238 if (dependency.kind != DependencyKind.normal) 239 specialDependencies.add(dependency.name); 240 } 241 } 242 243 // Now that we have all the dependencies we explicitly care about, we are 244 // going to create a fake package and then run "pub upgrade" on it. The 245 // pub tool will attempt to bring these dependencies up to the most recent 246 // possible versions while honoring all their constraints. 247 final PubDependencyTree tree = PubDependencyTree(); // object to collect results 248 final Directory tempDir = fs.systemTempDirectory.createTempSync('flutter_update_packages.'); 249 try { 250 final File fakePackage = _pubspecFor(tempDir); 251 fakePackage.createSync(); 252 fakePackage.writeAsStringSync(_generateFakePubspec(dependencies.values)); 253 // First we run "pub upgrade" on this generated package: 254 await pubGet( 255 context: PubContext.updatePackages, 256 directory: tempDir.path, 257 upgrade: true, 258 checkLastModified: false, 259 ); 260 // Then we run "pub deps --style=compact" on the result. We pipe all the 261 // output to tree.fill(), which parses it so that it can create a graph 262 // of all the dependencies so that we can figure out the transitive 263 // dependencies later. It also remembers which version was selected for 264 // each package. 265 await pub( 266 <String>['deps', '--style=compact'], 267 context: PubContext.updatePackages, 268 directory: tempDir.path, 269 filter: tree.fill, 270 retry: false, // errors here are usually fatal since we're not hitting the network 271 ); 272 } finally { 273 tempDir.deleteSync(recursive: true); 274 } 275 276 // The transitive dependency tree for the fake package does not contain 277 // dependencies between Flutter SDK packages and pub packages. We add them 278 // here. 279 for (PubspecYaml pubspec in pubspecs) { 280 final String package = pubspec.name; 281 final String version = pubspec.version; 282 for (PubspecDependency dependency in pubspec.dependencies) { 283 if (dependency.kind == DependencyKind.normal) { 284 tree._versions[package] = version; 285 tree._dependencyTree[package] ??= <String>{}; 286 tree._dependencyTree[package].add(dependency.name); 287 } 288 } 289 } 290 291 if (isPrintTransitiveClosure) { 292 tree._dependencyTree.forEach((String from, Set<String> to) { 293 printStatus('$from -> $to'); 294 }); 295 return null; 296 } 297 298 if (isPrintPaths) { 299 showDependencyPaths(from: argResults['from'], to: argResults['to'], tree: tree); 300 return null; 301 } 302 303 // Now that we have collected all the data, we can apply our dependency 304 // versions to each pubspec.yaml that we collected. This mutates the 305 // pubspec.yaml files. 306 // 307 // The specialDependencies argument is the set of package names to not pin 308 // to specific versions because they are explicitly pinned by their 309 // constraints. Here we list the names we earlier established we didn't 310 // need to pin because they come from the Dart or Flutter SDKs. 311 for (PubspecYaml pubspec in pubspecs) 312 pubspec.apply(tree, specialDependencies); 313 314 // Now that the pubspec.yamls are updated, we run "pub get" on each one so 315 // that the various packages are ready to use. This is what "flutter 316 // update-packages" does without --force-upgrade, so we can just fall into 317 // the regular code path. 318 } 319 320 final Stopwatch timer = Stopwatch()..start(); 321 int count = 0; 322 323 for (Directory dir in packages) { 324 await pubGet(context: PubContext.updatePackages, directory: dir.path, checkLastModified: false); 325 count += 1; 326 } 327 328 await _downloadCoverageData(); 329 330 final double seconds = timer.elapsedMilliseconds / 1000.0; 331 printStatus('\nRan \'pub\' $count time${count == 1 ? "" : "s"} and fetched coverage data in ${seconds.toStringAsFixed(1)}s.'); 332 333 return null; 334 } 335 336 void showDependencyPaths({ 337 @required String from, 338 @required String to, 339 @required PubDependencyTree tree, 340 }) { 341 if (!tree.contains(from)) 342 throwToolExit('Package $from not found in the dependency tree.'); 343 if (!tree.contains(to)) 344 throwToolExit('Package $to not found in the dependency tree.'); 345 346 final Queue<_DependencyLink> traversalQueue = Queue<_DependencyLink>(); 347 final Set<String> visited = <String>{}; 348 final List<_DependencyLink> paths = <_DependencyLink>[]; 349 350 traversalQueue.addFirst(_DependencyLink(from: null, to: from)); 351 while (traversalQueue.isNotEmpty) { 352 final _DependencyLink link = traversalQueue.removeLast(); 353 if (link.to == to) 354 paths.add(link); 355 if (link.from != null) 356 visited.add(link.from.to); 357 for (String dependency in tree._dependencyTree[link.to]) { 358 if (!visited.contains(dependency)) { 359 traversalQueue.addFirst(_DependencyLink(from: link, to: dependency)); 360 } 361 } 362 } 363 364 for (_DependencyLink path in paths) { 365 final StringBuffer buf = StringBuffer(); 366 while (path != null) { 367 buf.write('${path.to}'); 368 path = path.from; 369 if (path != null) 370 buf.write(' <- '); 371 } 372 printStatus(buf.toString(), wrap: false); 373 } 374 375 if (paths.isEmpty) { 376 printStatus('No paths found from $from to $to'); 377 } 378 } 379} 380 381class _DependencyLink { 382 _DependencyLink({ 383 @required this.from, 384 @required this.to, 385 }); 386 387 final _DependencyLink from; 388 final String to; 389 390 @override 391 String toString() => '${from?.to} -> $to'; 392} 393 394/// The various sections of a pubspec.yaml file. 395/// 396/// We care about the "dependencies", "dev_dependencies", and 397/// "dependency_overrides" sections, as well as the "name" and "version" fields 398/// in the pubspec header bucketed into [header]. The others are all bucketed 399/// into [other]. 400enum Section { header, dependencies, devDependencies, dependencyOverrides, builders, other } 401 402/// The various kinds of dependencies we know and care about. 403enum DependencyKind { 404 // Dependencies that will be path or sdk dependencies but 405 // for which we haven't yet parsed the data. 406 unknown, 407 408 // Regular dependencies with a specified version range. 409 normal, 410 411 // Dependency that uses an explicit path, e.g. into the Dart SDK. 412 path, 413 414 // Dependency defined as coming from an SDK (typically "sdk: flutter"). 415 sdk, 416 417 // A dependency that was "normal", but for which we later found a "path" or 418 // "sdk" dependency in the dependency_overrides section. 419 overridden, 420} 421 422/// This is the string we output next to each of our autogenerated transitive 423/// dependencies so that we can ignore them the next time we parse the 424/// pubspec.yaml file. 425const String kTransitiveMagicString= '# THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"'; 426 427 428/// This is the string output before a checksum of the packages used. 429const String kDependencyChecksum = '# PUBSPEC CHECKSUM: '; 430 431/// This class represents a pubspec.yaml file for the purposes of upgrading the 432/// dependencies as done by this file. 433class PubspecYaml { 434 /// You create one of these by providing a directory, from which we obtain the 435 /// pubspec.yaml and parse it into a line-by-line form. 436 factory PubspecYaml(Directory directory) { 437 final File file = _pubspecFor(directory); 438 return _parse(file, file.readAsLinesSync()); 439 } 440 441 PubspecYaml._(this.file, this.name, this.version, this.inputData, this.checksum); 442 443 final File file; // The actual pubspec.yaml file. 444 445 /// The package name. 446 final String name; 447 448 /// The package version. 449 final String version; 450 451 final List<PubspecLine> inputData; // Each line of the pubspec.yaml file, parsed(ish). 452 453 /// The package checksum. 454 /// 455 /// If this was not found in the pubspec, a synthetic checksum is created 456 /// with a value of `-1`. 457 final PubspecChecksum checksum; 458 459 /// This parses each line of a pubspec.yaml file (a list of lines) into 460 /// slightly more structured data (in the form of a list of PubspecLine 461 /// objects). We don't just use a YAML parser because we care about comments 462 /// and also because we can just define the style of pubspec.yaml files we care 463 /// about (since they're all under our control). 464 static PubspecYaml _parse(File file, List<String> lines) { 465 final String filename = file.path; 466 String packageName; 467 String packageVersion; 468 PubspecChecksum checksum; // the checksum value used to verify that dependencies haven't changed. 469 final List<PubspecLine> result = <PubspecLine>[]; // The output buffer. 470 Section section = Section.other; // Which section we're currently reading from. 471 bool seenMain = false; // Whether we've seen the "dependencies:" section. 472 bool seenDev = false; // Whether we've seen the "dev_dependencies:" section. 473 // The masterDependencies map is used to keep track of the objects 474 // representing actual dependencies we've seen so far in this file so that 475 // if we see dependency overrides we can update the actual dependency so it 476 // knows that it's not really a dependency. 477 final Map<String, PubspecDependency> masterDependencies = <String, PubspecDependency>{}; 478 // The "special" dependencies (the ones that use git: or path: or sdk: or 479 // whatnot) have the style of having extra data after the line that declares 480 // the dependency. So we track what is the "current" (or "last") dependency 481 // that we are dealing with using this variable. 482 PubspecDependency lastDependency; 483 for (String line in lines) { 484 if (lastDependency == null) { 485 // First we look to see if we're transitioning to a new top-level section. 486 // The PubspecHeader.parse static method can recognize those headers. 487 final PubspecHeader header = PubspecHeader.parse(line); // See if it's a header. 488 if (header != null) { // It is! 489 section = header.section; // The parser determined what kind of section it is. 490 if (section == Section.header) { 491 if (header.name == 'name') 492 packageName = header.value; 493 else if (header.name == 'version') 494 packageVersion = header.value; 495 } else if (section == Section.dependencies) { 496 // If we're entering the "dependencies" section, we want to make sure that 497 // it's the first section (of those we care about) that we've seen so far. 498 if (seenMain) 499 throw 'Two dependencies sections found in $filename. There should only be one.'; 500 if (seenDev) { 501 throw 'The dependencies section was after the dev_dependencies section in $filename. ' 502 'To enable one-pass processing, the dependencies section must come before the ' 503 'dev_dependencies section.'; 504 } 505 seenMain = true; 506 } else if (section == Section.devDependencies) { 507 // Similarly, if we're entering the dev_dependencies section, we should verify 508 // that we've not seen one already. 509 if (seenDev) 510 throw 'Two dev_dependencies sections found in $filename. There should only be one.'; 511 seenDev = true; 512 } 513 result.add(header); 514 } else if (section == Section.builders) { 515 // Do nothing. 516 // This line isn't a section header, and we're not in a section we care about. 517 // We just stick the line into the output unmodified. 518 result.add(PubspecLine(line)); 519 } else if (section == Section.other) { 520 if (line.contains(kDependencyChecksum)) { 521 // This is the pubspec checksum. After computing it, we remove it from the output data 522 // since it will be recomputed later. 523 checksum = PubspecChecksum.parse(line); 524 } else { 525 // This line isn't a section header, and we're not in a section we care about. 526 // We just stick the line into the output unmodified. 527 result.add(PubspecLine(line)); 528 } 529 } else { 530 // We're in a section we care about. Try to parse out the dependency: 531 final PubspecDependency dependency = PubspecDependency.parse(line, filename: filename); 532 if (dependency != null) { // We got one! 533 // Track whether or not this a dev dependency. 534 dependency.isDevDependency = seenDev; 535 result.add(dependency); 536 if (dependency.kind == DependencyKind.unknown) { 537 // If we didn't get a version number, then we need to be ready to 538 // read the next line as part of this dependency, so keep track of 539 // this dependency object. 540 lastDependency = dependency; 541 } 542 if (section != Section.dependencyOverrides) { 543 // If we're not in the overrides section, then just remember the 544 // dependency, in case it comes up again later in the overrides 545 // section. 546 // 547 // First, make sure it's a unique dependency. Listing dependencies 548 // twice doesn't make sense. 549 if (masterDependencies.containsKey(dependency.name)) 550 throw '$filename contains two dependencies on ${dependency.name}.'; 551 masterDependencies[dependency.name] = dependency; 552 } else { 553 // If we _are_ in the overrides section, then go tell the version 554 // we saw earlier (if any -- there might not be, we might be 555 // overriding a transitive dependency) that we have overridden it, 556 // so that later when we output the dependencies we can leave 557 // the line unmodified. 558 masterDependencies[dependency.name]?.markOverridden(dependency); 559 } 560 } else if (line.contains(kDependencyChecksum)) { 561 // This is the pubspec checksum. After computing it, we remove it from the output data 562 // since it will be recomputed later. 563 checksum = PubspecChecksum.parse(line); 564 } else { 565 // We're in a section we care about but got a line we didn't 566 // recognize. Maybe it's a comment or a blank line or something. 567 // Just pass it through. 568 result.add(PubspecLine(line)); 569 } 570 } 571 } else { 572 // If we're here it means the last line was a dependency that needed 573 // extra information to be parsed from the next line. 574 // 575 // Try to parse the line by giving it to the last PubspecDependency 576 // object we created. If parseLock fails to recognize the line, it will 577 // throw. If it does recognize the line but decides it's one we don't 578 // care about (specifically, "git:" dependencies), it'll return false. 579 // Otherwise it returns true. 580 // 581 // If it returns true, then it will have updated itself internally to 582 // store the information from this line. 583 if (!lastDependency.parseLock(line, filename, lockIsOverride: section == Section.dependencyOverrides)) { 584 // Ok we're dealing with some "git:" dependency. Let's pretend we 585 // never saw it. In practice this is only used for the flutter gallery 586 // assets dependency which we don't care about especially since it has 587 // no subdependencies and it's pinned by git hash. 588 // 589 // Remove the PubspecDependency entry we had for it and replace it 590 // with a PubspecLine entry, and add such an entry for this line. 591 result.removeLast(); 592 result.add(PubspecLine(lastDependency.line)); 593 result.add(PubspecLine(line)); 594 } 595 // We're done with this special dependency, so reset back to null so 596 // we'll go in the top section next time instead. 597 lastDependency = null; 598 } 599 } 600 return PubspecYaml._(file, packageName, packageVersion, result, checksum ?? PubspecChecksum(null, '')); 601 } 602 603 /// This returns all the explicit dependencies that this pubspec.yaml lists under dependencies. 604 Iterable<PubspecDependency> get dependencies sync* { 605 // It works by iterating over the parsed data from _parse above, collecting 606 // all the dependencies that were found, ignoring any that are flagged as as 607 // overridden by subsequent entries in the same file and any that have the 608 // magic comment flagging them as auto-generated transitive dependencies 609 // that we added in a previous run. 610 for (PubspecLine data in inputData) { 611 if (data is PubspecDependency && data.kind != DependencyKind.overridden && !data.isTransitive && !data.isDevDependency) 612 yield data; 613 } 614 } 615 616 /// This returns all regular dependencies and all dev dependencies. 617 Iterable<PubspecDependency> get allDependencies sync* { 618 for (PubspecLine data in inputData) { 619 if (data is PubspecDependency && data.kind != DependencyKind.overridden && !data.isTransitive) 620 yield data; 621 } 622 } 623 624 /// Take a dependency graph with explicit version numbers, and apply them to 625 /// the pubspec.yaml, ignoring any that we know are special dependencies (those 626 /// that depend on the Flutter or Dart SDK directly and are thus automatically 627 /// pinned). 628 void apply(PubDependencyTree versions, Set<String> specialDependencies) { 629 assert(versions != null); 630 final List<String> output = <String>[]; // the string data to output to the file, line by line 631 final Set<String> directDependencies = <String>{}; // packages this pubspec directly depends on (i.e. not transitive) 632 final Set<String> devDependencies = <String>{}; 633 Section section = Section.other; // the section we're currently handling 634 635 // the line number where we're going to insert the transitive dependencies. 636 int endOfDirectDependencies; 637 // The line number where we're going to insert the transitive dev dependencies. 638 int endOfDevDependencies; 639 // Walk the pre-parsed input file, outputting it unmodified except for 640 // updating version numbers, removing the old transitive dependencies lines, 641 // and adding our new transitive dependencies lines. We also do a little 642 // cleanup, removing trailing spaces, removing double-blank lines, leading 643 // blank lines, and trailing blank lines, and ensuring the file ends with a 644 // newline. This cleanup lets us be a little more aggressive while building 645 // the output. 646 for (PubspecLine data in inputData) { 647 if (data is PubspecHeader) { 648 // This line was a header of some sort. 649 // 650 // If we're leaving one of the sections in which we can list transitive 651 // dependencies, then remember this as the current last known valid 652 // place to insert our transitive dependencies. 653 if (section == Section.dependencies) 654 endOfDirectDependencies = output.length; 655 if (section == Section.devDependencies) 656 endOfDevDependencies = output.length; 657 section = data.section; // track which section we're now in. 658 output.add(data.line); // insert the header into the output 659 } else if (data is PubspecDependency) { 660 // This was a dependency of some sort. 661 // How we handle this depends on the section. 662 switch (section) { 663 case Section.devDependencies: 664 case Section.dependencies: 665 // For the dependencies and dev_dependencies sections, we reinsert 666 // the dependency if it wasn't one of our autogenerated transitive 667 // dependency lines. 668 if (!data.isTransitive) { 669 // Assert that we haven't seen it in this file already. 670 assert(!directDependencies.contains(data.name) && !devDependencies.contains(data.name)); 671 if (data.kind == DependencyKind.normal) { 672 // This is a regular dependency, so we need to update the 673 // version number. 674 // 675 // We output data that matches the format that 676 // PubspecDependency.parse can handle. The data.suffix is any 677 // previously-specified trailing comment. 678 assert(versions.contains(data.name)); 679 output.add(' ${data.name}: ${versions.versionFor(data.name)}${data.suffix}'); 680 } else { 681 // If it wasn't a regular dependency, then we output the line 682 // unmodified. If there was an additional line (e.g. an "sdk: 683 // flutter" line) then we output that too. 684 output.add(data.line); 685 if (data.lockLine != null) 686 output.add(data.lockLine); 687 } 688 // Remember that we've dealt with this dependency so we don't 689 // mention it again when doing the transitive dependencies. 690 if (section == Section.dependencies) { 691 directDependencies.add(data.name); 692 } else { 693 devDependencies.add(data.name); 694 } 695 } 696 // Since we're in one of the places where we can list dependencies, 697 // remember this as the current last known valid place to insert our 698 // transitive dev dependencies. If the section is for regular dependencies, 699 // then also rememeber the line for the end of direct dependencies. 700 if (section == Section.dependencies) { 701 endOfDirectDependencies = output.length; 702 } 703 endOfDevDependencies = output.length; 704 break; 705 default: 706 // In other sections, pass everything through in its original form. 707 output.add(data.line); 708 if (data.lockLine != null) 709 output.add(data.lockLine); 710 break; 711 } 712 } else { 713 // Not a header, not a dependency, just pass that through unmodified. 714 output.add(data.line); 715 } 716 } 717 718 // If there are no dependencies or dev_dependencies sections, these will be 719 // null. We have such files in our tests, so account for them here. 720 endOfDirectDependencies ??= output.length; 721 endOfDevDependencies ??= output.length; 722 723 // Now include all the transitive dependencies and transitive dev dependencies. 724 // The blocks of text to insert for each dependency section. 725 final List<String> transitiveDependencyOutput = <String>[]; 726 final List<String> transitiveDevDependencyOutput = <String>[]; 727 728 // Which dependencies we need to handle for the transitive and dev dependency sections. 729 final Set<String> transitiveDependencies = <String>{}; 730 final Set<String> transitiveDevDependencies = <String>{}; 731 732 // Merge the lists of dependencies we've seen in this file from dependencies, dev dependencies, 733 // and the dependencies we know this file mentions that are already pinned 734 // (and which didn't get special processing above). 735 final Set<String> implied = <String>{ 736 ...directDependencies, 737 ...specialDependencies, 738 ...devDependencies, 739 }; 740 741 // Create a new set to hold the list of packages we've already processed, so 742 // that we don't redundantly process them multiple times. 743 final Set<String> done = <String>{}; 744 for (String package in directDependencies) 745 transitiveDependencies.addAll(versions.getTransitiveDependenciesFor(package, seen: done, exclude: implied)); 746 for (String package in devDependencies) 747 transitiveDevDependencies.addAll(versions.getTransitiveDependenciesFor(package, seen: done, exclude: implied)); 748 749 // Sort each dependency block lexically so that we don't get noisy diffs when upgrading. 750 final List<String> transitiveDependenciesAsList = transitiveDependencies.toList()..sort(); 751 final List<String> transitiveDevDependenciesAsList = transitiveDevDependencies.toList()..sort(); 752 753 // Add a line for each transitive dependency and transitive dev dependency using our magic string to recognize them later. 754 for (String package in transitiveDependenciesAsList) 755 transitiveDependencyOutput.add(' $package: ${versions.versionFor(package)} $kTransitiveMagicString'); 756 for (String package in transitiveDevDependenciesAsList) 757 transitiveDevDependencyOutput.add(' $package: ${versions.versionFor(package)} $kTransitiveMagicString'); 758 759 // Build a sorted list of all dependencies for the checksum. 760 final Set<String> checksumDependencies = <String>{ 761 ...directDependencies, 762 ...devDependencies, 763 ...transitiveDependenciesAsList, 764 ...transitiveDevDependenciesAsList, 765 }..removeAll(specialDependencies); 766 767 // Add a blank line before and after each section to keep the resulting output clean. 768 transitiveDependencyOutput 769 ..insert(0, '') 770 ..add(''); 771 transitiveDevDependencyOutput 772 ..insert(0, '') 773 ..add(''); 774 775 // Compute a new checksum from all sorted dependencies and their version and convert to a hex string. 776 final String checksumString = _computeChecksum(checksumDependencies, versions.versionFor); 777 778 // Insert the block of transitive dependency declarations into the output after [endOfDirectDependencies], 779 // and the blocks of transitive dev dependency declarations into the output after [lastPossiblePlace]. Finally, 780 // insert the [checksumString] at the very end. 781 output 782 ..insertAll(endOfDevDependencies, transitiveDevDependencyOutput) 783 ..insertAll(endOfDirectDependencies, transitiveDependencyOutput) 784 ..add('') 785 ..add('$kDependencyChecksum$checksumString'); 786 787 // Remove trailing lines. 788 while (output.last.isEmpty) 789 output.removeLast(); 790 791 // Output the result to the pubspec.yaml file, skipping leading and 792 // duplicate blank lines and removing trailing spaces. 793 final StringBuffer contents = StringBuffer(); 794 bool hadBlankLine = true; 795 for (String line in output) { 796 line = line.trimRight(); 797 if (line == '') { 798 if (!hadBlankLine) 799 contents.writeln(''); 800 hadBlankLine = true; 801 } else { 802 contents.writeln(line); 803 hadBlankLine = false; 804 } 805 } 806 file.writeAsStringSync(contents.toString()); 807 } 808} 809 810/// This is the base class for the objects that represent lines in the 811/// pubspec.yaml files. 812class PubspecLine { 813 PubspecLine(this.line); 814 815 /// The raw line as we saw it in the original file. This is used so that we can 816 /// output the same line unmodified for the majority of lines. 817 final String line; 818} 819 820/// A checksum of the non autogenerated dependencies. 821class PubspecChecksum extends PubspecLine { 822 PubspecChecksum(this.value, String line) : super(line); 823 824 /// The checksum value, computed using [hashValues] over the direct, dev, 825 /// and special dependencies sorted lexically. 826 /// 827 /// If the line cannot be parsed, [value] will be null. 828 final String value; 829 830 /// Parses a [PubspecChecksum] from a line. 831 /// 832 /// The returned PubspecChecksum will have a null [value] if no checksum could 833 /// be found on this line. This is a value that [_computeChecksum] cannot return. 834 static PubspecChecksum parse(String line) { 835 final List<String> tokens = line.split(kDependencyChecksum); 836 if (tokens.length != 2) 837 return PubspecChecksum(null, line); 838 return PubspecChecksum(tokens.last.trim(), line); 839 } 840} 841 842/// A header, e.g. "dependencies:". 843class PubspecHeader extends PubspecLine { 844 PubspecHeader(String line, this.section, { this.name, this.value }) : super(line); 845 846 /// The section of the pubspec where the parse [line] appears. 847 final Section section; 848 849 /// The name in the pubspec line providing a name/value pair, such as "name" 850 /// and "version". 851 /// 852 /// Example: 853 /// 854 /// The value of this field extracted from the following line is "version". 855 /// 856 /// ``` 857 /// version: 0.16.5 858 /// ``` 859 final String name; 860 861 /// The value in the pubspec line providing a name/value pair, such as "name" 862 /// and "version". 863 /// 864 /// Example: 865 /// 866 /// The value of this field extracted from the following line is "0.16.5". 867 /// 868 /// ``` 869 /// version: 0.16.5 870 /// ``` 871 final String value; 872 873 static PubspecHeader parse(String line) { 874 // We recognize any line that: 875 // * doesn't start with a space (i.e. is aligned on the left edge) 876 // * ignoring trailing spaces and comments, ends with a colon 877 // * has contents before the colon 878 // We also try to recognize which of the kinds of Sections it is 879 // by comparing those contents against known strings. 880 if (line.startsWith(' ')) 881 return null; 882 final String strippedLine = _stripComments(line); 883 if (!strippedLine.contains(':') || strippedLine.length <= 1) 884 return null; 885 final List<String> parts = strippedLine.split(':'); 886 final String sectionName = parts.first; 887 final String value = parts.last.trim(); 888 switch (sectionName) { 889 case 'dependencies': 890 return PubspecHeader(line, Section.dependencies); 891 case 'dev_dependencies': 892 return PubspecHeader(line, Section.devDependencies); 893 case 'dependency_overrides': 894 return PubspecHeader(line, Section.dependencyOverrides); 895 case 'builders': 896 return PubspecHeader(line, Section.builders); 897 case 'name': 898 case 'version': 899 return PubspecHeader(line, Section.header, name: sectionName, value: value); 900 default: 901 return PubspecHeader(line, Section.other); 902 } 903 } 904 905 /// Returns the input after removing trailing spaces and anything after the 906 /// first "#". 907 static String _stripComments(String line) { 908 final int hashIndex = line.indexOf('#'); 909 if (hashIndex < 0) 910 return line.trimRight(); 911 return line.substring(0, hashIndex).trimRight(); 912 } 913} 914 915/// A dependency, as represented by a line (or two) from a pubspec.yaml file. 916class PubspecDependency extends PubspecLine { 917 PubspecDependency( 918 String line, 919 this.name, 920 this.suffix, { 921 @required this.isTransitive, 922 DependencyKind kind, 923 this.version, 924 this.sourcePath, 925 }) : _kind = kind, 926 super(line); 927 928 static PubspecDependency parse(String line, { @required String filename }) { 929 // We recognize any line that: 930 // * starts with exactly two spaces, no more or less 931 // * has some content, then a colon 932 // 933 // If we recognize the line, then we look to see if there's anything after 934 // the colon, ignoring comments. If there is, then this is a normal 935 // dependency, otherwise it's an unknown one. 936 // 937 // We also try and save the version string, if any. This is used to verify 938 // the checksum of package deps. 939 // 940 // We also look at the trailing comment, if any, to see if it is the magic 941 // string that identifies the line as a transitive dependency that we 942 // previously pinned, so we can ignore it. 943 // 944 // We remember the trailing comment, if any, so that we can reconstruct the 945 // line later. We forget the specified version range, if any. 946 if (line.length < 4 || line.startsWith(' ') || !line.startsWith(' ')) 947 return null; 948 final int colonIndex = line.indexOf(':'); 949 final int hashIndex = line.indexOf('#'); 950 if (colonIndex < 3) // two spaces at 0 and 1, a character at 2 951 return null; 952 if (hashIndex >= 0 && hashIndex < colonIndex) 953 return null; 954 final String package = line.substring(2, colonIndex).trimRight(); 955 assert(package.isNotEmpty); 956 assert(line.startsWith(' $package')); 957 String suffix = ''; 958 bool isTransitive = false; 959 String stripped; 960 String version = ''; 961 if (hashIndex >= 0) { 962 assert(hashIndex > colonIndex); 963 final String trailingComment = line.substring(hashIndex, line.length); 964 assert(line.endsWith(trailingComment)); 965 isTransitive = trailingComment == kTransitiveMagicString; 966 suffix = ' ' + trailingComment; 967 stripped = line.substring(colonIndex + 1, hashIndex).trimRight(); 968 } else { 969 stripped = line.substring(colonIndex + 1, line.length).trimRight(); 970 } 971 if (colonIndex != -1) { 972 version = line.substring(colonIndex + 1, hashIndex != -1 ? hashIndex : line.length).trim(); 973 } 974 return PubspecDependency(line, package, suffix, isTransitive: isTransitive, version: version, kind: stripped.isEmpty ? DependencyKind.unknown : DependencyKind.normal, sourcePath: filename); 975 } 976 977 final String name; // the package name 978 final String suffix; // any trailing comment we found 979 final String version; // the version string if found, or blank. 980 final bool isTransitive; // whether the suffix matched kTransitiveMagicString 981 final String sourcePath; // the filename of the pubspec.yaml file, for error messages 982 bool isDevDependency; // Whether this dependency is under the `dev dependencies` section. 983 984 DependencyKind get kind => _kind; 985 DependencyKind _kind = DependencyKind.normal; 986 987 /// If we're a path or sdk dependency, the path or sdk in question. 988 String get lockTarget => _lockTarget; 989 String _lockTarget; 990 991 /// If we were a two-line dependency, the second line (see the inherited [line] 992 /// for the first). 993 String get lockLine => _lockLine; 994 String _lockLine; 995 996 /// If we're a path or sdk dependency, whether we were found in a 997 /// dependencies/dev_dependencies section, or a dependency_overrides section. 998 /// We track this so that we can put ourselves in the right section when 999 /// generating the fake pubspec.yaml. 1000 bool get lockIsOverride => _lockIsOverride; 1001 bool _lockIsOverride; 1002 1003 static const String _pathPrefix = ' path: '; 1004 static const String _sdkPrefix = ' sdk: '; 1005 static const String _gitPrefix = ' git:'; 1006 1007 /// Whether the dependency points to a package in the Flutter SDK. 1008 /// 1009 /// There are two ways one can point to a Flutter package: 1010 /// 1011 /// - Using a "sdk: flutter" dependency. 1012 /// - Using a "path" dependency that points somewhere in the Flutter 1013 /// repository other than the "bin" directory. 1014 bool get pointsToSdk { 1015 if (_kind == DependencyKind.sdk) 1016 return true; 1017 1018 if (_kind == DependencyKind.path && 1019 !fs.path.isWithin(fs.path.join(Cache.flutterRoot, 'bin'), _lockTarget) && 1020 fs.path.isWithin(Cache.flutterRoot, _lockTarget)) 1021 return true; 1022 1023 return false; 1024 } 1025 1026 /// If parse decided we were a two-line dependency, this is called to parse the second line. 1027 /// We throw if we couldn't parse this line. 1028 /// We return true if we parsed it and stored the line in lockLine. 1029 /// We return false if we parsed it but want to forget the whole thing. 1030 bool parseLock(String line, String pubspecPath, { @required bool lockIsOverride }) { 1031 assert(lockIsOverride != null); 1032 assert(kind == DependencyKind.unknown); 1033 if (line.startsWith(_pathPrefix)) { 1034 // We're a path dependency; remember the (absolute) path. 1035 _lockTarget = fs.path.canonicalize( 1036 fs.path.absolute(fs.path.dirname(pubspecPath), line.substring(_pathPrefix.length, line.length)) 1037 ); 1038 _kind = DependencyKind.path; 1039 } else if (line.startsWith(_sdkPrefix)) { 1040 // We're an SDK dependency. 1041 _lockTarget = line.substring(_sdkPrefix.length, line.length); 1042 _kind = DependencyKind.sdk; 1043 } else if (line.startsWith(_gitPrefix)) { 1044 // We're a git: dependency. Return false so we'll be forgotten. 1045 return false; 1046 } else { 1047 throw 'Could not parse additional details for dependency $name; line was: "$line"'; 1048 } 1049 _lockIsOverride = lockIsOverride; 1050 _lockLine = line; 1051 return true; 1052 } 1053 1054 void markOverridden(PubspecDependency sibling) { 1055 // This is called when we find a dependency is mentioned a second time, 1056 // first in dependencies/dev_dependencies, and then in dependency_overrides. 1057 // It is called on the one found in dependencies/dev_dependencies, so that 1058 // we'll later know to report our version as "any" in the fake pubspec.yaml 1059 // and unmodified in the official pubspec.yamls. 1060 assert(sibling.name == name); 1061 assert(sibling.sourcePath == sourcePath); 1062 assert(sibling.kind != DependencyKind.normal); 1063 _kind = DependencyKind.overridden; 1064 } 1065 1066 /// This generates the entry for this dependency for the pubspec.yaml for the 1067 /// fake package that we'll use to get the version numbers figured out. 1068 void describeForFakePubspec(StringBuffer dependencies, StringBuffer overrides) { 1069 switch (kind) { 1070 case DependencyKind.unknown: 1071 case DependencyKind.overridden: 1072 assert(kind != DependencyKind.unknown); 1073 break; 1074 case DependencyKind.normal: 1075 if (!_kManuallyPinnedDependencies.containsKey(name)) 1076 dependencies.writeln(' $name: any'); 1077 break; 1078 case DependencyKind.path: 1079 if (_lockIsOverride) { 1080 dependencies.writeln(' $name: any'); 1081 overrides.writeln(' $name:'); 1082 overrides.writeln(' path: $lockTarget'); 1083 } else { 1084 dependencies.writeln(' $name:'); 1085 dependencies.writeln(' path: $lockTarget'); 1086 } 1087 break; 1088 case DependencyKind.sdk: 1089 if (_lockIsOverride) { 1090 dependencies.writeln(' $name: any'); 1091 overrides.writeln(' $name:'); 1092 overrides.writeln(' sdk: $lockTarget'); 1093 } else { 1094 dependencies.writeln(' $name:'); 1095 dependencies.writeln(' sdk: $lockTarget'); 1096 } 1097 break; 1098 } 1099 } 1100} 1101 1102/// Generates the File object for the pubspec.yaml file of a given Directory. 1103File _pubspecFor(Directory directory) { 1104 return fs.file(fs.path.join(directory.path, 'pubspec.yaml')); 1105} 1106 1107/// Generates the source of a fake pubspec.yaml file given a list of 1108/// dependencies. 1109String _generateFakePubspec(Iterable<PubspecDependency> dependencies) { 1110 final StringBuffer result = StringBuffer(); 1111 final StringBuffer overrides = StringBuffer(); 1112 result.writeln('name: flutter_update_packages'); 1113 result.writeln('dependencies:'); 1114 overrides.writeln('dependency_overrides:'); 1115 if (_kManuallyPinnedDependencies.isNotEmpty) { 1116 printStatus('WARNING: the following packages use hard-coded version constraints:'); 1117 final Set<String> allTransitive = <String>{ 1118 for (PubspecDependency dependency in dependencies) 1119 dependency.name 1120 }; 1121 for (String package in _kManuallyPinnedDependencies.keys) { 1122 // Don't add pinned dependency if it is not in the set of all transitive dependencies. 1123 if (!allTransitive.contains(package)) { 1124 printStatus('Skipping $package because it was not transitive'); 1125 continue; 1126 } 1127 final String version = _kManuallyPinnedDependencies[package]; 1128 result.writeln(' $package: $version'); 1129 printStatus(' - $package: $version'); 1130 } 1131 } 1132 for (PubspecDependency dependency in dependencies) 1133 if (!dependency.pointsToSdk) 1134 dependency.describeForFakePubspec(result, overrides); 1135 result.write(overrides.toString()); 1136 return result.toString(); 1137} 1138 1139/// This object tracks the output of a call to "pub deps --style=compact". 1140/// 1141/// It ends up holding the full graph of dependencies, and the version number for 1142/// each one. 1143class PubDependencyTree { 1144 final Map<String, String> _versions = <String, String>{}; 1145 final Map<String, Set<String>> _dependencyTree = <String, Set<String>>{}; 1146 1147 /// Handles the output from "pub deps --style=compact". 1148 /// 1149 /// That output is of this form: 1150 /// 1151 /// ``` 1152 /// package_name 0.0.0 1153 /// 1154 /// dependencies: 1155 /// - analyzer 0.31.0-alpha.0 [watcher args package_config collection] 1156 /// - archive 1.0.31 [crypto args path] 1157 /// - args 0.13.7 1158 /// - cli_util 0.1.2+1 [path] 1159 /// 1160 /// dev dependencies: 1161 /// - async 1.13.3 [collection] 1162 /// - barback 0.15.2+11 [stack_trace source_span pool async collection path] 1163 /// 1164 /// dependency overrides: 1165 /// - analyzer 0.31.0-alpha.0 [watcher args package_config collection] 1166 /// ``` 1167 /// 1168 /// We ignore all the lines that don't start with a hyphen. For each other 1169 /// line, we ignore any line that mentions a package we've already seen (this 1170 /// happens when the overrides section mentions something that was in the 1171 /// dependencies section). We ignore if something is a dependency or 1172 /// dev_dependency (pub won't use different versions for those two). 1173 /// 1174 /// We then parse out the package name, version number, and subdependencies for 1175 /// each entry, and store than in our _versions and _dependencyTree fields 1176 /// above. 1177 String fill(String message) { 1178 if (message.startsWith('- ')) { 1179 final int space2 = message.indexOf(' ', 2); 1180 int space3 = message.indexOf(' ', space2 + 1); 1181 if (space3 < 0) 1182 space3 = message.length; 1183 final String package = message.substring(2, space2); 1184 if (!contains(package)) { 1185 // Some packages get listed in the dependency overrides section too. 1186 // We just ignore those. The data is the same either way. 1187 final String version = message.substring(space2 + 1, space3); 1188 List<String> dependencies; 1189 if (space3 < message.length) { 1190 assert(message[space3 + 1] == '['); 1191 assert(message[message.length - 1] == ']'); 1192 final String allDependencies = message.substring(space3 + 2, message.length - 1); 1193 dependencies = allDependencies.split(' '); 1194 } else { 1195 dependencies = const <String>[]; 1196 } 1197 _versions[package] = version; 1198 _dependencyTree[package] = Set<String>.from(dependencies); 1199 } 1200 } 1201 return null; 1202 } 1203 1204 /// Whether we know about this package. 1205 bool contains(String package) { 1206 return _versions.containsKey(package); 1207 } 1208 1209 /// The transitive closure of all the dependencies for the given package, 1210 /// excluding any listen in `seen`. 1211 Iterable<String> getTransitiveDependenciesFor( 1212 String package, { 1213 @required Set<String> seen, 1214 @required Set<String> exclude, 1215 }) sync* { 1216 assert(seen != null); 1217 assert(exclude != null); 1218 if (!_dependencyTree.containsKey(package)) { 1219 // We have no transitive dependencies extracted for flutter_sdk packages 1220 // because they were omitted from pubspec.yaml used for 'pub upgrade' run. 1221 return; 1222 } 1223 for (String dependency in _dependencyTree[package]) { 1224 if (!seen.contains(dependency)) { 1225 if (!exclude.contains(dependency)) 1226 yield dependency; 1227 seen.add(dependency); 1228 yield* getTransitiveDependenciesFor(dependency, seen: seen, exclude: exclude); 1229 } 1230 } 1231 } 1232 1233 /// The version that a particular package ended up with. 1234 String versionFor(String package) { 1235 return _versions[package]; 1236 } 1237} 1238 1239// Produces a 16-bit checksum from the codePoints of the package name and 1240// version strings using Fletcher's algorithm. 1241String _computeChecksum(Iterable<String> names, String getVersion(String name)) { 1242 int lowerCheck = 0; 1243 int upperCheck = 0; 1244 final List<String> sortedNames = names.toList()..sort(); 1245 for (String name in sortedNames) { 1246 final String version = getVersion(name); 1247 assert(version != ''); 1248 if (version == null) 1249 continue; 1250 final String value = '$name: $version'; 1251 // Each code unit is 16 bits. 1252 for (int codeUnit in value.codeUnits) { 1253 final int upper = codeUnit >> 8; 1254 final int lower = codeUnit & 0xFF; 1255 lowerCheck = (lowerCheck + upper) % 255; 1256 upperCheck = (upperCheck + lowerCheck) % 255; 1257 lowerCheck = (lowerCheck + lower) % 255; 1258 upperCheck = (upperCheck + lowerCheck) % 255; 1259 } 1260 } 1261 return ((upperCheck << 8) | lowerCheck).toRadixString(16).padLeft(4, '0'); 1262} 1263