#!/usr/bin/env ruby require "rubygems" require "bundler" Bundler.with_clean_env do require_relative "../gemfile_util" options = {} opts = OptionParser.new do |opts| opts.banner = "Usage: create-override-gemfile [OPTIONS]" opts.on("--gemfile GEMFILE", "The Gemfile to read (default: Gemfile).") { |path| options[:gemfile_path] = path } opts.on("--lockfile GEMFILE", "The lockfile to read (default: .lock).") { |path| options[:lockfile_path] = path } opts.on("--group GROUP", "Groups to include (whitelist).") do |group| options[:groups] ||= [] options[:groups] << group.to_sym end opts.on("--without GROUP", "Groups to exclude.") do |group| options[:without_groups] ||= [] options[:without_groups] << group.to_sym end opts.on("--gem GEM", "Gems to include regardless of groups.") do |name| options[:gems] ||= [] options[:gems] << name end opts.on("--relative-to PATH", "A path to prepend to any relative paths in the Gemfile.") do |path| unless Pathname.new(path).absolute? puts opts raise "--relative-to #{path} was not an absolute path!" end options[:relative_to] = path end opts.on("--[no-]copy-groups", "Whether to copy groups over from the original Gemfile or not (default: false).") { |val| options[:copy_groups] = val } opts.on("--[no-]override", "Whether to emit override: true on each gem line (default: false).") { |val| options[:override] = val } opts.on("-h", "--help", "Print this message.") do puts opts exit(0) end end args = opts.parse(ARGV) if args.size > 0 puts opts raise "Invalid arguments #{args}" end def create_override_gemfile(gemfile_path: "Gemfile", lockfile_path: "#{gemfile_path}.lock", groups: nil, without_groups: nil, gems: [], copy_groups: false, relative_to: ".", override: false) relative_to = Pathname.new(relative_to).realpath # Select the gems we want bundle = GemfileUtil::Bundle.parse(gemfile_path, lockfile_path) gems_to_include = bundle.select_gems(groups: groups, without_groups: without_groups) gems.each do |name| raise "Requested gem #{name} is not in #{gemfile_path}.lock!" if !bundle.gems[name] gems_to_include[name] ||= bundle.gems[name] gems_to_include[name][:dependencies].each do |dep| gems_to_include[name] ||= bundle.gems[dep] end end # Add the gems to the Gemfile gem_root = Pathname.new(gemfile_path).dirname.realpath gems_to_include.sort_by { |name, options| options[:declared_groups].empty? ? 1 : 0 }.each do |name, options| comment = nil options = options.dup version = options.delete(:version) if copy_groups # Some dependencies have no groups (are not in the Gemfile--just runtime # dependencies). If we actually record that they have no groups, they # will *always* be installed (or perhaps never). We only want them to # install if their other deps do, so we mark them with the groups of the # things that brought them in (the gems that depended on them). To do # this, we just leave :groups intact. if options[:declared_groups].empty? options.delete(:declared_groups) comment = " # Transitive dependency, not actually in original Gemfile" else # For other things, we want to copy the actual declared_groups--the # ones that were in the Gemfile. We want the same --with and --without # options to include and exclude them as worked with the original # Gemfile. options[:groups] = options.delete(:declared_groups) end else options.delete(:groups) options.delete(:declared_groups) end options.delete(:dependencies) options.delete(:development_dependencies) options[:override] = true if override options[:path] = Pathname.new(options[:path]).expand_path(gem_root).relative_path_from(relative_to).to_s if options[:path] line = "gem #{name.inspect}, #{version.inspect}" options.each do |name, value| line << ", #{name}: #{value.inspect}" end line << comment if comment puts line end end create_override_gemfile(options) end