[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.
|
# Detect if building on Android via Termux.
|
||||||
host_os_is_android = False
|
host_linux_platform_is_android = False
|
||||||
if sys.platform == 'linux':
|
if sys.platform == 'linux':
|
||||||
try:
|
try:
|
||||||
host_os_is_android = subprocess.Popen(
|
host_linux_platform_is_android = subprocess.Popen(
|
||||||
['uname', '-o'], stdout=subprocess.PIPE, stderr=subprocess.DEVNULL,
|
['uname', '-o'], stdout=subprocess.PIPE, stderr=subprocess.DEVNULL,
|
||||||
universal_newlines=True).communicate()[0] == 'Android\n'
|
universal_newlines=True).communicate()[0] == 'Android\n'
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
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():
|
def main():
|
||||||
# Add self to the root search path.
|
# Add self to the root search path.
|
||||||
sys.path.insert(0, self_path)
|
sys.path.insert(0, self_path)
|
||||||
|
@ -57,13 +130,11 @@ def main():
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
# Grab Visual Studio version and execute shell to set up environment.
|
# Grab Visual Studio version and execute shell to set up environment.
|
||||||
if sys.platform == 'win32':
|
if sys.platform == 'win32' and vs_version is None:
|
||||||
vs_version = import_vs_environment()
|
print('WARNING: Visual Studio not found!')
|
||||||
if vs_version is None:
|
print('Building for Windows will not be supported.')
|
||||||
print('ERROR: Visual Studio not found!')
|
print('Please refer to the building guide:')
|
||||||
print('Please refer to the building guide:')
|
print('https://github.com/xenia-project/xenia/blob/master/docs/building.md')
|
||||||
print('https://github.com/xenia-project/xenia/blob/master/docs/building.md')
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
# Setup main argument parser and common arguments.
|
# Setup main argument parser and common arguments.
|
||||||
parser = argparse.ArgumentParser(prog='xenia-build')
|
parser = argparse.ArgumentParser(prog='xenia-build')
|
||||||
|
@ -109,72 +180,6 @@ def print_box(msg):
|
||||||
.format('', msg, len(msg) + 2))
|
.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):
|
def has_bin(binary):
|
||||||
"""Checks whether the given binary is present.
|
"""Checks whether the given binary is present.
|
||||||
|
|
||||||
|
@ -347,6 +352,37 @@ def get_clang_format_binary():
|
||||||
sys.exit(1)
|
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):
|
def run_premake(target_os, action, cc=None):
|
||||||
"""Runs premake on the main project with the given format.
|
"""Runs premake on the main project with the given format.
|
||||||
|
|
||||||
|
@ -373,42 +409,26 @@ def run_premake(target_os, action, cc=None):
|
||||||
|
|
||||||
return ret
|
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(target_os_override=None, cc='clang', devenv=None):
|
||||||
def run_platform_premake(cc='clang', devenv=None):
|
|
||||||
"""Runs all gyp configurations.
|
"""Runs all gyp configurations.
|
||||||
"""
|
"""
|
||||||
if sys.platform == 'darwin':
|
target_os = get_premake_target_os(target_os_override)
|
||||||
return run_premake('macosx', 'xcode4')
|
if devenv is None:
|
||||||
elif sys.platform == 'win32':
|
if target_os == 'macosx':
|
||||||
vs_version = '2015'
|
devenv = 'xcode4'
|
||||||
if 'VSVERSION' in os.environ:
|
elif target_os == 'windows':
|
||||||
vs_version = os.environ['VSVERSION']
|
vs_version = '2015'
|
||||||
|
if 'VSVERSION' in os.environ:
|
||||||
return run_premake('windows', devenv or ('vs' + vs_version))
|
vs_version = os.environ['VSVERSION']
|
||||||
else:
|
devenv = 'vs' + vs_version
|
||||||
return run_premake('linux', devenv or 'gmake2', cc)
|
elif target_os == 'android':
|
||||||
|
devenv = 'androidmk'
|
||||||
|
else:
|
||||||
def run_premake_export_commands():
|
devenv = 'gmake2'
|
||||||
"""Runs premake to generate an LLVM compile_commands.json file.
|
if target_os != 'linux':
|
||||||
"""
|
cc = None
|
||||||
# TODO(benvanik): only do linux? whatever clang-tidy is ok with.
|
return run_premake(target_os=target_os, action=devenv, cc=cc)
|
||||||
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')
|
|
||||||
|
|
||||||
|
|
||||||
def get_build_bin_path(args):
|
def get_build_bin_path(args):
|
||||||
|
@ -545,6 +565,9 @@ class SetupCommand(Command):
|
||||||
name='setup',
|
name='setup',
|
||||||
help_short='Setup the build environment.',
|
help_short='Setup the build environment.',
|
||||||
*args, **kwargs)
|
*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):
|
def execute(self, args, pass_args, cwd):
|
||||||
print('Setting up the build environment...')
|
print('Setting up the build environment...')
|
||||||
|
@ -559,7 +582,7 @@ class SetupCommand(Command):
|
||||||
print('')
|
print('')
|
||||||
|
|
||||||
print('- running premake...')
|
print('- running premake...')
|
||||||
if run_platform_premake() == 0:
|
if run_platform_premake(target_os_override=args['target_os']) == 0:
|
||||||
print('')
|
print('')
|
||||||
print('Success!')
|
print('Success!')
|
||||||
|
|
||||||
|
@ -575,8 +598,12 @@ class PullCommand(Command):
|
||||||
name='pull',
|
name='pull',
|
||||||
help_short='Pulls the repo and all dependencies and rebases changes.',
|
help_short='Pulls the repo and all dependencies and rebases changes.',
|
||||||
*args, **kwargs)
|
*args, **kwargs)
|
||||||
self.parser.add_argument('--merge', action='store_true',
|
self.parser.add_argument(
|
||||||
help='Merges on master instead of rebasing.')
|
'--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):
|
def execute(self, args, pass_args, cwd):
|
||||||
print('Pulling...')
|
print('Pulling...')
|
||||||
|
@ -609,7 +636,7 @@ class PullCommand(Command):
|
||||||
print('')
|
print('')
|
||||||
|
|
||||||
print('- running premake...')
|
print('- running premake...')
|
||||||
if run_platform_premake() == 0:
|
if run_platform_premake(target_os_override=args['target_os']) == 0:
|
||||||
print('')
|
print('')
|
||||||
print('Success!')
|
print('Success!')
|
||||||
|
|
||||||
|
@ -629,12 +656,16 @@ class PremakeCommand(Command):
|
||||||
'--cc', default='clang', help='Compiler toolchain passed to premake')
|
'--cc', default='clang', help='Compiler toolchain passed to premake')
|
||||||
self.parser.add_argument(
|
self.parser.add_argument(
|
||||||
'--devenv', default=None, help='Development environment')
|
'--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):
|
def execute(self, args, pass_args, cwd):
|
||||||
# Update premake. If no binary found, it will be built from source.
|
# Update premake. If no binary found, it will be built from source.
|
||||||
print('Running premake...')
|
print('Running premake...')
|
||||||
print('')
|
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!')
|
print('Success!')
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
|
@ -667,7 +698,7 @@ class BaseBuildCommand(Command):
|
||||||
def execute(self, args, pass_args, cwd):
|
def execute(self, args, pass_args, cwd):
|
||||||
if not args['no_premake']:
|
if not args['no_premake']:
|
||||||
print('- running premake...')
|
print('- running premake...')
|
||||||
run_platform_premake(args['cc'])
|
run_platform_premake(cc=args['cc'])
|
||||||
print('')
|
print('')
|
||||||
|
|
||||||
threads = args['j']
|
threads = args['j']
|
||||||
|
@ -678,21 +709,27 @@ class BaseBuildCommand(Command):
|
||||||
'all' if not len(args['target']) else ', '.join(args['target']),
|
'all' if not len(args['target']) else ', '.join(args['target']),
|
||||||
args['config']))
|
args['config']))
|
||||||
if sys.platform == 'win32':
|
if sys.platform == 'win32':
|
||||||
targets = None
|
if vs_version is None:
|
||||||
if len(args['target']):
|
print('ERROR: Visual Studio is not installed.');
|
||||||
targets = '/t:' + ';'.join(target + (':Rebuild' if args['force'] else '')
|
result = 1
|
||||||
for target in args['target'])
|
|
||||||
else:
|
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([
|
result = subprocess.call([
|
||||||
'msbuild',
|
'msbuild',
|
||||||
'build/xenia.sln',
|
'build/xenia.sln',
|
||||||
'/nologo',
|
'/nologo',
|
||||||
'/m',
|
'/m',
|
||||||
'/v:m',
|
'/v:m',
|
||||||
'/p:Configuration=' + args['config'],
|
'/p:Configuration=' + args['config'],
|
||||||
] + ([targets] if targets is not None else []) + pass_args, shell=False)
|
] + ([targets] if targets is not None else []) + pass_args,
|
||||||
|
shell=False)
|
||||||
elif sys.platform == 'darwin':
|
elif sys.platform == 'darwin':
|
||||||
# TODO(benvanik): other platforms.
|
# TODO(benvanik): other platforms.
|
||||||
print('ERROR: don\'t know how to build on this platform.')
|
print('ERROR: don\'t know how to build on this platform.')
|
||||||
|
@ -1174,13 +1211,16 @@ class CleanCommand(Command):
|
||||||
name='clean',
|
name='clean',
|
||||||
help_short='Removes intermediate files and build outputs.',
|
help_short='Removes intermediate files and build outputs.',
|
||||||
*args, **kwargs)
|
*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):
|
def execute(self, args, pass_args, cwd):
|
||||||
print('Cleaning build artifacts...')
|
print('Cleaning build artifacts...')
|
||||||
print('')
|
print('')
|
||||||
|
|
||||||
print('- premake clean...')
|
print('- premake clean...')
|
||||||
run_premake_clean()
|
run_premake(get_premake_target_os(args['target_os']), 'clean')
|
||||||
print('')
|
print('')
|
||||||
|
|
||||||
print('Success!')
|
print('Success!')
|
||||||
|
@ -1196,6 +1236,9 @@ class NukeCommand(Command):
|
||||||
name='nuke',
|
name='nuke',
|
||||||
help_short='Removes all build/ output.',
|
help_short='Removes all build/ output.',
|
||||||
*args, **kwargs)
|
*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):
|
def execute(self, args, pass_args, cwd):
|
||||||
print('Cleaning build artifacts...')
|
print('Cleaning build artifacts...')
|
||||||
|
@ -1216,7 +1259,7 @@ class NukeCommand(Command):
|
||||||
print('')
|
print('')
|
||||||
|
|
||||||
print('- running premake...')
|
print('- running premake...')
|
||||||
run_platform_premake()
|
run_platform_premake(target_os_override=args['target_os'])
|
||||||
print('')
|
print('')
|
||||||
|
|
||||||
print('Success!')
|
print('Success!')
|
||||||
|
@ -1444,10 +1487,15 @@ class TidyCommand(Command):
|
||||||
self.parser.add_argument(
|
self.parser.add_argument(
|
||||||
'--fix', action='store_true',
|
'--fix', action='store_true',
|
||||||
help='Applies suggested fixes, where possible.')
|
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):
|
def execute(self, args, pass_args, cwd):
|
||||||
# Run premake to generate our compile_commands.json file for clang to use.
|
# 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 = ''
|
platform_name = ''
|
||||||
if sys.platform == 'darwin':
|
if sys.platform == 'darwin':
|
||||||
|
@ -1507,6 +1555,9 @@ class DevenvCommand(Command):
|
||||||
devenv = None
|
devenv = None
|
||||||
show_reload_prompt = False
|
show_reload_prompt = False
|
||||||
if sys.platform == 'win32':
|
if sys.platform == 'win32':
|
||||||
|
if vs_version is None:
|
||||||
|
print('ERROR: Visual Studio is not installed.');
|
||||||
|
return 1
|
||||||
print('Launching Visual Studio...')
|
print('Launching Visual Studio...')
|
||||||
elif has_bin('clion') or has_bin('clion.sh'):
|
elif has_bin('clion') or has_bin('clion.sh'):
|
||||||
print('Launching CLion...')
|
print('Launching CLion...')
|
||||||
|
|
Loading…
Reference in New Issue