]> Cypherpunks repositories - gostls13.git/commitdiff
cmd/go: document and fix 'go build -o' semantics
authorRuss Cox <rsc@golang.org>
Fri, 31 Jul 2015 17:07:41 +0000 (13:07 -0400)
committerRuss Cox <rsc@golang.org>
Mon, 3 Aug 2015 19:59:08 +0000 (19:59 +0000)
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 <iant@golang.org>
Reviewed-by: Dave Day <djd@golang.org>
src/cmd/go/alldocs.go
src/cmd/go/build.go
src/cmd/go/go_test.go

index 20ae55d4908461e9a2bc8572ea370dfa93cc4ec6..661c7c08f640e573a074e2264810ac4019665723 100644 (file)
@@ -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.
 
index a669da9997f7e6771c09b7cde60835f5ccdb6546..b876c51ecfa386de2ae31b4f3c07780febc31b34 100644 (file)
@@ -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 {
index 55b81f719e3f59fda011f4d9a966d4e2518a5cda..8b5917b6339b2598653dc969c0d7c5d43855770d 100644 (file)
@@ -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("!<arch>\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")
+}