/ build.py
build.py
1 #!/usr/bin/env python 2 3 import os 4 import subprocess 5 import sys 6 import shutil 7 import zipfile 8 from contextlib import contextmanager 9 import click 10 11 12 def get_platform(): 13 """ a name for the platform """ 14 if sys.platform.startswith('win'): 15 return 'win' 16 elif sys.platform == 'darwin': 17 return 'osx' 18 elif sys.platform.startswith('linux'): 19 return 'linux' 20 raise Exception('Unsupported platform ' + sys.platform) 21 22 23 SCRIPT_PATH = os.path.dirname(os.path.abspath(__file__)) 24 # we use Buildkite which sets this env variable by default 25 IS_BUILD_MACHINE = os.environ.get('CI', '') == 'true' 26 PLATFORM = get_platform() 27 INSTALL_ROOT = os.path.join(SCRIPT_PATH, 'builds', 'install') 28 29 30 def get_signtool(): 31 """ get path to code signing tool """ 32 if PLATFORM == 'win': 33 sdk_dir = 'c:\\Program Files (x86)\\Windows Kits\\10' # os.environ['WindowsSdkDir'] 34 return os.path.join(sdk_dir, 'bin', 'x86', 'signtool.exe') 35 elif PLATFORM == 'osx': 36 return '/usr/bin/codesign' 37 38 39 @contextmanager 40 def cd(new_dir): 41 """ Temporarily change current directory """ 42 if new_dir: 43 old_dir = os.getcwd() 44 os.chdir(new_dir) 45 yield 46 if new_dir: 47 os.chdir(old_dir) 48 49 50 def mkdir_p(path): 51 """ mkdir -p """ 52 if not os.path.isdir(path): 53 click.secho('Making ' + path, fg='yellow') 54 os.makedirs(path) 55 56 57 @click.group(invoke_without_command=True) 58 @click.pass_context 59 @click.option('--clean', is_flag=True) 60 def cli(ctx, clean): 61 """ click wrapper for command line stuff """ 62 if ctx.invoked_subcommand is None: 63 ctx.invoke(libs, clean=clean) 64 if IS_BUILD_MACHINE: 65 ctx.invoke(sign) 66 ctx.invoke(archive) 67 68 69 @cli.command() 70 @click.pass_context 71 def unity(ctx): 72 """ build just dynamic libs for use in unity project """ 73 ctx.invoke(libs, clean=False, static=False, shared=True, skip_formatter=True, just_release=True) 74 BUILDS = [] 75 76 click.echo('--- Copying libs and header into unity example') 77 UNITY_PROJECT_PATH = os.path.join(SCRIPT_PATH, 'examples', 'button-clicker', 'Assets', 'Plugins') 78 79 if sys.platform.startswith('win'): 80 LIBRARY_NAME = 'discord-rpc.dll' 81 BUILD_64_BASE_PATH = os.path.join(SCRIPT_PATH, 'builds', 'win64-dynamic', 'src', 'Release') 82 UNITY_64_DLL_PATH = os.path.join(UNITY_PROJECT_PATH, 'x86_64') 83 BUILDS.append({BUILD_64_BASE_PATH: UNITY_64_DLL_PATH}) 84 85 BUILD_32_BASE_PATH = os.path.join(SCRIPT_PATH, 'builds', 'win32-dynamic', 'src', 'Release') 86 UNITY_32_DLL_PATH = os.path.join(UNITY_PROJECT_PATH, 'x86') 87 BUILDS.append({BUILD_32_BASE_PATH: UNITY_32_DLL_PATH}) 88 89 elif sys.platform == 'darwin': 90 LIBRARY_NAME = 'discord-rpc.bundle' 91 BUILD_BASE_PATH = os.path.join(SCRIPT_PATH, 'builds', 'osx-dynamic', 'src') 92 UNITY_DLL_PATH = UNITY_PROJECT_PATH 93 os.rename( 94 os.path.join(BUILD_BASE_PATH, 'libdiscord-rpc.dylib'), os.path.join(BUILD_BASE_PATH, 'discord-rpc.bundle')) 95 96 BUILDS.append({BUILD_BASE_PATH: UNITY_DLL_PATH}) 97 98 elif sys.platform.startswith('linux'): 99 LIBRARY_NAME = 'discord-rpc.so' 100 BUILD_BASE_PATH = os.path.join(SCRIPT_PATH, 'builds', 'linux-dynamic', 'src') 101 UNITY_DLL_PATH = os.path.join(UNITY_PROJECT_PATH, 'x86') 102 os.rename(os.path.join(BUILD_BASE_PATH, 'libdiscord-rpc.so'), os.path.join(BUILD_BASE_PATH, 'discord-rpc.so')) 103 104 BUILDS.append({BUILD_BASE_PATH: UNITY_DLL_PATH}) 105 106 else: 107 raise Exception('Unsupported platform ' + sys.platform) 108 109 for build in BUILDS: 110 for i in build: 111 mkdir_p(build[i]) 112 shutil.copy(os.path.join(i, LIBRARY_NAME), build[i]) 113 114 115 @cli.command() 116 @click.pass_context 117 def unreal(ctx): 118 """ build libs and copy them into the unreal project """ 119 ctx.invoke(libs, clean=False, static=False, shared=True, skip_formatter=True, just_release=True) 120 BUILDS = [] 121 122 click.echo('--- Copying libs and header into unreal example') 123 UNREAL_PROJECT_PATH = os.path.join(SCRIPT_PATH, 'examples', 'unrealstatus', 'Plugins', 'discordrpc') 124 UNREAL_INCLUDE_PATH = os.path.join(UNREAL_PROJECT_PATH, 'Source', 'ThirdParty', 'DiscordRpcLibrary', 'Include') 125 mkdir_p(UNREAL_INCLUDE_PATH) 126 shutil.copy(os.path.join(SCRIPT_PATH, 'include', 'discord_rpc.h'), UNREAL_INCLUDE_PATH) 127 128 if sys.platform.startswith('win'): 129 LIBRARY_NAME = 'discord-rpc.lib' 130 BUILD_64_BASE_PATH = os.path.join(SCRIPT_PATH, 'builds', 'win64-dynamic', 'src', 'Release') 131 UNREAL_64_DLL_PATH = os.path.join(UNREAL_PROJECT_PATH, 'Source', 'ThirdParty', 'DiscordRpcLibrary', 'Win64') 132 BUILDS.append({BUILD_64_BASE_PATH: UNREAL_64_DLL_PATH}) 133 134 BUILD_32_BASE_PATH = os.path.join(SCRIPT_PATH, 'builds', 'win32-dynamic', 'src', 'Release') 135 UNREAL_32_DLL_PATH = os.path.join(UNREAL_PROJECT_PATH, 'Source', 'ThirdParty', 'DiscordRpcLibrary', 'Win32') 136 BUILDS.append({BUILD_32_BASE_PATH: UNREAL_32_DLL_PATH}) 137 138 elif sys.platform == 'darwin': 139 LIBRARY_NAME = 'libdiscord-rpc.dylib' 140 BUILD_BASE_PATH = os.path.join(SCRIPT_PATH, 'builds', 'osx-dynamic', 'src') 141 UNREAL_DLL_PATH = os.path.join(UNREAL_PROJECT_PATH, 'Source', 'ThirdParty', 'DiscordRpcLibrary', 'Mac') 142 143 BUILDS.append({BUILD_BASE_PATH: UNREAL_DLL_PATH}) 144 145 elif sys.platform.startswith('linux'): 146 LIBRARY_NAME = 'libdiscord-rpc.so' 147 BUILD_BASE_PATH = os.path.join(SCRIPT_PATH, 'builds', 'linux-dynamic', 'src') 148 UNREAL_DLL_PATH = os.path.join(UNREAL_PROJECT_PATH, 'Source', 'ThirdParty', 'DiscordRpcLibrary', 'Linux') 149 150 BUILDS.append({BUILD_BASE_PATH: UNREAL_DLL_PATH}) 151 152 else: 153 raise Exception('Unsupported platform ' + sys.platform) 154 155 for build in BUILDS: 156 for i in build: 157 mkdir_p(build[i]) 158 shutil.copy(os.path.join(i, LIBRARY_NAME), build[i]) 159 160 161 def build_lib(build_name, generator, options, just_release): 162 """ Create a dir under builds, run build and install in it """ 163 build_path = os.path.join(SCRIPT_PATH, 'builds', build_name) 164 install_path = os.path.join(INSTALL_ROOT, build_name) 165 mkdir_p(build_path) 166 mkdir_p(install_path) 167 with cd(build_path): 168 initial_cmake = ['cmake', SCRIPT_PATH, '-DCMAKE_INSTALL_PREFIX=%s' % os.path.join('..', 'install', build_name)] 169 if generator: 170 initial_cmake.extend(['-G', generator]) 171 for key in options: 172 val = options[key] 173 if type(val) is bool: 174 val = 'ON' if val else 'OFF' 175 initial_cmake.append('-D%s=%s' % (key, val)) 176 click.echo('--- Building ' + build_name) 177 subprocess.check_call(initial_cmake) 178 if not just_release: 179 subprocess.check_call(['cmake', '--build', '.', '--config', 'Debug']) 180 subprocess.check_call(['cmake', '--build', '.', '--config', 'Release', '--target', 'install']) 181 182 183 @cli.command() 184 def archive(): 185 """ create zip of install dir """ 186 click.echo('--- Archiving') 187 archive_file_path = os.path.join(SCRIPT_PATH, 'builds', 'discord-rpc-%s.zip' % get_platform()) 188 archive_file = zipfile.ZipFile(archive_file_path, 'w', zipfile.ZIP_DEFLATED) 189 archive_src_base_path = INSTALL_ROOT 190 archive_dst_base_path = 'discord-rpc' 191 with cd(archive_src_base_path): 192 for path, _, filenames in os.walk('.'): 193 for fname in filenames: 194 fpath = os.path.join(path, fname) 195 dst_path = os.path.normpath(os.path.join(archive_dst_base_path, fpath)) 196 click.echo('Adding ' + dst_path) 197 archive_file.write(fpath, dst_path) 198 199 200 @cli.command() 201 def sign(): 202 """ Do code signing within install directory using our cert """ 203 tool = get_signtool() 204 signable_extensions = set() 205 if PLATFORM == 'win': 206 signable_extensions.add('.dll') 207 sign_command_base = [ 208 tool, 209 'sign', 210 '/n', 211 'Discord Inc.', 212 '/a', 213 '/tr', 214 'http://timestamp.digicert.com/rfc3161', 215 '/as', 216 '/td', 217 'sha256', 218 '/fd', 219 'sha256', 220 ] 221 elif PLATFORM == 'osx': 222 signable_extensions.add('.dylib') 223 sign_command_base = [ 224 tool, 225 '--keychain', 226 os.path.expanduser('~/Library/Keychains/login.keychain'), 227 '-vvvv', 228 '--deep', 229 '--force', 230 '--sign', 231 'Developer ID Application: Hammer & Chisel Inc. (53Q6R32WPB)', 232 ] 233 else: 234 click.secho('Not signing things on this platform yet', fg='red') 235 return 236 237 click.echo('--- Signing') 238 for path, _, filenames in os.walk(INSTALL_ROOT): 239 for fname in filenames: 240 ext = os.path.splitext(fname)[1] 241 if ext not in signable_extensions: 242 continue 243 fpath = os.path.join(path, fname) 244 click.echo('Sign ' + fpath) 245 sign_command = sign_command_base + [fpath] 246 subprocess.check_call(sign_command) 247 248 249 @cli.command() 250 @click.option('--clean', is_flag=True) 251 @click.option('--static', is_flag=True) 252 @click.option('--shared', is_flag=True) 253 @click.option('--skip_formatter', is_flag=True) 254 @click.option('--just_release', is_flag=True) 255 def libs(clean, static, shared, skip_formatter, just_release): 256 """ Do all the builds for this platform """ 257 if clean: 258 shutil.rmtree('builds', ignore_errors=True) 259 260 mkdir_p('builds') 261 262 if not (static or shared): 263 static = True 264 shared = True 265 266 static_options = {} 267 dynamic_options = { 268 'BUILD_SHARED_LIBS': True, 269 'USE_STATIC_CRT': True, 270 } 271 272 if skip_formatter or IS_BUILD_MACHINE: 273 static_options['CLANG_FORMAT_SUFFIX'] = 'none' 274 dynamic_options['CLANG_FORMAT_SUFFIX'] = 'none' 275 276 if IS_BUILD_MACHINE: 277 just_release = True 278 static_options['WARNINGS_AS_ERRORS'] = True 279 dynamic_options['WARNINGS_AS_ERRORS'] = True 280 281 if PLATFORM == 'win': 282 generator32 = 'Visual Studio 14 2015' 283 generator64 = 'Visual Studio 14 2015 Win64' 284 if static: 285 build_lib('win32-static', generator32, static_options, just_release) 286 build_lib('win64-static', generator64, static_options, just_release) 287 if shared: 288 build_lib('win32-dynamic', generator32, dynamic_options, just_release) 289 build_lib('win64-dynamic', generator64, dynamic_options, just_release) 290 elif PLATFORM == 'osx': 291 if static: 292 build_lib('osx-static', None, static_options, just_release) 293 if shared: 294 build_lib('osx-dynamic', None, dynamic_options, just_release) 295 elif PLATFORM == 'linux': 296 if static: 297 build_lib('linux-static', None, static_options, just_release) 298 if shared: 299 build_lib('linux-dynamic', None, dynamic_options, just_release) 300 301 302 if __name__ == '__main__': 303 os.chdir(SCRIPT_PATH) 304 sys.exit(cli())