[Build] Support cross-compilation via xb premake --target_os
This commit is contained in:
parent
25606774e1
commit
14157e063a
311
xenia-build
311
xenia-build
|
@ -23,16 +23,89 @@ self_path = os.path.dirname(os.path.abspath(__file__))
|
|||
|
||||
|
||||
# Detect if building on Android via Termux.
|
||||
host_os_is_android = False
|
||||
host_linux_platform_is_android = False
|
||||
if sys.platform == 'linux':
|
||||
try:
|
||||
host_os_is_android = subprocess.Popen(
|
||||
host_linux_platform_is_android = subprocess.Popen(
|
||||
['uname', '-o'], stdout=subprocess.PIPE, stderr=subprocess.DEVNULL,
|
||||
universal_newlines=True).communicate()[0] == 'Android\n'
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def import_subprocess_environment(args):
|
||||
popen = subprocess.Popen(
|
||||
args, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True)
|
||||
variables, _ = popen.communicate()
|
||||
envvars_to_save = (
|
||||
'devenvdir',
|
||||
'include',
|
||||
'lib',
|
||||
'libpath',
|
||||
'path',
|
||||
'pathext',
|
||||
'systemroot',
|
||||
'temp',
|
||||
'tmp',
|
||||
'windowssdkdir',
|
||||
)
|
||||
for line in variables.splitlines():
|
||||
for envvar in envvars_to_save:
|
||||
if re.match(envvar + '=', line.lower()):
|
||||
var, setting = line.split('=', 1)
|
||||
if envvar == 'path':
|
||||
setting = os.path.dirname(sys.executable) + os.pathsep + setting
|
||||
os.environ[var.upper()] = setting
|
||||
break
|
||||
|
||||
|
||||
def import_vs_environment():
|
||||
"""Finds the installed Visual Studio version and imports
|
||||
interesting environment variables into os.environ.
|
||||
|
||||
Returns:
|
||||
A version such as 2015 or None if no installation is found.
|
||||
"""
|
||||
|
||||
if sys.platform != 'win32':
|
||||
return None
|
||||
|
||||
version = 0
|
||||
install_path = None
|
||||
env_tool_args = None
|
||||
|
||||
vswhere = subprocess.check_output('third_party/vswhere/vswhere.exe -version "[15,)" -latest -prerelease -format json -utf8', shell=False, universal_newlines=True, encoding="utf-8")
|
||||
if vswhere:
|
||||
vswhere = json.loads(vswhere)
|
||||
if vswhere and len(vswhere) > 0:
|
||||
version = int(vswhere[0].get("catalog", {}).get("productLineVersion", 2017))
|
||||
install_path = vswhere[0].get("installationPath", None)
|
||||
|
||||
if version < 2017:
|
||||
if 'VS140COMNTOOLS' in os.environ:
|
||||
version = 2015
|
||||
vcvars_path = os.environ['VS140COMNTOOLS']
|
||||
vcvars_path = os.path.join(tools_path, '..\\..\\vc\\vcvarsall.bat')
|
||||
env_tool_args = [vcvars_path, 'x64', '&&', 'set']
|
||||
else:
|
||||
vsdevcmd_path = os.path.join(install_path, 'Common7\\Tools\\VsDevCmd.bat')
|
||||
if os.path.isfile(vsdevcmd_path) and os.access(vsdevcmd_path, os.X_OK):
|
||||
env_tool_args = [vsdevcmd_path, '-arch=amd64', '-host_arch=amd64', '&&', 'set']
|
||||
else:
|
||||
vcvars_path = os.path.join(install_path, 'VC\\Auxiliary\\Build\\vcvarsall.bat')
|
||||
env_tool_args = [vcvars_path, 'x64', '&&', 'set']
|
||||
|
||||
if version == 0:
|
||||
return None
|
||||
|
||||
import_subprocess_environment(env_tool_args)
|
||||
os.environ['VSVERSION'] = str(version)
|
||||
return version
|
||||
|
||||
|
||||
vs_version = import_vs_environment()
|
||||
|
||||
|
||||
def main():
|
||||
# Add self to the root search path.
|
||||
sys.path.insert(0, self_path)
|
||||
|
@ -57,13 +130,11 @@ def main():
|
|||
sys.exit(1)
|
||||
|
||||
# Grab Visual Studio version and execute shell to set up environment.
|
||||
if sys.platform == 'win32':
|
||||
vs_version = import_vs_environment()
|
||||
if vs_version is None:
|
||||
print('ERROR: Visual Studio not found!')
|
||||
print('Please refer to the building guide:')
|
||||
print('https://github.com/xenia-project/xenia/blob/master/docs/building.md')
|
||||
sys.exit(1)
|
||||
if sys.platform == 'win32' and vs_version is None:
|
||||
print('WARNING: Visual Studio not found!')
|
||||
print('Building for Windows will not be supported.')
|
||||
print('Please refer to the building guide:')
|
||||
print('https://github.com/xenia-project/xenia/blob/master/docs/building.md')
|
||||
|
||||
# Setup main argument parser and common arguments.
|
||||
parser = argparse.ArgumentParser(prog='xenia-build')
|
||||
|
@ -109,72 +180,6 @@ def print_box(msg):
|
|||
.format('', msg, len(msg) + 2))
|
||||
|
||||
|
||||
def import_vs_environment():
|
||||
"""Finds the installed Visual Studio version and imports
|
||||
interesting environment variables into os.environ.
|
||||
|
||||
Returns:
|
||||
A version such as 2015 or None if no installation is found.
|
||||
"""
|
||||
version = 0
|
||||
install_path = None
|
||||
env_tool_args = None
|
||||
|
||||
vswhere = subprocess.check_output('third_party/vswhere/vswhere.exe -version "[15,)" -latest -prerelease -format json -utf8', shell=False, universal_newlines=True, encoding="utf-8")
|
||||
if vswhere:
|
||||
vswhere = json.loads(vswhere)
|
||||
if vswhere and len(vswhere) > 0:
|
||||
version = int(vswhere[0].get("catalog", {}).get("productLineVersion", 2017))
|
||||
install_path = vswhere[0].get("installationPath", None)
|
||||
|
||||
if version < 2017:
|
||||
if 'VS140COMNTOOLS' in os.environ:
|
||||
version = 2015
|
||||
vcvars_path = os.environ['VS140COMNTOOLS']
|
||||
vcvars_path = os.path.join(tools_path, '..\\..\\vc\\vcvarsall.bat')
|
||||
env_tool_args = [vcvars_path, 'x64', '&&', 'set']
|
||||
else:
|
||||
vsdevcmd_path = os.path.join(install_path, 'Common7\\Tools\\VsDevCmd.bat')
|
||||
if os.path.isfile(vsdevcmd_path) and os.access(vsdevcmd_path, os.X_OK):
|
||||
env_tool_args = [vsdevcmd_path, '-arch=amd64', '-host_arch=amd64', '&&', 'set']
|
||||
else:
|
||||
vcvars_path = os.path.join(install_path, 'VC\\Auxiliary\\Build\\vcvarsall.bat')
|
||||
env_tool_args = [vcvars_path, 'x64', '&&', 'set']
|
||||
|
||||
if version == 0:
|
||||
return None
|
||||
|
||||
import_subprocess_environment(env_tool_args)
|
||||
os.environ['VSVERSION'] = str(version)
|
||||
return version
|
||||
|
||||
|
||||
def import_subprocess_environment(args):
|
||||
popen = subprocess.Popen(
|
||||
args, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True)
|
||||
variables, _ = popen.communicate()
|
||||
envvars_to_save = (
|
||||
'devenvdir',
|
||||
'include',
|
||||
'lib',
|
||||
'libpath',
|
||||
'path',
|
||||
'pathext',
|
||||
'systemroot',
|
||||
'temp',
|
||||
'tmp',
|
||||
'windowssdkdir',
|
||||
)
|
||||
for line in variables.splitlines():
|
||||
for envvar in envvars_to_save:
|
||||
if re.match(envvar + '=', line.lower()):
|
||||
var, setting = line.split('=', 1)
|
||||
if envvar == 'path':
|
||||
setting = os.path.dirname(sys.executable) + os.pathsep + setting
|
||||
os.environ[var.upper()] = setting
|
||||
break
|
||||
|
||||
|
||||
def has_bin(binary):
|
||||
"""Checks whether the given binary is present.
|
||||
|
||||
|
@ -347,6 +352,37 @@ def get_clang_format_binary():
|
|||
sys.exit(1)
|
||||
|
||||
|
||||
def get_premake_target_os(target_os_override=None):
|
||||
"""Gets the target --os to pass to premake, either for the current platform
|
||||
or for the user-specified cross-compilation target.
|
||||
|
||||
Args:
|
||||
target_os_override: override specified by the user for cross-compilation,
|
||||
or None to target the host platform.
|
||||
|
||||
Returns:
|
||||
Target --os to pass to premake. If a return value of this function valid
|
||||
for the current configuration is passed to it function again, the same
|
||||
value will be returned.
|
||||
"""
|
||||
if sys.platform == 'darwin':
|
||||
target_os = 'macosx'
|
||||
elif sys.platform == 'win32':
|
||||
target_os = 'windows'
|
||||
elif host_linux_platform_is_android:
|
||||
target_os = 'android'
|
||||
else:
|
||||
target_os = 'linux'
|
||||
if target_os_override is not None and target_os_override != target_os:
|
||||
if target_os_override == 'android':
|
||||
target_os = target_os_override
|
||||
else:
|
||||
print(
|
||||
'ERROR: cross-compilation is only supported for Android target')
|
||||
sys.exit(0)
|
||||
return target_os
|
||||
|
||||
|
||||
def run_premake(target_os, action, cc=None):
|
||||
"""Runs premake on the main project with the given format.
|
||||
|
||||
|
@ -373,42 +409,26 @@ def run_premake(target_os, action, cc=None):
|
|||
|
||||
return ret
|
||||
|
||||
def run_premake_clean():
|
||||
"""Runs a premake clean operation.
|
||||
"""
|
||||
if sys.platform == 'darwin':
|
||||
return run_premake('macosx', 'clean')
|
||||
elif sys.platform == 'win32':
|
||||
return run_premake('windows', 'clean')
|
||||
else:
|
||||
return run_premake('linux', 'clean')
|
||||
|
||||
|
||||
def run_platform_premake(cc='clang', devenv=None):
|
||||
def run_platform_premake(target_os_override=None, cc='clang', devenv=None):
|
||||
"""Runs all gyp configurations.
|
||||
"""
|
||||
if sys.platform == 'darwin':
|
||||
return run_premake('macosx', 'xcode4')
|
||||
elif sys.platform == 'win32':
|
||||
vs_version = '2015'
|
||||
if 'VSVERSION' in os.environ:
|
||||
vs_version = os.environ['VSVERSION']
|
||||
|
||||
return run_premake('windows', devenv or ('vs' + vs_version))
|
||||
else:
|
||||
return run_premake('linux', devenv or 'gmake2', cc)
|
||||
|
||||
|
||||
def run_premake_export_commands():
|
||||
"""Runs premake to generate an LLVM compile_commands.json file.
|
||||
"""
|
||||
# TODO(benvanik): only do linux? whatever clang-tidy is ok with.
|
||||
if sys.platform == 'darwin':
|
||||
run_premake('macosx', 'export-compile-commands')
|
||||
elif sys.platform == 'win32':
|
||||
run_premake('windows', 'export-compile-commands')
|
||||
else:
|
||||
run_premake('linux', 'export-compile-commands')
|
||||
target_os = get_premake_target_os(target_os_override)
|
||||
if devenv is None:
|
||||
if target_os == 'macosx':
|
||||
devenv = 'xcode4'
|
||||
elif target_os == 'windows':
|
||||
vs_version = '2015'
|
||||
if 'VSVERSION' in os.environ:
|
||||
vs_version = os.environ['VSVERSION']
|
||||
devenv = 'vs' + vs_version
|
||||
elif target_os == 'android':
|
||||
devenv = 'androidmk'
|
||||
else:
|
||||
devenv = 'gmake2'
|
||||
if target_os != 'linux':
|
||||
cc = None
|
||||
return run_premake(target_os=target_os, action=devenv, cc=cc)
|
||||
|
||||
|
||||
def get_build_bin_path(args):
|
||||
|
@ -545,6 +565,9 @@ class SetupCommand(Command):
|
|||
name='setup',
|
||||
help_short='Setup the build environment.',
|
||||
*args, **kwargs)
|
||||
self.parser.add_argument(
|
||||
'--target_os', default=None,
|
||||
help='Target OS passed to premake, for cross-compilation')
|
||||
|
||||
def execute(self, args, pass_args, cwd):
|
||||
print('Setting up the build environment...')
|
||||
|
@ -559,7 +582,7 @@ class SetupCommand(Command):
|
|||
print('')
|
||||
|
||||
print('- running premake...')
|
||||
if run_platform_premake() == 0:
|
||||
if run_platform_premake(target_os_override=args['target_os']) == 0:
|
||||
print('')
|
||||
print('Success!')
|
||||
|
||||
|
@ -575,8 +598,12 @@ class PullCommand(Command):
|
|||
name='pull',
|
||||
help_short='Pulls the repo and all dependencies and rebases changes.',
|
||||
*args, **kwargs)
|
||||
self.parser.add_argument('--merge', action='store_true',
|
||||
help='Merges on master instead of rebasing.')
|
||||
self.parser.add_argument(
|
||||
'--merge', action='store_true',
|
||||
help='Merges on master instead of rebasing.')
|
||||
self.parser.add_argument(
|
||||
'--target_os', default=None,
|
||||
help='Target OS passed to premake, for cross-compilation')
|
||||
|
||||
def execute(self, args, pass_args, cwd):
|
||||
print('Pulling...')
|
||||
|
@ -609,7 +636,7 @@ class PullCommand(Command):
|
|||
print('')
|
||||
|
||||
print('- running premake...')
|
||||
if run_platform_premake() == 0:
|
||||
if run_platform_premake(target_os_override=args['target_os']) == 0:
|
||||
print('')
|
||||
print('Success!')
|
||||
|
||||
|
@ -629,12 +656,16 @@ class PremakeCommand(Command):
|
|||
'--cc', default='clang', help='Compiler toolchain passed to premake')
|
||||
self.parser.add_argument(
|
||||
'--devenv', default=None, help='Development environment')
|
||||
self.parser.add_argument(
|
||||
'--target_os', default=None,
|
||||
help='Target OS passed to premake, for cross-compilation')
|
||||
|
||||
def execute(self, args, pass_args, cwd):
|
||||
# Update premake. If no binary found, it will be built from source.
|
||||
print('Running premake...')
|
||||
print('')
|
||||
if run_platform_premake(cc=args['cc'], devenv=args['devenv']) == 0:
|
||||
if run_platform_premake(target_os_override=args['target_os'],
|
||||
cc=args['cc'], devenv=args['devenv']) == 0:
|
||||
print('Success!')
|
||||
|
||||
return 0
|
||||
|
@ -667,7 +698,7 @@ class BaseBuildCommand(Command):
|
|||
def execute(self, args, pass_args, cwd):
|
||||
if not args['no_premake']:
|
||||
print('- running premake...')
|
||||
run_platform_premake(args['cc'])
|
||||
run_platform_premake(cc=args['cc'])
|
||||
print('')
|
||||
|
||||
threads = args['j']
|
||||
|
@ -678,21 +709,27 @@ class BaseBuildCommand(Command):
|
|||
'all' if not len(args['target']) else ', '.join(args['target']),
|
||||
args['config']))
|
||||
if sys.platform == 'win32':
|
||||
targets = None
|
||||
if len(args['target']):
|
||||
targets = '/t:' + ';'.join(target + (':Rebuild' if args['force'] else '')
|
||||
for target in args['target'])
|
||||
if vs_version is None:
|
||||
print('ERROR: Visual Studio is not installed.');
|
||||
result = 1
|
||||
else:
|
||||
targets = '/t:Rebuild' if args['force'] else None
|
||||
targets = None
|
||||
if len(args['target']):
|
||||
targets = '/t:' + ';'.join(
|
||||
target + (':Rebuild' if args['force'] else '')
|
||||
for target in args['target'])
|
||||
else:
|
||||
targets = '/t:Rebuild' if args['force'] else None
|
||||
|
||||
result = subprocess.call([
|
||||
'msbuild',
|
||||
'build/xenia.sln',
|
||||
'/nologo',
|
||||
'/m',
|
||||
'/v:m',
|
||||
'/p:Configuration=' + args['config'],
|
||||
] + ([targets] if targets is not None else []) + pass_args, shell=False)
|
||||
result = subprocess.call([
|
||||
'msbuild',
|
||||
'build/xenia.sln',
|
||||
'/nologo',
|
||||
'/m',
|
||||
'/v:m',
|
||||
'/p:Configuration=' + args['config'],
|
||||
] + ([targets] if targets is not None else []) + pass_args,
|
||||
shell=False)
|
||||
elif sys.platform == 'darwin':
|
||||
# TODO(benvanik): other platforms.
|
||||
print('ERROR: don\'t know how to build on this platform.')
|
||||
|
@ -1174,13 +1211,16 @@ class CleanCommand(Command):
|
|||
name='clean',
|
||||
help_short='Removes intermediate files and build outputs.',
|
||||
*args, **kwargs)
|
||||
self.parser.add_argument(
|
||||
'--target_os', default=None,
|
||||
help='Target OS passed to premake, for cross-compilation')
|
||||
|
||||
def execute(self, args, pass_args, cwd):
|
||||
print('Cleaning build artifacts...')
|
||||
print('')
|
||||
|
||||
print('- premake clean...')
|
||||
run_premake_clean()
|
||||
run_premake(get_premake_target_os(args['target_os']), 'clean')
|
||||
print('')
|
||||
|
||||
print('Success!')
|
||||
|
@ -1196,6 +1236,9 @@ class NukeCommand(Command):
|
|||
name='nuke',
|
||||
help_short='Removes all build/ output.',
|
||||
*args, **kwargs)
|
||||
self.parser.add_argument(
|
||||
'--target_os', default=None,
|
||||
help='Target OS passed to premake, for cross-compilation')
|
||||
|
||||
def execute(self, args, pass_args, cwd):
|
||||
print('Cleaning build artifacts...')
|
||||
|
@ -1216,7 +1259,7 @@ class NukeCommand(Command):
|
|||
print('')
|
||||
|
||||
print('- running premake...')
|
||||
run_platform_premake()
|
||||
run_platform_premake(target_os_override=args['target_os'])
|
||||
print('')
|
||||
|
||||
print('Success!')
|
||||
|
@ -1444,10 +1487,15 @@ class TidyCommand(Command):
|
|||
self.parser.add_argument(
|
||||
'--fix', action='store_true',
|
||||
help='Applies suggested fixes, where possible.')
|
||||
self.parser.add_argument(
|
||||
'--target_os', default=None,
|
||||
help='Target OS passed to premake, for cross-compilation')
|
||||
|
||||
def execute(self, args, pass_args, cwd):
|
||||
# Run premake to generate our compile_commands.json file for clang to use.
|
||||
run_premake_export_commands()
|
||||
# TODO(benvanik): only do linux? whatever clang-tidy is ok with.
|
||||
run_premake(get_premake_target_os(args['target_os']),
|
||||
'export-compile-commands')
|
||||
|
||||
platform_name = ''
|
||||
if sys.platform == 'darwin':
|
||||
|
@ -1507,6 +1555,9 @@ class DevenvCommand(Command):
|
|||
devenv = None
|
||||
show_reload_prompt = False
|
||||
if sys.platform == 'win32':
|
||||
if vs_version is None:
|
||||
print('ERROR: Visual Studio is not installed.');
|
||||
return 1
|
||||
print('Launching Visual Studio...')
|
||||
elif has_bin('clion') or has_bin('clion.sh'):
|
||||
print('Launching CLion...')
|
||||
|
|
Loading…
Reference in New Issue