1#!/usr/bin/env bash
2# SPDX-License-Identifier: LGPL-2.0-or-later
3# SPDX-FileCopyrightText: 2021 Marco Trevisan <marco.trevisan@canonical.com>
4set -e
5
6if [ -n "$SELFTEST" ]; then
7    unset SELFTEST
8    set -x
9    self="$(realpath "$0")"
10    test_paths=()
11    trap 'rm -rf -- "${test_paths[@]}"' EXIT
12
13    test_env() {
14        local code_path="$(mktemp -t -d "check-pch-XXXXXX")"
15        test_paths+=("$code_path")
16        cd "$code_path"
17        mkdir gjs gi
18        echo "#include <stlib.h>" >> gjs/gjs_pch.hh
19    }
20
21    expect_success() {
22        "$self" || exit 1
23    }
24    expect_failure() {
25        "$self" && exit 1 || true
26    }
27
28    test_env
29    echo "#include <stlib.h>" >> gi/code.c
30    expect_success
31
32    test_env
33    echo "#include <stlib.h>" >> gi/code.c
34    echo "#include <stdio.h>" >> gi/code.c
35    expect_failure
36
37    test_env
38    echo "#include <stlib.h>" >> gi/code.c
39    echo "#include <invalid1.h>" >> gi/code1.cpp
40    echo "#include <invalid2.h>" >> gi/code1.c
41    expect_failure
42
43    test_env
44    echo "#include <stlib.h>" >> gi/code.c
45    echo "#include <invalid.h> // check-pch: ignore" >> gi/other-code.c
46    expect_success
47
48    test_env
49    echo "#include <stlib.h>" >> gi/code.c
50    echo "#include <invalid1.h> // NOcheck-pch: ignore" >> gi/code.c
51    echo "#include <invalid2.h> // check-pch: ignoreNO" >> gi/code.c
52    echo "#include <invalid3.h> // check-pch: ignore, yes" >> gi/other-code.c
53    expect_failure
54
55    test_env
56    echo "#include <invalid.h>" >> gjs/gjs_pch.hh
57    echo "#include <stlib.h>" >> gi/code.c
58    expect_failure
59
60    test_env
61    echo "#include <invalid.h> // check-pch: ignore, yes" >> gjs/gjs_pch.hh
62    echo "#include <stlib.h>" >> gi/code.c
63    expect_success
64
65    test_env
66    echo "#include <invalid.h>" >> gi/ignored-file.hh
67    echo "#include <stlib.h>" >> gi/code.c
68    expect_success
69
70    test_env
71    echo '#  		  include  		  <stlib.h>' >> gi/code.c
72    echo '#  		  include  		  "local/header.h"' >> gi/code.c
73    expect_success
74
75    test_env
76    echo "#include <stlib.h>" >> gi/code.c
77    echo '#include "local/header.h"' >> gjs/gjs_pch.hh
78    expect_failure
79
80    test_env
81    echo "#  		  include  		  <stlib.h>" >> gi/code.c
82    echo "#  		  include  		  <other/include.h>" >> gi/code.c
83    echo "  	   #  		  include  		  <other/include.h>" >> gi/other-file.c
84    echo "# include <other/include.h>" >> gjs/gjs_pch.hh
85    expect_success
86
87    test_env
88    echo "#    include    <stlib.h>" >> gi/code.c
89    echo "#   	  include    		     <invalid.h>/*comment*/" >> gi/invalid-file.c
90    expect_failure
91
92    test_env
93    echo "#    include    <stlib.h>" >> gi/code.c
94    echo "  	   #  		  include  		  <other/include.h>" >> gi/other-file.c
95    expect_failure
96
97    test_env
98    echo "#include <stlib.h>" >> gi/code.c
99    echo "//#include <invalid.h>" >> gi/invalid-file.c
100    echo "// #include <invalid.h>" >> gi/invalid-file.c
101    echo "//#include <invalid.h>" >> gjs/gjs_pch.hh
102    expect_success
103
104    test_env
105    echo "#include <stlib.h>" >> gi/code.c
106    echo "/*comment*/#include <invalid.h>/*comment*/" >> gi/invalid-file.c
107    # This is not supported: expect_failure
108
109    test_env
110    echo "#include <stlib.h>" >> gi/code.c
111    echo "#   /*FIXME */  include  /*Why should you do it?*/  <invalid.h>" >> gi/invalid-file.c
112    # This is not supported: expect_failure
113
114    exit 0
115fi
116
117PCH_FILES=(gjs/gjs_pch.hh)
118IGNORE_COMMENT="check-pch: ignore"
119
120CODE_PATHS=(gjs gi)
121INCLUDED_FILES=(
122    \*.c
123    \*.cpp
124    \*.h
125)
126
127grep_include_lines() {
128    grep -h '^\s*#\s*include\s*[<"][^>"]\+[>"]' "$@" | uniq
129}
130
131grep_header_file() {
132    local header_file="${1//./\\.}"
133    shift
134    grep -qs "^\s*#\s*include\s*[<\"]${header_file}[>\"]" "$@"
135}
136
137# List all the included headers
138mapfile -t includes < <(grep_include_lines \
139    -r \
140    $(printf -- "--include %s\n" "${INCLUDED_FILES[@]}") \
141    "${CODE_PATHS[@]}" \
142    | grep -vw "$IGNORE_COMMENT")
143
144missing=()
145for h in "${includes[@]}"; do
146    if [[ "$h" =~ \#[[:space:]]*include[[:space:]]*\<([^\>]+)\> ]]; then
147        header_file="${BASH_REMATCH[1]}"
148        if ! grep_header_file "$header_file" "${PCH_FILES[@]}"; then
149            echo "Header <$header_file> not added to PCH file"
150            missing+=("$header_file")
151        fi
152    fi
153done
154
155if [ "${#missing[@]}" -gt 0 ]; then
156    echo
157    echo "Headers not added to the PCH file found, please add to ${PCH_FILES[*]}"
158    echo "Otherwise you can ignore them with a leading comment such as"
159    echo "  #include <${missing[0]}>  // $IGNORE_COMMENT"
160    exit 1
161fi
162
163# And now, the other way around...
164mapfile -t pch_includes < <(grep_include_lines \
165    "${PCH_FILES[@]}" \
166    | grep -vw "$IGNORE_COMMENT")
167
168unneeded=()
169for h in "${pch_includes[@]}"; do
170    if [[ "$h" =~ \#[[:space:]]*include[[:space:]]*[\<\"]([^\>\"]+)[\>\"] ]]; then
171        header_file="${BASH_REMATCH[1]}"
172        if ! grep_header_file "$header_file" -r \
173            $(printf -- "--include %s\n" "${INCLUDED_FILES[@]}") \
174            "${CODE_PATHS[@]}"; then
175            echo "Header <$header_file> included in one PCH is not used in code"
176            unneeded+=("$header_file")
177        fi
178    fi
179done
180
181if [ "${#unneeded[@]}" -gt 0 ]; then
182    echo
183    echo "Unneeded headers added to the PCH file found, remove from ${PCH_FILES[*]}"
184    echo "Otherwise you can ignore them with a leading comment such as"
185    echo "  #include <${unneeded[0]}>  // $IGNORE_COMMENT"
186    exit 1
187fi
188