123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243 |
- #!/usr/bin/env python3
- #
- # 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.
- #
- # SPDX-License-Identifier: Apache-2.0 OR MIT
- #
- #
- """
- Package builder for vcpkg-based packages
- """
- from pathlib import Path
- import errno
- import json
- import os
- import pathlib
- import shutil
- import string
- import subprocess
- import platform
- class VcpkgBuilder(object):
- def __init__(self, packageName: str, portName: str, vcpkgDir: pathlib.Path, targetPlatform: str, static: bool):
- self._packageName = packageName
- self._portName = portName
- self._vcpkgDir = vcpkgDir
- self._triplet = VcpkgBuilder.tripletForPlatform(targetPlatform, static)
- self._customTripletsDir = Path(__file__).resolve().parents[1] / 'vcpkg/triplets'
- self._customEnviron = os.environ.copy()
- if targetPlatform == 'android':
- if 'ANDROID_NDK_HOME' not in os.environ:
- raise RuntimeError('Unable to find the Android NDK. '
- 'Please set the ANDROID_NDK_HOME environment variable to the root of the Android NDK')
- ANDROID_NDK_HOME = os.environ["ANDROID_NDK_HOME"]
- print(f"ANDROID_NDK_HOME ='{ANDROID_NDK_HOME}'")
- if targetPlatform == 'mac':
- # set default env vars that control minos version. Change this if you change the toolchain files.
- # ideally we'd put this in triplet files, but it turns out VCPKG triplets like the minos version
- # don't acutally work in the case where the target is built using existing makefiles instead of
- # CMake (ie, packages like OpenSSL1.1.1x)
- self._customEnviron["MACOSX_DEPLOYMENT_TARGET"] = "11.0"
- elif targetPlatform == 'ios':
- self._customEnviron["IPHONEOS_DEPLOYMENT_TARGET"] = "14.0"
- @staticmethod
- def tripletForPlatform(platformName: str, static: bool):
- platformMap = {
- 'mac': {
- True: 'x64-osx',
- False: 'x64-osx-dynamic',
- },
- 'windows': {
- True: 'x64-windows-static',
- False: 'x64-windows',
- },
- 'linux': {
- True: 'x64-linux',
- False: 'x64-linux-shared',
- },
- 'android': {
- True: 'arm64-android-static', # arm64-v8a
- False: 'arm64-android', # arm64-v8a
- },
- 'ios': {
- True: 'arm64-ios',
- False: 'arm64-ios-dynamic',
- }
- }
- try:
- useStaticLibsMap = platformMap[platformName]
- except KeyError:
- raise RuntimeError(f'Platform {platformName} not supported')
- try:
- return useStaticLibsMap[static]
- except KeyError:
- raise RuntimeError('Platform {platformName} does not support building {linkageType} libraries'.format(
- platformName=platformName,
- linkageType='static' if static else 'dynamic',
- ))
- @staticmethod
- def defaultPackagePlatformName():
- platformMap = {
- 'Darwin': 'mac',
- 'Windows': 'windows',
- 'Linux': 'linux',
- }
- return platformMap[platform.system()]
- @staticmethod
- def deleteFolder(folder):
- """
- Use the system's remove folder command instead of os.rmdir().
- This function does various checks before trying, to avoid having to do those
- checks over and over in code.
- """
- # wrap it up in a Path so that if a string is passed in, this still works.
- path_folder = pathlib.Path(folder).resolve(strict=False)
- if path_folder.is_file():
- raise Exception(f"deleteFolder: Expected a folder, but found a file: {path_folder}. Continuing may be unsafe.")
- if not path_folder.is_dir():
- print(f'deleteFolder: Folder is already not present: {path_folder}')
- return
-
- if platform.system() == 'Windows':
- call_result = subprocess.run(' '.join(['rmdir', '/Q', '/S', str(path_folder)]),
- shell=True,
- capture_output=True,
- cwd=str(path_folder.parent.resolve()))
- else:
- call_result = subprocess.run(' '.join(['rm', '-rf', str(path_folder)]),
- shell=True,
- capture_output=True,
- cwd=str(path_folder.parent.resolve()))
- if call_result.returncode != 0:
- raise Exception(f"deleteFolder: Unable to delete folder {str(path_folder)}: {str(call_result.stderr.decode())}")
- @property
- def customTripletsDir(self):
- return self._customTripletsDir
- @property
- def packageName(self):
- """The name of the package that this builder will build"""
- return self._packageName
- @property
- def portName(self):
- """The name of the vcpkg port that this builder will build"""
- return self._portName
- @property
- def vcpkgDir(self):
- """The directory where vcpkg will be cloned to"""
- return self._vcpkgDir
- @property
- def triplet(self):
- """The vcpkg triplet to build"""
- return self._triplet
- def cloneVcpkg(self, lockToCommit: str):
- if not (self.vcpkgDir / '.git').exists():
- subprocess.check_call(
- ['git', 'init',],
- cwd=self.vcpkgDir,
- )
- subprocess.check_call(
- ['git', 'remote', 'add', 'origin', 'https://github.com/microsoft/vcpkg.git',],
- cwd=self.vcpkgDir,
- )
- subprocess.check_call(
- ['git', 'fetch', 'origin', '--depth=1', lockToCommit,],
- cwd=self.vcpkgDir,
- )
- subprocess.check_call(
- ['git', 'checkout', lockToCommit,],
- cwd=self.vcpkgDir,
- )
- def bootstrap(self):
- if platform.system() == 'Windows':
- subprocess.check_call(
- ['powershell', '-NoProfile', '-ExecutionPolicy', 'Bypass', 'scripts/bootstrap.ps1', '-disableMetrics'],
- cwd=self.vcpkgDir,
- )
- else:
- subprocess.check_call(
- [self.vcpkgDir / 'bootstrap-vcpkg.sh', '-disableMetrics'],
- cwd=self.vcpkgDir,
- )
- def patch(self, patchFile: pathlib.Path):
- subprocess.check_output(
- ['git', 'apply', '--whitespace=fix', '--verbose', str(patchFile)],
- cwd=self.vcpkgDir,
- )
- def build(self, allow_unsupported=False):
- self.remove()
- command = [
- str(self.vcpkgDir / 'vcpkg'),
- 'install',
- f'{self.portName}:{self.triplet}',
- '--no-binarycaching',
- f'--overlay-triplets={self.customTripletsDir}'
- ]
- if allow_unsupported:
- command.append('--allow-unsupported')
- subprocess.check_call(
- command,
- cwd=self.vcpkgDir,
- env=self._customEnviron
- )
- def remove(self):
- subprocess.check_call(
- [str(self.vcpkgDir / 'vcpkg'), 'remove', f'{self.portName}:{self.triplet}', f'--overlay-triplets={self.customTripletsDir}'],
- cwd=self.vcpkgDir,
- )
- def copyBuildOutputTo(self, packageDir: pathlib.Path, extraFiles: dict, subdir:pathlib.Path=None):
- destdir = packageDir / self.packageName
- if subdir is not None:
- destdir /= subdir
- if destdir.exists():
- shutil.rmtree(destdir)
- shutil.copytree(
- src=self.vcpkgDir / 'packages' / f'{self.portName}_{self.triplet}',
- dst=destdir,
- symlinks=True,
- )
- for (src, dst) in extraFiles.items():
- try:
- shutil.copy2(src, dst)
- except IOError as e:
- # ENOENT(2): file does not exist, raised also on missing dest parent dir
- if e.errno != errno.ENOENT:
- raise
- # try creating parent directories
- Path(dst).parent.mkdir(parents=True, exist_ok=True)
- shutil.copy2(src, dst)
- def writePackageInfoFile(self, packageDir: pathlib.Path, settings: dict):
- with (packageDir / 'PackageInfo.json').open('w') as fh:
- json.dump(settings, fh, indent=4)
- def writeCMakeFindFile(self, packageDir: pathlib.Path, template, templateEnv:dict, overwrite_find_file:str or None):
- cmakeFindFile = packageDir / f'Find{overwrite_find_file or self.packageName}.cmake'
- cmakeFindFile.write_text(string.Template(template).substitute(templateEnv))
|