From 961f456a1dbf6c86c033f8c9205766d3bbf228a1 Mon Sep 17 00:00:00 2001 From: Russ Cox Date: Fri, 31 Jul 2015 13:07:41 -0400 Subject: [PATCH] cmd/go: document and fix 'go build -o' semantics MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Quoting the new docs: « If the arguments to build are a list of .go files, build treats them as a list of source files specifying a single package. When compiling a single main package, build writes the resulting executable to an output file named after the first source file ('go build ed.go rx.go' writes 'ed' or 'ed.exe') or the source code directory ('go build unix/sam' writes 'sam' or 'sam.exe'). The '.exe' suffix is added when writing a Windows executable. When compiling multiple packages or a single non-main package, build compiles the packages but discards the resulting object, serving only as a check that the packages can be built. The -o flag, only allowed when compiling a single package, forces build to write the resulting executable or object to the named output file, instead of the default behavior described in the last two paragraphs. » There is a change in behavior here, namely that 'go build -o x.a x.go' where x.go is not a command (not package main) did not write any output files (back to at least Go 1.2) but now writes x.a. This seems more reasonable than trying to explain that -o is sometimes silently ignored. Otherwise the behavior is unchanged. The lines being deleted in goFilesPackage look like they are setting up 'go build x.o' to write 'x.a', but they were overridden by the p.target = "" in runBuild. Again back to at least Go 1.2, 'go build x.go' for a non-main package has never produced output. It seems better to keep it that way than to change it, both for historical consistency and for consistency with 'go build strings' and 'go build std'. All of this behavior is now tested. Fixes #10865. Change-Id: Iccdf21f366fbc8b5ae600a1e50dfe7fc3bff8b1c Reviewed-on: https://go-review.googlesource.com/13024 Reviewed-by: Ian Lance Taylor Reviewed-by: Dave Day --- src/cmd/go/alldocs.go | 22 ++++++------ src/cmd/go/build.go | 58 +++++++++++++++++++------------ src/cmd/go/go_test.go | 81 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 129 insertions(+), 32 deletions(-) diff --git a/src/cmd/go/alldocs.go b/src/cmd/go/alldocs.go index 20ae55d490..661c7c08f6 100644 --- a/src/cmd/go/alldocs.go +++ b/src/cmd/go/alldocs.go @@ -59,18 +59,20 @@ along with their dependencies, but it does not install the results. If the arguments to build are a list of .go files, build treats them as a list of source files specifying a single package. -When the command line specifies a single main package, -build writes the resulting executable to output. -Otherwise build compiles the packages but discards the results, +When compiling a single main package, build writes +the resulting executable to an output file named after +the first source file ('go build ed.go rx.go' writes 'ed' or 'ed.exe') +or the source code directory ('go build unix/sam' writes 'sam' or 'sam.exe'). +The '.exe' suffix is added when writing a Windows executable. + +When compiling multiple packages or a single non-main package, +build compiles the packages but discards the resulting object, serving only as a check that the packages can be built. -The -o flag specifies the output file name. If not specified, the -output file name depends on the arguments and derives from the name -of the package, such as p.a for package p, unless p is 'main'. If -the package is main and file names are provided, the file name -derives from the first file name mentioned, such as f1 for 'go build -f1.go f2.go'; with no files provided ('go build'), the output file -name is the base name of the containing directory. +The -o flag, only allowed when compiling a single package, +forces build to write the resulting executable or object +to the named output file, instead of the default behavior described +in the last two paragraphs. The -i flag installs the packages that are dependencies of the target. diff --git a/src/cmd/go/build.go b/src/cmd/go/build.go index a669da9997..b876c51ecf 100644 --- a/src/cmd/go/build.go +++ b/src/cmd/go/build.go @@ -38,18 +38,20 @@ along with their dependencies, but it does not install the results. If the arguments to build are a list of .go files, build treats them as a list of source files specifying a single package. -When the command line specifies a single main package, -build writes the resulting executable to output. -Otherwise build compiles the packages but discards the results, +When compiling a single main package, build writes +the resulting executable to an output file named after +the first source file ('go build ed.go rx.go' writes 'ed' or 'ed.exe') +or the source code directory ('go build unix/sam' writes 'sam' or 'sam.exe'). +The '.exe' suffix is added when writing a Windows executable. + +When compiling multiple packages or a single non-main package, +build compiles the packages but discards the resulting object, serving only as a check that the packages can be built. -The -o flag specifies the output file name. If not specified, the -output file name depends on the arguments and derives from the name -of the package, such as p.a for package p, unless p is 'main'. If -the package is main and file names are provided, the file name -derives from the first file name mentioned, such as f1 for 'go build -f1.go f2.go'; with no files provided ('go build'), the output file -name is the base name of the containing directory. +The -o flag, only allowed when compiling a single package, +forces build to write the resulting executable or object +to the named output file, instead of the default behavior described +in the last two paragraphs. The -i flag installs the packages that are dependencies of the target. @@ -445,14 +447,9 @@ func runBuild(cmd *Command, args []string) { fatalf("no packages to build") } p := pkgs[0] - p.target = "" // must build - not up to date + p.target = *buildO + p.Stale = true // must build - not up to date a := b.action(modeInstall, depMode, p) - a.target = *buildO - if p.local { - // If p.local, then b.action did not really install, - // so install the header file now if necessary. - a = b.maybeAddHeaderAction(a, false) - } b.do(a) return } @@ -764,11 +761,8 @@ func goFilesPackage(gofiles []string) *Package { if gobin != "" { pkg.target = filepath.Join(gobin, exe) } - } else { - if *buildO == "" { - *buildO = pkg.Name + ".a" - } } + pkg.Target = pkg.target pkg.Stale = true @@ -931,6 +925,13 @@ func (b *builder) action1(mode buildMode, depMode buildMode, p *Package, looksha name := "a.out" if p.exeName != "" { name = p.exeName + } else if goos == "darwin" && buildBuildmode == "c-shared" && p.target != "" { + // On OS X, the linker output name gets recorded in the + // shared library's LC_ID_DYLIB load command. + // The code invoking the linker knows to pass only the final + // path element. Arrange that the path element matches what + // we'll install it as; otherwise the library is only loadable as "a.out". + _, name = filepath.Split(p.target) } a.target = a.objdir + filepath.Join("exe", name) + exeSuffix } @@ -2368,7 +2369,20 @@ func (gcToolchain) ld(b *builder, root *action, out string, allactions []*action ldflags = append(ldflags, "-buildid="+root.p.buildID) } ldflags = append(ldflags, buildLdflags...) - return b.run(".", root.p.ImportPath, nil, buildToolExec, tool("link"), "-o", out, importArgs, ldflags, mainpkg) + + // On OS X when using external linking to build a shared library, + // the argument passed here to -o ends up recorded in the final + // shared library in the LC_ID_DYLIB load command. + // To avoid putting the temporary output directory name there + // (and making the resulting shared library useless), + // run the link in the output directory so that -o can name + // just the final path element. + dir := "." + if goos == "darwin" && buildBuildmode == "c-shared" { + dir, out = filepath.Split(out) + } + + return b.run(dir, root.p.ImportPath, nil, buildToolExec, tool("link"), "-o", out, importArgs, ldflags, mainpkg) } func (gcToolchain) ldShared(b *builder, toplevelactions []*action, out string, allactions []*action) error { diff --git a/src/cmd/go/go_test.go b/src/cmd/go/go_test.go index 55b81f719e..8b5917b633 100644 --- a/src/cmd/go/go_test.go +++ b/src/cmd/go/go_test.go @@ -11,6 +11,7 @@ import ( "go/build" "go/format" "internal/testenv" + "io" "io/ioutil" "os" "os/exec" @@ -499,6 +500,13 @@ func (tg *testgoData) path(name string) string { return filepath.Join(tg.tempdir, name) } +// mustNotExist fails if path exists. +func (tg *testgoData) mustNotExist(path string) { + if _, err := os.Stat(path); err == nil || !os.IsNotExist(err) { + tg.t.Fatalf("%s exists but should not (%v)", path, err) + } +} + // wantExecutable fails with msg if path is not executable. func (tg *testgoData) wantExecutable(path, msg string) { if st, err := os.Stat(path); err != nil { @@ -513,6 +521,20 @@ func (tg *testgoData) wantExecutable(path, msg string) { } } +// wantArchive fails if path is not an archive. +func (tg *testgoData) wantArchive(path string) { + f, err := os.Open(path) + if err != nil { + tg.t.Fatal(err) + } + buf := make([]byte, 100) + io.ReadFull(f, buf) + f.Close() + if !bytes.HasPrefix(buf, []byte("!\n")) { + tg.t.Fatalf("file %s exists but is not an archive", path) + } +} + // isStale returns whether pkg is stale. func (tg *testgoData) isStale(pkg string) bool { tg.run("list", "-f", "{{.Stale}}", pkg) @@ -2263,3 +2285,62 @@ func TestIssue11709(t *testing.T) { tg.unsetenv("TERM") tg.run("run", tg.path("run.go")) } + +func TestGoBuildOutput(t *testing.T) { + tg := testgo(t) + defer tg.cleanup() + + tg.makeTempdir() + tg.cd(tg.path(".")) + + nonExeSuffix := ".exe" + if exeSuffix == ".exe" { + nonExeSuffix = "" + } + + tg.tempFile("x.go", "package main\nfunc main(){}\n") + tg.run("build", "x.go") + tg.wantExecutable("x"+exeSuffix, "go build x.go did not write x"+exeSuffix) + tg.must(os.Remove(tg.path("x" + exeSuffix))) + tg.mustNotExist("x" + nonExeSuffix) + + tg.run("build", "-o", "myprog", "x.go") + tg.mustNotExist("x") + tg.mustNotExist("x.exe") + tg.wantExecutable("myprog", "go build -o myprog x.go did not write myprog") + tg.mustNotExist("myprog.exe") + + tg.tempFile("p.go", "package p\n") + tg.run("build", "p.go") + tg.mustNotExist("p") + tg.mustNotExist("p.a") + tg.mustNotExist("p.o") + tg.mustNotExist("p.exe") + + tg.run("build", "-o", "p.a", "p.go") + tg.wantArchive("p.a") + + tg.run("build", "cmd/gofmt") + tg.wantExecutable("gofmt"+exeSuffix, "go build cmd/gofmt did not write gofmt"+exeSuffix) + tg.must(os.Remove(tg.path("gofmt" + exeSuffix))) + tg.mustNotExist("gofmt" + nonExeSuffix) + + tg.run("build", "-o", "mygofmt", "cmd/gofmt") + tg.wantExecutable("mygofmt", "go build -o mygofmt cmd/gofmt did not write mygofmt") + tg.mustNotExist("mygofmt.exe") + tg.mustNotExist("gofmt") + tg.mustNotExist("gofmt.exe") + + tg.run("build", "sync/atomic") + tg.mustNotExist("atomic") + tg.mustNotExist("atomic.exe") + + tg.run("build", "-o", "myatomic.a", "sync/atomic") + tg.wantArchive("myatomic.a") + tg.mustNotExist("atomic") + tg.mustNotExist("atomic.a") + tg.mustNotExist("atomic.exe") + + tg.runFail("build", "-o", "whatever", "cmd/gofmt", "sync/atomic") + tg.grepStderr("multiple packages", "did not reject -o with multiple packages") +} -- 2.48.1