#!/usr/bin/env python3 # SPDX-License-Identifier: GPL-2.0-or-later # SPDX-FileCopyrightText: 2011-2024 Blender Authors # import argparse import os import pathlib import re import subprocess import worker.utils class CodeBuilder(worker.utils.Builder): def __init__(self, args: argparse.Namespace): super().__init__(args, "blender", "blender") self.needs_full_clean = args.needs_full_clean self.needs_gpu_binaries = args.needs_gpu_binaries self.needs_gpu_tests = args.needs_gpu_tests self.needs_ninja = True self.python_module = args.python_module self.build_configuration = args.build_configuration track_path: pathlib.Path = self.track_path if self.platform in {"darwin", "windows"}: if len(args.architecture): self.architecture = args.architecture if self.platform == "darwin": self.build_dir = ( track_path / f"build_{self.architecture}_{self.build_configuration}" ) else: self.build_dir = track_path / f"build_{self.build_configuration}" self.blender_dir = track_path / "blender.git" self.install_dir = track_path / f"install_{self.build_configuration}" self.package_dir = track_path / "build_package" self.build_doc_path = track_path / "build_doc_api" def clean(self): worker.utils.remove_dir(self.install_dir) worker.utils.remove_dir(self.package_dir) worker.utils.remove_dir(self.build_doc_path) # Call command with in compiler environment. def call( self, cmd: worker.utils.CmdSequence, env: worker.utils.Cmdenvironment = None ) -> int: cmd_prefix: worker.utils.CmdList = [] if self.platform == "darwin": # On macOS, override Xcode version if requested. pipeline_config = self.pipeline_config() xcode = pipeline_config.get("xcode", None) xcode_version = xcode.get("version", None) if xcode else None if xcode_version: developer_dir = ( f"/Applications/Xcode-{xcode_version}.app/Contents/Developer" ) else: developer_dir = "/Applications/Xcode.app/Contents/Developer" if ( self.service_env_id == "LOCAL" and not pathlib.Path(developer_dir).exists() ): worker.utils.warning( f"Skip using non-existent {developer_dir} in LOCAL service environment" ) else: cmd_prefix = ["xcrun"] env = dict(env) if env else os.environ.copy() env["DEVELOPER_DIR"] = developer_dir elif worker.utils.is_tool("scl"): pipeline_config = self.pipeline_config() gcc_version = pipeline_config["gcc"]["version"] gcc_major_version = gcc_version.split(".")[0] # On Rocky if os.path.exists("/etc/rocky-release"): # Stub to override configured GCC version, remove when blender build config is fixed gcc_major_version = "11" cmd_prefix = ["scl", "enable", f"gcc-toolset-{gcc_major_version}", "--"] return worker.utils.call(cmd_prefix + list(cmd), env=env) def pipeline_config(self) -> dict: config_file_path = ( self.code_path / "build_files" / "config" / "pipeline_config.json" ) if not config_file_path.exists(): config_file_path = config_file_path.with_suffix(".yaml") if not config_file_path.exists(): raise Exception(f"Config file [{config_file_path}] not found, aborting") with open(config_file_path, "r") as read_file: if config_file_path.suffix == ".json": import json pipeline_config = json.load(read_file) else: import yaml pipeline_config = yaml.load(read_file, Loader=yaml.SafeLoader) return pipeline_config["buildbot"] def blender_command_path(self) -> pathlib.Path: if self.platform == "darwin": return self.install_dir / "Blender.app" / "Contents" / "macOS" / "Blender" elif self.platform == "windows": return self.install_dir / "blender.exe" else: return self.install_dir / "blender" def setup_build_environment(self) -> None: if self.platform != "windows": return # CMake goes first to avoid using chocolaty cpack command. worker.utils.info("Setting CMake path") os.environ["PATH"] = ( "C:\\Program Files\\CMake\\bin" + os.pathsep + os.environ["PATH"] ) worker.utils.info("Setting VC Tools env variables") windows_build_version = "10.0.19041.0" os.environ["PATH"] = ( f"C:\\Program Files (x86)\\Windows Kits\\10\\bin\\{windows_build_version}\\x64" + os.pathsep + os.environ["PATH"] ) os.environ["PATH"] = ( "C:\\Program Files (x86)\\WiX Toolset v3.11\\bin" + os.pathsep + os.environ["PATH"] ) if self.architecture == "arm64": vs_build_tool_path = pathlib.Path( "C:\\Program Files\\Microsoft Visual Studio\\2022\\Community\\VC\\Auxiliary\\Build\\vcvarsarm64.bat" ) vs_tool_install_dir_suffix = "\\bin\\Hostarm64\\arm64" else: vs_build_tool_path = pathlib.Path( "C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\Community\\VC\\Auxiliary\\Build\\vcvars64.bat" ) vs_tool_install_dir_suffix = "\\bin\\Hostx64\\x64" vcvars_output = subprocess.check_output( [vs_build_tool_path, "&&", "set"], shell=True ) vcvars_text = vcvars_output.decode("utf-8", "ignore") for line in vcvars_text.splitlines(): match = re.match(r"(.*?)=(.*)", line) if match: key = match.group(1) value = match.group(2) if key not in os.environ: if key not in ["PROMPT", "Path"]: worker.utils.info(f"Adding key {key}={value}") os.environ[key] = value os.environ["PATH"] = ( os.environ["VCToolsInstallDir"] + vs_tool_install_dir_suffix + os.pathsep + os.environ["PATH"] ) def create_argument_parser(steps: worker.utils.BuilderSteps) -> argparse.ArgumentParser: parser = worker.utils.create_argument_parser(steps=steps) parser.add_argument("--needs-full-clean", action="store_true", required=False) parser.add_argument("--needs-gpu-binaries", action="store_true", required=False) parser.add_argument("--needs-gpu-tests", action="store_true", required=False) parser.add_argument("--python-module", action="store_true", required=False) parser.add_argument( "--build-configuration", default="release", type=str, choices=["release", "asserts", "sanitizer", "debug"], required=False, ) parser.add_argument( "--architecture", default="", type=str, choices=["arm64", "x86_64", "amd64"], required=False, ) return parser