Rollback if an optional subproject fails

If a subproject is not required and fails during its configuration, the
parent project continues, but should not include any target or state set
by the failed subproject. This fix ninja still trying to build targets
generated by subprojects before they fail in their configuration.

The 'build' object is now per-interpreter instead of being global. Once
a subproject interpreter succeed, values from its 'build' object are
merged back into its parent 'build' object.
This commit is contained in:
Xavier Claessens 2018-10-28 11:51:13 -04:00 committed by Jussi Pakkanen
parent e4da09b8f9
commit edccb11f01
5 changed files with 23 additions and 1 deletions

View File

@ -138,6 +138,19 @@ class Build:
self.find_overrides = {}
self.searched_programs = set() # The list of all programs that have been searched for.
def copy(self):
other = Build(self.environment)
for k, v in self.__dict__.items():
if isinstance(v, (list, dict, set, OrderedDict)):
other.__dict__[k] = v.copy()
else:
other.__dict__[k] = v
return other
def merge(self, other):
for k, v in other.__dict__.items():
self.__dict__[k] = v
def add_compiler(self, compiler):
if self.static_linker is None and compiler.needs_static_linker():
self.static_linker = self.environment.detect_static_linker(compiler)

View File

@ -2286,7 +2286,8 @@ external dependencies (including libraries) must go to "dependencies".''')
with mlog.nested():
try:
mlog.log('\nExecuting subproject', mlog.bold(dirname), '\n')
subi = Interpreter(self.build, self.backend, dirname, subdir, self.subproject_dir,
new_build = self.build.copy()
subi = Interpreter(new_build, self.backend, dirname, subdir, self.subproject_dir,
self.modules, default_options)
subi.subprojects = self.subprojects
@ -2312,6 +2313,7 @@ external dependencies (including libraries) must go to "dependencies".''')
self.subprojects.update(subi.subprojects)
self.subprojects[dirname] = SubprojectHolder(subi, self.subproject_dir, dirname)
self.build_def_files += subi.build_def_files
self.build.merge(subi.build)
return self.subprojects[dirname]
def get_option_internal(self, optname):

View File

@ -0,0 +1,2 @@
project('test broken subproject')
subproject('broken', required : false)

View File

@ -0,0 +1 @@
#error This must not compile

View File

@ -0,0 +1,4 @@
project('broken', 'c')
executable('app', 'broken.c')
assert(false, 'This subproject must fail')