@@ -7,6 +7,7 @@ package load
7
7
8
8
import (
9
9
"bytes"
10
+ "encoding/json"
10
11
"errors"
11
12
"fmt"
12
13
"go/build"
@@ -304,9 +305,9 @@ func (p *Package) copyBuild(pp *build.Package) {
304
305
type PackageError struct {
305
306
ImportStack []string // shortest path from package named on command line to this one
306
307
Pos string // position of error
307
- Err string // the error itself
308
- IsImportCycle bool `json:"-"` // the error is an import cycle
309
- Hard bool `json:"-"` // whether the error is soft or hard; soft errors are ignored in some places
308
+ Err error // the error itself
309
+ IsImportCycle bool // the error is an import cycle
310
+ Hard bool // whether the error is soft or hard; soft errors are ignored in some places
310
311
}
311
312
312
313
func (p * PackageError ) Error () string {
@@ -317,12 +318,77 @@ func (p *PackageError) Error() string {
317
318
if p .Pos != "" {
318
319
// Omit import stack. The full path to the file where the error
319
320
// is the most important thing.
320
- return p .Pos + ": " + p .Err
321
+ return p .Pos + ": " + p .Err . Error ()
321
322
}
322
- if len (p .ImportStack ) == 0 {
323
- return p .Err
323
+
324
+ // If the error is an ImportPathError, and the last path on the stack appears
325
+ // in the error message, omit that path from the stack to avoid repetition.
326
+ // If an ImportPathError wraps another ImportPathError that matches the
327
+ // last path on the stack, we don't omit the path. An error like
328
+ // "package A imports B: error loading C caused by B" would not be clearer
329
+ // if "imports B" were omitted.
330
+ stack := p .ImportStack
331
+ var ierr ImportPathError
332
+ if len (stack ) > 0 && errors .As (p .Err , & ierr ) && ierr .ImportPath () == stack [len (stack )- 1 ] {
333
+ stack = stack [:len (stack )- 1 ]
334
+ }
335
+ if len (stack ) == 0 {
336
+ return p .Err .Error ()
337
+ }
338
+ return "package " + strings .Join (stack , "\n \t imports " ) + ": " + p .Err .Error ()
339
+ }
340
+
341
+ // PackageError implements MarshalJSON so that Err is marshaled as a string
342
+ // and non-essential fields are omitted.
343
+ func (p * PackageError ) MarshalJSON () ([]byte , error ) {
344
+ perr := struct {
345
+ ImportStack []string
346
+ Pos string
347
+ Err string
348
+ }{p .ImportStack , p .Pos , p .Err .Error ()}
349
+ return json .Marshal (perr )
350
+ }
351
+
352
+ // ImportPathError is a type of error that prevents a package from being loaded
353
+ // for a given import path. When such a package is loaded, a *Package is
354
+ // returned with Err wrapping an ImportPathError: the error is attached to
355
+ // the imported package, not the importing package.
356
+ //
357
+ // The string returned by ImportPath must appear in the string returned by
358
+ // Error. Errors that wrap ImportPathError (such as PackageError) may omit
359
+ // the import path.
360
+ type ImportPathError interface {
361
+ error
362
+ ImportPath () string
363
+ }
364
+
365
+ type importError struct {
366
+ importPath string
367
+ err error // created with fmt.Errorf
368
+ }
369
+
370
+ var _ ImportPathError = (* importError )(nil )
371
+
372
+ func ImportErrorf (path , format string , args ... interface {}) ImportPathError {
373
+ err := & importError {importPath : path , err : fmt .Errorf (format , args ... )}
374
+ if errStr := err .Error (); ! strings .Contains (errStr , path ) {
375
+ panic (fmt .Sprintf ("path %q not in error %q" , path , errStr ))
324
376
}
325
- return "package " + strings .Join (p .ImportStack , "\n \t imports " ) + ": " + p .Err
377
+ return err
378
+ }
379
+
380
+ func (e * importError ) Error () string {
381
+ return e .err .Error ()
382
+ }
383
+
384
+ func (e * importError ) Unwrap () error {
385
+ // Don't return e.err directly, since we're only wrapping an error if %w
386
+ // was passed to ImportErrorf.
387
+ return errors .Unwrap (e .err )
388
+ }
389
+
390
+ func (e * importError ) ImportPath () string {
391
+ return e .importPath
326
392
}
327
393
328
394
// An ImportStack is a stack of import paths, possibly with the suffix " (test)" appended.
@@ -489,7 +555,7 @@ func loadImport(pre *preload, path, srcDir string, parent *Package, stk *ImportS
489
555
ImportPath : path ,
490
556
Error : & PackageError {
491
557
ImportStack : stk .Copy (),
492
- Err : err . Error () ,
558
+ Err : err ,
493
559
},
494
560
},
495
561
}
@@ -516,7 +582,7 @@ func loadImport(pre *preload, path, srcDir string, parent *Package, stk *ImportS
516
582
if ! cfg .ModulesEnabled && path != cleanImport (path ) {
517
583
p .Error = & PackageError {
518
584
ImportStack : stk .Copy (),
519
- Err : fmt .Sprintf ("non-canonical import path: %q should be %q" , path , pathpkg .Clean (path )),
585
+ Err : fmt .Errorf ("non-canonical import path: %q should be %q" , path , pathpkg .Clean (path )),
520
586
}
521
587
p .Incomplete = true
522
588
}
@@ -536,20 +602,22 @@ func loadImport(pre *preload, path, srcDir string, parent *Package, stk *ImportS
536
602
perr := * p
537
603
perr .Error = & PackageError {
538
604
ImportStack : stk .Copy (),
539
- Err : fmt . Sprintf ( "import %q is a program, not an importable package" , path ),
605
+ Err : ImportErrorf ( path , "import %q is a program, not an importable package" , path ),
540
606
}
541
607
return setErrorPos (& perr , importPos )
542
608
}
543
609
544
610
if p .Internal .Local && parent != nil && ! parent .Internal .Local {
545
611
perr := * p
546
- errMsg := fmt . Sprintf ( "local import %q in non-local package" , path )
612
+ var err error
547
613
if path == "." {
548
- errMsg = "cannot import current directory"
614
+ err = ImportErrorf (path , "%s: cannot import current directory" , path )
615
+ } else {
616
+ err = ImportErrorf (path , "local import %q in non-local package" , path )
549
617
}
550
618
perr .Error = & PackageError {
551
619
ImportStack : stk .Copy (),
552
- Err : errMsg ,
620
+ Err : err ,
553
621
}
554
622
return setErrorPos (& perr , importPos )
555
623
}
@@ -1125,7 +1193,7 @@ func reusePackage(p *Package, stk *ImportStack) *Package {
1125
1193
if p .Error == nil {
1126
1194
p .Error = & PackageError {
1127
1195
ImportStack : stk .Copy (),
1128
- Err : "import cycle not allowed" ,
1196
+ Err : errors . New ( "import cycle not allowed" ) ,
1129
1197
IsImportCycle : true ,
1130
1198
}
1131
1199
}
@@ -1228,7 +1296,7 @@ func disallowInternal(srcDir string, importer *Package, importerPath string, p *
1228
1296
perr := * p
1229
1297
perr .Error = & PackageError {
1230
1298
ImportStack : stk .Copy (),
1231
- Err : "use of internal package " + p .ImportPath + " not allowed" ,
1299
+ Err : ImportErrorf ( p . ImportPath , "use of internal package " + p .ImportPath + " not allowed" ) ,
1232
1300
}
1233
1301
perr .Incomplete = true
1234
1302
return & perr
@@ -1275,7 +1343,7 @@ func disallowVendor(srcDir string, importer *Package, importerPath, path string,
1275
1343
perr := * p
1276
1344
perr .Error = & PackageError {
1277
1345
ImportStack : stk .Copy (),
1278
- Err : " must be imported as " + path [i + len ("vendor/" ):],
1346
+ Err : ImportErrorf ( path , "%s must be imported as %s" , path , path [i + len ("vendor/" ):]) ,
1279
1347
}
1280
1348
perr .Incomplete = true
1281
1349
return & perr
@@ -1329,7 +1397,7 @@ func disallowVendorVisibility(srcDir string, p *Package, stk *ImportStack) *Pack
1329
1397
perr := * p
1330
1398
perr .Error = & PackageError {
1331
1399
ImportStack : stk .Copy (),
1332
- Err : "use of vendored package not allowed" ,
1400
+ Err : errors . New ( "use of vendored package not allowed" ) ,
1333
1401
}
1334
1402
perr .Incomplete = true
1335
1403
return & perr
@@ -1455,7 +1523,7 @@ func (p *Package) load(stk *ImportStack, bp *build.Package, err error) {
1455
1523
err = base .ExpandScanner (err )
1456
1524
p .Error = & PackageError {
1457
1525
ImportStack : stk .Copy (),
1458
- Err : err . Error () ,
1526
+ Err : err ,
1459
1527
}
1460
1528
return
1461
1529
}
@@ -1472,7 +1540,7 @@ func (p *Package) load(stk *ImportStack, bp *build.Package, err error) {
1472
1540
// Report an error when the old code.google.com/p/go.tools paths are used.
1473
1541
if InstallTargetDir (p ) == StalePath {
1474
1542
newPath := strings .Replace (p .ImportPath , "code.google.com/p/go." , "golang.org/x/" , 1 )
1475
- e := fmt . Sprintf ( "the %v command has moved; use %v instead." , p .ImportPath , newPath )
1543
+ e := ImportErrorf ( p . ImportPath , "the %v command has moved; use %v instead." , p .ImportPath , newPath )
1476
1544
p .Error = & PackageError {Err : e }
1477
1545
return
1478
1546
}
@@ -1585,7 +1653,7 @@ func (p *Package) load(stk *ImportStack, bp *build.Package, err error) {
1585
1653
if f1 != "" {
1586
1654
p .Error = & PackageError {
1587
1655
ImportStack : stk .Copy (),
1588
- Err : fmt .Sprintf ("case-insensitive file name collision: %q and %q" , f1 , f2 ),
1656
+ Err : fmt .Errorf ("case-insensitive file name collision: %q and %q" , f1 , f2 ),
1589
1657
}
1590
1658
return
1591
1659
}
@@ -1601,22 +1669,22 @@ func (p *Package) load(stk *ImportStack, bp *build.Package, err error) {
1601
1669
if ! SafeArg (file ) || strings .HasPrefix (file , "_cgo_" ) {
1602
1670
p .Error = & PackageError {
1603
1671
ImportStack : stk .Copy (),
1604
- Err : fmt .Sprintf ("invalid input file name %q" , file ),
1672
+ Err : fmt .Errorf ("invalid input file name %q" , file ),
1605
1673
}
1606
1674
return
1607
1675
}
1608
1676
}
1609
1677
if name := pathpkg .Base (p .ImportPath ); ! SafeArg (name ) {
1610
1678
p .Error = & PackageError {
1611
1679
ImportStack : stk .Copy (),
1612
- Err : fmt .Sprintf ("invalid input directory name %q" , name ),
1680
+ Err : fmt .Errorf ("invalid input directory name %q" , name ),
1613
1681
}
1614
1682
return
1615
1683
}
1616
1684
if ! SafeArg (p .ImportPath ) {
1617
1685
p .Error = & PackageError {
1618
1686
ImportStack : stk .Copy (),
1619
- Err : fmt . Sprintf ( "invalid import path %q" , p .ImportPath ),
1687
+ Err : ImportErrorf ( p . ImportPath , "invalid import path %q" , p .ImportPath ),
1620
1688
}
1621
1689
return
1622
1690
}
@@ -1662,31 +1730,31 @@ func (p *Package) load(stk *ImportStack, bp *build.Package, err error) {
1662
1730
// code; see issue #16050).
1663
1731
}
1664
1732
1665
- setError := func (msg string ) {
1733
+ setError := func (err error ) {
1666
1734
p .Error = & PackageError {
1667
1735
ImportStack : stk .Copy (),
1668
- Err : msg ,
1736
+ Err : err ,
1669
1737
}
1670
1738
}
1671
1739
1672
1740
// The gc toolchain only permits C source files with cgo or SWIG.
1673
1741
if len (p .CFiles ) > 0 && ! p .UsesCgo () && ! p .UsesSwig () && cfg .BuildContext .Compiler == "gc" {
1674
- setError (fmt .Sprintf ("C source files not allowed when not using cgo or SWIG: %s" , strings .Join (p .CFiles , " " )))
1742
+ setError (fmt .Errorf ("C source files not allowed when not using cgo or SWIG: %s" , strings .Join (p .CFiles , " " )))
1675
1743
return
1676
1744
}
1677
1745
1678
1746
// C++, Objective-C, and Fortran source files are permitted only with cgo or SWIG,
1679
1747
// regardless of toolchain.
1680
1748
if len (p .CXXFiles ) > 0 && ! p .UsesCgo () && ! p .UsesSwig () {
1681
- setError (fmt .Sprintf ("C++ source files not allowed when not using cgo or SWIG: %s" , strings .Join (p .CXXFiles , " " )))
1749
+ setError (fmt .Errorf ("C++ source files not allowed when not using cgo or SWIG: %s" , strings .Join (p .CXXFiles , " " )))
1682
1750
return
1683
1751
}
1684
1752
if len (p .MFiles ) > 0 && ! p .UsesCgo () && ! p .UsesSwig () {
1685
- setError (fmt .Sprintf ("Objective-C source files not allowed when not using cgo or SWIG: %s" , strings .Join (p .MFiles , " " )))
1753
+ setError (fmt .Errorf ("Objective-C source files not allowed when not using cgo or SWIG: %s" , strings .Join (p .MFiles , " " )))
1686
1754
return
1687
1755
}
1688
1756
if len (p .FFiles ) > 0 && ! p .UsesCgo () && ! p .UsesSwig () {
1689
- setError (fmt .Sprintf ("Fortran source files not allowed when not using cgo or SWIG: %s" , strings .Join (p .FFiles , " " )))
1757
+ setError (fmt .Errorf ("Fortran source files not allowed when not using cgo or SWIG: %s" , strings .Join (p .FFiles , " " )))
1690
1758
return
1691
1759
}
1692
1760
@@ -1695,7 +1763,7 @@ func (p *Package) load(stk *ImportStack, bp *build.Package, err error) {
1695
1763
if other := foldPath [fold ]; other == "" {
1696
1764
foldPath [fold ] = p .ImportPath
1697
1765
} else if other != p .ImportPath {
1698
- setError (fmt . Sprintf ( "case-insensitive import collision: %q and %q" , p .ImportPath , other ))
1766
+ setError (ImportErrorf ( p . ImportPath , "case-insensitive import collision: %q and %q" , p .ImportPath , other ))
1699
1767
return
1700
1768
}
1701
1769
@@ -2102,7 +2170,7 @@ func GoFilesPackage(gofiles []string) *Package {
2102
2170
pkg .Internal .CmdlineFiles = true
2103
2171
pkg .Name = f
2104
2172
pkg .Error = & PackageError {
2105
- Err : fmt .Sprintf ("named files must be .go files: %s" , pkg .Name ),
2173
+ Err : fmt .Errorf ("named files must be .go files: %s" , pkg .Name ),
2106
2174
}
2107
2175
return pkg
2108
2176
}
0 commit comments