|
|
|
# Copyright 2016-2018 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 typing as T
|
|
|
|
import time
|
|
|
|
import sys
|
|
|
|
import datetime
|
|
|
|
import os.path
|
|
|
|
import platform
|
|
|
|
import cProfile as profile
|
|
|
|
import argparse
|
|
|
|
import tempfile
|
|
|
|
import shutil
|
|
|
|
import glob
|
|
|
|
from pathlib import Path
|
|
|
|
|
|
|
|
from . import environment, interpreter, mesonlib
|
|
|
|
from . import build
|
|
|
|
from . import mlog, coredata
|
|
|
|
from . import mintro
|
|
|
|
from .mesonlib import MesonException, MachineChoice
|
|
|
|
from .dependencies import PkgConfigDependency
|
|
|
|
|
|
|
|
git_ignore_file = '''# This file is autogenerated by Meson. If you change or delete it, it won't be recreated.
|
|
|
|
*
|
|
|
|
'''
|
|
|
|
|
|
|
|
hg_ignore_file = '''# This file is autogenerated by Meson. If you change or delete it, it won't be recreated.
|
|
|
|
syntax: glob
|
|
|
|
**/*
|
|
|
|
'''
|
|
|
|
|
|
|
|
|
|
|
|
def add_arguments(parser: argparse.ArgumentParser) -> None:
|
|
|
|
coredata.register_builtin_arguments(parser)
|
|
|
|
parser.add_argument('--native-file',
|
|
|
|
default=[],
|
|
|
|
action='append',
|
|
|
|
help='File containing overrides for native compilation environment.')
|
|
|
|
parser.add_argument('--cross-file',
|
|
|
|
default=[],
|
|
|
|
action='append',
|
|
|
|
help='File describing cross compilation environment.')
|
|
|
|
parser.add_argument('-v', '--version', action='version',
|
|
|
|
version=coredata.version)
|
|
|
|
parser.add_argument('--profile-self', action='store_true', dest='profile',
|
|
|
|
help=argparse.SUPPRESS)
|
|
|
|
parser.add_argument('--fatal-meson-warnings', action='store_true', dest='fatal_warnings',
|
|
|
|
help='Make all Meson warnings fatal')
|
|
|
|
parser.add_argument('--reconfigure', action='store_true',
|
|
|
|
help='Set options and reconfigure the project. Useful when new ' +
|
|
|
|
'options have been added to the project and the default value ' +
|
|
|
|
'is not working.')
|
|
|
|
parser.add_argument('--wipe', action='store_true',
|
|
|
|
help='Wipe build directory and reconfigure using previous command line options. ' +
|
|
|
|
'Useful when build directory got corrupted, or when rebuilding with a ' +
|
|
|
|
'newer version of meson.')
|
|
|
|
parser.add_argument('builddir', nargs='?', default=None)
|
|
|
|
parser.add_argument('sourcedir', nargs='?', default=None)
|
|
|
|
|
|
|
|
class MesonApp:
|
|
|
|
def __init__(self, options: argparse.Namespace) -> None:
|
|
|
|
(self.source_dir, self.build_dir) = self.validate_dirs(options.builddir,
|
|
|
|
options.sourcedir,
|
|
|
|
options.reconfigure,
|
|
|
|
options.wipe)
|
|
|
|
if options.wipe:
|
|
|
|
# Make a copy of the cmd line file to make sure we can always
|
|
|
|
# restore that file if anything bad happens. For example if
|
|
|
|
# configuration fails we need to be able to wipe again.
|
|
|
|
restore = []
|
|
|
|
with tempfile.TemporaryDirectory() as d:
|
|
|
|
for filename in [coredata.get_cmd_line_file(self.build_dir)] + glob.glob(os.path.join(self.build_dir, environment.Environment.private_dir, '*.ini')):
|
|
|
|
try:
|
|
|
|
restore.append((shutil.copy(filename, d), filename))
|
|
|
|
except FileNotFoundError:
|
|
|
|
# validate_dirs() already verified that build_dir has
|
|
|
|
# a partial build or is empty.
|
|
|
|
pass
|
|
|
|
|
|
|
|
coredata.read_cmd_line_file(self.build_dir, options)
|
|
|
|
|
|
|
|
try:
|
|
|
|
# Don't delete the whole tree, just all of the files and
|
|
|
|
# folders in the tree. Otherwise calling wipe form the builddir
|
|
|
|
# will cause a crash
|
|
|
|
for l in os.listdir(self.build_dir):
|
|
|
|
l = os.path.join(self.build_dir, l)
|
|
|
|
if os.path.isdir(l) and not os.path.islink(l):
|
|
|
|
mesonlib.windows_proof_rmtree(l)
|
|
|
|
else:
|
|
|
|
mesonlib.windows_proof_rm(l)
|
|
|
|
finally:
|
|
|
|
self.add_vcs_ignore_files(self.build_dir)
|
|
|
|
for b, f in restore:
|
|
|
|
os.makedirs(os.path.dirname(f), exist_ok=True)
|
|
|
|
shutil.move(b, f)
|
|
|
|
|
|
|
|
self.options = options
|
|
|
|
|
|
|
|
def has_build_file(self, dirname: str) -> bool:
|
|
|
|
fname = os.path.join(dirname, environment.build_filename)
|
|
|
|
return os.path.exists(fname)
|
|
|
|
|
|
|
|
def validate_core_dirs(self, dir1: str, dir2: str) -> T.Tuple[str, str]:
|
|
|
|
invalid_msg_prefix = f'Neither source directory {dir1!r} nor build directory {dir2!r}'
|
|
|
|
if dir1 is None:
|
|
|
|
if dir2 is None:
|
|
|
|
if not self.has_build_file('.') and self.has_build_file('..'):
|
|
|
|
dir2 = '..'
|
|
|
|
else:
|
|
|
|
raise MesonException('Must specify at least one directory name.')
|
|
|
|
dir1 = os.getcwd()
|
|
|
|
if dir2 is None:
|
|
|
|
dir2 = os.getcwd()
|
|
|
|
ndir1 = os.path.abspath(os.path.realpath(dir1))
|
|
|
|
ndir2 = os.path.abspath(os.path.realpath(dir2))
|
|
|
|
if not os.path.exists(ndir1) and not os.path.exists(ndir2):
|
|
|
|
raise MesonException(f'{invalid_msg_prefix} exist.')
|
|
|
|
try:
|
|
|
|
os.makedirs(ndir1, exist_ok=True)
|
|
|
|
except FileExistsError as e:
|
|
|
|
raise MesonException(f'{dir1} is not a directory') from e
|
|
|
|
try:
|
|
|
|
os.makedirs(ndir2, exist_ok=True)
|
|
|
|
except FileExistsError as e:
|
|
|
|
raise MesonException(f'{dir2} is not a directory') from e
|
|
|
|
if os.path.samefile(ndir1, ndir2):
|
|
|
|
# Fallback to textual compare if undefined entries found
|
|
|
|
has_undefined = any((s.st_ino == 0 and s.st_dev == 0) for s in (os.stat(ndir1), os.stat(ndir2)))
|
|
|
|
if not has_undefined or ndir1 == ndir2:
|
|
|
|
raise MesonException('Source and build directories must not be the same. Create a pristine build directory.')
|
|
|
|
if self.has_build_file(ndir1):
|
|
|
|
if self.has_build_file(ndir2):
|
|
|
|
raise MesonException(f'Both directories contain a build file {environment.build_filename}.')
|
|
|
|
return ndir1, ndir2
|
|
|
|
if self.has_build_file(ndir2):
|
|
|
|
return ndir2, ndir1
|
|
|
|
raise MesonException(f'{invalid_msg_prefix} contain a build file {environment.build_filename}.')
|
|
|
|
|
|
|
|
def add_vcs_ignore_files(self, build_dir: str) -> None:
|
|
|
|
with open(os.path.join(build_dir, '.gitignore'), 'w', encoding='utf-8') as ofile:
|
|
|
|
ofile.write(git_ignore_file)
|
|
|
|
with open(os.path.join(build_dir, '.hgignore'), 'w', encoding='utf-8') as ofile:
|
|
|
|
ofile.write(hg_ignore_file)
|
|
|
|
|
|
|
|
def validate_dirs(self, dir1: str, dir2: str, reconfigure: bool, wipe: bool) -> T.Tuple[str, str]:
|
|
|
|
(src_dir, build_dir) = self.validate_core_dirs(dir1, dir2)
|
|
|
|
if Path(build_dir) in Path(src_dir).parents:
|
|
|
|
raise MesonException(f'Build directory {build_dir} cannot be a parent of source directory {src_dir}')
|
|
|
|
if not os.listdir(build_dir):
|
|
|
|
self.add_vcs_ignore_files(build_dir)
|
|
|
|
return src_dir, build_dir
|
|
|
|
priv_dir = os.path.join(build_dir, 'meson-private')
|
|
|
|
has_valid_build = os.path.exists(os.path.join(priv_dir, 'coredata.dat'))
|
|
|
|
has_partial_build = os.path.isdir(priv_dir)
|
|
|
|
if has_valid_build:
|
|
|
|
if not reconfigure and not wipe:
|
|
|
|
print('Directory already configured.\n\n'
|
|
|
|
'Just run your build command (e.g. ninja) and Meson will regenerate as necessary.\n'
|
|
|
|
'If ninja fails, run "ninja reconfigure" or "meson setup --reconfigure"\n'
|
|
|
|
'to force Meson to regenerate.\n\n'
|
|
|
|
'If build failures persist, run "meson setup --wipe" to rebuild from scratch\n'
|
|
|
|
'using the same options as passed when configuring the build.\n'
|
|
|
|
'To change option values, run "meson configure" instead.')
|
|
|
|
# FIXME: This returns success and ignores new option values from CLI.
|
|
|
|
# We should either make this a hard error, or update options and
|
|
|
|
# return success.
|
|
|
|
# Note that making this an error would not be backward compatible (and also isn't
|
|
|
|
# universally agreed on): https://github.com/mesonbuild/meson/pull/4249.
|
|
|
|
raise SystemExit(0)
|
|
|
|
elif not has_partial_build and wipe:
|
|
|
|
raise MesonException(f'Directory is not empty and does not contain a previous build:\n{build_dir}')
|
|
|
|
return src_dir, build_dir
|
|
|
|
|
|
|
|
def generate(self) -> None:
|
|
|
|
env = environment.Environment(self.source_dir, self.build_dir, self.options)
|
|
|
|
mlog.initialize(env.get_log_dir(), self.options.fatal_warnings)
|
|
|
|
if self.options.profile:
|
|
|
|
mlog.set_timestamp_start(time.monotonic())
|
|
|
|
with mesonlib.BuildDirLock(self.build_dir):
|
|
|
|
self._generate(env)
|
|
|
|
|
|
|
|
def _generate(self, env: environment.Environment) -> None:
|
|
|
|
# Get all user defined options, including options that have been defined
|
|
|
|
# during a previous invocation or using meson configure.
|
|
|
|
user_defined_options = argparse.Namespace(**vars(self.options))
|
|
|
|
coredata.read_cmd_line_file(self.build_dir, user_defined_options)
|
|
|
|
|
|
|
|
mlog.debug('Build started at', datetime.datetime.now().isoformat())
|
|
|
|
mlog.debug('Main binary:', sys.executable)
|
|
|
|
mlog.debug('Build Options:', coredata.format_cmd_line_options(user_defined_options))
|
|
|
|
mlog.debug('Python system:', platform.system())
|
|
|
|
mlog.log(mlog.bold('The Meson build system'))
|
|
|
|
mlog.log('Version:', coredata.version)
|
|
|
|
mlog.log('Source dir:', mlog.bold(self.source_dir))
|
|
|
|
mlog.log('Build dir:', mlog.bold(self.build_dir))
|
|
|
|
if env.is_cross_build():
|
|
|
|
mlog.log('Build type:', mlog.bold('cross build'))
|
|
|
|
else:
|
|
|
|
mlog.log('Build type:', mlog.bold('native build'))
|
|
|
|
b = build.Build(env)
|
|
|
|
|
|
|
|
intr = interpreter.Interpreter(b, user_defined_options=user_defined_options)
|
|
|
|
# Super hack because mlog.log and mlog.debug have different signatures,
|
|
|
|
# and there is currently no way to annotate them correctly, unionize them, or
|
|
|
|
# even to write `T.Callable[[*mlog.TV_Loggable], None]`
|
|
|
|
logger_fun = T.cast('T.Callable[[mlog.TV_Loggable, mlog.TV_Loggable], None]',
|
|
|
|
(mlog.log if env.is_cross_build() else mlog.debug))
|
|
|
|
build_machine = intr.builtin['build_machine']
|
|
|
|
host_machine = intr.builtin['host_machine']
|
|
|
|
target_machine = intr.builtin['target_machine']
|
|
|
|
assert isinstance(build_machine, interpreter.MachineHolder)
|
|
|
|
assert isinstance(host_machine, interpreter.MachineHolder)
|
|
|
|
assert isinstance(target_machine, interpreter.MachineHolder)
|
|
|
|
logger_fun('Build machine cpu family:', mlog.bold(build_machine.cpu_family_method([], {})))
|
|
|
|
logger_fun('Build machine cpu:', mlog.bold(build_machine.cpu_method([], {})))
|
|
|
|
mlog.log('Host machine cpu family:', mlog.bold(host_machine.cpu_family_method([], {})))
|
|
|
|
mlog.log('Host machine cpu:', mlog.bold(host_machine.cpu_method([], {})))
|
|
|
|
logger_fun('Target machine cpu family:', mlog.bold(target_machine.cpu_family_method([], {})))
|
|
|
|
logger_fun('Target machine cpu:', mlog.bold(target_machine.cpu_method([], {})))
|
|
|
|
try:
|
|
|
|
if self.options.profile:
|
|
|
|
fname = os.path.join(self.build_dir, 'meson-private', 'profile-interpreter.log')
|
|
|
|
profile.runctx('intr.run()', globals(), locals(), filename=fname)
|
|
|
|
else:
|
|
|
|
intr.run()
|
|
|
|
except Exception as e:
|
|
|
|
mintro.write_meson_info_file(b, [e])
|
|
|
|
raise
|
|
|
|
|
|
|
|
cdf: T.Optional[str] = None
|
|
|
|
try:
|
|
|
|
dumpfile = os.path.join(env.get_scratch_dir(), 'build.dat')
|
|
|
|
# We would like to write coredata as late as possible since we use the existence of
|
|
|
|
# this file to check if we generated the build file successfully. Since coredata
|
|
|
|
# includes settings, the build files must depend on it and appear newer. However, due
|
|
|
|
# to various kernel caches, we cannot guarantee that any time in Python is exactly in
|
|
|
|
# sync with the time that gets applied to any files. Thus, we dump this file as late as
|
|
|
|
# possible, but before build files, and if any error occurs, delete it.
|
|
|
|
cdf = env.dump_coredata()
|
|
|
|
|
|
|
|
self.finalize_postconf_hooks(b, intr)
|
|
|
|
if self.options.profile:
|
|
|
|
fname = f'profile-{intr.backend.name}-backend.log'
|
|
|
|
fname = os.path.join(self.build_dir, 'meson-private', fname)
|
|
|
|
profile.runctx('intr.backend.generate()', globals(), locals(), filename=fname)
|
|
|
|
else:
|
|
|
|
intr.backend.generate()
|
|
|
|
build.save(b, dumpfile)
|
|
|
|
if env.first_invocation:
|
|
|
|
# Use path resolved by coredata because they could have been
|
|
|
|
# read from a pipe and wrote into a private file.
|
|
|
|
self.options.cross_file = env.coredata.cross_files
|
|
|
|
self.options.native_file = env.coredata.config_files
|
|
|
|
coredata.write_cmd_line_file(self.build_dir, self.options)
|
|
|
|
else:
|
|
|
|
coredata.update_cmd_line_file(self.build_dir, self.options)
|
|
|
|
|
|
|
|
# Generate an IDE introspection file with the same syntax as the already existing API
|
|
|
|
if self.options.profile:
|
|
|
|
fname = os.path.join(self.build_dir, 'meson-private', 'profile-introspector.log')
|
|
|
|
profile.runctx('mintro.generate_introspection_file(b, intr.backend)', globals(), locals(), filename=fname)
|
|
|
|
else:
|
|
|
|
mintro.generate_introspection_file(b, intr.backend)
|
|
|
|
mintro.write_meson_info_file(b, [], True)
|
|
|
|
|
|
|
|
# Post-conf scripts must be run after writing coredata or else introspection fails.
|
|
|
|
intr.backend.run_postconf_scripts()
|
|
|
|
|
|
|
|
# collect warnings about unsupported build configurations; must be done after full arg processing
|
|
|
|
# by Interpreter() init, but this is most visible at the end
|
|
|
|
if env.coredata.options[mesonlib.OptionKey('backend')].value == 'xcode':
|
|
|
|
mlog.warning('xcode backend is currently unmaintained, patches welcome')
|
deprecate layout=flat and warn people never to use it
It's broken in various circumstances, no one seems to actually use it,
CI doesn't test it, no one is committed to maintaining it, etc. etc.
etc.
Also, projects doing trivially reasonable things, such as generating
"foo/util.py" and "bar/util.py", create clashing output names. This will
never, ever, ever, ever work with layout=flat.
Closes #996
Closes #1521
Closes #1908
Closes #7133
Closes #7135
Closes #7480
Closes #8378
3 years ago
|
|
|
if env.coredata.options[mesonlib.OptionKey('layout')].value == 'flat':
|
|
|
|
mlog.warning('-Dlayout=flat is unsupported and probably broken. It was a failed experiment at '
|
|
|
|
'making Windows build artifacts runnable while uninstalled, due to PATH considerations, '
|
|
|
|
'but was untested by CI and anyways breaks reasonable use of conflicting targets in different subdirs. '
|
|
|
|
'Please consider using `meson devenv` instead. See https://github.com/mesonbuild/meson/pull/9243 '
|
|
|
|
'for details.')
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
mintro.write_meson_info_file(b, [e])
|
|
|
|
if cdf is not None:
|
|
|
|
old_cdf = cdf + '.prev'
|
|
|
|
if os.path.exists(old_cdf):
|
|
|
|
os.replace(old_cdf, cdf)
|
|
|
|
else:
|
|
|
|
os.unlink(cdf)
|
|
|
|
raise
|
|
|
|
|
|
|
|
def finalize_postconf_hooks(self, b: build.Build, intr: interpreter.Interpreter) -> None:
|
|
|
|
b.devenv.append(intr.backend.get_devenv())
|
|
|
|
b.devenv.append(PkgConfigDependency.get_env(intr.environment, MachineChoice.HOST, uninstalled=True))
|
|
|
|
for mod in intr.modules.values():
|
|
|
|
mod.postconf_hook(b)
|
|
|
|
|
|
|
|
def run(options: T.Union[argparse.Namespace, T.List[str]]) -> int:
|
|
|
|
if not isinstance(options, argparse.Namespace):
|
|
|
|
parser = argparse.ArgumentParser()
|
|
|
|
add_arguments(parser)
|
|
|
|
options = parser.parse_args(options)
|
|
|
|
coredata.parse_cmd_line_options(options)
|
|
|
|
app = MesonApp(options)
|
|
|
|
app.generate()
|
|
|
|
return 0
|