summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--importer_base.rb5
-rw-r--r--importer_bundler_extensions.rb89
-rw-r--r--importer_omnibus_extensions.rb93
-rw-r--r--main.py89
-rwxr-xr-xomnibus.find_deps138
-rwxr-xr-xomnibus.to_chunk152
-rwxr-xr-xrubygems.find_deps114
-rwxr-xr-xrubygems.to_chunk128
8 files changed, 521 insertions, 287 deletions
diff --git a/importer_base.rb b/importer_base.rb
index 4e7a7b5..b16b436 100644
--- a/importer_base.rb
+++ b/importer_base.rb
@@ -63,6 +63,11 @@ module Importer
file.write(YAML.dump(morph))
end
+ def write_dependencies(file, dependencies)
+ format_options = { :indent => ' ' }
+ file.puts(JSON.pretty_generate(dependencies, format_options))
+ end
+
def create_logger
# Use the logger that was passed in from the 'main' import process, if
# detected.
diff --git a/importer_bundler_extensions.rb b/importer_bundler_extensions.rb
new file mode 100644
index 0000000..90a5ae4
--- /dev/null
+++ b/importer_bundler_extensions.rb
@@ -0,0 +1,89 @@
+#!/usr/bin/env ruby
+#
+# Extensions to Bundler library which allow using it in importers.
+#
+# 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'
+
+class << Bundler
+ def default_gemfile
+ # This is a hack to make things not crash when there's no Gemfile
+ Pathname.new('.')
+ end
+end
+
+module Importer
+ module BundlerExtensions
+ def create_bundler_definition_for_gemspec(gem_name)
+ # Using the real Gemfile doesn't get great results, because people can put
+ # lots of stuff in there that is handy for developers to have but
+ # irrelevant if you just want to produce a .gem. Also, there is only one
+ # Gemfile per repo, but a repo may include multiple .gemspecs that we want
+ # to process individually. Also, some projects don't use Bundler and may
+ # not have a Gemfile at all.
+ #
+ # Instead of reading the real Gemfile, invent one that simply includes the
+ # chosen .gemspec. If present, the Gemfile.lock will be honoured.
+ fake_gemfile = Bundler::Dsl.new
+ fake_gemfile.source('https://rubygems.org')
+ begin
+ fake_gemfile.gemspec({:name => gem_name})
+ rescue Bundler::InvalidOption
+ error "Did not find #{gem_name}.gemspec in current directory."
+ exit 1
+ end
+
+ fake_gemfile.to_definition('Gemfile.lock', true)
+ end
+
+ def get_spec_for_gem(specs, gem_name)
+ found = specs[gem_name].select {|s| Gem::Platform.match(s.platform)}
+ if found.empty?
+ raise Exception,
+ "No Gemspecs found matching '#{gem_name}'"
+ elsif found.length != 1
+ raise Exception,
+ "Unsure which Gem to use for #{gem_name}, got #{found}"
+ end
+ found[0]
+ end
+
+ def spec_is_from_current_source_tree(spec, source_dir)
+ Dir.chdir(source_dir) do
+ spec.source.instance_of? Bundler::Source::Path and
+ File.identical?(spec.source.path, '.')
+ end
+ end
+
+ def validate_spec(spec, source_dir_name, expected_version)
+ if not spec_is_from_current_source_tree(spec, source_dir_name)
+ error "Specified gem '#{spec.name}' doesn't live in the source in " +
+ "'#{source_dir_name}'"
+ log.debug "SPEC: #{spec.inspect} #{spec.source}"
+ exit 1
+ end
+
+ if expected_version != nil && spec.version != expected_version
+ # This check is brought to you by Coderay, which changes its version
+ # number based on an environment variable. Other Gems may do this too.
+ error "Source in #{source_dir_name} produces #{spec.full_name}, but " +
+ "the expected version was #{expected_version}."
+ exit 1
+ end
+ end
+ end
+end
diff --git a/importer_omnibus_extensions.rb b/importer_omnibus_extensions.rb
new file mode 100644
index 0000000..ce0d780
--- /dev/null
+++ b/importer_omnibus_extensions.rb
@@ -0,0 +1,93 @@
+# Extensions for the Omnibus tool that allow using it to generate morphologies.
+#
+# 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 'omnibus'
+
+require 'optparse'
+require 'rubygems/commands/build_command'
+require 'rubygems/commands/install_command'
+require 'shellwords'
+
+class Omnibus::Builder
+ # It's possible to use `gem install` in build commands, which is a great
+ # way of subverting the dependency tracking Omnibus provides. It's done
+ # in `omnibus-chef/config/software/chefdk.rb`, for example.
+ #
+ # To handle this, here we extend the class that executes the build commands
+ # to detect when `gem install` is run. It uses the Gem library to turn the
+ # commandline back into a Bundler::Dependency object that we can use.
+ #
+ # We also trap `gem build` so we know when a software component is a RubyGem
+ # that should be handled by 'rubygems.to_chunk'.
+
+ class GemBuildCommandParser < Gem::Commands::BuildCommand
+ def gemspec_path(args)
+ handle_options args
+ if options[:args].length != 1
+ raise Exception, "Invalid `gem build` commandline: 1 argument " +
+ "expected, got #{options[:args]}."
+ end
+ options[:args][0]
+ end
+ end
+
+ class GemInstallCommandParser < Gem::Commands::InstallCommand
+ def dependency_list_from_commandline(args)
+ handle_options args
+
+ # `gem install foo*` is sometimes used when installing a locally built
+ # Gem, to avoid needing to know the exact version number that was built.
+ # We only care about remote Gems being installed, so anything with a '*'
+ # in its name can be ignored.
+ gem_names = options[:args].delete_if { |name| name.include?('*') }
+
+ gem_names.collect do |gem_name|
+ Bundler::Dependency.new(gem_name, options[:version])
+ end
+ end
+ end
+
+ def gem(command, options = {})
+ # This function re-implements the 'gem' function in the build-commands DSL.
+ if command.start_with? 'build'
+ parser = GemBuildCommandParser.new
+ args = Shellwords.split(command).drop(1)
+ if built_gemspec != nil
+ raise Exception, "More than one `gem build` command was run as part " +
+ "of the build process. The 'rubygems.to_chunk' " +
+ "program currently supports only one .gemspec " +
+ "build per chunk, so this can't be processed " +
+ "automatically."
+ end
+ @built_gemspec = parser.gemspec_path(args)
+ elsif command.start_with? 'install'
+ parser = GemInstallCommandParser.new
+ args = Shellwords.split(command).drop(1)
+ args_without_build_flags = args.take_while { |item| item != '--' }
+ gems = parser.dependency_list_from_commandline(args_without_build_flags)
+ manually_installed_rubygems.concat gems
+ end
+ end
+
+ def built_gemspec
+ @built_gemspec
+ end
+
+ def manually_installed_rubygems
+ @manually_installed_rubygems ||= []
+ end
+end
diff --git a/main.py b/main.py
index b5ebece..7f9a1d6 100644
--- a/main.py
+++ b/main.py
@@ -264,6 +264,7 @@ class Package(object):
self.version = version
self.required_by = []
self.morphology = None
+ self.dependencies = None
self.is_build_dep = False
self.version_in_use = version
@@ -290,6 +291,9 @@ class Package(object):
def set_morphology(self, morphology):
self.morphology = morphology
+ def set_dependencies(self, dependencies):
+ self.dependencies = dependencies
+
def set_is_build_dep(self, is_build_dep):
self.is_build_dep = is_build_dep
@@ -405,8 +409,8 @@ class ImportLoop(object):
processed.add_node(current_item)
if not error:
- self._process_dependencies_from_morphology(
- current_item, current_item.morphology, to_process,
+ self._process_dependencies(
+ current_item, current_item.dependencies, to_process,
processed)
if len(errors) > 0:
@@ -448,34 +452,25 @@ class ImportLoop(object):
package.set_morphology(chunk_morph)
- def _process_dependencies_from_morphology(self, current_item, morphology,
- to_process, processed):
- '''Enqueue all dependencies of a package that are yet to be processed.
+ dependencies = self._find_or_create_dependency_list(
+ kind, name, checked_out_version, source_repo)
- Dependencies are communicated using extra fields in morphologies,
- currently.
+ package.set_dependencies(dependencies)
- '''
- for key, value in morphology.iteritems():
- if key.startswith('x-build-dependencies-'):
- kind = key[len('x-build-dependencies-'):]
- is_build_deps = True
- elif key.startswith('x-runtime-dependencies-'):
- kind = key[len('x-runtime-dependencies-'):]
- is_build_deps = False
- else:
- continue
+ def _process_dependencies(self, current_item, dependencies, to_process,
+ processed):
+ '''Enqueue all dependencies of a package that are yet to be processed.
- # We need to validate this field because it doesn't go through the
- # normal MorphologyFactory validation, being an extension.
- if not hasattr(value, 'iteritems'):
- value_type = type(value).__name__
- raise cliapp.AppException(
- "Morphology for %s has invalid '%s': should be a dict, but "
- "got a %s." % (morphology['name'], key, value_type))
+ '''
+ for key, value in dependencies.iteritems():
+ kind = key
self._process_dependency_list(
- current_item, kind, value, to_process, processed, is_build_deps)
+ current_item, kind, value['build-dependencies'], to_process,
+ processed, True)
+ self._process_dependency_list(
+ current_item, kind, value['runtime-dependencies'], to_process,
+ processed, False)
def _process_dependency_list(self, current_item, kind, deps, to_process,
processed, these_are_build_deps):
@@ -690,6 +685,50 @@ class ImportLoop(object):
return self.morphloader.load_from_string(text, filename)
+ def _find_or_create_dependency_list(self, kind, name, version,
+ source_repo):
+ depends_filename = 'strata/%s/%s-%s.foreign-dependencies' % (
+ self.goal_name, name, version)
+ depends_path = os.path.join(
+ self.app.settings['definitions-dir'], depends_filename)
+
+ def calculate_dependencies():
+ dependencies = self._calculate_dependencies_for_package(
+ source_repo, kind, name, version, depends_path)
+ with open(depends_path, 'w') as f:
+ json.dump(dependencies, f)
+ return dependencies
+
+ if self.app.settings['update-existing']:
+ dependencies = calculate_dependencies()
+ elif os.path.exists(depends_path):
+ with open(depends_path) as f:
+ dependencies = json.load(f)
+ else:
+ logging.debug("Didn't find %s", depends_path)
+ dependencies = calculate_dependencies()
+
+ return dependencies
+
+ def _calculate_dependencies_for_package(self, source_repo, kind, name,
+ version, filename):
+ tool = '%s.find_deps' % kind
+
+ if kind not in self.importers:
+ raise Exception('Importer for %s was not enabled.' % kind)
+ extra_args = self.importers[kind]['extra_args']
+
+ self.app.status(
+ 'Calling %s to calculate dependencies for %s %s', tool, name,
+ version)
+
+ args = extra_args + [source_repo.dirname, name]
+ if version != 'master':
+ args.append(version)
+ text = run_extension(tool, args)
+
+ return json.loads(text)
+
def _sort_chunks_by_build_order(self, graph):
order = reversed(sorted(graph.nodes()))
try:
diff --git a/omnibus.find_deps b/omnibus.find_deps
new file mode 100755
index 0000000..8fea31d
--- /dev/null
+++ b/omnibus.find_deps
@@ -0,0 +1,138 @@
+#!/usr/bin/env ruby
+#
+# Find dependencies for an Omnibus software component.
+#
+# 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_omnibus_extensions'
+
+BANNER = "Usage: omnibus.find_deps PROJECT_DIR PROJECT_NAME SOURCE_DIR SOFTWARE_NAME"
+
+DESCRIPTION = <<-END
+Calculate dependencies for a given Omnibus software component.
+END
+
+class OmnibusDependencyFinder < Importer::Base
+ def initialize
+ local_data = YAML.load_file(local_data_path("omnibus.yaml"))
+ @dependency_blacklist = local_data['dependency-blacklist']
+ end
+
+ def parse_options(arguments)
+ opts = create_option_parser(BANNER, DESCRIPTION)
+
+ parsed_arguments = opts.parse!(arguments)
+
+ if parsed_arguments.length != 4 and parsed_arguments.length != 5
+ STDERR.puts "Expected 4 or 5 arguments, got #{parsed_arguments}."
+ opts.parse(['-?'])
+ exit 255
+ end
+
+ project_dir, project_name, source_dir, software_name, expected_version = \
+ parsed_arguments
+ # Not yet implemented
+ #if expected_version != nil
+ # expected_version = Gem::Version.new(expected_version)
+ #end
+ [project_dir, project_name, source_dir, software_name, expected_version]
+ end
+
+ def resolve_rubygems_deps(requirements)
+ return {} if requirements.empty?
+
+ log.info('Resolving RubyGem requirements with Bundler')
+
+ fake_gemfile = Bundler::Dsl.new
+ fake_gemfile.source('https://rubygems.org')
+
+ requirements.each do |dep|
+ fake_gemfile.gem(dep.name, dep.requirement)
+ end
+
+ definition = fake_gemfile.to_definition('Gemfile.lock', true)
+ resolved_specs = definition.resolve_remotely!
+
+ Hash[resolved_specs.collect { |spec| [spec.name, spec.version.to_s]}]
+ end
+
+ def calculate_dependencies_for_software(project, software, source_dir)
+ omnibus_deps = {}
+ rubygems_deps = {}
+
+ software.dependencies.each do |name|
+ software = Omnibus::Software.load(project, name)
+ if @dependency_blacklist.member? name
+ log.info(
+ "Not adding #{name} as a dependency as it is marked to be ignored.")
+ elsif software.fetcher.instance_of?(Omnibus::PathFetcher)
+ log.info(
+ "Not adding #{name} as a dependency: it's installed from " +
+ "a path which probably means that it is package configuration, not " +
+ "a 3rd-party component to be imported.")
+ elsif software.fetcher.instance_of?(Omnibus::NullFetcher)
+ if software.builder.built_gemspec
+ log.info(
+ "Adding #{name} as a RubyGem dependency because it builds " +
+ "#{software.builder.built_gemspec}")
+ rubygems_deps[name] = software.version
+ else
+ log.info(
+ "Not adding #{name} as a dependency: no sources listed.")
+ end
+ else
+ omnibus_deps[name] = software.version
+ end
+ end
+
+ gem_requirements = software.builder.manually_installed_rubygems
+ rubygems_deps = resolve_rubygems_deps(gem_requirements)
+
+ {
+ "omnibus" => {
+ # FIXME: are these build or runtime dependencies? We'll assume both.
+ "build-dependencies" => omnibus_deps,
+ "runtime-dependencies" => omnibus_deps,
+ },
+ "rubygems" => {
+ "build-dependencies" => {},
+ "runtime-dependencies" => rubygems_deps,
+ }
+ }
+ end
+
+ def run
+ project_dir, project_name, source_dir, software_name = parse_options(ARGV)
+
+ log.info("Calculating dependencies for #{software_name} from project " +
+ "#{project_name}, defined in #{project_dir}")
+
+ Dir.chdir(project_dir)
+
+ project = Omnibus::Project.load(project_name)
+
+ software = Omnibus::Software.load(@project, software_name)
+
+ dependencies = calculate_dependencies_for_software(
+ project, software, source_dir)
+ write_dependencies(STDOUT, dependencies)
+ end
+end
+
+OmnibusDependencyFinder.new.run
diff --git a/omnibus.to_chunk b/omnibus.to_chunk
index 1189199..5e527a9 100755
--- a/omnibus.to_chunk
+++ b/omnibus.to_chunk
@@ -1,6 +1,6 @@
#!/usr/bin/env ruby
#
-# Create a chunk morphology to integrate Omnibus software in Baserock
+# Create a chunk morphology to build Omnibus software in Baserock
#
# Copyright (C) 2014 Codethink Limited
#
@@ -17,15 +17,8 @@
# 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 'omnibus'
-
-require 'optparse'
-require 'rubygems/commands/build_command'
-require 'rubygems/commands/install_command'
-require 'shellwords'
-
require_relative 'importer_base'
+require_relative 'importer_omnibus_extensions'
BANNER = "Usage: omnibus.to_chunk PROJECT_DIR PROJECT_NAME SOURCE_DIR SOFTWARE_NAME"
@@ -33,82 +26,7 @@ DESCRIPTION = <<-END
Generate a .morph file for a given Omnibus software component.
END
-class Omnibus::Builder
- # It's possible to use `gem install` in build commands, which is a great
- # way of subverting the dependency tracking Omnibus provides. It's done
- # in `omnibus-chef/config/software/chefdk.rb`, for example.
- #
- # To handle this, here we extend the class that executes the build commands
- # to detect when `gem install` is run. It uses the Gem library to turn the
- # commandline back into a Bundler::Dependency object that we can use.
- #
- # We also trap `gem build` so we know when a software component is a RubyGem
- # that should be handled by 'rubygems.to_chunk'.
-
- class GemBuildCommandParser < Gem::Commands::BuildCommand
- def gemspec_path(args)
- handle_options args
- if options[:args].length != 1
- raise Exception, "Invalid `gem build` commandline: 1 argument " +
- "expected, got #{options[:args]}."
- end
- options[:args][0]
- end
- end
-
- class GemInstallCommandParser < Gem::Commands::InstallCommand
- def dependency_list_from_commandline(args)
- handle_options args
-
- # `gem install foo*` is sometimes used when installing a locally built
- # Gem, to avoid needing to know the exact version number that was built.
- # We only care about remote Gems being installed, so anything with a '*'
- # in its name can be ignored.
- gem_names = options[:args].delete_if { |name| name.include?('*') }
-
- gem_names.collect do |gem_name|
- Bundler::Dependency.new(gem_name, options[:version])
- end
- end
- end
-
- def gem(command, options = {})
- # This function re-implements the 'gem' function in the build-commands DSL.
- if command.start_with? 'build'
- parser = GemBuildCommandParser.new
- args = Shellwords.split(command).drop(1)
- if built_gemspec != nil
- raise Exception, "More than one `gem build` command was run as part " +
- "of the build process. The 'rubygems.to_chunk' " +
- "program currently supports only one .gemspec " +
- "build per chunk, so this can't be processed " +
- "automatically."
- end
- @built_gemspec = parser.gemspec_path(args)
- elsif command.start_with? 'install'
- parser = GemInstallCommandParser.new
- args = Shellwords.split(command).drop(1)
- args_without_build_flags = args.take_while { |item| item != '--' }
- gems = parser.dependency_list_from_commandline(args_without_build_flags)
- manually_installed_rubygems.concat gems
- end
- end
-
- def built_gemspec
- @built_gemspec
- end
-
- def manually_installed_rubygems
- @manually_installed_rubygems ||= []
- end
-end
-
class OmnibusChunkMorphologyGenerator < Importer::Base
- def initialize
- local_data = YAML.load_file(local_data_path("omnibus.yaml"))
- @dependency_blacklist = local_data['dependency-blacklist']
- end
-
def parse_options(arguments)
opts = create_option_parser(BANNER, DESCRIPTION)
@@ -133,6 +51,7 @@ class OmnibusChunkMorphologyGenerator < Importer::Base
end
def run_tool_capture_output(tool_name, *args)
+ scripts_dir = local_data_path('.')
tool_path = local_data_path(tool_name)
# FIXME: something breaks when we try to share this FD, it's not
@@ -171,24 +90,6 @@ class OmnibusChunkMorphologyGenerator < Importer::Base
exit 1
end
- def resolve_rubygems_deps(requirements)
- return {} if requirements.empty?
-
- log.info('Resolving RubyGem requirements with Bundler')
-
- fake_gemfile = Bundler::Dsl.new
- fake_gemfile.source('https://rubygems.org')
-
- requirements.each do |dep|
- fake_gemfile.gem(dep.name, dep.requirement)
- end
-
- definition = fake_gemfile.to_definition('Gemfile.lock', true)
- resolved_specs = definition.resolve_remotely!
-
- Hash[resolved_specs.collect { |spec| [spec.name, spec.version.to_s]}]
- end
-
def generate_chunk_morph_for_software(project, software, source_dir)
if software.builder.built_gemspec != nil
morphology = generate_chunk_morph_for_rubygems_software(software,
@@ -201,49 +102,9 @@ class OmnibusChunkMorphologyGenerator < Importer::Base
}
end
- omnibus_deps = {}
- rubygems_deps = {}
-
- software.dependencies.each do |name|
- software = Omnibus::Software.load(project, name)
- if @dependency_blacklist.member? name
- log.info(
- "Not adding #{name} as a dependency as it is marked to be ignored.")
- elsif software.fetcher.instance_of?(Omnibus::PathFetcher)
- log.info(
- "Not adding #{name} as a dependency: it's installed from " +
- "a path which probably means that it is package configuration, not " +
- "a 3rd-party component to be imported.")
- elsif software.fetcher.instance_of?(Omnibus::NullFetcher)
- if software.builder.built_gemspec
- log.info(
- "Adding #{name} as a RubyGem dependency because it builds " +
- "#{software.builder.built_gemspec}")
- rubygems_deps[name] = software.version
- else
- log.info(
- "Not adding #{name} as a dependency: no sources listed.")
- end
- else
- omnibus_deps[name] = software.version
- end
- end
-
- gem_requirements = software.builder.manually_installed_rubygems
- rubygems_deps = resolve_rubygems_deps(gem_requirements)
-
- morphology.update({
- # Possibly this tool should look at software.build and
- # generate suitable configure, build and install-commands.
- # For now: don't bother!
-
- # FIXME: are these build or runtime dependencies? We'll assume both.
- "x-build-dependencies-omnibus" => omnibus_deps,
- "x-runtime-dependencies-omnibus" => omnibus_deps,
-
- "x-build-dependencies-rubygems" => {},
- "x-runtime-dependencies-rubygems" => rubygems_deps,
- })
+ # Possibly this tool should look at software.build and
+ # generate suitable configure, build and install-commands.
+ # For now: don't bother!
if software.description
morphology['description'] = software.description + '\n\n' +
@@ -266,7 +127,6 @@ class OmnibusChunkMorphologyGenerator < Importer::Base
software = Omnibus::Software.load(@project, software_name)
morph = generate_chunk_morph_for_software(project, software, source_dir)
-
write_morph(STDOUT, morph)
end
end
diff --git a/rubygems.find_deps b/rubygems.find_deps
new file mode 100755
index 0000000..a9f4f08
--- /dev/null
+++ b/rubygems.find_deps
@@ -0,0 +1,114 @@
+#!/usr/bin/env ruby
+#
+# Find dependencies for a RubyGem.
+#
+# 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_relative 'importer_base'
+require_relative 'importer_bundler_extensions'
+
+BANNER = "Usage: rubygems.find_deps SOURCE_DIR GEM_NAME [VERSION]"
+
+DESCRIPTION = <<-END
+This tool looks for a .gemspec file for GEM_NAME in SOURCE_DIR, and outputs the
+set of RubyGems dependencies required to build it. It will honour a
+Gemfile.lock file if one is present.
+
+It is intended for use with the `baserock-import` tool.
+END
+
+class RubyGemDependencyFinder < Importer::Base
+ include Importer::BundlerExtensions
+
+ def initialize
+ local_data = YAML.load_file(local_data_path("rubygems.yaml"))
+ @build_dependency_whitelist = local_data['build-dependency-whitelist']
+ end
+
+ 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 build_deps_for_gem(spec)
+ deps = spec.dependencies.select do |d|
+ d.type == :development && @build_dependency_whitelist.member?(d.name)
+ end
+ end
+
+ def runtime_deps_for_gem(spec)
+ spec.dependencies.select {|d| d.type == :runtime}
+ end
+
+ def run
+ source_dir_name, gem_name, expected_version = parse_options(ARGV)
+
+ log.info("Finding dependencies for #{gem_name} based on " +
+ "source code in #{source_dir_name}")
+
+ resolved_specs = Dir.chdir(source_dir_name) do
+ 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)
+
+ # One might think that you could use the Bundler::Dependency.groups
+ # field to filter but it doesn't seem to be useful. Instead we go back to
+ # the Gem::Specification of the target Gem and use the dependencies fild
+ # there. We look up each dependency in the resolved_specset to find out
+ # what version Bundler has chosen of it.
+
+ def format_deps(specset, dep_list)
+ info = dep_list.collect do |dep|
+ spec = specset[dep][0]
+ [spec.name, spec.version.to_s]
+ end
+ Hash[info]
+ end
+
+ build_deps = format_deps(
+ resolved_specs, build_deps_for_gem(spec))
+ runtime_deps = format_deps(
+ resolved_specs, runtime_deps_for_gem(spec))
+
+ deps = {
+ 'rubygems' => {
+ 'build-dependencies' => build_deps,
+ 'runtime-dependencies' => runtime_deps,
+ }
+ }
+
+ write_dependencies(STDOUT, deps)
+ end
+end
+
+RubyGemDependencyFinder.new.run
diff --git a/rubygems.to_chunk b/rubygems.to_chunk
index 796fe89..e1f7132 100755
--- a/rubygems.to_chunk
+++ b/rubygems.to_chunk
@@ -1,6 +1,6 @@
#!/usr/bin/env ruby
#
-# Create a chunk morphology to integrate a RubyGem in Baserock
+# Create a chunk morphology to build a RubyGem in Baserock
#
# Copyright (C) 2014 Codethink Limited
#
@@ -20,35 +20,20 @@
require 'bundler'
require_relative 'importer_base'
-
-class << Bundler
- def default_gemfile
- # This is a hack to make things not crash when there's no Gemfile
- Pathname.new('.')
- end
-end
-
-def spec_is_from_current_source_tree(spec, source_dir)
- spec.source.instance_of? Bundler::Source::Path and
- File.identical?(spec.source.path, source_dir)
-end
+require_relative 'importer_bundler_extensions'
BANNER = "Usage: rubygems.to_chunk SOURCE_DIR GEM_NAME [VERSION]"
DESCRIPTION = <<-END
-This tool reads the Gemfile and optionally the Gemfile.lock from a Ruby project
-source tree in SOURCE_DIR. It outputs a chunk morphology for GEM_NAME on
-stdout. If VERSION is supplied, it is used to check that the build instructions
-will produce the expected version of the Gem.
+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
- def initialize
- local_data = YAML.load_file(local_data_path("rubygems.yaml"))
- @build_dependency_whitelist = local_data['build-dependency-whitelist']
- end
+ include Importer::BundlerExtensions
def parse_options(arguments)
opts = create_option_parser(BANNER, DESCRIPTION)
@@ -69,39 +54,6 @@ class RubyGemChunkMorphologyGenerator < Importer::Base
[source_dir, gem_name, expected_version]
end
- def load_local_gemspecs()
- # Look for .gemspec files in the source repo.
- #
- # If there is no .gemspec, but you set 'name' and 'version' then
- # inside Bundler::Source::Path.load_spec_files this call will create a
- # fake gemspec matching that name and version. That's probably not useful.
-
- dir = '.'
-
- source = Bundler::Source::Path.new({
- 'path' => dir,
- })
-
- log.info "Loaded #{source.specs.count} specs from source dir."
- source.specs.each do |spec|
- log.debug " * #{spec.inspect} #{spec.dependencies.inspect}"
- end
-
- source
- end
-
- def get_spec_for_gem(specs, gem_name)
- found = specs[gem_name].select {|s| Gem::Platform.match(s.platform)}
- if found.empty?
- raise Exception,
- "No Gemspecs found matching '#{gem_name}'"
- elsif found.length != 1
- raise Exception,
- "Unsure which Gem to use for #{gem_name}, got #{found}"
- end
- found[0]
- 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
@@ -195,79 +147,23 @@ class RubyGemChunkMorphologyGenerator < Importer::Base
}
end
- def build_deps_for_gem(spec)
- deps = spec.dependencies.select do |d|
- d.type == :development && @build_dependency_whitelist.member?(d.name)
- end
- end
-
- def runtime_deps_for_gem(spec)
- spec.dependencies.select {|d| d.type == :runtime}
- 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}")
- Dir.chdir(source_dir_name)
-
- # Instead of reading the real Gemfile, invent one that simply includes the
- # chosen .gemspec. If present, the Gemfile.lock will be honoured.
- fake_gemfile = Bundler::Dsl.new
- fake_gemfile.source('https://rubygems.org')
- begin
- fake_gemfile.gemspec({:name => gem_name})
- rescue Bundler::InvalidOption
- error "Did not find #{gem_name}.gemspec in #{source_dir_name}"
- exit 1
+ 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
- definition = fake_gemfile.to_definition('Gemfile.lock', true)
- resolved_specs = definition.resolve_remotely!
-
spec = get_spec_for_gem(resolved_specs, gem_name)
-
- if not spec_is_from_current_source_tree(spec, source_dir_name)
- error "Specified gem '#{spec.name}' doesn't live in the source in " +
- "'#{source_dir_name}'"
- log.debug "SPEC: #{spec.inspect} #{spec.source}"
- exit 1
- end
-
- if expected_version != nil && spec.version != expected_version
- # This check is brought to you by Coderay, which changes its version
- # number based on an environment variable. Other Gems may do this too.
- error "Source in #{source_dir_name} produces #{spec.full_name}, but " +
- "the expected version was #{expected_version}."
- exit 1
- end
+ validate_spec(spec, source_dir_name, expected_version)
morph = generate_chunk_morph_for_gem(spec)
-
- # One might think that you could use the Bundler::Dependency.groups
- # field to filter but it doesn't seem to be useful. Instead we go back to
- # the Gem::Specification of the target Gem and use the dependencies fild
- # there. We look up each dependency in the resolved_specset to find out
- # what version Bundler has chosen of it.
-
- def format_deps_for_morphology(specset, dep_list)
- info = dep_list.collect do |dep|
- spec = specset[dep][0]
- [spec.name, spec.version.to_s]
- end
- Hash[info]
- end
-
- build_deps = format_deps_for_morphology(
- resolved_specs, build_deps_for_gem(spec))
- runtime_deps = format_deps_for_morphology(
- resolved_specs, runtime_deps_for_gem(spec))
-
- morph['x-build-dependencies-rubygems'] = build_deps
- morph['x-runtime-dependencies-rubygems'] = runtime_deps
-
write_morph(STDOUT, morph)
end
end