/ 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())