diff --git a/.gitignore b/.gitignore index 55c39d405..682680f38 100644 --- a/.gitignore +++ b/.gitignore @@ -95,3 +95,6 @@ __pycache__ MSBuild/Robust.Custom.targets .idea/ + + +release/ diff --git a/BuildFiles/Mac/Space Station 14.app/Contents/Info.plist b/BuildFiles/Mac/Space Station 14.app/Contents/Info.plist new file mode 100644 index 000000000..4c1070941 --- /dev/null +++ b/BuildFiles/Mac/Space Station 14.app/Contents/Info.plist @@ -0,0 +1,20 @@ + + + + + CFBundleName + SS14 + CFBundleDisplayName + Space Station 14 + CFBundleExecutable + SS14 + + CFBundleIconFile + ss14 + + diff --git a/BuildFiles/Mac/Space Station 14.app/Contents/MacOS/SS14 b/BuildFiles/Mac/Space Station 14.app/Contents/MacOS/SS14 new file mode 100755 index 000000000..3c47c0b1f --- /dev/null +++ b/BuildFiles/Mac/Space Station 14.app/Contents/MacOS/SS14 @@ -0,0 +1,8 @@ +#!/bin/sh + +# cd to file containing script or something? +BASEDIR=$(dirname "$0") +echo "$BASEDIR" +cd "$BASEDIR" + +exec ../Resources/Robust.Client "$@" diff --git a/BuildFiles/Mac/Space Station 14.app/Contents/Resources/ss14.icns b/BuildFiles/Mac/Space Station 14.app/Contents/Resources/ss14.icns new file mode 100644 index 000000000..cba0912b0 Binary files /dev/null and b/BuildFiles/Mac/Space Station 14.app/Contents/Resources/ss14.icns differ diff --git a/Robust.Client/Robust.Client.csproj b/Robust.Client/Robust.Client.csproj index e6b595a9f..c32393a69 100644 --- a/Robust.Client/Robust.Client.csproj +++ b/Robust.Client/Robust.Client.csproj @@ -4,7 +4,7 @@ false true - Exe + WinExe false ../bin/Client NU1701 diff --git a/Tools/exe_set_subsystem.py b/Tools/exe_set_subsystem.py new file mode 100755 index 000000000..511a4752d --- /dev/null +++ b/Tools/exe_set_subsystem.py @@ -0,0 +1,71 @@ +#!/usr/bin/env python3 + +# exe_set_subsystem + +# Copyright (c) 2020 20kdc +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +import sys +import struct + +if len(sys.argv) != 3: + print("exe_set_subsystem.py ") + print(" alters EXE in-place to change it's subsystem to SUBSYSTEM") + print("") + print("SUBSYSTEM values:") + print(" 2: GUI") + print(" 3: Console") + sys.exit(1) + +file = open(sys.argv[1], "r+b") +if file.read(2) != b"MZ": + print("Header must be 'MZ'.") + sys.exit(2) +file.seek(0x3C) + +peSignatureOfs = struct.unpack(" None: + parser = argparse.ArgumentParser( + description="Packages the Robust client repo for release on all platforms.") + parser.add_argument("--platform", + "-p", + action="store", + choices=[PLATFORM_WINDOWS, PLATFORM_MACOS, PLATFORM_LINUX], + nargs="*", + help="Which platform to build for. If not provided, all platforms will be built") + + parser.add_argument("--skip-build", + action="store_true", + help=argparse.SUPPRESS) + + args = parser.parse_args() + platforms = args.platform + skip_build = args.skip_build + + if not platforms: + platforms = [PLATFORM_WINDOWS, PLATFORM_MACOS, PLATFORM_LINUX] + + if os.path.exists("release"): + print(Fore.BLUE + Style.DIM + + "Cleaning old release packages (release/)..." + Style.RESET_ALL) + shutil.rmtree("release") + + os.mkdir("release") + + if PLATFORM_WINDOWS in platforms: + if not skip_build: + wipe_bin() + build_windows(skip_build) + + if PLATFORM_LINUX in platforms: + if not skip_build: + wipe_bin() + build_linux(skip_build) + + if PLATFORM_LINUX_ARM64 in platforms: + if not skip_build: + wipe_bin() + build_linux_arm64(skip_build) + + if PLATFORM_MACOS in platforms: + if not skip_build: + wipe_bin() + build_macos(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_windows(skip_build: bool) -> None: + # Run a full build. + print(Fore.GREEN + "Building project for Windows x64..." + Style.RESET_ALL) + + if not skip_build: + publish_client("win-x64", "Windows") + subprocess.run(["Tools/download_natives.py", "x64", "Windows", "_", p("bin", "Client", "win-x64")]) + if sys.platform != "win32": + subprocess.run(["Tools/exe_set_subsystem.py", p("bin", "Client", "win-x64", "publish", "Robust.Client"), "2"]) + + + print(Fore.GREEN + "Packaging Windows x64 client..." + Style.RESET_ALL) + + client_zip = zipfile.ZipFile( + p("release", "Robust.Client_win-x64.zip"), "w", + compression=zipfile.ZIP_DEFLATED) + + copy_dir_into_zip(p("bin", "Client", "win-x64", "publish"), "", client_zip) + copy_resources("Resources", client_zip) + # Cool we're done. + client_zip.close() + +def build_macos(skip_build: bool) -> None: + print(Fore.GREEN + "Building project for macOS x64..." + Style.RESET_ALL) + + if not skip_build: + publish_client("osx-x64", "MacOS") + subprocess.run(["Tools/download_natives.py", "x64", "MacOS", "_", p("bin", "Client", "osx-x64")]) + + print(Fore.GREEN + "Packaging macOS x64 client..." + Style.RESET_ALL) + # Client has to go in an app bundle. + client_zip = zipfile.ZipFile(p("release", "Robust.Client_osx-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("bin", "Client", "osx-x64", "publish"), contents, client_zip) + copy_resources(p(contents, "Resources"), client_zip) + client_zip.close() + + +def build_linux(skip_build: bool) -> None: + # Run a full build. + print(Fore.GREEN + "Building project for Linux x64..." + Style.RESET_ALL) + + if not skip_build: + publish_client("linux-x64", "Linux") + subprocess.run(["Tools/download_natives.py", "x64", "Linux", "_", p("bin", "Client", "linux-x64")]) + + print(Fore.GREEN + "Packaging Linux x64 client..." + Style.RESET_ALL) + + client_zip = zipfile.ZipFile( + p("release", "Robust.Client_linux-x64.zip"), "w", + compression=zipfile.ZIP_DEFLATED) + + copy_dir_into_zip(p("bin", "Client", "linux-x64", "publish"), "", client_zip) + copy_resources("Resources", client_zip) + # Cool we're done. + client_zip.close() + + + +def build_linux_arm64(skip_build: bool) -> None: + # Run a full build. + # TODO: Linux-arm64 is currently server-only. + pass +""" print(Fore.GREEN + "Building project for Linux ARM64 (SERVER ONLY)..." + Style.RESET_ALL) + + if not skip_build: + subprocess.run([ + "dotnet", + "build", + "SpaceStation14.sln", + "-c", "Release", + "--nologo", + "/v:m", + "/p:TargetOS=Linux", + "/t:Rebuild", + "/p:FullRelease=True" + ], check=True) + + publish_client("linux-arm64", "Linux", True) + + 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) + server_zip.close()""" + + +def publish_client(runtime: str, target_os: str) -> None: + base = [ + "dotnet", "publish", + "--runtime", runtime, + "--no-self-contained", + "-c", "Release", + f"/p:TargetOS={target_os}", + "/p:FullRelease=True" + ] + + subprocess.run(base + ["Robust.Client/Robust.Client.csproj"], check=True) + + +def copy_resources(target, zipf): + do_resource_copy(target, "Resources", zipf, IGNORED_RESOURCES) + + +def do_resource_copy(target, source, zipf, ignore_set): + for filename in os.listdir(source): + if filename in ignore_set: + continue + + path = p(source, filename) + target_path = p(target, 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_dir_or_file(src: str, dst: str): + """ + Just something from src to dst. If src is a dir it gets copied recursively. + """ + + if os.path.isfile(src): + shutil.copy2(src, dst) + + elif os.path.isdir(src): + shutil.copytree(src, dst) + + else: + raise IOError("{} is neither file nor directory. Can't copy.".format(src)) + + +if __name__ == '__main__': + main()