1#!/usr/bin/env python
2
3# Copyright (C) 2004, 2005, 2006 Nathaniel Smith
4# Copyright (C) 2007 Holger Hans Peter Freyther
5#
6# Redistribution and use in source and binary forms, with or without
7# modification, are permitted provided that the following conditions
8# are met:
9#
10# 1.  Redistributions of source code must retain the above copyright
11#     notice, this list of conditions and the following disclaimer. 
12# 2.  Redistributions in binary form must reproduce the above copyright
13#     notice, this list of conditions and the following disclaimer in the
14#     documentation and/or other materials provided with the distribution. 
15# 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
16#     its contributors may be used to endorse or promote products derived
17#     from this software without specific prior written permission. 
18#
19# THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
20# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
21# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22# DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
23# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
26# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
28# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
30import os, sys
31
32# from BitBake
33def mkdirhier(dir):
34    """Create a directory like 'mkdir -p', but does not complain if
35    directory already exists like os.makedirs
36    """
37    try:
38        os.makedirs(dir)
39    except OSError, e:
40        if e.errno != 17: raise e
41
42def collect_base(src,match_array):
43    """
44    Collect all files that match the match_array.
45    """
46
47    sources = []
48    for root, dirs, files in os.walk(src):
49        if ".svn" in root:
50            continue
51
52        for file in files:
53            base,ext = os.path.splitext(file)
54            if ext in match_array:
55                sources.append( os.path.join(root, file) )
56
57    return sources
58
59def collect_depends(src):
60    return collect_base(src, [".d"])
61
62def parse_dependency_file(src, base_dir, black_list):
63    """
64    Parse the .d files of the gcc
65
66    Wow, the first time os.path.join is doing the right thing. We might
67    have a relative path in the depends using os.path.join(dirname of .d, dep)
68    we will end up in 
69    """
70    file = open(src)
71    file = file.read()
72    file = file.replace('\\', '').replace('\n', '')
73
74    # We now have object: dependencies splitted
75    ar  = file.split(':', 1)
76    obj = ar[0].strip()
77    dir = os.path.dirname(obj)
78    deps = ar[1].split(' ')
79
80    # Remove files outside WebKit, make path absolute
81    deps = filter(lambda x: base_dir in x, deps)
82    deps = map(lambda x: os.path.abspath(os.path.join(dir, x)), deps)
83    return (obj, dir, deps)
84
85def collect_cov(base_path,targets):
86    """
87    Collect gcov files, collect_sources is not used as it also creates
88    dirs and needs to do substituting.
89    Actually we will build a mapping from source file to gcov files of
90    interest. This is because we could have bytestream.h in many different
91    subdirectories. And we would endup with bla.cpp##bytestream.h and we
92    do not know which bytestream file was tested
93    """
94    def find_source_file(root,cov_file):
95        """ Find a Source line or crash
96
97        '#Users#ich#projekte#src#threadmessage.cpp###space#dports#include#qt3#qstring.h.gcov'
98        '#Users#ich#projekte#src#threadmessage.cpp##..#^#src#threadmessage.cpp.gcov'
99
100        ### is absolute path
101        ##..#^# is relative path... well a gcov bug as well
102        ##  normal split file in the same directory
103        """
104        if '###' in cov_file:
105            split = cov_file.split('###')
106            if not len(split) == 2:
107                raise "Unexpected split result"
108            filepath = split[1][:-5].replace('#',os.path.sep)
109            return os.path.join(os.path.sep,filepath)
110        elif '##..#^#' in cov_file: 
111            split = cov_file.split('##..#^#')
112            if not len(split) == 2:
113                raise "Unexpected split result"
114            filepath = split[1][:-5].replace('#',os.path.sep)
115            return os.path.abspath(os.path.join(root,os.path.pardir,os.path.pardir,filepath))
116        elif '##' in cov_file:
117            split = cov_file.split('##')
118            if not len(split) == 2:
119                raise "Unexpected split result"
120            filepath = split[1][:-5].replace('#',os.path.sep)
121            return os.path.abspath(os.path.join(root,filepath))
122        elif '#' in cov_file:
123            # wow a not broken gcov on OSX
124            basename=os.path.basename(cov_file).replace('#',os.path.sep)[:-5]
125            return os.path.abspath(os.path.join(root,basename))
126
127        else:
128            raise "No source found %s" % cov_file
129
130    def sanitize_path(path):
131        """
132        Well fix up paths once again /usr/lib/gcc/i486-linux-gnu/4.1.2/^/^/^/^/include/c++/4.1.2/bits/stl_pair.h
133        according to gcov '^' is a relative path, we will now build one from this one. Somehow it depends
134        on the gcov version if .. really gets replaced to ^....
135        """
136        import os
137        split = path.split(os.path.sep)
138        str = ""
139        for part in split:
140            if part == '':
141                str = os.path.sep
142            elif part == '^':
143                str = "%s..%s" % (str,os.path.sep)
144            else:
145                str = "%s%s%s" % (str,part,os.path.sep)
146        return os.path.abspath(str)
147
148
149    gcov = {}
150    for root, dirs, files in os.walk(base_path):
151        if ".svn" in root:
152            continue
153        for file in files:
154            base,ext = os.path.splitext(file)
155            if ext in [".gcov"]:
156                try:
157                    cov = os.path.join(root, file)
158                    src = find_source_file( root, cov )
159                    src = sanitize_path( src )
160
161                    if not src in gcov:
162                        gcov[src] = []
163                    gcov[src].append( cov )
164                except Exception,e:
165                    print "Exception on ", e
166                    #import sys
167                    #sys.exit(0)
168                    pass
169
170    #print gcov
171    return gcov
172
173def generate_covs(candidates):
174    """
175    Generate gcov files in the right directory
176
177    candidtaes contains the directories we have used when
178    building. Each directory contains a set of files we will
179    try to generate gcov files for.
180    """
181    print candidates.keys()
182    for dir in candidates.keys():
183        print "Trying in %s" % (dir)
184        for dep in candidates[dir].keys():
185            cmd = "cd %s; gcov -p -l %s" % (dir, dep)
186            os.system("%s > /dev/null 2>&1 " % cmd)
187
188
189def analyze_coverage(sources,data,dirs,runid,base):
190    """
191    sources actual source files relative to src_dir e.g kdelibs/kdecore/klibloader.cpp
192    data    Where to put the stuff
193    dirs    Where to take a look for gcov files
194    base    The base directory for files. All files not inside base will be ignored
195    """
196    import cov
197    print base
198    gcov = collect_cov(base,dirs)
199    result = cov.analyze_coverage(gcov, sources, runid, data, base)
200    print result
201
202if __name__ == "__main__":
203    #global targets
204    if not len(sys.argv) == 3:
205        print "This script needs three parameters"
206        print "Call it with generate_cov RUNID ResultsDir"
207        sys.exit(-1)
208    runid = sys.argv[1]
209    results = sys.argv[2]
210
211    # create directories for out result
212    mkdirhier(results)
213
214    print "Collection Sources and preparing data tree"
215    base_dir = os.path.abspath(os.path.curdir)
216    depends = collect_depends(base_dir)
217    candidates = map(lambda x: parse_dependency_file(x,base_dir,[]), depends)
218
219    # Build a number of sources from the candidates. This is a Set for the poor
220    # Two level dict. One for 
221    dirs = {}
222    files = {}
223    for (_,dir,deps) in candidates:
224        if not dir in dirs:
225            dirs[dir] = {}
226        for dep in deps:
227            if not dep in dirs[dir]:
228                dirs[dir][dep] = dep
229            if not dep in files:
230                files[dep] = dep
231
232    sources = files.keys()
233
234    print "Found %d candidates" % (len(sources))
235    print "Will run inefficient generation of gcov files now"
236    generate_covs(dirs)
237
238    print "Analyzing Gcov"
239    analyze_coverage(sources, results, dirs.keys(), runid, base_dir)
240    print "Done"
241