Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
.goop
.vendor

# Compiled Object files, Static and Dynamic libs (Shared Objects)
Expand Down
1 change: 1 addition & 0 deletions Goopfile
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
github.com/onsi/ginkgo/ginkgo
github.com/onsi/gomega
code.google.com/p/go.tools/go/vcs
github.com/alecthomas/kingpin
6 changes: 5 additions & 1 deletion Goopfile.lock
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
code.google.com/p/go.net #937a34c9de13c766c814510f76bca091dee06028
code.google.com/p/go.tools/go/vcs #c5e1d3cb5e8e8c1c7c0738642555d7669d1d41e0
github.com/alecthomas/kingpin #55a69a226d28a43b913720f5d765706d49c8fa9e
github.com/alecthomas/units #6b4e7dc5e3143b85ea77909c72caf89416fc2915
github.com/onsi/ginkgo/ginkgo #12446bbcc74950944c897e32ea99cc8d058f9c92
github.com/onsi/gomega #036273e6f643552b16fe7a6464afd2ae0324992e
code.google.com/p/go.tools/go/vcs #c5e1d3cb5e8e8c1c7c0738642555d7669d1d41e0
golang.org/x/net #b6fdb7d8a4ccefede406f8fe0f017fb58265054c
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,11 @@ A dependency manager for Go (golang), inspired by Bundler. It is different from
github.com/gorilla/mux !git@github.com:nitrous-io/mux.git // override repo url
```

3. Run `goop install`. This will install packages inside a subdirectory called `.vendor` and create `Goopfile.lock`, recording exact versions used for each package and its dependencies. Subsequent `goop install` runs will ignore `Goopfile` and install the versions specified in `Goopfile.lock`. You should check this file in to your source version control. It's a good idea to add `.vendor` to your version control system's ignore settings (e.g. `.gitignore`).
3. Run `goop install`. This will install packages inside a subdirectory called `.vendor` and create `Goopfile.lock`,
recording exact versions used for each package and its dependencies. Subsequent `goop install` runs will ignore `Goopfile`
and install the versions specified in `Goopfile.lock`. You should check this file in to your source version control.
It's a good idea to add `.vendor` to your version control system's ignore settings (e.g. `.gitignore`).
Use ``goop install --path my/deps`` to install to another directory.

4. Run commands using `goop exec` (e.g. `goop exec make`). This will execute your command in an environment that has correct `GOPATH` and `PATH` set.

Expand Down
87 changes: 74 additions & 13 deletions goop/goop.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
package goop

import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"os"
"os/exec"
"path"
"path/filepath"
"sort"
"strings"

Expand All @@ -16,6 +19,33 @@ import (
"github.com/nitrous-io/goop/pkg/env"
)

type GoopConfig struct {
GoopPath string
}

func (gc *GoopConfig) load(filename string) error {
config, err := ioutil.ReadFile(filename)
if err == nil {
// ignore if file does not exist
if err := json.Unmarshal(config, &gc); err != nil {
return err
}
}
return nil
}

func (gc *GoopConfig) write(filename string) error {
// try to create directory first
os.MkdirAll(path.Dir(filename), 0755)

str, err := json.MarshalIndent(gc, "", " ")
if err != nil {
return err
}
ioutil.WriteFile(filename, str, 0755)
return nil
}

type UnsupportedVCSError struct {
VCS string
}
Expand All @@ -29,21 +59,37 @@ type Goop struct {
stdin io.Reader
stdout io.Writer
stderr io.Writer

config *GoopConfig
configFilename string
}

func NewGoop(dir string, stdin io.Reader, stdout io.Writer, stderr io.Writer) *Goop {
return &Goop{dir: dir, stdin: stdin, stdout: stdout, stderr: stderr}
func NewGoop(dir string, stdin io.Reader, stdout io.Writer, stderr io.Writer) (*Goop, error) {
g := &Goop{
dir: dir,
stdin: stdin,
stdout: stdout,
stderr: stderr,
config: &GoopConfig{},
configFilename: path.Join(dir, ".goop", "config"),
}

if err := g.config.load(g.configFilename); err != nil {
return nil, err
}

return g, nil
}

func (g *Goop) patchedEnv(replaceGopath bool) env.Env {
e := env.NewEnv()

binPath := path.Join(g.vendorDir(), "bin")
binPath := path.Join(g.absVendorDir(), "bin")

if replaceGopath {
e["GOPATH"] = g.vendorDir()
e["GOPATH"] = g.absVendorDir()
} else {
e.Prepend("GOPATH", g.vendorDir())
e.Prepend("GOPATH", g.absVendorDir())
}
e["GOBIN"] = binPath
e.Prepend("PATH", binPath)
Expand All @@ -54,15 +100,15 @@ func (g *Goop) patchedEnv(replaceGopath bool) env.Env {
func (g *Goop) PrintEnv() {
gopath := os.Getenv("GOPATH")
if gopath == "" {
g.stdout.Write([]byte(fmt.Sprintf("GOPATH=%s\n", g.vendorDir())))
g.stdout.Write([]byte(fmt.Sprintf("GOPATH=%s\n", g.absVendorDir())))
} else {
g.stdout.Write([]byte(fmt.Sprintf("GOPATH=%s:%s\n", g.vendorDir(), gopath)))
g.stdout.Write([]byte(fmt.Sprintf("GOPATH=%s:%s\n", g.absVendorDir(), gopath)))
}
g.stdout.Write([]byte(fmt.Sprintf("PATH=%s:%s\n", path.Join(g.vendorDir(), "bin"), os.Getenv("PATH"))))
g.stdout.Write([]byte(fmt.Sprintf("PATH=%s:%s\n", path.Join(g.absVendorDir(), "bin"), os.Getenv("PATH"))))
}

func (g *Goop) Exec(name string, args ...string) error {
vname := path.Join(g.vendorDir(), "bin", name)
vname := path.Join(g.absVendorDir(), "bin", name)
_, err := os.Stat(vname)
if err == nil {
name = vname
Expand All @@ -75,7 +121,13 @@ func (g *Goop) Exec(name string, args ...string) error {
return cmd.Run()
}

func (g *Goop) Install() error {
func (g *Goop) Install(installPath string) error {
if installPath != "" {
// set install path in config file
g.config.GoopPath = installPath
g.config.write(g.configFilename)
}

writeLockFile := false
f, err := os.Open(path.Join(g.dir, "Goopfile.lock"))
if err == nil {
Expand Down Expand Up @@ -106,8 +158,8 @@ func (g *Goop) parseAndInstall(goopfile *os.File, writeLockFile bool) error {
return err
}

srcPath := path.Join(g.vendorDir(), "src")
tmpGoPath := path.Join(g.vendorDir(), "tmp")
srcPath := path.Join(g.absVendorDir(), "src")
tmpGoPath := path.Join(g.absVendorDir(), "tmp")
tmpSrcPath := path.Join(tmpGoPath, "src")

err = os.RemoveAll(tmpGoPath)
Expand Down Expand Up @@ -302,7 +354,16 @@ func (g *Goop) parseAndInstall(goopfile *os.File, writeLockFile bool) error {
}

func (g *Goop) vendorDir() string {
return path.Join(g.dir, ".vendor")
if g.config != nil && g.config.GoopPath != "" {
return g.config.GoopPath
} else {
return ".vendor"
}
}

func (g *Goop) absVendorDir() string {
abs, _ := filepath.Abs(g.vendorDir()) // TODO handle error
return abs
}

func (g *Goop) currentRev(vcsCmd string, path string) (string, error) {
Expand Down
100 changes: 60 additions & 40 deletions main.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,29 @@
package main

import (
"errors"
"github.com/alecthomas/kingpin"
"github.com/nitrous-io/goop/colors"
"github.com/nitrous-io/goop/goop"
"os"
"path"
"strconv"
"strings"
)

"github.com/nitrous-io/goop/colors"
"github.com/nitrous-io/goop/goop"
var (
app = kingpin.New("goop", "A a tool for managing Go dependencies.")

installCmd = app.Command("install", "Install the dependencies specified by Goopfile or Goopfile.lock")
installPath = installCmd.Flag("path", "Install dependencies to this directory").String()

updateCmd = app.Command("update", "Update dependencies to their latest versions")
envCmd = app.Command("env", "Print GOPATH and PATH environment variables, with the vendor path prepended")

execCmd = app.Command("exec", "Execute a command in the context of the installed dependencies")
execArgs = StringList(execCmd.Arg("command", "Command and arguments to execute").Required())

goCmd = app.Command("go", "Execute a go command in the context of the installed dependencies")
goArgs = StringList(goCmd.Arg("command", "Command and arguments to execute").Required())
)

func main() {
Expand All @@ -19,34 +34,38 @@ func main() {
os.Stderr.WriteString(colors.Error + name + ": failed to determine present working directory!" + colors.Reset + "\n")
}

g := goop.NewGoop(path.Join(pwd), os.Stdin, os.Stdout, os.Stderr)

if len(os.Args) < 2 {
printUsage()
g, err := goop.NewGoop(path.Join(pwd), os.Stdin, os.Stdout, os.Stderr)
if err != nil {
os.Stderr.WriteString(colors.Error + name + ": " + err.Error() + colors.Reset + "\n")
os.Exit(1)
}

cmd := os.Args[1]
switch cmd {
case "help":
printUsage()
case "install":
err = g.Install()
case "update":
switch kingpin.MustParse(app.Parse(os.Args[1:])) {

case installCmd.FullCommand():
err = g.Install(*installPath)

case updateCmd.FullCommand():
err = g.Update()
case "exec":
if len(os.Args) < 3 {
printUsage()

case envCmd.FullCommand():
g.PrintEnv()

case execCmd.FullCommand():
args := *execArgs
if len(args) > 0 {
err = g.Exec(args[0], args[1:]...)
}
err = g.Exec(os.Args[2], os.Args[3:]...)
case "go":
if len(os.Args) < 3 {
printUsage()

case goCmd.FullCommand():
args := *goArgs
if len(args) > 0 {
err = g.Exec("go", args...)
}
err = g.Exec("go", os.Args[2:]...)
case "env":
g.PrintEnv()

default:
err = errors.New(`unrecognized command "` + cmd + `"`)
app.Usage(os.Stdout)

}

if err != nil {
Expand All @@ -68,22 +87,23 @@ func main() {
}
}

func printUsage() {
os.Stdout.WriteString(strings.TrimSpace(usage) + "\n\n")
os.Exit(0)
}
type stringList []string

const usage = `
Goop is a tool for managing Go dependencies.
func (l *stringList) Set(value string) error {
*l = append(*l, value)
return nil
}

goop command [arguments]
func (l *stringList) String() string {
return ""
}

The commands are:
func (l *stringList) IsCumulative() bool {
return true
}

install install the dependencies specified by Goopfile or Goopfile.lock
update update dependencies to their latest versions
env print GOPATH and PATH environment variables, with the vendor path prepended
exec execute a command in the context of the installed dependencies
go execute a go command in the context of the installed dependencies
help print this message
`
func StringList(s kingpin.Settings) (target *[]string) {
target = new([]string)
s.SetValue((*stringList)(target))
return
}