diff --git a/Jenkinsfile b/Jenkinsfile index 5fc5bacb9e..dfcd04b25e 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -9,7 +9,8 @@ pipeline { } stage('Build') { steps { - sh 'Tools/package_release_build.py -p windows mac linux linux-arm64' + sh 'Tools/package_server_build.py -p windows mac linux linux-arm64' + sh 'Tools/package_client_build.py' } } stage('Update build info') { diff --git a/RobustToolbox b/RobustToolbox index 78ceaa50d5..7ef2cec121 160000 --- a/RobustToolbox +++ b/RobustToolbox @@ -1 +1 @@ -Subproject commit 78ceaa50d5bec5ac85c610acbe0374b107133ed0 +Subproject commit 7ef2cec121edb831a73a0e02d4e7283b4f3d08e5 diff --git a/SpaceStation14.sln b/SpaceStation14.sln index c9e65b467e..0095624659 100644 --- a/SpaceStation14.sln +++ b/SpaceStation14.sln @@ -57,7 +57,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build", "Build", "{806ED41A Tools\gen_build_info.py = Tools\gen_build_info.py Tools\generate_hashes.ps1 = Tools\generate_hashes.ps1 Jenkinsfile = Jenkinsfile - Tools\package_release_build.py = Tools\package_release_build.py + Tools\package_client_build.py = Tools\package_client_build.py + Tools\package_server_build.py = Tools\package_server_build.py EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Robust.Shared.Scripting", "RobustToolbox\Robust.Shared.Scripting\Robust.Shared.Scripting.csproj", "{41B450C0-A361-4CD7-8121-7072B8995CFC}" diff --git a/Tools/gen_build_info.py b/Tools/gen_build_info.py index b988609d67..5fc8f5f733 100755 --- a/Tools/gen_build_info.py +++ b/Tools/gen_build_info.py @@ -5,13 +5,10 @@ import hashlib import json import os +import subprocess from zipfile import ZipFile, ZIP_DEFLATED -FILES = { - "linux": "SS14.Client_Linux_x64.zip", - "windows": "SS14.Client_Windows_x64.zip", - "macos": "SS14.Client_macOS_x64.zip" -} +FILE = "SS14.Client.zip" SERVER_FILES = [ "SS14.Server_Linux_x64.zip", @@ -38,17 +35,25 @@ def inject_manifest(zip_path: str, manifest: str) -> None: def generate_manifest(dir: str) -> str: # Env variables set by Jenkins. - jenkins_base = f"{os.environ['BUILD_URL']}artifact/release/" - version = os.environ["BUILD_NUMBER"] - downloads = {} - hashes = {} + download = f"{os.environ['BUILD_URL']}artifact/release/{FILE}" + hash = sha256_file(os.path.join(dir, FILE)) + engine_version = get_engine_version() - for platform, filename in FILES.items(): - downloads[platform] = f"{jenkins_base}{filename}" - hashes[platform] = sha256_file(os.path.join(dir, filename)) + return json.dumps({ + "download": download, + "hash": hash, + "version": version, + "fork_id": FORK_ID, + "engine_version": engine_version + }) - return json.dumps({"downloads": downloads, "hashes": hashes, "version": version, "fork_id": FORK_ID}) + +def get_engine_version() -> str: + proc = subprocess.run(["git", "describe", "--tags", "--abbrev=0"], stdout=subprocess.PIPE, cwd="RobustToolbox", check=True, encoding="UTF-8") + tag = proc.stdout.strip() + assert tag.startswith("v") + return tag[1:] # Cut off v prefix. def sha256_file(path: str) -> str: diff --git a/Tools/package_client_build.py b/Tools/package_client_build.py new file mode 100755 index 0000000000..6061312365 --- /dev/null +++ b/Tools/package_client_build.py @@ -0,0 +1,187 @@ +#!/usr/bin/env python3 + +import os +import shutil +import subprocess +import sys +import zipfile +import argparse + +from typing import List, Optional + +try: + from colorama import init, Fore, Style + init() + +except ImportError: + # Just give an empty string for everything, no colored logging. + class ColorDummy(object): + def __getattr__(self, name): + return "" + + Fore = ColorDummy() + Style = ColorDummy() + + +p = os.path.join + +SHARED_IGNORED_RESOURCES = { + "ss13model.7z", + "ResourcePack.zip", + "buildResourcePack.py", + "CONTENT_GOES_HERE", + ".gitignore", + ".directory", + ".DS_Store" +} + +CLIENT_IGNORED_RESOURCES = { + "Maps", + "emotes.xml", + "Groups" +} + +CLIENT_CONTENT_ASSEMBLIES = [ + "Content.Client", + "Content.Shared" +] + +def main() -> None: + parser = argparse.ArgumentParser( + description="Packages the SS14 client.") + + parser.add_argument("--skip-build", + action="store_true", + help=argparse.SUPPRESS) + + args = parser.parse_args() + skip_build = args.skip_build + + if os.path.exists("release"): + pass + # print(Fore.BLUE + Style.DIM + + # "Cleaning old release packages (release/)..." + Style.RESET_ALL) + # shutil.rmtree("release") + else: + os.mkdir("release") + + if not skip_build: + wipe_bin() + + build(skip_build) + + +def wipe_bin(): + print(Fore.BLUE + Style.DIM + + "Clearing old build artifacts (if any)..." + Style.RESET_ALL) + + if os.path.exists("bin"): + shutil.rmtree("bin") + + +def build(skip_build: bool) -> None: + # Run a full build. + print(Fore.GREEN + "Building project..." + Style.RESET_ALL) + + if not skip_build: + subprocess.run([ + "dotnet", + "build", + p("Content.Client", "Content.Client.csproj"), + "-c", "Release", + "--nologo", + "/v:m", + "/t:Rebuild", + "/p:FullRelease=True" + ], check=True) + + print(Fore.GREEN + "Packaging client..." + Style.RESET_ALL) + + client_zip = zipfile.ZipFile( + p("release", "SS14.Client.zip"), "w", + compression=zipfile.ZIP_DEFLATED) + + copy_resources(client_zip) + copy_content_assemblies("Assemblies", client_zip) + # Cool we're done. + client_zip.close() + + +def copy_resources(zipf): + ignore_set = SHARED_IGNORED_RESOURCES.union(CLIENT_IGNORED_RESOURCES) + + do_resource_copy("Resources", zipf, ignore_set) + + +def do_resource_copy(source, zipf, ignore_set): + for filename in os.listdir(source): + if filename in ignore_set: + continue + + path = p(source, filename) + target_path = filename + if os.path.isdir(path): + copy_dir_into_zip(path, target_path, zipf) + + else: + zipf.write(path, target_path) + + +def zip_entry_exists(zipf, name): + try: + # Trick ZipInfo into sanitizing the name for us so this awful module stops spewing warnings. + zinfo = zipfile.ZipInfo.from_file("Resources", name) + zipf.getinfo(zinfo.filename) + except KeyError: + return False + return True + + +def copy_dir_into_zip(directory, basepath, zipf): + if basepath and not zip_entry_exists(zipf, basepath): + zipf.write(directory, basepath) + + for root, _, files in os.walk(directory): + relpath = os.path.relpath(root, directory) + if relpath != "." and not zip_entry_exists(zipf, p(basepath, relpath)): + zipf.write(root, p(basepath, relpath)) + + for filename in files: + zippath = p(basepath, relpath, filename) + filepath = p(root, filename) + + message = "{dim}{diskroot}{sep}{zipfile}{dim} -> {ziproot}{sep}{zipfile}".format( + sep=os.sep + Style.NORMAL, + dim=Style.DIM, + diskroot=directory, + ziproot=zipf.filename, + zipfile=os.path.normpath(zippath)) + + print(Fore.CYAN + message + Style.RESET_ALL) + zipf.write(filepath, zippath) + + +def copy_content_assemblies(target, zipf): + files = [] + + source_dir = p("bin", "Content.Client") + base_assemblies = CLIENT_CONTENT_ASSEMBLIES + + # Include content assemblies. + for asm in base_assemblies: + files.append(asm + ".dll") + # If PDB available, include it aswell. + pdb_path = asm + ".pdb" + if os.path.exists(p(source_dir, pdb_path)): + files.append(pdb_path) + + # Write assemblies dir if necessary. + if not zip_entry_exists(zipf, target): + zipf.write(".", target) + + for x in files: + zipf.write(p(source_dir, x), f"{target}/{x}") + + +if __name__ == '__main__': + main() diff --git a/Tools/package_release_build.py b/Tools/package_server_build.py similarity index 74% rename from Tools/package_release_build.py rename to Tools/package_server_build.py index e01dab6117..751ab79603 100755 --- a/Tools/package_release_build.py +++ b/Tools/package_server_build.py @@ -40,11 +40,7 @@ SHARED_IGNORED_RESOURCES = { ".directory", ".DS_Store" } -CLIENT_IGNORED_RESOURCES = { - "Maps", - "emotes.xml", - "Groups" -} + SERVER_IGNORED_RESOURCES = { "Textures", "Fonts", @@ -54,11 +50,6 @@ SERVER_IGNORED_RESOURCES = { "Shaders", } -LAUNCHER_RESOURCES = { - "Nano", - "Fonts", -} - # Assembly names to copy from content. # PDBs are included if available, .dll/.pdb appended automatically. SERVER_CONTENT_ASSEMBLIES = [ @@ -67,11 +58,6 @@ SERVER_CONTENT_ASSEMBLIES = [ "Content.Shared" ] -CLIENT_CONTENT_ASSEMBLIES = [ - "Content.Client", - "Content.Shared" -] - # Extra assemblies to copy on the server, with a startswith SERVER_EXTRA_ASSEMBLIES = [ "Npgsql.", @@ -97,7 +83,7 @@ def main() -> None: skip_build = args.skip_build if not platforms: - platforms = [PLATFORM_WINDOWS, PLATFORM_MACOS, PLATFORM_LINUX] + platforms = [PLATFORM_WINDOWS, PLATFORM_MACOS, PLATFORM_LINUX, PLATFORM_LINUX_ARM64] if os.path.exists("release"): print(Fore.BLUE + Style.DIM + @@ -145,7 +131,7 @@ def build_windows(skip_build: bool) -> None: subprocess.run([ "dotnet", "build", - "SpaceStation14.sln", + p("Content.Server", "Content.Server.csproj"), "-c", "Release", "--nologo", "/v:m", @@ -156,24 +142,12 @@ def build_windows(skip_build: bool) -> None: publish_client_server("win-x64", "Windows") - print(Fore.GREEN + "Packaging Windows x64 client..." + Style.RESET_ALL) - - client_zip = zipfile.ZipFile( - p("release", "SS14.Client_Windows_x64.zip"), "w", - compression=zipfile.ZIP_DEFLATED) - - copy_dir_into_zip(p("RobustToolbox", "bin", "Client", "win-x64", "publish"), "", client_zip) - copy_resources("Resources", client_zip, server=False) - copy_content_assemblies(p("Resources", "Assemblies"), client_zip, server=False) - # Cool we're done. - client_zip.close() - print(Fore.GREEN + "Packaging Windows x64 server..." + Style.RESET_ALL) server_zip = zipfile.ZipFile(p("release", "SS14.Server_Windows_x64.zip"), "w", compression=zipfile.ZIP_DEFLATED) copy_dir_into_zip(p("RobustToolbox", "bin", "Server", "win-x64", "publish"), "", server_zip) - copy_resources(p("Resources"), server_zip, server=True) - copy_content_assemblies(p("Resources", "Assemblies"), server_zip, server=True) + copy_resources(p("Resources"), server_zip) + copy_content_assemblies(p("Resources", "Assemblies"), server_zip) server_zip.close() def build_macos(skip_build: bool) -> None: @@ -183,7 +157,7 @@ def build_macos(skip_build: bool) -> None: subprocess.run([ "dotnet", "build", - "SpaceStation14.sln", + p("Content.Server", "Content.Server.csproj"), "-c", "Release", "--nologo", "/v:m", @@ -194,24 +168,12 @@ def build_macos(skip_build: bool) -> None: publish_client_server("osx-x64", "MacOS") - print(Fore.GREEN + "Packaging macOS x64 client..." + Style.RESET_ALL) - # Client has to go in an app bundle. - client_zip = zipfile.ZipFile(p("release", "SS14.Client_macOS_x64.zip"), "a", - compression=zipfile.ZIP_DEFLATED) - - contents = p("Space Station 14.app", "Contents", "Resources") - copy_dir_into_zip(p("BuildFiles", "Mac", "Space Station 14.app"), "Space Station 14.app", client_zip) - copy_dir_into_zip(p("RobustToolbox", "bin", "Client", "osx-x64", "publish"), contents, client_zip) - copy_resources(p(contents, "Resources"), client_zip, server=False) - copy_content_assemblies(p(contents, "Resources", "Assemblies"), client_zip, server=False) - client_zip.close() - print(Fore.GREEN + "Packaging macOS x64 server..." + Style.RESET_ALL) server_zip = zipfile.ZipFile(p("release", "SS14.Server_macOS_x64.zip"), "w", compression=zipfile.ZIP_DEFLATED) copy_dir_into_zip(p("RobustToolbox", "bin", "Server", "osx-x64", "publish"), "", server_zip) - copy_resources(p("Resources"), server_zip, server=True) - copy_content_assemblies(p("Resources", "Assemblies"), server_zip, server=True) + copy_resources(p("Resources"), server_zip) + copy_content_assemblies(p("Resources", "Assemblies"), server_zip) server_zip.close() @@ -223,7 +185,7 @@ def build_linux(skip_build: bool) -> None: subprocess.run([ "dotnet", "build", - "SpaceStation14.sln", + p("Content.Server", "Content.Server.csproj"), "-c", "Release", "--nologo", "/v:m", @@ -234,24 +196,12 @@ def build_linux(skip_build: bool) -> None: publish_client_server("linux-x64", "Linux") - print(Fore.GREEN + "Packaging Linux x64 client..." + Style.RESET_ALL) - - client_zip = zipfile.ZipFile( - p("release", "SS14.Client_Linux_x64.zip"), "w", - compression=zipfile.ZIP_DEFLATED) - - copy_dir_into_zip(p("RobustToolbox", "bin", "Client", "linux-x64", "publish"), "", client_zip) - copy_resources("Resources", client_zip, server=False) - copy_content_assemblies(p("Resources", "Assemblies"), client_zip, server=False) - # Cool we're done. - client_zip.close() - print(Fore.GREEN + "Packaging Linux x64 server..." + Style.RESET_ALL) server_zip = zipfile.ZipFile(p("release", "SS14.Server_Linux_x64.zip"), "w", compression=zipfile.ZIP_DEFLATED) copy_dir_into_zip(p("RobustToolbox", "bin", "Server", "linux-x64", "publish"), "", server_zip) - copy_resources(p("Resources"), server_zip, server=True) - copy_content_assemblies(p("Resources", "Assemblies"), server_zip, server=True) + copy_resources(p("Resources"), server_zip) + copy_content_assemblies(p("Resources", "Assemblies"), server_zip) server_zip.close() @@ -263,7 +213,7 @@ def build_linux_arm64(skip_build: bool) -> None: subprocess.run([ "dotnet", "build", - "SpaceStation14.sln", + p("Content.Server", "Content.Server.csproj"), "-c", "Release", "--nologo", "/v:m", @@ -272,18 +222,18 @@ def build_linux_arm64(skip_build: bool) -> None: "/p:FullRelease=True" ], check=True) - publish_client_server("linux-arm64", "Linux", True) + publish_client_server("linux-arm64", "Linux") print(Fore.GREEN + "Packaging Linux ARM64 server..." + Style.RESET_ALL) server_zip = zipfile.ZipFile(p("release", "SS14.Server_Linux_ARM64.zip"), "w", compression=zipfile.ZIP_DEFLATED) copy_dir_into_zip(p("RobustToolbox", "bin", "Server", "linux-arm64", "publish"), "", server_zip) - copy_resources(p("Resources"), server_zip, server=True) - copy_content_assemblies(p("Resources", "Assemblies"), server_zip, server=True) + copy_resources(p("Resources"), server_zip) + copy_content_assemblies(p("Resources", "Assemblies"), server_zip) server_zip.close() -def publish_client_server(runtime: str, target_os: str, actually_only_server: bool = False) -> None: +def publish_client_server(runtime: str, target_os: str) -> None: # Runs dotnet publish on client and server. base = [ "dotnet", "publish", @@ -294,19 +244,12 @@ def publish_client_server(runtime: str, target_os: str, actually_only_server: bo "/p:FullRelease=True" ] - if not actually_only_server: - subprocess.run(base + ["RobustToolbox/Robust.Client/Robust.Client.csproj"], check=True) - subprocess.run(base + ["RobustToolbox/Robust.Server/Robust.Server.csproj"], check=True) -def copy_resources(target, zipf, server): +def copy_resources(target, zipf): # Content repo goes FIRST so that it won't override engine files as that's forbidden. - ignore_set = SHARED_IGNORED_RESOURCES - if server: - ignore_set = ignore_set.union(SERVER_IGNORED_RESOURCES) - else: - ignore_set = ignore_set.union(CLIENT_IGNORED_RESOURCES) + ignore_set = SHARED_IGNORED_RESOURCES.union(SERVER_IGNORED_RESOURCES) do_resource_copy(target, "Resources", zipf, ignore_set) do_resource_copy(target, p("RobustToolbox", "Resources"), zipf, ignore_set) @@ -367,22 +310,17 @@ def copy_dir_into_zip(directory, basepath, zipf): zipf.write(filepath, zippath) -def copy_content_assemblies(target, zipf, server): +def copy_content_assemblies(target, zipf): files = [] - if server: - source_dir = p("bin", "Content.Server") - base_assemblies = SERVER_CONTENT_ASSEMBLIES + source_dir = p("bin", "Content.Server") + base_assemblies = SERVER_CONTENT_ASSEMBLIES - # Additional assemblies that need to be copied such as EFCore. - for filename in os.listdir(source_dir): - for extra_assembly_start in SERVER_EXTRA_ASSEMBLIES: - if filename.startswith(extra_assembly_start): - files.append(filename) - break - - else: - source_dir = p("bin", "Content.Client") - base_assemblies = CLIENT_CONTENT_ASSEMBLIES + # Additional assemblies that need to be copied such as EFCore. + for filename in os.listdir(source_dir): + for extra_assembly_start in SERVER_EXTRA_ASSEMBLIES: + if filename.startswith(extra_assembly_start): + files.append(filename) + break # Include content assemblies. for asm in base_assemblies: