# SPDX-License-Identifier: GPL-2.0-or-later # SPDX-FileCopyrightText: 2011-2024 Blender Authors # import json import os import pathlib import time import worker.blender.version import worker.deploy import worker.utils def extract_file( builder: worker.deploy.CodeStoreBuilder, source_file_path: pathlib.Path, platform: str, ) -> None: worker.utils.info(f"Extracting artifact [{source_file_path}] for Steam") if not source_file_path.exists(): raise Exception("File not found, aborting") dest_extract_path = builder.store_steam_dir / platform dest_content_path = dest_extract_path / "content" worker.utils.remove_dir(dest_extract_path) worker.utils.remove_dir(dest_content_path) os.makedirs(dest_extract_path, exist_ok=True) if platform == "linux": worker.utils.info(f"Extract [{source_file_path}] to [{dest_extract_path}]") cmd = ["tar", "-xf", source_file_path, "--directory", dest_extract_path] worker.utils.call(cmd) # Move any folder there as ./content for source_content_path in dest_extract_path.iterdir(): if source_content_path.is_dir(): worker.utils.info( f"Move [{source_content_path.name}] -> [{dest_content_path}]" ) worker.utils.move(source_content_path, dest_content_path) break elif platform == "darwin": source_content_path = dest_extract_path / "Blender" if source_content_path.exists(): worker.utils.info(f"Removing [{source_content_path}]") worker.utils.remove_dir(source_content_path) image_file_path = source_file_path.with_suffix(".img") cmd = ["dmg2img", "-v", "-i", source_file_path, "-o", image_file_path] worker.utils.call(cmd) cmd = ["7z", "x", f"-o{dest_extract_path}", image_file_path] worker.utils.call(cmd) os.makedirs(dest_content_path, exist_ok=True) worker.utils.remove_file(image_file_path) worker.utils.info( f"Move Blender app from [{source_content_path}] -> [{dest_content_path}]" ) worker.utils.move( source_content_path / "Blender.app", dest_content_path / "Blender.app" ) worker.utils.remove_dir(source_content_path) elif platform == "windows": worker.utils.info(f"Extracting zip file [{source_file_path}]") cmd = ["7z", "x", f"-o{dest_extract_path}", source_file_path] worker.utils.call(cmd) # Move any folder there as ./content for source_content_path in dest_extract_path.iterdir(): if source_content_path.is_dir(): worker.utils.info( f"Move [{source_content_path.name}] -> [{dest_content_path}]" ) worker.utils.move(source_content_path, dest_content_path) break else: raise Exception(f"Don't know how to extract for platform [{platform}]") def extract(builder: worker.deploy.CodeStoreBuilder) -> None: package_manifest = builder.package_dir / "manifest.json" builds = json.loads(package_manifest.read_text()) for build in builds: if build["file_extension"] not in ["zip", "tar.xz", "dmg"]: continue if build["architecture"] == "arm64": continue file_path = builder.package_dir / build["file_name"] platform = build["platform"] extract_file(builder, file_path, platform) def build(builder: worker.deploy.CodeStoreBuilder, is_preview: bool) -> None: dry_run = False if builder.service_env_id == "LOCAL": worker.utils.warning("Performing dry run on LOCAL service environment") dry_run = True version_info = worker.blender.version.VersionInfo(builder) branches_config = builder.get_branches_config() is_lts = builder.track_id in branches_config.all_lts_tracks is_latest = ( branches_config.track_major_minor_versions["vdev"] == version_info.short_version ) log_path = builder.track_path / "log" worker.utils.remove_dir(log_path) os.makedirs(log_path, exist_ok=True) worker_config = builder.get_worker_config() steam_credentials = worker_config.steam_credentials(builder.service_env_id) steam_user_id, steam_user_password = steam_credentials if not steam_user_id or not steam_user_password: if not dry_run: raise Exception("Steam user id or password not available, aborting") env = os.environ.copy() env["PATH"] = env["PATH"] + os.pathsep + "/usr/games" cmd: worker.utils.CmdSequence = [ "steamcmd", "+login", worker.utils.HiddenArgument(steam_user_id), worker.utils.HiddenArgument(steam_user_password), "+quit", ] worker.utils.call(cmd, dry_run=dry_run, env=env) worker.utils.info("Waiting 5 seconds for next steam command") time.sleep(5.0) steam_app_id = worker_config.steam_app_id steam_platform_depot_ids = worker_config.steam_platform_depot_ids for platform_id in ["linux", "darwin", "windows"]: worker.utils.info(f"Platform {platform_id}") platform_depot_id = steam_platform_depot_ids[platform_id] track_build_root_path = builder.store_steam_dir / platform_id if not track_build_root_path.exists(): raise Exception(f"Folder {track_build_root_path} does not exist") platform_build_file_path = track_build_root_path / "depot_build.vdf" source_root_path = track_build_root_path / "content" if not source_root_path.exists(): raise Exception(f"Folder {source_root_path} does not exist") dest_root_path = track_build_root_path / "output" # Steam branches cannot be uper case and no spaces allowed # Branches are named "daily" and "devtest" on Steam, so rename those. steam_branch_id = builder.service_env_id.lower() steam_branch_id = steam_branch_id.replace("prod", "daily") steam_branch_id = steam_branch_id.replace("uatest", "devtest") if is_lts: # daily-X.X and devtest-X.X branches for LTS. steam_branch_id = f"{steam_branch_id}-{version_info.short_version}" elif is_latest: # daily and devtest branches for main without suffix. pass else: # Not setting this live. steam_branch_id = "" preview = "1" if is_preview else "0" app_build_script = f""" "appbuild" {{ "appid" "{steam_app_id}" "desc" "Blender {version_info.version}" // description for this build "buildoutput" "{dest_root_path}" // build output folder for .log, .csm & .csd files, relative to location of this file "contentroot" "{source_root_path}" // root content folder, relative to location of this file "setlive" "{steam_branch_id}" // branch to set live after successful build, non if empty "preview" "{preview}" // 1 to enable preview builds, 0 to commit build to steampipe "local" "" // set to file path of local content server "depots" {{ "{platform_depot_id}" "{platform_build_file_path}" }} }} """ platform_build_script = f""" "DepotBuildConfig" {{ // Set your assigned depot ID here "DepotID" "{platform_depot_id}" // Set a root for all content. // All relative paths specified below (LocalPath in FileMapping entries, and FileExclusion paths) // will be resolved relative to this root. // If you don't define ContentRoot, then it will be assumed to be // the location of this script file, which probably isn't what you want "ContentRoot" "{source_root_path}" // include all files recursivley "FileMapping" {{ // This can be a full path, or a path relative to ContentRoot "LocalPath" "*" // This is a path relative to the install folder of your game "DepotPath" "." // If LocalPath contains wildcards, setting this means that all // matching files within subdirectories of LocalPath will also // be included. "recursive" "1" }} // but exclude all symbol files // This can be a full path, or a path relative to ContentRoot //"FileExclusion" "*.pdb" }} """ (track_build_root_path / "app_build.vdf").write_text(app_build_script) platform_build_file_path.write_text(platform_build_script) worker.utils.info( f"Version [{version_info.version}] for [{platform_id}] in preview [{is_preview}] for steam branch [{steam_branch_id}], building" ) cmd = [ "steamcmd", "+login", worker.utils.HiddenArgument(steam_user_id), worker.utils.HiddenArgument(steam_user_password), "+run_app_build", track_build_root_path / "app_build.vdf", "+quit", ] retry_count = 0 if preview else 3 worker.utils.call( cmd, retry_count=retry_count, retry_wait_time=120, dry_run=dry_run, env=env ) worker.utils.info("Waiting 5 seconds for next steam command") time.sleep(5.0) worker.utils.info( f"Version [{version_info.version}] for [{platform_id}] in preview [{is_preview}] is done, success" ) def package(builder: worker.deploy.CodeStoreBuilder) -> None: worker.utils.remove_dir(builder.store_steam_dir) os.makedirs(builder.store_steam_dir, exist_ok=True) # Extract and prepare content extract(builder) build(builder, is_preview=True) def deliver(builder: worker.deploy.CodeStoreBuilder) -> None: # This will push to the store build(builder, is_preview=False)