Commit f5ab0cf2 authored by Andy Wilkinson's avatar Andy Wilkinson Committed by Andy Wilkinson

Update CLI install to prefer local snapshots to those in a remote repo

Previously, InstallCommand used a custom Grape root and then walked
the tree of files downloaded by Aether to determine which files it
should install or uninstall. In some scenarios two files for the
same module would be present: one named foo-1.0.0.BUILD-SNAPSHOT.jar
and one named foo-1.0.0.BUILD-20140905.091809-2.jar. The former is
from the local repository and the later is from a remote repository.
In this case, the visitor would do the wrong thing and the latter
would be installed into lib.

This commit updates InstallCommand to determine the jars that it
should process by consulting GroovyCompiler's classpath, rather than
by walking Aether's cache. This approach selects the correct jar as
Aether has already figured this out as part of resolving the
dependency. It also brings InstallCommand into line with JarCommand.

The previous implementation used Java 7-specific File APIs. As part
of the above-described change this usage has been removed. The
install command can now be used on Java 6.

Fixes gh-1515
parent 84249da9
...@@ -19,18 +19,15 @@ import java.io.File; ...@@ -19,18 +19,15 @@ import java.io.File;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStreamWriter; import java.io.OutputStreamWriter;
import java.nio.file.FileVisitResult; import java.net.URISyntaxException;
import java.nio.file.FileVisitor; import java.net.URL;
import java.nio.file.Files; import java.util.ArrayList;
import java.nio.file.Path; import java.util.Arrays;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.List; import java.util.List;
import joptsimple.OptionSet; import joptsimple.OptionSet;
import joptsimple.OptionSpec; import joptsimple.OptionSpec;
import org.codehaus.plexus.util.FileUtils;
import org.springframework.boot.cli.command.Command; import org.springframework.boot.cli.command.Command;
import org.springframework.boot.cli.command.OptionParsingCommand; import org.springframework.boot.cli.command.OptionParsingCommand;
import org.springframework.boot.cli.command.options.CompilerOptionHandler; import org.springframework.boot.cli.command.options.CompilerOptionHandler;
...@@ -41,36 +38,38 @@ import org.springframework.boot.cli.compiler.GroovyCompilerConfiguration; ...@@ -41,36 +38,38 @@ import org.springframework.boot.cli.compiler.GroovyCompilerConfiguration;
import org.springframework.boot.cli.compiler.RepositoryConfigurationFactory; import org.springframework.boot.cli.compiler.RepositoryConfigurationFactory;
import org.springframework.boot.cli.compiler.grape.RepositoryConfiguration; import org.springframework.boot.cli.compiler.grape.RepositoryConfiguration;
import org.springframework.boot.cli.util.Log; import org.springframework.boot.cli.util.Log;
import org.springframework.util.FileCopyUtils;
import org.springframework.util.SystemPropertyUtils; import org.springframework.util.SystemPropertyUtils;
/** /**
* {@link Command} to install additional dependencies into the CLI. * {@link Command} to install additional dependencies into the CLI.
* *
* @author Dave Syer * @author Dave Syer
* @author Andy Wilkinson
*/ */
public class InstallCommand extends OptionParsingCommand { public class InstallCommand extends OptionParsingCommand {
public static Command install() { public static Command install() {
return new InstallCommand("install", "Install dependencies to lib directory", return new InstallCommand("install", "Install dependencies to lib directory",
new InstallFileVisitorFactory()); new InstallFileProcessorFactory());
} }
public static Command uninstall() { public static Command uninstall() {
return new InstallCommand("uninstall", return new InstallCommand("uninstall",
"Uninstall dependencies from a lib directory", "Uninstall dependencies from a lib directory",
new UninstallFileVisitorFactory()); new UninstallFileProcessorFactory());
} }
private InstallCommand(String name, String description, FileVisitorFactory visitor) { private InstallCommand(String name, String description, FileProcessorFactory visitor) {
super(name, description, new InstallOptionHandler(visitor)); super(name, description, new InstallOptionHandler(visitor));
} }
private static final class InstallOptionHandler extends CompilerOptionHandler { private static final class InstallOptionHandler extends CompilerOptionHandler {
private FileVisitorFactory factory; private FileProcessorFactory factory;
private OptionSpec<Void> allOption; private OptionSpec<Void> allOption;
public InstallOptionHandler(FileVisitorFactory factory) { public InstallOptionHandler(FileProcessorFactory factory) {
this.factory = factory; this.factory = factory;
} }
...@@ -111,43 +110,60 @@ public class InstallCommand extends OptionParsingCommand { ...@@ -111,43 +110,60 @@ public class InstallCommand extends OptionParsingCommand {
} }
}; };
Path tmpdir = Files.createTempDirectory("SpringInstallCommand")
.toAbsolutePath();
String grapeRoot = System.getProperty("grape.root");
System.setProperty("grape.root", tmpdir.toString());
GroovyCompiler groovyCompiler = new GroovyCompiler(configuration); GroovyCompiler groovyCompiler = new GroovyCompiler(configuration);
try { try {
if (!args.isEmpty()) { if (!args.isEmpty()) {
List<URL> initialUrls = getClassPathUrls(groovyCompiler);
groovyCompiler.compile(createSources(args)); groovyCompiler.compile(createSources(args));
installJars(tmpdir); List<URL> urlsToProcessor = getClassPathUrls(groovyCompiler);
urlsToProcessor.removeAll(initialUrls);
processJars(urlsToProcessor);
} }
FileUtils.deleteDirectory(tmpdir.toFile());
} }
catch (Exception ex) { catch (Exception ex) {
String message = ex.getMessage(); String message = ex.getMessage();
Log.error(message != null ? message : ex.getClass().toString()); Log.error(message != null ? message : ex.getClass().toString());
} }
finally {
if (grapeRoot != null) {
System.setProperty("grape.root", grapeRoot);
}
else {
System.clearProperty("grape.root");
}
}
return ExitStatus.OK; return ExitStatus.OK;
} }
private void installJars(Path tmpdir) throws IOException { 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(); File lib = getDefaultLibDirectory();
Files.walkFileTree(tmpdir, this.factory.visitor(lib));
FileProcessor processor = this.factory.processor(lib);
for (URL url : urlsToProcess) {
File file = toFile(url);
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 { private void uninstallAllJars() throws IOException {
File lib = getDefaultLibDirectory(); File lib = getDefaultLibDirectory();
Files.walkFileTree(lib.toPath(), new DeleteNotTheCliVisitor()); 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 { private String createSources(List<String> args) throws IOException {
...@@ -177,70 +193,51 @@ public class InstallCommand extends OptionParsingCommand { ...@@ -177,70 +193,51 @@ public class InstallCommand extends OptionParsingCommand {
} }
private interface FileVisitorFactory { private interface FileProcessorFactory {
FileVisitor<Path> visitor(File lib); FileProcessor processor(File lib);
}
private interface FileProcessor {
void processFile(File file) throws IOException;
} }
private static class DeleteNotTheCliVisitor extends SimpleFileVisitor<Path> { private static class DeleteNotTheCliProcessor implements FileProcessor {
@Override @Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) public void processFile(File file) throws IOException {
throws IOException { if (!file.getName().startsWith("spring-boot-cli")) {
String name = file.getFileName().toString(); file.delete();
if (name.endsWith(".jar") && !name.startsWith("spring-boot-cli")) {
file.toFile().delete();
} }
return FileVisitResult.CONTINUE;
} }
} }
private static class InstallFileVisitorFactory implements FileVisitorFactory { private static class InstallFileProcessorFactory implements FileProcessorFactory {
@Override @Override
public SimpleFileVisitor<Path> visitor(final File lib) { public FileProcessor processor(final File lib) {
Log.info("Installing into: " + lib); Log.info("Installing into: " + lib);
lib.mkdirs(); lib.mkdirs();
return new SimpleFileVisitor<Path>() { return new FileProcessor() {
@Override @Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) public void processFile(File file) throws IOException {
throws IOException { FileCopyUtils.copy(file, new File(lib, file.getName()));
if (file.getFileName().toString().endsWith(".jar")) {
Files.copy(file, new FileOutputStream(new File(lib, file
.getFileName().toString())));
return FileVisitResult.SKIP_SIBLINGS;
}
return FileVisitResult.CONTINUE;
} }
}; };
} }
} }
private static class UninstallFileVisitorFactory implements FileVisitorFactory { private static class UninstallFileProcessorFactory implements FileProcessorFactory {
@Override @Override
public SimpleFileVisitor<Path> visitor(final File lib) { public FileProcessor processor(final File lib) {
Log.info("Uninstalling from: " + lib); Log.info("Uninstalling from: " + lib);
if (!lib.exists()) { return new FileProcessor() {
return new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
throws IOException {
return FileVisitResult.TERMINATE;
}
};
}
return new SimpleFileVisitor<Path>() {
@Override @Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) public void processFile(File file) throws IOException {
throws IOException { new File(lib, file.getName()).delete();
if (file.getFileName().toString().endsWith(".jar")) {
new File(lib, file.getFileName().toString()).delete();
return FileVisitResult.SKIP_SIBLINGS;
}
return FileVisitResult.CONTINUE;
} }
}; };
} }
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment