#!/bin/sh set -eu base="${TMPDIR-/tmp}/mcpu-cpp-forced-files-$$" mkdir -p "$base/project/src" "$base/project/user" "$base/project/sys" trap 'rm -rf "$base"' EXIT HUP INT TERM cat > "$base/project/defs1.h" <<'EOT' IMACROS1_BODY_MUST_NOT_APPEAR #if CLI_VALUE != 9 #error command-line macros were not applied before -imacros #endif #if __INCLUDE_LEVEL__ != 1 #error -imacros must run at include level 1 #endif #define ORDER_VALUE 1 #define FROM_IMACROS1 11 #include "imacros-child.h" #lang "as" #define FROM_AS_LANG 13 #endlang EOT cat > "$base/project/imacros-child.h" <<'EOT' IMACROS_CHILD_BODY_MUST_NOT_APPEAR #if __INCLUDE_LEVEL__ != 2 #error nested include from -imacros must run at include level 2 #endif #define FROM_IMACROS_CHILD 12 EOT cat > "$base/project/defs2.h" <<'EOT' IMACROS2_BODY_MUST_NOT_APPEAR #if ORDER_VALUE != 1 #error -imacros command-line order is broken #endif #if FROM_IMACROS_CHILD != 12 || FROM_AS_LANG != 13 #error -imacros did not retain preprocessing state #endif #undef ORDER_VALUE #define ORDER_VALUE 2 EOT cat > "$base/project/a.h" <<'EOT' #if ORDER_VALUE != 2 #error all -imacros files must precede all -include files #endif INCLUDE_A_BODY FORCED_LEVEL __INCLUDE_LEVEL__ FORCED_BASE __BASE_FILE__ #undef ORDER_VALUE #define ORDER_VALUE 3 EOT cat > "$base/project/b.h" <<'EOT' #if ORDER_VALUE != 3 #error -include command-line order is broken #endif INCLUDE_B_BODY #undef ORDER_VALUE #define ORDER_VALUE 4 EOT cat > "$base/project/src/main.c" <<'EOT' #if ORDER_VALUE != 4 #error forced files did not complete before primary input #endif MAIN_BODY MAIN_LEVEL __INCLUDE_LEVEL__ FROM_IMACROS1 FROM_IMACROS_CHILD FROM_AS_LANG CLI_VALUE EOT # Deliberately interleave -include and -imacros. The semantic order is all # command-line -D/-U first, then all -imacros in their own argv order, then all # -include in their own argv order, and only then the primary input. ( cd "$base/project" "$MCPU_CPP" --no-config \ -include a.h -DCLI_VALUE=9 -imacros defs1.h \ -include b.h -imacros defs2.h \ src/main.c -o "$base/order.out" ) ! grep 'IMACROS1_BODY_MUST_NOT_APPEAR' "$base/order.out" >/dev/null ! grep 'IMACROS2_BODY_MUST_NOT_APPEAR' "$base/order.out" >/dev/null ! grep 'IMACROS_CHILD_BODY_MUST_NOT_APPEAR' "$base/order.out" >/dev/null ! grep '^#lang ' "$base/order.out" >/dev/null ! grep '^#endlang' "$base/order.out" >/dev/null awk ' /^INCLUDE_A_BODY$/ { a = NR } /^INCLUDE_B_BODY$/ { b = NR } /^MAIN_BODY$/ { c = NR } END { exit !(a && a < b && b < c) } ' "$base/order.out" grep '^11 12 13 9$' "$base/order.out" >/dev/null grep '^FORCED_LEVEL 1$' "$base/order.out" >/dev/null grep '^FORCED_BASE "src/main.c"$' "$base/order.out" >/dev/null grep '^MAIN_LEVEL 0$' "$base/order.out" >/dev/null # Each visible forced include returns to the primary file in the line-marker # stack before another forced include or the primary body continues. test `grep -c '^# 1 "src/main.c" 2$' "$base/order.out"` -eq 2 # A forced command-line file is searched from the current working directory, # not from the physical directory of the primary input. cat > "$base/project/cwd-first.h" <<'EOT' CWD_FORCED_HEADER EOT cat > "$base/project/src/cwd-first.h" <<'EOT' PRIMARY_DIRECTORY_MUST_NOT_WIN EOT cat > "$base/project/src/cwd-main.c" <<'EOT' CWD_MAIN EOT ( cd "$base/project" "$MCPU_CPP" --no-config -include cwd-first.h src/cwd-main.c \ -o "$base/cwd.out" ) grep '^CWD_FORCED_HEADER$' "$base/cwd.out" >/dev/null ! grep 'PRIMARY_DIRECTORY_MUST_NOT_WIN' "$base/cwd.out" >/dev/null # After CWD, the ordinary quoted include chain is used. A forced file found # through -I retains its physical provenance, so quoted includes inside it are # resolved relative to that file's own directory. cat > "$base/project/user/forced-user.h" <<'EOT' FORCED_USER_BEGIN #include "forced-child.h" FORCED_USER_END EOT cat > "$base/project/user/forced-child.h" <<'EOT' FORCED_USER_CHILD EOT ( cd "$base/project" "$MCPU_CPP" --no-config -I user -include forced-user.h src/cwd-main.c \ -o "$base/user.out" ) grep '^FORCED_USER_BEGIN$' "$base/user.out" >/dev/null grep '^FORCED_USER_CHILD$' "$base/user.out" >/dev/null grep '^FORCED_USER_END$' "$base/user.out" >/dev/null # Search-chain provenance of a forced file is retained for #include_next. cat > "$base/project/user/wrapped.h" <<'EOT' FORCED_WRAPPER #include_next EOT cat > "$base/project/sys/wrapped.h" <<'EOT' FORCED_INCLUDE_NEXT EOT ( cd "$base/project" "$MCPU_CPP" --no-config -I user -isystem sys -include wrapped.h \ src/cwd-main.c -o "$base/next.out" ) grep '^FORCED_WRAPPER$' "$base/next.out" >/dev/null grep '^FORCED_INCLUDE_NEXT$' "$base/next.out" >/dev/null # -dD does not make normal output from -imacros visible; macro state still # reaches the primary input. cat > "$base/project/only-macros.h" <<'EOT' IMACROS_DD_BODY_MUST_NOT_APPEAR #define DD_VALUE 77 EOT cat > "$base/project/src/dd-main.c" <<'EOT' DD_VALUE EOT ( cd "$base/project" "$MCPU_CPP" --no-config -dD -imacros only-macros.h src/dd-main.c \ -o "$base/dd.out" ) ! grep 'IMACROS_DD_BODY_MUST_NOT_APPEAR' "$base/dd.out" >/dev/null ! grep '^#define DD_VALUE 77$' "$base/dd.out" >/dev/null grep '^77$' "$base/dd.out" >/dev/null # Forced files participate in physical dependency tracking. The primary input # remains the first dependency even though forced files are preprocessed first. ( cd "$base/project" "$MCPU_CPP" --no-config -M -DCLI_VALUE=9 \ -imacros defs1.h -imacros defs2.h -include a.h -include b.h src/main.c \ > "$base/deps.out" ) grep '^main\.o:' "$base/deps.out" >/dev/null first_dep=`sed 's/^[^:]*: //' "$base/deps.out" | awk '{print $1}'` test "$first_dep" = 'src/main.c' || { echo 'primary input is not the first forced-file dependency' >&2 exit 1 } for f in defs1.h imacros-child.h defs2.h a.h b.h; do grep -E "(^|[[:space:]])(\./)?$f([[:space:]]|$)" "$base/deps.out" >/dev/null || { echo "forced dependency missing: $f" >&2 exit 1 } done # A forced file found through -isystem has system dependency class and is # therefore omitted by -MM while the primary user source remains. cat > "$base/project/sys/system-forced.h" <<'EOT' SYSTEM_FORCED_BODY EOT ( cd "$base/project" "$MCPU_CPP" --no-config -MM -isystem sys -include system-forced.h \ src/cwd-main.c > "$base/mm.out" ) grep -E '(^|[[:space:]])src/cwd-main\.c([[:space:]]|$)' "$base/mm.out" >/dev/null ! grep -F "$base/project/sys/system-forced.h" "$base/mm.out" >/dev/null # Public help advertises both implemented forced-file options. "$MCPU_CPP" --help > "$base/help" grep '^ -imacros FILE ' "$base/help" >/dev/null grep '^ -include FILE ' "$base/help" >/dev/null # Both option families require an argument. for opt in -include -imacros; do if "$MCPU_CPP" --no-config "$opt" > /dev/null 2>"$base/missing-arg.err"; then echo "$opt accepted a missing argument" >&2 exit 1 fi grep -- "argument missing after '$opt'" "$base/missing-arg.err" >/dev/null done # Missing forced files are preprocessing errors for both option families. for opt in -include -imacros; do if ( cd "$base/project" "$MCPU_CPP" --no-config "$opt" no-such-forced-file.h src/cwd-main.c \ -o "$base/missing.out" 2>"$base/missing.err" ); then echo "$opt accepted a missing forced file" >&2 exit 1 fi grep -- "$opt cannot find file 'no-such-forced-file.h'" "$base/missing.err" >/dev/null done # The same forced-file pipeline also applies when the primary source is stdin. printf 'DD_VALUE\n' | ( cd "$base/project" "$MCPU_CPP" --no-config -imacros only-macros.h - ) > "$base/stdin.out" grep '^77$' "$base/stdin.out" >/dev/null