260 lines
		
	
	
	
		
			9.3 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			260 lines
		
	
	
	
		
			9.3 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| # SPDX-License-Identifier: GPL-2.0-or-later
 | |
| # SPDX-FileCopyrightText: 2011-2024 Blender Authors
 | |
| # <pep8 compliant>
 | |
| 
 | |
| 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
 | |
| 
 | |
|     track_path = builder.track_path
 | |
|     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)
 |