From 2386bcc64f863bf9aab62b3c458ae76e798f4b6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20P=C3=B6hn?= Date: Fri, 24 Oct 2025 12:21:26 +0200 Subject: [PATCH 1/3] =?UTF-8?q?=F0=9F=96=BC=20set=20cpus=20and=20memory=20?= =?UTF-8?q?from=20CLI=20options?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fdroidserver/up.py | 42 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/fdroidserver/up.py b/fdroidserver/up.py index 927e123a..47cfbcbb 100644 --- a/fdroidserver/up.py +++ b/fdroidserver/up.py @@ -30,6 +30,7 @@ Since this is an internal command, the strings are not localized. """ import logging +import os import sys import textwrap import traceback @@ -39,7 +40,7 @@ from . import common from .exception import BuildException -def run_podman(appid, vercode): +def run_podman(appid, vercode, cpus=None, memory=None): """Create a Podman container env isolated for a single app build. This creates a Podman "pod", which is like an isolated box to @@ -52,6 +53,8 @@ def run_podman(appid, vercode): The container is set up with an interactive bash process to keep the container running. + The CPU configuration assumes a Linux kernel. + """ container_name = common.get_container_name(appid, vercode) pod_name = common.get_pod_name(appid, vercode) @@ -70,6 +73,10 @@ def run_podman(appid, vercode): logging.warning(f'Pod {pod_name} exists, removing!') p.remove(force=True) + if cpus: + # TODO implement some kind of CPU weighting + logging.warning('--cpus is currently ignored by the Podman setup') + pod = client.pods.create(pod_name) container = client.containers.create( image, @@ -79,6 +86,7 @@ def run_podman(appid, vercode): detach=True, remove=True, stdin_open=True, + mem_limit=memory, ) pod.start() pod.reload() @@ -132,11 +140,39 @@ def run_vagrant(appid, vercode, cpus, memory): v.up() +def get_virt_cpus_opt(cpus): + """Read options and deduce number of requested CPUs for build VM. + + If no CPU count is configured, calculate a reasonable default value. + + """ + if cpus: + return cpus + cpu_cnt = os.cpu_count() + if cpu_cnt < 8: + return max(1, int(0.5 * cpu_cnt)) + # use a quarter of available CPUs if there + return 2 + int(0.25 * cpu_cnt) + + +def get_virt_memory_opt(memory): + """Read VM memory size in GB from options or return default. + + Defaults to 6 GB (minimum to build org.fdroid.fdroid in 2025). + + """ + if memory: + return memory + return 6144 + + def up_wrapper(appid, vercode, virt_container_type, cpus=None, memory=None): + cpus = get_virt_cpus_opt(cpus) + memory = get_virt_memory_opt(memory) if virt_container_type == 'vagrant': run_vagrant(appid, vercode, cpus, memory) elif virt_container_type == 'podman': - run_podman(appid, vercode) + run_podman(appid, vercode, cpus, memory) def main(): @@ -170,6 +206,8 @@ def main(): appid, vercode, common.get_virt_container_type(options), + cpus=options.cpus, + memory=options.memory, ) except Exception as e: if options.verbose: From 8421d613696978baa7e996364803658274b88e6b Mon Sep 17 00:00:00 2001 From: Hans-Christoph Steiner Date: Fri, 24 Oct 2025 12:30:03 +0200 Subject: [PATCH 2/3] get_virt_memory_opt: support str values like "16GB" --- fdroidserver/up.py | 19 ++++++++++++------- tests/test_up.py | 22 ++++++++++++++++++++++ 2 files changed, 34 insertions(+), 7 deletions(-) diff --git a/fdroidserver/up.py b/fdroidserver/up.py index 47cfbcbb..f6b561f3 100644 --- a/fdroidserver/up.py +++ b/fdroidserver/up.py @@ -31,6 +31,7 @@ Since this is an internal command, the strings are not localized. import logging import os +import re import sys import textwrap import traceback @@ -107,6 +108,7 @@ def run_vagrant(appid, vercode, cpus, memory): raise BuildException( f"vagrant memory setting required, '{memory}' not a valid value!" ) + memory = int(memory / 1024**2) # libvirt.memory expects a value in MiB vagrantfile = common.get_vagrantfile_path(appid, vercode) @@ -156,14 +158,19 @@ def get_virt_cpus_opt(cpus): def get_virt_memory_opt(memory): - """Read VM memory size in GB from options or return default. + """Return binary VM memory size from or default value in bytes. + + Since this is for memory, this only converts using power-of-two + binary forms. For example, GB is forced to GiB. Defaults to 6 GB (minimum to build org.fdroid.fdroid in 2025). """ - if memory: - return memory - return 6144 + if not memory: + memory = '6144MiB' + return common.parse_human_readable_size( + re.sub(r'([KMGT])B$', r'\1iB', str(memory), re.IGNORECASE) + ) def up_wrapper(appid, vercode, virt_container_type, cpus=None, memory=None): @@ -187,14 +194,12 @@ def main(): ) parser.add_argument( "--cpus", - default=None, type=int, help="How many CPUs the Vagrant VM should be allocated.", ) parser.add_argument( "--memory", - default=None, - type=int, + type=common.parse_human_readable_size, help="How many MB of RAM the Vagrant VM should be allocated.", ) options = common.parse_args(parser) diff --git a/tests/test_up.py b/tests/test_up.py index e97c9954..09820b73 100755 --- a/tests/test_up.py +++ b/tests/test_up.py @@ -142,3 +142,25 @@ class Up_run_vagrant(UpTest): up.run_vagrant(APPID, VERCODE, 1, 1) vagrant_destroy.assert_called_once() self.assertNotEqual(ctime, os.path.getctime(vagrantfile)) + + +class Up_options(UpTest): + def test_get_virt_memory_opt_default(self): + self.assertEqual(up.get_virt_memory_opt(None), 6 * 1024**3) + + def test_get_virt_memory_opt_int(self): + testvalue = 1234567890 + self.assertEqual(up.get_virt_memory_opt(testvalue), testvalue) + + def test_get_virt_memory_opt_str_int(self): + testvalue = 1234567890 + self.assertEqual(up.get_virt_memory_opt(str(testvalue)), testvalue) + + def test_get_virt_memory_opt_str_upper(self): + self.assertEqual(up.get_virt_memory_opt('1GB'), 1024**3) + + def test_get_virt_memory_opt_str_lower(self): + self.assertEqual(up.get_virt_memory_opt('1tib'), 1024**4) + + def test_get_virt_memory_opt_str_mixed(self): + self.assertEqual(up.get_virt_memory_opt('1MiB'), 1024**2) From 950efbbb45472b6b102c3a0a388e45eca426519c Mon Sep 17 00:00:00 2001 From: Hans-Christoph Steiner Date: Tue, 28 Oct 2025 07:40:12 +0100 Subject: [PATCH 3/3] safety catch for --cpus so it isn't higher than actual CPUs --- fdroidserver/up.py | 15 +++++++++------ tests/test_up.py | 10 ++++++++++ 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/fdroidserver/up.py b/fdroidserver/up.py index f6b561f3..24c71da5 100644 --- a/fdroidserver/up.py +++ b/fdroidserver/up.py @@ -148,13 +148,16 @@ def get_virt_cpus_opt(cpus): If no CPU count is configured, calculate a reasonable default value. """ - if cpus: - return cpus cpu_cnt = os.cpu_count() - if cpu_cnt < 8: - return max(1, int(0.5 * cpu_cnt)) - # use a quarter of available CPUs if there - return 2 + int(0.25 * cpu_cnt) + if not cpus: + if cpu_cnt < 8: + cpus = max(1, int(0.5 * cpu_cnt)) + else: + # use a quarter of available CPUs if there + cpus = 2 + int(0.25 * cpu_cnt) + if min(cpus, cpu_cnt) != cpus: + logging.warning(f'Capping {cpus} CPUs to how many are available ({cpu_cnt}).') + return min(cpus, cpu_cnt) def get_virt_memory_opt(memory): diff --git a/tests/test_up.py b/tests/test_up.py index 09820b73..1e75ed8e 100755 --- a/tests/test_up.py +++ b/tests/test_up.py @@ -145,6 +145,16 @@ class Up_run_vagrant(UpTest): class Up_options(UpTest): + def test_get_virt_cpus_opt_default(self): + self.assertTrue(up.get_virt_cpus_opt(None) > 0) + + def test_get_virt_cpus_opt_too_small(self): + self.assertTrue(up.get_virt_cpus_opt(0.1) > 0) + + def test_get_virt_cpus_opt_too_big(self): + with self.assertLogs(): + self.assertEqual(up.get_virt_cpus_opt(99999999), os.cpu_count()) + def test_get_virt_memory_opt_default(self): self.assertEqual(up.get_virt_memory_opt(None), 6 * 1024**3)