class FPM::Package::Gem
A rubygems package.
This does not currently support 'output'
The following attributes are supported:
-
:gem_bin_path
-
:gem_package_name_prefix
-
:gem_gem
Constants
- P_RE_DATE
- P_RE_DATE1
- P_RE_DATE2
- P_RE_DATE3
- P_RE_DATE_VERSION
- P_RE_GTMAGIC
- P_RE_LEADIN
Regular expression to accept a gem changelog line, and store date & version, if any, in named capture groups. Supports formats suggested by keepachangelog.com and github.com/tech-angels/vandamme as well as other similar formats that actually occur in the wild. Build it in pieces for readability, and allow version and date in either order. Whenever you change this, add a row to the test case in spec/fpm/package/gem_spec.rb. Don't even try to handle dates that lack four-digit years. Building blocks:
- P_RE_SEPARATOR
- P_RE_URL
- P_RE_VERSION
- P_RE_VERSION_
- P_RE_VERSION_DATE
The final RE's:
Public Instance Methods
Detect release date, if found, store in attributes
# File lib/fpm/package/gem.rb, line 330 def detect_source_date_from_changelog(installdir) name = self.name.sub("rubygem-", "") + "-" + self.version changelog = nil datestr = nil r1 = Regexp.new(P_RE_VERSION_DATE) r2 = Regexp.new(P_RE_DATE_VERSION) # Changelog doesn't have a standard name, so check all common variations # Sort this list using LANG=C, i.e. caps first [ "CHANGELIST", "CHANGELOG", "CHANGELOG.asciidoc", "CHANGELOG.md", "CHANGELOG.rdoc", "CHANGELOG.rst", "CHANGELOG.txt", "CHANGES", "CHANGES.md", "CHANGES.txt", "ChangeLog", "ChangeLog.md", "ChangeLog.txt", "Changelog", "Changelog.md", "Changelog.txt", "changelog", "changelog.md", "changelog.txt", ].each do |changelogname| path = File.join(installdir, "gems", name, changelogname) if File.exist?(path) changelog = path File.open path do |file| file.each_line do |line| if line =~ /#{self.version}/ [r1, r2].each do |r| if r.match(line) datestr = $~[:date] break end end end end end end end if datestr date = Date.parse(datestr) sec = date.strftime("%s") attributes[:source_date_epoch] = sec logger.debug("Gem %s has changelog date %s, setting source_date_epoch to %s" % [name, datestr, sec]) elsif changelog logger.debug("Gem %s changelog %s did not have recognizable date for release %s" % [name, changelog, self.version]) else logger.debug("Gem %s did not have changelog with recognized name" % [name]) # FIXME: check rubygems.org? end end
# File lib/fpm/package/gem.rb, line 100 def download(gem_name, gem_version=nil) logger.info("Trying to download", :gem => gem_name, :version => gem_version) download_dir = build_path(gem_name) FileUtils.mkdir(download_dir) unless File.directory?(download_dir) if attributes[:gem_git_repo] logger.debug("Git cloning in directory #{download_dir}") g = Git.clone(attributes[:gem_git_repo],gem_name,:path => download_dir) if attributes[:gem_git_branch] g.branch(attributes[:gem_git_branch]).checkout g.pull('origin',attributes[:gem_git_branch]) end gem_build = [ "#{attributes[:gem_gem]}", "build", "#{g.dir.to_s}/#{gem_name}.gemspec"] ::Dir.chdir(g.dir.to_s) do |dir| logger.debug("Building in directory #{dir}") safesystem(*gem_build) end gem_files = ::Dir.glob(File.join(g.dir.to_s, "*.gem")) else gem_fetch = [ "#{attributes[:gem_gem]}", "fetch", gem_name] gem_fetch += ["--prerelease"] if attributes[:gem_prerelease?] gem_fetch += ["--version", gem_version] if gem_version ::Dir.chdir(download_dir) do |dir| logger.debug("Downloading in directory #{dir}") safesystem(*gem_fetch) end gem_files = ::Dir.glob(File.join(download_dir, "*.gem")) end if gem_files.length != 1 raise "Unexpected number of gem files in #{download_dir}, #{gem_files.length} should be 1" end return gem_files.first end
# File lib/fpm/package/gem.rb, line 90 def download_if_necessary(gem, gem_version) path = gem if !File.exist?(path) path = download(gem, gem_version) end logger.info("Using gem file", :path => path) return path end
Sanitize package name. This prefixes the package name with 'rubygem' (but depends on the attribute :gem_package_name_prefix
# File lib/fpm/package/gem.rb, line 304 def fix_name(name) return [attributes[:gem_package_name_prefix], name].join("-") end
# File lib/fpm/package/gem.rb, line 78 def input(gem) # 'arg' is the name of the rubygem we should unpack. path_to_gem = download_if_necessary(gem, version) # Got a good gem now (downloaded or otherwise) # # 1. unpack it into staging_path # 2. take the metadata from it and update our wonderful package with it. load_package_info(path_to_gem) install_to_staging(path_to_gem) end
# File lib/fpm/package/gem.rb, line 213 def install_to_staging(gem_path) if attributes.include?(:prefix) && ! attributes[:prefix].nil? installdir = "#{staging_path}/#{attributes[:prefix]}" else gemdir = safesystemout(*[attributes[:gem_gem], 'env', 'gemdir']).chomp installdir = File.join(staging_path, gemdir) end ::FileUtils.mkdir_p(installdir) # TODO(sissel): Allow setting gem tool path args = [attributes[:gem_gem], "install", "--quiet", "--no-user-install", "--install-dir", installdir] if ::Gem::VERSION =~ /^[012]\./ args += [ "--no-ri", "--no-rdoc" ] else # Rubygems 3.0.0 changed --no-ri to --no-document args += [ "--no-document" ] end if !attributes[:gem_embed_dependencies?] args += ["--ignore-dependencies"] end if attributes[:gem_env_shebang?] args += ["-E"] end if attributes.include?(:gem_bin_path) && ! attributes[:gem_bin_path].nil? bin_path = File.join(staging_path, attributes[:gem_bin_path]) else gem_env = safesystemout(*[attributes[:gem_gem], 'env']).split("\n") gem_bin = gem_env.select{ |line| line =~ /EXECUTABLE DIRECTORY/ }.first.split(': ').last bin_path = File.join(staging_path, gem_bin) end args += ["--bindir", bin_path] ::FileUtils.mkdir_p(bin_path) args << gem_path safesystem(*args) # Replace the shebangs in the executables if attributes[:gem_shebang] ::Dir.entries(bin_path).each do |file_name| # exclude . and .. next if ['.', '..'].include?(file_name) # exclude everything which is not a file file_path = File.join(bin_path, file_name) next unless File.ftype(file_path) == 'file' # replace shebang in files if there is one file = File.read(file_path) if file.gsub!(/\A#!.*$/, "#!#{attributes[:gem_shebang]}") File.open(file_path, 'w'){|f| f << file} end end end # Delete bin_path if it's empty, and any empty parents (#612) # Above, we mkdir_p bin_path because rubygems aborts if the parent # directory doesn't exist, for example: # ERROR: While executing gem ... (Errno::ENOENT) # No such file or directory - /tmp/something/weird/bin tmp = bin_path while ::Dir.entries(tmp).size == 2 || tmp == "/" # just [ "..", "." ] is an empty directory logger.info("Deleting empty bin_path", :path => tmp) ::Dir.rmdir(tmp) tmp = File.dirname(tmp) end if attributes[:gem_version_bins?] and File.directory?(bin_path) (::Dir.entries(bin_path) - ['.','..']).each do |bin| FileUtils.mv("#{bin_path}/#{bin}", "#{bin_path}/#{bin}-#{self.version}") end end if attributes[:source_date_epoch_from_changelog?] detect_source_date_from_changelog(installdir) end # Remove generated Makefile and gem_make.out files, if any; they # are not needed, and may contain generated paths that cause # different output on successive runs. Find.find(installdir) do |path| if path =~ /.*(gem_make.out|Makefile|mkmf.log)$/ logger.info("Removing no longer needed file %s to reduce nondeterminism" % path) File.unlink(path) end end end
# File lib/fpm/package/gem.rb, line 138 def load_package_info(gem_path) spec = YAML.load(%x{#{attributes[:gem_gem]} specification #{gem_path} --yaml}) if !attributes[:gem_package_prefix].nil? attributes[:gem_package_name_prefix] = attributes[:gem_package_prefix] end # name prefixing is optional, if enabled, a name 'foo' will become # 'rubygem-foo' (depending on what the gem_package_name_prefix is) self.name = spec.name if attributes[:gem_fix_name?] self.name = fix_name(spec.name) end #self.name = [attributes[:gem_package_name_prefix], spec.name].join("-") self.license = (spec.license or "no license listed in #{File.basename(gem_path)}") # expand spec's version to match RationalVersioningPolicy to prevent cases # where missing 'build' number prevents correct dependency resolution by target # package manager. Ie. for dpkg 1.1 != 1.1.0 m = spec.version.to_s.scan(/(\d+)\.?/) self.version = m.flatten.fill('0', m.length..2).join('.') self.vendor = spec.author self.url = spec.homepage self.category = "Languages/Development/Ruby" # if the gemspec has C extensions defined, then this should be a 'native' arch. if !spec.extensions.empty? self.architecture = "native" else self.architecture = "all" end # make sure we have a description description_options = [ spec.description, spec.summary, "#{spec.name} - no description given" ] self.description = description_options.find { |d| !(d.nil? or d.strip.empty?) } # Upstream rpms seem to do this, might as well share. # TODO(sissel): Figure out how to hint this only to rpm? # maybe something like attributes[:rpm_provides] for rpm specific stuff? # Or just ignore it all together. #self.provides << "rubygem(#{self.name})" # By default, we'll usually automatically provide this, but in the case that we are # composing multiple packages, it's best to explicitly include it in the provides list. self.provides << "#{self.name} = #{self.version}" if !attributes[:no_auto_depends?] && !attributes[:gem_embed_dependencies?] spec.runtime_dependencies.map do |dep| # rubygems 1.3.5 doesn't have 'Gem::Dependency#requirement' if dep.respond_to?(:requirement) reqs = dep.requirement.to_s else reqs = dep.version_requirements end # Some reqs can be ">= a, < b" versions, let's handle that. reqs.to_s.split(/, */).each do |req| if attributes[:gem_disable_dependencies] next if attributes[:gem_disable_dependencies].include?(dep.name) end if attributes[:gem_fix_dependencies?] name = fix_name(dep.name) else name = dep.name end self.dependencies << "#{name} #{req}" end end # runtime_dependencies end #no_auto_depends end
Override parent method
# File lib/fpm/package/gem.rb, line 67 def staging_path(path=nil) @gem_staging_path ||= attributes[:gem_stagingdir] || Stud::Temporary.directory("package-#{type}-staging") @staging_path = @gem_staging_path if path.nil? return @staging_path else return File.join(@staging_path, path) end end