builder.braak.pro/config/worker/deploy/steam.py
2024-11-19 21:59:53 +01:00

271 lines
9.4 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
)
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)