build.py 11 KB

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