From 383d774ff782dc4d49d0c1d9a164bc4a52e5b423 Mon Sep 17 00:00:00 2001 From: Michael Cho Date: Sat, 2 Mar 2024 19:18:35 -0500 Subject: [PATCH] language/python: add types Also add `Virtualenv` getters to get `Pathname`s to the virtualenv's `root` and `site_packages`. Signed-off-by: Michael Cho --- Library/Homebrew/language/python.rb | 81 +++++++++++++++++++++++------ 1 file changed, 66 insertions(+), 15 deletions(-) diff --git a/Library/Homebrew/language/python.rb b/Library/Homebrew/language/python.rb index 5887cc6ab2080..27880d88e3270 100644 --- a/Library/Homebrew/language/python.rb +++ b/Library/Homebrew/language/python.rb @@ -6,6 +6,7 @@ module Language # # @api public module Python + sig { params(python: T.any(String, Pathname)).returns(T.nilable(Version)) } def self.major_minor_version(python) version = `#{python} --version 2>&1`.chomp[/(\d\.\d+)/, 1] return unless version @@ -13,10 +14,12 @@ def self.major_minor_version(python) Version.new(version) end + sig { params(python: T.any(String, Pathname)).returns(Pathname) } def self.homebrew_site_packages(python = "python3.7") HOMEBREW_PREFIX/site_packages(python) end + sig { params(python: T.any(String, Pathname)).returns(String) } def self.site_packages(python = "python3.7") if (python == "pypy") || (python == "pypy3") "site-packages" @@ -38,7 +41,7 @@ def self.each_python(build, &block) ENV["PYTHONPATH"] = if python_formula.latest_version_installed? nil else - homebrew_site_packages(python) + homebrew_site_packages(python).to_s end block&.call python, version end @@ -70,6 +73,7 @@ def self.in_sys_path?(python, path) quiet_system python, "-c", script end + sig { params(prefix: Pathname, python: T.any(String, Pathname)).returns(T::Array[String]) } def self.setup_install_args(prefix, python = "python3") shim = <<~PYTHON import setuptools, tokenize @@ -110,8 +114,8 @@ def python_shebang_rewrite_info(python_path) ) end - sig { params(formula: T.untyped, use_python_from_path: T::Boolean).returns(Utils::Shebang::RewriteInfo) } - def detected_python_shebang(formula = self, use_python_from_path: false) + sig { params(formula: Formula, use_python_from_path: T::Boolean).returns(Utils::Shebang::RewriteInfo) } + def detected_python_shebang(formula = T.cast(self, Formula), use_python_from_path: false) python_path = if use_python_from_path "/usr/bin/env python3" else @@ -137,12 +141,21 @@ module Virtualenv # # @param venv_root [Pathname, String] the path to the root of the virtualenv # (often `libexec/"venv"`) - # @param python [String] which interpreter to use (e.g. "python3" + # @param python [String, Pathname] which interpreter to use (e.g. "python3" # or "python3.x") # @param formula [Formula] the active {Formula} # @return [Virtualenv] a {Virtualenv} instance - def virtualenv_create(venv_root, python = "python", formula = self, system_site_packages: true, - without_pip: true) + sig { + params( + venv_root: T.any(String, Pathname), + python: T.any(String, Pathname), + formula: Formula, + system_site_packages: T::Boolean, + without_pip: T::Boolean, + ).returns(Virtualenv) + } + def virtualenv_create(venv_root, python = "python", formula = T.cast(self, Formula), + system_site_packages: true, without_pip: true) # Limit deprecation to 3.12+ for now (or if we can't determine the version). # Some used this argument for setuptools, which we no longer bundle since 3.12. unless without_pip @@ -169,9 +182,7 @@ def virtualenv_create(venv_root, python = "python", formula = self, system_site_ "import site; site.addsitedir('#{dep_site_packages}')\n" end - unless pth_contents.empty? - (venv_root/Language::Python.site_packages(python)/"homebrew_deps.pth").write pth_contents.join - end + (venv.site_packages/"homebrew_deps.pth").write pth_contents.join unless pth_contents.empty? venv end @@ -184,6 +195,7 @@ def virtualenv_create(venv_root, python = "python", formula = self, system_site_ # corresponding depends_on statement. # # @api private + sig { params(python: String).returns(T::Boolean) } def needs_python?(python) return true if build.with?(python) @@ -197,6 +209,14 @@ def needs_python?(python) # formula preference for python or python@x.y, or to resolve an ambiguous # case where it's not clear whether python or python@x.y should be the # default guess. + sig { + params( + using: T.nilable(String), + system_site_packages: T::Boolean, + without_pip: T::Boolean, + link_manpages: T::Boolean, + ).returns(Virtualenv) + } def virtualenv_install_with_resources(using: nil, system_site_packages: true, without_pip: true, link_manpages: false) python = using @@ -205,13 +225,13 @@ def virtualenv_install_with_resources(using: nil, system_site_packages: true, wi raise FormulaUnknownPythonError, self if wanted.empty? raise FormulaAmbiguousPythonError, self if wanted.size > 1 - python = wanted.first + python = T.must(wanted.first) python = "python3" if python == "python" end venv = virtualenv_create(libexec, python.delete("@"), system_site_packages: system_site_packages, without_pip: without_pip) venv.pip_install resources - venv.pip_install_and_link(buildpath, link_manpages: link_manpages) + venv.pip_install_and_link(T.must(buildpath), link_manpages: link_manpages) venv end @@ -229,17 +249,29 @@ class Virtualenv # @param formula [Formula] the active {Formula} # @param venv_root [Pathname, String] the path to the root of the # virtualenv - # @param python [String] which interpreter to use, e.g. "python" or - # "python2" + # @param python [String, Pathname] which interpreter to use, e.g. + # "python" or "python2" + sig { params(formula: Formula, venv_root: T.any(String, Pathname), python: T.any(String, Pathname)).void } def initialize(formula, venv_root, python) @formula = formula @venv_root = Pathname.new(venv_root) @python = python end + sig { returns(Pathname) } + def root + @venv_root + end + + sig { returns(Pathname) } + def site_packages + @venv_root/Language::Python.site_packages(@python) + end + # Obtains a copy of the virtualenv library and creates a new virtualenv on disk. # # @return [void] + sig { params(system_site_packages: T::Boolean, without_pip: T::Boolean).void } def create(system_site_packages: true, without_pip: true) return if (@venv_root/"bin/python").exist? @@ -285,13 +317,19 @@ def create(system_site_packages: true, without_pip: true) # Multiline strings are allowed and treated as though they represent # the contents of a `requirements.txt`. # @return [void] + sig { + params( + targets: T.any(String, Pathname, Resource, T::Array[T.any(String, Pathname, Resource)]), + build_isolation: T::Boolean, + ).void + } def pip_install(targets, build_isolation: true) targets = Array(targets) targets.each do |t| - if t.respond_to? :stage + if t.is_a?(Resource) t.stage { do_install(Pathname.pwd, build_isolation: build_isolation) } else - t = t.lines.map(&:strip) if t.respond_to?(:lines) && t.include?("\n") + t = t.lines.map(&:strip) if t.is_a?(String) && t.include?("\n") do_install(t, build_isolation: build_isolation) end end @@ -302,6 +340,13 @@ def pip_install(targets, build_isolation: true) # # @param (see #pip_install) # @return (see #pip_install) + sig { + params( + targets: T.any(String, Pathname, Resource, T::Array[T.any(String, Pathname, Resource)]), + link_manpages: T::Boolean, + build_isolation: T::Boolean, + ).void + } def pip_install_and_link(targets, link_manpages: false, build_isolation: true) bin_before = Dir[@venv_root/"bin/*"].to_set man_before = Dir[@venv_root/"share/man/man*/*"].to_set if link_manpages @@ -322,6 +367,12 @@ def pip_install_and_link(targets, link_manpages: false, build_isolation: true) private + sig { + params( + targets: T.any(String, Pathname, T::Array[T.any(String, Pathname)]), + build_isolation: T::Boolean, + ).void + } def do_install(targets, build_isolation: true) targets = Array(targets) args = @formula.std_pip_args(prefix: false, build_isolation: build_isolation)