@@ -8,6 +8,7 @@ package toolchain
8
8
import (
9
9
"context"
10
10
"errors"
11
+ "flag"
11
12
"fmt"
12
13
"go/build"
13
14
"io/fs"
@@ -24,6 +25,7 @@ import (
24
25
"cmd/go/internal/modfetch"
25
26
"cmd/go/internal/modload"
26
27
"cmd/go/internal/run"
28
+ "cmd/go/internal/work"
27
29
28
30
"golang.org/x/mod/module"
29
31
)
@@ -486,74 +488,132 @@ func goInstallVersion() bool {
486
488
// Note: We assume there are no flags between 'go' and 'install' or 'run'.
487
489
// During testing there are some debugging flags that are accepted
488
490
// in that position, but in production go binaries there are not.
489
- if len (os .Args ) < 3 || ( os . Args [ 1 ] != "install" && os . Args [ 1 ] != "run" ) {
491
+ if len (os .Args ) < 3 {
490
492
return false
491
493
}
492
494
493
- // Check for pkg@version.
494
- var arg string
495
+ var cmdFlags * flag.FlagSet
495
496
switch os .Args [1 ] {
496
497
default :
498
+ // Command doesn't support a pkg@version as the main module.
497
499
return false
498
500
case "install" :
499
- // We would like to let 'go install -newflag pkg@version' work even
500
- // across a toolchain switch. To make that work, assume the pkg@version
501
- // is the last argument and skip the flag parsing.
502
- arg = os .Args [len (os .Args )- 1 ]
501
+ cmdFlags = & work .CmdInstall .Flag
503
502
case "run" :
504
- // For run, the pkg@version can be anywhere on the command line,
505
- // because it is preceded by run flags and followed by arguments to the
506
- // program being run. To handle that precisely, we have to interpret the
507
- // flags a little bit, to know whether each flag takes an optional argument.
508
- // We can still allow unknown flags as long as they have an explicit =value.
509
- args := os .Args [2 :]
510
- for i := 0 ; i < len (args ); i ++ {
511
- a := args [i ]
512
- if ! strings .HasPrefix (a , "-" ) {
513
- arg = a
514
- break
515
- }
516
- if a == "-" {
517
- // non-flag but also non-pkg@version
503
+ cmdFlags = & run .CmdRun .Flag
504
+ }
505
+
506
+ // The modcachrw flag is unique, in that it affects how we fetch the
507
+ // requested module to even figure out what toolchain it needs.
508
+ // We need to actually set it before we check the toolchain version.
509
+ // (See https://go.dev/issue/64282.)
510
+ modcacherwFlag := cmdFlags .Lookup ("modcacherw" )
511
+ if modcacherwFlag == nil {
512
+ base .Fatalf ("internal error: modcacherw flag not registered for command" )
513
+ }
514
+ modcacherwVal , ok := modcacherwFlag .Value .(interface {
515
+ IsBoolFlag () bool
516
+ flag.Value
517
+ })
518
+ if ! ok || ! modcacherwVal .IsBoolFlag () {
519
+ base .Fatalf ("internal error: modcacherw is not a boolean flag" )
520
+ }
521
+
522
+ // Make a best effort to parse the command's args to find the pkg@version
523
+ // argument and the -modcacherw flag.
524
+ var (
525
+ pkgArg string
526
+ modcacherwSeen bool
527
+ )
528
+ for args := os .Args [2 :]; len (args ) > 0 ; {
529
+ a := args [0 ]
530
+ args = args [1 :]
531
+ if a == "--" {
532
+ if len (args ) == 0 {
518
533
return false
519
534
}
520
- if a == "--" {
521
- if i + 1 >= len (args ) {
522
- return false
523
- }
524
- arg = args [i + 1 ]
525
- break
535
+ pkgArg = args [0 ]
536
+ break
537
+ }
538
+
539
+ a , ok := strings .CutPrefix (a , "-" )
540
+ if ! ok {
541
+ // Not a flag argument. Must be a package.
542
+ pkgArg = a
543
+ break
544
+ }
545
+ a = strings .TrimPrefix (a , "-" ) // Treat --flag as -flag.
546
+
547
+ name , val , hasEq := strings .Cut (a , "=" )
548
+
549
+ if name == "modcacherw" {
550
+ if ! hasEq {
551
+ val = "true"
526
552
}
527
- a = strings .TrimPrefix (a , "-" )
528
- a = strings .TrimPrefix (a , "-" )
529
- if strings .HasPrefix (a , "-" ) {
530
- // non-flag but also non-pkg@version
553
+ if err := modcacherwVal .Set (val ); err != nil {
531
554
return false
532
555
}
533
- if strings .Contains (a , "=" ) {
534
- // already has value
535
- continue
536
- }
537
- f := run .CmdRun .Flag .Lookup (a )
538
- if f == nil {
539
- // Unknown flag. Give up. The command is going to fail in flag parsing.
556
+ modcacherwSeen = true
557
+ continue
558
+ }
559
+
560
+ if hasEq {
561
+ // Already has a value; don't bother parsing it.
562
+ continue
563
+ }
564
+
565
+ f := run .CmdRun .Flag .Lookup (a )
566
+ if f == nil {
567
+ // We don't know whether this flag is a boolean.
568
+ if os .Args [1 ] == "run" {
569
+ // We don't know where to find the pkg@version argument.
570
+ // For run, the pkg@version can be anywhere on the command line,
571
+ // because it is preceded by run flags and followed by arguments to the
572
+ // program being run. Since we don't know whether this flag takes
573
+ // an argument, we can't reliably identify the end of the run flags.
574
+ // Just give up and let the user clarify using the "=" form..
540
575
return false
541
576
}
542
- if bf , ok := f .Value .(interface { IsBoolFlag () bool }); ok && bf .IsBoolFlag () {
543
- // Does not take value.
544
- continue
577
+
578
+ // We would like to let 'go install -newflag pkg@version' work even
579
+ // across a toolchain switch. To make that work, assume by default that
580
+ // the pkg@version is the last argument and skip the remaining args unless
581
+ // we spot a plausible "-modcacherw" flag.
582
+ for len (args ) > 0 {
583
+ a := args [0 ]
584
+ name , _ , _ := strings .Cut (a , "=" )
585
+ if name == "-modcacherw" || name == "--modcacherw" {
586
+ break
587
+ }
588
+ if len (args ) == 1 && ! strings .HasPrefix (a , "-" ) {
589
+ pkgArg = a
590
+ }
591
+ args = args [1 :]
545
592
}
546
- i ++ // Does take a value; skip it.
593
+ continue
594
+ }
595
+
596
+ if bf , ok := f .Value .(interface { IsBoolFlag () bool }); ! ok || ! bf .IsBoolFlag () {
597
+ // The next arg is the value for this flag. Skip it.
598
+ args = args [1 :]
599
+ continue
547
600
}
548
601
}
549
- if ! strings .Contains (arg , "@" ) || build .IsLocalImport (arg ) || filepath .IsAbs (arg ) {
602
+
603
+ if ! strings .Contains (pkgArg , "@" ) || build .IsLocalImport (pkgArg ) || filepath .IsAbs (pkgArg ) {
550
604
return false
551
605
}
552
- path , version , _ := strings .Cut (arg , "@" )
606
+ path , version , _ := strings .Cut (pkgArg , "@" )
553
607
if path == "" || version == "" || gover .IsToolchain (path ) {
554
608
return false
555
609
}
556
610
611
+ if ! modcacherwSeen && base .InGOFLAGS ("-modcacherw" ) {
612
+ fs := flag .NewFlagSet ("goInstallVersion" , flag .ExitOnError )
613
+ fs .Var (modcacherwVal , "modcacherw" , modcacherwFlag .Usage )
614
+ base .SetFromGOFLAGS (fs )
615
+ }
616
+
557
617
// It would be correct to simply return true here, bypassing use
558
618
// of the current go.mod or go.work, and let "go run" or "go install"
559
619
// do the rest, including a toolchain switch.
0 commit comments