From b65a590719a4dde05d13e49c726522f16519f2dd Mon Sep 17 00:00:00 2001 From: Fabio Valentini Date: May 05 2022 17:13:41 +0000 Subject: spectool: implement support for SourceScript This commit adds support for automatically running an external script for generating Source files (for example, if the upstream sources need to be cleaned of unwanted files and repackaged). This functionality is triggered if any specified Source file is a local file, or when downloading all sources. In this case, the spec file contents are parsed and if a line matches the following format # SourceScript: path-to-script.sh The script is executed, with the package version supplied as an argument: ./path-to-script.sh $VERSION In environments where running user-supplied code is not desirable, the "--safe" CLI parameter can be used to turn off executing detected SourceScript files. --- diff --git a/pyproject.toml b/pyproject.toml index aa4949a..55ec8d7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,2 +1,2 @@ [tool.black] -line-length = 100 +line-length = 120 diff --git a/rpmdev-spectool b/rpmdev-spectool index 1d57256..836ca5c 100755 --- a/rpmdev-spectool +++ b/rpmdev-spectool @@ -22,6 +22,7 @@ import argparse import os +import subprocess import tempfile import time from collections import OrderedDict @@ -191,6 +192,14 @@ def get_args() -> dict: help="output debug info, don't clean up when done", ) + misc.add_argument( + "--safe", + action="store_const", + const=True, + default=False, + help="run in safe mode (i.e. do not run a detected SourceScript)", + ) + specfile = parser.add_argument("specfile", action="store") if argcomplete: @@ -339,7 +348,7 @@ class Spec: self.print_patch(number, value) @staticmethod - def _get_file(value: str, directory: str, force: bool, dry: bool): + def _get_file(value: str, directory: str, force: bool, dry: bool) -> bool: parsed = urlparse(value) if "#" not in value: @@ -350,8 +359,7 @@ class Spec: basename = basename.lstrip("/") except ValueError: # multiple "#" characters inside - print("Invalid URL:", value) - return + raise ValueError(f"Invalid URL: {value}") if parsed.scheme: if not dry: @@ -361,6 +369,7 @@ class Spec: print("Downloading: {}".format(value)) os.makedirs(directory, exist_ok=True) really = get_file(value, path, force) + if really: print("Downloaded: {}".format(basename)) @@ -379,11 +388,16 @@ class Spec: else: print("Would have downloaded: {}".format(value)) - def get_source(self, number: int, directory: str, force: bool, dry: bool, value: str = None): + return True + + else: + return False + + def get_source(self, number: int, directory: str, force: bool, dry: bool, value: str = None) -> bool: if not value: value = self.sources[number] - self._get_file(value, directory, force, dry) + return self._get_file(value, directory, force, dry) def get_patch(self, number: int, directory: str, force: bool, dry: bool, value: str = None): if not value: @@ -391,14 +405,40 @@ class Spec: self._get_file(value, directory, force, dry) - def get_sources(self, directory: str, force: bool, dry: bool): + def get_sources(self, directory: str, force: bool, dry: bool, safe: bool): + any_local = False for number, value in self.sources.items(): - self.get_source(number, directory, force, dry, value) + any_local = any_local or not self.get_source(number, directory, force, dry, value) + + if any_local: + self.run_source_script(safe) def get_patches(self, directory: str, force: bool, dry: bool): for number, value in self.patches.items(): self.get_patch(number, directory, force, dry, value) + def run_source_script(self, safe: bool): + with open(self.path) as file: + contents = file.read() + + source_script = None + for line in contents.splitlines(): + if line.startswith("# SourceScript:"): + source_script = line.split()[2] + break + + if source_script and safe: + print("Not running detected SourceScript in safe mode.") + return + + if source_script: + version = self.spec.sourceHeader[rpm.RPMTAG_VERSION] + print(f"Running script to generate sources: {source_script} {version}") + + cmd = [f"./{source_script}", version] + ret = subprocess.run(cmd) + ret.check_returncode() + def main() -> int: args = get_args() @@ -484,6 +524,7 @@ def main() -> int: if args["get_files"]: force = args["force"] dry = args["dry_run"] + safe = args["safe"] if args["directory"] and args["sourcedir"]: print("Conflicting requests for download directory.") @@ -504,10 +545,12 @@ def main() -> int: print("No patch with number '{}' found.".format(number)) continue - spec.get_source(number, directory, force, dry) + local = not spec.get_source(number, directory, force, dry) + if local: + spec.run_source_script(safe) elif args["sources"] and not args["patch"]: - spec.get_sources(directory, force, dry) + spec.get_sources(directory, force, dry, safe) if args["patch"]: numbers = split_numbers(args["patch"])