1
0
mirror of https://github.com/golang/go synced 2024-09-25 15:10:11 -06:00

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/13024
Reviewed-by: Ian Lance Taylor <iant@golang.org>
Reviewed-by: Dave Day <djd@golang.org>
This commit is contained in:
Russ Cox 2015-07-31 13:07:41 -04:00
parent e3c26b2b32
commit 961f456a1d
3 changed files with 129 additions and 32 deletions

View 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.

View 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 {

View 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")
}