Skip to content

Commit a2c5b6a

Browse files
committed
Preserve dependencies when uninstalling from the CLI
Previously, the CLI did not keep track of a dependency's users. This meant that installing two extensions with a common dependency and then unistalling one extension would break the other extension as the common dependency would be deleted: 1. Install a that depends on c 2. Install b that depends on c 3. Uninstall b 4. a is now broken as c has been deleted This commit updates the CLI to maintain a count for each artifact that's installed into /lib. An artifact is now only deleted when the count reaches zero. As part of this change the code has been extensively refactored to bring the structure into line with other CLI commands and to improve testability. Closes spring-projectsgh-1410
1 parent 4e636f0 commit a2c5b6a

File tree

8 files changed

+697
-194
lines changed

8 files changed

+697
-194
lines changed

spring-boot-cli/src/main/java/org/springframework/boot/cli/DefaultCommandFactory.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import org.springframework.boot.cli.command.core.VersionCommand;
2626
import org.springframework.boot.cli.command.grab.GrabCommand;
2727
import org.springframework.boot.cli.command.install.InstallCommand;
28+
import org.springframework.boot.cli.command.install.UninstallCommand;
2829
import org.springframework.boot.cli.command.jar.JarCommand;
2930
import org.springframework.boot.cli.command.run.RunCommand;
3031
import org.springframework.boot.cli.command.test.TestCommand;
@@ -38,7 +39,7 @@ public class DefaultCommandFactory implements CommandFactory {
3839

3940
private static final List<Command> DEFAULT_COMMANDS = Arrays.<Command> asList(
4041
new VersionCommand(), new RunCommand(), new TestCommand(), new GrabCommand(),
41-
new JarCommand(), InstallCommand.install(), InstallCommand.uninstall());
42+
new JarCommand(), new InstallCommand(), new UninstallCommand());
4243

4344
@Override
4445
public Collection<Command> getCommands() {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*
2+
* Copyright 2012-2014 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.cli.command.install;
18+
19+
import java.io.File;
20+
import java.util.List;
21+
22+
/**
23+
* @author Andy Wilkinson
24+
* @since 1.2.0
25+
*/
26+
interface DependencyResolver {
27+
28+
/**
29+
* Resolves the given {@code artifactIdentifiers}, typically in the form
30+
* "group:artifact:version", and their dependencies.
31+
*
32+
* @param artifactIdentifiers The artifacts to resolve
33+
* @return The {@code File}s for the resolved artifacts
34+
* @throws Exception if dependency resolution fails
35+
*/
36+
List<File> resolve(List<String> artifactIdentifiers) throws Exception;
37+
38+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
/*
2+
* Copyright 2012-2014 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.cli.command.install;
18+
19+
import java.io.File;
20+
import java.io.FileOutputStream;
21+
import java.io.IOException;
22+
import java.io.OutputStreamWriter;
23+
import java.net.URISyntaxException;
24+
import java.net.URL;
25+
import java.util.ArrayList;
26+
import java.util.Arrays;
27+
import java.util.List;
28+
29+
import org.codehaus.groovy.control.CompilationFailedException;
30+
import org.springframework.boot.cli.compiler.GroovyCompiler;
31+
import org.springframework.boot.cli.compiler.GroovyCompilerConfiguration;
32+
33+
/**
34+
* A {@code DependencyResolver} implemented using Groovy's {@code @Grab}
35+
*
36+
* @author Dave Syer
37+
* @author Andy Wilkinson
38+
* @since 1.2.0
39+
*/
40+
class GroovyGrabDependencyResolver implements DependencyResolver {
41+
42+
private final GroovyCompilerConfiguration configuration;
43+
44+
public GroovyGrabDependencyResolver(GroovyCompilerConfiguration configuration) {
45+
this.configuration = configuration;
46+
}
47+
48+
@Override
49+
public List<File> resolve(List<String> artifactIdentifiers)
50+
throws CompilationFailedException, IOException {
51+
GroovyCompiler groovyCompiler = new GroovyCompiler(this.configuration);
52+
List<File> artifactFiles = new ArrayList<File>();
53+
if (!artifactIdentifiers.isEmpty()) {
54+
List<URL> initialUrls = getClassPathUrls(groovyCompiler);
55+
groovyCompiler.compile(createSources(artifactIdentifiers));
56+
List<URL> artifactUrls = getClassPathUrls(groovyCompiler);
57+
artifactUrls.removeAll(initialUrls);
58+
59+
for (URL artifactUrl : artifactUrls) {
60+
artifactFiles.add(toFile(artifactUrl));
61+
}
62+
63+
}
64+
return artifactFiles;
65+
}
66+
67+
private List<URL> getClassPathUrls(GroovyCompiler compiler) {
68+
return new ArrayList<URL>(Arrays.asList(compiler.getLoader().getURLs()));
69+
}
70+
71+
private String createSources(List<String> artifactIdentifiers) throws IOException {
72+
File file = File.createTempFile("SpringCLIDependency", ".groovy");
73+
file.deleteOnExit();
74+
OutputStreamWriter stream = new OutputStreamWriter(new FileOutputStream(file),
75+
"UTF-8");
76+
try {
77+
for (String artifactIdentifier : artifactIdentifiers) {
78+
stream.write("@Grab('" + artifactIdentifier + "')");
79+
}
80+
// Dummy class to force compiler to do grab
81+
stream.write("class Installer {}");
82+
}
83+
finally {
84+
stream.close();
85+
}
86+
// Windows paths get tricky unless you work with URI
87+
return file.getAbsoluteFile().toURI().toString();
88+
}
89+
90+
private File toFile(URL url) {
91+
try {
92+
return new File(url.toURI());
93+
}
94+
catch (URISyntaxException ex) {
95+
return new File(url.getPath());
96+
}
97+
}
98+
99+
}

0 commit comments

Comments
 (0)