diff --git a/Gopkg.lock b/Gopkg.lock index d06653d..2b101e4 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -27,16 +27,18 @@ version = "v1.3.1" [[projects]] + branch = "master" name = "github.com/golang/dep" packages = [ + ".", "gps", "gps/internal/pb", "gps/paths", "gps/pkgtree", "internal/fs" ] - revision = "37d9ea0ac16f0e0a05afc3b60e1ac8c364b6c329" - version = "v0.4.1" + revision = "fa9f32339c8855ebe7e7bc66e549036a7e06d37a" + source = "https://github.com/CrushedPixel/dep" [[projects]] branch = "master" @@ -77,11 +79,7 @@ [[projects]] branch = "master" name = "golang.org/x/net" - packages = [ - "context", - "html", - "html/atom" - ] + packages = ["context"] revision = "dc948dff8834a7fe1ca525f8d04e261c2b56e70d" [[projects]] @@ -99,6 +97,6 @@ [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "a317572c6678a0a7322ac0c531cf3f8bc12c055c48d24411227b930158a2f8bc" + inputs-digest = "5ba2894346289bbb0f893f7893e38248bd7621f1a558d4927a46063b1ad3a9d2" solver-name = "gps-cdcl" solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml index a829c0f..e8a96ac 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -26,16 +26,9 @@ [[constraint]] + branch = "master" name = "github.com/golang/dep" - version = "0.4.1" - -[[constraint]] - name = "github.com/pelletier/go-toml" - version = "1.1.0" - -[[constraint]] - name = "github.com/pkg/errors" - version = "0.8.0" + source = "https://github.com/CrushedPixel/dep" [prune] go-tests = true diff --git a/deps.go b/deps.go new file mode 100644 index 0000000..0cee618 --- /dev/null +++ b/deps.go @@ -0,0 +1,52 @@ +package main + +import ( + "fmt" +) + +const depNixFormat = ` + { + goPackagePath = "%s"; + fetch = { + type = "%s"; + url = "%s"; + rev = "%s"; + sha256 = "%s"; + }; + }` + +// Dep represents a project dependency +// to write to deps.nix. +type Dep struct { + PackagePath string + VCS string + URL string + Revision string + SHA256 string +} + +// toNix converts d into a nix set +// for use in the generated deps.nix. +func (d *Dep) toNix() string { + return fmt.Sprintf(depNixFormat, + d.PackagePath, d.VCS, d.URL, + d.Revision, d.SHA256) +} + +const depsFileHeader = `# file generated from Gopkg.lock using dep2nix (https://github.com/nixcloud/dep2nix) +[` +const depsFileFooter = ` +]` + +type Deps []*Dep + +// toNix converts d into a deps.nix file +// for use with pkgs.buildGoPackage. +func (d Deps) toNix() string { + nix := depsFileHeader + for _, dep := range d { + nix += dep.toNix() + } + nix += depsFileFooter + return nix +} diff --git a/deps.nix b/deps.nix index 326afae..fc9280e 100644 --- a/deps.nix +++ b/deps.nix @@ -46,9 +46,9 @@ goPackagePath = "github.com/golang/dep"; fetch = { type = "git"; - url = "https://github.com/golang/dep"; - rev = "37d9ea0ac16f0e0a05afc3b60e1ac8c364b6c329"; - sha256 = "0183xq5l4sinnclynv6xi85vmk69mqpy5wjfsgh8bxwziq3vkd7y"; + url = "https://github.com/CrushedPixel/dep"; + rev = "fa9f32339c8855ebe7e7bc66e549036a7e06d37a"; + sha256 = "1knaxs1ji1b0b68393f24r8qzvahxz9x7rqwc8jsjlshvpz0hlm6"; }; } diff --git a/lock.go b/lock.go deleted file mode 100644 index 1997a20..0000000 --- a/lock.go +++ /dev/null @@ -1,203 +0,0 @@ -// Copyright 2016 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package main - -import ( - "bytes" - "encoding/hex" - "io" - "sort" - - "github.com/golang/dep/gps" - "github.com/pelletier/go-toml" - "github.com/pkg/errors" -) - -// LockName is the lock file name used by dep. -const LockName = "Gopkg.lock" - -// Lock holds lock file data and implements gps.Lock. -type Lock struct { - SolveMeta SolveMeta - P []gps.LockedProject -} - -// SolveMeta holds solver meta data. -type SolveMeta struct { - InputsDigest []byte - AnalyzerName string - AnalyzerVersion int - SolverName string - SolverVersion int -} - -type rawLock struct { - SolveMeta solveMeta `toml:"solve-meta"` - Projects []rawLockedProject `toml:"projects"` -} - -type solveMeta struct { - InputsDigest string `toml:"inputs-digest"` - AnalyzerName string `toml:"analyzer-name"` - AnalyzerVersion int `toml:"analyzer-version"` - SolverName string `toml:"solver-name"` - SolverVersion int `toml:"solver-version"` -} - -type rawLockedProject struct { - Name string `toml:"name"` - Branch string `toml:"branch,omitempty"` - Revision string `toml:"revision"` - Version string `toml:"version,omitempty"` - Source string `toml:"source,omitempty"` - Packages []string `toml:"packages"` -} - -func readLock(r io.Reader) (*Lock, error) { - buf := &bytes.Buffer{} - _, err := buf.ReadFrom(r) - if err != nil { - return nil, errors.Wrap(err, "Unable to read byte stream") - } - - raw := rawLock{} - err = toml.Unmarshal(buf.Bytes(), &raw) - if err != nil { - return nil, errors.Wrap(err, "Unable to parse the lock as TOML") - } - - return fromRawLock(raw) -} - -func fromRawLock(raw rawLock) (*Lock, error) { - var err error - l := &Lock{ - P: make([]gps.LockedProject, len(raw.Projects)), - } - - l.SolveMeta.InputsDigest, err = hex.DecodeString(raw.SolveMeta.InputsDigest) - if err != nil { - return nil, errors.Errorf("invalid hash digest in lock's memo field") - } - - l.SolveMeta.AnalyzerName = raw.SolveMeta.AnalyzerName - l.SolveMeta.AnalyzerVersion = raw.SolveMeta.AnalyzerVersion - l.SolveMeta.SolverName = raw.SolveMeta.SolverName - l.SolveMeta.SolverVersion = raw.SolveMeta.SolverVersion - - for i, ld := range raw.Projects { - r := gps.Revision(ld.Revision) - - var v gps.Version = r - if ld.Version != "" { - if ld.Branch != "" { - return nil, errors.Errorf("lock file specified both a branch (%s) and version (%s) for %s", ld.Branch, ld.Version, ld.Name) - } - v = gps.NewVersion(ld.Version).Pair(r) - } else if ld.Branch != "" { - v = gps.NewBranch(ld.Branch).Pair(r) - } else if r == "" { - return nil, errors.Errorf("lock file has entry for %s, but specifies no branch or version", ld.Name) - } - - id := gps.ProjectIdentifier{ - ProjectRoot: gps.ProjectRoot(ld.Name), - Source: ld.Source, - } - l.P[i] = gps.NewLockedProject(id, v, ld.Packages) - } - - return l, nil -} - -// InputsDigest returns the hash of inputs which produced this lock data. -func (l *Lock) InputsDigest() []byte { - return l.SolveMeta.InputsDigest -} - -// Projects returns the list of LockedProjects contained in the lock data. -func (l *Lock) Projects() []gps.LockedProject { - return l.P -} - -// HasProjectWithRoot checks if the lock contains a project with the provided -// ProjectRoot. -// -// This check is O(n) in the number of projects. -func (l *Lock) HasProjectWithRoot(root gps.ProjectRoot) bool { - for _, p := range l.P { - if p.Ident().ProjectRoot == root { - return true - } - } - - return false -} - -// toRaw converts the manifest into a representation suitable to write to the lock file -func (l *Lock) toRaw() rawLock { - raw := rawLock{ - SolveMeta: solveMeta{ - InputsDigest: hex.EncodeToString(l.SolveMeta.InputsDigest), - AnalyzerName: l.SolveMeta.AnalyzerName, - AnalyzerVersion: l.SolveMeta.AnalyzerVersion, - SolverName: l.SolveMeta.SolverName, - SolverVersion: l.SolveMeta.SolverVersion, - }, - Projects: make([]rawLockedProject, len(l.P)), - } - - sort.Slice(l.P, func(i, j int) bool { - return l.P[i].Ident().Less(l.P[j].Ident()) - }) - - for k, lp := range l.P { - id := lp.Ident() - ld := rawLockedProject{ - Name: string(id.ProjectRoot), - Source: id.Source, - Packages: lp.Packages(), - } - - v := lp.Version() - ld.Revision, ld.Branch, ld.Version = gps.VersionComponentStrings(v) - - raw.Projects[k] = ld - } - - return raw -} - -// MarshalTOML serializes this lock into TOML via an intermediate raw form. -func (l *Lock) MarshalTOML() ([]byte, error) { - raw := l.toRaw() - var buf bytes.Buffer - enc := toml.NewEncoder(&buf).ArraysWithOneElementPerLine(true) - err := enc.Encode(raw) - return buf.Bytes(), errors.Wrap(err, "Unable to marshal lock to TOML string") -} - -// LockFromSolution converts a gps.Solution to dep's representation of a lock. -// -// Data is defensively copied wherever necessary to ensure the resulting *lock -// shares no memory with the original lock. -func LockFromSolution(in gps.Solution) *Lock { - h, p := in.InputsDigest(), in.Projects() - - l := &Lock{ - SolveMeta: SolveMeta{ - InputsDigest: make([]byte, len(h)), - AnalyzerName: in.AnalyzerName(), - AnalyzerVersion: in.AnalyzerVersion(), - SolverName: in.SolverName(), - SolverVersion: in.SolverVersion(), - }, - P: make([]gps.LockedProject, len(p)), - } - - copy(l.SolveMeta.InputsDigest, h) - copy(l.P, p) - return l -} diff --git a/main.go b/main.go index 0a2b809..782b9b8 100644 --- a/main.go +++ b/main.go @@ -5,28 +5,18 @@ package main import ( - "bufio" "bytes" "encoding/json" "flag" "fmt" - "github.com/pelletier/go-toml" - "io" + "github.com/golang/dep" + "github.com/golang/dep/gps" + "io/ioutil" "log" - "net/http" "os" "os/exec" "path/filepath" - "strings" - - "golang.org/x/net/html" -) - -var ( - data *os.File - part []byte - count int - buffer *bytes.Buffer + "time" ) var ( @@ -34,215 +24,121 @@ var ( outputFileFlag = flag.String("o", "deps.nix", "output nix file") ) -// FindRealPath queries url to try to locate real vcs path -// The meta tag has the form: -// -// -// For example, -// import "example.org/pkg/foo" -// -// will result in the following requests: -// https://example.org/pkg/foo?go-get=1 (preferred) -// http://example.org/pkg/foo?go-get=1 (fallback, only with -insecure) -func FindRealPath(url string) (string, error) { - // golang http client will follow redirects, so if http don't work should query https if 301 redirect - resp, err := http.Get("http://" + url + "?go-get=1") - if err != nil { - return "", fmt.Errorf("Failed to query %v", url) - } - defer resp.Body.Close() - - z := html.NewTokenizer(resp.Body) - for { - tt := z.Next() - - switch { - case tt == html.ErrorToken: - // End of the document, we're done - return "", fmt.Errorf("end of body") - case tt == html.StartTagToken: - t := z.Token() - - // Check if the token is an tag - isMeta := t.Data == "meta" - if !isMeta { - continue - } - - // Extract vcs url - for _, a := range t.Attr { - if a.Key == "name" && a.Val == "go-import" { - var content []string - for _, b := range t.Attr { - if b.Key == "content" { - content = strings.Fields(b.Val) - } - } - - if len(content) < 3 { - return "", fmt.Errorf("could not find content attribute for meta tag") - } - - // go help importpath - // content[0] : original import path - // content[1] : vcs type - // content[2] : vcs url - - // expand for non git vcs - if content[1] == "git" { - return content[2], nil - } - return "", fmt.Errorf("could not find git url") - } - } - } - } - -} - -// IsCommonPath checks to see if it's one of the common vcs locations go get supports -// see `go help importpath` -func IsCommonPath(url string) bool { - // from `go help importpath` - commonPaths := [...]string{ - "bitbucket.org", - "github.com", - "launchpad.net", - "hub.jazz.net", - } - for _, path := range commonPaths { - if strings.Split(url, "/")[0] == path { - return true - } - } - return false -} - func main() { flag.Parse() + logger := log.New(os.Stdout, "", 0) + + defer func(start time.Time) { + logger.Printf("Finished execution in %s.\n", time.Since(start).Round(time.Second).String()) + }(time.Now()) + // parse input file path inFile, err := filepath.Abs(*inputFileFlag) if err != nil { - log.Fatalln("Invalid input file path:", err.Error()) + logger.Fatalln("Invalid input file path:", err.Error()) } + // parse output file path outFile, err := filepath.Abs(*outputFileFlag) if err != nil { - log.Fatalln("Invalid output file path:", err.Error()) + logger.Fatalln("Invalid output file path:", err.Error()) } - data, err = os.Open(inFile) + // parse lock file + f, err := os.Open(inFile) if err != nil { - log.Fatalln("Error opening input file:", err.Error()) - } - defer data.Close() - - reader := bufio.NewReader(data) - buffer = bytes.NewBuffer(make([]byte, 0)) - part = make([]byte, 1024) - - for { - if count, err = reader.Read(part); err != nil { - break - } - buffer.Write(part[:count]) - } - if err != io.EOF { - log.Fatalln("Error reading input file:", err.Error()) + logger.Fatalln("Error opening input file:", err.Error()) } + defer f.Close() - raw := rawLock{} - err = toml.Unmarshal(buffer.Bytes(), &raw) + lock, err := dep.ReadLock(f) if err != nil { - log.Fatalln("Error parsing lock file:", err.Error()) + logger.Fatalln("Error parsing lock file:", err.Error()) } - //fmt.Println(raw.Projects) - fmt.Printf("Found %d libraries to process: \n", len(raw.Projects)) + logger.Printf("Found %d projects to process.\n", len(lock.Projects())) - for i := 0; i < len(raw.Projects); i++ { - t := raw.Projects[i] - fmt.Println(t.Name) + // create temporary directory for source manager cache + cachedir, err := ioutil.TempDir(os.TempDir(), "") + if err != nil { + logger.Fatalln(err) } - fmt.Print("\n\n") - - var godepnix string - - godepnix += ` - # file automatically generated from Gopkg.lock with https://github.com/nixcloud/dep2nix (golang dep) - [ - ` - - for i := 0; i < len(raw.Projects); i++ { - - t := raw.Projects[i] + defer os.RemoveAll(cachedir) - var url string - // check if it's a common git path `go get` supports and if not find real path - if !IsCommonPath(t.Name) { - realURL, err := FindRealPath(t.Name) - - if err != nil { - //fmt.Printf("could not find real git url for import path %v: %+v\n", t.Name, err) - log.Fatal(err) - } - url = realURL - } else { - url = "https://" + t.Name - } + // create source manager + sm, err := gps.NewSourceManager(gps.SourceManagerConfig{ + Cachedir: cachedir, + Logger: logger, + }) + if err != nil { + logger.Fatalln(err) + } - fmt.Println(" * Processing: \"" + t.Name + "\"") + // Process all projects, converting them into deps + var deps Deps + for _, project := range lock.Projects() { + fmt.Printf("* Processing: \"%s\"\n", project.Ident().ProjectRoot) - cmd := exec.Command("nix-prefetch-git", url, "--rev", t.Revision, "--quiet") - var out bytes.Buffer - cmd.Stdout = &out - err := cmd.Run() + // get repository for project + src, err := sm.SourceFor(project.Ident()) if err != nil { - log.Fatal(err) + logger.Fatalln(err) } + repo := src.Repo() - type response struct { - Url string `json:"url"` - Rev string `json:"rev"` - Date string `json:"date"` - SHA256 string `json:"sha256"` - FetchSubmodules bool `json:"fetchSubmodules"` + // get vcs type + typ := string(repo.Vcs()) + if typ != "git" { + logger.Fatalln("non-git repositories are not supported yet") } - var jsonStr = out.String() - var res response - err1 := json.Unmarshal([]byte(jsonStr), &res) + // check out repository + if err := repo.Get(); err != nil { + logger.Fatalln("error fetching project:", err.Error()) + } + // get resolved revision + rev, err := src.Repo().Version() if err != nil { - fmt.Println("There was a problem in decoding the result from nix-prefetch-git returned JSON:") - fmt.Println(jsonStr) - fmt.Println(err1) - os.Exit(1) + logger.Fatal(err) } - //fmt.Println(res) - - godepnix += ` - { - goPackagePath = "` + t.Name + `"; - fetch = { - type = "git"; - url = "` + url + `"; - rev = "` + res.Rev + `"; - sha256 = "` + res.SHA256 + `"; - }; - } - ` + // use locally fetched repository as remote for nix-prefetch-git + // to it being downloaded from the remote again + localUrl := fmt.Sprintf("file://%s", repo.LocalPath()) + // use nix-prefetch-git to get the hash of the checkout + cmd := exec.Command("nix-prefetch-git", "--url", localUrl, "--rev", rev, "--quiet") + var out bytes.Buffer + cmd.Stdout = &out + if err := cmd.Run(); err != nil { + logger.Fatal(err) + } + // extract hash from response + res := &struct { + SHA256 string `json:"sha256"` + }{} + json.Unmarshal(out.Bytes(), res) + + // create dep instance + deps = append(deps, &Dep{ + PackagePath: string(project.Ident().ProjectRoot), + VCS: string(typ), + URL: src.Repo().Remote(), + Revision: rev, + SHA256: res.SHA256, + }) } - godepnix += "\n]" - //fmt.Println(godepnix) - - f, _ := os.Create(outFile) - defer f.Close() + // write deps to output file + out, err := os.Create(outFile) + if err != nil { + logger.Fatalln("Error creating output file:", err.Error()) + } + defer out.Close() - _, _ = f.WriteString(godepnix) - fmt.Printf("\n -> Wrote %s, everything fine!\n", outFile) + if _, err := out.WriteString(deps.toNix()); err != nil { + logger.Fatalln("Error writing output file:", err.Error()) + } - os.Exit(0) + fmt.Printf("\n -> Wrote to %s.\n", outFile) }