Class: Gem::Commands::RebuildCommand
Relationships & Source Files | |
Super Chains via Extension / Inclusion / Inheritance | |
Class Chain:
self,
::Gem::Command
|
|
Instance Chain:
|
|
Inherits: |
Gem::Command
|
Defined in: | lib/rubygems/commands/rebuild_command.rb |
Constant Summary
::Gem::Command
- Inherited
Class Attribute Summary
::Gem::Command
- Inherited
.build_args | Arguments used when building gems. |
.build_args=, .extra_args, .extra_args= |
Class Method Summary
- .new ⇒ RebuildCommand constructor
::Gem::Command
- Inherited
.add_common_option, | |
.add_specific_extra_args | Add a list of extra arguments for the given command. |
.common_options, | |
.new | Initializes a generic gem command named |
.specific_extra_args | Return an array of extra arguments for the command. |
.specific_extra_args_hash | Accessor for the specific extra args hash (self initializing). |
Instance Attribute Summary
::Gem::Command
- Inherited
#command | The name of the command. |
#defaults | The default options for the command. |
#deprecated?, | |
#options | The options for the command. |
#program_name | The name of the command for command-line invocation. |
#summary | A short description of the command. |
::Gem::DefaultUserInteraction
- Included
Instance Method Summary
- #execute
- #build_gem(gem_name, source_date_epoch, output_file) private
- #build_package(gemspec, source_date_epoch, output_file) private
- #compare(source_date_epoch, old_file, new_file) private
- #download_gem(gem_name, gem_version, old_file) private
- #error_message(gem_name) private
- #get_gem_name_and_version private
- #get_timestamp(file) private
- #prep_dirs private
- #rubygems_version(gem_file) private
- #sha256(file) private
- #with_source_date_epoch(source_date_epoch) private
- #arguments Internal use only
- #description Internal use only
- #usage Internal use only
::Gem::GemspecHelpers
- Included
::Gem::Command
- Inherited
#add_extra_args | Adds extra args from ~/.gemrc. |
#add_option | Add a command-line option and handler to the command. |
#arguments | Override to provide details of the arguments a command takes. |
#begins? | True if |
#check_deprecated_options, | |
#defaults_str | Override to display the default values of the command options. |
#deprecate_option | Mark a command-line option as deprecated, and optionally specify a deprecation horizon. |
#description | Override to display a longer description of what this command does. |
#execute | Override to provide command handling. |
#get_all_gem_names | Get all gem names from the command line. |
#get_all_gem_names_and_versions | Get all [gem, version] from the command line. |
#get_one_gem_name | Get a single gem name from the command line. |
#get_one_optional_argument | Get a single optional argument from the command line. |
#handle_options | Handle the given list of arguments by parsing them and recording the results. |
#handles? | True if the command handles the given argument list. |
#invoke | Invoke the command with the given list of arguments. |
#invoke_with_build_args | Invoke the command with the given list of normal arguments and additional build arguments. |
#merge_options | Merge a set of command options with the set of default options (without modifying the default option hash). |
#remove_option | Remove previously defined command-line argument |
#show_help | Display the help message for the command. |
#show_lookup_failure | Display to the user that a gem couldn’t be found and reasons why –. |
#usage | Override to display the usage for an individual gem command. |
#when_invoked | Call the given block when invoked. |
#add_parser_run_info | Adds a section with |
#configure_options, | |
#create_option_parser | Creates an option parser and fills it in with the help info for the command. |
#option_is_deprecated?, | |
#parser | Create on demand parser. |
#wrap | Wraps |
#extract_gem_name_and_version, #add_parser_description, #add_parser_options, #add_parser_summary |
::Gem::UserInteraction
- Included
#alert | Displays an alert |
#alert_error | Displays an error |
#alert_warning | Displays a warning |
#ask | Asks a |
#ask_for_password | Asks for a password with a |
#ask_yes_no | Asks a yes or no |
#choose_from_list | Asks the user to answer |
#say | Displays the given |
#terminate_interaction | Terminates the RubyGems process with the given |
#verbose | Calls |
::Gem::DefaultUserInteraction
- Included
::Gem::Text
- Included
#clean_text | Remove any non-printable characters and make the text suitable for printing. |
#format_text | Wraps |
#levenshtein_distance | Returns a value representing the “cost” of transforming str1 into str2 Vendored version of |
#truncate_text, #min3 |
Constructor Details
.new ⇒ RebuildCommand
# File 'lib/rubygems/commands/rebuild_command.rb', line 13
def initialize super "rebuild", "Attempt to reproduce a build of a gem." add_option "--diff", "If the files don't match, compare them using diffoscope." do |_value, | [:diff] = true end add_option "--force", "Skip validation of the spec." do |_value, | [:force] = true end add_option "--strict", "Consider warnings as errors when validating the spec." do |_value, | [:strict] = true end add_option "--source GEM_SOURCE", "Specify the source to download the gem from." do |value, | [:source] = value end add_option "--original GEM_FILE", "Specify a local file to compare against (instead of downloading it)." do |value, | [:original_gem_file] = value end add_option "--gemspec GEMSPEC_FILE", "Specify the name of the gemspec file." do |value, | [:gemspec_file] = value end add_option "-C PATH", "Run as if gem build was started in <PATH> instead of the current working directory." do |value, | [:build_path] = value end end
Instance Method Details
#arguments
# File 'lib/rubygems/commands/rebuild_command.rb', line 45
def arguments # :nodoc: "GEM_NAME gem name on gem server\n" \ "GEM_VERSION gem version you are attempting to rebuild" end
#build_gem(gem_name, source_date_epoch, output_file) (private)
[ GitHub ]# File 'lib/rubygems/commands/rebuild_command.rb', line 190
def build_gem(gem_name, source_date_epoch, output_file) gemspec = [:gemspec_file] || find_gemspec("#{gem_name}.gemspec") if gemspec build_package(gemspec, source_date_epoch, output_file) else alert_error (gem_name) terminate_interaction(1) end end
#build_package(gemspec, source_date_epoch, output_file) (private)
[ GitHub ]# File 'lib/rubygems/commands/rebuild_command.rb', line 201
def build_package(gemspec, source_date_epoch, output_file) with_source_date_epoch(source_date_epoch) do spec = Gem::Specification.load(gemspec) if spec Gem::Package.build( spec, [:force], [:strict], output_file ) else alert_error "Error loading gemspec. Aborting." terminate_interaction 1 end end end
#compare(source_date_epoch, old_file, new_file) (private)
[ GitHub ]# File 'lib/rubygems/commands/rebuild_command.rb', line 131
def compare(source_date_epoch, old_file, new_file) date = Time.at(source_date_epoch.to_i).strftime("%F %T %Z") old_hash = sha256(old_file) new_hash = sha256(new_file) say say "Built at: #{date} (#{source_date_epoch})" say "Original build saved to: #{old_file}" say "Reproduced build saved to: #{new_file}" say "Working directory: #{ [:build_path] || Dir.pwd}" say say "Hash comparison:" say " #{old_hash}\t#{old_file}" say " #{new_hash}\t#{new_file}" say if old_hash == new_hash say "SUCCESS - original and rebuild hashes matched" else say "FAILURE - original and rebuild hashes did not match" say if [:diff] if system("diffoscope", old_file, new_file).nil? alert_error "error: could not find `diffoscope` executable" end else say "Pass --diff for more details (requires diffoscope to be installed)." end terminate_interaction 1 end end
#description
# File 'lib/rubygems/commands/rebuild_command.rb', line 50
def description # :nodoc: <<-EOF The rebuild command allows you to (attempt to) reproduce a build of a gem from a ruby gemspec. This command assumes the gemspec can be built with the `gem build` command. If you use any of `gem build`, `rake build`, or`rake release` in the build/release process for a gem, it is a potential candidate. You will need to match the RubyGems version used, since this is included in the Gem metadata. If the gem includes lockfiles (e.g. Gemfile.lock) and similar, it will require more effort to reproduce a build. For example, it might require more precisely matched versions of Ruby and/or Bundler to be used. EOF end
#download_gem(gem_name, gem_version, old_file) (private)
[ GitHub ]# File 'lib/rubygems/commands/rebuild_command.rb', line 235
def download_gem(gem_name, gem_version, old_file) # This code was based loosely off the `gem fetch` command. version = "= #{gem_version}" dep = Gem::Dependency.new gem_name, version specs_and_sources, errors = Gem::SpecFetcher.fetcher.spec_for_dependency dep # There should never be more than one item in specs_and_sources, # since we search for an exact version. spec, source = specs_and_sources[0] if spec.nil? show_lookup_failure gem_name, version, errors, [:domain] terminate_interaction 1 end download_path = source.download spec FileUtils.move(download_path, old_file) say "Downloaded #{gem_name} version #{gem_version} as #{old_file}." end
#error_message(gem_name) (private)
[ GitHub ]# File 'lib/rubygems/commands/rebuild_command.rb', line 227
def (gem_name) if gem_name "Couldn't find a gemspec file matching '#{gem_name}' in #{Dir.pwd}" else "Couldn't find a gemspec file in #{Dir.pwd}" end end
#execute
[ GitHub ]# File 'lib/rubygems/commands/rebuild_command.rb', line 72
def execute gem_name, gem_version = get_gem_name_and_version old_dir, new_dir = prep_dirs gem_filename = "#{gem_name}-#{gem_version}.gem" old_file = File.join(old_dir, gem_filename) new_file = File.join(new_dir, gem_filename) if [:original_gem_file] FileUtils.copy_file( [:original_gem_file], old_file) else download_gem(gem_name, gem_version, old_file) end rg_version = rubygems_version(old_file) unless rg_version == Gem::VERSION alert_error <<-EOF You need to use the same RubyGems version #{gem_name} v#{gem_version} was built with. #{gem_name} v#{gem_version} was built using RubyGems v#{rg_version}. Gem files include the version of RubyGems used to build them. This means in order to reproduce #{gem_filename}, you must also use RubyGems v#{rg_version}. You're using RubyGems v#{Gem::VERSION}. Please install RubyGems v#{rg_version} and try again. EOF terminate_interaction 1 end source_date_epoch = (old_file).to_s if build_path = [:build_path] Dir.chdir(build_path) { build_gem(gem_name, source_date_epoch, new_file) } else build_gem(gem_name, source_date_epoch, new_file) end compare(source_date_epoch, old_file, new_file) end
#get_gem_name_and_version (private)
[ GitHub ]# File 'lib/rubygems/commands/rebuild_command.rb', line 177
def get_gem_name_and_version args = [:args] || [] if args.length == 2 gem_name, gem_version = args elsif args.length > 2 raise Gem::CommandLineError, "Too many arguments" else raise Gem::CommandLineError, "Expected GEM_NAME and GEM_VERSION arguments (gem rebuild GEM_NAME GEM_VERSION)" end [gem_name, gem_version] end
#get_timestamp(file) (private)
[ GitHub ]#prep_dirs (private)
[ GitHub ]# File 'lib/rubygems/commands/rebuild_command.rb', line 166
def prep_dirs rebuild_dir = Dir.mktmpdir("gem_rebuild") old_dir = File.join(rebuild_dir, "old") new_dir = File.join(rebuild_dir, "new") FileUtils.mkdir_p(old_dir) FileUtils.mkdir_p(new_dir) [old_dir, new_dir] end
#rubygems_version(gem_file) (private)
[ GitHub ]#sha256(file) (private)
[ GitHub ]# File 'lib/rubygems/commands/rebuild_command.rb', line 116
def sha256(file) Digest::SHA256.hexdigest(Gem.read_binary(file)) end
#usage
# File 'lib/rubygems/commands/rebuild_command.rb', line 68
def usage # :nodoc: "#{program_name} GEM_NAME GEM_VERSION" end
#with_source_date_epoch(source_date_epoch) (private)
[ GitHub ]# File 'lib/rubygems/commands/rebuild_command.rb', line 218
def with_source_date_epoch(source_date_epoch) old_sde = ENV["SOURCE_DATE_EPOCH"] ENV["SOURCE_DATE_EPOCH"] = source_date_epoch.to_s yield ensure ENV["SOURCE_DATE_EPOCH"] = old_sde end