Class: BundlerSourcePathext::ExtConfAlwaysCopyBuilder

Inherits:
Gem::Ext::Builder
  • Object
show all
Defined in:
lib/bundler-source-pathext.rb

Overview

Modified version of Gem::Ext::ExtConfBuilder that always copies the built binary to the destination path. This is necessary because when using local gems we cannot rely on versions being bumped when the gem changes.

Class Method Summary collapse

Class Method Details

.build(extension, dest_path, results, args = [], lib_dir = nil, extension_dir = Dir.pwd, target_rbconfig = nil, n_jobs: nil) ⇒ Object



169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
# File 'lib/bundler-source-pathext.rb', line 169

def self.build(extension, dest_path, results, args = [], lib_dir = nil, extension_dir = Dir.pwd,
  target_rbconfig = nil, n_jobs: nil)
  require "fileutils"
  require "tempfile"

  target_rbconfig ||= Gem.target_rbconfig if HAS_TARGET_RBCONFIG

  tmp_dest = Dir.mktmpdir(".gem.", extension_dir)

  # Some versions of `mktmpdir` return absolute paths, which will break make
  # if the paths contain spaces.
  #
  # As such, we convert to a relative path.
  tmp_dest_relative = get_relative_path(tmp_dest.clone, extension_dir)

  destdir = ENV["DESTDIR"]

  begin
    cmd = ruby << File.basename(extension)
    cmd << "--target-rbconfig=#{target_rbconfig.path}" if HAS_TARGET_RBCONFIG && target_rbconfig.path
    cmd.push(*args)

    run(cmd, results, 'ExtConfAlwaysCopy', extension_dir) do |s, r|
      mkmf_log = File.join(extension_dir, "mkmf.log")
      if File.exist? mkmf_log
        unless s.success?
          r << "To see why this extension failed to compile, please check" \
            " the mkmf.log which can be found here:\n"
          r << "  " + File.join(dest_path, "mkmf.log") + "\n"
        end
        FileUtils.mv mkmf_log, dest_path
      end
    end

    ENV["DESTDIR"] = nil

    if HAS_TARGET_RBCONFIG && HAS_NJOBS
      make dest_path, results, extension_dir, tmp_dest_relative, target_rbconfig: target_rbconfig, n_jobs: n_jobs
    elsif HAS_TARGET_RBCONFIG
      make dest_path, results, extension_dir, tmp_dest_relative, target_rbconfig: target_rbconfig
    else
      make dest_path, results, extension_dir, tmp_dest_relative
    end

    full_tmp_dest = File.join(extension_dir, tmp_dest_relative)

    is_cross_compiling = HAS_TARGET_RBCONFIG && target_rbconfig["platform"] != RbConfig::CONFIG["platform"]

    entries = (Dir.entries(full_tmp_dest) - %w[. ..]).map {|entry| File.join full_tmp_dest, entry }

    # MODIFIED
    # Rubygems copies the built files into the gem's lib directory, and then
    # additionally moves them into the extension install directory, but only
    # the ones that don't exist there yet. We install into a single location,
    # always overwriting, so a rebuilt extension leaves no stale binary behind.
    #
    # Note that install_extension_in_lib can be turned off through gemrc, and
    # that Rubygems intends to eventually default it to false, which would
    # make the extension directory the regular case rather than a fallback.
    #
    # Rubygems skips the copy into lib when cross-compiling, because that
    # directory is shared with the build for the host platform, and relies on
    # the extension install directory being specific to the target (it follows
    # Gem::Platform.local, which is derived from the target rbconfig). The
    # directory we use is keyed to the platform of the running Ruby, so we
    # have nowhere to put a cross-compiled build, and skip it entirely.
    unless is_cross_compiling
      if Gem.install_extension_in_lib && lib_dir
        FileUtils.mkdir_p lib_dir
        FileUtils.cp_r entries, lib_dir, remove_destination: true
      else
        FileUtils.cp_r entries, dest_path, remove_destination: true
      end
    end

    if HAS_TARGET_RBCONFIG
      make dest_path, results, extension_dir, tmp_dest_relative, ["clean"], target_rbconfig: target_rbconfig
    else
      make dest_path, results, extension_dir, tmp_dest_relative, ["clean"]
    end
  ensure
    ENV["DESTDIR"] = destdir
  end

  results
rescue => error
  if defined?(Gem::Ext::Builder::NoMakefileError) && error.is_a?(Gem::Ext::Builder::NoMakefileError)
    results << error.message
    results << "Skipping make for #{extension} as no Makefile was found."
    # We are good, do not re-raise the error.
  else
    raise
  end
ensure
  FileUtils.rm_rf tmp_dest if tmp_dest
end

.get_relative_path(path, base) ⇒ Object



266
267
268
269
# File 'lib/bundler-source-pathext.rb', line 266

def self.get_relative_path(path, base)
  path[0..base.length - 1] = "." if path.start_with?(base)
  path
end