diff options
Diffstat (limited to 'exts/rubygems.to_chunk')
-rwxr-xr-x | exts/rubygems.to_chunk | 171 |
1 files changed, 171 insertions, 0 deletions
diff --git a/exts/rubygems.to_chunk b/exts/rubygems.to_chunk new file mode 100755 index 0000000..c1a3e7c --- /dev/null +++ b/exts/rubygems.to_chunk @@ -0,0 +1,171 @@ +#!/usr/bin/env ruby +# +# Create a chunk morphology to build a RubyGem in Baserock +# +# 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_relative 'importer_base' +require_relative 'importer_bundler_extensions' + +BANNER = "Usage: rubygems.to_chunk SOURCE_DIR GEM_NAME [VERSION]" + +DESCRIPTION = <<-END +This tool looks in SOURCE_DIR to generate a chunk morphology with build +instructions for GEM_NAME. If VERSION is supplied, it is used to check that the +build instructions will produce the expected version of the Gem. + +It is intended for use with the `baserock-import` tool. +END + +class RubyGemChunkMorphologyGenerator < Importer::Base + include Importer::BundlerExtensions + + def parse_options(arguments) + opts = create_option_parser(BANNER, DESCRIPTION) + + parsed_arguments = opts.parse!(arguments) + + if parsed_arguments.length != 2 && parsed_arguments.length != 3 + STDERR.puts "Expected 2 or 3 arguments, got #{parsed_arguments}." + opts.parse(['-?']) + exit 255 + end + + source_dir, gem_name, expected_version = parsed_arguments + source_dir = File.absolute_path(source_dir) + if expected_version != nil + expected_version = Gem::Version.new(expected_version.dup) + end + [source_dir, gem_name, expected_version] + end + + def chunk_name_for_gemspec(spec) + # 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. + spec.full_name + end + + def is_signed_gem(spec) + spec.signing_key != nil + end + + def generate_chunk_morph_for_gem(spec) + description = 'Automatically generated by rubygems.to_chunk' + + bin_dir = "\"$DESTDIR/$PREFIX/bin\"" + gem_dir = "\"$DESTDIR/$(gem environment home)\"" + + # 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. + # + # Adding this to Morph's default ruleset is painful, because: + # - Changing the default split rules triggers a rebuild of everything. + # - The whole split rule code needs reworking to prevent overlaps and to + # make it possible to extend rules without creating overlaps. It's + # otherwise impossible to reason about. + + split_rules = [ + { + 'artifact' => "#{spec.full_name}-doc", + 'include' => [ + 'usr/lib/ruby/gems/\d[\w.]*/doc/.*' + ] + } + ] + + # It'd be rather tricky to include these build instructions as a + # BuildSystem implementation in Morph. The problem is that there's no + # way for the default commands to know what .gemspec file they should + # be building. It doesn't help that the .gemspec may be in a subdirectory + # (as in Rails, for example). + # + # Note that `gem help build` says the following: + # + # The best way to build a gem is to use a Rakefile and the + # Gem::PackageTask which ships with RubyGems. + # + # It's often possible to run `rake gem`, but this may require Hoe, + # rake-compiler, Jeweler or other assistance tools to be present at Gem + # construction time. It seems that many Ruby projects that use these tools + # also maintain an up-to-date generated .gemspec file, which means that we + # can get away with using `gem build` just fine in many cases. + # + # Were we to use `setup.rb install` or `rake install`, programs that loaded + # with the 'rubygems' library would complain that required Gems were not + # installed. We must have the Gem metadata available, and `gem build; gem + # install` seems the easiest way to achieve that. + + configure_commands = [] + + if is_signed_gem(spec) + # This is a best-guess hack for allowing unsigned builds of Gems that are + # normally built signed. There's no value in building signed Gems when we + # control the build and deployment environment, and we obviously can't + # provide the private key of the Gem's maintainer. + configure_commands << + "sed -e '/cert_chain\\s*=/d' -e '/signing_key\\s*=/d' -i " \ + "#{spec.name}.gemspec" + end + + build_commands = [ + "gem build #{spec.name}.gemspec", + ] + + install_commands = [ + "mkdir -p #{gem_dir}", + "gem install --install-dir #{gem_dir} --bindir #{bin_dir} " \ + "--ignore-dependencies --local ./#{spec.full_name}.gem" + ] + + { + 'name' => chunk_name_for_gemspec(spec), + 'kind' => 'chunk', + 'description' => description, + 'build-system' => 'manual', + 'products' => split_rules, + 'configure-commands' => configure_commands, + 'build-commands' => build_commands, + 'install-commands' => install_commands, + } + end + + def run + source_dir_name, gem_name, expected_version = parse_options(ARGV) + + log.info("Creating chunk morph for #{gem_name} based on " \ + "source code in #{source_dir_name}") + + resolved_specs = Dir.chdir(source_dir_name) do + # FIXME: we don't need to do this here, it'd be enough just to load + # the given gemspec + definition = create_bundler_definition_for_gemspec(gem_name) + definition.resolve_remotely! + end + + spec = get_spec_for_gem(resolved_specs, gem_name) + validate_spec(spec, source_dir_name, expected_version) + + morph = generate_chunk_morph_for_gem(spec) + write_morph(STDOUT, morph) + end +end + +RubyGemChunkMorphologyGenerator.new.run |