Skip to content

Commit 84ffb1e

Browse files
committed
Initial support for removing dynamically imported artifacts.
1 parent d29a589 commit 84ffb1e

File tree

9 files changed

+183
-56
lines changed

9 files changed

+183
-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: 22 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,9 @@ 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+
// The artifact parameter is only necessary for tracking dynamically imported artifacts after being
296+
// added by an ecosystem.
297+
func (d *depot) Track(id strfmt.UUID, deploy *deployment, artifact *buildplan.Artifact) error {
290298
d.mapMutex.Lock()
291299
defer d.mapMutex.Unlock()
292300

@@ -308,6 +316,14 @@ func (d *depot) Track(id strfmt.UUID, deploy *deployment) error {
308316
}
309317
d.config.Cache[id].InUse = true
310318
d.config.Cache[id].LastAccessTime = time.Now().Unix()
319+
320+
// For dynamically imported artifacts, also include artifact metadata.
321+
if artifact != nil {
322+
d.config.Cache[id].Namespace = artifact.Ingredients[0].Namespace
323+
d.config.Cache[id].Name = artifact.Name()
324+
d.config.Cache[id].Version = artifact.Version()
325+
}
326+
311327
return nil
312328
}
313329

@@ -317,8 +333,8 @@ func (d *depot) Track(id strfmt.UUID, deploy *deployment) error {
317333
// This is automatically called by the `Undeploy()` function.
318334
// This should be called for ecosystems that handle uninstallation of artifacts.
319335
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 })
336+
if deployments, ok := d.config.Deployments[id]; ok {
337+
d.config.Deployments[id] = sliceutils.Filter(deployments, func(d deployment) bool { return d.Path != path })
322338
}
323339
}
324340

@@ -445,7 +461,7 @@ func (d *depot) Save() error {
445461
for id := range d.artifacts {
446462
if deployments, ok := d.config.Deployments[id]; !ok || len(deployments) == 0 {
447463
if _, exists := d.config.Cache[id]; !exists {
448-
err := d.Track(id, nil) // create cache entry for previously used artifact
464+
err := d.Track(id, nil, nil) // create cache entry for previously used artifact
449465
if err != nil {
450466
return errs.Wrap(err, "Could not update depot cache with previously used artifact")
451467
}

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: 57 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,28 @@ 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+
// Remove a module's .mod and .zip files from the filesystem proxy.
142+
func (e *Golang) Remove(name, version string, installedFiles []string) (rerr error) {
143+
for _, proxyDir := range installedFiles {
144+
modFile := filepath.Join(proxyDir, "@v", version+".mod")
145+
if fileutils.TargetExists(modFile) {
146+
err := os.Remove(modFile)
147+
if err != nil {
148+
rerr = errs.Pack(rerr, errs.Wrap(err, "Unable to remove mod file for '%s': %s", name, modFile))
149+
}
150+
}
151+
152+
zipFile := filepath.Join(proxyDir, "@v", version+".zip")
153+
if fileutils.TargetExists(zipFile) {
154+
err := os.Remove(zipFile)
155+
if err != nil {
156+
rerr = errs.Pack(rerr, errs.Wrap(err, "Unable to remove zip file for '%s': %s", name, zipFile))
157+
}
158+
}
159+
160+
e.removedModuleVersions[name] = append(e.removedModuleVersions[name], version)
161+
}
162+
return rerr
140163
}
141164

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

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)