Files
XCEngine/engine/third_party/physx/buildtools/cmake_generate_projects.py

420 lines
19 KiB
Python

import sys
import os
import glob
import os.path
import shutil
import subprocess
import xml.etree.ElementTree
def cmakeExt():
if sys.platform == 'win32':
return '.exe'
return ''
def filterPreset(presetPath):
# If this is a file path, extract the actual preset name from XML or filename
if os.path.isfile(presetPath):
try:
presetXml = xml.etree.ElementTree.parse(presetPath).getroot()
presetName = presetXml.get('name')
except:
# Fall back to just using the basename without extension if XML parsing fails
basename = os.path.basename(presetPath)
presetName = os.path.splitext(basename)[0]
else:
# If not a file path, assume it's already a preset name
presetName = presetPath
# Platform-specific filtering
winPresetFilter = ['win','switch','crosscompile']
if sys.platform == 'win32':
# On Windows, include presets that contain win, switch, or crosscompile
# (but not windows-crosscompile)
if any((presetName.find(elem) != -1 and 'windows-crosscompile' not in presetName) for elem in winPresetFilter):
return True
else:
# On non-Windows, include Linux presets and windows-crosscompile
# Check for Linux or other Unix/macOS presets (those not containing Windows-specific terms)
# Special case: include windows-crosscompile, which is for cross-compiling Windows targets
if 'linux' in presetName.lower() or 'mac' in presetName.lower() or 'windows-crosscompile' in presetName:
return True
if all(presetName.find(elem) == -1 for elem in ['win', 'switch']):
return True
return False
def noPresetProvided(physx_root_dir):
global input
print('Preset parameter required, available presets:')
presets_dir = os.path.join(physx_root_dir, "buildtools", "presets")
internal_presets = os.path.join(presets_dir, "*.xml")
public_presets = os.path.join(presets_dir, "public", "*.xml")
# Get all XML files in the presets directory
internal_preset_files = glob.glob(internal_presets)
# Check if we have any non-directory XML files directly in presets folder
presetfiles = []
for file in internal_preset_files:
if not os.path.isdir(file): # Make sure it's a file, not a directory
basename = os.path.basename(file)
dirname = os.path.dirname(file)
if os.path.basename(dirname) != "public": # Skip files in public subdirectory
presetfiles.append(file)
# If no XML files in main presets directory, we're in public distribution
# So use the files from public directory
if len(presetfiles) == 0:
print("No presets in main folder, using public presets")
presetfiles = glob.glob(public_presets)
if len(presetfiles) == 0:
print("Error: No preset files found. Make sure the directory structure is correct.")
exit(1)
counter = 0
presetList = []
for preset in presetfiles:
if filterPreset(preset):
try:
presetXml = xml.etree.ElementTree.parse(preset).getroot()
if preset.find('user') == -1:
print('(' + str(counter) + ') ' + presetXml.get('name') +
' <--- ' + presetXml.get('comment'))
presetList.append(presetXml.get('name'))
else:
print('(' + str(counter) + ') ' + presetXml.get('name') +
'.user <--- ' + presetXml.get('comment'))
presetList.append(presetXml.get('name') + '.user')
counter = counter + 1
except Exception as e:
print(f"Warning: Could not parse preset file {preset}: {e}")
continue
if counter == 0:
print("Error: No valid presets found for this platform.")
exit(1)
# Fix Python 2.x.
try:
input = raw_input
except NameError:
pass
mode = int(eval(input('Enter preset number: ')))
return presetList[mode]
class CMakePreset:
presetName = ''
targetPlatform = ''
compiler = ''
generator = ''
cmakeSwitches = []
cmakeParams = []
def __init__(self, presetName, physx_root_dir):
xmlPath = os.path.join(physx_root_dir, "buildtools", "presets", f"{presetName}.xml")
if os.path.isfile(xmlPath):
print('Using preset xml: '+xmlPath)
else:
xmlPath = os.path.join(physx_root_dir, "buildtools", "presets", "public", f"{presetName}.xml")
if os.path.isfile(xmlPath):
print('Using preset xml: '+xmlPath)
else:
print('Preset xml file: '+xmlPath+' not found')
exit()
# get the xml
presetNode = xml.etree.ElementTree.parse(xmlPath).getroot()
self.presetName = presetNode.attrib['name']
for platform in presetNode.findall('platform'):
self.targetPlatform = platform.attrib['targetPlatform']
self.compiler = platform.attrib['compiler']
self.generator = platform.get('generator')
print('Target platform: ' + self.targetPlatform +
' using compiler: ' + self.compiler)
if self.generator is not None:
print(' using generator: ' + self.generator)
for cmakeSwitch in presetNode.find('CMakeSwitches'):
cmSwitch = '-D' + \
cmakeSwitch.attrib['name'] + '=' + \
cmakeSwitch.attrib['value'].upper()
self.cmakeSwitches.append(cmSwitch)
for cmakeParam in presetNode.find('CMakeParams'):
if cmakeParam.attrib['name'] == 'CMAKE_INSTALL_PREFIX' or cmakeParam.attrib['name'] == 'PX_OUTPUT_LIB_DIR' or cmakeParam.attrib['name'] == 'PX_OUTPUT_EXE_DIR' or cmakeParam.attrib['name'] == 'PX_OUTPUT_DLL_DIR':
cmParam = '-D' + cmakeParam.attrib['name'] + '=\"' + \
os.environ['PHYSX_ROOT_DIR'] + '/' + \
cmakeParam.attrib['value'] + '\"'
else:
cmParam = '-D' + \
cmakeParam.attrib['name'] + '=' + \
cmakeParam.attrib['value']
self.cmakeParams.append(cmParam)
pass
def isMultiConfigPlatform(self):
if self.targetPlatform == 'linux':
return False
elif self.targetPlatform == 'linuxAarch64':
return False
elif self.compiler == 'x86_64-w64-mingw32-g++':
return False
return True
def getCMakeSwitches(self):
outString = ''
# We need to check both GPU-related switches
gpuProjectsEnabled = False
gpuProjectsOnlyEnabled = False
# Define the switch names for clarity and consistency
GPU_PROJECTS_SWITCH = 'PX_GENERATE_GPU_PROJECTS'
GPU_PROJECTS_ONLY_SWITCH = 'PX_GENERATE_GPU_PROJECTS_ONLY'
# First pass: Check the state of GPU-related switches
gpu_projects_found = False
gpu_projects_only_found = False
for cmakeSwitch in self.cmakeSwitches:
# Format of cmakeSwitch is "-DSWITCH_NAME=VALUE"
# Use a more flexible approach to match switches
if f'-D{GPU_PROJECTS_SWITCH}=' in cmakeSwitch:
gpu_projects_found = True
gpuProjectsEnabled = cmakeSwitch.endswith('=TRUE')
elif f'-D{GPU_PROJECTS_ONLY_SWITCH}=' in cmakeSwitch:
gpu_projects_only_found = True
gpuProjectsOnlyEnabled = cmakeSwitch.endswith('=TRUE')
# Log the state of GPU switches for debugging
if not gpu_projects_found:
print(f"Warning: {GPU_PROJECTS_SWITCH} switch not found in preset. Defaulting to disabled.")
if not gpu_projects_only_found:
print(f"Warning: {GPU_PROJECTS_ONLY_SWITCH} switch not found in preset. Defaulting to disabled.")
# Determine if we need to add CUDA paths
gpuEnabled = gpuProjectsEnabled or gpuProjectsOnlyEnabled
# Log GPU status
print(f"GPU projects enabled: {gpuEnabled} ({GPU_PROJECTS_SWITCH}={gpuProjectsEnabled}, {GPU_PROJECTS_ONLY_SWITCH}={gpuProjectsOnlyEnabled})")
# Second pass: Add all switches to output
for cmakeSwitch in self.cmakeSwitches:
outString = outString + ' ' + cmakeSwitch
# Only add CUDA paths if GPU is enabled
if gpuEnabled:
if os.environ.get('PM_CUDA_PATH') is not None:
if os.environ.get('PM_CUDA_PATH') is not None:
outString = outString + ' -DCUDAToolkit_ROOT_DIR=' + \
os.environ['PM_CUDA_PATH']
if self.compiler in ['vc15', 'vc16', 'vc17'] and self.generator != 'ninja':
outString = outString + ' -T cuda=' + os.environ['PM_CUDA_PATH']
# TODO: Need to do the same for gcc (aarch64) when we package it with Packman
elif self.compiler == 'clang':
if os.environ.get('PM_clang_PATH') is not None:
outString = outString + ' -DCMAKE_CUDA_HOST_COMPILER=' + \
os.environ['PM_clang_PATH'] + '/bin/clang++'
return outString
def getCMakeParams(self):
outString = ''
for cmakeParam in self.cmakeParams:
outString = outString + ' ' + cmakeParam # + ' --trace'
return outString
def getPlatformCMakeParams(self):
cmake_modules_root = os.environ['PHYSX_ROOT_DIR'] + '/source/compiler/cmake/modules'
outString = ' '
vs_versions = {
'vc15': '\"Visual Studio 15 2017\"',
'vc16': '\"Visual Studio 16 2019\"',
'vc17': '\"Visual Studio 17 2022\"'
}
# Visual studio
if self.compiler in vs_versions:
generator = '-G \"Ninja Multi-Config\"' if self.generator == 'ninja' else '-G ' + vs_versions[self.compiler]
outString += generator
# Windows crosscompile
elif self.compiler == 'x86_64-w64-mingw32-g++':
outString = outString + '-G \"Ninja\"'
# mac
elif self.compiler == 'xcode':
outString = outString + '-G Xcode'
# Linux
elif self.targetPlatform in ['linux', 'linuxAarch64']:
if self.generator is not None and self.generator == 'ninja':
outString = outString + '-G \"Ninja\"'
outString = outString + ' -DCMAKE_MAKE_PROGRAM=' + os.environ['PM_ninja_PATH'] + '/ninja'
else:
outString = outString + '-G \"Unix Makefiles\"'
if self.targetPlatform == 'win64':
if self.generator != 'ninja':
outString = outString + ' -Ax64'
outString = outString + ' -DTARGET_BUILD_PLATFORM=windows'
outString = outString + ' -DPX_OUTPUT_ARCH=x86'
if self.compiler == 'x86_64-w64-mingw32-g++':
outString = outString + ' -DCMAKE_TOOLCHAIN_FILE=' + \
cmake_modules_root + '/linux/WindowsCrossToolchain.linux-unknown-x86_64.cmake'
return outString
elif self.targetPlatform == 'switch64':
outString = outString + ' -DTARGET_BUILD_PLATFORM=switch'
outString = outString + ' -DCMAKE_TOOLCHAIN_FILE=' + \
cmake_modules_root + '/switch/NX64Toolchain.txt'
outString = outString + ' -DCMAKE_GENERATOR_PLATFORM=NX64'
return outString
elif self.targetPlatform == 'linux':
outString = outString + ' -DTARGET_BUILD_PLATFORM=linux'
outString = outString + ' -DPX_OUTPUT_ARCH=x86'
if self.compiler == 'clang-crosscompile':
outString = outString + ' -DCMAKE_TOOLCHAIN_FILE=' + \
cmake_modules_root + '/linux/LinuxCrossToolchain.x86_64-unknown-linux-gnu.cmake'
outString = outString + ' -DCMAKE_MAKE_PROGRAM=' + os.environ.get('PM_MinGW_PATH') + '/bin/mingw32-make.exe'
elif self.compiler == 'clang':
if os.environ.get('PM_clang_PATH') is not None:
outString = outString + ' -DCMAKE_C_COMPILER=' + \
os.environ['PM_clang_PATH'] + '/bin/clang'
outString = outString + ' -DCMAKE_CXX_COMPILER=' + \
os.environ['PM_clang_PATH'] + '/bin/clang++'
else:
outString = outString + ' -DCMAKE_C_COMPILER=clang'
outString = outString + ' -DCMAKE_CXX_COMPILER=clang++'
return outString
elif self.targetPlatform == 'linuxAarch64':
outString = outString + ' -DTARGET_BUILD_PLATFORM=linux'
outString = outString + ' -DPX_OUTPUT_ARCH=arm'
if self.compiler == 'clang-crosscompile':
outString = outString + ' -DCMAKE_TOOLCHAIN_FILE=' + \
cmake_modules_root + '/linux/LinuxCrossToolchain.aarch64-unknown-linux-gnueabihf.cmake'
outString = outString + ' -DCMAKE_MAKE_PROGRAM=' + os.environ.get('PM_MinGW_PATH') + '/bin/mingw32-make.exe'
elif self.compiler == 'gcc':
# TODO: To change so it uses Packman's compiler. Then add it as
# host compiler for CUDA above.
outString = outString + ' -DCMAKE_TOOLCHAIN_FILE=\"' + \
cmake_modules_root + '/linux/LinuxAarch64.cmake\"'
elif self.compiler == 'clang':
if os.environ.get('PM_clang_PATH') is not None:
outString = outString + ' -DCMAKE_C_COMPILER=' + \
os.environ['PM_clang_PATH'] + '/bin/clang'
outString = outString + ' -DCMAKE_CXX_COMPILER=' + \
os.environ['PM_clang_PATH'] + '/bin/clang++'
else:
outString = outString + ' -DCMAKE_C_COMPILER=clang'
outString = outString + ' -DCMAKE_CXX_COMPILER=clang++'
return outString
elif self.targetPlatform == 'mac64':
outString = outString + ' -DTARGET_BUILD_PLATFORM=mac'
outString = outString + ' -DPX_OUTPUT_ARCH=x86'
return outString
return ''
def getCommonParams():
outString = '--no-warn-unused-cli'
outString = outString + ' -DCMAKE_PREFIX_PATH=\"' + os.environ['PM_PATHS'] + '\"'
outString = outString + ' -DPHYSX_ROOT_DIR=\"' + \
os.environ['PHYSX_ROOT_DIR'] + '\"'
outString = outString + ' -DPX_OUTPUT_LIB_DIR=\"' + \
os.environ['PHYSX_ROOT_DIR'] + '\"'
outString = outString + ' -DPX_OUTPUT_BIN_DIR=\"' + \
os.environ['PHYSX_ROOT_DIR'] + '\"'
if os.environ.get('GENERATE_SOURCE_DISTRO') == '1':
outString = outString + ' -DPX_GENERATE_SOURCE_DISTRO=1'
return outString
def cleanupCompilerDir(compilerDirName):
if os.path.exists(compilerDirName):
if sys.platform == 'win32':
os.system('rmdir /S /Q ' + compilerDirName)
else:
shutil.rmtree(compilerDirName, True)
if os.path.exists(compilerDirName) == False:
os.makedirs(compilerDirName)
def presetProvided(pName, physx_root_dir):
parsedPreset = CMakePreset(pName, physx_root_dir)
print('PM_PATHS: ' + os.environ['PM_PATHS'])
if os.environ.get('PM_cmake_PATH') is not None:
cmakeExec = os.environ['PM_cmake_PATH'] + '/bin/cmake' + cmakeExt()
else:
cmakeExec = 'cmake' + cmakeExt()
print('Cmake: ' + cmakeExec)
# gather cmake parameters
cmakeParams = parsedPreset.getPlatformCMakeParams()
cmakeParams = cmakeParams + ' ' + getCommonParams()
cmakeParams = cmakeParams + ' ' + parsedPreset.getCMakeSwitches()
cmakeParams = cmakeParams + ' ' + parsedPreset.getCMakeParams()
# print(cmakeParams)
if os.path.isfile(physx_root_dir + '/compiler/internal/CMakeLists.txt'):
cmakeMasterDir = 'internal'
else:
cmakeMasterDir = 'public'
if parsedPreset.isMultiConfigPlatform():
# cleanup and create output directory
outputDir = os.path.join(physx_root_dir, 'compiler', parsedPreset.presetName)
cleanupCompilerDir(outputDir)
# run the cmake script
#print('Cmake params:' + cmakeParams)
os.chdir(outputDir)
os.system(cmakeExec + ' \"' +
physx_root_dir + '/compiler/' + cmakeMasterDir + '\"' + cmakeParams)
os.chdir(physx_root_dir)
else:
configs = ['debug', 'checked', 'profile', 'release']
for config in configs:
# cleanup and create output directory
outputDir = os.path.join(physx_root_dir, 'compiler', parsedPreset.presetName + '-' + config)
cleanupCompilerDir(outputDir)
# run the cmake script
#print('Cmake params:' + cmakeParams)
os.chdir(outputDir)
# print(cmakeExec + ' \"' + physx_root_dir + '/compiler/' + cmakeMasterDir + '\"' + cmakeParams + ' -DCMAKE_BUILD_TYPE=' + config)
os.system(cmakeExec + ' \"' + physx_root_dir + '/compiler/' +
cmakeMasterDir + '\"' + cmakeParams + ' -DCMAKE_BUILD_TYPE=' + config)
os.chdir(physx_root_dir)
pass
def main():
if (sys.version_info[0] < 3) or (sys.version_info[0] == 3 and sys.version_info[1] < 5):
print("You are using Python {}. You must use Python 3.5 and up. Please read README.md for requirements.").format(sys.version)
exit()
physx_root_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir))
os.environ['PHYSX_ROOT_DIR'] = physx_root_dir.replace("\\", "/")
if len(sys.argv) != 2:
presetName = noPresetProvided(physx_root_dir) # Ensure this function returns the preset name
if sys.platform == 'win32':
print('Running generate_projects.bat ' + presetName)
cmd_path = os.path.join(physx_root_dir, 'generate_projects.bat')
cmd = f'"{cmd_path}" {presetName}'
result = subprocess.run(cmd, cwd=physx_root_dir, check=True, shell=True, universal_newlines=True)
# TODO: catch exception and add capture errors
else:
print('Running generate_projects.sh ' + presetName)
cmd_path = os.path.join(physx_root_dir, 'generate_projects.sh')
cmd = [cmd_path, presetName]
result = subprocess.run(cmd, cwd=physx_root_dir, check=True, universal_newlines=True)
# TODO: catch exception and add capture errors
else:
presetName = sys.argv[1]
if filterPreset(presetName):
presetProvided(presetName, physx_root_dir)
else:
print('Preset not supported on this build platform.')
main()