diff --git a/bundler/lib/bundler/installer/parallel_installer.rb b/bundler/lib/bundler/installer/parallel_installer.rb index 619ed14a7d4f..f65f171cb8a8 100644 --- a/bundler/lib/bundler/installer/parallel_installer.rb +++ b/bundler/lib/bundler/installer/parallel_installer.rb @@ -119,22 +119,40 @@ def install_with_worker end def with_jobserver - r, w = IO.pipe - r.close_on_exec = false - w.close_on_exec = false - w.write("*" * @size) - - old_makeflags = ENV["MAKEFLAGS"] - ENV["MAKEFLAGS"] = [old_makeflags, "--jobserver-auth=#{r.fileno},#{w.fileno}"].compact.join(" ") - - yield - ensure - # Restore MAKEFLAGS before closing the pipe so a close failure can't - # leave the process with descriptors that point at a closed pipe. - old_makeflags ? ENV["MAKEFLAGS"] = old_makeflags : ENV.delete("MAKEFLAGS") + # The jobserver hands tokens to child `make` processes through MAKEFLAGS + # using the GNU make `--jobserver-auth` protocol. nmake, the default make + # on mswin, instead reads MAKEFLAGS as bare option letters and aborts + # every native extension build with `fatal error U1065: invalid option + # '-'`. Skip the jobserver when nmake is in use. Other Windows toolchains + # such as mingw use GNU make and keep working through the inherited pipe. + return yield if nmake? + + begin + r, w = IO.pipe + r.close_on_exec = false + w.close_on_exec = false + w.write("*" * @size) + + old_makeflags = ENV["MAKEFLAGS"] + ENV["MAKEFLAGS"] = [old_makeflags, "--jobserver-auth=#{r.fileno},#{w.fileno}"].compact.join(" ") + + yield + ensure + # Restore MAKEFLAGS before closing the pipe so a close failure can't + # leave the process with descriptors that point at a closed pipe. + old_makeflags ? ENV["MAKEFLAGS"] = old_makeflags : ENV.delete("MAKEFLAGS") + + r&.close + w&.close + end + end - r&.close - w&.close + # Mirror how RubyGems' extension builder picks the make program so the + # jobserver is only set up when a GNU-compatible make will consume it. + def nmake? + make = ENV["MAKE"] || ENV["make"] + make ||= "nmake" if RUBY_PLATFORM.include?("mswin") + /\bnmake/i.match?(make.to_s) end def install_serially diff --git a/spec/bundler/installer/parallel_installer_spec.rb b/spec/bundler/installer/parallel_installer_spec.rb index a2ec1429390d..6a91f05bf8b4 100644 --- a/spec/bundler/installer/parallel_installer_spec.rb +++ b/spec/bundler/installer/parallel_installer_spec.rb @@ -219,4 +219,29 @@ def redefine_build_jobs Bundler::RubyGemsGemInstaller.define_method(:build_jobs, old_method) end end + + describe "make jobserver with nmake" do + # nmake reads MAKEFLAGS from the environment and treats its contents as + # bare option letters, so a GNU make `--jobserver-auth` aborts the build + # with `fatal error U1065: invalid option '-'`. The jobserver must be + # skipped when nmake is the make program. + it "leaves MAKEFLAGS untouched" do + parallel_installer = Bundler::ParallelInstaller.new(nil, [], 5, false, false) + + makeflags_before = ENV["MAKEFLAGS"] + makeflags_during = :not_yielded + + old_make = ENV["MAKE"] + ENV["MAKE"] = "nmake" + begin + parallel_installer.send(:with_jobserver) do + makeflags_during = ENV["MAKEFLAGS"] + end + ensure + ENV["MAKE"] = old_make + end + + expect(makeflags_during).to eq(makeflags_before) + end + end end