Repository: mesonbuild/meson

Issue:
meson claims Unknown compiler(s): ['c++', 'g++', 'clang++', 'pgc++', 'icpc']
This is kinda weird: I have a very small dummy meson.build which errors out with:

> meson.build:7:0: ERROR: Unknown compiler(s): ['c++', 'g++', 'clang++', 'pgc++', 'icpc']

Whereas it's correct I don't have any of those compilers on my system, there is also no indication in my meson.build that this would be needed.

The sample meson.build is:

```
project('sample',
	'c',
	version: '1.0.0',
	meson_version: '>= 0.47',
)

dep = dependency('doesnotexist', required: false)
```



Discussion:

Patch:
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,<br>nodownload, forcefallback} | default | Wrap mode to use                            | no             |
diff --git a/docs/markdown/CMake-module.md b/docs/markdown/CMake-module.md
index 4cd62a5f7..a02139644 100644
--- a/docs/markdown/CMake-module.md
+++ b/docs/markdown/CMake-module.md
@@ -12,6 +12,11 @@ following functions will then be available as methods on the object
 with the name `cmake`. You can, of course, replace the name `cmake`
 with anything else.
 
+It is generally recommended to use the latest Meson version and
+CMake >=3.17 for best compatibility. CMake subprojects will
+usually also work with older CMake versions. However, this can
+lead to unexpected issues in rare cases.
+
 ## CMake subprojects
 
 Using CMake subprojects is similar to using the "normal" meson
diff --git a/docs/markdown/Release-notes-for-0.53.0.md b/docs/markdown/Release-notes-for-0.53.0.md
index 22db714df..b29759f46 100644
--- a/docs/markdown/Release-notes-for-0.53.0.md
+++ b/docs/markdown/Release-notes-for-0.53.0.md
@@ -74,15 +74,33 @@ flags passed via language flags and hoped things worked out. In meson 0.52.0
 meson started detecting the linker and making intelligent decisions about
 using it. Unfortunately this broke choosing a non-default linker.
 
-Now there is a generic mechanism for doing this, you may use the LD
-environment variable (with normal meson environment variable rules), or add
-the following to a cross or native file:
+Now there is a generic mechanism for doing this. In 0.53.0, you can use the `LD`
+environment variable. **In 0.53.1** this was changed to `<compiler_variable>_LD`,
+such as `CC_LD`, `CXX_LD`, `D_LD`, etc due to regressions. The usual meson
+[environment variable rules](https://mesonbuild.com/Running-Meson.html#environment-variables)
+apply. Alternatively, you can add the following to a cross or native file:
+
+In 0.53.0:
 
 ```ini
 [binaries]
 ld = 'gold'
 ```
 
+**In 0.53.1 or newer**:
+
+```ini
+[binaries]
+c = 'gcc'
+c_ld = 'gold'
+```
+
+```ini
+[binaries]
+c = 'clang'
+c_ld = 'lld'
+```
+
 And meson will select the linker if possible.
 
 ## `fortran_std` option
diff --git a/docs/markdown/howtox.md b/docs/markdown/howtox.md
index 452da2c24..f70ff47c3 100644
--- a/docs/markdown/howtox.md
+++ b/docs/markdown/howtox.md
@@ -31,11 +31,11 @@ native-files and the latter via the cross file only.
 *New in 0.53.0*
 
 Like the compiler, the linker is selected via the `<compiler variable>_LD`
-environment variable, or through the `<compiler entry>ld` entry in a native
+environment variable, or through the `<compiler entry>_ld` entry in a native
 or cross file. You must be aware of whether you're using a compiler that
 invokes the linker itself (most compilers including GCC and Clang) or a
 linker that is invoked directly (when using MSVC or compilers that act like
-it, including Clang-Cl). With the former `cld` or `CC_LD` should be the value
+it, including Clang-Cl). With the former `c_ld` or `CC_LD` should be the value
 to pass to the compiler's special argument (such as `-fuse-ld` with clang and
 gcc), with the latter it should be an executable, such as `lld-link.exe`.
 
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/cmake/executor.py b/mesonbuild/cmake/executor.py
index c3303ebd6..7b9edf7b0 100644
--- a/mesonbuild/cmake/executor.py
+++ b/mesonbuild/cmake/executor.py
@@ -23,6 +23,7 @@ import re
 import os
 import shutil
 import ctypes
+import textwrap
 
 from .. import mlog, mesonlib
 from ..mesonlib import PerMachine, Popen_safe, version_compare, MachineChoice
@@ -264,9 +265,12 @@ class CMakeExecutor:
             if lang in compilers:
                 exe_list = compilers[lang].get_exelist()
             else:
-                comp_obj = self.environment.compiler_from_language(lang, MachineChoice.BUILD)
-                if comp_obj is not None:
-                    exe_list = comp_obj.get_exelist()
+                try:
+                    comp_obj = self.environment.compiler_from_language(lang, MachineChoice.BUILD)
+                    if comp_obj is not None:
+                        exe_list = comp_obj.get_exelist()
+                except Exception:
+                    pass
 
             if len(exe_list) == 1:
                 return make_abs(exe_list[0], lang), ''
@@ -278,10 +282,7 @@ class CMakeExecutor:
 
         c_comp, c_launcher = choose_compiler('c')
         cxx_comp, cxx_launcher = choose_compiler('cpp')
-        try:
-            fortran_comp, fortran_launcher = choose_compiler('fortran')
-        except Exception:
-            fortran_comp = fortran_launcher = ''
+        fortran_comp, fortran_launcher = choose_compiler('fortran')
 
         # on Windows, choose_compiler returns path with \ as separator - replace by / before writing to CMAKE file
         c_comp = c_comp.replace('\\', '/')
@@ -302,47 +303,50 @@ class CMakeExecutor:
         cxx_comp_file = comp_dir / 'CMakeCXXCompiler.cmake'
         fortran_comp_file = comp_dir / 'CMakeFortranCompiler.cmake'
 
-        if not c_comp_file.is_file():
-            c_comp_file.write_text('''# Fake CMake file to skip the boring and slow stuff
-set(CMAKE_C_COMPILER "{}") # Should be a valid compiler for try_compile, etc.
-set(CMAKE_C_COMPILER_LAUNCHER "{}") # The compiler launcher (if presentt)
-set(CMAKE_C_COMPILER_ID "GNU") # Pretend we have found GCC
-set(CMAKE_COMPILER_IS_GNUCC 1)
-set(CMAKE_C_COMPILER_LOADED 1)
-set(CMAKE_C_COMPILER_WORKS TRUE)
-set(CMAKE_C_ABI_COMPILED TRUE)
-set(CMAKE_C_SOURCE_FILE_EXTENSIONS c;m)
-set(CMAKE_C_IGNORE_EXTENSIONS h;H;o;O;obj;OBJ;def;DEF;rc;RC)
-set(CMAKE_SIZEOF_VOID_P "{}")
-'''.format(c_comp, c_launcher, ctypes.sizeof(ctypes.c_voidp)))
-
-        if not cxx_comp_file.is_file():
-            cxx_comp_file.write_text('''# Fake CMake file to skip the boring and slow stuff
-set(CMAKE_CXX_COMPILER "{}") # Should be a valid compiler for try_compile, etc.
-set(CMAKE_CXX_COMPILER_LAUNCHER "{}") # The compiler launcher (if presentt)
-set(CMAKE_CXX_COMPILER_ID "GNU") # Pretend we have found GCC
-set(CMAKE_COMPILER_IS_GNUCXX 1)
-set(CMAKE_CXX_COMPILER_LOADED 1)
-set(CMAKE_CXX_COMPILER_WORKS TRUE)
-set(CMAKE_CXX_ABI_COMPILED TRUE)
-set(CMAKE_CXX_IGNORE_EXTENSIONS inl;h;hpp;HPP;H;o;O;obj;OBJ;def;DEF;rc;RC)
-set(CMAKE_CXX_SOURCE_FILE_EXTENSIONS C;M;c++;cc;cpp;cxx;mm;CPP)
-set(CMAKE_SIZEOF_VOID_P "{}")
-'''.format(cxx_comp, cxx_launcher, ctypes.sizeof(ctypes.c_voidp)))
+        if c_comp and not c_comp_file.is_file():
+            c_comp_file.write_text(textwrap.dedent('''\
+                # Fake CMake file to skip the boring and slow stuff
+                set(CMAKE_C_COMPILER "{}") # Should be a valid compiler for try_compile, etc.
+                set(CMAKE_C_COMPILER_LAUNCHER "{}") # The compiler launcher (if presentt)
+                set(CMAKE_C_COMPILER_ID "GNU") # Pretend we have found GCC
+                set(CMAKE_COMPILER_IS_GNUCC 1)
+                set(CMAKE_C_COMPILER_LOADED 1)
+                set(CMAKE_C_COMPILER_WORKS TRUE)
+                set(CMAKE_C_ABI_COMPILED TRUE)
+                set(CMAKE_C_SOURCE_FILE_EXTENSIONS c;m)
+                set(CMAKE_C_IGNORE_EXTENSIONS h;H;o;O;obj;OBJ;def;DEF;rc;RC)
+                set(CMAKE_SIZEOF_VOID_P "{}")
+            '''.format(c_comp, c_launcher, ctypes.sizeof(ctypes.c_voidp))))
+
+        if cxx_comp and not cxx_comp_file.is_file():
+            cxx_comp_file.write_text(textwrap.dedent('''\
+                # Fake CMake file to skip the boring and slow stuff
+                set(CMAKE_CXX_COMPILER "{}") # Should be a valid compiler for try_compile, etc.
+                set(CMAKE_CXX_COMPILER_LAUNCHER "{}") # The compiler launcher (if presentt)
+                set(CMAKE_CXX_COMPILER_ID "GNU") # Pretend we have found GCC
+                set(CMAKE_COMPILER_IS_GNUCXX 1)
+                set(CMAKE_CXX_COMPILER_LOADED 1)
+                set(CMAKE_CXX_COMPILER_WORKS TRUE)
+                set(CMAKE_CXX_ABI_COMPILED TRUE)
+                set(CMAKE_CXX_IGNORE_EXTENSIONS inl;h;hpp;HPP;H;o;O;obj;OBJ;def;DEF;rc;RC)
+                set(CMAKE_CXX_SOURCE_FILE_EXTENSIONS C;M;c++;cc;cpp;cxx;mm;CPP)
+                set(CMAKE_SIZEOF_VOID_P "{}")
+            '''.format(cxx_comp, cxx_launcher, ctypes.sizeof(ctypes.c_voidp))))
 
         if fortran_comp and not fortran_comp_file.is_file():
-            fortran_comp_file.write_text('''# Fake CMake file to skip the boring and slow stuff
-set(CMAKE_Fortran_COMPILER "{}") # Should be a valid compiler for try_compile, etc.
-set(CMAKE_Fortran_COMPILER_LAUNCHER "{}") # The compiler launcher (if presentt)
-set(CMAKE_Fortran_COMPILER_ID "GNU") # Pretend we have found GCC
-set(CMAKE_COMPILER_IS_GNUG77 1)
-set(CMAKE_Fortran_COMPILER_LOADED 1)
-set(CMAKE_Fortran_COMPILER_WORKS TRUE)
-set(CMAKE_Fortran_ABI_COMPILED TRUE)
-set(CMAKE_Fortran_IGNORE_EXTENSIONS h;H;o;O;obj;OBJ;def;DEF;rc;RC)
-set(CMAKE_Fortran_SOURCE_FILE_EXTENSIONS f;F;fpp;FPP;f77;F77;f90;F90;for;For;FOR;f95;F95)
-set(CMAKE_SIZEOF_VOID_P "{}")
-'''.format(fortran_comp, fortran_launcher, ctypes.sizeof(ctypes.c_voidp)))
+            fortran_comp_file.write_text(textwrap.dedent('''\
+                # Fake CMake file to skip the boring and slow stuff
+                set(CMAKE_Fortran_COMPILER "{}") # Should be a valid compiler for try_compile, etc.
+                set(CMAKE_Fortran_COMPILER_LAUNCHER "{}") # The compiler launcher (if presentt)
+                set(CMAKE_Fortran_COMPILER_ID "GNU") # Pretend we have found GCC
+                set(CMAKE_COMPILER_IS_GNUG77 1)
+                set(CMAKE_Fortran_COMPILER_LOADED 1)
+                set(CMAKE_Fortran_COMPILER_WORKS TRUE)
+                set(CMAKE_Fortran_ABI_COMPILED TRUE)
+                set(CMAKE_Fortran_IGNORE_EXTENSIONS h;H;o;O;obj;OBJ;def;DEF;rc;RC)
+                set(CMAKE_Fortran_SOURCE_FILE_EXTENSIONS f;F;fpp;FPP;f77;F77;f90;F90;for;For;FOR;f95;F95)
+                set(CMAKE_SIZEOF_VOID_P "{}")
+            '''.format(fortran_comp, fortran_launcher, ctypes.sizeof(ctypes.c_voidp))))
 
         return self.call(args, build_dir, env)
 
diff --git a/mesonbuild/cmake/traceparser.py b/mesonbuild/cmake/traceparser.py
index 4aa34f1cc..7b29c86df 100644
--- a/mesonbuild/cmake/traceparser.py
+++ b/mesonbuild/cmake/traceparser.py
@@ -24,6 +24,7 @@ import typing as T
 from pathlib import Path
 import re
 import os
+import json
 
 class CMakeTraceLine:
     def __init__(self, file, line, func, args):
@@ -80,11 +81,12 @@ class CMakeTraceParser:
         self.cmake_version = cmake_version  # type: str
         self.trace_file = 'cmake_trace.txt'
         self.trace_file_path = Path(build_dir) / self.trace_file
-        self.trace_format = 'human'
+        self.trace_format = 'json-v1' if version_compare(cmake_version, '>=3.17') else 'human'
 
     def trace_args(self) -> T.List[str]:
         arg_map = {
             'human': ['--trace', '--trace-expand'],
+            'json-v1': ['--trace-expand', '--trace-format=json-v1'],
         }
 
         base_args = ['--no-warn-unused-cli']
@@ -109,8 +111,10 @@ class CMakeTraceParser:
         lexer1 = None
         if self.trace_format == 'human':
             lexer1 = self._lex_trace_human(trace)
+        elif self.trace_format == 'json-v1':
+            lexer1 = self._lex_trace_json(trace)
         else:
-            raise CMakeException('CMake: Internal error: Invalid trace format {}. Expected [human]'.format(self.trace_format))
+            raise CMakeException('CMake: Internal error: Invalid trace format {}. Expected [human, json-v1]'.format(self.trace_format))
 
         # All supported functions
         functions = {
@@ -421,21 +425,27 @@ class CMakeTraceParser:
         # Neither of these is awesome for obvious reasons. I'm going to try
         # option 1 first and fall back to 2, as 1 requires less code and less
         # synchroniztion for cmake changes.
+        #
+        # With the JSON output format, introduced in CMake 3.17, spaces are
+        # handled properly and we don't have to do either options
 
         arglist = []  # type: T.List[T.Tuple[str, T.List[str]]]
-        name = args.pop(0)
-        values = []
-        prop_regex = re.compile(r'^[A-Z_]+$')
-        for a in args:
-            if prop_regex.match(a):
-                if values:
-                    arglist.append((name, ' '.join(values).split(';')))
-                name = a
-                values = []
-            else:
-                values.append(a)
-        if values:
-            arglist.append((name, ' '.join(values).split(';')))
+        if self.trace_format == 'human':
+            name = args.pop(0)
+            values = []
+            prop_regex = re.compile(r'^[A-Z_]+$')
+            for a in args:
+                if prop_regex.match(a):
+                    if values:
+                        arglist.append((name, ' '.join(values).split(';')))
+                    name = a
+                    values = []
+                else:
+                    values.append(a)
+            if values:
+                arglist.append((name, ' '.join(values).split(';')))
+        else:
+            arglist = [(x[0], x[1].split(';')) for x in zip(args[::2], args[1::2])]
 
         for name, value in arglist:
             for i in targets:
@@ -549,7 +559,20 @@ class CMakeTraceParser:
 
             yield CMakeTraceLine(file, line, func, args)
 
+    def _lex_trace_json(self, trace: str):
+        lines = trace.splitlines(keepends=False)
+        lines.pop(0)  # The first line is the version
+        for i in lines:
+            data = json.loads(i)
+            args = data['args']
+            args = [parse_generator_expressions(x) for x in args]
+            yield CMakeTraceLine(data['file'], data['line'], data['cmd'], args)
+
     def _guess_files(self, broken_list: T.List[str]) -> T.List[str]:
+        # Nothing has to be done for newer formats
+        if self.trace_format != 'human':
+            return broken_list
+
         # Try joining file paths that contain spaces
 
         reg_start = re.compile(r'^([A-Za-z]:)?/.*/[^./]+$')
diff --git a/mesonbuild/coredata.py b/mesonbuild/coredata.py
index aea2d908e..720d0648a 100644
--- a/mesonbuild/coredata.py
+++ b/mesonbuild/coredata.py
@@ -96,11 +96,12 @@ class UserBooleanOption(UserOption[bool]):
         raise MesonException('Value %s is not boolean (true or false).' % value)
 
 class UserIntegerOption(UserOption[int]):
-    def __init__(self, description, min_value, max_value, value, yielding=None):
+    def __init__(self, description, value, yielding=None):
+        min_value, max_value, default_value = value
         super().__init__(description, [True, False], yielding)
         self.min_value = min_value
         self.max_value = max_value
-        self.set_value(value)
+        self.set_value(default_value)
         c = []
         if min_value is not None:
             c.append('>=' + str(min_value))
@@ -127,7 +128,7 @@ class UserIntegerOption(UserOption[int]):
 
 class UserUmaskOption(UserIntegerOption, UserOption[T.Union[str, int]]):
     def __init__(self, description, value, yielding=None):
-        super().__init__(description, 0, 0o777, value, yielding)
+        super().__init__(description, (0, 0o777, value), yielding)
         self.choices = ['preserve', '0000-0777']
 
     def printable_value(self):
@@ -525,7 +526,7 @@ class CoreData:
                 UserIntegerOption(
                     'Maximum number of linker processes to run or 0 for no '
                     'limit',
-                    0, None, 0)
+                    (0, None, 0))
         elif backend_name.startswith('vs'):
             self.backend_options['backend_startup_project'] = \
                 UserStringOption(
@@ -967,7 +968,7 @@ class BuiltinOption(T.Generic[_T, _U]):
 
     """Class for a builtin option type.
 
-    Currently doesn't support UserIntegerOption, or a few other cases.
+    There are some cases that are not fully supported yet.
     """
 
     def __init__(self, opt_type: T.Type[_U], description: str, default: T.Any, yielding: bool = True, *,
@@ -1066,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/mesonbuild/optinterpreter.py b/mesonbuild/optinterpreter.py
index 4d790b6bc..1a8a04a4a 100644
--- a/mesonbuild/optinterpreter.py
+++ b/mesonbuild/optinterpreter.py
@@ -90,10 +90,9 @@ def ComboParser(description, kwargs):
 def IntegerParser(description, kwargs):
     if 'value' not in kwargs:
         raise OptionException('Integer option must contain value argument.')
+    inttuple = (kwargs.get('min', None), kwargs.get('max', None), kwargs['value'])
     return coredata.UserIntegerOption(description,
-                                      kwargs.get('min', None),
-                                      kwargs.get('max', None),
-                                      kwargs['value'],
+                                      inttuple,
                                       kwargs.get('yield', coredata.default_yielding))
 
 # FIXME: Cannot use FeatureNew while parsing options because we parse it before


Test patch:
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()