summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSam Thursfield <sam.thursfield@codethink.co.uk>2014-07-24 16:32:21 +0100
committerSam Thursfield <sam.thursfield@codethink.co.uk>2014-08-01 14:35:33 +0000
commite01a86898aaebee8ba14f5b35fe2059717014241 (patch)
tree7fcbb035e17992b665005b92b1ef220fe5ca1f90
parent97789680308a8fc38c0981da2809c8c676510d9f (diff)
downloadmorph-sam/ruby-using-gems.tar.gz
Add scripts/import-rubygem to generate morphologies for RubyGemssam/ruby-using-gems
This generates a stratum and a set of chunk morphologies for integrating a given RubyGem project into Baserock. It uses the 'bundler' Gem to collate the list dependencies. This means that it works for any Gem that has a "Gemfile", and will also honour the "Gemfile.lock" file created by Bundler if the file exists. It requires the new 'rubygem' build mode.
-rwxr-xr-xscripts/import-rubygem189
1 files changed, 189 insertions, 0 deletions
diff --git a/scripts/import-rubygem b/scripts/import-rubygem
new file mode 100755
index 00000000..6b869a7d
--- /dev/null
+++ b/scripts/import-rubygem
@@ -0,0 +1,189 @@
+#!/usr/bin/env ruby
+#
+# Create a stratum to integrate a Ruby project in Baserock, using RubyGems
+#
+# Copyright (C) 2014 Codethink Limited
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; version 2 of the License.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+require 'bundler'
+require 'optparse'
+require 'yaml'
+
+BASEROCK_RUBY_VERSION = '2.0.0'
+
+def parse_options(arguments)
+ # No options so far ..
+ opts = OptionParser.new
+
+ opts.banner = "Usage: import-ruby PROJECT_DIR OUTPUT_DIR"
+ opts.separator ""
+ opts.separator "This tool reads the Gemfile and optionally the " +
+ "Gemfile.lock from a Ruby project "
+ opts.separator "source tree in PROJECT_DIR. It outputs a stratum " +
+ "morphology and a set of chunk "
+ opts.separator "morphology files to OUTPUT_DIR."
+
+ parsed_arguments = opts.parse!(arguments)
+
+ if parsed_arguments.length != 2 then
+ STDERR.puts opts.help
+ exit 1
+ end
+
+ parsed_arguments
+end
+
+def get_project_name(project_dir_name)
+ # One Git repo can produce any number of Gems, or none, so it's hard to
+ # work out a project name that way. Instead, use the repo name :)
+ project_name = File.basename(project_dir_name)
+end
+
+def load_gemfile()
+ # Load and parse the Gemfile and, if found, the Gemfile.lock file.
+ definition = Bundler::Definition.build(
+ 'Gemfile', 'Gemfile.lock', update=false)
+end
+
+def get_all_specs_for_project(dir_name)
+ Dir.chdir(dir_name) { begin
+ load_gemfile.specs
+ rescue Bundler::GemNotFound
+ # If we're missing some Gem info, try remotely resolving. This is very
+ # slow so it's nice if it can be avoided. Perhaps setting up a local
+ # mirror of the necessary specs would avoid this problem. There seems
+ # to be no way to "reset" the Definition instance after the exception,
+ # so we have to call load_gemfile again.
+ STDERR.puts "Resolving definitions remotely (this may take a while!)"
+ load_gemfile.resolve_remotely!
+ rescue Bundler::GemfileNotFound
+ STDERR.puts "Did not find a Gemfile in #{dir_name}."
+ exit
+ end }
+end
+
+def generate_morphs_for_specset(project_name, specs)
+ # Chunk names are the Gem's "full name" (name + version number), so that we
+ # don't break in the rare but possible case that two different versions of
+ # the same Gem are required for something to work. It'd be nicer to only
+ # use the full_name if we detect such a conflict. If we do, at least this
+ # function below can be removed with the much simpler:
+ # spec.deps.collect |dep| dep.name
+ runtime_depends = proc do |spec|
+ result = []
+ spec.dependencies.each do |dep|
+ next if dep.type == :development
+ found = specs[dep]
+ if found.length != 1
+ raise Exception,
+ "Unsure which Gem to use for #{dep}, got #{found}"
+ end
+ result << found[0].full_name
+ end
+ result
+ end
+
+ description = 'Automatically generated by import-ruby. This is a ' +
+ 'prototype of a method for integrating RubyGems into ' +
+ 'Baserock.'
+
+ bin_dir = "\"$DESTDIR/$PREFIX/bin\""
+ gem_dir = "\"$DESTDIR/$PREFIX/lib/ruby/gems/#{BASEROCK_RUBY_VERSION}\""
+
+ chunk_morphs = specs.collect do |spec|
+ # There's more splitting to be done, but putting the docs in the
+ # correct artifact is the single biggest win for enabling smaller
+ # system images.
+ split_rules = [
+ {
+ 'artifact' => "#{spec.full_name}-doc",
+ 'include' => [
+ "usr/lib/ruby/gems/#{BASEROCK_RUBY_VERSION}/doc/.*"
+ ]
+ }
+ ]
+
+ install_commands = [
+ "mkdir -p #{gem_dir}",
+ "gem install --install-dir #{gem_dir} --bindir #{bin_dir} " +
+ "--ignore-dependencies --local #{spec.full_name}.gem"
+ ]
+
+ {
+ 'name' => spec.full_name,
+ 'kind' => 'chunk',
+ 'description' => description,
+ 'build-system' => 'manual',
+ # FIXME: this is not how we should calculate the URL field!
+ 'gem-url' => "http://rubygems.org/downloads/#{spec.full_name}.gem",
+ 'products' => split_rules,
+ 'install-commands' => install_commands
+ }
+ end
+
+ chunks = specs.collect do |spec|
+ {
+ 'name' => spec.full_name,
+ 'description' => description,
+ # This is a dummy value; there is no repo for these chunks.
+ # The 'repo' field should perhaps become optional!
+ 'repo' => 'baserock:baserock/definitions',
+ 'ref' => 'master',
+ 'morph' => File.join(project_name, spec.full_name + '.morph'),
+ # Runtime depends must be present at "build" (Gem install) time.
+ 'build-depends' => runtime_depends.call(spec),
+ # This feature is not in morph.git master yet
+ 'build-mode' => 'rubygem',
+ }
+ end
+
+ stratum_morph = {
+ 'name' => project_name,
+ 'kind' => 'stratum',
+ 'description' => description,
+ 'build-depends' => [
+ { 'morph' => 'ruby' }
+ ],
+ 'chunks' => chunks,
+ }
+
+ return [stratum_morph] + chunk_morphs
+end
+
+def write_morphs(morphs, project_name, target_dir_name)
+ target_dir_name = File.join(target_dir_name, project_name)
+ FileUtils.makedirs(target_dir_name)
+ Dir.chdir(target_dir_name) do
+ morphs.each do |morph|
+ morph_filename = morph['name'] + '.morph'
+ File.open(morph_filename, 'w') do |file|
+ file.write(YAML.dump(morph))
+ end
+ end
+ end
+end
+
+def run
+ project_dir_name, target_dir_name = parse_options(ARGV)
+
+ project_name = get_project_name(project_dir_name)
+ specset = get_all_specs_for_project(project_dir_name)
+
+ morphs = generate_morphs_for_specset(project_name, specset)
+
+ write_morphs(morphs, project_name, target_dir_name)
+end
+
+run