
For casks with certain stanzas, *flight and language stanzas in this case, we need to use the caskfile to install them correctly. The JSON API is not an option. This delays loading from the source API until just before we try to install one of these casks. This reduces the number of requests we make to the source API.
384 lines
9.6 KiB
Ruby
384 lines
9.6 KiB
Ruby
# typed: false
|
|
# frozen_string_literal: true
|
|
|
|
require "locale"
|
|
require "lazy_object"
|
|
require "livecheck"
|
|
|
|
require "cask/artifact"
|
|
require "cask/artifact_set"
|
|
|
|
require "cask/caskroom"
|
|
require "cask/exceptions"
|
|
|
|
require "cask/dsl/appcast"
|
|
require "cask/dsl/base"
|
|
require "cask/dsl/caveats"
|
|
require "cask/dsl/conflicts_with"
|
|
require "cask/dsl/container"
|
|
require "cask/dsl/depends_on"
|
|
require "cask/dsl/postflight"
|
|
require "cask/dsl/preflight"
|
|
require "cask/dsl/uninstall_postflight"
|
|
require "cask/dsl/uninstall_preflight"
|
|
require "cask/dsl/version"
|
|
|
|
require "cask/url"
|
|
require "cask/utils"
|
|
|
|
require "extend/on_system"
|
|
|
|
module Cask
|
|
# Class representing the domain-specific language used for casks.
|
|
#
|
|
# @api private
|
|
class DSL
|
|
ORDINARY_ARTIFACT_CLASSES = [
|
|
Artifact::Installer,
|
|
Artifact::App,
|
|
Artifact::Artifact,
|
|
Artifact::AudioUnitPlugin,
|
|
Artifact::Binary,
|
|
Artifact::Colorpicker,
|
|
Artifact::Dictionary,
|
|
Artifact::Font,
|
|
Artifact::InputMethod,
|
|
Artifact::InternetPlugin,
|
|
Artifact::Manpage,
|
|
Artifact::Pkg,
|
|
Artifact::Prefpane,
|
|
Artifact::Qlplugin,
|
|
Artifact::Mdimporter,
|
|
Artifact::ScreenSaver,
|
|
Artifact::Service,
|
|
Artifact::StageOnly,
|
|
Artifact::Suite,
|
|
Artifact::VstPlugin,
|
|
Artifact::Vst3Plugin,
|
|
Artifact::Uninstall,
|
|
Artifact::Zap,
|
|
].freeze
|
|
|
|
ACTIVATABLE_ARTIFACT_CLASSES = (ORDINARY_ARTIFACT_CLASSES - [Artifact::StageOnly]).freeze
|
|
|
|
ARTIFACT_BLOCK_CLASSES = [
|
|
Artifact::PreflightBlock,
|
|
Artifact::PostflightBlock,
|
|
].freeze
|
|
|
|
DSL_METHODS = Set.new([
|
|
:appcast,
|
|
:artifacts,
|
|
:auto_updates,
|
|
:caveats,
|
|
:conflicts_with,
|
|
:container,
|
|
:desc,
|
|
:depends_on,
|
|
:homepage,
|
|
:language,
|
|
:name,
|
|
:sha256,
|
|
:staged_path,
|
|
:url,
|
|
:version,
|
|
:appdir,
|
|
:discontinued?,
|
|
:livecheck,
|
|
:livecheckable?,
|
|
:on_system_blocks_exist?,
|
|
*ORDINARY_ARTIFACT_CLASSES.map(&:dsl_key),
|
|
*ACTIVATABLE_ARTIFACT_CLASSES.map(&:dsl_key),
|
|
*ARTIFACT_BLOCK_CLASSES.flat_map { |klass| [klass.dsl_key, klass.uninstall_dsl_key] },
|
|
]).freeze
|
|
|
|
extend Predicable
|
|
include OnSystem::MacOSOnly
|
|
|
|
attr_reader :cask, :token
|
|
|
|
attr_predicate :on_system_blocks_exist?
|
|
|
|
def initialize(cask)
|
|
@cask = cask
|
|
@token = cask.token
|
|
end
|
|
|
|
# @api public
|
|
def name(*args)
|
|
@name ||= []
|
|
return @name if args.empty?
|
|
|
|
@name.concat(args.flatten)
|
|
end
|
|
|
|
# @api public
|
|
def desc(description = nil)
|
|
set_unique_stanza(:desc, description.nil?) { description }
|
|
end
|
|
|
|
def set_unique_stanza(stanza, should_return)
|
|
return instance_variable_get("@#{stanza}") if should_return
|
|
|
|
unless @cask.allow_reassignment
|
|
if instance_variable_defined?("@#{stanza}") && !@called_in_on_system_block
|
|
raise CaskInvalidError.new(cask, "'#{stanza}' stanza may only appear once.")
|
|
end
|
|
|
|
if instance_variable_defined?("@#{stanza}_set_in_block") && @called_in_on_system_block
|
|
raise CaskInvalidError.new(cask, "'#{stanza}' stanza may only be overridden once.")
|
|
end
|
|
end
|
|
|
|
instance_variable_set("@#{stanza}_set_in_block", true) if @called_in_on_system_block
|
|
instance_variable_set("@#{stanza}", yield)
|
|
rescue CaskInvalidError
|
|
raise
|
|
rescue => e
|
|
raise CaskInvalidError.new(cask, "'#{stanza}' stanza failed with: #{e}")
|
|
end
|
|
|
|
# @api public
|
|
def homepage(homepage = nil)
|
|
set_unique_stanza(:homepage, homepage.nil?) { homepage }
|
|
end
|
|
|
|
def language(*args, default: false, &block)
|
|
if args.empty?
|
|
language_eval
|
|
elsif block
|
|
@language_blocks ||= {}
|
|
@language_blocks[args] = block
|
|
|
|
return unless default
|
|
|
|
if !@cask.allow_reassignment && @language_blocks.default.present?
|
|
raise CaskInvalidError.new(cask, "Only one default language may be defined.")
|
|
end
|
|
|
|
@language_blocks.default = block
|
|
else
|
|
raise CaskInvalidError.new(cask, "No block given to language stanza.")
|
|
end
|
|
end
|
|
|
|
def language_eval
|
|
return @language_eval if defined?(@language_eval)
|
|
|
|
return @language_eval = nil if @language_blocks.blank?
|
|
|
|
raise CaskInvalidError.new(cask, "No default language specified.") if @language_blocks.default.nil?
|
|
|
|
locales = cask.config.languages
|
|
.map do |language|
|
|
Locale.parse(language)
|
|
rescue Locale::ParserError
|
|
nil
|
|
end
|
|
.compact
|
|
|
|
locales.each do |locale|
|
|
key = locale.detect(@language_blocks.keys)
|
|
|
|
next if key.nil?
|
|
|
|
return @language_eval = @language_blocks[key].call
|
|
end
|
|
|
|
@language_eval = @language_blocks.default.call
|
|
end
|
|
|
|
def languages
|
|
return [] if @language_blocks.nil?
|
|
|
|
@language_blocks.keys.flatten
|
|
end
|
|
|
|
# @api public
|
|
def url(*args, **options, &block)
|
|
caller_location = caller_locations[0]
|
|
|
|
set_unique_stanza(:url, args.empty? && options.empty? && !block) do
|
|
if block
|
|
URL.new(*args, **options, caller_location: caller_location, dsl: self, &block)
|
|
else
|
|
URL.new(*args, **options, caller_location: caller_location)
|
|
end
|
|
end
|
|
end
|
|
|
|
# @api public
|
|
def appcast(*args, **kwargs)
|
|
set_unique_stanza(:appcast, args.empty? && kwargs.empty?) { DSL::Appcast.new(*args, **kwargs) }
|
|
end
|
|
|
|
# @api public
|
|
def container(**kwargs)
|
|
set_unique_stanza(:container, kwargs.empty?) do
|
|
DSL::Container.new(**kwargs)
|
|
end
|
|
end
|
|
|
|
# @api public
|
|
def version(arg = nil)
|
|
set_unique_stanza(:version, arg.nil?) do
|
|
if !arg.is_a?(String) && arg != :latest
|
|
raise CaskInvalidError.new(cask, "invalid 'version' value: #{arg.inspect}")
|
|
end
|
|
|
|
DSL::Version.new(arg)
|
|
end
|
|
end
|
|
|
|
# @api public
|
|
def sha256(arg = nil, arm: nil, intel: nil)
|
|
should_return = arg.nil? && arm.nil? && intel.nil?
|
|
|
|
set_unique_stanza(:sha256, should_return) do
|
|
@on_system_blocks_exist = true if arm.present? || intel.present?
|
|
|
|
arg ||= on_arch_conditional(arm: arm, intel: intel)
|
|
case arg
|
|
when :no_check
|
|
arg
|
|
when String
|
|
Checksum.new(arg)
|
|
else
|
|
raise CaskInvalidError.new(cask, "invalid 'sha256' value: #{arg.inspect}")
|
|
end
|
|
end
|
|
end
|
|
|
|
# @api public
|
|
def arch(arm: nil, intel: nil)
|
|
should_return = arm.nil? && intel.nil?
|
|
|
|
set_unique_stanza(:arch, should_return) do
|
|
@on_system_blocks_exist = true
|
|
|
|
on_arch_conditional(arm: arm, intel: intel)
|
|
end
|
|
end
|
|
|
|
# `depends_on` uses a load method so that multiple stanzas can be merged.
|
|
# @api public
|
|
def depends_on(**kwargs)
|
|
@depends_on ||= DSL::DependsOn.new
|
|
return @depends_on if kwargs.empty?
|
|
|
|
begin
|
|
@depends_on.load(**kwargs)
|
|
rescue RuntimeError => e
|
|
raise CaskInvalidError.new(cask, e)
|
|
end
|
|
@depends_on
|
|
end
|
|
|
|
# @api public
|
|
def conflicts_with(**kwargs)
|
|
# TODO: remove this constraint, and instead merge multiple conflicts_with stanzas
|
|
set_unique_stanza(:conflicts_with, kwargs.empty?) { DSL::ConflictsWith.new(**kwargs) }
|
|
end
|
|
|
|
def artifacts
|
|
@artifacts ||= ArtifactSet.new
|
|
end
|
|
|
|
def caskroom_path
|
|
cask.caskroom_path
|
|
end
|
|
|
|
# @api public
|
|
def staged_path
|
|
return @staged_path if @staged_path
|
|
|
|
cask_version = version || :unknown
|
|
@staged_path = caskroom_path.join(cask_version.to_s)
|
|
end
|
|
|
|
# @api public
|
|
def caveats(*strings, &block)
|
|
@caveats ||= DSL::Caveats.new(cask)
|
|
if block
|
|
@caveats.eval_caveats(&block)
|
|
elsif strings.any?
|
|
strings.each do |string|
|
|
@caveats.eval_caveats { string }
|
|
end
|
|
else
|
|
return @caveats.to_s
|
|
end
|
|
@caveats
|
|
end
|
|
|
|
def discontinued?
|
|
@caveats&.discontinued? == true
|
|
end
|
|
|
|
# @api public
|
|
def auto_updates(auto_updates = nil)
|
|
set_unique_stanza(:auto_updates, auto_updates.nil?) { auto_updates }
|
|
end
|
|
|
|
# @api public
|
|
def livecheck(&block)
|
|
@livecheck ||= Livecheck.new(self)
|
|
return @livecheck unless block
|
|
|
|
if !@cask.allow_reassignment && @livecheckable
|
|
raise CaskInvalidError.new(cask, "'livecheck' stanza may only appear once.")
|
|
end
|
|
|
|
@livecheckable = true
|
|
@livecheck.instance_eval(&block)
|
|
end
|
|
|
|
def livecheckable?
|
|
@livecheckable == true
|
|
end
|
|
|
|
ORDINARY_ARTIFACT_CLASSES.each do |klass|
|
|
define_method(klass.dsl_key) do |*args, **kwargs|
|
|
if [*artifacts.map(&:class), klass].include?(Artifact::StageOnly) &&
|
|
(artifacts.map(&:class) & ACTIVATABLE_ARTIFACT_CLASSES).any?
|
|
raise CaskInvalidError.new(cask, "'stage_only' must be the only activatable artifact.")
|
|
end
|
|
|
|
artifacts.add(klass.from_args(cask, *args, **kwargs))
|
|
rescue CaskInvalidError
|
|
raise
|
|
rescue => e
|
|
raise CaskInvalidError.new(cask, "invalid '#{klass.dsl_key}' stanza: #{e}")
|
|
end
|
|
end
|
|
|
|
ARTIFACT_BLOCK_CLASSES.each do |klass|
|
|
[klass.dsl_key, klass.uninstall_dsl_key].each do |dsl_key|
|
|
define_method(dsl_key) do |&block|
|
|
artifacts.add(klass.new(cask, dsl_key => block))
|
|
end
|
|
end
|
|
end
|
|
|
|
def method_missing(method, *)
|
|
if method
|
|
Utils.method_missing_message(method, token)
|
|
nil
|
|
else
|
|
super
|
|
end
|
|
end
|
|
|
|
def respond_to_missing?(*)
|
|
true
|
|
end
|
|
|
|
# @api public
|
|
def appdir
|
|
return Cask::APPDIR_PLACEHOLDER if Cask.generating_hash?
|
|
|
|
cask.config.appdir
|
|
end
|
|
end
|
|
end
|