blob: 43baf1f8736f25903ae6894caad0f732010b7cd1 [file] [edit]
# frozen_string_literal: true
# Shared helpers for the maintenance update scripts.
require 'open3'
module Pcre2Maintenance
LibraryFilenameVersion = Struct.new(:version, :soname, keyword_init: true)
ROOT = File.expand_path('..', __dir__)
C_LOCALE = {'LANG' => 'C', 'LC_ALL' => 'C'}.freeze
# Extract release and ABI version definitions from a configure.ac file.
def self.version_info
entries = File.read(File.join(ROOT, 'configure.ac'), encoding: Encoding::UTF_8).scan(
/m4_define\((pcre2_(?:major|minor|prerelease|date)|libpcre2_(?:posix|8|16|32)_version), *\[(.*?)\]\)/
)
raise 'Duplicate version definitions in configure.ac' unless entries.map(&:first).uniq.length == entries.length
entries.to_h
end
private_class_method :version_info
VERSION_INFO = version_info.freeze
CURRENT_RELEASE = "#{VERSION_INFO.fetch('pcre2_major')}.#{VERSION_INFO.fetch('pcre2_minor')}#{VERSION_INFO.fetch('pcre2_prerelease')}"
module_function
# Return the filename version and SONAME for each library from its libtool
# current:revision:age definition.
def library_filename_versions
VERSION_INFO.each_with_object({}) do |(definition, value), versions|
match = /\Alibpcre2_(posix|8|16|32)_version\z/.match(definition)
next unless match
components = value.split(':', -1).map(&:strip)
unless components.length == 3 && components.all? {|component| component.match?(/\A\d+\z/) }
raise "Invalid libtool version for #{definition}: #{value.inspect}"
end
current, revision, age = components.map(&:to_i)
raise "Invalid libtool version for #{definition}: current is less than age" if current < age
library = match[1].upcase
soname = current - age
versions[library] = LibraryFilenameVersion.new(
version: "#{soname}.#{age}.#{revision}",
soname: soname.to_s
).freeze
end.freeze
end
# Update every match in a UTF-8 file. A missing match is an error: it usually
# means that the source format changed and the maintenance script needs work.
# Rewrites the file only when the replacement changes its contents.
def update_file(filename, pattern, replacement = nil, &block)
content = File.read(filename, encoding: 'UTF-8')
raise "Pattern not found in #{filename}: #{pattern.inspect}" unless content.match?(pattern)
updated = if block
content.gsub(pattern) { |text| block.call(text, Regexp.last_match) }
else
content.gsub(pattern, replacement)
end
File.write(filename, updated, encoding: 'UTF-8') unless updated == content
end
# Raise if a command cannot be run successfully, rather than allowing a
# maintenance update to continue after a partial failure.
# Returns true when the command exits successfully.
def run!(*command)
system(*command, exception: true)
end
# Run a command and return its standard output.
def capture!(*command)
output, status = Open3.capture2(*command)
raise "Command failed (#{status.exitstatus}): #{command.inspect}" unless status.success?
output
end
end