Skip to content

Commit 9ee5e09

Browse files
committed
Initial support for removing dynamically imported artifacts.
1 parent ceafc3b commit 9ee5e09

File tree

9 files changed

+179
-56
lines changed

9 files changed

+179
-56
lines changed

internal/captain/values.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -155,9 +155,9 @@ func (p *PackageValue) Set(s string) error {
155155
}
156156
switch {
157157
case strings.Contains(s, ":"):
158-
v := strings.Split(s, ":")
159-
p.Namespace = strings.TrimSpace(strings.Join(v[0:len(v)-1], ":"))
160-
p.Name = strings.TrimSpace(v[len(v)-1])
158+
namespace, name, _ := strings.Cut(s, ":")
159+
p.Namespace = strings.TrimSpace(namespace)
160+
p.Name = strings.TrimSpace(name)
161161
case strings.Contains(s, "/"):
162162
v := strings.Split(s, "/")
163163
p.Namespace = strings.TrimSpace(strings.Join(v[0:len(v)-1], "/"))

pkg/runtime/depot.go

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
configMediator "github.com/ActiveState/cli/internal/mediators/config"
2222
"github.com/ActiveState/cli/internal/sliceutils"
2323
"github.com/ActiveState/cli/internal/smartlink"
24+
"github.com/ActiveState/cli/pkg/buildplan"
2425
)
2526

2627
const (
@@ -52,6 +53,11 @@ type artifactInfo struct {
5253
Size int64 `json:"size"`
5354
LastAccessTime int64 `json:"lastAccessTime"`
5455

56+
// These fields are used by ecosystems during Add/Remove/Apply.
57+
Namespace string `json:"namespace,omitempty"`
58+
Name string `json:"name,omitempty"`
59+
Version string `json:"version,omitempty"`
60+
5561
id strfmt.UUID // for convenience when removing stale artifacts; should NOT have json tag
5662
}
5763

@@ -218,7 +224,7 @@ func (d *depot) DeployViaLink(id strfmt.UUID, relativeSrc, absoluteDest string)
218224
Path: absoluteDest,
219225
Files: files.RelativePaths(),
220226
RelativeSrc: relativeSrc,
221-
})
227+
}, nil)
222228
if err != nil {
223229
return errs.Wrap(err, "Could not record artifact use")
224230
}
@@ -275,7 +281,7 @@ func (d *depot) DeployViaCopy(id strfmt.UUID, relativeSrc, absoluteDest string)
275281
Path: absoluteDest,
276282
Files: files.RelativePaths(),
277283
RelativeSrc: relativeSrc,
278-
})
284+
}, nil)
279285
if err != nil {
280286
return errs.Wrap(err, "Could not record artifact use")
281287
}
@@ -286,7 +292,7 @@ func (d *depot) DeployViaCopy(id strfmt.UUID, relativeSrc, absoluteDest string)
286292
// Track will record an artifact deployment.
287293
// This is automatically called by `DeployVia*()` functions.
288294
// This should be called for ecosystems that handle installation of artifacts.
289-
func (d *depot) Track(id strfmt.UUID, deploy *deployment) error {
295+
func (d *depot) Track(id strfmt.UUID, deploy *deployment, artifact *buildplan.Artifact) error {
290296
d.mapMutex.Lock()
291297
defer d.mapMutex.Unlock()
292298

@@ -308,6 +314,14 @@ func (d *depot) Track(id strfmt.UUID, deploy *deployment) error {
308314
}
309315
d.config.Cache[id].InUse = true
310316
d.config.Cache[id].LastAccessTime = time.Now().Unix()
317+
318+
// For dynamically imported artifacts, also include artifact metadata.
319+
if artifact != nil {
320+
d.config.Cache[id].Namespace = artifact.Ingredients[0].Namespace
321+
d.config.Cache[id].Name = artifact.Name()
322+
d.config.Cache[id].Version = artifact.Version()
323+
}
324+
311325
return nil
312326
}
313327

@@ -317,8 +331,8 @@ func (d *depot) Track(id strfmt.UUID, deploy *deployment) error {
317331
// This is automatically called by the `Undeploy()` function.
318332
// This should be called for ecosystems that handle uninstallation of artifacts.
319333
func (d *depot) Untrack(id strfmt.UUID, path string) {
320-
if _, ok := d.config.Deployments[id]; ok {
321-
d.config.Deployments[id] = sliceutils.Filter(d.config.Deployments[id], func(d deployment) bool { return d.Path != path })
334+
if deployments, ok := d.config.Deployments[id]; ok {
335+
d.config.Deployments[id] = sliceutils.Filter(deployments, func(d deployment) bool { return d.Path != path })
322336
}
323337
}
324338

@@ -448,7 +462,7 @@ func (d *depot) Save() error {
448462
for id := range d.artifacts {
449463
if deployments, ok := d.config.Deployments[id]; !ok || len(deployments) == 0 {
450464
if _, exists := d.config.Cache[id]; !exists {
451-
err := d.Track(id, nil) // create cache entry for previously used artifact
465+
err := d.Track(id, nil, nil) // create cache entry for previously used artifact
452466
if err != nil {
453467
return errs.Wrap(err, "Could not update depot cache with previously used artifact")
454468
}

pkg/runtime/ecosystem.go

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ type ecosystem interface {
99
Init(runtimePath string, buildplan *buildplan.BuildPlan) error
1010
Namespaces() []string
1111
Add(artifact *buildplan.Artifact, artifactSrcPath string) ([]string, error)
12-
Remove(artifact *buildplan.Artifact) error
12+
Remove(name, version string, installedFiles []string) error
1313
Apply() error
1414
}
1515

@@ -36,12 +36,10 @@ func artifactMatchesEcosystem(a *buildplan.Artifact, e ecosystem) bool {
3636
return false
3737
}
3838

39-
func namespacesMatchesEcosystem(namespaces []string, e ecosystem) bool {
40-
for _, namespace := range e.Namespaces() {
41-
for _, n := range namespaces {
42-
if n == namespace {
43-
return true
44-
}
39+
func namespaceMatchesEcosystem(namespace string, e ecosystem) bool {
40+
for _, n := range e.Namespaces() {
41+
if n == namespace {
42+
return true
4543
}
4644
}
4745
return false
@@ -56,12 +54,11 @@ func filterEcosystemMatchingArtifact(artifact *buildplan.Artifact, ecosystems []
5654
return nil
5755
}
5856

59-
func filterEcosystemsMatchingNamespaces(ecosystems []ecosystem, namespaces []string) []ecosystem {
60-
result := []ecosystem{}
57+
func filterEcosystemMatchingNamespace(ecosystems []ecosystem, namespace string) ecosystem {
6158
for _, ecosystem := range ecosystems {
62-
if namespacesMatchesEcosystem(namespaces, ecosystem) {
63-
result = append(result, ecosystem)
59+
if namespaceMatchesEcosystem(namespace, ecosystem) {
60+
return ecosystem
6461
}
6562
}
66-
return result
63+
return nil
6764
}

pkg/runtime/ecosystem/dotnet.go

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,8 +105,17 @@ func (e *DotNet) Add(artifact *buildplan.Artifact, artifactSrcPath string) ([]st
105105
return installedFiles, nil
106106
}
107107

108-
func (e *DotNet) Remove(artifact *buildplan.Artifact) error {
109-
return nil // TODO: CP-956
108+
func (e *DotNet) Remove(name, version string, installedFiles []string) (rerr error) {
109+
for _, dir := range installedFiles {
110+
if !fileutils.DirExists(dir) {
111+
continue
112+
}
113+
err := os.RemoveAll(dir)
114+
if err != nil {
115+
rerr = errs.Pack(rerr, errs.Wrap(err, "Unable to remove directory for '%s': %s", name, dir))
116+
}
117+
}
118+
return rerr
110119
}
111120

112121
func (e *DotNet) Apply() error {

pkg/runtime/ecosystem/golang.go

Lines changed: 56 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,17 @@ import (
1010

1111
"github.com/ActiveState/cli/internal/errs"
1212
"github.com/ActiveState/cli/internal/fileutils"
13+
"github.com/ActiveState/cli/internal/sliceutils"
1314
"github.com/ActiveState/cli/internal/smartlink"
1415
"github.com/ActiveState/cli/internal/unarchiver"
1516
"github.com/ActiveState/cli/pkg/buildplan"
1617
)
1718

1819
type Golang struct {
19-
runtimeDir string
20-
proxyDir string
21-
addedModuleVersions map[string][]string
20+
runtimeDir string
21+
proxyDir string
22+
addedModuleVersions map[string][]string
23+
removedModuleVersions map[string][]string
2224
}
2325

2426
func (e *Golang) Init(runtimePath string, buildplan *buildplan.BuildPlan) error {
@@ -29,6 +31,7 @@ func (e *Golang) Init(runtimePath string, buildplan *buildplan.BuildPlan) error
2931
return errs.Wrap(err, "Unable to create Go proxy directory")
3032
}
3133
e.addedModuleVersions = make(map[string][]string)
34+
e.removedModuleVersions = make(map[string][]string)
3235
return nil
3336
}
3437

@@ -135,8 +138,27 @@ func (e *Golang) Add(artifact *buildplan.Artifact, artifactSrcPath string) (_ []
135138
return installedFiles, nil
136139
}
137140

138-
func (e *Golang) Remove(artifact *buildplan.Artifact) error {
139-
return nil // TODO: CP-956
141+
func (e *Golang) Remove(name, version string, installedFiles []string) (rerr error) {
142+
for _, proxyDir := range installedFiles {
143+
modFile := filepath.Join(proxyDir, "@v", version+".mod")
144+
if fileutils.TargetExists(modFile) {
145+
err := os.Remove(modFile)
146+
if err != nil {
147+
rerr = errs.Pack(rerr, errs.Wrap(err, "Unable to remove mod file for '%s': %s", name, modFile))
148+
}
149+
}
150+
151+
zipFile := filepath.Join(proxyDir, "@v", version+".zip")
152+
if fileutils.TargetExists(zipFile) {
153+
err := os.Remove(zipFile)
154+
if err != nil {
155+
rerr = errs.Pack(rerr, errs.Wrap(err, "Unable to remove zip file for '%s': %s", name, zipFile))
156+
}
157+
}
158+
159+
e.removedModuleVersions[name] = append(e.removedModuleVersions[name], version)
160+
}
161+
return rerr
140162
}
141163

142164
// Create/update each added module's version list file.
@@ -165,5 +187,34 @@ func (e *Golang) Apply() error {
165187
return errs.Wrap(err, "Unable to write %s", listFile)
166188
}
167189
}
190+
191+
for name, versions := range e.removedModuleVersions {
192+
listFile := filepath.Join(e.runtimeDir, e.proxyDir, name, "@v", "list")
193+
if !fileutils.FileExists(listFile) {
194+
continue
195+
}
196+
197+
// Read known versions and remove the ones being removed.
198+
contents, err := fileutils.ReadFile(listFile)
199+
if err != nil {
200+
return errs.Wrap(err, "Unable to read %s", listFile)
201+
}
202+
knownVersions := strings.Split(string(contents), "\n")
203+
knownVersions = sliceutils.Filter(knownVersions, func(version string) bool {
204+
for _, v := range versions {
205+
if v == version {
206+
return false
207+
}
208+
}
209+
return true
210+
})
211+
212+
// Write the remaining versions.
213+
err = fileutils.WriteFile(listFile, []byte(strings.Join(knownVersions, "\n")))
214+
if err != nil {
215+
return errs.Wrap(err, "Unable to write %s", listFile)
216+
}
217+
}
218+
168219
return nil
169220
}

pkg/runtime/ecosystem/java.go

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package ecosystem
22

33
import (
44
"encoding/json"
5+
"os"
56
"path/filepath"
67
"strings"
78

@@ -59,8 +60,17 @@ func (e *Java) Add(artifact *buildplan.Artifact, artifactSrcPath string) ([]stri
5960
return installedFiles, nil
6061
}
6162

62-
func (e *Java) Remove(artifact *buildplan.Artifact) error {
63-
return nil // TODO: CP-956
63+
func (e *Java) Remove(name, version string, installedFiles []string) (rerr error) {
64+
for _, file := range installedFiles {
65+
if !fileutils.TargetExists(file) {
66+
continue
67+
}
68+
err := os.Remove(file)
69+
if err != nil {
70+
rerr = errs.Pack(rerr, errs.Wrap(err, "Unable to remove installed file for '%s': %s", name, file))
71+
}
72+
}
73+
return rerr
6474
}
6575

6676
func (e *Java) Apply() error {

pkg/runtime/ecosystem/javascript.go

Lines changed: 29 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,14 @@ import (
1616
const nodeModulesDir = "usr/lib/node_modules"
1717

1818
type JavaScript struct {
19-
runtimePath string
20-
packages []string
19+
runtimePath string
20+
installPackages []string
21+
uninstallPackages []string
2122
}
2223

2324
func (e *JavaScript) Init(runtimePath string, buildplan *buildplan.BuildPlan) error {
2425
e.runtimePath = runtimePath
25-
e.packages = []string{}
26+
e.installPackages = []string{}
2627
return nil
2728
}
2829

@@ -56,33 +57,48 @@ func (e *JavaScript) Add(artifact *buildplan.Artifact, artifactSrcPath string) (
5657
packageName = file.Name()[:i]
5758
}
5859
}
59-
e.packages = append(e.packages, file.AbsolutePath())
60+
e.installPackages = append(e.installPackages, file.AbsolutePath())
6061
}
6162
installedDir := filepath.Join(nodeModulesDir, packageName) // Apply() will install here
6263
return []string{installedDir}, nil
6364
}
6465

65-
func (e *JavaScript) Remove(artifact *buildplan.Artifact) error {
66-
return nil // TODO: CP-956
66+
func (e *JavaScript) Remove(name, version string, installedFiles []string) error {
67+
e.uninstallPackages = append(e.uninstallPackages, name)
68+
return nil
6769
}
6870

6971
func (e *JavaScript) Apply() error {
70-
if len(e.packages) == 0 {
72+
if len(e.installPackages) == 0 && len(e.uninstallPackages) == 0 {
7173
return nil // nothing to do
7274
}
7375

7476
binDir := filepath.Join(e.runtimePath, "usr", "bin")
75-
args := []string{"install", "-g", "--offline"} // do not install to current directory
76-
for _, arg := range e.packages {
77-
args = append(args, arg)
77+
installArgs := []string{"install", "-g", "--offline"} // do not install to current directory
78+
for _, arg := range e.installPackages {
79+
installArgs = append(installArgs, arg)
80+
}
81+
uninstallArgs := []string{"uninstall", "-g", "--no-save"} // do not remove from current directory
82+
for _, arg := range e.uninstallPackages {
83+
uninstallArgs = append(uninstallArgs, arg)
7884
}
7985
env := []string{
8086
fmt.Sprintf("PATH=%s%s%s", binDir, string(os.PathListSeparator), os.Getenv("PATH")),
8187
fmt.Sprintf("NPM_CONFIG_PREFIX=%s", filepath.Join(e.runtimePath, "usr")),
8288
}
83-
_, stderr, err := osutils.ExecSimple(filepath.Join(binDir, "npm"), args, env)
84-
if err != nil {
85-
return errs.Wrap(err, "Error running npm: %s", stderr)
89+
90+
if len(e.installPackages) > 0 {
91+
_, stderr, err := osutils.ExecSimple(filepath.Join(binDir, "npm"), installArgs, env)
92+
if err != nil {
93+
return errs.Wrap(err, "Error running npm install: %s", stderr)
94+
}
95+
}
96+
97+
if len(e.uninstallPackages) > 0 {
98+
_, stderr, err := osutils.ExecSimple(filepath.Join(binDir, "npm"), uninstallArgs, env)
99+
if err != nil {
100+
return errs.Wrap(err, "Error running npm uninstall: %s", stderr)
101+
}
86102
}
87103
return nil
88104
}

pkg/runtime/ecosystem/rust.go

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,8 +92,17 @@ func (e *Rust) Add(artifact *buildplan.Artifact, artifactSrcPath string) ([]stri
9292
return installedFiles, nil
9393
}
9494

95-
func (e *Rust) Remove(artifact *buildplan.Artifact) error {
96-
return nil // TODO: CP-956
95+
func (e *Rust) Remove(name, version string, installedFiles []string) (rerr error) {
96+
for _, dir := range installedFiles {
97+
if !fileutils.DirExists(dir) {
98+
continue
99+
}
100+
err := os.RemoveAll(dir)
101+
if err != nil {
102+
rerr = errs.Pack(rerr, errs.Wrap(err, "Unable to remove directory for '%s': %s", name, dir))
103+
}
104+
}
105+
return rerr
97106
}
98107

99108
// configFileContents replaces the crates.io source with our vendored crates.

0 commit comments

Comments
 (0)