blob: c8c9245ddd22547381289b84a8de225243cfb724 [file] [log] [blame]
#!/usr/bin/env ruby
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
require "digest/sha2"
require "json"
require "open-uri"
require "optparse"
option_parser = OptionParser.new
option_parser.banner =
"Usage: #{$0} [options] PRODUCT_PATTERN1 PRODUCT_PATTERN2 ..."
patterns = option_parser.parse!(ARGV)
if patterns.empty?
puts(option_parser)
exit(false)
end
# Extract product information from the `cpp/thirdparty/versions.txt`
# content.
#
# Output:
#
# {
# "ABSL" => {
# version: "20211102.0",
# checksum: "dcf71b9cba8dc0ca9940c4b316a0c796be8fab42b070bb6b7cab62b48f0e66c4",
# url_template: "https://github.com/abseil/abseil-cpp/archive/%{version}.tar.gz"
# },
# "AWS_C_AUTH" => {
# version: "v0.9.0",
# checksum: "aa6e98864fefb95c249c100da4ae7aed36ba13a8a91415791ec6fad20bec0427",
# url_template: "https://github.com/awslabs/aws-c-auth/archive/%{version}.tar.gz",
# },
# ...
# }
def parse_versions_txt_content(content)
products = {}
content.each_line(chomp: true) do |line|
case line
when /\AARROW_([A-Za-z0-9_-]+)_BUILD_VERSION=(.+?)\z/
product = Regexp.last_match[1]
version = Regexp.last_match[2]
products[product] = {version: version}
when /\AARROW_([A-Za-z0-9_-]+)_BUILD_SHA256_CHECKSUM=(.+?)\z/
product = Regexp.last_match[1]
checksum = Regexp.last_match[2]
products[product][:checksum] = checksum
when /\A "ARROW_([A-Za-z0-9_-]+)_URL (?:\S+) (\S+)"\z/
product = Regexp.last_match[1]
url_template = Regexp.last_match[2]
url_template.gsub!(/\${.+?}/) do |matched|
if matched.end_with?("//./_}")
"%{version_underscore}"
else
"%{version}"
end
end
products[product][:url_template] = url_template
end
end
products
end
# Update `metadata[:version]` and `metadata[:checksum]` for
# `latest_version`.
#
# This is used by product specific `#update_product_*` such as
# `#update_product_github` and `#update_product_apache`.
def update_product_generic(product, metadata, latest_version)
version = metadata[:version]
url_template = metadata[:url_template]
url = url_template % {
version: latest_version,
version_underscore: latest_version.gsub(".", "_"),
}
$stderr.puts("Updating #{product}: #{version} -> #{latest_version}: #{url}")
metadata[:version] = latest_version
URI.open(url, "rb") do |response|
metadata[:checksum] = Digest::SHA256.hexdigest(response.read)
end
$stderr.puts(" Checksum: #{metadata[:checksum]}")
end
# Update metadata to the latest version. This is for products hosted
# on GitHub.
def update_product_github(product, metadata, repository)
version = metadata[:version]
tags_url = "https://api.github.com/repos/#{repository}/tags"
tags = URI.open(tags_url) do |response|
JSON.parse(response.read)
end
latest_tag_name = tags[0]["name"]
if latest_tag_name.start_with?("boost-")
latest_version = latest_tag_name.delete_prefix("boost-")
elsif latest_tag_name.start_with?("v")
if metadata[:version].start_with?("v")
latest_version = latest_tag_name
else
latest_version = latest_tag_name[1..-1]
end
else
latest_version = latest_tag_name
end
return if version == latest_version
update_product_generic(product, metadata, latest_version)
end
# Update metadata to the latest version. This is for products
# developed by Apache Software Foundation.
def update_product_apache(product, metadata, project)
version = metadata[:version]
version_directory_pattern = metadata[:version_directory_template] % {
version: "(\\d+(?:\\.\\d+)+)",
}
versions = URI.open("https://downloads.apache.org/#{project}/") do |response|
response.read.scan(/<a href="#{version_directory_pattern}\/">/).flatten
end
latest_version = versions.last
return if version == latest_version
update_product_generic(product, metadata, latest_version)
end
# Update one product to the latest version.
def update_product(product, metadata)
url_template = metadata[:url_template]
if url_template.nil?
$stderr.puts("#{product} isn't supported " +
"because there is no associated URL")
return
end
case url_template
when /\Ahttps:\/\/github\.com\/((?:[^\/]+)\/(?:[^\/]+))\//
github_repository = Regexp.last_match[1]
update_product_github(product, metadata, github_repository)
when /\Ahttps:\/\/www\.apache\.org\/dyn\/closer\.lua\/
((?:[^\/]+))\/((?:[^\/]+))\//x
apache_project = Regexp.last_match[1]
metadata[:version_directory_template] = Regexp.last_match[2]
update_product_apache(product, metadata, apache_project)
else
$stderr.puts("TODO: #{product} isn't supported yet: #{url_template}")
end
end
# Update `versions.txt` content with `products`. `products` must be
# the same structure as `Hash` returned by
# `#parse_versions_txt_content`.
def update_versions_txt_content!(content, products)
products.each do |product, metadata|
prefix = "ARROW_#{Regexp.escape(product)}"
content.gsub!(/^#{prefix}_BUILD_VERSION=.*$/) do
"ARROW_#{product}_BUILD_VERSION=#{metadata[:version]}"
end
content.gsub!(/^#{prefix}_BUILD_SHA256_CHECKSUM=.*?$/) do
"ARROW_#{product}_BUILD_SHA256_CHECKSUM=#{metadata[:checksum]}"
end
end
end
versions_txt = File.join(__dir__, "versions.txt")
versions_txt_content = File.read(versions_txt)
products = parse_versions_txt_content(versions_txt_content)
patterns.each do |pattern|
target_products = products.filter do |product, _|
File.fnmatch?(pattern, product)
end
target_products.each do |product, metadata|
update_product(product, metadata)
end
end
update_versions_txt_content!(versions_txt_content, products)
File.write(versions_txt, versions_txt_content)