summaryrefslogtreecommitdiff
path: root/scripts/test_build_internal.py
blob: b31f63d54b9c910ee8b07c355c8dbf585cffd69d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
import argparse
import os
from pathlib import Path
import platform
import shutil
import subprocess
import sys


parser = argparse.ArgumentParser(
	description='Test building the API repository and examples in-tree'
)
parser.add_argument('--headless', default=False, action='store_true', help='Only include headless plugins')
parser.add_argument('-j', '--parallel', default=4, help='Number of parallel jobs to tell cmake to run.')
parser.add_argument('--bn-install-dir', default=os.environ.get('BN_INSTALL_DIR'),
                    help='Path to a Binary Ninja installation. Defaults to BN_INSTALL_DIR.')
parser.add_argument('--qt-install-dir', default=os.environ.get('QT_INSTALL_DIR'),
                    help='Path to a Qt installation. Defaults to QT_INSTALL_DIR.')
parser.add_argument('--qmake', default=os.environ.get('QMAKE'), help='Path to qmake. Defaults to QMAKE or PATH lookup.')
parser.add_argument('--cmake', default=os.environ.get('CMAKE'), help='Path to cmake. Defaults to CMAKE or PATH lookup.')
parser.add_argument('--cc', default=os.environ.get('CC'), help='C compiler to use. Defaults to CC or CMake platform default.')
parser.add_argument('--cxx', default=os.environ.get('CXX'), help='C++ compiler to use. Defaults to CXX or CMake platform default.')
args = parser.parse_args()

api_base = Path(__file__).parent.parent.absolute()


def fail(message):
	print(f'ERROR: {message}')
	sys.exit(1)


def prepend_path(env, path):
	env['PATH'] = f'{path}{os.pathsep}{env.get("PATH", "")}'


def find_executable(name, explicit_path=None, env=None):
	if explicit_path:
		path = Path(explicit_path).expanduser()
		if path.is_file():
			return str(path)
		fail(f'{name} was specified as {path}, but that file does not exist.')

	return shutil.which(name, path=(env or os.environ).get('PATH'))


def run_tool(tool, args_for_tool, env=None):
	try:
		result = subprocess.run([tool] + args_for_tool, check=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
		                        text=True, env=env)
		return result.stdout.strip()
	except subprocess.CalledProcessError as e:
		output = e.stdout.strip() if e.stdout else ''
		fail(f'{tool} failed with exit code {e.returncode}.\n{output}')


def print_tool(tool_name, tool_path, version_args, env=None):
	print(f'{tool_name}: {tool_path}')
	version = run_tool(tool_path, version_args, env=env)
	print(version)


def configure_qt_env(env):
	qt_dir = Path(args.qt_install_dir).expanduser().absolute() if args.qt_install_dir else None
	if qt_dir:
		if not qt_dir.exists():
			fail(f'Qt directory was specified as {qt_dir}, but it does not exist.')
		qt_bin_dir = qt_dir / 'bin'
		if not qt_bin_dir.exists():
			fail(f'Qt directory {qt_dir} does not contain a bin directory.')
		prepend_path(env, qt_bin_dir)
		env.setdefault('CMAKE_PREFIX_PATH', str(qt_dir / 'lib' / 'cmake'))
		print(f'Qt directory: {qt_dir}')
		print(f'CMAKE_PREFIX_PATH: {env["CMAKE_PREFIX_PATH"]}')

	if args.headless:
		return

	qmake = find_executable('qmake', args.qmake, env=env)
	if not qmake:
		fail('qmake not found. Install Qt and add its bin directory to PATH, set QT_INSTALL_DIR, or pass --qt-install-dir/--qmake.')

	print_tool('qmake', qmake, ['--version'], env=env)
	qt_lib_path = run_tool(qmake, ['-query', 'QT_INSTALL_LIBS'], env=env)
	env.setdefault('CMAKE_PREFIX_PATH', str(Path(qt_lib_path) / 'cmake'))
	print(f'Qt libraries: {qt_lib_path}')
	print(f'CMAKE_PREFIX_PATH: {env["CMAKE_PREFIX_PATH"]}')


def configure_binary_ninja_env(env):
	bn_install_dir = Path(args.bn_install_dir).expanduser().absolute() if args.bn_install_dir else None
	if not bn_install_dir:
		fail('Binary Ninja install directory was not specified. Set BN_INSTALL_DIR or pass --bn-install-dir.')
	if not bn_install_dir.exists():
		fail(f'Binary Ninja install directory {bn_install_dir} does not exist.')
	env['BN_INSTALL_DIR'] = str(bn_install_dir)
	print(f'Binary Ninja install: {bn_install_dir}')


env = os.environ.copy()
configure_qt_env(env)
configure_binary_ninja_env(env)

if args.cc:
	env['CC'] = args.cc
if args.cxx:
	env['CXX'] = args.cxx

cmake = find_executable('cmake', args.cmake, env=env)
if not cmake:
	fail('cmake not found. Install CMake and add it to PATH, set CMAKE, or pass --cmake.')
print_tool('cmake', cmake, ['--version'], env=env)

print(f'C compiler: {env.get("CC", "CMake platform default")}')
print(f'C++ compiler: {env.get("CXX", "CMake platform default")}')

configure_args = ['-DBN_API_BUILD_EXAMPLES=1']
build_args = []

if platform.system() == 'Windows':
	env['CXXFLAGS'] = f'/MP{args.parallel}'
	env['CFLAGS'] = f'/MP{args.parallel}'
else:
	build_args.extend(['-j', str(args.parallel)])

if args.headless:
	configure_args.extend(['-DHEADLESS=1'])

build_dir = api_base / 'build'
try:
	subprocess.check_call([cmake, '-B', str(build_dir)] + configure_args, cwd=api_base, env=env)
	subprocess.check_call([cmake, '--build', str(build_dir)] + build_args, cwd=api_base)
finally:
	if build_dir.exists():
		shutil.rmtree(build_dir)