forked from golang/dep
-
Notifications
You must be signed in to change notification settings - Fork 1
/
remove.go
199 lines (171 loc) · 5.48 KB
/
remove.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
// 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 (
"flag"
"fmt"
"log"
"os"
"strings"
"github.com/pkg/errors"
"github.com/sdboyer/gps"
)
const removeShortHelp = `Remove a dependency from the project`
const removeLongHelp = `
Remove a dependency from the project's manifest file, lock file, and vendor
folder. If the project includes that dependency in its import graph, remove will
fail unless -force is specified.
`
func (cmd *removeCommand) Name() string { return "remove" }
func (cmd *removeCommand) Args() string { return "[spec...]" }
func (cmd *removeCommand) ShortHelp() string { return removeShortHelp }
func (cmd *removeCommand) LongHelp() string { return removeLongHelp }
func (cmd *removeCommand) Hidden() bool { return false }
func (cmd *removeCommand) Register(fs *flag.FlagSet) {
fs.BoolVar(&cmd.dryRun, "n", false, "dry run, don't actually remove anything")
fs.BoolVar(&cmd.unused, "unused", false, "remove all dependencies that aren't imported by the project")
fs.BoolVar(&cmd.force, "force", false, "remove the given dependencies even if they are imported by the project")
fs.BoolVar(&cmd.keepSource, "keep-source", false, "don't remove source code")
}
type removeCommand struct {
dryRun bool
unused bool
force bool
keepSource bool
}
func (cmd *removeCommand) Run(args []string) error {
p, err := depContext.loadProject("")
if err != nil {
return err
}
sm, err := depContext.sourceManager()
if err != nil {
return err
}
sm.UseDefaultSignalHandling()
defer sm.Release()
cpr, err := depContext.splitAbsoluteProjectRoot(p.absroot)
if err != nil {
return errors.Wrap(err, "determineProjectRoot")
}
pkgT, err := gps.ListPackages(p.absroot, cpr)
if err != nil {
return errors.Wrap(err, "gps.ListPackages")
}
// TODO this will end up ignoring internal pkgs with errs (and any other
// internal pkgs that import them), which is not what we want for this mode.
// A new callback, or a new param on this one, will be introduced to gps
// soon, and we'll want to use that when it arrives.
//reachlist := pkgT.ExternalReach(true, true).ListExternalImports()
reachmap := pkgT.ExternalReach(true, true, nil)
if cmd.unused {
if len(args) > 0 {
return fmt.Errorf("remove takes no arguments when running with -unused")
}
reachlist := reachmap.ListExternalImports()
// warm the cache in parallel, in case any paths require go get metadata
// discovery
for _, im := range reachlist {
go sm.DeduceProjectRoot(im)
}
otherroots := make(map[gps.ProjectRoot]bool)
for _, im := range reachlist {
if isStdLib(im) {
continue
}
pr, err := sm.DeduceProjectRoot(im)
if err != nil {
// not being able to detect the root for an import path that's
// actually in the import list is a deeper problem. However,
// it's not our direct concern here, so we just warn.
logf("could not infer root for %q", pr)
continue
}
otherroots[pr] = true
}
var rm []gps.ProjectRoot
for pr := range p.m.Dependencies {
if _, has := otherroots[pr]; !has {
delete(p.m.Dependencies, pr)
rm = append(rm, pr)
}
}
if len(rm) == 0 {
logf("nothing to do")
return nil
}
} else {
// warm the cache in parallel, in case any paths require go get metadata
// discovery
for _, arg := range args {
go sm.DeduceProjectRoot(arg)
}
for _, arg := range args {
pr, err := sm.DeduceProjectRoot(arg)
if err != nil {
// couldn't detect the project root for this string -
// a non-valid project root was provided
return errors.Wrap(err, "gps.DeduceProjectRoot")
}
if string(pr) != arg {
// don't be magical with subpaths, otherwise we muddy the waters
// between project roots and import paths
return fmt.Errorf("%q is not a project root, but %q is - is that what you want to remove?", arg, pr)
}
/*
* - Remove package from manifest
* - if the package IS NOT being used, solving should do what we want
* - if the package IS being used:
* - Desired behavior: stop and tell the user, unless --force
* - Actual solver behavior: ?
*/
var pkgimport []string
for pkg, imports := range reachmap {
for _, im := range imports {
if hasImportPathPrefix(im, arg) {
pkgimport = append(pkgimport, pkg)
break
}
}
}
if _, indeps := p.m.Dependencies[gps.ProjectRoot(arg)]; !indeps {
return fmt.Errorf("%q is not present in the manifest, cannot remove it", arg)
}
if len(pkgimport) > 0 && !cmd.force {
if len(pkgimport) == 1 {
return fmt.Errorf("not removing %q because it is imported by %q (pass -force to override)", arg, pkgimport[0])
} else {
return fmt.Errorf("not removing %q because it is imported by:\n\t%s (pass -force to override)", arg, strings.Join(pkgimport, "\n\t"))
}
}
delete(p.m.Dependencies, gps.ProjectRoot(arg))
}
}
params := p.makeParams()
params.RootPackageTree = pkgT
if *verbose {
params.Trace = true
params.TraceLogger = log.New(os.Stderr, "", 0)
}
s, err := gps.Prepare(params, sm)
if err != nil {
return errors.Wrap(err, "prepare solver")
}
soln, err := s.Solve()
if err != nil {
handleAllTheFailuresOfTheWorld(err)
return err
}
sw := safeWriter{
root: p.absroot,
m: p.m,
l: p.l,
nl: soln,
sm: sm,
}
if err := sw.writeAllSafe(false); err != nil {
return errors.Wrap(err, "grouped write of manifest, lock and vendor")
}
return nil
}