| 
									
										
										
										
											2023-04-01 18:56:42 -07:00
										 |  |  | # typed: true | 
					
						
							| 
									
										
										
										
											2020-07-27 10:37:46 -04:00
										 |  |  | # frozen_string_literal: true | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-15 09:34:15 -04:00
										 |  |  | require "utils/inreplace" | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-05 17:17:03 -05:00
										 |  |  | # Helper functions for updating PyPI resources. | 
					
						
							| 
									
										
										
										
											2020-07-27 10:37:46 -04:00
										 |  |  | module PyPI | 
					
						
							|  |  |  |   PYTHONHOSTED_URL_PREFIX = "https://files.pythonhosted.org/packages/" | 
					
						
							| 
									
										
										
										
											2020-08-26 09:39:51 +02:00
										 |  |  |   private_constant :PYTHONHOSTED_URL_PREFIX | 
					
						
							| 
									
										
										
										
											2020-07-27 10:37:46 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-02 20:46:46 -04:00
										 |  |  |   # Represents a Python package. | 
					
						
							|  |  |  |   # This package can be a PyPI package (either by name/version or PyPI distribution URL), | 
					
						
							|  |  |  |   # or it can be a non-PyPI URL. | 
					
						
							| 
									
										
										
										
											2020-11-22 15:23:43 -05:00
										 |  |  |   class Package | 
					
						
							| 
									
										
										
										
											2024-02-22 09:33:10 -05:00
										 |  |  |     sig { params(package_string: String, is_url: T::Boolean, python_name: String).void } | 
					
						
							|  |  |  |     def initialize(package_string, is_url: false, python_name: "python") | 
					
						
							| 
									
										
										
										
											2020-11-22 15:23:43 -05:00
										 |  |  |       @pypi_info = nil | 
					
						
							| 
									
										
										
										
											2023-07-02 20:46:46 -04:00
										 |  |  |       @package_string = package_string | 
					
						
							|  |  |  |       @is_url = is_url | 
					
						
							|  |  |  |       @is_pypi_url = package_string.start_with? PYTHONHOSTED_URL_PREFIX | 
					
						
							| 
									
										
										
										
											2024-02-22 09:33:10 -05:00
										 |  |  |       @python_name = python_name | 
					
						
							| 
									
										
										
										
											2023-07-02 20:46:46 -04:00
										 |  |  |     end | 
					
						
							| 
									
										
										
										
											2023-06-03 23:09:49 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-02 20:46:46 -04:00
										 |  |  |     sig { returns(String) } | 
					
						
							|  |  |  |     def name | 
					
						
							|  |  |  |       basic_metadata if @name.blank? | 
					
						
							|  |  |  |       @name | 
					
						
							|  |  |  |     end | 
					
						
							| 
									
										
										
										
											2023-06-03 23:09:49 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-02 20:46:46 -04:00
										 |  |  |     sig { returns(T::Array[T.nilable(String)]) } | 
					
						
							|  |  |  |     def extras | 
					
						
							|  |  |  |       basic_metadata if @extras.blank? | 
					
						
							|  |  |  |       @extras | 
					
						
							|  |  |  |     end | 
					
						
							| 
									
										
										
										
											2020-07-31 10:10:07 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-02 20:46:46 -04:00
										 |  |  |     sig { returns(T.nilable(String)) } | 
					
						
							|  |  |  |     def version | 
					
						
							|  |  |  |       basic_metadata if @version.blank? | 
					
						
							|  |  |  |       @version | 
					
						
							|  |  |  |     end | 
					
						
							| 
									
										
										
										
											2020-11-22 15:23:43 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-02 20:46:46 -04:00
										 |  |  |     sig { params(new_version: String).void } | 
					
						
							|  |  |  |     def version=(new_version) | 
					
						
							|  |  |  |       raise ArgumentError, "can't update version for non-PyPI packages" unless valid_pypi_package? | 
					
						
							| 
									
										
										
										
											2020-11-22 15:23:43 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-02 20:46:46 -04:00
										 |  |  |       @version = new_version | 
					
						
							| 
									
										
										
										
											2023-06-04 23:31:40 -04:00
										 |  |  |     end | 
					
						
							| 
									
										
										
										
											2020-11-22 15:23:43 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-02 20:46:46 -04:00
										 |  |  |     sig { returns(T::Boolean) } | 
					
						
							|  |  |  |     def valid_pypi_package? | 
					
						
							|  |  |  |       @is_pypi_url || !@is_url | 
					
						
							|  |  |  |     end | 
					
						
							| 
									
										
										
										
											2020-11-22 15:23:43 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-30 11:10:23 +02:00
										 |  |  |     # Get name, URL, SHA-256 checksum and latest version for a given package. | 
					
						
							| 
									
										
										
										
											2023-07-02 20:46:46 -04:00
										 |  |  |     # This only works for packages from PyPI or from a PyPI URL; packages | 
					
						
							|  |  |  |     # derived from non-PyPI URLs will produce `nil` here. | 
					
						
							|  |  |  |     sig { params(new_version: T.nilable(T.any(String, Version))).returns(T.nilable(T::Array[String])) } | 
					
						
							|  |  |  |     def pypi_info(new_version: nil) | 
					
						
							|  |  |  |       return unless valid_pypi_package? | 
					
						
							|  |  |  |       return @pypi_info if @pypi_info.present? && new_version.blank? | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       new_version ||= version | 
					
						
							|  |  |  |       metadata_url = if new_version.present? | 
					
						
							|  |  |  |         "https://pypi.org/pypi/#{name}/#{new_version}/json" | 
					
						
							| 
									
										
										
										
											2020-11-22 15:23:43 -05:00
										 |  |  |       else | 
					
						
							| 
									
										
										
										
											2023-07-02 20:46:46 -04:00
										 |  |  |         "https://pypi.org/pypi/#{name}/json" | 
					
						
							| 
									
										
										
										
											2020-11-22 15:23:43 -05:00
										 |  |  |       end | 
					
						
							| 
									
										
										
										
											2023-09-04 22:17:57 -04:00
										 |  |  |       out, _, status = Utils::Curl.curl_output metadata_url, "--location", "--fail" | 
					
						
							| 
									
										
										
										
											2020-11-22 15:23:43 -05:00
										 |  |  | 
 | 
					
						
							|  |  |  |       return unless status.success? | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       begin | 
					
						
							|  |  |  |         json = JSON.parse out | 
					
						
							|  |  |  |       rescue JSON::ParserError | 
					
						
							|  |  |  |         return | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-14 11:58:36 -04:00
										 |  |  |       dist = json["urls"].find do |url| | 
					
						
							|  |  |  |         url["packagetype"] == "sdist" | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-14 12:51:41 -04:00
										 |  |  |       # If there isn't an sdist, we use the first universal wheel. | 
					
						
							| 
									
										
										
										
											2024-07-14 11:58:36 -04:00
										 |  |  |       if dist.nil? | 
					
						
							|  |  |  |         dist = json["urls"].find do |url| | 
					
						
							|  |  |  |           url["filename"].end_with?("-none-any.whl") | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       return if dist.nil? | 
					
						
							| 
									
										
										
										
											2020-11-22 15:23:43 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-04-27 10:14:17 -06:00
										 |  |  |       @pypi_info = [ | 
					
						
							| 
									
										
										
										
											2024-07-14 11:58:36 -04:00
										 |  |  |         PyPI.normalize_python_package(json["info"]["name"]), dist["url"], | 
					
						
							|  |  |  |         dist["digests"]["sha256"], json["info"]["version"] | 
					
						
							| 
									
										
										
										
											2023-04-27 10:14:17 -06:00
										 |  |  |       ] | 
					
						
							| 
									
										
										
										
											2020-11-22 15:23:43 -05:00
										 |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     sig { returns(String) } | 
					
						
							|  |  |  |     def to_s | 
					
						
							| 
									
										
										
										
											2023-07-02 20:46:46 -04:00
										 |  |  |       if valid_pypi_package? | 
					
						
							|  |  |  |         out = name | 
					
						
							|  |  |  |         out += "[#{extras.join(",")}]" if extras.present? | 
					
						
							|  |  |  |         out += "==#{version}" if version.present? | 
					
						
							|  |  |  |         out | 
					
						
							|  |  |  |       else | 
					
						
							|  |  |  |         @package_string | 
					
						
							|  |  |  |       end | 
					
						
							| 
									
										
										
										
											2020-11-18 02:25:55 -05:00
										 |  |  |     end | 
					
						
							| 
									
										
										
										
											2020-07-27 10:37:46 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-22 15:23:43 -05:00
										 |  |  |     sig { params(other: Package).returns(T::Boolean) } | 
					
						
							|  |  |  |     def same_package?(other) | 
					
						
							| 
									
										
										
										
											2023-07-02 20:46:46 -04:00
										 |  |  |       # These names are pre-normalized, so we can compare them directly. | 
					
						
							|  |  |  |       name == other.name | 
					
						
							| 
									
										
										
										
											2020-11-22 15:23:43 -05:00
										 |  |  |     end | 
					
						
							| 
									
										
										
										
											2020-07-27 10:37:46 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-14 03:26:40 -08:00
										 |  |  |     # Compare only names so we can use .include? and .uniq on a Package array | 
					
						
							| 
									
										
										
										
											2020-11-22 15:23:43 -05:00
										 |  |  |     sig { params(other: Package).returns(T::Boolean) } | 
					
						
							|  |  |  |     def ==(other) | 
					
						
							|  |  |  |       same_package?(other) | 
					
						
							| 
									
										
										
										
											2020-07-27 10:37:46 -04:00
										 |  |  |     end | 
					
						
							| 
									
										
										
										
											2021-11-14 03:26:40 -08:00
										 |  |  |     alias eql? == | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     sig { returns(Integer) } | 
					
						
							|  |  |  |     def hash | 
					
						
							| 
									
										
										
										
											2023-07-02 20:46:46 -04:00
										 |  |  |       name.hash | 
					
						
							| 
									
										
										
										
											2021-11-14 03:26:40 -08:00
										 |  |  |     end | 
					
						
							| 
									
										
										
										
											2020-07-27 10:37:46 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-22 15:23:43 -05:00
										 |  |  |     sig { params(other: Package).returns(T.nilable(Integer)) } | 
					
						
							|  |  |  |     def <=>(other) | 
					
						
							| 
									
										
										
										
											2023-07-02 20:46:46 -04:00
										 |  |  |       name <=> other.name | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     private | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Returns [name, [extras], version] for this package. | 
					
						
							|  |  |  |     def basic_metadata | 
					
						
							|  |  |  |       if @is_pypi_url | 
					
						
							|  |  |  |         match = File.basename(@package_string).match(/^(.+)-([a-z\d.]+?)(?:.tar.gz|.zip)$/) | 
					
						
							|  |  |  |         raise ArgumentError, "Package should be a valid PyPI URL" if match.blank? | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-17 21:50:26 -04:00
										 |  |  |         @name ||= PyPI.normalize_python_package match[1] | 
					
						
							|  |  |  |         @extras ||= [] | 
					
						
							|  |  |  |         @version ||= match[2] | 
					
						
							| 
									
										
										
										
											2023-07-02 20:46:46 -04:00
										 |  |  |       elsif @is_url | 
					
						
							| 
									
										
										
										
											2024-02-22 09:33:10 -05:00
										 |  |  |         ensure_formula_installed!(@python_name) | 
					
						
							| 
									
										
										
										
											2023-07-02 20:46:46 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  |         # The URL might be a source distribution hosted somewhere; | 
					
						
							|  |  |  |         # try and use `pip install -q --no-deps --dry-run --report ...` to get its | 
					
						
							|  |  |  |         # name and version. | 
					
						
							|  |  |  |         # Note that this is different from the (similar) `pip install --report` we | 
					
						
							|  |  |  |         # do below, in that it uses `--no-deps` because we only care about resolving | 
					
						
							|  |  |  |         # this specific URL's project metadata. | 
					
						
							|  |  |  |         command = | 
					
						
							| 
									
										
										
										
											2024-02-22 09:33:10 -05:00
										 |  |  |           [Formula[@python_name].opt_libexec/"bin/python", "-m", "pip", "install", "-q", "--no-deps", | 
					
						
							| 
									
										
										
										
											2023-07-02 20:46:46 -04:00
										 |  |  |            "--dry-run", "--ignore-installed", "--report", "/dev/stdout", @package_string] | 
					
						
							|  |  |  |         pip_output = Utils.popen_read({ "PIP_REQUIRE_VIRTUALENV" => "false" }, *command) | 
					
						
							|  |  |  |         unless $CHILD_STATUS.success? | 
					
						
							|  |  |  |           raise ArgumentError, <<~EOS | 
					
						
							|  |  |  |             Unable to determine metadata for "#{@package_string}" because of a failure when running | 
					
						
							|  |  |  |             `#{command.join(" ")}`. | 
					
						
							|  |  |  |           EOS | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         metadata = JSON.parse(pip_output)["install"].first["metadata"] | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-17 21:50:26 -04:00
										 |  |  |         @name ||= PyPI.normalize_python_package metadata["name"] | 
					
						
							|  |  |  |         @extras ||= [] | 
					
						
							|  |  |  |         @version ||= metadata["version"] | 
					
						
							| 
									
										
										
										
											2023-07-02 20:46:46 -04:00
										 |  |  |       else | 
					
						
							|  |  |  |         if @package_string.include? "==" | 
					
						
							|  |  |  |           name, version = @package_string.split("==") | 
					
						
							|  |  |  |         else | 
					
						
							|  |  |  |           name = @package_string | 
					
						
							|  |  |  |           version = nil | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (match = T.must(name).match(/^(.*?)\[(.+)\]$/)) | 
					
						
							|  |  |  |           name = match[1] | 
					
						
							|  |  |  |           extras = T.must(match[2]).split "," | 
					
						
							|  |  |  |         else | 
					
						
							|  |  |  |           extras = [] | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-17 21:50:26 -04:00
										 |  |  |         @name ||= PyPI.normalize_python_package name | 
					
						
							|  |  |  |         @extras ||= extras | 
					
						
							|  |  |  |         @version ||= version | 
					
						
							| 
									
										
										
										
											2023-07-02 20:46:46 -04:00
										 |  |  |       end | 
					
						
							| 
									
										
										
										
											2020-11-22 15:23:43 -05:00
										 |  |  |     end | 
					
						
							|  |  |  |   end | 
					
						
							| 
									
										
										
										
											2020-07-31 14:21:44 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-22 15:23:43 -05:00
										 |  |  |   sig { params(url: String, version: T.any(String, Version)).returns(T.nilable(String)) } | 
					
						
							| 
									
										
										
										
											2023-04-01 18:56:42 -07:00
										 |  |  |   def self.update_pypi_url(url, version) | 
					
						
							| 
									
										
										
										
											2020-11-22 15:23:43 -05:00
										 |  |  |     package = Package.new url, is_url: true | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-03 15:00:30 -05:00
										 |  |  |     return unless package.valid_pypi_package? | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-02 20:46:46 -04:00
										 |  |  |     _, url = package.pypi_info(new_version: version) | 
					
						
							| 
									
										
										
										
											2020-11-22 15:23:43 -05:00
										 |  |  |     url | 
					
						
							| 
									
										
										
										
											2020-12-03 15:00:30 -05:00
										 |  |  |   rescue ArgumentError | 
					
						
							|  |  |  |     nil | 
					
						
							| 
									
										
										
										
											2020-07-27 10:37:46 -04:00
										 |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-05 17:17:03 -05:00
										 |  |  |   # Return true if resources were checked (even if no change). | 
					
						
							| 
									
										
										
										
											2021-01-17 22:45:55 -08:00
										 |  |  |   sig { | 
					
						
							| 
									
										
										
										
											2020-11-22 15:23:43 -05:00
										 |  |  |     params( | 
					
						
							|  |  |  |       formula:                  Formula, | 
					
						
							|  |  |  |       version:                  T.nilable(String), | 
					
						
							|  |  |  |       package_name:             T.nilable(String), | 
					
						
							|  |  |  |       extra_packages:           T.nilable(T::Array[String]), | 
					
						
							|  |  |  |       exclude_packages:         T.nilable(T::Array[String]), | 
					
						
							| 
									
										
										
										
											2024-02-29 15:10:08 -05:00
										 |  |  |       dependencies:             T.nilable(T::Array[String]), | 
					
						
							|  |  |  |       install_dependencies:     T.nilable(T::Boolean), | 
					
						
							| 
									
										
										
										
											2020-11-29 22:36:40 +01:00
										 |  |  |       print_only:               T.nilable(T::Boolean), | 
					
						
							|  |  |  |       silent:                   T.nilable(T::Boolean), | 
					
						
							| 
									
										
										
										
											2023-12-30 11:23:53 -05:00
										 |  |  |       verbose:                  T.nilable(T::Boolean), | 
					
						
							| 
									
										
										
										
											2020-11-29 22:36:40 +01:00
										 |  |  |       ignore_non_pypi_packages: T.nilable(T::Boolean), | 
					
						
							| 
									
										
										
										
											2020-11-22 15:23:43 -05:00
										 |  |  |     ).returns(T.nilable(T::Boolean)) | 
					
						
							| 
									
										
										
										
											2021-01-17 22:45:55 -08:00
										 |  |  |   } | 
					
						
							| 
									
										
										
										
											2023-04-01 18:56:42 -07:00
										 |  |  |   def self.update_python_resources!(formula, version: nil, package_name: nil, extra_packages: nil, | 
					
						
							| 
									
										
										
										
											2024-02-29 15:10:08 -05:00
										 |  |  |                                     exclude_packages: nil, dependencies: nil, install_dependencies: false, | 
					
						
							|  |  |  |                                     print_only: false, silent: false, verbose: false, | 
					
						
							| 
									
										
										
										
											2023-04-01 18:56:42 -07:00
										 |  |  |                                     ignore_non_pypi_packages: false) | 
					
						
							| 
									
										
										
										
											2020-11-18 02:25:55 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-24 16:52:29 -05:00
										 |  |  |     auto_update_list = formula.tap&.pypi_formula_mappings | 
					
						
							| 
									
										
										
										
											2020-11-20 01:55:34 -05:00
										 |  |  |     if auto_update_list.present? && auto_update_list.key?(formula.full_name) && | 
					
						
							|  |  |  |        package_name.blank? && extra_packages.blank? && exclude_packages.blank? | 
					
						
							| 
									
										
										
										
											2020-11-18 02:25:55 -05:00
										 |  |  | 
 | 
					
						
							|  |  |  |       list_entry = auto_update_list[formula.full_name] | 
					
						
							|  |  |  |       case list_entry | 
					
						
							|  |  |  |       when false | 
					
						
							| 
									
										
										
										
											2020-11-20 01:55:34 -05:00
										 |  |  |         unless print_only | 
					
						
							|  |  |  |           odie "The resources for \"#{formula.name}\" need special attention. Please update them manually." | 
					
						
							|  |  |  |         end | 
					
						
							| 
									
										
										
										
											2020-11-18 02:25:55 -05:00
										 |  |  |       when String | 
					
						
							|  |  |  |         package_name = list_entry | 
					
						
							|  |  |  |       when Hash | 
					
						
							|  |  |  |         package_name = list_entry["package_name"] | 
					
						
							|  |  |  |         extra_packages = list_entry["extra_packages"] | 
					
						
							|  |  |  |         exclude_packages = list_entry["exclude_packages"] | 
					
						
							| 
									
										
										
										
											2024-02-29 15:10:08 -05:00
										 |  |  |         dependencies = list_entry["dependencies"] | 
					
						
							| 
									
										
										
										
											2020-11-18 02:25:55 -05:00
										 |  |  |       end | 
					
						
							| 
									
										
										
										
											2020-07-31 16:42:53 -04:00
										 |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-29 15:10:08 -05:00
										 |  |  |     missing_dependencies = Array(dependencies).reject do |dependency| | 
					
						
							|  |  |  |       Formula[dependency].any_version_installed? | 
					
						
							|  |  |  |     rescue FormulaUnavailableError | 
					
						
							|  |  |  |       odie "Formula \"#{dependency}\" not found but it is a dependency to update \"#{formula.name}\" resources." | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  |     if missing_dependencies.present? | 
					
						
							|  |  |  |       missing_msg = "formulae required to update \"#{formula.name}\" resources: #{missing_dependencies.join(", ")}" | 
					
						
							|  |  |  |       odie "Missing #{missing_msg}" unless install_dependencies | 
					
						
							|  |  |  |       ohai "Installing #{missing_msg}" | 
					
						
							|  |  |  |       missing_dependencies.each(&method(:ensure_formula_installed!)) | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-22 09:33:10 -05:00
										 |  |  |     python_deps = formula.deps | 
					
						
							|  |  |  |                          .select { |d| d.name.match?(/^python(@.+)?$/) } | 
					
						
							|  |  |  |                          .map(&:to_formula) | 
					
						
							|  |  |  |                          .sort_by(&:version) | 
					
						
							|  |  |  |                          .reverse | 
					
						
							|  |  |  |     python_name = if python_deps.empty? | 
					
						
							|  |  |  |       "python" | 
					
						
							|  |  |  |     else | 
					
						
							|  |  |  |       (python_deps.find(&:any_version_installed?) || python_deps.first).name | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-22 15:23:43 -05:00
										 |  |  |     main_package = if package_name.present? | 
					
						
							| 
									
										
										
										
											2024-02-29 13:46:41 -05:00
										 |  |  |       package_string = package_name | 
					
						
							|  |  |  |       package_string += "==#{formula.version}" if version.blank? && formula.version.present? | 
					
						
							| 
									
										
										
										
											2024-03-07 16:20:20 +00:00
										 |  |  |       Package.new(package_string, python_name:) | 
					
						
							| 
									
										
										
										
											2024-02-27 10:40:42 -05:00
										 |  |  |     elsif package_name == "" | 
					
						
							|  |  |  |       nil | 
					
						
							| 
									
										
										
										
											2020-11-22 15:23:43 -05:00
										 |  |  |     else | 
					
						
							| 
									
										
										
										
											2023-08-05 17:01:25 -07:00
										 |  |  |       stable = T.must(formula.stable) | 
					
						
							|  |  |  |       url = if stable.specs[:tag].present? | 
					
						
							|  |  |  |         url = "git+#{stable.url}@#{stable.specs[:tag]}" | 
					
						
							|  |  |  |       else | 
					
						
							|  |  |  |         stable.url | 
					
						
							|  |  |  |       end | 
					
						
							| 
									
										
										
										
											2024-03-07 16:20:20 +00:00
										 |  |  |       Package.new(url, is_url: true, python_name:) | 
					
						
							| 
									
										
										
										
											2020-11-22 15:23:43 -05:00
										 |  |  |     end | 
					
						
							| 
									
										
										
										
											2020-11-18 02:25:55 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-27 10:40:42 -05:00
										 |  |  |     if main_package.nil? | 
					
						
							|  |  |  |       odie "The main package was skipped but no PyPI `extra_packages` were provided." if extra_packages.blank? | 
					
						
							|  |  |  |     elsif version.present? | 
					
						
							| 
									
										
										
										
											2023-07-02 20:46:46 -04:00
										 |  |  |       if main_package.valid_pypi_package? | 
					
						
							|  |  |  |         main_package.version = version | 
					
						
							|  |  |  |       else | 
					
						
							| 
									
										
										
										
											2023-07-02 21:08:54 -04:00
										 |  |  |         return if ignore_non_pypi_packages | 
					
						
							| 
									
										
										
										
											2023-07-02 21:15:49 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-02 21:54:27 -04:00
										 |  |  |         odie "The main package is not a PyPI package, meaning that version-only updates cannot be \
 | 
					
						
							|  |  |  |           performed. Please update its URL manually."
 | 
					
						
							| 
									
										
										
										
											2023-07-02 20:46:46 -04:00
										 |  |  |       end | 
					
						
							| 
									
										
										
										
											2023-06-05 17:21:56 +01:00
										 |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-22 15:23:43 -05:00
										 |  |  |     extra_packages = (extra_packages || []).map { |p| Package.new p } | 
					
						
							|  |  |  |     exclude_packages = (exclude_packages || []).map { |p| Package.new p } | 
					
						
							| 
									
										
										
										
											2024-01-06 14:15:07 -08:00
										 |  |  |     exclude_packages += %w[argparse pip wsgiref].map { |p| Package.new p } | 
					
						
							| 
									
										
										
										
											2024-01-09 08:50:08 +00:00
										 |  |  |     if (newest_python = python_deps.first) && newest_python.version < Version.new("3.12") | 
					
						
							| 
									
										
										
										
											2024-01-06 14:15:07 -08:00
										 |  |  |       exclude_packages.append(Package.new("setuptools")) | 
					
						
							|  |  |  |     end | 
					
						
							| 
									
										
										
										
											2021-01-18 09:01:15 -05:00
										 |  |  |     # remove packages from the exclude list if we've explicitly requested them as an extra package | 
					
						
							|  |  |  |     exclude_packages.delete_if { |package| extra_packages.include?(package) } | 
					
						
							| 
									
										
										
										
											2020-11-22 15:23:43 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-27 13:32:52 -05:00
										 |  |  |     input_packages = Array(main_package) | 
					
						
							| 
									
										
										
										
											2020-11-22 15:23:43 -05:00
										 |  |  |     extra_packages.each do |extra_package| | 
					
						
							|  |  |  |       if !extra_package.valid_pypi_package? && !ignore_non_pypi_packages | 
					
						
							|  |  |  |         odie "\"#{extra_package}\" is not available on PyPI." | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       input_packages.each do |existing_package| | 
					
						
							|  |  |  |         if existing_package.same_package?(extra_package) && existing_package.version != extra_package.version | 
					
						
							| 
									
										
										
										
											2022-06-28 10:09:59 +01:00
										 |  |  |           odie "Conflicting versions specified for the `#{extra_package.name}` package: " \ | 
					
						
							| 
									
										
										
										
											2021-07-06 23:44:09 +05:30
										 |  |  |                "#{existing_package.version}, #{extra_package.version}" | 
					
						
							| 
									
										
										
										
											2020-11-22 15:23:43 -05:00
										 |  |  |         end | 
					
						
							|  |  |  |       end | 
					
						
							| 
									
										
										
										
											2020-07-27 10:37:46 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-22 15:23:43 -05:00
										 |  |  |       input_packages << extra_package unless input_packages.include? extra_package | 
					
						
							| 
									
										
										
										
											2020-07-27 10:37:46 -04:00
										 |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-22 15:23:43 -05:00
										 |  |  |     formula.resources.each do |resource| | 
					
						
							|  |  |  |       if !print_only && !resource.url.start_with?(PYTHONHOSTED_URL_PREFIX) | 
					
						
							|  |  |  |         odie "\"#{formula.name}\" contains non-PyPI resources. Please update the resources manually." | 
					
						
							|  |  |  |       end | 
					
						
							| 
									
										
										
										
											2020-07-27 10:37:46 -04:00
										 |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-12-19 18:06:53 -05:00
										 |  |  |     ensure_formula_installed!(python_name) | 
					
						
							| 
									
										
										
										
											2020-07-31 16:56:21 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-21 21:45:54 -07:00
										 |  |  |     # Resolve the dependency tree of all input packages | 
					
						
							| 
									
										
										
										
											2023-12-30 11:23:53 -05:00
										 |  |  |     show_info = !print_only && !silent | 
					
						
							|  |  |  |     ohai "Retrieving PyPI dependencies for \"#{input_packages.join(" ")}\"..." if show_info | 
					
						
							| 
									
										
										
										
											2024-03-07 16:20:20 +00:00
										 |  |  |     found_packages = pip_report(input_packages, python_name:, print_stderr: verbose && show_info) | 
					
						
							| 
									
										
										
										
											2023-08-21 21:45:54 -07:00
										 |  |  |     # Resolve the dependency tree of excluded packages to prune the above | 
					
						
							|  |  |  |     exclude_packages.delete_if { |package| found_packages.exclude? package } | 
					
						
							| 
									
										
										
										
											2023-12-30 11:23:53 -05:00
										 |  |  |     ohai "Retrieving PyPI dependencies for excluded \"#{exclude_packages.join(" ")}\"..." if show_info | 
					
						
							| 
									
										
										
										
											2024-03-07 16:20:20 +00:00
										 |  |  |     exclude_packages = pip_report(exclude_packages, python_name:, print_stderr: verbose && show_info) | 
					
						
							| 
									
										
										
										
											2024-02-27 10:40:42 -05:00
										 |  |  |     exclude_packages += [Package.new(main_package.name)] unless main_package.nil? | 
					
						
							| 
									
										
										
										
											2020-07-27 10:37:46 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  |     new_resource_blocks = "" | 
					
						
							| 
									
										
										
										
											2020-11-22 15:23:43 -05:00
										 |  |  |     found_packages.sort.each do |package| | 
					
						
							| 
									
										
										
										
											2020-11-18 02:25:55 -05:00
										 |  |  |       if exclude_packages.include? package | 
					
						
							| 
									
										
										
										
											2023-12-30 11:23:53 -05:00
										 |  |  |         ohai "Excluding \"#{package}\"" if show_info | 
					
						
							| 
									
										
										
										
											2024-02-06 10:14:19 +01:00
										 |  |  |         exclude_packages.delete package | 
					
						
							| 
									
										
										
										
											2020-11-18 02:25:55 -05:00
										 |  |  |         next | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-12-30 11:23:53 -05:00
										 |  |  |       ohai "Getting PyPI info for \"#{package}\"" if show_info | 
					
						
							| 
									
										
										
										
											2020-11-22 15:23:43 -05:00
										 |  |  |       name, url, checksum = package.pypi_info | 
					
						
							| 
									
										
										
										
											2020-07-27 10:37:46 -04:00
										 |  |  |       # Fail if unable to find name, url or checksum for any resource | 
					
						
							|  |  |  |       if name.blank? | 
					
						
							|  |  |  |         odie "Unable to resolve some dependencies. Please update the resources for \"#{formula.name}\" manually." | 
					
						
							|  |  |  |       elsif url.blank? || checksum.blank? | 
					
						
							|  |  |  |         odie <<~EOS | 
					
						
							| 
									
										
										
										
											2022-12-13 10:54:22 +00:00
										 |  |  |           Unable to find the URL and/or sha256 for the "#{name}" resource. | 
					
						
							|  |  |  |           Please update the resources for "#{formula.name}" manually. | 
					
						
							| 
									
										
										
										
											2020-07-27 10:37:46 -04:00
										 |  |  |         EOS | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       # Append indented resource block | 
					
						
							|  |  |  |       new_resource_blocks += <<-EOS
 | 
					
						
							|  |  |  |   resource "#{name}" do | 
					
						
							|  |  |  |     url "#{url}" | 
					
						
							|  |  |  |     sha256 "#{checksum}" | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       EOS | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-12 16:12:52 +01:00
										 |  |  |     odie "Excluded superfluous packages: #{exclude_packages.join(", ")}" if exclude_packages.any? | 
					
						
							| 
									
										
										
										
											2024-02-06 10:14:19 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-07-27 10:37:46 -04:00
										 |  |  |     if print_only | 
					
						
							|  |  |  |       puts new_resource_blocks.chomp | 
					
						
							|  |  |  |       return | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-06 23:22:07 -04:00
										 |  |  |     # Check whether resources already exist (excluding virtualenv dependencies) | 
					
						
							|  |  |  |     if formula.resources.all? { |resource| resource.name.start_with?("homebrew-") } | 
					
						
							| 
									
										
										
										
											2020-07-27 10:37:46 -04:00
										 |  |  |       # Place resources above install method | 
					
						
							|  |  |  |       inreplace_regex = /  def install/ | 
					
						
							|  |  |  |       new_resource_blocks += "  def install" | 
					
						
							|  |  |  |     else | 
					
						
							|  |  |  |       # Replace existing resource blocks with new resource blocks | 
					
						
							| 
									
										
										
										
											2024-03-16 13:55:19 -04:00
										 |  |  |       inreplace_regex = /
 | 
					
						
							|  |  |  |         \ \ ( | 
					
						
							|  |  |  |         resource\ .*\ do\s+ | 
					
						
							|  |  |  |           url\ .*\s+ | 
					
						
							|  |  |  |           sha256\ .*\s+ | 
					
						
							|  |  |  |           ((\#.*\s+)* | 
					
						
							|  |  |  |           patch\ (.*\ )?do\s+ | 
					
						
							|  |  |  |             url\ .*\s+ | 
					
						
							|  |  |  |             sha256\ .*\s+ | 
					
						
							|  |  |  |           end\s+)* | 
					
						
							|  |  |  |         end\s+)+ | 
					
						
							|  |  |  |       /x
 | 
					
						
							| 
									
										
										
										
											2020-07-27 10:37:46 -04:00
										 |  |  |       new_resource_blocks += "  " | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     ohai "Updating resource blocks" unless silent | 
					
						
							|  |  |  |     Utils::Inreplace.inreplace formula.path do |s| | 
					
						
							| 
									
										
										
										
											2024-03-01 16:08:54 -05:00
										 |  |  |       if T.must(s.inreplace_string.split(/^  test do\b/, 2).first).scan(inreplace_regex).length > 1
 | 
					
						
							| 
									
										
										
										
											2020-07-27 10:37:46 -04:00
										 |  |  |         odie "Unable to update resource blocks for \"#{formula.name}\" automatically. Please update them manually." | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  |       s.sub! inreplace_regex, new_resource_blocks | 
					
						
							|  |  |  |     end | 
					
						
							| 
									
										
										
										
											2020-09-17 21:35:01 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  |     true | 
					
						
							| 
									
										
										
										
											2020-07-27 10:37:46 -04:00
										 |  |  |   end | 
					
						
							| 
									
										
										
										
											2021-11-14 03:26:40 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-04-24 17:05:51 -06:00
										 |  |  |   def self.normalize_python_package(name) | 
					
						
							| 
									
										
										
										
											2023-04-24 20:52:35 -06:00
										 |  |  |     # This normalization is defined in the PyPA packaging specifications; | 
					
						
							|  |  |  |     # https://packaging.python.org/en/latest/specifications/name-normalization/#name-normalization | 
					
						
							| 
									
										
										
										
											2023-04-24 17:05:51 -06:00
										 |  |  |     name.gsub(/[-_.]+/, "-").downcase | 
					
						
							|  |  |  |   end | 
					
						
							| 
									
										
										
										
											2021-11-14 03:26:40 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-12-30 11:23:53 -05:00
										 |  |  |   def self.pip_report(packages, python_name: "python", print_stderr: false) | 
					
						
							| 
									
										
										
										
											2023-08-21 21:45:54 -07:00
										 |  |  |     return [] if packages.blank? | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-12-30 11:23:53 -05:00
										 |  |  |     command = [ | 
					
						
							| 
									
										
										
										
											2024-02-22 09:33:10 -05:00
										 |  |  |       Formula[python_name].opt_libexec/"bin/python", "-m", "pip", "install", "-q", "--disable-pip-version-check", | 
					
						
							| 
									
										
										
										
											2023-12-30 11:23:53 -05:00
										 |  |  |       "--dry-run", "--ignore-installed", "--report=/dev/stdout", *packages.map(&:to_s) | 
					
						
							|  |  |  |     ] | 
					
						
							|  |  |  |     options = {} | 
					
						
							|  |  |  |     options[:err] = :err if print_stderr | 
					
						
							|  |  |  |     pip_output = Utils.popen_read({ "PIP_REQUIRE_VIRTUALENV" => "false" }, *command, **options) | 
					
						
							| 
									
										
										
										
											2023-08-21 21:45:54 -07:00
										 |  |  |     unless $CHILD_STATUS.success? | 
					
						
							|  |  |  |       odie <<~EOS | 
					
						
							|  |  |  |         Unable to determine dependencies for "#{packages.join(" ")}" because of a failure when running | 
					
						
							|  |  |  |         `#{command.join(" ")}`. | 
					
						
							|  |  |  |         Please update the resources manually. | 
					
						
							|  |  |  |       EOS | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  |     pip_report_to_packages(JSON.parse(pip_output)).uniq | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   def self.pip_report_to_packages(report) | 
					
						
							| 
									
										
										
										
											2023-04-24 17:53:09 -06:00
										 |  |  |     return [] if report.blank? | 
					
						
							| 
									
										
										
										
											2021-11-14 03:26:40 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-22 23:29:55 +00:00
										 |  |  |     report["install"].filter_map do |package| | 
					
						
							| 
									
										
										
										
											2023-04-24 17:53:09 -06:00
										 |  |  |       name = normalize_python_package(package["metadata"]["name"]) | 
					
						
							|  |  |  |       version = package["metadata"]["version"] | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-21 21:45:54 -07:00
										 |  |  |       Package.new "#{name}==#{version}" | 
					
						
							| 
									
										
										
										
											2024-02-22 23:29:55 +00:00
										 |  |  |     end | 
					
						
							| 
									
										
										
										
											2021-11-14 03:26:40 -08:00
										 |  |  |   end | 
					
						
							| 
									
										
										
										
											2020-07-27 10:37:46 -04:00
										 |  |  | end |