#!/usr/bin/env python
"""

   A tool to generate gcov files from a kde builddir. This should
   work reasonable well for any KDE CMake based project.


  Copyright (C)  2006 Holger Freyther <freyther@kde.org>

  Redistribution and use in source and binary forms, with or without
  modification, are permitted provided that the following conditions
  are met:

  1. Redistributions of source code must retain the above copyright
     notice, this list of conditions and the following disclaimer.
  2. Redistributions in binary form must reproduce the above copyright
     notice, this list of conditions and the following disclaimer in the
     documentation and/or other materials provided with the distribution.

  THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
  IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
  OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
  NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
  THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""

import os, sys


# "BlackList" of targets
blacklist = [
"CMakeTmp/cmTryCompileExec",
"Continuous",
"ContinuousBuild",
"ContinuousConfigure",
"ContinuousCoverage",
"ContinuousMemCheck",
"ContinuousStart",
"ContinuousSubmit",
"ContinuousTest",
"ContinuousUpdate",
"Experimental",
"ExperimentalBuild",
"ExperimentalConfigure",
"ExperimentalCoverage",
"ExperimentalMemCheck",
"ExperimentalStart",
"ExperimentalSubmit",
"ExperimentalTest",
"ExperimentalUpdate",
"Nightly",
"NightlyBuild",
"NightlyConfigure",
"NightlyCoverage",
"NightlyMemCheck",
"NightlyMemoryCheck",
"NightlyStart",
"NightlySubmit",
"NightlyTest",
"NightlyUpdate",
"uninstall" ]


# from BitBake
def mkdirhier(dir):
    """Create a directory like 'mkdir -p', but does not complain if
    directory already exists like os.makedirs
    """
    try:
        os.makedirs(dir)
    except OSError, e:
        if e.errno != 17: raise e

def collect_target(build):
    """
    Collect all targets from CMake
    """
    targets = []
    for root, dirs, files in os.walk(build):
        if ".svn" in root:
            continue

        if root[-4:] == ".dir" and "CMakeFiles" in root:
            # Check if this target is blacklisted
            target_name = root[:-4].replace("CMakeFiles/","").replace(build,"")
            # os.path.join("dir", "/foo") -> /foo so let us remove the separator
            if target_name[0] == "/":
                target_name = target_name[1:]
            if target_name in blacklist:
                continue

            targets.append( root )

    return targets

def collect_sources(src,data):
    """
    Collect all source files. For each folder we will create on in the data dir
    which will contain our gcov files
    """

    sources = []
    for root, dirs, files in os.walk(src):
        if ".svn" in root:
            continue

        target = root.replace(src,data)
        for dir in dirs:
            if ".svn" in dir:
                continue

            mkdirhier(os.path.join(target, dir))
        for file in files:
            base,ext = os.path.splitext(file)
            if ext in [".h", ".hh", ".cc", ".cxx", ".c", ".cpp", ".moc"]:
                sources.append( os.path.join(root, file) )

    return sources

def collect_cov(data):
    """
    Collect gcov files, collect_sources is not used as it also creates
    dirs and needs to do substituting.
    Actually we will build a mapping from source file to gcov files of
    interest. This is because we could have bytestream.h in many different
    subdirectories. And we would endup with bla.cpp##bytestream.h and we
    do not know which bytestream file was tested
    """
    def find_source_file(cov_file):
        """ Find a Sourc line or crash
        -:    0:Source:/home/freyther/kde/kdelibs/kioslave/file/file.cc
        """
        for line in open(cov_file, 'r'):
            split = line.split(':')
            if split[1].strip() == "0" and split[2] == "Source":
                return split[3][:-1]
        raise "No source found"

    gcov = {}
    for root, dirs, files in os.walk(data):
        if ".svn" in root:
            continue
        for file in files:
            base,ext = os.path.splitext(file)
            if ext in [".gcov"]:
                try:
                    cov = os.path.join(root, file)
                    src = find_source_file( cov )
                    if not src in gcov:
                        gcov[src] = []
                    gcov[src].append( cov )
                except:
                    pass

    return gcov

def clean_target_name(build,targetp):
    target = targetp.replace(build,"")
    if target.startswith("/CMakeFiles"):
        return ""
    elif target[:1] == "/":
        target = target[1:]

    return target[0:target.find("/CMakeFiles")]

def generate_cov(output, file, targets, root):
    base,ext = os.path.splitext(file)
    if ext in [".h", ".hh", ".cc", ".cxx", ".c", ".cpp", ".moc"]:
        src_file = os.path.join(root, file)
        found = False
        for target in targets:
            name = clean_target_name(build,target)
            if name in root:
                found = True
                cmd = "cd %s; gcov -l -o %s %s" % (output, target, src_file)
                #print "Executing %s" % cmd
                os.system("%s > /dev/null 2>&1 " % cmd)
        if not found:
            print "No target found for %s/%s" % (root,file)


def analyze_coverage(sources,data,runid):
    """
    src_dir the Source directory e.g. kdleibs
    sources actual source files relative to src_dir e.g kdelibs/kdecore/klibloader.cpp
    data    The source of the gcov files
    runid   A number for this build
    """
    import cov
    gcov = collect_cov(data)
    result = cov.analyze_coverage(gcov, sources, runid, data)
    #print result

if __name__ == "__main__":
    if not len(sys.argv) == 5:
        print "This script needs three parameters"
        print "Call it with foo builds/kdelibs src/kdelibs result/kdelibs RUNID"
        sys.exit(-1)
    build, src, data, runid = sys.argv[1:]
    #walk_dir_src(build, src, data)

    print "Collecting CMake targets"
    targets = collect_target(build)

    print "Collection Sources and preparing data tree"
    sources = collect_sources(src,data)

    print "Found %d targets and %s sources" % (len(targets),len(sources))

    print "Will run inefficient generation of gcov files now"
    # Currently this is quite inefficient. I used to find the targets and generate
    # the gcov files for each target. The heuristic was broken though and I ended up
    # with a lot of empty dirs

    #for target in targets:
    #    print "Normal %s: %s" % (target,clean_target_name(build,target))

    # Walk each source directory again
    for root, dirs, files in os.walk(src):
        output = root.replace(src,data)
        for file in files:
            generate_cov(output, file, targets, root)


    print "Analyzing GCov"
    analyze_coverage(sources, data, runid)
