vcpkgbuilder.py 6.7 KB

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