From 65fd115fa3be0e5a7f71da1b24500c62b85b1890 Mon Sep 17 00:00:00 2001 From: "R. Tyler Croy" Date: Thu, 13 Aug 2015 17:31:40 -0700 Subject: [PATCH] Convert the nascent Jem code into Java Fixes #1 --- build.gradle | 6 +- .../github/jrubygradle/jem/Dependency.groovy | 17 -- .../github/jrubygradle/jem/Dependency.java | 14 ++ .../com/github/jrubygradle/jem/Gem.groovy | 135 ----------- .../com/github/jrubygradle/jem/Gem.java | 155 +++++++++++++ .../jrubygradle/jem/GemInstaller.groovy | 36 --- .../github/jrubygradle/jem/GemInstaller.java | 35 +++ .../github/jrubygradle/jem/GemReader.groovy | 7 - .../github/jrubygradle/jem/Requirement.groovy | 11 - .../github/jrubygradle/jem/Requirement.java | 6 + .../com/github/jrubygradle/jem/Version.groovy | 12 - .../com/github/jrubygradle/jem/Version.java | 10 + .../jem/internal/GemInstaller.groovy | 169 -------------- .../jem/internal/GemInstaller.java | 212 ++++++++++++++++++ .../com/github/jrubygradle/jem/GemSpec.groovy | 2 +- 15 files changed, 437 insertions(+), 390 deletions(-) delete mode 100644 src/main/groovy/com/github/jrubygradle/jem/Dependency.groovy create mode 100644 src/main/groovy/com/github/jrubygradle/jem/Dependency.java delete mode 100644 src/main/groovy/com/github/jrubygradle/jem/Gem.groovy create mode 100644 src/main/groovy/com/github/jrubygradle/jem/Gem.java delete mode 100644 src/main/groovy/com/github/jrubygradle/jem/GemInstaller.groovy create mode 100644 src/main/groovy/com/github/jrubygradle/jem/GemInstaller.java delete mode 100644 src/main/groovy/com/github/jrubygradle/jem/GemReader.groovy delete mode 100644 src/main/groovy/com/github/jrubygradle/jem/Requirement.groovy create mode 100644 src/main/groovy/com/github/jrubygradle/jem/Requirement.java delete mode 100644 src/main/groovy/com/github/jrubygradle/jem/Version.groovy create mode 100644 src/main/groovy/com/github/jrubygradle/jem/Version.java delete mode 100644 src/main/groovy/com/github/jrubygradle/jem/internal/GemInstaller.groovy create mode 100644 src/main/groovy/com/github/jrubygradle/jem/internal/GemInstaller.java diff --git a/build.gradle b/build.gradle index 68b838c..90a3504 100644 --- a/build.gradle +++ b/build.gradle @@ -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() diff --git a/src/main/groovy/com/github/jrubygradle/jem/Dependency.groovy b/src/main/groovy/com/github/jrubygradle/jem/Dependency.groovy deleted file mode 100644 index 8c5293f..0000000 --- a/src/main/groovy/com/github/jrubygradle/jem/Dependency.groovy +++ /dev/null @@ -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 requirements -} diff --git a/src/main/groovy/com/github/jrubygradle/jem/Dependency.java b/src/main/groovy/com/github/jrubygradle/jem/Dependency.java new file mode 100644 index 0000000..18da142 --- /dev/null +++ b/src/main/groovy/com/github/jrubygradle/jem/Dependency.java @@ -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 requirements; +} diff --git a/src/main/groovy/com/github/jrubygradle/jem/Gem.groovy b/src/main/groovy/com/github/jrubygradle/jem/Gem.groovy deleted file mode 100644 index c85da41..0000000 --- a/src/main/groovy/com/github/jrubygradle/jem/Gem.groovy +++ /dev/null @@ -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 authors = [] - - @JsonProperty - List files - - @JsonProperty(value='test_files') - List testFiles - - @JsonProperty - List executables - - @JsonProperty - String bindir - - @JsonProperty(value='require_paths') - List requirePaths - - @JsonProperty - List 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 - * - * @return - */ - String toRuby() { - return """\ -# -*- encoding: utf-8 -*- -# stub: ${name} ${version.version} ${platform} ${requirePaths.join("\0")} -# -# NOTE: This specification was generated by 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 - } -} diff --git a/src/main/groovy/com/github/jrubygradle/jem/Gem.java b/src/main/groovy/com/github/jrubygradle/jem/Gem.java new file mode 100644 index 0000000..45fb33b --- /dev/null +++ b/src/main/groovy/com/github/jrubygradle/jem/Gem.java @@ -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 authors; + + @JsonProperty + public List files; + + @JsonProperty(value="test_files") + public List testFiles; + + @JsonProperty + public List executables; + + @JsonProperty + public String bindir; + + @JsonProperty(value="require_paths") + public List requirePaths; + + @JsonProperty + public List 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 + * + * @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`", + "# ", + "", + "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; + } +} diff --git a/src/main/groovy/com/github/jrubygradle/jem/GemInstaller.groovy b/src/main/groovy/com/github/jrubygradle/jem/GemInstaller.groovy deleted file mode 100644 index 4757749..0000000 --- a/src/main/groovy/com/github/jrubygradle/jem/GemInstaller.groovy +++ /dev/null @@ -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 gemPaths) { - impl = new GemInstallerImpl(installDir, gemPaths) - } - - void install() { - impl.install() - } -} diff --git a/src/main/groovy/com/github/jrubygradle/jem/GemInstaller.java b/src/main/groovy/com/github/jrubygradle/jem/GemInstaller.java new file mode 100644 index 0000000..a6716d0 --- /dev/null +++ b/src/main/groovy/com/github/jrubygradle/jem/GemInstaller.java @@ -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 gemPaths) { + impl = new com.github.jrubygradle.jem.internal.GemInstaller(installDir, gemPaths); + } + + public void install() { + impl.install(); + } +} diff --git a/src/main/groovy/com/github/jrubygradle/jem/GemReader.groovy b/src/main/groovy/com/github/jrubygradle/jem/GemReader.groovy deleted file mode 100644 index 8f937b6..0000000 --- a/src/main/groovy/com/github/jrubygradle/jem/GemReader.groovy +++ /dev/null @@ -1,7 +0,0 @@ -package com.github.jrubygradle.jem - -/** - * Reader for taking a .gem flie and turning it into objects - */ -class GemReader { -} diff --git a/src/main/groovy/com/github/jrubygradle/jem/Requirement.groovy b/src/main/groovy/com/github/jrubygradle/jem/Requirement.groovy deleted file mode 100644 index 0ce7746..0000000 --- a/src/main/groovy/com/github/jrubygradle/jem/Requirement.groovy +++ /dev/null @@ -1,11 +0,0 @@ -package com.github.jrubygradle.jem - -import groovy.transform.CompileStatic -import groovy.transform.TypeChecked - -/** - */ -@TypeChecked -@CompileStatic -class Requirement { -} diff --git a/src/main/groovy/com/github/jrubygradle/jem/Requirement.java b/src/main/groovy/com/github/jrubygradle/jem/Requirement.java new file mode 100644 index 0000000..001996f --- /dev/null +++ b/src/main/groovy/com/github/jrubygradle/jem/Requirement.java @@ -0,0 +1,6 @@ +package com.github.jrubygradle.jem; + +/** + */ +public class Requirement { +} diff --git a/src/main/groovy/com/github/jrubygradle/jem/Version.groovy b/src/main/groovy/com/github/jrubygradle/jem/Version.groovy deleted file mode 100644 index 366ebac..0000000 --- a/src/main/groovy/com/github/jrubygradle/jem/Version.groovy +++ /dev/null @@ -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 -} diff --git a/src/main/groovy/com/github/jrubygradle/jem/Version.java b/src/main/groovy/com/github/jrubygradle/jem/Version.java new file mode 100644 index 0000000..51afd8e --- /dev/null +++ b/src/main/groovy/com/github/jrubygradle/jem/Version.java @@ -0,0 +1,10 @@ +package com.github.jrubygradle.jem; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + */ +public class Version { + @JsonProperty + public String version; +} diff --git a/src/main/groovy/com/github/jrubygradle/jem/internal/GemInstaller.groovy b/src/main/groovy/com/github/jrubygradle/jem/internal/GemInstaller.groovy deleted file mode 100644 index 4194ce3..0000000 --- a/src/main/groovy/com/github/jrubygradle/jem/internal/GemInstaller.groovy +++ /dev/null @@ -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 GEM_HOME_DIRS = ['bin', 'build_info', 'cache', 'doc', - 'extensions', 'gems', 'specifications'] - - protected Logger logger = LoggerFactory.getLogger(this.class) - protected File installDirectory - protected List gems - - GemInstaller(String installDir, List 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 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()) - } - } - } - } -} diff --git a/src/main/groovy/com/github/jrubygradle/jem/internal/GemInstaller.java b/src/main/groovy/com/github/jrubygradle/jem/internal/GemInstaller.java new file mode 100644 index 0000000..c7bee82 --- /dev/null +++ b/src/main/groovy/com/github/jrubygradle/jem/internal/GemInstaller.java @@ -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 gems; + + public GemInstaller(String installDir, List 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 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)); + } + } + } +} diff --git a/src/test/groovy/com/github/jrubygradle/jem/GemSpec.groovy b/src/test/groovy/com/github/jrubygradle/jem/GemSpec.groovy index 3f30f94..52087cc 100644 --- a/src/test/groovy/com/github/jrubygradle/jem/GemSpec.groovy +++ b/src/test/groovy/com/github/jrubygradle/jem/GemSpec.groovy @@ -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