Skip to content

Commit 22c41d3

Browse files
committed
feat: add pypi_packages formula DSL
Signed-off-by: botantony <[email protected]>
1 parent c28b3bc commit 22c41d3

File tree

4 files changed

+129
-17
lines changed

4 files changed

+129
-17
lines changed

Library/Homebrew/ast_constants.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
[{ name: :cxxstdlib_check, type: :method_call }],
4444
[{ name: :link_overwrite, type: :method_call }],
4545
[{ name: :fails_with, type: :method_call }, { name: :fails_with, type: :block_call }],
46+
[{ name: :pypi_packages, type: :method_call }],
4647
[{ name: :resource, type: :block_call }],
4748
[{ name: :patch, type: :method_call }, { name: :patch, type: :block_call }],
4849
[{ name: :needs, type: :method_call }],

Library/Homebrew/formula.rb

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
require "api"
4343
require "api_hashable"
4444
require "utils/output"
45+
require "pypi_packages"
4546

4647
# A formula provides instructions and metadata for Homebrew to install a piece
4748
# of software. Every Homebrew formula is a {Formula}.
@@ -219,6 +220,9 @@ class Formula
219220
sig { returns(T.any(BuildOptions, Tab)) }
220221
attr_reader :build
221222

223+
sig { returns(PypiPackages) }
224+
attr_reader :pypi_packages_info
225+
222226
# Whether this formula should be considered outdated
223227
# if the target of the alias it was installed with has since changed.
224228
# Defaults to true.
@@ -267,6 +271,8 @@ def initialize(name, path, spec, alias_path: nil, tap: nil, force_bottle: false)
267271
Tap.from_path(path)
268272
end
269273

274+
@pypi_packages_info = T.let(PypiPackages.from_json_file(@tap, @name), PypiPackages)
275+
270276
@full_name = T.let(T.must(full_name_with_optional_tap(name)), String)
271277
@full_alias_name = T.let(full_name_with_optional_tap(@alias_name), T.nilable(String))
272278

@@ -584,6 +590,47 @@ def bottle_for_tag(tag = nil)
584590
# @see .api_source
585591
delegate api_source: :"self.class"
586592

593+
# Information about PyPI packages for this formula.
594+
# @!method pypi_packages
595+
# @see .pypi_packages
596+
delegate pypi_packages: :"self.class"
597+
598+
# TODO: add docs
599+
# @api public
600+
sig {
601+
params(
602+
package_name: T.nilable(String),
603+
extra_packages: T.nilable(T.any(String, T::Array[String])),
604+
exclude_packages: T.nilable(T.any(String, T::Array[String])),
605+
dependencies: T.nilable(T.any(String, T::Array[String])),
606+
needs_manual_update: T::Boolean,
607+
).void
608+
}
609+
def pypi_packages(
610+
package_name: nil,
611+
extra_packages: nil,
612+
exclude_packages: nil,
613+
dependencies: nil,
614+
needs_manual_update: false
615+
)
616+
if needs_manual_update
617+
@pypi_packages_info = PypiPackages.new needs_manual_update: true
618+
return
619+
end
620+
621+
if [package_name, extra_packages, exclude_packages, dependencies].all?(&:nil?)
622+
raise ArgumentError, "must provide at least one argument"
623+
end
624+
625+
# Sadly `v1, v2, v3 = [v1, v2, v3].map { |x| Array(x) }` does not work
626+
# for typechecker
627+
extra_packages = Array(extra_packages)
628+
exclude_packages = Array(exclude_packages)
629+
dependencies = Array(dependencies)
630+
631+
@pypi_packages_info = PypiPackages.new(package_name:, extra_packages:, exclude_packages:, dependencies:)
632+
end
633+
587634
sig { returns(Formula) }
588635
def fully_loaded_formula
589636
@fully_loaded_formula ||= if loaded_from_stub?

Library/Homebrew/pypi_packages.rb

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
# typed: strict
2+
# frozen_string_literal: true
3+
4+
# Helper class for `pypi_packages` DSL.
5+
# @api internal
6+
class PypiPackages
7+
sig { returns(T.nilable(String)) }
8+
attr_reader :package_name
9+
10+
sig { returns(T::Array[String]) }
11+
attr_reader :extra_packages
12+
13+
sig { returns(T::Array[String]) }
14+
attr_reader :exclude_packages
15+
16+
sig { returns(T::Array[String]) }
17+
attr_reader :dependencies
18+
19+
sig { params(tap: T.nilable(Tap), formula_name: String).returns(T.attached_class) }
20+
def self.from_json_file(tap, formula_name)
21+
list_entry = tap&.pypi_formula_mappings&.fetch(formula_name, nil)
22+
23+
return new(defined_pypi_mapping: false) unless list_entry.present?
24+
25+
case T.cast(list_entry, T.any(FalseClass, String, T::Hash[String, T.any(String, T::Array[String])]))
26+
when false
27+
new needs_manual_update: true
28+
when String
29+
new package_name: list_entry
30+
when Hash
31+
package_name = list_entry["package_name"]
32+
extra_packages = list_entry.fetch("extra_packages", [])
33+
exclude_packages = list_entry.fetch("exclude_packages", [])
34+
dependencies = list_entry.fetch("dependencies", [])
35+
36+
new package_name:, extra_packages:, exclude_packages:, dependencies:
37+
end
38+
end
39+
40+
sig {
41+
params(
42+
package_name: T.nilable(String),
43+
extra_packages: T::Array[String],
44+
exclude_packages: T::Array[String],
45+
dependencies: T::Array[String],
46+
needs_manual_update: T::Boolean,
47+
defined_pypi_mapping: T::Boolean,
48+
).void
49+
}
50+
def initialize(
51+
package_name: nil,
52+
extra_packages: [],
53+
exclude_packages: [],
54+
dependencies: [],
55+
needs_manual_update: false,
56+
defined_pypi_mapping: true
57+
)
58+
@package_name = T.let(package_name, T.nilable(String))
59+
@extra_packages = T.let(extra_packages, T::Array[String])
60+
@exclude_packages = T.let(exclude_packages, T::Array[String])
61+
@dependencies = T.let(dependencies, T::Array[String])
62+
@needs_manual_update = T.let(needs_manual_update, T::Boolean)
63+
@defined_pypi_mapping = defined_pypi_mapping
64+
end
65+
66+
sig { returns(T::Boolean) }
67+
def defined_pypi_mapping? = @defined_pypi_mapping
68+
69+
sig { returns(T::Boolean) }
70+
def needs_manual_update? = @needs_manual_update
71+
end

Library/Homebrew/utils/pypi.rb

Lines changed: 10 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -237,23 +237,16 @@ def self.update_python_resources!(formula, version: nil, package_name: nil, extr
237237
exclude_packages: nil, dependencies: nil, install_dependencies: false,
238238
print_only: false, silent: false, verbose: false,
239239
ignore_errors: false, ignore_non_pypi_packages: false)
240-
auto_update_list = formula.tap&.pypi_formula_mappings
241-
if auto_update_list.present? && auto_update_list.key?(formula.full_name) &&
242-
package_name.blank? && extra_packages.blank? && exclude_packages.blank?
243-
244-
list_entry = auto_update_list[formula.full_name]
245-
case list_entry
246-
when false
247-
unless print_only
248-
odie "The resources for \"#{formula.name}\" need special attention. Please update them manually."
249-
end
250-
when String
251-
package_name = list_entry
252-
when Hash
253-
package_name = list_entry["package_name"]
254-
extra_packages = list_entry["extra_packages"]
255-
exclude_packages = list_entry["exclude_packages"]
256-
dependencies = list_entry["dependencies"]
240+
list_entry = formula.pypi_packages_info
241+
if list_entry.defined_pypi_mapping? && package_name.blank? && extra_packages.blank? && exclude_packages.blank?
242+
243+
if list_entry.needs_manual_update? && !print_only
244+
odie "The resources for \"#{formula.name}\" need special attention. Please update them manually."
245+
else
246+
package_name = list_entry.package_name
247+
extra_packages = list_entry.extra_packages
248+
exclude_packages = list_entry.exclude_packages
249+
dependencies = list_entry.dependencies
257250
end
258251
end
259252

0 commit comments

Comments
 (0)