#!/usr/bin/env ruby # version.rb version 1.1 (for Chromebrew) if ARGV.include?('-h') || ARGV.include?('--help') abort <<~EOM Usage: ./version.rb [] [-h, --help, -v, --verbose, --update-package-files] Example: ./version.rb abcde -v The can contain '*': ./version.rb "xorg_*" If is omitted, all packages will be checked. Passing --update-package-files will try to update the version field in the package file. EOM end require 'fileutils' require 'json' require 'net/http' require 'ruby_libversion' require 'uri/http' # Add >LOCAL< lib to LOAD_PATH $LOAD_PATH.unshift '../lib' require_relative '../lib/color' require_relative '../lib/package' require_relative '../lib/package_utils' UPDATE_PACKAGE_FILES = ARGV.include?('--update-package-files') versions_updated = {} excluded_packages = Set[ { pkg_name: 'cf', comments: 'Uses a dynamic source package URL.' }, { pkg_name: 'cursor', comments: 'Uses a dynamic source package URL.' } ] excluded_pkgs = excluded_packages.map { |h| h[:pkg_name] } exclusion_regex = "(#{excluded_pkgs.join('|')})" def get_version(name, homepage, source) # Determine if the source is a GitHub repository. unless source.nil? || source.is_a?(Hash) source.sub!('www.', '') url = URI.parse(source) if url.host == 'github.com' url_parts = url.path.split('/') unless url_parts.count < 3 repo = "#{url_parts[1]}/#{url_parts[2]}" return `curl https://api.github.com/repos/#{repo}/releases/latest -s | jq .name -r` end end end anitya_id = get_anitya_id(name, homepage) # If we weren't able to get an Anitya ID, return early here to save time and headaches return if anitya_id.nil? # Get the latest stable version of the package json = JSON.parse(Net::HTTP.get(URI("https://release-monitoring.org/api/v2/versions/?project_id=#{anitya_id}"))) return if json['stable_versions'].nil? return json['stable_versions'][0] end def get_anitya_id(name, homepage) # Ignore python and ruby packages. return if name.to_s.start_with?('py2_', 'py3_', 'ruby_') # Find out how many packages Anitya has with the provided name. json = JSON.parse(Net::HTTP.get(URI("https://release-monitoring.org/api/v2/projects/?name=#{name}"))) number_of_packages = json['total_items'] if number_of_packages == 1 # We assume we have the right package, take the ID and move on. return json['items'][0]['id'] elsif number_of_packages.zero? # Anitya either doesn't have this package, or has it under a different name. # If it has it under a different name, check if it has the name used by Chromebrew. json2 = JSON.parse(Net::HTTP.get(URI("https://release-monitoring.org/api/v2/packages/?name=#{name.tr('-', '_')}"))) return if json2['total_items'].zero? (0..(json2['total_items'] - 1)).each do |i| next unless json2['items'][i]['distribution'] == 'Chromebrew' return get_anitya_id(json2['items'][i]['project'], homepage) if json2['items'][i]['name'] == name.tr('-', '_') end else # Anitya has more than one package with this exact name. candidates = [] # First, we remove any candidates which are provided by language package managers, such as pip. # This is because Chromebrew does not repackage them (#7713), so they won't be what we're looking for. (0..(number_of_packages - 1)).each do |i| # If a package is not provided by a language package manager, the ecosystem will be set to the homepage. # https://release-monitoring.org/static/docs/api.html#get--api-v2-projects- candidates.append(i) if json['items'][i]['ecosystem'] == json['items'][i]['homepage'] end if candidates.length == 1 # If there's only one candidate left, we're done. return json['items'][candidates[0]]['id'] elsif candidates.empty? # The package we're looking for is provided by a language package manager. # We probably shouldn't be providing this package. return else # There are still multiple candidates left. # This is where things get a little uncertain. # We check if the homepage Anitya has matches ours, but this really only works for Github projects. # For other projects, there's a lot more opinion involved in choosing the exact homepage. # Nevertheless, its our best shot at this point. candidates.each do |candidate| # We assume there is only one candidate with the same name and homepage as their crew counterpart. # Even if there are multiple candidates with the same name and homepage, its probably fine to treat them as identical. # If it isn't fine to treat them as identical, something has gone horribly wrong. return json['items'][candidate]['id'] if homepage == json['items'][candidate]['homepage'] end # If we're still here, that means none of the candidates had the same homepage as their crew counterpart. # Not much we can do at this point to find the version, and its better to be cautious to avoid getting the wrong candidate. return end end end filelist = [] verbose = ARGV.include?('-v') || ARGV.include?('--verbose') if ARGV.length.positive? && !(ARGV.length == 1 && verbose) ARGV.each do |arg| arg = arg.gsub('.rb', '') next unless arg =~ /^[0-9a-zA-Z\_\*]+$/ if arg.include?('*') Dir["../packages/#{arg}.rb"].each do |filename| filelist.push filename end else filename = "../packages/#{arg}.rb" filelist.push filename if File.exist?(filename) end end else Dir['../packages/*.rb'].each do |filename| filelist.push filename end end if filelist.length.positive? puts "#{'Package'.ljust(35)}#{'Status'.ljust(20)}#{'Current'.ljust(20)}Upstream" puts "#{'-------'.ljust(35)}#{'------'.ljust(20)}#{'-------'.ljust(20)}--------" filelist.each do |filename| pkg = Package.load_package(filename) # Instead of typing out the name of every python package, we just use a regex here. # Also, we annotate some packages to let us know that they won't work here. if pkg.name.match?(/py3\S+/) || pkg.no_upstream_update? puts pkg.name.ljust(35) + 'noupdate'.lightred if verbose next end # We skip fake packages. if pkg.is_fake? puts pkg.name.ljust(35) + 'fake'.lightred if verbose next end # Skip ruby and pip buildsystem packages. if %w[RUBY Pip].include?(pkg.superclass.to_s) puts pkg.name.ljust(35) + 'skipped'.lightred if verbose next end # Get the upstream version. upstream_version = get_version(pkg.name.tr('_', '-'), pkg.homepage, pkg.source_url) # Some packages don't work with this yet, so gracefully exit now rather than throwing false positives. if upstream_version.nil? || upstream_version.to_s.chomp == 'null' puts pkg.name.ljust(35) + 'notfound'.lightred if verbose next end # Bail out if we arent verbose and so dont want to print packages that are up to date. next if Libversion.version_compare2(PackageUtils.get_clean_version(pkg.version), upstream_version) >= 0 && !verbose # Print the package name. print pkg.name.ljust(35) # Print the package update status. if Libversion.version_compare2(PackageUtils.get_clean_version(pkg.version), upstream_version) >= 0 print 'uptodate'.ljust(20).lightgreen elsif Libversion.version_compare2(PackageUtils.get_clean_version(pkg.version), upstream_version) == -1 unless pkg.name[/#{exclusion_regex}/] sed_cmd = <<~SED grep "^ version '#{PackageUtils.get_clean_version(pkg.version)}'" #{filename} && sed "s,^ version '#{PackageUtils.get_clean_version(pkg.version)}', version '#{upstream_version.chomp}'," #{filename} > #{filename}.tmp SED `#{sed_cmd}` versions_updated[pkg.name.to_sym] = $CHILD_STATUS.success? end if versions_updated[pkg.name.to_sym] FileUtils.mv "#{filename}.tmp", filename print 'updated for build'.ljust(20).blue else print pkg.name[/#{exclusion_regex}/] ? 'Update MANUALLY.'.ljust(20).red : 'outdated'.ljust(20).yellow FileUtils.rm_f "#{filename}.tmp" end end # Print the package versions. puts PackageUtils.get_clean_version(pkg.version).ljust(20) + upstream_version print "failed sed cmd: #{sed_cmd}".ljust(20).yellow if versions_updated[pkg.name.to_sym].to_s == 'false' end end