• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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