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 gh-1410
This commit is contained in:
parent
4e636f069f
commit
a2c5b6a7bb
|
@ -25,6 +25,7 @@ import org.springframework.boot.cli.command.CommandFactory;
|
|||
import org.springframework.boot.cli.command.core.VersionCommand;
|
||||
import org.springframework.boot.cli.command.grab.GrabCommand;
|
||||
import org.springframework.boot.cli.command.install.InstallCommand;
|
||||
import org.springframework.boot.cli.command.install.UninstallCommand;
|
||||
import org.springframework.boot.cli.command.jar.JarCommand;
|
||||
import org.springframework.boot.cli.command.run.RunCommand;
|
||||
import org.springframework.boot.cli.command.test.TestCommand;
|
||||
|
@ -38,7 +39,7 @@ public class DefaultCommandFactory implements CommandFactory {
|
|||
|
||||
private static final List<Command> DEFAULT_COMMANDS = Arrays.<Command> asList(
|
||||
new VersionCommand(), new RunCommand(), new TestCommand(), new GrabCommand(),
|
||||
new JarCommand(), InstallCommand.install(), InstallCommand.uninstall());
|
||||
new JarCommand(), new InstallCommand(), new UninstallCommand());
|
||||
|
||||
@Override
|
||||
public Collection<Command> getCommands() {
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* Copyright 2012-2014 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.cli.command.install;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Andy Wilkinson
|
||||
* @since 1.2.0
|
||||
*/
|
||||
interface DependencyResolver {
|
||||
|
||||
/**
|
||||
* Resolves the given {@code artifactIdentifiers}, typically in the form
|
||||
* "group:artifact:version", and their dependencies.
|
||||
*
|
||||
* @param artifactIdentifiers The artifacts to resolve
|
||||
* @return The {@code File}s for the resolved artifacts
|
||||
* @throws Exception if dependency resolution fails
|
||||
*/
|
||||
List<File> resolve(List<String> artifactIdentifiers) throws Exception;
|
||||
|
||||
}
|
|
@ -0,0 +1,99 @@
|
|||
/*
|
||||
* Copyright 2012-2014 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.cli.command.install;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.codehaus.groovy.control.CompilationFailedException;
|
||||
import org.springframework.boot.cli.compiler.GroovyCompiler;
|
||||
import org.springframework.boot.cli.compiler.GroovyCompilerConfiguration;
|
||||
|
||||
/**
|
||||
* A {@code DependencyResolver} implemented using Groovy's {@code @Grab}
|
||||
*
|
||||
* @author Dave Syer
|
||||
* @author Andy Wilkinson
|
||||
* @since 1.2.0
|
||||
*/
|
||||
class GroovyGrabDependencyResolver implements DependencyResolver {
|
||||
|
||||
private final GroovyCompilerConfiguration configuration;
|
||||
|
||||
public GroovyGrabDependencyResolver(GroovyCompilerConfiguration configuration) {
|
||||
this.configuration = configuration;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<File> resolve(List<String> artifactIdentifiers)
|
||||
throws CompilationFailedException, IOException {
|
||||
GroovyCompiler groovyCompiler = new GroovyCompiler(this.configuration);
|
||||
List<File> artifactFiles = new ArrayList<File>();
|
||||
if (!artifactIdentifiers.isEmpty()) {
|
||||
List<URL> initialUrls = getClassPathUrls(groovyCompiler);
|
||||
groovyCompiler.compile(createSources(artifactIdentifiers));
|
||||
List<URL> artifactUrls = getClassPathUrls(groovyCompiler);
|
||||
artifactUrls.removeAll(initialUrls);
|
||||
|
||||
for (URL artifactUrl : artifactUrls) {
|
||||
artifactFiles.add(toFile(artifactUrl));
|
||||
}
|
||||
|
||||
}
|
||||
return artifactFiles;
|
||||
}
|
||||
|
||||
private List<URL> getClassPathUrls(GroovyCompiler compiler) {
|
||||
return new ArrayList<URL>(Arrays.asList(compiler.getLoader().getURLs()));
|
||||
}
|
||||
|
||||
private String createSources(List<String> artifactIdentifiers) throws IOException {
|
||||
File file = File.createTempFile("SpringCLIDependency", ".groovy");
|
||||
file.deleteOnExit();
|
||||
OutputStreamWriter stream = new OutputStreamWriter(new FileOutputStream(file),
|
||||
"UTF-8");
|
||||
try {
|
||||
for (String artifactIdentifier : artifactIdentifiers) {
|
||||
stream.write("@Grab('" + artifactIdentifier + "')");
|
||||
}
|
||||
// Dummy class to force compiler to do grab
|
||||
stream.write("class Installer {}");
|
||||
}
|
||||
finally {
|
||||
stream.close();
|
||||
}
|
||||
// Windows paths get tricky unless you work with URI
|
||||
return file.getAbsoluteFile().toURI().toString();
|
||||
}
|
||||
|
||||
private File toFile(URL url) {
|
||||
try {
|
||||
return new File(url.toURI());
|
||||
}
|
||||
catch (URISyntaxException ex) {
|
||||
return new File(url.getPath());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -15,242 +15,59 @@
|
|||
*/
|
||||
package org.springframework.boot.cli.command.install;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import joptsimple.OptionSet;
|
||||
import joptsimple.OptionSpec;
|
||||
|
||||
import org.springframework.boot.cli.command.Command;
|
||||
import org.springframework.boot.cli.command.OptionParsingCommand;
|
||||
import org.springframework.boot.cli.command.options.CompilerOptionHandler;
|
||||
import org.springframework.boot.cli.command.options.OptionSetGroovyCompilerConfiguration;
|
||||
import org.springframework.boot.cli.command.status.ExitStatus;
|
||||
import org.springframework.boot.cli.compiler.GroovyCompiler;
|
||||
import org.springframework.boot.cli.compiler.GroovyCompilerConfiguration;
|
||||
import org.springframework.boot.cli.compiler.RepositoryConfigurationFactory;
|
||||
import org.springframework.boot.cli.compiler.grape.RepositoryConfiguration;
|
||||
import org.springframework.boot.cli.util.Log;
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
import org.springframework.util.SystemPropertyUtils;
|
||||
|
||||
/**
|
||||
* {@link Command} to install additional dependencies into the CLI.
|
||||
*
|
||||
* @author Dave Syer
|
||||
* @author Andy Wilkinson
|
||||
* @since 1.2.0
|
||||
*/
|
||||
public class InstallCommand extends OptionParsingCommand {
|
||||
|
||||
private final String usageHelp;
|
||||
|
||||
public static Command install() {
|
||||
return new InstallCommand("install", "Install dependencies to lib directory",
|
||||
"[options] <coordinates>", new InstallFileProcessorFactory());
|
||||
}
|
||||
|
||||
public static Command uninstall() {
|
||||
return new InstallCommand("uninstall",
|
||||
"Uninstall dependencies from lib directory", "[options] <coordinates>",
|
||||
new UninstallFileProcessorFactory());
|
||||
}
|
||||
|
||||
private InstallCommand(String name, String description, String usageHelp,
|
||||
FileProcessorFactory visitor) {
|
||||
super(name, description, new InstallOptionHandler(visitor));
|
||||
this.usageHelp = usageHelp;
|
||||
public InstallCommand() {
|
||||
super("install", "Install dependencies to the lib directory",
|
||||
new InstallOptionHandler());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsageHelp() {
|
||||
return this.usageHelp;
|
||||
return "[options] <coordinates>";
|
||||
}
|
||||
|
||||
private static final class InstallOptionHandler extends CompilerOptionHandler {
|
||||
|
||||
private FileProcessorFactory factory;
|
||||
private OptionSpec<Void> allOption;
|
||||
|
||||
public InstallOptionHandler(FileProcessorFactory factory) {
|
||||
this.factory = factory;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doOptions() {
|
||||
if (this.factory.getClass().getName().contains("UninstallFile")) {
|
||||
this.allOption = option("all", "Uninstall all");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ExitStatus run(OptionSet options) throws Exception {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
List<String> args = (List<String>) options.nonOptionArguments();
|
||||
|
||||
if (options.has(this.allOption)) {
|
||||
if (!args.isEmpty()) {
|
||||
throw new IllegalArgumentException(
|
||||
"No non-optional arguments permitted with --all");
|
||||
}
|
||||
uninstallAllJars();
|
||||
return ExitStatus.OK;
|
||||
}
|
||||
if (args.isEmpty()) {
|
||||
throw new IllegalArgumentException(
|
||||
"Non-optional arguments required. Specify dependencies via group:artifact:version");
|
||||
"Please specify at least one dependency, in the form group:artifact:version, to install");
|
||||
}
|
||||
|
||||
List<RepositoryConfiguration> repositoryConfiguration = RepositoryConfigurationFactory
|
||||
.createDefaultRepositoryConfiguration();
|
||||
|
||||
GroovyCompilerConfiguration configuration = new OptionSetGroovyCompilerConfiguration(
|
||||
options, this, repositoryConfiguration) {
|
||||
@Override
|
||||
public boolean isAutoconfigure() {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
GroovyCompiler groovyCompiler = new GroovyCompiler(configuration);
|
||||
try {
|
||||
if (!args.isEmpty()) {
|
||||
List<URL> initialUrls = getClassPathUrls(groovyCompiler);
|
||||
groovyCompiler.compile(createSources(args));
|
||||
List<URL> urlsToProcessor = getClassPathUrls(groovyCompiler);
|
||||
urlsToProcessor.removeAll(initialUrls);
|
||||
|
||||
processJars(urlsToProcessor);
|
||||
}
|
||||
new Installer(options, this).install(args);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
String message = ex.getMessage();
|
||||
Log.error(message != null ? message : ex.getClass().toString());
|
||||
catch (Exception e) {
|
||||
String message = e.getMessage();
|
||||
Log.error(message != null ? message : e.getClass().toString());
|
||||
}
|
||||
|
||||
return ExitStatus.OK;
|
||||
}
|
||||
|
||||
private List<URL> getClassPathUrls(GroovyCompiler compiler) {
|
||||
return new ArrayList<URL>(Arrays.asList(compiler.getLoader().getURLs()));
|
||||
}
|
||||
|
||||
private void processJars(List<URL> urlsToProcess) throws IOException {
|
||||
File lib = getDefaultLibDirectory();
|
||||
|
||||
FileProcessor processor = this.factory.processor(lib);
|
||||
|
||||
for (URL url : urlsToProcess) {
|
||||
File file = toFile(url);
|
||||
Log.info("Processing: " + file);
|
||||
if (file.getName().endsWith(".jar")) {
|
||||
processor.processFile(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private File toFile(URL url) {
|
||||
try {
|
||||
return new File(url.toURI());
|
||||
}
|
||||
catch (URISyntaxException ex) {
|
||||
return new File(url.getPath());
|
||||
}
|
||||
}
|
||||
|
||||
private void uninstallAllJars() throws IOException {
|
||||
File lib = getDefaultLibDirectory();
|
||||
File[] filesInLib = lib.listFiles();
|
||||
if (filesInLib != null) {
|
||||
FileProcessor processor = new DeleteNotTheCliProcessor();
|
||||
for (File file : filesInLib) {
|
||||
processor.processFile(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String createSources(List<String> args) throws IOException {
|
||||
File file = File.createTempFile("SpringInstallCommand", ".groovy");
|
||||
file.deleteOnExit();
|
||||
OutputStreamWriter stream = new OutputStreamWriter(
|
||||
new FileOutputStream(file), "UTF-8");
|
||||
try {
|
||||
for (String arg : args) {
|
||||
stream.write("@Grab('" + arg + "')");
|
||||
}
|
||||
// Dummy class to force compiler to do grab
|
||||
stream.write("class Installer {}");
|
||||
}
|
||||
finally {
|
||||
stream.close();
|
||||
}
|
||||
// Windows paths get tricky unless you work with URI
|
||||
return file.getAbsoluteFile().toURI().toString();
|
||||
}
|
||||
|
||||
private static File getDefaultLibDirectory() {
|
||||
String home = SystemPropertyUtils
|
||||
.resolvePlaceholders("${spring.home:${SPRING_HOME:.}}");
|
||||
final File lib = new File(home, "lib");
|
||||
return lib;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private interface FileProcessorFactory {
|
||||
FileProcessor processor(File lib);
|
||||
}
|
||||
|
||||
private interface FileProcessor {
|
||||
void processFile(File file) throws IOException;
|
||||
}
|
||||
|
||||
private static class DeleteNotTheCliProcessor implements FileProcessor {
|
||||
@Override
|
||||
public void processFile(File file) throws IOException {
|
||||
if (!file.getName().startsWith("spring-boot-cli")) {
|
||||
file.delete();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class InstallFileProcessorFactory implements FileProcessorFactory {
|
||||
|
||||
@Override
|
||||
public FileProcessor processor(final File lib) {
|
||||
Log.info("Installing into: " + lib);
|
||||
lib.mkdirs();
|
||||
return new FileProcessor() {
|
||||
@Override
|
||||
public void processFile(File file) throws IOException {
|
||||
FileCopyUtils.copy(file, new File(lib, file.getName()));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class UninstallFileProcessorFactory implements FileProcessorFactory {
|
||||
|
||||
@Override
|
||||
public FileProcessor processor(final File lib) {
|
||||
Log.info("Uninstalling from: " + lib);
|
||||
return new FileProcessor() {
|
||||
@Override
|
||||
public void processFile(File file) throws IOException {
|
||||
new File(lib, file.getName()).delete();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,172 @@
|
|||
/*
|
||||
* Copyright 2012-2014 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.cli.command.install;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileReader;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
|
||||
import joptsimple.OptionSet;
|
||||
|
||||
import org.springframework.boot.cli.command.options.CompilerOptionHandler;
|
||||
import org.springframework.boot.cli.command.options.OptionSetGroovyCompilerConfiguration;
|
||||
import org.springframework.boot.cli.compiler.GroovyCompilerConfiguration;
|
||||
import org.springframework.boot.cli.compiler.RepositoryConfigurationFactory;
|
||||
import org.springframework.boot.cli.compiler.grape.RepositoryConfiguration;
|
||||
import org.springframework.boot.cli.util.Log;
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
import org.springframework.util.SystemPropertyUtils;
|
||||
|
||||
/**
|
||||
* @author Andy Wilkinson
|
||||
* @since 1.2.0
|
||||
*/
|
||||
class Installer {
|
||||
|
||||
private final DependencyResolver dependencyResolver;
|
||||
|
||||
private final Properties installCounts;
|
||||
|
||||
Installer(OptionSet options, CompilerOptionHandler compilerOptionHandler)
|
||||
throws IOException {
|
||||
this(new GroovyGrabDependencyResolver(createCompilerConfiguration(options,
|
||||
compilerOptionHandler)));
|
||||
}
|
||||
|
||||
Installer(DependencyResolver resolver) throws IOException {
|
||||
this.dependencyResolver = resolver;
|
||||
this.installCounts = loadInstallCounts();
|
||||
}
|
||||
|
||||
private static GroovyCompilerConfiguration createCompilerConfiguration(
|
||||
OptionSet options, CompilerOptionHandler compilerOptionHandler) {
|
||||
List<RepositoryConfiguration> repositoryConfiguration = RepositoryConfigurationFactory
|
||||
.createDefaultRepositoryConfiguration();
|
||||
|
||||
return new OptionSetGroovyCompilerConfiguration(options, compilerOptionHandler,
|
||||
repositoryConfiguration) {
|
||||
@Override
|
||||
public boolean isAutoconfigure() {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static Properties loadInstallCounts() throws IOException {
|
||||
|
||||
Properties properties = new Properties();
|
||||
|
||||
File installed = getInstalled();
|
||||
if (installed.exists()) {
|
||||
FileReader reader = new FileReader(installed);
|
||||
properties.load(reader);
|
||||
reader.close();
|
||||
}
|
||||
|
||||
return properties;
|
||||
}
|
||||
|
||||
private void saveInstallCounts() throws IOException {
|
||||
FileWriter writer = new FileWriter(getInstalled());
|
||||
try {
|
||||
this.installCounts.store(writer, null);
|
||||
}
|
||||
finally {
|
||||
writer.close();
|
||||
}
|
||||
}
|
||||
|
||||
public void install(List<String> artifactIdentifiers) throws Exception {
|
||||
File libDirectory = getDefaultLibDirectory();
|
||||
libDirectory.mkdirs();
|
||||
|
||||
Log.info("Installing into: " + libDirectory);
|
||||
|
||||
List<File> artifactFiles = this.dependencyResolver.resolve(artifactIdentifiers);
|
||||
for (File artifactFile : artifactFiles) {
|
||||
int installCount = getInstallCount(artifactFile);
|
||||
if (installCount == 0) {
|
||||
FileCopyUtils.copy(artifactFile,
|
||||
new File(libDirectory, artifactFile.getName()));
|
||||
}
|
||||
setInstallCount(artifactFile, installCount + 1);
|
||||
}
|
||||
|
||||
saveInstallCounts();
|
||||
}
|
||||
|
||||
private int getInstallCount(File file) {
|
||||
String countString = this.installCounts.getProperty(file.getName());
|
||||
if (countString == null) {
|
||||
return 0;
|
||||
}
|
||||
return Integer.valueOf(countString);
|
||||
}
|
||||
|
||||
private void setInstallCount(File file, int count) {
|
||||
if (count == 0) {
|
||||
this.installCounts.remove(file.getName());
|
||||
}
|
||||
else {
|
||||
this.installCounts.setProperty(file.getName(), Integer.toString(count));
|
||||
}
|
||||
}
|
||||
|
||||
public void uninstall(List<String> artifactIdentifiers) throws Exception {
|
||||
File libDirectory = getDefaultLibDirectory();
|
||||
|
||||
Log.info("Uninstalling from: " + libDirectory);
|
||||
|
||||
List<File> artifactFiles = this.dependencyResolver.resolve(artifactIdentifiers);
|
||||
for (File artifactFile : artifactFiles) {
|
||||
int installCount = getInstallCount(artifactFile);
|
||||
if (installCount <= 1) {
|
||||
new File(libDirectory, artifactFile.getName()).delete();
|
||||
}
|
||||
setInstallCount(artifactFile, installCount - 1);
|
||||
}
|
||||
|
||||
saveInstallCounts();
|
||||
}
|
||||
|
||||
public void uninstallAll() throws Exception {
|
||||
File libDirectory = getDefaultLibDirectory();
|
||||
|
||||
Log.info("Uninstalling from: " + libDirectory);
|
||||
|
||||
for (String name : this.installCounts.stringPropertyNames()) {
|
||||
new File(libDirectory, name).delete();
|
||||
}
|
||||
|
||||
this.installCounts.clear();
|
||||
saveInstallCounts();
|
||||
}
|
||||
|
||||
private static File getDefaultLibDirectory() {
|
||||
String home = SystemPropertyUtils
|
||||
.resolvePlaceholders("${spring.home:${SPRING_HOME:.}}");
|
||||
final File lib = new File(home, "lib");
|
||||
return lib;
|
||||
}
|
||||
|
||||
private static File getInstalled() {
|
||||
return new File(getDefaultLibDirectory(), ".installed");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,91 @@
|
|||
/*
|
||||
* Copyright 2012-2014 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.cli.command.install;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import joptsimple.OptionSet;
|
||||
import joptsimple.OptionSpec;
|
||||
|
||||
import org.springframework.boot.cli.command.Command;
|
||||
import org.springframework.boot.cli.command.OptionParsingCommand;
|
||||
import org.springframework.boot.cli.command.options.CompilerOptionHandler;
|
||||
import org.springframework.boot.cli.command.status.ExitStatus;
|
||||
import org.springframework.boot.cli.util.Log;
|
||||
|
||||
/**
|
||||
* {@link Command} to uninstall dependencies from the CLI's lib directory
|
||||
*
|
||||
* @author Dave Syer
|
||||
* @author Andy Wilkinson
|
||||
* @since 1.2.0
|
||||
*/
|
||||
public class UninstallCommand extends OptionParsingCommand {
|
||||
|
||||
public UninstallCommand() {
|
||||
super("uninstall", "Uninstall dependencies from the lib directory",
|
||||
new UninstallOptionHandler());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsageHelp() {
|
||||
return "[options] <coordinates>";
|
||||
}
|
||||
|
||||
private static class UninstallOptionHandler extends CompilerOptionHandler {
|
||||
|
||||
private OptionSpec<Void> allOption;
|
||||
|
||||
@Override
|
||||
protected void doOptions() {
|
||||
this.allOption = option("all", "Uninstall all");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ExitStatus run(OptionSet options) throws Exception {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
List<String> args = (List<String>) options.nonOptionArguments();
|
||||
|
||||
try {
|
||||
if (options.has(this.allOption)) {
|
||||
if (!args.isEmpty()) {
|
||||
throw new IllegalArgumentException(
|
||||
"Please use --all without specifying any dependencies");
|
||||
}
|
||||
|
||||
new Installer(options, this).uninstallAll();
|
||||
}
|
||||
if (args.isEmpty()) {
|
||||
throw new IllegalArgumentException(
|
||||
"Please specify at least one dependency, in the form group:artifact:version, to uninstall");
|
||||
}
|
||||
|
||||
new Installer(options, this).uninstall(args);
|
||||
}
|
||||
catch (Exception e) {
|
||||
String message = e.getMessage();
|
||||
Log.error(message != null ? message : e.getClass().toString());
|
||||
}
|
||||
|
||||
return ExitStatus.OK;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,130 @@
|
|||
/*
|
||||
* Copyright 2012-2014 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.cli.command.install;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.boot.cli.compiler.GroovyCompilerConfiguration;
|
||||
import org.springframework.boot.cli.compiler.GroovyCompilerScope;
|
||||
import org.springframework.boot.cli.compiler.RepositoryConfigurationFactory;
|
||||
import org.springframework.boot.cli.compiler.grape.RepositoryConfiguration;
|
||||
|
||||
import static org.hamcrest.Matchers.hasItems;
|
||||
import static org.hamcrest.Matchers.hasSize;
|
||||
import static org.hamcrest.Matchers.startsWith;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link GroovyGrabDependencyResolver}.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
public class GroovyGrabDependencyResolverTests {
|
||||
|
||||
private DependencyResolver resolver;
|
||||
|
||||
@Before
|
||||
public void setupResolver() {
|
||||
GroovyCompilerConfiguration configuration = new GroovyCompilerConfiguration() {
|
||||
|
||||
@Override
|
||||
public boolean isGuessImports() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isGuessDependencies() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAutoconfigure() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GroovyCompilerScope getScope() {
|
||||
return GroovyCompilerScope.DEFAULT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<RepositoryConfiguration> getRepositoryConfiguration() {
|
||||
return RepositoryConfigurationFactory
|
||||
.createDefaultRepositoryConfiguration();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getClasspath() {
|
||||
return new String[] { "." };
|
||||
}
|
||||
};
|
||||
this.resolver = new GroovyGrabDependencyResolver(configuration);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveArtifactWithNoDependencies() throws Exception {
|
||||
List<File> resolved = this.resolver.resolve(Arrays
|
||||
.asList("commons-logging:commons-logging:1.1.3"));
|
||||
assertThat(resolved, hasSize(1));
|
||||
assertThat(getNames(resolved), hasItems("commons-logging-1.1.3.jar"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveArtifactWithDependencies() throws Exception {
|
||||
List<File> resolved = this.resolver.resolve(Arrays
|
||||
.asList("org.springframework:spring-core:4.1.1.RELEASE"));
|
||||
assertThat(resolved, hasSize(2));
|
||||
assertThat(getNames(resolved),
|
||||
hasItems("commons-logging-1.1.3.jar", "spring-core-4.1.1.RELEASE.jar"));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Test
|
||||
public void resolveShorthandArtifactWithDependencies() throws Exception {
|
||||
List<File> resolved = this.resolver.resolve(Arrays.asList("spring-core"));
|
||||
assertThat(resolved, hasSize(2));
|
||||
assertThat(getNames(resolved),
|
||||
hasItems(startsWith("commons-logging-"), startsWith("spring-core-")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveMultipleArtifacts() throws Exception {
|
||||
List<File> resolved = this.resolver.resolve(Arrays.asList("junit:junit:4.11",
|
||||
"commons-logging:commons-logging:1.1.3"));
|
||||
assertThat(resolved, hasSize(3));
|
||||
assertThat(
|
||||
getNames(resolved),
|
||||
hasItems("junit-4.11.jar", "commons-logging-1.1.3.jar",
|
||||
"hamcrest-core-1.3.jar"));
|
||||
}
|
||||
|
||||
public Set<String> getNames(Collection<File> files) {
|
||||
Set<String> names = new HashSet<String>(files.size());
|
||||
for (File file : files) {
|
||||
names.add(file.getName());
|
||||
}
|
||||
return names;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,155 @@
|
|||
/*
|
||||
* Copyright 2012-2014 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.cli.command.install;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
import org.springframework.util.FileSystemUtils;
|
||||
|
||||
import static org.hamcrest.Matchers.contains;
|
||||
import static org.hamcrest.Matchers.containsInAnyOrder;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* Tests for {@link Installer}
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
public class InstallerTests {
|
||||
|
||||
public DependencyResolver resolver = mock(DependencyResolver.class);
|
||||
|
||||
@Rule
|
||||
public TemporaryFolder tempFolder = new TemporaryFolder();
|
||||
|
||||
private Installer installer;
|
||||
|
||||
@Before
|
||||
public void setUp() throws IOException {
|
||||
System.setProperty("spring.home", "target");
|
||||
FileSystemUtils.deleteRecursively(new File("target/lib"));
|
||||
|
||||
this.installer = new Installer(this.resolver);
|
||||
}
|
||||
|
||||
@After
|
||||
public void cleanUp() {
|
||||
System.clearProperty("spring.home");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void installNewDependency() throws Exception {
|
||||
File foo = createTemporaryFile("foo.jar");
|
||||
|
||||
when(this.resolver.resolve(Arrays.asList("foo"))).thenReturn(Arrays.asList(foo));
|
||||
this.installer.install(Arrays.asList("foo"));
|
||||
|
||||
assertThat(getNamesOfFilesInLib(), containsInAnyOrder("foo.jar", ".installed"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void installAndUninstall() throws Exception {
|
||||
File foo = createTemporaryFile("foo.jar");
|
||||
|
||||
when(this.resolver.resolve(Arrays.asList("foo"))).thenReturn(Arrays.asList(foo));
|
||||
|
||||
this.installer.install(Arrays.asList("foo"));
|
||||
this.installer.uninstall(Arrays.asList("foo"));
|
||||
|
||||
assertThat(getNamesOfFilesInLib(), contains(".installed"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void installAndUninstallWithCommonDependencies() throws Exception {
|
||||
File alpha = createTemporaryFile("alpha.jar");
|
||||
File bravo = createTemporaryFile("bravo.jar");
|
||||
File charlie = createTemporaryFile("charlie.jar");
|
||||
|
||||
when(this.resolver.resolve(Arrays.asList("bravo"))).thenReturn(
|
||||
Arrays.asList(bravo, alpha));
|
||||
|
||||
when(this.resolver.resolve(Arrays.asList("charlie"))).thenReturn(
|
||||
Arrays.asList(charlie, alpha));
|
||||
|
||||
this.installer.install(Arrays.asList("bravo"));
|
||||
|
||||
assertThat(getNamesOfFilesInLib(),
|
||||
containsInAnyOrder("alpha.jar", "bravo.jar", ".installed"));
|
||||
|
||||
this.installer.install(Arrays.asList("charlie"));
|
||||
|
||||
assertThat(getNamesOfFilesInLib(),
|
||||
containsInAnyOrder("alpha.jar", "bravo.jar", "charlie.jar", ".installed"));
|
||||
|
||||
this.installer.uninstall(Arrays.asList("bravo"));
|
||||
|
||||
assertThat(getNamesOfFilesInLib(),
|
||||
containsInAnyOrder("alpha.jar", "charlie.jar", ".installed"));
|
||||
|
||||
this.installer.uninstall(Arrays.asList("charlie"));
|
||||
|
||||
assertThat(getNamesOfFilesInLib(), containsInAnyOrder(".installed"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void uninstallAll() throws Exception {
|
||||
File alpha = createTemporaryFile("alpha.jar");
|
||||
File bravo = createTemporaryFile("bravo.jar");
|
||||
File charlie = createTemporaryFile("charlie.jar");
|
||||
|
||||
when(this.resolver.resolve(Arrays.asList("bravo"))).thenReturn(
|
||||
Arrays.asList(bravo, alpha));
|
||||
|
||||
when(this.resolver.resolve(Arrays.asList("charlie"))).thenReturn(
|
||||
Arrays.asList(charlie, alpha));
|
||||
|
||||
this.installer.install(Arrays.asList("bravo"));
|
||||
this.installer.install(Arrays.asList("charlie"));
|
||||
|
||||
assertThat(getNamesOfFilesInLib(),
|
||||
containsInAnyOrder("alpha.jar", "bravo.jar", "charlie.jar", ".installed"));
|
||||
|
||||
this.installer.uninstallAll();
|
||||
|
||||
assertThat(getNamesOfFilesInLib(), containsInAnyOrder(".installed"));
|
||||
}
|
||||
|
||||
private Set<String> getNamesOfFilesInLib() {
|
||||
Set<String> names = new HashSet<String>();
|
||||
for (File file : new File("target/lib").listFiles()) {
|
||||
names.add(file.getName());
|
||||
}
|
||||
return names;
|
||||
}
|
||||
|
||||
private File createTemporaryFile(String name) throws IOException {
|
||||
File temporaryFile = this.tempFolder.newFile(name);
|
||||
temporaryFile.deleteOnExit();
|
||||
return temporaryFile;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue