Class: Gem::Precompiler

Inherits:
Object
  • Object
show all
Includes:
FileUtils
Defined in:
lib/rubygems/precompiler.rb

Instance Method Summary collapse

Constructor Details

#initialize(gemfile, opts = {}) ⇒ Precompiler

Returns a new instance of Precompiler.



13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# File 'lib/rubygems/precompiler.rb', line 13

def initialize(gemfile, opts = {})
  @installer = Gem::Installer.new(gemfile, opts.dup.merge(:unpack => true, :build_args => opts.fetch(:build_config,[])))
  @spec = @installer.spec

  @target_dir = opts.fetch(:output, Dir.pwd)
  @target_dir = File.join(@target_dir, arch_string) if opts.fetch(:arch, false)
  @debug = opts.fetch(:debug, false)
  @options = opts

  # This writes out a build_info file that the extension builder will process to set the
  # build configuration. We use the write_build_info_file method which should work on ruby 2+
  # on ruby  < 1.9.3 we don't support setting of build options. 1.9.3 is EOL.
  # However most simple gems that do not require build time config will still work.
  return if opts.fetch(:build_config,[]).empty?

  if Gem::Installer.method_defined?(:write_build_info_file)
    @installer.write_build_info_file
  else
    puts("Older version of rubygems, rubygems-precompiled does not support build options on this rubygems release (pull req welcome)")
    puts("Try again without build configuration")
    exit(1)
  end
end

Instance Method Details

#arch_stringObject

Private: Return a string that uniquely keys this machines ruby version and architecture

Returns string



88
89
90
# File 'lib/rubygems/precompiler.rb', line 88

def arch_string
  "ruby-#{RUBY_VERSION}-p#{RUBY_PATCHLEVEL}/#{Gem::Platform.local.to_s}"
end

#build_extensions(install_root) ⇒ Object

 Private: Calls the code necessary to build all the extensions into a specified install root

Returns a list of files beneath that root making up the build products of the extensions



105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
# File 'lib/rubygems/precompiler.rb', line 105

def build_extensions(install_root)
  if @spec.respond_to?(:extension_dir=)
    tempdir do |workroot|
      extract_files_into(workroot)

      # override the full_gem_path function so we can return
      # the directory we want. Otherwise by default the build process will
      # look for the gem installed in the usual place won't find it and will then
      # bail
      class <<@spec
        attr_accessor :workroot
        def full_gem_path
          return workroot
        end
      end
      @spec.workroot = workroot

      @spec.extension_dir = install_root
      @spec.installed_by_version = Gem::VERSION
      @spec.build_extensions
      Dir.glob(File.join(install_root, "**", "*"))
    end
  else
    extract_files_into(install_root)
    @installer.build_extensions

    dlext = RbConfig::CONFIG["DLEXT"]
    lib_dirs = gem_require_paths.join(',')
    Dir.glob("#{install_root}/{#{lib_dirs}}/**/*.#{dlext}")
  end
end

#compileObject

Public: Compile

This compiles into a temporary file, then moves into place. Otherwise we potentially confuse the gem installer with partial files!



143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
# File 'lib/rubygems/precompiler.rb', line 143

def compile
  temp_output = Tempfile.new('partial-output')
  tempdir do |path|

    targz_file(temp_output) do |tar_writer|

      build_extensions(path).each do |product_path|
        next if File.directory?(product_path)
        product_path = Pathname.new(product_path)
        relative_path = product_path.relative_path_from(Pathname.new(path))

        stat = File.stat(product_path)
        mode = stat.mode
        size = stat.size

        File.open(product_path, "r") do |source|

          tar_writer.add_file_simple(relative_path.to_s, mode, size) do |dest|
            dest.write source.read(1024*1024) until source.eof?
          end
        end

      end

    end
  end

  FileUtils.mkdir_p(@target_dir)
  FileUtils.mv(temp_output.path, output_path)
end

#extract_files_into(dir) ⇒ Object

 Private: Extracts the gem files into the specified path



39
40
41
# File 'lib/rubygems/precompiler.rb', line 39

def extract_files_into(dir)
  @installer.unpack(dir)
end

#gem_nameObject

 Public: Returns the name of the gem

Returns a string



46
47
48
# File 'lib/rubygems/precompiler.rb', line 46

def gem_name
  @spec.name
end

#gem_require_pathsObject

 Public: Returns the relative require-paths specified by the gem

Returns an array of strings



60
61
62
# File 'lib/rubygems/precompiler.rb', line 60

def gem_require_paths
  @spec.require_paths
end

#gem_versionObject

 Public: Returns the version string of the gem

 Returns a Gem::Version



53
54
55
# File 'lib/rubygems/precompiler.rb', line 53

def gem_version
  @spec.version
end

#has_extension?Boolean

 Public: Does the gem actually have any compiled extensions?

Returns boolean - true if the gem has a c-extension that needs building

Returns:

  • (Boolean)


67
68
69
# File 'lib/rubygems/precompiler.rb', line 67

def has_extension?
  !@spec.extensions.empty?
end

#output_pathObject

Public: The filename of the compiled bundle for this gem

Returns a string



95
96
97
# File 'lib/rubygems/precompiler.rb', line 95

def output_path
  File.join(*[@target_dir, "#{gem_name}-#{gem_version.to_s}.tar.gz"].compact)
end

#targz_file(path, &block) ⇒ Object

 Private: Yield a reference to a TarWriter that writes to the specified .tar.gz file



177
178
179
180
181
# File 'lib/rubygems/precompiler.rb', line 177

def targz_file(path, &block)
  Zlib::GzipWriter.open(path) do |tar_file_io|
    Gem::Package::TarWriter.new(tar_file_io, &block)
  end
end

#tempdirObject

Private: Yield the path to a temporary directory that will get deleted when the block returns, unless debug option was used on the cli



74
75
76
77
78
79
80
81
82
83
# File 'lib/rubygems/precompiler.rb', line 74

def tempdir
  temp_dir = Dir.mktmpdir
  yield temp_dir
ensure
  if @debug
    puts("\nLeaving #{temp_dir} in place")
  else
    rm_rf temp_dir
  end
end