brew/Library/Homebrew/resource.rb

280 lines
6.5 KiB
Ruby
Raw Normal View History

2020-10-10 14:16:11 +02:00
# typed: false
# frozen_string_literal: true
require "download_strategy"
require "checksum"
require "version"
require "mktemp"
2013-09-17 21:25:38 -05:00
# Resource is the fundamental representation of an external resource. The
# primary formula download, along with other declared resources, are instances
# of this class.
2020-08-19 06:58:36 +02:00
#
# @api private
class Resource
include Context
include FileUtils
2018-01-21 08:29:38 -08:00
attr_reader :mirrors, :specs, :using, :source_modified_time, :patches, :owner
2015-01-12 00:37:24 -05:00
attr_writer :version
attr_accessor :download_strategy, :checksum
2013-09-17 21:25:38 -05:00
# Formula name must be set after the DSL, as we have no access to the
# formula name before initialization of the formula.
2018-01-21 08:29:38 -08:00
attr_accessor :name
def initialize(name = nil, &block)
@name = name
@url = nil
@version = nil
2013-09-17 21:25:38 -05:00
@mirrors = []
@specs = {}
@checksum = nil
@using = nil
2018-01-21 08:29:38 -08:00
@patches = []
2020-11-16 22:18:56 +01:00
instance_eval(&block) if block
end
2018-01-21 08:29:38 -08:00
def owner=(owner)
@owner = owner
patches.each { |p| p.owner = owner }
end
def downloader
@downloader ||= download_strategy.new(url, download_name, version,
mirrors: mirrors.dup, **specs)
end
# Removes /s from resource names; this allows Go package names
# to be used as resource names without confusing software that
# interacts with {download_name}, e.g. `github.com/foo/bar`.
def escaped_name
name.tr("/", "-")
end
2013-09-17 21:25:40 -05:00
def download_name
return owner.name if name.nil?
return escaped_name if owner.nil?
2018-09-17 02:45:00 +02:00
"#{owner.name}--#{escaped_name}"
2013-09-17 21:25:40 -05:00
end
def downloaded?
cached_download.exist?
end
2013-09-17 21:25:40 -05:00
def cached_download
downloader.cached_location
end
def clear_cache
downloader.clear_cache
end
# Verifies download and unpacks it.
2020-08-19 06:58:36 +02:00
# The block may call `|resource, staging| staging.retain!` to retain the staging
# directory. Subclasses that override stage should implement the tmp
# dir using {Mktemp} so that works with all subtypes.
2020-08-19 06:58:36 +02:00
#
# @api public
def stage(target = nil, &block)
raise ArgumentError, "target directory or block is required" if !target && block.blank?
2014-12-13 22:51:21 -05:00
prepare_patches
fetch_patches(skip_downloaded: true)
fetch unless downloaded?
unpack(target, &block)
end
def prepare_patches
2018-01-21 08:29:38 -08:00
patches.grep(DATAPatch) { |p| p.path = owner.owner.path }
end
def fetch_patches(skip_downloaded: false)
2020-05-14 09:20:58 +01:00
external_patches = patches.select(&:external?)
external_patches.reject!(&:downloaded?) if skip_downloaded
external_patches.each(&:fetch)
2018-01-21 08:29:38 -08:00
end
def apply_patches
return if patches.empty?
2018-09-17 02:45:00 +02:00
2018-01-21 08:29:38 -08:00
ohai "Patching #{name}"
patches.each(&:apply)
end
# If a target is given, unpack there; else unpack to a temp folder.
# If block is given, yield to that block with `|stage|`, where stage
# is a {ResourceStageContext}.
# A target or a block must be given, but not both.
def unpack(target = nil)
mktemp(download_name) do |staging|
downloader.stage
@source_modified_time = downloader.source_modified_time
2018-01-21 08:29:38 -08:00
apply_patches
if block_given?
yield ResourceStageContext.new(self, staging)
elsif target
2018-07-05 11:53:04 +02:00
target = Pathname(target)
target.install Pathname.pwd.children
end
end
end
Partial = Struct.new(:resource, :files)
def files(*files)
Partial.new(self, files)
end
def fetch(verify_download_integrity: true)
HOMEBREW_CACHE.mkpath
2014-10-10 20:30:29 -05:00
fetch_patches
2014-10-10 20:30:29 -05:00
begin
downloader.fetch
rescue ErrorDuringExecution, CurlDownloadStrategyError => e
raise DownloadError.new(self, e)
end
download = cached_download
verify_download_integrity(download) if verify_download_integrity
download
end
def verify_download_integrity(fn)
if fn.file?
ohai "Verifying #{fn.basename} checksum" if verbose?
fn.verify_checksum(checksum)
end
2013-09-17 21:25:38 -05:00
rescue ChecksumMissingError
opoo "Cannot verify integrity of #{fn.basename}"
2019-11-29 14:53:01 -05:00
puts "A checksum was not provided for this resource."
2019-04-08 12:47:15 -04:00
puts "For your reference the SHA-256 is: #{fn.sha256}"
2013-09-17 21:25:38 -05:00
end
Checksum::TYPES.each do |type|
define_method(type) { |val| @checksum = Checksum.new(type, val) }
2013-09-17 21:25:38 -05:00
end
def url(val = nil, **specs)
2013-09-17 21:25:38 -05:00
return @url if val.nil?
2018-09-17 02:45:00 +02:00
2013-09-17 21:25:38 -05:00
@url = val
@specs.merge!(specs)
@using = @specs.delete(:using)
2014-07-15 13:42:03 -05:00
@download_strategy = DownloadStrategyDetector.detect(url, using)
2013-09-17 21:25:38 -05:00
end
def version(val = nil)
@version ||= begin
version = detect_version(val)
version.null? ? nil : version
end
2013-09-17 21:25:38 -05:00
end
def mirror(val)
2013-09-17 21:25:38 -05:00
mirrors << val
end
2018-01-21 08:29:38 -08:00
def patch(strip = :p1, src = nil, &block)
p = Patch.create(strip, src, &block)
patches << p
end
2020-06-06 12:31:13 +02:00
# Block only executed on macOS. No-op on Linux.
# <pre>on_macos do
# url "mac_only_url"
# end</pre>
def on_macos(&_block); end
# Block only executed on Linux. No-op on macOS.
# <pre>on_linux do
# url "linux_only_url"
# end</pre>
def on_linux(&_block); end
protected
2020-08-19 17:12:32 +01:00
def mktemp(prefix, &block)
Mktemp.new(prefix).run(&block)
end
2013-09-17 21:25:38 -05:00
private
def detect_version(val)
return Version::NULL if val.nil? && url.nil?
2013-09-17 21:25:38 -05:00
case val
2020-08-09 02:59:18 +02:00
when nil then Version.detect(url, **specs)
2016-06-18 20:56:01 +03:00
when String then Version.create(val)
when Version then val
2013-09-17 21:25:38 -05:00
else
raise TypeError, "version '#{val.inspect}' should be a string"
end
end
2020-08-19 06:58:36 +02:00
# A resource containing a Go package.
class Go < Resource
def stage(target)
super(target/name)
end
end
2020-08-19 06:58:36 +02:00
# A resource containing a patch.
2018-01-21 08:29:38 -08:00
class PatchResource < Resource
attr_reader :patch_files
def initialize(&block)
@patch_files = []
@directory = nil
super "patch", &block
end
def apply(*paths)
paths.flatten!
@patch_files.concat(paths)
@patch_files.uniq!
end
def directory(val = nil)
return @directory if val.nil?
@directory = val
end
end
end
# The context in which a {Resource#stage} occurs. Supports access to both
# the {Resource} and associated {Mktemp} in a single block argument. The interface
# is back-compatible with {Resource} itself as used in that context.
2020-08-19 06:58:36 +02:00
#
# @api private
class ResourceStageContext
2020-10-20 12:03:48 +02:00
extend T::Sig
extend Forwardable
# The {Resource} that is being staged.
attr_reader :resource
# The {Mktemp} in which {#resource} is staged.
attr_reader :staging
def_delegators :@resource, :version, :url, :mirrors, :specs, :using, :source_modified_time
def_delegators :@staging, :retain!
def initialize(resource, staging)
@resource = resource
@staging = staging
end
2020-10-20 12:03:48 +02:00
sig { returns(String) }
def to_s
"<#{self.class}: resource=#{resource} staging=#{staging}>"
end
end
2020-06-06 12:31:13 +02:00
require "extend/os/resource"