diff options
| author | Mike McQuaid | 2016-09-05 21:37:02 +0100 |
|---|---|---|
| committer | Mike McQuaid | 2016-09-08 20:46:37 +0100 |
| commit | af8605ea4ba1d9856c055c8c76b447e030540e3f (patch) | |
| tree | d093b11340406c21a4b873a80effe3b068fd07d4 /Library/Homebrew/cmd | |
| parent | 4f6bae46f9c0f7b713cdbb999318460135f423de (diff) | |
| download | brew-af8605ea4ba1d9856c055c8c76b447e030540e3f.tar.bz2 | |
Move developer-focused commands to dev-cmd.
Diffstat (limited to 'Library/Homebrew/cmd')
| -rw-r--r-- | Library/Homebrew/cmd/audit.rb | 1361 | ||||
| -rw-r--r-- | Library/Homebrew/cmd/bottle.rb | 458 | ||||
| -rw-r--r-- | Library/Homebrew/cmd/create.rb | 218 | ||||
| -rw-r--r-- | Library/Homebrew/cmd/edit.rb | 50 | ||||
| -rw-r--r-- | Library/Homebrew/cmd/man.rb | 88 | ||||
| -rw-r--r-- | Library/Homebrew/cmd/pull.rb | 575 | ||||
| -rw-r--r-- | Library/Homebrew/cmd/tap-readme.rb | 36 | ||||
| -rw-r--r-- | Library/Homebrew/cmd/test.rb | 89 | ||||
| -rw-r--r-- | Library/Homebrew/cmd/tests.rb | 63 |
9 files changed, 0 insertions, 2938 deletions
diff --git a/Library/Homebrew/cmd/audit.rb b/Library/Homebrew/cmd/audit.rb deleted file mode 100644 index 6d1fa055f..000000000 --- a/Library/Homebrew/cmd/audit.rb +++ /dev/null @@ -1,1361 +0,0 @@ -#: * `audit` [`--strict`] [`--online`] [`--new-formula`] [`--display-cop-names`] [`--display-filename`] [<formulae>]: -#: Check <formulae> for Homebrew coding style violations. This should be -#: run before submitting a new formula. -#: -#: If no <formulae> are provided, all of them are checked. -#: -#: If `--strict` is passed, additional checks are run, including RuboCop -#: style checks. -#: -#: If `--online` is passed, additional slower checks that require a network -#: connection are run. -#: -#: If `--new-formula` is passed, various additional checks are run that check -#: if a new formula is eligable for Homebrew. This should be used when creating -#: new formulae and implies `--strict` and `--online`. -#: -#: If `--display-cop-names` is passed, the RuboCop cop name for each violation -#: is included in the output. -#: -#: If `--display-filename` is passed, every line of output is prefixed with the -#: name of the file or formula being audited, to make the output easy to grep. -#: -#: `audit` exits with a non-zero status if any errors are found. This is useful, -#: for instance, for implementing pre-commit hooks. - -# Undocumented options: -# -D activates debugging and profiling of the audit methods (not the same as --debug) - -require "formula" -require "formula_versions" -require "utils" -require "extend/ENV" -require "formula_cellar_checks" -require "official_taps" -require "cmd/search" -require "cmd/style" -require "date" - -module Homebrew - def audit - if ARGV.switch? "D" - Homebrew.inject_dump_stats!(FormulaAuditor, /^audit_/) - end - - formula_count = 0 - problem_count = 0 - - new_formula = ARGV.include? "--new-formula" - strict = new_formula || ARGV.include?("--strict") - online = new_formula || ARGV.include?("--online") - - ENV.activate_extensions! - ENV.setup_build_environment - - if ARGV.named.empty? - ff = Formula - files = Tap.map(&:formula_dir) - else - ff = ARGV.resolved_formulae - files = ARGV.resolved_formulae.map(&:path) - end - - if strict - # Check style in a single batch run up front for performance - style_results = check_style_json(files, :realpath => true) - end - - ff.each do |f| - options = { :new_formula => new_formula, :strict => strict, :online => online } - options[:style_offenses] = style_results.file_offenses(f.path) if strict - fa = FormulaAuditor.new(f, options) - fa.audit - - next if fa.problems.empty? - fa.problems - formula_count += 1 - problem_count += fa.problems.size - problem_lines = fa.problems.map { |p| "* #{p.chomp.gsub("\n", "\n ")}" } - if ARGV.include? "--display-filename" - puts problem_lines.map { |s| "#{f.path}: #{s}" } - else - puts "#{f.full_name}:", problem_lines.map { |s| " #{s}" } - end - end - - unless problem_count.zero? - problems = "problem" + plural(problem_count) - formulae = "formula" + plural(formula_count, "e") - ofail "#{problem_count} #{problems} in #{formula_count} #{formulae}" - end - end -end - -class FormulaText - def initialize(path) - @text = path.open("rb", &:read) - @lines = @text.lines.to_a - end - - def without_patch - @text.split("\n__END__").first - end - - def has_DATA? - /^[^#]*\bDATA\b/ =~ @text - end - - def has_END? - /^__END__$/ =~ @text - end - - def has_trailing_newline? - /\Z\n/ =~ @text - end - - def =~(regex) - regex =~ @text - end - - def include?(s) - @text.include? s - end - - def line_number(regex, skip = 0) - index = @lines.drop(skip).index { |line| line =~ regex } - index ? index + 1 : nil - end - - def reverse_line_number(regex) - index = @lines.reverse.index { |line| line =~ regex } - index ? @lines.count - index : nil - end -end - -class FormulaAuditor - include FormulaCellarChecks - - attr_reader :formula, :text, :problems - - BUILD_TIME_DEPS = %W[ - autoconf - automake - boost-build - bsdmake - cmake - godep - imake - intltool - libtool - pkg-config - scons - smake - sphinx-doc - swig - ] - - FILEUTILS_METHODS = FileUtils.singleton_methods(false).map { |m| Regexp.escape(m) }.join "|" - - def initialize(formula, options = {}) - @formula = formula - @new_formula = !!options[:new_formula] - @strict = !!options[:strict] - @online = !!options[:online] - # Accept precomputed style offense results, for efficiency - @style_offenses = options[:style_offenses] - @problems = [] - @text = FormulaText.new(formula.path) - @specs = %w[stable devel head].map { |s| formula.send(s) }.compact - end - - def audit_style - return unless @style_offenses - display_cop_names = ARGV.include?("--display-cop-names") - @style_offenses.each do |offense| - problem offense.to_s(:display_cop_name => display_cop_names) - end - end - - def component_problem(before, after, offset = 0) - problem "`#{before[1]}` (line #{before[0] + offset}) should be put before `#{after[1]}` (line #{after[0] + offset})" - end - - # scan in the reverse direction for remaining problems but report problems - # in the forward direction so that contributors don't reverse the order of - # lines in the file simply by following instructions - def audit_components(reverse = true, previous_pair = nil) - component_list = [ - [/^ include Language::/, "include directive"], - [/^ desc ["'][\S\ ]+["']/, "desc"], - [/^ homepage ["'][\S\ ]+["']/, "homepage"], - [/^ url ["'][\S\ ]+["']/, "url"], - [/^ mirror ["'][\S\ ]+["']/, "mirror"], - [/^ version ["'][\S\ ]+["']/, "version"], - [/^ (sha1|sha256) ["'][\S\ ]+["']/, "checksum"], - [/^ revision/, "revision"], - [/^ version_scheme/, "version_scheme"], - [/^ head ["'][\S\ ]+["']/, "head"], - [/^ stable do/, "stable block"], - [/^ bottle do/, "bottle block"], - [/^ devel do/, "devel block"], - [/^ head do/, "head block"], - [/^ bottle (:unneeded|:disable)/, "bottle modifier"], - [/^ keg_only/, "keg_only"], - [/^ option/, "option"], - [/^ depends_on/, "depends_on"], - [/^ conflicts_with/, "conflicts_with"], - [/^ (go_)?resource/, "resource"], - [/^ def install/, "install method"], - [/^ def caveats/, "caveats method"], - [/^ (plist_options|def plist)/, "plist block"], - [/^ test do/, "test block"], - ] - if previous_pair - previous_before = previous_pair[0] - previous_after = previous_pair[1] - end - offset = (previous_after && previous_after[0] && previous_after[0] >= 1) ? previous_after[0] - 1 : 0 - present = component_list.map do |regex, name| - lineno = if reverse - text.reverse_line_number regex - else - text.line_number regex, offset - end - next unless lineno - [lineno, name] - end.compact - no_problem = true - present.each_cons(2) do |c1, c2| - if reverse - # scan in the forward direction from the offset - audit_components(false, [c1, c2]) if c1[0] > c2[0] # at least one more offense - elsif c1[0] > c2[0] && (offset == 0 || previous_pair.nil? || (c1[0] + offset) != previous_before[0] || (c2[0] + offset) != previous_after[0]) - component_problem c1, c2, offset - no_problem = false - end - end - if no_problem && previous_pair - component_problem previous_before, previous_after - end - present - end - - def audit_file - # Under normal circumstances (umask 0022), we expect a file mode of 644. If - # the user's umask is more restrictive, respect that by masking out the - # corresponding bits. (The also included 0100000 flag means regular file.) - wanted_mode = 0100644 & ~File.umask - actual_mode = formula.path.stat.mode - unless actual_mode == wanted_mode - problem format("Incorrect file permissions (%03o): chmod %03o %s", - actual_mode & 0777, wanted_mode & 0777, formula.path) - end - - if text.has_DATA? && !text.has_END? - problem "'DATA' was found, but no '__END__'" - end - - if text.has_END? && !text.has_DATA? - problem "'__END__' was found, but 'DATA' is not used" - end - - if text =~ /inreplace [^\n]* do [^\n]*\n[^\n]*\.gsub![^\n]*\n\ *end/m - problem "'inreplace ... do' was used for a single substitution (use the non-block form instead)." - end - - unless text.has_trailing_newline? - problem "File should end with a newline" - end - - return unless @strict - - present = audit_components - - present.map!(&:last) - if present.include?("stable block") - %w[url checksum mirror].each do |component| - if present.include?(component) - problem "`#{component}` should be put inside `stable block`" - end - end - end - if present.include?("head") && present.include?("head block") - problem "Should not have both `head` and `head do`" - end - if present.include?("bottle modifier") && present.include?("bottle block") - problem "Should not have `bottle :unneeded/:disable` and `bottle do`" - end - end - - def audit_class - if @strict - unless formula.test_defined? - problem "A `test do` test block should be added" - end - end - - classes = %w[GithubGistFormula ScriptFileFormula AmazonWebServicesFormula] - klass = classes.find do |c| - Object.const_defined?(c) && formula.class < Object.const_get(c) - end - - problem "#{klass} is deprecated, use Formula instead" if klass - end - - # core aliases + tap alias names + tap alias full name - @@aliases ||= Formula.aliases + Formula.tap_aliases - - def audit_formula_name - return unless @strict - # skip for non-official taps - return if formula.tap.nil? || !formula.tap.official? - - name = formula.name - full_name = formula.full_name - - if Formula.aliases.include? name - problem "Formula name conflicts with existing aliases." - return - end - - if oldname = CoreTap.instance.formula_renames[name] - problem "'#{name}' is reserved as the old name of #{oldname}" - return - end - - if !formula.core_formula? && Formula.core_names.include?(name) - problem "Formula name conflicts with existing core formula." - return - end - - @@local_official_taps_name_map ||= Tap.select(&:official?).flat_map(&:formula_names). - reduce(Hash.new) do |name_map, tap_formula_full_name| - tap_formula_name = tap_formula_full_name.split("/").last - name_map[tap_formula_name] ||= [] - name_map[tap_formula_name] << tap_formula_full_name - name_map - end - - same_name_tap_formulae = @@local_official_taps_name_map[name] || [] - - if @online - @@remote_official_taps ||= OFFICIAL_TAPS - Tap.select(&:official?).map(&:repo) - - same_name_tap_formulae += @@remote_official_taps.map do |tap| - Thread.new { Homebrew.search_tap "homebrew", tap, name } - end.flat_map(&:value) - end - - same_name_tap_formulae.delete(full_name) - - unless same_name_tap_formulae.empty? - problem "Formula name conflicts with #{same_name_tap_formulae.join ", "}" - end - end - - def audit_deps - @specs.each do |spec| - # Check for things we don't like to depend on. - # We allow non-Homebrew installs whenever possible. - spec.deps.each do |dep| - begin - dep_f = dep.to_formula - rescue TapFormulaUnavailableError - # Don't complain about missing cross-tap dependencies - next - rescue FormulaUnavailableError - problem "Can't find dependency #{dep.name.inspect}." - next - rescue TapFormulaAmbiguityError - problem "Ambiguous dependency #{dep.name.inspect}." - next - rescue TapFormulaWithOldnameAmbiguityError - problem "Ambiguous oldname dependency #{dep.name.inspect}." - next - end - - if dep_f.oldname && dep.name.split("/").last == dep_f.oldname - problem "Dependency '#{dep.name}' was renamed; use new name '#{dep_f.name}'." - end - - if @@aliases.include?(dep.name) - problem "Dependency '#{dep.name}' is an alias; use the canonical name '#{dep.to_formula.full_name}'." - end - - dep.options.reject do |opt| - next true if dep_f.option_defined?(opt) - dep_f.requirements.detect do |r| - if r.recommended? - opt.name == "with-#{r.name}" - elsif r.optional? - opt.name == "without-#{r.name}" - end - end - end.each do |opt| - problem "Dependency #{dep} does not define option #{opt.name.inspect}" - end - - case dep.name - when *BUILD_TIME_DEPS - next if dep.build? || dep.run? - problem <<-EOS.undent - #{dep} dependency should be - depends_on "#{dep}" => :build - Or if it is indeed a runtime dependency - depends_on "#{dep}" => :run - EOS - when "git" - problem "Don't use git as a dependency" - when "mercurial" - problem "Use `depends_on :hg` instead of `depends_on 'mercurial'`" - when "gfortran" - problem "Use `depends_on :fortran` instead of `depends_on 'gfortran'`" - when "ruby" - problem <<-EOS.undent - Don't use "ruby" as a dependency. If this formula requires a - minimum Ruby version not provided by the system you should - use the RubyRequirement: - depends_on :ruby => "1.8" - where "1.8" is the minimum version of Ruby required. - EOS - when "open-mpi", "mpich" - problem <<-EOS.undent - There are multiple conflicting ways to install MPI. Use an MPIRequirement: - depends_on :mpi => [<lang list>] - Where <lang list> is a comma delimited list that can include: - :cc, :cxx, :f77, :f90 - EOS - end - end - end - end - - def audit_conflicts - formula.conflicts.each do |c| - begin - Formulary.factory(c.name) - rescue TapFormulaUnavailableError - # Don't complain about missing cross-tap conflicts. - next - rescue FormulaUnavailableError - problem "Can't find conflicting formula #{c.name.inspect}." - rescue TapFormulaAmbiguityError, TapFormulaWithOldnameAmbiguityError - problem "Ambiguous conflicting formula #{c.name.inspect}." - end - end - end - - def audit_options - formula.options.each do |o| - next unless @strict - if o.name !~ /with(out)?-/ && o.name != "c++11" && o.name != "universal" && o.name != "32-bit" - problem "Options should begin with with/without. Migrate '--#{o.name}' with `deprecated_option`." - end - - if o.name =~ /^with(out)?-(?:checks?|tests)$/ - unless formula.deps.any? { |d| d.name == "check" && (d.optional? || d.recommended?) } - problem "Use '--with#{$1}-test' instead of '--#{o.name}'. Migrate '--#{o.name}' with `deprecated_option`." - end - end - end - end - - def audit_desc - # For now, only check the description when using `--strict` - return unless @strict - - desc = formula.desc - - unless desc && !desc.empty? - problem "Formula should have a desc (Description)." - return - end - - # Make sure the formula name plus description is no longer than 80 characters - # Note full_name includes the name of the tap, while name does not - linelength = formula.name.length + ": ".length + desc.length - if linelength > 80 - problem <<-EOS.undent - Description is too long. \"name: desc\" should be less than 80 characters. - Length is calculated as #{formula.name} + desc. (currently #{linelength}) - EOS - end - - if desc =~ /([Cc]ommand ?line)/ - problem "Description should use \"command-line\" instead of \"#{$1}\"" - end - - if desc =~ /^([Aa]n?)\s/ - problem "Description shouldn't start with an indefinite article (#{$1})" - end - - if desc.downcase.start_with? "#{formula.name} " - problem "Description shouldn't include the formula name" - end - end - - def audit_homepage - homepage = formula.homepage - - unless homepage =~ %r{^https?://} - problem "The homepage should start with http or https (URL is #{homepage})." - end - - # Check for http:// GitHub homepage urls, https:// is preferred. - # Note: only check homepages that are repo pages, not *.github.com hosts - if homepage.start_with? "http://github.com/" - problem "Please use https:// for #{homepage}" - end - - # Savannah has full SSL/TLS support but no auto-redirect. - # Doesn't apply to the download URLs, only the homepage. - if homepage.start_with? "http://savannah.nongnu.org/" - problem "Please use https:// for #{homepage}" - end - - # Freedesktop is complicated to handle - It has SSL/TLS, but only on certain subdomains. - # To enable https Freedesktop change the URL from http://project.freedesktop.org/wiki to - # https://wiki.freedesktop.org/project_name. - # "Software" is redirected to https://wiki.freedesktop.org/www/Software/project_name - if homepage =~ %r{^http://((?:www|nice|libopenraw|liboil|telepathy|xorg)\.)?freedesktop\.org/(?:wiki/)?} - if homepage =~ /Software/ - problem "#{homepage} should be styled `https://wiki.freedesktop.org/www/Software/project_name`" - else - problem "#{homepage} should be styled `https://wiki.freedesktop.org/project_name`" - end - end - - # Google Code homepages should end in a slash - if homepage =~ %r{^https?://code\.google\.com/p/[^/]+[^/]$} - problem "#{homepage} should end with a slash" - end - - # People will run into mixed content sometimes, but we should enforce and then add - # exemptions as they are discovered. Treat mixed content on homepages as a bug. - # Justify each exemptions with a code comment so we can keep track here. - if homepage =~ %r{^http://[^/]*github\.io/} - problem "Please use https:// for #{homepage}" - end - - # There's an auto-redirect here, but this mistake is incredibly common too. - # Only applies to the homepage and subdomains for now, not the FTP URLs. - if homepage =~ %r{^http://((?:build|cloud|developer|download|extensions|git|glade|help|library|live|nagios|news|people|projects|rt|static|wiki|www)\.)?gnome\.org} - problem "Please use https:// for #{homepage}" - end - - # Compact the above into this list as we're able to remove detailed notations, etc over time. - case homepage - when %r{^http://[^/]*\.apache\.org}, - %r{^http://packages\.debian\.org}, - %r{^http://wiki\.freedesktop\.org/}, - %r{^http://((?:www)\.)?gnupg\.org/}, - %r{^http://ietf\.org}, - %r{^http://[^/.]+\.ietf\.org}, - %r{^http://[^/.]+\.tools\.ietf\.org}, - %r{^http://www\.gnu\.org/}, - %r{^http://code\.google\.com/}, - %r{^http://bitbucket\.org/}, - %r{^http://(?:[^/]*\.)?archive\.org} - problem "Please use https:// for #{homepage}" - end - - return unless @online - begin - nostdout { curl "--connect-timeout", "15", "-o", "/dev/null", homepage } - rescue ErrorDuringExecution - problem "The homepage is not reachable (curl exit code #{$?.exitstatus})" - end - end - - def audit_bottle_spec - if formula.bottle_disabled? && !formula.bottle_disable_reason.valid? - problem "Unrecognized bottle modifier" - end - end - - def audit_github_repository - return unless @online - return unless @new_formula - - regex = %r{https?://github\.com/([^/]+)/([^/]+)/?.*} - _, user, repo = *regex.match(formula.stable.url) if formula.stable - _, user, repo = *regex.match(formula.homepage) unless user - return if !user || !repo - - repo.gsub!(/.git$/, "") - - begin - metadata = GitHub.repository(user, repo) - rescue GitHub::HTTPNotFoundError - return - end - - return if metadata.nil? - - problem "GitHub fork (not canonical repository)" if metadata["fork"] - if (metadata["forks_count"] < 20) && (metadata["subscribers_count"] < 20) && - (metadata["stargazers_count"] < 50) - problem "GitHub repository not notable enough (<20 forks, <20 watchers and <50 stars)" - end - - if Date.parse(metadata["created_at"]) > (Date.today - 30) - problem "GitHub repository too new (<30 days old)" - end - end - - def audit_specs - if head_only?(formula) && formula.tap.to_s.downcase !~ %r{[-/]head-only$} - problem "Head-only (no stable download)" - end - - if devel_only?(formula) && formula.tap.to_s.downcase !~ %r{[-/]devel-only$} - problem "Devel-only (no stable download)" - end - - %w[Stable Devel HEAD].each do |name| - next unless spec = formula.send(name.downcase) - - ra = ResourceAuditor.new(spec).audit - problems.concat ra.problems.map { |problem| "#{name}: #{problem}" } - - spec.resources.each_value do |resource| - ra = ResourceAuditor.new(resource).audit - problems.concat ra.problems.map { |problem| - "#{name} resource #{resource.name.inspect}: #{problem}" - } - end - - spec.patches.each { |p| audit_patch(p) if p.external? } - end - - %w[Stable Devel].each do |name| - next unless spec = formula.send(name.downcase) - version = spec.version - if version.to_s !~ /\d/ - problem "#{name}: version (#{version}) is set to a string without a digit" - end - end - - if formula.stable && formula.devel - if formula.devel.version < formula.stable.version - problem "devel version #{formula.devel.version} is older than stable version #{formula.stable.version}" - elsif formula.devel.version == formula.stable.version - problem "stable and devel versions are identical" - end - end - - stable = formula.stable - case stable && stable.url - when %r{download\.gnome\.org/sources}, %r{ftp\.gnome\.org/pub/GNOME/sources}i - version = Version.parse(stable.url) - if version >= Version.create("1.0") - minor_version = version.to_s.split(".", 3)[1].to_i - if minor_version.odd? - problem "#{stable.version} is a development release" - end - end - end - end - - def audit_revision_and_version_scheme - return unless formula.tap # skip formula not from core or any taps - return unless formula.tap.git? # git log is required - - fv = FormulaVersions.new(formula, :max_depth => 10) - attributes = [:revision, :version_scheme] - attributes_map = fv.version_attributes_map(attributes, "origin/master") - - attributes.each do |attribute| - attributes_for_version = attributes_map[attribute][formula.version] - if !attributes_for_version.empty? - if formula.send(attribute) < attributes_for_version.max - problem "#{attribute} should not decrease" - end - end - end - - revision_map = attributes_map[:revision] - if formula.revision != 0 - if formula.stable - if revision_map[formula.stable.version].empty? # check stable spec - problem "'revision #{formula.revision}' should be removed" - end - else # head/devel-only formula - problem "'revision #{formula.revision}' should be removed" - end - end - end - - def audit_legacy_patches - return unless formula.respond_to?(:patches) - legacy_patches = Patch.normalize_legacy_patches(formula.patches).grep(LegacyPatch) - unless legacy_patches.empty? - problem "Use the patch DSL instead of defining a 'patches' method" - legacy_patches.each { |p| audit_patch(p) } - end - end - - def audit_patch(patch) - case patch.url - when /raw\.github\.com/, %r{gist\.github\.com/raw}, %r{gist\.github\.com/.+/raw}, - %r{gist\.githubusercontent\.com/.+/raw} - unless patch.url =~ /[a-fA-F0-9]{40}/ - problem "GitHub/Gist patches should specify a revision:\n#{patch.url}" - end - when %r{https?://patch-diff\.githubusercontent\.com/raw/(.+)/(.+)/pull/(.+)\.(?:diff|patch)} - problem <<-EOS.undent - use GitHub pull request URLs: - https://github.com/#{$1}/#{$2}/pull/#{$3}.patch - Rather than patch-diff: - #{patch.url} - EOS - when %r{macports/trunk} - problem "MacPorts patches should specify a revision instead of trunk:\n#{patch.url}" - when %r{^http://trac\.macports\.org} - problem "Patches from MacPorts Trac should be https://, not http:\n#{patch.url}" - when %r{^http://bugs\.debian\.org} - problem "Patches from Debian should be https://, not http:\n#{patch.url}" - end - end - - def audit_text - if text =~ /system\s+['"]scons/ - problem "use \"scons *args\" instead of \"system 'scons', *args\"" - end - - if text =~ /system\s+['"]xcodebuild/ - problem %(use "xcodebuild *args" instead of "system 'xcodebuild', *args") - end - - if text =~ /xcodebuild[ (]["'*]/ && !text.include?("SYMROOT=") - problem %(xcodebuild should be passed an explicit "SYMROOT") - end - - if text.include? "Formula.factory(" - problem "\"Formula.factory(name)\" is deprecated in favor of \"Formula[name]\"" - end - - if text.include?("def plist") && !text.include?("plist_options") - problem "Please set plist_options when using a formula-defined plist." - end - - if text.include?('require "language/go"') && !text.include?("go_resource") - problem "require \"language/go\" is unnecessary unless using `go_resource`s" - end - end - - def audit_line(line, lineno) - if line =~ /<(Formula|AmazonWebServicesFormula|ScriptFileFormula|GithubGistFormula)/ - problem "Use a space in class inheritance: class Foo < #{$1}" - end - - # Commented-out cmake support from default template - if line.include?('# system "cmake') - problem "Commented cmake call found" - end - - # Comments from default template - [ - "# PLEASE REMOVE", - "# Documentation:", - "# if this fails, try separate make/make install steps", - "# The URL of the archive", - "## Naming --", - "# if your formula requires any X11/XQuartz components", - "# if your formula fails when building in parallel", - "# Remove unrecognized options if warned by configure", - ].each do |comment| - if line.include? comment - problem "Please remove default template comments" - end - end - - # FileUtils is included in Formula - # encfs modifies a file with this name, so check for some leading characters - if line =~ /[^'"\/]FileUtils\.(\w+)/ - problem "Don't need 'FileUtils.' before #{$1}." - end - - # Check for long inreplace block vars - if line =~ /inreplace .* do \|(.{2,})\|/ - problem "\"inreplace <filenames> do |s|\" is preferred over \"|#{$1}|\"." - end - - # Check for string interpolation of single values. - if line =~ /(system|inreplace|gsub!|change_make_var!).*[ ,]"#\{([\w.]+)\}"/ - problem "Don't need to interpolate \"#{$2}\" with #{$1}" - end - - # Check for string concatenation; prefer interpolation - if line =~ /(#\{\w+\s*\+\s*['"][^}]+\})/ - problem "Try not to concatenate paths in string interpolation:\n #{$1}" - end - - # Prefer formula path shortcuts in Pathname+ - if line =~ %r{\(\s*(prefix\s*\+\s*(['"])(bin|include|libexec|lib|sbin|share|Frameworks)[/'"])} - problem "\"(#{$1}...#{$2})\" should be \"(#{$3.downcase}+...)\"" - end - - if line =~ /((man)\s*\+\s*(['"])(man[1-8])(['"]))/ - problem "\"#{$1}\" should be \"#{$4}\"" - end - - # Prefer formula path shortcuts in strings - if line =~ %r[(\#\{prefix\}/(bin|include|libexec|lib|sbin|share|Frameworks))] - problem "\"#{$1}\" should be \"\#{#{$2.downcase}}\"" - end - - if line =~ %r[((\#\{prefix\}/share/man/|\#\{man\}/)(man[1-8]))] - problem "\"#{$1}\" should be \"\#{#{$3}}\"" - end - - if line =~ %r[((\#\{share\}/(man)))[/'"]] - problem "\"#{$1}\" should be \"\#{#{$3}}\"" - end - - if line =~ %r[(\#\{prefix\}/share/(info|man))] - problem "\"#{$1}\" should be \"\#{#{$2}}\"" - end - - if line =~ /depends_on :(automake|autoconf|libtool)/ - problem ":#{$1} is deprecated. Usage should be \"#{$1}\"" - end - - # Commented-out depends_on - if line =~ /#\s*depends_on\s+(.+)\s*$/ - problem "Commented-out dep #{$1}" - end - - # No trailing whitespace, please - if line =~ /[\t ]+$/ - problem "#{lineno}: Trailing whitespace was found" - end - - if line =~ /if\s+ARGV\.include\?\s+'--(HEAD|devel)'/ - problem "Use \"if build.#{$1.downcase}?\" instead" - end - - if line.include?("make && make") - problem "Use separate make calls" - end - - if line =~ /^[ ]*\t/ - problem "Use spaces instead of tabs for indentation" - end - - if line.include?("ENV.x11") - problem "Use \"depends_on :x11\" instead of \"ENV.x11\"" - end - - # Avoid hard-coding compilers - if line =~ %r{(system|ENV\[.+\]\s?=)\s?['"](/usr/bin/)?(gcc|llvm-gcc|clang)['" ]} - problem "Use \"\#{ENV.cc}\" instead of hard-coding \"#{$3}\"" - end - - if line =~ %r{(system|ENV\[.+\]\s?=)\s?['"](/usr/bin/)?((g|llvm-g|clang)\+\+)['" ]} - problem "Use \"\#{ENV.cxx}\" instead of hard-coding \"#{$3}\"" - end - - if line =~ /system\s+['"](env|export)(\s+|['"])/ - problem "Use ENV instead of invoking '#{$1}' to modify the environment" - end - - if line =~ /version == ['"]HEAD['"]/ - problem "Use 'build.head?' instead of inspecting 'version'" - end - - if line =~ /build\.include\?[\s\(]+['"]\-\-(.*)['"]/ - problem "Reference '#{$1}' without dashes" - end - - if line =~ /build\.include\?[\s\(]+['"]with(out)?-(.*)['"]/ - problem "Use build.with#{$1}? \"#{$2}\" instead of build.include? 'with#{$1}-#{$2}'" - end - - if line =~ /build\.with\?[\s\(]+['"]-?-?with-(.*)['"]/ - problem "Don't duplicate 'with': Use `build.with? \"#{$1}\"` to check for \"--with-#{$1}\"" - end - - if line =~ /build\.without\?[\s\(]+['"]-?-?without-(.*)['"]/ - problem "Don't duplicate 'without': Use `build.without? \"#{$1}\"` to check for \"--without-#{$1}\"" - end - - if line =~ /unless build\.with\?(.*)/ - problem "Use if build.without?#{$1} instead of unless build.with?#{$1}" - end - - if line =~ /unless build\.without\?(.*)/ - problem "Use if build.with?#{$1} instead of unless build.without?#{$1}" - end - - if line =~ /(not\s|!)\s*build\.with?\?/ - problem "Don't negate 'build.without?': use 'build.with?'" - end - - if line =~ /(not\s|!)\s*build\.without?\?/ - problem "Don't negate 'build.with?': use 'build.without?'" - end - - if line =~ /ARGV\.(?!(debug\?|verbose\?|value[\(\s]))/ - problem "Use build instead of ARGV to check options" - end - - if line.include?("def options") - problem "Use new-style option definitions" - end - - if line.end_with?("def test") - problem "Use new-style test definitions (test do)" - end - - if line.include?("MACOS_VERSION") - problem "Use MacOS.version instead of MACOS_VERSION" - end - - if line.include?("MACOS_FULL_VERSION") - problem "Use MacOS.full_version instead of MACOS_FULL_VERSION" - end - - cats = %w[leopard snow_leopard lion mountain_lion].join("|") - if line =~ /MacOS\.(?:#{cats})\?/ - problem "\"#{$&}\" is deprecated, use a comparison to MacOS.version instead" - end - - if line =~ /skip_clean\s+:all/ - problem "`skip_clean :all` is deprecated; brew no longer strips symbols\n" \ - "\tPass explicit paths to prevent Homebrew from removing empty folders." - end - - if line =~ /depends_on [A-Z][\w:]+\.new$/ - problem "`depends_on` can take requirement classes instead of instances" - end - - if line =~ /^def (\w+).*$/ - problem "Define method #{$1.inspect} in the class body, not at the top-level" - end - - if line.include?("ENV.fortran") && !formula.requirements.map(&:class).include?(FortranRequirement) - problem "Use `depends_on :fortran` instead of `ENV.fortran`" - end - - if line =~ /JAVA_HOME/i && !formula.requirements.map(&:class).include?(JavaRequirement) - problem "Use `depends_on :java` to set JAVA_HOME" - end - - if line =~ /depends_on :(.+) (if.+|unless.+)$/ - audit_conditional_dep($1.to_sym, $2, $&) - end - - if line =~ /depends_on ['"](.+)['"] (if.+|unless.+)$/ - audit_conditional_dep($1, $2, $&) - end - - if line =~ /(Dir\[("[^\*{},]+")\])/ - problem "#{$1} is unnecessary; just use #{$2}" - end - - if line =~ /system (["'](#{FILEUTILS_METHODS})["' ])/o - system = $1 - method = $2 - problem "Use the `#{method}` Ruby method instead of `system #{system}`" - end - - if line =~ /assert [^!]+\.include?/ - problem "Use `assert_match` instead of `assert ...include?`" - end - - if line.include?('system "npm", "install"') && !line.include?("Language::Node") && formula.name !~ /^kibana(\d{2})?$/ - problem "Use Language::Node for npm install args" - end - - if @strict - if line =~ /system ((["'])[^"' ]*(?:\s[^"' ]*)+\2)/ - bad_system = $1 - unless %w[| < > & ; *].any? { |c| bad_system.include? c } - good_system = bad_system.gsub(" ", "\", \"") - problem "Use `system #{good_system}` instead of `system #{bad_system}` " - end - end - - if line =~ /(require ["']formula["'])/ - problem "`#{$1}` is now unnecessary" - end - - if line =~ %r{#\{share\}/#{Regexp.escape(formula.name)}[/'"]} - problem "Use \#{pkgshare} instead of \#{share}/#{formula.name}" - end - - if line =~ %r{share(\s*[/+]\s*)(['"])#{Regexp.escape(formula.name)}(?:\2|/)} - problem "Use pkgshare instead of (share#{$1}\"#{formula.name}\")" - end - end - end - - def audit_caveats - caveats = formula.caveats.to_s - - if caveats.include?("setuid") - problem "Don't recommend setuid in the caveats, suggest sudo instead." - end - end - - def audit_reverse_migration - # Only enforce for new formula being re-added to core and official taps - return unless @strict - return unless formula.tap && formula.tap.official? - - if formula.tap.tap_migrations.key?(formula.name) - problem <<-EOS.undent - #{formula.name} seems to be listed in tap_migrations.json! - Please remove #{formula.name} from present tap & tap_migrations.json - before submitting it to Homebrew/homebrew-#{formula.tap.repo}. - EOS - end - end - - def audit_prefix_has_contents - return unless formula.prefix.directory? - - if Keg.new(formula.prefix).empty_installation? - problem <<-EOS.undent - The installation seems to be empty. Please ensure the prefix - is set correctly and expected files are installed. - The prefix configure/make argument may be case-sensitive. - EOS - end - end - - def audit_conditional_dep(dep, condition, line) - quoted_dep = quote_dep(dep) - dep = Regexp.escape(dep.to_s) - - case condition - when /if build\.include\? ['"]with-#{dep}['"]$/, /if build\.with\? ['"]#{dep}['"]$/ - problem %(Replace #{line.inspect} with "depends_on #{quoted_dep} => :optional") - when /unless build\.include\? ['"]without-#{dep}['"]$/, /unless build\.without\? ['"]#{dep}['"]$/ - problem %(Replace #{line.inspect} with "depends_on #{quoted_dep} => :recommended") - end - end - - def quote_dep(dep) - Symbol === dep ? dep.inspect : "'#{dep}'" - end - - def audit_check_output(output) - problem(output) if output - end - - def audit - audit_file - audit_formula_name - audit_class - audit_specs - audit_revision_and_version_scheme - audit_desc - audit_homepage - audit_bottle_spec - audit_github_repository - audit_deps - audit_conflicts - audit_options - audit_legacy_patches - audit_text - audit_caveats - text.without_patch.split("\n").each_with_index { |line, lineno| audit_line(line, lineno+1) } - audit_installed - audit_prefix_has_contents - audit_reverse_migration - audit_style - end - - private - - def problem(p) - @problems << p - end - - def head_only?(formula) - formula.head && formula.devel.nil? && formula.stable.nil? - end - - def devel_only?(formula) - formula.devel && formula.stable.nil? - end -end - -class ResourceAuditor - attr_reader :problems - attr_reader :version, :checksum, :using, :specs, :url, :mirrors, :name - - def initialize(resource) - @name = resource.name - @version = resource.version - @checksum = resource.checksum - @url = resource.url - @mirrors = resource.mirrors - @using = resource.using - @specs = resource.specs - @problems = [] - end - - def audit - audit_version - audit_checksum - audit_download_strategy - audit_urls - self - end - - def audit_version - if version.nil? - problem "missing version" - elsif version.to_s.empty? - problem "version is set to an empty string" - elsif !version.detected_from_url? - version_text = version - version_url = Version.detect(url, specs) - if version_url.to_s == version_text.to_s && version.instance_of?(Version) - problem "version #{version_text} is redundant with version scanned from URL" - end - end - - if version.to_s.start_with?("v") - problem "version #{version} should not have a leading 'v'" - end - - if version.to_s =~ /_\d+$/ - problem "version #{version} should not end with an underline and a number" - end - end - - def audit_checksum - return unless checksum - - case checksum.hash_type - when :md5 - problem "MD5 checksums are deprecated, please use SHA256" - return - when :sha1 - problem "SHA1 checksums are deprecated, please use SHA256" - return - when :sha256 then len = 64 - end - - if checksum.empty? - problem "#{checksum.hash_type} is empty" - else - problem "#{checksum.hash_type} should be #{len} characters" unless checksum.hexdigest.length == len - problem "#{checksum.hash_type} contains invalid characters" unless checksum.hexdigest =~ /^[a-fA-F0-9]+$/ - problem "#{checksum.hash_type} should be lowercase" unless checksum.hexdigest == checksum.hexdigest.downcase - end - end - - def audit_download_strategy - if url =~ %r{^(cvs|bzr|hg|fossil)://} || url =~ %r{^(svn)\+http://} - problem "Use of the #{$&} scheme is deprecated, pass `:using => :#{$1}` instead" - end - - url_strategy = DownloadStrategyDetector.detect(url) - - if using == :git || url_strategy == GitDownloadStrategy - if specs[:tag] && !specs[:revision] - problem "Git should specify :revision when a :tag is specified." - end - end - - return unless using - - if using == :ssl3 || \ - (Object.const_defined?("CurlSSL3DownloadStrategy") && using == CurlSSL3DownloadStrategy) - problem "The SSL3 download strategy is deprecated, please choose a different URL" - elsif (Object.const_defined?("CurlUnsafeDownloadStrategy") && using == CurlUnsafeDownloadStrategy) || \ - (Object.const_defined?("UnsafeSubversionDownloadStrategy") && using == UnsafeSubversionDownloadStrategy) - problem "#{using.name} is deprecated, please choose a different URL" - end - - if using == :cvs - mod = specs[:module] - - if mod == name - problem "Redundant :module value in URL" - end - - if url =~ %r{:[^/]+$} - mod = url.split(":").last - - if mod == name - problem "Redundant CVS module appended to URL" - else - problem "Specify CVS module as `:module => \"#{mod}\"` instead of appending it to the URL" - end - end - end - - using_strategy = DownloadStrategyDetector.detect("", using) - - if url_strategy == using_strategy - problem "Redundant :using value in URL" - end - end - - def audit_urls - # Check GNU urls; doesn't apply to mirrors - if url =~ %r{^(?:https?|ftp)://(?!alpha).+/gnu/} - problem "Please use \"https://ftpmirror.gnu.org\" instead of #{url}." - end - - if mirrors.include?(url) - problem "URL should not be duplicated as a mirror: #{url}" - end - - urls = [url] + mirrors - - # Check a variety of SSL/TLS URLs that don't consistently auto-redirect - # or are overly common errors that need to be reduced & fixed over time. - urls.each do |p| - case p - when %r{^http://ftp\.gnu\.org/}, - %r{^http://ftpmirror\.gnu\.org/}, - %r{^http://download\.savannah\.gnu\.org/}, - %r{^http://download-mirror\.savannah\.gnu\.org/}, - %r{^http://[^/]*\.apache\.org/}, - %r{^http://code\.google\.com/}, - %r{^http://fossies\.org/}, - %r{^http://mirrors\.kernel\.org/}, - %r{^http://(?:[^/]*\.)?bintray\.com/}, - %r{^http://tools\.ietf\.org/}, - %r{^http://launchpad\.net/}, - %r{^http://bitbucket\.org/}, - %r{^http://anonscm\.debian\.org/}, - %r{^http://cpan\.metacpan\.org/}, - %r{^http://hackage\.haskell\.org/}, - %r{^http://(?:[^/]*\.)?archive\.org}, - %r{^http://(?:[^/]*\.)?freedesktop\.org}, - %r{^http://(?:[^/]*\.)?mirrorservice\.org/} - problem "Please use https:// for #{p}" - when %r{^http://search\.mcpan\.org/CPAN/(.*)}i - problem "#{p} should be `https://cpan.metacpan.org/#{$1}`" - when %r{^(http|ftp)://ftp\.gnome\.org/pub/gnome/(.*)}i - problem "#{p} should be `https://download.gnome.org/#{$2}`" - when %r{^git://anonscm\.debian\.org/users/(.*)}i - problem "#{p} should be `https://anonscm.debian.org/git/users/#{$1}`" - end - end - - # Prefer HTTP/S when possible over FTP protocol due to possible firewalls. - urls.each do |p| - case p - when %r{^ftp://ftp\.mirrorservice\.org} - problem "Please use https:// for #{p}" - when %r{^ftp://ftp\.cpan\.org/pub/CPAN(.*)}i - problem "#{p} should be `http://search.cpan.org/CPAN#{$1}`" - end - end - - # Check SourceForge urls - urls.each do |p| - # Skip if the URL looks like a SVN repo - next if p.include? "/svnroot/" - next if p.include? "svn.sourceforge" - - # Is it a sourceforge http(s) URL? - next unless p =~ %r{^https?://.*\b(sourceforge|sf)\.(com|net)} - - if p =~ /(\?|&)use_mirror=/ - problem "Don't use #{$1}use_mirror in SourceForge urls (url is #{p})." - end - - if p.end_with?("/download") - problem "Don't use /download in SourceForge urls (url is #{p})." - end - - if p =~ %r{^https?://sourceforge\.} - problem "Use https://downloads.sourceforge.net to get geolocation (url is #{p})." - end - - if p =~ %r{^https?://prdownloads\.} - problem "Don't use prdownloads in SourceForge urls (url is #{p}).\n" \ - "\tSee: http://librelist.com/browser/homebrew/2011/1/12/prdownloads-is-bad/" - end - - if p =~ %r{^http://\w+\.dl\.} - problem "Don't use specific dl mirrors in SourceForge urls (url is #{p})." - end - - if p.start_with? "http://downloads" - problem "Please use https:// for #{p}" - end - end - - # Debian has an abundance of secure mirrors. Let's not pluck the insecure - # one out of the grab bag. - urls.each do |u| - next unless u =~ %r{^http://http\.debian\.net/debian/(.*)}i - problem <<-EOS.undent - Please use a secure mirror for Debian URLs. - We recommend: - https://mirrors.ocf.berkeley.edu/debian/#{$1} - EOS - end - - # Check for Google Code download urls, https:// is preferred - # Intentionally not extending this to SVN repositories due to certificate - # issues. - urls.grep(%r{^http://.*\.googlecode\.com/files.*}) do |u| - problem "Please use https:// for #{u}" - end - - # Check for new-url Google Code download urls, https:// is preferred - urls.grep(%r{^http://code\.google\.com/}) do |u| - problem "Please use https:// for #{u}" - end - - # Check for git:// GitHub repo urls, https:// is preferred. - urls.grep(%r{^git://[^/]*github\.com/}) do |u| - problem "Please use https:// for #{u}" - end - - # Check for git:// Gitorious repo urls, https:// is preferred. - urls.grep(%r{^git://[^/]*gitorious\.org/}) do |u| - problem "Please use https:// for #{u}" - end - - # Check for http:// GitHub repo urls, https:// is preferred. - urls.grep(%r{^http://github\.com/.*\.git$}) do |u| - problem "Please use https:// for #{u}" - end - - # Use new-style archive downloads - urls.each do |u| - next unless u =~ %r{https://.*github.*/(?:tar|zip)ball/} && u !~ /\.git$/ - problem "Use /archive/ URLs for GitHub tarballs (url is #{u})." - end - - # Don't use GitHub .zip files - urls.each do |u| - next unless u =~ %r{https://.*github.*/(archive|releases)/.*\.zip$} && u !~ %r{releases/download} - problem "Use GitHub tarballs rather than zipballs (url is #{u})." - end - - # Don't use GitHub codeload URLs - urls.each do |u| - next unless u =~ %r{https?://codeload\.github\.com/(.+)/(.+)/(?:tar\.gz|zip)/(.+)} - problem <<-EOS.undent - use GitHub archive URLs: - https://github.com/#{$1}/#{$2}/archive/#{$3}.tar.gz - Rather than codeload: - #{u} - EOS - end - - # Check for Maven Central urls, prefer HTTPS redirector over specific host - urls.each do |u| - next unless u =~ %r{https?://(?:central|repo\d+)\.maven\.org/maven2/(.+)$} - problem "#{u} should be `https://search.maven.org/remotecontent?filepath=#{$1}`" - end - end - - def problem(text) - @problems << text - end -end diff --git a/Library/Homebrew/cmd/bottle.rb b/Library/Homebrew/cmd/bottle.rb deleted file mode 100644 index 1980fbe9d..000000000 --- a/Library/Homebrew/cmd/bottle.rb +++ /dev/null @@ -1,458 +0,0 @@ -#: @hide_from_man_page -#: * `bottle` [`--verbose`] [`--no-rebuild`] [`--keep-old`] [`--skip-relocation`] [`--root-url=<root_url>`]: -#: * `bottle` `--merge` [`--no-commit`] [`--keep-old`] [`--write`]: -#: -#: Generate a bottle (binary package) from a formula installed with -#: `--build-bottle`. - -require "formula" -require "utils/bottles" -require "tab" -require "keg" -require "formula_versions" -require "utils/inreplace" -require "erb" -require "extend/pathname" - -BOTTLE_ERB = <<-EOS - bottle do - <% if !root_url.start_with?(BottleSpecification::DEFAULT_DOMAIN) %> - root_url "<%= root_url %>" - <% end %> - <% if prefix != BottleSpecification::DEFAULT_PREFIX %> - prefix "<%= prefix %>" - <% end %> - <% if cellar.is_a? Symbol %> - cellar :<%= cellar %> - <% elsif cellar != BottleSpecification::DEFAULT_CELLAR %> - cellar "<%= cellar %>" - <% end %> - <% if rebuild > 0 %> - rebuild <%= rebuild %> - <% end %> - <% checksums.each do |checksum_type, checksum_values| %> - <% checksum_values.each do |checksum_value| %> - <% checksum, osx = checksum_value.shift %> - <%= checksum_type %> "<%= checksum %>" => :<%= osx %> - <% end %> - <% end %> - end -EOS - -MAXIMUM_STRING_MATCHES = 100 - -module Homebrew - def keg_contain?(string, keg, ignores) - @put_string_exists_header, @put_filenames = nil - - def print_filename(string, filename) - unless @put_string_exists_header - opoo "String '#{string}' still exists in these files:" - @put_string_exists_header = true - end - - @put_filenames ||= [] - unless @put_filenames.include? filename - puts "#{Tty.red}#{filename}#{Tty.reset}" - @put_filenames << filename - end - end - - result = false - - keg.each_unique_file_matching(string) do |file| - # skip document file. - next if Metafiles::EXTENSIONS.include? file.extname - - linked_libraries = Keg.file_linked_libraries(file, string) - result ||= !linked_libraries.empty? - - if ARGV.verbose? - print_filename(string, file) unless linked_libraries.empty? - linked_libraries.each do |lib| - puts " #{Tty.gray}-->#{Tty.reset} links to #{lib}" - end - end - - text_matches = [] - - # Use strings to search through the file for each string - Utils.popen_read("strings", "-t", "x", "-", file.to_s) do |io| - until io.eof? - str = io.readline.chomp - next if ignores.any? { |i| i =~ str } - next unless str.include? string - offset, match = str.split(" ", 2) - next if linked_libraries.include? match # Don't bother reporting a string if it was found by otool - - result = true - text_matches << [match, offset] - end - end - - if ARGV.verbose? && !text_matches.empty? - print_filename string, file - text_matches.first(MAXIMUM_STRING_MATCHES).each do |match, offset| - puts " #{Tty.gray}-->#{Tty.reset} match '#{match}' at offset #{Tty.em}0x#{offset}#{Tty.reset}" - end - - if text_matches.size > MAXIMUM_STRING_MATCHES - puts "Only the first #{MAXIMUM_STRING_MATCHES} matches were output" - end - end - end - - keg_contain_absolute_symlink_starting_with?(string, keg) || result - end - - def keg_contain_absolute_symlink_starting_with?(string, keg) - absolute_symlinks_start_with_string = [] - keg.find do |pn| - if pn.symlink? && (link = pn.readlink).absolute? - if link.to_s.start_with?(string) - absolute_symlinks_start_with_string << pn - end - end - end - - if ARGV.verbose? - unless absolute_symlinks_start_with_string.empty? - opoo "Absolute symlink starting with #{string}:" - absolute_symlinks_start_with_string.each do |pn| - puts " #{pn} -> #{pn.resolved_path}" - end - end - end - - !absolute_symlinks_start_with_string.empty? - end - - def bottle_output(bottle) - erb = ERB.new BOTTLE_ERB - erb.result(bottle.instance_eval { binding }).gsub(/^\s*$\n/, "") - end - - def bottle_formula(f) - unless f.installed? - return ofail "Formula not installed or up-to-date: #{f.full_name}" - end - - unless f.tap - return ofail "Formula not from core or any taps: #{f.full_name}" - end - - if f.bottle_disabled? - ofail "Formula has disabled bottle: #{f.full_name}" - puts f.bottle_disable_reason - return - end - - unless Utils::Bottles::built_as? f - return ofail "Formula not installed with '--build-bottle': #{f.full_name}" - end - - unless f.stable - return ofail "Formula has no stable version: #{f.full_name}" - end - - if ARGV.include? "--no-rebuild" - rebuild = 0 - elsif ARGV.include? "--keep-old" - rebuild = f.bottle_specification.rebuild - else - ohai "Determining #{f.full_name} bottle rebuild..." - versions = FormulaVersions.new(f) - rebuilds = versions.bottle_version_map("origin/master")[f.pkg_version] - rebuilds.pop if rebuilds.last.to_i > 0 - rebuild = rebuilds.empty? ? 0 : rebuilds.max.to_i + 1 - end - - filename = Bottle::Filename.create(f, Utils::Bottles.tag, rebuild) - bottle_path = Pathname.pwd/filename - - tar_filename = filename.to_s.sub(/.gz$/, "") - tar_path = Pathname.pwd/tar_filename - - prefix = HOMEBREW_PREFIX.to_s - cellar = HOMEBREW_CELLAR.to_s - - ohai "Bottling #{filename}..." - - keg = Keg.new(f.prefix) - relocatable = false - skip_relocation = false - - keg.lock do - original_tab = nil - - begin - unless ARGV.include? "--skip-relocation" - keg.relocate_dynamic_linkage prefix, Keg::PREFIX_PLACEHOLDER, - cellar, Keg::CELLAR_PLACEHOLDER - keg.relocate_text_files prefix, Keg::PREFIX_PLACEHOLDER, - cellar, Keg::CELLAR_PLACEHOLDER - end - - keg.delete_pyc_files! - - Tab.clear_cache - tab = Tab.for_keg(keg) - original_tab = tab.dup - tab.poured_from_bottle = false - tab.HEAD = nil - tab.time = nil - tab.write - - keg.find do |file| - if file.symlink? - # Ruby does not support `File.lutime` yet. - # Shellout using `touch` to change modified time of symlink itself. - system "/usr/bin/touch", "-h", - "-t", tab.source_modified_time.strftime("%Y%m%d%H%M.%S"), file - else - file.utime(tab.source_modified_time, tab.source_modified_time) - end - end - - cd cellar do - safe_system "tar", "cf", tar_path, "#{f.name}/#{f.pkg_version}" - tar_path.utime(tab.source_modified_time, tab.source_modified_time) - relocatable_tar_path = "#{f}-bottle.tar" - mv tar_path, relocatable_tar_path - # Use gzip, faster to compress than bzip2, faster to uncompress than bzip2 - # or an uncompressed tarball (and more bandwidth friendly). - safe_system "gzip", "-f", relocatable_tar_path - mv "#{relocatable_tar_path}.gz", bottle_path - end - - if bottle_path.size > 1*1024*1024 - ohai "Detecting if #{filename} is relocatable..." - end - - if prefix == "/usr/local" - prefix_check = File.join(prefix, "opt") - else - prefix_check = prefix - end - - ignores = [] - if f.deps.any? { |dep| dep.name == "go" } - ignores << %r{#{Regexp.escape(HOMEBREW_CELLAR)}/go/[\d\.]+/libexec} - end - - relocatable = true - if ARGV.include? "--skip-relocation" - skip_relocation = true - else - relocatable = false if keg_contain?(prefix_check, keg, ignores) - relocatable = false if keg_contain?(cellar, keg, ignores) - if prefix != prefix_check - relocatable = false if keg_contain_absolute_symlink_starting_with?(prefix, keg) - end - skip_relocation = relocatable && !keg.require_install_name_tool? - end - puts if !relocatable && ARGV.verbose? - rescue Interrupt - ignore_interrupts { bottle_path.unlink if bottle_path.exist? } - raise - ensure - ignore_interrupts do - original_tab.write if original_tab - unless ARGV.include? "--skip-relocation" - keg.relocate_dynamic_linkage Keg::PREFIX_PLACEHOLDER, prefix, - Keg::CELLAR_PLACEHOLDER, cellar - keg.relocate_text_files Keg::PREFIX_PLACEHOLDER, prefix, - Keg::CELLAR_PLACEHOLDER, cellar - end - end - end - end - - root_url = ARGV.value("root-url") - # Use underscored version for legacy reasons. Remove at some point. - root_url ||= ARGV.value("root_url") - - bottle = BottleSpecification.new - bottle.root_url(root_url) if root_url - if relocatable - if skip_relocation - bottle.cellar :any_skip_relocation - else - bottle.cellar :any - end - else - bottle.cellar cellar - bottle.prefix prefix - end - bottle.rebuild rebuild - sha256 = bottle_path.sha256 - bottle.sha256 sha256 => Utils::Bottles.tag - - old_spec = f.bottle_specification - if ARGV.include?("--keep-old") && !old_spec.checksums.empty? - bad_fields = [:root_url, :prefix, :cellar, :rebuild].select do |field| - old_spec.send(field) != bottle.send(field) - end - bad_fields.delete(:cellar) if old_spec.cellar == :any && bottle.cellar == :any_skip_relocation - unless bad_fields.empty? - bottle_path.unlink if bottle_path.exist? - odie "--keep-old is passed but there are changes in: #{bad_fields.join ", "}" - end - end - - output = bottle_output bottle - - puts "./#{filename}" - puts output - - if ARGV.include? "--json" - json = { - f.full_name => { - "formula" => { - "pkg_version" => f.pkg_version.to_s, - "path" => f.path.to_s.strip_prefix("#{HOMEBREW_REPOSITORY}/"), - }, - "bottle" => { - "root_url" => bottle.root_url, - "prefix" => bottle.prefix, - "cellar" => bottle.cellar.to_s, - "rebuild" => bottle.rebuild, - "tags" => { - Utils::Bottles.tag.to_s => { - "filename" => filename.to_s, - "sha256" => sha256, - }, - } - }, - "bintray" => { - "package" => Utils::Bottles::Bintray.package(f.name), - "repository" => Utils::Bottles::Bintray.repository(f.tap), - }, - }, - } - File.open("#{filename.prefix}.bottle.json", "w") do |file| - file.write Utils::JSON.dump json - end - end - end - - def merge - write = ARGV.include? "--write" - - bottles_hash = ARGV.named.reduce({}) do |hash, json_file| - deep_merge_hashes hash, Utils::JSON.load(IO.read(json_file)) - end - - bottles_hash.each do |formula_name, bottle_hash| - ohai formula_name - - bottle = BottleSpecification.new - bottle.root_url bottle_hash["bottle"]["root_url"] - cellar = bottle_hash["bottle"]["cellar"] - if cellar == "any" || cellar == "any_skip_relocation" - cellar = cellar.to_sym - end - bottle.cellar cellar - bottle.prefix bottle_hash["bottle"]["prefix"] - bottle.rebuild bottle_hash["bottle"]["rebuild"] - bottle_hash["bottle"]["tags"].each do |tag, tag_hash| - bottle.sha256 tag_hash["sha256"] => tag.to_sym - end - - output = bottle_output bottle - - if write - path = Pathname.new("#{HOMEBREW_REPOSITORY/bottle_hash["formula"]["path"]}") - update_or_add = nil - - Utils::Inreplace.inreplace(path) do |s| - if s.include? "bottle do" - update_or_add = "update" - if ARGV.include? "--keep-old" - mismatches = [] - bottle_block_contents = s[/ bottle do(.+?)end\n/m, 1] - bottle_block_contents.lines.each do |line| - line = line.strip - next if line.empty? - key, value, _, tag = line.split " ", 4 - valid_key = %w[root_url prefix cellar rebuild sha1 sha256].include? key - next unless valid_key - - value = value.to_s.delete ":'\"" - tag = tag.to_s.delete ":" - - if !tag.empty? - if !bottle_hash["bottle"]["tags"][tag].to_s.empty? - mismatches << "#{key} => #{tag}" - else - bottle.send(key, value => tag.to_sym) - end - next - end - - old_value = bottle_hash["bottle"][key].to_s - next if key == "cellar" && old_value == "any" && value == "any_skip_relocation" - mismatches << key if old_value.empty? || value != old_value - end - unless mismatches.empty? - odie "--keep-old was passed but there were changes in #{mismatches.join(", ")}!" - end - output = bottle_output bottle - end - puts output - string = s.sub!(/ bottle do.+?end\n/m, output) - odie "Bottle block update failed!" unless string - else - if ARGV.include? "--keep-old" - odie "--keep-old was passed but there was no existing bottle block!" - end - puts output - update_or_add = "add" - if s.include? "stable do" - indent = s.slice(/^ +stable do/).length - "stable do".length - string = s.sub!(/^ {#{indent}}stable do(.|\n)+?^ {#{indent}}end\n/m, '\0' + output + "\n") - else - string = s.sub!( - /( - \ {2}( # two spaces at the beginning - (url|head)\ ['"][\S\ ]+['"] # url or head with a string - ( - ,[\S\ ]*$ # url may have options - (\n^\ {3}[\S\ ]+$)* # options can be in multiple lines - )?| - (homepage|desc|sha1|sha256|version|mirror)\ ['"][\S\ ]+['"]| # specs with a string - rebuild\ \d+ # rebuild with a number - )\n+ # multiple empty lines - )+ - /mx, '\0' + output + "\n") - end - odie "Bottle block addition failed!" unless string - end - end - - unless ARGV.include? "--no-commit" - short_name = formula_name.split("/", -1).last - pkg_version = bottle_hash["formula"]["pkg_version"] - - path.parent.cd do - safe_system "git", "commit", "--no-edit", "--verbose", - "--message=#{short_name}: #{update_or_add} #{pkg_version} bottle.", - "--", path - end - end - else - puts output - end - end - end - - def bottle - if ARGV.include? "--merge" - merge - else - ARGV.resolved_formulae.each do |f| - bottle_formula f - end - end - end -end diff --git a/Library/Homebrew/cmd/create.rb b/Library/Homebrew/cmd/create.rb deleted file mode 100644 index 9be990318..000000000 --- a/Library/Homebrew/cmd/create.rb +++ /dev/null @@ -1,218 +0,0 @@ -#: * `create` <URL> [`--autotools`|`--cmake`] [`--no-fetch`] [`--set-name` <name>] [`--set-version` <version>] [`--tap` <user>`/`<repo>]: -#: Generate a formula for the downloadable file at <URL> and open it in the editor. -#: Homebrew will attempt to automatically derive the formula name -#: and version, but if it fails, you'll have to make your own template. The `wget` -#: formula serves as a simple example. For the complete API have a look at -#: -#: <http://www.rubydoc.info/github/Homebrew/brew/master/Formula> -#: -#: If `--autotools` is passed, create a basic template for an Autotools-style build. -#: If `--cmake` is passed, create a basic template for a CMake-style build. -#: -#: If `--no-fetch` is passed, Homebrew will not download <URL> to the cache and -#: will thus not add the SHA256 to the formula for you. -#: -#: The options `--set-name` and `--set-version` each take an argument and allow -#: you to explicitly set the name and version of the package you are creating. -#: -#: The option `--tap` takes a tap as its argument and generates the formula in -#: the specified tap. - -require "formula" -require "blacklist" -require "digest" -require "erb" - -module Homebrew - # Create a formula from a tarball URL - def create - # Allow searching MacPorts or Fink. - if ARGV.include? "--macports" - opoo "`brew create --macports` is deprecated; use `brew search --macports` instead" - exec_browser "https://www.macports.org/ports.php?by=name&substr=#{ARGV.next}" - elsif ARGV.include? "--fink" - opoo "`brew create --fink` is deprecated; use `brew search --fink` instead" - exec_browser "http://pdb.finkproject.org/pdb/browse.php?summary=#{ARGV.next}" - end - - raise UsageError if ARGV.named.empty? - - # Ensure that the cache exists so we can fetch the tarball - HOMEBREW_CACHE.mkpath - - url = ARGV.named.first # Pull the first (and only) url from ARGV - - version = ARGV.next if ARGV.include? "--set-version" - name = ARGV.next if ARGV.include? "--set-name" - tap = ARGV.next if ARGV.include? "--tap" - - fc = FormulaCreator.new - fc.name = name - fc.version = version - fc.tap = Tap.fetch(tap || "homebrew/core") - raise TapUnavailableError, tap unless fc.tap.installed? - fc.url = url - - fc.mode = if ARGV.include? "--cmake" - :cmake - elsif ARGV.include? "--autotools" - :autotools - end - - if fc.name.nil? || fc.name.strip.empty? - stem = Pathname.new(url).stem - print "Formula name [#{stem}]: " - fc.name = __gets || stem - fc.update_path - end - - # Don't allow blacklisted formula, or names that shadow aliases, - # unless --force is specified. - unless ARGV.force? - if msg = blacklisted?(fc.name) - raise "#{fc.name} is blacklisted for creation.\n#{msg}\nIf you really want to create this formula use --force." - end - - if Formula.aliases.include? fc.name - realname = Formulary.canonical_name(fc.name) - raise <<-EOS.undent - The formula #{realname} is already aliased to #{fc.name} - Please check that you are not creating a duplicate. - To force creation use --force. - EOS - end - end - - fc.generate! - - puts "Please `brew audit --new-formula #{fc.name}` before submitting, thanks." - exec_editor fc.path - end - - def __gets - gots = $stdin.gets.chomp - if gots.empty? then nil else gots end - end -end - -class FormulaCreator - attr_reader :url, :sha256 - attr_accessor :name, :version, :tap, :path, :mode - - def url=(url) - @url = url - path = Pathname.new(url) - if @name.nil? - case url - when %r{github\.com/\S+/(\S+)\.git} - @name = $1 - @head = true - when %r{github\.com/\S+/(\S+)/archive/} - @name = $1 - else - @name = path.basename.to_s[/(.*?)[-_.]?#{Regexp.escape(path.version.to_s)}/, 1] - end - end - update_path - if @version - @version = Version.create(@version) - else - @version = Pathname.new(url).version - end - end - - def update_path - return if @name.nil? || @tap.nil? - @path = Formulary.path "#{@tap}/#{@name}" - end - - def fetch? - !head? && !ARGV.include?("--no-fetch") - end - - def head? - @head || ARGV.build_head? - end - - def generate! - raise "#{path} already exists" if path.exist? - - if version.nil? - opoo "Version cannot be determined from URL." - puts "You'll need to add an explicit 'version' to the formula." - end - - if fetch? && version - r = Resource.new - r.url(url) - r.version(version) - r.owner = self - @sha256 = r.fetch.sha256 if r.download_strategy == CurlDownloadStrategy - end - - path.write ERB.new(template, nil, ">").result(binding) - end - - def template; <<-EOS.undent - # Documentation: https://github.com/Homebrew/brew/blob/master/share/doc/homebrew/Formula-Cookbook.md - # http://www.rubydoc.info/github/Homebrew/brew/master/Formula - # PLEASE REMOVE ALL GENERATED COMMENTS BEFORE SUBMITTING YOUR PULL REQUEST! - - class #{Formulary.class_s(name)} < Formula - desc "" - homepage "" - <% if head? %> - head "#{url}" - <% else %> - url "#{url}" - <% unless version.nil? or version.detected_from_url? %> - version "#{version}" - <% end %> - sha256 "#{sha256}" - <% end %> - - <% if mode == :cmake %> - depends_on "cmake" => :build - <% elsif mode.nil? %> - # depends_on "cmake" => :build - <% end %> - depends_on :x11 # if your formula requires any X11/XQuartz components - - def install - # ENV.deparallelize # if your formula fails when building in parallel - - <% if mode == :cmake %> - system "cmake", ".", *std_cmake_args - <% elsif mode == :autotools %> - # Remove unrecognized options if warned by configure - system "./configure", "--disable-debug", - "--disable-dependency-tracking", - "--disable-silent-rules", - "--prefix=\#{prefix}" - <% else %> - # Remove unrecognized options if warned by configure - system "./configure", "--disable-debug", - "--disable-dependency-tracking", - "--disable-silent-rules", - "--prefix=\#{prefix}" - # system "cmake", ".", *std_cmake_args - <% end %> - system "make", "install" # if this fails, try separate make/make install steps - end - - test do - # `test do` will create, run in and delete a temporary directory. - # - # This test will fail and we won't accept that! It's enough to just replace - # "false" with the main program this formula installs, but it'd be nice if you - # were more thorough. Run the test with `brew test #{name}`. Options passed - # to `brew install` such as `--HEAD` also need to be provided to `brew test`. - # - # The installed folder is not in the path, so use the entire path to any - # executables being tested: `system "\#{bin}/program", "do", "something"`. - system "false" - end - end - EOS - end -end diff --git a/Library/Homebrew/cmd/edit.rb b/Library/Homebrew/cmd/edit.rb deleted file mode 100644 index ef325b8b6..000000000 --- a/Library/Homebrew/cmd/edit.rb +++ /dev/null @@ -1,50 +0,0 @@ -#: * `edit`: -#: Open all of Homebrew for editing. -#: -#: * `edit` <formula>: -#: Open <formula> in the editor. - -require "formula" - -module Homebrew - def edit - unless (HOMEBREW_REPOSITORY/".git").directory? - raise <<-EOS.undent - Changes will be lost! - The first time you `brew update', all local changes will be lost, you should - thus `brew update' before you `brew edit'! - EOS - end - - # If no brews are listed, open the project root in an editor. - if ARGV.named.empty? - editor = File.basename which_editor - if editor == "mate" || editor == "subl" - # If the user is using TextMate or Sublime Text, - # give a nice project view instead. - exec_editor HOMEBREW_REPOSITORY+"bin/brew", - HOMEBREW_REPOSITORY+"README.md", - HOMEBREW_REPOSITORY+".gitignore", - *library_folders - else - exec_editor HOMEBREW_REPOSITORY - end - else - # Don't use ARGV.formulae as that will throw if the file doesn't parse - paths = ARGV.named.map do |name| - path = Formulary.path(name) - unless path.file? || ARGV.force? - raise FormulaUnavailableError, name - end - path - end - exec_editor(*paths) - end - end - - def library_folders - Dir["#{HOMEBREW_LIBRARY}/*"].reject do |d| - case File.basename(d) when "LinkedKegs", "Aliases" then true end - end - end -end diff --git a/Library/Homebrew/cmd/man.rb b/Library/Homebrew/cmd/man.rb deleted file mode 100644 index 6754a15f2..000000000 --- a/Library/Homebrew/cmd/man.rb +++ /dev/null @@ -1,88 +0,0 @@ -#: @hide_from_man_page -#: * `man`: -#: Generate Homebrew's manpages. - -require "formula" -require "erb" -require "ostruct" - -module Homebrew - SOURCE_PATH = HOMEBREW_LIBRARY_PATH/"manpages" - TARGET_MAN_PATH = HOMEBREW_REPOSITORY/"share/man/man1" - TARGET_DOC_PATH = HOMEBREW_REPOSITORY/"share/doc/homebrew" - - def man - raise UsageError unless ARGV.named.empty? - - if ARGV.flag? "--link" - odie "`brew man --link` is now done automatically by `brew update`." - else - regenerate_man_pages - end - end - - private - - def regenerate_man_pages - Homebrew.install_gem_setup_path! "ronn" - - markup = build_man_page - convert_man_page(markup, TARGET_DOC_PATH/"brew.1.html") - convert_man_page(markup, TARGET_MAN_PATH/"brew.1") - - cask_markup = (HOMEBREW_LIBRARY/"Homebrew/manpages/brew-cask.1.md").read - convert_man_page(cask_markup, TARGET_MAN_PATH/"brew-cask.1") - end - - def build_man_page - template = (SOURCE_PATH/"brew.1.md.erb").read - variables = OpenStruct.new - - variables[:commands] = Pathname.glob("#{HOMEBREW_LIBRARY_PATH}/cmd/*.{rb,sh}"). - sort_by { |source_file| sort_key_for_path(source_file) }. - map { |source_file| - source_file.read.lines. - grep(/^#:/). - map { |line| line.slice(2..-1) }. - join - }. - reject { |s| s.strip.empty? || s.include?("@hide_from_man_page") } - - variables[:maintainers] = (HOMEBREW_REPOSITORY/"README.md"). - read[/Homebrew's current maintainers are (.*)\./, 1]. - scan(/\[([^\]]*)\]/).flatten - - ERB.new(template, nil, ">").result(variables.instance_eval{ binding }) - end - - def sort_key_for_path(path) - # Options after regular commands (`~` comes after `z` in ASCII table). - path.basename.to_s.sub(/\.(rb|sh)$/, "").sub(/^--/, "~~") - end - - def convert_man_page(markup, target) - shared_args = %W[ - --pipe - --organization=Homebrew - --manual=#{target.basename(".1")} - ] - - format_flag, format_desc = target_path_to_format(target) - - puts "Writing #{format_desc} to #{target}" - Utils.popen(["ronn", format_flag] + shared_args, "rb+") do |ronn| - ronn.write markup - ronn.close_write - target.atomic_write ronn.read - end - end - - def target_path_to_format(target) - case target.basename - when /\.html?$/ then ["--fragment", "HTML fragment"] - when /\.\d$/ then ["--roff", "man page"] - else - odie "Failed to infer output format from '#{target.basename}'." - end - end -end diff --git a/Library/Homebrew/cmd/pull.rb b/Library/Homebrew/cmd/pull.rb deleted file mode 100644 index 341eed34a..000000000 --- a/Library/Homebrew/cmd/pull.rb +++ /dev/null @@ -1,575 +0,0 @@ -#: @hide_from_man_page -#: `pull` [`--bottle`] [`--bump`] [`--clean`] [`--ignore-whitespace`] [`--resolve`] [`--branch-okay`] [`--no-pbcopy`] [`--no-publish`] <patch-source> [<patch-source>] -#: -#: Gets a patch from a GitHub commit or pull request and applies it to Homebrew. -#: Optionally, installs the formulae changed by the patch. -#: -#: -#: Each <patch-source> may be one of: -#: * The ID number of a PR (Pull Request) in the homebrew/core GitHub -#: repository -#: * The URL of a PR on GitHub, using either the web page or API URL -#: formats. In this form, the PR may be on Homebrew/brew, -#: Homebrew/homebrew-core or any tap. -#: * The URL of a commit on GitHub -#: * A "http://bot.brew.sh/job/..." string specifying a testing job ID -#: -#: If `--bottle` was passed, handle bottles, pulling the bottle-update -#: commit and publishing files on Bintray. -#: If `--bump` was passed, for one-formula PRs, automatically reword -#: commit message to our preferred format. -#: If `--clean` was passed, do not rewrite or otherwise modify the -#: commits found in the pulled PR. -#: If `--ignore-whitespace` was passed, silently ignore whitespace -#: discrepancies when applying diffs. -#: If `--resolve` was passed, when a patch fails to apply, leave in -#: progress and allow user to -#: resolve, instead of aborting. -#: If `--branch-okay` was passed, do not warn if pulling to a branch -#: besides master (useful for testing). -#: If `--no-pbcopy` was passed, do not copy anything to the system -# clipboard. -#: If `--no-publish` was passed, do not publish bottles to Bintray. - -require "net/http" -require "net/https" -require "utils" -require "utils/json" -require "formula" -require "formulary" -require "tap" -require "version" -require "pkg_version" - -module Homebrew - def pull - if ARGV[0] == "--rebase" - odie "You meant `git pull --rebase`." - end - if ARGV.named.empty? - odie "This command requires at least one argument containing a URL or pull request number" - end - do_bump = ARGV.include?("--bump") && !ARGV.include?("--clean") - - # Formulae with affected bottles that were published - bintray_published_formulae = [] - tap = nil - - ARGV.named.each do |arg| - if arg.to_i > 0 - issue = arg - url = "https://github.com/Homebrew/homebrew-core/pull/#{arg}" - tap = CoreTap.instance - elsif (testing_match = arg.match %r{/job/Homebrew.*Testing/(\d+)/}) - tap = ARGV.value("tap") - tap = if tap && tap.start_with?("homebrew/") - Tap.fetch("homebrew", tap.strip_prefix("homebrew/")) - elsif tap - odie "Tap option did not start with \"homebrew/\": #{tap}" - else - CoreTap.instance - end - _, testing_job = *testing_match - url = "https://github.com/Homebrew/homebrew-#{tap.repo}/compare/master...BrewTestBot:testing-#{testing_job}" - odie "Testing URLs require `--bottle`!" unless ARGV.include?("--bottle") - elsif (api_match = arg.match HOMEBREW_PULL_API_REGEX) - _, user, repo, issue = *api_match - url = "https://github.com/#{user}/#{repo}/pull/#{issue}" - tap = Tap.fetch(user, repo) if repo.start_with?("homebrew-") - elsif (url_match = arg.match HOMEBREW_PULL_OR_COMMIT_URL_REGEX) - url, user, repo, issue = *url_match - tap = Tap.fetch(user, repo) if repo.start_with?("homebrew-") - else - odie "Not a GitHub pull request or commit: #{arg}" - end - - if !testing_job && ARGV.include?("--bottle") && issue.nil? - odie "No pull request detected!" - end - - if tap - tap.install unless tap.installed? - Dir.chdir tap.path - else - Dir.chdir HOMEBREW_REPOSITORY - end - - # The cache directory seems like a good place to put patches. - HOMEBREW_CACHE.mkpath - - # Store current revision and branch - orig_revision = `git rev-parse --short HEAD`.strip - branch = `git symbolic-ref --short HEAD`.strip - - unless branch == "master" || ARGV.include?("--clean") || ARGV.include?("--branch-okay") - opoo "Current branch is #{branch}: do you need to pull inside master?" - end - - patch_puller = PatchPuller.new(url) - patch_puller.fetch_patch - patch_changes = files_changed_in_patch(patch_puller.patchpath, tap) - - is_bumpable = patch_changes[:formulae].length == 1 && patch_changes[:others].empty? - if do_bump - odie "No changed formulae found to bump" if patch_changes[:formulae].empty? - if patch_changes[:formulae].length > 1 - odie "Can only bump one changed formula; bumped #{patch_changes[:formulae]}" - end - odie "Can not bump if non-formula files are changed" unless patch_changes[:others].empty? - end - if is_bumpable - old_versions = current_versions_from_info_external(patch_changes[:formulae].first) - end - patch_puller.apply_patch - - changed_formulae_names = [] - - if tap - Utils.popen_read( - "git", "diff-tree", "-r", "--name-only", - "--diff-filter=AM", orig_revision, "HEAD", "--", tap.formula_dir.to_s - ).each_line do |line| - next unless line.end_with? ".rb\n" - name = "#{tap.name}/#{File.basename(line.chomp, ".rb")}" - changed_formulae_names << name - end - end - - fetch_bottles = false - changed_formulae_names.each do |name| - next if ENV["HOMEBREW_DISABLE_LOAD_FORMULA"] - - begin - f = Formula[name] - # Make sure we catch syntax errors. - rescue Exception - next - end - - if ARGV.include? "--bottle" - if f.bottle_unneeded? - ohai "#{f}: skipping unneeded bottle." - elsif f.bottle_disabled? - ohai "#{f}: skipping disabled bottle: #{f.bottle_disable_reason}" - else - fetch_bottles = true - end - else - next unless f.bottle_defined? - opoo "#{f.full_name} has a bottle: do you need to update it with --bottle?" - end - end - - orig_message = message = `git log HEAD^.. --format=%B` - if issue && !ARGV.include?("--clean") - ohai "Patch closes issue ##{issue}" - close_message = "Closes ##{issue}." - # If this is a pull request, append a close message. - message += "\n#{close_message}" unless message.include? close_message - end - - if changed_formulae_names.empty? - odie "cannot bump: no changed formulae found after applying patch" if do_bump - is_bumpable = false - end - - is_bumpable = false if ARGV.include?("--clean") - is_bumpable = false if ENV["HOMEBREW_DISABLE_LOAD_FORMULA"] - - if is_bumpable - formula = Formula[changed_formulae_names.first] - new_versions = current_versions_from_info_external(patch_changes[:formulae].first) - orig_subject = message.empty? ? "" : message.lines.first.chomp - bump_subject = subject_for_bump(formula, old_versions, new_versions) - if do_bump - odie "No version changes found for #{formula.name}" if bump_subject.nil? - unless orig_subject == bump_subject - ohai "New bump commit subject: #{bump_subject}" - pbcopy bump_subject unless ARGV.include? "--no-pbcopy" - message = "#{bump_subject}\n\n#{message}" - end - elsif bump_subject != orig_subject && !bump_subject.nil? - opoo "Nonstandard bump subject: #{orig_subject}" - opoo "Subject should be: #{bump_subject}" - end - end - - if message != orig_message && !ARGV.include?("--clean") - safe_system "git", "commit", "--amend", "--signoff", "--allow-empty", "-q", "-m", message - end - - # Bottles: Pull bottle block commit and publish bottle files on Bintray - if fetch_bottles - bottle_commit_url = if testing_job - bottle_branch = "testing-bottle-#{testing_job}" - url - else - bottle_branch = "pull-bottle-#{issue}" - "https://github.com/BrewTestBot/homebrew-#{tap.repo}/compare/homebrew:master...pr-#{issue}" - end - - curl "--silent", "--fail", "-o", "/dev/null", "-I", bottle_commit_url - - safe_system "git", "checkout", "--quiet", "-B", bottle_branch, orig_revision - pull_patch bottle_commit_url, "bottle commit" - safe_system "git", "rebase", "--quiet", branch - safe_system "git", "checkout", "--quiet", branch - safe_system "git", "merge", "--quiet", "--ff-only", "--no-edit", bottle_branch - safe_system "git", "branch", "--quiet", "-D", bottle_branch - - # Publish bottles on Bintray - unless ARGV.include? "--no-publish" - published = publish_changed_formula_bottles(tap, changed_formulae_names) - bintray_published_formulae.concat(published) - end - end - - ohai "Patch changed:" - safe_system "git", "diff-tree", "-r", "--stat", orig_revision, "HEAD" - end - - # Verify bintray publishing after all patches have been applied - bintray_published_formulae.uniq! - verify_bintray_published(bintray_published_formulae) - end - - def force_utf8!(str) - str.force_encoding("UTF-8") if str.respond_to?(:force_encoding) - end - - private - - def publish_changed_formula_bottles(tap, changed_formulae_names) - if ENV["HOMEBREW_DISABLE_LOAD_FORMULA"] - raise "Need to load formulae to publish them!" - end - - published = [] - bintray_creds = { :user => ENV["BINTRAY_USER"], :key => ENV["BINTRAY_KEY"] } - if bintray_creds[:user] && bintray_creds[:key] - changed_formulae_names.each do |name| - f = Formula[name] - next if f.bottle_unneeded? || f.bottle_disabled? - ohai "Publishing on Bintray: #{f.name} #{f.pkg_version}" - publish_bottle_file_on_bintray(f, bintray_creds) - published << f.full_name - end - else - opoo "You must set BINTRAY_USER and BINTRAY_KEY to add or update bottles on Bintray!" - end - published - end - - def pull_patch(url, description = nil) - PatchPuller.new(url, description).pull_patch - end - - class PatchPuller - attr_reader :base_url - attr_reader :patch_url - attr_reader :patchpath - - def initialize(url, description = nil) - @base_url = url - # GitHub provides commits/pull-requests raw patches using this URL. - @patch_url = url + ".patch" - @patchpath = HOMEBREW_CACHE + File.basename(patch_url) - @description = description - end - - def pull_patch - fetch_patch - apply_patch - end - - def fetch_patch - extra_msg = @description ? "(#{@description})" : nil - ohai "Fetching patch #{extra_msg}" - puts "Patch: #{patch_url}" - curl patch_url, "-s", "-o", patchpath - end - - def apply_patch - # Applies a patch previously downloaded with fetch_patch() - # Deletes the patch file as a side effect, regardless of success - - ohai "Applying patch" - patch_args = [] - # Normally we don't want whitespace errors, but squashing them can break - # patches so an option is provided to skip this step. - if ARGV.include?("--ignore-whitespace") || ARGV.include?("--clean") - patch_args << "--whitespace=nowarn" - else - patch_args << "--whitespace=fix" - end - - # Fall back to three-way merge if patch does not apply cleanly - patch_args << "-3" - patch_args << patchpath - - begin - safe_system "git", "am", *patch_args - rescue ErrorDuringExecution - if ARGV.include? "--resolve" - odie "Patch failed to apply: try to resolve it." - else - system "git", "am", "--abort" - odie "Patch failed to apply: aborted." - end - ensure - patchpath.unlink - end - end - end - - # List files changed by a patch, partitioned in to those that are (probably) - # formula definitions, and those which aren't. Only applies to patches on - # Homebrew core or taps, based simply on relative pathnames of affected files. - def files_changed_in_patch(patchfile, tap) - files = [] - formulae = [] - others = [] - File.foreach(patchfile) do |line| - files << $1 if line =~ %r{^\+\+\+ b/(.*)} - end - files.each do |file| - if tap && tap.formula_file?(file) - formula_name = File.basename(file, ".rb") - formulae << formula_name unless formulae.include?(formula_name) - else - others << file - end - end - { :files => files, :formulae => formulae, :others => others } - end - - # Get current formula versions without loading formula definition in this process - # Returns info as a hash (type => version), for pull.rb's internal use - # Uses special key :nonexistent => true for nonexistent formulae - def current_versions_from_info_external(formula_name) - info = FormulaInfoFromJson.lookup(formula_name) - versions = {} - if info - [:stable, :devel, :head].each do |spec_type| - versions[spec_type] = info.version(spec_type) - end - else - versions[:nonexistent] = true - end - versions - end - - def subject_for_bump(formula, old, new) - if old[:nonexistent] - # New formula - headline_ver = new[:stable] ? new[:stable] : new[:devel] ? new[:devel] : new[:head] - subject = "#{formula.name} #{headline_ver} (new formula)" - else - # Update to existing formula - subject_strs = [] - formula_name_str = formula.name - if old[:stable] != new[:stable] - if new[:stable].nil? - subject_strs << "remove stable" - formula_name_str += ":" # just for cosmetics - else - subject_strs << formula.version.to_s - end - end - if old[:devel] != new[:devel] - if new[:devel].nil? - # Only bother mentioning if there's no accompanying stable change - if !new[:stable].nil? && old[:stable] == new[:stable] - subject_strs << "remove devel" - formula_name_str += ":" # just for cosmetics - end - else - subject_strs << "#{formula.devel.version} (devel)" - end - end - subject = subject_strs.empty? ? nil : "#{formula_name_str} #{subject_strs.join(", ")}" - end - subject - end - - def pbcopy(text) - Utils.popen_write("pbcopy") { |io| io.write text } - end - - # Publishes the current bottle files for a given formula to Bintray - def publish_bottle_file_on_bintray(f, creds) - repo = Utils::Bottles::Bintray.repository(f.tap) - package = Utils::Bottles::Bintray.package(f.name) - info = FormulaInfoFromJson.lookup(f.name) - if info.nil? - raise "Failed publishing bottle: failed reading formula info for #{f.full_name}" - end - version = info.pkg_version - curl "-w", '\n', "--silent", "--fail", - "-u#{creds[:user]}:#{creds[:key]}", "-X", "POST", - "-H", "Content-Type: application/json", - "-d", '{"publish_wait_for_secs": 0}', - "https://api.bintray.com/content/homebrew/#{repo}/#{package}/#{version}/publish" - end - - # Formula info drawn from an external "brew info --json" call - class FormulaInfoFromJson - # The whole info structure parsed from the JSON - attr_accessor :info - - def initialize(info) - @info = info - end - - # Looks up formula on disk and reads its info - # Returns nil if formula is absent or if there was an error reading it - def self.lookup(name) - json = Utils.popen_read(HOMEBREW_BREW_FILE, "info", "--json=v1", name) - unless $?.success? - return nil - end - Homebrew.force_utf8!(json) - FormulaInfoFromJson.new(Utils::JSON.load(json)[0]) - end - - def bottle_tags() - return [] unless info["bottle"]["stable"] - info["bottle"]["stable"]["files"].keys - end - - def bottle_info(my_bottle_tag = Utils::Bottles.tag) - tag_s = my_bottle_tag.to_s - return nil unless info["bottle"]["stable"] - btl_info = info["bottle"]["stable"]["files"][tag_s] - return nil unless btl_info - BottleInfo.new(btl_info["url"], btl_info["sha256"]) - end - - def bottle_info_any - bottle_info(any_bottle_tag) - end - - def any_bottle_tag - tag = Utils::Bottles.tag - # Prefer native bottles as a convenience for download caching - bottle_tags.include?(tag) ? tag : bottle_tags.first - end - - def version(spec_type) - version_str = info["versions"][spec_type.to_s] - version_str && Version.create(version_str) - end - - def pkg_version(spec_type = :stable) - PkgVersion.new(version(spec_type), revision) - end - - def revision - info["revision"] - end - end - - - # Bottle info as used internally by pull, with alternate platform support - class BottleInfo - # URL of bottle as string - attr_accessor :url - # Expected SHA256 as string - attr_accessor :sha256 - - def initialize(url, sha256) - @url = url - @sha256 = sha256 - end - end - - # Verifies that formulae have been published on Bintray by downloading a bottle file - # for each one. Blocks until the published files are available. - # Raises an error if the verification fails. - # This does not currently work for `brew pull`, because it may have cached the old - # version of a formula. - def verify_bintray_published(formulae_names) - return if formulae_names.empty? - - if ENV["HOMEBREW_DISABLE_LOAD_FORMULA"] - raise "Need to load formulae to verify their publication!" - end - - ohai "Verifying bottles published on Bintray" - formulae = formulae_names.map { |n| Formula[n] } - max_retries = 300 # shared among all bottles - poll_retry_delay_seconds = 2 - - HOMEBREW_CACHE.cd do - formulae.each do |f| - retry_count = 0 - wrote_dots = false - # Choose arbitrary bottle just to get the host/port for Bintray right - jinfo = FormulaInfoFromJson.lookup(f.full_name) - unless jinfo - opoo "Cannot publish bottle: Failed reading info for formula #{f.full_name}" - next - end - bottle_info = jinfo.bottle_info(jinfo.bottle_tags.first) - unless bottle_info - opoo "No bottle defined in formula #{f.full_name}" - next - end - - # Poll for publication completion using a quick partial HEAD, to avoid spurious error messages - # 401 error is normal while file is still in async publishing process - url = URI(bottle_info.url) - puts "Verifying bottle: #{File.basename(url.path)}" - http = Net::HTTP.new(url.host, url.port) - http.use_ssl = true - retry_count = 0 - http.start do - while true do - req = Net::HTTP::Head.new bottle_info.url - req.initialize_http_header "User-Agent" => HOMEBREW_USER_AGENT_RUBY - res = http.request req - if res.is_a?(Net::HTTPSuccess) - break - elsif res.is_a?(Net::HTTPClientError) - if retry_count >= max_retries - raise "Failed to find published #{f} bottle at #{url}!" - end - print(wrote_dots ? "." : "Waiting on Bintray.") - wrote_dots = true - sleep poll_retry_delay_seconds - retry_count += 1 - else - raise "Failed to find published #{f} bottle at #{url} (#{res.code} #{res.message})!" - end - end - end - - # Actual download and verification - # We do a retry on this, too, because sometimes the external curl will fail even - # when the prior HEAD has succeeded. - puts "\n" if wrote_dots - filename = File.basename(url.path) - curl_retry_delay_seconds = 4 - max_curl_retries = 1 - retry_count = 0 - # We're in the cache; make sure to force re-download - while true do - begin - curl url, "-o", filename - break - rescue - if retry_count >= max_curl_retries - raise "Failed to download #{f} bottle from #{url}!" - end - puts "curl download failed; retrying in #{curl_retry_delay_seconds} sec" - sleep curl_retry_delay_seconds - curl_retry_delay_seconds *= 2 - retry_count += 1 - end - end - checksum = Checksum.new(:sha256, bottle_info.sha256) - Pathname.new(filename).verify_checksum(checksum) - end - end - end -end diff --git a/Library/Homebrew/cmd/tap-readme.rb b/Library/Homebrew/cmd/tap-readme.rb deleted file mode 100644 index ad115a53e..000000000 --- a/Library/Homebrew/cmd/tap-readme.rb +++ /dev/null @@ -1,36 +0,0 @@ -#: @hide_from_man_page -#: * `tap_readme` [`-v`] <name>: -#: Generate the README.md file for a new tap. - -module Homebrew - def tap_readme - name = ARGV.first - raise "A name is required" if name.nil? - - titleized_name = name.dup - titleized_name[0..0] = titleized_name[0..0].upcase - - template = <<-EOS.undent - # Homebrew #{titleized_name} - - ## How do I install these formulae? - `brew install homebrew/#{name}/<formula>` - - Or `brew tap homebrew/#{name}` and then `brew install <formula>`. - - Or install via URL (which will not receive updates): - - ``` - brew install https://raw.githubusercontent.com/Homebrew/homebrew-#{name}/master/<formula>.rb - ``` - - ## Documentation - `brew help`, `man brew` or check [Homebrew's documentation](https://github.com/Homebrew/brew/tree/master/share/doc/homebrew#readme). - EOS - - puts template if ARGV.verbose? - path = HOMEBREW_LIBRARY/"Taps/homebrew/homebrew-#{name}/README.md" - raise "#{path} already exists" if path.exist? - path.write template - end -end diff --git a/Library/Homebrew/cmd/test.rb b/Library/Homebrew/cmd/test.rb deleted file mode 100644 index a80fa5e4f..000000000 --- a/Library/Homebrew/cmd/test.rb +++ /dev/null @@ -1,89 +0,0 @@ -#: * `test` [`--devel`|`--HEAD`] [`--debug`] [`--keep-tmp`] <formula>: -#: A few formulae provide a test method. `brew test` <formula> runs this -#: test method. There is no standard output or return code, but it should -#: generally indicate to the user if something is wrong with the installed -#: formula. -#: -#: To test the development or head version of a formula, use `--devel` or -#: `--HEAD`. -#: -#: If `--debug` is passed and the test fails, an interactive debugger will be -#: launched with access to IRB or a shell inside the temporary test directory. -#: -#: If `--keep-tmp` is passed, the temporary files created for the test are -#: not deleted. -#: -#: Example: `brew install jruby && brew test jruby` - -require "extend/ENV" -require "formula_assertions" -require "sandbox" -require "timeout" - -module Homebrew - def test - raise FormulaUnspecifiedError if ARGV.named.empty? - - ARGV.resolved_formulae.each do |f| - # Cannot test uninstalled formulae - unless f.installed? - ofail "Testing requires the latest version of #{f.full_name}" - next - end - - # Cannot test formulae without a test method - unless f.test_defined? - ofail "#{f.full_name} defines no test" - next - end - - puts "Testing #{f.full_name}" - - env = ENV.to_hash - - begin - args = %W[ - #{RUBY_PATH} - -W0 - -I #{HOMEBREW_LOAD_PATH} - -- - #{HOMEBREW_LIBRARY_PATH}/test.rb - #{f.path} - ].concat(ARGV.options_only) - - if f.head? - args << "--HEAD" - elsif f.devel? - args << "--devel" - end - - Sandbox.print_sandbox_message if Sandbox.test? - - Utils.safe_fork do - if Sandbox.test? - sandbox = Sandbox.new - f.logs.mkpath - sandbox.record_log(f.logs/"test.sandbox.log") - sandbox.allow_write_temp_and_cache - sandbox.allow_write_log(f) - sandbox.allow_write_xcode - sandbox.allow_write_path(HOMEBREW_PREFIX/"var/cache") - sandbox.allow_write_path(HOMEBREW_PREFIX/"var/log") - sandbox.allow_write_path(HOMEBREW_PREFIX/"var/run") - sandbox.exec(*args) - else - exec(*args) - end - end - rescue Assertions::FailedAssertion => e - ofail "#{f.full_name}: failed" - puts e.message - rescue Exception => e - ofail "#{f.full_name}: failed" - puts e, e.backtrace - ensure - ENV.replace(env) - end - end - end -end diff --git a/Library/Homebrew/cmd/tests.rb b/Library/Homebrew/cmd/tests.rb deleted file mode 100644 index be8f72ace..000000000 --- a/Library/Homebrew/cmd/tests.rb +++ /dev/null @@ -1,63 +0,0 @@ -#: @hide_from_man_page -#: * `tests` [`-v`] [`--coverage`] [`--generic`] [`--no-compat`] [`--only=`<test_script/test_method>] [`--seed` <seed>] [`--trace`] [`--online`] [`--official-cmd-taps`]: -#: Run Homebrew's unit and integration tests. - -require "fileutils" -require "tap" - -module Homebrew - def tests - (HOMEBREW_LIBRARY/"Homebrew/test").cd do - ENV["HOMEBREW_NO_ANALYTICS_THIS_RUN"] = "1" - ENV["TESTOPTS"] = "-v" if ARGV.verbose? - ENV["HOMEBREW_NO_COMPAT"] = "1" if ARGV.include? "--no-compat" - ENV["HOMEBREW_TEST_GENERIC_OS"] = "1" if ARGV.include? "--generic" - ENV["HOMEBREW_NO_GITHUB_API"] = "1" unless ARGV.include? "--online" - if ARGV.include? "--official-cmd-taps" - ENV["HOMEBREW_TEST_OFFICIAL_CMD_TAPS"] = "1" - end - - if ARGV.include? "--coverage" - ENV["HOMEBREW_TESTS_COVERAGE"] = "1" - FileUtils.rm_f "coverage/.resultset.json" - end - - # Override author/committer as global settings might be invalid and thus - # will cause silent failure during the setup of dummy Git repositories. - %w[AUTHOR COMMITTER].each do |role| - ENV["GIT_#{role}_NAME"] = "brew tests" - ENV["GIT_#{role}_EMAIL"] = "brew-tests@localhost" - end - - Homebrew.install_gem_setup_path! "bundler" - unless quiet_system("bundle", "check") - system "bundle", "install", "--path", "vendor/bundle" - end - - # Make it easier to reproduce test runs. - ENV["SEED"] = ARGV.next if ARGV.include? "--seed" - - args = [] - args << "--trace" if ARGV.include? "--trace" - if ARGV.value("only") - ENV["HOMEBREW_TESTS_ONLY"] = "1" - test_name, test_method = ARGV.value("only").split("/", 2) - args << "TEST=test_#{test_name}.rb" - args << "TESTOPTS=--name=test_#{test_method}" if test_method - end - args += ARGV.named.select { |v| v[/^TEST(OPTS)?=/] } - system "bundle", "exec", "rake", "test", *args - - Homebrew.failed = !$?.success? - - if (fs_leak_log = HOMEBREW_LIBRARY/"Homebrew/test/fs_leak_log").file? - fs_leak_log_content = fs_leak_log.read - unless fs_leak_log_content.empty? - opoo "File leak is detected" - puts fs_leak_log_content - Homebrew.failed = true - end - end - end - end -end |
