# SPDX-License-Identifier: GPL-2.0-or-later # SPDX-FileCopyrightText: 2011-2024 Blender Authors # import pathlib import sys from typing import Optional, Sequence import worker.blender import worker.utils def sign_windows_files( service_env_id: str, file_paths: Sequence[pathlib.Path], description: Optional[str] = None, certificate_id: str = "", ) -> None: import conf.worker worker_config = conf.worker.get_config(service_env_id) # TODO: Rotate them if first 1 fails timeserver = worker_config.sign_code_windows_time_servers[0] server_url = worker_config.sign_code_windows_server_url if not certificate_id: certificate_id = worker_config.sign_code_windows_certificate dry_run = False if service_env_id == "LOCAL" and not certificate_id: worker.utils.warning("Performing dry run on LOCAL service environment") dry_run = True cmd_args = [ sys.executable, "C:\\tools\\codesign.py", "--server-url", worker.utils.HiddenArgument(server_url), ] if description: cmd_args += ["--description", description] cmd: worker.utils.CmdSequence = cmd_args # Signing one file at a time causes a stampede on servers, resulting in blocking. # Instead sign in chunks of multiple files. chunk_size = 25 # Sign how many files at a time retry_count = 3 for i in range(0, len(file_paths), chunk_size): file_chunks = file_paths[i : i + chunk_size] worker.utils.call(list(cmd) + list(file_chunks), retry_count=retry_count, dry_run=dry_run) def sign_windows(service_env_id: str, install_path: pathlib.Path) -> None: # TODO: Why use a junction? Is there some failure with long file paths? # worker.utils.info("Creating building link") # temp_build_root_path = pathlib.Path("C:/BlenderTemp") # os.makedirs(temp_build_root_path, exist_ok=True) # orig_install_path = install_path # install_path = temp_build_root_path / install_path.name try: # TODO # New-Item -type Junction -path install_path -value orig_install_path worker.utils.info("Collecting files to process") file_paths = list(install_path.glob("*.exe")) file_paths += list(install_path.glob("*.dll")) file_paths += list(install_path.glob("*.pyd")) file_paths = [f for f in file_paths if str(f).find("blender.crt") == -1] for f in file_paths: print(f) sign_windows_files(service_env_id, file_paths) finally: # worker.utils.info(f"Removing temporary folder {temp_build_root_path}") # worker.utils.remove_dir(temp_build_root_path, retry_count=5, retry_wait_time=5.0) # TODO: is this really necessary? # worker.utils.info("Flushing volume cache...") # Write-VolumeCache -DriveLetter C # core_shell_retry_command -retry_count 5 -delay_in_milliseconds 1000 -script_block ` # worker.utils.info("Junction information...") # junction = Get-Item -Path install_path # worker.utils.info(junction | Format-Table) # worker.utils.info("Attempting to remove...") # junction.Delete() # worker.utils.info("Junction deleted!") pass worker.utils.info("End of codesign steps") def sign_darwin_files( builder: worker.blender.CodeBuilder, file_paths: Sequence[pathlib.Path], entitlements_file_name: str ) -> None: entitlements_path = builder.code_path / "release" / "darwin" / entitlements_file_name if not entitlements_path.exists(): raise Exception(f"File {entitlements_path} not found, aborting") worker_config = builder.get_worker_config() certificate_id = worker_config.sign_code_darwin_certificate dry_run = False if builder.service_env_id == "LOCAL" and not certificate_id: worker.utils.warning("Performing dry run on LOCAL service environment") dry_run = True keychain_password = worker_config.darwin_keychain_password(builder.service_env_id) cmd: worker.utils.CmdSequence = [ "security", "unlock-keychain", "-p", worker.utils.HiddenArgument(keychain_password), ] worker.utils.call(cmd, dry_run=dry_run) for file_path in file_paths: if file_path.is_dir() and file_path.suffix != ".app": continue # Remove signature if file_path.suffix != ".dmg": worker.utils.call( ["codesign", "--remove-signature", file_path], exit_on_error=False, dry_run=dry_run ) # Add signature worker.utils.call( [ "codesign", "--force", "--timestamp", "--options", "runtime", f"--entitlements={entitlements_path}", "--sign", certificate_id, file_path, ], retry_count=3, dry_run=dry_run, ) if file_path.suffix == ".app": worker.utils.info(f"Vaildating app bundle {file_path}") worker.utils.call( ["codesign", "-vvv", "--deep", "--strict", file_path], dry_run=dry_run ) def sign_darwin(builder: worker.blender.CodeBuilder) -> None: bundle_path = builder.install_dir / "Blender.app" # Executables sign_path = bundle_path / "Contents" / "MacOS" worker.utils.info(f"Collecting files to process in {sign_path}") sign_darwin_files(builder, list(sign_path.rglob("*")), "entitlements.plist") # Thumbnailer app extension. thumbnailer_appex_path = bundle_path / "Contents" / "PlugIns" / "blender-thumbnailer.appex" if thumbnailer_appex_path.exists(): sign_path = thumbnailer_appex_path / "Contents" / "MacOS" worker.utils.info(f"Collecting files to process in {sign_path}") sign_darwin_files(builder, list(sign_path.rglob("*")), "thumbnailer_entitlements.plist") # Shared librarys and Python sign_path = bundle_path / "Contents" / "Resources" worker.utils.info(f"Collecting files to process in {sign_path}") file_paths = list( set(sign_path.rglob("*.dylib")) | set(sign_path.rglob("*.so")) | set(sign_path.rglob("python3.*")) ) sign_darwin_files(builder, file_paths, "entitlements.plist") # Bundle worker.utils.info(f"Signing app bundle {bundle_path}") sign_darwin_files(builder, [bundle_path], "entitlements.plist") def sign(builder: worker.blender.CodeBuilder) -> None: builder.setup_build_environment() if builder.platform == "windows": sign_windows(builder.service_env_id, builder.install_dir) elif builder.platform == "darwin": sign_darwin(builder) else: worker.utils.info("No code signing to be done on this platform")