blob: 6b94b6af752b9d8358482bf1c4cad7690b12b4a2 [file] [edit]
#! /usr/bin/env ruby
# frozen_string_literal: true
# Prepare generated files before "make dist". Pass "doc" to update only docs.
# This script must be run before "make dist"; no arguments other than "doc"
# are accepted.
require 'fileutils'
require 'open3'
require_relative 'UpdateCommon'
require_relative 'AsciidocConverterPcre2'
require_relative 'AsciidocDemo'
require_relative 'AsciidocIndex'
module Pcre2UpdateAlways
extend Pcre2Maintenance
# Use groff's conventional terminal width for rendered manpage text.
MANPAGE_WIDTH = 78
# Concatenate these section-three manuals into pcre2.txt, omitting the
# individual function pages and pcre2demo as the generated header explains.
MANUALS = %w[
pcre2
pcre2api
pcre2build
pcre2callout
pcre2compat
pcre2convert
pcre2jit
pcre2limits
pcre2matching
pcre2partial
pcre2pattern
pcre2perform
pcre2posix
pcre2sample
pcre2serialize
pcre2syntax
pcre2unicode
].freeze
# Detrail these text files, but do not touch test data because trailing
# spaces can be significant. Configure-generated files are also omitted.
TEXT_FILES = %w[
AUTHORS.md
BUILD.bazel
CMakeLists.txt
ChangeLog
HACKING
INSTALL
LICENCE.md
MODULE.bazel
Makefile.am
NEWS
NON-AUTOTOOLS-BUILD.md
PerlTest.sh
README.md
RunGrepTest
RunGrepTest.py
RunPerlTest
RunTest
RunTest.py
SECURITY.md
SUPPORT-LIFECYCLE.md
build.zig
build.zig.zon
cmake/*.cmake
cmake/*.in
cmake/COPYING-CMAKE-SCRIPTS
configure.ac
doc/html/*.html
doc/html/*.txt
doc/html/css/*.css
doc/man/*
doc/p*
libpcre2-16.pc.in
libpcre2-32.pc.in
libpcre2-8.pc.in
libpcre2-posix.pc.in
m4/ax_pthread.m4
m4/pcre2_check_vscript.m4
m4/pcre2_visibility.m4
m4/pcre2_zos.m4
pcre2-config.in
src/libpcre2-*.sym.in
].freeze
# C sources must contain only ASCII text. The release script also detrails
# them, but performs the stricter encoding validation separately.
C_FILES = %w[
src/config-cmake.h.in
src/config-meson.h.in
src/pcre2.h.in
src/pcre2_auto_possess.c
src/pcre2_chartables.c.dist
src/pcre2_chartables.c.ebcdic-1047-nl15
src/pcre2_chartables.c.ebcdic-1047-nl25
src/pcre2_chkdint.c
src/pcre2_compile.c
src/pcre2_compile.h
src/pcre2_compile_cgroup.c
src/pcre2_compile_class.c
src/pcre2_config.c
src/pcre2_context.c
src/pcre2_convert.c
src/pcre2_dfa_match.c
src/pcre2_dftables.c
src/pcre2_error.c
src/pcre2_extuni.c
src/pcre2_find_bracket.c
src/pcre2_fuzzsupport.c
src/pcre2_internal.h
src/pcre2_intmodedep.h
src/pcre2_jit_char_inc.h
src/pcre2_jit_compile.c
src/pcre2_jit_match_inc.h
src/pcre2_jit_misc_inc.h
src/pcre2_jit_simd_inc.h
src/pcre2_jit_test.c
src/pcre2_maketables.c
src/pcre2_match.c
src/pcre2_match_data.c
src/pcre2_match_next.c
src/pcre2_newline.c
src/pcre2_ord2utf.c
src/pcre2_pattern_info.c
src/pcre2_printint_inc.h
src/pcre2_script_run.c
src/pcre2_serialize.c
src/pcre2_string_utils.c
src/pcre2_study.c
src/pcre2_substitute.c
src/pcre2_substring.c
src/pcre2_tables.c
src/pcre2_ucd.c
src/pcre2_ucp.h
src/pcre2_ucptables_inc.h
src/pcre2_util.h
src/pcre2_valid_utf.c
src/pcre2_xclass.c
src/pcre2demo.c
src/pcre2grep.c
src/pcre2posix.c
src/pcre2posix.h
src/pcre2posix_test.c
src/pcre2test.c
src/pcre2test_inc.h
].freeze
# Keep the explicit list in sync with the C sources that need ASCII checks.
source_root = "#{Pcre2Maintenance::ROOT}/"
c_sources = (Dir.glob("#{source_root}src/*.{c,h}") +
Dir.glob("#{source_root}src/*.{c,h}.in") +
Dir.glob("#{source_root}src/pcre2_chartables.c.*")).map { |path| path.delete_prefix(source_root) }
raise 'C_FILES does not match the C source files' unless C_FILES.sort == c_sources.sort
module_function
# Clean nroff -man output for online reading. Current nroff no longer emits
# the repeated headers, footer chunks, or screen controls handled by the old
# Perl script. It still uses overstriking for bold text.
def nroff_output_to_plain_text(text)
text.gsub(/.\x08/, '')
end
# Render a generated manpage to cleaned plain text.
def render_man_to_text(manpage)
# nroff must not replace ASCII hyphens with Unicode hyphens; forcing C locale ensures this.
nroff_output_to_plain_text(capture!(Pcre2Maintenance::C_LOCALE, 'nroff', '-c', '-t', '-Kutf8', '-Tutf8',
"-rLL=#{MANPAGE_WIDTH}n", '-man', "man/#{manpage}"))
end
# Remove trailing whitespace while preserving unterminated final lines.
def detrail(filename)
content = File.binread(filename)
updated = content.lines.map { |line| line.sub(/[ \t\r\f\v]+\n\z/, "\n") }.join
# Do not rewrite an unchanged file; release checks rely on idempotence.
File.binwrite(filename, updated) unless updated == content
end
# Validate UTF-8, line endings, and optionally the ASCII-only C sources.
def validate_text(filename, ascii: false, crlf: false)
content = File.binread(filename).force_encoding(Encoding::UTF_8)
unless content.valid_encoding?
warn "Bad UTF-8 in #{filename}"
return false
end
valid = true
if !crlf && content.include?("\r")
warn "CR in #{filename}"
valid = false
end
if ascii && content.match?(/[^\x01-\x7e]/)
warn "Non-ASCII in #{filename}"
valid = false
end
valid
end
# Expand required glob patterns, rejecting a stale pattern that matches nothing.
def expand_paths(patterns)
patterns.flat_map do |pattern|
paths = Dir.glob(pattern)
# A missing glob indicates a stale maintenance file list, not an empty set.
raise "No files found for #{pattern}" if paths.empty?
paths
end
end
# Verify that man can process every generated page without warnings.
def validate_manpages
puts 'Validating man pages'
raise 'man-db does not support warnings' unless capture!(Pcre2Maintenance::C_LOCALE, 'man', '--help').include?('warnings')
Dir.glob('man/*.{1,3}').each do |file|
_, warnings, status = Open3.capture3(
Pcre2Maintenance::C_LOCALE.merge('MANROFFSEQ' => '', 'MANWIDTH' => MANPAGE_WIDTH.to_s),
'man', '--warnings=w,all', '-E', 'UTF-8', '-l', '-Tutf8', '-Z', file
)
raise "Running man failed for #{file}:\n#{warnings}" unless status.success?
raise "Running man generated warnings for #{file}:\n#{warnings}" unless warnings.empty?
end
end
# Generate all derived documentation files for the current release.
def update_documentation
puts 'Processing documentation'
Dir.chdir('doc') do
# Generate the two AsciiDoc sources that are derived from project data.
puts 'Making pcre2demo.adoc'
Pcre2AsciidocDemo.run
puts 'Making index.adoc'
Pcre2AsciidocIndex.run
# The AsciiDoc sources are canonical. Check their release version before
# generating the retained troff and HTML output from them.
sources = Dir.glob('*.adoc')
sources.each do |file|
next if file == 'index.adoc'
unless File.read(file, encoding: Encoding::UTF_8).lines.any? { |line| line.chomp == ":mansource: PCRE2 #{Pcre2Maintenance::CURRENT_RELEASE}" }
raise "Version number in #{file} does not match current release"
end
end
puts 'Making HTML and manpage documentation'
FileUtils.mkdir_p(%w[html man])
# Assets under html are source files, not generated output.
FileUtils.rm(Dir.glob('html/*.{html,txt}') + Dir.glob('man/*'))
FileUtils.cp('../README.md', 'html/README.txt')
FileUtils.cp('../NON-AUTOTOOLS-BUILD.md', 'html/NON-AUTOTOOLS-BUILD.txt')
Pcre2AsciidocConverter.run(sources)
# Make tidy plain-text manuals for online reading. Concatenate the main
# section-three manuals but omit individual function pages and pcre2demo.
puts 'Making pcre2.txt'
generated_manuals = Dir.glob('man/*.3').map { |path| File.basename(path, '.3') }
excluded_manuals = generated_manuals.grep(/\Apcre2_/) + %w[pcre2demo]
unless MANUALS.sort == (generated_manuals - excluded_manuals).sort
raise 'MANUALS does not match the generated section-three manuals'
end
File.open('pcre2.txt', 'w:UTF-8') do |output|
output << <<~TEXT
-----------------------------------------------------------------------------
This file contains a concatenation of the PCRE2 man pages, converted to plain
text format for ease of searching with a text editor, or for use on systems
that do not have a man page processor. The small individual files that give
synopses of each function in the library have not been included. Neither has
the pcre2demo program. There are separate text files for the pcre2grep and
pcre2test commands.
-----------------------------------------------------------------------------
TEXT
MANUALS.each do |base|
puts " Processing #{base}.3"
output << render_man_to_text("#{base}.3")
output << "#{'-' * 78}\n"
output << "\n\n"
end
end
# Render the three section-one command manuals as individual text files.
%w[pcre2test pcre2grep pcre2-config].each do |base|
puts "Making #{base}.txt"
File.write("#{base}.txt", render_man_to_text("#{base}.1"), encoding: 'UTF-8')
end
# Validate the generated manpages after documentation rendering completes.
validate_manpages
puts 'Documentation done'
end
end
# Update build.zig.zon to pin SLJIT to the checked-out dependency revision.
def update_sljit_zig_dependency
commit = capture!('git', 'rev-parse', 'HEAD:deps/sljit').strip
raise 'deps/sljit has no commit' if commit.empty?
if File.read('build.zig.zon', encoding: Encoding::UTF_8).include?("sljit.git##{commit}")
puts "SLJIT pinned in build.zig.zon to #{commit}"
else
puts "Updating SLJIT pin in build.zig.zon to #{commit}"
run!('zig', 'fetch', '--save=sljit', "git+https://github.com/zherczeg/sljit.git##{commit}")
end
end
# Verify the non-document release numbers maintained by UpdateRelease.
def validate_release_numbers
release = Pcre2Maintenance::CURRENT_RELEASE
info = Pcre2Maintenance::VERSION_INFO
cmake_release_components = {
'PCRE2_MAJOR' => 'pcre2_major',
'PCRE2_MINOR' => 'pcre2_minor',
'PCRE2_PRERELEASE' => 'pcre2_prerelease'
}
cmake = File.read('CMakeLists.txt', encoding: Encoding::UTF_8)
cmake_release_components.each do |variable, configure_name|
expected = info.fetch(configure_name)
unless cmake.match?(/^set\(#{variable} "#{Regexp.escape(expected)}"\)$/)
raise "Version number in CMakeLists.txt does not match current release"
end
end
meson = File.read('meson.build', encoding: Encoding::UTF_8)
unless meson.match?(/^\s*version:\s*'#{Regexp.escape(release)}',$/)
raise 'Version number in meson.build does not match current release'
end
module_bazel = File.read('MODULE.bazel', encoding: Encoding::UTF_8)
unless module_bazel.match?(/^\s+version = "#{Regexp.escape(release)}",$/)
raise 'Version number in MODULE.bazel does not match current release'
end
zig_release = release.sub(/\A(\d+\.\d+)(.*)\z/, '\1.0\2')
zig = File.read('build.zig.zon', encoding: Encoding::UTF_8)
unless zig.match?(/^\s+\.version = "#{Regexp.escape(zig_release)}",$/)
raise 'Version number in build.zig.zon does not match current release'
end
end
# Regenerate release-maintained files, optionally limiting work to documentation.
def update_always(arguments)
raise ArgumentError, 'Usage: maint/UpdateAlways.rb [doc]' unless arguments.empty? || arguments == ['doc']
update_documentation
return if arguments == ['doc']
# Detrail selected source and generated documentation files, but never test
# data, where trailing spaces may carry meaning.
text_files = expand_paths(TEXT_FILES)
c_files = expand_paths(C_FILES)
puts 'Detrailing'
(text_files + c_files).each { |file| detrail(file) }
puts 'Validating all text'
results = text_files.map { |file| validate_text(file) }
results += c_files.map { |file| validate_text(file, ascii: true) }
raise 'Text validation failed' unless results.all?
update_sljit_zig_dependency
validate_release_numbers
puts 'Done'
end
end
if $PROGRAM_NAME == __FILE__
Dir.chdir(Pcre2Maintenance::ROOT) { Pcre2UpdateAlways.update_always(ARGV) }
end