-
Notifications
You must be signed in to change notification settings - Fork 4
/
build.py
executable file
·328 lines (255 loc) · 8.78 KB
/
build.py
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
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
#!/usr/bin/env python
########################################
## \file build.py
## \date 01/01/2014
## \brief Automated build script for C++ projects using CMake.
########################################
#
# Global imports:
#
from __future__ import print_function
import multiprocessing, string, subprocess
from time import time
from colorama import init, Fore
from sys import exit
from os import remove
#
# Global start time:
#
startTime = time()
#
# Calculate elapsed time:
#
def elapsedTime(time1, time2):
elapsedTime = time2 - time1
totalHours = int(elapsedTime / 3600)
totalMinutes = int(elapsedTime / 60) % 60
totalSeconds = elapsedTime % 60
if totalHours != 0:
elapsedTimeString = "{:d} hours, {:d} minutes, & {:.4f} seconds.".format(totalHours, totalMinutes, totalSeconds)
elif totalMinutes != 0:
elapsedTimeString = "{:d} minutes & {:.4f} seconds.".format(totalMinutes, totalSeconds)
else:
elapsedTimeString = "{:.4f} seconds.".format(totalSeconds)
return elapsedTimeString
#
# Print log to stdout:
#
def displayLog(log):
print('')
print(Fore.MAGENTA + '#############################')
print(Fore.MAGENTA + 'Dumping log file: ' + log.name)
print(Fore.MAGENTA + '#############################')
print('')
# Open file and print it to stdout:
with open(log.name, 'r') as fin:
print(fin.read())
print('')
print(Fore.MAGENTA + '#############################')
print(Fore.MAGENTA + 'End log dump.')
print(Fore.MAGENTA + '#############################')
print('')
#
# Execute system call:
#
def sysCall(cmds, log, pad="", shellExec=False):
# Print command and run:
print(pad + "Running '" + " ".join(cmds) + "'...", end=' ')
beginTime = time()
returnCode = subprocess.call(cmds, shell=shellExec, stdout=log, stderr=subprocess.STDOUT)
endTime = time()
if returnCode != 0:
print(Fore.RED + 'ERROR!!! Please see log output below!')
displayLog(log)
completeScript(1)
print(Fore.GREEN + 'done. ' + elapsedTime(beginTime,endTime))
#
# Complete script:
#
def completeScript(exitCode=0):
# Elapsed time:
stopTime = time()
# Display execution time:
print('Execution time: ' + elapsedTime(startTime,stopTime))
print('')
# Script completion output:
if exitCode == 0:
print(Fore.GREEN + 'Script completed successfully: {:d}.'.format(exitCode))
else:
print(Fore.RED + 'Script completed UNsuccessfully: {:d}.'.format(exitCode))
print('')
# Exit:
exit(exitCode)
#
# UNIX build:
#
def unixBuild(log, args):
print('')
print('UNIX build starting...')
# Execute build commands:
sysCall(["make", "-j"+str(args.num_cpus)], log, "\t")
#sysCall(["make", "-j"+str(args.num_cpus), "test"], log, "\t")
sysCall(["make", "-j"+str(args.num_cpus), "install"], log, "\t")
print('UNIX build complete!')
print('')
#
# Windows build:
#
def windowsBuild(log, args):
print('')
print('Windows build starting...')
# Execute build commands:
sysCall(["msbuild", "ALL_BUILD.vcxproj"], log, "\t")
#sysCall(["msbuild", "RUN_TESTS.vcxproj"], log, "\t")
sysCall(["msbuild", "ZERO_CHECK.vcxproj"], log, "\t")
sysCall(["msbuild", "INSTALL.vcxproj"], log, "\t")
print('Windows build complete!')
print('')
#
# Main:
#
if __name__ == "__main__":
# Imports:
import argparse
from sys import argv
from os import path, getcwd, mkdir, chdir
from platform import system
from datetime import datetime
from shutil import rmtree
from glob import glob
#
# Argument parsing:
#
parser = argparse.ArgumentParser()
parser.add_argument("-b", "--build-type", help="Build type (default: Debug).", choices=["Debug","Release","RelWithDebInfo","MinSizeRel"], default="Debug")
parser.add_argument("-c", "--clean", help="Remove build directory in current working directory matching 'build_${BUILD_TYPE}' then continue build", action="store_true")
parser.add_argument("-g", "--build-generator", help="Build generator type that CMake produces, see 'cmake --help' for the available options on this platform (default: CMake decides based on system settings).", type=str, default="")
parser.add_argument("-i", "--install-prefix", help="Prefix for the install directory.", type=str, default="")
parser.add_argument("-j", "--num-cpus", help="Number of CPUs for parallel builds (default: number of CPUs on machine)", type=int, default=multiprocessing.cpu_count())
parser.add_argument("-l", "--log-display", help="Display build log to stdout.", action="store_true")
parser.add_argument("-n", "--build-dir-name", help="Name of the build directory created (default: 'build_${BUILD_TYPE}')", type=str, default="")
parser.add_argument("-r", "--remove-build", help="Remove current build directory after completion.", action="store_true")
parser.add_argument("-s", "--static-build", help="Build and link libraries static instead of shared.", action="store_true")
args = parser.parse_args()
#
# Init colorama:
#
init(autoreset=True)
#
# Build script init output:
#
print('')
print(Fore.GREEN + '######################')
print(Fore.GREEN + 'Automated Build Script')
print(Fore.GREEN + '######################')
print('')
# Get timestamp:
timeStamp = datetime.fromtimestamp(time()).strftime('%Y-%m-%d_%H-%M-%S')
# Get current path:
currentPath = path.abspath(getcwd())
# Get script path:
# NOTE:
# This SHOULD be cross-platform but some people say it isn't because of
# the argv... I have yet to come across a situation where this fails so
# I'm leaving it. I believe when it fails is if you have this in a module
# therefore there is no argv define. However, in this case, this script
# will always be run from the commnad line so we should not run into any
# issues.
scriptPath = path.abspath(path.dirname(argv[0]))
# Get OS:
localOS = system()
# Construct correct build directory name:
buildDirName = ""
if args.build_dir_name == "":
buildDirName = "build_" + args.build_type
else:
buildDirName = args.build_dir_name
# Remove build directory if exists and clean specified:
if args.clean:
if path.exists(buildDirName):
print("Removing build directory '" + buildDirName + "'... ", end='')
rmtree(buildDirName)
print(Fore.GREEN + 'done.')
print('')
else:
print("No build directory to clean.")
print('')
# Create build directories:
buildDirName = ""
if args.build_dir_name == "":
buildDirName = "build_" + args.build_type
else:
buildDirName = args.build_dir_name
buildRoot = path.join(currentPath, buildDirName)
buildDir = path.join(buildRoot, 'build')
# Try and create build directory. If it exists, use dir and rebuild.
reBuild = False;
try:
mkdir(buildRoot)
mkdir(buildDir)
except:
reBuild = True;
print("Build directory '" + buildRoot + "' already exists; re-running build process.")
print('')
# Diplay build directory to screen:
print('Build location: ' + Fore.MAGENTA + buildRoot + "\n")
# Create install directory if prefix was not supplied:
if args.install_prefix == "":
installDir = path.join(buildRoot, 'local-install')
if not reBuild:
mkdir(installDir)
else:
installDir = path.join(args.install_prefix)
# Set library build type:
if args.static_build:
sharedLibs = "OFF"
else:
sharedLibs = "ON"
# Move into build directory:
chdir(buildDir)
# Create logfile:
logFile = path.join(buildRoot, 'build.log')
log = open(logFile, 'w');
# Check number of CPUs:
if args.num_cpus <= 2:
args.num_cpus = 1
elif (args.num_cpus > 2) and (args.num_cpus <= 4):
args.num_cpus = args.num_cpus - 1
elif (args.num_cpus > 5) and (args.num_cpus <= 10):
args.num_cpus = args.num_cpus - 2
else:
args.num_cpus = args.num_cpus - 4
# Build up CMake command based on CLI options:
cmakeCmd = [
"cmake", scriptPath,
"-DCMAKE_INSTALL_PREFIX:PATH='" + installDir + "'",
"-DCMAKE_BUILD_TYPE:STRING=" + args.build_type,
"-DBUILD_SHARED_LIBS:BOOL=" + sharedLibs,
"-DCMAKE_INSTALL_SO_NO_EXE:BOOL=OFF"
]
if args.build_generator != "":
cmakeCmd.append("-G" + args.build_generator)
# On all platforms, we at least have to run cmake first to get build files:
sysCall(cmakeCmd, log)
# Execute build based on platform from this point on:
if localOS == 'Linux' or localOS == 'Darwin':
unixBuild(log, args)
elif localOS == 'Windows':
windowsBuild(log, args)
else:
print(Fore.RED + '**ERROR**: OS platform "' + localOS + '" not recognized; aborting!')
completeScript(1)
# Display log if cmd argument set:
if args.log_display:
displayLog(log)
# Close log file:
log.close()
# Remove build directory if flag set
chdir(scriptPath)
if args.remove_build:
print('Removing current build directory...', end=' ')
rmtree(buildRoot)
print(Fore.GREEN + 'done.')
print('')
# Exit cleanly:
completeScript()