mirror of https://github.com/jruby-gradle/jem
parent
55fc37d9cf
commit
65fd115fa3
|
@ -1,10 +1,12 @@
|
||||||
|
apply plugin: 'java'
|
||||||
apply plugin: 'groovy'
|
apply plugin: 'groovy'
|
||||||
apply plugin: 'maven'
|
apply plugin: 'maven'
|
||||||
apply plugin: 'codenarc'
|
apply plugin: 'codenarc'
|
||||||
|
|
||||||
version = '0.1'
|
version = '0.1.0'
|
||||||
group = 'com.github.jrubygradle'
|
group = 'com.github.jrubygradle'
|
||||||
description = 'A Groovy-based library for managing Ruby gems'
|
description = 'A library for managing Ruby gems'
|
||||||
|
defaultTasks 'check', 'assemble'
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
jcenter()
|
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'
|
gem.version.version == '0.19.1'
|
||||||
|
|
||||||
and: "its properties should equal what's in the YAML"
|
and: "its properties should equal what's in the YAML"
|
||||||
gem.getProperty(property) == value
|
gem."${property}" == value
|
||||||
|
|
||||||
where:
|
where:
|
||||||
property | value
|
property | value
|
||||||
|
|
Loading…
Reference in New Issue