|
|
|
# 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 mlog
|
|
|
|
|
|
|
|
if T.TYPE_CHECKING:
|
|
|
|
import argparse
|
|
|
|
|
|
|
|
UNIXY_ENVVARS_COMPILER = {'c': 'CC',
|
|
|
|
'cpp': 'CXX',
|
|
|
|
'objc': 'OBJCC',
|
|
|
|
'objcpp': 'OBJCXX',
|
|
|
|
'fortran': 'FC',
|
|
|
|
'rust': 'RUSTC',
|
|
|
|
'vala': 'VALAC',
|
|
|
|
'cs': 'CSC',
|
|
|
|
}
|
|
|
|
|
|
|
|
UNIXY_ENVVARS_TOOLS = {'ar': 'AR',
|
|
|
|
'strip': 'STRIP',
|
|
|
|
'windres': 'WINDRES',
|
|
|
|
'pkgconfig': 'PKG_CONFIG',
|
|
|
|
'vapigen': 'VAPIGEN',
|
|
|
|
'cmake': 'CMAKE',
|
|
|
|
'qmake': 'QMAKE',
|
|
|
|
}
|
|
|
|
|
|
|
|
UNIXY_ENVVARS_FLAGS = {'c': 'CFLAGS',
|
|
|
|
'cpp': 'CXXFLAGS',
|
|
|
|
'objc': 'OBJCFLAGS',
|
|
|
|
'objcpp': 'OBJCXXFLAGS',
|
|
|
|
'fortran': 'FFLAGS',
|
|
|
|
'rust': 'RUSTFLAGS',
|
|
|
|
'vala': 'VALAFLAGS',
|
|
|
|
'cs': 'CSFLAGS', # This one might not be standard.
|
|
|
|
}
|
|
|
|
|
|
|
|
TYPICAL_UNIXY_COMPILER_NAMES = {'c': ['cc', 'gcc', 'clang'],
|
|
|
|
'cpp': ['c++', 'g++', 'clang++'],
|
|
|
|
'objc': ['objc', 'clang'],
|
|
|
|
'objcpp': ['objcpp', 'clang++'],
|
|
|
|
'fortran': ['gfortran'],
|
|
|
|
}
|
|
|
|
|
|
|
|
LANGS_USING_CPPFLAGS = {'c', 'cpp', 'objc', 'objcxx'}
|
|
|
|
|
|
|
|
def has_for_build() -> bool:
|
|
|
|
for cenv in UNIXY_ENVVARS_COMPILER.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.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.List[str]) -> None:
|
|
|
|
if len(args) == 0:
|
|
|
|
return
|
|
|
|
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
|
|
|
|
|
|
|
|
cpu_family_map = dict(mips64el="mips64",
|
|
|
|
i686='x86')
|
|
|
|
cpu_map = dict(armhf="arm7hlf",
|
|
|
|
mips64el="mips64",)
|
|
|
|
|
|
|
|
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 is None:
|
|
|
|
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 = cpu_family_map.get(data['DEB_HOST_GNU_CPU'],
|
|
|
|
data['DEB_HOST_GNU_CPU'])
|
|
|
|
host_cpu = 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['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') 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])
|
|
|
|
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 = shlex.split(os.environ.get(UNIXY_ENVVARS_FLAGS[langname] + envvar_suffix, ''))
|
|
|
|
if langname in LANGS_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 UNIXY_ENVVARS_COMPILER.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 UNIXY_ENVVARS_TOOLS.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)
|
|
|
|
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 TYPICAL_UNIXY_COMPILER_NAMES.items():
|
|
|
|
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(UNIXY_ENVVARS_TOOLS.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)
|