420 lines
19 KiB
Python
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()
|