#!/usr/bin/env python3 # # Copyright (c) Contributors to the Open 3D Engine Project. # For complete copyright and license terms please see the LICENSE at the root of this distribution. # # SPDX-License-Identifier: Apache-2.0 OR MIT # # import argparse import glob import json import os import pathlib import subprocess import shutil import sys import urllib.request import zipfile from typing import List # O3DE_PACKAGE_NAME is given in the format -- O3DE_PACKAGE_NAME = os.environ['O3DE_PACKAGE_NAME'] O3DE_PACKAGE_NAME_PARTS = O3DE_PACKAGE_NAME.split('-') PACKAGE_NAME: str = "AWSGameLiftServerSDK" PACKAGE_PLATFORM: str = "" PACKAGE_URL: str = "https://aws.amazon.com/documentation/gamelift/" PACKAGE_LICENSE: str = "Apache-2.0" PACKAGE_LICENSE_FILE: str = "LICENSE_AMAZON_GAMELIFT_SDK.TXT" GAMELIFT_SERVER_SDK_RELEASE_VERSION: str = O3DE_PACKAGE_NAME_PARTS[1] GAMELIFT_SERVER_SDK_DOWNLOAD_URL: str = "https://gamelift-server-sdk-release.s3.us-west-2.amazonaws.com/cpp/GameLift-Cpp-ServerSDK-5.1.2.zip" PACKAGE_BASE_PATH: pathlib.Path = pathlib.Path(os.path.dirname(__file__)) PACKAGE_ROOT_PATH: pathlib.Path = PACKAGE_BASE_PATH.parent REPO_ROOT_PATH: pathlib.Path = PACKAGE_ROOT_PATH.parent GENERAL_SCRIPTS_PATH = REPO_ROOT_PATH / 'Scripts' / 'extras' PACKAGE_BUILD_TYPES: List[str] = ["Debug", "Release"] PACKAGE_LIB_TYPES: List[str] = ["Shared", "Static"] PACKAGE_PLATFORM_OPTIONS: List[str] = ["windows", "linux", "linux-aarch64"] # Insert the 3p scripts path so we can use the package downloader to # download the openssl dependency sys.path.insert(1, str(GENERAL_SCRIPTS_PATH.resolve())) from package_downloader import PackageDownloader # Lookup table for the Find{PACKAGE_NAME}.cmake.{platform} source file to copy in case there are shared files across different platforms FIND_CMAKE_PLATFORM_SUFFIX_BY_PLATFORM = { 'Windows': 'Windows', 'Linux': 'Linux', 'Linux-Aarch64': 'Linux' } # utils class WorkingDirectoryInfo(object): def __init__(self, root_path: pathlib.Path, source_path: pathlib.Path, build_path: pathlib.Path, output_path: pathlib.Path, libs_output_path: pathlib.Path) -> None: self.root_path = root_path self.source_path = source_path self.build_path = build_path self.output_path = output_path self.libs_output_path = libs_output_path self.dependencies_folder_path = (self.root_path / 'dependencies').resolve() # OpenSSL dependency: (packagename, hash) OPENSSL_PACKAGE = ('OpenSSL-1.1.1o-rev1-windows', '52b9d2bc5f3e0c6e405a0f290d1904bf545acc0c73d6a52351247d917c4a06d2') def get_dependencies(working_directory: WorkingDirectoryInfo) -> None: # Only windows has an additional dependency on openssl that we need to download from our own packages # The linux builds also rely on openssl, but they use the version on the system if PACKAGE_PLATFORM != 'windows': return print("Downloading dependencies...") package_name = OPENSSL_PACKAGE[0] package_hash = OPENSSL_PACKAGE[1] if not (working_directory.dependencies_folder_path / package_name).exists(): if not PackageDownloader.DownloadAndUnpackPackage(package_name, package_hash, str(working_directory.dependencies_folder_path)): raise Exception("Failed to download OpenSSL dependency!") else: print(f'OpenSSL already in dependencies folder, skipping. Use --clean to refresh') def subp_args(args) -> str: arg_string = " ".join([arg for arg in args]) print(f"Command: {arg_string}") return arg_string def delete_folder(folder: pathlib.Path) -> None: shutil.rmtree(folder.resolve()) def copy_file_to_destination(file: str, destination: str) -> None: print(f"Copying {file} to {destination}") shutil.copy2(file, destination) # collect package build required information, like platform def collect_package_info() -> None: parser = argparse.ArgumentParser() parser.add_argument( '--platform-name', required=True, choices=PACKAGE_PLATFORM_OPTIONS ) args = parser.parse_args() global PACKAGE_PLATFORM PACKAGE_PLATFORM = args.platform_name # prepare working directories for package build def prepare_working_directory() -> WorkingDirectoryInfo: # root directory root_directory: pathlib.Path = PACKAGE_BASE_PATH.joinpath("temp") if root_directory.is_dir(): delete_folder(root_directory) # source and build directory source_directory = root_directory build_directory: pathlib.Path = root_directory.joinpath("build") build_directory.mkdir(parents=True) # build output and libs output directory build_output_directory: pathlib.Path = PACKAGE_ROOT_PATH.joinpath(f"{PACKAGE_NAME}-{PACKAGE_PLATFORM}") if build_output_directory.is_dir(): delete_folder(build_output_directory) build_libs_output_directory: pathlib.Path = build_output_directory.joinpath(f"{PACKAGE_NAME}") build_libs_output_directory.mkdir(parents=True) return WorkingDirectoryInfo(root_directory, source_directory, build_directory, build_output_directory, build_libs_output_directory) # download gamelift server server sdk source code for package build def download_gamelift_server_sdk(working_directory: WorkingDirectoryInfo) -> None: # download sdk from url gamelift_sdk_zip_file: str = str(working_directory.root_path.joinpath("gamelift_server_sdk.zip").resolve()) with urllib.request.urlopen(GAMELIFT_SERVER_SDK_DOWNLOAD_URL) as response, open(gamelift_sdk_zip_file, 'wb') as out_file: shutil.copyfileobj(response, out_file) # unzip sdk contents with zipfile.ZipFile(gamelift_sdk_zip_file, "r") as f: unzip_path = working_directory.root_path.resolve() f.extractall(unzip_path) # get required custom environment for package build def get_custom_build_env(): if PACKAGE_PLATFORM in (PACKAGE_PLATFORM_OPTIONS[1], PACKAGE_PLATFORM_OPTIONS[2]): custom_env = os.environ.copy() custom_env["CC"] = "gcc" custom_env["CXX"] = "g++" custom_env["CFLAGS"] = "-fPIC" custom_env["CXXFLAGS"] = "-fPIC" return custom_env return None # configure gamelift server sdk project def configure_sdk_project(working_directory: WorkingDirectoryInfo, build_folder: str, build_type: str, lib_type: str) -> None: source_folder: str = working_directory.source_path.resolve() build_shared: str = "ON" if lib_type == "Shared" else "OFF" if PACKAGE_PLATFORM == PACKAGE_PLATFORM_OPTIONS[0]: generator: str = '-G "Visual Studio 17 2022" -A x64' elif PACKAGE_PLATFORM in (PACKAGE_PLATFORM_OPTIONS[1], PACKAGE_PLATFORM_OPTIONS[2]): generator: str = "-G \"Unix Makefiles\"" else: raise Exception(f"Error unsupported platform: {PACKAGE_PLATFORM}") configure_cmd: List[str] = [f"cmake {generator} -S .", f"-B {build_folder}", f"-DBUILD_SHARED_LIBS={build_shared}", f"-DCMAKE_BUILD_TYPE={build_type}"] # On windows add our OpenSSL dependency to the DCMAKE_MODULE_PATH so the # GameLift build can find it if PACKAGE_PLATFORM == 'windows': package_name = OPENSSL_PACKAGE[0] openssl_dependency_path = (working_directory.dependencies_folder_path / package_name).resolve().as_posix() configure_cmd.append(f"-DCMAKE_MODULE_PATH=\"{openssl_dependency_path}\"") configure_result = subprocess.run(subp_args(configure_cmd), shell=True, capture_output=True, cwd=source_folder, env=get_custom_build_env()) if configure_result.returncode != 0: raise Exception(f"Error generating project: {configure_result.stderr.decode()}") # build gamelift server sdk project def build_sdk_project(source_folder: str, build_folder: str, build_type: str) -> None: target = "--target aws-cpp-sdk-gamelift-server" build_cmd: List[str] = ["cmake", f"--build {build_folder}", f"--config {build_type}", f"{target} -j"] build_result = subprocess.run(subp_args(build_cmd), shell=True, capture_output=True, cwd=source_folder, env=get_custom_build_env()) if build_result.returncode != 0: raise Exception(f"Error building project: {build_result.stderr.decode()}") # copy all built gamelift server sdk libs into expected output folder def copy_sdk_libs(libs_output_path: pathlib.Path, build_path: pathlib.Path, build_type: str, lib_type: str) -> None: if lib_type == PACKAGE_LIB_TYPES[0]: destination: pathlib.Path = libs_output_path.joinpath(f"bin/{build_type}") else: destination: pathlib.Path = libs_output_path.joinpath(f"lib/{build_type}") destination.mkdir(parents=True) install_folder: pathlib.Path = build_path.joinpath("prefix") if PACKAGE_PLATFORM == PACKAGE_PLATFORM_OPTIONS[0]: shared_libs_pattern: str = str(install_folder.joinpath("bin/*.dll")) static_libs_pattern: str = str(install_folder.joinpath("lib/*.lib")) # for windows, it always requires .lib file, .dll file is only required for shared libs if lib_type == PACKAGE_LIB_TYPES[0]: for file in glob.glob(shared_libs_pattern): copy_file_to_destination(file, str(destination.resolve())) for file in glob.glob(static_libs_pattern): copy_file_to_destination(file, str(destination.resolve())) elif PACKAGE_PLATFORM in (PACKAGE_PLATFORM_OPTIONS[1], PACKAGE_PLATFORM_OPTIONS[2]): shared_libs_pattern: str = str(install_folder.joinpath("lib/*.so*")) static_libs_pattern: str = str(install_folder.joinpath("lib/*.a")) # for linux, it requires .a file for static libs and .so file for shared libs if lib_type == PACKAGE_LIB_TYPES[0]: for file in glob.glob(shared_libs_pattern): copy_file_to_destination(file, str(destination.resolve())) else: for file in glob.glob(static_libs_pattern): copy_file_to_destination(file, str(destination.resolve())) else: raise Exception(f"Error unsupported platform: {PACKAGE_PLATFORM}") def build_gamelift_server_sdk(working_directory: WorkingDirectoryInfo, build_type: str, lib_type: str) -> None: build_folder: pathlib.Path = working_directory.build_path.joinpath(f"{build_type}_{lib_type}").resolve() print(f"Generating GameLift Server SDK project from source ({working_directory.source_path}) with {build_type} {lib_type} configuration...") configure_sdk_project(working_directory, build_folder.resolve(), build_type, lib_type) print(f"Building GameLift Server SDK project with {build_type} {lib_type} configuration...") build_sdk_project(working_directory.source_path.resolve(), build_folder.resolve(), build_type) print(f"Copying {build_type} {lib_type} built sdk libs into output folder...") copy_sdk_libs(working_directory.libs_output_path, build_folder, build_type, lib_type) # generate required information for package, like name, url, and license def generate_packageInfo(working_directory: WorkingDirectoryInfo) -> None: settings={ 'PackageName': f'{O3DE_PACKAGE_NAME}', "URL" : f'{PACKAGE_URL}', "License" : f'{PACKAGE_LICENSE}', 'LicenseFile': f'{PACKAGE_NAME}/{PACKAGE_LICENSE_FILE}' } package_file: str = str(working_directory.output_path.joinpath("PackageInfo.json").resolve()) with open(package_file, 'w') as fh: json.dump(settings, fh, indent=4) # generate required cmake file which is used to find package libs def generate_cmake_file(working_directory: WorkingDirectoryInfo) -> None: cmake_file_source_suffix = FIND_CMAKE_PLATFORM_SUFFIX_BY_PLATFORM[PACKAGE_PLATFORM.title()] cmake_file_source: pathlib.Path = PACKAGE_BASE_PATH.joinpath(f"Find{PACKAGE_NAME}.cmake.{cmake_file_source_suffix}") if cmake_file_source.is_file(): find_cmake_content = cmake_file_source.read_text("UTF-8", "ignore") target_cmake_file: pathlib.Path = working_directory.output_path.joinpath(f"Find{PACKAGE_NAME}.cmake") target_cmake_file.write_text(find_cmake_content) else: raise Exception(f"Error finding cmake source file: {str(cmake_file_source.resolve())}") if __name__ == '__main__': try: print("Collecting package build info for GameLift Server SDK...") collect_package_info() print("Prepare working directory...") working_directory: WorkingDirectoryInfo = prepare_working_directory() print(f"Downloading GameLift Server SDK from {GAMELIFT_SERVER_SDK_DOWNLOAD_URL}...") download_gamelift_server_sdk(working_directory) # Retrieve any dependencies (if needed) get_dependencies(working_directory) # build sdk with different configurations for build_type in PACKAGE_BUILD_TYPES: for lib_type in PACKAGE_LIB_TYPES: build_gamelift_server_sdk(working_directory, build_type, lib_type) print("Copying include and license files into output directory...") shutil.copytree(working_directory.source_path.joinpath("gamelift-server-sdk/include").resolve(), working_directory.libs_output_path.joinpath("include").resolve()) copy_file_to_destination(str(working_directory.source_path.joinpath(PACKAGE_LICENSE_FILE).resolve()), str(working_directory.libs_output_path.resolve())) copy_file_to_destination(str(working_directory.source_path.joinpath("NOTICE_C++_AMAZON_GAMELIFT_SDK.TXT").resolve()), str(working_directory.libs_output_path.resolve())) print("Generating package info into output directory...") generate_packageInfo(working_directory) print("Generating cmake file into output directory...") generate_cmake_file(working_directory) exit(0) except Exception as e: print(e) exit(1)