1
0
mirror of https://gitlab.com/fdroid/fdroidserver.git synced 2024-09-21 04:10:37 +02:00

⏱️ add schedule_build subcommand

Internal subcommand for getting a (JSON) list of all apps that need
building in the next cycle.
This commit is contained in:
Michael Pöhn 2024-06-06 17:22:09 +02:00
parent 629fd1a204
commit af1205f44c
No known key found for this signature in database
GPG Key ID: 725F386C05529A5A
3 changed files with 274 additions and 0 deletions

View File

@ -62,6 +62,7 @@ COMMANDS_INTERNAL = [
"build_local_prepare",
"build_local_run",
"build_local_sudo",
"schedule_build",
]

View File

@ -0,0 +1,128 @@
#!/usr/bin/env python3
#
# build.py - part of the FDroid server tools
# Copyright (C) 2024, Michael Pöhn <michael@poehn.at>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import os
import sys
import json
import traceback
import argparse
from fdroidserver import _
import fdroidserver.common
import fdroidserver.metadata
import fdroidserver.update
def is_binary_artifact_present(appid, build):
"""Check if a build artifact/result form a previous run exists.
Parameters
----------
appid
app id you're looking for (e.g. 'org.fdroid.fdroid')
build
metadata build object you're checking
Returns
-------
True if a build artifact exists, otherwise False.
"""
bin_dirs = ["archive", "repo", "unsigned"]
ext = get_output_extension(build)
for bin_dir in bin_dirs:
if os.path.exists(f"./{bin_dir}/{appid}_{build.versionCode}.{ext}"):
return True
return False
def collect_schedule_entries(apps):
"""Get list of schedule entries for next build run.
This function matches which builds in metadata are not built yet.
Parameters
----------
apps
list of all metadata app objects of current repo
Returns
-------
list of schedule entries
"""
schedule = []
for appid, app in apps.items():
if not app.get("Disabled"):
for build in app.get("Builds", {}):
if not build.get("disable"):
if not is_binary_artifact_present(appid, build):
schedule.append(
{
"applicationId": appid,
"versionCode": build.get("versionCode"),
"timeout": build.get("timeout"),
}
)
return schedule
# TODO remove this, and replace with this function from common.py
def get_output_extension(build):
if build.output:
return fdroidserver.common.get_file_extension(
fdroidserver.common.replace_build_vars(build.output, build)
)
return 'apk'
def main():
parser = argparse.ArgumentParser(
description=_(""""""),
)
parser.add_argument(
"--pretty",
'-p',
action="store_true",
default=False,
help="pretty output formatting",
)
# fdroid args/opts boilerplate
fdroidserver.common.setup_global_opts(parser)
options = fdroidserver.common.parse_args(parser)
config = fdroidserver.common.get_config()
config # silcense pyflakes
try:
apps = fdroidserver.metadata.read_metadata()
schedule = collect_schedule_entries(apps)
indent = 2 if options.pretty else None
print(json.dumps(schedule, indent=indent))
except Exception as e:
if options.verbose:
traceback.print_exc()
else:
print(e)
sys.exit(1)
if __name__ == "__main__":
main()

145
tests/schedule_build.TestCase Executable file
View File

@ -0,0 +1,145 @@
#!/usr/bin/env python3
import os
import sys
import inspect
import tempfile
import unittest
import unittest.mock
localmodule = os.path.realpath(
os.path.join(os.path.dirname(inspect.getfile(inspect.currentframe())), '..')
)
print('localmodule: ' + localmodule)
if localmodule not in sys.path:
sys.path.insert(0, localmodule)
import fdroidserver.schedule_build
import fdroidserver.metadata
import testcommon
class IsBinaryArtifactPresentTest(unittest.TestCase):
def mktestdirs(self):
os.mkdir("unsigned")
os.mkdir("archive")
os.mkdir("repo")
def test_not_present(self):
build = unittest.mock.Mock()
build.output = None
with tempfile.TemporaryDirectory() as tmpdir, testcommon.TmpCwd(tmpdir):
result = fdroidserver.schedule_build.is_binary_artifact_present(
"fake.app", build
)
self.assertFalse(result)
def test_present_in_repo(self):
build = fdroidserver.metadata.Build()
build["versionCode"] = 123
build["output"] = None
with tempfile.TemporaryDirectory() as tmpdir, testcommon.TmpCwd(tmpdir):
self.mktestdirs()
with open('./repo/fake.app_123.apk', 'w') as f:
f.write('')
result = fdroidserver.schedule_build.is_binary_artifact_present(
"fake.app", build
)
self.assertTrue(result)
def test_present_in_unsigned(self):
build = fdroidserver.metadata.Build()
build["versionCode"] = 123
build["output"] = None
with tempfile.TemporaryDirectory() as tmpdir, testcommon.TmpCwd(tmpdir):
self.mktestdirs()
with open('./unsigned/fake.app_123.apk', 'w') as f:
f.write('')
result = fdroidserver.schedule_build.is_binary_artifact_present(
"fake.app", build
)
self.assertTrue(result)
def test_present_in_archive(self):
build = fdroidserver.metadata.Build()
build["versionCode"] = 123
build["output"] = None
with tempfile.TemporaryDirectory() as tmpdir, testcommon.TmpCwd(tmpdir):
self.mktestdirs()
with open('./archive/fake.app_123.apk', 'w') as f:
f.write('')
result = fdroidserver.schedule_build.is_binary_artifact_present(
"fake.app", build
)
self.assertTrue(result)
def test_present_in_repo_with_output(self):
build = fdroidserver.metadata.Build()
build["versionCode"] = 9000
build["versionName"] = "vvv"
build["commit"] = "commit1"
build["output"] = "blah/blah/build_result.zip"
with tempfile.TemporaryDirectory() as tmpdir, testcommon.TmpCwd(tmpdir):
self.mktestdirs()
with open('./repo/fake.app_9000.zip', 'w') as f:
f.write('')
result = fdroidserver.schedule_build.is_binary_artifact_present(
"fake.app", build
)
self.assertTrue(result)
class CollectScheduleEntriesTest(unittest.TestCase):
def setUp(self):
self.apps = {
"chat.hal": fdroidserver.metadata.App(),
"chat.gpt": fdroidserver.metadata.App(),
"microsoft.copilot": fdroidserver.metadata.App(),
}
self.apps['chat.hal']["Builds"].append(fdroidserver.metadata.Build())
self.apps['chat.hal']["Builds"][0]["versionCode"] = 9000
self.apps['chat.gpt']["Disabled"] = "this is jank!"
self.apps['chat.gpt']["Builds"].append(fdroidserver.metadata.Build())
self.apps['chat.gpt']["Builds"][0]["versionCode"] = 4
self.apps['microsoft.copilot']["Builds"].append(fdroidserver.metadata.Build())
self.apps['microsoft.copilot']["Builds"][0]["versionCode"] = 3
self.apps['microsoft.copilot']["Builds"][0]["disable"] = "also jank!"
def test_asdf(self):
with unittest.mock.patch(
"fdroidserver.schedule_build.is_binary_artifact_present", return_value=False
):
result = fdroidserver.schedule_build.collect_schedule_entries(self.apps)
self.maxDiff = None
self.assertListEqual(
result,
[{"applicationId": "chat.hal", "versionCode": 9000, "timeout": None}],
)
if __name__ == "__main__":
os.chdir(os.path.dirname(__file__))
import argparse
parser = argparse.ArgumentParser()
parser.add_argument(
"-v",
"--verbose",
action="store_true",
default=False,
help="Spew out even more information than normal",
)
fdroidserver.common.options = fdroidserver.common.parse_args(parser)
newSuite = unittest.TestSuite()
newSuite.addTest(unittest.makeSuite(IsBinaryArtifactPresentTest))
unittest.main(failfast=False)