From bac1e9f30ae9eaf3564a2585b6db33d51c580cea Mon Sep 17 00:00:00 2001 From: Jatin Bharti Date: Wed, 1 Apr 2026 14:59:01 +0530 Subject: [PATCH 1/3] Merge stable RT patches, build kernel, and validate target deployment - Merge patches,build x64 kernel - Install kernel on target with reboot and SSH readiness checks. - Rebuild out-of-tree drivers automatically using DKMS. - Kernel source is mounted via SSHFS to avoid local copy. - Fix build/source symlinks and prepare kernel headers. Verified on real NI RT target with DKMS-managed drivers. Signed-off-by: Jatin Bharti --- .../dev/upstream_merge/automation_conf.json | 13 +- scripts/dev/upstream_merge/json_config.py | 36 +- .../upstream_merge/kernel_build_and_test.py | 503 ++++++++++++++++++ scripts/dev/upstream_merge/rebuild_drivers.py | 163 ++++++ scripts/dev/upstream_merge/toolchain_build.py | 150 ++++++ .../dev/upstream_merge/utils/git_commands.py | 59 ++ 6 files changed, 921 insertions(+), 3 deletions(-) create mode 100644 scripts/dev/upstream_merge/kernel_build_and_test.py create mode 100644 scripts/dev/upstream_merge/rebuild_drivers.py create mode 100644 scripts/dev/upstream_merge/toolchain_build.py diff --git a/scripts/dev/upstream_merge/automation_conf.json b/scripts/dev/upstream_merge/automation_conf.json index 58b6a6805..4208a0eb1 100644 --- a/scripts/dev/upstream_merge/automation_conf.json +++ b/scripts/dev/upstream_merge/automation_conf.json @@ -12,5 +12,16 @@ "email_to": "", "username": "", "build_args":"", - "ssh_connection":"" + "ssh_connection":"", + + "kernel_build": { + "kernel_src_dir": "nilrt-kernel-build/linux", + "arch": "x86_64", + "temp_modules_dir": "tmp-glibc/modules", + "target_branch": "nilrt/master/6.12", + "toolchain_prefix": "/usr/local/oecore-x86_64/sysroots/x86_64-nilrtsdk-linux/usr/bin/x86_64-nilrt-linux/x86_64-nilrt-linux-", + "target_ip": "10.152.9.10", + "target_user": "admin", + "build_host_ip": "10.152.9.56" +} } diff --git a/scripts/dev/upstream_merge/json_config.py b/scripts/dev/upstream_merge/json_config.py index 533fd64f9..873297837 100644 --- a/scripts/dev/upstream_merge/json_config.py +++ b/scripts/dev/upstream_merge/json_config.py @@ -2,6 +2,7 @@ configuration file.""" import json +from logging import config import os @@ -9,8 +10,8 @@ class JsonConfig: """Handles loading and validating the automation_conf.json configuration file.""" - def __init__(self, automation_conf_path, work_item_id): - with open(automation_conf_path, "r", encoding="utf-8") as file: + def __init__(self, config_path, work_item_id): + with open(config_path, "r", encoding="utf-8") as file: config = json.load(file) self.work_item_id = work_item_id self.nilrt_branch = config.get("nilrt_branch") @@ -27,3 +28,34 @@ def __init__(self, automation_conf_path, work_item_id): self.log_level = config.get("log_level") self.build_args = config.get("build_args", "") self.ssh_connection = config.get("ssh_connection") + + # Kernel build configuration (optional section) + kernel_config = config.get("kernel_build", {}) + # Expand environment variables in paths + self.kernel_src_dir = self._expand_path(kernel_config.get("kernel_src_dir")) + self.kernel_target_host = kernel_config.get("target_host") + self.kernel_target_user = kernel_config.get("target_user") + self.arch = kernel_config.get("arch") + self.temp_modules_dir = self._expand_path(kernel_config.get("temp_modules_dir")) + self.toolchain_prefix = self._expand_path(kernel_config.get("toolchain_prefix")) + self.merge_workdir = self._expand_path(kernel_config.get("merge_workdir")) + self.target_branch = kernel_config.get("target_branch") + self.stable_rt_remote = kernel_config.get("stable_rt_remote") + self.nilrt_root = self._expand_path(kernel_config.get("nilrt_root")) + self.required_packages = kernel_config.get("required_packages", []) + self.make_jobs = kernel_config.get("make_jobs", "$(nproc)") + self.kernel_config = kernel_config.get("kernel_config", "defconfig") + self.repo_url = kernel_config.get("repo_url") + self.target_name = kernel_config.get("target_name") + self.target_ip = kernel_config.get("target_ip") + self.target_user = kernel_config.get("target_user", "admin") + self.build_host_ip = kernel_config.get("build_host_ip") + self.ssh_options = kernel_config.get("ssh_options", "-o StrictHostKeyChecking=no") + self.build_host_user = kernel_config.get("build_host_user", os.getenv("USER", "builduser")) + + + def _expand_path(self, path): + """Expand environment variables and user home directory in paths.""" + if path: + return os.path.expandvars(os.path.expanduser(path)) + return path diff --git a/scripts/dev/upstream_merge/kernel_build_and_test.py b/scripts/dev/upstream_merge/kernel_build_and_test.py new file mode 100644 index 000000000..2c8a6ce07 --- /dev/null +++ b/scripts/dev/upstream_merge/kernel_build_and_test.py @@ -0,0 +1,503 @@ +#!/usr/bin/env python3 +import os +import sys +import re +import argparse +import time +from log_and_email_utils import setup_logging, write_log_and_send_email +from utils.git_commands import ( + git_fetch, + git_remote, + git_merge, + git_reset, + git_clean, + git_merge_abort, + git_tag, + git_status, + git_clone, + git_checkout, +) +from utils.git_repo import GitRepo +from json_config import JsonConfig +from toolchain_build import copy_toolchain_from_nirvana +from toolchain_build import prepare_toolchain_environment +from rebuild_drivers import step0_install_sshfs_fuse +from rebuild_drivers import step1_mount_kernel_source +from rebuild_drivers import step2_fix_symlinks +from rebuild_drivers import step3_prepare_headers +from rebuild_drivers import step4_dkms_autoinstall +# -------------------- +# Import shared utils +# -------------------- +script_dir = os.path.dirname(os.path.abspath(__file__)) +dev_dir = os.path.dirname(script_dir) +upstream_merge_dir = os.path.join(dev_dir, "upstream_merge") +sys.path.append(upstream_merge_dir) + +REPO_URL = "https://github.com/ni/linux.git" +STABLE_RT_REMOTE = ( + "https://git.kernel.org/pub/scm/linux/kernel/git/rt/linux-stable-rt.git" +) + + +# -------------------- +# Kernel build config (x86_64) +# -------------------- +KERNEL_DEFCONFIG = "nati_x86_64_defconfig" + + +config = None + + +# -------------------- +# CLI +# -------------------- +def parse_args(): + parser = argparse.ArgumentParser(description="Stable-RT merge automation") + parser.add_argument( + "-c", + "--config", + default="scripts/dev/upstream_merge/automation_conf.json" + ) + parser.add_argument("--skip-merge", action="store_true") + parser.add_argument("--dry-run", action="store_true") + parser.add_argument("--work-dir", default=None) + return parser.parse_args() + + +def wait_for_ssh(target, user, ssh_opts="", timeout=120): + print(f"[INFO] Waiting for SSH on {target}...") + + for _ in range(timeout // 5): + ret = os.system( + f'ssh {ssh_opts} {user}@{target} "echo ok" > /dev/null 2>&1' + ) + if ret == 0: + print("[INFO] SSH is available on target") + return 0 + time.sleep(5) + + return 1 + + +def run_cmd(cmd): + """ + Run a shell command and return (rc, output). + rc = 0 on success, 1 on failure. + """ + stream = os.popen(cmd + " 2>&1") + output = stream.read().strip() + rc = stream.close() + if rc is not None: + return 1, output + return 0, output + + +# -------------------- +# Kernel repo handling +# -------------------- +def clone_kernel_repository(): + print("[INFO] Cloning kernel repository") + + repo = os.path.abspath(config.kernel_src_dir) + config.kernel_src_dir = repo + + os.makedirs(os.path.dirname(repo), exist_ok=True) + git_clone(REPO_URL, repo) + + +# -------------------- +# RT Merge +# -------------------- +def run_upstream_merge_script(args): + original_cwd = os.getcwd() + print("[INFO] Running upstream RT merge") + + clone_kernel_repository() + + kernel_repo = GitRepo( + local_repo=config.kernel_src_dir, + upstream_repo_url=REPO_URL, + upstream_branch=config.target_branch, + ) + + os.chdir(config.kernel_src_dir) + + try: + git_merge_abort() + except Exception: + pass + + git_reset(hard=True) + git_clean(force=True, directories=True, ignored_files=True) + + git_fetch("origin") + git_checkout(config.target_branch, create=True, force_checkout=True) + git_reset(hard=True, target=f"origin/{config.target_branch}") + + _, remotes = git_remote() + if "stable-rt" not in remotes: + git_remote("stable-rt", STABLE_RT_REMOTE) + + git_fetch("stable-rt", "--tags") + + kernel_version = config.target_branch.split("/")[-1] + _, tags = git_tag(list_pattern=f"v{kernel_version}.*-rt*") + + clean_tags = [ + t + for t in tags.splitlines() + if re.match(rf"^v{kernel_version}\.\d+-rt\d+$", t) + ] + + if not clean_tags: + raise RuntimeError("No stable-RT tags found") + + latest_tag = sorted( + clean_tags, key=lambda t: list(map(int, re.findall(r"\d+", t))) + )[-1] + + print(f"[INFO] Latest RT tag: {latest_tag}") + + result = git_merge( + latest_tag, signoff=True, message=f"Merge latest upstream {latest_tag}" + ) + + # -------------------- + # Failure case + # -------------------- + if result[0] != 0: + _, status_out = git_status() + + merge_details = ( + status_out + if ("both modified" in status_out or "unmerged" in status_out) + else "Merge failed before conflicts were created." + ) + + merge_report = { + kernel_repo: (1, merge_details), + "Build and Test": (1, "Skipped because merge failed"), + } + + os.chdir(original_cwd) + write_log_and_send_email( + email_from=config.email_from, + email_to=config.email_to, + merge_report=merge_report, + email_log_level=0, + skip_push_and_pr=True, + ) + return + + # -------------------- + # Success case + # -------------------- + print("[INFO] RT merge successful") + + # -------------------- + # Copy toolchain from Nirvana + # -------------------- + build_root = os.path.dirname(config.kernel_src_dir) + tc_copy_status, tc_copy_msg = copy_toolchain_from_nirvana(build_root) + if tc_copy_status != 0: + merge_report = { + kernel_repo: ( + 1, + f"RT tag {latest_tag} merged successfully.\n" + f"Branch: {config.target_branch}", + ), + "Build and Test": (1, tc_copy_msg), + } + + os.chdir(original_cwd) + write_log_and_send_email( + email_from=config.email_from, + email_to=config.email_to, + merge_report=merge_report, + email_log_level=0, + skip_push_and_pr=True, + ) + return + # -------------------- + # Prepare toolchain environment + # -------------------- + tc_status, tc_msg = prepare_toolchain_environment(build_root) + if tc_status != 0: + merge_report = { + kernel_repo: ( + 1, + f"RT tag {latest_tag} merged successfully.\n" + f"Branch: {config.target_branch}", + ), + "Build and Test": (1, tc_msg), + } + + os.chdir(original_cwd) + write_log_and_send_email( + email_from=config.email_from, + email_to=config.email_to, + merge_report=merge_report, + email_log_level=0, + skip_push_and_pr=True, + ) + return + + # -------------------- + # Kernel build (x86_64) + # -------------------- + build_status, build_msg = build_kernel_x86_64(config.kernel_src_dir) + + merge_report = { + kernel_repo: ( + build_status, + f"RT tag {latest_tag} merged successfully.\n" + f"Branch: {config.target_branch}", + ), + "Build and Test": (build_status, build_msg), + } + + os.chdir(original_cwd) + write_log_and_send_email( + email_from=config.email_from, + email_to=config.email_to, + merge_report=merge_report, + email_log_level=0, + skip_push_and_pr=True, + ) + + # -------------------- + # Install kernel to target + # -------------------- + install_status, install_msg = install_kernel_to_target( + config.kernel_src_dir) + merge_report = { + kernel_repo: ( + install_status, + f"RT tag {latest_tag} merged successfully.\n" + f"Branch: {config.target_branch}", + ), + "Build and Test": (install_status, install_msg), + } + print("[INFO] Kernel install issued reboot request") + + time.sleep(20) + + print("[INFO] Waiting for target to come back after kernel reboot") + + if wait_for_ssh(config.target_ip, config.target_user, timeout=300) != 0: + print("[ERROR] Target did not come back after kernel reboot") + return + + print("[INFO] Target is back online, starting rebuild drivers") + + # STEP 0: Ensure sshfs is available + rc = step0_install_sshfs_fuse(config, run_cmd) + if rc != 0: + print("[ERROR] STEP 0 failed") + return + + # STEP 1: Mount kernel source via SSHFS + rc = step1_mount_kernel_source(config, run_cmd) + if rc != 0: + print("[ERROR] STEP 1 failed") + return + + # STEP 2: Fix build/source symlinks + rc = step2_fix_symlinks(config, run_cmd) + if rc != 0: + print("[ERROR] STEP 2 failed") + return + + # STEP 3: Prepare kernel headers + rc = step3_prepare_headers(config, run_cmd) + if rc != 0: + print("[ERROR] STEP 3 failed") + return + + # STEP 4: DKMS rebuild + rc = step4_dkms_autoinstall(config, run_cmd) + if rc != 0: + print("[ERROR] STEP 4 failed") + return + os.chdir(original_cwd) + write_log_and_send_email( + email_from=config.email_from, + email_to=config.email_to, + merge_report=merge_report, + email_log_level=0, + skip_push_and_pr=True, + ) + return + + +def build_kernel_x86_64(kernel_src_dir): + print("[INFO] Starting x86_64 kernel build") + + if not os.path.isdir(kernel_src_dir): + return 1, f"Kernel source directory not found: {kernel_src_dir}" + + # kernel_src_dir = /nilrt-kernel-build/linux + build_root = os.path.dirname(kernel_src_dir) + + # Read from config + jobs = getattr(config, "kernel_build_jobs", os.cpu_count() or 8) + temp_modules_rel = getattr(config, "temp_modules_dir", "tmp-glibc/modules") + + temp_modules_dir = os.path.join(build_root, temp_modules_rel) + os.makedirs(temp_modules_dir, exist_ok=True) + + os.chdir(kernel_src_dir) + + # Ensure ARCH is correct + os.environ["ARCH"] = "x86_64" + + # Step 1: Configure kernel + ret = os.system(f"make {KERNEL_DEFCONFIG}") + if ret != 0: + return 1, "Kernel defconfig failed" + + # Step 2: Build kernel and modules + ret = os.system(f"make -j{jobs} bzImage modules") + if ret != 0: + return 1, "Kernel build failed (bzImage/modules)" + + # Step 3: Install modules to temp directory + ret = os.system( + f"make modules_install INSTALL_MOD_PATH={temp_modules_dir}") + if ret != 0: + return 1, "Kernel modules_install failed" + + return 0, ( + "Kernel built successfully (bzImage + modules)\n" + f"Modules staged at: {temp_modules_dir}" + ) + + +def install_kernel_to_target(kernel_src_dir): + """ + Install x86_64 kernel and modules to an NILRT target (cRIO). + """ + + target = config.target_ip + user = config.target_user + ssh_opts = getattr(config, "ssh_options", "") + + build_root = os.path.dirname(kernel_src_dir) + + bzimage = os.path.join(kernel_src_dir, "arch", "x86", "boot", "bzImage") + + if not os.path.isfile(bzimage): + return 1, f"Kernel image not found: {bzimage}" + + # Get kernel version from build tree + os.chdir(kernel_src_dir) + kernel_version = os.popen("make -s kernelrelease").read().strip() + + print(f"[INFO] Installing kernel version: {kernel_version}") + + modules_root = os.path.join( + build_root, "tmp-glibc", "modules", "lib", "modules", kernel_version + ) + + if not os.path.isdir(modules_root): + return ( + 1, + f"Kernel modules for {kernel_version} " + f"not found at {modules_root}", + ) + + # Backup existing kernel on target + rc = os.system( + f"ssh {ssh_opts} {user}@{target} " + '"test ! -h /boot/runmode/bzImage && ' + 'mv /boot/runmode/bzImage /boot/runmode/bzImage-$(uname -r) || true"' + ) + if rc != 0: + return 1, "Failed to backup existing kernel on target" + + # Copy kernel image + rc = os.system( + f"scp {ssh_opts} {bzimage} " + f"{user}@{target}:/boot/runmode/bzImage-{kernel_version}" + ) + if rc != 0: + return 1, "FAILED to copy kernel bzImage to target (ABORTING)" + + # VERIFY kernel image exists on target + rc = os.system( + f"ssh {ssh_opts} {user}@{target} " + f'"test -f /boot/runmode/bzImage-{kernel_version}"' + ) + if rc != 0: + return 1, "Kernel bzImage missing on target after copy (ABORTING)" + + # Update boot symlink + rc = os.system( + f"ssh {ssh_opts} {user}@{target} " + f'"ln -sf bzImage-{kernel_version} /boot/runmode/bzImage"' + ) + if rc != 0: + return 1, "FAILED to update bzImage symlink (ABORTING)" + + # Copy kernel modules (versioned directory) + rc = os.system( + f"tar cz -C {modules_root} . | " + f"ssh {ssh_opts} {user}@{target} " + f'"mkdir -p /lib/modules/{kernel_version} && ' + f'tar xz -C /lib/modules/{kernel_version}"' + ) + if rc != 0: + return 1, "FAILED to copy kernel modules to target (ABORTING)" + + # VERIFY modules directory exists on target + rc = os.system( + f"ssh {ssh_opts} {user}@{target} " + f'"test -d /lib/modules/{kernel_version}"' + ) + if rc != 0: + return 1, "Kernel modules missing on target after copy (ABORTING)" + + # CRITICAL: rebuild module dependency indexes + rc = os.system( + f"ssh {ssh_opts} {user}@{target} " + f'"depmod -a {kernel_version}"' + ) + if rc != 0: + return 1, "depmod failed on target (ABORTING reboot)" + + # Reboot target ONLY after everything succeeded + rc = os.system(f"ssh {ssh_opts} {user}@{target} reboot") + if rc != 0: + return 1, "FAILED to reboot target after install" + + return 0, ( + f"Kernel and modules installed successfully\n" + f"Kernel version: {kernel_version}" + ) + + +# -------------------- +# Main +# -------------------- +def main(): + global config + + setup_logging() + args = parse_args() + + config = JsonConfig(config_path=args.config, work_item_id=None) + + if args.work_dir: + config.kernel_src_dir = os.path.join( + args.work_dir, "nilrt-kernel-build", "linux" + ) + + print(f"[INFO] Branch: {config.target_branch}, " f"Arch: {config.arch}") + + if not args.skip_merge: + run_upstream_merge_script(args) + + +if __name__ == "__main__": + main() diff --git a/scripts/dev/upstream_merge/rebuild_drivers.py b/scripts/dev/upstream_merge/rebuild_drivers.py new file mode 100644 index 000000000..d30130d85 --- /dev/null +++ b/scripts/dev/upstream_merge/rebuild_drivers.py @@ -0,0 +1,163 @@ +""" +Rebuild NI out-of-tree drivers using DKMS. +This file is intentionally isolated from kernel build/install logic. +""" + + +def rebuild_out_of_tree_drivers(config, run_cmd): + """ + Entry point for rebuilding NI out-of-tree drivers. + Called after kernel install and reboot. + """ + + print("\n[REBUILD] Starting out-of-tree driver rebuild") + + rc = step0_install_sshfs_fuse(config, run_cmd) + if rc != 0: + return 1, "STEP 0 failed" + # Next steps will be added one-by-one: + # step2_fix_symlinks + # step3_prepare_headers + # step4_dkms_autoinstall + + return 0, "Rebuild drivers completed" + + +def step0_install_sshfs_fuse(config, run_cmd): + target = config.target_ip + user = config.target_user + + print("[REBUILD][STEP 0] Install sshfs-fuse and load fuse") + + # opkg update (SSH drop expected) + rc, out = run_cmd(f"ssh {user}@{target} 'opkg update || true'") + print(out) + + # opkg install + rc, out = run_cmd( + f"ssh {user}@{target} 'opkg install sshfs-fuse || true'" + ) + print(out) + + # VERIFY sshfs exists + rc, out = run_cmd(f"ssh {user}@{target} 'which sshfs'") + print(out) + if rc != 0: + print("[REBUILD][ERROR] sshfs not installed") + return 1 + + # load fuse + rc, out = run_cmd(f"ssh {user}@{target} 'modprobe fuse'") + print(out) + if rc != 0: + print("[REBUILD][ERROR] modprobe fuse failed") + return 1 + + print("[REBUILD][OK] sshfs-fuse ready") + return 0 + + +def step1_mount_kernel_source(config, run_cmd): + target = config.target_ip + user = config.target_user + kernel_src_dir = config.kernel_src_dir + host_user = config.build_host_user + host_ip = config.build_host_ip + + print("[REBUILD][STEP 1] Mount kernel source via SSHFS") + + run_cmd(f'ssh {user}@{target} "mkdir -p /usr/src/linux"') + run_cmd( + f'ssh {user}@{target}' + f'"mount | grep /usr/src/linux && umount /usr/src/linux || true"' + ) + + rc, out = run_cmd( + f'ssh {user}@{target} ' + f'"sshfs {host_user}@{host_ip}:{kernel_src_dir} /usr/src/linux"' + ) + print(out) + + rc, out = run_cmd( + f'ssh {user}@{target} "test -f /usr/src/linux/Makefile && echo OK"' + ) + print(out) + + if rc != 0: + print("[REBUILD][ERROR] Kernel source not visible at /usr/src/linux") + return 1 + + print("[REBUILD][OK] Kernel source mounted via SSHFS") + return 0 + + +def step2_fix_symlinks(config, run_cmd): + target = config.target_ip + user = config.target_user + + print("[REBUILD][STEP 2] Fix build/source symlinks") + + rc, out = run_cmd( + f"ssh {user}@{target} " + "'cd /lib/modules/$(uname -r) && " + "rm -f build source && " + "ln -s /usr/src/linux source && " + "ln -s source build'" + ) + print(out) + + if rc != 0: + print("[REBUILD][ERROR] Failed to fix build/source symlinks") + return 1 + + # Optional but good verification + rc, out = run_cmd( + f"ssh {user}@{target} " + "'ls -l /lib/modules/$(uname -r)/build " + "/lib/modules/$(uname -r)/source'" + ) + print(out) + + print("[REBUILD][OK] build and source symlinks fixed") + return 0 + + +def step3_prepare_headers(config, run_cmd): + target = config.target_ip + user = config.target_user + + print("[REBUILD][STEP 3] Prepare kernel headers") + + rc, out = run_cmd( + f"ssh {user}@{target} " + "'cd /lib/modules/$(uname -r)/build && " + "make prepare && make modules_prepare'" + ) + print(out) + + if rc != 0: + print("[REBUILD][ERROR] Kernel header preparation failed") + return 1 + + print("[REBUILD][OK] Kernel headers prepared") + return 0 + + +def step4_dkms_autoinstall(config, run_cmd): + target = config.target_ip + user = config.target_user + + print("[REBUILD][STEP 4] DKMS autoinstall") + + rc, out = run_cmd(f"ssh {user}@{target} 'dkms autoinstall'") + print(out) + + if rc != 0: + print("[REBUILD][ERROR] DKMS autoinstall failed") + return 1 + + rc, out = run_cmd(f"ssh {user}@{target} 'dkms status'") + print(out) + + print("[REBUILD][OK] DKMS rebuild complete") + return 0 diff --git a/scripts/dev/upstream_merge/toolchain_build.py b/scripts/dev/upstream_merge/toolchain_build.py new file mode 100644 index 000000000..81772516a --- /dev/null +++ b/scripts/dev/upstream_merge/toolchain_build.py @@ -0,0 +1,150 @@ +import os + +# -------------------- +# Nirvana mount base (assumed present) +# -------------------- +NIRVANA_SDK_BASE = ( + "/mnt/nirvana/perforceExports/build/exports/" + "ni/rtos/rtos_toolchain/official/export" +) + + +def _get_latest_major_version(): + if not os.path.isdir(NIRVANA_SDK_BASE): + return None + + versions = [] + for d in os.listdir(NIRVANA_SDK_BASE): + path = os.path.join(NIRVANA_SDK_BASE, d) + if os.path.isdir(path): + try: + versions.append((tuple(map(int, d.split("."))), d)) + except ValueError: + continue + + return sorted(versions)[-1][1] if versions else None + + +def _get_latest_build_version(version_path): + builds = [] + for d in os.listdir(version_path): + path = os.path.join(version_path, d) + if os.path.isdir(path): + try: + builds.append(( + list(map(int, d.replace("d", ".").split("."))), d)) + except ValueError: + continue + + return sorted(builds)[-1][1] if builds else None + + +def copy_toolchain_from_nirvana(build_root): + """ + Copy latest x64 NILRT toolchain from mounted Nirvana to: + /toolchain/NILinuxRT-x64 + """ + + latest_version = _get_latest_major_version() + if not latest_version: + return 1, "No Nirvana toolchain versions found" + + version_path = os.path.join(NIRVANA_SDK_BASE, latest_version) + latest_build = _get_latest_build_version(version_path) + if not latest_build: + return 1, f"No builds found under Nirvana version {latest_version}" + + sdk_src = os.path.join( + version_path, + latest_build, + "toolchain", + "sdk", + "NILinuxRT-x64" + ) + + if not os.path.isdir(sdk_src): + return 1, f"x64 SDK not found at: {sdk_src}" + + toolchain_root = os.path.join(build_root, "toolchain") + toolchain_dst = os.path.join(toolchain_root, "NILinuxRT-x64") + + # Idempotent: skip if already present + if os.path.isdir(toolchain_dst): + return 0, f"Toolchain already present at {toolchain_dst}" + + os.makedirs(toolchain_root, exist_ok=True) + + ret = os.system(f"cp -r {sdk_src} {toolchain_root}/") + if ret != 0: + return 1, "Failed to copy toolchain from Nirvana" + + return 0, ( + f"Toolchain copied from Nirvana\n" + f"Version: {latest_version}/{latest_build}\n" + f"Location: {toolchain_dst}" + ) + + +def prepare_toolchain_environment(build_root): + """ + Run the NILinuxRT SDK installer (non-interactive) if needed, + extracting into the local build toolchain directory, + then source the environment. + """ + + toolchain_path = os.path.join(build_root, "toolchain", "NILinuxRT-x64") + if not os.path.isdir(toolchain_path): + return 1, f"Toolchain directory not found: {toolchain_path}" + + env_script = None + for root, dirs, files in os.walk(toolchain_path): + for f in files: + if f.startswith("environment-setup"): + env_script = os.path.join(root, f) + break + if env_script: + break + + # If SDK is not yet extracted, run installer + if not env_script: + installers = [ + f for f in os.listdir(toolchain_path) + if f.endswith(".sh") + ] + + if not installers: + return 1, "No SDK installer (.sh) found in toolchain directory" + + installer = os.path.join(toolchain_path, installers[0]) + os.chmod(installer, 0o755) + + # IMPORTANT: force extraction into toolchain_path + ret = os.system( + f'bash "{installer}" -y -d "{toolchain_path}"' + ) + if ret != 0: + return 1, "Failed to run SDK installer" + + # Look again for environment-setup after extraction + for root, dirs, files in os.walk(toolchain_path): + for f in files: + if f.startswith("environment-setup"): + env_script = os.path.join(root, f) + break + if env_script: + break + + if not env_script: + return 1, "installer ran , env-setup script not found" + + # Source environment into Python process + cmd = f'bash -c "source {env_script} >/dev/null && env"' + with os.popen(cmd) as proc: + for line in proc: + key, _, value = line.strip().partition("=") + os.environ[key] = value + + if "CROSS_COMPILE" not in os.environ: + return 1, "CROSS_COMPILE not set after sourcing toolchain" + + return 0, "Toolchain environment ready" diff --git a/scripts/dev/upstream_merge/utils/git_commands.py b/scripts/dev/upstream_merge/utils/git_commands.py index fe0270862..ad822d422 100644 --- a/scripts/dev/upstream_merge/utils/git_commands.py +++ b/scripts/dev/upstream_merge/utils/git_commands.py @@ -282,3 +282,62 @@ def send_email( f'--confirm=never --encoding=UTF-8 {file}' ) return run_command(command) + + +def git_tag(list_pattern=None): + """ + List git tags, optionally filtered by a pattern. + """ + command = "git tag" + if list_pattern: + command += f" -l '{list_pattern}'" + return run_command(command, capture_output=True) + + +def git_merge_abort(): + """ + Abort a merge in progress. + """ + return run_command("git merge --abort", capture_output=True) + + +def git_status(capture_output=True): + """ + Show the working tree status. + """ + return run_command("git status", capture_output) + + +def git_reset(hard=False, target="HEAD", capture_output=True): + """ + Reset current HEAD to the specified state. + :param hard: Whether to do a hard reset (--hard). + :param target: The commit/branch to reset to (default: HEAD). + """ + command = "git reset" + if hard: + command += " --hard" + command += f" {target}" + return run_command(command, capture_output) + + +def git_clean( + force=False, + directories=False, + ignored_files=False, + capture_output=True + ): + """ + Remove untracked files from the working tree. + :param force: Required by git to actually delete files. + :param directories: Remove untracked directories as well (-d). + :param ignored_files: Remove ignored files as well (-x). + """ + command = "git clean" + if force: + command += " -f" + if directories: + command += "d" + if ignored_files: + command += "x" + return run_command(command, capture_output) From d4fa409306e92f8b50dfac8aaa1539edfc81edca Mon Sep 17 00:00:00 2001 From: Jatin Bharti Date: Thu, 7 May 2026 12:01:24 +0530 Subject: [PATCH 2/3] Automate defconfig and improve merge flow - Automated defconfig regeneration process - Integrated gated kernel PR creation workflow - Pulled latest upstream changes for consistency - Improved SSH connection handling logic - Cleaned and formatted json.config structure - Enhanced overall script reliability and clarity Signed-off-by: Jatin Bharti --- .../dev/upstream_merge/automation_conf.json | 10 +- scripts/dev/upstream_merge/json_config.py | 26 ++-- .../upstream_merge/kernel_build_and_test.py | 135 ++++++++++++++---- scripts/dev/upstream_merge/rebuild_drivers.py | 41 +++--- 4 files changed, 150 insertions(+), 62 deletions(-) diff --git a/scripts/dev/upstream_merge/automation_conf.json b/scripts/dev/upstream_merge/automation_conf.json index 4208a0eb1..a29100bad 100644 --- a/scripts/dev/upstream_merge/automation_conf.json +++ b/scripts/dev/upstream_merge/automation_conf.json @@ -20,8 +20,10 @@ "temp_modules_dir": "tmp-glibc/modules", "target_branch": "nilrt/master/6.12", "toolchain_prefix": "/usr/local/oecore-x86_64/sysroots/x86_64-nilrtsdk-linux/usr/bin/x86_64-nilrt-linux/x86_64-nilrt-linux-", - "target_ip": "10.152.9.10", - "target_user": "admin", - "build_host_ip": "10.152.9.56" -} + "ssh_target": "", + "build_host_ip": "", + "pr_enabled": false, + "pr_target_branch": "nilrt/master/6.12", + "work_item_id": "" + } } diff --git a/scripts/dev/upstream_merge/json_config.py b/scripts/dev/upstream_merge/json_config.py index 873297837..9a662f39f 100644 --- a/scripts/dev/upstream_merge/json_config.py +++ b/scripts/dev/upstream_merge/json_config.py @@ -2,7 +2,6 @@ configuration file.""" import json -from logging import config import os @@ -28,17 +27,20 @@ def __init__(self, config_path, work_item_id): self.log_level = config.get("log_level") self.build_args = config.get("build_args", "") self.ssh_connection = config.get("ssh_connection") - # Kernel build configuration (optional section) kernel_config = config.get("kernel_build", {}) # Expand environment variables in paths - self.kernel_src_dir = self._expand_path(kernel_config.get("kernel_src_dir")) + self.kernel_src_dir = self._expand_path( + kernel_config.get("kernel_src_dir")) self.kernel_target_host = kernel_config.get("target_host") self.kernel_target_user = kernel_config.get("target_user") self.arch = kernel_config.get("arch") - self.temp_modules_dir = self._expand_path(kernel_config.get("temp_modules_dir")) - self.toolchain_prefix = self._expand_path(kernel_config.get("toolchain_prefix")) - self.merge_workdir = self._expand_path(kernel_config.get("merge_workdir")) + self.temp_modules_dir = self._expand_path( + kernel_config.get("temp_modules_dir")) + self.toolchain_prefix = self._expand_path( + kernel_config.get("toolchain_prefix")) + self.merge_workdir = self._expand_path( + kernel_config.get("merge_workdir")) self.target_branch = kernel_config.get("target_branch") self.stable_rt_remote = kernel_config.get("stable_rt_remote") self.nilrt_root = self._expand_path(kernel_config.get("nilrt_root")) @@ -50,10 +52,16 @@ def __init__(self, config_path, work_item_id): self.target_ip = kernel_config.get("target_ip") self.target_user = kernel_config.get("target_user", "admin") self.build_host_ip = kernel_config.get("build_host_ip") - self.ssh_options = kernel_config.get("ssh_options", "-o StrictHostKeyChecking=no") - self.build_host_user = kernel_config.get("build_host_user", os.getenv("USER", "builduser")) + self.ssh_options = kernel_config.get( + "ssh_options", "-o StrictHostKeyChecking=no") + self.build_host_user = kernel_config.get( + "build_host_user", os.getenv("USER", "builduser")) + self.pr_enabled = config.get("pr_enabled", False) + self.pr_target_branch = config.get( + "pr_target_branch", self.target_branch) + self.work_item_id = config.get("work_item_id", "") + self.ssh_target = kernel_config.get("ssh_target") - def _expand_path(self, path): """Expand environment variables and user home directory in paths.""" if path: diff --git a/scripts/dev/upstream_merge/kernel_build_and_test.py b/scripts/dev/upstream_merge/kernel_build_and_test.py index 2c8a6ce07..88947d270 100644 --- a/scripts/dev/upstream_merge/kernel_build_and_test.py +++ b/scripts/dev/upstream_merge/kernel_build_and_test.py @@ -26,6 +26,7 @@ from rebuild_drivers import step2_fix_symlinks from rebuild_drivers import step3_prepare_headers from rebuild_drivers import step4_dkms_autoinstall +from utils.push_branch_and_create_pr import push_branch_and_create_pr # -------------------- # Import shared utils # -------------------- @@ -62,15 +63,20 @@ def parse_args(): parser.add_argument("--skip-merge", action="store_true") parser.add_argument("--dry-run", action="store_true") parser.add_argument("--work-dir", default=None) + parser.add_argument( + "--skip-push-and-pr", + action="store_true", + help="Skip pushing branch and creating PR" + ) return parser.parse_args() -def wait_for_ssh(target, user, ssh_opts="", timeout=120): - print(f"[INFO] Waiting for SSH on {target}...") +def wait_for_ssh(ssh_target, ssh_opts="", timeout=120): + print(f"[INFO] Waiting for SSH on {ssh_target}...") for _ in range(timeout // 5): ret = os.system( - f'ssh {ssh_opts} {user}@{target} "echo ok" > /dev/null 2>&1' + f'ssh {ssh_opts} {ssh_target} "echo ok" > /dev/null 2>&1' ) if ret == 0: print("[INFO] SSH is available on target") @@ -106,9 +112,63 @@ def clone_kernel_repository(): git_clone(REPO_URL, repo) +def regenerate_defconfig(kernel_src_dir): + """ + US-4: + Regenerate nati_x86_64_defconfig and create a commit if it changes. + Returns True if a commit was created, False otherwise. + """ + print("[US4] Regenerating nati_x86_64_defconfig") + + original_cwd = os.getcwd() + os.chdir(kernel_src_dir) + + cmds = [ + "make mrproper", + "make nati_x86_64_defconfig", + "make savedefconfig", + "mv defconfig arch/x86/configs/nati_x86_64_defconfig", + ] + + for cmd in cmds: + rc = os.system(cmd) + if rc != 0: + os.chdir(original_cwd) + raise RuntimeError(f"[US4][ERROR] Command failed: {cmd}") + + # Check for diff + diff_rc = os.system( + "git diff --quiet arch/x86/configs/nati_x86_64_defconfig" + ) + + if diff_rc == 0: + print("[US4] Defconfig unchanged") + os.chdir(original_cwd) + return False + + print("[US4] Defconfig changed, creating commit") + + rc = os.system("git add arch/x86/configs/nati_x86_64_defconfig") + if rc != 0: + os.chdir(original_cwd) + raise RuntimeError("[US4][ERROR] git add failed") + + rc = os.system( + 'git commit -s -m ' + '"nati_x86_64_defconfig: regenerate; no functional changes"' + ) + if rc != 0: + os.chdir(original_cwd) + raise RuntimeError("[US4][ERROR] git commit failed") + + os.chdir(original_cwd) + return True + # -------------------- # RT Merge # -------------------- + + def run_upstream_merge_script(args): original_cwd = os.getcwd() print("[INFO] Running upstream RT merge") @@ -243,6 +303,9 @@ def run_upstream_merge_script(args): ) return + defconfig_changed = regenerate_defconfig(config.kernel_src_dir) + if defconfig_changed: + print("[US4] Defconfig regeneration commit created") # -------------------- # Kernel build (x86_64) # -------------------- @@ -257,15 +320,6 @@ def run_upstream_merge_script(args): "Build and Test": (build_status, build_msg), } - os.chdir(original_cwd) - write_log_and_send_email( - email_from=config.email_from, - email_to=config.email_to, - merge_report=merge_report, - email_log_level=0, - skip_push_and_pr=True, - ) - # -------------------- # Install kernel to target # -------------------- @@ -284,8 +338,7 @@ def run_upstream_merge_script(args): time.sleep(20) print("[INFO] Waiting for target to come back after kernel reboot") - - if wait_for_ssh(config.target_ip, config.target_user, timeout=300) != 0: + if wait_for_ssh(config.ssh_target, timeout=300) != 0: print("[ERROR] Target did not come back after kernel reboot") return @@ -320,13 +373,43 @@ def run_upstream_merge_script(args): if rc != 0: print("[ERROR] STEP 4 failed") return + # PR creation (FINAL STEP ONLY) ---- + if not args.skip_push_and_pr: + pr_title = f"[{config.work_item_id}] Merge RT {latest_tag}" + + pr_description = ( + f"AB#{config.work_item_id}\n\n" + f"RT tag merged: {latest_tag}\n" + f"Defconfig regenerated: " + f"{'Yes' if defconfig_changed else 'No'}\n" + f"Kernel build: Success\n" + f"Driver rebuild: Success\n" + ) + + git_obj = GitRepo( + local_repo=config.kernel_src_dir, + local_base_branch=config.pr_target_branch, + ) + + status, msg = push_branch_and_create_pr( + git_obj, + branch_name=config.target_branch, + username=config.pr_username, + pr_title=pr_title, + pr_description=pr_description, + ) + + if status != 0: + raise RuntimeError(f"Failed to create PR: {msg}") + + print("[INFO] Branch pushed and PR created successfully") os.chdir(original_cwd) write_log_and_send_email( email_from=config.email_from, email_to=config.email_to, merge_report=merge_report, email_log_level=0, - skip_push_and_pr=True, + skip_push_and_pr=args.skip_push_and_pr, ) return @@ -379,8 +462,7 @@ def install_kernel_to_target(kernel_src_dir): Install x86_64 kernel and modules to an NILRT target (cRIO). """ - target = config.target_ip - user = config.target_user + ssh_target = config.ssh_target ssh_opts = getattr(config, "ssh_options", "") build_root = os.path.dirname(kernel_src_dir) @@ -409,7 +491,7 @@ def install_kernel_to_target(kernel_src_dir): # Backup existing kernel on target rc = os.system( - f"ssh {ssh_opts} {user}@{target} " + f"ssh {ssh_opts} {ssh_target} " '"test ! -h /boot/runmode/bzImage && ' 'mv /boot/runmode/bzImage /boot/runmode/bzImage-$(uname -r) || true"' ) @@ -419,14 +501,14 @@ def install_kernel_to_target(kernel_src_dir): # Copy kernel image rc = os.system( f"scp {ssh_opts} {bzimage} " - f"{user}@{target}:/boot/runmode/bzImage-{kernel_version}" + f"{ssh_target}:/boot/runmode/bzImage-{kernel_version}" ) if rc != 0: return 1, "FAILED to copy kernel bzImage to target (ABORTING)" # VERIFY kernel image exists on target rc = os.system( - f"ssh {ssh_opts} {user}@{target} " + f"ssh {ssh_opts} {ssh_target} " f'"test -f /boot/runmode/bzImage-{kernel_version}"' ) if rc != 0: @@ -434,7 +516,7 @@ def install_kernel_to_target(kernel_src_dir): # Update boot symlink rc = os.system( - f"ssh {ssh_opts} {user}@{target} " + f"ssh {ssh_opts} {ssh_target} " f'"ln -sf bzImage-{kernel_version} /boot/runmode/bzImage"' ) if rc != 0: @@ -443,7 +525,7 @@ def install_kernel_to_target(kernel_src_dir): # Copy kernel modules (versioned directory) rc = os.system( f"tar cz -C {modules_root} . | " - f"ssh {ssh_opts} {user}@{target} " + f"ssh {ssh_opts} {ssh_target} " f'"mkdir -p /lib/modules/{kernel_version} && ' f'tar xz -C /lib/modules/{kernel_version}"' ) @@ -452,7 +534,7 @@ def install_kernel_to_target(kernel_src_dir): # VERIFY modules directory exists on target rc = os.system( - f"ssh {ssh_opts} {user}@{target} " + f"ssh {ssh_opts} {ssh_target} " f'"test -d /lib/modules/{kernel_version}"' ) if rc != 0: @@ -460,14 +542,14 @@ def install_kernel_to_target(kernel_src_dir): # CRITICAL: rebuild module dependency indexes rc = os.system( - f"ssh {ssh_opts} {user}@{target} " + f"ssh {ssh_opts} {ssh_target} " f'"depmod -a {kernel_version}"' ) if rc != 0: return 1, "depmod failed on target (ABORTING reboot)" # Reboot target ONLY after everything succeeded - rc = os.system(f"ssh {ssh_opts} {user}@{target} reboot") + rc = os.system(f"ssh {ssh_opts} {ssh_target} reboot") if rc != 0: return 1, "FAILED to reboot target after install" @@ -476,10 +558,11 @@ def install_kernel_to_target(kernel_src_dir): f"Kernel version: {kernel_version}" ) - # -------------------- # Main # -------------------- + + def main(): global config diff --git a/scripts/dev/upstream_merge/rebuild_drivers.py b/scripts/dev/upstream_merge/rebuild_drivers.py index d30130d85..6ea96ccbd 100644 --- a/scripts/dev/upstream_merge/rebuild_drivers.py +++ b/scripts/dev/upstream_merge/rebuild_drivers.py @@ -24,30 +24,29 @@ def rebuild_out_of_tree_drivers(config, run_cmd): def step0_install_sshfs_fuse(config, run_cmd): - target = config.target_ip - user = config.target_user + ssh_target = config.ssh_target print("[REBUILD][STEP 0] Install sshfs-fuse and load fuse") # opkg update (SSH drop expected) - rc, out = run_cmd(f"ssh {user}@{target} 'opkg update || true'") + rc, out = run_cmd(f"ssh {ssh_target}'opkg update || true'") print(out) # opkg install rc, out = run_cmd( - f"ssh {user}@{target} 'opkg install sshfs-fuse || true'" + f"ssh {ssh_target} 'opkg install sshfs-fuse || true'" ) print(out) # VERIFY sshfs exists - rc, out = run_cmd(f"ssh {user}@{target} 'which sshfs'") + rc, out = run_cmd(f"ssh {ssh_target} 'which sshfs'") print(out) if rc != 0: print("[REBUILD][ERROR] sshfs not installed") return 1 # load fuse - rc, out = run_cmd(f"ssh {user}@{target} 'modprobe fuse'") + rc, out = run_cmd(f"ssh {ssh_target} 'modprobe fuse'") print(out) if rc != 0: print("[REBUILD][ERROR] modprobe fuse failed") @@ -58,28 +57,27 @@ def step0_install_sshfs_fuse(config, run_cmd): def step1_mount_kernel_source(config, run_cmd): - target = config.target_ip - user = config.target_user + ssh_target = config.ssh_target kernel_src_dir = config.kernel_src_dir host_user = config.build_host_user host_ip = config.build_host_ip print("[REBUILD][STEP 1] Mount kernel source via SSHFS") - run_cmd(f'ssh {user}@{target} "mkdir -p /usr/src/linux"') + run_cmd(f'ssh {ssh_target} "mkdir -p /usr/src/linux"') run_cmd( - f'ssh {user}@{target}' + f'ssh {ssh_target}' f'"mount | grep /usr/src/linux && umount /usr/src/linux || true"' ) rc, out = run_cmd( - f'ssh {user}@{target} ' + f'ssh {ssh_target} ' f'"sshfs {host_user}@{host_ip}:{kernel_src_dir} /usr/src/linux"' ) print(out) rc, out = run_cmd( - f'ssh {user}@{target} "test -f /usr/src/linux/Makefile && echo OK"' + f'ssh {ssh_target} "test -f /usr/src/linux/Makefile && echo OK"' ) print(out) @@ -92,13 +90,12 @@ def step1_mount_kernel_source(config, run_cmd): def step2_fix_symlinks(config, run_cmd): - target = config.target_ip - user = config.target_user + ssh_target = config.ssh_target print("[REBUILD][STEP 2] Fix build/source symlinks") rc, out = run_cmd( - f"ssh {user}@{target} " + f"ssh {ssh_target} " "'cd /lib/modules/$(uname -r) && " "rm -f build source && " "ln -s /usr/src/linux source && " @@ -112,7 +109,7 @@ def step2_fix_symlinks(config, run_cmd): # Optional but good verification rc, out = run_cmd( - f"ssh {user}@{target} " + f"ssh {ssh_target} " "'ls -l /lib/modules/$(uname -r)/build " "/lib/modules/$(uname -r)/source'" ) @@ -123,13 +120,12 @@ def step2_fix_symlinks(config, run_cmd): def step3_prepare_headers(config, run_cmd): - target = config.target_ip - user = config.target_user + ssh_target = config.ssh_target print("[REBUILD][STEP 3] Prepare kernel headers") rc, out = run_cmd( - f"ssh {user}@{target} " + f"ssh {ssh_target} " "'cd /lib/modules/$(uname -r)/build && " "make prepare && make modules_prepare'" ) @@ -144,19 +140,18 @@ def step3_prepare_headers(config, run_cmd): def step4_dkms_autoinstall(config, run_cmd): - target = config.target_ip - user = config.target_user + ssh_target = config.ssh_target print("[REBUILD][STEP 4] DKMS autoinstall") - rc, out = run_cmd(f"ssh {user}@{target} 'dkms autoinstall'") + rc, out = run_cmd(f"ssh {ssh_target} 'dkms autoinstall'") print(out) if rc != 0: print("[REBUILD][ERROR] DKMS autoinstall failed") return 1 - rc, out = run_cmd(f"ssh {user}@{target} 'dkms status'") + rc, out = run_cmd(f"ssh {ssh_target} 'dkms status'") print(out) print("[REBUILD][OK] DKMS rebuild complete") From aacf739ba2e2cd200d1d82148a367db9b205f263 Mon Sep 17 00:00:00 2001 From: Jatin Bharti Date: Tue, 12 May 2026 12:27:28 +0530 Subject: [PATCH 3/3] Refactor build logic and clean up scripts - Moved build functions into build.py module - Centralized reusable build utilities - Removed redundant code across scripts - Cleaned up unused and inconsistent files - Improved modularity and maintainability Signed-off-by: Jatin Bharti --- .../dev/upstream_merge/automation_conf.json | 12 +- ...build.py => copy_toolchain_from_server.py} | 42 +-- scripts/dev/upstream_merge/json_config.py | 33 +- .../upstream_merge/kernel_build_and_test.py | 288 +++++++----------- scripts/dev/upstream_merge/rebuild_drivers.py | 32 +- scripts/dev/upstream_merge/utils/build.py | 104 +++++++ 6 files changed, 260 insertions(+), 251 deletions(-) rename scripts/dev/upstream_merge/{toolchain_build.py => copy_toolchain_from_server.py} (79%) diff --git a/scripts/dev/upstream_merge/automation_conf.json b/scripts/dev/upstream_merge/automation_conf.json index a29100bad..08ddfca63 100644 --- a/scripts/dev/upstream_merge/automation_conf.json +++ b/scripts/dev/upstream_merge/automation_conf.json @@ -15,15 +15,13 @@ "ssh_connection":"", "kernel_build": { - "kernel_src_dir": "nilrt-kernel-build/linux", - "arch": "x86_64", - "temp_modules_dir": "tmp-glibc/modules", - "target_branch": "nilrt/master/6.12", - "toolchain_prefix": "/usr/local/oecore-x86_64/sysroots/x86_64-nilrtsdk-linux/usr/bin/x86_64-nilrt-linux/x86_64-nilrt-linux-", + "kernel_src_dir": "", + "arch": "", + "temp_modules_dir": "", + "target_branch": "", + "toolchain_prefix": "", "ssh_target": "", "build_host_ip": "", - "pr_enabled": false, - "pr_target_branch": "nilrt/master/6.12", "work_item_id": "" } } diff --git a/scripts/dev/upstream_merge/toolchain_build.py b/scripts/dev/upstream_merge/copy_toolchain_from_server.py similarity index 79% rename from scripts/dev/upstream_merge/toolchain_build.py rename to scripts/dev/upstream_merge/copy_toolchain_from_server.py index 81772516a..e9265b876 100644 --- a/scripts/dev/upstream_merge/toolchain_build.py +++ b/scripts/dev/upstream_merge/copy_toolchain_from_server.py @@ -70,7 +70,7 @@ def copy_toolchain_from_nirvana(build_root): # Idempotent: skip if already present if os.path.isdir(toolchain_dst): - return 0, f"Toolchain already present at {toolchain_dst}" + return 0, f"Toolchain already present at {toolchain_dst}", toolchain_dst os.makedirs(toolchain_root, exist_ok=True) @@ -82,22 +82,21 @@ def copy_toolchain_from_nirvana(build_root): f"Toolchain copied from Nirvana\n" f"Version: {latest_version}/{latest_build}\n" f"Location: {toolchain_dst}" - ) + ), toolchain_dst -def prepare_toolchain_environment(build_root): +def prepare_toolchain_environment(toolchain_dst): """ Run the NILinuxRT SDK installer (non-interactive) if needed, extracting into the local build toolchain directory, then source the environment. """ - toolchain_path = os.path.join(build_root, "toolchain", "NILinuxRT-x64") - if not os.path.isdir(toolchain_path): - return 1, f"Toolchain directory not found: {toolchain_path}" + if not os.path.isdir(toolchain_dst): + return 1, f"Toolchain directory not found: {toolchain_dst}" env_script = None - for root, dirs, files in os.walk(toolchain_path): + for root, dirs, files in os.walk(toolchain_dst): for f in files: if f.startswith("environment-setup"): env_script = os.path.join(root, f) @@ -107,26 +106,26 @@ def prepare_toolchain_environment(build_root): # If SDK is not yet extracted, run installer if not env_script: - installers = [ - f for f in os.listdir(toolchain_path) - if f.endswith(".sh") - ] - - if not installers: + installer = next( + (f for f in os.listdir(toolchain_dst) if f.endswith(".sh")), + None + ) + if not installer: return 1, "No SDK installer (.sh) found in toolchain directory" - installer = os.path.join(toolchain_path, installers[0]) + installer = os.path.join(toolchain_dst, installer) + os.chmod(installer, 0o755) - # IMPORTANT: force extraction into toolchain_path + # IMPORTANT: force extraction into toolchain_dst ret = os.system( - f'bash "{installer}" -y -d "{toolchain_path}"' + f'bash "{installer}" -y -d "{toolchain_dst}"' ) if ret != 0: return 1, "Failed to run SDK installer" # Look again for environment-setup after extraction - for root, dirs, files in os.walk(toolchain_path): + for root, dirs, files in os.walk(toolchain_dst): for f in files: if f.startswith("environment-setup"): env_script = os.path.join(root, f) @@ -144,7 +143,14 @@ def prepare_toolchain_environment(build_root): key, _, value = line.strip().partition("=") os.environ[key] = value +# CROSS_COMPILE is expected to be exported by the SDK environment-setup script + if "CROSS_COMPILE" not in os.environ: - return 1, "CROSS_COMPILE not set after sourcing toolchain" + return 1, ( + "CROSS_COMPILE not set after sourcing toolchain environment. " + "Ensure the SDK environment-setup script exports it correctly." + ) + + print(f"[TOOLCHAIN] CROSS_COMPILE set to: {os.environ['CROSS_COMPILE']}") return 0, "Toolchain environment ready" diff --git a/scripts/dev/upstream_merge/json_config.py b/scripts/dev/upstream_merge/json_config.py index 9a662f39f..58e5be5d3 100644 --- a/scripts/dev/upstream_merge/json_config.py +++ b/scripts/dev/upstream_merge/json_config.py @@ -12,7 +12,6 @@ class JsonConfig: def __init__(self, config_path, work_item_id): with open(config_path, "r", encoding="utf-8") as file: config = json.load(file) - self.work_item_id = work_item_id self.nilrt_branch = config.get("nilrt_branch") self.meta_nilrt_branch = config.get("meta_nilrt_branch") self.conf_file_path = os.getcwd() + f"/{config.get('conf_file_path')}" @@ -30,27 +29,21 @@ def __init__(self, config_path, work_item_id): # Kernel build configuration (optional section) kernel_config = config.get("kernel_build", {}) # Expand environment variables in paths - self.kernel_src_dir = self._expand_path( + self.kernel_src_dir = os.path.abspath( kernel_config.get("kernel_src_dir")) - self.kernel_target_host = kernel_config.get("target_host") - self.kernel_target_user = kernel_config.get("target_user") self.arch = kernel_config.get("arch") - self.temp_modules_dir = self._expand_path( - kernel_config.get("temp_modules_dir")) - self.toolchain_prefix = self._expand_path( - kernel_config.get("toolchain_prefix")) - self.merge_workdir = self._expand_path( - kernel_config.get("merge_workdir")) + self.temp_modules_dir = kernel_config.get( + "temp_modules_dir") + self.toolchain_prefix = kernel_config.get( + "toolchain_prefix") + self.merge_workdir = kernel_config.get( + "merge_workdir") self.target_branch = kernel_config.get("target_branch") - self.stable_rt_remote = kernel_config.get("stable_rt_remote") - self.nilrt_root = self._expand_path(kernel_config.get("nilrt_root")) + self.nilrt_root = kernel_config.get("nilrt_root") self.required_packages = kernel_config.get("required_packages", []) self.make_jobs = kernel_config.get("make_jobs", "$(nproc)") self.kernel_config = kernel_config.get("kernel_config", "defconfig") - self.repo_url = kernel_config.get("repo_url") self.target_name = kernel_config.get("target_name") - self.target_ip = kernel_config.get("target_ip") - self.target_user = kernel_config.get("target_user", "admin") self.build_host_ip = kernel_config.get("build_host_ip") self.ssh_options = kernel_config.get( "ssh_options", "-o StrictHostKeyChecking=no") @@ -59,11 +52,5 @@ def __init__(self, config_path, work_item_id): self.pr_enabled = config.get("pr_enabled", False) self.pr_target_branch = config.get( "pr_target_branch", self.target_branch) - self.work_item_id = config.get("work_item_id", "") - self.ssh_target = kernel_config.get("ssh_target") - - def _expand_path(self, path): - """Expand environment variables and user home directory in paths.""" - if path: - return os.path.expandvars(os.path.expanduser(path)) - return path + self.work_item_id = work_item_id or config.get("work_item_id", "") + self.ssh_target = kernel_config.get("ssh_target") \ No newline at end of file diff --git a/scripts/dev/upstream_merge/kernel_build_and_test.py b/scripts/dev/upstream_merge/kernel_build_and_test.py index 88947d270..4c439a545 100644 --- a/scripts/dev/upstream_merge/kernel_build_and_test.py +++ b/scripts/dev/upstream_merge/kernel_build_and_test.py @@ -5,13 +5,10 @@ import argparse import time from log_and_email_utils import setup_logging, write_log_and_send_email +from utils import build from utils.git_commands import ( git_fetch, - git_remote, git_merge, - git_reset, - git_clean, - git_merge_abort, git_tag, git_status, git_clone, @@ -19,14 +16,17 @@ ) from utils.git_repo import GitRepo from json_config import JsonConfig -from toolchain_build import copy_toolchain_from_nirvana -from toolchain_build import prepare_toolchain_environment -from rebuild_drivers import step0_install_sshfs_fuse -from rebuild_drivers import step1_mount_kernel_source -from rebuild_drivers import step2_fix_symlinks -from rebuild_drivers import step3_prepare_headers -from rebuild_drivers import step4_dkms_autoinstall +from copy_toolchain_from_server import copy_toolchain_from_nirvana +from copy_toolchain_from_server import prepare_toolchain_environment +from rebuild_drivers import install_sshfs_fuse +from rebuild_drivers import mount_kernel_source +from rebuild_drivers import fix_symlinks +from rebuild_drivers import prepare_headers +from rebuild_drivers import dkms_autoinstall from utils.push_branch_and_create_pr import push_branch_and_create_pr +from utils.build import build_kernel_x86_64 +from utils.build import regenerate_defconfig + # -------------------- # Import shared utils # -------------------- @@ -40,13 +40,6 @@ "https://git.kernel.org/pub/scm/linux/kernel/git/rt/linux-stable-rt.git" ) - -# -------------------- -# Kernel build config (x86_64) -# -------------------- -KERNEL_DEFCONFIG = "nati_x86_64_defconfig" - - config = None @@ -105,68 +98,82 @@ def run_cmd(cmd): def clone_kernel_repository(): print("[INFO] Cloning kernel repository") - repo = os.path.abspath(config.kernel_src_dir) - config.kernel_src_dir = repo + os.makedirs(os.path.dirname(config.kernel_src_dir), exist_ok=True) + git_clone(REPO_URL, config.kernel_src_dir) - os.makedirs(os.path.dirname(repo), exist_ok=True) - git_clone(REPO_URL, repo) +# -------------------- +# RT Merge +# -------------------- -def regenerate_defconfig(kernel_src_dir): - """ - US-4: - Regenerate nati_x86_64_defconfig and create a commit if it changes. - Returns True if a commit was created, False otherwise. - """ - print("[US4] Regenerating nati_x86_64_defconfig") +def run_rebuild_drivers(config, run_cmd): + print("[INFO] Target is back online, starting rebuild drivers") - original_cwd = os.getcwd() - os.chdir(kernel_src_dir) + # STEP 0: Ensure sshfs is available + rc = install_sshfs_fuse(config, run_cmd) + if rc != 0: + print("[ERROR] STEP 0 failed") + return 1 - cmds = [ - "make mrproper", - "make nati_x86_64_defconfig", - "make savedefconfig", - "mv defconfig arch/x86/configs/nati_x86_64_defconfig", - ] + # STEP 1: Mount kernel source via SSHFS + rc = mount_kernel_source(config, run_cmd) + if rc != 0: + print("[ERROR] STEP 1 failed") + return 1 - for cmd in cmds: - rc = os.system(cmd) - if rc != 0: - os.chdir(original_cwd) - raise RuntimeError(f"[US4][ERROR] Command failed: {cmd}") + # STEP 2: Fix build/source symlinks + rc = fix_symlinks(config, run_cmd) + if rc != 0: + print("[ERROR] STEP 2 failed") + return 1 - # Check for diff - diff_rc = os.system( - "git diff --quiet arch/x86/configs/nati_x86_64_defconfig" - ) + # STEP 3: Prepare kernel headers + rc = prepare_headers(config, run_cmd) + if rc != 0: + print("[ERROR] STEP 3 failed") + return 1 - if diff_rc == 0: - print("[US4] Defconfig unchanged") - os.chdir(original_cwd) - return False + # STEP 4: DKMS rebuild + rc = dkms_autoinstall(config, run_cmd) + if rc != 0: + print("[ERROR] STEP 4 failed") + return 1 - print("[US4] Defconfig changed, creating commit") + return 0 - rc = os.system("git add arch/x86/configs/nati_x86_64_defconfig") - if rc != 0: - os.chdir(original_cwd) - raise RuntimeError("[US4][ERROR] git add failed") - rc = os.system( - 'git commit -s -m ' - '"nati_x86_64_defconfig: regenerate; no functional changes"' +def create_kernel_pr(args, config, latest_tag, defconfig_changed): + if args.skip_push_and_pr: + return + + pr_title = f"[{config.work_item_id}] Merge RT {latest_tag}" + + pr_description = ( + f"AB#{config.work_item_id}\n\n" + f"RT tag merged: {latest_tag}\n" + f"Defconfig regenerated: " + f"{'Yes' if defconfig_changed else 'No'}\n" + f"Kernel build: Success\n" + f"Driver rebuild: Success\n" ) - if rc != 0: - os.chdir(original_cwd) - raise RuntimeError("[US4][ERROR] git commit failed") - os.chdir(original_cwd) - return True + git_obj = GitRepo( + local_repo=config.kernel_src_dir, + local_base_branch=config.target_branch, + ) -# -------------------- -# RT Merge -# -------------------- + status, msg = push_branch_and_create_pr( + git_obj, + branch_name=config.target_branch, + username=config.username, + pr_title=pr_title, + pr_description=pr_description, + ) + + if status != 0: + raise RuntimeError(f"Failed to create PR: {msg}") + + print("[INFO] Branch pushed and PR created successfully") def run_upstream_merge_script(args): @@ -183,22 +190,13 @@ def run_upstream_merge_script(args): os.chdir(config.kernel_src_dir) - try: - git_merge_abort() - except Exception: - pass - - git_reset(hard=True) - git_clean(force=True, directories=True, ignored_files=True) - git_fetch("origin") git_checkout(config.target_branch, create=True, force_checkout=True) - git_reset(hard=True, target=f"origin/{config.target_branch}") - _, remotes = git_remote() - if "stable-rt" not in remotes: - git_remote("stable-rt", STABLE_RT_REMOTE) + # Ensure stable-rt remote exists using GitRepo abstraction + kernel_repo.add_remote("stable-rt", STABLE_RT_REMOTE) + # Fetch RT tags git_fetch("stable-rt", "--tags") kernel_version = config.target_branch.split("/")[-1] @@ -259,7 +257,8 @@ def run_upstream_merge_script(args): # Copy toolchain from Nirvana # -------------------- build_root = os.path.dirname(config.kernel_src_dir) - tc_copy_status, tc_copy_msg = copy_toolchain_from_nirvana(build_root) + tc_copy_status, tc_copy_msg, toolchain_dst = copy_toolchain_from_nirvana( + build_root) if tc_copy_status != 0: merge_report = { kernel_repo: ( @@ -282,7 +281,7 @@ def run_upstream_merge_script(args): # -------------------- # Prepare toolchain environment # -------------------- - tc_status, tc_msg = prepare_toolchain_environment(build_root) + tc_status, tc_msg = prepare_toolchain_environment(toolchain_dst) if tc_status != 0: merge_report = { kernel_repo: ( @@ -341,68 +340,37 @@ def run_upstream_merge_script(args): if wait_for_ssh(config.ssh_target, timeout=300) != 0: print("[ERROR] Target did not come back after kernel reboot") return + # -------------------- + # VERIFY kernel version on target + # -------------------- + ssh_target = config.ssh_target + ssh_opts = getattr(config, "ssh_options", "") - print("[INFO] Target is back online, starting rebuild drivers") - - # STEP 0: Ensure sshfs is available - rc = step0_install_sshfs_fuse(config, run_cmd) - if rc != 0: - print("[ERROR] STEP 0 failed") - return - - # STEP 1: Mount kernel source via SSHFS - rc = step1_mount_kernel_source(config, run_cmd) - if rc != 0: - print("[ERROR] STEP 1 failed") - return + cmd = f'ssh {ssh_opts} {ssh_target} "uname -r"' + kernel_running = os.popen(cmd).read().strip() - # STEP 2: Fix build/source symlinks - rc = step2_fix_symlinks(config, run_cmd) - if rc != 0: - print("[ERROR] STEP 2 failed") - return + print(f"[VERIFY] Running kernel on target: {kernel_running}") - # STEP 3: Prepare kernel headers - rc = step3_prepare_headers(config, run_cmd) - if rc != 0: - print("[ERROR] STEP 3 failed") - return + # expected kernel version (from build step) + os.chdir(config.kernel_src_dir) + expected_kernel = os.popen("make -s kernelrelease").read().strip() - # STEP 4: DKMS rebuild - rc = step4_dkms_autoinstall(config, run_cmd) - if rc != 0: - print("[ERROR] STEP 4 failed") - return - # PR creation (FINAL STEP ONLY) ---- - if not args.skip_push_and_pr: - pr_title = f"[{config.work_item_id}] Merge RT {latest_tag}" - - pr_description = ( - f"AB#{config.work_item_id}\n\n" - f"RT tag merged: {latest_tag}\n" - f"Defconfig regenerated: " - f"{'Yes' if defconfig_changed else 'No'}\n" - f"Kernel build: Success\n" - f"Driver rebuild: Success\n" - ) + print(f"[VERIFY] Expected kernel: {expected_kernel}") - git_obj = GitRepo( - local_repo=config.kernel_src_dir, - local_base_branch=config.pr_target_branch, + if kernel_running != expected_kernel: + print( + f"[ERROR] Kernel version mismatch: expected {expected_kernel}, " + f"got {kernel_running}" ) + return - status, msg = push_branch_and_create_pr( - git_obj, - branch_name=config.target_branch, - username=config.pr_username, - pr_title=pr_title, - pr_description=pr_description, - ) + print("[INFO] Kernel version verified successfully") - if status != 0: - raise RuntimeError(f"Failed to create PR: {msg}") + rebuild_status = run_rebuild_drivers(config, run_cmd) + if rebuild_status != 0: + return + create_kernel_pr(args, config, latest_tag, defconfig_changed) - print("[INFO] Branch pushed and PR created successfully") os.chdir(original_cwd) write_log_and_send_email( email_from=config.email_from, @@ -414,52 +382,9 @@ def run_upstream_merge_script(args): return -def build_kernel_x86_64(kernel_src_dir): - print("[INFO] Starting x86_64 kernel build") - - if not os.path.isdir(kernel_src_dir): - return 1, f"Kernel source directory not found: {kernel_src_dir}" - - # kernel_src_dir = /nilrt-kernel-build/linux - build_root = os.path.dirname(kernel_src_dir) - - # Read from config - jobs = getattr(config, "kernel_build_jobs", os.cpu_count() or 8) - temp_modules_rel = getattr(config, "temp_modules_dir", "tmp-glibc/modules") - - temp_modules_dir = os.path.join(build_root, temp_modules_rel) - os.makedirs(temp_modules_dir, exist_ok=True) - - os.chdir(kernel_src_dir) - - # Ensure ARCH is correct - os.environ["ARCH"] = "x86_64" - - # Step 1: Configure kernel - ret = os.system(f"make {KERNEL_DEFCONFIG}") - if ret != 0: - return 1, "Kernel defconfig failed" - - # Step 2: Build kernel and modules - ret = os.system(f"make -j{jobs} bzImage modules") - if ret != 0: - return 1, "Kernel build failed (bzImage/modules)" - - # Step 3: Install modules to temp directory - ret = os.system( - f"make modules_install INSTALL_MOD_PATH={temp_modules_dir}") - if ret != 0: - return 1, "Kernel modules_install failed" - - return 0, ( - "Kernel built successfully (bzImage + modules)\n" - f"Modules staged at: {temp_modules_dir}" - ) - - def install_kernel_to_target(kernel_src_dir): """ - Install x86_64 kernel and modules to an NILRT target (cRIO). + Install x86_64 kernel and modules to an NILRT target . """ ssh_target = config.ssh_target @@ -548,6 +473,14 @@ def install_kernel_to_target(kernel_src_dir): if rc != 0: return 1, "depmod failed on target (ABORTING reboot)" + # Set bootdelay for safe mode recovery + rc = os.system( + f"ssh {ssh_opts} {ssh_target} " + f'"fw_setenv bootdelay 5"' + ) + if rc != 0: + return 1, "Failed to set bootdelay for safe mode" + # Reboot target ONLY after everything succeeded rc = os.system(f"ssh {ssh_opts} {ssh_target} reboot") if rc != 0: @@ -570,6 +503,7 @@ def main(): args = parse_args() config = JsonConfig(config_path=args.config, work_item_id=None) + build.config = config if args.work_dir: config.kernel_src_dir = os.path.join( diff --git a/scripts/dev/upstream_merge/rebuild_drivers.py b/scripts/dev/upstream_merge/rebuild_drivers.py index 6ea96ccbd..a820919d2 100644 --- a/scripts/dev/upstream_merge/rebuild_drivers.py +++ b/scripts/dev/upstream_merge/rebuild_drivers.py @@ -3,33 +3,13 @@ This file is intentionally isolated from kernel build/install logic. """ - -def rebuild_out_of_tree_drivers(config, run_cmd): - """ - Entry point for rebuilding NI out-of-tree drivers. - Called after kernel install and reboot. - """ - - print("\n[REBUILD] Starting out-of-tree driver rebuild") - - rc = step0_install_sshfs_fuse(config, run_cmd) - if rc != 0: - return 1, "STEP 0 failed" - # Next steps will be added one-by-one: - # step2_fix_symlinks - # step3_prepare_headers - # step4_dkms_autoinstall - - return 0, "Rebuild drivers completed" - - -def step0_install_sshfs_fuse(config, run_cmd): +def install_sshfs_fuse(config, run_cmd): ssh_target = config.ssh_target print("[REBUILD][STEP 0] Install sshfs-fuse and load fuse") # opkg update (SSH drop expected) - rc, out = run_cmd(f"ssh {ssh_target}'opkg update || true'") + rc, out = run_cmd(f"ssh {ssh_target} 'opkg update || true'") print(out) # opkg install @@ -56,7 +36,7 @@ def step0_install_sshfs_fuse(config, run_cmd): return 0 -def step1_mount_kernel_source(config, run_cmd): +def mount_kernel_source(config, run_cmd): ssh_target = config.ssh_target kernel_src_dir = config.kernel_src_dir host_user = config.build_host_user @@ -89,7 +69,7 @@ def step1_mount_kernel_source(config, run_cmd): return 0 -def step2_fix_symlinks(config, run_cmd): +def fix_symlinks(config, run_cmd): ssh_target = config.ssh_target print("[REBUILD][STEP 2] Fix build/source symlinks") @@ -119,7 +99,7 @@ def step2_fix_symlinks(config, run_cmd): return 0 -def step3_prepare_headers(config, run_cmd): +def prepare_headers(config, run_cmd): ssh_target = config.ssh_target print("[REBUILD][STEP 3] Prepare kernel headers") @@ -139,7 +119,7 @@ def step3_prepare_headers(config, run_cmd): return 0 -def step4_dkms_autoinstall(config, run_cmd): +def dkms_autoinstall(config, run_cmd): ssh_target = config.ssh_target print("[REBUILD][STEP 4] DKMS autoinstall") diff --git a/scripts/dev/upstream_merge/utils/build.py b/scripts/dev/upstream_merge/utils/build.py index e25d8d3d1..4962dfdf9 100644 --- a/scripts/dev/upstream_merge/utils/build.py +++ b/scripts/dev/upstream_merge/utils/build.py @@ -4,9 +4,13 @@ """ import argparse +import os from .shell_commands import execute_and_stream_cmd_output +config = None +KERNEL_DEFCONFIG = "nati_x86_64_defconfig" + def setup_env_and_build_packages(args="", clean_build=False): """ @@ -122,6 +126,106 @@ def build_core_images(args=""): ) +def build_kernel_x86_64(kernel_src_dir): + print("[INFO] Starting x86_64 kernel build") + + if not os.path.isdir(kernel_src_dir): + return 1, f"Kernel source directory not found: {kernel_src_dir}" + + # kernel_src_dir = /nilrt-kernel-build/linux + build_root = os.path.dirname(kernel_src_dir) + + # Read from config + temp_modules_rel = getattr(config, "temp_modules_dir", "tmp-glibc/modules") + + temp_modules_dir = os.path.join(build_root, temp_modules_rel) + os.makedirs(temp_modules_dir, exist_ok=True) + + os.chdir(kernel_src_dir) + + # Ensure ARCH is correct + os.environ["ARCH"] = "x86_64" + + # Step 1: Configure kernel + ret = os.system(f"make {KERNEL_DEFCONFIG}") + if ret != 0: + return 1, "Kernel defconfig failed" + + # Step 2: Build kernel and modules + jobs = getattr(config, "kernel_build_jobs", None) + + if jobs is None: + jobs = "$(nproc)" # fallback to all available cores + + ret = os.system(f"make -j{jobs} bzImage modules") + if ret != 0: + return 1, "Kernel build failed (bzImage/modules)" + + # Step 3: Install modules to temp directory + ret = os.system( + f"make modules_install INSTALL_MOD_PATH={temp_modules_dir}") + if ret != 0: + return 1, "Kernel modules_install failed" + + return 0, ( + "Kernel built successfully (bzImage + modules)\n" + f"Modules staged at: {temp_modules_dir}" + ) + + +def regenerate_defconfig(kernel_src_dir): + """ + US-4: + Regenerate nati_x86_64_defconfig and create a commit if it changes. + Returns True if a commit was created, False otherwise. + """ + print("[US4] Regenerating nati_x86_64_defconfig") + + original_cwd = os.getcwd() + os.chdir(kernel_src_dir) + + cmds = [ + "make mrproper", + f"make {KERNEL_DEFCONFIG}", + "make savedefconfig", + f"mv defconfig arch/x86/configs/{KERNEL_DEFCONFIG}", + ] + + for cmd in cmds: + rc = os.system(cmd) + if rc != 0: + os.chdir(original_cwd) + raise RuntimeError(f"[US4][ERROR] Command failed: {cmd}") + + # Check for diff + diff_rc = os.system( + f"git diff --quiet arch/x86/configs/{KERNEL_DEFCONFIG}" + ) + + if diff_rc == 0: + print("[US4] Defconfig unchanged") + os.chdir(original_cwd) + return False + + print("[US4] Defconfig changed, creating commit") + + rc = os.system(f"git add arch/x86/configs/{KERNEL_DEFCONFIG}") + if rc != 0: + os.chdir(original_cwd) + raise RuntimeError("[US4][ERROR] git add failed") + + rc = os.system( + 'git commit -s -m ' + '"nati_x86_64_defconfig: regenerate; no functional changes"' + ) + if rc != 0: + os.chdir(original_cwd) + raise RuntimeError("[US4][ERROR] git commit failed") + + os.chdir(original_cwd) + return True + + def parse_args(): """ Parse command-line arguments for building the core feeds and images,