librsvg source for verification 2026-05-22
This commit is contained in:
20
ci/Cargo.toml
Normal file
20
ci/Cargo.toml
Normal file
@@ -0,0 +1,20 @@
|
||||
# IMPORTANT: See
|
||||
# https://gnome.pages.gitlab.gnome.org/librsvg/devel-docs/ci.html#container-image-version
|
||||
|
||||
[package]
|
||||
name = "ci"
|
||||
license.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
|
||||
# Due to the unconventional layout of files
|
||||
autobins = false
|
||||
|
||||
[dependencies]
|
||||
clap.workspace = true
|
||||
regex.workspace = true
|
||||
rsvg_convert = { path = "../rsvg_convert" }
|
||||
|
||||
[[bin]]
|
||||
name = "check-rsvg-convert-options"
|
||||
path = "check_rsvg_convert_options.rs"
|
||||
111
ci/build-dependencies.sh
Normal file
111
ci/build-dependencies.sh
Normal file
@@ -0,0 +1,111 @@
|
||||
#!/bin/bash
|
||||
# The following is to disable "info" warnings for the unquoted instandes of $MESON_FLAGS
|
||||
# in the meson invocations below. We want the shell to actually split $MESON_FLAGS by spaces.
|
||||
# shellcheck disable=SC2086
|
||||
#
|
||||
# IMPORTANT: See
|
||||
# https://gnome.pages.gitlab.gnome.org/librsvg/devel-docs/ci.html#container-image-version
|
||||
|
||||
set -o errexit -o pipefail -o noclobber -o nounset
|
||||
|
||||
# See the versions here:
|
||||
# https://gitlab.gnome.org/GNOME/gnome-build-meta/-/tree/gnome-47/elements/sdk (or later versions)
|
||||
# https://gitlab.com/freedesktop-sdk/freedesktop-sdk/-/tree/master/elements/components
|
||||
|
||||
FREETYPE2_TAG="VER-2-13-3"
|
||||
FONTCONFIG_TAG="2.17.1"
|
||||
CAIRO_TAG="1.18.4"
|
||||
HARFBUZZ_TAG="12.3.2"
|
||||
PANGO_TAG="1.57.0"
|
||||
LIBXML2_TAG="v2.15.1"
|
||||
GDK_PIXBUF_TAG="2.44.5"
|
||||
|
||||
PARSED=$(getopt --options '' --longoptions 'prefix:,meson-flags:' --name "$0" -- "$@")
|
||||
eval set -- "$PARSED"
|
||||
unset PARSED
|
||||
|
||||
PREFIX=
|
||||
MESON_FLAGS=
|
||||
|
||||
while true; do
|
||||
case "$1" in
|
||||
'--prefix')
|
||||
PREFIX=$2
|
||||
shift 2
|
||||
;;
|
||||
|
||||
'--meson-flags')
|
||||
MESON_FLAGS=$2
|
||||
shift 2
|
||||
;;
|
||||
|
||||
'--')
|
||||
shift
|
||||
break
|
||||
;;
|
||||
|
||||
*)
|
||||
echo "Programming error"
|
||||
exit 3
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [ -z "$PREFIX" ]; then
|
||||
echo "please specify a --prefix"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# The following assumes that $PREFIX has been set
|
||||
source ci/setup-dependencies-env.sh
|
||||
|
||||
cd ..
|
||||
git clone --depth 1 --branch $FREETYPE2_TAG https://gitlab.freedesktop.org/freetype/freetype
|
||||
cd freetype
|
||||
meson setup _build --prefix "$PREFIX" -Dharfbuzz=disabled $MESON_FLAGS
|
||||
meson compile -C _build
|
||||
meson install -C _build
|
||||
|
||||
cd ..
|
||||
git clone --depth 1 --branch $FONTCONFIG_TAG https://gitlab.freedesktop.org/fontconfig/fontconfig
|
||||
cd fontconfig
|
||||
meson setup _build --prefix "$PREFIX" $MESON_FLAGS
|
||||
meson compile -C _build
|
||||
meson install -C _build
|
||||
|
||||
cd ..
|
||||
git clone --depth 1 --branch $CAIRO_TAG https://gitlab.freedesktop.org/cairo/cairo
|
||||
cd cairo
|
||||
meson setup _build --prefix "$PREFIX" $MESON_FLAGS
|
||||
meson compile -C _build
|
||||
meson install -C _build
|
||||
|
||||
cd ..
|
||||
git clone --depth 1 --branch $HARFBUZZ_TAG https://github.com/harfbuzz/harfbuzz
|
||||
cd harfbuzz
|
||||
meson setup _build --prefix "$PREFIX" $MESON_FLAGS
|
||||
meson compile -C _build
|
||||
meson install -C _build
|
||||
|
||||
cd ..
|
||||
git clone --depth 1 --branch $PANGO_TAG https://gitlab.gnome.org/GNOME/pango
|
||||
cd pango
|
||||
meson setup _build --prefix "$PREFIX" $MESON_FLAGS
|
||||
meson compile -C _build
|
||||
meson install -C _build
|
||||
|
||||
cd ..
|
||||
git clone --depth 1 --branch $LIBXML2_TAG https://gitlab.gnome.org/GNOME/libxml2
|
||||
cd libxml2
|
||||
mkdir _build
|
||||
cd _build
|
||||
../autogen.sh --prefix "$PREFIX" --libdir "$PREFIX"/lib64 --without-python
|
||||
make
|
||||
make install
|
||||
|
||||
cd ..
|
||||
git clone --depth 1 --branch $GDK_PIXBUF_TAG https://gitlab.gnome.org/GNOME/gdk-pixbuf
|
||||
cd gdk-pixbuf
|
||||
meson setup _build --prefix "$PREFIX" -Dman=false -Dglycin=disabled $MESON_FLAGS
|
||||
meson compile -C _build
|
||||
meson install -C _build
|
||||
29
ci/build-with-coverage.sh
Normal file
29
ci/build-with-coverage.sh
Normal file
@@ -0,0 +1,29 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -eux -o pipefail
|
||||
|
||||
clang_version=$(clang --version | head -n 1 | cut -d' ' -f 3 | cut -d'.' -f 1)
|
||||
clang_libraries_path="/usr/lib64/clang/$clang_version/lib/linux"
|
||||
clang_profile_lib="clang_rt.profile-x86_64"
|
||||
|
||||
if [ ! -d "$clang_libraries_path" ]
|
||||
then
|
||||
echo "Expected clang libraries (for $clang_profile_lib) to be in $clang_libraries_path"
|
||||
echo "but that directory does not exist. Please adjust the build-with-coverage.sh script."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Mixed gcc and Rust/LLVM coverage for the C API tests:
|
||||
# https://searchfox.org/mozilla-central/source/browser/config/mozconfigs/linux64/code-coverage#15
|
||||
export CC="clang"
|
||||
export RUSTDOCFLAGS="-C instrument-coverage"
|
||||
LLVM_PROFILE_FILE="$(pwd)/coverage-profiles/coverage-%p-%m.profraw"
|
||||
export LLVM_PROFILE_FILE
|
||||
export RUSTC_BOOTSTRAP="1" # hack to make unstable options work on the non-nightly compiler
|
||||
export RUSTFLAGS="-C instrument-coverage -Z coverage-options=condition -Ccodegen-units=1 -Clink-dead-code -Coverflow-checks=off"
|
||||
|
||||
# meson setup _build -Db_coverage=true -Dauto_features=disabled -Dpixbuf{,-loader}=enabled --buildtype=debugoptimized
|
||||
# meson compile -C _build
|
||||
# meson test -C _build --maxfail 0 --print-errorlogs
|
||||
|
||||
cargo test -- --include-ignored
|
||||
106
ci/check_crate_versions_in_example.py
Normal file
106
ci/check_crate_versions_in_example.py
Normal file
@@ -0,0 +1,106 @@
|
||||
# Checks that the example Cargo.toml snippet from rsvg/src/lib.rs has the same versions for
|
||||
# dependencies that librsvg uses during compilation.
|
||||
|
||||
import sys
|
||||
import toml
|
||||
|
||||
# Looks for a crate version in the 'dependencies' section of a TOML document, either of these:
|
||||
#
|
||||
# [dependencies]
|
||||
# foo = "1.2.3"
|
||||
# bar = { version = "4.5.6", features=["something", "else", "here"]
|
||||
def get_crate_version(toml_doc, crate_name):
|
||||
if 'dependencies' in toml_doc:
|
||||
crate_decl = toml_doc['dependencies'][crate_name]
|
||||
else:
|
||||
crate_decl = toml_doc['workspace']['dependencies'][crate_name]
|
||||
|
||||
if isinstance(crate_decl, str):
|
||||
version = crate_decl
|
||||
else:
|
||||
version = crate_decl['version']
|
||||
|
||||
return version
|
||||
|
||||
# Given a Rust file that has a toplevel comment somewhere like
|
||||
#
|
||||
# //! ```toml
|
||||
# //! [dependencies]
|
||||
# //! librsvg = "2.57.0-beta.2"
|
||||
# //! cairo-rs = "0.18"
|
||||
# //! gio = "0.18" # only if you need streams
|
||||
# //! ```
|
||||
#
|
||||
# extracts just the TOML as a string, without the //! prefix.
|
||||
def find_toml_in_rust_toplevel_docs(lines):
|
||||
found_start = False
|
||||
start_index = 0
|
||||
end_index = 0
|
||||
|
||||
for (i, line) in enumerate(lines):
|
||||
if not found_start and line.startswith('//! ```toml'):
|
||||
found_start = True
|
||||
start_index = i
|
||||
end_index = i
|
||||
elif found_start and line.startswith('//! ```'):
|
||||
end_index = i
|
||||
break
|
||||
|
||||
if not found_start:
|
||||
raise Exception(
|
||||
"did not find start of ```toml block in the toplevel documentation comments"
|
||||
)
|
||||
|
||||
snippet = lines[(start_index + 1):end_index]
|
||||
|
||||
without_comment = [s.removeprefix('//! ') for s in snippet]
|
||||
|
||||
return "".join(without_comment)
|
||||
|
||||
def check_dependency_version(cargo_toml_filename, cargo_toml, other_filename, other_toml,
|
||||
dependency_name):
|
||||
dep_in_cargo_toml = get_crate_version(cargo_toml, dependency_name)
|
||||
dep_in_other = get_crate_version(other_toml, dependency_name)
|
||||
|
||||
if dep_in_cargo_toml != dep_in_other:
|
||||
raise Exception(
|
||||
f"""{dependency_name} version in {cargo_toml_filename} is {dep_in_cargo_toml} but
|
||||
is referenced in {other_filename} as {dep_in_other}"""
|
||||
)
|
||||
|
||||
def check():
|
||||
cargo_toml = toml.load('rsvg/Cargo.toml')
|
||||
librsvg_version = cargo_toml['package']['version']
|
||||
|
||||
example_file = open('rsvg/src/lib.rs')
|
||||
example_contents = example_file.readlines()
|
||||
example_toml_str = find_toml_in_rust_toplevel_docs(example_contents)
|
||||
example_toml = toml.loads(example_toml_str)
|
||||
|
||||
example_version = get_crate_version(example_toml, 'librsvg')
|
||||
|
||||
if librsvg_version != example_version:
|
||||
raise Exception(
|
||||
f"""librsvg version in rsvg/Cargo.toml is {librsvg_version} but is referenced as
|
||||
{example_version} in rsvg/src/lib.rs"""
|
||||
)
|
||||
|
||||
DEPENDENCIES = [
|
||||
'cairo-rs',
|
||||
'gio',
|
||||
]
|
||||
|
||||
cargo_toml = toml.load('Cargo.toml')
|
||||
for dependency_name in DEPENDENCIES:
|
||||
check_dependency_version(
|
||||
'Cargo.toml',
|
||||
cargo_toml,
|
||||
'rsvg/src/lib.rs',
|
||||
example_toml,
|
||||
dependency_name
|
||||
)
|
||||
|
||||
print("Dependency versions match in rsvg/src/lib.rs. All good!", file=sys.stderr)
|
||||
|
||||
if __name__ == '__main__':
|
||||
check()
|
||||
6
ci/check_docs_links.sh
Normal file
6
ci/check_docs_links.sh
Normal file
@@ -0,0 +1,6 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -eu
|
||||
|
||||
mkdir -p public/devel-docs-check
|
||||
sphinx-build -b linkcheck devel-docs public/devel-docs-check
|
||||
39
ci/check_project_version.py
Normal file
39
ci/check_project_version.py
Normal file
@@ -0,0 +1,39 @@
|
||||
# This script checks that the project's version is the same in a few files where it must appear.
|
||||
|
||||
import sys
|
||||
import toml
|
||||
|
||||
from utils import get_project_version_str
|
||||
|
||||
def get_cargo_toml_version():
|
||||
doc = toml.load('Cargo.toml')
|
||||
return doc['workspace']['package']['version']
|
||||
|
||||
def get_doc_version():
|
||||
doc = toml.load('doc/librsvg.toml')
|
||||
return doc['library']['version']
|
||||
|
||||
def main():
|
||||
versions = [
|
||||
['meson.build', get_project_version_str()],
|
||||
['Cargo.toml', get_cargo_toml_version()],
|
||||
['doc/librsvg.toml', get_doc_version()],
|
||||
]
|
||||
|
||||
all_the_same = True
|
||||
|
||||
for filename, version in versions[1:]:
|
||||
if version != versions[0][1]:
|
||||
all_the_same = False
|
||||
|
||||
if not all_the_same:
|
||||
print('Version numbers do not match, please fix them!\n', file=sys.stderr)
|
||||
for filename, version in versions:
|
||||
print(f'{filename}: {version}', file=sys.stderr)
|
||||
|
||||
sys.exit(1)
|
||||
|
||||
print('Versions number match. All good!', file=sys.stderr)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
66
ci/check_public_crate_version.py
Normal file
66
ci/check_public_crate_version.py
Normal file
@@ -0,0 +1,66 @@
|
||||
# Checks that the version of the librsvg public crate matches the version for the GNOME library.
|
||||
#
|
||||
# For stable releases:
|
||||
# - GNOME: 2.57.2
|
||||
# - crate: 2.57.2
|
||||
#
|
||||
# For development relases, .9x vs. -beta.x
|
||||
# - GNOME: 2.57.90
|
||||
# - crate: 2.58.0-beta.0
|
||||
|
||||
import semver
|
||||
import sys
|
||||
import toml
|
||||
|
||||
from utils import get_project_version_str
|
||||
|
||||
def gen_crate_version_from_project_version(v):
|
||||
if v.patch < 90:
|
||||
# stable release, just return it
|
||||
return v
|
||||
elif v.patch >= 90 and v.patch < 99:
|
||||
# development release, mangle it for semver
|
||||
patch_level = v.patch - 90
|
||||
beta = f'beta.{patch_level}'
|
||||
return v.bump_minor().replace(prerelease = beta)
|
||||
else:
|
||||
raise Exception("don't know what to do with patch versions larger than 99")
|
||||
|
||||
def check_crate_version(project_version_str, crate_version_str):
|
||||
# GNOME only likes x.y.z versions
|
||||
main_version = semver.Version.parse(project_version_str)
|
||||
assert main_version.major is not None
|
||||
assert main_version.minor is not None
|
||||
assert main_version.patch is not None
|
||||
assert main_version.prerelease is None
|
||||
assert main_version.build is None
|
||||
|
||||
crate_version = semver.Version.parse(crate_version_str)
|
||||
|
||||
if gen_crate_version_from_project_version(main_version) != crate_version:
|
||||
raise Exception(
|
||||
f'meson.build version {main_version} does not match rsvg crate version {crate_version}'
|
||||
)
|
||||
|
||||
def test_stable():
|
||||
a = semver.Version.parse('2.56.3')
|
||||
assert gen_crate_version_from_project_version(a) == a
|
||||
|
||||
def test_development():
|
||||
a = semver.Version.parse('2.56.90')
|
||||
assert gen_crate_version_from_project_version(a) == semver.Version.parse('2.57.0-beta.0')
|
||||
|
||||
a = semver.Version.parse('2.57.93')
|
||||
assert gen_crate_version_from_project_version(a) == semver.Version.parse('2.58.0-beta.3')
|
||||
|
||||
def main():
|
||||
project_version_str = get_project_version_str()
|
||||
|
||||
doc = toml.load('rsvg/Cargo.toml')
|
||||
crate_version_str = doc['package']['version']
|
||||
|
||||
check_crate_version(project_version_str, crate_version_str)
|
||||
print('Versions number match. All good!', file=sys.stderr)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
731
ci/check_rsvg_convert_options.rs
Normal file
731
ci/check_rsvg_convert_options.rs
Normal file
@@ -0,0 +1,731 @@
|
||||
//! This crate tests that rsvg-convert's man page fully and properly documents its options.
|
||||
//! It uses/enforces the format specified in the "OPTIONS" section of `rsvg-convert.rst`.
|
||||
|
||||
// Allow references to `mut` statics since there's no multithreading.
|
||||
//
|
||||
// If this ever changes and mutable statics are used in any function possibly
|
||||
// executed in multiple threads, please do the needful (maybe use `RefCell`).
|
||||
#![allow(static_mut_refs)]
|
||||
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::fmt;
|
||||
use std::fs::File;
|
||||
use std::io::{self, BufRead, BufReader};
|
||||
use std::iter::Peekable;
|
||||
use std::mem::MaybeUninit;
|
||||
use std::ops::RangeInclusive;
|
||||
use std::process::ExitCode;
|
||||
|
||||
use clap::builder::{PossibleValue, ValueRange};
|
||||
use clap::{Arg, ArgAction};
|
||||
use regex::Regex;
|
||||
|
||||
use self::Error::*;
|
||||
|
||||
type UnitResult<E> = Result<(), E>;
|
||||
|
||||
// These are statics to avoid recompiling Regex patterns every time the functions in
|
||||
// which they're used are called, since those functions are called multile times by
|
||||
// design.
|
||||
//
|
||||
// Initialized and dropped in `main()`.
|
||||
static mut VALUE_NAME_SEGMENT_RE: MaybeUninit<Regex> = MaybeUninit::uninit();
|
||||
static mut VALUE_NAME_RE: MaybeUninit<Regex> = MaybeUninit::uninit();
|
||||
static mut POSSIBLE_VALUES_RE: MaybeUninit<Regex> = MaybeUninit::uninit();
|
||||
|
||||
#[derive(Debug)]
|
||||
enum Error {
|
||||
InvalidFormat {
|
||||
message: String,
|
||||
string: String,
|
||||
causes: &'static [&'static str],
|
||||
},
|
||||
UnspecifiedOption(String),
|
||||
|
||||
// Short name
|
||||
MismacthedShortNames {
|
||||
option: String,
|
||||
documented: char,
|
||||
specified: char,
|
||||
},
|
||||
UndocumentedShortName {
|
||||
option: String,
|
||||
short: char,
|
||||
},
|
||||
UnspecifiedShortName {
|
||||
option: String,
|
||||
short: char,
|
||||
},
|
||||
|
||||
// Value name
|
||||
InvalidValueName {
|
||||
option: String,
|
||||
value_name: String,
|
||||
},
|
||||
MismacthedValueNames {
|
||||
option: String,
|
||||
documented: String,
|
||||
specified: String,
|
||||
},
|
||||
UndocumentedValueName {
|
||||
option: String,
|
||||
value_name: String,
|
||||
},
|
||||
UnspecifiedValueName {
|
||||
option: String,
|
||||
value_name: String,
|
||||
},
|
||||
|
||||
// Description
|
||||
InvalidDescriptionIndentation {
|
||||
option: String,
|
||||
line_no: i32,
|
||||
indent: String,
|
||||
expected: String,
|
||||
},
|
||||
NoDescription {
|
||||
option: String,
|
||||
},
|
||||
NoValueDescription {
|
||||
option: String,
|
||||
required_because: &'static str,
|
||||
},
|
||||
|
||||
// // Possible values
|
||||
InvalidPossibleValues {
|
||||
option: String,
|
||||
line_range: RangeInclusive<i32>,
|
||||
values: Vec<String>,
|
||||
},
|
||||
MismatchedPossibleValues {
|
||||
option: String,
|
||||
line_range: RangeInclusive<i32>,
|
||||
documented: HashSet<String>,
|
||||
specified: HashSet<String>,
|
||||
},
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
InvalidFormat {
|
||||
message,
|
||||
string,
|
||||
causes,
|
||||
} => {
|
||||
f.write_str(message)?;
|
||||
write!(f, "\n string: {string:?}")?;
|
||||
|
||||
if !causes.is_empty() {
|
||||
f.write_str("\n possible causes:")?;
|
||||
for cause in *causes {
|
||||
write!(f, "\n - {cause}")?;
|
||||
}
|
||||
}
|
||||
}
|
||||
UnspecifiedOption(option) => write!(f, "`--{option}` is documented but not specified")?,
|
||||
|
||||
// Short name
|
||||
MismacthedShortNames {
|
||||
option,
|
||||
documented,
|
||||
specified,
|
||||
} => write!(
|
||||
f,
|
||||
"`--{option}` specifies short name `-{specified}` but documents `-{documented}`",
|
||||
)?,
|
||||
UndocumentedShortName { option, short } => write!(
|
||||
f,
|
||||
"`--{option}` specifies short name `-{short}` but documents none",
|
||||
)?,
|
||||
UnspecifiedShortName { option, short } => write!(
|
||||
f,
|
||||
"`--{option}` specifies no short name but documents `-{short}`",
|
||||
)?,
|
||||
|
||||
// Value name
|
||||
InvalidValueName { option, value_name } => {
|
||||
write!(f, "Invalid value name {value_name:?} for `--{option}`")?;
|
||||
f.write_str(
|
||||
"\
|
||||
\n possible causes:\
|
||||
\n - contains a non-alphabetic character other than '-', '.', '_'\
|
||||
\n - contains any of the allowed non-alphabetic characters in succession\
|
||||
\n - doesn't start with an alphabetic character\
|
||||
\n - doesn't end with an alphabetic character",
|
||||
)?;
|
||||
}
|
||||
MismacthedValueNames {
|
||||
option,
|
||||
documented,
|
||||
specified,
|
||||
} => write!(
|
||||
f,
|
||||
"`--{option}` specifies value name {specified:?} but documents {documented:?}",
|
||||
)?,
|
||||
UndocumentedValueName { option, value_name } => write!(
|
||||
f,
|
||||
"`--{option}` specifies value name {value_name:?} but documents none",
|
||||
)?,
|
||||
UnspecifiedValueName { option, value_name } => write!(
|
||||
f,
|
||||
"`--{option}` specifies no value name but documents {value_name:?}",
|
||||
)?,
|
||||
|
||||
// Description
|
||||
InvalidDescriptionIndentation {
|
||||
option,
|
||||
line_no,
|
||||
indent,
|
||||
expected,
|
||||
} => {
|
||||
write!(f, "Invalid indentation in the description of `--{option}`")?;
|
||||
write!(f, "\n line no: {line_no}")?;
|
||||
write!(f, "\n indent: {indent:?}")?;
|
||||
write!(f, "\n expected: {expected:?}")?;
|
||||
}
|
||||
NoDescription { option } => write!(f, "`--{option}` has no description")?,
|
||||
NoValueDescription {
|
||||
option,
|
||||
required_because,
|
||||
} => {
|
||||
write!(f, "`--{option}` has no value description")?;
|
||||
write!(f, "\n required because {required_because}")?;
|
||||
}
|
||||
|
||||
// // Possible values
|
||||
InvalidPossibleValues {
|
||||
option,
|
||||
line_range,
|
||||
values,
|
||||
} => {
|
||||
write!(f, "Invalid possible values {values:?} for `--{option}`")?;
|
||||
f.write_str("\n these values contain whitespace")?;
|
||||
write!(f, "\n possible values documented on lines {line_range:?}")?;
|
||||
}
|
||||
MismatchedPossibleValues {
|
||||
option,
|
||||
line_range,
|
||||
documented,
|
||||
specified,
|
||||
} => {
|
||||
let undocumented = specified - documented;
|
||||
let unspecified = documented - specified;
|
||||
|
||||
write!(f, "Mismatched possible values for `--{option}`")?;
|
||||
if !undocumented.is_empty() {
|
||||
write!(f, "\n specified but not documented: {undocumented:?}")?;
|
||||
}
|
||||
if !unspecified.is_empty() {
|
||||
write!(f, "\n documented but not specified: {unspecified:?}")?;
|
||||
}
|
||||
write!(f, "\n possible values documented on lines {line_range:?}")?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> ExitCode {
|
||||
let command = rsvg_convert::build_cli();
|
||||
let mut man_page = BufReader::new(File::open("rsvg-convert.rst").unwrap());
|
||||
let mut n_errors = 0;
|
||||
let mut options: HashMap<&str, &Arg> = HashMap::new();
|
||||
|
||||
for option in command.get_opts() {
|
||||
options.insert(option.get_long().unwrap(), option);
|
||||
}
|
||||
|
||||
// Initialize static `Regex`s (see the comment above the static items).
|
||||
unsafe {
|
||||
VALUE_NAME_SEGMENT_RE.write(Regex::new(r"^\*(.+)\*$").unwrap());
|
||||
VALUE_NAME_RE.write(Regex::new(r"(?i)^[a-z]+(?:[-._][a-z]+)*$").unwrap());
|
||||
POSSIBLE_VALUES_RE.write(
|
||||
Regex::new(r"^Possible values are ((?:\s*``[^`]+``\s*, )+\s*``[^`]+``)\.$").unwrap(),
|
||||
);
|
||||
}
|
||||
|
||||
if let Err(errors) = check_options(&mut options, &mut man_page) {
|
||||
n_errors += errors.len();
|
||||
for (line_no, error) in errors {
|
||||
eprintln!("line {line_no}: {error}\n");
|
||||
}
|
||||
}
|
||||
|
||||
if !options.is_empty() {
|
||||
n_errors += options.len();
|
||||
for long_name in options.keys() {
|
||||
eprintln!("`--{long_name}` is specified but not documented\n");
|
||||
}
|
||||
}
|
||||
|
||||
// Drop static `Regex`s (see the comment above the static items).
|
||||
unsafe {
|
||||
VALUE_NAME_SEGMENT_RE.assume_init_drop();
|
||||
VALUE_NAME_RE.assume_init_drop();
|
||||
POSSIBLE_VALUES_RE.assume_init_drop();
|
||||
}
|
||||
|
||||
if n_errors == 0 {
|
||||
ExitCode::SUCCESS
|
||||
} else {
|
||||
eprintln!("{n_errors} error(s) occurred.");
|
||||
ExitCode::FAILURE
|
||||
}
|
||||
}
|
||||
|
||||
fn check_options(
|
||||
options: &mut HashMap<&str, &Arg>,
|
||||
man_page: &mut BufReader<File>,
|
||||
) -> UnitResult<Vec<(i32, Error)>> {
|
||||
let option_header_re = Regex::new(concat!(
|
||||
r"^(?:``-(?<short_name>[a-zA-Z?])``, )?",
|
||||
r"``--(?<long_name>[a-z]+(?:-[a-z]+)*)``",
|
||||
r"(?: (?<value_names>\S.*?))?\s*$",
|
||||
))
|
||||
.unwrap();
|
||||
let mut errors: Vec<(i32, Error)> = Vec::new();
|
||||
let mut man_page_lines = man_page.lines().map(io::Result::unwrap).zip(1..).peekable();
|
||||
|
||||
for (line, _) in &mut man_page_lines {
|
||||
if line == ".. START OF OPTIONS" {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
while let Some((line, line_no)) = man_page_lines.next() {
|
||||
if line == ".. END OF OPTIONS" {
|
||||
break;
|
||||
}
|
||||
|
||||
if !line.starts_with("``-") {
|
||||
continue;
|
||||
}
|
||||
|
||||
if !option_header_re.is_match(&line) {
|
||||
errors.push((
|
||||
line_no,
|
||||
InvalidFormat {
|
||||
message: "Invalid option header format".to_string(),
|
||||
string: line,
|
||||
causes: &[
|
||||
"no long name",
|
||||
"no double backquotes around the short and/or long name",
|
||||
"wrong amount of '-' before short and/or long name",
|
||||
"no comma followed by space between the short and long name",
|
||||
"multiple space between the short name and long name",
|
||||
"the short name contains more than one character",
|
||||
"the short name contains a character other than 'a'..'z', 'A'..'Z'",
|
||||
"the long name contains a character other than 'a'..'z', '-'",
|
||||
"no space between the long name and value name",
|
||||
"multiple space between the long name and value name",
|
||||
"value name after short name",
|
||||
],
|
||||
},
|
||||
));
|
||||
continue;
|
||||
}
|
||||
|
||||
let header = option_header_re.captures(&line).unwrap();
|
||||
let long_name = header.name("long_name").unwrap().as_str();
|
||||
|
||||
// Removing so we can easily know what options are undocumented at end.
|
||||
if let Some(option) = options.remove(long_name) {
|
||||
if let Err(option_errs) = check_option(
|
||||
option,
|
||||
long_name,
|
||||
header
|
||||
.name("short_name")
|
||||
.map(|r#match| r#match.as_str().chars().next().unwrap()),
|
||||
header.name("value_names").map(|r#match| r#match.as_str()),
|
||||
&mut man_page_lines,
|
||||
) {
|
||||
errors.extend(option_errs.into_iter().map(|err| (line_no, err)));
|
||||
}
|
||||
} else {
|
||||
errors.push((line_no, UnspecifiedOption(long_name.to_string())));
|
||||
}
|
||||
}
|
||||
|
||||
if errors.is_empty() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(errors)
|
||||
}
|
||||
}
|
||||
|
||||
fn check_option(
|
||||
option: &Arg,
|
||||
long_name: &str,
|
||||
short_name: Option<char>,
|
||||
value_names: Option<&str>,
|
||||
man_page_lines: &mut Peekable<impl Iterator<Item = (String, i32)>>,
|
||||
) -> UnitResult<Vec<Error>> {
|
||||
let mut errors: Vec<Error> = Vec::new();
|
||||
let value_range = match option.get_num_args() {
|
||||
Some(value_range) => value_range,
|
||||
None => match option.get_value_names() {
|
||||
Some(value_names) => ValueRange::new(value_names.len()),
|
||||
None => match option.get_action() {
|
||||
ArgAction::Set | ArgAction::Append => ValueRange::SINGLE,
|
||||
_ => ValueRange::EMPTY,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
if let Err(error) = check_short_name(option, long_name, short_name) {
|
||||
errors.push(*error);
|
||||
}
|
||||
if let Err(error) = check_value_names(option, long_name, &value_range, value_names) {
|
||||
errors.push(*error);
|
||||
}
|
||||
if let Err(error) = check_description(option, long_name, &value_range, man_page_lines) {
|
||||
errors.push(*error);
|
||||
}
|
||||
|
||||
if errors.is_empty() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(errors)
|
||||
}
|
||||
}
|
||||
|
||||
fn check_short_name(
|
||||
option: &Arg,
|
||||
long_name: &str,
|
||||
short_name: Option<char>,
|
||||
) -> UnitResult<Box<Error>> {
|
||||
if let Some(documented) = short_name {
|
||||
if let Some(specified) = option.get_short() {
|
||||
if documented != specified {
|
||||
return Err(Box::new(MismacthedShortNames {
|
||||
option: long_name.to_string(),
|
||||
documented,
|
||||
specified,
|
||||
}));
|
||||
}
|
||||
} else {
|
||||
return Err(Box::new(UnspecifiedShortName {
|
||||
option: long_name.to_string(),
|
||||
short: documented,
|
||||
}));
|
||||
}
|
||||
} else if let Some(short) = option.get_short() {
|
||||
return Err(Box::new(UndocumentedShortName {
|
||||
option: long_name.to_string(),
|
||||
short,
|
||||
}));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// NOTE: Even though this function accepts input for any kind/combination of option
|
||||
// values, it currently doesn't handle multiple or optional values since `rsvg-convert`
|
||||
// doesn't use any of such yet. In any such case, this function panics.
|
||||
//
|
||||
// If at some point in the future the basis for this is no longer valid, this function
|
||||
// will need to be refactored.
|
||||
fn check_value_names(
|
||||
option: &Arg,
|
||||
long_name: &str,
|
||||
value_range: &ValueRange,
|
||||
value_names_segment: Option<&str>,
|
||||
) -> UnitResult<Box<Error>> {
|
||||
assert!(
|
||||
value_range.max_values() <= 1,
|
||||
"Multiple option values are not yet handled: `--{}` takes a maximum of {} values",
|
||||
long_name,
|
||||
value_range.max_values(),
|
||||
);
|
||||
assert!(
|
||||
value_range.min_values() == value_range.max_values(),
|
||||
"Optional option values are not yet handled: `--{}` takes {} optional value(s)",
|
||||
long_name,
|
||||
value_range.max_values() - value_range.min_values(),
|
||||
);
|
||||
|
||||
let value_name = if value_range.takes_values() {
|
||||
Some(
|
||||
option
|
||||
.get_value_names()
|
||||
.map_or(option.get_id().as_str(), |value_names| {
|
||||
value_names[0].as_str()
|
||||
}),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if let Some(value_names_segment_str) = value_names_segment {
|
||||
let documented = get_value_name(long_name, value_names_segment_str)?;
|
||||
|
||||
if let Some(specified) = value_name {
|
||||
if documented != specified {
|
||||
return Err(Box::new(MismacthedValueNames {
|
||||
option: long_name.to_string(),
|
||||
documented: documented.to_string(),
|
||||
specified: specified.to_string(),
|
||||
}));
|
||||
}
|
||||
|
||||
let value_name_re = unsafe { VALUE_NAME_RE.assume_init_ref() };
|
||||
|
||||
if !value_name_re.is_match(documented) {
|
||||
return Err(Box::new(InvalidValueName {
|
||||
option: long_name.to_string(),
|
||||
value_name: documented.to_string(),
|
||||
}));
|
||||
}
|
||||
} else {
|
||||
return Err(Box::new(UnspecifiedValueName {
|
||||
option: long_name.to_string(),
|
||||
value_name: documented.to_string(),
|
||||
}));
|
||||
}
|
||||
} else if let Some(specified) = value_name {
|
||||
return Err(Box::new(UndocumentedValueName {
|
||||
option: long_name.to_string(),
|
||||
value_name: specified.to_string(),
|
||||
}));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn check_description(
|
||||
option: &Arg,
|
||||
long_name: &str,
|
||||
value_range: &ValueRange,
|
||||
man_page_lines: &mut Peekable<impl Iterator<Item = (String, i32)>>,
|
||||
) -> UnitResult<Box<Error>> {
|
||||
let description_lines = get_description_lines(long_name, man_page_lines)?;
|
||||
|
||||
check_description_indentation(long_name, &description_lines)?;
|
||||
|
||||
if value_range.takes_values() {
|
||||
// If more description segments get checked at some point, the
|
||||
// following statement should be moved outside this block.
|
||||
let description_segments = get_description_segments(&description_lines);
|
||||
|
||||
check_value_description(option, long_name, &description_segments)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn check_description_indentation(
|
||||
long_name: &str,
|
||||
description_lines: &[(String, i32)],
|
||||
) -> UnitResult<Box<Error>> {
|
||||
// First line's indentation.
|
||||
let expected: String = description_lines[0]
|
||||
.0
|
||||
.chars()
|
||||
.take_while(char::is_ascii_whitespace)
|
||||
.collect();
|
||||
|
||||
for (line, line_no) in &description_lines[1..] {
|
||||
if line
|
||||
.strip_prefix(&expected)
|
||||
// Less, or more indentation -> invalid.
|
||||
.map_or(true, |line_indent_stripped| {
|
||||
line_indent_stripped
|
||||
.chars()
|
||||
.next()
|
||||
.unwrap()
|
||||
.is_ascii_whitespace()
|
||||
})
|
||||
{
|
||||
return Err(Box::new(InvalidDescriptionIndentation {
|
||||
option: long_name.to_string(),
|
||||
line_no: *line_no,
|
||||
indent: line.chars().take_while(char::is_ascii_whitespace).collect(),
|
||||
expected,
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn check_value_description(
|
||||
option: &Arg,
|
||||
long_name: &str,
|
||||
description_segments: &[(String, RangeInclusive<i32>)],
|
||||
) -> UnitResult<Box<Error>> {
|
||||
let possible_values = option.get_possible_values();
|
||||
|
||||
// Value description is only required for options with a fixed set of possible values.
|
||||
if possible_values.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if let Some((value_description, value_desc_range)) = description_segments.get(1) {
|
||||
check_possible_values(
|
||||
long_name,
|
||||
&possible_values,
|
||||
value_description,
|
||||
value_desc_range,
|
||||
)?;
|
||||
} else {
|
||||
return Err(Box::new(NoValueDescription {
|
||||
option: long_name.to_string(),
|
||||
required_because: "the option has a fixed set of possible values",
|
||||
}));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn check_possible_values(
|
||||
long_name: &str,
|
||||
possible_values: &[PossibleValue],
|
||||
value_description: &str,
|
||||
value_desc_range: &RangeInclusive<i32>,
|
||||
) -> UnitResult<Box<Error>> {
|
||||
let documented = get_possible_values(long_name, value_description, value_desc_range)?;
|
||||
let specified: HashSet<&str> = possible_values
|
||||
.iter()
|
||||
.map(PossibleValue::get_name)
|
||||
.collect();
|
||||
|
||||
if documented != specified {
|
||||
return Err(Box::new(MismatchedPossibleValues {
|
||||
option: long_name.to_string(),
|
||||
line_range: value_desc_range.clone(),
|
||||
documented: documented.into_iter().map(str::to_string).collect(),
|
||||
specified: specified.into_iter().map(str::to_string).collect(),
|
||||
}));
|
||||
}
|
||||
|
||||
let invalid_values: Vec<&str> = documented
|
||||
.into_iter()
|
||||
.filter(|value| value.contains(|ch: char| ch.is_ascii_whitespace()))
|
||||
.collect();
|
||||
|
||||
if !invalid_values.is_empty() {
|
||||
return Err(Box::new(InvalidPossibleValues {
|
||||
option: long_name.to_string(),
|
||||
line_range: value_desc_range.clone(),
|
||||
values: invalid_values.into_iter().map(str::to_string).collect(),
|
||||
}));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_description_lines(
|
||||
long_name: &str,
|
||||
man_page_lines: &mut Peekable<impl Iterator<Item = (String, i32)>>,
|
||||
) -> Result<Vec<(String, i32)>, Box<Error>> {
|
||||
let mut lines: Vec<(String, i32)> = Vec::new();
|
||||
|
||||
while let Some((line, _)) = man_page_lines.peek() {
|
||||
// Skip blank/all-whitespace lines.
|
||||
if line.chars().all(|ch| ch.is_ascii_whitespace()) {
|
||||
man_page_lines.next();
|
||||
continue;
|
||||
}
|
||||
|
||||
// Description lines must be indented underneath the option header.
|
||||
// The first line after the header not fulfiling this criteria marks
|
||||
// the end of the description.
|
||||
if !line.chars().next().unwrap().is_ascii_whitespace() {
|
||||
break;
|
||||
}
|
||||
|
||||
lines.push(man_page_lines.next().unwrap());
|
||||
}
|
||||
|
||||
if lines.is_empty() {
|
||||
Err(Box::new(NoDescription {
|
||||
option: long_name.to_string(),
|
||||
}))
|
||||
} else {
|
||||
Ok(lines)
|
||||
}
|
||||
}
|
||||
|
||||
fn get_description_segments(
|
||||
description_lines: &[(String, i32)],
|
||||
) -> Vec<(String, RangeInclusive<i32>)> {
|
||||
let mut description_lines_iter = description_lines.iter().peekable();
|
||||
let mut segments: Vec<(String, RangeInclusive<i32>)> = Vec::new();
|
||||
|
||||
while let Some(&&(_, segment_start)) = description_lines_iter.peek() {
|
||||
let mut segment: Vec<&str> = Vec::new();
|
||||
// Initialized with the number of the last line in case it doesn't end with '.'.
|
||||
let mut segment_end = description_lines.last().unwrap().1;
|
||||
|
||||
for &(ref line, line_no) in &mut description_lines_iter {
|
||||
let line_trimmed = line.trim();
|
||||
|
||||
segment.push(line_trimmed);
|
||||
|
||||
if line_trimmed.ends_with(".") {
|
||||
segment_end = line_no;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
segments.push((segment.join(" "), segment_start..=segment_end));
|
||||
}
|
||||
|
||||
segments
|
||||
}
|
||||
|
||||
fn get_possible_values<'a>(
|
||||
long_name: &str,
|
||||
value_description: &'a str,
|
||||
value_desc_range: &RangeInclusive<i32>,
|
||||
) -> Result<HashSet<&'a str>, Box<Error>> {
|
||||
let possible_values_re = unsafe { POSSIBLE_VALUES_RE.assume_init_ref() };
|
||||
|
||||
if let Some(possible_values) = possible_values_re.captures(value_description) {
|
||||
Ok(possible_values
|
||||
.get(1)
|
||||
.unwrap()
|
||||
.as_str()
|
||||
.split(", ")
|
||||
.map(|value| {
|
||||
value
|
||||
.trim()
|
||||
.strip_prefix("``")
|
||||
.unwrap()
|
||||
.strip_suffix("``")
|
||||
.unwrap()
|
||||
})
|
||||
.collect())
|
||||
} else {
|
||||
Err(Box::new(InvalidFormat {
|
||||
message: format!(
|
||||
"Invalid possible values description (lines {:?}) for option `--{}`",
|
||||
value_desc_range, long_name,
|
||||
),
|
||||
string: value_description.to_string(),
|
||||
causes: &[
|
||||
"doesn't start on a new line",
|
||||
r#"doesn't start with "Possible values are ""#,
|
||||
"no double backquotes around values",
|
||||
"no comma followed by space between values",
|
||||
"no period '.' at the end of the description",
|
||||
],
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
fn get_value_name<'a>(long_name: &str, value_name_segment: &'a str) -> Result<&'a str, Box<Error>> {
|
||||
let value_name_segment_re = unsafe { VALUE_NAME_SEGMENT_RE.assume_init_ref() };
|
||||
|
||||
if let Some(captures) = value_name_segment_re.captures(value_name_segment) {
|
||||
Ok(captures.get(1).unwrap().as_str())
|
||||
} else {
|
||||
Err(Box::new(InvalidFormat {
|
||||
message: format!("Invalid value name segment for option `--{}`", long_name),
|
||||
string: value_name_segment.to_string(),
|
||||
causes: &["the value name is not sorrounded by asterisks '*'"],
|
||||
}))
|
||||
}
|
||||
}
|
||||
60
ci/check_rust_versions.py
Normal file
60
ci/check_rust_versions.py
Normal file
@@ -0,0 +1,60 @@
|
||||
# This script checks that the Minimum Supported Rust Version (MSRV) has the same value
|
||||
# in several places throughout the source tree.
|
||||
|
||||
import re
|
||||
import sys
|
||||
|
||||
PLACES_WITH_RUST_VERSION = [
|
||||
['meson.build', r"msrv = '(.*)'"],
|
||||
['Cargo.toml', r'rust-version\s*=\s*"(.*)"'],
|
||||
['ci/container_builds.yml', r'RUST_MINIMUM:\s*"(.*)"'],
|
||||
['devel-docs/_build_dependencies.rst', r'`rust .*`_ (.*) or later'],
|
||||
]
|
||||
|
||||
PLACES_WITH_CARGO_CBUILD_VERSION = [
|
||||
['meson.build', r"cargo_cbuild_version = '(.*)'"],
|
||||
['librsvg-c/Cargo.toml', r'min_version = "(.*)"'],
|
||||
]
|
||||
|
||||
def check_versions(name, places):
|
||||
versions = []
|
||||
|
||||
for filename, regex in places:
|
||||
r = re.compile(regex)
|
||||
|
||||
with open(filename) as f:
|
||||
matched = False
|
||||
for idx, line in enumerate(f.readlines()):
|
||||
matches = r.search(line)
|
||||
if matches is not None:
|
||||
matched = True
|
||||
line_number = idx + 1
|
||||
versions.append([filename, line_number, matches.group(1), line])
|
||||
|
||||
if not matched:
|
||||
raise Exception(f'file {filename} does not have a line that matches {regex}')
|
||||
|
||||
assert len(versions) > 0
|
||||
|
||||
all_the_same = True
|
||||
|
||||
for filename, line_number, version, line in versions[1:]:
|
||||
if version != versions[0][2]:
|
||||
all_the_same = False
|
||||
|
||||
if not all_the_same:
|
||||
print(f'{name}: Version numbers do not match in these lines, please fix them!\n', file=sys.stderr)
|
||||
|
||||
for filename, line_number, version, line in versions:
|
||||
print(f' {filename}:{line_number}: {line}', file=sys.stderr)
|
||||
|
||||
sys.exit(1)
|
||||
|
||||
print(f'{name}: Versions number match. All good!', file=sys.stderr)
|
||||
|
||||
def main():
|
||||
check_versions('rustc', PLACES_WITH_RUST_VERSION)
|
||||
check_versions('cargo-cbuild', PLACES_WITH_CARGO_CBUILD_VERSION)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
5
ci/check_shell_scripts.sh
Normal file
5
ci/check_shell_scripts.sh
Normal file
@@ -0,0 +1,5 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
shellcheck --external-sources ci/*.sh
|
||||
156
ci/container_builds.yml
Normal file
156
ci/container_builds.yml
Normal file
@@ -0,0 +1,156 @@
|
||||
# The following includes are for the CI templates that are used as a
|
||||
# base to construct our container images. They need to be updated periodically, but
|
||||
# not frequently, by pointing them to a more recent commit.
|
||||
include:
|
||||
- remote: "https://gitlab.gnome.org/Infrastructure/freedesktop-ci-templates/-/raw/a4eb2bbf65c482f024cae7ee178b8fe0cfef0537/templates/opensuse.yml"
|
||||
- remote: "https://gitlab.freedesktop.org/alatiera/ci-templates/-/raw/104fbc7115a99a450ba926d2a96208457d77cac0/templates/gnomeos.yml"
|
||||
|
||||
# IMPORTANT: See
|
||||
# https://gnome.pages.gitlab.gnome.org/librsvg/devel-docs/ci.html#container-image-version
|
||||
variables:
|
||||
BASE_TAG: "2026-03-27.2-main"
|
||||
RUST_STABLE: "1.92.0"
|
||||
RUST_MINIMUM: "1.92.0"
|
||||
RUST_NIGHTLY: "nightly"
|
||||
RUSTUP_VERSION: "1.28.0"
|
||||
GNOMEOS_STABLE: "core-50"
|
||||
|
||||
# This bunch of packages are the system's C/C++ compilers, and the indirect dependencies needed
|
||||
# to build librsvg's direct dependencies. E.g. we must build cairo from a git tag, but we don't
|
||||
# care about libpng too much and so use it as a system library.
|
||||
.container.opensuse@common:
|
||||
stage: "container-build"
|
||||
before_script:
|
||||
- source ./ci/env.sh
|
||||
variables:
|
||||
FDO_DISTRIBUTION_VERSION: "tumbleweed"
|
||||
FDO_UPSTREAM_REPO: "gnome/librsvg"
|
||||
FDO_DISTRIBUTION_PACKAGES: >-
|
||||
autoconf
|
||||
automake
|
||||
bison
|
||||
clang
|
||||
clang-tools
|
||||
curl
|
||||
dav1d-devel
|
||||
diffutils
|
||||
findutils
|
||||
flex
|
||||
gawk
|
||||
gcc
|
||||
gcc-c++
|
||||
gdb
|
||||
gettext
|
||||
gettext-tools
|
||||
git
|
||||
gobject-introspection-devel
|
||||
google-roboto-fonts
|
||||
gperf
|
||||
itstool
|
||||
libbrotli-devel
|
||||
libbz2-devel
|
||||
libexpat-devel
|
||||
libffi-devel
|
||||
libjson-c-devel
|
||||
libpng-devel
|
||||
libstdc++-devel
|
||||
libtool
|
||||
libuuid-devel
|
||||
make
|
||||
meson
|
||||
openssl-devel
|
||||
pcre2-devel
|
||||
python3-pip
|
||||
python3-devel
|
||||
shadow
|
||||
shared-mime-info
|
||||
ShellCheck
|
||||
system-group-wheel
|
||||
vala
|
||||
wget
|
||||
xz
|
||||
zlib-devel
|
||||
|
||||
.container.opensuse@x86_64.stable:
|
||||
extends: .container.opensuse@common
|
||||
variables:
|
||||
FDO_DISTRIBUTION_TAG: "x86_64-${RUST_STABLE}-${BASE_TAG}"
|
||||
FDO_DISTRIBUTION_EXEC: >-
|
||||
bash ci/install-python-tools.sh &&
|
||||
bash ci/install-rust.sh --rustup-version ${RUSTUP_VERSION} \
|
||||
--stable ${RUST_STABLE} \
|
||||
--minimum ${RUST_MINIMUM} \
|
||||
--nightly ${RUST_NIGHTLY} \
|
||||
--arch x86_64-unknown-linux-gnu &&
|
||||
bash ci/install-cargo-cbuild.sh &&
|
||||
bash ci/install-rust-linters.sh &&
|
||||
bash ci/install-grcov.sh &&
|
||||
mkdir -p /usr/local/librsvg &&
|
||||
bash ci/build-dependencies.sh --prefix /usr/local/librsvg --meson-flags "--buildtype=release" &&
|
||||
rm -rf /root/.cargo /root/.cache # cleanup compilation dirs; binaries are installed now
|
||||
|
||||
.container.opensuse@aarch64:
|
||||
extends: .container.opensuse@common
|
||||
variables:
|
||||
FDO_DISTRIBUTION_TAG: "aarch64-${RUST_STABLE}-${BASE_TAG}"
|
||||
FDO_DISTRIBUTION_EXEC: >-
|
||||
bash ci/install-rust.sh --rustup-version ${RUSTUP_VERSION} \
|
||||
--stable ${RUST_STABLE} \
|
||||
--arch aarch64-unknown-linux-gnu &&
|
||||
mkdir -p /usr/local/librsvg &&
|
||||
bash ci/build-dependencies.sh --prefix /usr/local/librsvg --meson-flags "--buildtype=release" &&
|
||||
rm -rf /root/.cargo /root/.cache # cleanup compilation dirs; binaries are installed now
|
||||
tags:
|
||||
- aarch64
|
||||
|
||||
opensuse-container@x86_64.stable:
|
||||
extends:
|
||||
- .fdo.container-build@opensuse@x86_64
|
||||
- .container.opensuse@x86_64.stable
|
||||
stage: "container-build"
|
||||
|
||||
opensuse-container@aarch64:
|
||||
extends:
|
||||
- .fdo.container-build@opensuse@aarch64
|
||||
- .container.opensuse@aarch64
|
||||
stage: "container-build"
|
||||
|
||||
.container.gnomeos@common:
|
||||
stage: "container-build"
|
||||
before_script:
|
||||
- cat /etc/os-release
|
||||
- source ./ci/env.sh
|
||||
variables:
|
||||
RUST_VERSION: "${RUST_STABLE}"
|
||||
FDO_UPSTREAM_REPO: "gnome/librsvg"
|
||||
FDO_DISTRIBUTION_EXEC: >-
|
||||
bash ci/install-python-tools.sh &&
|
||||
bash ci/install-rust.sh --rustup-version ${RUSTUP_VERSION} \
|
||||
--stable ${RUST_STABLE} \
|
||||
--arch x86_64-unknown-linux-gnu &&
|
||||
bash ci/install-cargo-cbuild.sh &&
|
||||
rm -rf /root/.cargo /root/.cache # cleanup compilation dirs; binaries are installed now
|
||||
|
||||
.container.gnomeos.nightly@x86_64:
|
||||
extends: .container.gnomeos@common
|
||||
variables:
|
||||
FDO_DISTRIBUTION_TAG: "x86_64-${RUST_STABLE}-${BASE_TAG}"
|
||||
FDO_DISTRIBUTION_VERSION: "core-nightly"
|
||||
|
||||
.container.gnomeos.stable@x86_64:
|
||||
extends: .container.gnomeos@common
|
||||
variables:
|
||||
FDO_DISTRIBUTION_TAG: "x86_64-${RUST_STABLE}-${BASE_TAG}"
|
||||
FDO_DISTRIBUTION_VERSION: "$GNOMEOS_STABLE"
|
||||
|
||||
gnomeos-container.nightly@x86_64:
|
||||
extends:
|
||||
- .fdo.container-build@gnomeos@x86_64
|
||||
- .container.gnomeos.nightly@x86_64
|
||||
stage: "container-build"
|
||||
|
||||
gnomeos-container.stable@x86_64:
|
||||
extends:
|
||||
- .fdo.container-build@gnomeos@x86_64
|
||||
- .container.gnomeos.stable@x86_64
|
||||
stage: "container-build"
|
||||
21
ci/env.sh
Normal file
21
ci/env.sh
Normal file
@@ -0,0 +1,21 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# IMPORTANT: See
|
||||
# https://gnome.pages.gitlab.gnome.org/librsvg/devel-docs/ci.html#container-image-version
|
||||
|
||||
# Activate the Python virtual environment for CI scripts.
|
||||
#
|
||||
# We test for the presence of the file, since when first creating the container images for CI,
|
||||
# the venv has not been created yet. This is mostly a hack to allow having a single "env.sh"
|
||||
# script instead of one for container creation and one for CI jobs.
|
||||
if [ -f /usr/local/python/bin/activate ]; then
|
||||
source /usr/local/python/bin/activate
|
||||
fi
|
||||
|
||||
export RUSTUP_HOME='/usr/local/rustup'
|
||||
export PATH=$PATH:/usr/local/cargo/bin
|
||||
|
||||
if [ ! -v CARGO_HOME ]; then
|
||||
export CARGO_HOME=/srv/project/cargo_cache
|
||||
mkdir -p /srv/project/cargo_cache
|
||||
fi
|
||||
56
ci/gen-coverage.sh
Normal file
56
ci/gen-coverage.sh
Normal file
@@ -0,0 +1,56 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -eu
|
||||
|
||||
mkdir -p public
|
||||
|
||||
call_grcov() {
|
||||
output_type=$1
|
||||
output_path=$2
|
||||
|
||||
# Explanation of the options below:
|
||||
# grcov coverage-profiles _build - paths where to find .rawprof (llvm) and .gcda (gcc) files, respectively
|
||||
# --binary-path ./_build/target/debug/ - where the Rust test binaries are located
|
||||
# --source-dir . - toplevel source directory
|
||||
# --branch - compute branch coverage if possible
|
||||
# --ignore '**/build/markup5ever*' - ignore generated code from dependencies
|
||||
# --ignore '**/build/cssparser*' - ignore generated code from dependencies
|
||||
# --ignore 'cargo_cache/*' - ignore code from dependencies
|
||||
# --ignore '_build/*' - ignore generated code
|
||||
# --ignore 'rsvg-bench/*' - ignore benchmarks; they are not useful for the test coverage report
|
||||
# --excl-line 'unreachable!' - ignore lines with the unreachable!() macro
|
||||
# --output-type $output_type
|
||||
# --output-path $output_path
|
||||
|
||||
grcov coverage-profiles \
|
||||
--binary-path ./target/debug/ \
|
||||
--source-dir . \
|
||||
--branch \
|
||||
--ignore 'cargo_cache/*' \
|
||||
--ignore 'target/*' \
|
||||
--excl-line 'unreachable!|panic!' \
|
||||
--output-type "$output_type" \
|
||||
--output-path "$output_path"
|
||||
}
|
||||
|
||||
call_grcov html public/coverage
|
||||
|
||||
# Generate the cobertura XML format for GitLab's line-by-line coverage report in MR diffs.
|
||||
#
|
||||
# However, guard it for not being over 10 MB in size; we had a case before where it was over
|
||||
# 500 MB and that OOM'd gitlab's redis.
|
||||
|
||||
call_grcov cobertura coverage.xml
|
||||
size=$(wc -c < coverage.xml)
|
||||
if [ "$size" -ge 10485760 ]
|
||||
then
|
||||
rm coverage.xml
|
||||
echo "coverage.xml is over 10 MB, removing it so it will not be used"
|
||||
fi
|
||||
|
||||
# Print "Coverage: 42.42" so .gitlab-ci.yml will pick it up with a regex
|
||||
#
|
||||
# We scrape this from the HTML report, not the JSON summary, because coverage.json
|
||||
# uses no decimal places, just something like "42%".
|
||||
|
||||
grep -Eo 'abbr title.* %' public/coverage/index.html | head -n 1 | grep -Eo '[0-9.]+ %' | grep -Eo '[0-9.]+' | awk '{ print "Coverage:", $1 }'
|
||||
7
ci/gen-devel-docs.sh
Normal file
7
ci/gen-devel-docs.sh
Normal file
@@ -0,0 +1,7 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -eu
|
||||
|
||||
mkdir -p public/devel-docs
|
||||
sphinx-build --fail-on-warning --keep-going devel-docs public/devel-docs
|
||||
|
||||
20
ci/gen-rust-docs.sh
Normal file
20
ci/gen-rust-docs.sh
Normal file
@@ -0,0 +1,20 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# Generates the Rust documentation in the following directories:
|
||||
# public/internals - internals documentation, for librsvg development
|
||||
# public/doc - public API documentation
|
||||
|
||||
set -eu
|
||||
|
||||
# turn warnings into errors
|
||||
export RUSTDOCFLAGS='-D warnings'
|
||||
|
||||
cargo doc --workspace --document-private-items --no-deps
|
||||
# cargo doc --document-private-items --no-deps --package librsvg
|
||||
mkdir -p public/internals
|
||||
mv target/doc/* public/internals
|
||||
|
||||
cargo doc --no-deps --package librsvg --package 'librsvg-rebind*'
|
||||
# cargo doc --no-deps --package librsvg
|
||||
mkdir -p public/doc
|
||||
cp -r target/doc/* public/doc
|
||||
11
ci/install-cargo-cbuild.sh
Normal file
11
ci/install-cargo-cbuild.sh
Normal file
@@ -0,0 +1,11 @@
|
||||
#!/bin/bash
|
||||
|
||||
# IMPORTANT: See
|
||||
# https://gnome.pages.gitlab.gnome.org/librsvg/devel-docs/ci.html#container-image-version
|
||||
|
||||
source ./ci/env.sh
|
||||
|
||||
set -eu
|
||||
export CARGO_HOME='/usr/local/cargo'
|
||||
|
||||
cargo install --force --locked cargo-c --version 0.10.10
|
||||
13
ci/install-grcov.sh
Normal file
13
ci/install-grcov.sh
Normal file
@@ -0,0 +1,13 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# IMPORTANT: See
|
||||
# https://gnome.pages.gitlab.gnome.org/librsvg/devel-docs/ci.html#container-image-version
|
||||
|
||||
source ./ci/env.sh
|
||||
|
||||
set -eu
|
||||
export CARGO_HOME='/usr/local/cargo'
|
||||
|
||||
# Coverage tools
|
||||
cargo install grcov
|
||||
rustup component add llvm-tools-preview
|
||||
15
ci/install-python-tools.sh
Normal file
15
ci/install-python-tools.sh
Normal file
@@ -0,0 +1,15 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Creates a Python virtual environment in /usr/local/python and installs
|
||||
# the modules from requirements.txt in it. These modules are required
|
||||
# by various jobs in the CI pipeline.
|
||||
#
|
||||
# IMPORTANT: See
|
||||
# https://gnome.pages.gitlab.gnome.org/librsvg/devel-docs/ci.html#container-image-version
|
||||
|
||||
set -eux -o pipefail
|
||||
|
||||
python3 -m venv /usr/local/python
|
||||
source /usr/local/python/bin/activate
|
||||
pip3 install --upgrade pip
|
||||
pip3 install -r ci/requirements.txt
|
||||
16
ci/install-rust-linters.sh
Normal file
16
ci/install-rust-linters.sh
Normal file
@@ -0,0 +1,16 @@
|
||||
#!/bin/bash
|
||||
|
||||
# IMPORTANT: See
|
||||
# https://gnome.pages.gitlab.gnome.org/librsvg/devel-docs/ci.html#container-image-version
|
||||
|
||||
source ./ci/env.sh
|
||||
|
||||
set -eu
|
||||
export CARGO_HOME='/usr/local/cargo'
|
||||
|
||||
rustup component add clippy
|
||||
rustup component add rustfmt
|
||||
cargo install --version ^1.0 gitlab_clippy
|
||||
cargo install --force --locked cargo-deny
|
||||
cargo install --force --locked rumdl --version 0.1.11
|
||||
# cargo install --force cargo-outdated
|
||||
90
ci/install-rust.sh
Executable file
90
ci/install-rust.sh
Executable file
@@ -0,0 +1,90 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# IMPORTANT: See
|
||||
# https://gnome.pages.gitlab.gnome.org/librsvg/devel-docs/ci.html#container-image-version
|
||||
|
||||
set -o errexit -o pipefail -o noclobber -o nounset
|
||||
|
||||
source ./ci/env.sh
|
||||
|
||||
export CARGO_HOME='/usr/local/cargo'
|
||||
|
||||
PARSED=$(getopt --options '' --longoptions 'rustup-version:,stable:,minimum:,nightly:,arch:' --name "$0" -- "$@")
|
||||
eval set -- "$PARSED"
|
||||
unset PARSED
|
||||
|
||||
RUSTUP_VERSION=
|
||||
STABLE=
|
||||
MINIMUM=
|
||||
NIGHTLY=
|
||||
ARCH=
|
||||
|
||||
while true; do
|
||||
case "$1" in
|
||||
'--rustup-version')
|
||||
RUSTUP_VERSION=$2
|
||||
shift 2
|
||||
;;
|
||||
|
||||
'--stable')
|
||||
STABLE=$2
|
||||
shift 2
|
||||
;;
|
||||
|
||||
'--minimum')
|
||||
MINIMUM=$2
|
||||
shift 2
|
||||
;;
|
||||
|
||||
'--nightly')
|
||||
NIGHTLY=$2
|
||||
shift 2
|
||||
;;
|
||||
|
||||
'--arch')
|
||||
ARCH=$2
|
||||
shift 2
|
||||
;;
|
||||
|
||||
'--')
|
||||
shift
|
||||
break
|
||||
;;
|
||||
|
||||
*)
|
||||
echo "Programming error"
|
||||
exit 3
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [ -z "$RUSTUP_VERSION" ]; then
|
||||
echo "missing --rustup-version argument"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "$STABLE" ]; then
|
||||
echo "missing --stable argument, please pass the stable version of rustc you want"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "$ARCH" ]; then
|
||||
echo "missing --arch argument, please pass an architecture triple like x86_64-unknown-linux-gnu"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
RUSTUP_URL="https://static.rust-lang.org/rustup/archive/$RUSTUP_VERSION/$ARCH/rustup-init"
|
||||
wget "$RUSTUP_URL"
|
||||
|
||||
chmod +x rustup-init
|
||||
./rustup-init -y --no-modify-path --profile minimal --default-toolchain "$STABLE"
|
||||
rm rustup-init
|
||||
chmod -R a+w "$RUSTUP_HOME" "$CARGO_HOME"
|
||||
|
||||
if [ -n "$MINIMUM" ]; then
|
||||
rustup toolchain install "$MINIMUM"
|
||||
fi
|
||||
|
||||
if [ -n "$NIGHTLY" ]; then
|
||||
rustup toolchain install "$NIGHTLY"
|
||||
fi
|
||||
11
ci/msvc-requirements.txt
Normal file
11
ci/msvc-requirements.txt
Normal file
@@ -0,0 +1,11 @@
|
||||
# This file is used from ci/test-msvc.bat. It lists the Python packages required for
|
||||
# building librsvg and its dependencies on the CI.
|
||||
|
||||
meson==1.7.2
|
||||
jinja2
|
||||
markdown
|
||||
markupsafe
|
||||
packaging
|
||||
pygments
|
||||
typogrify
|
||||
tomli
|
||||
17
ci/pages-index.html
Normal file
17
ci/pages-index.html
Normal file
@@ -0,0 +1,17 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>Librsvg's cabinet of curiosities</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>Librsvg's cabinet of curiosities</h1>
|
||||
|
||||
<ul>
|
||||
<li><a href="Rsvg-2.0/">C API documentation</a></li>
|
||||
<li><a href="doc/rsvg/">Rust API documentation</a></li>
|
||||
<li><a href="coverage/">Test coverage report</a></li>
|
||||
<li><a href="devel-docs/">Development guide for librsvg</a></li>
|
||||
<li><a href="internals/rsvg/">Library internals documentation</a></li>
|
||||
</ul>
|
||||
</body>
|
||||
</html>
|
||||
21
ci/pkgconfig.nmake.patch
Normal file
21
ci/pkgconfig.nmake.patch
Normal file
@@ -0,0 +1,21 @@
|
||||
--- a/Makefile.vc 2023-09-16 11:28:40.214382500 +0800
|
||||
+++ b/Makefile.vc 2023-09-18 10:37:02.810835600 +0800
|
||||
@@ -73,7 +73,7 @@
|
||||
@-if exist $@.manifest mt /manifest $@.manifest /outputresource:$@;1
|
||||
|
||||
$(CFG)\$(PLAT)\pkg-config:
|
||||
- @-mkdir $@
|
||||
+ @-md $@
|
||||
|
||||
config.h: config.h.win32
|
||||
@-copy $@.win32 $@
|
||||
@@ -84,7 +84,8 @@
|
||||
@-del /f /q $(CFG)\$(PLAT)\*.exe
|
||||
@-del /f /q $(CFG)\$(PLAT)\*.ilk
|
||||
@-del /f /q $(CFG)\$(PLAT)\pkg-config\*.obj
|
||||
- @-rmdir /s /q $(CFG)\$(PLAT)
|
||||
+ @-del /f /q $(CFG)\$(PLAT)\pkg-config\*.pdb
|
||||
+ @-rd $(CFG)\$(PLAT)
|
||||
@-del vc$(VSVER)0.pdb
|
||||
@-del config.h
|
||||
|
||||
34
ci/pull-container-image.sh
Normal file
34
ci/pull-container-image.sh
Normal file
@@ -0,0 +1,34 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Utility script so you can pull the container image from CI for local development.
|
||||
# Run this script and follow the instructions; the script will tell you how
|
||||
# to run "podman run" to launch a container that has the same environment as the
|
||||
# one used during CI pipelines. You can debug things at leisure there.
|
||||
|
||||
set -eu
|
||||
set -o pipefail
|
||||
|
||||
CONTAINER_BUILDS=ci/container_builds.yml
|
||||
|
||||
if [ ! -f $CONTAINER_BUILDS ]
|
||||
then
|
||||
echo "Please run this from the toplevel source directory in librsvg"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
tag=$(grep -e '^ BASE_TAG:' $CONTAINER_BUILDS | head -n 1 | sed -E 's/.*BASE_TAG: "(.+)"/\1/')
|
||||
rust_version=$(grep -e '^ RUST_STABLE:' $CONTAINER_BUILDS | head -n 1 | sed -E 's/.*RUST_STABLE: "(.+)"/\1/')
|
||||
full_tag=x86_64-$rust_version-$tag
|
||||
|
||||
image_name=registry.gitlab.gnome.org/gnome/librsvg/opensuse/tumbleweed:$full_tag
|
||||
|
||||
echo pulling image "$image_name"
|
||||
podman pull "$image_name"
|
||||
|
||||
echo ""
|
||||
echo "You can now run this:"
|
||||
echo " podman run --rm -ti --cap-add=SYS_PTRACE -v \$(pwd):/srv/project:z -w /srv/project $image_name"
|
||||
echo ""
|
||||
echo "Don't forget to run this once inside the container:"
|
||||
echo " source ci/env.sh"
|
||||
echo " source ci/setup-dependencies-env.sh"
|
||||
7
ci/pyproject.toml
Normal file
7
ci/pyproject.toml
Normal file
@@ -0,0 +1,7 @@
|
||||
# Configuration for the Ruff linter for Python.
|
||||
#
|
||||
# Repository: https://github.com/astral-sh/ruff
|
||||
#
|
||||
# Documentation: https://beta.ruff.rs/docs/
|
||||
[tool.ruff]
|
||||
line-length = 100 # same as rustfmt
|
||||
13
ci/requirements.txt
Normal file
13
ci/requirements.txt
Normal file
@@ -0,0 +1,13 @@
|
||||
# IMPORTANT: See
|
||||
# https://gnome.pages.gitlab.gnome.org/librsvg/devel-docs/ci.html#container-image-version
|
||||
|
||||
docutils
|
||||
furo
|
||||
gi-docgen
|
||||
pytest
|
||||
ruff
|
||||
semver
|
||||
setuptools
|
||||
Sphinx
|
||||
sphinx-issues
|
||||
toml
|
||||
17
ci/setup-dependencies-env.sh
Normal file
17
ci/setup-dependencies-env.sh
Normal file
@@ -0,0 +1,17 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# IMPORTANT: See
|
||||
# https://gnome.pages.gitlab.gnome.org/librsvg/devel-docs/ci.html#container-image-version
|
||||
|
||||
if [ -z "$PREFIX" ]; then
|
||||
echo "Using default prefix /usr/local/librsvg for dependencies."
|
||||
echo "If this is not what you want, set the PREFIX variable"
|
||||
echo "before sourcing this script."
|
||||
PREFIX=/usr/local/librsvg
|
||||
fi
|
||||
|
||||
export PATH=$PREFIX/bin:$PATH
|
||||
export LD_LIBRARY_PATH=$PREFIX/lib64
|
||||
export PKG_CONFIG_PATH=$PREFIX/lib64/pkgconfig
|
||||
export XDG_DATA_DIRS=${PREFIX}/share:/usr/share
|
||||
export ACLOCAL_PATH=${PREFIX}/share/aclocal
|
||||
126
ci/test-msvc.bat
Normal file
126
ci/test-msvc.bat
Normal file
@@ -0,0 +1,126 @@
|
||||
@echo on
|
||||
:: vcvarsall.bat sets various env vars like PATH, INCLUDE, LIB, LIBPATH for the
|
||||
:: specified build architecture
|
||||
call "C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\VC\Auxiliary\Build\vcvarsall.bat" x64
|
||||
@echo on
|
||||
|
||||
:: set PATH, LIB and INCLUDE to first include our install directory, as well as to where
|
||||
:: `tar`, `bzip2` and `gzip` are.
|
||||
@set INST=%CD%\rsvg.ci.bin
|
||||
@set INST_PSX=%INST:\=/%
|
||||
@set MSYS2_BINDIR=c:\msys64\usr\bin
|
||||
@set BASEPATH=%INST%\bin;%PATH%
|
||||
@set PATH=%BASEPATH%
|
||||
@set LIB=%INST%\lib;%LIB%
|
||||
@set INCLUDE=%INST%\include\glib-2.0;%INST%\lib\glib-2.0\include;%INST%\include;%INCLUDE%
|
||||
@set RUST_HOST=x86_64-pc-windows-msvc
|
||||
|
||||
:: Packaged dep versions
|
||||
@set LIBXML2_VER=2.12.6
|
||||
@set FREETYPE2_VER=2.13.0
|
||||
@set PKG_CONFIG_VER=0.29.2
|
||||
|
||||
@set CURRDIR=%CD%
|
||||
pip3 install --upgrade --user -r ci/msvc-requirements.txt || goto :error
|
||||
git clone --depth 1 --no-tags https://gitlab.gnome.org/GNOME/gdk-pixbuf.git
|
||||
git clone --depth 1 --no-tags https://gitlab.gnome.org/GNOME/pango.git
|
||||
|
||||
:: build and install GDK-Pixbuf (includes glib, libpng, libjpeg-turbo and their deps)
|
||||
md _build_gdk_pixbuf
|
||||
cd _build_gdk_pixbuf
|
||||
meson setup ../gdk-pixbuf --buildtype=release --prefix=%INST_PSX% -Dman=false -Ddocumentation=false
|
||||
ninja install || goto :error
|
||||
cd ..
|
||||
rmdir /s/q _build_gdk_pixbuf
|
||||
copy /b %INST%\lib\z.lib %INST%\lib\zlib.lib
|
||||
|
||||
:: Download rustup-init, pkg-config, FreeType and libxml2
|
||||
:: (sadly there is no CUrl, but wget, so MSYS2 is needed temporarily)
|
||||
:: %MSYS2_BINDIR% must be in PATH to find gzip/xz.
|
||||
set PATH=%PATH%;%MSYS2_BINDIR%
|
||||
if not exist %HOMEPATH%\.cargo\bin\rustup.exe %MSYS2_BINDIR%\wget https://static.rust-lang.org/rustup/dist/x86_64-pc-windows-msvc/rustup-init.exe
|
||||
%MSYS2_BINDIR%\wget https://pkgconfig.freedesktop.org/releases/pkg-config-%PKG_CONFIG_VER%.tar.gz
|
||||
%MSYS2_BINDIR%\wget https://downloads.sourceforge.net/freetype/freetype-%FREETYPE2_VER%.tar.xz
|
||||
%MSYS2_BINDIR%\wget https://download.gnome.org/sources/libxml2/2.12/libxml2-%LIBXML2_VER%.tar.xz
|
||||
:: Ensure it sets the filename correctly
|
||||
%MSYS2_BINDIR%\wget --content-disposition https://wrapdb.mesonbuild.com/v2/libxml2_%LIBXML2_VER%-1/get_patch
|
||||
|
||||
%MSYS2_BINDIR%\tar -xf pkg-config-%PKG_CONFIG_VER%.tar.gz
|
||||
%MSYS2_BINDIR%\tar -Jxf freetype-%FREETYPE2_VER%.tar.xz
|
||||
%MSYS2_BINDIR%\tar -Jxf libxml2-%LIBXML2_VER%.tar.xz
|
||||
:: Sorry for this hack! Please remove when the runner gets unzip through Pacman
|
||||
python -c "import zipfile; zipfile.ZipFile('libxml2_%LIBXML2_VER%-1_patch.zip', 'r').extractall()"
|
||||
:: Having the gnutools/msys64 in the %PATH% during the MSVC builds
|
||||
:: can cause trouble...
|
||||
del /f/q pkg-config-%PKG_CONFIG_VER%.tar.gz freetype-%FREETYPE2_VER%.tar.xz libxml2-%LIBXML2_VER%.tar.xz libxml2_%LIBXML2_VER%-1_patch.zip
|
||||
|
||||
:: build and install pkg-config
|
||||
cd pkg-config-%PKG_CONFIG_VER%
|
||||
|
||||
:: patch pkg-config's NMake Makefile so that GNU's mkdir won'y be used by accident
|
||||
%MSYS2_BINDIR%\patch -p1 < %CURRDIR:\=/%/ci/pkgconfig.nmake.patch
|
||||
set PATH=%BASEPATH%
|
||||
nmake /f Makefile.vc CFG=release || goto :error
|
||||
copy /b release\x64\pkg-config.exe %INST%\bin
|
||||
nmake /f Makefile.vc CFG=release clean
|
||||
cd ..
|
||||
|
||||
:: build and install FreeType
|
||||
md _build_ft
|
||||
cd _build_ft
|
||||
meson setup ../freetype-%FREETYPE2_VER% --buildtype=release --prefix=%INST_PSX% --pkg-config-path=%INST%\lib\pkgconfig --cmake-prefix-path=%INST%
|
||||
ninja install || goto :error
|
||||
cd ..
|
||||
rmdir /s/q _build_ft
|
||||
|
||||
:: build and install libxml2 (use the Meson wrap overlaid before)
|
||||
md _build_libxml
|
||||
cd _build_libxml
|
||||
meson setup ../libxml2-%LIBXML2_VER% --buildtype=release --prefix=%INST_PSX% -Diconv=disabled --pkg-config-path=%INST%\lib\pkgconfig --cmake-prefix-path=%INST%
|
||||
ninja install || goto :error
|
||||
cd ..
|
||||
rmdir /s/q _build_libxml
|
||||
|
||||
:: build and install Pango (with HarfBuzz and Cairo)
|
||||
md _build_pango
|
||||
cd _build_pango
|
||||
meson setup ../pango --buildtype=release --prefix=%INST_PSX% -Dfontconfig=disabled --pkg-config-path=%INST%\lib\pkgconfig
|
||||
ninja install || goto :error
|
||||
cd ..
|
||||
rmdir /s/q _build_pango
|
||||
|
||||
:: Install Rust
|
||||
if exist %HOMEPATH%\.cargo\bin\rustup.exe %HOMEPATH%\.cargo\bin\rustup update
|
||||
if not exist %HOMEPATH%\.cargo\bin\rustup.exe rustup-init -y --default-toolchain=stable-%RUST_HOST% --default-host=%RUST_HOST%
|
||||
%HOMEPATH%\.cargo\bin\cargo install cargo-c || goto :error
|
||||
|
||||
:: Enable workaround if latest stable Rust caused issues like #968.
|
||||
:: Update RUST_DOWNGRADE_VER below as well as required.
|
||||
@set DOWNGRADE_RUST_VERSION=0
|
||||
|
||||
:: now build librsvg
|
||||
set PATH=%PATH%;%HOMEPATH%\.cargo\bin
|
||||
set PKG_CONFIG=%INST%\bin\pkg-config.exe
|
||||
md msvc-build
|
||||
cd msvc-build
|
||||
|
||||
:: Fix linking to PCRE for CI's sake
|
||||
if exist %INST%\lib\libpcre2-8.a copy /b %INST%\lib\libpcre2-8.a %INST%\lib\pcre2-8.lib
|
||||
|
||||
if not "%DOWNGRADE_RUST_VERSION%" == "1" goto :normal_rust_build
|
||||
@set RUST_DOWNGRADE_VER=1.82.0
|
||||
%HOMEPATH%\.cargo\bin\rustup install %RUST_DOWNGRADE_VER%-%RUST_HOST%
|
||||
meson setup .. --buildtype=release --prefix=%INST_PSX% --pkg-config-path=%INST%\lib\pkgconfig --cmake-prefix-path=%INST% -Dtriplet=%RUST_HOST% -Drustc-version=%RUST_DOWNGRADE_VER% || goto :error
|
||||
goto :continue_build
|
||||
|
||||
:normal_rust_build
|
||||
meson setup .. --buildtype=release --prefix=%INST_PSX% --pkg-config-path=%INST%\lib\pkgconfig --cmake-prefix-path=%INST% || goto :error
|
||||
|
||||
:continue_build
|
||||
ninja || goto :error
|
||||
ninja test
|
||||
ninja install || goto :error
|
||||
|
||||
goto :EOF
|
||||
:error
|
||||
exit /b 1
|
||||
48
ci/test-msys2.sh
Normal file
48
ci/test-msys2.sh
Normal file
@@ -0,0 +1,48 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
if [[ "$MSYSTEM" == "MINGW32" ]]; then
|
||||
export MSYS2_ARCH="i686"
|
||||
else
|
||||
export MSYS2_ARCH="x86_64"
|
||||
fi
|
||||
|
||||
pacman --noconfirm -Suy
|
||||
|
||||
pacman --noconfirm -S --needed \
|
||||
base-devel \
|
||||
pactoys
|
||||
|
||||
pacboy --noconfirm -S --needed \
|
||||
meson:p \
|
||||
cargo-c:p \
|
||||
gi-docgen:p \
|
||||
gobject-introspection:p \
|
||||
gdk-pixbuf2:p \
|
||||
harfbuzz:p \
|
||||
fontconfig:p \
|
||||
fribidi:p \
|
||||
libthai:p \
|
||||
cairo:p \
|
||||
pango:p \
|
||||
python-docutils:p \
|
||||
libxml2:p \
|
||||
toolchain:p \
|
||||
rust:p \
|
||||
cantarell-fonts:p
|
||||
|
||||
# https://github.com/rust-lang/cargo/issues/10885
|
||||
CARGO=$(where cargo)
|
||||
export CARGO
|
||||
|
||||
RUSTC=$(where rustc)
|
||||
export RUSTC
|
||||
|
||||
meson setup _build -Dauto_features=disabled -Dpixbuf{,-loader}=enabled
|
||||
meson compile -C _build
|
||||
|
||||
export RUST_BACKTRACE=1
|
||||
TESTS_OUTPUT_DIR=$(pwd)/tests/output
|
||||
export TESTS_OUTPUT_DIR
|
||||
# meson test -C _build --print-errorlogs
|
||||
12
ci/utils.py
Normal file
12
ci/utils.py
Normal file
@@ -0,0 +1,12 @@
|
||||
import re
|
||||
|
||||
def get_project_version_str():
|
||||
regex = re.compile(r" +version: '(\d+\.\d+\.\d+)',")
|
||||
with open("meson.build") as f:
|
||||
for line in f.readlines():
|
||||
matches = regex.match(line)
|
||||
if matches is not None:
|
||||
version_str = matches.group(1)
|
||||
return version_str
|
||||
|
||||
raise Exception('meson.build does not have a version string for the project')
|
||||
Reference in New Issue
Block a user