mirror of https://github.com/jruby-gradle/jem
parent
55fc37d9cf
commit
65fd115fa3
|
@ -1,10 +1,12 @@
|
|||
apply plugin: 'java'
|
||||
apply plugin: 'groovy'
|
||||
apply plugin: 'maven'
|
||||
apply plugin: 'codenarc'
|
||||
|
||||
version = '0.1'
|
||||
version = '0.1.0'
|
||||
group = 'com.github.jrubygradle'
|
||||
description = 'A Groovy-based library for managing Ruby gems'
|
||||
description = 'A library for managing Ruby gems'
|
||||
defaultTasks 'check', 'assemble'
|
||||
|
||||
repositories {
|
||||
jcenter()
|
||||
|
|
|
@ -1,17 +0,0 @@
|
|||
package com.github.jrubygradle.jem
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import groovy.transform.CompileStatic
|
||||
import groovy.transform.TypeChecked
|
||||
|
||||
/**
|
||||
*/
|
||||
@TypeChecked
|
||||
@CompileStatic
|
||||
class Dependency {
|
||||
@JsonProperty
|
||||
String name
|
||||
|
||||
@JsonProperty
|
||||
List<Requirement> requirements
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
package com.github.jrubygradle.jem;
|
||||
|
||||
import java.util.List;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
/**
|
||||
*/
|
||||
public class Dependency {
|
||||
@JsonProperty
|
||||
public String name;
|
||||
|
||||
@JsonProperty
|
||||
public List<Requirement> requirements;
|
||||
}
|
|
@ -1,135 +0,0 @@
|
|||
package com.github.jrubygradle.jem
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory
|
||||
import groovy.json.JsonOutput
|
||||
import groovy.transform.CompileStatic
|
||||
import groovy.transform.TypeChecked
|
||||
|
||||
/**
|
||||
* Plain Old Groovy Object for an enumeration of metadata provided by a gem
|
||||
*/
|
||||
@TypeChecked
|
||||
@CompileStatic
|
||||
class Gem {
|
||||
@JsonProperty
|
||||
String name
|
||||
|
||||
@JsonProperty
|
||||
Version version
|
||||
|
||||
@JsonProperty
|
||||
String description
|
||||
|
||||
@JsonProperty
|
||||
String platform
|
||||
|
||||
@JsonProperty
|
||||
Object email
|
||||
|
||||
@JsonProperty
|
||||
String homepage
|
||||
|
||||
@JsonProperty
|
||||
List<String> authors = []
|
||||
|
||||
@JsonProperty
|
||||
List<String> files
|
||||
|
||||
@JsonProperty(value='test_files')
|
||||
List<String> testFiles
|
||||
|
||||
@JsonProperty
|
||||
List<String> executables
|
||||
|
||||
@JsonProperty
|
||||
String bindir
|
||||
|
||||
@JsonProperty(value='require_paths')
|
||||
List<String> requirePaths
|
||||
|
||||
@JsonProperty
|
||||
List<String> licenses
|
||||
|
||||
@JsonProperty(value='specification_version')
|
||||
Integer specificationVersion
|
||||
|
||||
@JsonProperty(value='rubygems_version')
|
||||
String rubygemsVersion
|
||||
|
||||
/**
|
||||
* Take the given argument and produce a {@code Gem} instance
|
||||
*
|
||||
* @param metadata a {@code java.lang.String}, a {@code java.io.File} or a {@code java.util.zip.GZIPInputStream}
|
||||
* @return
|
||||
*/
|
||||
static Gem fromFile(Object metadata) {
|
||||
if (metadata instanceof String) {
|
||||
return createGemFromFile(new File(metadata))
|
||||
}
|
||||
if (metadata instanceof File) {
|
||||
return createGemFromFile(metadata as File)
|
||||
}
|
||||
if (metadata instanceof InputStream) {
|
||||
return createGemFromInputStream(metadata as InputStream)
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Output the gemspec stub for this file
|
||||
*
|
||||
* See <https://github.com/rubygems/rubygems/blob/165030689defe16680b7f336232db62024f49de4/lib/rubygems/specification.rb#L2422-L2512>
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
String toRuby() {
|
||||
return """\
|
||||
# -*- encoding: utf-8 -*-
|
||||
# stub: ${name} ${version.version} ${platform} ${requirePaths.join("\0")}
|
||||
#
|
||||
# NOTE: This specification was generated by groovy-gem
|
||||
# <https://github.com/jruby-gradle/groovy-gem>
|
||||
|
||||
Gem::Specification.new do |s|
|
||||
s.name = ${sanitize(name)}
|
||||
s.version = ${sanitize(version.version)}
|
||||
s.description = ${sanitize(description)}
|
||||
s.homepage = ${sanitize(homepage)}
|
||||
s.authors = ${sanitize(authors)}
|
||||
s.email = ${sanitize(email)}
|
||||
s.licenses = ${sanitize(licenses)}
|
||||
|
||||
s.platform = ${sanitize(platform)}
|
||||
s.require_paths = ${sanitize(requirePaths)}
|
||||
s.executables = ${sanitize(executables)}
|
||||
s.rubygems_version = ${sanitize(rubygemsVersion)}
|
||||
end
|
||||
"""
|
||||
}
|
||||
|
||||
/** Convert whatever object we're given into a safe (see: JSON) reprepsentation */
|
||||
protected String sanitize(Object value) {
|
||||
return JsonOutput.toJson(value)
|
||||
}
|
||||
|
||||
private static Gem createGemFromFile(File gemMetadataFile) {
|
||||
if (!gemMetadataFile.exists()) {
|
||||
return null
|
||||
}
|
||||
return getYamlMapper().readValue(gemMetadataFile, Gem)
|
||||
}
|
||||
|
||||
private static Gem createGemFromInputStream(InputStream gemMetadataStream) {
|
||||
return getYamlMapper().readValue(gemMetadataStream, Gem)
|
||||
}
|
||||
|
||||
private static ObjectMapper getYamlMapper() {
|
||||
ObjectMapper mapper = new ObjectMapper(new YAMLFactory())
|
||||
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
|
||||
return mapper
|
||||
}
|
||||
}
|
|
@ -0,0 +1,155 @@
|
|||
package com.github.jrubygradle.jem;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOError;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Plain Old Java Object for an enumeration of metadata provided by a gem
|
||||
*/
|
||||
public class Gem {
|
||||
@JsonProperty
|
||||
public String name;
|
||||
|
||||
@JsonProperty
|
||||
public Version version;
|
||||
|
||||
@JsonProperty
|
||||
public String description;
|
||||
|
||||
@JsonProperty
|
||||
public String platform;
|
||||
|
||||
@JsonProperty
|
||||
public Object email;
|
||||
|
||||
@JsonProperty
|
||||
public String homepage;
|
||||
|
||||
@JsonProperty
|
||||
public List<String> authors;
|
||||
|
||||
@JsonProperty
|
||||
public List<String> files;
|
||||
|
||||
@JsonProperty(value="test_files")
|
||||
public List<String> testFiles;
|
||||
|
||||
@JsonProperty
|
||||
public List<String> executables;
|
||||
|
||||
@JsonProperty
|
||||
public String bindir;
|
||||
|
||||
@JsonProperty(value="require_paths")
|
||||
public List<String> requirePaths;
|
||||
|
||||
@JsonProperty
|
||||
public List<String> licenses;
|
||||
|
||||
@JsonProperty(value="specification_version")
|
||||
public Integer specificationVersion;
|
||||
|
||||
@JsonProperty(value="rubygems_version")
|
||||
public String rubygemsVersion;
|
||||
|
||||
/**
|
||||
* Take the given argument and produce a {@code Gem} instance
|
||||
*
|
||||
* @param metadata a {@code java.lang.String}, a {@code java.io.File} or a {@code java.util.zip.GZIPInputStream}
|
||||
* @return
|
||||
*/
|
||||
public static Gem fromFile(Object metadata) throws JsonProcessingException, IOException {
|
||||
if (metadata instanceof String) {
|
||||
return createGemFromFile(new File((String)metadata));
|
||||
}
|
||||
if (metadata instanceof File) {
|
||||
return createGemFromFile((File)(metadata));
|
||||
}
|
||||
if (metadata instanceof InputStream) {
|
||||
return createGemFromInputStream((InputStream)(metadata));
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Output the gemspec stub for this file
|
||||
*
|
||||
* See <https://github.com/rubygems/rubygems/blob/165030689defe16680b7f336232db62024f49de4/lib/rubygems/specification.rb#L2422-L2512>
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public String toRuby() throws JsonProcessingException {
|
||||
String[] specification = {
|
||||
"# -*- encoding: utf-8 -*-",
|
||||
"#",
|
||||
String.format("# stub: %s %s %s %s",
|
||||
name, version.version, platform, join(requirePaths.toArray(new String[0]), "\0")),
|
||||
"#",
|
||||
"# NOTE: This specification was generated by `jem`",
|
||||
"# <https://github.com/jruby-gradle/jem>",
|
||||
"",
|
||||
"Gem::Specification.new do |s|",
|
||||
" s.name = " + sanitize(name),
|
||||
" s.version = " + sanitize(version.version),
|
||||
" s.description = " + sanitize(description),
|
||||
" s.homepage = " + sanitize(homepage),
|
||||
" s.authors = " + sanitize(authors),
|
||||
" s.email = " + sanitize(email),
|
||||
" s.licenses = " + sanitize(licenses),
|
||||
"",
|
||||
" s.platform = " + sanitize(platform),
|
||||
" s.require_paths = " + sanitize(requirePaths),
|
||||
" s.executables = " + sanitize(executables),
|
||||
" s.rubygems_version = " + sanitize(rubygemsVersion),
|
||||
"end",
|
||||
};
|
||||
return join(specification);
|
||||
}
|
||||
|
||||
private String join(String[] segments) {
|
||||
return this.join(segments, System.getProperty("line.separator"));
|
||||
}
|
||||
|
||||
private String join(String[] segments, String split) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
for (String segment : segments) {
|
||||
builder.append(String.format("%s%s", segment, split));
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
/** Convert whatever object we're given into a safe (see: JSON) reprepsentation */
|
||||
protected String sanitize(Object value) throws JsonProcessingException {
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
return mapper.writeValueAsString(value);
|
||||
}
|
||||
|
||||
private static Gem createGemFromFile(File gemMetadataFile) throws
|
||||
JsonProcessingException, IOException {
|
||||
if (!gemMetadataFile.exists()) {
|
||||
return null;
|
||||
}
|
||||
return getYamlMapper().readValue(gemMetadataFile, Gem.class);
|
||||
}
|
||||
|
||||
private static Gem createGemFromInputStream(InputStream gemMetadataStream) throws
|
||||
JsonProcessingException, IOException {
|
||||
return getYamlMapper().readValue(gemMetadataStream, Gem.class);
|
||||
}
|
||||
|
||||
private static ObjectMapper getYamlMapper() {
|
||||
ObjectMapper mapper = new ObjectMapper(new YAMLFactory());
|
||||
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
|
||||
return mapper;
|
||||
}
|
||||
}
|
|
@ -1,36 +0,0 @@
|
|||
package com.github.jrubygradle.jem
|
||||
|
||||
import groovy.transform.CompileStatic
|
||||
|
||||
import com.github.jrubygradle.jem.internal.GemInstaller as GemInstallerImpl
|
||||
|
||||
/**
|
||||
* GemInstaller manages the installation of a .gem file into a given directory
|
||||
*/
|
||||
@CompileStatic
|
||||
class GemInstaller {
|
||||
static enum DuplicateBehavior {
|
||||
OVERWRITE,
|
||||
SKIP,
|
||||
FAIL
|
||||
}
|
||||
|
||||
|
||||
protected GemInstallerImpl impl
|
||||
|
||||
GemInstaller(String installDir, String gemPath) {
|
||||
this(installDir, new File(gemPath))
|
||||
}
|
||||
|
||||
GemInstaller(String installDir, File gemFile) {
|
||||
this(installDir, [gemFile])
|
||||
}
|
||||
|
||||
GemInstaller(String installDir, List<File> gemPaths) {
|
||||
impl = new GemInstallerImpl(installDir, gemPaths)
|
||||
}
|
||||
|
||||
void install() {
|
||||
impl.install()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
package com.github.jrubygradle.jem;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* GemInstaller manages the installation of a .gem file into a given directory
|
||||
*/
|
||||
public class GemInstaller {
|
||||
public static enum DuplicateBehavior {
|
||||
OVERWRITE,
|
||||
SKIP,
|
||||
FAIL
|
||||
};
|
||||
|
||||
|
||||
protected com.github.jrubygradle.jem.internal.GemInstaller impl;
|
||||
|
||||
public GemInstaller(String installDir, String gemPath) {
|
||||
this(installDir, new File(gemPath));
|
||||
}
|
||||
|
||||
public GemInstaller(String installDir, File gemFile) {
|
||||
this(installDir, Arrays.asList(gemFile));
|
||||
}
|
||||
|
||||
public GemInstaller(String installDir, List<File> gemPaths) {
|
||||
impl = new com.github.jrubygradle.jem.internal.GemInstaller(installDir, gemPaths);
|
||||
}
|
||||
|
||||
public void install() {
|
||||
impl.install();
|
||||
}
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
package com.github.jrubygradle.jem
|
||||
|
||||
/**
|
||||
* Reader for taking a .gem flie and turning it into objects
|
||||
*/
|
||||
class GemReader {
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
package com.github.jrubygradle.jem
|
||||
|
||||
import groovy.transform.CompileStatic
|
||||
import groovy.transform.TypeChecked
|
||||
|
||||
/**
|
||||
*/
|
||||
@TypeChecked
|
||||
@CompileStatic
|
||||
class Requirement {
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
package com.github.jrubygradle.jem;
|
||||
|
||||
/**
|
||||
*/
|
||||
public class Requirement {
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
package com.github.jrubygradle.jem
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import groovy.transform.CompileStatic
|
||||
|
||||
/**
|
||||
*/
|
||||
@CompileStatic
|
||||
class Version {
|
||||
@JsonProperty
|
||||
String version
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
package com.github.jrubygradle.jem;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
/**
|
||||
*/
|
||||
public class Version {
|
||||
@JsonProperty
|
||||
public String version;
|
||||
}
|
|
@ -1,169 +0,0 @@
|
|||
package com.github.jrubygradle.jem.internal
|
||||
|
||||
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 groovy.transform.CompileStatic
|
||||
|
||||
import com.github.jrubygradle.jem.Gem
|
||||
import com.github.jrubygradle.jem.GemInstaller.DuplicateBehavior
|
||||
|
||||
import java.nio.file.Files
|
||||
import java.util.zip.GZIPInputStream
|
||||
|
||||
@CompileStatic
|
||||
class GemInstaller {
|
||||
static final List<String> GEM_HOME_DIRS = ['bin', 'build_info', 'cache', 'doc',
|
||||
'extensions', 'gems', 'specifications']
|
||||
|
||||
protected Logger logger = LoggerFactory.getLogger(this.class)
|
||||
protected File installDirectory
|
||||
protected List<File> gems
|
||||
|
||||
GemInstaller(String installDir, List<File> gems) {
|
||||
this.installDirectory = new File(installDir)
|
||||
this.gems = gems
|
||||
}
|
||||
|
||||
/** Install and overwrite anything that stands in the way */
|
||||
void install() {
|
||||
install(DuplicateBehavior.OVERWRITE)
|
||||
}
|
||||
|
||||
void install(DuplicateBehavior onDuplicateBehavior) {
|
||||
if (!mkdirs()) {
|
||||
/* raise some exception? */
|
||||
}
|
||||
|
||||
gems.each { File gem ->
|
||||
installGem(installDirectory, gem, onDuplicateBehavior)
|
||||
}
|
||||
}
|
||||
|
||||
boolean installGem(File installDir, File gem, DuplicateBehavior onDuplicate) {
|
||||
/* TODO: isValidGem? */
|
||||
cacheGemInInstallDir(installDir, gem)
|
||||
|
||||
GenericArchive gemArchive = ShrinkWrap.create(TarImporter).importFrom(gem).as(GenericArchive)
|
||||
Node metadata = gemArchive.get('metadata.gz')
|
||||
GenericArchive dataArchive = gemArchive.getAsType(GenericArchive.class,
|
||||
"data.tar.gz",
|
||||
ArchiveFormat.TAR_GZ);
|
||||
|
||||
Gem gemMetadata = Gem.fromFile(new GZIPInputStream(metadata.asset.openStream()))
|
||||
logger.info("We've processed metadata for ${gemMetadata.name} at version ${gemMetadata.version}")
|
||||
|
||||
extractSpecification(installDir, gemMetadata)
|
||||
extractData(installDir, dataArchive, gemMetadata)
|
||||
extractExecutables(installDir, dataArchive, gemMetadata)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
boolean mkdirs() {
|
||||
boolean success = true
|
||||
GEM_HOME_DIRS.each { String dirName ->
|
||||
File newDir = new File(installDirectory, dirName)
|
||||
logger.info("Attempting to create: ${newDir.absolutePath}")
|
||||
|
||||
if (!newDir.mkdirs()) {
|
||||
logger.error("Failed to make ${newDir.absolutePath}, bailing on mkdirs()")
|
||||
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
|
||||
*/
|
||||
boolean isValidGem(File gemFile) {
|
||||
logger.info("Validating gem ${gemFile}")
|
||||
|
||||
/* If it doesn't end with gem, let's not even consider it a gem file */
|
||||
if (!gemFile.absolutePath.endsWith('.gem')) {
|
||||
return false
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
String gemFullName(Gem gem) {
|
||||
String fullName = "${gem.name}-${gem.version.version}"
|
||||
|
||||
if ((gem.platform) && (gem.platform != 'ruby')) {
|
||||
fullName = "${fullName}-${gem.platform}"
|
||||
}
|
||||
|
||||
return fullName
|
||||
}
|
||||
|
||||
/** Cache the gem in GEM_HOME/cache */
|
||||
protected void cacheGemInInstallDir(File installDir, File gem) {
|
||||
File cacheDir = new File(installDir, 'cache')
|
||||
Files.copy(gem.toPath(), (new File(cacheDir, gem.name)).toPath())
|
||||
}
|
||||
|
||||
/** Extract the gemspec file from the {@code Gem} provided into the ${installDir}/specifications */
|
||||
protected void extractSpecification(File installDir, Gem gem) {
|
||||
String outputFileName = "${gemFullName(gem)}.gemspec"
|
||||
File outputFile = new File(installDir, ['specifications', outputFileName].join(File.separator))
|
||||
|
||||
PrintWriter writer = new PrintWriter(outputFile.newOutputStream())
|
||||
writer.write(gem.toRuby())
|
||||
writer.flush()
|
||||
}
|
||||
|
||||
/** Extract the data.tar.gz contents into gems/full-name/* */
|
||||
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 */
|
||||
protected void extractExecutables(File installDir, GenericArchive dataTarGz, Gem gem) {
|
||||
/*
|
||||
* 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 ?: 'bin'
|
||||
File bin = new File(installDir, binDir)
|
||||
List<String> execs = gem.executables.collect { String ex -> [binDir, ex].join(File.separator) }
|
||||
|
||||
bin.mkdirs()
|
||||
dataTarGz.content.each { ArchivePath path, Node node ->
|
||||
execs.each { String exec ->
|
||||
if (path.get().matches(/.*${exec}/)) {
|
||||
IOUtil.copy(node.asset.openStream(), (new File(installDir, exec)).newOutputStream())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,212 @@
|
|||
package com.github.jrubygradle.jem.internal;
|
||||
|
||||
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.nio.file.Files;
|
||||
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", "cache", "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) {
|
||||
if (!mkdirs()) {
|
||||
/* raise some exception? */
|
||||
}
|
||||
|
||||
for (File gem : this.gems) {
|
||||
installGem(installDirectory, gem, onDuplicateBehavior);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean installGem(File installDir, File gem, DuplicateBehavior onDuplicate) {
|
||||
/* TODO: isValidGem? */
|
||||
try {
|
||||
cacheGemInInstallDir(installDir, gem);
|
||||
}
|
||||
catch (IOException ex) {
|
||||
logger.error("Failed to cache our gem in %s", installDir, ex);
|
||||
return false;
|
||||
}
|
||||
|
||||
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 false;
|
||||
}
|
||||
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 true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
/** Cache the gem in GEM_HOME/cache */
|
||||
protected void cacheGemInInstallDir(File installDir, File gem) throws IOException {
|
||||
File cacheDir = new File(installDir, "cache");
|
||||
Files.copy(gem.toPath(), (new File(cacheDir, gem.getName())).toPath());
|
||||
}
|
||||
|
||||
/** 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/* */
|
||||
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 */
|
||||
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();
|
||||
logger.info(node.toString());
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -70,7 +70,7 @@ class GemSpec extends Specification {
|
|||
gem.version.version == '0.19.1'
|
||||
|
||||
and: "its properties should equal what's in the YAML"
|
||||
gem.getProperty(property) == value
|
||||
gem."${property}" == value
|
||||
|
||||
where:
|
||||
property | value
|
||||
|
|
Loading…
Reference in New Issue