diff --git a/clang/include/clang/Driver/Driver.h b/clang/include/clang/Driver/Driver.h --- a/clang/include/clang/Driver/Driver.h +++ b/clang/include/clang/Driver/Driver.h @@ -355,11 +355,13 @@ /// @name Driver Steps /// @{ - /// ParseDriverMode - Look for and handle the driver mode option in Args. + /// Look for and handle the workdir option in Args. + void ParseWorkdir(ArrayRef Args); + + /// Look for and handle the driver mode option in Args. void ParseDriverMode(StringRef ProgramName, ArrayRef Args); - /// ParseArgStrings - Parse the given list of strings into an - /// ArgList. + /// Parse the given list of strings into an ArgList. llvm::opt::InputArgList ParseArgStrings(ArrayRef Args, bool IsClCompatMode, bool &ContainsError); diff --git a/clang/include/clang/Driver/Options.td b/clang/include/clang/Driver/Options.td --- a/clang/include/clang/Driver/Options.td +++ b/clang/include/clang/Driver/Options.td @@ -2007,6 +2007,9 @@ Flags<[CC1Option]>; def ivfsoverlay : JoinedOrSeparate<["-"], "ivfsoverlay">, Group, Flags<[CC1Option]>, HelpText<"Overlay the virtual filesystem described by file over the real file system">; +def iworkdir : Joined<["-"], "iworkdir=">, Group, Flags<[CC1Option, HelpHidden]>, + HelpText<"When looking up file, use the working directory provided by the operating system, or by the virtual file system.">, + Values<"os,virtual">; def imultilib : Separate<["-"], "imultilib">, Group; def keep__private__externs : Flag<["-"], "keep_private_externs">; def l : JoinedOrSeparate<["-"], "l">, Flags<[LinkerInput, RenderJoined]>, diff --git a/clang/lib/Driver/Driver.cpp b/clang/lib/Driver/Driver.cpp --- a/clang/lib/Driver/Driver.cpp +++ b/clang/lib/Driver/Driver.cpp @@ -131,10 +131,6 @@ CCCGenericGCCName(""), Saver(Alloc), CheckInputsExist(true), GenReproducer(false), SuppressMissingInputWarning(false) { - // Provide a sane fallback if no VFS is specified. - if (!this->VFS) - this->VFS = llvm::vfs::createPhysicalFileSystem().release(); - Name = llvm::sys::path::filename(ClangExecutable); Dir = llvm::sys::path::parent_path(ClangExecutable); InstalledDir = Dir; // Provide a sensible default installed dir. @@ -150,6 +146,42 @@ ResourceDir = GetResourcesPath(ClangExecutable, CLANG_RESOURCE_DIR); } +void Driver::ParseWorkdir(ArrayRef Args) { + const std::string OptionPrefix = + getOpts().getOption(options::OPT_iworkdir).getPrefixedName(); + enum class Workdir { Unknown, OS, Virtual } Mode = Workdir::Unknown; + for (const char *ArgPtr : Args) { + if (!ArgPtr) + continue; + const StringRef Arg = ArgPtr; + if (!Arg.startswith(OptionPrefix)) + continue; + const StringRef Value = Arg.drop_front(OptionPrefix.size()); + Workdir M = llvm::StringSwitch(Value) + .Case("os", Workdir::OS) + .Case("virtual", Workdir::Virtual) + .Default(Workdir::Unknown); + if (M == Workdir::Unknown) { + Diag(diag::err_drv_unsupported_option_argument) << OptionPrefix << Value; + break; + } + Mode = M; + } + + switch (Mode) { + case Workdir::Unknown: + if (!this->VFS) + this->VFS = llvm::vfs::createPhysicalFileSystem().release(); + break; + case Workdir::Virtual: + this->VFS = llvm::vfs::createPhysicalFileSystem().release(); + break; + case Workdir::OS: + this->VFS = llvm::vfs::getRealFileSystem().get(); + break; + } +} + void Driver::ParseDriverMode(StringRef ProgramName, ArrayRef Args) { if (ClangNameParts.isEmpty()) @@ -946,6 +978,10 @@ } } + // We might try accessing the VFS fairly early, so make sure we have the + // right one. + ParseWorkdir(ArgList.slice(1)); + // We look for the driver mode option early, because the mode can affect // how other options are parsed. ParseDriverMode(ClangExecutable, ArgList.slice(1)); diff --git a/clang/test/Driver/workdir.py b/clang/test/Driver/workdir.py new file mode 100644 --- /dev/null +++ b/clang/test/Driver/workdir.py @@ -0,0 +1,49 @@ +# Check that we can change the working directory mode between 'os' and +# 'virtual', which affects the maximum path length that clang can deal with. + +# UNSUPPORTED: system-windows + +# RUN: rm -rf xxx* +# RUN: python %s > %t +# RUN: cat %t | xargs not %clang 2>&1 | FileCheck %s --check-prefix=VIRTUAL +# RUN: cat %t | xargs not %clang -iworkdir=virtual 2>&1 | FileCheck %s --check-prefix=VIRTUAL +# RUN: cat %t | xargs %clang -iworkdir=os + +# VIRTUAL: error: no such file or directory: + +import os +import subprocess + +current = os.path.realpath(os.curdir) +name_max = int(subprocess.check_output("getconf NAME_MAX %s" % current, shell=True)) +path_max = int(subprocess.check_output("getconf PATH_MAX %s" % current, shell=True)) +assert name_max > 0, 'we want to test NAME_MAX properties, knowing its value is required' +assert path_max > 0, 'we want to test PATH_MAX properties, knowing its value is required' + +filename = 'o.c' +code = 'int main() { return 0; }\n' + +relative = '' +absolute = current + +# Create directories which almost, but not quite, exhaust PATH_MAX. +target_len = path_max - 1 +while len(absolute) < target_len: + new_name_len = min(target_len - len(absolute) - 1, name_max) + absolute = os.path.join(absolute, 'x' * new_name_len) + relative = os.path.join(relative, 'x' * new_name_len) + os.mkdir(relative) + assert os.path.exists(absolute), 'creating a directory must succeed' + assert os.path.exists(relative), 'creating a directory must succeed' + +# Create a file whose relative path doesn't exceed PATH_MAX, but whose absolute +# path does. +file_relative_path = os.path.join(relative, filename) +with open(file_relative_path, 'w') as f: + f.write(code) + +assert os.path.exists(file_relative_path), 'the relative path should work because it is still smaller than PATH_MAX' +assert not os.path.exists(os.path.join(absolute, filename)), 'the absolute path should exceed PATH_MAX' + +# Print out the relative path so lit can pass it to clang. +print file_relative_path