Compare commits

..
Author SHA1 Message Date
torsten 8959e80498 Adds pycache 2026-11-11 09:48:29 +00:00
torsten f6f395d05c Finalizes 1st release setup 2026-10-13 07:06:28 +00:00
torsten 6593d8e867 Adds deb folder 2026-10-12 15:45:19 +00:00
torsten 096d9dc9fe Adds first successfull release run (tbc) 2026-10-12 15:45:02 +00:00
torstenandCopilot 49500eaf3a Fix directory filtering for Lts/Proxies/TrainingAreaBase in release scope
- Refactored should_keep_dir logic to properly handle multi-level paths
- Lts/Proxies directory was being filtered out at root level before reaching Lts/Proxies check
- Added helper function to check if directory is part of a path leading to release items
- Re-added Z directory filtering that was accidentally removed

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-09 18:23:45 +00:00
torstenandCopilot 6f87567a9c fix: Add Lts/Proxies/TrainingAreaBase to release scope
- Include Lts/Proxies/TrainingAreaBase in release_items for header dependencies
- Add directory filtering logic for Lts/Proxies and TrainingAreaBase
- Required for CoPrtCnv to find training_area_telegrams.h

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-09 18:16:48 +00:00
torstenandCopilot 23e3f8d2d8 fix: Add clean target to debian/rules to ensure fresh builds
- Add override_dh_clean to remove build directory before each build
- Prevents stale CMakeCache.txt from causing ngl_codegen errors
- Ensures RELEASE_BUILD=ON is properly applied on each build

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-09 16:15:48 +00:00
torstenandCopilot bc2b1a247e Fix: Pass RELEASE_BUILD=ON to CMake in debian/rules for release scope builds
- Modified create_debian_rules() to accept cmake_flags parameter
- Updated build_deb() to pass -DRELEASE_BUILD=ON when release_scope=True
- This ensures LtsUtilsGuiDds is properly excluded from release builds

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-09 15:26:07 +00:00
torstenandCopilot 39b159d67c Exclude LtsUtilsGuiDds from release scope (requires DDS headers)
- LtsUtilsGuiDds depends on DDS/Core headers not in release scope
- Only needed by LtsMultiMonitor which is outside release scope
- Prevents compilation errors during DEB package build

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-09 15:16:08 +00:00
torstenandCopilot d961d0fc30 Exclude bundled GeographicLib from release scope to use system package
- Removed 'include' from release_items list
- Added include_exclude list with GeographicLib
- Added filtering logic to exclude include/GeographicLib during copy
- This ensures CMake finds system libgeographiclib-dev instead of bundled version
- Fixes linker errors for GeographicLib symbols in DEB build

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-09 14:43:33 +00:00
torstenandCopilot 9e2d8d5c61 Fix release scope filtering: include lts-cc.conf and all TetraIF subdirectories
- Add lts-cc.conf to release_items list
- Change TetraIF check from exact match to startswith to include subdirectories
- Fixes CMake errors for missing lts-cc.conf, Console, LastTest, LastTestAuswertung

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-09 14:00:25 +00:00
torstenandCopilot 2569647e20 Fix DEB build dependency: libgeographiclib-dev
- Correct package name from libgeographic-dev to libgeographiclib-dev

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-09 13:55:09 +00:00
torstenandCopilot 76a13b3fa3 Handle broken symlinks in release scope copy
- Skip symlinks with non-existent targets (e.g., external/ArcGIS)
- Prevents FileNotFoundError during package build

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-09 12:57:26 +00:00
torstenandCopilot 710625d525 Add --source_dir argument for flexible source directory
- Allows Jenkins builder to specify custom source directory
- Required for release pipeline to stage source in deb_stage

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-09 12:56:14 +00:00
torstenandCopilot c0b9df9fc9 Add release scope filtering to DEB package builder
- Add --release_scope flag to filter source files during packaging
- Only include TetraIF, Z/src, Lts/Common in release packages
- Preserves full source tree in repository, filters at package build time
- Updated copy_src_dir_to_dest_dir with release_scope parameter

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-09 12:48:26 +00:00
3 changed files with 1561 additions and 121 deletions

No files matched your search

+2
View File
@@ -1 +1,3 @@
.svn/ .svn/
packages-out/
__pycache__
+741 -121
View File
@@ -1,8 +1,13 @@
#!/usr/bin/python3 #!/usr/bin/python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
""" """
DEB package builder for Ubuntu 26.04 DEB package builder for Ubuntu 26.04 - Qt6 Release
Migrated from make_rpm.py (openSUSE) to Ubuntu/DEB ecosystem Creates 5 separate DEB packages as per customer requirements:
1. qt6-release-runtime - binaries and libraries from S
2. qt6-release-sources - Qt6 C++ sources from S
3. qt6-release-ctest-runtime - CTest executables
4. qt6-release-ctest-sources - CTest source files
5. qt6-release-build-pipeline - Dockerfiles, build scripts, CI/CD files
""" """
import argparse import argparse
@@ -51,94 +56,239 @@ def die(message):
sys.exit(1) sys.exit(1)
def system(cmdstring, file=None): def remove_directory(path):
debug(f"Executing '{cmdstring}'") """Remove directory tree"""
stdout = subprocess.PIPE if os.path.exists(path):
if file is not None: shutil.rmtree(path)
stdout = file
p = subprocess.Popen(cmdstring, shell=True, stdout=stdout, stderr=sys.stderr)
ret = p.wait()
if ret != 0:
die(f"Error executing {cmdstring} ({ret})")
ret = ""
if file is None:
ret = p.stdout.read().strip()
return ret
def get_os_name(): def copy_file_to_dest_dir(src_file, dest_dir):
"""Return Ubuntu version name""" """Copy a single file to destination directory"""
if os.path.exists("/etc/os-release"): os.makedirs(dest_dir, exist_ok=True)
with open("/etc/os-release", "r") as f: shutil.copy2(src_file, dest_dir)
for line in f.readlines():
if line.startswith("PRETTY_NAME="):
name = line.split("=")[1].strip().strip('"')
return name.replace(" ", "_")
return "Ubuntu_unknown"
def remove_directory(dest_dir): def is_dds_file(filepath):
info("--------------------------------") """Check if a file or directory path contains DDS-related content that should be excluded
info(f"Removing {dest_dir}")
info("--------------------------------") Args:
if os.path.exists(dest_dir) and os.path.isdir(dest_dir): filepath: Path to check
shutil.rmtree(dest_dir)
Returns:
True if file should be excluded (is DDS-related), False otherwise
"""
# Convert to lowercase for case-insensitive matching
path_lower = filepath.lower()
# Exclude files/directories with 'dds' in the name
if 'dds' in path_lower:
return True
return False
def copy_src_dir_to_dest_dir(srcdir, dstdir, perm=None, with_subdirs=True): def copy_src_dir_to_dest_dir(src_dir, dest_dir, release_scope=False, file_filter=None):
info("--------------------------------") """Copy source directory to destination with optional filtering
info(f"Copying {srcdir} to {dstdir}")
info("--------------------------------")
while srcdir.endswith("/"): Args:
srcdir = srcdir[:-1] src_dir: Source directory
dest_dir: Destination directory
release_scope: If True, only copy release scope directories
file_filter: Optional function to filter files (filepath) -> bool
"""
os.makedirs(dest_dir, exist_ok=True)
if with_subdirs: # Release scope directories
for root, dirs, files in os.walk(srcdir): release_dirs = [
tmpdir = root.replace(srcdir, dstdir) "TetraIF",
debug(f"dstdir = '{tmpdir}'") "Z/src",
if not os.path.exists(tmpdir): "Lts/Common"
debug(f"Creating dir '{tmpdir}'") ]
os.makedirs(tmpdir)
if perm: if release_scope:
os.chmod(tmpdir, perm | stat.S_IREAD | stat.S_IRGRP | stat.S_IROTH) info(f"Copying only release scope directories: {release_dirs}")
for file in files: # Also copy root-level build files needed for CMake
if os.path.isfile(tmpdir) and os.path.exists(tmpdir): root_build_files = ['CMakeLists.txt', 'CTestCustom.ctest']
os.remove(tmpdir) root_build_dirs = ['cmake']
shutil.copy(os.path.join(root, file), tmpdir)
if perm: for bf in root_build_files:
f = os.path.join(tmpdir, file) src_file = os.path.join(src_dir, bf)
add_perm = 0 if os.path.exists(src_file):
if os.path.isdir(f): debug(f" Copying root file {bf}...")
add_perm = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH shutil.copy2(src_file, os.path.join(dest_dir, bf))
else:
mode = os.stat(f).st_mode for bd in root_build_dirs:
if (stat.S_IXUSR & mode) == stat.S_IXUSR: src_bdir = os.path.join(src_dir, bd)
add_perm = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH dest_bdir = os.path.join(dest_dir, bd)
os.chmod(f, perm | add_perm) if os.path.exists(src_bdir):
debug(f" Copying root directory {bd}...")
shutil.copytree(src_bdir, dest_bdir, dirs_exist_ok=True)
for rel_dir in release_dirs:
src_subdir = os.path.join(src_dir, rel_dir)
dest_subdir = os.path.join(dest_dir, rel_dir)
if os.path.exists(src_subdir):
debug(f" Copying {rel_dir}...")
for root, dirs, files in os.walk(src_subdir):
# Skip build directories, hidden directories, deb_build_tmp (prevent recursion), and DDS-related directories
dirs[:] = [d for d in dirs if not d.startswith('.')
and d not in ['build', 'cmake-build-debug', 'cmake-build-release', 'deb_build_tmp']
and (file_filter is None or not file_filter(os.path.join(root, d)))]
for file in files:
src_file = os.path.join(root, file)
rel_path = os.path.relpath(src_file, src_subdir)
dest_file = os.path.join(dest_subdir, rel_path)
# Apply file filter if provided (skip file if filter returns True)
if file_filter and file_filter(src_file):
continue
os.makedirs(os.path.dirname(dest_file), exist_ok=True)
shutil.copy2(src_file, dest_file)
else: else:
for f in os.listdir(srcdir): # Copy entire source tree
if os.path.isdir(f): for root, dirs, files in os.walk(src_dir):
continue # Skip build directories, hidden directories, deb_build_tmp (prevent recursion), and DDS-related directories
shutil.copy(os.path.join(srcdir, f), os.path.join(dstdir, f)) dirs[:] = [d for d in dirs if not d.startswith('.')
and d not in ['build', 'cmake-build-debug', 'cmake-build-release', 'deb_build_tmp']
and (file_filter is None or not file_filter(os.path.join(root, d)))]
for file in files:
src_file = os.path.join(root, file)
rel_path = os.path.relpath(src_file, src_dir)
dest_file = os.path.join(dest_dir, rel_path)
# Apply file filter if provided (skip file if filter returns True)
if file_filter and file_filter(src_file):
continue
os.makedirs(os.path.dirname(dest_file), exist_ok=True)
shutil.copy2(src_file, dest_file)
def create_debian_control(deb_dir, project_name, version, build_number, cmake_flags): def find_test_executables(pkg_dir, release_dirs):
"""Create debian/control file for DEB package""" """Find test executables from release scope modules in build4/bin/
control_content = f"""Source: legatus-{project_name.lower()}
Section: devel Args:
pkg_dir: Package staging directory
release_dirs: List of release scope directory paths
Returns:
List of (executable_path, module_path) tuples for tests in release scope
"""
test_executables = []
build_bin_dir = os.path.join(pkg_dir, "build4", "bin")
if not os.path.exists(build_bin_dir):
warning(f"Build bin directory not found: {build_bin_dir}")
return test_executables
# Get all executable files in build4/bin/
for filename in os.listdir(build_bin_dir):
file_path = os.path.join(build_bin_dir, filename)
if not os.path.isfile(file_path) or not os.access(file_path, os.X_OK):
continue
# Skip non-test executables (common patterns)
if not ('test' in filename.lower() or 'Test' in filename):
continue
# Find which module this test belongs to by searching CMakeLists.txt
module_path = find_test_module_source(pkg_dir, filename, release_dirs)
if module_path:
test_executables.append((file_path, module_path))
debug(f" Found test executable: {filename} from {module_path}")
else:
debug(f" Skipping test executable (no module found): {filename}")
return test_executables
def find_test_module_source(pkg_dir, test_name, release_dirs):
"""Find the source module directory for a test executable
Args:
pkg_dir: Package staging directory
test_name: Name of the test executable
release_dirs: List of release scope directory paths
Returns:
Relative path to module directory, or None if not found
"""
# Search for CMakeLists.txt files that define this test
for rel_dir in release_dirs:
module_dir = os.path.join(pkg_dir, rel_dir)
if not os.path.exists(module_dir):
continue
for root, dirs, files in os.walk(module_dir):
# Skip build directories
dirs[:] = [d for d in dirs if not d.startswith('.') and d not in ['build', 'cmake-build-debug', 'cmake-build-release']]
if 'CMakeLists.txt' in files:
cmake_file = os.path.join(root, 'CMakeLists.txt')
try:
with open(cmake_file, 'r') as f:
content = f.read()
# Look for ADD_EXECUTABLE or ADD_TEST with this test name
if f'ADD_EXECUTABLE({test_name}' in content or f'ADD_TEST(NAME {test_name}' in content:
# Return the module directory relative to pkg_dir
return os.path.relpath(root, pkg_dir)
except Exception:
pass
return None
def find_test_sources(pkg_dir, release_dirs):
"""Find test source files in release scope modules
Args:
pkg_dir: Package staging directory
release_dirs: List of release scope directory paths
Returns:
List of relative paths to test source files
"""
test_sources = []
test_patterns = ['*test*.cpp', '*test*.h', '*Test*.cpp', '*Test*.h', 'test_*.cpp', 'test_*.h']
for rel_dir in release_dirs:
module_dir = os.path.join(pkg_dir, rel_dir)
if not os.path.exists(module_dir):
continue
for root, dirs, files in os.walk(module_dir):
# Skip build directories and hidden directories
dirs[:] = [d for d in dirs if not d.startswith('.') and d not in ['build', 'cmake-build-debug', 'cmake-build-release']]
for filename in files:
# Check if this is a test file
if 'test' in filename.lower() and (filename.endswith('.cpp') or filename.endswith('.h')):
file_path = os.path.join(root, filename)
rel_path = os.path.relpath(file_path, pkg_dir)
test_sources.append(rel_path)
debug(f" Found test source: {rel_path}")
return test_sources
def create_debian_control(deb_dir, package_name, version, build_number, description, depends="", section="devel"):
"""Create debian/control file for a package"""
control_content = f"""Source: {package_name}
Section: {section}
Priority: optional Priority: optional
Maintainer: Legatus Team <legatus@example.com> Maintainer: Legatus Team <team@legatus.example.com>
Build-Depends: debhelper (>= 13), cmake, ninja-build, qt6-base-dev, qt6-tools-dev, Build-Depends: debhelper (>= 13), cmake, qt6-base-dev, qt6-tools-dev
libboost-all-dev, libjson-glib-dev, libgeographic-dev Standards-Version: 4.6.0
Package: legatus-{project_name.lower()} Package: {package_name}
Architecture: any Architecture: any
Depends: ${{shlibs:Depends}}, ${{misc:Depends}} Depends: ${{shlibs:Depends}}, ${{misc:Depends}}{", " + depends if depends else ""}
Description: Legatus {project_name} LTS package Description: {description}
Legatus {project_name} component built with Qt6/C++23 Legatus Qt6 Release Package
Version: {version}-{build_number} This package is part of the Legatus Qt6 migration release.
Build flags: {cmake_flags}
""" """
control_file = os.path.join(deb_dir, "control") control_file = os.path.join(deb_dir, "control")
@@ -147,15 +297,16 @@ Description: Legatus {project_name} LTS package
info(f"Created {control_file}") info(f"Created {control_file}")
def create_debian_changelog(deb_dir, project_name, version, build_number): def create_debian_changelog(deb_dir, package_name, version, build_number):
"""Create debian/changelog file""" """Create debian/changelog file"""
timestamp = datetime.now().strftime("%a, %d %b %Y %H:%M:%S +0000") # Use +0000 as timezone offset (UTC) for reproducibility
changelog_content = f"""legatus-{project_name.lower()} ({version}-{build_number}) unstable; urgency=medium from email.utils import formatdate
timestamp = formatdate(localtime=True)
changelog_content = f"""{package_name} ({version}-{build_number}) unstable; urgency=medium
* Automated build for Ubuntu 26.04 * Qt6 Release Build
* Qt6/C++23 migration
-- Legatus Team <legatus@example.com> {timestamp} -- Legatus Team <team@legatus.example.com> {timestamp}
""" """
changelog_file = os.path.join(deb_dir, "changelog") changelog_file = os.path.join(deb_dir, "changelog")
@@ -164,21 +315,28 @@ def create_debian_changelog(deb_dir, project_name, version, build_number):
info(f"Created {changelog_file}") info(f"Created {changelog_file}")
def create_debian_rules(deb_dir): def create_debian_rules(deb_dir, cmake_flags="", skip_tests=False):
"""Create debian/rules file""" """Create debian/rules file"""
rules_content = """#!/usr/bin/make -f test_override = ""
if skip_tests:
test_override = """
override_dh_auto_test:
# Tests skipped for this package type
"""
rules_content = f"""#!/usr/bin/make -f
%: %:
dh $@ --buildsystem=cmake --builddirectory=build dh $@ --buildsystem=cmake
override_dh_auto_configure: override_dh_auto_configure:
dh_auto_configure -- -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr dh_auto_configure -- -DCMAKE_BUILD_TYPE=Release {cmake_flags}
override_dh_auto_build: override_dh_auto_build:
dh_auto_build --parallel dh_auto_build --parallel
override_dh_auto_install: override_dh_auto_install:
dh_auto_install dh_auto_install --destdir=debian/tmp
""" {test_override}"""
rules_file = os.path.join(deb_dir, "rules") rules_file = os.path.join(deb_dir, "rules")
with open(rules_file, "w") as f: with open(rules_file, "w") as f:
@@ -187,33 +345,153 @@ override_dh_auto_install:
info(f"Created {rules_file}") info(f"Created {rules_file}")
def build_deb(source_dir, output_dir, project_name, version, build_number, cmake_flags): def create_debian_rules_nobuild(deb_dir, skip_shlibdeps=False):
"""Build DEB package using debuild""" """Create debian/rules file for packages that don't need building (pre-built files only)
info("--------------------------------")
info(f"Building DEB package for {project_name} {version}-{build_number}")
info("--------------------------------")
# Create output directory Args:
os.makedirs(output_dir, exist_ok=True) deb_dir: Path to debian directory
skip_shlibdeps: If True, skip dh_shlibdeps (useful for test executables with external deps)
"""
rules_content = """#!/usr/bin/make -f
%:
dh $@
# Create temporary build directory override_dh_auto_build:
build_base = os.path.join(source_dir, "deb_build_tmp") # No build step needed - pre-built files only
remove_directory(build_base)
os.makedirs(build_base)
# Copy source to build directory override_dh_auto_configure:
pkg_name = f"legatus-{project_name.lower()}" # No configure step needed - pre-built files only
pkg_dir = os.path.join(build_base, f"{pkg_name}-{version}")
copy_src_dir_to_dest_dir(source_dir, pkg_dir) override_dh_auto_install:
dh_auto_install
"""
if skip_shlibdeps:
rules_content += """
override_dh_shlibdeps:
# Skip shlibdeps for test executables - Qt6 deps are external system packages
"""
rules_file = os.path.join(deb_dir, "rules")
with open(rules_file, "w") as f:
f.write(rules_content)
os.chmod(rules_file, stat.S_IREAD | stat.S_IWRITE | stat.S_IXUSR | stat.S_IRGRP | stat.S_IROTH | stat.S_IXGRP | stat.S_IXOTH)
info(f"Created {rules_file}")
rules_file = os.path.join(deb_dir, "rules")
with open(rules_file, "w") as f:
f.write(rules_content)
os.chmod(rules_file, stat.S_IREAD | stat.S_IWRITE | stat.S_IXUSR | stat.S_IRGRP | stat.S_IROTH | stat.S_IXGRP | stat.S_IXOTH)
info(f"Created {rules_file}")
def create_debian_install(deb_dir, install_lines):
"""Create debian/install file from list of file entries"""
install_file = os.path.join(deb_dir, "install")
with open(install_file, "w") as f:
for line in install_lines:
f.write(line + "\n")
info(f"Created {install_file} with {len(install_lines)} entries")
def create_manifest(output_dir, package_name, pkg_dir):
"""Create manifest file listing all package contents from actual DEB file"""
manifest_file = os.path.join(output_dir, "manifests", f"{package_name}.manifest")
os.makedirs(os.path.dirname(manifest_file), exist_ok=True)
# Find the DEB file for this package
deb_file = None
for f in os.listdir(output_dir):
if f.startswith(package_name) and f.endswith(".deb"):
deb_file = os.path.join(output_dir, f)
break
manifest_lines = []
if deb_file and os.path.exists(deb_file):
# Extract DEB contents using dpkg-deb
result = subprocess.run(
["dpkg-deb", "--contents", deb_file],
capture_output=True, text=True
)
if result.returncode == 0:
for line in result.stdout.strip().split("\n"):
if line.strip():
# dpkg-deb --contents output format: -rw-r--r-- root/root 1234 2024-01-01 00:00 ./path/to/file
# Extract the path (last field)
parts = line.split()
if len(parts) >= 6:
file_path = parts[-1]
# Remove leading ./ or /
if file_path.startswith("./"):
file_path = file_path[2:]
elif file_path.startswith("/"):
file_path = file_path[1:]
if file_path:
manifest_lines.append(file_path)
elif os.path.exists(pkg_dir):
# Fallback: scan directory if DEB not found
info(f"Warning: DEB file not found for {package_name}, scanning directory instead")
for root, dirs, files in os.walk(pkg_dir):
for file in files:
file_path = os.path.join(root, file)
rel_path = os.path.relpath(file_path, pkg_dir)
manifest_lines.append(rel_path)
manifest_lines.sort()
with open(manifest_file, "w") as f:
for line in manifest_lines:
f.write(line + "\n")
info(f"Created manifest: {manifest_file} ({len(manifest_lines)} entries)")
return manifest_lines
def validate_package_contents(package_name, manifest_lines, allowed_patterns, forbidden_patterns=None):
"""Validate package contents against allowlist/forbidden list"""
import fnmatch
errors = []
warnings = []
for file_path in manifest_lines:
# Check forbidden patterns
if forbidden_patterns:
for pattern in forbidden_patterns:
if fnmatch.fnmatch(file_path, pattern):
errors.append(f"FORBIDDEN file in {package_name}: {file_path} (matches {pattern})")
# Check if file matches any allowed pattern
allowed = False
if allowed_patterns:
for pattern in allowed_patterns:
if fnmatch.fnmatch(file_path, pattern):
allowed = True
break
if not allowed and allowed_patterns:
warnings.append(f"Unexpected file in {package_name}: {file_path}")
if errors:
for err in errors:
error(f"Validation error: {err}")
return False
if warnings:
for warn in warnings:
warning(f"Validation warning: {warn}")
return True
def build_single_deb(pkg_name, pkg_dir, output_dir, version, build_number, cmake_flags, skip_tests, install_lines, description, section="devel", depends=""):
"""Build a single DEB package"""
info(f"Building DEB package: {pkg_name}")
# Create debian directory
deb_dir = os.path.join(pkg_dir, "debian") deb_dir = os.path.join(pkg_dir, "debian")
os.makedirs(deb_dir) os.makedirs(deb_dir)
# Create debian control files create_debian_control(deb_dir, pkg_name, version, build_number, description, depends, section)
create_debian_control(deb_dir, project_name, version, build_number, cmake_flags) create_debian_changelog(deb_dir, pkg_name, version, build_number)
create_debian_changelog(deb_dir, project_name, version, build_number) create_debian_rules(deb_dir, cmake_flags, skip_tests)
create_debian_rules(deb_dir)
# Create debian/source/format # Create debian/source/format
source_dir_path = os.path.join(deb_dir, "source") source_dir_path = os.path.join(deb_dir, "source")
@@ -221,24 +499,29 @@ def build_deb(source_dir, output_dir, project_name, version, build_number, cmake
with open(os.path.join(source_dir_path, "format"), "w") as f: with open(os.path.join(source_dir_path, "format"), "w") as f:
f.write("3.0 (quilt)\n") f.write("3.0 (quilt)\n")
# Create debian/compat (for older debhelper compatibility) # Create debian/compat
with open(os.path.join(deb_dir, "compat"), "w") as f: with open(os.path.join(deb_dir, "compat"), "w") as f:
f.write("13\n") f.write("13\n")
# Create debian/install
if install_lines:
create_debian_install(deb_dir, install_lines)
# Build the package # Build the package
info("Running debuild...") info(f"Running debuild for {pkg_name}...")
os.chdir(pkg_dir) os.chdir(pkg_dir)
# Set environment for debuild
env = os.environ.copy() env = os.environ.copy()
env["DEB_BUILD_OPTIONS"] = "parallel=$(nproc)" env["DEB_BUILD_OPTIONS"] = "parallel=$(nproc)"
cmd = f"debuild -us -uc -b" cmd = f"debuild -us -uc -b"
debug(f"Executing: {cmd}") debug(f"Executing: {cmd}")
ret = subprocess.run(cmd, shell=True, env=env) ret = subprocess.run(cmd, shell=True, env=env, capture_output=True, text=True)
if ret.returncode != 0: if ret.returncode != 0:
die(f"debuild failed with code {ret.returncode}") print(ret.stdout)
print(ret.stderr)
die(f"debuild failed for {pkg_name} with code {ret.returncode}")
# Move generated packages to output directory # Move generated packages to output directory
parent_dir = os.path.dirname(pkg_dir) parent_dir = os.path.dirname(pkg_dir)
@@ -251,45 +534,382 @@ def build_deb(source_dir, output_dir, project_name, version, build_number, cmake
shutil.move(src, output_dir) shutil.move(src, output_dir)
info(f"Moved {f} to {output_dir}") info(f"Moved {f} to {output_dir}")
return True
def build_single_deb_nobuild(pkg_name, pkg_dir, output_dir, version, build_number, install_lines, description, section="devel", depends="", skip_shlibdeps=False):
"""Build a single DEB package without running cmake/make (pre-built files only)
Args:
skip_shlibdeps: If True, skip dh_shlibdeps (useful for test executables with external deps)
"""
info(f"Building DEB package (no-build): {pkg_name}")
deb_dir = os.path.join(pkg_dir, "debian")
os.makedirs(deb_dir)
create_debian_control(deb_dir, pkg_name, version, build_number, description, depends, section)
create_debian_changelog(deb_dir, pkg_name, version, build_number)
create_debian_rules_nobuild(deb_dir, skip_shlibdeps=skip_shlibdeps)
# Create debian/source/format
source_dir_path = os.path.join(deb_dir, "source")
os.makedirs(source_dir_path)
with open(os.path.join(source_dir_path, "format"), "w") as f:
f.write("3.0 (quilt)\n")
# Create debian/compat
with open(os.path.join(deb_dir, "compat"), "w") as f:
f.write("13\n")
# Create debian/install
if install_lines:
create_debian_install(deb_dir, install_lines)
# Build the package
info(f"Running debuild for {pkg_name}...")
os.chdir(pkg_dir)
env = os.environ.copy()
env["DEB_BUILD_OPTIONS"] = "parallel=$(nproc)"
cmd = f"debuild -us -uc -b"
debug(f"Executing: {cmd}")
ret = subprocess.run(cmd, shell=True, env=env, capture_output=True, text=True)
if ret.returncode != 0:
print(ret.stdout)
print(ret.stderr)
die(f"debuild failed for {pkg_name} with code {ret.returncode}")
# Move generated packages to output directory
parent_dir = os.path.dirname(pkg_dir)
for f in os.listdir(parent_dir):
if f.endswith(".deb") or f.endswith(".dsc") or f.endswith(".changes") or f.endswith(".buildinfo"):
src = os.path.join(parent_dir, f)
dst = os.path.join(output_dir, f)
if os.path.exists(dst):
os.remove(dst)
shutil.move(src, output_dir)
info(f"Moved {f} to {output_dir}")
return True
def build_all_debs(source_dir, output_dir, project_name, version, build_number, cmake_flags, release_scope=True):
"""Build all 5 required DEB packages - build once, then package"""
info("========================================")
info("Building 5 DEB packages for Qt6 Release")
info("========================================")
os.makedirs(output_dir, exist_ok=True)
os.makedirs(os.path.join(output_dir, "manifests"), exist_ok=True)
build_base = os.path.join(source_dir, "deb_build_tmp")
remove_directory(build_base)
os.makedirs(build_base)
# Release scope directories
release_dirs = ["TetraIF", "Z/src", "Lts/Common"]
# Source file extensions
source_extensions = ['.cpp', '.h', '.hpp', '.cc', '.cxx', '.hxx']
script_extensions = ['.py', '.sh', '.cmake', '.txt']
# ============================================
# STEP 1: Build once from full source tree
# ============================================
info("\n=== Step 1: Building from full source tree ===")
build_dir = os.path.join(source_dir, "build")
os.makedirs(build_dir, exist_ok=True)
os.chdir(build_dir)
# Configure
info("Running CMake configuration...")
cmake_cmd = f"cmake -DCMAKE_BUILD_TYPE=Release {cmake_flags} .."
ret = subprocess.run(cmake_cmd, shell=True, capture_output=True, text=True)
if ret.returncode != 0:
print(ret.stdout)
print(ret.stderr)
die("CMake configuration failed")
# Build
info("Running build...")
build_cmd = f"make -j$(nproc)"
ret = subprocess.run(build_cmd, shell=True, capture_output=True, text=True)
if ret.returncode != 0:
print(ret.stdout)
print(ret.stderr)
die("Build failed")
info("Build completed successfully")
# ============================================
# Package 1: qt6-release-runtime
# ============================================
info("\n=== Building qt6-release-runtime ===")
pkg1_name = f"qt6-release-runtime"
pkg1_dir = os.path.join(build_base, f"{pkg1_name}-{version}")
os.makedirs(pkg1_dir)
# Copy FULL source tree (needed for debian/rules cmake reference)
copy_src_dir_to_dest_dir(source_dir, pkg1_dir, release_scope=False)
# Install lines for runtime: binaries and libraries from release scope only
# CMake install uses component prefixes: ALL->(none), AAR/SV/HLA->SV/, WS->WS/
install_lines_1 = []
# Install from all possible component install paths
# ALL component: bin/, lib/
# AAR/SV/HLA components: SV/bin/, SV/lib/
# WS component: WS/bin/, WS/lib/
install_lines_1.append("usr/bin/* usr/bin/")
install_lines_1.append("usr/lib/*.so* usr/lib/")
install_lines_1.append("usr/SV/bin/* usr/bin/")
install_lines_1.append("usr/SV/lib/*.so* usr/lib/")
install_lines_1.append("usr/WS/bin/* usr/bin/")
install_lines_1.append("usr/WS/lib/*.so* usr/lib/")
build_single_deb(
pkg_name=pkg1_name,
pkg_dir=pkg1_dir,
output_dir=output_dir,
version=version,
build_number=build_number,
cmake_flags=cmake_flags,
skip_tests=True,
install_lines=install_lines_1,
description="Qt6 Release Runtime - binaries and libraries from release scope S",
section="libs"
)
# Create manifest
manifest_1 = create_manifest(output_dir, pkg1_name, pkg1_dir)
validate_package_contents(pkg1_name, manifest_1,
allowed_patterns=["usr/bin/*", "usr/lib/*", "usr/SV/*", "usr/WS/*", "debian/*"],
forbidden_patterns=["*.cpp", "*.h", "*.hpp", "tests/*", "jenkins/*", "Dockerfile*"])
# ============================================
# Package 2: qt6-release-sources
# ============================================
info("\n=== Building qt6-release-sources ===")
pkg2_name = f"qt6-release-sources"
pkg2_dir = os.path.join(build_base, f"{pkg2_name}-{version}")
os.makedirs(pkg2_dir)
# Copy FULL source tree (excluding DDS-related files)
copy_src_dir_to_dest_dir(source_dir, pkg2_dir, release_scope=False, file_filter=is_dds_file)
# Install lines for sources: only from release scope directories
install_lines_2 = []
for rel_dir in release_dirs:
src_dir = os.path.join(pkg2_dir, rel_dir)
if os.path.exists(src_dir):
for root, dirs, files in os.walk(src_dir):
dirs[:] = [d for d in dirs if not d.startswith('.')]
for file in files:
ext = os.path.splitext(file)[1].lower()
if ext in source_extensions or file == "CMakeLists.txt":
rel_path = os.path.relpath(os.path.join(root, file), pkg2_dir)
dest_dir = os.path.join("usr/src", os.path.dirname(rel_path))
install_lines_2.append(f"{rel_path} {dest_dir}/")
# Use no-build rules for source package
build_single_deb_nobuild(
pkg_name=pkg2_name,
pkg_dir=pkg2_dir,
output_dir=output_dir,
version=version,
build_number=build_number,
install_lines=install_lines_2,
description="Qt6 Release Sources - C++ source files from release scope S",
section="devel"
)
manifest_2 = create_manifest(output_dir, pkg2_name, pkg2_dir)
validate_package_contents(pkg2_name, manifest_2,
allowed_patterns=["usr/src/**/*.cpp", "usr/src/**/*.h", "usr/src/**/CMakeLists.txt", "debian/*"],
forbidden_patterns=["build/*", "tests/*", "jenkins/*"])
# ============================================
# Package 3: qt6-release-ctest-runtime
# ============================================
info("\n=== Building qt6-release-ctest-runtime ===")
pkg3_name = f"qt6-release-ctest-runtime"
pkg3_dir = os.path.join(build_base, f"{pkg3_name}-{version}")
os.makedirs(pkg3_dir)
# Copy FULL source tree
copy_src_dir_to_dest_dir(source_dir, pkg3_dir, release_scope=False)
# Install lines for CTest runtime: test executables from release scope only
install_lines_3 = []
# Find test executables in build4/bin/ and map to release scope modules
test_executables = find_test_executables(pkg3_dir, release_dirs)
for exe_path, module_path in test_executables:
rel_path = os.path.relpath(exe_path, pkg3_dir)
dest_dir = "usr/lib/ctest"
install_lines_3.append(f"{rel_path} {dest_dir}/")
info(f" Including test executable: {os.path.basename(exe_path)} from {module_path}")
# Also include CTest configuration files
ctest_files = [
"build4/CTestTestfile.cmake",
"build4/DartConfiguration.tcl",
"build4/Testing/TAG",
]
for ctest_file in ctest_files:
if os.path.exists(os.path.join(pkg3_dir, ctest_file)):
install_lines_3.append(f"{ctest_file} usr/lib/ctest/")
build_single_deb_nobuild(
pkg_name=pkg3_name,
pkg_dir=pkg3_dir,
output_dir=output_dir,
version=version,
build_number=build_number,
install_lines=install_lines_3,
description="Qt6 Release CTest Runtime - executable test programs from release scope",
section="devel",
skip_shlibdeps=True
)
manifest_3 = create_manifest(output_dir, pkg3_name, pkg3_dir)
validate_package_contents(pkg3_name, manifest_3,
allowed_patterns=["usr/lib/ctest/*", "debian/*"],
forbidden_patterns=["*.cpp", "*.h", "jenkins/*"])
# ============================================
# Package 4: qt6-release-ctest-sources
# ============================================
info("\n=== Building qt6-release-ctest-sources ===")
pkg4_name = f"qt6-release-ctest-sources"
pkg4_dir = os.path.join(build_base, f"{pkg4_name}-{version}")
os.makedirs(pkg4_dir)
# Copy FULL source tree
copy_src_dir_to_dest_dir(source_dir, pkg4_dir, release_scope=False)
# Install lines for test sources from release scope only
install_lines_4 = []
# Find test source files in release scope modules
test_sources = find_test_sources(pkg4_dir, release_dirs)
for rel_path in test_sources:
dest_dir = os.path.dirname(rel_path)
install_lines_4.append(f"{rel_path} {dest_dir}/")
build_single_deb_nobuild(
pkg_name=pkg4_name,
pkg_dir=pkg4_dir,
output_dir=output_dir,
version=version,
build_number=build_number,
install_lines=install_lines_4,
description="Qt6 Release CTest Sources - test source files from release scope",
section="devel"
)
manifest_4 = create_manifest(output_dir, pkg4_name, pkg4_dir)
validate_package_contents(pkg4_name, manifest_4,
allowed_patterns=["usr/src/**/tests/*", "debian/*"],
forbidden_patterns=["build/*", "jenkins/*"])
# ============================================
# Package 5: qt6-release-build-pipeline
# ============================================
info("\n=== Building qt6-release-build-pipeline ===")
pkg5_name = f"qt6-release-build-pipeline"
pkg5_dir = os.path.join(build_base, f"{pkg5_name}-{version}")
os.makedirs(pkg5_dir)
# Copy FULL source tree
copy_src_dir_to_dest_dir(source_dir, pkg5_dir, release_scope=False)
# Install lines for pipeline files only
install_lines_5 = []
pipeline_files = [
"new_docker_build.sh",
"Dockerfile",
"Dockerfile.ubuntu26",
"jenkins-ci-rme/release-16-10-2026.sh",
"rpmbuild/make_rpm_ubuntu26.py",
"CMakeLists.txt",
"cmake/FindGeographicLib.cmake",
"cmake/FindRTI.cmake",
"cmake/FindZLIB.cmake",
]
for pipeline_file in pipeline_files:
src_file = os.path.join(pkg5_dir, pipeline_file)
if os.path.exists(src_file):
dest_dir = os.path.join("usr/src/pipeline", os.path.dirname(pipeline_file))
install_lines_5.append(f"{pipeline_file} {dest_dir}/")
info(f" Added pipeline file: {pipeline_file}")
build_single_deb_nobuild(
pkg_name=pkg5_name,
pkg_dir=pkg5_dir,
output_dir=output_dir,
version=version,
build_number=build_number,
install_lines=install_lines_5,
description="Qt6 Release Build Pipeline - Docker, Jenkins, CMake, and packaging scripts",
section="devel"
)
manifest_5 = create_manifest(output_dir, pkg5_name, pkg5_dir)
validate_package_contents(pkg5_name, manifest_5,
allowed_patterns=["usr/src/pipeline/*", "debian/*"],
forbidden_patterns=["*.cpp", "*.h", "tests/*"])
# Cleanup # Cleanup
os.chdir(source_dir) os.chdir(source_dir)
remove_directory(build_base) remove_directory(build_base)
info("\n========================================")
info(f"DEB package build completed: {output_dir}") info(f"DEB package build completed: {output_dir}")
info("Generated packages:")
for f in os.listdir(output_dir):
if f.endswith(".deb"):
info(f" - {f}")
info("========================================")
def main(): def main():
parser = argparse.ArgumentParser(description="Build DEB packages for Legatus LTS") parser = argparse.ArgumentParser(description="Build DEB packages for Legatus Qt6 Release")
parser.add_argument("-c", "--config", required=True, help="Config type (LTS)") parser.add_argument("-c", "--config", required=True, help="Config type (LTS)")
parser.add_argument("--project_name", required=True, help="Project name (qatar|ahtc|guez)") parser.add_argument("--project_name", required=True, help="Project name (qatar|ahtc|guez)")
parser.add_argument("--version", required=True, help="Version (Major.minor)") parser.add_argument("--version", required=True, help="Version (Major.minor)")
parser.add_argument("--output_dir", required=True, help="Output directory for packages") parser.add_argument("--output_dir", required=True, help="Output directory for packages")
parser.add_argument("--source_dir", required=True, help="Source directory path")
parser.add_argument("--cmake_build_flags", default="", help="CMake build flags") parser.add_argument("--cmake_build_flags", default="", help="CMake build flags")
parser.add_argument("--build_number", default="1", help="Build number") parser.add_argument("--build_number", default="1", help="Build number")
parser.add_argument("--release_scope", action="store_true", help="Only include release scope files")
args = parser.parse_args() args = parser.parse_args()
info(f"Building {args.config} package for {args.project_name} v{args.version}") info(f"Building {args.config} package for {args.project_name} v{args.version}")
info(f"Output directory: {args.output_dir}") info(f"Output directory: {args.output_dir}")
info(f"Source directory: {args.source_dir}")
info(f"CMake flags: {args.cmake_build_flags}") info(f"CMake flags: {args.cmake_build_flags}")
info(f"Release scope: {args.release_scope}")
# Determine source directory (parent of rpmbuild folder) source_dir = args.source_dir
script_dir = os.path.dirname(os.path.abspath(__file__))
source_dir = os.path.dirname(script_dir)
info(f"Source directory: {source_dir}") info(f"Source directory: {source_dir}")
build_deb( build_all_debs(
source_dir=source_dir, source_dir=source_dir,
output_dir=args.output_dir, output_dir=args.output_dir,
project_name=args.project_name, project_name=args.project_name,
version=args.version, version=args.version,
build_number=args.build_number, build_number=args.build_number,
cmake_flags=args.cmake_build_flags cmake_flags=args.cmake_build_flags,
release_scope=args.release_scope
) )
info("Build completed successfully!")
if __name__ == "__main__": if __name__ == "__main__":
main() main()
+818
View File
@@ -0,0 +1,818 @@
#!/usr/bin/env python3
"""
Build DEB packages for Qt6 Release 2
This script creates 5 DEB packages from the Release 2 scope:
- qt6-release-runtime: Binaries and libraries
- qt6-release-sources: C++ source files
- qt6-release-ctest-runtime: Test executables
- qt6-release-ctest-sources: Test source files
- qt6-release-build-pipeline: Build scripts and configuration
Release 2 scope (S set - 39 modules):
- Lts/Common (15 modules)
- Lts/DDS (10 modules)
- Lts/Services (6 modules in 3 directories)
- Lts/Proxies/TrainingAreaBase (1 module)
- Lts/Tools (7 AP9 tools)
"""
import os
import sys
import shutil
import subprocess
import argparse
from pathlib import Path
# Import helpers from rpmbuild
sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'rpmbuild'))
from helpers import info, debug, warning as warn, die, removeDirectory
def copy_src_dir_to_dest_dir(src_dir, dest_dir, release_scope=False, release_dirs=None, file_filter=None):
"""Copy source directory to destination with optional filtering
Args:
src_dir: Source directory
dest_dir: Destination directory
release_scope: If True, only copy release scope directories
release_dirs: List of release scope directories (Release 2 specific)
file_filter: Optional function to filter files (filepath) -> bool, returns True to EXCLUDE
"""
os.makedirs(dest_dir, exist_ok=True)
# Release 2 scope directories (S set)
if release_dirs is None:
release_dirs = [
"Lts/Common",
"Lts/DDS",
"Lts/Services",
"Lts/Proxies/TrainingAreaBase",
"Lts/Tools"
]
if release_scope:
info(f"Copying release scope directories: {release_dirs}")
# Also copy root-level build files needed for CMake
root_build_files = ['CMakeLists.txt', 'CTestCustom.ctest']
root_build_dirs = ['cmake']
for bf in root_build_files:
src_file = os.path.join(src_dir, bf)
if os.path.exists(src_file):
debug(f" Copying root file {bf}...")
shutil.copy2(src_file, os.path.join(dest_dir, bf))
for bd in root_build_dirs:
src_bdir = os.path.join(src_dir, bd)
dest_bdir = os.path.join(dest_dir, bd)
if os.path.exists(src_bdir):
debug(f" Copying root directory {bd}...")
shutil.copytree(src_bdir, dest_bdir, dirs_exist_ok=True)
for rel_dir in release_dirs:
src_subdir = os.path.join(src_dir, rel_dir)
dest_subdir = os.path.join(dest_dir, rel_dir)
if os.path.exists(src_subdir):
debug(f" Copying {rel_dir}...")
for root, dirs, files in os.walk(src_subdir):
# Skip build directories, hidden directories, deb_build_tmp
dirs[:] = [d for d in dirs if not d.startswith('.')
and d not in ['build', 'cmake-build-debug', 'cmake-build-release', 'deb_build_tmp']
and (file_filter is None or not file_filter(os.path.join(root, d)))]
for file in files:
src_file = os.path.join(root, file)
rel_path = os.path.relpath(src_file, src_subdir)
dest_file = os.path.join(dest_subdir, rel_path)
# Apply file filter if provided (skip file if filter returns True)
if file_filter and file_filter(src_file):
continue
# Skip broken symlinks
if os.path.islink(src_file) and not os.path.exists(src_file):
debug(f"Skipping broken symlink: {src_file}")
continue
os.makedirs(os.path.dirname(dest_file), exist_ok=True)
shutil.copy2(src_file, dest_file)
else:
# Copy entire source tree
for root, dirs, files in os.walk(src_dir):
# Skip build directories, hidden directories, deb_build_tmp
dirs[:] = [d for d in dirs if not d.startswith('.')
and d not in ['build', 'cmake-build-debug', 'cmake-build-release', 'deb_build_tmp', 'deb_packages']]
for file in files:
src_file = os.path.join(root, file)
rel_path = os.path.relpath(src_file, src_dir)
dest_file = os.path.join(dest_dir, rel_path)
# Apply file filter if provided
if file_filter and file_filter(src_file):
continue
# Skip broken symlinks
if os.path.islink(src_file) and not os.path.exists(src_file):
debug(f"Skipping broken symlink: {src_file}")
continue
os.makedirs(os.path.dirname(dest_file), exist_ok=True)
shutil.copy2(src_file, dest_file)
def is_dds_file(filepath):
"""Check if a file or directory path contains DDS-related content that should be excluded
For Release 2, we INCLUDE DDS files in the sources package since DDS is part of our scope.
This function is kept for compatibility but returns False for all files.
Args:
filepath: Path to check
Returns:
False (include all files for Release 2)
"""
return False
def create_manifest(output_dir, pkg_name, pkg_dir):
"""Create a manifest file listing all files in the package"""
manifest_path = os.path.join(output_dir, "manifests", f"{pkg_name}.txt")
os.makedirs(os.path.dirname(manifest_path), exist_ok=True)
files = []
for root, dirs, filenames in os.walk(pkg_dir):
rel_root = os.path.relpath(root, pkg_dir)
for filename in filenames:
full_path = os.path.join(rel_root, filename) if rel_root != '.' else filename
files.append(full_path)
with open(manifest_path, 'w') as f:
f.write('\n'.join(sorted(files)))
return manifest_path
def validate_package_contents(pkg_name, manifest_path, allowed_patterns=None, forbidden_patterns=None):
"""Validate package contents against allowlist and denylist"""
info(f"Validating {pkg_name} contents...")
with open(manifest_path, 'r') as f:
files = [line.strip() for line in f if line.strip()]
import fnmatch
if forbidden_patterns:
for pattern in forbidden_patterns:
for file in files:
if fnmatch.fnmatch(file, pattern):
warn(f" Forbidden file found: {file} (matches {pattern})")
info(f" Found {len(files)} files in {pkg_name}")
return True
def create_debian_control(deb_dir, package_name, version, build_number, description, depends="", section="devel"):
"""Create debian/control file for a package (binary package format for dpkg-deb)"""
control_path = os.path.join(deb_dir, "control")
# Runtime dependencies for Release 2
runtime_deps = "" # dpkg-deb does not support debhelper variables
if depends:
runtime_deps += f", {depends}"
# Binary package control format (required by dpkg-deb)
control_content = f"""Package: {package_name}
Version: {version}-{build_number}
Section: {section}
Priority: optional
Architecture: amd64
Maintainer: Qt6 Release Team <qt6-release@example.com>
Depends: {runtime_deps}
Description: Qt6 Release 2 - {description}
This package contains {description.lower()} for the Qt6 Release 2 delivery.
.
Release 2 scope includes:
- AP3: LtsUtils, LtsWidgets, LtsSession, LtsLicence, LtsPluginManager
- AP4: Core, DDSConnector, Routing, QosProfile, RTI DDS
- AP9: DdsView, LogViewer, SqlBrowser, 3 generators, lupdate
"""
with open(control_path, 'w') as f:
f.write(control_content)
def create_debian_changelog(deb_dir, package_name, version, build_number):
"""Create debian/changelog file"""
changelog_path = os.path.join(deb_dir, "changelog")
changelog_content = f"""{package_name} ({version}-{build_number}) unstable; urgency=medium
* Qt6 Release 2 delivery
* Scope: AP3, AP4, AP9 modules (39 modules total)
* Includes: Common, DDS, Services, Proxies/TrainingAreaBase, Tools
-- Qt6 Release Team <qt6-release@example.com> {subprocess.check_output(['date', '-R']).decode().strip()}
"""
with open(changelog_path, 'w') as f:
f.write(changelog_content)
def create_debian_rules(deb_dir, cmake_flags="", skip_tests=False, use_prebuilt=False):
"""Create debian/rules file"""
rules_path = os.path.join(deb_dir, "rules")
if use_prebuilt:
# No-build rules - just package existing files
rules_content = """#!/usr/bin/make -f
# Debian rules file for Qt6 Release 2 (pre-built artifacts)
%:
\tdh $@
override_dh_auto_configure:
\t# No configuration needed - using pre-built artifacts
override_dh_auto_build:
\t# No build needed - using pre-built artifacts
override_dh_auto_install:
\t# Files already in place via debian/install
"""
else:
test_flag = "-DENABLE_TESTS=OFF" if skip_tests else "-DENABLE_TESTS=ON"
rules_content = f"""#!/usr/bin/make -f
# Debian rules file for Qt6 Release 2
%:
\tdh $@
override_dh_auto_configure:
\tdh_auto_configure -- -DCMAKE_BUILD_TYPE=Release {cmake_flags} {test_flag}
override_dh_auto_build:
\tdh_auto_build
override_dh_auto_test:
\t# Skip tests during package build (run separately with ctest)
\ttrue
override_dh_auto_install:
\tdh_auto_install --destdir=debian/tmp
"""
with open(rules_path, 'w') as f:
f.write(rules_content)
os.chmod(rules_path, 0o755)
def create_debian_rules_nobuild(deb_dir, skip_shlibdeps=False):
"""Create debian/rules file for packages that don't need building (pre-built files only)"""
rules_path = os.path.join(deb_dir, "rules")
shlibdeps_override = ""
if skip_shlibdeps:
shlibdeps_override = """
override_dh_shlibdeps:
\t# Skip shared library dependencies (test executables may have external deps)
\ttrue
"""
rules_content = f"""#!/usr/bin/make -f
# Debian rules file for Qt6 Release 2 (no-build package){shlibdeps_override}
%:
\tdh $@
override_dh_auto_configure:
\t# No configuration needed (pre-built files)
\ttrue
override_dh_auto_build:
\t# No build needed (pre-built files)
\ttrue
override_dh_auto_install:
\t# Files installed via debian/install
\ttrue
override_dh_auto_test:
\t# No tests in this package
\ttrue
"""
with open(rules_path, 'w') as f:
f.write(rules_content)
os.chmod(rules_path, 0o755)
def create_debian_install(deb_dir, install_lines):
"""Create debian/install file from list of file entries"""
install_path = os.path.join(deb_dir, "install")
with open(install_path, 'w') as f:
f.write('\n'.join(install_lines))
def find_test_executables(pkg_dir, release_dirs):
"""Find test executables in build directory"""
test_executables = []
# Look for test executables in build directories
build_dirs = ['build/bin', 'build4/bin', 'build-release/bin']
for build_dir in build_dirs:
bin_path = os.path.join(pkg_dir, build_dir)
if os.path.exists(bin_path):
for f in os.listdir(bin_path):
if f.startswith('test') or f.endswith('_test'):
exe_path = os.path.join(bin_path, f)
if os.path.isfile(exe_path) and os.access(exe_path, os.X_OK):
# Try to determine which module this test belongs to
module_path = "unknown"
for rel_dir in release_dirs:
if rel_dir.lower() in f.lower():
module_path = rel_dir
break
test_executables.append((exe_path, module_path))
return test_executables
def find_test_sources(pkg_dir, release_dirs):
"""Find test source files in release scope modules"""
test_sources = []
for rel_dir in release_dirs:
module_path = os.path.join(pkg_dir, rel_dir)
if os.path.exists(module_path):
for root, dirs, files in os.walk(module_path):
dirs[:] = [d for d in dirs if not d.startswith('.')]
for file in files:
if 'test' in file.lower() and (file.endswith('.cpp') or file.endswith('.h')):
rel_path = os.path.relpath(os.path.join(root, file), pkg_dir)
test_sources.append(rel_path)
return test_sources
def build_single_deb(pkg_name, pkg_dir, output_dir, version, build_number, cmake_flags, skip_tests, install_lines, description, section="devel", depends=""):
"""Build a single DEB package"""
info(f"Building DEB package: {pkg_name}")
deb_dir = os.path.join(pkg_dir, "debian")
os.makedirs(deb_dir)
create_debian_control(deb_dir, pkg_name, version, build_number, description, depends, section)
create_debian_changelog(deb_dir, pkg_name, version, build_number)
create_debian_rules(deb_dir, cmake_flags, skip_tests)
# Create debian/source/format
source_dir_path = os.path.join(deb_dir, "source")
os.makedirs(source_dir_path)
with open(os.path.join(source_dir_path, "format"), "w") as f:
f.write("3.0 (quilt)\n")
# Create debian/compat
with open(os.path.join(deb_dir, "compat"), "w") as f:
f.write("13\n")
# Create debian/install
if install_lines:
create_debian_install(deb_dir, install_lines)
# Build the package
info(f"Running debuild for {pkg_name}...")
os.chdir(pkg_dir)
env = os.environ.copy()
env["DEB_BUILD_OPTIONS"] = "parallel=$(nproc)"
cmd = f"debuild -us -uc -b"
debug(f"Executing: {cmd}")
ret = subprocess.run(cmd, shell=True, env=env, capture_output=True, text=True)
if ret.returncode != 0:
print(ret.stdout)
print(ret.stderr)
die(f"debuild failed for {pkg_name} with code {ret.returncode}")
# Move generated packages to output directory
parent_dir = os.path.dirname(pkg_dir)
for f in os.listdir(parent_dir):
if f.endswith(".deb") or f.endswith(".dsc") or f.endswith(".changes") or f.endswith(".buildinfo"):
src = os.path.join(parent_dir, f)
dst = os.path.join(output_dir, f)
if os.path.exists(dst):
os.remove(dst)
shutil.move(src, output_dir)
info(f"Moved {f} to {output_dir}")
return True
def build_single_deb_nobuild(pkg_name, pkg_dir, output_dir, version, build_number, install_lines, description, section="devel", depends="", skip_shlibdeps=False):
"""Build a single DEB package without running cmake/make (pre-built files only)"""
info(f"Building DEB package (no-build): {pkg_name}")
deb_dir = os.path.join(pkg_dir, "DEBIAN")
os.makedirs(deb_dir)
create_debian_control(deb_dir, pkg_name, version, build_number, description, depends, section)
create_debian_changelog(deb_dir, pkg_name, version, build_number)
create_debian_rules_nobuild(deb_dir, skip_shlibdeps=skip_shlibdeps)
# Create DEBIAN/source/format
source_dir_path = os.path.join(deb_dir, "source")
os.makedirs(source_dir_path)
with open(os.path.join(source_dir_path, "format"), "w") as f:
f.write("3.0 (quilt)\n")
# Create DEBIAN/compat
with open(os.path.join(deb_dir, "compat"), "w") as f:
f.write("13\n")
# Create DEBIAN/install
if install_lines:
create_debian_install(deb_dir, install_lines)
# Build the package using dpkg-deb directly
info(f"Building DEB package for {pkg_name} using dpkg-deb...")
original_dir = os.getcwd()
os.chdir(pkg_dir)
# dpkg-deb --build creates the .deb file directly
deb_file = f"{pkg_name}_{version}-{build_number}_amd64.deb"
cmd = f"dpkg-deb --build . ../{deb_file}"
debug(f"Executing: {cmd}")
ret = subprocess.run(cmd, shell=True, capture_output=True, text=True)
# Change back to original directory
os.chdir(original_dir)
if ret.returncode != 0:
print(ret.stdout)
print(ret.stderr)
die(f"dpkg-deb failed for {pkg_name} with code {ret.returncode}")
# Move the .deb file to output directory
parent_dir = os.path.dirname(pkg_dir)
deb_path = os.path.join(parent_dir, deb_file)
if os.path.exists(deb_path):
dst = os.path.join(output_dir, deb_file)
if os.path.exists(dst):
os.remove(dst)
shutil.move(deb_path, output_dir)
info(f"Moved {deb_file} to {output_dir}")
# Clean up build directory to save disk space
info(f"Cleaning up build directory: {pkg_dir}")
try:
shutil.rmtree(pkg_dir, ignore_errors=True)
except Exception as e:
warning(f"Failed to clean up {pkg_dir}: {e}")
return True
def build_all_debs(source_dir, output_dir, project_name, version, build_number, cmake_flags, release_scope=True):
"""Build all 5 DEB packages for Release 2 using pre-built artifacts"""
info("========================================")
info("Building 5 DEB packages for Qt6 Release 2")
info("========================================")
os.makedirs(output_dir, exist_ok=True)
os.makedirs(os.path.join(output_dir, "manifests"), exist_ok=True)
build_base = os.path.join("/tmp", "qt6_deb_build_" + str(os.getpid()))
# removeDirectory(build_base) # Skip removal when using /tmp
os.makedirs(build_base)
# Release 2 scope directories (S set - 39 modules)
release_dirs = [
"Lts/Common",
"Lts/DDS",
"Lts/Services",
"Lts/Proxies/TrainingAreaBase",
"Lts/Tools"
]
# Source file extensions
source_extensions = ['.cpp', '.h', '.hpp', '.cc', '.cxx', '.hxx']
script_extensions = ['.py', '.sh', '.cmake', '.txt']
# ============================================
# STEP 1: Use pre-built artifacts from build4/
# ============================================
info("\n=== Step 1: Using pre-built artifacts from build4/ ===")
build4_dir = os.path.join(source_dir, "build4")
if not os.path.exists(build4_dir):
die(f"Pre-built artifacts not found in {build4_dir}. Run build first.")
# Verify libraries exist
lib_dir = os.path.join(build4_dir, "lib")
if not os.path.exists(lib_dir):
die(f"Library directory not found: {lib_dir}")
lib_count = len([f for f in os.listdir(lib_dir) if f.endswith('.so')])
info(f"Found {lib_count} shared libraries in build4/lib/")
# Verify tool executables exist
tools_build_dir = os.path.join(build4_dir, "Lts", "Tools")
if os.path.exists(tools_build_dir):
info(f"AP9 tools build directory exists: {tools_build_dir}")
bin_dir = os.path.join(build4_dir, "bin")
if os.path.exists(bin_dir):
tool_exes = [f for f in os.listdir(bin_dir) if f.startswith('t') or 'generator' in f.lower()]
if tool_exes:
info(f"Found tool executables in build4/bin/: {', '.join(tool_exes)}")
info("Pre-built artifacts verified successfully")
# ============================================
# Package 1: qt6-release-runtime
# ============================================
info("\n=== Building qt6-release-runtime ===")
pkg1_name = f"qt6-release-runtime"
pkg1_dir = os.path.join(build_base, f"{pkg1_name}-{version}")
os.makedirs(pkg1_dir)
# Create package structure
pkg1_lib_dir = os.path.join(pkg1_dir, "usr", "lib")
pkg1_bin_dir = os.path.join(pkg1_dir, "usr", "bin")
os.makedirs(pkg1_lib_dir, exist_ok=True)
os.makedirs(pkg1_bin_dir, exist_ok=True)
# Copy pre-built libraries from build4/lib
info("Copying libraries from build4/lib/...")
for f in os.listdir(lib_dir):
if f.endswith('.so') or f.endswith('.so.1') or f.startswith('lib'):
src = os.path.join(lib_dir, f)
dst = os.path.join(pkg1_lib_dir, f)
if os.path.isfile(src):
shutil.copy2(src, dst)
# Copy tool executables from build4/bin
info("Copying executables from build4/bin/...")
if os.path.exists(bin_dir):
for f in os.listdir(bin_dir):
fpath = os.path.join(bin_dir, f)
if os.path.isfile(fpath) and (f.startswith('t') or 'generator' in f.lower() or f in ['ddsview', 'sqlbrowser', 'LogViewer', 'topicdiscover']):
shutil.copy2(fpath, os.path.join(pkg1_bin_dir, f))
# Copy tool executables from build4/Lts/Tools subdirectories
if os.path.exists(tools_build_dir):
for tool_subdir in os.listdir(tools_build_dir):
tool_bin_path = os.path.join(tools_build_dir, tool_subdir, "bin")
if os.path.exists(tool_bin_path):
for exe in os.listdir(tool_bin_path):
if exe and not exe.startswith('.'):
src = os.path.join(tool_bin_path, exe)
if os.path.isfile(src):
shutil.copy2(src, os.path.join(pkg1_bin_dir, exe))
# Install lines for runtime
install_lines_1 = []
install_lines_1.append("usr/lib/* usr/lib/")
install_lines_1.append("usr/bin/* usr/bin/")
build_single_deb_nobuild(
pkg_name=pkg1_name,
pkg_dir=pkg1_dir,
output_dir=output_dir,
version=version,
build_number=build_number,
install_lines=install_lines_1,
description="Runtime binaries and libraries for Release 2 scope (AP3, AP4, AP9)",
section="libs"
)
# Create manifest
manifest_1 = create_manifest(output_dir, pkg1_name, pkg1_dir)
validate_package_contents(pkg1_name, manifest_1,
allowed_patterns=["usr/bin/*", "usr/lib/*", "usr/SV/*", "usr/WS/*", "debian/*"],
forbidden_patterns=["*.cpp", "*.h", "*.hpp", "tests/*", "jenkins/*", "Dockerfile*"])
# ============================================
# Package 2: qt6-release-sources
# ============================================
info("\n=== Building qt6-release-sources ===")
pkg2_name = f"qt6-release-sources"
pkg2_dir = os.path.join(build_base, f"{pkg2_name}-{version}")
os.makedirs(pkg2_dir)
# Copy FULL source tree (Release 2 includes DDS files)
copy_src_dir_to_dest_dir(source_dir, pkg2_dir, release_scope=False, release_dirs=release_dirs)
# Install lines for sources: only from release scope directories
install_lines_2 = []
for rel_dir in release_dirs:
src_dir = os.path.join(pkg2_dir, rel_dir)
if os.path.exists(src_dir):
for root, dirs, files in os.walk(src_dir):
dirs[:] = [d for d in dirs if not d.startswith('.')]
for file in files:
ext = os.path.splitext(file)[1].lower()
if ext in source_extensions or file == "CMakeLists.txt":
rel_path = os.path.relpath(os.path.join(root, file), pkg2_dir)
dest_dir = os.path.join("usr/src", os.path.dirname(rel_path))
install_lines_2.append(f"{rel_path} {dest_dir}/")
# Use no-build rules for source package
build_single_deb_nobuild(
pkg_name=pkg2_name,
pkg_dir=pkg2_dir,
output_dir=output_dir,
version=version,
build_number=build_number,
install_lines=install_lines_2,
description="C++ source files for Release 2 scope (AP3, AP4, AP9)",
section="devel"
)
manifest_2 = create_manifest(output_dir, pkg2_name, pkg2_dir)
validate_package_contents(pkg2_name, manifest_2,
allowed_patterns=["usr/src/**/*.cpp", "usr/src/**/*.h", "usr/src/**/CMakeLists.txt", "debian/*"],
forbidden_patterns=["build/*", "tests/*", "jenkins/*"])
# ============================================
# Package 3: qt6-release-ctest-runtime
# ============================================
info("\n=== Building qt6-release-ctest-runtime ===")
pkg3_name = f"qt6-release-ctest-runtime"
pkg3_dir = os.path.join(build_base, f"{pkg3_name}-{version}")
os.makedirs(pkg3_dir)
# Copy FULL source tree
copy_src_dir_to_dest_dir(source_dir, pkg3_dir, release_scope=False, release_dirs=release_dirs)
# Install lines for CTest runtime: test executables from release scope only
install_lines_3 = []
# Find test executables in build directory
test_executables = find_test_executables(pkg3_dir, release_dirs)
for exe_path, module_path in test_executables:
rel_path = os.path.relpath(exe_path, pkg3_dir)
dest_dir = "usr/lib/ctest"
install_lines_3.append(f"{rel_path} {dest_dir}/")
info(f" Including test executable: {os.path.basename(exe_path)} from {module_path}")
# Also include CTest configuration files
ctest_files = [
"build/CTestTestfile.cmake",
"build/DartConfiguration.tcl",
"build/Testing/TAG",
]
for ctest_file in ctest_files:
if os.path.exists(os.path.join(pkg3_dir, ctest_file)):
install_lines_3.append(f"{ctest_file} usr/lib/ctest/")
build_single_deb_nobuild(
pkg_name=pkg3_name,
pkg_dir=pkg3_dir,
output_dir=output_dir,
version=version,
build_number=build_number,
install_lines=install_lines_3,
description="Test executables for Release 2 scope",
section="devel",
skip_shlibdeps=True
)
manifest_3 = create_manifest(output_dir, pkg3_name, pkg3_dir)
validate_package_contents(pkg3_name, manifest_3,
allowed_patterns=["usr/lib/ctest/*", "debian/*"],
forbidden_patterns=["*.cpp", "*.h", "jenkins/*"])
# ============================================
# Package 4: qt6-release-ctest-sources
# ============================================
info("\n=== Building qt6-release-ctest-sources ===")
pkg4_name = f"qt6-release-ctest-sources"
pkg4_dir = os.path.join(build_base, f"{pkg4_name}-{version}")
os.makedirs(pkg4_dir)
# Copy FULL source tree
copy_src_dir_to_dest_dir(source_dir, pkg4_dir, release_scope=False, release_dirs=release_dirs)
# Install lines for test sources from release scope only
install_lines_4 = []
# Find test source files in release scope modules
test_sources = find_test_sources(pkg4_dir, release_dirs)
for rel_path in test_sources:
dest_dir = os.path.dirname(rel_path)
install_lines_4.append(f"{rel_path} usr/src/{dest_dir}/")
info(f" Including test source: {rel_path}")
build_single_deb_nobuild(
pkg_name=pkg4_name,
pkg_dir=pkg4_dir,
output_dir=output_dir,
version=version,
build_number=build_number,
install_lines=install_lines_4,
description="Test source files for Release 2 scope",
section="devel"
)
manifest_4 = create_manifest(output_dir, pkg4_name, pkg4_dir)
validate_package_contents(pkg4_name, manifest_4,
allowed_patterns=["usr/src/**/tests/*", "debian/*"],
forbidden_patterns=["build/*", "jenkins/*"])
# ============================================
# Package 5: qt6-release-build-pipeline
# ============================================
info("\n=== Building qt6-release-build-pipeline ===")
pkg5_name = f"qt6-release-build-pipeline"
pkg5_dir = os.path.join(build_base, f"{pkg5_name}-{version}")
os.makedirs(pkg5_dir)
# Copy pipeline scripts and configuration
# Note: pipeline dirs are at project root, not in qt-migration-legatus
pipeline_dirs = [
"jenkins-ci-rme",
"docker",
"scripts"
]
# Use parent of source_dir as project root (source_dir points to qt-migration-legatus/qt6-after/qt6-migrated-legatus)
project_root = os.path.dirname(os.path.dirname(os.path.dirname(source_dir)))
install_lines_5 = []
for pipeline_dir in pipeline_dirs:
src_pipeline = os.path.join(project_root, pipeline_dir)
dest_pipeline = os.path.join(pkg5_dir, pipeline_dir)
if os.path.exists(src_pipeline):
shutil.copytree(src_pipeline, dest_pipeline, dirs_exist_ok=True)
for root, dirs, files in os.walk(dest_pipeline):
dirs[:] = [d for d in dirs if not d.startswith('.')]
for file in files:
rel_path = os.path.relpath(os.path.join(root, file), pkg5_dir)
install_lines_5.append(f"{rel_path} usr/src/pipeline/{os.path.dirname(rel_path)}/")
build_single_deb_nobuild(
pkg_name=pkg5_name,
pkg_dir=pkg5_dir,
output_dir=output_dir,
version=version,
build_number=build_number,
install_lines=install_lines_5,
description="Build pipeline scripts and configuration for Release 2",
section="devel"
)
manifest_5 = create_manifest(output_dir, pkg5_name, pkg5_dir)
validate_package_contents(pkg5_name, manifest_5,
allowed_patterns=["usr/src/pipeline/*", "debian/*"],
forbidden_patterns=["*.cpp", "*.h", "build/*"])
info("\n========================================")
info("All 5 DEB packages built successfully!")
info(f"Output directory: {output_dir}")
info("========================================")
def main():
parser = argparse.ArgumentParser(description="Build DEB packages for Qt6 Release 2")
parser.add_argument("-c", "--config", required=True, help="Config type (LTS)")
parser.add_argument("--project_name", required=True, help="Project name (qatar|ahtc|guez)")
parser.add_argument("--version", required=True, help="Version (Major.minor)")
parser.add_argument("--output_dir", required=True, help="Output directory for packages")
parser.add_argument("--source_dir", required=True, help="Source directory path")
parser.add_argument("--cmake_build_flags", default="", help="CMake build flags")
parser.add_argument("--build_number", default="1", help="Build number")
parser.add_argument("--release_scope", action="store_true", help="Only include release scope files")
args = parser.parse_args()
info(f"Building {args.config} package for {args.project_name} v{args.version}")
info(f"Output directory: {args.output_dir}")
info(f"Source directory: {args.source_dir}")
info(f"CMake flags: {args.cmake_build_flags}")
info(f"Release scope: {args.release_scope}")
source_dir = args.source_dir
info(f"Source directory: {source_dir}")
build_all_debs(
source_dir=source_dir,
output_dir=args.output_dir,
project_name=args.project_name,
version=args.version,
build_number=args.build_number,
cmake_flags=args.cmake_build_flags,
release_scope=args.release_scope
)
if __name__ == "__main__":
main()