diff options
-rw-r--r-- | mesonbuild/coredata.py | 43 | ||||
-rw-r--r-- | mesonbuild/environment.py | 29 | ||||
-rw-r--r-- | mesonbuild/mconf.py | 1 | ||||
-rw-r--r-- | mesonbuild/msetup.py | 42 | ||||
-rwxr-xr-x | run_unittests.py | 36 | ||||
-rw-r--r-- | test cases/unit/46 reconfigure/main.c | 4 | ||||
-rw-r--r-- | test cases/unit/46 reconfigure/meson.build | 9 | ||||
-rw-r--r-- | test cases/unit/46 reconfigure/meson_options.txt | 4 |
8 files changed, 153 insertions, 15 deletions
diff --git a/mesonbuild/coredata.py b/mesonbuild/coredata.py index 819481b..cdfed7c 100644 --- a/mesonbuild/coredata.py +++ b/mesonbuild/coredata.py @@ -22,6 +22,7 @@ from .mesonlib import default_libdir, default_libexecdir, default_prefix from .wrap import WrapMode import ast import argparse +import configparser version = '0.48.999' backendlist = ['ninja', 'vs', 'vs2010', 'vs2015', 'vs2017', 'xcode'] @@ -459,6 +460,45 @@ class CoreData: sub = 'In subproject {}: '.format(subproject) if subproject else '' mlog.warning('{}Unknown options: "{}"'.format(sub, unknown_options)) +def get_cmd_line_file(build_dir): + return os.path.join(build_dir, 'meson-private', 'cmd_line.txt') + +def read_cmd_line_file(build_dir, options): + filename = get_cmd_line_file(build_dir) + config = configparser.ConfigParser() + config.read(filename) + + # Do a copy because config is not really a dict. options.cmd_line_options + # overrides values from the file. + d = dict(config['options']) + d.update(options.cmd_line_options) + options.cmd_line_options = d + + properties = config['properties'] + if options.cross_file is None: + options.cross_file = properties.get('cross_file', None) + +def write_cmd_line_file(build_dir, options): + filename = get_cmd_line_file(build_dir) + config = configparser.ConfigParser() + + properties = {} + if options.cross_file is not None: + properties['cross_file'] = options.cross_file + + config['options'] = options.cmd_line_options + config['properties'] = properties + with open(filename, 'w') as f: + config.write(f) + +def update_cmd_line_file(build_dir, options): + filename = get_cmd_line_file(build_dir) + config = configparser.ConfigParser() + config.read(filename) + config['options'].update(options.cmd_line_options) + with open(filename, 'w') as f: + config.write(f) + def load(build_dir): filename = os.path.join(build_dir, 'meson-private', 'coredata.dat') load_fail_msg = 'Coredata file {!r} is corrupted. Try with a fresh build tree.'.format(filename) @@ -470,7 +510,8 @@ def load(build_dir): if not isinstance(obj, CoreData): raise MesonException(load_fail_msg) if obj.version != version: - raise MesonException('Build directory has been generated with Meson version %s, which is incompatible with current version %s.\nPlease delete this build directory AND create a new one.' % + raise MesonException('Build directory has been generated with Meson version %s, ' + 'which is incompatible with current version %s.\n' % (obj.version, version)) return obj diff --git a/mesonbuild/environment.py b/mesonbuild/environment.py index a002aa1..8891b5c 100644 --- a/mesonbuild/environment.py +++ b/mesonbuild/environment.py @@ -17,7 +17,7 @@ import configparser, os, platform, re, sys, shlex, shutil, subprocess from . import coredata from .linkers import ArLinker, ArmarLinker, VisualStudioLinker, DLinker from . import mesonlib -from .mesonlib import EnvironmentException, PerMachine, Popen_safe +from .mesonlib import MesonException, EnvironmentException, PerMachine, Popen_safe from . import mlog from . import compilers @@ -317,13 +317,17 @@ class Environment: self.coredata = coredata.load(self.get_build_dir()) self.first_invocation = False except FileNotFoundError: - # WARNING: Don't use any values from coredata in __init__. It gets - # re-initialized with project options by the interpreter during - # build file parsing. - self.coredata = coredata.CoreData(options) - # Used by the regenchecker script, which runs meson - self.coredata.meson_command = mesonlib.meson_command - self.first_invocation = True + self.create_new_coredata(options) + except MesonException as e: + # If we stored previous command line options, we can recover from + # a broken/outdated coredata. + if os.path.isfile(coredata.get_cmd_line_file(self.build_dir)): + mlog.warning('Regenerating configuration from scratch.') + mlog.log('Reason:', mlog.red(str(e))) + coredata.read_cmd_line_file(self.build_dir, options) + self.create_new_coredata(options) + else: + raise e self.exe_wrapper = None self.machines = MachineInfos() @@ -389,6 +393,15 @@ class Environment: else: self.native_strip_bin = ['strip'] + def create_new_coredata(self, options): + # WARNING: Don't use any values from coredata in __init__. It gets + # re-initialized with project options by the interpreter during + # build file parsing. + self.coredata = coredata.CoreData(options) + # Used by the regenchecker script, which runs meson + self.coredata.meson_command = mesonlib.meson_command + self.first_invocation = True + def is_cross_build(self): return self.cross_info is not None diff --git a/mesonbuild/mconf.py b/mesonbuild/mconf.py index 576c574..d0f837d 100644 --- a/mesonbuild/mconf.py +++ b/mesonbuild/mconf.py @@ -153,6 +153,7 @@ def run(options): save = False if len(options.cmd_line_options) > 0: c.set_options(options.cmd_line_options) + coredata.update_cmd_line_file(builddir, options) save = True elif options.clearcache: c.clear_cache() diff --git a/mesonbuild/msetup.py b/mesonbuild/msetup.py index 4256272..ce03f43 100644 --- a/mesonbuild/msetup.py +++ b/mesonbuild/msetup.py @@ -39,6 +39,10 @@ def add_arguments(parser): 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. ' + + 'Userful 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) @@ -46,7 +50,28 @@ class MesonApp: def __init__(self, options): (self.source_dir, self.build_dir) = self.validate_dirs(options.builddir, options.sourcedir, - options.reconfigure) + 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. + filename = coredata.get_cmd_line_file(self.build_dir) + with open(filename, 'r') as f: + content = f.read() + + coredata.read_cmd_line_file(self.build_dir, options) + + try: + mesonlib.windows_proof_rmtree(self.build_dir) + finally: + # Restore the file + path = os.path.dirname(filename) + os.makedirs(path, exist_ok=True) + with open(filename, 'w') as f: + f.write(content) + self.options = options def has_build_file(self, dirname): @@ -83,21 +108,22 @@ class MesonApp: return ndir2, ndir1 raise MesonException('Neither directory contains a build file %s.' % environment.build_filename) - def validate_dirs(self, dir1, dir2, reconfigure): + def validate_dirs(self, dir1, dir2, reconfigure, wipe): (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: + 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, manually wipe your build directory to clear any\n' - 'stored system data.\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.') sys.exit(0) else: - if reconfigure: + 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): print('Directory does not contain a valid build tree:\n{}'.format(build_dir)) sys.exit(1) return src_dir, build_dir @@ -168,6 +194,10 @@ class MesonApp: build.save(b, dumpfile) # Post-conf scripts must be run after writing coredata or else introspection fails. intr.backend.run_postconf_scripts() + 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) except: if 'cdf' in locals(): old_cdf = cdf + '.prev' diff --git a/run_unittests.py b/run_unittests.py index 5574b5f..d63a961 100755 --- a/run_unittests.py +++ b/run_unittests.py @@ -25,6 +25,7 @@ import shutil import sys import unittest import platform +import pickle from itertools import chain from unittest import mock from configparser import ConfigParser @@ -2778,6 +2779,41 @@ recommended as it is not supported on some platforms''') self.wipe() self.init(testdir, extra_args=['-Dstart_native=true']) + def test_reconfigure(self): + testdir = os.path.join(self.unit_test_dir, '46 reconfigure') + self.init(testdir, extra_args=['-Dopt1=val1']) + self.setconf('-Dopt2=val2') + + # Set an older version to force a reconfigure from scratch + filename = os.path.join(self.privatedir, 'coredata.dat') + with open(filename, 'rb') as f: + obj = pickle.load(f) + obj.version = '0.47.0' + with open(filename, 'wb') as f: + pickle.dump(obj, f) + + out = self.init(testdir, extra_args=['--reconfigure', '-Dopt3=val3']) + self.assertRegex(out, 'WARNING:.*Regenerating configuration from scratch') + self.assertRegex(out, 'opt1 val1') + self.assertRegex(out, 'opt2 val2') + self.assertRegex(out, 'opt3 val3') + self.assertRegex(out, 'opt4 default4') + self.build() + self.run_tests() + + # Create a file in builddir and verify wipe command removes it + filename = os.path.join(self.builddir, 'something') + open(filename, 'w').close() + self.assertTrue(os.path.exists(filename)) + out = self.init(testdir, extra_args=['--wipe', '-Dopt4=val4']) + self.assertFalse(os.path.exists(filename)) + self.assertRegex(out, 'opt1 val1') + self.assertRegex(out, 'opt2 val2') + self.assertRegex(out, 'opt3 val3') + self.assertRegex(out, 'opt4 val4') + self.build() + self.run_tests() + class FailureTests(BasePlatformTests): ''' Tests that test failure conditions. Build files here should be dynamically diff --git a/test cases/unit/46 reconfigure/main.c b/test cases/unit/46 reconfigure/main.c new file mode 100644 index 0000000..25927f5 --- /dev/null +++ b/test cases/unit/46 reconfigure/main.c @@ -0,0 +1,4 @@ +int main(int argc, char *argv[]) +{ + return 0; +} diff --git a/test cases/unit/46 reconfigure/meson.build b/test cases/unit/46 reconfigure/meson.build new file mode 100644 index 0000000..6eaac5d --- /dev/null +++ b/test cases/unit/46 reconfigure/meson.build @@ -0,0 +1,9 @@ +project('test-reconfigure', 'c') + +message('opt1 ' + get_option('opt1')) +message('opt2 ' + get_option('opt2')) +message('opt3 ' + get_option('opt3')) +message('opt4 ' + get_option('opt4')) + +exe = executable('test1', 'main.c') +test('test1', exe) diff --git a/test cases/unit/46 reconfigure/meson_options.txt b/test cases/unit/46 reconfigure/meson_options.txt new file mode 100644 index 0000000..728f7b7 --- /dev/null +++ b/test cases/unit/46 reconfigure/meson_options.txt @@ -0,0 +1,4 @@ +option('opt1', type : 'string', value : 'default1') +option('opt2', type : 'string', value : 'default2') +option('opt3', type : 'string', value : 'default3') +option('opt4', type : 'string', value : 'default4') |