def invoke_side_effects(argv):
    log("invoke_side_effects: %s"
        % ' '.join(sys.argv))

    gccinv = GccInvocation(argv)

    # Try to run each side effect in a subprocess, passing in a path
    # for the XML results to be written to.
    # Cover a multitude of possible failures by detecting if no output
    # was written, and capturing *that* as a failure
    for sourcefile in gccinv.sources:
        if sourcefile.endswith('.c'): # FIXME: other extensions?
            for script, genname in [('invoke-cppcheck', 'cppcheck'),
                                    ('invoke-clang-analyzer', 'clang-analyzer'),
                                    ('invoke-cpychecker', 'cpychecker'),

                                    # Uncomment the following to test a
                                    # checker that fails to write any XML:
                                    # ('echo', 'failing-checker'),

                                    ]:
                with tempfile.NamedTemporaryFile() as f:
                    dstxmlpath = f.name
                assert not os.path.exists(dstxmlpath)

                # Restrict the invocation to just one source file at a
                # time:
                singleinv = gccinv.restrict_to_one_source(sourcefile)
                singleargv = singleinv.argv

                TIMEOUT=60
                t = Timer()

                args = [script, dstxmlpath] + singleargv
                log('invoking args: %r' % args)
                p = Popen(args,
                          stdout=PIPE, stderr=PIPE)
                try:
                    out, err = p.communicate(timeout=TIMEOUT)
                    write_streams(script, out, err)

                    if os.path.exists(dstxmlpath):
                        with open(dstxmlpath) as f:
                            analysis = Analysis.from_xml(f)
                    else:
                        analysis = make_failed_analysis(genname, sourcefile, t,
                                                        msgtext=('Unable to locate XML output from %s'
                                                                 % script),
                                                        failureid='no-output-found')
                        analysis.set_custom_field('stdout', out)
                        analysis.set_custom_field('stderr', err)
                        analysis.set_custom_field('returncode', p.returncode)
                except TimeoutExpired:
                    analysis = make_failed_analysis(genname, sourcefile, t,
                                                    msgtext='Timeout running %s' % genname,
                                                    failureid='timeout')
                    analysis.set_custom_field('timeout', TIMEOUT)
                analysis.set_custom_field('gcc-invocation', ' '.join(argv))
                write_analysis_as_xml(analysis)
    def invoke(self, argv):
        """FIXME"""
        self.log("Driver.invoke: %s"
            % ' '.join(sys.argv))

        gccinv = GccInvocation(argv)

        self.log('  gccinv.sources: %r' % gccinv.sources)

        # Run the side effects on each source file:
        for sourcefile in gccinv.sources:
            self.log('    sourcefile: %r' % sourcefile)
            if sourcefile.endswith('.c'): # FIXME: other extensions?
                single_source_gccinv = gccinv.restrict_to_one_source(sourcefile)

                # Avoid linker errors due to splitting up the build into
                # multiple gcc invocations:
                single_source_gccinv.argv += ['-c']

                self.log('    single_source_gccinv: %r' % single_source_gccinv)
                for side_effect in self.side_effects:
                    analysis = self.invoke_tool(side_effect,
                                                single_source_gccinv,
                                                sourcefile)
                    #analysis.set_custom_field('gcc-invocation', ' '.join(argv))
                    self.write_analysis_as_xml(analysis)

        # Now run the real driver.
        # Note that we already ran the real gcc earlier as a
        # side-effect per source-file, capturing warnings there.
        # We have to do it separately from here since the invocation
        # might cover multiple source files.

        argv = [self.real_driver] + gccinv.argv[1:]
        env=os.environ.copy()
        # FIXME: this probably shouldn't be hardcoded
        env['LANG'] = 'C'
        p = Popen(argv,
                  stdout=PIPE, stderr=PIPE, env=env)
        out, err = p.communicate()
        self.ctxt.stdout.write(out)
        self.ctxt.stderr.write(err)
        self.returncode = p.returncode