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
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
|
# Copyright 2022 The Meson development team
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import annotations
import sys, os, subprocess, shutil
import shlex
import typing as T
from .. import envconfig
from .. import mlog
from ..compilers import compilers
from ..compilers.detect import defaults as compiler_names
if T.TYPE_CHECKING:
import argparse
def has_for_build() -> bool:
for cenv in envconfig.ENV_VAR_COMPILER_MAP.values():
if os.environ.get(cenv + '_FOR_BUILD'):
return True
return False
def add_arguments(parser: 'argparse.ArgumentParser') -> None:
parser.add_argument('--debarch', default=None,
help='The dpkg architecture to generate.')
parser.add_argument('--gccsuffix', default="",
help='A particular gcc version suffix if necessary.')
parser.add_argument('-o', required=True, dest='outfile',
help='The output file.')
parser.add_argument('--cross', default=False, action='store_true',
help='Generate a cross compilation file.')
parser.add_argument('--native', default=False, action='store_true',
help='Generate a native compilation file.')
parser.add_argument('--system', default=None,
help='Define system for cross compilation.')
parser.add_argument('--cpu', default=None,
help='Define cpu for cross compilation.')
parser.add_argument('--cpu-family', default=None,
help='Define cpu family for cross compilation.')
parser.add_argument('--endian', default='little', choices=['big', 'little'],
help='Define endianness for cross compilation.')
class MachineInfo:
def __init__(self) -> None:
self.compilers: T.Dict[str, T.List[str]] = {}
self.binaries: T.Dict[str, T.List[str]] = {}
self.properties: T.Dict[str, T.Union[str, T.List[str]]] = {}
self.compile_args: T.Dict[str, T.List[str]] = {}
self.link_args: T.Dict[str, T.List[str]] = {}
self.cmake: T.Dict[str, T.Union[str, T.List[str]]] = {}
self.system: T.Optional[str] = None
self.cpu: T.Optional[str] = None
self.cpu_family: T.Optional[str] = None
self.endian: T.Optional[str] = None
#parser = argparse.ArgumentParser(description='''Generate cross compilation definition file for the Meson build system.
#
#If you do not specify the --arch argument, Meson assumes that running
#plain 'dpkg-architecture' will return correct information for the
#host system.
#
#This script must be run in an environment where CPPFLAGS et al are set to the
#same values used in the actual compilation.
#'''
#)
def locate_path(program: str) -> T.List[str]:
if os.path.isabs(program):
return [program]
for d in os.get_exec_path():
f = os.path.join(d, program)
if os.access(f, os.X_OK):
return [f]
raise ValueError("%s not found on $PATH" % program)
def write_args_line(ofile: T.TextIO, name: str, args: T.Union[str, T.List[str]]) -> None:
if len(args) == 0:
return
if isinstance(args, str):
ostr = name + "= '" + args + "'\n"
else:
ostr = name + ' = ['
ostr += ', '.join("'" + i + "'" for i in args)
ostr += ']\n'
ofile.write(ostr)
def get_args_from_envvars(infos: MachineInfo) -> None:
cppflags = shlex.split(os.environ.get('CPPFLAGS', ''))
cflags = shlex.split(os.environ.get('CFLAGS', ''))
cxxflags = shlex.split(os.environ.get('CXXFLAGS', ''))
objcflags = shlex.split(os.environ.get('OBJCFLAGS', ''))
objcxxflags = shlex.split(os.environ.get('OBJCXXFLAGS', ''))
ldflags = shlex.split(os.environ.get('LDFLAGS', ''))
c_args = cppflags + cflags
cpp_args = cppflags + cxxflags
c_link_args = cflags + ldflags
cpp_link_args = cxxflags + ldflags
objc_args = cppflags + objcflags
objcpp_args = cppflags + objcxxflags
objc_link_args = objcflags + ldflags
objcpp_link_args = objcxxflags + ldflags
if c_args:
infos.compile_args['c'] = c_args
if c_link_args:
infos.link_args['c'] = c_link_args
if cpp_args:
infos.compile_args['cpp'] = cpp_args
if cpp_link_args:
infos.link_args['cpp'] = cpp_link_args
if objc_args:
infos.compile_args['objc'] = objc_args
if objc_link_args:
infos.link_args['objc'] = objc_link_args
if objcpp_args:
infos.compile_args['objcpp'] = objcpp_args
if objcpp_link_args:
infos.link_args['objcpp'] = objcpp_link_args
deb_cpu_family_map = {
'mips64el': 'mips64',
'i686': 'x86',
'powerpc64le': 'ppc64',
}
deb_cpu_map = {
'armhf': 'arm7hlf',
'mips64el': 'mips64',
'powerpc64le': 'ppc64',
}
def deb_detect_cmake(infos: MachineInfo, data: T.Dict[str, str]) -> None:
system_name_map = {'linux': 'Linux', 'kfreebsd': 'kFreeBSD', 'hurd': 'GNU'}
system_processor_map = {'arm': 'armv7l', 'mips64el': 'mips64', 'powerpc64le': 'ppc64le'}
infos.cmake["CMAKE_C_COMPILER"] = infos.compilers['c']
try:
infos.cmake["CMAKE_CXX_COMPILER"] = infos.compilers['cpp']
except KeyError:
pass
infos.cmake["CMAKE_SYSTEM_NAME"] = system_name_map[data['DEB_HOST_ARCH_OS']]
infos.cmake["CMAKE_SYSTEM_PROCESSOR"] = system_processor_map.get(data['DEB_HOST_GNU_CPU'],
data['DEB_HOST_GNU_CPU'])
def deb_compiler_lookup(infos: MachineInfo, compilerstems: T.List[T.Tuple[str, str]], host_arch: str, gccsuffix: str) -> None:
for langname, stem in compilerstems:
compilername = f'{host_arch}-{stem}{gccsuffix}'
try:
p = locate_path(compilername)
infos.compilers[langname] = p
except ValueError:
pass
def detect_cross_debianlike(options: T.Any) -> MachineInfo:
if options.debarch == 'auto':
cmd = ['dpkg-architecture']
else:
cmd = ['dpkg-architecture', '-a' + options.debarch]
output = subprocess.check_output(cmd, universal_newlines=True,
stderr=subprocess.DEVNULL)
data = {}
for line in output.split('\n'):
line = line.strip()
if line == '':
continue
k, v = line.split('=', 1)
data[k] = v
host_arch = data['DEB_HOST_GNU_TYPE']
host_os = data['DEB_HOST_ARCH_OS']
host_cpu_family = deb_cpu_family_map.get(data['DEB_HOST_GNU_CPU'],
data['DEB_HOST_GNU_CPU'])
host_cpu = deb_cpu_map.get(data['DEB_HOST_ARCH'],
data['DEB_HOST_ARCH'])
host_endian = data['DEB_HOST_ARCH_ENDIAN']
compilerstems = [('c', 'gcc'),
('cpp', 'g++'),
('objc', 'gobjc'),
('objcpp', 'gobjc++')]
infos = MachineInfo()
deb_compiler_lookup(infos, compilerstems, host_arch, options.gccsuffix)
if len(infos.compilers) == 0:
print('Warning: no compilers were detected.')
infos.binaries['ar'] = locate_path("%s-ar" % host_arch)
infos.binaries['strip'] = locate_path("%s-strip" % host_arch)
infos.binaries['objcopy'] = locate_path("%s-objcopy" % host_arch)
infos.binaries['ld'] = locate_path("%s-ld" % host_arch)
try:
infos.binaries['cmake'] = locate_path("cmake")
deb_detect_cmake(infos, data)
except ValueError:
pass
try:
infos.binaries['pkgconfig'] = locate_path("%s-pkg-config" % host_arch)
except ValueError:
pass # pkg-config is optional
try:
infos.binaries['cups-config'] = locate_path("cups-config")
except ValueError:
pass
infos.system = host_os
infos.cpu_family = host_cpu_family
infos.cpu = host_cpu
infos.endian = host_endian
get_args_from_envvars(infos)
return infos
def write_machine_file(infos: MachineInfo, ofilename: str, write_system_info: bool) -> None:
tmpfilename = ofilename + '~'
with open(tmpfilename, 'w', encoding='utf-8') as ofile:
ofile.write('[binaries]\n')
ofile.write('# Compilers\n')
for langname in sorted(infos.compilers.keys()):
compiler = infos.compilers[langname]
write_args_line(ofile, langname, compiler)
ofile.write('\n')
ofile.write('# Other binaries\n')
for exename in sorted(infos.binaries.keys()):
exe = infos.binaries[exename]
write_args_line(ofile, exename, exe)
ofile.write('\n')
ofile.write('[properties]\n')
all_langs = list(set(infos.compile_args.keys()).union(set(infos.link_args.keys())))
all_langs.sort()
for lang in all_langs:
if lang in infos.compile_args:
write_args_line(ofile, lang + '_args', infos.compile_args[lang])
if lang in infos.link_args:
write_args_line(ofile, lang + '_link_args', infos.link_args[lang])
for k, v in infos.properties.items():
write_args_line(ofile, k, v)
ofile.write('\n')
if infos.cmake:
ofile.write('[cmake]\n\n')
for k, v in infos.cmake.items():
write_args_line(ofile, k, v)
ofile.write('\n')
if write_system_info:
ofile.write('[host_machine]\n')
ofile.write(f"cpu = '{infos.cpu}'\n")
ofile.write(f"cpu_family = '{infos.cpu_family}'\n")
ofile.write(f"endian = '{infos.endian}'\n")
ofile.write(f"system = '{infos.system}'\n")
os.replace(tmpfilename, ofilename)
def detect_language_args_from_envvars(langname: str, envvar_suffix: str = '') -> T.Tuple[T.List[str], T.List[str]]:
ldflags = tuple(shlex.split(os.environ.get('LDFLAGS' + envvar_suffix, '')))
compile_args = []
if langname in compilers.CFLAGS_MAPPING:
compile_args = shlex.split(os.environ.get(compilers.CFLAGS_MAPPING[langname] + envvar_suffix, ''))
if langname in compilers.LANGUAGES_USING_CPPFLAGS:
cppflags = tuple(shlex.split(os.environ.get('CPPFLAGS' + envvar_suffix, '')))
lang_compile_args = list(cppflags) + compile_args
else:
lang_compile_args = compile_args
lang_link_args = list(ldflags) + compile_args
return (lang_compile_args, lang_link_args)
def detect_compilers_from_envvars(envvar_suffix: str = '') -> MachineInfo:
infos = MachineInfo()
for langname, envvarname in envconfig.ENV_VAR_COMPILER_MAP.items():
compilerstr = os.environ.get(envvarname + envvar_suffix)
if not compilerstr:
continue
compiler = shlex.split(compilerstr)
infos.compilers[langname] = compiler
lang_compile_args, lang_link_args = detect_language_args_from_envvars(langname, envvar_suffix)
if lang_compile_args:
infos.compile_args[langname] = lang_compile_args
if lang_link_args:
infos.link_args[langname] = lang_link_args
return infos
def detect_binaries_from_envvars(infos: MachineInfo, envvar_suffix: str = '') -> None:
for binname, envvar_base in envconfig.ENV_VAR_TOOL_MAP.items():
envvar = envvar_base + envvar_suffix
binstr = os.environ.get(envvar)
if binstr:
infos.binaries[binname] = shlex.split(binstr)
def detect_cross_system(infos: MachineInfo, options: T.Any) -> None:
for optname in ('system', 'cpu', 'cpu_family', 'endian'):
v = getattr(options, optname)
if not v:
mlog.error(f'Cross property "{optname}" missing, set it with --{optname.replace("_", "-")}.')
sys.exit(1)
setattr(infos, optname, v)
def detect_cross_env(options: T.Any) -> MachineInfo:
if options.debarch:
print('Detecting cross environment via dpkg-reconfigure.')
infos = detect_cross_debianlike(options)
else:
print('Detecting cross environment via environment variables.')
infos = detect_compilers_from_envvars()
detect_cross_system(infos, options)
detect_binaries_from_envvars(infos)
return infos
def add_compiler_if_missing(infos: MachineInfo, langname: str, exe_names: T.List[str]) -> None:
if langname in infos.compilers:
return
for exe_name in exe_names:
lookup = shutil.which(exe_name)
if not lookup:
continue
compflags, linkflags = detect_language_args_from_envvars(langname)
infos.compilers[langname] = [lookup]
if compflags:
infos.compile_args[langname] = compflags
if linkflags:
infos.link_args[langname] = linkflags
return
def detect_missing_native_compilers(infos: MachineInfo) -> None:
# T.Any per-platform special detection should go here.
for langname, exes in compiler_names.items():
if langname not in envconfig.ENV_VAR_COMPILER_MAP:
continue
add_compiler_if_missing(infos, langname, exes)
def detect_missing_native_binaries(infos: MachineInfo) -> None:
# T.Any per-platform special detection should go here.
for toolname in sorted(envconfig.ENV_VAR_TOOL_MAP.keys()):
if toolname in infos.binaries:
continue
exe = shutil.which(toolname)
if exe:
infos.binaries[toolname] = [exe]
def detect_native_env(options: T.Any) -> MachineInfo:
use_for_build = has_for_build()
if use_for_build:
mlog.log('Using FOR_BUILD envvars for detection')
esuffix = '_FOR_BUILD'
else:
mlog.log('Using regular envvars for detection.')
esuffix = ''
infos = detect_compilers_from_envvars(esuffix)
detect_missing_native_compilers(infos)
detect_binaries_from_envvars(infos, esuffix)
detect_missing_native_binaries(infos)
return infos
def run(options: T.Any) -> None:
if options.cross and options.native:
sys.exit('You can only specify either --cross or --native, not both.')
if not options.cross and not options.native:
sys.exit('You must specify --cross or --native.')
mlog.notice('This functionality is experimental and subject to change.')
detect_cross = options.cross
if detect_cross:
infos = detect_cross_env(options)
write_system_info = True
else:
infos = detect_native_env(options)
write_system_info = False
write_machine_file(infos, options.outfile, write_system_info)
|