require 'formula_support' require 'formula_lock' require 'formula_pin' require 'hardware' require 'bottles' require 'build_environment' require 'build_options' require 'formulary' require 'software_spec' require 'install_renamed' require 'pkg_version' # A formula provides instructions and metadata for Homebrew to install a piece # of software. Every Homebrew formula is a {Formula}. # All subclasses of {Formula} (and all Ruby classes) have to be named # `UpperCase` and `not-use-dashes`. # A formula specified in `this-formula.rb` should have a class named # `ThisFormula`. Homebrew does enforce that the name of the file and the class # correspond. # Make sure you check with `brew search` that the name is free! # @abstract class Formula include FileUtils include Utils::Inreplace extend Enumerable # The name of this {Formula}. # e.g. `this-formula` attr_reader :name # The full path to this {Formula}. # e.g. `/usr/local/Library/Formula/this-formula.rb` attr_reader :path # The stable (and default) {SoftwareSpec} for this {Formula} # This contains all the attributes (e.g. URL, checksum) that apply to the # stable version of this formula. attr_reader :stable # The development {SoftwareSpec} for this {Formula}. # Installed when using `brew install --devel` # `nil` if there is no development version. # @see #stable attr_reader :devel # The HEAD {SoftwareSpec} for this {Formula}. # Installed when using `brew install --HEAD` # This is always installed with the version `HEAD` and taken from the latest # commit in the version control system. # `nil` if there is no HEAD version. # @see #stable attr_reader :head # The currently active {SoftwareSpec}. # @see #determine_active_spec attr_reader :active_spec protected :active_spec # The {PkgVersion} for this formula with version and {#revision} information. attr_reader :pkg_version # Used for creating new Homebrew versions of software without new upstream # versions. # @see .revision attr_reader :revision # The current working directory during builds. # Will only be non-`nil` inside {#install}. attr_reader :buildpath # The current working directory during tests. # Will only be non-`nil` inside {#test}. attr_reader :testpath # When installing a bottle (binary package) from a local path this will be # set to the full path to the bottle tarball. If not, it will be `nil`. attr_accessor :local_bottle_path # The {BuildOptions} for this {Formula}. Lists the arguments passed and any # {#options} in the {Formula}. Note that these may differ at different times # during the installation of a {Formula}. This is annoying but the result of # state that we're trying to eliminate. attr_accessor :build # @private def initialize(name, path, spec) @name = name @path = path @revision = self.class.revision || 0 set_spec :stable set_spec :devel set_spec :head @active_spec = determine_active_spec(spec) validate_attributes! @pkg_version = PkgVersion.new(version, revision) @build = active_spec.build @pin = FormulaPin.new(self) end private def set_spec(name) spec = self.class.send(name) if spec.url spec.owner = self instance_variable_set("@#{name}", spec) end end def determine_active_spec(requested) spec = send(requested) || stable || devel || head spec or raise FormulaSpecificationError, "formulae require at least a URL" end def validate_attributes! if name.nil? || name.empty? || name =~ /\s/ raise FormulaValidationError.new(:name, name) end url = active_spec.url if url.nil? || url.empty? || url =~ /\s/ raise FormulaValidationError.new(:url, url) end val = version.respond_to?(:to_str) ? version.to_str : version if val.nil? || val.empty? || val =~ /\s/ raise FormulaValidationError.new(:version, val) end end public # Is the currently active {SoftwareSpec} a {#stable} build? def stable? active_spec == stable end # Is the currently active {SoftwareSpec} a {#devel} build? def devel? active_spec == devel end # Is the currently active {SoftwareSpec} a {#head} build? def head? active_spec == head end # @private def bottled? active_spec.bottled? end # @private def bottle_specification active_spec.bottle_specification end # The Bottle object for the currently active {SoftwareSpec}. # @private def bottle Bottle.new(self, bottle_specification) if bottled? end # The homepage for the software. # @see .homepage def homepage self.class.homepage end # The version for the currently active {SoftwareSpec}. # The version is autodetected from the URL and/or tag so only needs to be # declared if it cannot be autodetected correctly. # @see .version def version active_spec.version end # A named Resource for the currently active {SoftwareSpec}. def resource(name) active_spec.resource(name) end # The {Resource}s for the currently active {SoftwareSpec}. def resources active_spec.resources.values end # The {Dependency}s for the currently active {SoftwareSpec}. def deps active_spec.deps end # The {Requirement}s for the currently active {SoftwareSpec}. def requirements active_spec.requirements end # The cached download for the currently active {SoftwareSpec}. def cached_download active_spec.cached_download end # Deletes the download for the currently active {SoftwareSpec}. def clear_cache active_spec.clear_cache end # The list of patches for the currently active {SoftwareSpec}. def patchlist active_spec.patches end # The options for the currently active {SoftwareSpec}. def options active_spec.options end # The deprecated options for the currently active {SoftwareSpec}. def deprecated_options active_spec.deprecated_options end def deprecated_flags active_spec.deprecated_flags end # If a named option is defined for the currently active {SoftwareSpec}. def option_defined?(name) active_spec.option_defined?(name) end # All the {.fails_with} for the currently active {SoftwareSpec}. def compiler_failures active_spec.compiler_failures end # If this {Formula} is installed. # This is actually just a check for if the {#installed_prefix} directory # exists and is not empty. def installed? (dir = installed_prefix).directory? && dir.children.length > 0 end # @deprecated # The `LinkedKegs` directory for this {Formula}. # You probably want {#opt_prefix} instead. def linked_keg Pathname.new("#{HOMEBREW_LIBRARY}/LinkedKegs/#{name}") end # The latest prefix for this formula. Checks for {#head}, then {#devel} # and then {#stable}'s {#prefix} def installed_prefix if head && (head_prefix = prefix(head.version)).directory? head_prefix elsif devel && (devel_prefix = prefix(devel.version)).directory? devel_prefix else prefix end end def installed_version require 'keg' Keg.new(installed_prefix).version end # The directory in the cellar that the formula is installed to. # This directory contains the formula's name and version. def prefix(v=pkg_version) Pathname.new("#{HOMEBREW_CELLAR}/#{name}/#{v}") end # The parent of the prefix; the named directory in the cellar containing all # installed versions of this software def rack; prefix.parent end def bin; prefix+'bin' end def doc; share+'doc'+name end def include; prefix+'include' end def info; share+'info' end def lib; prefix+'lib' end def libexec; prefix+'libexec' end def man; share+'man' end def man1; man+'man1' end def man2; man+'man2' end def man3; man+'man3' end def man4; man+'man4' end def man5; man+'man5' end def man6; man+'man6' end def man7; man+'man7' end def man8; man+'man8' end def sbin; prefix+'sbin' end def share; prefix+'share' end def frameworks; prefix+'Frameworks' end def kext_prefix; prefix+'Library/Extensions' end # configuration needs to be preserved past upgrades def etc; (HOMEBREW_PREFIX+'etc').extend(InstallRenamed) end # generally we don't want var stuff inside the keg def var; HOMEBREW_PREFIX+'var' end def bash_completion; prefix+'etc/bash_completion.d' end def zsh_completion; share+'zsh/site-functions' end # for storing etc, var files for later copying from bottles def bottle_prefix; prefix+'.bottle' end # override this to provide a plist def plist; nil; end alias :startup_plist :plist # plist name, i.e. the name of the launchd service def plist_name; 'homebrew.mxcl.'+name end def plist_path; prefix+(plist_name+'.plist') end def plist_manual; self.class.plist_manual end def plist_startup; self.class.plist_startup end # A stable path for this formula, when installed. Contains the formula name # but no version number. Only the active version will be linked here if # multiple versions are installed. # # This is the prefered way to refer a formula in plists or from another # formula, as the path is stable even when the software is updated. def opt_prefix Pathname.new("#{HOMEBREW_PREFIX}/opt/#{name}") end def opt_bin; opt_prefix+'bin' end def opt_include; opt_prefix+'include' end def opt_lib; opt_prefix+'lib' end def opt_libexec; opt_prefix+'libexec' end def opt_sbin; opt_prefix+'sbin' end def opt_share; opt_prefix+'share' end # Can be overridden to selectively disable bottles from formulae. # Defaults to true so overridden version does not have to check if bottles # are supported. def pour_bottle?; true end # Can be overridden to run commands on both source and bottle installation. def post_install; end def post_install_defined? method(:post_install).owner == self.class end # @private def run_post_install build, self.build = self.build, Tab.for_formula(self) post_install ensure self.build = build end # tell the user about any caveats regarding this package, return a string def caveats; nil end # @deprecated DATA = :DATA # @deprecated def patches; {} end # rarely, you don't want your library symlinked into the main prefix # see gettext.rb for an example def keg_only? keg_only_reason && keg_only_reason.valid? end def keg_only_reason self.class.keg_only_reason end # sometimes the formula cleaner breaks things # skip cleaning paths in a formula with a class method like this: # skip_clean "bin/foo", "lib/bar" # keep .la files with: # skip_clean :la def skip_clean? path return true if path.extname == '.la' and self.class.skip_clean_paths.include? :la to_check = path.relative_path_from(prefix).to_s self.class.skip_clean_paths.include? to_check end def skip_cxxstdlib_check? false end def require_universal_deps? false end def patch unless patchlist.empty? ohai "Patching" patchlist.each(&:apply) end end # yields self with current working directory set to the uncompressed tarball # @private def brew stage do prepare_patches begin yield self ensure cp Dir["config.log", "CMakeCache.txt"], HOMEBREW_LOGS+name end end end def lock @lock = FormulaLock.new(name) @lock.lock end def unlock @lock.unlock unless @lock.nil? end def pinnable? @pin.pinnable? end def pinned? @pin.pinned? end def pin @pin.pin end def unpin @pin.unpin end def == other instance_of?(other.class) && name == other.name && active_spec == other.active_spec end alias_method :eql?, :== def hash name.hash end def <=>(other) return unless Formula === other name <=> other.name end def to_s name end def inspect s = "#<Formula #{name} (" s << if head? then "head" elsif devel? then "devel" else "stable" end s << ") #{path}>" end # Standard parameters for CMake builds. # Using Build Type "None" tells cmake to use our CFLAGS,etc. settings. # Setting it to Release would ignore our flags. # Setting CMAKE_FIND_FRAMEWORK to "LAST" tells CMake to search for our # libraries before trying to utilize Frameworks, many of which will be from # 3rd party installs. # Note: there isn't a std_autotools variant because autotools is a lot # less consistent and the standard parameters are more memorable. def std_cmake_args %W[ -DCMAKE_INSTALL_PREFIX=#{prefix} -DCMAKE_BUILD_TYPE=None -DCMAKE_FIND_FRAMEWORK=LAST -DCMAKE_VERBOSE_MAKEFILE=ON -Wno-dev ] end # @deprecated def python(options={}, &block) opoo 'Formula#python is deprecated and will go away shortly.' block.call if block_given? PythonDependency.new end alias_method :python2, :python alias_method :python3, :python # an array of all {Formula} names def self.names Dir["#{HOMEBREW_LIBRARY}/Formula/*.rb"].map{ |f| File.basename f, '.rb' }.sort end def self.each names.each do |name| begin yield Formulary.factory(name) rescue StandardError => e # Don't let one broken formula break commands. But do complain. onoe "Failed to import: #{name}" puts e next end end end # An array of all installed {Formula} def self.installed return [] unless HOMEBREW_CELLAR.directory? HOMEBREW_CELLAR.subdirs.map do |rack| begin Formulary.factory(rack.basename.to_s) rescue FormulaUnavailableError end end.compact end def self.aliases Dir["#{HOMEBREW_LIBRARY}/Aliases/*"].map{ |f| File.basename f }.sort end def self.[](name) Formulary.factory(name) end def tap? HOMEBREW_TAP_DIR_REGEX === path end def tap if path.to_s =~ HOMEBREW_TAP_DIR_REGEX "#$1/#$2" elsif core_formula? "Homebrew/homebrew" else HOMEBREW_PATH_URL_TAP end end def print_tap_action options={} if tap? verb = options[:verb] || "Installing" ohai "#{verb} #{name} from #{tap}" end end # True if this formula is provided by Homebrew itself def core_formula? path == Formula.path(name) end def self.path name Pathname.new("#{HOMEBREW_LIBRARY}/Formula/#{name.downcase}.rb") end def env self.class.env end def conflicts self.class.conflicts end # Returns a list of Dependency objects in an installable order, which # means if a depends on b then b will be ordered before a in this list def recursive_dependencies(&block) Dependency.expand(self, &block) end # The full set of Requirements for this formula's dependency tree. def recursive_requirements(&block) Requirement.expand(self, &block) end def to_hash hsh = { "name" => name, "homepage" => homepage, "versions" => { "stable" => (stable.version.to_s if stable), "bottle" => bottle ? true : false, "devel" => (devel.version.to_s if devel), "head" => (head.version.to_s if head) }, "revision" => revision, "installed" => [], "linked_keg" => (linked_keg.resolved_path.basename.to_s if linked_keg.exist?), "keg_only" => keg_only?, "dependencies" => deps.map(&:name).uniq, "conflicts_with" => conflicts.map(&:name), "caveats" => caveats } hsh["options"] = options.map { |opt| { "option" => opt.flag, "description" => opt.description } } if rack.directory? rack.subdirs.each do |keg_path| keg = Keg.new keg_path tab = Tab.for_keg keg_path hsh["installed"] << { "version" => keg.version.to_s, "used_options" => tab.used_options.as_flags, "built_as_bottle" => tab.built_bottle, "poured_from_bottle" => tab.poured_from_bottle } end hsh["installed"] = hsh["installed"].sort_by { |i| Version.new(i["version"]) } end hsh end def fetch active_spec.fetch end def verify_download_integrity fn active_spec.verify_download_integrity(fn) end def run_test old_home = ENV["HOME"] build, self.build = self.build, Tab.for_formula(self) mktemp do @testpath = Pathname.pwd ENV["HOME"] = @testpath test end ensure @testpath = nil self.build = build ENV["HOME"] = old_home end def test_defined? false end def test end def test_fixtures(file) HOMEBREW_LIBRARY.join("Homebrew", "test", "fixtures", file) end def install end protected # Pretty titles the command and buffers stdout/stderr # Throws if there's an error def system cmd, *args verbose = ARGV.verbose? # remove "boring" arguments so that the important ones are more likely to # be shown considering that we trim long ohai lines to the terminal width pretty_args = args.dup if cmd == "./configure" and not verbose pretty_args.delete "--disable-dependency-tracking" pretty_args.delete "--disable-debug" end pretty_args.each_index do |i| if pretty_args[i].to_s.start_with? "import setuptools" pretty_args[i] = "import setuptools..." end end ohai "#{cmd} #{pretty_args*' '}".strip @exec_count ||= 0 @exec_count += 1 logd = HOMEBREW_LOGS/name logfn = "#{logd}/%02d.%s" % [@exec_count, File.basename(cmd).split(' ').first] mkdir_p(logd) File.open(logfn, "w") do |log| log.puts Time.now, "", cmd, args, "" log.flush if verbose rd, wr = IO.pipe begin pid = fork do rd.close log.close exec_cmd(cmd, args, wr, logfn) end wr.close while buf = rd.gets log.puts buf puts buf end ensure rd.close end else pid = fork { exec_cmd(cmd, args, log, logfn) } end Process.wait(pid) $stdout.flush unless $?.success? log.flush Kernel.system "/usr/bin/tail", "-n", "5", logfn unless verbose log.puts require "cmd/config" require "cmd/--env" env = ENV.to_hash Homebrew.dump_verbose_config(log) log.puts Homebrew.dump_build_env(env, log) raise BuildError.new(self, cmd, args, env) end end end private def exec_cmd(cmd, args, out, logfn) ENV['HOMEBREW_CC_LOG_PATH'] = logfn # TODO system "xcodebuild" is deprecated, this should be removed soon. if cmd.to_s.start_with? "xcodebuild" ENV.remove_cc_etc end # Turn on argument filtering in the superenv compiler wrapper. # We should probably have a better mechanism for this than adding # special cases to this method. if cmd == "python" setup_py_in_args = %w[setup.py build.py].include?(args.first) setuptools_shim_in_args = args.any? { |a| a.to_s.start_with? "import setuptools" } if setup_py_in_args || setuptools_shim_in_args ENV.refurbish_args end end $stdout.reopen(out) $stderr.reopen(out) out.close args.collect!{|arg| arg.to_s} exec(cmd, *args) rescue nil puts "Failed to execute: #{cmd}" exit! 1 # never gets here unless exec threw or failed end def stage active_spec.stage do @buildpath = Pathname.pwd yield @buildpath = nil end end def prepare_patches active_spec.add_legacy_patches(patches) patchlist.grep(DATAPatch) { |p| p.path = path } patchlist.select(&:external?).each do |patch| patch.verify_download_integrity(patch.fetch) end end def self.method_added method case method when :brew raise "You cannot override Formula#brew in class #{name}" when :test define_method(:test_defined?) { true } when :options instance = allocate specs.each do |spec| instance.options.each do |opt, desc| spec.option(opt[/^--(.+)$/, 1], desc) end end remove_method(:options) end end # The methods below define the formula DSL. class << self include BuildEnvironmentDSL # The reason for why this software is not linked (by default) to # {::HOMEBREW_PREFIX}. # @private attr_reader :keg_only_reason # @!attribute [w] # The homepage for the software. Used by users to get more information # about the software and Homebrew maintainers as a point of contact for # e.g. submitting patches. # Can be opened with running `brew home`. attr_rw :homepage # The `:startup` attribute set by {.plist_options}. # @private attr_reader :plist_startup # The `:manual` attribute set by {.plist_options}. # @private attr_reader :plist_manual # @!attribute [w] # Used for creating new Homebrew versions of software without new upstream # versions. For example, if we bump the major version of a library this # {Formula} {.depends_on} then we may need to update the `revision` of this # {Formula} to install a new version linked against the new library version. # `0` if unset. attr_rw :revision # A list of the {.stable}, {.devel} and {.head} {SoftwareSpec}s. # @private def specs @specs ||= [stable, devel, head].freeze end # @!attribute [w] url # The URL used to download the source for the {#stable} version of the formula. # We prefer `https` for security and proxy reasons. def url val, specs={} stable.url(val, specs) end # @!attribute [w] version # The version string for the {#stable} version of the formula. # The version is autodetected from the URL and/or tag so only needs to be # declared if it cannot be autodetected correctly. def version val=nil stable.version(val) end # @!attribute [w] mirror # Additional URLs for the {#stable} version of the formula. # These are only used if the {.url} fails to download. It's optional and # there can be more than one. Generally we add them when the main {.url} # is unreliable. If {.url} is really unreliable then we may swap the # {.mirror} and {.url}. def mirror val stable.mirror(val) end # @!attribute [w] sha1 # @scope class # To verify the {#cached_download}'s integrity and security we verify the # SHA-1 hash matches what we've declared in the {Formula}. To quickly fill # this value you can leave it blank and run `brew fetch --force` and it'll # tell you the currently valid value. # @!attribute [w] sha256 # @scope class # Similar to {.sha1} but using a SHA-256 hash instead. Checksum::TYPES.each do |type| define_method(type) { |val| stable.send(type, val) } end def bottle *, &block stable.bottle(&block) end def build stable.build end def stable &block @stable ||= SoftwareSpec.new return @stable unless block_given? @stable.instance_eval(&block) end def devel &block @devel ||= SoftwareSpec.new return @devel unless block_given? @devel.instance_eval(&block) end def head val=nil, specs={}, &block @head ||= HeadSoftwareSpec.new if block_given? @head.instance_eval(&block) elsif val @head.url(val, specs) else @head end end # Define a named resource using a {SoftwareSpec} style block def resource name, klass=Resource, &block specs.each do |spec| spec.resource(name, klass, &block) unless spec.resource_defined?(name) end end def go_resource name, &block resource name, Resource::Go, &block end def depends_on dep specs.each { |spec| spec.depends_on(dep) } end def option name, description="" specs.each { |spec| spec.option(name, description) } end def deprecated_option hash specs.each { |spec| spec.deprecated_option(hash) } end def patch strip=:p1, src=nil, &block specs.each { |spec| spec.patch(strip, src, &block) } end def plist_options options @plist_startup = options[:startup] @plist_manual = options[:manual] end def conflicts @conflicts ||= [] end def conflicts_with *names opts = Hash === names.last ? names.pop : {} names.each { |name| conflicts << FormulaConflict.new(name, opts[:because]) } end def skip_clean *paths paths.flatten! # Specifying :all is deprecated and will become an error skip_clean_paths.merge(paths) end def skip_clean_paths @skip_clean_paths ||= Set.new end def keg_only reason, explanation="" @keg_only_reason = KegOnlyReason.new(reason, explanation) end # Pass :skip to this method to disable post-install stdlib checking def cxxstdlib_check check_type define_method(:skip_cxxstdlib_check?) { true } if check_type == :skip end # For Apple compilers, this should be in the format: # fails_with compiler do # cause "An explanation for why the build doesn't work." # build "The Apple build number for the newest incompatible release." # end # # The block may be omitted, and if present the build may be omitted; # if so, then the compiler will be blacklisted for *all* versions. # # For GNU GCC compilers, this should be in the format: # fails_with compiler => major_version do # cause # version "The official release number for the latest incompatible # version, for instance 4.8.1" # end # # `major_version` should be the major release number only, for instance # '4.8' for the GCC 4.8 series (4.8.0, 4.8.1, etc.). # If `version` or the block is omitted, then the compiler will be # blacklisted for all compilers in that series. # # For example, if a bug is only triggered on GCC 4.8.1 but is not # encountered on 4.8.2: # # fails_with :gcc => '4.8' do # version '4.8.1' # end def fails_with compiler, &block specs.each { |spec| spec.fails_with(compiler, &block) } end def needs *standards specs.each { |spec| spec.needs(*standards) } end def test &block define_method(:test, &block) end end end require 'formula_specialties'