3 build helper script for edk2, see
4 https://gitlab.com/kraxel/edk2-build-config
16 version_override
= None
19 # pylint: disable=unused-variable
21 """ detect 'git rebase -x edk2-build.py master' testbuilds """
23 global version_override
26 if os
.path
.isfile(gitdir
):
27 with
open(gitdir
, 'r', encoding
= 'utf-8') as f
:
28 (unused
, gitdir
) = f
.read().split()
30 if not os
.path
.exists(f
'{gitdir}/rebase-merge/msgnum'):
32 with
open(f
'{gitdir}/rebase-merge/msgnum', 'r', encoding
= 'utf-8') as f
:
33 msgnum
= int(f
.read())
34 with
open(f
'{gitdir}/rebase-merge/end', 'r', encoding
= 'utf-8') as f
:
36 with
open(f
'{gitdir}/rebase-merge/head-name', 'r', encoding
= 'utf-8') as f
:
37 head
= f
.read().strip().split('/')
39 rebase_prefix
= f
'[ {int(msgnum/2)} / {int(end/2)} - {head[-1]} ] '
40 if msgnum
!= end
and not version_override
:
41 # fixed version speeds up builds
42 version_override
= "test-build-patch-series"
45 if cfg
.has_option('global', 'core'):
46 return os
.path
.abspath(cfg
['global']['core'])
49 def get_toolchain(cfg
, build
):
50 if cfg
.has_option(build
, 'tool'):
51 return cfg
[build
]['tool']
52 if cfg
.has_option('global', 'tool'):
53 return cfg
['global']['tool']
56 def get_version(cfg
, silent
= False):
57 coredir
= get_coredir(cfg
)
59 version
= version_override
62 print(f
'### version [override]: {version}')
64 if os
.environ
.get('RPM_PACKAGE_NAME'):
65 version
= os
.environ
.get('RPM_PACKAGE_NAME')
66 version
+= '-' + os
.environ
.get('RPM_PACKAGE_VERSION')
67 version
+= '-' + os
.environ
.get('RPM_PACKAGE_RELEASE')
70 print(f
'### version [rpmbuild]: {version}')
72 if os
.path
.exists(coredir
+ '/.git'):
73 cmdline
= [ 'git', 'describe', '--tags', '--abbrev=8',
74 '--match=edk2-stable*' ]
75 result
= subprocess
.run(cmdline
, cwd
= coredir
,
76 stdout
= subprocess
.PIPE
,
78 version
= result
.stdout
.decode().strip()
81 print(f
'### version [git]: {version}')
85 def pcd_string(name
, value
):
86 return f
'{name}=L{value}\\0'
88 def pcd_version(cfg
, silent
= False):
89 version
= get_version(cfg
, silent
)
92 return [ '--pcd', pcd_string('PcdFirmwareVersionString', version
) ]
94 def pcd_release_date():
95 if release_date
is None:
97 return [ '--pcd', pcd_string('PcdFirmwareReleaseDateString', release_date
) ]
99 def build_message(line
, line2
= None, silent
= False):
100 if os
.environ
.get('TERM') in [ 'xterm', 'xterm-256color' ]:
104 print(f
'{start}{rebase_prefix}{line}{end}', end
= '')
107 print(f
'### {rebase_prefix}{line}', flush
= True)
111 print(f
'### {rebase_prefix}{line}')
113 print(f
'### {line2}')
114 print('###', flush
= True)
116 def build_run(cmdline
, name
, section
, silent
= False, nologs
= False):
118 logfile
= f
'{section}.log'
120 print(f
'### building in silent mode [no log] ...', flush
= True)
122 print(f
'### building in silent mode [{logfile}] ...', flush
= True)
124 result
= subprocess
.run(cmdline
, check
= False,
125 stdout
= subprocess
.PIPE
,
126 stderr
= subprocess
.STDOUT
)
128 with
open(logfile
, 'wb') as f
:
129 f
.write(result
.stdout
)
131 if result
.returncode
:
132 print('### BUILD FAILURE')
136 print(result
.stdout
.decode())
137 print(f
'### exit code: {result.returncode}')
139 secs
= int(time
.time() - start
)
140 print(f
'### OK ({int(secs/60)}:{secs%60:02d})')
142 print(cmdline
, flush
= True)
143 result
= subprocess
.run(cmdline
, check
= False)
144 if result
.returncode
:
145 print(f
'ERROR: {cmdline[0]} exited with {result.returncode}'
146 f
' while building {name}')
147 sys
.exit(result
.returncode
)
149 def build_copy(plat
, tgt
, toolchain
, dstdir
, copy
):
150 srcdir
= f
'Build/{plat}/{tgt}_{toolchain}'
156 dstfile
= os
.path
.basename(srcfile
)
157 print(f
'# copy: {srcdir} / {srcfile} => {dstdir} / {dstfile}')
159 src
= srcdir
+ '/' + srcfile
160 dst
= dstdir
+ '/' + dstfile
161 os
.makedirs(os
.path
.dirname(dst
), exist_ok
= True)
162 shutil
.copy(src
, dst
)
164 def pad_file(dstdir
, pad
):
167 raise RuntimeError(f
'missing arg for pad ({args})')
175 print(f
'# padding: {dstdir} / {name} => {size}')
176 subprocess
.run(cmdline
, check
= True)
178 # pylint: disable=too-many-branches
179 def build_one(cfg
, build
, jobs
= None, silent
= False, nologs
= False):
182 cmdline
= [ 'build' ]
183 cmdline
+= [ '-t', get_toolchain(cfg
, build
) ]
184 cmdline
+= [ '-p', b
['conf'] ]
186 if (b
['conf'].startswith('OvmfPkg/') or
187 b
['conf'].startswith('ArmVirtPkg/')):
188 cmdline
+= pcd_version(cfg
, silent
)
189 cmdline
+= pcd_release_date()
192 cmdline
+= [ '-n', jobs
]
193 for arch
in b
['arch'].split():
194 cmdline
+= [ '-a', arch
]
196 for name
in b
['opts'].split():
197 section
= 'opts.' + name
198 for opt
in cfg
[section
]:
199 cmdline
+= [ '-D', opt
+ '=' + cfg
[section
][opt
] ]
201 for name
in b
['pcds'].split():
202 section
= 'pcds.' + name
203 for pcd
in cfg
[section
]:
204 cmdline
+= [ '--pcd', pcd
+ '=' + cfg
[section
][pcd
] ]
206 tgts
= b
['tgts'].split()
213 build_message(f
'building: {b["conf"]} ({b["arch"]}, {tgt})',
214 f
'description: {desc}',
216 build_run(cmdline
+ [ '-b', tgt
],
225 if not cpy
.startswith('cpy'):
227 build_copy(b
['plat'], tgt
,
228 get_toolchain(cfg
, build
),
232 if not pad
.startswith('pad'):
234 pad_file(b
['dest'], b
[pad
])
236 def build_basetools(silent
= False, nologs
= False):
237 build_message('building: BaseTools', silent
= silent
)
238 basedir
= os
.environ
['EDK_TOOLS_PATH']
239 cmdline
= [ 'make', '-C', basedir
]
240 build_run(cmdline
, 'BaseTools', 'build.basetools', silent
, nologs
)
242 def binary_exists(name
):
243 for pdir
in os
.environ
['PATH'].split(':'):
244 if os
.path
.exists(pdir
+ '/' + name
):
248 def prepare_env(cfg
, silent
= False):
249 """ mimic Conf/BuildEnv.sh """
250 workspace
= os
.getcwd()
251 packages
= [ workspace
, ]
252 path
= os
.environ
['PATH'].split(':')
254 'BaseTools/Bin/Linux-x86_64',
255 'BaseTools/BinWrappers/PosixLike'
258 if cfg
.has_option('global', 'pkgs'):
259 for pkgdir
in cfg
['global']['pkgs'].split():
260 packages
.append(os
.path
.abspath(pkgdir
))
261 coredir
= get_coredir(cfg
)
262 if coredir
!= workspace
:
263 packages
.append(coredir
)
265 # add basetools to path
267 p
= coredir
+ '/' + pdir
268 if not os
.path
.exists(p
):
274 # run edksetup if needed
275 toolsdef
= coredir
+ '/Conf/tools_def.txt'
276 if not os
.path
.exists(toolsdef
):
277 os
.makedirs(os
.path
.dirname(toolsdef
), exist_ok
= True)
278 build_message('running BaseTools/BuildEnv', silent
= silent
)
279 cmdline
= [ 'bash', 'BaseTools/BuildEnv' ]
280 subprocess
.run(cmdline
, cwd
= coredir
, check
= True)
283 os
.environ
['PATH'] = ':'.join(path
)
284 os
.environ
['PACKAGES_PATH'] = ':'.join(packages
)
285 os
.environ
['WORKSPACE'] = workspace
286 os
.environ
['EDK_TOOLS_PATH'] = coredir
+ '/BaseTools'
287 os
.environ
['CONF_PATH'] = coredir
+ '/Conf'
288 os
.environ
['PYTHON_COMMAND'] = '/usr/bin/python3'
289 os
.environ
['PYTHONHASHSEED'] = '1'
292 if binary_exists('arm-linux-gnueabi-gcc'):
294 os
.environ
['GCC5_ARM_PREFIX'] = 'arm-linux-gnueabi-'
295 os
.environ
['GCC_ARM_PREFIX'] = 'arm-linux-gnueabi-'
296 elif binary_exists('arm-linux-gnu-gcc'):
298 os
.environ
['GCC5_ARM_PREFIX'] = 'arm-linux-gnu-'
299 os
.environ
['GCC_ARM_PREFIX'] = 'arm-linux-gnu-'
300 if binary_exists('loongarch64-linux-gnu-gcc'):
301 os
.environ
['GCC5_LOONGARCH64_PREFIX'] = 'loongarch64-linux-gnu-'
302 os
.environ
['GCC_LOONGARCH64_PREFIX'] = 'loongarch64-linux-gnu-'
304 hostarch
= os
.uname().machine
305 if binary_exists('aarch64-linux-gnu-gcc') and hostarch
!= 'aarch64':
306 os
.environ
['GCC5_AARCH64_PREFIX'] = 'aarch64-linux-gnu-'
307 os
.environ
['GCC_AARCH64_PREFIX'] = 'aarch64-linux-gnu-'
308 if binary_exists('riscv64-linux-gnu-gcc') and hostarch
!= 'riscv64':
309 os
.environ
['GCC5_RISCV64_PREFIX'] = 'riscv64-linux-gnu-'
310 os
.environ
['GCC_RISCV64_PREFIX'] = 'riscv64-linux-gnu-'
311 if binary_exists('x86_64-linux-gnu-gcc') and hostarch
!= 'x86_64':
312 os
.environ
['GCC5_IA32_PREFIX'] = 'x86_64-linux-gnu-'
313 os
.environ
['GCC5_X64_PREFIX'] = 'x86_64-linux-gnu-'
314 os
.environ
['GCC5_BIN'] = 'x86_64-linux-gnu-'
315 os
.environ
['GCC_IA32_PREFIX'] = 'x86_64-linux-gnu-'
316 os
.environ
['GCC_X64_PREFIX'] = 'x86_64-linux-gnu-'
317 os
.environ
['GCC_BIN'] = 'x86_64-linux-gnu-'
320 for build
in cfg
.sections():
321 if not build
.startswith('build.'):
323 name
= build
.lstrip('build.')
324 desc
= 'no description'
325 if 'desc' in cfg
[build
]:
326 desc
= cfg
[build
]['desc']
327 print(f
'# {name:20s} - {desc}')
330 parser
= argparse
.ArgumentParser(prog
= 'edk2-build',
331 description
= 'edk2 build helper script')
332 parser
.add_argument('-c', '--config', dest
= 'configfile',
333 type = str, default
= '.edk2.builds', metavar
= 'FILE',
334 help = 'read configuration from FILE (default: .edk2.builds)')
335 parser
.add_argument('-C', '--directory', dest
= 'directory', type = str,
336 help = 'change to DIR before building', metavar
= 'DIR')
337 parser
.add_argument('-j', '--jobs', dest
= 'jobs', type = str,
338 help = 'allow up to JOBS parallel build jobs',
340 parser
.add_argument('-m', '--match', dest
= 'match',
341 type = str, action
= 'append',
342 help = 'only run builds matching INCLUDE (substring)',
344 parser
.add_argument('-x', '--exclude', dest
= 'exclude',
345 type = str, action
= 'append',
346 help = 'skip builds matching EXCLUDE (substring)',
348 parser
.add_argument('-l', '--list', dest
= 'list',
349 action
= 'store_true', default
= False,
350 help = 'list build configs available')
351 parser
.add_argument('--silent', dest
= 'silent',
352 action
= 'store_true', default
= False,
353 help = 'write build output to logfiles, '
354 'write to console only on errors')
355 parser
.add_argument('--no-logs', dest
= 'nologs',
356 action
= 'store_true', default
= False,
357 help = 'do not write build log files (with --silent)')
358 parser
.add_argument('--core', dest
= 'core', type = str, metavar
= 'DIR',
359 help = 'location of the core edk2 repository '
360 '(i.e. where BuildTools are located)')
361 parser
.add_argument('--pkg', '--package', dest
= 'pkgs',
362 type = str, action
= 'append', metavar
= 'DIR',
363 help = 'location(s) of additional packages '
364 '(can be specified multiple times)')
365 parser
.add_argument('-t', '--toolchain', dest
= 'toolchain',
366 type = str, metavar
= 'NAME',
367 help = 'tool chain to be used to build edk2')
368 parser
.add_argument('--version-override', dest
= 'version_override',
369 type = str, metavar
= 'VERSION',
370 help = 'set firmware build version')
371 parser
.add_argument('--release-date', dest
= 'release_date',
372 type = str, metavar
= 'DATE',
373 help = 'set firmware build release date (in MM/DD/YYYY format)')
374 options
= parser
.parse_args()
376 if options
.directory
:
377 os
.chdir(options
.directory
)
379 if not os
.path
.exists(options
.configfile
):
380 print(f
'config file "{options.configfile}" not found')
383 cfg
= configparser
.ConfigParser()
384 cfg
.optionxform
= str
385 cfg
.read(options
.configfile
)
391 if not cfg
.has_section('global'):
392 cfg
.add_section('global')
394 cfg
.set('global', 'core', options
.core
)
396 cfg
.set('global', 'pkgs', ' '.join(options
.pkgs
))
397 if options
.toolchain
:
398 cfg
.set('global', 'tool', options
.toolchain
)
400 global version_override
403 if options
.version_override
:
404 version_override
= options
.version_override
405 if options
.release_date
:
406 release_date
= options
.release_date
408 prepare_env(cfg
, options
.silent
)
409 build_basetools(options
.silent
, options
.nologs
)
410 for build
in cfg
.sections():
411 if not build
.startswith('build.'):
415 for item
in options
.match
:
419 print(f
'# skipping "{build}" (not matching "{"|".join(options.match)}")')
423 for item
in options
.exclude
:
425 print(f
'# skipping "{build}" (matching "{item}")')
429 build_one(cfg
, build
, options
.jobs
, options
.silent
, options
.nologs
)
433 if __name__
== '__main__':