diff --git a/docs/ahriman.1 b/docs/ahriman.1 index 4167e288..4e91cd1b 100644 --- a/docs/ahriman.1 +++ b/docs/ahriman.1 @@ -507,9 +507,10 @@ root path of the extracted files .SH COMMAND \fI\,'ahriman repo\-setup'\/\fR usage: ahriman repo\-setup [\-h] [\-\-build\-as\-user BUILD_AS_USER] [\-\-build\-command BUILD_COMMAND] - [\-\-from\-configuration FROM_CONFIGURATION] [\-\-multilib | \-\-no\-multilib] \-\-packager PACKAGER - \-\-repository REPOSITORY [\-\-sign\-key SIGN_KEY] [\-\-sign\-target {disabled,pacakges,repository}] - [\-\-web\-port WEB_PORT] [\-\-web\-unix\-socket WEB_UNIX_SOCKET] + [\-\-from\-configuration FROM_CONFIGURATION] [\-\-makeflags\-jobs | \-\-no\-makeflags\-jobs] + [\-\-multilib | \-\-no\-multilib] \-\-packager PACKAGER \-\-repository REPOSITORY [\-\-sign\-key SIGN_KEY] + [\-\-sign\-target {disabled,pacakges,repository}] [\-\-web\-port WEB_PORT] + [\-\-web\-unix\-socket WEB_UNIX_SOCKET] create initial service configuration, requires root @@ -526,6 +527,10 @@ build command prefix \fB\-\-from\-configuration\fR \fI\,FROM_CONFIGURATION\/\fR path to default devtools pacman configuration +.TP +\fB\-\-makeflags\-jobs\fR, \fB\-\-no\-makeflags\-jobs\fR +append MAKEFLAGS variable with parallelism set to number of cores (default: True) + .TP \fB\-\-multilib\fR, \fB\-\-no\-multilib\fR add or do not multilib repository (default: True) diff --git a/src/ahriman/application/ahriman.py b/src/ahriman/application/ahriman.py index 3bc64869..c9218e79 100644 --- a/src/ahriman/application/ahriman.py +++ b/src/ahriman/application/ahriman.py @@ -632,6 +632,8 @@ def _set_repo_setup_parser(root: SubParserAction) -> argparse.ArgumentParser: parser.add_argument("--build-command", help="build command prefix", default="ahriman") parser.add_argument("--from-configuration", help="path to default devtools pacman configuration", type=Path, default=Path("/usr/share/devtools/pacman-extra.conf")) + parser.add_argument("--makeflags-jobs", help="append MAKEFLAGS variable with parallelism set to number of cores", + action=argparse.BooleanOptionalAction, default=True) parser.add_argument("--multilib", help="add or do not multilib repository", action=argparse.BooleanOptionalAction, default=True) parser.add_argument("--packager", help="packager name and email", required=True) diff --git a/src/ahriman/application/handlers/setup.py b/src/ahriman/application/handlers/setup.py index 6da30387..d328a8de 100644 --- a/src/ahriman/application/handlers/setup.py +++ b/src/ahriman/application/handlers/setup.py @@ -64,7 +64,7 @@ class Setup(Handler): application = Application(architecture, configuration, report=report, unsafe=unsafe) - Setup.configuration_create_makepkg(args.packager, application.repository.paths) + Setup.configuration_create_makepkg(args.packager, args.makeflags_jobs, application.repository.paths) Setup.executable_create(application.repository.paths, args.build_command, architecture) Setup.configuration_create_devtools(args.build_command, architecture, args.from_configuration, args.multilib, args.repository, application.repository.paths) @@ -170,17 +170,23 @@ class Setup(Handler): configuration.write(devtools_configuration) @staticmethod - def configuration_create_makepkg(packager: str, paths: RepositoryPaths) -> None: + def configuration_create_makepkg(packager: str, makeflags_jobs: bool, paths: RepositoryPaths) -> None: """ create configuration for makepkg Args: packager(str): packager identifier (e.g. name, email) + makeflags_jobs(bool): set MAKEFLAGS variable to number of cores paths(RepositoryPaths): repository paths instance """ + + content = f"PACKAGER='{packager}'\n" + if makeflags_jobs: + content += """MAKEFLAGS="-j$(nproc)"\n""" + uid, _ = paths.root_owner home_dir = Path(getpwuid(uid).pw_dir) - (home_dir / ".makepkg.conf").write_text(f"PACKAGER='{packager}'\n", encoding="utf8") + (home_dir / ".makepkg.conf").write_text(content, encoding="utf8") @staticmethod def configuration_create_sudo(paths: RepositoryPaths, prefix: str, architecture: str) -> None: diff --git a/src/ahriman/core/util.py b/src/ahriman/core/util.py index 5315bbc4..7d4bbfe9 100644 --- a/src/ahriman/core/util.py +++ b/src/ahriman/core/util.py @@ -27,6 +27,7 @@ import subprocess from enum import Enum from pathlib import Path +from pwd import getpwuid from typing import Any, Dict, Generator, IO, Iterable, List, Optional, Type, Union from ahriman.core.exceptions import OptionError, UnsafeRunError @@ -84,10 +85,11 @@ def check_output(*args: str, exception: Optional[Exception] = None, cwd: Optiona if logger is not None: logger.debug(single) + environment = {"HOME": getpwuid(user).pw_dir} if user is not None else {} # FIXME additional workaround for linter and type check which do not know that user arg is supported # pylint: disable=unexpected-keyword-arg with subprocess.Popen(args, cwd=cwd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, - user=user, text=True, encoding="utf8", bufsize=1) as process: + user=user, env=environment, text=True, encoding="utf8", bufsize=1) as process: if input_data is not None: input_channel = get_io(process, "stdin") input_channel.write(input_data) diff --git a/tests/ahriman/application/handlers/conftest.py b/tests/ahriman/application/handlers/conftest.py deleted file mode 100644 index 27e405d2..00000000 --- a/tests/ahriman/application/handlers/conftest.py +++ /dev/null @@ -1,16 +0,0 @@ -import pytest - -from unittest.mock import MagicMock - - -@pytest.fixture -def passwd() -> MagicMock: - """ - get passwd structure for the user - - Returns: - MagicMock: passwd structure test instance - """ - passwd = MagicMock() - passwd.pw_dir = "home" - return passwd diff --git a/tests/ahriman/application/handlers/test_handler_setup.py b/tests/ahriman/application/handlers/test_handler_setup.py index ad2c5ab8..e2204479 100644 --- a/tests/ahriman/application/handlers/test_handler_setup.py +++ b/tests/ahriman/application/handlers/test_handler_setup.py @@ -25,6 +25,7 @@ def _default_args(args: argparse.Namespace) -> argparse.Namespace: args.build_as_user = "ahriman" args.build_command = "ahriman" args.from_configuration = Path("/usr/share/devtools/pacman-extra.conf") + args.makeflags_jobs = True args.multilib = True args.packager = "John Doe " args.repository = "aur-clone" @@ -54,10 +55,10 @@ def test_run(args: argparse.Namespace, configuration: Configuration, repository_ args, "x86_64", args.repository, configuration.include, repository_paths) devtools_configuration_mock.assert_called_once_with( args.build_command, "x86_64", args.from_configuration, args.multilib, args.repository, repository_paths) - makepkg_configuration_mock.assert_called_once_with(args.packager, repository_paths) + makepkg_configuration_mock.assert_called_once_with(args.packager, args.makeflags_jobs, repository_paths) sudo_configuration_mock.assert_called_once_with(repository_paths, args.build_command, "x86_64") executable_mock.assert_called_once_with(repository_paths, args.build_command, "x86_64") - init_mock.assert_called_once() + init_mock.assert_called_once_with() def test_build_command(args: argparse.Namespace) -> None: @@ -138,7 +139,7 @@ def test_configuration_create_makepkg(args: argparse.Namespace, repository_paths mocker.patch("ahriman.application.handlers.setup.getpwuid", return_value=passwd) write_text_mock = mocker.patch("pathlib.Path.write_text", autospec=True) - Setup.configuration_create_makepkg(args.packager, repository_paths) + Setup.configuration_create_makepkg(args.packager, args.makeflags_jobs, repository_paths) write_text_mock.assert_called_once_with( Path("home") / ".makepkg.conf", pytest.helpers.anyvar(str, True), encoding="utf8") diff --git a/tests/ahriman/conftest.py b/tests/ahriman/conftest.py index 721652b6..e2b0bd13 100644 --- a/tests/ahriman/conftest.py +++ b/tests/ahriman/conftest.py @@ -363,6 +363,19 @@ def pacman(configuration: Configuration) -> Pacman: return Pacman("x86_64", configuration, refresh_database=0) +@pytest.fixture +def passwd() -> MagicMock: + """ + get passwd structure for the user + + Returns: + MagicMock: passwd structure test instance + """ + passwd = MagicMock() + passwd.pw_dir = "home" + return passwd + + @pytest.fixture def remote_source() -> RemoteSource: """ diff --git a/tests/ahriman/core/test_util.py b/tests/ahriman/core/test_util.py index 2c536aec..31c0d42b 100644 --- a/tests/ahriman/core/test_util.py +++ b/tests/ahriman/core/test_util.py @@ -1,11 +1,13 @@ import datetime import logging +import os import pytest import requests import subprocess from pathlib import Path from pytest_mock import MockerFixture +from typing import Any from unittest.mock import MagicMock from ahriman.core.exceptions import BuildError, OptionError, UnsafeRunError @@ -75,6 +77,19 @@ def test_check_output_multiple_with_stdin_newline() -> None: input_data="multiple\nlines\n") == "multiple\nlines" +def test_check_output_with_user(passwd: Any, mocker: MockerFixture) -> None: + """ + must run command as specified user and set its homedir + """ + assert check_output("python", "-c", "import os; print(os.getenv('HOME'))") != passwd.pw_dir + + getpwuid_mock = mocker.patch("ahriman.core.util.getpwuid", return_value=passwd) + user = os.getuid() + + assert check_output("python", "-c", "import os; print(os.getenv('HOME'))", user=user) == passwd.pw_dir + getpwuid_mock.assert_called_once_with(user) + + def test_check_output_failure(mocker: MockerFixture) -> None: """ must process exception correctly