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
+
1
5
package main
2
6
3
7
import (
@@ -41,20 +45,24 @@ var initCmd = &command{
41
45
` ,
42
46
}
43
47
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 ) {
45
54
gopath := os .Getenv ("GOPATH" )
46
55
for _ , gp := range filepath .SplitList (gopath ) {
47
56
srcprefix := filepath .Join (gp , "src" ) + string (filepath .Separator )
48
57
if strings .HasPrefix (path , srcprefix ) {
49
58
// filepath.ToSlash because we're dealing with an import path now,
50
59
// not an fs path
51
- return filepath .ToSlash (strings .TrimPrefix (path , srcprefix )), nil
60
+ return filepath .ToSlash (strings .TrimPrefix (path , srcprefix )), gp , nil
52
61
}
53
62
}
54
- return "" , fmt .Errorf ("%s not in any $GOPATH" , path )
63
+ return "" , "" , fmt .Errorf ("%s not in any $GOPATH" , path )
55
64
}
56
65
57
- // TODO: Error when there is a lockfile, but no manifest?
58
66
func runInit (args []string ) error {
59
67
if len (args ) > 1 {
60
68
return fmt .Errorf ("Too many args: %d" , len (args ))
@@ -71,18 +79,27 @@ func runInit(args []string) error {
71
79
}
72
80
73
81
mf := filepath .Join (p , manifestName )
82
+ lf := filepath .Join (p , lockName )
74
83
75
84
// 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 )
79
88
}
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 )
82
99
if err != nil {
83
100
return errors .Wrap (err , "determineProjectRoot" )
84
101
}
85
- pkgT , err := gps .ListPackages (p , pr )
102
+ pkgT , err := gps .ListPackages (p , cpr )
86
103
if err != nil {
87
104
return errors .Wrap (err , "gps.ListPackages" )
88
105
}
@@ -91,7 +108,16 @@ func runInit(args []string) error {
91
108
return errors .Wrap (err , "getSourceManager" )
92
109
}
93
110
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 )
95
121
for _ , v := range pkgT .Packages {
96
122
// TODO: Some errors maybe should not be skipped ;-)
97
123
if v .Err != nil {
@@ -106,33 +132,268 @@ func runInit(args []string) error {
106
132
if err != nil {
107
133
return errors .Wrap (err , "sm.DeduceProjectRoot" ) // TODO: Skip and report ?
108
134
}
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 ,
111
311
}
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" )
112
329
}
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
114
335
}
336
+
115
337
return errors .Wrap (err , "runInit fall through" )
116
338
}
117
339
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
118
351
func isStdLib (i string ) bool {
119
352
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 " :
121
354
return true
122
355
}
123
356
return false
124
357
}
125
358
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 {
127
386
f , err := os .Create (path )
128
387
if err != nil {
129
388
return err
130
389
}
131
390
defer f .Close ()
132
- e := json .NewEncoder (f )
133
- return e .Encode (m )
134
- }
135
391
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
138
399
}
0 commit comments