Sync rust-toolset macros to rust-packaging v25.2

This bring RHEL rust-toolset in sync with the latest Fedora
rust-packaging, most importantly:

* Fedora's new style (-v vendor) vendoring is implemented. The old RHEL
style (-V #) vendoring is still supported, at least until we can convert
everything in ELN.

* Automatic generation of bundled provides using %cargo_vendor_manifest.
However, instead of depending on cargo2rpm, a very stripped-down version
of just its parse-vendor-manifest command is provided as a private
script, along with a fileattr to call it.

Other changes incorporated in this commit:

* -Cstrip=none added to %build_rustflags.

* --profile rpm is used instead of --release.

* Errors in spawned commands are now caught.

* Comments and whitespace are synced for easier comparison with Fedora.

* --target all is dropped from license and vendor macros, to avoid false
  alarms from windows crates.
This commit is contained in:
Yaakov Selkowitz 2023-11-16 22:08:03 -05:00 committed by yselkowitz
parent 8c1ae0e762
commit 6501920abb
4 changed files with 255 additions and 56 deletions

2
cargo_vendor.attr Normal file
View File

@ -0,0 +1,2 @@
%__cargo_vendor_path ^%{_defaultlicensedir}(/[^/]+)+/cargo-vendor.txt$
%__cargo_vendor_provides %{_rpmconfigdir}/cargo_vendor.prov

127
cargo_vendor.prov Executable file
View File

@ -0,0 +1,127 @@
#! /usr/bin/python3 -s
# Stripped down replacement for cargo2rpm parse-vendor-manifest
import re
import subprocess
import sys
from typing import Optional
VERSION_REGEX = re.compile(
r"""
^
(?P<major>0|[1-9]\d*)
\.(?P<minor>0|[1-9]\d*)
\.(?P<patch>0|[1-9]\d*)
(?:-(?P<pre>(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?
(?:\+(?P<build>[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$
""",
re.VERBOSE,
)
class Version:
"""
Version that adheres to the "semantic versioning" format.
"""
def __init__(self, major: int, minor: int, patch: int, pre: Optional[str] = None, build: Optional[str] = None):
self.major: int = major
self.minor: int = minor
self.patch: int = patch
self.pre: Optional[str] = pre
self.build: Optional[str] = build
@staticmethod
def parse(version: str) -> "Version":
"""
Parses a version string and return a `Version` object.
Raises a `ValueError` if the string does not match the expected format.
"""
match = VERSION_REGEX.match(version)
if not match:
raise ValueError(f"Invalid version: {version!r}")
matches = match.groupdict()
major_str = matches["major"]
minor_str = matches["minor"]
patch_str = matches["patch"]
pre = matches["pre"]
build = matches["build"]
major = int(major_str)
minor = int(minor_str)
patch = int(patch_str)
return Version(major, minor, patch, pre, build)
def to_rpm(self) -> str:
"""
Formats the `Version` object as an equivalent RPM version string.
Characters that are invalid in RPM versions are replaced ("-" -> "_")
Build metadata (the optional `Version.build` attribute) is dropped, so
the conversion is not lossless for versions where this attribute is not
`None`. However, build metadata is not intended to be part of the
version (and is not even considered when doing version comparison), so
dropping it when converting to the RPM version format is correct.
"""
s = f"{self.major}.{self.minor}.{self.patch}"
if self.pre:
s += f"~{self.pre.replace('-', '_')}"
return s
def break_the_build(error: str):
"""
This function writes a string that is an invalid RPM dependency specifier,
which causes dependency generators to fail and break the build. The
additional error message is printed to stderr.
"""
print("*** FATAL ERROR ***")
print(error, file=sys.stderr)
def get_cargo_vendor_txt_paths_from_stdin() -> set[str]: # pragma nocover
"""
Read lines from standard input and filter out lines that look like paths
to `cargo-vendor.txt` files. This is how RPM generators pass lists of files.
"""
lines = {line.rstrip("\n") for line in sys.stdin.readlines()}
return {line for line in lines if line.endswith("/cargo-vendor.txt")}
def action_parse_vendor_manifest():
paths = get_cargo_vendor_txt_paths_from_stdin()
for path in paths:
with open(path) as file:
manifest = file.read()
for line in manifest.strip().splitlines():
crate, version = line.split(" v")
print(f"bundled(crate({crate})) = {Version.parse(version).to_rpm()}")
def main():
try:
action_parse_vendor_manifest()
exit(0)
# print an error message that is not a valid RPM dependency
# to cause the generator to break the build
except (IOError, ValueError) as exc:
break_the_build(str(exc))
exit(1)
break_the_build("Uncaught exception: This should not happen, please report a bug.")
exit(1)
if __name__ == "__main__":
main()

View File

@ -1,12 +1,7 @@
# Explicitly use bindir tools, in case others are in the PATH, # __rustc: path to the default rustc executable
# like the rustup shims in a user's ~/.cargo/bin/.
#
# Since cargo 1.31, install only uses $CARGO_HOME/config, ignoring $PWD.
# https://github.com/rust-lang/cargo/issues/6397
# But we can set CARGO_HOME locally, which is a good idea anyway to make sure
# it never writes to ~/.cargo during rpmbuild.
%__cargo /usr/bin/env CARGO_HOME=.cargo RUSTFLAGS='%{build_rustflags}' /usr/bin/cargo
%__rustc /usr/bin/rustc %__rustc /usr/bin/rustc
# __rustdoc: path to the default rustdoc executable
%__rustdoc /usr/bin/rustdoc %__rustdoc /usr/bin/rustdoc
# rustflags_opt_level: default optimization level # rustflags_opt_level: default optimization level
@ -46,22 +41,56 @@
-Copt-level=%rustflags_opt_level -Copt-level=%rustflags_opt_level
-Cdebuginfo=%rustflags_debuginfo -Cdebuginfo=%rustflags_debuginfo
-Ccodegen-units=%rustflags_codegen_units -Ccodegen-units=%rustflags_codegen_units
-Cstrip=none
%{expr:0%{?_include_frame_pointers} && ("%{_arch}" != "ppc64le" && "%{_arch}" != "s390x" && "%{_arch}" != "i386") ? "-Cforce-frame-pointers=yes" : ""} %{expr:0%{?_include_frame_pointers} && ("%{_arch}" != "ppc64le" && "%{_arch}" != "s390x" && "%{_arch}" != "i386") ? "-Cforce-frame-pointers=yes" : ""}
%[0%{?_package_note_status} ? "-Clink-arg=%_package_note_flags" : ""] %[0%{?_package_note_status} ? "-Clink-arg=%_package_note_flags" : ""]
} }
# __cargo: cargo command with environment variables
#
# CARGO_HOME: This ensures cargo reads configuration file from .cargo/config,
# and prevents writing any files to $HOME during RPM builds.
%__cargo /usr/bin/env CARGO_HOME=.cargo RUSTFLAGS='%{build_rustflags}' /usr/bin/cargo
# __cargo_common_opts: common command line flags for cargo # __cargo_common_opts: common command line flags for cargo
# #
# _smp_mflags: run builds and tests in parallel # _smp_mflags: run builds and tests in parallel
%__cargo_common_opts %{?_smp_mflags} %__cargo_common_opts %{?_smp_mflags}
%cargo_prep(V:) (\ # cargo_prep: macro to set up build environment for cargo projects
%{__mkdir} -p .cargo \ #
cat > .cargo/config << EOF \ # This involves four steps:
# - create the ".cargo" directory if it doesn't exist yet
# - dump custom cargo configuration into ".cargo/config"
# - remove "Cargo.lock" if it exists (it breaks builds with custom cargo config)
# - remove "Cargo.toml.orig" if it exists (it breaks running "cargo package")
#
# Options:
# -V <number> - unpack and use vendored sources from Source<number> tarball
# (deprecated; use -v instead)
# -v <directory> - use vendored sources from <directory>
# -N - Don't set up any registry. Only set up the build configuration.
%cargo_prep(V:v:N)\
%{-v:%{-V:%{error:-v and -V are mutually exclusive!}}}\
%{-v:%{-N:%{error:-v and -N are mutually exclusive!}}}\
(\
set -euo pipefail\
%{__mkdir} -p target/rpm\
/usr/bin/ln -s rpm target/release\
%{__rm} -rf .cargo/\
%{__mkdir} -p .cargo\
cat > .cargo/config << EOF\
[build]\ [build]\
rustc = "%{__rustc}"\ rustc = "%{__rustc}"\
rustdoc = "%{__rustdoc}"\ rustdoc = "%{__rustdoc}"\
\ \
[profile.rpm]\
inherits = "release"\
opt-level = %{rustflags_opt_level}\
codegen-units = %{rustflags_codegen_units}\
debug = %{rustflags_debuginfo}\
strip = "none"\
\
[env]\ [env]\
CFLAGS = "%{build_cflags}"\ CFLAGS = "%{build_cflags}"\
CXXFLAGS = "%{build_cxxflags}"\ CXXFLAGS = "%{build_cxxflags}"\
@ -73,37 +102,38 @@ root = "%{buildroot}%{_prefix}"\
[term]\ [term]\
verbose = true\ verbose = true\
EOF\ EOF\
%if 0%{-V:1}\ %{-V:%{__tar} -xoaf %{S:%{-V*}}}\
%{__tar} -xoaf %{S:%{-V*}}\ %{!?-N:\
cat >> .cargo/config << EOF \ cat >> .cargo/config << EOF\
[source.vendored-sources]\
directory = "%{-v*}%{-V:./vendor}"\
\ \
[source.crates-io]\ [source.crates-io]\
registry = "https://crates.io"\
replace-with = "vendored-sources"\ replace-with = "vendored-sources"\
\ EOF}\
[source.vendored-sources]\ %{__rm} -f Cargo.toml.orig\
directory = "./vendor"\
EOF\
%endif\
) )
# __cargo_parse_opts: function-like macro which parses common flags into the # __cargo_parse_opts: function-like macro which parses common flags into the
# equivalent command-line flags for cargo # equivalent command-line flags for cargo
%__cargo_parse_opts(naf:) %{shrink:\ %__cargo_parse_opts(naf:) %{shrink:\
%{-f:%{-a:%{error:Can't specify both -f(%{-f*}) and -a}}} \ %{-n:%{-a:%{error:Can't specify both -n and -a}}} \
%{-n:--no-default-features} \ %{-f:%{-a:%{error:Can't specify both -f(%{-f*}) and -a}}} \
%{-a:--all-features} \ %{-n:--no-default-features} \
%{-f:--features %{-f*}} \ %{-a:--all-features} \
%{nil} %{-f:--features %{-f*}} \
%{nil} \
} }
# cargo_build: builds the crate with cargo with the specified feature flags # cargo_build: builds the crate with cargo with the specified feature flags
%cargo_build(naf:) \ %cargo_build(naf:)\
%{shrink:\ %{shrink: \
%{__cargo} build \ %{__cargo} build \
%{__cargo_common_opts} \ %{__cargo_common_opts} \
--release \ --profile rpm \
%{__cargo_parse_opts %{-n} %{-a} %{-f:-f%{-f*}}} \ %{__cargo_parse_opts %{-n} %{-a} %{-f:-f%{-f*}}} \
%* \ %* \
} }
# cargo_test: runs the test suite with cargo with the specified feature flags # cargo_test: runs the test suite with cargo with the specified feature flags
@ -113,14 +143,14 @@ EOF\
# macro argument parsing and "cargo test" argument parsing need to be bypassed, # macro argument parsing and "cargo test" argument parsing need to be bypassed,
# i.e. "%%cargo_test -- -- --skip foo" for skipping all tests with names that # i.e. "%%cargo_test -- -- --skip foo" for skipping all tests with names that
# match "foo". # match "foo".
%cargo_test(naf:) \ %cargo_test(naf:)\
%{shrink:\ %{shrink: \
%{__cargo} test \ %{__cargo} test \
%{__cargo_common_opts} \ %{__cargo_common_opts} \
--release \ --profile rpm \
--no-fail-fast \ --no-fail-fast \
%{__cargo_parse_opts %{-n} %{-a} %{-f:-f%{-f*}}} \ %{__cargo_parse_opts %{-n} %{-a} %{-f:-f%{-f*}}} \
%* \ %* \
} }
# cargo_install: install files into the buildroot # cargo_install: install files into the buildroot
@ -130,16 +160,18 @@ EOF\
# "$CARGO_HOME/.crates.toml" file, which is used to keep track of which version # "$CARGO_HOME/.crates.toml" file, which is used to keep track of which version
# of a specific binary has been installed, but which conflicts between builds # of a specific binary has been installed, but which conflicts between builds
# of different Rust applications and is not needed when building RPM packages. # of different Rust applications and is not needed when building RPM packages.
%cargo_install(t:naf:) ( \ %cargo_install(t:naf:)\
set -eu \ (\
%{shrink: \ set -euo pipefail \
%{__cargo} install \ %{shrink: \
%{__cargo_common_opts} \ %{__cargo} install \
--no-track \ %{__cargo_common_opts} \
--path . \ --profile rpm \
%{__cargo_parse_opts %{-n} %{-a} %{-f:-f%{-f*}}} \ --no-track \
%* \ --path . \
} \ %{__cargo_parse_opts %{-n} %{-a} %{-f:-f%{-f*}}} \
%* \
} \
) )
# cargo_license: print license information for all crate dependencies # cargo_license: print license information for all crate dependencies
@ -156,19 +188,21 @@ set -eu \
# The "cargo tree" command called by this macro will fail if there are missing # The "cargo tree" command called by this macro will fail if there are missing
# (optional) dependencies. # (optional) dependencies.
%cargo_license(naf:)\ %cargo_license(naf:)\
%{shrink:\ (\
set -euo pipefail\
%{shrink: \
%{__cargo} tree \ %{__cargo} tree \
--workspace \ --workspace \
--offline \ --offline \
--edges no-build,no-dev,no-proc-macro \ --edges no-build,no-dev,no-proc-macro \
--no-dedupe \ --no-dedupe \
--target all \
%{__cargo_parse_opts %{-n} %{-a} %{-f:-f%{-f*}}} \ %{__cargo_parse_opts %{-n} %{-a} %{-f:-f%{-f*}}} \
--prefix none \ --prefix none \
--format "{l}: {p}" \ --format "{l}: {p}" \
| sed -e "s: ($(pwd)[^)]*)::g" -e "s: / :/:g" -e "s:/: OR :g" \ | sed -e "s: ($(pwd)[^)]*)::g" -e "s: / :/:g" -e "s:/: OR :g" \
| sort -u | sort -u \
} }\
)
# cargo_license_summary: print license summary for all crate dependencies # cargo_license_summary: print license summary for all crate dependencies
# #
@ -177,16 +211,46 @@ set -eu \
# in the dependency tree. This is useful for determining the correct License # in the dependency tree. This is useful for determining the correct License
# tag for packages that contain compiled Rust binaries. # tag for packages that contain compiled Rust binaries.
%cargo_license_summary(naf:)\ %cargo_license_summary(naf:)\
%{shrink:\ (\
set -euo pipefail\
%{shrink: \
%{__cargo} tree \ %{__cargo} tree \
--workspace \ --workspace \
--offline \ --offline \
--edges no-build,no-dev,no-proc-macro \ --edges no-build,no-dev,no-proc-macro \
--no-dedupe \ --no-dedupe \
--target all \
%{__cargo_parse_opts %{-n} %{-a} %{-f:-f%{-f*}}} \ %{__cargo_parse_opts %{-n} %{-a} %{-f:-f%{-f*}}} \
--prefix none \ --prefix none \
--format "# {l}" \ --format "# {l}" \
| sed -e "s: / :/:g" -e "s:/: OR :g" \ | sed -e "s: / :/:g" -e "s:/: OR :g" \
| sort -u \ | sort -u \
} }\
)
# cargo_vendor_manifest: write list of vendored crates and their versions
#
# The arguments for the internal "cargo tree" call emulate the logic
# that determines which crates are included when running "cargo vendor".
# The results are written to "cargo-vendor.txt".
#
# TODO: --all-features may be overly broad; this should be modified to
# use %%__cargo_parse_opts to handle feature flags.
%cargo_vendor_manifest()\
(\
set -euo pipefail\
%{shrink: \
%{__cargo} tree \
--workspace \
--offline \
--edges normal,build \
--no-dedupe \
--all-features \
--prefix none \
--format "{p}" \
| grep -v "$(pwd)" \
| sed -e "s: (proc-macro)::" \
| sort -u \
> cargo-vendor.txt \
}\
)

View File

@ -126,6 +126,8 @@ Patch6: 0001-bootstrap-only-show-PGO-warnings-when-verbose.patch
# Simple rpm macros for rust-toolset (as opposed to full rust-packaging) # Simple rpm macros for rust-toolset (as opposed to full rust-packaging)
Source100: macros.rust-toolset Source100: macros.rust-toolset
Source101: macros.rust-srpm Source101: macros.rust-srpm
Source102: cargo_vendor.attr
Source103: cargo_vendor.prov
# Disable cargo->libgit2->libssh2 on RHEL, as it's not approved for FIPS (rhbz1732949) # Disable cargo->libgit2->libssh2 on RHEL, as it's not approved for FIPS (rhbz1732949)
Patch100: rustc-1.75.0-disable-libssh2.patch Patch100: rustc-1.75.0-disable-libssh2.patch
@ -858,6 +860,8 @@ rm -f %{buildroot}%{rustlibdir}/%{rust_triple}/bin/rust-ll*
# This allows users to build packages using Rust Toolset. # This allows users to build packages using Rust Toolset.
%{__install} -D -m 644 %{S:100} %{buildroot}%{rpmmacrodir}/macros.rust-toolset %{__install} -D -m 644 %{S:100} %{buildroot}%{rpmmacrodir}/macros.rust-toolset
%{__install} -D -m 644 %{S:101} %{buildroot}%{rpmmacrodir}/macros.rust-srpm %{__install} -D -m 644 %{S:101} %{buildroot}%{rpmmacrodir}/macros.rust-srpm
%{__install} -D -m 644 %{S:102} %{buildroot}%{_fileattrsdir}/cargo_vendor.attr
%{__install} -D -m 755 %{S:103} %{buildroot}%{_rpmconfigdir}/cargo_vendor.prov
%endif %endif
@ -1042,6 +1046,8 @@ rm -rf "./build/%{rust_triple}/stage2-tools/%{rust_triple}/cit/"
%files toolset %files toolset
%{rpmmacrodir}/macros.rust-toolset %{rpmmacrodir}/macros.rust-toolset
%{_fileattrsdir}/cargo_vendor.attr
%{_rpmconfigdir}/cargo_vendor.prov
%endif %endif