diff --git a/docs/markdown/Builtin-options.md b/docs/markdown/Builtin-options.md index 9eac37149..067966fc9 100644 --- a/docs/markdown/Builtin-options.md +++ b/docs/markdown/Builtin-options.md @@ -82,6 +82,7 @@ Using the option as-is with no prefix affects all machines. For example: | stdsplit | true | Split stdout and stderr in test logs | no | | strip | false | Strip targets on install | no | | unity {on, off, subprojects} | off | Unity build | no | +| unity_size {>=2} | 4 | Unity file block size | no | | warning_level {0, 1, 2, 3} | 1 | Set the warning level. From 0 = none to 3 = highest | no | | werror | false | Treat warnings as errors | no | | wrap_mode {default, nofallback,
nodownload, forcefallback} | default | Wrap mode to use | no | diff --git a/docs/markdown/snippets/unitysize.md b/docs/markdown/snippets/unitysize.md new file mode 100644 index 000000000..4919ce044 --- /dev/null +++ b/docs/markdown/snippets/unitysize.md @@ -0,0 +1,12 @@ +## Unity file block size is configurable + +Traditionally the unity files that Meson autogenerates contain all +source files that belong to a single target. This is the most +efficient setting for full builds but makes incremental builds slow. +This release adds a new option `unity_size` which specifies how many +source files should be put in each unity file. + +The default value for block size is 4. This means that if you have a +target that has eight source files, Meson will generate two unity +files each of which includes four source files. The old behaviour can +be replicated by setting `unity_size` to a large value, such as 10000. diff --git a/mesonbuild/backend/backends.py b/mesonbuild/backend/backends.py index 520332392..a8f4789e8 100644 --- a/mesonbuild/backend/backends.py +++ b/mesonbuild/backend/backends.py @@ -243,19 +243,20 @@ class Backend: # target that the GeneratedList is used in return os.path.join(self.get_target_private_dir(target), src) - def get_unity_source_file(self, target, suffix): + def get_unity_source_file(self, target, suffix, number): # There is a potential conflict here, but it is unlikely that # anyone both enables unity builds and has a file called foo-unity.cpp. - osrc = target.name + '-unity.' + suffix + osrc = '{}-unity{}.{}'.format(target.name, number, suffix) return mesonlib.File.from_built_file(self.get_target_private_dir(target), osrc) def generate_unity_files(self, target, unity_src): abs_files = [] result = [] compsrcs = classify_unity_sources(target.compilers.values(), unity_src) + unity_size = self.get_option_for_target('unity_size', target) - def init_language_file(suffix): - unity_src = self.get_unity_source_file(target, suffix) + def init_language_file(suffix, unity_file_number): + unity_src = self.get_unity_source_file(target, suffix, unity_file_number) outfileabs = unity_src.absolute_path(self.environment.get_source_dir(), self.environment.get_build_dir()) outfileabs_tmp = outfileabs + '.tmp' @@ -266,11 +267,23 @@ class Backend: result.append(unity_src) return open(outfileabs_tmp, 'w') - # For each language, generate a unity source file and return the list + # For each language, generate unity source files and return the list for comp, srcs in compsrcs.items(): - with init_language_file(comp.get_default_suffix()) as ofile: - for src in srcs: - ofile.write('#include<%s>\n' % src) + files_in_current = unity_size + 1 + unity_file_number = 0 + ofile = None + for src in srcs: + if files_in_current >= unity_size: + if ofile: + ofile.close() + ofile = init_language_file(comp.get_default_suffix(), unity_file_number) + unity_file_number += 1 + files_in_current = 0 + ofile.write('#include<%s>\n' % src) + files_in_current += 1 + if ofile: + ofile.close() + [mesonlib.replace_if_different(x, x + '.tmp') for x in abs_files] return result @@ -489,16 +502,18 @@ class Backend: targetdir = self.get_target_private_dir(extobj.target) - # With unity builds, there's just one object that contains all the - # sources, and we only support extracting all the objects in this mode, - # so just return that. + # With unity builds, sources don't map directly to objects, + # we only support extracting all the objects in this mode, + # so just return all object files. if self.is_unity(extobj.target): compsrcs = classify_unity_sources(extobj.target.compilers.values(), sources) sources = [] - for comp in compsrcs.keys(): - osrc = self.get_unity_source_file(extobj.target, - comp.get_default_suffix()) - sources.append(osrc) + unity_size = self.get_option_for_target('unity_size', extobj.target) + for comp, srcs in compsrcs.items(): + for i in range(len(srcs) // unity_size + 1): + osrc = self.get_unity_source_file(extobj.target, + comp.get_default_suffix(), i) + sources.append(osrc) for osrc in sources: objname = self.object_filename_from_source(extobj.target, osrc) diff --git a/mesonbuild/coredata.py b/mesonbuild/coredata.py index 5e842bccc..720d0648a 100644 --- a/mesonbuild/coredata.py +++ b/mesonbuild/coredata.py @@ -1067,6 +1067,7 @@ builtin_options = OrderedDict([ ('stdsplit', BuiltinOption(UserBooleanOption, 'Split stdout and stderr in test logs', True)), ('strip', BuiltinOption(UserBooleanOption, 'Strip targets on install', False)), ('unity', BuiltinOption(UserComboOption, 'Unity build', 'off', choices=['on', 'off', 'subprojects'])), + ('unity_size', BuiltinOption(UserIntegerOption, 'Unity block size', (2, None, 4))), ('warning_level', BuiltinOption(UserComboOption, 'Compiler warning level to use', '1', choices=['0', '1', '2', '3'])), ('werror', BuiltinOption(UserBooleanOption, 'Treat warnings as errors', False)), ('wrap_mode', BuiltinOption(UserComboOption, 'Wrap mode', 'default', choices=['default', 'nofallback', 'nodownload', 'forcefallback'])), diff --git a/run_unittests.py b/run_unittests.py index c5ad49934..7c2ae0555 100755 --- a/run_unittests.py +++ b/run_unittests.py @@ -5305,9 +5305,9 @@ class LinuxlikeTests(BasePlatformTests): testdir = os.path.join(self.common_test_dir, '45 subproject') self.init(testdir, extra_args='--unity=subprojects') simpletest_id = Target.construct_id_from_path('subprojects/sublib', 'simpletest', '@exe') - self.assertPathExists(os.path.join(self.builddir, 'subprojects/sublib', simpletest_id, 'simpletest-unity.c')) + self.assertPathExists(os.path.join(self.builddir, 'subprojects/sublib', simpletest_id, 'simpletest-unity0.c')) sublib_id = Target.construct_id_from_path('subprojects/sublib', 'sublib', '@sha') - self.assertPathExists(os.path.join(self.builddir, 'subprojects/sublib', sublib_id, 'sublib-unity.c')) + self.assertPathExists(os.path.join(self.builddir, 'subprojects/sublib', sublib_id, 'sublib-unity0.c')) self.assertPathDoesNotExist(os.path.join(self.builddir, 'user@exe/user-unity.c')) self.build()