1
0
mirror of https://github.com/golang/go synced 2024-09-24 17:20:12 -06:00

cmd/go: fix relative imports again

I tried before to make relative imports work by simply
invoking the compiler in the right directory, so that
an import of ./foo could be resolved by ./foo.a.
This required creating a separate tree of package binaries
that included the full path to the source directory, so that
/home/gopher/bar.go would be compiled in
tmpdir/work/local/home/gopher and perhaps find
a ./foo.a in that directory.

This model breaks on Windows because : appears in path
names but cannot be used in subdirectory names, and I
missed one or two places where it needed to be removed.

The model breaks more fundamentally when compiling
a test of a package that lives outside the Go path, because
we effectively use a ./ import in the generated testmain,
but there we want to be able to resolve the ./ import
of the test package to one directory and all the other ./
imports to a different directory.  Piggybacking on the compiler's
current working directory is then no longer possible.

Instead, introduce a new compiler option -D prefix that
makes the compiler turn a ./ import into prefix+that,
so that import "./foo" with -D a/b/c turns into import
"a/b/c/foo".  Then we can invent a package hierarchy
"_/" with subdirectories named for file system paths:
import "./foo" in the directory /home/gopher becomes
import "_/home/gopher/foo", and since that final path
is just an ordinary import now, all the ordinary processing
works, without special cases.

We will have to change the name of the hierarchy if we
ever decide to introduce a standard package with import
path "_", but that seems unlikely, and the detail is known
only in temporary packages that get thrown away at the
end of a build.

Fixes #3169.

R=golang-dev, r
CC=golang-dev
https://golang.org/cl/5732045
This commit is contained in:
Russ Cox 2012-03-02 22:16:02 -05:00
parent 120c223822
commit 604f375110
14 changed files with 171 additions and 92 deletions

View File

@ -38,6 +38,8 @@ Flags:
-p path -p path
assume that path is the eventual import path for this code, assume that path is the eventual import path for this code,
and diagnose any attempt to import a package that depends on it. and diagnose any attempt to import a package that depends on it.
-D path
treat a relative import as relative to path
-L -L
show entire file path when printing line numbers in errors show entire file path when printing line numbers in errors
-I dir1 -I dir2 -I dir1 -I dir2

View File

@ -772,6 +772,7 @@ extern char* runtimeimport;
extern char* unsafeimport; extern char* unsafeimport;
EXTERN char* myimportpath; EXTERN char* myimportpath;
EXTERN Idir* idirs; EXTERN Idir* idirs;
EXTERN char* localimport;
EXTERN Type* types[NTYPE]; EXTERN Type* types[NTYPE];
EXTERN Type* idealstring; EXTERN Type* idealstring;

View File

@ -148,6 +148,7 @@ usage(void)
// -y print declarations in cannedimports (used with -d) // -y print declarations in cannedimports (used with -d)
// -% print non-static initializers // -% print non-static initializers
// -+ indicate that the runtime is being compiled // -+ indicate that the runtime is being compiled
print(" -D PATH interpret local imports relative to this import path\n");
print(" -I DIR search for packages in DIR\n"); print(" -I DIR search for packages in DIR\n");
print(" -L show full path in file:line prints\n"); print(" -L show full path in file:line prints\n");
print(" -N disable optimizations\n"); print(" -N disable optimizations\n");
@ -238,14 +239,18 @@ main(int argc, char *argv[])
myimportpath = EARGF(usage()); myimportpath = EARGF(usage());
break; break;
case 'I':
addidir(EARGF(usage()));
break;
case 'u': case 'u':
safemode = 1; safemode = 1;
break; break;
case 'D':
localimport = EARGF(usage());
break;
case 'I':
addidir(EARGF(usage()));
break;
case 'V': case 'V':
p = expstring(); p = expstring();
if(strcmp(p, "X:none") == 0) if(strcmp(p, "X:none") == 0)
@ -516,8 +521,12 @@ islocalname(Strlit *name)
return 1; return 1;
if(name->len >= 2 && strncmp(name->s, "./", 2) == 0) if(name->len >= 2 && strncmp(name->s, "./", 2) == 0)
return 1; return 1;
if(name->len == 1 && strncmp(name->s, ".", 1) == 0)
return 1;
if(name->len >= 3 && strncmp(name->s, "../", 3) == 0) if(name->len >= 3 && strncmp(name->s, "../", 3) == 0)
return 1; return 1;
if(name->len == 2 && strncmp(name->s, "..", 2) == 0)
return 1;
return 0; return 0;
} }
@ -588,7 +597,7 @@ importfile(Val *f, int line)
int32 c; int32 c;
int len; int len;
Strlit *path; Strlit *path;
char *cleanbuf; char *cleanbuf, *prefix;
USED(line); USED(line);
@ -642,8 +651,11 @@ importfile(Val *f, int line)
fakeimport(); fakeimport();
return; return;
} }
cleanbuf = mal(strlen(pathname) + strlen(path->s) + 2); prefix = pathname;
strcpy(cleanbuf, pathname); if(localimport != nil)
prefix = localimport;
cleanbuf = mal(strlen(prefix) + strlen(path->s) + 2);
strcpy(cleanbuf, prefix);
strcat(cleanbuf, "/"); strcat(cleanbuf, "/");
strcat(cleanbuf, path->s); strcat(cleanbuf, path->s);
cleanname(cleanbuf); cleanname(cleanbuf);

View File

@ -349,8 +349,9 @@ func goFilesPackage(gofiles []string) *Package {
bp, err := ctxt.ImportDir(dir, 0) bp, err := ctxt.ImportDir(dir, 0)
pkg := new(Package) pkg := new(Package)
pkg.load(&stk, bp, err) pkg.load(&stk, bp, err)
pkg.localPrefix = dirToImportPath(dir)
pkg.ImportPath = "command-line-arguments"
pkg.ImportPath = "command-line arguments"
if *buildO == "" { if *buildO == "" {
if pkg.Name == "main" { if pkg.Name == "main" {
_, elem := filepath.Split(gofiles[0]) _, elem := filepath.Split(gofiles[0])
@ -425,14 +426,12 @@ func (b *builder) action(mode buildMode, depMode buildMode, p *Package) *action
return a return a
} }
prefix := "obj" if p.local {
if p.target == "" && p.Dir == p.ImportPath {
// Imported via local path. No permanent target. // Imported via local path. No permanent target.
mode = modeBuild mode = modeBuild
prefix = "local"
} }
a.objdir = filepath.Join(b.work, prefix, a.p.ImportPath, "_obj") + string(filepath.Separator) a.objdir = filepath.Join(b.work, a.p.ImportPath, "_obj") + string(filepath.Separator)
a.objpkg = buildToolchain.pkgpath(b.work+"/"+prefix, a.p) a.objpkg = buildToolchain.pkgpath(b.work, a.p)
a.link = p.Name == "main" a.link = p.Name == "main"
switch mode { switch mode {
@ -635,32 +634,9 @@ func (b *builder) build(a *action) error {
// Prepare Go import path list. // Prepare Go import path list.
inc := b.includeArgs("-I", a.deps) inc := b.includeArgs("-I", a.deps)
// In what directory shall we run the Go compiler?
// We only pass absolute paths, so most of the time it doesn't matter.
// Default to the root directory.
// However, if the package contains local imports (./ or ../)
// then we need to run the compiler in a directory in the parallel
// tree of local package objects, so that those imports resolve to the
// compiled package objects.
gcdir := filepath.Clean("/")
for _, imp := range a.p.Imports {
if build.IsLocalImport(imp) {
dir := a.p.Dir
if filepath.Separator == '\\' {
// Avoid use of : on Windows.
dir = strings.Replace(dir, ":", "_", -1)
}
gcdir = filepath.Join(b.work, "local", dir)
if err := b.mkdir(gcdir); err != nil {
return err
}
break
}
}
// Compile Go. // Compile Go.
if len(gofiles) > 0 { if len(gofiles) > 0 {
if out, err := buildToolchain.gc(b, a.p, obj, gcdir, inc, gofiles); err != nil { if out, err := buildToolchain.gc(b, a.p, obj, inc, gofiles); err != nil {
return err return err
} else { } else {
objects = append(objects, out) objects = append(objects, out)
@ -768,9 +744,9 @@ func (b *builder) install(a *action) error {
func (b *builder) includeArgs(flag string, all []*action) []string { func (b *builder) includeArgs(flag string, all []*action) []string {
inc := []string{} inc := []string{}
incMap := map[string]bool{ incMap := map[string]bool{
b.work + "/obj": true, // handled later b.work: true, // handled later
gorootPkg: true, gorootPkg: true,
"": true, // ignore empty strings "": true, // ignore empty strings
} }
// Look in the temporary space for results of test-specific actions. // Look in the temporary space for results of test-specific actions.
@ -785,7 +761,7 @@ func (b *builder) includeArgs(flag string, all []*action) []string {
// Also look in $WORK for any non-test packages that have // Also look in $WORK for any non-test packages that have
// been built but not installed. // been built but not installed.
inc = append(inc, flag, b.work+"/obj") inc = append(inc, flag, b.work)
// Finally, look in the installed package directories for each action. // Finally, look in the installed package directories for each action.
for _, a1 := range all { for _, a1 := range all {
@ -994,7 +970,7 @@ var errPrintedOutput = errors.New("already printed output - no need to show erro
// run runs the command given by cmdline in the directory dir. // run runs the command given by cmdline in the directory dir.
// If the command fails, run prints information about the failure // If the command fails, run prints information about the failure
// and returns a non-nil error. // and returns a non-nil error.
func (b *builder) run(dir, shortenDir string, desc string, cmdargs ...interface{}) error { func (b *builder) run(dir string, desc string, cmdargs ...interface{}) error {
out, err := b.runOut(dir, desc, cmdargs...) out, err := b.runOut(dir, desc, cmdargs...)
if len(out) > 0 { if len(out) > 0 {
if out[len(out)-1] != '\n' { if out[len(out)-1] != '\n' {
@ -1003,7 +979,7 @@ func (b *builder) run(dir, shortenDir string, desc string, cmdargs ...interface{
if desc == "" { if desc == "" {
desc = b.fmtcmd(dir, "%s", strings.Join(stringList(cmdargs...), " ")) desc = b.fmtcmd(dir, "%s", strings.Join(stringList(cmdargs...), " "))
} }
b.showOutput(shortenDir, desc, string(out)) b.showOutput(dir, desc, string(out))
if err != nil { if err != nil {
err = errPrintedOutput err = errPrintedOutput
} }
@ -1076,7 +1052,7 @@ type toolchain interface {
// gc runs the compiler in a specific directory on a set of files // gc runs the compiler in a specific directory on a set of files
// and returns the name of the generated output file. // and returns the name of the generated output file.
// The compiler runs in the directory dir. // The compiler runs in the directory dir.
gc(b *builder, p *Package, obj, dir string, importArgs []string, gofiles []string) (ofile string, err error) gc(b *builder, p *Package, obj string, importArgs []string, gofiles []string) (ofile string, err error)
// cc runs the toolchain's C compiler in a directory on a C file // cc runs the toolchain's C compiler in a directory on a C file
// to produce an output file. // to produce an output file.
cc(b *builder, p *Package, objdir, ofile, cfile string) error cc(b *builder, p *Package, objdir, ofile, cfile string) error
@ -1121,7 +1097,7 @@ func (goToolchain) linker() string {
return tool(archChar + "l") return tool(archChar + "l")
} }
func (goToolchain) gc(b *builder, p *Package, obj, dir string, importArgs []string, gofiles []string) (ofile string, err error) { func (goToolchain) gc(b *builder, p *Package, obj string, importArgs []string, gofiles []string) (ofile string, err error) {
out := "_go_." + archChar out := "_go_." + archChar
ofile = obj + out ofile = obj + out
gcargs := []string{"-p", p.ImportPath} gcargs := []string{"-p", p.ImportPath}
@ -1131,16 +1107,16 @@ func (goToolchain) gc(b *builder, p *Package, obj, dir string, importArgs []stri
gcargs = append(gcargs, "-+") gcargs = append(gcargs, "-+")
} }
args := stringList(tool(archChar+"g"), "-o", ofile, buildGcflags, gcargs, importArgs) args := stringList(tool(archChar+"g"), "-o", ofile, buildGcflags, gcargs, "-D", p.localPrefix, importArgs)
for _, f := range gofiles { for _, f := range gofiles {
args = append(args, mkAbs(p.Dir, f)) args = append(args, mkAbs(p.Dir, f))
} }
return ofile, b.run(dir, p.Dir, p.ImportPath, args) return ofile, b.run(p.Dir, p.ImportPath, args)
} }
func (goToolchain) asm(b *builder, p *Package, obj, ofile, sfile string) error { func (goToolchain) asm(b *builder, p *Package, obj, ofile, sfile string) error {
sfile = mkAbs(p.Dir, sfile) sfile = mkAbs(p.Dir, sfile)
return b.run(p.Dir, p.Dir, p.ImportPath, tool(archChar+"a"), "-I", obj, "-o", ofile, "-DGOOS_"+goos, "-DGOARCH_"+goarch, sfile) return b.run(p.Dir, p.ImportPath, tool(archChar+"a"), "-I", obj, "-o", ofile, "-DGOOS_"+goos, "-DGOARCH_"+goarch, sfile)
} }
func (goToolchain) pkgpath(basedir string, p *Package) string { func (goToolchain) pkgpath(basedir string, p *Package) string {
@ -1153,18 +1129,18 @@ func (goToolchain) pack(b *builder, p *Package, objDir, afile string, ofiles []s
for _, f := range ofiles { for _, f := range ofiles {
absOfiles = append(absOfiles, mkAbs(objDir, f)) absOfiles = append(absOfiles, mkAbs(objDir, f))
} }
return b.run(p.Dir, p.Dir, p.ImportPath, tool("pack"), "grc", mkAbs(objDir, afile), absOfiles) return b.run(p.Dir, p.ImportPath, tool("pack"), "grc", mkAbs(objDir, afile), absOfiles)
} }
func (goToolchain) ld(b *builder, p *Package, out string, allactions []*action, mainpkg string, ofiles []string) error { func (goToolchain) ld(b *builder, p *Package, out string, allactions []*action, mainpkg string, ofiles []string) error {
importArgs := b.includeArgs("-L", allactions) importArgs := b.includeArgs("-L", allactions)
return b.run(p.Dir, p.Dir, p.ImportPath, tool(archChar+"l"), "-o", out, importArgs, buildLdflags, mainpkg) return b.run(p.Dir, p.ImportPath, tool(archChar+"l"), "-o", out, importArgs, buildLdflags, mainpkg)
} }
func (goToolchain) cc(b *builder, p *Package, objdir, ofile, cfile string) error { func (goToolchain) cc(b *builder, p *Package, objdir, ofile, cfile string) error {
inc := filepath.Join(goroot, "pkg", fmt.Sprintf("%s_%s", goos, goarch)) inc := filepath.Join(goroot, "pkg", fmt.Sprintf("%s_%s", goos, goarch))
cfile = mkAbs(p.Dir, cfile) cfile = mkAbs(p.Dir, cfile)
return b.run(p.Dir, p.Dir, p.ImportPath, tool(archChar+"c"), "-FVw", return b.run(p.Dir, p.ImportPath, tool(archChar+"c"), "-FVw",
"-I", objdir, "-I", inc, "-o", ofile, "-I", objdir, "-I", inc, "-o", ofile,
"-DGOOS_"+goos, "-DGOARCH_"+goarch, cfile) "-DGOOS_"+goos, "-DGOARCH_"+goarch, cfile)
} }
@ -1181,7 +1157,7 @@ func (gccgoToolchain) linker() string {
return gccgoBin return gccgoBin
} }
func (gccgoToolchain) gc(b *builder, p *Package, obj, dir string, importArgs []string, gofiles []string) (ofile string, err error) { func (gccgoToolchain) gc(b *builder, p *Package, obj string, importArgs []string, gofiles []string) (ofile string, err error) {
out := p.Name + ".o" out := p.Name + ".o"
ofile = obj + out ofile = obj + out
gcargs := []string{"-g"} gcargs := []string{"-g"}
@ -1196,12 +1172,12 @@ func (gccgoToolchain) gc(b *builder, p *Package, obj, dir string, importArgs []s
for _, f := range gofiles { for _, f := range gofiles {
args = append(args, mkAbs(p.Dir, f)) args = append(args, mkAbs(p.Dir, f))
} }
return ofile, b.run(dir, p.Dir, p.ImportPath, args) return ofile, b.run(p.Dir, p.ImportPath, args)
} }
func (gccgoToolchain) asm(b *builder, p *Package, obj, ofile, sfile string) error { func (gccgoToolchain) asm(b *builder, p *Package, obj, ofile, sfile string) error {
sfile = mkAbs(p.Dir, sfile) sfile = mkAbs(p.Dir, sfile)
return b.run(p.Dir, p.Dir, p.ImportPath, "gccgo", "-I", obj, "-o", ofile, "-DGOOS_"+goos, "-DGOARCH_"+goarch, sfile) return b.run(p.Dir, p.ImportPath, "gccgo", "-I", obj, "-o", ofile, "-DGOOS_"+goos, "-DGOARCH_"+goarch, sfile)
} }
func (gccgoToolchain) pkgpath(basedir string, p *Package) string { func (gccgoToolchain) pkgpath(basedir string, p *Package) string {
@ -1216,7 +1192,7 @@ func (gccgoToolchain) pack(b *builder, p *Package, objDir, afile string, ofiles
for _, f := range ofiles { for _, f := range ofiles {
absOfiles = append(absOfiles, mkAbs(objDir, f)) absOfiles = append(absOfiles, mkAbs(objDir, f))
} }
return b.run(p.Dir, p.Dir, p.ImportPath, "ar", "cru", mkAbs(objDir, afile), absOfiles) return b.run(p.Dir, p.ImportPath, "ar", "cru", mkAbs(objDir, afile), absOfiles)
} }
func (tools gccgoToolchain) ld(b *builder, p *Package, out string, allactions []*action, mainpkg string, ofiles []string) error { func (tools gccgoToolchain) ld(b *builder, p *Package, out string, allactions []*action, mainpkg string, ofiles []string) error {
@ -1239,13 +1215,13 @@ func (tools gccgoToolchain) ld(b *builder, p *Package, out string, allactions []
ldflags = append(ldflags, afile) ldflags = append(ldflags, afile)
} }
ldflags = append(ldflags, cgoldflags...) ldflags = append(ldflags, cgoldflags...)
return b.run(p.Dir, p.Dir, p.ImportPath, "gccgo", "-o", out, buildGccgoflags, ofiles, "-Wl,-(", ldflags, "-Wl,-)") return b.run(p.Dir, p.ImportPath, "gccgo", "-o", out, buildGccgoflags, ofiles, "-Wl,-(", ldflags, "-Wl,-)")
} }
func (gccgoToolchain) cc(b *builder, p *Package, objdir, ofile, cfile string) error { func (gccgoToolchain) cc(b *builder, p *Package, objdir, ofile, cfile string) error {
inc := filepath.Join(goroot, "pkg", fmt.Sprintf("%s_%s", goos, goarch)) inc := filepath.Join(goroot, "pkg", fmt.Sprintf("%s_%s", goos, goarch))
cfile = mkAbs(p.Dir, cfile) cfile = mkAbs(p.Dir, cfile)
return b.run(p.Dir, p.Dir, p.ImportPath, "gcc", "-Wall", "-g", return b.run(p.Dir, p.ImportPath, "gcc", "-Wall", "-g",
"-I", objdir, "-I", inc, "-o", ofile, "-I", objdir, "-I", inc, "-o", ofile,
"-DGOOS_"+goos, "-DGOARCH_"+goarch, "-c", cfile) "-DGOOS_"+goos, "-DGOARCH_"+goarch, "-c", cfile)
} }
@ -1253,12 +1229,12 @@ func (gccgoToolchain) cc(b *builder, p *Package, objdir, ofile, cfile string) er
// gcc runs the gcc C compiler to create an object from a single C file. // gcc runs the gcc C compiler to create an object from a single C file.
func (b *builder) gcc(p *Package, out string, flags []string, cfile string) error { func (b *builder) gcc(p *Package, out string, flags []string, cfile string) error {
cfile = mkAbs(p.Dir, cfile) cfile = mkAbs(p.Dir, cfile)
return b.run(p.Dir, p.Dir, p.ImportPath, b.gccCmd(p.Dir), flags, "-o", out, "-c", cfile) return b.run(p.Dir, p.ImportPath, b.gccCmd(p.Dir), flags, "-o", out, "-c", cfile)
} }
// gccld runs the gcc linker to create an executable from a set of object files // gccld runs the gcc linker to create an executable from a set of object files
func (b *builder) gccld(p *Package, out string, flags []string, obj []string) error { func (b *builder) gccld(p *Package, out string, flags []string, obj []string) error {
return b.run(p.Dir, p.Dir, p.ImportPath, b.gccCmd(p.Dir), "-o", out, obj, flags) return b.run(p.Dir, p.ImportPath, b.gccCmd(p.Dir), "-o", out, obj, flags)
} }
// gccCmd returns a gcc command line prefix // gccCmd returns a gcc command line prefix
@ -1348,7 +1324,7 @@ func (b *builder) cgo(p *Package, cgoExe, obj string, gccfiles []string) (outGo,
if _, ok := buildToolchain.(gccgoToolchain); ok { if _, ok := buildToolchain.(gccgoToolchain); ok {
cgoflags = append(cgoflags, "-gccgo") cgoflags = append(cgoflags, "-gccgo")
} }
if err := b.run(p.Dir, p.Dir, p.ImportPath, cgoExe, "-objdir", obj, cgoflags, "--", cgoCFLAGS, p.CgoFiles); err != nil { if err := b.run(p.Dir, p.ImportPath, cgoExe, "-objdir", obj, cgoflags, "--", cgoCFLAGS, p.CgoFiles); err != nil {
return nil, nil, err return nil, nil, err
} }
outGo = append(outGo, gofiles...) outGo = append(outGo, gofiles...)
@ -1392,7 +1368,7 @@ func (b *builder) cgo(p *Package, cgoExe, obj string, gccfiles []string) (outGo,
// cgo -dynimport // cgo -dynimport
importC := obj + "_cgo_import.c" importC := obj + "_cgo_import.c"
if err := b.run(p.Dir, p.Dir, p.ImportPath, cgoExe, "-objdir", obj, "-dynimport", dynobj, "-dynout", importC); err != nil { if err := b.run(p.Dir, p.ImportPath, cgoExe, "-objdir", obj, "-dynimport", dynobj, "-dynout", importC); err != nil {
return nil, nil, err return nil, nil, err
} }

View File

@ -12,6 +12,7 @@ import (
"go/scanner" "go/scanner"
"go/token" "go/token"
"os" "os"
pathpkg "path"
"path/filepath" "path/filepath"
"sort" "sort"
"strings" "strings"
@ -60,15 +61,16 @@ type Package struct {
XTestImports []string `json:",omitempty"` // imports from XTestGoFiles XTestImports []string `json:",omitempty"` // imports from XTestGoFiles
// Unexported fields are not part of the public API. // Unexported fields are not part of the public API.
build *build.Package build *build.Package
pkgdir string // overrides build.PkgDir pkgdir string // overrides build.PkgDir
imports []*Package imports []*Package
deps []*Package deps []*Package
gofiles []string // GoFiles+CgoFiles+TestGoFiles+XTestGoFiles files, absolute paths gofiles []string // GoFiles+CgoFiles+TestGoFiles+XTestGoFiles files, absolute paths
target string // installed file for this package (may be executable) target string // installed file for this package (may be executable)
fake bool // synthesized package fake bool // synthesized package
forceBuild bool // this package must be rebuilt forceBuild bool // this package must be rebuilt
local bool // imported via local path (./ or ../) local bool // imported via local path (./ or ../)
localPrefix string // interpret ./ and ../ imports relative to this prefix
} }
func (p *Package) copyBuild(pp *build.Package) { func (p *Package) copyBuild(pp *build.Package) {
@ -161,6 +163,17 @@ func reloadPackage(arg string, stk *importStack) *Package {
return loadPackage(arg, stk) return loadPackage(arg, stk)
} }
// dirToImportPath returns the pseudo-import path we use for a package
// outside the Go path. It begins with _/ and then contains the full path
// to the directory. If the package lives in c:\home\gopher\my\pkg then
// the pseudo-import path is _/c_/home/gopher/my/pkg.
// Using a pseudo-import path like this makes the ./ imports no longer
// a special case, so that all the code to deal with ordinary imports works
// automatically.
func dirToImportPath(dir string) string {
return pathpkg.Join("_", strings.Replace(filepath.ToSlash(dir), ":", "_", -1))
}
// loadImport scans the directory named by path, which must be an import path, // loadImport scans the directory named by path, which must be an import path,
// but possibly a local import path (an absolute file system path or one beginning // but possibly a local import path (an absolute file system path or one beginning
// with ./ or ../). A local relative path is interpreted relative to srcDir. // with ./ or ../). A local relative path is interpreted relative to srcDir.
@ -170,24 +183,28 @@ func loadImport(path string, srcDir string, stk *importStack, importPos []token.
defer stk.pop() defer stk.pop()
// Determine canonical identifier for this package. // Determine canonical identifier for this package.
// For a local path (./ or ../) the identifier is the full // For a local import the identifier is the pseudo-import path
// directory name. Otherwise it is the import path. // we create from the full directory to the package.
pkgid := path // Otherwise it is the usual import path.
importPath := path
isLocal := build.IsLocalImport(path) isLocal := build.IsLocalImport(path)
if isLocal { if isLocal {
pkgid = filepath.Join(srcDir, path) importPath = dirToImportPath(filepath.Join(srcDir, path))
} }
if p := packageCache[pkgid]; p != nil { if p := packageCache[importPath]; p != nil {
return reusePackage(p, stk) return reusePackage(p, stk)
} }
p := new(Package) p := new(Package)
packageCache[pkgid] = p p.local = isLocal
p.ImportPath = importPath
packageCache[importPath] = p
// Load package. // Load package.
// Import always returns bp != nil, even if an error occurs, // Import always returns bp != nil, even if an error occurs,
// in order to return partial information. // in order to return partial information.
bp, err := buildContext.Import(path, srcDir, build.AllowBinary) bp, err := buildContext.Import(path, srcDir, build.AllowBinary)
bp.ImportPath = importPath
p.load(stk, bp, err) p.load(stk, bp, err)
if p.Error != nil && len(importPos) > 0 { if p.Error != nil && len(importPos) > 0 {
pos := importPos[0] pos := importPos[0]
@ -211,7 +228,6 @@ func reusePackage(p *Package, stk *importStack) *Package {
ImportStack: stk.copy(), ImportStack: stk.copy(),
Err: "import loop", Err: "import loop",
} }
panic("loop")
} }
p.Incomplete = true p.Incomplete = true
} }
@ -258,14 +274,12 @@ func expandScanner(err error) error {
// be the result of calling build.Context.Import. // be the result of calling build.Context.Import.
func (p *Package) load(stk *importStack, bp *build.Package, err error) *Package { func (p *Package) load(stk *importStack, bp *build.Package, err error) *Package {
p.copyBuild(bp) p.copyBuild(bp)
p.local = build.IsLocalImport(p.ImportPath)
if p.local { // The localPrefix is the path we interpret ./ imports relative to.
// The correct import for this package depends on which // Now that we've fixed the import path, it's just the import path.
// directory you are in. Instead, record the full directory path. // Synthesized main packages sometimes override this.
// That can't be used as an import path at all, but at least p.localPrefix = p.ImportPath
// it uniquely identifies the package.
p.ImportPath = p.Dir
}
if err != nil { if err != nil {
p.Incomplete = true p.Incomplete = true
err = expandScanner(err) err = expandScanner(err)
@ -326,7 +340,7 @@ func (p *Package) load(stk *importStack, bp *build.Package, err error) *Package
} }
p1 := loadImport(path, p.Dir, stk, p.build.ImportPos[path]) p1 := loadImport(path, p.Dir, stk, p.build.ImportPos[path])
if p1.local { if p1.local {
path = p1.Dir path = p1.ImportPath
importPaths[i] = path importPaths[i] = path
} }
deps[path] = true deps[path] = true

View File

@ -45,6 +45,12 @@ func runRun(cmd *Command, args []string) {
} }
files, cmdArgs := args[:i], args[i:] files, cmdArgs := args[:i], args[i:]
p := goFilesPackage(files) p := goFilesPackage(files)
if p.Error != nil {
fatalf("%s", p.Error)
}
if p.Name != "main" {
fatalf("cannot run non-main package")
}
p.target = "" // must build - not up to date p.target = "" // must build - not up to date
a1 := b.action(modeBuild, modeBuild, p) a1 := b.action(modeBuild, modeBuild, p)
a := &action{f: (*builder).runProgram, args: cmdArgs, deps: []*action{a1}} a := &action{f: (*builder).runProgram, args: cmdArgs, deps: []*action{a1}}

View File

@ -30,6 +30,14 @@ if ! grep -q '^easysub\.Hello' hello.out; then
ok=false ok=false
fi fi
./testgo build -o hello testdata/local/easysub/main.go
./hello >hello.out
if ! grep -q '^easysub\.Hello' hello.out; then
echo "testdata/local/easysub/main.go did not generate expected output"
cat hello.out
ok=false
fi
./testgo build -o hello testdata/local/hard.go ./testgo build -o hello testdata/local/hard.go
./hello >hello.out ./hello >hello.out
if ! grep -q '^sub\.Hello' hello.out || ! grep -q '^subsub\.Hello' hello.out ; then if ! grep -q '^sub\.Hello' hello.out || ! grep -q '^subsub\.Hello' hello.out ; then
@ -46,6 +54,19 @@ if ./testgo install testdata/local/easy.go >/dev/null 2>&1; then
ok=false ok=false
fi fi
# Test tests with relative imports.
if ! ./testgo test ./testdata/testimport; then
echo "go test ./testdata/testimport failed"
ok=false
fi
# Test tests with relative imports in packages synthesized
# from Go files named on the command line.
if ! ./testgo test ./testdata/testimport/*.go; then
echo "go test ./testdata/testimport/*.go failed"
ok=false
fi
if $ok; then if $ok; then
echo PASS echo PASS
else else

View File

@ -351,7 +351,7 @@ func runTest(cmd *Command, args []string) {
warned := false warned := false
for _, a := range actionList(root) { for _, a := range actionList(root) {
if a.p != nil && a.f != nil && !okBuild[a.p] && !a.p.fake { if a.p != nil && a.f != nil && !okBuild[a.p] && !a.p.fake && !a.p.local {
okBuild[a.p] = true // don't warn again okBuild[a.p] = true // don't warn again
if !warned { if !warned {
fmt.Fprintf(os.Stderr, "warning: building out-of-date packages:\n") fmt.Fprintf(os.Stderr, "warning: building out-of-date packages:\n")
@ -474,11 +474,12 @@ func (b *builder) test(p *Package) (buildAction, runAction, printAction *action,
// External test package. // External test package.
if len(p.XTestGoFiles) > 0 { if len(p.XTestGoFiles) > 0 {
pxtest = &Package{ pxtest = &Package{
Name: p.Name + "_test", Name: p.Name + "_test",
ImportPath: p.ImportPath + "_test", ImportPath: p.ImportPath + "_test",
Dir: p.Dir, localPrefix: p.localPrefix,
GoFiles: p.XTestGoFiles, Dir: p.Dir,
Imports: p.XTestImports, GoFiles: p.XTestGoFiles,
Imports: p.XTestImports,
build: &build.Package{ build: &build.Package{
ImportPos: p.build.XTestImportPos, ImportPos: p.build.XTestImportPos,
}, },

View File

@ -0,0 +1,9 @@
// +build ignore
package main
import "."
func main() {
easysub.Hello()
}

3
src/cmd/go/testdata/testimport/p.go vendored Normal file
View File

@ -0,0 +1,3 @@
package p
func F() int { return 1 }

View File

@ -0,0 +1,3 @@
package p1
func F() int { return 1 }

View File

@ -0,0 +1,3 @@
package p2
func F() int { return 1 }

View File

@ -0,0 +1,13 @@
package p
import (
"./p1"
"testing"
)
func TestF(t *testing.T) {
if F() != p1.F() {
t.Fatal(F())
}
}

View File

@ -0,0 +1,15 @@
package p_test
import (
. "../testimport"
"./p2"
"testing"
)
func TestF1(t *testing.T) {
if F() != p2.F() {
t.Fatal(F())
}
}