jem/src/main/java/com/github/jrubygradle/jem/internal/GemInstaller.java

236 lines
8.4 KiB
Java

package com.github.jrubygradle.jem.internal;
import com.github.jrubygradle.jem.GemInstallEvent;
import com.github.jrubygradle.jem.GemInstallResult;
import org.jboss.shrinkwrap.api.ArchiveFormat;
import org.jboss.shrinkwrap.api.ArchivePath;
import org.jboss.shrinkwrap.api.GenericArchive;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.Node;
import org.jboss.shrinkwrap.api.exporter.ExplodedExporter;
import org.jboss.shrinkwrap.api.importer.TarImporter;
import org.jboss.shrinkwrap.impl.base.io.IOUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.github.jrubygradle.jem.Gem;
import com.github.jrubygradle.jem.GemInstaller.DuplicateBehavior;
import java.io.*;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.GZIPInputStream;
public class GemInstaller {
public static final String[] GEM_HOME_DIRS = {"bin", "build_info", "doc",
"extensions", "gems", "specifications"};
protected Logger logger = LoggerFactory.getLogger(GemInstaller.class);
protected File installDirectory;
protected List<File> gems;
public GemInstaller(String installDir, List<File> gems) {
this.installDirectory = new File(installDir);
this.gems = gems;
}
/** Install and overwrite anything that stands in the way */
public void install() {
install(DuplicateBehavior.OVERWRITE);
}
public void install(DuplicateBehavior onDuplicateBehavior) {
install(null, onDuplicateBehavior);
}
public void install(GemInstallEvent eventCallback, DuplicateBehavior onDuplicateBehavior) {
if (!mkdirs()) {
/* raise some exception? */
}
for (File gemFile : this.gems) {
boolean shouldContinue = true;
Exception caughtException = null;
Gem gem = null;
try {
gem = installGem(installDirectory, gemFile, onDuplicateBehavior);
}
catch (Exception exc) {
caughtException = exc;
}
if (eventCallback instanceof GemInstallEvent) {
shouldContinue = eventCallback.onInstall(new GemInstallResult(gem,
gemFile,
installDirectory,
caughtException));
}
if (!shouldContinue) {
break;
}
}
}
public Gem installGem(File installDir, File gem, DuplicateBehavior onDuplicate) {
/* TODO: isValidGem? */
Gem gemMetadata;
GenericArchive gemArchive = ShrinkWrap.create(TarImporter.class)
.importFrom(gem).as(GenericArchive.class);
Node metadata = gemArchive.get("metadata.gz");
GenericArchive dataArchive = gemArchive.getAsType(GenericArchive.class,
"data.tar.gz",
ArchiveFormat.TAR_GZ);
try {
gemMetadata = Gem.fromFile(new GZIPInputStream(metadata.getAsset().openStream()));
}
catch (IOException ex) {
logger.error("Failed to process the metadata", ex);
return null;
}
logger.info(String.format("We've processed metadata for %s at version %s",
gemMetadata.name, gemMetadata.version.version));
try {
extractSpecification(installDir, gemMetadata);
}
catch (Exception ex) {
logger.error(String.format("Could not extract the gem specification for %s into %s",
gemMetadata.name, installDir), ex);
}
extractData(installDir, dataArchive, gemMetadata);
try {
extractExecutables(installDir, dataArchive, gemMetadata);
}
catch (Exception ex) {
logger.error(String.format("Could not extract the gem executables for %s into %s",
gemMetadata.name, installDir), ex);
}
return gemMetadata;
}
/**
* Create the requisite directories to map a GEM_HOME structure, namely:
* bin/
* build_info/
* cache/
* doc/
* extensions/
* gems/
* specifications/
*
* @return True if all directories were created successfully
*/
public boolean mkdirs() {
boolean success = true;
for (String dirName : GEM_HOME_DIRS) {
File newDir = new File(installDirectory, dirName);
logger.info(String.format("Attempting to create: %s", newDir.getAbsolutePath()));
if (!newDir.mkdirs()) {
logger.error(String.format("Failed to make %s, bailing on mkdirs()", newDir.getAbsolutePath()));
return false;
}
}
return success;
}
/**
* Primarily meant to be an internal method which will determine whether the
* given {@code java.io.File} is a valid gem archive or not. This includes looking
* inside it to see that it is a legitimate tar file
*
* @param gemFile Fully formed {@code java.io.File} object referencing a gem
* @return true if the file does in fact walk and talk like a gem
*/
public boolean isValidGem(File gemFile) {
logger.info(String.format("Validating gem %s", gemFile));
/* If it doesn"t end with gem, let"s not even consider it a gem file */
if (!gemFile.getAbsolutePath().endsWith(".gem")) {
return false;
}
return false;
}
public String gemFullName(Gem gem) {
String fullName = String.format("%s-%s", gem.name, gem.version.version);
if ((gem.platform instanceof String) && !(gem.platform.equals("ruby"))) {
fullName = String.format("%s-%s", fullName, gem.platform);
}
return fullName;
}
/** Extract the gemspec file from the {@code Gem} provided into the ${installDir}/specifications */
protected void extractSpecification(File installDir, Gem gem) throws Exception {
String outputFileName = String.format("%s.gemspec", gemFullName(gem));
File specDir = new File(installDir, "specifications");
FileOutputStream output = new FileOutputStream(new File(specDir, outputFileName));
PrintWriter writer = new PrintWriter(output);
writer.write(gem.toRuby());
writer.flush();
}
/**
* Extract the data.tar.gz contents into gems/full-name
*
* @param installDir relative or absoluate path to the installation destination directory
* @param dataTarGz instance of the archive from which to extract the gem data
* @param gem Gem instance containing the metadata about the provided dataTarGz archive
*/
protected void extractData(File installDir, GenericArchive dataTarGz, Gem gem) {
File outputDir = new File(installDir, "gems");
outputDir.mkdirs();
dataTarGz.as(ExplodedExporter.class).exportExploded(outputDir, gemFullName(gem));
}
/**
* Extract the executables from the specified bindir
*
* @param installDir relative or absoluate path to the installation destination directory
* @param dataTarGz instance of the archive from which to extract the gem executables
* @param gem Gem instance containing the metadata about the provided dataTarGz archive
* @throws Exception catch-all exceptions incurred while attempting to read/write out files
*/
protected void extractExecutables(File installDir, GenericArchive dataTarGz, Gem gem) throws Exception {
/*
* default to "bin" if the bindir isn"t otherwise set, it"s not clear whether
* it is always guaranteed to be set or not though
*/
String binDir = gem.bindir;
if (!(binDir instanceof String)) {
binDir = "bin";
}
File bin = new File(installDir, binDir);
bin.mkdirs();
Pattern p = Pattern.compile("/" + binDir + "/(.*)?");
for (Map.Entry<ArchivePath, Node> entry : dataTarGz.getContent().entrySet()) {
ArchivePath path = entry.getKey();
Node node = entry.getValue();
Matcher m = p.matcher(path.get());
if (m.matches()) {
File fullOutputPath = new File(bin, m.toMatchResult().group(1));
fullOutputPath.createNewFile();
IOUtil.copy(node.getAsset().openStream(),
new FileOutputStream(fullOutputPath));
}
}
}
}