| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104 |
- import os
- import shutil
- import subprocess
- import logging
- import time
- from build_manager import BuildManager, BuildState
- class Builder:
- def __init__(self, build_id: str = None, workdir: str = None, **kwargs):
- self.logger = logging.getLogger(__name__)
- self.bm = BuildManager.get_singleton()
-
- # --- MODIFICATION START ---
- # Reason: Upstream (web app) and worker container need a consistent
- # base directory. We default to /base where the ArduPilot source lives.
- self.workdir = workdir or os.environ.get("CBS_BASEDIR", "/base")
- # --- MODIFICATION END ---
-
- self.build_id = build_id
- if self.build_id:
- self._setup_paths()
- def _setup_paths(self):
- self.artifacts_dir = self.bm.get_build_artifacts_dir_path(self.build_id)
- self.log_file = self.bm.get_build_log_path(self.build_id)
- self.info = self.bm.get_build_info(self.build_id)
- def __run_cmd(self, cmd, cwd, log_handle):
- # --- MODIFICATION START ---
- # Reason: Using python3 explicitly to run 'waf' is more robust than
- # relying on the execution bit (+x) inside a Docker volume.
- process = subprocess.Popen(
- cmd, cwd=cwd, stdout=log_handle, stderr=subprocess.STDOUT, text=True
- )
- process.wait()
- if process.returncode != 0:
- raise subprocess.CalledProcessError(process.returncode, cmd)
- # --- MODIFICATION END ---
- def build(self, build_id: str = None):
- if build_id:
- self.build_id = build_id
- self._setup_paths()
- if not self.build_id:
- raise ValueError("No build_id provided to Builder.")
- try:
- self.logger.info(f"[{self.build_id}] Starting build process...")
- os.makedirs(self.artifacts_dir, exist_ok=True)
-
- # --- MODIFICATION START ---
- # Reason: We must ensure we are in the directory containing 'waf'.
- # If the path is wrong, the build fails immediately.
- repo = self.workdir
- if not os.path.exists(os.path.join(repo, "waf")):
- self.logger.error(f"[{self.build_id}] waf not found in {repo}")
- raise FileNotFoundError(f"Cannot find waf in {repo}")
- with open(self.log_file, "a") as log:
- log.write(f"Starting build: {self.info.vehicle_id} on {self.info.board}\n")
-
- # --- EQUALMASS OVERLAY INJECTION ---
- # Reason: This is where your custom files from the sidecar manager
- # are merged into the ArduPilot source before the compiler starts.
- overlay_dir = "/app/overlay"
- if os.path.exists(overlay_dir) and os.listdir(overlay_dir):
- self.logger.info(f"[{self.build_id}] Custom overlay found. Injecting...")
- shutil.copytree(overlay_dir, repo, dirs_exist_ok=True)
- else:
- self.logger.info(f"[{self.build_id}] No overlay files. Building vanilla.")
-
- # Running waf via python3 for better Docker compatibility
- self.logger.info(f"[{self.build_id}] Running waf configure...")
- self.__run_cmd(["python3", "waf", "configure", "--board", self.info.board], repo, log)
-
- self.logger.info(f"[{self.build_id}] Running waf build...")
- self.__run_cmd(["python3", "waf", self.info.vehicle_id], repo, log)
- # --- MODIFICATION END ---
- self.bm.update_build_progress_state(self.build_id, BuildState.SUCCESS)
- except Exception as e:
- self.logger.error(f"[{self.build_id}] Build failed: {e}")
- self.bm.update_build_progress_state(self.build_id, BuildState.FAILURE)
- # --- MODIFICATION START ---
- # Reason: The builder container needs this loop to stay alive and poll
- # Redis for jobs. The web app also checks for this method during startup.
- def run(self):
- """Main worker heartbeat loop."""
- self.logger.info("Worker online. Waiting for builds from Redis...")
-
- while True:
- try:
- build_id = self.bm.get_next_build_id()
- if build_id:
- self.logger.info(f"Job received: {build_id}")
- self.build(build_id=build_id)
-
- time.sleep(2) # Prevent CPU spiking
- except Exception as e:
- self.logger.error(f"Error in worker loop: {e}")
- time.sleep(5)
- # --- MODIFICATION END ---
|