build_package_image.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320
  1. #!/usr/bin/env python3
  2. #
  3. # Copyright (c) Contributors to the Open 3D Engine Project.
  4. # For complete copyright and license terms please see the LICENSE at the root of this distribution.
  5. #
  6. # SPDX-License-Identifier: Apache-2.0 OR MIT
  7. #
  8. #
  9. import argparse
  10. import glob
  11. import json
  12. import os
  13. import pathlib
  14. import subprocess
  15. import shutil
  16. import sys
  17. import urllib.request
  18. import zipfile
  19. from typing import List
  20. # O3DE_PACKAGE_NAME is given in the format <PACKAGE_NAME>-<VERSION>-<PLATFORM>
  21. O3DE_PACKAGE_NAME = os.environ['O3DE_PACKAGE_NAME']
  22. O3DE_PACKAGE_NAME_PARTS = O3DE_PACKAGE_NAME.split('-')
  23. PACKAGE_NAME: str = "AWSGameLiftServerSDK"
  24. PACKAGE_PLATFORM: str = ""
  25. PACKAGE_URL: str = "https://aws.amazon.com/documentation/gamelift/"
  26. PACKAGE_LICENSE: str = "Apache-2.0"
  27. PACKAGE_LICENSE_FILE: str = "LICENSE_AMAZON_GAMELIFT_SDK.TXT"
  28. GAMELIFT_SERVER_SDK_RELEASE_VERSION: str = O3DE_PACKAGE_NAME_PARTS[1]
  29. 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"
  30. PACKAGE_BASE_PATH: pathlib.Path = pathlib.Path(os.path.dirname(__file__))
  31. PACKAGE_ROOT_PATH: pathlib.Path = PACKAGE_BASE_PATH.parent
  32. REPO_ROOT_PATH: pathlib.Path = PACKAGE_ROOT_PATH.parent
  33. GENERAL_SCRIPTS_PATH = REPO_ROOT_PATH / 'Scripts' / 'extras'
  34. PACKAGE_BUILD_TYPES: List[str] = ["Debug", "Release"]
  35. PACKAGE_LIB_TYPES: List[str] = ["Shared", "Static"]
  36. PACKAGE_PLATFORM_OPTIONS: List[str] = ["windows", "linux", "linux-aarch64"]
  37. # Insert the 3p scripts path so we can use the package downloader to
  38. # download the openssl dependency
  39. sys.path.insert(1, str(GENERAL_SCRIPTS_PATH.resolve()))
  40. from package_downloader import PackageDownloader
  41. # Lookup table for the Find{PACKAGE_NAME}.cmake.{platform} source file to copy in case there are shared files across different platforms
  42. FIND_CMAKE_PLATFORM_SUFFIX_BY_PLATFORM = {
  43. 'Windows': 'Windows',
  44. 'Linux': 'Linux',
  45. 'Linux-Aarch64': 'Linux'
  46. }
  47. # utils
  48. class WorkingDirectoryInfo(object):
  49. def __init__(self, root_path: pathlib.Path,
  50. source_path: pathlib.Path,
  51. build_path: pathlib.Path,
  52. output_path: pathlib.Path,
  53. libs_output_path: pathlib.Path) -> None:
  54. self.root_path = root_path
  55. self.source_path = source_path
  56. self.build_path = build_path
  57. self.output_path = output_path
  58. self.libs_output_path = libs_output_path
  59. self.dependencies_folder_path = (self.root_path / 'dependencies').resolve()
  60. # OpenSSL dependency: (packagename, hash)
  61. OPENSSL_PACKAGE = ('OpenSSL-1.1.1o-rev1-windows', '52b9d2bc5f3e0c6e405a0f290d1904bf545acc0c73d6a52351247d917c4a06d2')
  62. def get_dependencies(working_directory: WorkingDirectoryInfo) -> None:
  63. # Only windows has an additional dependency on openssl that we need to download from our own packages
  64. # The linux builds also rely on openssl, but they use the version on the system
  65. if PACKAGE_PLATFORM != 'windows':
  66. return
  67. print("Downloading dependencies...")
  68. package_name = OPENSSL_PACKAGE[0]
  69. package_hash = OPENSSL_PACKAGE[1]
  70. if not (working_directory.dependencies_folder_path / package_name).exists():
  71. if not PackageDownloader.DownloadAndUnpackPackage(package_name, package_hash, str(working_directory.dependencies_folder_path)):
  72. raise Exception("Failed to download OpenSSL dependency!")
  73. else:
  74. print(f'OpenSSL already in dependencies folder, skipping. Use --clean to refresh')
  75. def subp_args(args) -> str:
  76. arg_string = " ".join([arg for arg in args])
  77. print(f"Command: {arg_string}")
  78. return arg_string
  79. def delete_folder(folder: pathlib.Path) -> None:
  80. shutil.rmtree(folder.resolve())
  81. def copy_file_to_destination(file: str, destination: str) -> None:
  82. print(f"Copying {file} to {destination}")
  83. shutil.copy2(file, destination)
  84. # collect package build required information, like platform
  85. def collect_package_info() -> None:
  86. parser = argparse.ArgumentParser()
  87. parser.add_argument(
  88. '--platform-name',
  89. required=True,
  90. choices=PACKAGE_PLATFORM_OPTIONS
  91. )
  92. args = parser.parse_args()
  93. global PACKAGE_PLATFORM
  94. PACKAGE_PLATFORM = args.platform_name
  95. # prepare working directories for package build
  96. def prepare_working_directory() -> WorkingDirectoryInfo:
  97. # root directory
  98. root_directory: pathlib.Path = PACKAGE_BASE_PATH.joinpath("temp")
  99. if root_directory.is_dir():
  100. delete_folder(root_directory)
  101. # source and build directory
  102. source_directory = root_directory
  103. build_directory: pathlib.Path = root_directory.joinpath("build")
  104. build_directory.mkdir(parents=True)
  105. # build output and libs output directory
  106. build_output_directory: pathlib.Path = PACKAGE_ROOT_PATH.joinpath(f"{PACKAGE_NAME}-{PACKAGE_PLATFORM}")
  107. if build_output_directory.is_dir():
  108. delete_folder(build_output_directory)
  109. build_libs_output_directory: pathlib.Path = build_output_directory.joinpath(f"{PACKAGE_NAME}")
  110. build_libs_output_directory.mkdir(parents=True)
  111. return WorkingDirectoryInfo(root_directory, source_directory, build_directory, build_output_directory, build_libs_output_directory)
  112. # download gamelift server server sdk source code for package build
  113. def download_gamelift_server_sdk(working_directory: WorkingDirectoryInfo) -> None:
  114. # download sdk from url
  115. gamelift_sdk_zip_file: str = str(working_directory.root_path.joinpath("gamelift_server_sdk.zip").resolve())
  116. with urllib.request.urlopen(GAMELIFT_SERVER_SDK_DOWNLOAD_URL) as response, open(gamelift_sdk_zip_file, 'wb') as out_file:
  117. shutil.copyfileobj(response, out_file)
  118. # unzip sdk contents
  119. with zipfile.ZipFile(gamelift_sdk_zip_file, "r") as f:
  120. unzip_path = working_directory.root_path.resolve()
  121. f.extractall(unzip_path)
  122. # get required custom environment for package build
  123. def get_custom_build_env():
  124. if PACKAGE_PLATFORM in (PACKAGE_PLATFORM_OPTIONS[1], PACKAGE_PLATFORM_OPTIONS[2]):
  125. custom_env = os.environ.copy()
  126. custom_env["CC"] = "gcc"
  127. custom_env["CXX"] = "g++"
  128. custom_env["CFLAGS"] = "-fPIC"
  129. custom_env["CXXFLAGS"] = "-fPIC"
  130. return custom_env
  131. return None
  132. # configure gamelift server sdk project
  133. def configure_sdk_project(working_directory: WorkingDirectoryInfo,
  134. build_folder: str,
  135. build_type: str,
  136. lib_type: str) -> None:
  137. source_folder: str = working_directory.source_path.resolve()
  138. build_shared: str = "ON" if lib_type == "Shared" else "OFF"
  139. if PACKAGE_PLATFORM == PACKAGE_PLATFORM_OPTIONS[0]:
  140. generator: str = '-G "Visual Studio 17 2022" -A x64'
  141. elif PACKAGE_PLATFORM in (PACKAGE_PLATFORM_OPTIONS[1], PACKAGE_PLATFORM_OPTIONS[2]):
  142. generator: str = "-G \"Unix Makefiles\""
  143. else:
  144. raise Exception(f"Error unsupported platform: {PACKAGE_PLATFORM}")
  145. configure_cmd: List[str] = [f"cmake {generator} -S .",
  146. f"-B {build_folder}",
  147. f"-DBUILD_SHARED_LIBS={build_shared}",
  148. f"-DCMAKE_BUILD_TYPE={build_type}"]
  149. # On windows add our OpenSSL dependency to the DCMAKE_MODULE_PATH so the
  150. # GameLift build can find it
  151. if PACKAGE_PLATFORM == 'windows':
  152. package_name = OPENSSL_PACKAGE[0]
  153. openssl_dependency_path = (working_directory.dependencies_folder_path / package_name).resolve().as_posix()
  154. configure_cmd.append(f"-DCMAKE_MODULE_PATH=\"{openssl_dependency_path}\"")
  155. configure_result = subprocess.run(subp_args(configure_cmd),
  156. shell=True,
  157. capture_output=True,
  158. cwd=source_folder,
  159. env=get_custom_build_env())
  160. if configure_result.returncode != 0:
  161. raise Exception(f"Error generating project: {configure_result.stderr.decode()}")
  162. # build gamelift server sdk project
  163. def build_sdk_project(source_folder: str,
  164. build_folder: str,
  165. build_type: str) -> None:
  166. target = "--target aws-cpp-sdk-gamelift-server"
  167. build_cmd: List[str] = ["cmake",
  168. f"--build {build_folder}",
  169. f"--config {build_type}",
  170. f"{target} -j"]
  171. build_result = subprocess.run(subp_args(build_cmd),
  172. shell=True,
  173. capture_output=True,
  174. cwd=source_folder,
  175. env=get_custom_build_env())
  176. if build_result.returncode != 0:
  177. raise Exception(f"Error building project: {build_result.stderr.decode()}")
  178. # copy all built gamelift server sdk libs into expected output folder
  179. def copy_sdk_libs(libs_output_path: pathlib.Path,
  180. build_path: pathlib.Path,
  181. build_type: str,
  182. lib_type: str) -> None:
  183. if lib_type == PACKAGE_LIB_TYPES[0]:
  184. destination: pathlib.Path = libs_output_path.joinpath(f"bin/{build_type}")
  185. else:
  186. destination: pathlib.Path = libs_output_path.joinpath(f"lib/{build_type}")
  187. destination.mkdir(parents=True)
  188. install_folder: pathlib.Path = build_path.joinpath("prefix")
  189. if PACKAGE_PLATFORM == PACKAGE_PLATFORM_OPTIONS[0]:
  190. shared_libs_pattern: str = str(install_folder.joinpath("bin/*.dll"))
  191. static_libs_pattern: str = str(install_folder.joinpath("lib/*.lib"))
  192. # for windows, it always requires .lib file, .dll file is only required for shared libs
  193. if lib_type == PACKAGE_LIB_TYPES[0]:
  194. for file in glob.glob(shared_libs_pattern):
  195. copy_file_to_destination(file, str(destination.resolve()))
  196. for file in glob.glob(static_libs_pattern):
  197. copy_file_to_destination(file, str(destination.resolve()))
  198. elif PACKAGE_PLATFORM in (PACKAGE_PLATFORM_OPTIONS[1], PACKAGE_PLATFORM_OPTIONS[2]):
  199. shared_libs_pattern: str = str(install_folder.joinpath("lib/*.so*"))
  200. static_libs_pattern: str = str(install_folder.joinpath("lib/*.a"))
  201. # for linux, it requires .a file for static libs and .so file for shared libs
  202. if lib_type == PACKAGE_LIB_TYPES[0]:
  203. for file in glob.glob(shared_libs_pattern):
  204. copy_file_to_destination(file, str(destination.resolve()))
  205. else:
  206. for file in glob.glob(static_libs_pattern):
  207. copy_file_to_destination(file, str(destination.resolve()))
  208. else:
  209. raise Exception(f"Error unsupported platform: {PACKAGE_PLATFORM}")
  210. def build_gamelift_server_sdk(working_directory: WorkingDirectoryInfo,
  211. build_type: str,
  212. lib_type: str) -> None:
  213. build_folder: pathlib.Path = working_directory.build_path.joinpath(f"{build_type}_{lib_type}").resolve()
  214. print(f"Generating GameLift Server SDK project from source ({working_directory.source_path}) with {build_type} {lib_type} configuration...")
  215. configure_sdk_project(working_directory, build_folder.resolve(), build_type, lib_type)
  216. print(f"Building GameLift Server SDK project with {build_type} {lib_type} configuration...")
  217. build_sdk_project(working_directory.source_path.resolve(), build_folder.resolve(), build_type)
  218. print(f"Copying {build_type} {lib_type} built sdk libs into output folder...")
  219. copy_sdk_libs(working_directory.libs_output_path, build_folder, build_type, lib_type)
  220. # generate required information for package, like name, url, and license
  221. def generate_packageInfo(working_directory: WorkingDirectoryInfo) -> None:
  222. settings={
  223. 'PackageName': f'{O3DE_PACKAGE_NAME}',
  224. "URL" : f'{PACKAGE_URL}',
  225. "License" : f'{PACKAGE_LICENSE}',
  226. 'LicenseFile': f'{PACKAGE_NAME}/{PACKAGE_LICENSE_FILE}'
  227. }
  228. package_file: str = str(working_directory.output_path.joinpath("PackageInfo.json").resolve())
  229. with open(package_file, 'w') as fh:
  230. json.dump(settings, fh, indent=4)
  231. # generate required cmake file which is used to find package libs
  232. def generate_cmake_file(working_directory: WorkingDirectoryInfo) -> None:
  233. cmake_file_source_suffix = FIND_CMAKE_PLATFORM_SUFFIX_BY_PLATFORM[PACKAGE_PLATFORM.title()]
  234. cmake_file_source: pathlib.Path = PACKAGE_BASE_PATH.joinpath(f"Find{PACKAGE_NAME}.cmake.{cmake_file_source_suffix}")
  235. if cmake_file_source.is_file():
  236. find_cmake_content = cmake_file_source.read_text("UTF-8", "ignore")
  237. target_cmake_file: pathlib.Path = working_directory.output_path.joinpath(f"Find{PACKAGE_NAME}.cmake")
  238. target_cmake_file.write_text(find_cmake_content)
  239. else:
  240. raise Exception(f"Error finding cmake source file: {str(cmake_file_source.resolve())}")
  241. if __name__ == '__main__':
  242. try:
  243. print("Collecting package build info for GameLift Server SDK...")
  244. collect_package_info()
  245. print("Prepare working directory...")
  246. working_directory: WorkingDirectoryInfo = prepare_working_directory()
  247. print(f"Downloading GameLift Server SDK from {GAMELIFT_SERVER_SDK_DOWNLOAD_URL}...")
  248. download_gamelift_server_sdk(working_directory)
  249. # Retrieve any dependencies (if needed)
  250. get_dependencies(working_directory)
  251. # build sdk with different configurations
  252. for build_type in PACKAGE_BUILD_TYPES:
  253. for lib_type in PACKAGE_LIB_TYPES:
  254. build_gamelift_server_sdk(working_directory, build_type, lib_type)
  255. print("Copying include and license files into output directory...")
  256. shutil.copytree(working_directory.source_path.joinpath("gamelift-server-sdk/include").resolve(),
  257. working_directory.libs_output_path.joinpath("include").resolve())
  258. copy_file_to_destination(str(working_directory.source_path.joinpath(PACKAGE_LICENSE_FILE).resolve()),
  259. str(working_directory.libs_output_path.resolve()))
  260. copy_file_to_destination(str(working_directory.source_path.joinpath("NOTICE_C++_AMAZON_GAMELIFT_SDK.TXT").resolve()),
  261. str(working_directory.libs_output_path.resolve()))
  262. print("Generating package info into output directory...")
  263. generate_packageInfo(working_directory)
  264. print("Generating cmake file into output directory...")
  265. generate_cmake_file(working_directory)
  266. exit(0)
  267. except Exception as e:
  268. print(e)
  269. exit(1)