198 lines
		
	
	
		
			5.8 KiB
		
	
	
	
		
			Ruby
		
	
	
	
	
	
			
		
		
	
	
			198 lines
		
	
	
		
			5.8 KiB
		
	
	
	
		
			Ruby
		
	
	
	
	
	
| # typed: strict
 | |
| # frozen_string_literal: true
 | |
| 
 | |
| require "utils/inreplace"
 | |
| require "utils/output"
 | |
| 
 | |
| # Helper functions for updating CPAN resources.
 | |
| module CPAN
 | |
|   METACPAN_URL_PREFIX = "https://cpan.metacpan.org/authors/id/"
 | |
|   CPAN_ARCHIVE_REGEX = /^(.+)-([0-9.v]+)\.(?:tar\.gz|tgz)$/
 | |
|   private_constant :METACPAN_URL_PREFIX, :CPAN_ARCHIVE_REGEX
 | |
| 
 | |
|   extend Utils::Output::Mixin
 | |
| 
 | |
|   # Represents a Perl package from an existing resource.
 | |
|   class Package
 | |
|     sig { params(resource_name: String, resource_url: String).void }
 | |
|     def initialize(resource_name, resource_url)
 | |
|       @cpan_info = T.let(nil, T.nilable(T::Array[String]))
 | |
|       @resource_name = resource_name
 | |
|       @resource_url = resource_url
 | |
|       @is_cpan_url = T.let(resource_url.start_with?(METACPAN_URL_PREFIX), T::Boolean)
 | |
|     end
 | |
| 
 | |
|     sig { returns(String) }
 | |
|     def name
 | |
|       @resource_name
 | |
|     end
 | |
| 
 | |
|     sig { returns(T.nilable(String)) }
 | |
|     def current_version
 | |
|       extract_version_from_url if @current_version.blank?
 | |
|       @current_version
 | |
|     end
 | |
| 
 | |
|     sig { returns(T::Boolean) }
 | |
|     def valid_cpan_package?
 | |
|       @is_cpan_url
 | |
|     end
 | |
| 
 | |
|     # Get latest release information from MetaCPAN API.
 | |
|     sig { returns(T.nilable(T::Array[String])) }
 | |
|     def latest_cpan_info
 | |
|       return @cpan_info if @cpan_info.present?
 | |
|       return unless valid_cpan_package?
 | |
| 
 | |
|       metadata_url = "https://fastapi.metacpan.org/v1/download_url/#{@resource_name}"
 | |
|       result = Utils::Curl.curl_output(metadata_url, "--location", "--fail")
 | |
|       return unless result.status.success?
 | |
| 
 | |
|       begin
 | |
|         json = JSON.parse(result.stdout)
 | |
|       rescue JSON::ParserError
 | |
|         return
 | |
|       end
 | |
| 
 | |
|       download_url = json["download_url"]
 | |
|       return unless download_url
 | |
| 
 | |
|       checksum = json["checksum_sha256"]
 | |
|       return unless checksum
 | |
| 
 | |
|       @cpan_info = [@resource_name, download_url, checksum, json["version"]]
 | |
|     end
 | |
| 
 | |
|     sig { returns(String) }
 | |
|     def to_s
 | |
|       @resource_name
 | |
|     end
 | |
| 
 | |
|     private
 | |
| 
 | |
|     sig { returns(T.nilable(String)) }
 | |
|     def extract_version_from_url
 | |
|       return unless @is_cpan_url
 | |
| 
 | |
|       match = File.basename(@resource_url).match(CPAN_ARCHIVE_REGEX)
 | |
|       return unless match
 | |
| 
 | |
|       @current_version = T.let(match[2], T.nilable(String))
 | |
|     end
 | |
|   end
 | |
| 
 | |
|   # Update CPAN resources in a formula.
 | |
|   sig {
 | |
|     params(
 | |
|       formula:       Formula,
 | |
|       print_only:    T.nilable(T::Boolean),
 | |
|       silent:        T.nilable(T::Boolean),
 | |
|       verbose:       T.nilable(T::Boolean),
 | |
|       ignore_errors: T.nilable(T::Boolean),
 | |
|     ).returns(T.nilable(T::Boolean))
 | |
|   }
 | |
|   def self.update_perl_resources!(formula, print_only: false, silent: false, verbose: false, ignore_errors: false)
 | |
|     cpan_resources = formula.resources.select { |resource| resource.url.start_with?(METACPAN_URL_PREFIX) }
 | |
| 
 | |
|     odie "\"#{formula.name}\" has no CPAN resources to update." if cpan_resources.empty?
 | |
| 
 | |
|     show_info = !print_only && !silent
 | |
| 
 | |
|     non_cpan_resources = formula.resources.reject { |resource| resource.url.start_with?(METACPAN_URL_PREFIX) }
 | |
|     ohai "Skipping #{non_cpan_resources.length} non-CPAN resources" if non_cpan_resources.any? && show_info
 | |
|     ohai "Found #{cpan_resources.length} CPAN resources to update" if show_info
 | |
| 
 | |
|     new_resource_blocks = ""
 | |
|     package_errors = ""
 | |
|     updated_count = 0
 | |
| 
 | |
|     cpan_resources.each do |resource|
 | |
|       package = Package.new(resource.name, resource.url)
 | |
| 
 | |
|       unless package.valid_cpan_package?
 | |
|         if ignore_errors
 | |
|           package_errors += "  # RESOURCE-ERROR: \"#{resource.name}\" is not a valid CPAN resource\n"
 | |
|           next
 | |
|         else
 | |
|           odie "\"#{resource.name}\" is not a valid CPAN resource"
 | |
|         end
 | |
|       end
 | |
| 
 | |
|       ohai "Checking \"#{resource.name}\" for updates..." if show_info
 | |
| 
 | |
|       info = package.latest_cpan_info
 | |
| 
 | |
|       unless info
 | |
|         if ignore_errors
 | |
|           package_errors += "  # RESOURCE-ERROR: Unable to resolve \"#{resource.name}\"\n"
 | |
|           next
 | |
|         else
 | |
|           odie "Unable to resolve \"#{resource.name}\""
 | |
|         end
 | |
|       end
 | |
| 
 | |
|       name, url, checksum, new_version = info
 | |
|       current_version = package.current_version
 | |
| 
 | |
|       if current_version && new_version && current_version != new_version
 | |
|         ohai "\"#{resource.name}\": #{current_version} -> #{new_version}" if show_info
 | |
|         updated_count += 1
 | |
|       elsif show_info
 | |
|         ohai "\"#{resource.name}\": already up to date (#{current_version})" if current_version
 | |
|       end
 | |
| 
 | |
|       new_resource_blocks += <<-EOS
 | |
|   resource "#{name}" do
 | |
|     url "#{url}"
 | |
|     sha256 "#{checksum}"
 | |
|   end
 | |
| 
 | |
|       EOS
 | |
|     end
 | |
| 
 | |
|     package_errors += "\n" if package_errors.present?
 | |
|     resource_section = "#{package_errors}#{new_resource_blocks}"
 | |
| 
 | |
|     if print_only
 | |
|       puts resource_section.chomp
 | |
|       return true
 | |
|     end
 | |
| 
 | |
|     if formula.resources.all? { |resource| resource.name.start_with?("homebrew-") }
 | |
|       inreplace_regex = /  def install/
 | |
|       resource_section += "  def install"
 | |
|     else
 | |
|       inreplace_regex = /
 | |
|         \ \ (
 | |
|         (\#\ RESOURCE-ERROR:\ .*\s+)*
 | |
|         resource\ .*\ do\s+
 | |
|           url\ .*\s+
 | |
|           sha256\ .*\s+
 | |
|           ((\#.*\s+)*
 | |
|           patch\ (.*\ )?do\s+
 | |
|             url\ .*\s+
 | |
|             sha256\ .*\s+
 | |
|           end\s+)*
 | |
|         end\s+)+
 | |
|       /x
 | |
|       resource_section += "  "
 | |
|     end
 | |
| 
 | |
|     ohai "Updating resource blocks" unless silent
 | |
|     Utils::Inreplace.inreplace formula.path do |s|
 | |
|       if s.inreplace_string.split(/^  test do\b/, 2).fetch(0).scan(inreplace_regex).length > 1
 | |
|         odie "Unable to update resource blocks for \"#{formula.name}\" automatically. Please update them manually."
 | |
|       end
 | |
|       s.sub! inreplace_regex, resource_section
 | |
|     end
 | |
| 
 | |
|     if package_errors.present?
 | |
|       ofail "Unable to resolve some dependencies. Please check #{formula.path} for RESOURCE-ERROR comments."
 | |
|     elsif updated_count.positive?
 | |
|       ohai "Updated #{updated_count} CPAN resource#{"s" if updated_count != 1}" unless silent
 | |
|     end
 | |
| 
 | |
|     true
 | |
|   end
 | |
| end
 | 
