Commit 961f456a authored by Russ Cox's avatar Russ Cox

cmd/go: document and fix 'go build -o' semantics

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/13024Reviewed-by: default avatarIan Lance Taylor <iant@golang.org>
Reviewed-by: default avatarDave Day <djd@golang.org>
parent e3c26b2b
......@@ -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.
......
......@@ -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 {
......
......@@ -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")
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment