├── Gemfile ├── .gitignore ├── lib ├── executable-hooks │ ├── version.rb │ ├── specification.rb │ ├── uninstaller.rb │ ├── hooks.rb │ ├── wrapper.rb │ ├── installer.rb │ └── regenerate_binstubs_command.rb ├── rubygems_executable_plugin.rb └── rubygems_plugin.rb ├── bin ├── executable-hooks-uninstaller └── ruby_executable_hooks ├── DEVELOPMENT.md ├── LICENSE ├── README.md ├── test-tf-bundle └── bundle_comment_test.sh ├── test-tf-truffleruby └── truffleruby_comment_test.sh ├── test-tf └── rubygems_comment_test.sh ├── ext └── wrapper_installer │ └── extconf.rb ├── executable-hooks.gemspec ├── .travis.yml └── CHANGELOG.md /Gemfile: -------------------------------------------------------------------------------- 1 | source "http://rubygems.org" 2 | 3 | gemspec 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | *.gem 3 | .bundle 4 | Gemfile.lock 5 | pkg/* 6 | -------------------------------------------------------------------------------- /lib/executable-hooks/version.rb: -------------------------------------------------------------------------------- 1 | module ExecutableHooks 2 | VERSION = "1.7.1" 3 | end 4 | -------------------------------------------------------------------------------- /bin/executable-hooks-uninstaller: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'executable-hooks/uninstaller' 4 | 5 | ExecutableHooks.uninstall 6 | -------------------------------------------------------------------------------- /lib/rubygems_executable_plugin.rb: -------------------------------------------------------------------------------- 1 | Gem.execute do |original_file| 2 | warn("Executing: #{original_file}") if ENV.key?('ExecutableHooks_DEBUG') 3 | end 4 | -------------------------------------------------------------------------------- /DEVELOPMENT.md: -------------------------------------------------------------------------------- 1 | # Releasing 2 | 3 | - update `lib/executable-hooks/version.rb` with new version (use semantic versioning) 4 | - update `CHANGELOG.md` with changes relevant to running hooks 5 | - git commit changes with version as comment 6 | - git tag version 7 | - `git push` 8 | - `git push --tags` 9 | - use ruby 1.8.7 for releasing `rvm use 1.8.7 --install` 10 | - `gem build executable-hooks.gemspec` 11 | - `gem push executable-hooks-1.6.1.gem` - update version 12 | -------------------------------------------------------------------------------- /lib/executable-hooks/specification.rb: -------------------------------------------------------------------------------- 1 | module ExecutableHooks 2 | module Specification 3 | def self.find 4 | @executable_hooks_spec ||= 5 | if Gem::Specification.respond_to?(:find_by_name) 6 | Gem::Specification.find_by_name("executable-hooks", ">=0", ">=0.alpha") 7 | else 8 | Gem.source_index.find_name("executable-hooks").last 9 | end 10 | rescue Gem::LoadError 11 | nil 12 | end 13 | def self.version 14 | find ? find.version.to_s : nil 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013-2014 Michal Papis 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Rubygems executable hooks 2 | 3 | Add next rubygems plugin support for executables. 4 | 5 | ## Usage 6 | 7 | Install the gem: 8 | 9 | gem install executable-hooks 10 | 11 | In gem `lib` dir create `rubygems_executable_plugin.rb`: 12 | 13 | Gem.execute do |original_file| 14 | warn("Executing: #{original_file}") 15 | end 16 | 17 | Generate and install the new gem with executable hook. 18 | 19 | Now try it: 20 | 21 | gem install haml 22 | haml --version 23 | 24 | Returns: 25 | 26 | Executing: /home/mpapis/.rvm/gems/ruby-1.8.7-p374-new1/bin/haml 27 | Haml 4.0.3 28 | -------------------------------------------------------------------------------- /lib/executable-hooks/uninstaller.rb: -------------------------------------------------------------------------------- 1 | require 'executable-hooks/wrapper' 2 | require 'executable-hooks/regenerate_binstubs_command' 3 | require 'rubygems/uninstaller' 4 | 5 | module ExecutableHooks 6 | def self.uninstall 7 | Gem.configuration[:custom_shebang] = "$env #{Gem.default_exec_format % "ruby"}" 8 | options = RegenerateBinstubsCommand.default_install_options 9 | RegenerateBinstubsCommand.new.execute_no_wrapper("ruby") 10 | ExecutableHooks::Wrapper.new(options).uninstall 11 | options.merge!(:executables => true, :all => true, :ignore => true) 12 | Gem::Uninstaller.new("executable-hooks", options).uninstall 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /test-tf-bundle/bundle_comment_test.sh: -------------------------------------------------------------------------------- 1 | gem install executable-hooks-$(awk -F'"' '/VERSION/{print $2}' < lib/executable-hooks/version.rb).gem --development 2 | # match=/installed/ 3 | gem install rubygems-bundler bundler 4 | # match=/installed/ 5 | gem install haml -v "4.0.7" # match=/installed/ 6 | # match=/installed/ 7 | 8 | true TMPDIR:${TMPDIR:=/tmp}: 9 | d=$TMPDIR/test-bundled 10 | mkdir $d 11 | pushd $d 12 | 13 | NOEXEC=1 haml -v # match=/4.0.7/ 14 | 15 | echo -e 'source "https://rubygems.org"\ngem "haml", "4.0.6"' > Gemfile 16 | bundle install # match=/haml 4.0.6/ 17 | NOEXEC=1 haml -v # match=/4.0.6/ 18 | 19 | popd 20 | rm -rf $d -------------------------------------------------------------------------------- /test-tf-truffleruby/truffleruby_comment_test.sh: -------------------------------------------------------------------------------- 1 | gem install executable-hooks-$(awk -F'"' '/VERSION/{print $2}' < lib/executable-hooks/version.rb).gem --development 2 | # match=/installed/ 3 | 4 | gem install haml -v "4.0.7" # match=/installed/ 5 | haml_bin=$(which haml ) 6 | head -n 1 $haml_bin # match=/ruby_executable_hooks/ 7 | 8 | ## simulate truffleruby style binary with shell code mixed in 9 | ## \043 - is a # ... somehow it breaks tf 10 | ( head -n 1 $haml_bin; echo -e 'echo $HOME\n\043!ruby'; tail -n +2 $haml_bin ) > $haml_bin.new 11 | mv $haml_bin.new $haml_bin 12 | chmod +x $haml_bin 13 | 14 | haml _4.0.7_ -v # match=/4.0.7/ 15 | 16 | gem uninstall -x haml -v 4.0.7 # match=/Successfully uninstalled/ 17 | -------------------------------------------------------------------------------- /test-tf/rubygems_comment_test.sh: -------------------------------------------------------------------------------- 1 | gem install executable-hooks-$(awk -F'"' '/VERSION/{print $2}' < lib/executable-hooks/version.rb).gem --development 2 | # match=/installed/ 3 | wrapper_name=$(ruby -I ./lib/ -r executable-hooks/wrapper -e "puts ExecutableHooks::Wrapper.expanded_wrapper_name") 4 | 5 | gem install haml -v "<5" # match=/installed/ 6 | head -n 1 $(which haml ) # match=/ruby_executable_hooks/ 7 | which ${wrapper_name} # status=0 8 | 9 | gem list # match=/haml/ 10 | executable-hooks-uninstaller # match=/haml/ 11 | 12 | head -n 1 $(which haml) # match!=/ruby_executable_hooks/ 13 | which ${wrapper_name} # status=1 14 | 15 | gem uninstall -x haml # match=/Successfully uninstalled/ 16 | -------------------------------------------------------------------------------- /bin/ruby_executable_hooks: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | title = "ruby #{ARGV*" "}" 4 | $0 = ARGV.shift 5 | Process.setproctitle(title) if Process.methods.include?(:setproctitle) 6 | 7 | require 'rubygems' 8 | begin 9 | require 'executable-hooks/hooks' 10 | Gem::ExecutableHooks.run($0) 11 | rescue LoadError 12 | warn "unable to load executable-hooks/hooks" if ENV.key?('ExecutableHooks_DEBUG') 13 | end unless $0.match(/\/executable-hooks-uninstaller$/) 14 | 15 | content = File.read($0) 16 | 17 | if (index = content.index("\n#!ruby\n")) && index > 0 18 | skipped_content = content.slice!(0..index) 19 | start_line = skipped_content.count("\n") + 1 20 | eval content, binding, $0, start_line 21 | else 22 | eval content, binding, $0 23 | end 24 | -------------------------------------------------------------------------------- /ext/wrapper_installer/extconf.rb: -------------------------------------------------------------------------------- 1 | # Fake building extension 2 | File.open('Makefile', 'w') { |f| f.write("all:\n\ninstall:\n\n") } 3 | File.open('make', 'w') do |f| 4 | f.write('#!/bin/sh') 5 | f.chmod(f.stat.mode | 0111) 6 | end 7 | File.open('wrapper_installer.so', 'w') {} 8 | File.open('wrapper_installer.dll', 'w') {} 9 | File.open('nmake.bat', 'w') { |f| } 10 | 11 | 12 | # add the gem to load path 13 | $: << File.expand_path("../../../lib", __FILE__) 14 | # load the actions 15 | require 'executable-hooks/wrapper' 16 | require 'executable-hooks/regenerate_binstubs_command' 17 | # call the actions 18 | options = RegenerateBinstubsCommand.default_install_options 19 | ExecutableHooks::Wrapper.new(options).install_from(File.expand_path("../../..", __FILE__)) 20 | RegenerateBinstubsCommand.new.execute_no_wrapper 21 | # unload the path, what was required stays ... but there is that much we can do 22 | $:.pop 23 | 24 | # just in case - it worked 25 | true 26 | -------------------------------------------------------------------------------- /executable-hooks.gemspec: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # -*- encoding: utf-8 -*- 3 | 4 | Kernel.load(File.expand_path("../lib/executable-hooks/version.rb", __FILE__)) 5 | 6 | Gem::Specification.new do |s| 7 | s.name = "executable-hooks" 8 | s.version = ExecutableHooks::VERSION 9 | s.license = 'Apache-2.0' 10 | s.authors = ["Michal Papis"] 11 | s.email = ["mpapis@gmail.com"] 12 | s.homepage = "https://github.com/rvm/executable-hooks" 13 | s.summary = %q{ 14 | Hook into rubygems executables allowing extra actions to be taken before executable is run. 15 | } 16 | s.post_install_message = <<-MESSAGE 17 | # In case of problems run the following command to update binstubs: 18 | gem regenerate_binstubs 19 | MESSAGE 20 | 21 | 22 | s.files = `git ls-files`.split("\n") 23 | s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") 24 | s.extensions = %w( ext/wrapper_installer/extconf.rb ) 25 | s.executables = %w( executable-hooks-uninstaller ) 26 | 27 | s.add_development_dependency "tf", "~>0.4" 28 | end 29 | -------------------------------------------------------------------------------- /lib/rubygems_plugin.rb: -------------------------------------------------------------------------------- 1 | # Simulate require_relative - it's required as the plugin can be called in wrong version or from bundler. 2 | require File.expand_path('../executable-hooks/specification.rb', __FILE__) 3 | 4 | called_path, called_version = __FILE__.match(/^(.*\/executable-hooks-([^\/]+)\/lib).*$/)[1..2] 5 | 6 | # continue only if loaded and called versions all the same, and not shared gems disabled in bundler 7 | if 8 | ( $:.include?(called_path) || ExecutableHooks::Specification.version == called_version ) and 9 | ( !defined?(Bundler) || ( defined?(Bundler) && Bundler::SharedHelpers.in_bundle? && !Bundler.settings[:disable_shared_gems]) ) 10 | 11 | require 'rubygems/version' 12 | require 'executable-hooks/wrapper' 13 | 14 | # Set the custom_shebang if user did not set one 15 | Gem.pre_install do |gem_installer| 16 | options = if gem_installer.methods.map{|m|m.to_s}.include?('options') 17 | gem_installer.options 18 | end 19 | ExecutableHooks::Wrapper.new(options).install 20 | end 21 | 22 | if Gem::Version.new(Gem::VERSION) < Gem::Version.new('2.0') then 23 | # Add custom_shebang support to rubygems 24 | require 'executable-hooks/installer' 25 | end 26 | 27 | require 'executable-hooks/regenerate_binstubs_command' 28 | Gem::CommandManager.instance.register_command :regenerate_binstubs 29 | end 30 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | rvm: 3 | - jruby 4 | - jruby-head 5 | - rbx-3 6 | before_install: 7 | - 'rvm @global do gem uninstall -a -x rubygems-bundler executable-hooks || true' 8 | - 'rm -f $(rvm @global do gem env home)/bin/jruby_executable_hooks' 9 | - 'rm -rf $rvm_path/gems/*/{bin,gems}/rubygems-bundler-* $rvm_path/gems/*/{bin,gems}/executable-hooks-* $rvm_path/gems/*/bin/ruby_*_{wrapper,hooks}' 10 | - hash -r 11 | - 'if [[ -n "${WITH_RUBYGEMS:-}" ]] ; then gem update --system "${WITH_RUBYGEMS}" ; fi' 12 | - gem build executable-hooks.gemspec 13 | - gem install executable-hooks-$(awk -F'"' '/VERSION/{print $2}' < lib/executable-hooks/version.rb).gem --development 14 | before_script: 15 | - unset BUNDLE_GEMFILE 16 | script: NOEXEC=0 tf --text test-tf${TEST_SUFFIX:-}/* 17 | notifications: 18 | irc: 19 | channels: 20 | - "irc.freenode.org#rubygems-bundler" 21 | email: 22 | recipients: 23 | - mpapis@gmail.com 24 | on_failure: change 25 | matrix: 26 | fast_finish: true 27 | include: 28 | - rvm: 1.8.7 29 | env: WITH_RUBYGEMS=1.4.2 30 | - rvm: 1.8.7 31 | env: WITH_RUBYGEMS=1.6.2 32 | - rvm: 2.5 33 | env: TEST_SUFFIX=-bundle 34 | - rvm: 2.5 35 | env: TEST_SUFFIX=-truffleruby 36 | - rvm: 1.8.7 37 | - rvm: 1.9.2 38 | - rvm: 1.9.3 39 | - rvm: 2.0.0 40 | - rvm: 2.1 41 | - rvm: 2.2 42 | - rvm: 2.3 43 | - rvm: 2.4 44 | - rvm: 2.5 45 | - rvm: ruby-head 46 | allow_failures: 47 | - rvm: rbx-3 48 | -------------------------------------------------------------------------------- /lib/executable-hooks/hooks.rb: -------------------------------------------------------------------------------- 1 | module Gem 2 | @executables_hooks ||= [] 3 | 4 | class << self 5 | unless method_defined?(:execute) 6 | def execute(&hook) 7 | @executables_hooks << hook 8 | end 9 | 10 | attr_reader :executables_hooks 11 | end 12 | 13 | unless method_defined?(:load_executable_plugins) 14 | def load_executable_plugins 15 | if ENV['RUBYGEMS_LOAD_ALL_PLUGINS'] 16 | load_plugin_files find_files('rubygems_executable_plugin', false) 17 | else 18 | begin 19 | load_plugin_files find_latest_files('rubygems_executable_plugin', false) 20 | rescue NoMethodError 21 | load_plugin_files find_files('rubygems_executable_plugin', false) 22 | end 23 | end 24 | rescue ArgumentError, NoMethodError 25 | # old rubygems 26 | plugins = find_files('rubygems_executable_plugin') 27 | 28 | plugins.each do |plugin| 29 | 30 | # Skip older versions of the GemCutter plugin: Its commands are in 31 | # RubyGems proper now. 32 | 33 | next if plugin =~ /gemcutter-0\.[0-3]/ 34 | 35 | begin 36 | load plugin 37 | rescue ::Exception => e 38 | details = "#{plugin.inspect}: #{e.message} (#{e.class})" 39 | warn "Error loading RubyGems plugin #{details}" 40 | end 41 | end 42 | end 43 | end 44 | end 45 | 46 | class ExecutableHooks 47 | def self.run(original_file) 48 | Gem.load_executable_plugins 49 | Gem.executables_hooks.each do |hook| 50 | hook.call(original_file) 51 | end 52 | end 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 1.7.1 4 | date: 2024-01-07 5 | 6 | - Replace use of File.exists? by File.exist? #41 7 | - Use Gem::Installer.at instead of Gem::Installer.new #40 8 | 9 | ## 1.6.2 10 | date: 2021-01-26 11 | 12 | - Converts end_with? to match for Ruby 1.8.6 compatibility #37 13 | 14 | ## 1.6.1 15 | date: 2021-01-26 16 | 17 | - Avoid Ruby 2.7 warning, merge #38 18 | 19 | ## 1.6.0 20 | date: 2018-10-25 21 | 22 | - Restore support for ruby-1.8.6, fixes #34 23 | - Do not force overwrite wrapper, fixes #33 24 | 25 | ## 1.5.0 26 | date: 2018-06-25 27 | 28 | - Support truffleruby, fixes rvm/rvm#4408 29 | 30 | ## 1.4.2 31 | date: 2018-02-14 32 | 33 | - Support rubygems 1.4.2, fixes #29 34 | 35 | ## 1.4.1 36 | date: 2018-02-12 37 | 38 | - Fix bundle install, fixes #28 39 | 40 | ## 1.4.0 41 | date: 2018-02-08 42 | 43 | - Add bindir support, fixes #24 44 | 45 | ## 1.3.2 46 | date: 2014-06-06 47 | 48 | - make sure executable mode is set on binaries when using fast fix, update wayneeseguin/rvm#2894 49 | 50 | ## 1.3.1 51 | date: 2014-01-16 52 | 53 | - do not hardcode env ruby_executable_hooks when using shortcut try_to_fix_binstubs, fix #12 54 | 55 | ## 1.3.0 56 | date: 2014-01-11 57 | 58 | - First try to fix binstubs before installing them, fix wayneeseguin/rvm#2536 59 | 60 | ## 1.2.6 61 | date: 2013-10-21 62 | 63 | - Remind users about regenerating binstubs "in case of problems", merge #11 64 | 65 | ## 1.2.5 66 | date: 2013-10-12 67 | 68 | - first try to create the bin dir then check if it is writable, update #9, update wayneeseguin/rvm#2277 69 | 70 | ## 1.2.4 71 | date: 2013-10-10 72 | 73 | - raise exception if can not write to bindir, fix #9 74 | 75 | ## 1.2.3 76 | date: 2013-09-24 77 | 78 | - fix detecting program name and system process title, fix #8, update #7, update #5 79 | 80 | ## 1.2.2 81 | date: 2013-09-23 82 | 83 | - change process title only when supported by Process, fix #7, update #5 84 | 85 | ## 1.2.1 86 | date: 2013-09-22 87 | 88 | - fix installing wrapper when first time installing the gem, fix #6 89 | 90 | ## 1.2.0 91 | date: 2013-09-17 92 | 93 | - fix full program name with params, fix #5 94 | - RG 2.1 introduces find_latest_files 95 | - add license to gemspec, fix #4 96 | 97 | ## 1.1.0 98 | date: 2013-07-11 99 | 100 | - fix hooks for older rubygems versions 101 | - add regenerating wrappers on gem installation, closes #2 102 | - fix uninstaller 103 | - honor the program suffix, fix #3 104 | - add a warning for custom custom_shebang, fix #1 105 | 106 | ## 1.0.0 107 | date: 2013-07-10 108 | 109 | - extracted hooking to rubygems executables from https://github.com/mpapis/rubygems-bundler 110 | -------------------------------------------------------------------------------- /lib/executable-hooks/wrapper.rb: -------------------------------------------------------------------------------- 1 | # install / uninstall wrapper 2 | require 'fileutils' 3 | require 'rubygems' 4 | require 'executable-hooks/specification' 5 | 6 | module ExecutableHooks 7 | class Wrapper 8 | def self.wrapper_name 9 | 'ruby_executable_hooks' 10 | end 11 | 12 | def self.expanded_wrapper_name 13 | Gem.default_exec_format % self.wrapper_name 14 | end 15 | 16 | attr_reader :options 17 | 18 | def initialize(options) 19 | @options = options || RegenerateBinstubsCommand.default_install_options 20 | end 21 | 22 | def install 23 | @bindir = options[:bin_dir] if options[:bin_dir] 24 | ensure_custom_shebang 25 | 26 | executable_hooks_spec = ExecutableHooks::Specification.find 27 | 28 | if executable_hooks_spec 29 | install_from( executable_hooks_spec.full_gem_path ) 30 | end 31 | end 32 | 33 | def install_from(full_gem_path) 34 | wrapper_path = File.expand_path( "bin/#{self.class.wrapper_name}", full_gem_path ) 35 | bindir = calculate_bindir(options) 36 | destination = calculate_destination(bindir) 37 | 38 | if File.exist?(wrapper_path) && !same_file(wrapper_path, destination) 39 | FileUtils.mkdir_p(bindir) unless File.exist?(bindir) 40 | # exception based on Gem::Installer.generate_bin 41 | raise Gem::FilePermissionError.new(bindir) unless File.writable?(bindir) 42 | FileUtils.cp(wrapper_path, destination) 43 | File.chmod(0775, destination) 44 | end 45 | end 46 | 47 | def same_file(file1, file2) 48 | File.exist?(file1) && File.exist?(file2) && 49 | File.read(file1) == File.read(file2) 50 | end 51 | private :same_file 52 | 53 | def uninstall 54 | destination = calculate_destination(calculate_bindir(options)) 55 | FileUtils.rm_f(destination) if File.exist?(destination) 56 | end 57 | 58 | def calculate_bindir(options) 59 | return options[:bin_dir] if options[:bin_dir] 60 | Gem.respond_to?(:bindir,true) ? Gem.send(:bindir) : File.join(Gem.dir, 'bin') 61 | end 62 | 63 | def calculate_destination(bindir) 64 | File.expand_path(self.class.expanded_wrapper_name, bindir) 65 | end 66 | 67 | 68 | def ensure_custom_shebang 69 | expected_shebang = "$env #{self.class.expanded_wrapper_name}" 70 | 71 | Gem.configuration[:custom_shebang] ||= expected_shebang 72 | 73 | if Gem.configuration[:custom_shebang] != expected_shebang 74 | warn(" 75 | Warning: 76 | Found custom_shebang: '#{Gem.configuration[:custom_shebang]}', 77 | Expected custom_shebang: '#{expected_shebang}', 78 | this can potentially break 'executable-hooks' and gem executables overall! 79 | Check your '~/.gemrc' and '/etc/gemrc' for 'custom_shebang' and remove it. 80 | ") 81 | end 82 | end 83 | 84 | end 85 | end 86 | -------------------------------------------------------------------------------- /lib/executable-hooks/installer.rb: -------------------------------------------------------------------------------- 1 | module ExecutableHooksInstaller 2 | # Iterate through executables and generate wrapper for each one, 3 | # extract of rubygems code 4 | def self.bundler_generate_bin(inst) 5 | return if inst.spec.executables.nil? 6 | executables = inst.spec.executables.reject{ |name| name == 'executable-hooks-uninstaller' } 7 | return if executables.empty? 8 | bindir = inst.bin_dir ? inst.bin_dir : Gem.bindir(inst.gem_home) 9 | executables.each do |filename| 10 | filename.untaint 11 | original = File.join bindir, filename 12 | if File.exists?( original ) 13 | bin_script_path = File.join bindir, inst.formatted_program_filename(filename) 14 | FileUtils.rm_f bin_script_path 15 | File.open bin_script_path, 'wb', 0755 do |file| 16 | file.print bundler_app_script_text(inst, filename) 17 | end 18 | inst.say bin_script_path if Gem.configuration.really_verbose 19 | else 20 | inst.say "Can not find #{inst.spec.name} in GEM_PATH" 21 | break 22 | end 23 | end 24 | end 25 | 26 | 27 | def self.shebang(inst, bin_file_name) 28 | # options were defined first in 1.5, we want to support back to 1.3.7 29 | ruby_name = Gem::ConfigMap[:ruby_install_name] if inst.instance_variable_get(:@env_shebang) 30 | bindir = inst.bin_dir ? inst.bin_dir : Gem.bindir(inst.gem_home) 31 | path = File.join bindir, inst.formatted_program_filename(bin_file_name) 32 | first_line = File.open(path, "rb") {|file| file.gets} 33 | 34 | if /\A#!/ =~ first_line then 35 | # Preserve extra words on shebang line, like "-w". Thanks RPA. 36 | shebang = first_line.sub(/\A\#!.*?ruby\S*((\s+\S+)+)/, "#!#{Gem.ruby}") 37 | opts = $1 38 | shebang.strip! # Avoid nasty ^M issues. 39 | end 40 | 41 | if which = Gem.configuration[:custom_shebang] 42 | which = which.gsub(/\$(\w+)/) do 43 | case $1 44 | when "env" 45 | env_path 46 | when "ruby" 47 | "#{Gem.ruby}#{opts}" 48 | when "exec" 49 | bin_file_name 50 | when "name" 51 | inst.spec.name 52 | end 53 | end 54 | 55 | return "#!#{which}" 56 | end 57 | 58 | if not ruby_name then 59 | "#!#{Gem.ruby}#{opts}" 60 | elsif opts then 61 | "#!/bin/sh\n'exec' #{ruby_name.dump} '-x' \"$0\" \"$@\"\n#{shebang}" 62 | else 63 | "#!#{env_path} #{ruby_name}" 64 | end 65 | end 66 | 67 | def self.env_path 68 | @env_path ||= Gem::Installer::ENV_PATHS.find {|path| File.executable?(path) } 69 | end 70 | 71 | # Return the text for an application file. 72 | def self.bundler_app_script_text(inst, bin_file_name) 73 | <<-TEXT 74 | #{shebang(inst, bin_file_name)} 75 | # 76 | # This file was generated by RubyGems. 77 | # 78 | # The application '#{inst.spec.name}' is installed as part of a gem, and 79 | # this file is here to facilitate running it. 80 | # 81 | 82 | require 'rubygems' 83 | 84 | version = "#{Gem::Requirement.default}" 85 | 86 | if ARGV.first =~ /^_(.*)_$/ and Gem::Version.correct? $1 then 87 | version = $1 88 | ARGV.shift 89 | end 90 | 91 | gem '#{inst.spec.name}', version 92 | load Gem.bin_path('#{inst.spec.name}', '#{bin_file_name}', version) 93 | TEXT 94 | end 95 | 96 | end 97 | 98 | Gem.post_install do |inst| 99 | ExecutableHooksInstaller.bundler_generate_bin(inst) 100 | end 101 | -------------------------------------------------------------------------------- /lib/executable-hooks/regenerate_binstubs_command.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems/command_manager' 2 | require 'rubygems/installer' 3 | require 'rubygems/version' 4 | require 'executable-hooks/wrapper' 5 | 6 | class RegenerateBinstubsCommand < Gem::Command 7 | def self.default_install_options 8 | require 'rubygems/commands/install_command' 9 | Gem::Command.extra_args = Gem.configuration[:gem] 10 | config_args = Gem.configuration[:install] 11 | config_args = 12 | case config_args 13 | when String 14 | config_args.split ' ' 15 | else 16 | Array(config_args) 17 | end 18 | Gem::Command.add_specific_extra_args 'install', config_args 19 | ic = Gem::Commands::InstallCommand.new 20 | ic.handle_options ["install"] 21 | ic.options 22 | end 23 | 24 | def initialize 25 | super 'regenerate_binstubs', 'Re run generation of executable wrappers for gems.' 26 | 27 | add_option(:"Install/Update", '-i', '--install-dir DIR', 28 | 'Gem repository directory to get installed', 29 | 'gems') do |value, options| 30 | options[:install_dir] = File.expand_path(value) 31 | end 32 | 33 | add_option(:"Install/Update", '-n', '--bindir DIR', 34 | 'Directory where binary files are', 35 | 'located') do |value, options| 36 | options[:bin_dir] = File.expand_path(value) 37 | end 38 | end 39 | 40 | def arguments # :nodoc: 41 | "STRING start of gem name to regenerate binstubs" 42 | end 43 | 44 | def usage # :nodoc: 45 | "#{program_name} [STRING]" 46 | end 47 | 48 | def defaults_str # :nodoc: 49 | "" 50 | end 51 | 52 | def description # :nodoc: 53 | 'Re run generation of executable wrappers for all gems. '+ 54 | 'Wrappers will be compatible with both rubygems and bundler. '+ 55 | 'The switcher is BUNDLE_GEMFILE environment variable, '+ 56 | 'when set it switches to bundler mode, when not set, '+ 57 | 'then the command will work as it was with pure rubygems.' 58 | end 59 | 60 | def execute 61 | if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('2.0.0') then 62 | # https://github.com/rubygems/rubygems/issues/326 63 | puts "try also: gem pristine --binstubs" 64 | end 65 | ExecutableHooks::Wrapper.new(options).install 66 | execute_no_wrapper 67 | end 68 | 69 | def execute_no_wrapper(wrapper_name = ExecutableHooks::Wrapper.expanded_wrapper_name) 70 | require 'executable-hooks/installer' 71 | name = get_one_optional_argument || '' 72 | specs = installed_gems.select{|spec| spec.name =~ /^#{name}/i } 73 | specs.each do |spec| 74 | executables = spec.executables.reject{ |name| name == 'executable-hooks-uninstaller' } 75 | unless executables.empty? 76 | try_to_fix_binstubs(spec, executables, wrapper_name) or 77 | try_to_install_binstubs(spec) or 78 | $stderr.puts "##{spec.name} #{spec.version} not found in GEM_PATH" 79 | end 80 | end 81 | end 82 | 83 | private 84 | 85 | def try_to_fix_binstubs(spec, executables, wrapper_name) 86 | executable_paths = 87 | executables.map do |executable| 88 | path = expanded_bin_paths.detect{|bin_path| File.exist?(File.join(bin_path, executable)) } 89 | File.join(path, executable) if path 90 | end 91 | return false if executable_paths.include?(nil) # not found 92 | executable_shebangs = 93 | executable_paths.map do |path| 94 | [path, File.readlines(path).map{|l|l.chomp}] 95 | end 96 | return false if executable_shebangs.detect{|path, lines| !(lines[0] =~ /^#!\//) } 97 | puts "#{spec.name} #{spec.version}" 98 | executable_mode = 0111 99 | executable_shebangs.map do |path, lines| 100 | lines[0] = "#!#{ExecutableHooksInstaller.env_path} #{wrapper_name}" 101 | File.open(path, "w") do |file| 102 | file.puts(lines) 103 | end 104 | mode = File.stat(path).mode 105 | File.chmod(mode | executable_mode, path) if mode & executable_mode != executable_mode 106 | end 107 | end 108 | 109 | def expanded_bin_paths 110 | @expanded_bin_paths ||= begin 111 | paths = expanded_gem_paths.map{|path| File.join(path, "bin") } 112 | paths << RbConfig::CONFIG["bindir"] 113 | # TODO: bindir from options? 114 | paths 115 | end 116 | end 117 | 118 | def try_to_install_binstubs(spec) 119 | org_gem_path = options[:install_dir] || existing_gem_path(spec.full_name) || Gem.dir 120 | cache_gem = File.join(org_gem_path, 'cache', spec.file_name) 121 | if File.exist? cache_gem 122 | puts "#{spec.name} #{spec.version}" 123 | installer_method = Gem::Installer.respond_to?(:at) ? :at : :new 124 | inst = Gem::Installer.public_send installer_method, Dir[cache_gem].first, :wrappers => true, :force => true, :install_dir => org_gem_path, :bin_dir => options[:bin_dir] 125 | ExecutableHooksInstaller.bundler_generate_bin(inst) 126 | else 127 | false 128 | end 129 | end 130 | 131 | def existing_gem_path(full_name) 132 | expanded_gem_paths.find{|path| File.exist?(File.join(path, 'gems', full_name))} 133 | end 134 | 135 | def expanded_gem_paths 136 | @expanded_gem_paths ||= 137 | Gem.path.map do |path| 138 | paths = [path] 139 | while File.symlink?(path) 140 | path = File.readlink(path) 141 | paths << path 142 | end 143 | paths 144 | end.flatten 145 | end 146 | 147 | def installed_gems 148 | if Gem::VERSION > '1.8' then 149 | Gem::Specification.to_a 150 | else 151 | Gem.source_index.map{|name,spec| spec} 152 | end 153 | end 154 | end 155 | --------------------------------------------------------------------------------