mirror of
https://gitlab.com/qemu-project/meson.git
synced 2025-07-02 21:13:00 +08:00

When wiping a build tree with --wipe, every entry in the build directory is removed with mesonlib.windows_proof_rmtree() for directories and mesonlib.windows_proof_rm() for other files. Symlinks to directories are considered directories, resulting in the former being called. This causes an exception to be raised, as the implementation calls shutil.rmtree(), which isn't allowed on symlinks. Fix this by using mesonlib.windows_proof_rm() for symlinks. Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
247 lines
12 KiB
Python
247 lines
12 KiB
Python
# 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.
|
|
|
|
import typing as T
|
|
import time
|
|
import sys, stat
|
|
import datetime
|
|
import os.path
|
|
import platform
|
|
import cProfile as profile
|
|
import argparse
|
|
import tempfile
|
|
import shutil
|
|
import glob
|
|
|
|
from . import environment, interpreter, mesonlib
|
|
from . import build
|
|
from . import mlog, coredata
|
|
from . import mintro
|
|
from .mconf import make_lower_case
|
|
from .mesonlib import MesonException
|
|
|
|
def add_arguments(parser):
|
|
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):
|
|
(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:
|
|
raise MesonException(
|
|
'Cannot find cmd_line.txt. This is probably because this '
|
|
'build directory was configured with a meson version < 0.49.0.')
|
|
|
|
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:
|
|
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]:
|
|
if dir1 is None:
|
|
if dir2 is None:
|
|
if not os.path.exists('meson.build') and os.path.exists('../meson.build'):
|
|
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):
|
|
os.makedirs(ndir1)
|
|
if not os.path.exists(ndir2):
|
|
os.makedirs(ndir2)
|
|
if not stat.S_ISDIR(os.stat(ndir1).st_mode):
|
|
raise MesonException('{} is not a directory'.format(dir1))
|
|
if not stat.S_ISDIR(os.stat(ndir2).st_mode):
|
|
raise MesonException('{} is not a directory'.format(dir2))
|
|
if os.path.samefile(dir1, dir2):
|
|
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('Both directories contain a build file {}.'.format(environment.build_filename))
|
|
return ndir1, ndir2
|
|
if self.has_build_file(ndir2):
|
|
return ndir2, ndir1
|
|
raise MesonException('Neither directory contains a build file {}.'.format(environment.build_filename))
|
|
|
|
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)
|
|
priv_dir = os.path.join(build_dir, 'meson-private/coredata.dat')
|
|
if os.path.exists(priv_dir):
|
|
if not reconfigure and not wipe:
|
|
print('Directory already configured.\n'
|
|
'\nJust run your build command (e.g. ninja) and Meson will regenerate as necessary.\n'
|
|
'If ninja fails, run "ninja reconfigure" or "meson --reconfigure"\n'
|
|
'to force Meson to regenerate.\n'
|
|
'\nIf build failures persist, run "meson setup --wipe" to rebuild from scratch\n'
|
|
'using the same options as passed when configuring the build.'
|
|
'\nTo change option values, run "meson configure" instead.')
|
|
raise SystemExit
|
|
else:
|
|
has_cmd_line_file = os.path.exists(coredata.get_cmd_line_file(build_dir))
|
|
if (wipe and not has_cmd_line_file) or (not wipe and reconfigure):
|
|
raise SystemExit('Directory does not contain a valid build tree:\n{}'.format(build_dir))
|
|
return src_dir, build_dir
|
|
|
|
def generate(self):
|
|
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):
|
|
mlog.debug('Build started at', datetime.datetime.now().isoformat())
|
|
mlog.debug('Main binary:', sys.executable)
|
|
mlog.debug('Build Options:', coredata.get_cmd_line_options(self.build_dir, self.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)
|
|
if env.is_cross_build():
|
|
logger_fun = mlog.log
|
|
else:
|
|
logger_fun = mlog.debug
|
|
logger_fun('Build machine cpu family:', mlog.bold(intr.builtin['build_machine'].cpu_family_method([], {})))
|
|
logger_fun('Build machine cpu:', mlog.bold(intr.builtin['build_machine'].cpu_method([], {})))
|
|
mlog.log('Host machine cpu family:', mlog.bold(intr.builtin['host_machine'].cpu_family_method([], {})))
|
|
mlog.log('Host machine cpu:', mlog.bold(intr.builtin['host_machine'].cpu_method([], {})))
|
|
logger_fun('Target machine cpu family:', mlog.bold(intr.builtin['target_machine'].cpu_family_method([], {})))
|
|
logger_fun('Target machine cpu:', mlog.bold(intr.builtin['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
|
|
# Print all default option values that don't match the current value
|
|
for def_opt_name, def_opt_value, cur_opt_value in intr.get_non_matching_default_options():
|
|
mlog.log('Option', mlog.bold(def_opt_name), 'is:',
|
|
mlog.bold('{}'.format(make_lower_case(cur_opt_value.printable_value()))),
|
|
'[default: {}]'.format(make_lower_case(def_opt_value)))
|
|
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()
|
|
if self.options.profile:
|
|
fname = 'profile-{}-backend.log'.format(intr.backend.name)
|
|
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:
|
|
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()
|
|
except Exception as e:
|
|
mintro.write_meson_info_file(b, [e])
|
|
if 'cdf' in locals():
|
|
old_cdf = cdf + '.prev'
|
|
if os.path.exists(old_cdf):
|
|
os.replace(old_cdf, cdf)
|
|
else:
|
|
os.unlink(cdf)
|
|
raise
|
|
|
|
def run(options) -> int:
|
|
coredata.parse_cmd_line_options(options)
|
|
app = MesonApp(options)
|
|
app.generate()
|
|
return 0
|