@@ -13,6 +13,7 @@ import 'package:pub_semver/pub_semver.dart';
1313
1414import 'barback/asset_environment.dart' ;
1515import 'entrypoint.dart' ;
16+ import 'exceptions.dart' ;
1617import 'executable.dart' as exe;
1718import 'io.dart' ;
1819import 'lock_file.dart' ;
@@ -27,10 +28,6 @@ import 'source/path.dart';
2728import 'system_cache.dart' ;
2829import 'utils.dart' ;
2930
30- /// Matches the package name that a binstub was created for inside the contents
31- /// of the shell script.
32- final _binStubPackagePattern = new RegExp (r"Package: ([a-zA-Z0-9_-]+)" );
33-
3431/// Maintains the set of packages that have been globally activated.
3532///
3633/// These have been hand-chosen by the user to make their executables in bin/
@@ -377,29 +374,35 @@ class GlobalPackages {
377374 String _getLockFilePath (String name) =>
378375 p.join (_directory, name, "pubspec.lock" );
379376
380- /// Shows to the user formatted list of globally activated packages.
377+ /// Shows the user a formatted list of globally activated packages.
381378 void listActivePackages () {
382379 if (! dirExists (_directory)) return ;
383380
384- // Loads lock [file] and returns [PackageId] of the activated package.
385- loadPackageId (file, name) {
386- var lockFile = new LockFile .load (p.join (_directory, file), cache.sources);
387- return lockFile.packages[name];
388- }
389-
390- var packages = listDir (_directory).map ((entry) {
391- if (fileExists (entry)) {
392- return loadPackageId (entry, p.basenameWithoutExtension (entry));
393- } else {
394- return loadPackageId (p.join (entry, 'pubspec.lock' ), p.basename (entry));
395- }
396- }).toList ();
397-
398- packages
381+ return listDir (_directory).map (_loadPackageId).toList ()
399382 ..sort ((id1, id2) => id1.name.compareTo (id2.name))
400383 ..forEach ((id) => log.message (_formatPackage (id)));
401384 }
402385
386+ /// Returns the [PackageId] for the globally-activated package at [path] .
387+ ///
388+ /// [path] should be a path within [_directory] . It can either be an old-style
389+ /// path to a single lockfile or a new-style path to a directory containing a
390+ /// lockfile.
391+ PackageId _loadPackageId (String path) {
392+ var name = p.basenameWithoutExtension (path);
393+ if (! fileExists (path)) path = p.join (path, 'pubspec.lock' );
394+
395+ var id = new LockFile .load (p.join (_directory, path), cache.sources)
396+ .packages[name];
397+
398+ if (id == null ) {
399+ throw new FormatException ("Pubspec for activated package $name didn't "
400+ "contain an entry for itself." );
401+ }
402+
403+ return id;
404+ }
405+
403406 /// Returns formatted string representing the package [id] .
404407 String _formatPackage (PackageId id) {
405408 if (id.source == 'git' ) {
@@ -413,6 +416,92 @@ class GlobalPackages {
413416 }
414417 }
415418
419+ /// Repairs any corrupted globally-activated packages and their binstubs.
420+ ///
421+ /// Returns a pair of two [int] s. The first indicates how many packages were
422+ /// successfully re-activated; the second indicates how many failed.
423+ Future <Pair <int , int >> repairActivatedPackages () async {
424+ var executables = {};
425+ if (dirExists (_binStubDir)) {
426+ for (var entry in listDir (_binStubDir)) {
427+ try {
428+ var binstub = readTextFile (entry);
429+ var package = _binStubProperty (binstub, "Package" );
430+ if (package == null ) {
431+ throw new ApplicationException ("No 'Package' property." );
432+ }
433+
434+ var executable = _binStubProperty (binstub, "Executable" );
435+ if (executable == null ) {
436+ throw new ApplicationException ("No 'Executable' property." );
437+ }
438+
439+ executables.putIfAbsent (package, () => []).add (executable);
440+ } catch (error, stackTrace) {
441+ log.error (
442+ "Error reading binstub for "
443+ "\" ${p .basenameWithoutExtension (entry )}\" " ,
444+ error, stackTrace);
445+
446+ tryDeleteEntry (entry);
447+ }
448+ }
449+ }
450+
451+ var successes = 0 ;
452+ var failures = 0 ;
453+ if (dirExists (_directory)) {
454+ for (var entry in listDir (_directory)) {
455+ var id;
456+ try {
457+ id = _loadPackageId (entry);
458+ log.message ("Reactivating ${log .bold (id .name )} ${id .version }..." );
459+
460+ var entrypoint = await find (id.name);
461+
462+ var graph = await entrypoint.loadPackageGraph ();
463+ var snapshots = await _precompileExecutables (entrypoint, id.name);
464+ var packageExecutables = executables.remove (id.name);
465+ if (packageExecutables == null ) packageExecutables = [];
466+ _updateBinStubs (graph.packages[id.name], packageExecutables,
467+ overwriteBinStubs: true , snapshots: snapshots,
468+ suggestIfNotOnPath: false );
469+ successes++ ;
470+ } catch (error, stackTrace) {
471+ var message = "Failed to reactivate "
472+ "${log .bold (p .basenameWithoutExtension (entry ))}" ;
473+ if (id != null ) {
474+ message += " ${id .version }" ;
475+ if (id.source != "hosted" ) message += " from ${id .source }" ;
476+ }
477+
478+ log.error (message, error, stackTrace);
479+ failures++ ;
480+
481+ tryDeleteEntry (entry);
482+ }
483+ }
484+ }
485+
486+ if (executables.isNotEmpty) {
487+ var packages = pluralize ("package" , executables.length);
488+ var message = new StringBuffer ("Binstubs exist for non-activated "
489+ "packages:\n " );
490+ executables.forEach ((package, executableNames) {
491+ // TODO(nweiz): Use a normal for loop here when
492+ // https://github.com/dart-lang/async_await/issues/68 is fixed.
493+ executableNames.forEach ((executable) =>
494+ deleteEntry (p.join (_binStubDir, executable)));
495+
496+ message.writeln (" From ${log .bold (package )}: "
497+ "${toSentence (executableNames )}" );
498+ });
499+ log.error (message);
500+ }
501+
502+ return new Pair (successes, failures);
503+ }
504+
416505 /// Updates the binstubs for [package] .
417506 ///
418507 /// A binstub is a little shell script in `PUB_CACHE/bin` that runs an
@@ -430,10 +519,14 @@ class GlobalPackages {
430519 /// Otherwise, the previous ones will be preserved.
431520 ///
432521 /// If [snapshots] is given, it is a map of the names of executables whose
433- /// snapshots that were precompiled to their paths. Binstubs for those will
434- /// run the snapshot directly and skip pub entirely.
522+ /// snapshots were precompiled to the paths of those snapshots. Binstubs for
523+ /// those will run the snapshot directly and skip pub entirely.
524+ ///
525+ /// If [suggestIfNotOnPath] is `true` (the default), this will warn the user if
526+ /// the bin directory isn't on their path.
435527 void _updateBinStubs (Package package, List <String > executables,
436- {bool overwriteBinStubs, Map <String , String > snapshots}) {
528+ {bool overwriteBinStubs, Map <String , String > snapshots,
529+ bool suggestIfNotOnPath: true }) {
437530 if (snapshots == null ) snapshots = const {};
438531
439532 // Remove any previously activated binstubs for this package, in case the
@@ -513,7 +606,9 @@ class GlobalPackages {
513606 }
514607 }
515608
516- if (installed.isNotEmpty) _suggestIfNotOnPath (installed);
609+ if (suggestIfNotOnPath && installed.isNotEmpty) {
610+ _suggestIfNotOnPath (installed.first);
611+ }
517612 }
518613
519614 /// Creates a binstub named [executable] that runs [script] from [package] .
@@ -537,12 +632,11 @@ class GlobalPackages {
537632 var previousPackage;
538633 if (fileExists (binStubPath)) {
539634 var contents = readTextFile (binStubPath);
540- var match = _binStubPackagePattern.firstMatch (contents);
541- if (match != null ) {
542- previousPackage = match[1 ];
543- if (! overwrite) return previousPackage;
544- } else {
635+ previousPackage = _binStubProperty (contents, "Package" );
636+ if (previousPackage == null ) {
545637 log.fine ("Could not parse binstub $binStubPath :\n $contents " );
638+ } else if (! overwrite) {
639+ return previousPackage;
546640 }
547641 }
548642
@@ -568,6 +662,20 @@ rem Executable: ${executable}
568662rem Script: ${script }
569663$invocation %*
570664""" ;
665+
666+ if (snapshot != null ) {
667+ batch += """
668+
669+ rem The VM exits with code 255 if the snapshot version is out-of-date.
670+ rem If it is, we need to delete it and run "pub global" manually.
671+ if not errorlevel 255 (
672+ exit /b %errorlevel%
673+ )
674+
675+ pub global run ${package .name }:$script %*
676+ """ ;
677+ }
678+
571679 writeTextFile (binStubPath, batch);
572680 } else {
573681 var bash = """
@@ -579,6 +687,21 @@ $invocation %*
579687# Script: ${script }
580688$invocation "\$ @"
581689""" ;
690+
691+ if (snapshot != null ) {
692+ bash += """
693+
694+ # The VM exits with code 255 if the snapshot version is out-of-date.
695+ # If it is, we need to delete it and run "pub global" manually.
696+ exit_code=\$ ?
697+ if [[ \$ exit_code != 255 ]]; then
698+ exit \$ exit_code
699+ fi
700+
701+ pub global run ${package .name }:$script "\$ @"
702+ """ ;
703+ }
704+
582705 writeTextFile (binStubPath, bash);
583706
584707 // Make it executable.
@@ -606,13 +729,13 @@ $invocation "\$@"
606729
607730 for (var file in listDir (_binStubDir, includeDirs: false )) {
608731 var contents = readTextFile (file);
609- var match = _binStubPackagePattern. firstMatch (contents);
610- if (match == null ) {
732+ var binStubPackage = _binStubProperty (contents, "Package" );
733+ if (binStubPackage == null ) {
611734 log.fine ("Could not parse binstub $file :\n $contents " );
612735 continue ;
613736 }
614737
615- if (match[ 1 ] == package) {
738+ if (binStubPackage == package) {
616739 log.fine ("Deleting old binstub $file " );
617740 deleteEntry (file);
618741 }
@@ -621,11 +744,14 @@ $invocation "\$@"
621744
622745 /// Checks to see if the binstubs are on the user's PATH and, if not, suggests
623746 /// that the user add the directory to their PATH.
624- void _suggestIfNotOnPath (List <String > installed) {
747+ ///
748+ /// [installed] should be the name of an installed executable that can be used
749+ /// to test whether accessing it on the path works.
750+ void _suggestIfNotOnPath (String installed) {
625751 if (Platform .operatingSystem == "windows" ) {
626752 // See if the shell can find one of the binstubs.
627753 // "\q" means return exit code 0 if found or 1 if not.
628- var result = runProcessSync ("where" , [r"\q" , installed.first + ".bat" ]);
754+ var result = runProcessSync ("where" , [r"\q" , installed + ".bat" ]);
629755 if (result.exitCode == 0 ) return ;
630756
631757 log.warning (
@@ -636,7 +762,7 @@ $invocation "\$@"
636762 'A web search for "configure windows path" will show you how.' );
637763 } else {
638764 // See if the shell can find one of the binstubs.
639- var result = runProcessSync ("which" , [installed.first ]);
765+ var result = runProcessSync ("which" , [installed]);
640766 if (result.exitCode == 0 ) return ;
641767
642768 var binDir = _binStubDir;
@@ -655,4 +781,12 @@ $invocation "\$@"
655781 "\n " );
656782 }
657783 }
784+
785+ /// Returns the value of the property named [name] in the bin stub script
786+ /// [source] .
787+ String _binStubProperty (String source, String name) {
788+ var pattern = new RegExp (quoteRegExp (name) + r": ([a-zA-Z0-9_-]+)" );
789+ var match = pattern.firstMatch (source);
790+ return match == null ? null : match[1 ];
791+ }
658792}
0 commit comments