summaryrefslogtreecommitdiff
path: root/baserockimport/exts/rubygems.to_chunk
diff options
context:
space:
mode:
Diffstat (limited to 'baserockimport/exts/rubygems.to_chunk')
-rwxr-xr-xbaserockimport/exts/rubygems.to_chunk171
1 files changed, 171 insertions, 0 deletions
diff --git a/baserockimport/exts/rubygems.to_chunk b/baserockimport/exts/rubygems.to_chunk
new file mode 100755
index 0000000..c1a3e7c
--- /dev/null
+++ b/baserockimport/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