#!/bin/sh set -eu base="${TMPDIR-/tmp}/mcpu-cpp-missing-generated-deps-$$" mkdir -p "$base/user" "$base/sys" trap 'rm -rf "$base"' EXIT HUP INT TERM cd "$base" cat > main.c <<'EOT' #include "generated-user.h" #include #define GENERATED_MACRO_HEADER "generated-macro.h" #include GENERATED_MACRO_HEADER EOT # Without -MG, the first unresolved include remains a hard error. if "$MCPU_CPP" --no-config -M main.c > no-mg.out 2> no-mg.err; then echo "missing include unexpectedly succeeded without -MG" >&2 exit 1 fi grep "cannot find include file 'generated-user.h'" no-mg.err >/dev/null # -M -MG records unresolved operands exactly as written and does not diagnose # them as missing files. Macro-expanded include operands use their expanded # filename. "$MCPU_CPP" --no-config -M -MG main.c > M.d 2> M.err test ! -s M.err grep '^main\.o:' M.d >/dev/null for f in main.c generated-user.h generated-system.h generated-macro.h do grep -F "$f" M.d >/dev/null || { echo "-M -MG omitted unresolved dependency: $f" >&2 exit 1 } done # -MM retains unresolved quoted/user includes but filters unresolved angle # includes as system-class dependencies. "$MCPU_CPP" --no-config -MM -MG main.c > MM.d grep -F 'generated-user.h' MM.d >/dev/null grep -F 'generated-macro.h' MM.d >/dev/null if grep -F 'generated-system.h' MM.d >/dev/null; then echo "-MM -MG kept unresolved angle/system dependency" >&2 exit 1 fi # Missing dependencies reached from a real system header inherit system # context, even when the nested directive uses quotes. User-header quoted # descendants remain user dependencies. cat > sys/system-parent.h <<'EOT' #include "generated-from-system.h" EOT cat > user/user-parent.h <<'EOT' #include "generated-from-user.h" EOT cat > nested.c <<'EOT' #include #include EOT "$MCPU_CPP" --no-config -MM -MG -I user -isystem sys nested.c > nested-MM.d grep -F 'user/user-parent.h' nested-MM.d >/dev/null grep -F 'generated-from-user.h' nested-MM.d >/dev/null if grep -F 'sys/system-parent.h' nested-MM.d >/dev/null || grep -F 'generated-from-system.h' nested-MM.d >/dev/null; then echo "-MM -MG kept system-context dependency" >&2 exit 1 fi "$MCPU_CPP" --no-config -M -MG -I user -isystem sys nested.c > nested-M.d grep -F 'sys/system-parent.h' nested-M.d >/dev/null grep -F 'generated-from-system.h' nested-M.d >/dev/null # Unresolved dependency identity is textual and separate from physical # st_dev/st_ino identity. shadow.h exists in CWD, but is not found # through the angle search chain under --no-config; a later quoted ./shadow.h # resolves that physical file. Both registry entries must survive under -M. cat > shadow.h <<'EOT' #define SHADOW_VALUE 1 EOT cat > identity.c <<'EOT' #include #include "./shadow.h" EOT "$MCPU_CPP" --no-config -M -MG identity.c > identity-M.d grep '^identity\.o: identity\.c shadow\.h \./\./shadow\.h$' identity-M.d >/dev/null || { echo "unresolved and physical dependency identities were incorrectly merged" >&2 cat identity-M.d >&2 exit 1 } # In -MM the unresolved angle entry is filtered, while the resolved quoted # physical dependency remains. "$MCPU_CPP" --no-config -MM -MG identity.c > identity-MM.d grep -F '././shadow.h' identity-MM.d >/dev/null case "$(cat identity-MM.d)" in *' shadow.h '*) echo "-MM kept the unresolved angle dependency" >&2 exit 1 ;; esac # Repeated unresolved operands are deduplicated textually. Their first # classification is retained, matching GNU CPP: angle-first stays system-only, # while quote-first stays visible to -MM. cat > first-system.c <<'EOT' #include #include "same-missing.h" EOT "$MCPU_CPP" --no-config -MM -MG first-system.c > first-system.d if grep -F 'same-missing.h' first-system.d >/dev/null; then echo "angle-first unresolved dependency lost its first classification" >&2 exit 1 fi cat > first-user.c <<'EOT' #include "same-missing.h" #include EOT "$MCPU_CPP" --no-config -MM -MG first-user.c > first-user.d grep -F 'same-missing.h' first-user.d >/dev/null # Different unresolved spellings are distinct because no physical identity is # available to canonicalize them. cat > spelling.c <<'EOT' #include "generated.h" #include "./generated.h" EOT "$MCPU_CPP" --no-config -M -MG spelling.c > spelling.d grep -F ' generated.h' spelling.d >/dev/null grep -F ' ./generated.h' spelling.d >/dev/null # -MG also applies to missing command-line forced files. They enter the same # unresolved registry as user dependencies and preserve the command-line # operand; no synthetic path is prepended. : > forced.c "$MCPU_CPP" --no-config -M -MG \ -imacros missing-macros.h -include missing-include.h forced.c > forced-M.d grep -F 'missing-macros.h' forced-M.d >/dev/null grep -F 'missing-include.h' forced-M.d >/dev/null "$MCPU_CPP" --no-config -MM -MG \ -imacros missing-macros.h -include missing-include.h forced.c > forced-MM.d grep -F 'missing-macros.h' forced-MM.d >/dev/null grep -F 'missing-include.h' forced-MM.d >/dev/null # -MG is dependency-only: GNU permits it with -M/-MM, not side-effect -MD/-MMD # and not without dependency generation. for opts in '-MG' '-MD -MG' '-MMD -MG' do if "$MCPU_CPP" --no-config $opts forced.c > invalid.out 2> invalid.err; then echo "$opts unexpectedly accepted" >&2 exit 1 fi grep -- '-MG may only be used with -M or -MM' invalid.err >/dev/null done # Explicit dependency targets continue to compose with -MG. "$MCPU_CPP" --no-config -M -MG -MT generated-target.o main.c > target.d grep '^generated-target\.o:' target.d >/dev/null grep -F 'generated-user.h' target.d >/dev/null # Public help exposes the implemented option. "$MCPU_CPP" --help > help.out grep '^ -MG treat missing headers as generated dependencies$' help.out >/dev/null