Merge tag 'pull-for-8.0-040423-2' of https://gitlab.com/stsquad/qemu into staging
[qemu/kevin.git] / roms / edk2-build.py
blob870893f7c8e33caa25e7751cfe3068fd3af6d3e7
1 #!/usr/bin/python3
2 """
3 build helper script for edk2, see
4 https://gitlab.com/kraxel/edk2-build-config
6 """
7 import os
8 import sys
9 import shutil
10 import argparse
11 import subprocess
12 import configparser
14 rebase_prefix = ""
15 version_override = None
16 release_date = None
18 # pylint: disable=unused-variable
19 def check_rebase():
20 """ detect 'git rebase -x edk2-build.py master' testbuilds """
21 global rebase_prefix
22 global version_override
23 gitdir = '.git'
25 if os.path.isfile(gitdir):
26 with open(gitdir, 'r', encoding = 'utf-8') as f:
27 (unused, gitdir) = f.read().split()
29 if not os.path.exists(f'{gitdir}/rebase-merge/msgnum'):
30 return
31 with open(f'{gitdir}/rebase-merge/msgnum', 'r', encoding = 'utf-8') as f:
32 msgnum = int(f.read())
33 with open(f'{gitdir}/rebase-merge/end', 'r', encoding = 'utf-8') as f:
34 end = int(f.read())
35 with open(f'{gitdir}/rebase-merge/head-name', 'r', encoding = 'utf-8') as f:
36 head = f.read().strip().split('/')
38 rebase_prefix = f'[ {int(msgnum/2)} / {int(end/2)} - {head[-1]} ] '
39 if msgnum != end and not version_override:
40 # fixed version speeds up builds
41 version_override = "test-build-patch-series"
43 def get_coredir(cfg):
44 if cfg.has_option('global', 'core'):
45 return os.path.abspath(cfg['global']['core'])
46 return os.getcwd()
48 def get_version(cfg):
49 coredir = get_coredir(cfg)
50 if version_override:
51 version = version_override
52 print('')
53 print(f'### version [override]: {version}')
54 return version
55 if os.environ.get('RPM_PACKAGE_NAME'):
56 version = os.environ.get('RPM_PACKAGE_NAME')
57 version += '-' + os.environ.get('RPM_PACKAGE_VERSION')
58 version += '-' + os.environ.get('RPM_PACKAGE_RELEASE')
59 print('')
60 print(f'### version [rpmbuild]: {version}')
61 return version
62 if os.path.exists(coredir + '/.git'):
63 cmdline = [ 'git', 'describe', '--tags', '--abbrev=8',
64 '--match=edk2-stable*' ]
65 result = subprocess.run(cmdline, cwd = coredir,
66 stdout = subprocess.PIPE,
67 check = True)
68 version = result.stdout.decode().strip()
69 print('')
70 print(f'### version [git]: {version}')
71 return version
72 return None
74 def pcd_string(name, value):
75 return f'{name}=L{value}\\0'
77 def pcd_version(cfg):
78 version = get_version(cfg)
79 if version is None:
80 return []
81 return [ '--pcd', pcd_string('PcdFirmwareVersionString', version) ]
83 def pcd_release_date():
84 if release_date is None:
85 return []
86 return [ '--pcd', pcd_string('PcdFirmwareReleaseDateString', release_date) ]
88 def build_message(line, line2 = None):
89 if os.environ.get('TERM') in [ 'xterm', 'xterm-256color' ]:
90 # setxterm title
91 start = '\x1b]2;'
92 end = '\x07'
93 print(f'{start}{rebase_prefix}{line}{end}', end = '')
95 print('')
96 print('###')
97 print(f'### {rebase_prefix}{line}')
98 if line2:
99 print(f'### {line2}')
100 print('###', flush = True)
102 def build_run(cmdline, name, section, silent = False):
103 print(cmdline, flush = True)
104 if silent:
105 print('### building in silent mode ...', flush = True)
106 result = subprocess.run(cmdline, check = False,
107 stdout = subprocess.PIPE,
108 stderr = subprocess.STDOUT)
110 logfile = f'{section}.log'
111 print(f'### writing log to {logfile} ...')
112 with open(logfile, 'wb') as f:
113 f.write(result.stdout)
115 if result.returncode:
116 print('### BUILD FAILURE')
117 print('### output')
118 print(result.stdout.decode())
119 print(f'### exit code: {result.returncode}')
120 else:
121 print('### OK')
122 else:
123 result = subprocess.run(cmdline, check = False)
124 if result.returncode:
125 print(f'ERROR: {cmdline[0]} exited with {result.returncode}'
126 f' while building {name}')
127 sys.exit(result.returncode)
129 def build_copy(plat, tgt, dstdir, copy):
130 srcdir = f'Build/{plat}/{tgt}_GCC5'
131 names = copy.split()
132 srcfile = names[0]
133 if len(names) > 1:
134 dstfile = names[1]
135 else:
136 dstfile = os.path.basename(srcfile)
137 print(f'# copy: {srcdir} / {srcfile} => {dstdir} / {dstfile}')
139 src = srcdir + '/' + srcfile
140 dst = dstdir + '/' + dstfile
141 os.makedirs(os.path.dirname(dst), exist_ok = True)
142 shutil.copy(src, dst)
144 def pad_file(dstdir, pad):
145 args = pad.split()
146 if len(args) < 2:
147 raise RuntimeError(f'missing arg for pad ({args})')
148 name = args[0]
149 size = args[1]
150 cmdline = [
151 'truncate',
152 '--size', size,
153 dstdir + '/' + name,
155 print(f'# padding: {dstdir} / {name} => {size}')
156 subprocess.run(cmdline, check = True)
158 # pylint: disable=too-many-branches
159 def build_one(cfg, build, jobs = None, silent = False):
160 cmdline = [ 'build' ]
161 cmdline += [ '-t', 'GCC5' ]
162 cmdline += [ '-p', cfg[build]['conf'] ]
164 if (cfg[build]['conf'].startswith('OvmfPkg/') or
165 cfg[build]['conf'].startswith('ArmVirtPkg/')):
166 cmdline += pcd_version(cfg)
167 cmdline += pcd_release_date()
169 if jobs:
170 cmdline += [ '-n', jobs ]
171 for arch in cfg[build]['arch'].split():
172 cmdline += [ '-a', arch ]
173 if 'opts' in cfg[build]:
174 for name in cfg[build]['opts'].split():
175 section = 'opts.' + name
176 for opt in cfg[section]:
177 cmdline += [ '-D', opt + '=' + cfg[section][opt] ]
178 if 'pcds' in cfg[build]:
179 for name in cfg[build]['pcds'].split():
180 section = 'pcds.' + name
181 for pcd in cfg[section]:
182 cmdline += [ '--pcd', pcd + '=' + cfg[section][pcd] ]
183 if 'tgts' in cfg[build]:
184 tgts = cfg[build]['tgts'].split()
185 else:
186 tgts = [ 'DEBUG' ]
187 for tgt in tgts:
188 desc = None
189 if 'desc' in cfg[build]:
190 desc = cfg[build]['desc']
191 build_message(f'building: {cfg[build]["conf"]} ({cfg[build]["arch"]}, {tgt})',
192 f'description: {desc}')
193 build_run(cmdline + [ '-b', tgt ],
194 cfg[build]['conf'],
195 build + '.' + tgt,
196 silent)
198 if 'plat' in cfg[build]:
199 # copy files
200 for cpy in cfg[build]:
201 if not cpy.startswith('cpy'):
202 continue
203 build_copy(cfg[build]['plat'],
204 tgt,
205 cfg[build]['dest'],
206 cfg[build][cpy])
207 # pad builds
208 for pad in cfg[build]:
209 if not pad.startswith('pad'):
210 continue
211 pad_file(cfg[build]['dest'],
212 cfg[build][pad])
214 def build_basetools(silent = False):
215 build_message('building: BaseTools')
216 basedir = os.environ['EDK_TOOLS_PATH']
217 cmdline = [ 'make', '-C', basedir ]
218 build_run(cmdline, 'BaseTools', 'build.basetools', silent)
220 def binary_exists(name):
221 for pdir in os.environ['PATH'].split(':'):
222 if os.path.exists(pdir + '/' + name):
223 return True
224 return False
226 def prepare_env(cfg):
227 """ mimic Conf/BuildEnv.sh """
228 workspace = os.getcwd()
229 packages = [ workspace, ]
230 path = os.environ['PATH'].split(':')
231 dirs = [
232 'BaseTools/Bin/Linux-x86_64',
233 'BaseTools/BinWrappers/PosixLike'
236 if cfg.has_option('global', 'pkgs'):
237 for pkgdir in cfg['global']['pkgs'].split():
238 packages.append(os.path.abspath(pkgdir))
239 coredir = get_coredir(cfg)
240 if coredir != workspace:
241 packages.append(coredir)
243 # add basetools to path
244 for pdir in dirs:
245 p = coredir + '/' + pdir
246 if not os.path.exists(p):
247 continue
248 if p in path:
249 continue
250 path.insert(0, p)
252 # run edksetup if needed
253 toolsdef = coredir + '/Conf/tools_def.txt'
254 if not os.path.exists(toolsdef):
255 os.makedirs(os.path.dirname(toolsdef), exist_ok = True)
256 build_message('running BaseTools/BuildEnv')
257 cmdline = [ 'bash', 'BaseTools/BuildEnv' ]
258 subprocess.run(cmdline, cwd = coredir, check = True)
260 # set variables
261 os.environ['PATH'] = ':'.join(path)
262 os.environ['PACKAGES_PATH'] = ':'.join(packages)
263 os.environ['WORKSPACE'] = workspace
264 os.environ['EDK_TOOLS_PATH'] = coredir + '/BaseTools'
265 os.environ['CONF_PATH'] = coredir + '/Conf'
266 os.environ['PYTHON_COMMAND'] = '/usr/bin/python3'
267 os.environ['PYTHONHASHSEED'] = '1'
269 # for cross builds
270 if binary_exists('arm-linux-gnu-gcc'):
271 os.environ['GCC5_ARM_PREFIX'] = 'arm-linux-gnu-'
272 if binary_exists('loongarch64-linux-gnu-gcc'):
273 os.environ['GCC5_LOONGARCH64_PREFIX'] = 'loongarch64-linux-gnu-'
275 hostarch = os.uname().machine
276 if binary_exists('aarch64-linux-gnu-gcc') and hostarch != 'aarch64':
277 os.environ['GCC5_AARCH64_PREFIX'] = 'aarch64-linux-gnu-'
278 if binary_exists('riscv64-linux-gnu-gcc') and hostarch != 'riscv64':
279 os.environ['GCC5_RISCV64_PREFIX'] = 'riscv64-linux-gnu-'
280 if binary_exists('x86_64-linux-gnu-gcc') and hostarch != 'x86_64':
281 os.environ['GCC5_IA32_PREFIX'] = 'x86_64-linux-gnu-'
282 os.environ['GCC5_X64_PREFIX'] = 'x86_64-linux-gnu-'
283 os.environ['GCC5_BIN'] = 'x86_64-linux-gnu-'
285 def build_list(cfg):
286 for build in cfg.sections():
287 if not build.startswith('build.'):
288 continue
289 name = build.lstrip('build.')
290 desc = 'no description'
291 if 'desc' in cfg[build]:
292 desc = cfg[build]['desc']
293 print(f'# {name:20s} - {desc}')
295 def main():
296 parser = argparse.ArgumentParser(prog = 'edk2-build',
297 description = 'edk2 build helper script')
298 parser.add_argument('-c', '--config', dest = 'configfile',
299 type = str, default = '.edk2.builds', metavar = 'FILE',
300 help = 'read configuration from FILE (default: .edk2.builds)')
301 parser.add_argument('-C', '--directory', dest = 'directory', type = str,
302 help = 'change to DIR before building', metavar = 'DIR')
303 parser.add_argument('-j', '--jobs', dest = 'jobs', type = str,
304 help = 'allow up to JOBS parallel build jobs',
305 metavar = 'JOBS')
306 parser.add_argument('-m', '--match', dest = 'match', type = str,
307 help = 'only run builds matching INCLUDE (substring)',
308 metavar = 'INCLUDE')
309 parser.add_argument('-x', '--exclude', dest = 'exclude', type = str,
310 help = 'skip builds matching EXCLUDE (substring)',
311 metavar = 'EXCLUDE')
312 parser.add_argument('-l', '--list', dest = 'list',
313 action = 'store_true', default = False,
314 help = 'list build configs available')
315 parser.add_argument('--silent', dest = 'silent',
316 action = 'store_true', default = False,
317 help = 'write build output to logfiles, '
318 'write to console only on errors')
319 parser.add_argument('--core', dest = 'core', type = str, metavar = 'DIR',
320 help = 'location of the core edk2 repository '
321 '(i.e. where BuildTools are located)')
322 parser.add_argument('--pkg', '--package', dest = 'pkgs',
323 type = str, action = 'append', metavar = 'DIR',
324 help = 'location(s) of additional packages '
325 '(can be specified multiple times)')
326 parser.add_argument('--version-override', dest = 'version_override',
327 type = str, metavar = 'VERSION',
328 help = 'set firmware build version')
329 parser.add_argument('--release-date', dest = 'release_date',
330 type = str, metavar = 'DATE',
331 help = 'set firmware build release date (in MM/DD/YYYY format)')
332 options = parser.parse_args()
334 if options.directory:
335 os.chdir(options.directory)
337 if not os.path.exists(options.configfile):
338 print('config file "{options.configfile}" not found')
339 return 1
341 cfg = configparser.ConfigParser()
342 cfg.optionxform = str
343 cfg.read(options.configfile)
345 if options.list:
346 build_list(cfg)
347 return
349 if not cfg.has_section('global'):
350 cfg.add_section('global')
351 if options.core:
352 cfg.set('global', 'core', options.core)
353 if options.pkgs:
354 cfg.set('global', 'pkgs', ' '.join(options.pkgs))
356 global version_override
357 global release_date
358 check_rebase()
359 if options.version_override:
360 version_override = options.version_override
361 if options.release_date:
362 release_date = options.release_date
364 prepare_env(cfg)
365 build_basetools(options.silent)
366 for build in cfg.sections():
367 if not build.startswith('build.'):
368 continue
369 if options.match and options.match not in build:
370 print(f'# skipping "{build}" (not matching "{options.match}")')
371 continue
372 if options.exclude and options.exclude in build:
373 print(f'# skipping "{build}" (matching "{options.exclude}")')
374 continue
375 build_one(cfg, build, options.jobs, options.silent)
377 return 0
379 if __name__ == '__main__':
380 sys.exit(main())