Commit 9f0cabfa authored by Andrew Gerrand's avatar Andrew Gerrand

goinstall: document GOPATH and support relative/absolute installs

goinstall: more verbose logging with -v

Fixes #1901.

R=rsc, n13m3y3r
CC=golang-dev
https://golang.org/cl/4524078
parent 9ec0c01e
...@@ -3,7 +3,6 @@ ...@@ -3,7 +3,6 @@
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
/* /*
Goinstall is an experiment in automatic package installation. Goinstall is an experiment in automatic package installation.
It installs packages, possibly downloading them from the internet. It installs packages, possibly downloading them from the internet.
It maintains a list of public Go packages at http://godashboard.appspot.com/package. It maintains a list of public Go packages at http://godashboard.appspot.com/package.
...@@ -100,5 +99,59 @@ Instead, it invokes "make install" after locating the package sources. ...@@ -100,5 +99,59 @@ Instead, it invokes "make install" after locating the package sources.
For local packages without a Makefile and all remote packages, For local packages without a Makefile and all remote packages,
goinstall creates and uses a temporary Makefile constructed from goinstall creates and uses a temporary Makefile constructed from
the import path and the list of Go files in the package. the import path and the list of Go files in the package.
The GOPATH Environment Variable
GOPATH may be set to a colon-separated list of paths inside which Go code,
package objects, and executables may be found.
Set a GOPATH to use goinstall to build and install your own code and
external libraries outside of the Go tree (and to avoid writing Makefiles).
The top-level directory structure of a GOPATH is prescribed:
The 'src' directory is for source code. The directory naming inside 'src'
determines the package import path or executable name.
The 'pkg' directory is for package objects. Like the Go tree, package objects
are stored inside a directory named after the target operating system and
processor architecture ('pkg/$GOOS_$GOARCH').
A package whose source is located at '$GOPATH/src/foo/bar' would be imported
as 'foo/bar' and installed as '$GOPATH/pkg/$GOOS_$GOARCH/foo/bar.a'.
The 'bin' directory is for executable files.
Goinstall installs program binaries using the name of the source folder.
A binary whose source is at 'src/foo/qux' would be built and installed to
'$GOPATH/bin/qux'. (Note 'bin/qux', not 'bin/foo/qux' - this is such that
you can put the bin directory in your PATH.)
Here's an example directory layout:
GOPATH=/home/user/gocode
/home/user/gocode/
src/foo/
bar/ (go code in package bar)
qux/ (go code in package main)
bin/qux (executable file)
pkg/linux_amd64/foo/bar.a (object file)
Run 'goinstall foo/bar' to build and install the package 'foo/bar'
(and its dependencies).
Goinstall will search each GOPATH (in order) for 'src/foo/bar'.
If the directory cannot be found, goinstall will attempt to fetch the
source from a remote repository and write it to the 'src' directory of the
first GOPATH (or $GOROOT/src/pkg if GOPATH is not set).
Goinstall recognizes relative and absolute paths (paths beginning with / or .).
The following commands would build our example packages:
goinstall /home/user/gocode/src/foo/bar # build and install foo/bar
cd /home/user/gocode/src/foo
goinstall ./bar # build and install foo/bar (again)
cd qux
goinstall . # build and install foo/qux
*/ */
package documentation package documentation
...@@ -32,9 +32,8 @@ var ( ...@@ -32,9 +32,8 @@ var (
argv0 = os.Args[0] argv0 = os.Args[0]
errors = false errors = false
parents = make(map[string]string) parents = make(map[string]string)
root = runtime.GOROOT()
visit = make(map[string]status) visit = make(map[string]status)
logfile = filepath.Join(root, "goinstall.log") logfile = filepath.Join(runtime.GOROOT(), "goinstall.log")
installedPkgs = make(map[string]bool) installedPkgs = make(map[string]bool)
allpkg = flag.Bool("a", false, "install all previously installed packages") allpkg = flag.Bool("a", false, "install all previously installed packages")
...@@ -52,14 +51,30 @@ const ( ...@@ -52,14 +51,30 @@ const (
done done
) )
func logf(format string, args ...interface{}) {
format = "%s: " + format
args = append([]interface{}{argv0}, args...)
fmt.Fprintf(os.Stderr, format, args...)
}
func vlogf(format string, args ...interface{}) {
if *verbose {
logf(format, args...)
}
}
func errorf(format string, args ...interface{}) {
errors = true
logf(format, args...)
}
func main() { func main() {
flag.Usage = usage flag.Usage = usage
flag.Parse() flag.Parse()
if root == "" { if runtime.GOROOT() == "" {
fmt.Fprintf(os.Stderr, "%s: no $GOROOT\n", argv0) fmt.Fprintf(os.Stderr, "%s: no $GOROOT\n", argv0)
os.Exit(1) os.Exit(1)
} }
root += filepath.FromSlash("/src/pkg/")
// special case - "unsafe" is already installed // special case - "unsafe" is already installed
visit["unsafe"] = done visit["unsafe"] = done
...@@ -88,6 +103,11 @@ func main() { ...@@ -88,6 +103,11 @@ func main() {
usage() usage()
} }
for _, path := range args { for _, path := range args {
if strings.HasPrefix(path, "http://") {
errorf("'http://' used in remote path, try '%s'\n", path[7:])
continue
}
install(path, "") install(path, "")
} }
if errors { if errors {
...@@ -131,11 +151,6 @@ func logPackage(pkg string) { ...@@ -131,11 +151,6 @@ func logPackage(pkg string) {
// install installs the package named by path, which is needed by parent. // install installs the package named by path, which is needed by parent.
func install(pkg, parent string) { func install(pkg, parent string) {
if isStandardPath(pkg) {
visit[pkg] = done
return
}
// Make sure we're not already trying to install pkg. // Make sure we're not already trying to install pkg.
switch visit[pkg] { switch visit[pkg] {
case done: case done:
...@@ -148,46 +163,44 @@ func install(pkg, parent string) { ...@@ -148,46 +163,44 @@ func install(pkg, parent string) {
} }
visit[pkg] = visiting visit[pkg] = visiting
parents[pkg] = parent parents[pkg] = parent
if *verbose {
fmt.Println(pkg) vlogf("%s: visit\n", pkg)
}
// Check whether package is local or remote. // Check whether package is local or remote.
// If remote, download or update it. // If remote, download or update it.
var dir string proot, pkg, err := findPackageRoot(pkg)
proot := gopath[0] // default to GOROOT // Don't build the standard library.
local := false if err == nil && proot.goroot && isStandardPath(pkg) {
if strings.HasPrefix(pkg, "http://") { if parent == "" {
fmt.Fprintf(os.Stderr, "%s: %s: 'http://' used in remote path, try '%s'\n", argv0, pkg, pkg[7:]) errorf("%s: can not goinstall the standard library\n", pkg)
errors = true } else {
vlogf("%s: skipping standard library\n", pkg)
}
visit[pkg] = done
return return
} }
if isLocalPath(pkg) { // Download remote packages if not found or forced with -u flag.
dir = pkg remote := isRemote(pkg)
local = true if remote && (err == ErrPackageNotFound || (err == nil && *update)) {
} else { vlogf("%s: download\n", pkg)
proot = findPkgroot(pkg) err = download(pkg, proot.srcDir())
err := download(pkg, proot.srcDir()) }
dir = filepath.Join(proot.srcDir(), pkg)
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "%s: %s: %s\n", argv0, pkg, err) errorf("%s: %v\n", pkg, err)
errors = true
visit[pkg] = done visit[pkg] = done
return return
} }
} dir := filepath.Join(proot.srcDir(), pkg)
// Install prerequisites. // Install prerequisites.
dirInfo, err := scanDir(dir, parent == "") dirInfo, err := scanDir(dir, parent == "")
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "%s: %s: %s\n", argv0, pkg, err) errorf("%s: %v\n", pkg, err)
errors = true
visit[pkg] = done visit[pkg] = done
return return
} }
if len(dirInfo.goFiles) == 0 { if len(dirInfo.goFiles) == 0 {
fmt.Fprintf(os.Stderr, "%s: %s: package has no files\n", argv0, pkg) errorf("%s: package has no files\n", pkg)
errors = true
visit[pkg] = done visit[pkg] = done
return return
} }
...@@ -200,22 +213,16 @@ func install(pkg, parent string) { ...@@ -200,22 +213,16 @@ func install(pkg, parent string) {
// Install this package. // Install this package.
if !errors { if !errors {
isCmd := dirInfo.pkgName == "main" isCmd := dirInfo.pkgName == "main"
if err := domake(dir, pkg, proot, local, isCmd); err != nil { if err := domake(dir, pkg, proot, isCmd); err != nil {
fmt.Fprintf(os.Stderr, "%s: installing %s: %s\n", argv0, pkg, err) errorf("installing: %v\n", err)
errors = true } else if remote && *logPkgs {
} else if !local && *logPkgs { // mark package as installed in $GOROOT/goinstall.log
// mark this package as installed in $GOROOT/goinstall.log
logPackage(pkg) logPackage(pkg)
} }
} }
visit[pkg] = done visit[pkg] = done
} }
// Is this a local path? /foo ./foo ../foo . ..
func isLocalPath(s string) bool {
const sep = string(filepath.Separator)
return strings.HasPrefix(s, sep) || strings.HasPrefix(s, "."+sep) || strings.HasPrefix(s, ".."+sep) || s == "." || s == ".."
}
// Is this a standard package path? strings container/vector etc. // Is this a standard package path? strings container/vector etc.
// Assume that if the first element has a dot, it's a domain name // Assume that if the first element has a dot, it's a domain name
...@@ -245,9 +252,7 @@ func genRun(dir string, stdin []byte, cmd []string, quiet bool) os.Error { ...@@ -245,9 +252,7 @@ func genRun(dir string, stdin []byte, cmd []string, quiet bool) os.Error {
return err return err
} }
p, err := exec.Run(bin, cmd, os.Environ(), dir, exec.Pipe, exec.Pipe, exec.MergeWithStdout) p, err := exec.Run(bin, cmd, os.Environ(), dir, exec.Pipe, exec.Pipe, exec.MergeWithStdout)
if *verbose { vlogf("%s: %s %s\n", dir, bin, strings.Join(cmd[1:], " "))
fmt.Fprintf(os.Stderr, "%s: %s; %s %s\n", argv0, dir, bin, strings.Join(cmd[1:], " "))
}
if err != nil { if err != nil {
return err return err
} }
...@@ -257,7 +262,6 @@ func genRun(dir string, stdin []byte, cmd []string, quiet bool) os.Error { ...@@ -257,7 +262,6 @@ func genRun(dir string, stdin []byte, cmd []string, quiet bool) os.Error {
}() }()
var buf bytes.Buffer var buf bytes.Buffer
io.Copy(&buf, p.Stdout) io.Copy(&buf, p.Stdout)
io.Copy(&buf, p.Stdout)
w, err := p.Wait(0) w, err := p.Wait(0)
p.Close() p.Close()
if err != nil { if err != nil {
......
...@@ -14,26 +14,14 @@ import ( ...@@ -14,26 +14,14 @@ import (
) )
// domake builds the package in dir. // domake builds the package in dir.
// If local is false, the package was copied from an external system.
// For non-local packages or packages without Makefiles,
// domake generates a standard Makefile and passes it // domake generates a standard Makefile and passes it
// to make on standard input. // to make on standard input.
func domake(dir, pkg string, root *pkgroot, local, isCmd bool) (err os.Error) { func domake(dir, pkg string, root *pkgroot, isCmd bool) (err os.Error) {
needMakefile := true makefile, err := makeMakefile(dir, pkg, root, isCmd)
if local { if err != nil {
_, err := os.Stat(filepath.Join(dir, "Makefile"))
if err == nil {
needMakefile = false
}
}
cmd := []string{"bash", "gomake"}
var makefile []byte
if needMakefile {
if makefile, err = makeMakefile(dir, pkg, root, isCmd); err != nil {
return err return err
} }
cmd = append(cmd, "-f-") cmd := []string{"bash", "gomake", "-f-"}
}
if *clean { if *clean {
cmd = append(cmd, "clean") cmd = append(cmd, "clean")
} }
...@@ -51,16 +39,8 @@ func makeMakefile(dir, pkg string, root *pkgroot, isCmd bool) ([]byte, os.Error) ...@@ -51,16 +39,8 @@ func makeMakefile(dir, pkg string, root *pkgroot, isCmd bool) ([]byte, os.Error)
targ := pkg targ := pkg
targDir := root.pkgDir() targDir := root.pkgDir()
if isCmd { if isCmd {
// use the last part of the package name only // use the last part of the package name for targ
_, targ = filepath.Split(pkg) _, targ = filepath.Split(pkg)
// if building the working dir use the directory name
if targ == "." {
d, err := filepath.Abs(dir)
if err != nil {
return nil, os.NewError("finding path: " + err.String())
}
_, targ = filepath.Split(d)
}
targDir = root.binDir() targDir = root.binDir()
} }
dirInfo, err := scanDir(dir, isCmd) dirInfo, err := scanDir(dir, isCmd)
......
...@@ -5,10 +5,12 @@ ...@@ -5,10 +5,12 @@
package main package main
import ( import (
"fmt"
"log" "log"
"os" "os"
"path/filepath" "path/filepath"
"runtime" "runtime"
"strings"
) )
var ( var (
...@@ -19,6 +21,7 @@ var ( ...@@ -19,6 +21,7 @@ var (
// set up gopath: parse and validate GOROOT and GOPATH variables // set up gopath: parse and validate GOROOT and GOPATH variables
func init() { func init() {
root := runtime.GOROOT()
p, err := newPkgroot(root) p, err := newPkgroot(root)
if err != nil { if err != nil {
log.Fatalf("Invalid GOROOT %q: %v", root, err) log.Fatalf("Invalid GOROOT %q: %v", root, err)
...@@ -105,13 +108,42 @@ func (r *pkgroot) hasPkg(name string) bool { ...@@ -105,13 +108,42 @@ func (r *pkgroot) hasPkg(name string) bool {
// TODO(adg): check object version is consistent // TODO(adg): check object version is consistent
} }
// findPkgroot searches each of the gopath roots
// for the source code for the given import path. var ErrPackageNotFound = os.NewError("package could not be found locally")
func findPkgroot(importPath string) *pkgroot {
// findPackageRoot takes an import or filesystem path and returns the
// root where the package source should be and the package import path.
func findPackageRoot(path string) (root *pkgroot, pkg string, err os.Error) {
if isLocalPath(path) {
if path, err = filepath.Abs(path); err != nil {
return
}
for _, r := range gopath {
rpath := r.srcDir() + filepath.SeparatorString
if !strings.HasPrefix(path, rpath) {
continue
}
root = r
pkg = path[len(rpath):]
return
}
err = fmt.Errorf("path %q not inside a GOPATH", path)
return
}
root = defaultRoot
pkg = path
for _, r := range gopath { for _, r := range gopath {
if r.hasSrcDir(importPath) { if r.hasSrcDir(path) {
return r root = r
return
} }
} }
return defaultRoot err = ErrPackageNotFound
return
}
// Is this a local path? /foo ./foo ../foo . ..
func isLocalPath(s string) bool {
const sep = string(filepath.Separator)
return strings.HasPrefix(s, sep) || strings.HasPrefix(s, "."+sep) || strings.HasPrefix(s, ".."+sep) || s == "." || s == ".."
} }
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