diff --git a/cachi2/core/package_managers/rpm/__init__.py b/cachi2/core/package_managers/rpm/__init__.py index e504e43c7..ec5026cce 100644 --- a/cachi2/core/package_managers/rpm/__init__.py +++ b/cachi2/core/package_managers/rpm/__init__.py @@ -1,3 +1,3 @@ -from cachi2.core.package_managers.rpm.main import fetch_rpm_source +from cachi2.core.package_managers.rpm.main import fetch_rpm_source, inject_files_post -__all__ = ["fetch_rpm_source"] +__all__ = ["fetch_rpm_source", "inject_files_post"] diff --git a/cachi2/core/package_managers/rpm/main.py b/cachi2/core/package_managers/rpm/main.py index b53324005..1e8f507af 100644 --- a/cachi2/core/package_managers/rpm/main.py +++ b/cachi2/core/package_managers/rpm/main.py @@ -1,6 +1,7 @@ import asyncio import hashlib import logging +import shlex from os import PathLike from pathlib import Path from typing import Any, Union @@ -214,3 +215,76 @@ def _generate_sbom_components(files_metadata: dict[Path, Any]) -> list[Component ) ) return components + + +def inject_files_post(*args: Any, **kwargs: Any) -> None: + """Run extra tasks for the rpm package manager (callback method) within `inject-files` cmd.""" + if "from_output_dir" in kwargs and "for_output_dir" in kwargs: + from_output_dir = kwargs["from_output_dir"] + for_output_dir = kwargs["for_output_dir"] + + if Path.exists(from_output_dir.joinpath(DEFAULT_PACKAGE_DIR)): + generate_repos(from_output_dir) + generate_repofiles(from_output_dir, for_output_dir) + + +def generate_repos(from_output_dir: Path) -> None: + """Search structure for all repoid dirs and create repository metadata \ + out of its RPMs (and SRPMs).""" + package_dir = from_output_dir.joinpath(DEFAULT_PACKAGE_DIR) + for arch in package_dir.iterdir(): + if not arch.is_dir(): + continue + for repoid in arch.iterdir(): + if not repoid.is_dir() or repoid.name == "repos.d": + continue + createrepo(repoid.name, repoid) + + +def createrepo(reponame: str, repodir: Path) -> None: + """Execute the createrepo utility.""" + log.info(f"Creating repository metadata for repoid '{reponame}': {repodir}") + cmd = ["createrepo_c", str(repodir)] + log.debug("$ " + shlex.join(cmd)) + stdout = run_cmd(cmd, params={}) + log.debug(stdout) + + +def generate_repofiles(from_output_dir: Path, for_output_dir: Path) -> None: + """ + Generate templates of repofiles for all arches. + + Search structure at 'path' and generate one repofile content for each arch. + Each repofile contains all arch's repoids (including repoids with source RPMs). + Repofile (cachi2.repo) for particular arch will be stored in arch's dir in 'repos.d' subdir. + Repofiles are not directly created from the templates in this method - templates are stored + in the project file. + """ + package_dir = from_output_dir.joinpath(DEFAULT_PACKAGE_DIR) + for arch in package_dir.iterdir(): + if not arch.is_dir(): + continue + log.debug(f"Preparing repofile content for arch '{arch.name}'") + content = "" + for repoid in arch.iterdir(): + if not repoid.is_dir() or repoid.name == "repos.d": + continue + localpath = for_output_dir.joinpath(DEFAULT_PACKAGE_DIR, arch.name, repoid.name) + content += f"[{repoid.name}]\n" + content += f"baseurl=file://{localpath}\n" + content += "gpgcheck=1\n" + if repoid.name.startswith("cachi2-"): # repoid directory matches the internal repoid + content += ( + "name=Generated repository containing all packages unaffiliated " + "with any official repository\n" + ) + if content: + repo_file_path = arch.joinpath("repos.d", "cachi2.repo") + if repo_file_path.exists(): + log.info(f"Overwriting {repo_file_path}") + else: + Path.mkdir(arch.joinpath("repos.d"), parents=True, exist_ok=True) + log.info(f"Creating {repo_file_path}") + + with open(repo_file_path, "w") as repo_file: + repo_file.write(content.strip()) diff --git a/cachi2/core/resolver.py b/cachi2/core/resolver.py index eca90178c..49c35bc54 100644 --- a/cachi2/core/resolver.py +++ b/cachi2/core/resolver.py @@ -1,7 +1,7 @@ from collections.abc import Iterable from pathlib import Path from tempfile import TemporaryDirectory -from typing import Callable +from typing import Any, Callable from cachi2.core.errors import UnsupportedFeature from cachi2.core.models.input import PackageManagerType, Request @@ -84,3 +84,11 @@ def _merge_outputs(outputs: Iterable[RequestOutput]) -> RequestOutput: environment_variables=env_vars, project_files=project_files, ) + + +def inject_files_post(*args: Any, **kwargs: Any) -> None: + """Do extra steps for package manager.""" + # if there is a callback method defined within the particular package manager, run it + if hasattr(rpm, "inject_files_post"): + callback_method = getattr(rpm, "inject_files_post") + callback_method(*args, **kwargs) diff --git a/cachi2/interface/cli.py b/cachi2/interface/cli.py index 62717b4e9..5f4eb0954 100644 --- a/cachi2/interface/cli.py +++ b/cachi2/interface/cli.py @@ -15,7 +15,7 @@ from cachi2.core.extras.envfile import EnvFormat, generate_envfile from cachi2.core.models.input import Flag, PackageInput, Request, parse_user_input from cachi2.core.models.output import BuildConfig -from cachi2.core.resolver import resolve_packages, supported_package_managers +from cachi2.core.resolver import inject_files_post, resolve_packages, supported_package_managers from cachi2.core.rooted_path import RootedPath from cachi2.interface.logging import LogLevel, setup_logging @@ -339,6 +339,8 @@ def inject_files( content = project_file.resolve_content(output_dir=for_output_dir) project_file.abspath.write_text(content) + inject_files_post(from_output_dir=from_output_dir, for_output_dir=for_output_dir) + def _get_build_config(output_dir: Path) -> BuildConfig: build_config_json = RootedPath(output_dir).join_within_root(".build-config.json").path diff --git a/pyproject.toml b/pyproject.toml index ed131212f..88e3faa23 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,6 +24,7 @@ dependencies = [ "aiohttp-retry", "backoff", "beautifulsoup4", + "createrepo_c", "gitpython", "packageurl-python", "packaging",