Skip to content

Commit 1c9813e

Browse files
committed
[Clang] Support for MSVC compatible header search path ordering.
Clang has historically matched GCC's behavior for header search path order and pruning of duplicate paths. That traditional behavior is to order user search paths before system search paths, to ignore user search paths that duplicate a (later) system search path, and to ignore search paths that duplicate an earlier search path of the same user/system kind. This differs from MSVC and can result in inconsistent header file resolution for `#include` directives. MSVC orders header search paths as follows: 1) Paths specified by the `/I` and `/external:I` options are processed in the order that they appear. Paths specified by `/I` that duplicate a path specified by `/external:I` are ignored regardless of the order of the options. Paths specified by `/I` that duplicate a path from a prior `/I` option are ignored. Paths specified by `/external:I` that duplicate a path from a later `/external:I` option are ignored. 2) Paths specified by `/external:env` are processed in the order that they appear. Paths that duplicate a path from a `/I` or `/external:I` option are ignored regardless of the order of the options. Paths that duplicate a path from a prior `/external:env` option or an earlier path from the same `/external:env` option are ignored. 3) Paths specified by the `INCLUDE` environment variable are processed in the order they appear. Paths that duplicate a path from a `/I`, `/external:I`, or `/external:env` option are ignored. Paths that duplicate an earlier path in the `INCLUDE` environment variable are ignored. 4) Paths specified by the `EXTERNAL_INCLUDE` environment variable are processed in the order they appear. Paths that duplicate a path from a `/I`, `/external:I`, or `/external:env` option are ignored. Paths that duplicate a path from the `INCLUDE` environment variable are ignored. Paths that duplicate an earlier path in the `EXTERNAL_INCLUDE environment variable are ignored. Prior to this change, Clang handled the MSVC `/external:I` and `/external:env` options and the paths present in the `INCLUDE` and `EXTERNAL_INCLUDE` environment variables as though they were specified with the `-isystem` option. The GCC behavior described above then lead to a command line such as `/external:I dir1 /Idir2` having a header search order of `dir2` followed by `dir1`; contrary to MSVC behavior. Paths specified by the MSVC `/external:I` or `/external:env` options or by the `EXTERNAL_INCLUDE` environment variable are not just used to nominate a header search path. MSVC also uses these paths as external directory prefixes to mark paths that are to be treated as system directories. When a header file is resolved to a path for which one of these paths is a prefix match, that header file is treated as a system header regardless of whether the header file was resolved against a `/I` specified path. Note that it is not necessary for the final path component of the external path to match a directory in the filesystem in order to be used as an external directory prefix, though trailing path separators are significant. For example: Include directive Command line System header ------------------------ ------------------ ------------- #include <foobar/file.h> /I. /external:Ifoo Yes #include <foobar/file.h> /I. /external:Ifoo\ No This change adds support for the MSVC external path concept through the addition of new driver options with the following option syntax. clang clang-cl ------------------------ ------------------- -iexternal <dir> /external:I <dir> -iexternal-env=<ENV> /external:env:<ENV> Paths specified by these options are treated as system paths. That is, whether warnings are issued in header files found via these paths remains subject to use of the `-Wsystem-headers` and `-Wno-system-headers` options. Note that the MSVC `/external:W<N>` options are mapped to these options. The MSVC behavior described above implies that (system) paths present in the `INCLUDE` and `EXTERNAL_INCLUDE` environment variables do not suppress matching user paths specified via `/I`. This contrasts with GCC's behavior, and Clang's historical behavior, of suppressing user paths that match a system path regardless of how each is specified. In order to support both behaviors, the following driver option has been added. The option arguments shown reflect the default behavior for each driver. clang clang-cl ------------------------ ------------------------- -fheader-search=gcc -fheader-search=microsoft Use of the MSVC compatible header search path order by default for `clang-cl` is a change in behavior with potential to cause problems for some projects that build with `clang-cl`. Potentially impacted projects include those that specify a header search path via either the `/I` option or in the `CPATH` environment variable, but rely on the path being suppressed and/or treated as a system header due to a duplicate path specified by another MSVC option or environment variable or by the `-imsvc` option or one of the `-isystem` family of options. Such projects can pass the `-fheader-search=gcc` option in their `clang-cl` invocations to (mostly) restore previous behavior. Clang emulates the MSVC behavior of resolving quoted header file inclusions (e.g., `#include "file.h"`) by searching for a matching file in the directories for the including files in the include stack. See Microsoft's documentation of this feature in the Microsoft-specific section of https://learn.microsoft.com/en-us/cpp/preprocessor/hash-include-directive-c-cpp. Previously, this behavior was implicitly enabled when the `-fms-compatibility` option is specified (implicitly for Windows targets). This change ties this behavior to the `-fheader-search=microsoft` option instead. Projects that use the `clang` (not `clang-cl`) driver to build code targeting Windows that depend on this local header file lookup may be impacted by this change. Such projects can try adding `-fheader-search=microsoft` to their `clang` invocations to restore the prior behavior. There is at least one behavior that MSVC exhibits that is not currently replicated with these changes. Clang treats paths specified in the `INCLUDE` environment variable as system directories; MSVC does not. Clang will therefore suppress warnings in header files found via these paths by default while MSVC will not. However, recent MSVC releases set the `EXTERNAL_INCLUDE` environment variable to have the same paths as `INCLUDE` by default, so, for recent releases, MSVC appears to suppress warnings for header files found via `INCLUDE` by default when it does not. Note that many Microsoft provided header files use `#pragma` directives to suppress warnings.
1 parent 1fa02b9 commit 1c9813e

24 files changed

+1474
-175
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1110,6 +1110,49 @@ Windows Support
11101110
When `-fms-compatibility-version=18.00` or prior is set on the command line this Microsoft extension is still
11111111
allowed as VS2013 and prior allow it.
11121112

1113+
- Clang now matches MSVC behavior regarding the handling of duplicate header
1114+
search paths when run via the ``clang-cl`` driver (by default) or with the
1115+
``-fheader-search=microsoft`` option otherwise. Historically, Clang has
1116+
mimicked GCC behavior in which user search paths are ordered before system
1117+
search paths, user search paths that duplicate a (later) system search
1118+
path are ignored, and search paths that duplicate an earlier search path of
1119+
the same user/system kind are ignored. This ordering is not compatible with
1120+
the ordering that MSVC uses when paths are duplicated across ``/I`` options
1121+
and the ``INCLUDE`` environment variable.
1122+
1123+
The order that MSVC uses and that Clang now replicates when the
1124+
``-fheader-search=microsoft`` option is enabled follows.
1125+
1126+
- Paths specified by the ``/I`` and ``/external:I`` options are processed in
1127+
the order that they appear. Paths specified by ``/I`` that duplicate a path
1128+
specified by ``/external:I`` are ignored regardless of the order of the
1129+
options. Paths specified by ``/I`` that duplicate a path from a prior ``/I``
1130+
option are ignored. Paths specified by ``/external:I`` that duplicate a
1131+
path from a later ``/external:I`` option are ignored.
1132+
1133+
- Paths specified by ``/external:env`` are processed in the order that they
1134+
appear. Paths that duplicate a path from a ``/I`` or ``/external:I`` option
1135+
are ignored regardless of the order of the options. Paths that duplicate a
1136+
path from a prior ``/external:env`` option or an earlier path from the same
1137+
``/external:env`` option are ignored.
1138+
1139+
- Paths specified by the ``INCLUDE`` environment variable are processed in
1140+
the order they appear. Paths that duplicate a path from a ``/I``,
1141+
``/external:I``, or ``/external:env`` option are ignored. Paths that
1142+
duplicate an earlier path in the ``INCLUDE`` environment variable are
1143+
ignored.
1144+
1145+
- Paths specified by the ``EXTERNAL_INCLUDE`` environment variable are
1146+
processed in the order they appear. Paths that duplicate a path from a
1147+
``/I``, ``/external:I``, or ``/external:env`` option are ignored. Paths that
1148+
duplicate a path from the ``INCLUDE`` environment variable are ignored.
1149+
Paths that duplicate an earlier path in the ``EXTERNAL_INCLUDE``
1150+
environment variable are ignored.
1151+
1152+
The ``-fheader-search=gcc`` option can be used to opt in to GCC duplicate
1153+
header search path handling (which remains the default behavior for the GCC
1154+
compatible drivers).
1155+
11131156
LoongArch Support
11141157
^^^^^^^^^^^^^^^^^
11151158

clang/docs/UsersManual.rst

Lines changed: 37 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5196,12 +5196,26 @@ follows:
51965196

51975197
2. Consult the environment.
51985198

5199-
TODO: This is not yet implemented.
5199+
- `/external:env:[VARIABLE]`
52005200

5201-
This will consult the environment variables:
5201+
This command line option specifies a user identified environment variable
5202+
which is treated as a path delimited (`;`) list of system header paths.
52025203

5203-
- `WindowsSdkDir`
5204-
- `UCRTVersion`
5204+
- `INCLUDE`
5205+
5206+
This environment variable is treated as a path delimited (`;`) list of
5207+
system header paths.
5208+
5209+
- `EXTERNAL_INCLUDE`
5210+
5211+
This environment variable is treated as a path delimited (`;`) list of
5212+
system header paths.
5213+
5214+
The following environment variables will be consulted and used to form paths
5215+
to validate and load content from as appropriate:
5216+
5217+
- `WindowsSdkDir`
5218+
- `UCRTVersion`
52055219

52065220
3. Fallback to the registry.
52075221

@@ -5213,6 +5227,8 @@ The Visual C++ Toolset has a slightly more elaborate mechanism for detection.
52135227

52145228
1. Consult the command line.
52155229

5230+
Anything the user specifies is always given precedence.
5231+
52165232
- `/winsysroot:`
52175233

52185234
The `/winsysroot:` is used as an equivalent to `-sysroot` on Unix
@@ -5230,21 +5246,30 @@ The Visual C++ Toolset has a slightly more elaborate mechanism for detection.
52305246

52315247
2. Consult the environment.
52325248

5233-
- `/external:[VARIABLE]`
5249+
- `/external:env:[VARIABLE]`
5250+
5251+
This command line option specifies a user identified environment variable
5252+
which is treated as a path delimited (`;`) list of external header search
5253+
paths. Additionally, any header search path provided by other means that
5254+
has a prefix that matches one of these paths is treated as an external
5255+
header search path.
5256+
5257+
- `INCLUDE`
52345258

5235-
This specifies a user identified environment variable which is treated as
5236-
a path delimiter (`;`) separated list of paths to map into `-imsvc`
5237-
arguments which are treated as `-isystem`.
5259+
This environment variable is treated as a path delimited (`;`) list of
5260+
header search paths.
52385261

5239-
- `INCLUDE` and `EXTERNAL_INCLUDE`
5262+
- `EXTERNAL_INCLUDE`
52405263

5241-
The path delimiter (`;`) separated list of paths will be mapped to
5242-
`-imsvc` arguments which are treated as `-isystem`.
5264+
This environment variable is treated as a path delimited (`;`) list of
5265+
external header search paths. Additionally, any header search path
5266+
provided by other means that has a prefix that matches one of these paths
5267+
is treated as an external header search path.
52435268

52445269
- `LIB` (indirectly)
52455270

52465271
The linker `link.exe` or `lld-link.exe` will honour the environment
5247-
variable `LIB` which is a path delimiter (`;`) set of paths to consult for
5272+
variable `LIB` which is a path delimited (`;`) set of paths to consult for
52485273
the import libraries to use when linking the final target.
52495274

52505275
The following environment variables will be consulted and used to form paths

clang/include/clang/Driver/Options.td

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4622,6 +4622,15 @@ def iapinotes_modules : JoinedOrSeparate<["-"], "iapinotes-modules">, Group<clan
46224622
def idirafter : JoinedOrSeparate<["-"], "idirafter">, Group<clang_i_Group>,
46234623
Visibility<[ClangOption, CC1Option]>,
46244624
HelpText<"Add directory to AFTER include search path">;
4625+
def iexternal : Separate<["-"], "iexternal">, Group<clang_i_Group>,
4626+
Visibility<[ClangOption, CC1Option]>,
4627+
HelpText<"Add directory to include search path with warnings suppressed">, MetaVarName<"<dir>">;
4628+
def iexternal_system : Separate<["-"], "iexternal-system">, Group<clang_i_Group>,
4629+
Visibility<[CC1Option]>,
4630+
HelpText<"Add directory to include search path with warnings suppressed">, MetaVarName<"<dir>">;
4631+
def iexternal_env_EQ : Joined<["-"], "iexternal-env=">, Group<clang_i_Group>,
4632+
Visibility<[ClangOption]>,
4633+
HelpText<"Add dirs in env var <var> to include search path with warnings suppressed">, MetaVarName<"<var>">;
46254634
def iframework : JoinedOrSeparate<["-"], "iframework">, Group<clang_i_Group>,
46264635
Visibility<[ClangOption, CC1Option]>,
46274636
HelpText<"Add directory to SYSTEM framework search path">;
@@ -4664,6 +4673,10 @@ def isysroot : JoinedOrSeparate<["-"], "isysroot">, Group<clang_i_Group>,
46644673
def isystem : JoinedOrSeparate<["-"], "isystem">, Group<clang_i_Group>,
46654674
Visibility<[ClangOption, CC1Option]>,
46664675
HelpText<"Add directory to SYSTEM include search path">, MetaVarName<"<directory>">;
4676+
def isystem_env_EQ : Joined<["-"], "isystem-env=">, Group<clang_i_Group>,
4677+
MetaVarName<"<var>">,
4678+
Visibility<[ClangOption]>,
4679+
HelpText<"Add directoires in env var <var> to SYSTEM include search path">;
46674680
def isystem_after : JoinedOrSeparate<["-"], "isystem-after">,
46684681
Group<clang_i_Group>, Flags<[NoXarchOption]>, MetaVarName<"<directory>">,
46694682
HelpText<"Add directory to end of the SYSTEM include search path">;
@@ -8281,6 +8294,17 @@ def fexperimental_max_bitint_width_EQ:
82818294
// Header Search Options
82828295
//===----------------------------------------------------------------------===//
82838296

8297+
let Visibility = [ClangOption, CC1Option, CLOption] in {
8298+
8299+
def fheader_search : Joined<["-"], "fheader-search=">, Group<clang_i_Group>,
8300+
HelpText<"Specify the method used to resolve included header files">,
8301+
Values<"gcc,microsoft">,
8302+
NormalizedValuesScope<"clang::HeaderSearchMode">,
8303+
NormalizedValues<["GCC", "Microsoft"]>,
8304+
MarshallingInfoEnum<HeaderSearchOpts<"Mode">, "GCC">;
8305+
8306+
} // let Visibility = [ClangOption, CC1Option, CLOption]
8307+
82848308
let Visibility = [CC1Option] in {
82858309

82868310
def nostdsysteminc : Flag<["-"], "nostdsysteminc">,
@@ -8309,6 +8333,12 @@ def internal_isystem : Separate<["-"], "internal-isystem">,
83098333
HelpText<"Add directory to the internal system include search path; these "
83108334
"are assumed to not be user-provided and are used to model system "
83118335
"and standard headers' paths.">;
8336+
def internal_iexternal_system : Separate<["-"], "internal-iexternal-system">,
8337+
MetaVarName<"<directory>">,
8338+
HelpText<"Add directory to the internal system include search path with "
8339+
"external directory prefix semantics; these are assumed to not be "
8340+
"user-provided and are used to model system and standard headers' "
8341+
"paths.">;
83128342
def internal_externc_isystem : Separate<["-"], "internal-externc-isystem">,
83138343
MetaVarName<"<directory>">,
83148344
HelpText<"Add directory to the internal system include search path with "
@@ -8552,9 +8582,12 @@ def _SLASH_diagnostics_classic : CLFlag<"diagnostics:classic">,
85528582
def _SLASH_D : CLJoinedOrSeparate<"D", [CLOption, DXCOption]>,
85538583
HelpText<"Define macro">, MetaVarName<"<macro[=value]>">, Alias<D>;
85548584
def _SLASH_E : CLFlag<"E">, HelpText<"Preprocess to stdout">, Alias<E>;
8555-
def _SLASH_external_COLON_I : CLJoinedOrSeparate<"external:I">, Alias<isystem>,
8585+
def _SLASH_external_COLON_I : CLJoinedOrSeparate<"external:I">, Alias<iexternal>,
85568586
HelpText<"Add directory to include search path with warnings suppressed">,
85578587
MetaVarName<"<dir>">;
8588+
def _SLASH_external_env : CLJoined<"external:env:">, Alias<iexternal_env_EQ>,
8589+
HelpText<"Add dirs in env var <var> to include search path with warnings suppressed">,
8590+
MetaVarName<"<var>">;
85588591
def _SLASH_fp_contract : CLFlag<"fp:contract">, HelpText<"">, Alias<ffp_contract>, AliasArgs<["on"]>;
85598592
def _SLASH_fp_except : CLFlag<"fp:except">, HelpText<"">, Alias<ffp_exception_behavior_EQ>, AliasArgs<["strict"]>;
85608593
def _SLASH_fp_except_ : CLFlag<"fp:except-">, HelpText<"">, Alias<ffp_exception_behavior_EQ>, AliasArgs<["ignore"]>;
@@ -8790,9 +8823,6 @@ def _SLASH_volatile_Group : OptionGroup<"</volatile group>">,
87908823
def _SLASH_EH : CLJoined<"EH">, HelpText<"Set exception handling model">;
87918824
def _SLASH_EP : CLFlag<"EP">,
87928825
HelpText<"Disable linemarker output and preprocess to stdout">;
8793-
def _SLASH_external_env : CLJoined<"external:env:">,
8794-
HelpText<"Add dirs in env var <var> to include search path with warnings suppressed">,
8795-
MetaVarName<"<var>">;
87968826
def _SLASH_FA : CLJoined<"FA">,
87978827
HelpText<"Output assembly code file during compilation">;
87988828
def _SLASH_Fa : CLJoined<"Fa">,

clang/include/clang/Driver/ToolChain.h

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -224,11 +224,12 @@ class ToolChain {
224224
/// \return The subdirectory path if it exists.
225225
std::optional<std::string> getTargetSubDirPath(StringRef BaseDir) const;
226226

227+
public:
227228
/// \name Utilities for implementing subclasses.
228229
///@{
229230
static void addSystemInclude(const llvm::opt::ArgList &DriverArgs,
230231
llvm::opt::ArgStringList &CC1Args,
231-
const Twine &Path);
232+
const Twine &Path, bool Internal = true);
232233
static void addExternCSystemInclude(const llvm::opt::ArgList &DriverArgs,
233234
llvm::opt::ArgStringList &CC1Args,
234235
const Twine &Path);
@@ -238,13 +239,24 @@ class ToolChain {
238239
const Twine &Path);
239240
static void addSystemIncludes(const llvm::opt::ArgList &DriverArgs,
240241
llvm::opt::ArgStringList &CC1Args,
241-
ArrayRef<StringRef> Paths);
242+
ArrayRef<StringRef> Paths,
243+
bool Internal = true);
244+
static bool addSystemIncludesFromEnv(const llvm::opt::ArgList &DriverArgs,
245+
llvm::opt::ArgStringList &CC1Args,
246+
StringRef Var, bool Internal = true);
247+
static void addExternalSystemIncludes(const llvm::opt::ArgList &DriverArgs,
248+
llvm::opt::ArgStringList &CC1Args,
249+
ArrayRef<StringRef> Paths,
250+
bool Internal = true);
251+
static bool
252+
addExternalSystemIncludesFromEnv(const llvm::opt::ArgList &DriverArgs,
253+
llvm::opt::ArgStringList &CC1Args,
254+
StringRef Var, bool Internal = true);
242255

243256
static std::string concat(StringRef Path, const Twine &A, const Twine &B = "",
244257
const Twine &C = "", const Twine &D = "");
245258
///@}
246259

247-
public:
248260
virtual ~ToolChain();
249261

250262
// Accessors

clang/include/clang/Lex/HeaderSearch.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,14 @@ class HeaderSearch {
276276
/// a system header.
277277
std::vector<std::pair<std::string, bool>> SystemHeaderPrefixes;
278278

279+
/// External directories are user specified directories that are to be treated
280+
/// like system directories for the purposes of warning suppression. A header
281+
/// file that has a path that matches one of these prefixes is promoted to a
282+
/// system header regardless of which header search path was used to resolve
283+
/// the \#include directive. These paths are normalized using
284+
/// llvm::sys::fs::real_path().
285+
llvm::StringSet<llvm::BumpPtrAllocator> ExternalDirectoryPrefixes;
286+
279287
/// The hash used for module cache paths.
280288
std::string ModuleHash;
281289

@@ -387,6 +395,9 @@ class HeaderSearch {
387395
SearchDirsUsage.push_back(false);
388396
}
389397

398+
/// Add an additional external directory prefix path.
399+
bool AddExternalDirectoryPrefix(StringRef Path);
400+
390401
/// Set the list of system header prefixes.
391402
void SetSystemHeaderPrefixes(ArrayRef<std::pair<std::string, bool>> P) {
392403
SystemHeaderPrefixes.assign(P.begin(), P.end());

clang/include/clang/Lex/HeaderSearchOptions.h

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,19 @@ enum IncludeDirGroup {
3535
/// Paths for '\#include <>' added by '-I'.
3636
Angled,
3737

38+
/// Like Angled, but marks the directory as an external directory prefix.
39+
/// This group is intended to match the semantics of the MSVC /external:I
40+
/// option.
41+
External,
42+
3843
/// Like Angled, but marks system directories.
3944
System,
4045

46+
/// Like System, but marks the directory as an external directory prefix.
47+
/// This group is intended to match the semantics of the MSVC
48+
/// /external:env option.
49+
ExternalSystem,
50+
4151
/// Like System, but headers are implicitly wrapped in extern "C".
4252
ExternCSystem,
4353

@@ -59,6 +69,11 @@ enum IncludeDirGroup {
5969

6070
} // namespace frontend
6171

72+
/// HeaderSearchMode - The method used to resolve included headers to files.
73+
/// This controls the order in which include paths are searched and how
74+
/// duplicate search paths are handled.
75+
enum class HeaderSearchMode { GCC, Microsoft };
76+
6277
/// HeaderSearchOptions - Helper class for storing options related to the
6378
/// initialization of the HeaderSearch object.
6479
class HeaderSearchOptions {
@@ -93,6 +108,9 @@ class HeaderSearchOptions {
93108
: Prefix(Prefix), IsSystemHeader(IsSystemHeader) {}
94109
};
95110

111+
/// The header search mode to use.
112+
HeaderSearchMode Mode = HeaderSearchMode::GCC;
113+
96114
/// If non-empty, the directory to use as a "virtual system root" for include
97115
/// paths.
98116
std::string Sysroot;

clang/lib/Driver/Driver.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1479,7 +1479,10 @@ Compilation *Driver::BuildCompilation(ArrayRef<const char *> ArgList) {
14791479
if (VFS->setCurrentWorkingDirectory(WD->getValue()))
14801480
Diag(diag::err_drv_unable_to_set_working_directory) << WD->getValue();
14811481

1482-
// Check for missing include directories.
1482+
// Check for missing include directories. Diagnostics should not be issued
1483+
// for directories specified with -iexternal, -iexternal-env=, or
1484+
// -iexternal-system since those options may be used to specify external
1485+
// directory prefixes that don't necessarily match an existing path.
14831486
if (!Diags.isIgnored(diag::warn_missing_include_dirs, SourceLocation())) {
14841487
for (auto IncludeDir : Args.getAllArgValues(options::OPT_I_Group)) {
14851488
if (!VFS->exists(IncludeDir))

0 commit comments

Comments
 (0)