Repository: packit/packit
Issue: cli: `build locally` with preexisting SRPM

Currently the `packit build locally` builds RPM from a specfile, but the `build` subcommand provides an `--srpm` option.

Can be easily worked around by creating a new method that calls `rpmbuild` with an SRPM rather than on specfile.

Changed files:
tests/functional/test_local_build.py, packit/upstream.py, packit/cli/builds/local_build.py

diff --git a/packit/cli/builds/local_build.py b/packit/cli/builds/local_build.py
index b93ff93a..3561cda9 100644
--- a/packit/cli/builds/local_build.py
+++ b/packit/cli/builds/local_build.py
@@ -14,6 +14,36 @@ from packit.utils.changelog_helper import ChangelogHelper
 logger = logging.getLogger("packit")
 
 
+def build_rpms_from_specfile(api, upstream_ref, release_suffix, default_release_suffix):
+    """
+    Build RPMs from the specfile definition and return the paths to the built RPMs.
+    """
+    release_suffix = ChangelogHelper.resolve_release_suffix(
+        api.package_config, release_suffix, default_release_suffix
+    )
+
+    return api.create_rpms(
+        upstream_ref=upstream_ref,
+        release_suffix=release_suffix,
+    )
+
+
+def build_rpms_from_srpm(api, srpm):
+    """
+    Build RPMs from the SRPM and return the paths to the built RPMs.
+    """
+    return api.up.create_rpms_from_srpm(srpm)
+
+
+def log_rpms(rpms):
+    """
+    Print out built RPMs after the RPM build is finished.
+    """
+    logger.info("RPMs:")
+    for path in rpms:
+        logger.info(f" * {path}")
+
+
 @click.command("locally", context_settings=get_context_settings())
 @click.option(
     "--upstream-ref",
@@ -53,15 +83,13 @@ def local(config, upstream_ref, release_suffix, default_release_suffix, path_or_
     it defaults to the current working directory
     """
     api = get_packit_api(config=config, local_project=path_or_url)
-    release_suffix = ChangelogHelper.resolve_release_suffix(
-        api.package_config, release_suffix, default_release_suffix
-    )
 
-    rpm_paths = api.create_rpms(
-        upstream_ref=upstream_ref,
-        release_suffix=release_suffix,
-        # srpm_path=config.srpm_path,
+    rpms = (
+        build_rpms_from_srpm(api, config.srpm_path)
+        if config.srpm_path is not None
+        else build_rpms_from_specfile(
+            api, upstream_ref, release_suffix, default_release_suffix
+        )
     )
-    logger.info("RPMs:")
-    for path in rpm_paths:
-        logger.info(f" * {path}")
+
+    log_rpms(rpms)
diff --git a/packit/upstream.py b/packit/upstream.py
index eb2347e7..bc295522 100644
--- a/packit/upstream.py
+++ b/packit/upstream.py
@@ -570,18 +570,33 @@ class Upstream(PackitRepositoryBase):
             print_live=True,
         ).stdout
 
-    def create_rpms(self, rpm_dir: Union[str, Path] = None) -> List[Path]:
+    def _build_rpms(
+        self,
+        mode: str,
+        rpm_dir: Union[str, Path],
+        rpmbuild_dir: Union[str, Path],
+        src_dir: Union[str, Path],
+        source: Union[str, Path],
+    ) -> List[Path]:
         """
-        Create RPMs from the actual content of the repo.
-        :param rpm_dir: path to the directory where the rpms are meant to be placed
-        :return: paths to the RPMs
+        Wrapper for building RPMs either from SRPM or specfile.
+
+        Args:
+            mode:
+            rpm_dir: Path to the directory where the RPMs are meant to be placed.
+            rpmbuild_dir: Path to the directory used during an RPM build.
+            src_dir: Path to the directory with sources.
+            source: Path to the source used for the build. Either SRPM or specfile.
+
+        Returns:
+            Paths to the built RPMs.
         """
         rpm_dir = rpm_dir or os.getcwd()
         src_dir = rpmbuild_dir = str(self.absolute_specfile_dir)
 
         cmd = [
             "rpmbuild",
-            "-bb",
+            mode,
             "--define",
             f"_sourcedir {rpmbuild_dir}",
             "--define",
@@ -596,7 +611,7 @@ class Upstream(PackitRepositoryBase):
             f"_rpmdir {rpm_dir}",
             "--define",
             f"_buildrootdir {rpmbuild_dir}",
-            self.package_config.specfile_path,
+            str(source),
         ]
 
         escaped_command = " ".join(cmd)
@@ -626,6 +641,42 @@ class Upstream(PackitRepositoryBase):
         rpms = Upstream._get_rpms_from_rpmbuild_output(out)
         return [Path(rpm) for rpm in rpms]
 
+    def create_rpms(self, rpm_dir: Union[str, Path, None] = None) -> List[Path]:
+        """
+        Create RPMs from the actual content of the repo.
+
+        Args:
+            rpm_dir: Path to the directory where the RPMs are meant to be placed.
+
+                Defaults to current working directory, if not given.
+
+        Returns:
+            List of paths to the built RPMs.
+        """
+        rpm_dir = rpm_dir or os.getcwd()
+        src_dir = rpmbuild_dir = str(self.absolute_specfile_dir)
+        return self._build_rpms(
+            "-bb", rpm_dir, rpmbuild_dir, src_dir, self.package_config.specfile_path
+        )
+
+    def create_rpms_from_srpm(
+        self, srpm: Union[str, Path], rpm_dir: Union[str, Path, None] = None
+    ) -> List[Path]:
+        """
+        Build RPMs from the given path to the SRPM.
+
+        Args:
+            srpm: Path to the SRPM to be built.
+            rpm_dir: Path to the directory where the RPMs are meant to be placed.
+
+                Defaults to current working directory, if not given.
+
+        Returns:
+            Paths to the built RPMs.
+        """
+        rpm_dir = rpm_dir or os.getcwd()
+        return self._build_rpms("-rb", rpm_dir, rpm_dir, rpm_dir, srpm)
+
     @staticmethod
     def _get_rpms_from_rpmbuild_output(output: str) -> List[str]:
         """
diff --git a/tests/functional/test_local_build.py b/tests/functional/test_local_build.py
index 3aaeed6d..5bc2acf6 100644
--- a/tests/functional/test_local_build.py
+++ b/tests/functional/test_local_build.py
@@ -5,6 +5,7 @@
 Functional tests for local-build command
 """
 from pathlib import Path
+import re
 from subprocess import CalledProcessError
 
 import pytest
@@ -52,3 +53,20 @@ def test_rpm_command_for_path(ogr_distgit_and_remote):
     )
     rpm_paths = Path.cwd().glob("noarch/*.rpm")
     assert all(rpm_path.exists() for rpm_path in rpm_paths)
+
+
+def test_local_build_from_srpm(ogr_distgit_and_remote):
+    call_real_packit(parameters=["--debug", "srpm"], cwd=ogr_distgit_and_remote[0])
+
+    the_chosen_srpm = list(ogr_distgit_and_remote[0].glob("*.src.rpm"))[0]
+
+    call_real_packit(
+        parameters=["--debug", "build", "--srpm", the_chosen_srpm, "locally"],
+        cwd=ogr_distgit_and_remote[0],
+    )
+    rpm_paths = ogr_distgit_and_remote[0].glob("noarch/*.rpm")
+
+    # we need to be able to find a part of version and release in the built RPMs
+    # if not ⇒ we ran RPM build from the CWD
+    version_release = re.search(r"ogr-(.*)\\.main.*", str(the_chosen_srpm)).group(1)
+    assert all(version_release in str(path) for path in rpm_paths)