1#!/bin/bash
2# -*- indent-tabs-mode:nil; -*-
3
4
5# Process doxygen log to generate sorted list of top offenders.
6#
7
8me=$(basename $0)
9DIR="$(dirname $0)"
10# Trick to get the absolute path, since doxygen prefixes errors that way
11ROOT=$(cd "$DIR/.."; pwd -P)
12
13# Known log files
14STANDARDLOGFILE=doxygen.log
15WARNINGSLOGFILE=doxygen.warnings.log
16# Default choice:  generate it
17LOG="$DIR/$WARNINGSLOGFILE"
18
19# Verbose log
20VERBLOG="$DIR/doxygen.verbose.log"
21
22
23# Options ------------------------------
24#
25
26# One line sysnopsis, continue
27function synopsis_short
28{
29    echo "Usage: $me [-beithv] [-s <log-file> | -l | -w] [-m <module>] [-f <regex>] [-F <regex>]"
30}
31
32# Two line synopsis, then exit with error
33function synopsis
34{
35    synopsis_short
36    echo "    -h  For detailed usage"
37    exit 1
38}
39
40# Full help, then exit with no error
41function usage
42{
43    synopsis_short
44    cat <<-EOF
45
46    Run doxygen to generate all errors; report error counts
47    by module and file.
48
49    -i  Skip the build, try print-introspected-doxygen anyway.
50
51    -s  Skip doxygen run; use existing <log-file>.
52    -w  Skip doxygen run; use existing warnings log doc/$WARNINGSLOGFILE
53    -l  Skip doxygen run; use the normal doxygen log doc/$STANDARDLOGFILE
54
55    -b  Omit the blacklist filter of files whose warnings we ignore
56    -e  Filter out warnings from */examples/*
57    -t  Filter out warnings from */test/*
58    -m  Only include files matching src/<module>
59    -f  Only include files matching the <regex>
60    -F  Exclude files matching the <regex>
61
62    -v  Show detailed output from each step.
63    -h  Print this usage message
64
65    The default behavior is to modify doxygen.conf temporarily to
66    report all undocumented elements, and to reduce the run time.
67    The output of this special run is kept in doc/$WARNINGSLOGFILE.
68    To further reduce the run time, the -i option also skips
69    print-introspected-doxygen, so waf doesn\'t have to compile
70    any modified files at all.
71
72    The -f, -l, and -s options skip the doxygen run altogether.
73    The first two use a specified or the standard log file;
74    the -s option uses the warnings log from a prior run.
75    Only the first of -f <log-file>, -s, or -l will have effect.
76
77    The -e and -t options exclude examples and test directories
78    from the counts.  The -m option only includes a specific module.
79    The -F option only includes files (or warnings) matching the <regex>.
80    The -m and -F options append the relevant warnings after the
81    numerical report.  These can be used in any combination.
82
83EOF
84    exit 0
85}
86
87# Messaging ----------------------------
88#
89
90# Arg -v Verbosity level
91verbosity=0
92
93function verbose
94{
95    if [ "$1" == "-n" ]; then
96        if [ $verbosity -eq 1 ]; then
97            echo "$me:  ${2}..."
98        else
99            echo -n "${2}..."
100        fi
101    elif [ $verbosity -eq 1 ]; then
102        echo "$me:  $1 $2"
103    else
104        echo "$2"
105    fi
106}
107
108# Use file handle 6 for verbose output
109rm -f $VERBLOG
110exec 6>$VERBLOG
111
112function status_report
113{
114    local status="$1"
115    local long_msg="$2"
116    local exitonerr="${3:-yes}"
117    if [ $status -eq 0 ]; then
118        [[ $verbosity -eq 1 && -e $VERBLOG ]] && cat $VERBLOG
119        verbose "$long_msg "  "done."
120        [[ -e $VERBLOG ]] && rm -f $VERBLOG
121    else
122        if [ $exitonerr == "yes" ]; then
123            verbose "$long_msg "  "FAILED.  Details:"
124            [[ -e $VERBLOG ]] && cat $VERBLOG && rm -f $VERBLOG
125            exit 1
126        else
127            verbose "$long_msg "  "FAILED, continuing"
128            [[ -e $VERBLOG ]] && cat $VERBLOG && rm -f $VERBLOG
129        fi
130    fi
131}
132
133
134# Argument processing ------------------
135#
136
137# -f argument
138use_filearg=0
139logfile_arg=
140# -l
141use_standard=0
142# skip doxygen run; using existing log file
143skip_doxy=0
144# skip print-introspected-doxygen, avoiding a build
145skip_intro=0
146
147# Filtering flags
148filter_blacklist=1
149filter_examples=0
150filter_test=0
151explicit_m_option=0
152filter_module=""
153explicit_f_option=0
154filter_in=""
155filter_out=""
156
157while getopts :bef:F:hilm:s:tvw option ; do
158
159    case $option in
160    (b)  filter_blacklist=0       ;;
161
162    (e)  filter_examples=1        ;;
163
164    (f)  filter_in="$OPTARG"
165         explicit_f_option=1
166         ;;
167
168    (F)  filter_out="$OPTARG"     ;;
169
170    (h)  usage                    ;;
171
172    (i)  skip_intro=1             ;;
173
174    (l)  use_standard=1           ;;
175
176    (m)  filter_module="$OPTARG"
177         explicit_m_option=1
178         ;;
179
180    (s)  use_filearg=1
181         logfile_arg="$OPTARG"
182         ;;
183
184    (t)  filter_test=1            ;;
185
186    (v)  verbosity=1
187         exec 6>&1
188         ;;
189
190    (w)  use_filearg=1
191         logfile_arg="$DIR/$WARNINGSLOGFILE"
192         ;;
193
194    (:)  echo "$me: Missing argument to -$OPTARG" ; synopsis ;;
195
196    (\?) echo "$me: Invalid option: -$OPTARG"     ; synopsis ;;
197
198    esac
199done
200
201function checklogfile
202{
203    if [ -e "$1" ] ; then
204        skip_doxy=1
205        LOG="$1"
206    else
207        echo "$me: log file $1 does not exist."
208        synopsis
209    fi
210}
211
212# Log file -----------------------------
213#
214
215if [[ $use_filearg -eq 1 && "${logfile_arg:-}" != "" ]] ; then
216    checklogfile "$logfile_arg"
217elif [ $use_standard -eq 1 ]; then
218    checklogfile "$DIR/$STANDARDLOGFILE"
219fi
220
221# Log filters --------------------------
222#
223
224# Append a regular expression to a parameter
225#  with '\|' alternation operator if the parameter wasn't empty to begin with.
226function REappend
227{
228    local param="$1"
229    local token="$2"
230
231    eval "${param}=\"${!param:-}${!param:+\\|}$token\""
232}
233
234# Explicit -f or -m with empty args should filter out all, not pass all
235[[ $explicit_f_option -eq 1 && "${filter_in:-}" == "" ]] && filter_out=".*"
236[[ $explicit_m_option -eq 1 && "${filter_module:-}" == "" ]] && filter_out=".*"
237
238# Filter in regular expression for -m and -f
239filter_inRE=""
240[[ "$filter_module" != "" ]] && REappend filter_inRE src/$filter_module
241[[ "$filter_in"     != "" ]] && REappend filter_inRE "$filter_in"
242
243# Blacklist filter of files whose warnings we ignore
244filter_blacklistRE=""
245
246#   External files: adding our own doxygen makes diffs with upstream very hard
247#     cairo-wideint
248REappend filter_blacklistRE "cairo-wideint"
249
250#   Functions with varying numbers of arguments
251#   This is temporary until we move to C++-14
252REappend filter_blacklistRE "Schedule(Time"
253REappend filter_blacklistRE "ScheduleWithContext(uint32_t"
254REappend filter_blacklistRE "Schedule\\(Now\\|Destroy\\)(\\(MEM\\|void\\)"
255
256#   ATTRIBUTE_HELPER_CPP( and _HEADER(
257REappend filter_blacklistRE "ATTRIBUTE_HELPER_\\(CPP\\|HEADER\\)"
258
259# Filter out regular expression for black list, -e, -t and -F
260filter_outRE=""
261[[ $filter_blacklist -eq 1 ]] && REappend filter_outRE "$filter_blacklistRE"
262[[ $filter_examples  -eq 1 ]] && REappend filter_outRE "/examples/"
263[[ $filter_test      -eq 1 ]] && REappend filter_outRE "/test/"
264[[ "$filter_out"     != "" ]] && REappend filter_outRE "$filter_out"
265
266
267# Configuration ------------------------
268#
269
270function on_off
271{
272    if [[ "${!1:-}" != "" && "${!1}" != "0" ]] ; then
273        echo "ON"
274    else
275        echo "off"
276    fi
277}
278
279if [ $verbosity -eq 1 ]; then
280    echo
281    echo "$me:"
282    echo "    Verbose:          $(on_off verbosity)"
283    echo "    Skip build:       $(on_off skip_intro)"
284    echo "    Log file to use:  $LOG"
285    echo "    Module filter:    $(on_off filter_module)  $filter_module"
286    echo "    Examples filter:  $(on_off filter_examples)"
287    echo "    Tests filter:     $(on_off filter_test)"
288    echo "    Blacklist filter: $(on_off filter_blacklist)"
289    echo "    Filter in:        $(on_off filter_in)  $filter_in"
290    echo "    Filter out:       $(on_off filter_out)  $filter_out"
291    echo
292
293    #  Show the resulting filters here, in addition to below
294    echo "    Net result of all filters:"
295    [[ "${filter_inRE:-}"  != "" ]] && echo "      Filtering in:   \"$filter_inRE\""
296    [[ "${filter_outRE:-}" != "" ]] && echo "      Filtering out:  \"$filter_outRE\""
297
298    echo
299fi
300
301
302#  Run doxygen -------------------------
303#
304
305if [ $skip_doxy -eq 1 ]; then
306    echo
307    echo "Skipping doxygen run, using existing log file $LOG"
308
309else
310
311    # Modify doxygen.conf to generate all the warnings
312    # (We also suppress dot graphs, so shorten the run time.)
313
314    conf=doc/doxygen.conf
315    cp $conf ${conf}.bak
316    cat <<-EOF >> $conf
317
318    # doxygen.warnings.report.sh:
319    EXTRACT_ALL = no
320    HAVE_DOT = no
321    CLASS_DIAGRAMS = no
322    WARNINGS = no
323    SOURCE_BROWSER = no
324    HTML_OUTPUT html-warn
325    WARN_LOGFILE = doc/$WARNINGSLOGFILE
326EOF
327
328
329    intro_h="introspected-doxygen.h"
330    if [ $skip_intro -eq 1 ]; then
331        verbose "" "Skipping ./waf build"
332        verbose -n "Trying print-introspected-doxygen with doxygen build"
333        (cd "$ROOT" && ./waf --run-no-build print-introspected-doxygen >doc/$intro_h 2>&6 )
334        status_report $? "./waf --run print-introspected-doxygen" noexit
335    else
336        # Run introspection, which may require a build
337        verbose -n "Building"
338        (cd "$ROOT" && ./waf build >&6 2>&6 )
339        status_report $? "./waf build"
340        verbose -n "Running print-introspected-doxygen with doxygen build"
341        (cd "$ROOT" && ./waf --run-no-build print-introspected-doxygen >doc/$intro_h 2>&6 )
342        status_report $? "./waf --run print-introspected-doxygen"
343    fi
344
345    # Waf insists on writing cruft to stdout
346    sed -i.bak -E '/^Waf:/d' doc/$intro_h
347    rm doc/$intro_h.bak
348
349    verbose -n "Rebuilding doxygen docs with full errors"
350    (cd "$ROOT" && ./waf --doxygen-no-build >&6 2>&6 )
351    status_report $? "./waf --doxygen-no-build"
352
353    # Swap back to original config
354    rm -f $conf
355    mv -f $conf.bak $conf
356fi
357
358# Filter log file
359function filter_log
360{
361    local flog;
362    flog=$( cat "$LOG" | grep "^$ROOT" )
363
364    [[ "${filter_inRE:-}"  != "" ]] && flog=$( echo "$flog" | grep "$filter_inRE" )
365    [[ "${filter_outRE:-}" != "" ]] && flog=$( echo "$flog" | grep -v "$filter_outRE" )
366
367    flog=$(                         \
368        echo "$flog"              | \
369        sort -t ':' -k1,1 -k2,2n  | \
370        uniq                        \
371        )
372
373    echo "$flog"
374}
375
376# Analyze the log ----------------------
377#
378#  Show the resulting filters
379echo
380echo "Net result of all filters:"
381[[ "${filter_inRE:-}"  != "" ]] && echo "Filtering in \"$filter_inRE\""
382[[ "${filter_outRE:-}" != "" ]] && echo "Filtering out \"$filter_outRE\""
383
384verbose -n "Filtering the doxygen log"
385
386# List of module directories (e.g, "src/core/model")
387undocmods=$(                \
388    filter_log            | \
389    cut -d ':' -f 1       | \
390    sed "s|$ROOT/||g"     | \
391    cut -d '/' -f 1-3     | \
392    sort                  | \
393    uniq -c               | \
394    sort -nr                \
395    )
396
397# Number of directories
398modcount=$(                         \
399    echo "$undocmods"             | \
400    wc -l                         | \
401    sed 's/^[ \t]*//;s/[ \t]*$//'   \
402    )
403
404# For a function with multiple undocumented parameters,
405# Doxygen prints the additional parameters on separate lines,
406# so they don't show up in the totals above.
407# Rather than work too hard to get the exact number for each file,
408# we just list the total here.
409addlparam=$(                                  \
410    grep "^  parameter '" "$LOG"            | \
411    wc -l                                   | \
412    sed 's/^[ \t]*//;s/[ \t]*$//'             \
413    )
414
415# Total number of warnings
416warncount=$(                                  \
417    echo "$undocmods"                       | \
418    awk '{total += $1}; END {print total}'    \
419    )
420warncount=$((warncount + addlparam))
421
422# List of files appearing in the log
423undocfiles=$(               \
424    filter_log            | \
425    cut -d ':' -f 1       | \
426    sed "s|$ROOT||g"      | \
427    cut -d '/' -f 2-      | \
428    sort                  | \
429    uniq -c               | \
430    sort -k 2               \
431    )
432
433# Sorted by number, decreasing
434undocsort=$(echo "$undocfiles" | sort -k1nr,2 )
435
436# Total number of files
437filecount=$(                        \
438    echo "$undocfiles"            | \
439    wc -l                         | \
440    sed 's/^[ \t]*//;s/[ \t]*$//'   \
441    )
442
443# Filtered in warnings
444filterin=
445if [ "${filter_inRE:-}" != "" ] ; then
446    filterin=$(              \
447        filter_log         | \
448        sed "s|$ROOT/||g"    \
449        )
450fi
451
452status_report 0 "Filter"
453echo
454
455
456# Summarize the log --------------------
457#
458
459echo
460echo "Report of Doxygen warnings"
461echo "----------------------------------------"
462echo
463echo "(All counts are lower bounds.)"
464echo
465echo "Warnings by module/directory:"
466echo
467echo "Count Directory"
468echo "----- ----------------------------------"
469echo "$undocmods"
470echo " $addlparam additional undocumented parameters."
471echo "----------------------------------------"
472printf "%6d total warnings\n" $warncount
473printf "%6d directories with warnings\n" $modcount
474echo
475echo
476echo "Warnings by file (alphabetical)"
477echo
478echo "Count File"
479echo "----- ----------------------------------"
480echo "$undocfiles"
481echo "----------------------------------------"
482printf "%6d files with warnings\n" $filecount
483echo
484echo
485echo "Warnings by file (numerical)"
486echo
487echo "Count File"
488echo "----- ----------------------------------"
489echo "$undocsort"
490echo "----------------------------------------"
491printf "%6d files with warnings\n" $filecount
492echo
493echo
494echo "Doxygen Warnings Summary"
495echo "----------------------------------------"
496printf "%6d directories\n" $modcount
497printf "%6d files\n" $filecount
498printf "%6d warnings\n" $warncount
499
500# Return status based on warnings
501exit_status=$((warncount > 0))
502
503# if [ "${filter_inRE:-}" != "" ] ; then
504#     if [ "$filterin" != "" ] ; then
505#         echo
506#         echo
507#         echo "Filtered Warnings"
508#         echo "========================================"
509#         echo "$filterin"
510#         exit_status=1
511#     else
512#         exit_status=0
513#     fi
514# fi
515
516if [ "$filterin" != "" ] ; then
517    echo
518    echo
519    echo "Filtered Warnings"
520    echo "========================================"
521    echo "$filterin"
522    exit_status=1
523else
524    exit_status=0
525fi
526
527status_report 0 $me
528
529exit $exit_status
530