summaryrefslogtreecommitdiffstats
path: root/mesonbuild/scripts/env2mfile.py
diff options
context:
space:
mode:
Diffstat (limited to '')
-rwxr-xr-xmesonbuild/scripts/env2mfile.py368
1 files changed, 368 insertions, 0 deletions
diff --git a/mesonbuild/scripts/env2mfile.py b/mesonbuild/scripts/env2mfile.py
new file mode 100755
index 0000000..af7ffc6
--- /dev/null
+++ b/mesonbuild/scripts/env2mfile.py
@@ -0,0 +1,368 @@
+# 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
+
+cpu_family_map = {
+ 'mips64el': 'mips64',
+ 'i686': 'x86',
+}
+cpu_map = {
+ 'armhf': 'arm7hlf',
+ 'mips64el': 'mips64'
+}
+
+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']
+ infos.cmake["CMAKE_CXX_COMPILER"] = infos.compilers['cpp']
+ 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 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['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 = 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)
+ 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)