389 lines
13 KiB
Python
389 lines
13 KiB
Python
|
#!/usr/bin/python
|
||
|
#----------------------------------------------------------------------------#
|
||
|
# #
|
||
|
# ozz-animation is hosted at http://github.com/guillaumeblanc/ozz-animation #
|
||
|
# and distributed under the MIT License (MIT). #
|
||
|
# #
|
||
|
# Copyright (c) 2019 Guillaume Blanc #
|
||
|
# #
|
||
|
# Permission is hereby granted, free of charge, to any person obtaining a #
|
||
|
# copy of this software and associated documentation files (the "Software"), #
|
||
|
# to deal in the Software without restriction, including without limitation #
|
||
|
# the rights to use, copy, modify, merge, publish, distribute, sublicense, #
|
||
|
# and/or sell copies of the Software, and to permit persons to whom the #
|
||
|
# Software is furnished to do so, subject to the following conditions: #
|
||
|
# #
|
||
|
# The above copyright notice and this permission notice shall be included in #
|
||
|
# all copies or substantial portions of the Software. #
|
||
|
# #
|
||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #
|
||
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #
|
||
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL #
|
||
|
# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #
|
||
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING #
|
||
|
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER #
|
||
|
# DEALINGS IN THE SOFTWARE. #
|
||
|
# #
|
||
|
#----------------------------------------------------------------------------#
|
||
|
|
||
|
# CMake python helper script.
|
||
|
|
||
|
import subprocess
|
||
|
import shutil
|
||
|
import sys
|
||
|
import os
|
||
|
import os.path
|
||
|
import re
|
||
|
import platform
|
||
|
from functools import partial
|
||
|
|
||
|
|
||
|
# Build global path variables.
|
||
|
root = os.path.abspath(os.path.join(os.getcwd(), '.'))
|
||
|
build_dir = os.path.join(root, 'build')
|
||
|
build_dir_cc = os.path.join(root, 'build-cc')
|
||
|
cmake_cache_file = os.path.join(build_dir, 'CMakeCache.txt')
|
||
|
ctest_cache_file = os.path.join(build_dir, 'CTestTestfile.cmake')
|
||
|
config = 'Release'
|
||
|
generators = {0: 'default'}
|
||
|
generator = generators[0]
|
||
|
enable_testing = False
|
||
|
emscripten_path = os.environ.get('EMSCRIPTEN')
|
||
|
|
||
|
def ValidateCMake():
|
||
|
try:
|
||
|
# Test that cmake can be executed, silently...
|
||
|
pipe = subprocess.Popen(['cmake'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||
|
out, err = pipe.communicate()
|
||
|
except OSError as e:
|
||
|
print("CMake is not installed or properly setup. Please visit www.cmake.org.")
|
||
|
return False
|
||
|
|
||
|
print("CMake is installed and setup properly.")
|
||
|
return True
|
||
|
|
||
|
def CheckEmscripten():
|
||
|
if(emscripten_path == None):
|
||
|
return False
|
||
|
|
||
|
try:
|
||
|
# Test that cmake can be executed, silently...
|
||
|
pipe = subprocess.Popen(['emcc'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||
|
out, err = pipe.communicate()
|
||
|
except OSError as e:
|
||
|
print("Emscripten is not installed or properly setup.")
|
||
|
return False
|
||
|
|
||
|
print("Emscripten is installed and setup properly.")
|
||
|
return True
|
||
|
|
||
|
def MakeBuildDir(_build_dir = build_dir):
|
||
|
print("Creating out-of-source build directory: \"" + _build_dir + "\".")
|
||
|
if not os.path.exists(_build_dir):
|
||
|
os.makedirs(_build_dir)
|
||
|
return True
|
||
|
|
||
|
def CleanBuildDir():
|
||
|
print("Deleting out-of-source build directory: \"" + build_dir + "\".")
|
||
|
if os.path.exists(build_dir):
|
||
|
shutil.rmtree(build_dir)
|
||
|
print("Deleting out-of-source cross compilation build directory: \"" + build_dir_cc + "\".")
|
||
|
if os.path.exists(build_dir_cc):
|
||
|
shutil.rmtree(build_dir_cc)
|
||
|
return True
|
||
|
|
||
|
def Configure():
|
||
|
# Configure build process.
|
||
|
print("Configuring build project.")
|
||
|
options = ['cmake']
|
||
|
options += ['-D', 'CMAKE_BUILD_TYPE=' + config]
|
||
|
|
||
|
if (enable_testing) :
|
||
|
options += ['-D', 'ozz_build_tests=1']
|
||
|
else:
|
||
|
options += ['-D', 'ozz_build_tests=0']
|
||
|
|
||
|
global generator
|
||
|
if(generator != 'default'):
|
||
|
options += ['-G', generator]
|
||
|
options += [root]
|
||
|
config_process = subprocess.Popen(options, cwd=build_dir)
|
||
|
config_process.wait()
|
||
|
if(config_process.returncode != 0):
|
||
|
print("Configuration failed.")
|
||
|
return False
|
||
|
print("Configuration succeeded.")
|
||
|
|
||
|
# Updates generator once configuration is complete
|
||
|
generator = DetectGenerator()
|
||
|
|
||
|
return True
|
||
|
|
||
|
def ConfigureCC():
|
||
|
# Configure build process.
|
||
|
print("Configuring emscripten cross compilation build with path: " + emscripten_path)
|
||
|
options = ['cmake']
|
||
|
|
||
|
options += ['-D', 'CMAKE_BUILD_TYPE=' + config]
|
||
|
options += ['-D', 'CMAKE_TOOLCHAIN_FILE=' + emscripten_path + '/cmake/Modules/Platform/Emscripten.cmake']
|
||
|
|
||
|
if(platform.system() == 'Windows'):
|
||
|
options += ['-G', 'MinGW Makefiles']
|
||
|
else:
|
||
|
options += ['-G', 'Unix Makefiles']
|
||
|
|
||
|
options += [root]
|
||
|
config_process = subprocess.Popen(options, cwd=build_dir_cc)
|
||
|
config_process.wait()
|
||
|
if(config_process.returncode != 0):
|
||
|
print("Configuration failed.")
|
||
|
return False
|
||
|
print("Configuration succeeded.")
|
||
|
|
||
|
# Updates generator once configuration is complete
|
||
|
generator = DetectGenerator()
|
||
|
|
||
|
return True
|
||
|
|
||
|
def Build(_build_dir = build_dir):
|
||
|
# Configure build process.
|
||
|
print("Building project.")
|
||
|
options = ['cmake', '--build', _build_dir, '--config', config, '--use-stderr'];
|
||
|
# Appends parallel build option if supported by the generator.
|
||
|
if "Unix Makefiles" in generator:
|
||
|
options += ['--', '-j4']
|
||
|
config_process = subprocess.Popen(options, cwd=_build_dir)
|
||
|
config_process.wait()
|
||
|
if(config_process.returncode != 0):
|
||
|
print("Build failed.")
|
||
|
return False
|
||
|
print("Build succeeded.")
|
||
|
return True
|
||
|
|
||
|
def Test():
|
||
|
# Configure Test process, parallelize a lot of tests in order to stress their dependencies
|
||
|
print("Running unit tests.")
|
||
|
options = ['ctest' ,'--output-on-failure', '-j8', '--build-config', config]
|
||
|
config_process = subprocess.Popen(options, cwd=build_dir)
|
||
|
config_process.wait()
|
||
|
if(config_process.returncode != 0):
|
||
|
print("Testing failed.")
|
||
|
return False
|
||
|
print("Testing succeeded.")
|
||
|
return True
|
||
|
|
||
|
def PackSources(_type):
|
||
|
print("Packing sources.")
|
||
|
options = ['cpack', '-G', _type, '--config', 'CPackSourceConfig.cmake']
|
||
|
config_process = subprocess.Popen(options, cwd=build_dir)
|
||
|
config_process.wait()
|
||
|
if(config_process.returncode != 0):
|
||
|
print("Packing sources of type " + _type + " failed.")
|
||
|
return False
|
||
|
print("Packing sources of type " + _type + " succeeded.")
|
||
|
return True
|
||
|
|
||
|
def PackBinaries(_type, _build_dir = build_dir):
|
||
|
print("Packing binaries.")
|
||
|
options = ['cpack', '-G', _type, '-C', config]
|
||
|
config_process = subprocess.Popen(options, cwd=_build_dir)
|
||
|
config_process.wait()
|
||
|
if(config_process.returncode != 0):
|
||
|
print("Packing binaries of type " + _type + " failed.")
|
||
|
return False
|
||
|
print("Packing binaries of type " + _type + " succeeded.")
|
||
|
return True
|
||
|
|
||
|
def SelecConfig():
|
||
|
configs = {
|
||
|
1: 'Debug',
|
||
|
2: 'Release',
|
||
|
3: 'RelWithDebInfo',
|
||
|
4: 'MinSizeRel'}
|
||
|
|
||
|
while True:
|
||
|
print("Select build configuration:")
|
||
|
for num, message in sorted(configs.iteritems()):
|
||
|
print("%d: %s") % (num, message)
|
||
|
|
||
|
# Get input and check validity
|
||
|
try:
|
||
|
answer = int(raw_input("Enter a value: "))
|
||
|
except:
|
||
|
continue
|
||
|
if not answer in configs:
|
||
|
continue
|
||
|
|
||
|
# Affect global configuration variable
|
||
|
global config
|
||
|
config = configs[answer]
|
||
|
return True
|
||
|
|
||
|
def FindGenerators():
|
||
|
# Finds all generators outputted from cmake usage
|
||
|
process = subprocess.Popen(['cmake', '--help'], stdout=subprocess.PIPE)
|
||
|
stdout = process.communicate()[0]
|
||
|
sub_stdout = stdout[stdout.rfind('Generators'):]
|
||
|
matches = re.findall(r"\s*\**\s*(.+)\s*=.+", sub_stdout, re.MULTILINE)
|
||
|
# Fills generators list
|
||
|
global generators
|
||
|
for match in matches:
|
||
|
generator_name = match.strip()
|
||
|
# Appends also Win64/ARM option if generator is VS
|
||
|
if " [arch]" in generator_name:
|
||
|
gen_name = generator_name[0:len(generator_name) - 7]
|
||
|
generators[len(generators)] = gen_name
|
||
|
generators[len(generators)] = gen_name + " Win64"
|
||
|
generators[len(generators)] = gen_name + " ARM"
|
||
|
else:
|
||
|
generators[len(generators)] = generator_name
|
||
|
|
||
|
def FindInCache(_regex):
|
||
|
try:
|
||
|
cache_file = open(cmake_cache_file)
|
||
|
except:
|
||
|
return None
|
||
|
return re.search(_regex, cache_file.read())
|
||
|
|
||
|
def DetectGenerator():
|
||
|
match = FindInCache(r"CMAKE_GENERATOR:INTERNAL=(.*)")
|
||
|
if match:
|
||
|
global generators
|
||
|
global generator
|
||
|
for num, message in sorted(generators.iteritems()):
|
||
|
if match.group(1) == message:
|
||
|
return message
|
||
|
return 'default'
|
||
|
|
||
|
def SelecGenerator():
|
||
|
global generators
|
||
|
while True:
|
||
|
print("Select generator:")
|
||
|
for num, message in sorted(generators.iteritems()):
|
||
|
print("%d: %s") % (num, message)
|
||
|
|
||
|
# Get input and check validity
|
||
|
try:
|
||
|
answer = int(raw_input("Enter a value: "))
|
||
|
except:
|
||
|
continue
|
||
|
if not answer in generators:
|
||
|
continue
|
||
|
|
||
|
# Check if this is the current generator
|
||
|
current_generator = DetectGenerator()
|
||
|
if current_generator == 'default':
|
||
|
global generator
|
||
|
generator = generators[answer]
|
||
|
return True
|
||
|
if current_generator != generators[answer]:
|
||
|
print("Selected generator '%s' is different from the current one '%s'.") % (generators[answer], current_generator)
|
||
|
clean = raw_input("Do you want to clean build directory to apply the change? (y/n): ") == "y"
|
||
|
if clean:
|
||
|
generator = generators[answer]
|
||
|
return CleanBuildDir()
|
||
|
return True
|
||
|
|
||
|
def DetectTesting():
|
||
|
global enable_testing
|
||
|
enable_testing = os.path.isfile(ctest_cache_file)
|
||
|
|
||
|
def EnableTesting():
|
||
|
global enable_testing
|
||
|
while True:
|
||
|
# Get input and check validity
|
||
|
answer = raw_input("enable testing (y/n): ")
|
||
|
if answer != 'y' and answer != 'n':
|
||
|
continue
|
||
|
wanted = (answer == 'y')
|
||
|
|
||
|
# Get current state
|
||
|
if (enable_testing != wanted):
|
||
|
enable_testing = wanted
|
||
|
print("Testing state has changed.")
|
||
|
|
||
|
return True
|
||
|
|
||
|
def ClearScreen():
|
||
|
os.system('cls' if os.name=='nt' else 'clear')
|
||
|
|
||
|
def Quit():
|
||
|
sys.exit(0)
|
||
|
return True
|
||
|
|
||
|
def main():
|
||
|
|
||
|
# Checks CMake installation is correct.
|
||
|
if not ValidateCMake():
|
||
|
return
|
||
|
|
||
|
# Emscripten is optional
|
||
|
CheckEmscripten()
|
||
|
|
||
|
# Detects available generators
|
||
|
FindGenerators()
|
||
|
|
||
|
# Detects testing state
|
||
|
DetectTesting()
|
||
|
|
||
|
# Update current generator
|
||
|
print("DetectGenerator")
|
||
|
global generator
|
||
|
generator = DetectGenerator()
|
||
|
|
||
|
options = {
|
||
|
'1': ["Build", [MakeBuildDir, Configure, Build]],
|
||
|
'2': ["Run unit tests", [MakeBuildDir, Configure, Build, Test]],
|
||
|
'3': ["Execute CMake generation step (don't build)", [MakeBuildDir, Configure]],
|
||
|
'4': ["Clean out-of-source build directory\n ------------------", [CleanBuildDir]],
|
||
|
'5': ["Pack binaries", [MakeBuildDir, Configure, Build, partial(PackBinaries, "ZIP"), partial(PackBinaries, "TBZ2")]],
|
||
|
'6': ["Pack sources\n ------------------", [MakeBuildDir, Configure, partial(PackSources, "ZIP"), partial(PackSources, "TBZ2")]],
|
||
|
'7': ["Enable testing", [EnableTesting]],
|
||
|
'8': ["Select build configuration", [SelecConfig]],
|
||
|
'9': ["Select cmake generator\n ------------------", [SelecGenerator]],
|
||
|
'q': ["Quit\n------------------", [Quit]]}
|
||
|
|
||
|
# Adds emscripten
|
||
|
global emscripten_path
|
||
|
if emscripten_path != None:
|
||
|
options['1a'] = ["Build emscripten", [partial(MakeBuildDir, build_dir_cc), ConfigureCC, partial(Build, build_dir_cc)]]
|
||
|
options['5a'] = ["Pack emscripten binaries", [MakeBuildDir, Configure, Build, partial(MakeBuildDir, build_dir_cc), ConfigureCC, partial(Build, build_dir_cc), partial(PackBinaries, "ZIP", build_dir_cc)]]
|
||
|
|
||
|
while True:
|
||
|
# Displays options
|
||
|
ClearScreen()
|
||
|
print("ozz CMake build helper tool")
|
||
|
print("")
|
||
|
print("Selected build configuration: %s") % config
|
||
|
print("Selected generator: %s") % generator
|
||
|
print("Testing enabled: %s") % enable_testing
|
||
|
print("")
|
||
|
print("Choose an option:")
|
||
|
print("------------------")
|
||
|
for key, message in sorted(options.iteritems()):
|
||
|
print(" %s: %s") % (key, message[0])
|
||
|
|
||
|
# Get input and check validity
|
||
|
answer = raw_input("Enter a value: ")
|
||
|
if not answer in options:
|
||
|
continue
|
||
|
|
||
|
# Execute command in a try catch to avoid crashes and allow retries.
|
||
|
ClearScreen()
|
||
|
try:
|
||
|
for command in options[answer][1]:
|
||
|
if command():
|
||
|
print("\nExecution success.\n")
|
||
|
else:
|
||
|
print("\nExecution failed.\n")
|
||
|
break
|
||
|
except Exception as e:
|
||
|
print("\nAn error occured during script execution: %s\n") % e
|
||
|
|
||
|
raw_input("Press enter to continue...")
|
||
|
|
||
|
return 0
|
||
|
|
||
|
if __name__ == '__main__':
|
||
|
main()
|