vcpkgbuilder.py 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  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. """
  10. Package builder for vcpkg-based packages
  11. """
  12. from pathlib import Path
  13. import errno
  14. import json
  15. import os
  16. import pathlib
  17. import shutil
  18. import string
  19. import subprocess
  20. import platform
  21. class VcpkgBuilder(object):
  22. def __init__(self, packageName: str, portName: str, vcpkgDir: pathlib.Path, targetPlatform: str, static: bool):
  23. self._packageName = packageName
  24. self._portName = portName
  25. self._vcpkgDir = vcpkgDir
  26. self._triplet = VcpkgBuilder.tripletForPlatform(targetPlatform, static)
  27. self._customTripletsDir = Path(__file__).resolve().parents[1] / 'vcpkg/triplets'
  28. if targetPlatform == 'android' and 'ANDROID_NDK_HOME' not in os.environ:
  29. # Copy some of the logic from vcpkg's android ndk detection, and see if we can print a warning early
  30. if 'ProgramData' in os.environ:
  31. androidNdkFound = (pathlib.Path(os.environ['ProgramData']) / 'Microsoft/AndroidNDK64/android-ndk-r13b/').exists()
  32. else:
  33. androidNdkFound = False
  34. if not androidNdkFound and 'ProgramFiles(x86)' in os.environ:
  35. # Use Xamarin default installation folder
  36. androidNdkFound = (pathlib.Path(os.environ['ProgramFiles(x86)']) / 'Android/android-sdk/ndk-bundle').exists()
  37. if not androidNdkFound:
  38. raise RuntimeError('Unable to find the Android NDK. '
  39. 'Please set the ANDROID_NDK_HOME environment variable to the root of the Android NDK')
  40. @staticmethod
  41. def tripletForPlatform(platformName: str, static: bool):
  42. platformMap = {
  43. 'mac': {
  44. True: 'x64-osx',
  45. False: 'x64-osx-dynamic',
  46. },
  47. 'windows': {
  48. True: 'x64-windows-static',
  49. False: 'x64-windows',
  50. },
  51. 'linux': {
  52. True: 'x64-linux',
  53. False: 'x64-linux-shared',
  54. },
  55. 'android': {
  56. True: 'arm64-android-static', # arm64-v8a
  57. False: 'arm64-android', # arm64-v8a
  58. },
  59. 'ios': {
  60. True: 'arm64-ios',
  61. False: 'arm64-ios-dynamic',
  62. }
  63. }
  64. try:
  65. useStaticLibsMap = platformMap[platformName]
  66. except KeyError:
  67. raise RuntimeError(f'Platform {platformName} not supported')
  68. try:
  69. return useStaticLibsMap[static]
  70. except KeyError:
  71. raise RuntimeError('Platform {platformName} does not support building {linkageType} libraries'.format(
  72. platformName=platformName,
  73. linkageType='static' if static else 'dynamic',
  74. ))
  75. @staticmethod
  76. def defaultPackagePlatformName():
  77. platformMap = {
  78. 'Darwin': 'mac',
  79. 'Windows': 'windows',
  80. 'Linux': 'linux',
  81. }
  82. return platformMap[platform.system()]
  83. @property
  84. def customTripletsDir(self):
  85. return self._customTripletsDir
  86. @property
  87. def packageName(self):
  88. """The name of the package that this builder will build"""
  89. return self._packageName
  90. @property
  91. def portName(self):
  92. """The name of the vcpkg port that this builder will build"""
  93. return self._portName
  94. @property
  95. def vcpkgDir(self):
  96. """The directory where vcpkg will be cloned to"""
  97. return self._vcpkgDir
  98. @property
  99. def triplet(self):
  100. """The vcpkg triplet to build"""
  101. return self._triplet
  102. def cloneVcpkg(self, lockToCommit: str):
  103. if not (self.vcpkgDir / '.git').exists():
  104. subprocess.check_call(
  105. ['git', 'init',],
  106. cwd=self.vcpkgDir,
  107. )
  108. subprocess.check_call(
  109. ['git', 'remote', 'add', 'origin', 'https://github.com/microsoft/vcpkg.git',],
  110. cwd=self.vcpkgDir,
  111. )
  112. subprocess.check_call(
  113. ['git', 'fetch', 'origin', '--depth=1', lockToCommit,],
  114. cwd=self.vcpkgDir,
  115. )
  116. subprocess.check_call(
  117. ['git', 'checkout', lockToCommit,],
  118. cwd=self.vcpkgDir,
  119. )
  120. def bootstrap(self):
  121. if platform.system() == 'Windows':
  122. subprocess.check_call(
  123. ['powershell', '-NoProfile', '-ExecutionPolicy', 'Bypass', 'scripts/bootstrap.ps1',],
  124. cwd=self.vcpkgDir,
  125. )
  126. else:
  127. subprocess.check_call(
  128. [self.vcpkgDir / 'bootstrap-vcpkg.sh'],
  129. cwd=self.vcpkgDir,
  130. )
  131. def patch(self, patchFile: pathlib.Path):
  132. subprocess.check_output(
  133. ['git', 'apply', '--whitespace=fix', str(patchFile)],
  134. cwd=self.vcpkgDir,
  135. )
  136. def build(self):
  137. self.remove()
  138. subprocess.check_call(
  139. [str(self.vcpkgDir / 'vcpkg'), 'install', f'{self.portName}:{self.triplet}', '--no-binarycaching', f'--overlay-triplets={self.customTripletsDir}'],
  140. cwd=self.vcpkgDir,
  141. )
  142. def remove(self):
  143. subprocess.check_call(
  144. [str(self.vcpkgDir / 'vcpkg'), 'remove', f'{self.portName}:{self.triplet}', f'--overlay-triplets={self.customTripletsDir}'],
  145. cwd=self.vcpkgDir,
  146. )
  147. def copyBuildOutputTo(self, packageDir: pathlib.Path, extraFiles: dict, subdir:pathlib.Path=None):
  148. destdir = packageDir / self.packageName
  149. if subdir is not None:
  150. destdir /= subdir
  151. if destdir.exists():
  152. shutil.rmtree(destdir)
  153. shutil.copytree(
  154. src=self.vcpkgDir / 'packages' / f'{self.portName}_{self.triplet}',
  155. dst=destdir,
  156. symlinks=True,
  157. )
  158. for (src, dst) in extraFiles.items():
  159. try:
  160. shutil.copy2(src, dst)
  161. except IOError as e:
  162. # ENOENT(2): file does not exist, raised also on missing dest parent dir
  163. if e.errno != errno.ENOENT:
  164. raise
  165. # try creating parent directories
  166. Path(dst).parent.mkdir(parents=True, exist_ok=True)
  167. shutil.copy2(src, dst)
  168. def writePackageInfoFile(self, packageDir: pathlib.Path, settings: dict):
  169. with (packageDir / 'PackageInfo.json').open('w') as fh:
  170. json.dump(settings, fh, indent=4)
  171. def writeCMakeFindFile(self, packageDir: pathlib.Path, template, templateEnv:dict):
  172. cmakeFindFile = packageDir / f'Find{self.packageName}.cmake'
  173. cmakeFindFile.write_text(string.Template(template).substitute(templateEnv))