Merge bitcoin/bitcoin#34831: lint: remove excluded files from whitespace check

f55c891a65 lint: more reuse of SHARED_EXCLUDED_SUBTREES (fanquake)
8864917d8b lint: add missing ipc/test to grep_boost_fixture_test_suite (fanquake)
ecefc12927 lint: fix lint issue in lint script (fanquake)
ee8c22eb6a contrib: fix whitespace issues in scripts (fanquake)
04e2118372 lint: remove excluded .cpp/.h files from whitespace check (fanquake)

Pull request description:

  The `.cpp`/`.h` have been fixed since #32482 and 5d25a82b9a.
  Fix other offending files.

ACKs for top commit:
  stickies-v:
    re-ACK f55c891a65
  maflcko:
    re-ACK f55c891a65 🗿
  kevkevinpal:
    ACK [f55c891](f55c891a65)
  sedited:
    Re-ACK f55c891a65

Tree-SHA512: 975c217d1cc82433b7960dad47976254d36526f3bd156133dcd4eb51b34616beb89e1449c245deb8b41ab6e1f746a96a0b629013e1996f355e521d075f4692c8
This commit is contained in:
merge-script
2026-03-16 21:43:50 +01:00
10 changed files with 98 additions and 108 deletions

View File

@@ -1,6 +1,6 @@
#!/sbin/openrc-run #!/sbin/openrc-run
# backward compatibility for existing gentoo layout # backward compatibility for existing gentoo layout
# #
if [ -d "/var/lib/bitcoin/.bitcoin" ]; then if [ -d "/var/lib/bitcoin/.bitcoin" ]; then
BITCOIND_DEFAULT_DATADIR="/var/lib/bitcoin/.bitcoin" BITCOIND_DEFAULT_DATADIR="/var/lib/bitcoin/.bitcoin"

View File

@@ -42,13 +42,13 @@ class FrameworkInfo(object):
self.sourceContentsDirectory = "" self.sourceContentsDirectory = ""
self.destinationResourcesDirectory = "" self.destinationResourcesDirectory = ""
self.destinationVersionContentsDirectory = "" self.destinationVersionContentsDirectory = ""
def __eq__(self, other): def __eq__(self, other):
if self.__class__ == other.__class__: if self.__class__ == other.__class__:
return self.__dict__ == other.__dict__ return self.__dict__ == other.__dict__
else: else:
return False return False
def __str__(self): def __str__(self):
return f""" Framework name: {self.frameworkName} return f""" Framework name: {self.frameworkName}
Framework directory: {self.frameworkDirectory} Framework directory: {self.frameworkDirectory}
@@ -62,51 +62,51 @@ class FrameworkInfo(object):
Source file Path: {self.sourceFilePath} Source file Path: {self.sourceFilePath}
Deployed Directory (relative to bundle): {self.destinationDirectory} Deployed Directory (relative to bundle): {self.destinationDirectory}
""" """
def isDylib(self): def isDylib(self):
return self.frameworkName.endswith(".dylib") return self.frameworkName.endswith(".dylib")
def isQtFramework(self): def isQtFramework(self):
if self.isDylib(): if self.isDylib():
return self.frameworkName.startswith("libQt") return self.frameworkName.startswith("libQt")
else: else:
return self.frameworkName.startswith("Qt") return self.frameworkName.startswith("Qt")
reOLine = re.compile(r'^(.+) \(compatibility version [0-9.]+, current version [0-9.]+\)$') reOLine = re.compile(r'^(.+) \(compatibility version [0-9.]+, current version [0-9.]+\)$')
bundleFrameworkDirectory = "Contents/Frameworks" bundleFrameworkDirectory = "Contents/Frameworks"
bundleBinaryDirectory = "Contents/MacOS" bundleBinaryDirectory = "Contents/MacOS"
@classmethod @classmethod
def fromLibraryLine(cls, line: str) -> Optional['FrameworkInfo']: def fromLibraryLine(cls, line: str) -> Optional['FrameworkInfo']:
# Note: line must be trimmed # Note: line must be trimmed
if line == "": if line == "":
return None return None
# Don't deploy system libraries # Don't deploy system libraries
if line.startswith("/System/Library/") or line.startswith("@executable_path") or line.startswith("/usr/lib/"): if line.startswith("/System/Library/") or line.startswith("@executable_path") or line.startswith("/usr/lib/"):
return None return None
m = cls.reOLine.match(line) m = cls.reOLine.match(line)
if m is None: if m is None:
raise RuntimeError(f"Line could not be parsed: {line}") raise RuntimeError(f"Line could not be parsed: {line}")
path = m.group(1) path = m.group(1)
info = cls() info = cls()
info.sourceFilePath = path info.sourceFilePath = path
info.installName = path info.installName = path
if path.endswith(".dylib"): if path.endswith(".dylib"):
dirname, filename = os.path.split(path) dirname, filename = os.path.split(path)
info.frameworkName = filename info.frameworkName = filename
info.frameworkDirectory = dirname info.frameworkDirectory = dirname
info.frameworkPath = path info.frameworkPath = path
info.binaryDirectory = dirname info.binaryDirectory = dirname
info.binaryName = filename info.binaryName = filename
info.binaryPath = path info.binaryPath = path
info.version = "-" info.version = "-"
info.installName = path info.installName = path
info.deployedInstallName = f"@executable_path/../Frameworks/{info.binaryName}" info.deployedInstallName = f"@executable_path/../Frameworks/{info.binaryName}"
info.sourceFilePath = path info.sourceFilePath = path
@@ -121,25 +121,25 @@ class FrameworkInfo(object):
i += 1 i += 1
if i == len(parts): if i == len(parts):
raise RuntimeError(f"Could not find .framework or .dylib in line: {line}") raise RuntimeError(f"Could not find .framework or .dylib in line: {line}")
info.frameworkName = parts[i] info.frameworkName = parts[i]
info.frameworkDirectory = "/".join(parts[:i]) info.frameworkDirectory = "/".join(parts[:i])
info.frameworkPath = os.path.join(info.frameworkDirectory, info.frameworkName) info.frameworkPath = os.path.join(info.frameworkDirectory, info.frameworkName)
info.binaryName = parts[i+3] info.binaryName = parts[i+3]
info.binaryDirectory = "/".join(parts[i+1:i+3]) info.binaryDirectory = "/".join(parts[i+1:i+3])
info.binaryPath = os.path.join(info.binaryDirectory, info.binaryName) info.binaryPath = os.path.join(info.binaryDirectory, info.binaryName)
info.version = parts[i+2] info.version = parts[i+2]
info.deployedInstallName = f"@executable_path/../Frameworks/{os.path.join(info.frameworkName, info.binaryPath)}" info.deployedInstallName = f"@executable_path/../Frameworks/{os.path.join(info.frameworkName, info.binaryPath)}"
info.destinationDirectory = os.path.join(cls.bundleFrameworkDirectory, info.frameworkName, info.binaryDirectory) info.destinationDirectory = os.path.join(cls.bundleFrameworkDirectory, info.frameworkName, info.binaryDirectory)
info.sourceResourcesDirectory = os.path.join(info.frameworkPath, "Resources") info.sourceResourcesDirectory = os.path.join(info.frameworkPath, "Resources")
info.sourceContentsDirectory = os.path.join(info.frameworkPath, "Contents") info.sourceContentsDirectory = os.path.join(info.frameworkPath, "Contents")
info.sourceVersionContentsDirectory = os.path.join(info.frameworkPath, "Versions", info.version, "Contents") info.sourceVersionContentsDirectory = os.path.join(info.frameworkPath, "Versions", info.version, "Contents")
info.destinationResourcesDirectory = os.path.join(cls.bundleFrameworkDirectory, info.frameworkName, "Resources") info.destinationResourcesDirectory = os.path.join(cls.bundleFrameworkDirectory, info.frameworkName, "Resources")
info.destinationVersionContentsDirectory = os.path.join(cls.bundleFrameworkDirectory, info.frameworkName, "Versions", info.version, "Contents") info.destinationVersionContentsDirectory = os.path.join(cls.bundleFrameworkDirectory, info.frameworkName, "Versions", info.version, "Contents")
return info return info
class ApplicationBundleInfo(object): class ApplicationBundleInfo(object):
@@ -289,45 +289,45 @@ def copyFramework(framework: FrameworkInfo, path: str, verbose: int) -> Optional
def deployFrameworks(frameworks: list[FrameworkInfo], bundlePath: str, binaryPath: str, strip: bool, verbose: int, deploymentInfo: Optional[DeploymentInfo] = None) -> DeploymentInfo: def deployFrameworks(frameworks: list[FrameworkInfo], bundlePath: str, binaryPath: str, strip: bool, verbose: int, deploymentInfo: Optional[DeploymentInfo] = None) -> DeploymentInfo:
if deploymentInfo is None: if deploymentInfo is None:
deploymentInfo = DeploymentInfo() deploymentInfo = DeploymentInfo()
while len(frameworks) > 0: while len(frameworks) > 0:
framework = frameworks.pop(0) framework = frameworks.pop(0)
deploymentInfo.deployedFrameworks.append(framework.frameworkName) deploymentInfo.deployedFrameworks.append(framework.frameworkName)
print("Processing", framework.frameworkName, "...") print("Processing", framework.frameworkName, "...")
# Get the Qt path from one of the Qt frameworks # Get the Qt path from one of the Qt frameworks
if deploymentInfo.qtPath is None and framework.isQtFramework(): if deploymentInfo.qtPath is None and framework.isQtFramework():
deploymentInfo.detectQtPath(framework.frameworkDirectory) deploymentInfo.detectQtPath(framework.frameworkDirectory)
if framework.installName.startswith("@executable_path") or framework.installName.startswith(bundlePath): if framework.installName.startswith("@executable_path") or framework.installName.startswith(bundlePath):
print(framework.frameworkName, "already deployed, skipping.") print(framework.frameworkName, "already deployed, skipping.")
continue continue
# install_name_tool the new id into the binary # install_name_tool the new id into the binary
changeInstallName(framework.installName, framework.deployedInstallName, binaryPath, verbose) changeInstallName(framework.installName, framework.deployedInstallName, binaryPath, verbose)
# Copy framework to app bundle. # Copy framework to app bundle.
deployedBinaryPath = copyFramework(framework, bundlePath, verbose) deployedBinaryPath = copyFramework(framework, bundlePath, verbose)
# Skip the rest if already was deployed. # Skip the rest if already was deployed.
if deployedBinaryPath is None: if deployedBinaryPath is None:
continue continue
if strip: if strip:
runStrip(deployedBinaryPath, verbose) runStrip(deployedBinaryPath, verbose)
# install_name_tool it a new id. # install_name_tool it a new id.
changeIdentification(framework.deployedInstallName, deployedBinaryPath, verbose) changeIdentification(framework.deployedInstallName, deployedBinaryPath, verbose)
# Check for framework dependencies # Check for framework dependencies
dependencies = getFrameworks(deployedBinaryPath, verbose, rpath=framework.frameworkDirectory) dependencies = getFrameworks(deployedBinaryPath, verbose, rpath=framework.frameworkDirectory)
for dependency in dependencies: for dependency in dependencies:
changeInstallName(dependency.installName, dependency.deployedInstallName, deployedBinaryPath, verbose) changeInstallName(dependency.installName, dependency.deployedInstallName, deployedBinaryPath, verbose)
# Deploy framework if necessary. # Deploy framework if necessary.
if dependency.frameworkName not in deploymentInfo.deployedFrameworks and dependency not in frameworks: if dependency.frameworkName not in deploymentInfo.deployedFrameworks and dependency not in frameworks:
frameworks.append(dependency) frameworks.append(dependency)
return deploymentInfo return deploymentInfo
def deployFrameworksForAppBundle(applicationBundle: ApplicationBundleInfo, strip: bool, verbose: int) -> DeploymentInfo: def deployFrameworksForAppBundle(applicationBundle: ApplicationBundleInfo, strip: bool, verbose: int) -> DeploymentInfo:
@@ -355,29 +355,29 @@ def deployPlugins(appBundleInfo: ApplicationBundleInfo, deploymentInfo: Deployme
continue continue
plugins.append((pluginDirectory, pluginName)) plugins.append((pluginDirectory, pluginName))
for pluginDirectory, pluginName in plugins: for pluginDirectory, pluginName in plugins:
print("Processing plugin", os.path.join(pluginDirectory, pluginName), "...") print("Processing plugin", os.path.join(pluginDirectory, pluginName), "...")
sourcePath = os.path.join(deploymentInfo.pluginPath, pluginDirectory, pluginName) sourcePath = os.path.join(deploymentInfo.pluginPath, pluginDirectory, pluginName)
destinationDirectory = os.path.join(appBundleInfo.pluginPath, pluginDirectory) destinationDirectory = os.path.join(appBundleInfo.pluginPath, pluginDirectory)
if not os.path.exists(destinationDirectory): if not os.path.exists(destinationDirectory):
os.makedirs(destinationDirectory) os.makedirs(destinationDirectory)
destinationPath = os.path.join(destinationDirectory, pluginName) destinationPath = os.path.join(destinationDirectory, pluginName)
shutil.copy2(sourcePath, destinationPath) shutil.copy2(sourcePath, destinationPath)
if verbose: if verbose:
print("Copied:", sourcePath) print("Copied:", sourcePath)
print(" to:", destinationPath) print(" to:", destinationPath)
if strip: if strip:
runStrip(destinationPath, verbose) runStrip(destinationPath, verbose)
dependencies = getFrameworks(destinationPath, verbose) dependencies = getFrameworks(destinationPath, verbose)
for dependency in dependencies: for dependency in dependencies:
changeInstallName(dependency.installName, dependency.deployedInstallName, destinationPath, verbose) changeInstallName(dependency.installName, dependency.deployedInstallName, destinationPath, verbose)
# Deploy framework if necessary. # Deploy framework if necessary.
if dependency.frameworkName not in deploymentInfo.deployedFrameworks: if dependency.frameworkName not in deploymentInfo.deployedFrameworks:
deployFrameworks([dependency], appBundleInfo.path, destinationPath, strip, verbose, deploymentInfo) deployFrameworks([dependency], appBundleInfo.path, destinationPath, strip, verbose, deploymentInfo)
@@ -446,7 +446,7 @@ except RuntimeError as e:
if config.plugins: if config.plugins:
print("+ Deploying plugins +") print("+ Deploying plugins +")
try: try:
deployPlugins(applicationBundle, deploymentInfo, config.strip, verbose) deployPlugins(applicationBundle, deploymentInfo, config.strip, verbose)
except RuntimeError as e: except RuntimeError as e:

View File

@@ -34,9 +34,9 @@ tc filter add dev ${IF} parent 1: protocol ip prio 1 handle 1 fw classid 1:10
tc filter add dev ${IF} parent 1: protocol ip prio 2 handle 2 fw classid 1:11 tc filter add dev ${IF} parent 1: protocol ip prio 2 handle 2 fw classid 1:11
if [ -n "${LOCALNET_V6}" ] ; then if [ -n "${LOCALNET_V6}" ] ; then
# v6 cannot have the same priority value as v4 # v6 cannot have the same priority value as v4
tc filter add dev ${IF} parent 1: protocol ipv6 prio 3 handle 1 fw classid 1:10 tc filter add dev ${IF} parent 1: protocol ipv6 prio 3 handle 1 fw classid 1:10
tc filter add dev ${IF} parent 1: protocol ipv6 prio 4 handle 2 fw classid 1:11 tc filter add dev ${IF} parent 1: protocol ipv6 prio 4 handle 2 fw classid 1:11
fi fi
#delete any existing rules #delete any existing rules
@@ -57,6 +57,6 @@ iptables -t mangle -A OUTPUT -p tcp -m tcp --dport 8333 ! -d ${LOCALNET_V4} -j M
iptables -t mangle -A OUTPUT -p tcp -m tcp --sport 8333 ! -d ${LOCALNET_V4} -j MARK --set-mark 0x2 iptables -t mangle -A OUTPUT -p tcp -m tcp --sport 8333 ! -d ${LOCALNET_V4} -j MARK --set-mark 0x2
if [ -n "${LOCALNET_V6}" ] ; then if [ -n "${LOCALNET_V6}" ] ; then
ip6tables -t mangle -A OUTPUT -p tcp -m tcp --dport 8333 ! -d ${LOCALNET_V6} -j MARK --set-mark 0x4 ip6tables -t mangle -A OUTPUT -p tcp -m tcp --dport 8333 ! -d ${LOCALNET_V6} -j MARK --set-mark 0x4
ip6tables -t mangle -A OUTPUT -p tcp -m tcp --sport 8333 ! -d ${LOCALNET_V6} -j MARK --set-mark 0x4 ip6tables -t mangle -A OUTPUT -p tcp -m tcp --sport 8333 ! -d ${LOCALNET_V6} -j MARK --set-mark 0x4
fi fi

View File

@@ -9,27 +9,27 @@ if [ "$BITCOIN_VERIFY_COMMITS_ALLOW_SHA1" = 1 ]; then
printf '%s\n' "$INPUT" | gpg --trust-model always "$@" 2>/dev/null printf '%s\n' "$INPUT" | gpg --trust-model always "$@" 2>/dev/null
exit $? exit $?
else else
# Note how we've disabled SHA1 with the --weak-digest option, disabling # Note how we've disabled SHA1 with the --weak-digest option, disabling
# signatures - including selfsigs - that use SHA1. While you might think that # signatures - including selfsigs - that use SHA1. While you might think that
# collision attacks shouldn't be an issue as they'd be an attack on yourself, # collision attacks shouldn't be an issue as they'd be an attack on yourself,
# in fact because what's being signed is a commit object that's # in fact because what's being signed is a commit object that's
# semi-deterministically generated by untrusted input (the pull-req) in theory # semi-deterministically generated by untrusted input (the pull-req) in theory
# an attacker could construct a pull-req that results in a commit object that # an attacker could construct a pull-req that results in a commit object that
# they've created a collision for. Not the most likely attack, but preventing # they've created a collision for. Not the most likely attack, but preventing
# it is pretty easy so we do so as a "belt-and-suspenders" measure. # it is pretty easy so we do so as a "belt-and-suspenders" measure.
for LINE in $(gpg --version); do for LINE in $(gpg --version); do
case "$LINE" in case "$LINE" in
"gpg (GnuPG) 1.4.1"*|"gpg (GnuPG) 2.0."*) "gpg (GnuPG) 1.4.1"*|"gpg (GnuPG) 2.0."*)
echo "Please upgrade to at least gpg 2.1.10 to check for weak signatures" > /dev/stderr echo "Please upgrade to at least gpg 2.1.10 to check for weak signatures" > /dev/stderr
printf '%s\n' "$INPUT" | gpg --trust-model always "$@" 2>/dev/null printf '%s\n' "$INPUT" | gpg --trust-model always "$@" 2>/dev/null
exit $? exit $?
;; ;;
# We assume if you're running 2.1+, you're probably running 2.1.10+ # We assume if you're running 2.1+, you're probably running 2.1.10+
# gpg will fail otherwise # gpg will fail otherwise
# We assume if you're running 1.X, it is either 1.4.1X or 1.4.20+ # We assume if you're running 1.X, it is either 1.4.1X or 1.4.20+
# gpg will fail otherwise # gpg will fail otherwise
esac esac
done done
printf '%s\n' "$INPUT" | gpg --trust-model always --weak-digest sha1 "$@" 2>/dev/null printf '%s\n' "$INPUT" | gpg --trust-model always --weak-digest sha1 "$@" 2>/dev/null
exit $? exit $?
fi fi

View File

@@ -47,33 +47,33 @@ fi
# Taken from git-subtree (Copyright (C) 2009 Avery Pennarun <apenwarr@gmail.com>) # Taken from git-subtree (Copyright (C) 2009 Avery Pennarun <apenwarr@gmail.com>)
find_latest_squash() find_latest_squash()
{ {
dir="$1" dir="$1"
sq= sq=
main= main=
sub= sub=
git log --grep="^git-subtree-dir: $dir/*\$" \ git log --grep="^git-subtree-dir: $dir/*\$" \
--pretty=format:'START %H%n%s%n%n%b%nEND%n' "$COMMIT" | --pretty=format:'START %H%n%s%n%n%b%nEND%n' "$COMMIT" |
while read a b _; do while read a b _; do
case "$a" in case "$a" in
START) sq="$b" ;; START) sq="$b" ;;
git-subtree-mainline:) main="$b" ;; git-subtree-mainline:) main="$b" ;;
git-subtree-split:) sub="$b" ;; git-subtree-split:) sub="$b" ;;
END) END)
if [ -n "$sub" ]; then if [ -n "$sub" ]; then
if [ -n "$main" ]; then if [ -n "$main" ]; then
# a rejoin commit? # a rejoin commit?
# Pretend its sub was a squash. # Pretend its sub was a squash.
sq="$sub" sq="$sub"
fi fi
echo "$sq" "$sub" echo "$sq" "$sub"
break break
fi fi
sq= sq=
main= main=
sub= sub=
;; ;;
esac esac
done done
} }
# find latest subtree update # find latest subtree update

View File

@@ -19,7 +19,6 @@ HEADER_ID_PREFIX = 'BITCOIN_'
HEADER_ID_SUFFIX = '_H' HEADER_ID_SUFFIX = '_H'
EXCLUDE_FILES_WITH_PREFIX = ['contrib/devtools/bitcoin-tidy', EXCLUDE_FILES_WITH_PREFIX = ['contrib/devtools/bitcoin-tidy',
'src/crypto/ctaes',
'src/tinyformat.h', 'src/tinyformat.h',
'src/bench/nanobench.h', 'src/bench/nanobench.h',
'src/test/fuzz/FuzzedDataProvider.h'] + SHARED_EXCLUDED_SUBTREES 'src/test/fuzz/FuzzedDataProvider.h'] + SHARED_EXCLUDED_SUBTREES

View File

@@ -40,6 +40,8 @@ import sys
from subprocess import check_output, CalledProcessError from subprocess import check_output, CalledProcessError
from lint_ignore_dirs import SHARED_EXCLUDED_SUBTREES
KNOWN_VIOLATIONS = [ KNOWN_VIOLATIONS = [
"src/dbwrapper.cpp:.*vsnprintf", "src/dbwrapper.cpp:.*vsnprintf",
@@ -50,13 +52,8 @@ KNOWN_VIOLATIONS = [
] ]
REGEXP_EXTERNAL_DEPENDENCIES_EXCLUSIONS = [ REGEXP_EXTERNAL_DEPENDENCIES_EXCLUSIONS = [
"src/crypto/ctaes/",
"src/ipc/libmultiprocess/",
"src/leveldb/",
"src/secp256k1/",
"src/minisketch/",
"src/tinyformat.h", "src/tinyformat.h",
] ] + SHARED_EXCLUDED_SUBTREES
LOCALE_DEPENDENT_FUNCTIONS = [ LOCALE_DEPENDENT_FUNCTIONS = [
"alphasort", # LC_COLLATE (via strcoll) "alphasort", # LC_COLLATE (via strcoll)

View File

@@ -20,6 +20,7 @@ def grep_boost_fixture_test_suite():
"-E", "-E",
r"^BOOST_FIXTURE_TEST_SUITE\(", r"^BOOST_FIXTURE_TEST_SUITE\(",
"--", "--",
"src/ipc/test/**.cpp",
"src/test/**.cpp", "src/test/**.cpp",
"src/wallet/test/**.cpp", "src/wallet/test/**.cpp",
] ]

View File

@@ -9,4 +9,5 @@ SHARED_EXCLUDED_SUBTREES = ["src/leveldb/",
"src/secp256k1/", "src/secp256k1/",
"src/minisketch/", "src/minisketch/",
"src/ipc/libmultiprocess/", "src/ipc/libmultiprocess/",
"src/crypto/ctaes/",
] ]

View File

@@ -18,17 +18,9 @@ fn get_pathspecs_exclude_whitespace() -> Vec<String> {
"contrib/windeploy/win-codesign.cert", "contrib/windeploy/win-codesign.cert",
"doc/README_windows.txt", "doc/README_windows.txt",
// Temporary excludes, or existing violations // Temporary excludes, or existing violations
"contrib/init/bitcoind.openrc",
"contrib/macdeploy/macdeployqtplus",
"src/crypto/sha256_sse4.cpp",
"src/qt/res/src/*.svg", "src/qt/res/src/*.svg",
"test/functional/test_framework/crypto/ellswift_decode_test_vectors.csv", "test/functional/test_framework/crypto/ellswift_decode_test_vectors.csv",
"test/functional/test_framework/crypto/xswiftec_inv_test_vectors.csv", "test/functional/test_framework/crypto/xswiftec_inv_test_vectors.csv",
"contrib/qos/tc.sh",
"contrib/verify-commits/gpg.sh",
"src/univalue/include/univalue_escapes.h",
"src/univalue/test/object.cpp",
"test/lint/git-subtree-check.sh",
] ]
.iter() .iter()
.map(|s| format!(":(exclude){s}")), .map(|s| format!(":(exclude){s}")),