Skip to content
This repository was archived by the owner on Sep 9, 2020. It is now read-only.

Commit d4cbaa4

Browse files
author
Edward Muller
authored
Merge pull request #18 from sdboyer/continue-init
More or less complete init (still with stubs)
2 parents 20bf102 + d6112ff commit d4cbaa4

File tree

2 files changed

+309
-21
lines changed

2 files changed

+309
-21
lines changed

init.go

Lines changed: 282 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
// Copyright 2016 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
15
package main
26

37
import (
@@ -41,20 +45,24 @@ var initCmd = &command{
4145
`,
4246
}
4347

44-
func determineProjectRoot(path string) (string, error) {
48+
// determineProjectRoot takes an absolute path and compares it against declared
49+
// GOPATH(s) to determine what portion of the input path should be treated as an
50+
// import path - as a project root.
51+
//
52+
// The second returned string indicates which GOPATH value was used.
53+
func determineProjectRoot(path string) (string, string, error) {
4554
gopath := os.Getenv("GOPATH")
4655
for _, gp := range filepath.SplitList(gopath) {
4756
srcprefix := filepath.Join(gp, "src") + string(filepath.Separator)
4857
if strings.HasPrefix(path, srcprefix) {
4958
// filepath.ToSlash because we're dealing with an import path now,
5059
// not an fs path
51-
return filepath.ToSlash(strings.TrimPrefix(path, srcprefix)), nil
60+
return filepath.ToSlash(strings.TrimPrefix(path, srcprefix)), gp, nil
5261
}
5362
}
54-
return "", fmt.Errorf("%s not in any $GOPATH", path)
63+
return "", "", fmt.Errorf("%s not in any $GOPATH", path)
5564
}
5665

57-
// TODO: Error when there is a lockfile, but no manifest?
5866
func runInit(args []string) error {
5967
if len(args) > 1 {
6068
return fmt.Errorf("Too many args: %d", len(args))
@@ -71,18 +79,27 @@ func runInit(args []string) error {
7179
}
7280

7381
mf := filepath.Join(p, manifestName)
82+
lf := filepath.Join(p, lockName)
7483

7584
// TODO: Lstat ? Do we care?
76-
_, err = os.Stat(mf)
77-
if err == nil {
78-
return fmt.Errorf("Manifest file '%s' already exists", mf)
85+
_, merr := os.Stat(mf)
86+
if merr == nil {
87+
return fmt.Errorf("Manifest file %q already exists", mf)
7988
}
80-
if os.IsNotExist(err) {
81-
pr, err := determineProjectRoot(p)
89+
_, lerr := os.Stat(lf)
90+
91+
if os.IsNotExist(merr) {
92+
if lerr == nil {
93+
return fmt.Errorf("Invalid state: manifest %q does not exist, but lock %q does.", mf, lf)
94+
} else if !os.IsNotExist(lerr) {
95+
return errors.Wrap(lerr, "stat lockfile")
96+
}
97+
98+
cpr, gopath, err := determineProjectRoot(p)
8299
if err != nil {
83100
return errors.Wrap(err, "determineProjectRoot")
84101
}
85-
pkgT, err := gps.ListPackages(p, pr)
102+
pkgT, err := gps.ListPackages(p, cpr)
86103
if err != nil {
87104
return errors.Wrap(err, "gps.ListPackages")
88105
}
@@ -91,7 +108,16 @@ func runInit(args []string) error {
91108
return errors.Wrap(err, "getSourceManager")
92109
}
93110
defer sm.Release()
94-
m := newRawManifest()
111+
112+
// TODO: This is just wrong, need to figure out manifest file structure
113+
m := manifest{
114+
Dependencies: make(gps.ProjectConstraints),
115+
}
116+
117+
processed := make(map[gps.ProjectRoot][]string)
118+
packages := make(map[string]bool)
119+
notondisk := make(map[gps.ProjectRoot]bool)
120+
ondisk := make(map[gps.ProjectRoot]gps.Version)
95121
for _, v := range pkgT.Packages {
96122
// TODO: Some errors maybe should not be skipped ;-)
97123
if v.Err != nil {
@@ -106,33 +132,268 @@ func runInit(args []string) error {
106132
if err != nil {
107133
return errors.Wrap(err, "sm.DeduceProjectRoot") // TODO: Skip and report ?
108134
}
109-
// TODO: This is just wrong, need to figure out manifest file structure
110-
m.Dependencies[string(pr)] = possibleProps{}
135+
136+
packages[i] = true
137+
if _, ok := processed[pr]; ok {
138+
if !contains(processed[pr], i) {
139+
processed[pr] = append(processed[pr], i)
140+
}
141+
142+
continue
143+
}
144+
processed[pr] = []string{i}
145+
146+
v, err := versionInWorkspace(pr)
147+
if err != nil {
148+
notondisk[pr] = true
149+
fmt.Printf("Could not determine version for %q, omitting from generated manifest\n", pr)
150+
continue
151+
}
152+
153+
ondisk[pr] = v
154+
pp := gps.ProjectProperties{}
155+
switch v.Type() {
156+
case "branch", "version", "rev":
157+
pp.Constraint = v
158+
case "semver":
159+
c, _ := gps.NewSemverConstraint("^" + v.String())
160+
pp.Constraint = c
161+
}
162+
163+
m.Dependencies[pr] = pp
164+
}
165+
}
166+
167+
// Explore the packages we've found for transitive deps, either
168+
// completing the lock or identifying (more) missing projects that we'll
169+
// need to ask gps to solve for us.
170+
colors := make(map[string]uint8)
171+
const (
172+
white uint8 = iota
173+
grey
174+
black
175+
)
176+
177+
// cache of PackageTrees, so we don't parse projects more than once
178+
ptrees := make(map[gps.ProjectRoot]gps.PackageTree)
179+
180+
// depth-first traverser
181+
var dft func(string) error
182+
dft = func(pkg string) error {
183+
switch colors[pkg] {
184+
case white:
185+
colors[pkg] = grey
186+
187+
pr, err := sm.DeduceProjectRoot(pkg)
188+
if err != nil {
189+
return errors.Wrap(err, "could not deduce project root for "+pkg)
190+
}
191+
192+
// We already visited this project root earlier via some other
193+
// pkg within it, and made the decision that it's not on disk.
194+
// Respect that decision, and pop the stack.
195+
if notondisk[pr] {
196+
colors[pkg] = black
197+
return nil
198+
}
199+
200+
ptree, has := ptrees[pr]
201+
if !has {
202+
// It's fine if the root does not exist - it indicates that this
203+
// project is not present in the workspace, and so we need to
204+
// solve to deal with this dep.
205+
r := filepath.Join(gopath, "src", string(pr))
206+
_, err := os.Lstat(r)
207+
if os.IsNotExist(err) {
208+
colors[pkg] = black
209+
notondisk[pr] = true
210+
return nil
211+
}
212+
213+
ptree, err = gps.ListPackages(r, string(pr))
214+
if err != nil {
215+
// Any error here other than an a nonexistent dir (which
216+
// can't happen because we covered that case above) is
217+
// probably critical, so bail out.
218+
return errors.Wrap(err, "gps.ListPackages")
219+
}
220+
}
221+
222+
rm := ptree.ExternalReach(false, false, nil)
223+
reached, ok := rm[pkg]
224+
if !ok {
225+
colors[pkg] = black
226+
// not on disk...
227+
notondisk[pr] = true
228+
return nil
229+
}
230+
231+
if _, ok := processed[pr]; ok {
232+
if !contains(processed[pr], pkg) {
233+
processed[pr] = append(processed[pr], pkg)
234+
}
235+
} else {
236+
processed[pr] = []string{pkg}
237+
}
238+
239+
// project must be on disk at this point; question is
240+
// whether we're first seeing it here, in the transitive
241+
// exploration, or if it arose in the direct dep parts
242+
if _, in := ondisk[pr]; !in {
243+
v, err := versionInWorkspace(pr)
244+
if err != nil {
245+
colors[pkg] = black
246+
notondisk[pr] = true
247+
return nil
248+
}
249+
ondisk[pr] = v
250+
}
251+
252+
// recurse
253+
for _, rpkg := range reached {
254+
if isStdLib(rpkg) {
255+
continue
256+
}
257+
258+
err := dft(rpkg)
259+
if err != nil {
260+
return err
261+
}
262+
}
263+
264+
colors[pkg] = black
265+
case grey:
266+
return fmt.Errorf("Import cycle detected on %s", pkg)
267+
}
268+
return nil
269+
}
270+
271+
// run the depth-first traversal from the set of immediate external
272+
// package imports we found in the current project
273+
for pkg := range packages {
274+
err := dft(pkg)
275+
if err != nil {
276+
return err // already errors.Wrap()'d internally
277+
}
278+
}
279+
280+
// Make an initial lock from what knowledge we've collected about the
281+
// versions on disk
282+
l := lock{
283+
P: make([]gps.LockedProject, 0, len(ondisk)),
284+
}
285+
286+
for pr, v := range ondisk {
287+
// That we have to chop off these path prefixes is a symptom of
288+
// a problem in gps itself
289+
pkgs := make([]string, 0, len(processed[pr]))
290+
prslash := string(pr) + "/"
291+
for _, pkg := range processed[pr] {
292+
if pkg == string(pr) {
293+
pkgs = append(pkgs, ".")
294+
} else {
295+
pkgs = append(pkgs, strings.TrimPrefix(pkg, prslash))
296+
}
297+
}
298+
299+
l.P = append(l.P, gps.NewLockedProject(
300+
gps.ProjectIdentifier{ProjectRoot: pr}, v, pkgs),
301+
)
302+
}
303+
304+
var l2 *lock
305+
if len(notondisk) > 0 {
306+
params := gps.SolveParameters{
307+
RootDir: p,
308+
RootPackageTree: pkgT,
309+
Manifest: &m,
310+
Lock: &l,
111311
}
312+
s, err := gps.Prepare(params, sm)
313+
if err != nil {
314+
return errors.Wrap(err, "prepare solver")
315+
}
316+
317+
soln, err := s.Solve()
318+
if err != nil {
319+
handleAllTheFailuresOfTheWorld(err)
320+
return err
321+
}
322+
l2 = lockFromInterface(soln)
323+
} else {
324+
l2 = &l
325+
}
326+
327+
if err := writeFile(mf, &m); err != nil {
328+
return errors.Wrap(err, "writeFile for manifest")
112329
}
113-
return errors.Wrap(writeManifest(mf, m), "writeManifest")
330+
if err := writeFile(lf, l2); err != nil {
331+
return errors.Wrap(err, "writeFile for lock")
332+
}
333+
334+
return nil
114335
}
336+
115337
return errors.Wrap(err, "runInit fall through")
116338
}
117339

340+
// contains checks if a array of strings contains a value
341+
func contains(a []string, b string) bool {
342+
for _, v := range a {
343+
if b == v {
344+
return true
345+
}
346+
}
347+
return false
348+
}
349+
350+
// TODO this is a stub, make it not a stub when gps gets its act together
118351
func isStdLib(i string) bool {
119352
switch i {
120-
case "bytes", "encoding/hex", "errors", "sort", "encoding/json", "flag", "fmt", "io", "os", "path/filepath", "strings", "text/tabwriter":
353+
case "bytes", "container/heap", "crypto/sha256", "encoding/hex", "encoding/xml", "errors", "sort", "encoding/json", "flag", "fmt", "go/build", "go/scanner", "io", "io/ioutil", "log", "math/rand", "net/http", "net/url", "os", "os/exec", "path", "path/filepath", "regexp", "runtime", "strconv", "strings", "sync", "sync/atomic", "text/scanner", "text/tabwriter", "time":
121354
return true
122355
}
123356
return false
124357
}
125358

126-
func writeManifest(path string, m rawManifest) error {
359+
// TODO stub; considerable effort required for the real impl
360+
func versionInWorkspace(pr gps.ProjectRoot) (gps.Version, error) {
361+
switch pr {
362+
case "github.com/sdboyer/gps":
363+
return gps.NewVersion("v0.12.0").Is("9ca61cb4e9851c80bb537e7d8e1be56e18e03cc9"), nil
364+
case "github.com/Masterminds/semver":
365+
return gps.NewBranch("2.x").Is("b3ef6b1808e9889dfb8767ce7068db923a3d07de"), nil
366+
case "github.com/Masterminds/vcs":
367+
return gps.Revision("fbe9fb6ad5b5f35b3e82a7c21123cfc526cbf895"), nil
368+
case "github.com/pkg/errors":
369+
return gps.NewVersion("v0.8.0").Is("645ef00459ed84a119197bfb8d8205042c6df63d"), nil
370+
case "github.com/armon/go-radix":
371+
return gps.NewBranch("master").Is("4239b77079c7b5d1243b7b4736304ce8ddb6f0f2"), nil
372+
case "github.com/termie/go-shutil":
373+
return gps.NewBranch("master").Is("4239b77079c7b5d1243b7b4736304ce8ddb6f0f2"), nil
374+
}
375+
376+
return nil, fmt.Errorf("unknown project")
377+
}
378+
379+
// TODO solve failures can be really creative - we need to be similarly creative
380+
// in handling them and informing the user appropriately
381+
func handleAllTheFailuresOfTheWorld(err error) {
382+
fmt.Println("ouchie, solve error: %s", err)
383+
}
384+
385+
func writeFile(path string, in json.Marshaler) error {
127386
f, err := os.Create(path)
128387
if err != nil {
129388
return err
130389
}
131390
defer f.Close()
132-
e := json.NewEncoder(f)
133-
return e.Encode(m)
134-
}
135391

136-
func createManifest(path string) error {
137-
return writeManifest(path, newRawManifest())
392+
b, err := in.MarshalJSON()
393+
if err != nil {
394+
return err
395+
}
396+
397+
_, err = f.Write(b)
398+
return err
138399
}

lock.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,3 +130,30 @@ func (l *lock) MarshalJSON() ([]byte, error) {
130130

131131
return buf.Bytes(), err
132132
}
133+
134+
// lockFromInterface converts an arbitrary gps.Lock to dep's representation of a
135+
// lock. If the input is already dep's *lock, the input is returned directly.
136+
//
137+
// Data is defensively copied wherever necessary to ensure the resulting *lock
138+
// shares no memory with the original lock.
139+
//
140+
// As gps.Solution is a superset of gps.Lock, this can also be used to convert
141+
// solutions to dep's lock form.
142+
func lockFromInterface(in gps.Lock) *lock {
143+
if in == nil {
144+
return nil
145+
} else if l, ok := in.(*lock); ok {
146+
return l
147+
}
148+
149+
h, p := in.InputHash(), in.Projects()
150+
151+
l := &lock{
152+
Memo: make([]byte, len(h)),
153+
P: make([]gps.LockedProject, len(p)),
154+
}
155+
156+
copy(l.Memo, h)
157+
copy(l.P, p)
158+
return l
159+
}

0 commit comments

Comments
 (0)