@@ -13,6 +13,7 @@ import 'package:pub_semver/pub_semver.dart';
13
13
14
14
import 'barback/asset_environment.dart' ;
15
15
import 'entrypoint.dart' ;
16
+ import 'exceptions.dart' ;
16
17
import 'executable.dart' as exe;
17
18
import 'io.dart' ;
18
19
import 'lock_file.dart' ;
@@ -27,10 +28,6 @@ import 'source/path.dart';
27
28
import 'system_cache.dart' ;
28
29
import 'utils.dart' ;
29
30
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
-
34
31
/// Maintains the set of packages that have been globally activated.
35
32
///
36
33
/// These have been hand-chosen by the user to make their executables in bin/
@@ -377,29 +374,35 @@ class GlobalPackages {
377
374
String _getLockFilePath (String name) =>
378
375
p.join (_directory, name, "pubspec.lock" );
379
376
380
- /// Shows to the user formatted list of globally activated packages.
377
+ /// Shows the user a formatted list of globally activated packages.
381
378
void listActivePackages () {
382
379
if (! dirExists (_directory)) return ;
383
380
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 ()
399
382
..sort ((id1, id2) => id1.name.compareTo (id2.name))
400
383
..forEach ((id) => log.message (_formatPackage (id)));
401
384
}
402
385
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
+
403
406
/// Returns formatted string representing the package [id] .
404
407
String _formatPackage (PackageId id) {
405
408
if (id.source == 'git' ) {
@@ -413,6 +416,92 @@ class GlobalPackages {
413
416
}
414
417
}
415
418
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
+
416
505
/// Updates the binstubs for [package] .
417
506
///
418
507
/// A binstub is a little shell script in `PUB_CACHE/bin` that runs an
@@ -430,10 +519,14 @@ class GlobalPackages {
430
519
/// Otherwise, the previous ones will be preserved.
431
520
///
432
521
/// 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.
435
527
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 }) {
437
530
if (snapshots == null ) snapshots = const {};
438
531
439
532
// Remove any previously activated binstubs for this package, in case the
@@ -513,7 +606,9 @@ class GlobalPackages {
513
606
}
514
607
}
515
608
516
- if (installed.isNotEmpty) _suggestIfNotOnPath (installed);
609
+ if (suggestIfNotOnPath && installed.isNotEmpty) {
610
+ _suggestIfNotOnPath (installed.first);
611
+ }
517
612
}
518
613
519
614
/// Creates a binstub named [executable] that runs [script] from [package] .
@@ -537,12 +632,11 @@ class GlobalPackages {
537
632
var previousPackage;
538
633
if (fileExists (binStubPath)) {
539
634
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 ) {
545
637
log.fine ("Could not parse binstub $binStubPath :\n $contents " );
638
+ } else if (! overwrite) {
639
+ return previousPackage;
546
640
}
547
641
}
548
642
@@ -568,6 +662,20 @@ rem Executable: ${executable}
568
662
rem Script: ${script }
569
663
$invocation %*
570
664
""" ;
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
+
571
679
writeTextFile (binStubPath, batch);
572
680
} else {
573
681
var bash = """
@@ -579,6 +687,21 @@ $invocation %*
579
687
# Script: ${script }
580
688
$invocation "\$ @"
581
689
""" ;
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
+
582
705
writeTextFile (binStubPath, bash);
583
706
584
707
// Make it executable.
@@ -606,13 +729,13 @@ $invocation "\$@"
606
729
607
730
for (var file in listDir (_binStubDir, includeDirs: false )) {
608
731
var contents = readTextFile (file);
609
- var match = _binStubPackagePattern. firstMatch (contents);
610
- if (match == null ) {
732
+ var binStubPackage = _binStubProperty (contents, "Package" );
733
+ if (binStubPackage == null ) {
611
734
log.fine ("Could not parse binstub $file :\n $contents " );
612
735
continue ;
613
736
}
614
737
615
- if (match[ 1 ] == package) {
738
+ if (binStubPackage == package) {
616
739
log.fine ("Deleting old binstub $file " );
617
740
deleteEntry (file);
618
741
}
@@ -621,11 +744,14 @@ $invocation "\$@"
621
744
622
745
/// Checks to see if the binstubs are on the user's PATH and, if not, suggests
623
746
/// 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) {
625
751
if (Platform .operatingSystem == "windows" ) {
626
752
// See if the shell can find one of the binstubs.
627
753
// "\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" ]);
629
755
if (result.exitCode == 0 ) return ;
630
756
631
757
log.warning (
@@ -636,7 +762,7 @@ $invocation "\$@"
636
762
'A web search for "configure windows path" will show you how.' );
637
763
} else {
638
764
// See if the shell can find one of the binstubs.
639
- var result = runProcessSync ("which" , [installed.first ]);
765
+ var result = runProcessSync ("which" , [installed]);
640
766
if (result.exitCode == 0 ) return ;
641
767
642
768
var binDir = _binStubDir;
@@ -655,4 +781,12 @@ $invocation "\$@"
655
781
"\n " );
656
782
}
657
783
}
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
+ }
658
792
}
0 commit comments