Initial commit

main
Simon Quigley 3 weeks ago
commit aa1173f7d9

1
.gitignore vendored

@ -0,0 +1 @@
__pycache__

@ -0,0 +1,408 @@
#!/usr/bin/env python3
import subprocess
import io
import os
import sys
import yaml
import argparse
import logging
from datetime import datetime
import tarfile
import shutil
from git import Repo, GitCommandError
import tempfile
from concurrent.futures import ThreadPoolExecutor, wait, FIRST_COMPLETED
import fnmatch
import re
from debian.copyright import Header, Copyright
import uuid
from common import clean_old_logs
BASE_DIR = "/srv/lubuntu-ci/repos"
DEBFULLNAME = "Lugito"
DEBEMAIL = "info@lubuntu.me"
OUTPUT_DIR = os.path.join(BASE_DIR, "build_output")
SUPPRESSED_LINTIAN_TAGS = [
"orig-tarball-missing-upstream-signature",
"package-has-long-file-name",
"adopted-extended-field"
]
BASE_OUTPUT_DIR = "/srv/lubuntu-ci/output"
LOG_DIR = os.path.join(BASE_OUTPUT_DIR, "logs", "source_builds")
BASE_LINTIAN_DIR = os.path.join(BASE_OUTPUT_DIR, f"lintian.tmp.{str(uuid.uuid4())[:8]}")
REAL_LINTIAN_DIR = os.path.join(BASE_OUTPUT_DIR, "lintian")
os.makedirs(LOG_DIR, exist_ok=True)
os.makedirs(OUTPUT_DIR, exist_ok=True)
os.makedirs(BASE_LINTIAN_DIR, exist_ok=True)
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(levelname)s - %(message)s",
handlers=[
logging.FileHandler(log_file),
logging.StreamHandler()
]
)
logger = logging.getLogger("TimeBasedLogger")
def run_command(cmd, cwd=None, env=None, show_output=False):
logging.info(f"Executing: {' '.join(cmd)} in {cwd or 'current directory'}")
try:
result = subprocess.run(
cmd,
cwd=cwd,
env=env,
check=True,
capture_output=True,
text=True
)
if show_output:
if result.stdout:
logging.info(f"Output: {result.stdout.strip()}")
if result.stderr:
logging.warning(f"Output: {result.stderr.strip()}")
logging.info(f"Command succeeded: {' '.join(cmd)}")
except subprocess.CalledProcessError as e:
logging.error(f"Command failed: {' '.join(cmd)}")
logging.error(e.stderr)
raise
def parse_version(changelog_path):
try:
with open(changelog_path, "r") as f:
first_line = f.readline().strip()
version_match = first_line.split("(")[1].split(")")[0]
# Remove Debian revision
upstream_version = version_match.split("-")[0]
# Remove '+git...' and '~release' if present
upstream_version = re.sub(r'(\+git[0-9]+)?(~[a-z]+)?$', '', upstream_version)
logging.info(f"Upstream version extracted: {upstream_version}")
current_date = datetime.now().strftime("%Y%m%d%H%M")
version = f"{upstream_version}+git{current_date}"
logging.info(f"Parsed VERSION: {version}")
return version
except (IndexError, FileNotFoundError) as e:
logging.error(f"Error parsing version from {changelog_path}: {e}")
raise
def get_exclusions(packaging):
exclusions = []
with io.open(os.path.join(packaging, "debian/copyright"), "rt", encoding="utf-8") as f:
copyright_obj = Copyright(f)
for paragraph in copyright_obj.all_paragraphs():
if isinstance(paragraph, Header):
if paragraph.files_excluded:
for file_name in paragraph.files_excluded:
exclusions.append(file_name)
break
return exclusions
def create_tarball(name, source_dir, exclusions=[]):
tar_filename = f"{name}_MAIN.orig.tar.gz"
logging.info(f"Creating tarball: {tar_filename}")
exclusions.append(".git/")
def exclusion_func(tarinfo):
for exclusion in exclusions:
if exclusion in tarinfo.name:
return None
return tarinfo
with tarfile.open(tar_filename, "w:gz") as tar:
tar.add(source_dir, arcname=os.path.basename(source_dir), filter=exclusion_func)
logging.info(f"Tarball created and compressed: {tar_filename}")
def update_changelog(packaging_dir, release, version, env):
name = os.path.basename(packaging_dir)
logging.info(f"Updating changelog for {name} to version {version}-0ubuntu1~ppa1")
run_command(["git", "checkout", "debian/changelog"], cwd=packaging_dir)
cmd = [
"dch",
"--distribution", release,
"--package", name,
"--newversion", f"{version}-0ubuntu1~ppa1",
"--urgency", "low",
"CI upload."
]
run_command(cmd, cwd=packaging_dir, env=env)
def build_package(packaging_dir, env):
name = os.path.basename(packaging_dir)
logging.info(f"Building source package for {name}")
temp_dir = tempfile.mkdtemp()
try:
temp_packaging_dir = os.path.join(temp_dir, name)
os.makedirs(temp_packaging_dir, exist_ok=True)
shutil.copytree(packaging_dir + "/debian", temp_packaging_dir + "/debian")
tarball_name = f"{name}_{env['VERSION']}.orig.tar.gz"
tarball_source = os.path.join(BASE_DIR, tarball_name)
tarball_dest = os.path.join(temp_dir, tarball_name)
shutil.copyfile(tarball_source, tarball_dest)
cmd_build = ["debuild", "--no-lintian", "-S", "-d", "-sa"]
run_command(cmd_build, cwd=temp_packaging_dir, env=env)
run_command(["git", "checkout", "debian/changelog"], cwd=packaging_dir)
pattern = f"{name}_{env['VERSION']}*"
for filename in os.listdir(temp_dir):
if fnmatch.fnmatch(filename, pattern):
source_file = os.path.join(temp_dir, filename)
dest_file = os.path.join(OUTPUT_DIR, filename)
shutil.copyfile(source_file, dest_file)
logging.info(f"Copied {filename} to {OUTPUT_DIR}")
changes_files = [f for f in os.listdir(OUTPUT_DIR) if f.startswith(f"{name}_{env['VERSION']}") and f.endswith("_source.changes")]
if changes_files:
changes_file = os.path.join(OUTPUT_DIR, changes_files[-1])
logging.info(f"Built package, changes file: {changes_file}")
return changes_file
else:
logging.error("No changes file found after build.")
raise FileNotFoundError("Changes file not found.")
finally:
shutil.rmtree(temp_dir)
def load_config(config_path):
try:
with open(config_path, "r") as f:
config = yaml.safe_load(f)
if "packages" not in config or "releases" not in config:
raise ValueError("Config file must contain 'packages' and 'releases' sections.")
return config
except Exception as e:
logging.error(f"Error loading config file: {e}")
sys.exit(1)
def clone_or_update_repo(destination, repo_url, repo_branch=None):
if os.path.exists(destination):
logging.info(f"Repository already exists at {destination}, checking branch and remote URL.")
try:
repo = Repo(destination)
current_remote_url = repo.remotes.origin.url
if current_remote_url != repo_url:
logging.info(f"Remote URL differs for {destination}. Removing and recloning.")
shutil.rmtree(destination)
else:
repo.git.reset("--hard", "HEAD")
current_branch = repo.active_branch.name
if repo_branch and current_branch != repo_branch:
logging.info(f"Branch differs for {destination}. Removing and recloning.")
shutil.rmtree(destination)
else:
logging.info(f"Repository matches desired remote and branch, pulling updates.")
repo.git.checkout(repo_branch or current_branch)
try:
repo.remotes.origin.pull()
repo.submodule_update(recursive=True)
logging.info(f"Pulled latest changes for {destination}")
except GitCommandError as e:
if 'non-fast-forward' in str(e):
logging.error(f"Pull failed due to non-fast-forward update: {e}")
logging.info(f"Removing repository {destination} and cloning again.")
shutil.rmtree(destination)
else:
logging.error(f"Pull failed for {destination}: {e}")
raise
else:
return
except Exception as e:
logging.error(f"Error updating repository {destination}: {e}")
logging.info(f"Removing repository {destination} and cloning again.")
shutil.rmtree(destination)
try:
logging.info(f"Cloning repository {repo_url} into {destination}")
repo = Repo.clone_from(repo_url, destination, recurse_submodules=True)
if repo_branch:
repo.git.checkout(repo_branch)
logging.info(f"Checked out {repo_branch} in {destination}")
except GitCommandError as e:
logging.error(f"Git clone failed for {repo_url}: {e}")
raise
def publish_lintian():
if os.path.exists(BASE_LINTIAN_DIR):
for root, dirs, files in os.walk(BASE_LINTIAN_DIR):
for file in files:
# Determine the source and destination paths
src_path = os.path.join(root, file)
rel_path = os.path.relpath(src_path, BASE_LINTIAN_DIR)
dest_path = os.path.join(REAL_LINTIAN_DIR, rel_path)
# Ensure the destination directory exists
os.makedirs(os.path.dirname(dest_path), exist_ok=True)
# Copy the file
shutil.copy2(src_path, dest_path)
# Remove the temporary directory
shutil.rmtree(BASE_LINTIAN_DIR)
def run_source_lintian(name, sources_path):
logging.info(f"Running Lintian for {name}")
with tempfile.NamedTemporaryFile(mode='w+', suffix='.txt') as temp_file:
temp_file.write("\n".join(SUPPRESSED_LINTIAN_TAGS))
temp_file.flush()
temp_file_path = temp_file.name
cmd = [
"lintian",
"-EvIL",
"+pedantic",
"--suppress-tags-from-file",
f"{temp_file_path}",
sources_path
]
result = subprocess.run(
cmd,
capture_output=True,
text=True
)
stderr, stdout = None, None
if result.stderr:
stderr = result.stderr.strip()
if result.stdout:
stdout = result.stdout.strip()
lintian_output = None
if stderr == stdout:
lintian_output = stderr
else:
lintian_output = f"{stderr}\n{stdout}".strip()
if lintian_output:
pkgdir = os.path.join(BASE_LINTIAN_DIR, name)
if not os.path.exists(pkgdir):
os.mkdir(pkgdir)
output_file = os.path.join(pkgdir, "source.txt")
with open(output_file, "a") as f:
f.write(lintian_output)
logging.info(f"Lintian run for {name} is complete")
def main():
parser = argparse.ArgumentParser(description="Automate Lubuntu package builds.")
parser.add_argument("config", help="Path to the YAML configuration file.")
parser.add_argument("--skip-dput", action="store_true", help="Skip the dput upload step.")
parser.add_argument("--skip-cleanup", action="store_true", help="Skip removal of build_output.")
args = parser.parse_args()
config = load_config(args.config)
packages = config["packages"]
releases = config["releases"]
os.makedirs(BASE_DIR, exist_ok=True)
logging.info(f"Using base directory: {BASE_DIR}")
os.chdir(BASE_DIR)
with ThreadPoolExecutor(max_workers=5) as executor:
def dput_source(name, upload_target, changes_files, devel_changes_files):
if changes_files:
hr_changes = ", ".join(changes_files)
logging.info(f"Uploading {hr_changes} to {upload_target} using dput")
cmd_upload = ["dput", upload_target] + changes_files
run_command(cmd_upload, cwd=OUTPUT_DIR)
logging.info(f"Completed upload of {hr_changes} to {upload_target}")
for file in devel_changes_files:
if file:
futures.add(executor.submit(run_source_lintian, name, file))
def prepare_package(pkg):
name = pkg.get("name")
if not name:
logging.warning(f"Skipping package due to missing name: {pkg}")
return
upstream_url = pkg.get("upstream_url") or f"https://github.com/lxqt/{name}.git"
upstream_destination = os.path.join(BASE_DIR, f"upstream-{name}")
clone_or_update_repo(upstream_destination, upstream_url)
packaging_url = pkg.get("packaging_url") or f"https://git.lubuntu.me/Lubuntu/{name}-packaging.git"
packaging_branch = pkg.get("packaging_branch") or f"ubuntu/{releases[0]}" if releases else None
packaging_destination = os.path.join(BASE_DIR, name)
clone_or_update_repo(packaging_destination, packaging_url, packaging_branch)
exclusions = get_exclusions(packaging_destination)
create_tarball(name, upstream_destination, exclusions)
run_command(["update-maintainer"], cwd=packaging_destination)
futures.add(executor.submit(process_package, pkg))
def process_package(pkg):
name = pkg.get("name")
upload_target = pkg.get("upload_target", "ppa:lubuntu-ci/unstable-ci-proposed")
if not name:
logging.warning(f"Skipping package due to missing name: {pkg}")
return []
package_changes = []
packaging_destination = os.path.join(BASE_DIR, name)
changelog_path = os.path.join(packaging_destination, "debian", "changelog")
version = parse_version(changelog_path)
for release in releases:
logging.info(f"Building {name} for {release}")
try:
release_version = f"{version}~{release}"
tarball_name = f"{name}_{release_version}.orig.tar.gz"
tarball_source = os.path.join(BASE_DIR, f"{name}_MAIN.orig.tar.gz")
tarball_dest = os.path.join(BASE_DIR, tarball_name)
shutil.copyfile(tarball_source, tarball_dest)
env = os.environ.copy()
env["DEBFULLNAME"] = DEBFULLNAME
env["DEBEMAIL"] = DEBEMAIL
env["VERSION"] = release_version
env["UPLOAD_TARGET"] = upload_target
# Update changelog and build package
update_changelog(packaging_destination, release, release_version, env)
changes_file = build_package(packaging_destination, env)
if changes_file:
package_changes.append((changes_file, env))
os.remove(os.path.join(BASE_DIR, tarball_name))
except Exception as e:
logging.error(f"Error processing package '{name}' for release '{release}': {e}")
changes_files = [os.path.basename(cf) for cf, env in package_changes]
devel_changes_files = set(os.path.join(OUTPUT_DIR, file) if releases[0] in file else None for file in changes_files)
if args.skip_dput:
for changes_file in devel_changes_files:
if changes_file:
futures.add(executor.submit(run_source_lintian, name, changes_file))
else:
upload_target = package_changes[0][1]["UPLOAD_TARGET"]
futures.add(executor.submit(dput_source, name, upload_target, changes_files, devel_changes_files))
os.remove(os.path.join(BASE_DIR, f"{name}_MAIN.orig.tar.gz"))
futures = set(executor.submit(prepare_package, pkg) for pkg in packages)
while futures:
done, not_done = wait(futures, return_when=FIRST_COMPLETED)
for future in done:
try:
result = future.result()
except Exception as e:
logging.exception("Task generated an exception")
finally:
futures.remove(future)
if not args.skip_cleanup:
shutil.rmtree(OUTPUT_DIR)
logging.info("Publishing Lintian output...")
publish_lintian()
clean_old_logs(LOG_DIR)
logging.info("Script completed successfully.")
if __name__ == "__main__":
main()

@ -0,0 +1,28 @@
#!/usr/bin/env python3
#
# Copyright (C) 2024 Simon Quigley <tsimonq2@ubuntu.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
import os
from datetime import datetime
def clean_old_logs(log_dir, max_age_seconds=86400):
now = datetime.utcnow()
for file_name in os.listdir(log_dir):
file_path = os.path.join(log_dir, file_name)
if os.path.isfile(file_path):
file_age = now - os.path.getmtime(file_path)
if file_age > max_age_seconds:
os.remove(file_path)

@ -0,0 +1,126 @@
# Configuration file for britney
# Paths for control files
TESTING = data/%(SERIES)
UNSTABLE = data/%(SERIES)-proposed
PARTIAL_UNSTABLE = yes
# Output
NONINST_STATUS = data/%(SERIES)/non-installable-status
EXCUSES_OUTPUT = output/%(SERIES)/excuses.html
EXCUSES_YAML_OUTPUT = output/%(SERIES)/excuses.yaml.xz
UPGRADE_OUTPUT = output/%(SERIES)/output.txt
HEIDI_OUTPUT = output/%(SERIES)/HeidiResult
# External policy/constraints/faux-packages information that
# (presumably) rarely changes. Examples include "constraints".
STATIC_INPUT_DIR = data/%(SERIES)/input
# Directory for input files that Britney will update herself
# (e.g. aging information) or will need regular updates
# (e.g. urgency information).
STATE_DIR = data/%(SERIES)/state
# List of architectures that Britney should consider.
# - defaults to the value in testing's Release file (if it is present).
# - Required for the legacy layout.
ARCHITECTURES = amd64 arm64 armhf ppc64el riscv64 s390x
# if you're not in this list, arch: all packages are allowed to break on you
NOBREAKALL_ARCHES = amd64
# primary architecture used for checking Build-Depends-Indep
ALL_BUILDARCH = amd64
# is arch-all built separately? i.e. can it fail independently of another arch?
HAS_ARCH_ALL_BUILDDS = no
# if you're in this list, your packages may not stay in sync with the source
OUTOFSYNC_ARCHES =
# if you're in this list, your uninstallability count may increase
BREAK_ARCHES =
# if you're in this list, you are a new architecture
NEW_ARCHES =
# priorities and delays
MINDAYS_LOW = 0
MINDAYS_MEDIUM = 0
MINDAYS_HIGH = 0
MINDAYS_CRITICAL = 0
MINDAYS_EMERGENCY = 0
DEFAULT_URGENCY = medium
NO_PENALTIES = high critical emergency
BOUNTY_MIN_AGE = 2
HINTSDIR = data/%(SERIES)-proposed/Hints
# hint permissions
HINTS_LANEY = ALL
HINTS_STEFANOR = ALL
HINTS_STGRABER = ALL
HINTS_VORLON = ALL
HINTS_PITTI = ALL
HINTS_UBUNTU-RELEASE = ALL
# Kernel team automated testing
HINTS_KERNEL-TESTING = block unblock
# SRU team
HINTS_APW = ALL
HINTS_ARGES = ALL
HINTS_BRIAN-MURRAY = ALL
HINTS_RACB = ALL
HINTS_RAOF = ALL
HINTS_SIL2100 = ALL
HINTS_TJAALTON = ALL
HINTS_UBUNTU-SRU = ALL
HINTS_FREEZE = block block-all
# support for old libraries in testing (smooth update)
# use ALL to enable smooth updates for all the sections
#
# naming a non-existent section will effectively disable new smooth
# updates but still allow removals to occur
SMOOTH_UPDATES = libs oldlibs
IGNORE_CRUFT = 0
REMOVE_OBSOLETE = no
CHECK_BUILDD = nxo
IMPLICIT_DEPS = no
ADT_ENABLE = no
#ADT_ARCHES = amd64 i386 armhf ppc64el arm64
#ADT_AMQP = amqp://test_request:password@autopkgtest-amqp.internal
# space separate list of PPAs to add for test requests and for polling results;
# the *last* one determines the swift container name
ADT_PPAS =
# set this to the path of a (r/o) autopkgtest-results.cache for running many parallel
# britney instances for PPAs without updating the cache
ADT_SHARED_RESULTS_CACHE =
# Swift base URL with the results (must be publicly readable and browsable)
# or file location if results are pre-fetched
#ADT_SWIFT_URL = https://objectstorage.prodstack5.canonical.com/swift/v1/AUTH_0f9aae918d5b4744bf7b827671c86842/
# Base URL for autopkgtest site, used for links in the excuses
#ADT_CI_URL = https://autopkgtest.ubuntu.com/
# URL for the autopkgtest database, if used
#ADT_DB_URL = https://autopkgtest.ubuntu.com/static/autopkgtest.db
#ADT_HUGE = 20
# Autopkgtest results can be used to influence the aging
ADT_REGRESSION_PENALTY =
ADT_SUCCESS_BOUNTY =
ADT_BASELINE = reference
ADT_RETRY_URL_MECH =
ADT_RETRY_OLDER_THAN =
ADT_REFERENCE_MAX_AGE =
# email uploaders for stuck uploads
EMAIL_ENABLE = no
# email SRU bugs when regressions are detected
SRUREGRESSIONEMAIL_ENABLE = no
# we don't run piuparts testing in Ubuntu
PIUPARTS_ENABLE = no

@ -0,0 +1,10 @@
packages:
- name: cmake
upstream_url: "https://gitlab.kitware.com/cmake/cmake.git"
packaging_url: "https://git.lubuntu.me/Lubuntu/cmake.git"
packaging_branch: "ci/unstable"
releases:
- plucky
- oracular
- noble

@ -0,0 +1,306 @@
packages:
- name: kf6-extra-cmake-modules
upstream_url: "https://invent.kde.org/frameworks/extra-cmake-modules.git"
packaging_url: "https://git.launchpad.net/~kubuntu-packagers/kubuntu-packaging/+git/kf6-extra-cmake-modules"
packaging_branch: "kubuntu_unstable"
- name: kf6-attica
upstream_url: "https://invent.kde.org/frameworks/attica.git"
packaging_url: "https://git.launchpad.net/~kubuntu-packagers/kubuntu-packaging/+git/kf6-attica"
packaging_branch: "kubuntu_unstable"
- name: kf6-baloo
upstream_url: "https://invent.kde.org/frameworks/baloo.git"
packaging_url: "https://git.launchpad.net/~kubuntu-packagers/kubuntu-packaging/+git/kf6-baloo"
packaging_branch: "kubuntu_unstable"
- name: kf6-bluez-qt
upstream_url: "https://invent.kde.org/frameworks/bluez-qt.git"
packaging_url: "https://git.launchpad.net/~kubuntu-packagers/kubuntu-packaging/+git/kf6-bluez-qt"
packaging_branch: "kubuntu_unstable"
- name: kf6-breeze-icons
upstream_url: "https://invent.kde.org/frameworks/breeze-icons.git"
packaging_url: "https://git.launchpad.net/~kubuntu-packagers/kubuntu-packaging/+git/kf6-breeze-icons"
packaging_branch: "kubuntu_unstable"
- name: kf6-frameworkintegration
upstream_url: "https://invent.kde.org/frameworks/frameworkintegration.git"
packaging_url: "https://git.launchpad.net/~kubuntu-packagers/kubuntu-packaging/+git/kf6-frameworkintegration"
packaging_branch: "kubuntu_unstable"
- name: kf6-kapidox
upstream_url: "https://invent.kde.org/frameworks/kapidox.git"
packaging_url: "https://git.launchpad.net/~kubuntu-packagers/kubuntu-packaging/+git/kf6-kapidox"
packaging_branch: "kubuntu_unstable"
- name: kf6-karchive
upstream_url: "https://invent.kde.org/frameworks/karchive.git"
packaging_url: "https://git.launchpad.net/~kubuntu-packagers/kubuntu-packaging/+git/kf6-karchive"
packaging_branch: "kubuntu_unstable"
- name: kf6-kauth
upstream_url: "https://invent.kde.org/frameworks/kauth.git"
packaging_url: "https://git.launchpad.net/~kubuntu-packagers/kubuntu-packaging/+git/kf6-kauth"
packaging_branch: "kubuntu_unstable"
- name: kf6-kbookmarks
upstream_url: "https://invent.kde.org/frameworks/kbookmarks.git"
packaging_url: "https://git.launchpad.net/~kubuntu-packagers/kubuntu-packaging/+git/kf6-kbookmarks"
packaging_branch: "kubuntu_unstable"
- name: kf6-kcalendarcore
upstream_url: "https://invent.kde.org/frameworks/kcalendarcore.git"
packaging_url: "https://git.launchpad.net/~kubuntu-packagers/kubuntu-packaging/+git/kf6-kcalendarcore"
packaging_branch: "kubuntu_unstable"
- name: kf6-kcmutils
upstream_url: "https://invent.kde.org/frameworks/kcmutils.git"
packaging_url: "https://git.launchpad.net/~kubuntu-packagers/kubuntu-packaging/+git/kf6-kcmutils"
packaging_branch: "kubuntu_unstable"
- name: kf6-kcodecs
upstream_url: "https://invent.kde.org/frameworks/kcodecs.git"
packaging_url: "https://git.launchpad.net/~kubuntu-packagers/kubuntu-packaging/+git/kf6-kcodecs"
packaging_branch: "kubuntu_unstable"
- name: kf6-kcolorscheme
upstream_url: "https://invent.kde.org/frameworks/kcolorscheme.git"
packaging_url: "https://git.launchpad.net/~kubuntu-packagers/kubuntu-packaging/+git/kf6-kcolorscheme"
packaging_branch: "kubuntu_unstable"
- name: kf6-kcompletion
upstream_url: "https://invent.kde.org/frameworks/kcompletion.git"
packaging_url: "https://git.launchpad.net/~kubuntu-packagers/kubuntu-packaging/+git/kf6-kcompletion"
packaging_branch: "kubuntu_unstable"
- name: kf6-kconfig
upstream_url: "https://invent.kde.org/frameworks/kconfig.git"
packaging_url: "https://git.launchpad.net/~kubuntu-packagers/kubuntu-packaging/+git/kf6-kconfig"
packaging_branch: "kubuntu_unstable"
- name: kf6-kconfigwidgets
upstream_url: "https://invent.kde.org/frameworks/kconfigwidgets.git"
packaging_url: "https://git.launchpad.net/~kubuntu-packagers/kubuntu-packaging/+git/kf6-kconfigwidgets"
packaging_branch: "kubuntu_unstable"
- name: kf6-kcoreaddons
upstream_url: "https://invent.kde.org/frameworks/kcoreaddons.git"
packaging_url: "https://git.launchpad.net/~kubuntu-packagers/kubuntu-packaging/+git/kf6-kcoreaddons"
packaging_branch: "kubuntu_unstable"
- name: kf6-kcrash
upstream_url: "https://invent.kde.org/frameworks/kcrash.git"
packaging_url: "https://git.launchpad.net/~kubuntu-packagers/kubuntu-packaging/+git/kf6-kcrash"
packaging_branch: "kubuntu_unstable"
- name: kf6-kdbusaddons
upstream_url: "https://invent.kde.org/frameworks/kdbusaddons.git"
packaging_url: "https://git.launchpad.net/~kubuntu-packagers/kubuntu-packaging/+git/kf6-kdbusaddons"
packaging_branch: "kubuntu_unstable"
- name: kf6-kdeclarative
upstream_url: "https://invent.kde.org/frameworks/kdeclarative.git"
packaging_url: "https://git.launchpad.net/~kubuntu-packagers/kubuntu-packaging/+git/kf6-kdeclarative"
packaging_branch: "kubuntu_unstable"
- name: kf6-kded
upstream_url: "https://invent.kde.org/frameworks/kded.git"
packaging_url: "https://git.launchpad.net/~kubuntu-packagers/kubuntu-packaging/+git/kf6-kded"
packaging_branch: "kubuntu_unstable"
- name: kf6-kdesu
upstream_url: "https://invent.kde.org/frameworks/kdesu.git"
packaging_url: "https://git.launchpad.net/~kubuntu-packagers/kubuntu-packaging/+git/kf6-kdesu"
packaging_branch: "kubuntu_unstable"
- name: kf6-kdnssd
upstream_url: "https://invent.kde.org/frameworks/kdnssd.git"
packaging_url: "https://git.launchpad.net/~kubuntu-packagers/kubuntu-packaging/+git/kf6-kdnssd"
packaging_branch: "kubuntu_unstable"
- name: kf6-kdoctools
upstream_url: "https://invent.kde.org/frameworks/kdoctools.git"
packaging_url: "https://git.launchpad.net/~kubuntu-packagers/kubuntu-packaging/+git/kf6-kdoctools"
packaging_branch: "kubuntu_unstable"
- name: kf6-kfilemetadata
upstream_url: "https://invent.kde.org/frameworks/kfilemetadata.git"
packaging_url: "https://git.launchpad.net/~kubuntu-packagers/kubuntu-packaging/+git/kf6-kfilemetadata"
packaging_branch: "kubuntu_unstable"
- name: kf6-kglobalaccel
upstream_url: "https://invent.kde.org/frameworks/kglobalaccel.git"
packaging_url: "https://git.launchpad.net/~kubuntu-packagers/kubuntu-packaging/+git/kf6-kglobalaccel"
packaging_branch: "kubuntu_unstable"
- name: kf6-kguiaddons
upstream_url: "https://invent.kde.org/frameworks/kguiaddons.git"
packaging_url: "https://git.launchpad.net/~kubuntu-packagers/kubuntu-packaging/+git/kf6-kguiaddons"
packaging_branch: "kubuntu_unstable"
- name: kf6-kholidays
upstream_url: "https://invent.kde.org/frameworks/kholidays.git"
packaging_url: "https://git.launchpad.net/~kubuntu-packagers/kubuntu-packaging/+git/kf6-kholidays"
packaging_branch: "kubuntu_unstable"
- name: kf6-ki18n
upstream_url: "https://invent.kde.org/frameworks/ki18n.git"
packaging_url: "https://git.launchpad.net/~kubuntu-packagers/kubuntu-packaging/+git/kf6-ki18n"
packaging_branch: "kubuntu_unstable"
- name: kf6-kiconthemes
upstream_url: "https://invent.kde.org/frameworks/kiconthemes.git"
packaging_url: "https://git.launchpad.net/~kubuntu-packagers/kubuntu-packaging/+git/kf6-kiconthemes"
packaging_branch: "kubuntu_unstable"
- name: kf6-kidletime
upstream_url: "https://invent.kde.org/frameworks/kidletime.git"
packaging_url: "https://git.launchpad.net/~kubuntu-packagers/kubuntu-packaging/+git/kf6-kidletime"
packaging_branch: "kubuntu_unstable"
- name: kf6-kimageformats
upstream_url: "https://invent.kde.org/frameworks/kimageformats.git"
packaging_url: "https://git.launchpad.net/~kubuntu-packagers/kubuntu-packaging/+git/kf6-kimageformats"
packaging_branch: "kubuntu_unstable"
- name: kf6-kio
upstream_url: "https://invent.kde.org/frameworks/kio.git"
packaging_url: "https://git.launchpad.net/~kubuntu-packagers/kubuntu-packaging/+git/kf6-kio"
packaging_branch: "kubuntu_unstable"
- name: kf6-kirigami
upstream_url: "https://invent.kde.org/frameworks/kirigami.git"
packaging_url: "https://git.launchpad.net/~kubuntu-packagers/kubuntu-packaging/+git/kf6-kirigami"
packaging_branch: "kubuntu_unstable"
- name: kf6-kitemmodels
upstream_url: "https://invent.kde.org/frameworks/kitemmodels.git"
packaging_url: "https://git.launchpad.net/~kubuntu-packagers/kubuntu-packaging/+git/kf6-kitemmodels"
packaging_branch: "kubuntu_unstable"
- name: kf6-kitemviews
upstream_url: "https://invent.kde.org/frameworks/kitemviews.git"
packaging_url: "https://git.launchpad.net/~kubuntu-packagers/kubuntu-packaging/+git/kf6-kitemviews"
packaging_branch: "kubuntu_unstable"
- name: kf6-kjobwidgets
upstream_url: "https://invent.kde.org/frameworks/kjobwidgets.git"
packaging_url: "https://git.launchpad.net/~kubuntu-packagers/kubuntu-packaging/+git/kf6-kjobwidgets"
packaging_branch: "kubuntu_unstable"
- name: kf6-knewstuff
upstream_url: "https://invent.kde.org/frameworks/knewstuff.git"
packaging_url: "https://git.launchpad.net/~kubuntu-packagers/kubuntu-packaging/+git/kf6-knewstuff"
packaging_branch: "kubuntu_unstable"
- name: kf6-knotifications
upstream_url: "https://invent.kde.org/frameworks/knotifications.git"
packaging_url: "https://git.launchpad.net/~kubuntu-packagers/kubuntu-packaging/+git/kf6-knotifications"
packaging_branch: "kubuntu_unstable"
- name: kf6-knotifyconfig
upstream_url: "https://invent.kde.org/frameworks/knotifyconfig.git"
packaging_url: "https://git.launchpad.net/~kubuntu-packagers/kubuntu-packaging/+git/kf6-knotifyconfig"
packaging_branch: "kubuntu_unstable"
- name: kf6-kpackage
upstream_url: "https://invent.kde.org/frameworks/kpackage.git"
packaging_url: "https://git.launchpad.net/~kubuntu-packagers/kubuntu-packaging/+git/kf6-kpackage"
packaging_branch: "kubuntu_unstable"
- name: kf6-kparts
upstream_url: "https://invent.kde.org/frameworks/kparts.git"
packaging_url: "https://git.launchpad.net/~kubuntu-packagers/kubuntu-packaging/+git/kf6-kparts"
packaging_branch: "kubuntu_unstable"
- name: kf6-kpeople
upstream_url: "https://invent.kde.org/frameworks/kpeople.git"
packaging_url: "https://git.launchpad.net/~kubuntu-packagers/kubuntu-packaging/+git/kf6-kpeople"
packaging_branch: "kubuntu_unstable"
- name: kf6-kplotting
upstream_url: "https://invent.kde.org/frameworks/kplotting.git"
packaging_url: "https://git.launchpad.net/~kubuntu-packagers/kubuntu-packaging/+git/kf6-kplotting"
packaging_branch: "kubuntu_unstable"
- name: kf6-kpty
upstream_url: "https://invent.kde.org/frameworks/kpty.git"
packaging_url: "https://git.launchpad.net/~kubuntu-packagers/kubuntu-packaging/+git/kf6-kpty"
packaging_branch: "kubuntu_unstable"
- name: kf6-kquickcharts
upstream_url: "https://invent.kde.org/frameworks/kquickcharts.git"
packaging_url: "https://git.launchpad.net/~kubuntu-packagers/kubuntu-packaging/+git/kf6-kquickcharts"
packaging_branch: "kubuntu_unstable"
- name: kf6-krunner
upstream_url: "https://invent.kde.org/frameworks/krunner.git"
packaging_url: "https://git.launchpad.net/~kubuntu-packagers/kubuntu-packaging/+git/kf6-krunner"
packaging_branch: "kubuntu_unstable"
- name: kf6-kservice
upstream_url: "https://invent.kde.org/frameworks/kservice.git"
packaging_url: "https://git.launchpad.net/~kubuntu-packagers/kubuntu-packaging/+git/kf6-kservice"
packaging_branch: "kubuntu_unstable"
- name: kf6-kstatusnotifieritem
upstream_url: "https://invent.kde.org/frameworks/kstatusnotifieritem.git"
packaging_url: "https://git.launchpad.net/~kubuntu-packagers/kubuntu-packaging/+git/kf6-kstatusnotifieritem"
packaging_branch: "kubuntu_unstable"
- name: kf6-ksvg
upstream_url: "https://invent.kde.org/frameworks/ksvg.git"
packaging_url: "https://git.launchpad.net/~kubuntu-packagers/kubuntu-packaging/+git/kf6-ksvg"
packaging_branch: "kubuntu_unstable"
- name: kf6-ktexteditor
upstream_url: "https://invent.kde.org/frameworks/ktexteditor.git"
packaging_url: "https://git.launchpad.net/~kubuntu-packagers/kubuntu-packaging/+git/kf6-ktexteditor"
packaging_branch: "kubuntu_unstable"
- name: kf6-ktexttemplate
upstream_url: "https://invent.kde.org/frameworks/ktexttemplate.git"
packaging_url: "https://git.launchpad.net/~kubuntu-packagers/kubuntu-packaging/+git/kf6-ktexttemplate"
packaging_branch: "kubuntu_unstable"
- name: kf6-ktextwidgets
upstream_url: "https://invent.kde.org/frameworks/ktextwidgets.git"
packaging_url: "https://git.launchpad.net/~kubuntu-packagers/kubuntu-packaging/+git/kf6-ktextwidgets"
packaging_branch: "kubuntu_unstable"
- name: kf6-kunitconversion
upstream_url: "https://invent.kde.org/frameworks/kunitconversion.git"
packaging_url: "https://git.launchpad.net/~kubuntu-packagers/kubuntu-packaging/+git/kf6-kunitconversion"
packaging_branch: "kubuntu_unstable"
- name: kf6-kuserfeedback
upstream_url: "https://invent.kde.org/frameworks/kuserfeedback.git"
packaging_url: "https://git.launchpad.net/~kubuntu-packagers/kubuntu-packaging/+git/kf6-kuserfeedback"
packaging_branch: "kubuntu_unstable"
- name: kf6-kwallet
upstream_url: "https://invent.kde.org/frameworks/kwallet.git"
packaging_url: "https://git.launchpad.net/~kubuntu-packagers/kubuntu-packaging/+git/kf6-kwallet"
packaging_branch: "kubuntu_unstable"
- name: kf6-kwidgetsaddons
upstream_url: "https://invent.kde.org/frameworks/kwidgetsaddons.git"
packaging_url: "https://git.launchpad.net/~kubuntu-packagers/kubuntu-packaging/+git/kf6-kwidgetsaddons"
packaging_branch: "kubuntu_unstable"
- name: kf6-kwindowsystem
upstream_url: "https://invent.kde.org/frameworks/kwindowsystem.git"
packaging_url: "https://git.launchpad.net/~kubuntu-packagers/kubuntu-packaging/+git/kf6-kwindowsystem"
packaging_branch: "kubuntu_unstable"
- name: kf6-kxmlgui
upstream_url: "https://invent.kde.org/frameworks/kxmlgui.git"
packaging_url: "https://git.launchpad.net/~kubuntu-packagers/kubuntu-packaging/+git/kf6-kxmlgui"
packaging_branch: "kubuntu_unstable"
- name: kf6-modemmanager-qt
upstream_url: "https://invent.kde.org/frameworks/modemmanager-qt.git"
packaging_url: "https://git.launchpad.net/~kubuntu-packagers/kubuntu-packaging/+git/kf6-modemmanager-qt"
packaging_branch: "kubuntu_unstable"
- name: kf6-networkmanager-qt
upstream_url: "https://invent.kde.org/frameworks/networkmanager-qt.git"
packaging_url: "https://git.launchpad.net/~kubuntu-packagers/kubuntu-packaging/+git/kf6-networkmanager-qt"
packaging_branch: "kubuntu_unstable"
- name: kf6-prison
upstream_url: "https://invent.kde.org/frameworks/prison.git"
packaging_url: "https://git.launchpad.net/~kubuntu-packagers/kubuntu-packaging/+git/kf6-prison"
packaging_branch: "kubuntu_unstable"
- name: kf6-purpose
upstream_url: "https://invent.kde.org/frameworks/purpose.git"
packaging_url: "https://git.launchpad.net/~kubuntu-packagers/kubuntu-packaging/+git/kf6-purpose"
packaging_branch: "kubuntu_unstable"
- name: kf6-qqc2-desktop-style
upstream_url: "https://invent.kde.org/frameworks/qqc2-desktop-style.git"
packaging_url: "https://git.launchpad.net/~kubuntu-packagers/kubuntu-packaging/+git/kf6-qqc2-desktop-style"
packaging_branch: "kubuntu_unstable"
- name: kf6-solid
upstream_url: "https://invent.kde.org/frameworks/solid.git"
packaging_url: "https://git.launchpad.net/~kubuntu-packagers/kubuntu-packaging/+git/kf6-solid"
packaging_branch: "kubuntu_unstable"
- name: kf6-sonnet
upstream_url: "https://invent.kde.org/frameworks/sonnet.git"
packaging_url: "https://git.launchpad.net/~kubuntu-packagers/kubuntu-packaging/+git/kf6-sonnet"
packaging_branch: "kubuntu_unstable"
- name: kf6-syndication
upstream_url: "https://invent.kde.org/frameworks/syndication.git"
packaging_url: "https://git.launchpad.net/~kubuntu-packagers/kubuntu-packaging/+git/kf6-syndication"
packaging_branch: "kubuntu_unstable"
- name: kf6-syntax-highlighting
upstream_url: "https://invent.kde.org/frameworks/syntax-highlighting.git"
packaging_url: "https://git.launchpad.net/~kubuntu-packagers/kubuntu-packaging/+git/kf6-syntax-highlighting"
packaging_branch: "kubuntu_unstable"
- name: kf6-threadweaver
upstream_url: "https://invent.kde.org/frameworks/threadweaver.git"
packaging_url: "https://git.launchpad.net/~kubuntu-packagers/kubuntu-packaging/+git/kf6-threadweaver"
packaging_branch: "kubuntu_unstable"
- name: kpmcore
upstream_url: "https://invent.kde.org/system/kpmcore.git"
packaging_url: "https://git.launchpad.net/~kubuntu-packagers/kubuntu-packaging/+git/kpmcore"
packaging_branch: "kubuntu_unstable"
- name: plasma-discover
upstream_url: "https://invent.kde.org/plasma/discover.git"
packaging_url: "https://git.launchpad.net/~kubuntu-packagers/kubuntu-packaging/+git/discover"
packaging_branch: "kubuntu_unstable"
- name: skanlite
upstream_url: "https://invent.kde.org/graphics/skanlite.git"
packaging_url: "https://git.launchpad.net/~kubuntu-packagers/kubuntu-packaging/+git/skanlite"
packaging_branch: "kubuntu_unstable"
- name: libksane
upstream_url: "https://invent.kde.org/graphics/libksane.git"
packaging_url: "https://git.launchpad.net/~kubuntu-packagers/kubuntu-packaging/+git/libksane"
packaging_branch: "kubuntu_unstable"
- name: ksanecore
upstream_url: "https://invent.kde.org/libraries/ksanecore.git"
packaging_url: "https://git.launchpad.net/~kubuntu-packagers/kubuntu-packaging/+git/ksanecore"
packaging_branch: "kubuntu_unstable"
releases:
- plucky
- oracular
- noble

@ -0,0 +1,45 @@
packages:
- name: lxqt-build-tools
- name: libdbusmenu-lxqt
packaging_branch: "ci/unstable"
- name: libqtxdg
- name: lxqt-menu-data
- name: liblxqt
- name: libsysstat
- name: qtxdg-tools
- name: libfm-qt
- name: lxqt-globalkeys
- name: lxqt-qtplugin
- name: qtermwidget
- name: lxqt-panel
- name: pcmanfm-qt
- name: qterminal
- name: lxqt-powermanagement
- name: lxqt-runner
- name: lxqt-themes
- name: lxqt-admin
- name: lxqt-notificationd
- name: lxqt-about
- name: lxqt-config
- name: lxqt-policykit
- name: lxqt-sudo
- name: lxqt-openssh-askpass
- name: lxqt-session
- name: pavucontrol-qt
- name: xdg-desktop-portal-lxqt
- name: lxqt-archiver
- name: screengrab
- name: lximage-qt
- name: qps
- name: obconf-qt
- name: nm-tray
upstream_url: "https://github.com/palinek/nm-tray.git"
- name: calamares
upstream_url: "https://github.com/calamares/calamares.git"
packaging_url: "https://git.launchpad.net/~ubuntu-qt-code/ubuntu/+source/calamares/+git/calamares"
packaging_branch: "ci/unstable"
releases:
- plucky
- oracular
- noble

@ -0,0 +1,110 @@
packages:
- name: qt6-base
upstream_url: "https://code.qt.io/qt/qtbase.git"
packaging_url: "https://git.launchpad.net/~ubuntu-qt-code/+git/qt6-base"
packaging_branch: "ci/unstable"
- name: qt6-imageformats
upstream_url: "https://code.qt.io/qt/qtimageformats.git"
packaging_url: "https://git.launchpad.net/~ubuntu-qt-code/+git/qt6-imageformats"
packaging_branch: "ci/unstable"
- name: qt6-languageserver
upstream_url: "https://code.qt.io/qt/qtlanguageserver.git"
packaging_url: "https://git.launchpad.net/~ubuntu-qt-code/+git/qt6-languageserver"
packaging_branch: "ci/unstable"
- name: qt6-shadertools
upstream_url: "https://code.qt.io/qt/qtshadertools.git"
packaging_url: "https://git.launchpad.net/~ubuntu-qt-code/+git/qt6-shadertools"
packaging_branch: "ci/unstable"
- name: qt6-svg
upstream_url: "https://code.qt.io/qt/qtsvg.git"
packaging_url: "https://git.launchpad.net/~ubuntu-qt-code/+git/qt6-svg"
packaging_branch: "ci/unstable"
- name: qt6-networkauth
upstream_url: "https://code.qt.io/qt/qtnetworkauth.git"
packaging_url: "https://git.launchpad.net/~ubuntu-qt-code/+git/qt6-networkauth"
packaging_branch: "ci/unstable"
- name: qt6-serialport
upstream_url: "https://code.qt.io/qt/qtserialport.git"
packaging_url: "https://git.launchpad.net/~ubuntu-qt-code/+git/qt6-serialport"
packaging_branch: "ci/unstable"
- name: qt6-declarative
upstream_url: "https://code.qt.io/qt/qtdeclarative.git"
packaging_url: "https://git.launchpad.net/~ubuntu-qt-code/+git/qt6-declarative"
packaging_branch: "ci/unstable"
- name: qt6-lottie
upstream_url: "https://code.qt.io/qt/qtlottie.git"
packaging_url: "https://git.launchpad.net/~ubuntu-qt-code/+git/qt6-lottie"
packaging_branch: "ci/unstable"
- name: qt6-websockets
upstream_url: "https://code.qt.io/qt/qtwebsockets.git"
packaging_url: "https://git.launchpad.net/~ubuntu-qt-code/+git/qt6-websockets"
packaging_branch: "ci/unstable"
- name: qt6-5compat
upstream_url: "https://code.qt.io/qt/qt5compat.git"
packaging_url: "https://git.launchpad.net/~ubuntu-qt-code/+git/qt6-5compat"
packaging_branch: "ci/unstable"
- name: qt6-connectivity
upstream_url: "https://code.qt.io/qt/qtconnectivity.git"
packaging_url: "https://git.launchpad.net/~ubuntu-qt-code/+git/qt6-connectivity"
packaging_branch: "ci/unstable"
- name: qt6-scxml
upstream_url: "https://code.qt.io/qt/qtscxml.git"
packaging_url: "https://git.launchpad.net/~ubuntu-qt-code/+git/qt6-scxml"
packaging_branch: "ci/unstable"
- name: qt6-sensors
upstream_url: "https://code.qt.io/qt/qtsensors.git"
packaging_url: "https://git.launchpad.net/~ubuntu-qt-code/+git/qt6-sensors"
packaging_branch: "ci/unstable"
- name: qt6-wayland
upstream_url: "https://code.qt.io/qt/qtwayland.git"
packaging_url: "https://git.launchpad.net/~ubuntu-qt-code/+git/qt6-wayland"
packaging_branch: "ci/unstable"
- name: qt6-datavis3d
upstream_url: "https://code.qt.io/qt/qtdatavis3d.git"
packaging_url: "https://git.launchpad.net/~ubuntu-qt-code/+git/qt6-datavis3d"
packaging_branch: "ci/unstable"
- name: qt6-grpc
upstream_url: "https://code.qt.io/qt/qtgrpc.git"
packaging_url: "https://git.launchpad.net/~ubuntu-qt-code/+git/qt6-grpc"
packaging_branch: "ci/unstable"
- name: qt6-positioning
upstream_url: "https://code.qt.io/qt/qtpositioning.git"
packaging_url: "https://git.launchpad.net/~ubuntu-qt-code/+git/qt6-positioning"
packaging_branch: "ci/unstable"
- name: qt6-quicktimeline
upstream_url: "https://code.qt.io/qt/qtquicktimeline.git"
packaging_url: "https://git.launchpad.net/~ubuntu-qt-code/+git/qt6-quicktimeline"
packaging_branch: "ci/unstable"
- name: qt6-serialbus
upstream_url: "https://code.qt.io/qt/qtserialbus.git"
packaging_url: "https://git.launchpad.net/~ubuntu-qt-code/+git/qt6-serialbus"
packaging_branch: "ci/unstable"
- name: qt6-tools
upstream_url: "https://code.qt.io/qt/qttools.git"
packaging_url: "https://git.launchpad.net/~ubuntu-qt-code/+git/qt6-tools"
packaging_branch: "ci/unstable"
- name: qt6-webchannel
upstream_url: "https://code.qt.io/qt/qtwebchannel.git"
packaging_url: "https://git.launchpad.net/~ubuntu-qt-code/+git/qt6-webchannel"
packaging_branch: "ci/unstable"
- name: qt6-httpserver
upstream_url: "https://code.qt.io/qt/qthttpserver.git"
packaging_url: "https://git.launchpad.net/~ubuntu-qt-code/+git/qt6-httpserver"
packaging_branch: "ci/unstable"
- name: qt6-remoteobjects
upstream_url: "https://code.qt.io/qt/qtremoteobjects.git"
packaging_url: "https://git.launchpad.net/~ubuntu-qt-code/+git/qt6-remoteobjects"
packaging_branch: "ci/unstable"
- name: qt6-location
upstream_url: "https://code.qt.io/qt/qtlocation.git"
packaging_url: "https://git.launchpad.net/~ubuntu-qt-code/+git/qt6-location"
packaging_branch: "ci/unstable"
- name: qt6-translations
upstream_url: "https://code.qt.io/qt/qttranslations.git"
packaging_url: "https://git.launchpad.net/~ubuntu-qt-code/+git/qt6-translations"
packaging_branch: "ci/unstable"
releases:
- plucky
- oracular
- noble

@ -0,0 +1,161 @@
#!/bin/bash
# download current package indexes to data/<series>{,-proposed}/ for running
# britney against a PPA. The PPA will play the role of "-proposed" (i. e.
# "unstable" in britney terms, containing the updated packages to test), the
# Ubuntu archive has the "-release" part (i. e. "testing" in britney terms, in
# which the -proposed packages are being landed).
#
# Copyright (C) 2019-2024 Simon Quigley <tsimonq2@ubuntu.com>
# Copyright (C) Canonical Ltd
# Author: Martin Pitt <martin.pitt@ubuntu.com>
# Author: Robert Bruce Park <robert.park@canonical.com>
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
set -u
./pending-packages $RELEASE || exit 0
export MAIN_ARCHIVE="http://us.archive.ubuntu.com/ubuntu/dists/"
export PORTS_ARCHIVE="http://us.ports.ubuntu.com/ubuntu-ports/dists/"
export LP_TEAM="lubuntu-ci"
export SOURCE_PPA="unstable-ci-proposed"
export DEST_PPA="unstable-ci"
export SOURCE_PPA_URL="https://ppa.launchpadcontent.net/$LP_TEAM/$SOURCE_PPA/ubuntu/dists/$RELEASE/main";
export DEST_PPA_URL="https://ppa.launchpadcontent.net/$LP_TEAM/$DEST_PPA/ubuntu/dists/$RELEASE/main";
export ARCHES="amd64"
export PORTS_ARCHES="arm64 armhf ppc64el riscv64 s390x"
export BRITNEY_CACHE="cache/"
export BRITNEY_DATADIR="data/"
export BRITNEY_OUTDIR="output/"
export BRITNEY_HINTDIR="../hints-ubuntu/"
export BRITNEY_LOC="/srv/lubuntu-ci/repos/britney2-ubuntu/britney.py"
export BRITNEY_TIMESTAMP=$(date +"%Y-%m-%d_%H:%M:%S")
echo "Release: $RELEASE";
echo "Timestamp: $BRITNEY_TIMESTAMP"
# Download files in parallel in background, only if there is an update
refresh() {
DIR=$BRITNEY_CACHE/$pocket/$(echo $1 | rev | cut --delimiter=/ --fields=2,3 | rev)
mkdir --parents $DIR
touch --no-create $BRITNEY_CACHE $BRITNEY_CACHE/$pocket "$(dirname $DIR)" $DIR # Timestamp thwarts expire.sh
wget --directory-prefix $DIR --timestamping $1 --append-output $DIR/$$-wget-log --no-verbose &
}
echo 'Refreshing package indexes...'
for pocket in $RELEASE $RELEASE-updates; do
for component in main restricted universe multiverse; do
for arch in $ARCHES; do
refresh $MAIN_ARCHIVE/$pocket/$component/binary-$arch/Packages.gz
done
for arch in $PORTS_ARCHES; do
refresh $PORTS_ARCHIVE/$pocket/$component/binary-$arch/Packages.gz
done
refresh $MAIN_ARCHIVE/$pocket/$component/source/Sources.gz
done
done
# Treat the destination PPA as just another pocket
for pocket in $RELEASE-ppa-proposed; do
for arch in $ARCHES $PORTS_ARCHES; do
refresh $DEST_PPA_URL/source/Sources.gz
refresh $DEST_PPA_URL/binary-$arch/Packages.gz
done
done
# Get the source PPA
pocket=$SOURCE_PPA-$RELEASE
for arch in $ARCHES $PORTS_ARCHES; do
refresh $SOURCE_PPA_URL/binary-$arch/Packages.gz
done
refresh $SOURCE_PPA_URL/source/Sources.gz
wait # for wgets to finish
find $BRITNEY_DATADIR -name "$$-wget-log*" -exec cat '{}' \; -delete 1>&2
echo 'Building britney indexes...'
mkdir --parents "$BRITNEY_OUTDIR/$BRITNEY_TIMESTAMP/"
# "Unstable" is SOURCE_PPA
DEST=$BRITNEY_DATADIR/$RELEASE-proposed
mkdir --parents $DEST
mkdir -pv $BRITNEY_DATADIR/$RELEASE-proposed/state/
touch $BRITNEY_DATADIR/$RELEASE-proposed/state/age-policy-dates
touch --no-create $DEST
ln --verbose --symbolic --force --no-dereference $BRITNEY_HINTDIR $DEST/Hints
zcat $BRITNEY_CACHE/$SOURCE_PPA-$RELEASE/*/source/Sources.gz > $DEST/Sources
for arch in $ARCHES $PORTS_ARCHES; do
zcat $BRITNEY_CACHE/$SOURCE_PPA-$RELEASE/*/binary-$arch/Packages.gz > $DEST/Packages_${arch}
done
touch $DEST/Blocks $DEST/Dates
# "Testing" is a combination of the archive and DEST_PPA
DEST=$BRITNEY_DATADIR/$RELEASE
mkdir --parents $DEST
mkdir -pv $BRITNEY_DATADIR/$RELEASE/state/
touch $BRITNEY_DATADIR/$RELEASE/state/age-policy-dates
touch --no-create $DEST
ln --verbose --symbolic --force --no-dereference $BRITNEY_HINTDIR $DEST/Hints
zcat $BRITNEY_CACHE/$RELEASE*/*/source/Sources.gz > $DEST/Sources
sed -i "s/Section: universe\//Section: /g" $DEST/Sources
for arch in $ARCHES $PORTS_ARCHES; do
zcat $BRITNEY_CACHE/$RELEASE*/*/binary-$arch/Packages.gz > $DEST/Packages_${arch}
sed -i "s/Section: universe\//Section: /g" $DEST/Packages_${arch}
done
touch $DEST/Blocks
touch "$BRITNEY_DATADIR/$SOURCE_PPA-$RELEASE/Dates"
# Create config file atomically.
CONFIG="britney.conf"
cp $CONFIG $CONFIG.bak
envsubst < "$CONFIG.bak" > "$CONFIG"
rm $CONFIG.bak
echo 'Running britney...'
$BRITNEY_LOC -v --config "$CONFIG" --series $RELEASE
echo 'Syncing output to frontend...'
rmdir output/;
rsync -da output/ ../../output/britney
echo "$0 done."
echo "Moving packages..."
egrep -v '^#' output/$RELEASE/HeidiResultDelta > candidates || echo "No candidates found.";
while read -r -a package; do
# This only acts on sources; binaries require manual cleanup
if [ ${#package[@]} = 2 ]; then
COPY="../ubuntu-archive-tools/copy-package"
REMOVE="../ubuntu-archive-tools/remove-package"
if echo ${package[0]} | egrep -q "^-"; then
PACKAGE=$(echo ${package[0]} | sed 's/-//')
echo "Demoting $PACKAGE..."
$COPY -y -b -s $RELEASE --from "ppa:$LP_TEAM/ubuntu/$DEST_PPA" --to "ppa:$LP_TEAM/ubuntu/$SOURCE_PPA" --version "${package[1]}" "$PACKAGE";
$REMOVE -y -s $RELEASE --archive "ppa:$LP_TEAM/ubuntu/$DEST_PPA" --version "${package[1]}" --removal-comment="demoted to proposed" "$PACKAGE";
else
echo "Migrating ${package[0]}..."
$COPY -y -b -s $RELEASE --from "ppa:$LP_TEAM/ubuntu/$SOURCE_PPA" --to "ppa:$LP_TEAM/ubuntu/$DEST_PPA" --version "${package[1]}" "${package[0]}";
$REMOVE -y -s $RELEASE --archive "ppa:$LP_TEAM/ubuntu/$SOURCE_PPA" --version "${package[1]}" --removal-comment="moved to release" "${package[0]}";
fi
fi
done < candidates;
rm candidates;
echo "Run the grim reaper..."
./grim-reaper

@ -0,0 +1,64 @@
#!/usr/bin/env python3
#
# Copyright (C) 2024 Simon Quigley <tsimonq2@ubuntu.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
from concurrent.futures import ThreadPoolExecutor, as_completed
from datetime import datetime, timedelta
from launchpadlib.launchpad import Launchpad
now = datetime.now()
def print_log(string):
global now
old_now = now
now = datetime.now()
time_elapsed = now - old_now
print(f"[{now}] (took {time_elapsed}) {string}")
print(f"[{now}] Logging into Launchpad...")
launchpad = Launchpad.login_with("grim-reaper", "production", version="devel")
print_log("Logged in. Initializing repositories...")
ubuntu = launchpad.distributions["ubuntu"]
lubuntu_ci = launchpad.people["lubuntu-ci"]
regular = lubuntu_ci.getPPAByName(distribution=ubuntu, name="unstable-ci")
proposed = lubuntu_ci.getPPAByName(distribution=ubuntu, name="unstable-ci-proposed")
print_log("IS THAT THE GRIM REAPER?!?!?!?!!!")
# Fetch packages once
two_weeks_ago = datetime.now() - timedelta(days=14)
packages = [proposed.getPublishedSources(status="Superseded"), regular.getPublishedSources(status="Superseded")]
total_removals = sum(len(packageset) for packageset in packages)
print_log(f"Total packages to remove: {total_removals}")
current_package = 1
current_percentage = 0
for packageset in packages:
for pkg in packageset:
# Cancel all running builds for the package:
for build in pkg.getBuilds():
if build.buildstate in ["Currently building", "Needs building"]:
# Only cancel the build if we can
if build.can_be_cancelled:
build.cancel()
# Delete the source package
pkg.requestDeletion(removal_comment="superseded")
new_percentage = int(current_package / total_removals)
if new_percentage > current_percentage:
current_percentage = new_percentage
print_log(f"{new_percentage}% complete ({current_package}/{total_removals})")
current_package += 1

@ -0,0 +1,167 @@
#!/usr/bin/env python3
#
# Copyright (C) 2024 Simon Quigley <tsimonq2@ubuntu.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
import argparse
import logging
import os
import shutil
import subprocess
import tempfile
import uuid
from common import clean_old_logs
from concurrent.futures import ThreadPoolExecutor, wait, FIRST_COMPLETED
from datetime import datetime, timedelta, timezone
from debian.deb822 import Changes
from launchpadlib.launchpad import Launchpad
from pathlib import Path
BASE_OUTPUT_DIR = "/srv/lubuntu-ci/output/"
LOG_DIR = os.path.join(BASE_OUTPUT_DIR, "logs/lintian/")
parser = argparse.ArgumentParser(description="")
parser.add_argument("--user", "-u", required=True)
parser.add_argument("--ppa", "-p", required=True)
parser.add_argument("--ppa2", "-p2")
args = parser.parse_args()
os.makedirs(LOG_DIR, exist_ok=True)
current_time = datetime.utcnow().strftime("%H-%M-%S")
log_file = os.path.join(LOG_DIR, f"{current_time}.log")
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(levelname)s - %(message)s",
handlers=[
logging.FileHandler(log_file)
]
)
logger = logging.getLogger("TimeBasedLogger")
launchpad = Launchpad.login_with("lintian-ppa", "production", version="devel")
ubuntu = launchpad.distributions["ubuntu"]
current_series = ubuntu.current_series
user = launchpad.people[args.user]
ppa = user.getPPAByName(distribution=ubuntu, name=args.ppa)
if args.ppa2:
ppa2 = user.getPPAByName(distribution=ubuntu, name=args.ppa)
if not os.path.exists(args.user):
os.mkdir(args.user)
lintian = os.path.join(BASE_OUTPUT_DIR, "lintian")
lintian_tmp = os.path.join(BASE_OUTPUT_DIR, f".lintian.tmp.{str(uuid.uuid4())[:8]}")
if not os.path.exists(lintian):
os.mkdir(lintian)
if os.path.exists(lintian_tmp):
shutil.rmtree(lintian_tmp)
os.mkdir(lintian_tmp)
def rsync(source, destination):
src = Path(source)
dst = Path(destination)
dst.mkdir(parents=True, exist_ok=True)
for item in src.iterdir():
src_path = item
dst_path = dst / item.name
if src_path.is_symlink():
if dst_path.exists() or dst_path.is_symlink():
dst_path.unlink()
os.symlink(os.readlink(src_path), dst_path)
elif src_path.is_dir():
shutil.copytree(src_path, dst_path, symlinks=True, dirs_exist_ok=True)
else:
shutil.copy2(src_path, dst_path)
def process_sources(url):
tmpdir = os.
changes_file = url.split("/")[-1]
logging.info(f"Downloading {changes_file} and friends via dget")
dget_command = ["dget", "-u", url]
result = subprocess.run(dget_command, cwd=tmpdir, capture_output=True)
with open(os.path.join(tmpdir, changes_file), "r") as f:
changes_obj = Changes(f)
source = changes_obj["Source"]
arch = changes_obj["Architecture"].replace("all", "").replace("_translations", "").split(" ")[0].strip()
if arch == "":
return
logging.info(f"Running Lintian for {source} on {arch}")
lintian_command = ["lintian", "-EvIL", "+pedantic", changes_file]
result = subprocess.run(lintian_command, cwd=tmpdir, capture_output=True)
stderr = result.stderr.decode("utf-8").strip()
stdout = result.stdout.decode("utf-8").strip()
if stderr == stdout:
lintian_output = stderr
elif stderr != "" and stdout == "":
lintian_output = stderr
elif stderr == "" and stdout != "":
lintian_output = stdout
else:
lintian_output = f"{stderr}\n{stdout}"
output_path = os.path.join(lintian_tmp, source)
if not os.path.exists(output_path):
os.mkdir(output_path)
with open(os.path.join(output_path, f"{arch}.txt"), "w") as f:
f.write(lintian_output)
with ThreadPoolExecutor(max_workers=30) as executor:
futures = set()
def main_source_iter():
last_run_file = os.path.join(args.user, ".LAST_RUN")
last_run_datetime = datetime.now(timezone.utc) - timedelta(days=365)
if os.path.exists(last_run_file):
with open(last_run_file, "r") as file:
last_run_time = file.read().strip()
last_run_datetime = datetime.fromisoformat(last_run_time)
last_run_datetime = last_run_datetime.replace(tzinfo=timezone.utc)
logging.info(f"Last run: {last_run_datetime}")
with open(last_run_file, "w") as file:
current_time = datetime.now(timezone.utc).isoformat()
file.write(current_time)
for source in ppa.getPublishedSources(status="Published", distro_series=current_series):
for build in source.getBuilds():
if build.buildstate == "Successfully built" and build.datebuilt >= last_run_datetime:
futures.add(executor.submit(process_sources, build.changesfile_url))
futures.add(executor.submit(main_source_iter))
while futures:
done, not_done = wait(futures, return_when=FIRST_COMPLETED)
for future in done:
try:
result = future.result()
except Exception as e:
logging.exception("Task generated an exception:")
finally:
futures.remove(future)
rsync(lintian_tmp, lintian)
shutil.rmtree(lintian_tmp)
clean_old_logs(LOG_DIR)
logging.info("Done")

@ -0,0 +1,103 @@
#!/usr/bin/env python3
#
# Copyright (C) 2024 Simon Quigley <tsimonq2@ubuntu.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
import argparse
from datetime import datetime, timedelta, timezone
from launchpadlib.launchpad import Launchpad
print(f"Logging into Launchpad...")
launchpad = Launchpad.login_with("pending-packages", "production", version="devel")
print("Logged in. Initializing repositories...")
ubuntu = launchpad.distributions["ubuntu"]
lubuntu_ci = launchpad.people["lubuntu-ci"]
regular = lubuntu_ci.getPPAByName(distribution=ubuntu, name="unstable-ci")
proposed = lubuntu_ci.getPPAByName(distribution=ubuntu, name="unstable-ci-proposed")
parser = argparse.ArgumentParser()
parser.add_argument("release")
args = parser.parse_args()
series = ubuntu.getSeries(name_or_version=args.release)
# First, check if any sources are still publishing
print("Repositories initialized. Checking for pending sources...")
records = [regular.getPublishedSources(status="Pending", distro_series=series),
proposed.getPublishedSources(status="Pending", distro_series=series)]
total_pending = sum([len(i) for i in records])
has_pending = total_pending != 0
if has_pending:
print(f"Total sources pending: {total_pending}")
print("Sources are still pending, not running Britney")
exit(1)
# Finally, check if any builds are still running/queued
print("No pending sources, continuing. Checking for pending builds...")
total_pending = 0
total_retried = 0
for archive in [proposed, regular]:
one_hour_ago = datetime.now(timezone.utc) - timedelta(hours=1)
for source in archive.getPublishedSources(status="Published", distro_series=series):
for build in source.getBuilds():
if build.buildstate == "Currently building":
if build.date_started >= one_hour_ago:
total_pending += 1
elif build.buildstate == "Needs building":
total_pending += 1
# This isn't technically related, but retry failed builds without logs
elif build.buildstate == "Chroot problem" or (build.buildstate == "Failed to build" and not build.build_log_url):
if build.can_be_retried:
build.retry()
total_pending += 1
total_retried += 1
if total_retried != 0:
print(f"Total builds retried due to builder flakiness: {total_retried}")
if total_pending != 0:
print(f"Total builds pending: {total_pending}")
print("Builds are still running, not running Britney")
exit(1)
print("No pending builds, continuing. Checking for pending binaries...")
has_pending = False
for pocket in [proposed, regular]:
if has_pending:
break
three_hours_ago = datetime.now(timezone.utc) - timedelta(hours=3)
check_builds = set()
current_builds = set()
source_packages = []
for build in pocket.getBuildRecords(build_state="Successfully built"):
if build.datebuilt < three_hours_ago:
del source_packages
break
check_builds.add(build.title)
source_package = build.current_source_publication
if source_package and source_package.distro_series == series and source_package not in source_packages:
source_packages.append(source_package)
for binary in source_package.getPublishedBinaries():
current_builds.add(binary.build.title)
has_pending = not check_builds.issuperset(current_builds) or has_pending
if has_pending:
print("Binaries are still pending, not running Britney")
exit(1)
print("All clear. Starting Britney.")

@ -0,0 +1,38 @@
#!/bin/bash
#
# Copyright (C) 2024 Simon Quigley <tsimonq2@ubuntu.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
# Configuration
LOG_DIR="/srv/lubuntu-ci/output/logs/britney"
SCRIPT_PATH="fetch-indexes"
MAX_LOG_AGE=86400 # 24 hours in seconds
# Ensure the log directory exists
mkdir -p "$LOG_DIR"
# Log rotation: Remove logs older than MAX_LOG_AGE
find "$LOG_DIR" -type f -mtime +1 -exec rm -f {} \;
# Execute the fetch-indexes script for each release and log output
for release in plucky oracular noble; do
export RELEASE="$release"
# Log file named by current UTC time (HH-MM-SS)
LOG_FILE="$LOG_DIR/$RELEASE_$(date -u +"%H-%M-%S").log"
echo "$(date -u +"%Y-%m-%d %H:%M:%S") - Running Britney for $RELEASE" >> "$LOG_FILE"
"$SCRIPT_PATH" >> "$LOG_FILE" 2>&1
done

@ -0,0 +1,11 @@
[Unit]
Description=Britney
Wants=britney.timer
After=network.target
[Service]
Type=simple
User=lugito
Group=lugito
WorkingDirectory=/srv/lubuntu-ci/repos/ci-tools
ExecStart=/usr/bin/python3 /srv/lubuntu-ci/repos/ci-tools/run-britney

@ -0,0 +1,10 @@
[Unit]
Description=Britney timer
[Timer]
OnBootSec=5min
OnUnitActiveSec=30min
Persistent=true
[Install]
WantedBy=timers.target
Loading…
Cancel
Save