From 99f279b3ffeea2d3f0ac3aca33442f907328a2ae Mon Sep 17 00:00:00 2001 From: David Schmitt Date: Wed, 20 Dec 2017 18:07:13 +0000 Subject: [PATCH] Only require FFI on Windows This allows users to trade off between full-featured and always-deployable by removing the hard dependency on a native extension. The user of the gem needs to depend on FFI themselves to enable `posix_spawn` on Linux. For testing purposes, the Gemfile loads ffi if required by the environment. --- Gemfile | 9 ++++++++- README.md | 2 ++ childprocess.gemspec | 4 ++-- ext/mkrf_conf.rb | 24 ++++++++++++++++++++++++ lib/childprocess.rb | 7 ++++++- lib/childprocess/errors.rb | 11 +++++++++++ 6 files changed, 53 insertions(+), 4 deletions(-) create mode 100644 ext/mkrf_conf.rb diff --git a/Gemfile b/Gemfile index 6bf5234..7b72822 100644 --- a/Gemfile +++ b/Gemfile @@ -10,6 +10,13 @@ if RUBY_VERSION =~ /^1\./ gem 'term-ansicolor', '< 1.4' # The 'term-ansicolor' gem requires Ruby 2.x on/after this version if RbConfig::CONFIG['host_os'].downcase =~ /mswin|msys|mingw32/ - gem 'ffi', '< 1.9.15' # The 'ffi' gem, for Windows, requires Ruby 2.x on/after this version + # The 'ffi' gem, for Windows, requires Ruby 2.x on/after this version + gem 'ffi', '< 1.9.15' + else + # Load 'ffi' for testing posix_spawn + gem 'ffi' if ENV['CHILDPROCESS_POSIX_SPAWN'] == 'true' end +else + # Load 'ffi' for testing posix_spawn, or on windows. + gem 'ffi' if ENV['CHILDPROCESS_POSIX_SPAWN'] == 'true' || RbConfig::CONFIG['host_os'].downcase =~ /mswin|msys|mingw32/ end diff --git a/README.md b/README.md index 940424e..f7364d6 100644 --- a/README.md +++ b/README.md @@ -125,6 +125,8 @@ ChildProcess.posix_spawn = true process = ChildProcess.build(*args) ``` +To be able to use this, please make sure that you have the `ffi` gem installed. + ### Ensure entire process tree dies By default, the child process does not create a new process group. This means there's no guarantee that the entire process tree will die when the child process is killed. To solve this: diff --git a/childprocess.gemspec b/childprocess.gemspec index cbf4591..e59dae2 100644 --- a/childprocess.gemspec +++ b/childprocess.gemspec @@ -19,12 +19,12 @@ Gem::Specification.new do |s| s.test_files = `git ls-files -- spec/*`.split("\n") s.require_paths = ["lib"] - s.add_runtime_dependency "ffi", "~> 1.0", ">= 1.0.11" - s.add_development_dependency "rspec", "~> 3.0" s.add_development_dependency "yard", "~> 0.0" s.add_development_dependency 'rake', '< 12.0' s.add_development_dependency 'coveralls', '< 1.0' + + s.extensions = 'ext/mkrf_conf.rb' end diff --git a/ext/mkrf_conf.rb b/ext/mkrf_conf.rb new file mode 100644 index 0000000..f605bfb --- /dev/null +++ b/ext/mkrf_conf.rb @@ -0,0 +1,24 @@ +# Based on the example from https://en.wikibooks.org/wiki/Ruby_Programming/RubyGems#How_to_install_different_versions_of_gems_depending_on_which_version_of_ruby_the_installee_is_using +require 'rubygems' +require 'rubygems/command.rb' +require 'rubygems/dependency_installer.rb' + +begin + Gem::Command.build_args = ARGV +rescue NoMethodError # rubocop:disable Lint/HandleExceptions +end + +inst = Gem::DependencyInstaller.new + +begin + if RbConfig::CONFIG['host_os'] =~ %r{mswin|msys|mingw32}i + inst.install 'ffi', "~> 1.0", ">= 1.0.11" + end +rescue # rubocop:disable Lint/RescueWithoutErrorClass + exit(1) +end + + # create dummy rakefile to indicate success +File.open(File.join(File.dirname(__FILE__), 'Rakefile'), 'w') do |f| + f.write("task :default\n") +end \ No newline at end of file diff --git a/lib/childprocess.rb b/lib/childprocess.rb index 158e45b..a6681fe 100644 --- a/lib/childprocess.rb +++ b/lib/childprocess.rb @@ -73,7 +73,12 @@ def posix_spawn? enabled = @posix_spawn || %w[1 true].include?(ENV['CHILDPROCESS_POSIX_SPAWN']) return false unless enabled - require 'ffi' + begin + require 'ffi' + rescue LoadError + raise ChildProcess::MissingFFIError + end + begin require "childprocess/unix/platform/#{ChildProcess.platform_name}" rescue LoadError diff --git a/lib/childprocess/errors.rb b/lib/childprocess/errors.rb index 45c28d5..d327319 100644 --- a/lib/childprocess/errors.rb +++ b/lib/childprocess/errors.rb @@ -14,6 +14,17 @@ class InvalidEnvironmentVariable < Error class LaunchError < Error end + class MissingFFIError < Error + def initialize + message = "FFI is a required pre-requisite for posix_spawn, falling back to default implementation. " + + "Please add it to your deployment to unlock this functionality. " + + "If you believe this is an error, please file a bug at http://github.com/enkessler/childprocess/issues" + + super(message) + end + + end + class MissingPlatformError < Error def initialize message = "posix_spawn is not yet supported on #{ChildProcess.platform_name} (#{RUBY_PLATFORM}), falling back to default implementation. " +