# Buggy code
def eval_custom_target_command(self, target, absolute_paths=False):
    if not absolute_paths:
        ofilenames = [
            os.path.join(self.get_target_dir(target), i) for i in target.output
        ]
    else:
        ofilenames = [
            os.path.join(
                self.environment.get_build_dir(), self.get_target_dir(target), i
            )
            for i in target.output
        ]
    srcs = []
    outdir = self.get_target_dir(target)
    # Many external programs fail on empty arguments.
    if outdir == "":
        outdir = "."
    if absolute_paths:
        outdir = os.path.join(self.environment.get_build_dir(), outdir)
    for i in target.get_sources():
        if hasattr(i, "held_object"):
            i = i.held_object
        if isinstance(i, str):
            fname = [os.path.join(self.build_to_src, target.subdir, i)]
        elif isinstance(i, build.BuildTarget):
            fname = [self.get_target_filename(i)]
        elif isinstance(i, build.CustomTarget):
            fname = [os.path.join(self.get_target_dir(i), p) for p in i.get_outputs()]
        elif isinstance(i, build.GeneratedList):
            fname = [
                os.path.join(self.get_target_private_dir(target), p)
                for p in i.get_outputs()
            ]
        else:
            fname = [i.rel_to_builddir(self.build_to_src)]
        if absolute_paths:
            fname = [os.path.join(self.environment.get_build_dir(), f) for f in fname]
        srcs += fname
    cmd = []
    for i in target.command:
        if isinstance(i, build.Executable):
            cmd += self.exe_object_to_cmd_array(i)
            continue
        elif isinstance(i, build.CustomTarget):
            # GIR scanner will attempt to execute this binary but
            # it assumes that it is in path, so always give it a full path.
            tmp = i.get_outputs()[0]
            i = os.path.join(self.get_target_dir(i), tmp)
        elif isinstance(i, mesonlib.File):
            i = i.rel_to_builddir(self.build_to_src)
            if absolute_paths:
                i = os.path.join(self.environment.get_build_dir(), i)
        # FIXME: str types are blindly added and ignore the 'absolute_paths' argument
        elif not isinstance(i, str):
            err_msg = "Argument {0} is of unknown type {1}"
            raise RuntimeError(err_msg.format(str(i), str(type(i))))
        for j, src in enumerate(srcs):
            i = i.replace("@INPUT%d@" % j, src)
        for j, res in enumerate(ofilenames):
            i = i.replace("@OUTPUT%d@" % j, res)
        if "@INPUT@" in i:
            msg = "Custom target {} has @INPUT@ in the command, but".format(target.name)
            if len(srcs) == 0:
                raise MesonException(msg + " no input files")
            if i == "@INPUT@":
                cmd += srcs
                continue
            else:
                if len(srcs) > 1:
                    raise MesonException(msg + " more than one input file")
                i = i.replace("@INPUT@", srcs[0])
        elif "@OUTPUT@" in i:
            msg = "Custom target {} has @OUTPUT@ in the command, but".format(
                target.name
            )
            if len(ofilenames) == 0:
                raise MesonException(msg + " no output files")
            if i == "@OUTPUT@":
                cmd += ofilenames
                continue
            else:
                if len(ofilenames) > 1:
                    raise MesonException(msg + " more than one output file")
                i = i.replace("@OUTPUT@", ofilenames[0])
        elif "@OUTDIR@" in i:
            i = i.replace("@OUTDIR@", outdir)
        elif "@DEPFILE@" in i:
            if target.depfile is None:
                raise MesonException(
                    "Custom target %s has @DEPFILE@ but no depfile keyword argument."
                    % target.name
                )
            dfilename = os.path.join(outdir, target.depfile)
            i = i.replace("@DEPFILE@", dfilename)
        elif "@PRIVATE_OUTDIR_" in i:
            match = re.search("@PRIVATE_OUTDIR_(ABS_)?([-a-zA-Z0-9.@:]*)@", i)
            source = match.group(0)
            if match.group(1) is None and not absolute_paths:
                lead_dir = ""
            else:
                lead_dir = self.environment.get_build_dir()
            i = i.replace(source, os.path.join(lead_dir, outdir))
        cmd.append(i)
    # This should not be necessary but removing it breaks
    # building GStreamer on Windows. The underlying issue
    # is problems with quoting backslashes on Windows
    # which is the seventh circle of hell. The downside is
    # that this breaks custom targets whose command lines
    # have backslashes. If you try to fix this be sure to
    # check that it does not break GST.
    #
    # The bug causes file paths such as c:\foo to get escaped
    # into c:\\foo.
    #
    # Unfortunately we have not been able to come up with an
    # isolated test case for this so unless you manage to come up
    # with one, the only way is to test the building with Gst's
    # setup. Note this in your MR or ping us and we will get it
    # fixed.
    #
    # https://github.com/mesonbuild/meson/pull/737
    cmd = [i.replace("\\", "/") for i in cmd]
    return (srcs, ofilenames, cmd)


# Traceback (AttributeError)
(dev_env)[meh@meh-host build]$ rm -rf * &amp;&amp; meson.py ..
The Meson build system
Version: 0.30.0.dev1
Source dir: /home/meh/devel/hotdoc/test_hotdoc
Build dir: /home/meh/devel/hotdoc/test_hotdoc/build
Build type: native build
Build machine cpu family: x86_64
Build machine cpu: x86_64
Project name: Hotdoc-Test
Native c compiler: cc (gcc 5.3.1)
Warning, glib compiled dependencies will not work reliably until this upstream issue is fixed: https://bugzilla.gnome.org/show_bug.cgi?id=745754
Found pkg-config: /usr/bin/pkg-config (0.28)
Native dependency glib-2.0 found: YES 2.44.1
Native dependency gobject-2.0 found: YES 2.44.1
Build targets in project: 3
Traceback (most recent call last):
File "/usr/lib/python3.4/site-packages/meson-0.30.0.dev1-py3.4.egg/mesonbuild/mesonmain.py", line 266, in run
app.generate()
File "/usr/lib/python3.4/site-packages/meson-0.30.0.dev1-py3.4.egg/mesonbuild/mesonmain.py", line 170, in generate
g.generate(intr)
File "/usr/lib/python3.4/site-packages/meson-0.30.0.dev1-py3.4.egg/mesonbuild/backend/ninjabackend.py", line 176, in generate
[self.generate_target(t, outfile) for t in self.build.get_targets().values()]
File "/usr/lib/python3.4/site-packages/meson-0.30.0.dev1-py3.4.egg/mesonbuild/backend/ninjabackend.py", line 176, in <listcomp>
[self.generate_target(t, outfile) for t in self.build.get_targets().values()]
File "/usr/lib/python3.4/site-packages/meson-0.30.0.dev1-py3.4.egg/mesonbuild/backend/ninjabackend.py", line 219, in generate_target
self.generate_custom_target(target, outfile)
File "/usr/lib/python3.4/site-packages/meson-0.30.0.dev1-py3.4.egg/mesonbuild/backend/ninjabackend.py", line 338, in generate_custom_target
(srcs, ofilenames, cmd) = self.eval_custom_target_command(target)
File "/usr/lib/python3.4/site-packages/meson-0.30.0.dev1-py3.4.egg/mesonbuild/backend/backends.py", line 427, in eval_custom_target_command
source = match.group(0)
AttributeError: 'NoneType' object has no attribute 'group'
(dev_env)[meh@meh-host build]$

# Fixed code
def eval_custom_target_command(self, target, absolute_paths=False):
    if not absolute_paths:
        ofilenames = [
            os.path.join(self.get_target_dir(target), i) for i in target.output
        ]
    else:
        ofilenames = [
            os.path.join(
                self.environment.get_build_dir(), self.get_target_dir(target), i
            )
            for i in target.output
        ]
    srcs = []
    outdir = self.get_target_dir(target)
    # Many external programs fail on empty arguments.
    if outdir == "":
        outdir = "."
    if absolute_paths:
        outdir = os.path.join(self.environment.get_build_dir(), outdir)
    for i in target.get_sources():
        if hasattr(i, "held_object"):
            i = i.held_object
        if isinstance(i, str):
            fname = [os.path.join(self.build_to_src, target.subdir, i)]
        elif isinstance(i, build.BuildTarget):
            fname = [self.get_target_filename(i)]
        elif isinstance(i, build.CustomTarget):
            fname = [os.path.join(self.get_target_dir(i), p) for p in i.get_outputs()]
        elif isinstance(i, build.GeneratedList):
            fname = [
                os.path.join(self.get_target_private_dir(target), p)
                for p in i.get_outputs()
            ]
        else:
            fname = [i.rel_to_builddir(self.build_to_src)]
        if absolute_paths:
            fname = [os.path.join(self.environment.get_build_dir(), f) for f in fname]
        srcs += fname
    cmd = []
    for i in target.command:
        if isinstance(i, build.Executable):
            cmd += self.exe_object_to_cmd_array(i)
            continue
        elif isinstance(i, build.CustomTarget):
            # GIR scanner will attempt to execute this binary but
            # it assumes that it is in path, so always give it a full path.
            tmp = i.get_outputs()[0]
            i = os.path.join(self.get_target_dir(i), tmp)
        elif isinstance(i, mesonlib.File):
            i = i.rel_to_builddir(self.build_to_src)
            if absolute_paths:
                i = os.path.join(self.environment.get_build_dir(), i)
        # FIXME: str types are blindly added and ignore the 'absolute_paths' argument
        elif not isinstance(i, str):
            err_msg = "Argument {0} is of unknown type {1}"
            raise RuntimeError(err_msg.format(str(i), str(type(i))))
        for j, src in enumerate(srcs):
            i = i.replace("@INPUT%d@" % j, src)
        for j, res in enumerate(ofilenames):
            i = i.replace("@OUTPUT%d@" % j, res)
        if "@INPUT@" in i:
            msg = "Custom target {} has @INPUT@ in the command, but".format(target.name)
            if len(srcs) == 0:
                raise MesonException(msg + " no input files")
            if i == "@INPUT@":
                cmd += srcs
                continue
            else:
                if len(srcs) > 1:
                    raise MesonException(msg + " more than one input file")
                i = i.replace("@INPUT@", srcs[0])
        elif "@OUTPUT@" in i:
            msg = "Custom target {} has @OUTPUT@ in the command, but".format(
                target.name
            )
            if len(ofilenames) == 0:
                raise MesonException(msg + " no output files")
            if i == "@OUTPUT@":
                cmd += ofilenames
                continue
            else:
                if len(ofilenames) > 1:
                    raise MesonException(msg + " more than one output file")
                i = i.replace("@OUTPUT@", ofilenames[0])
        elif "@OUTDIR@" in i:
            i = i.replace("@OUTDIR@", outdir)
        elif "@DEPFILE@" in i:
            if target.depfile is None:
                msg = (
                    "Custom target {!r} has @DEPFILE@ but no depfile "
                    "keyword argument.".format(target.name)
                )
                raise MesonException(msg)
            dfilename = os.path.join(outdir, target.depfile)
            i = i.replace("@DEPFILE@", dfilename)
        elif "@PRIVATE_OUTDIR_" in i:
            match = re.search("@PRIVATE_OUTDIR_(ABS_)?([^\/\s*]*)@", i)
            if not match:
                msg = "Custom target {!r} has an invalid argument {!r}".format(
                    target.name, i
                )
                raise MesonException(msg)
            source = match.group(0)
            if match.group(1) is None and not absolute_paths:
                lead_dir = ""
            else:
                lead_dir = self.environment.get_build_dir()
            i = i.replace(source, os.path.join(lead_dir, outdir))
        cmd.append(i)
    # This should not be necessary but removing it breaks
    # building GStreamer on Windows. The underlying issue
    # is problems with quoting backslashes on Windows
    # which is the seventh circle of hell. The downside is
    # that this breaks custom targets whose command lines
    # have backslashes. If you try to fix this be sure to
    # check that it does not break GST.
    #
    # The bug causes file paths such as c:\foo to get escaped
    # into c:\\foo.
    #
    # Unfortunately we have not been able to come up with an
    # isolated test case for this so unless you manage to come up
    # with one, the only way is to test the building with Gst's
    # setup. Note this in your MR or ping us and we will get it
    # fixed.
    #
    # https://github.com/mesonbuild/meson/pull/737
    cmd = [i.replace("\\", "/") for i in cmd]
    return (srcs, ofilenames, cmd)