| //===--- ModuleAssistant.cpp - Module map generation manager --*- C++ -*---===// |
| // |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| // |
| //===----------------------------------------------------------------------===// |
| // |
| // This file defines the module generation entry point function, |
| // createModuleMap, a Module class for representing a module, |
| // and various implementation functions for doing the underlying |
| // work, described below. |
| // |
| // The "Module" class represents a module, with members for storing the module |
| // name, associated header file names, and sub-modules, and an "output" |
| // function that recursively writes the module definitions. |
| // |
| // The "createModuleMap" function implements the top-level logic of the |
| // assistant mode. It calls a loadModuleDescriptions function to walk |
| // the header list passed to it and creates a tree of Module objects |
| // representing the module hierarchy, represented by a "Module" object, |
| // the "RootModule". This root module may or may not represent an actual |
| // module in the module map, depending on the "--root-module" option passed |
| // to modularize. It then calls a writeModuleMap function to set up the |
| // module map file output and walk the module tree, outputting the module |
| // map file using a stream obtained and managed by an |
| // llvm::ToolOutputFile object. |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "Modularize.h" |
| #include "llvm/ADT/SmallString.h" |
| #include "llvm/Support/FileSystem.h" |
| #include "llvm/Support/Path.h" |
| #include "llvm/Support/ToolOutputFile.h" |
| #include <vector> |
| |
| // Local definitions: |
| |
| namespace { |
| |
| // Internal class definitions: |
| |
| // Represents a module. |
| class Module { |
| public: |
| Module(llvm::StringRef Name, bool Problem); |
| ~Module(); |
| bool output(llvm::raw_fd_ostream &OS, int Indent); |
| Module *findSubModule(llvm::StringRef SubName); |
| |
| public: |
| std::string Name; |
| std::vector<std::string> HeaderFileNames; |
| std::vector<Module *> SubModules; |
| bool IsProblem; |
| }; |
| |
| } // end anonymous namespace. |
| |
| // Module functions: |
| |
| // Constructors. |
| Module::Module(llvm::StringRef Name, bool Problem) |
| : Name(Name), IsProblem(Problem) {} |
| |
| // Destructor. |
| Module::~Module() { |
| // Free submodules. |
| while (!SubModules.empty()) { |
| Module *last = SubModules.back(); |
| SubModules.pop_back(); |
| delete last; |
| } |
| } |
| |
| // Write a module hierarchy to the given output stream. |
| bool Module::output(llvm::raw_fd_ostream &OS, int Indent) { |
| // If this is not the nameless root module, start a module definition. |
| if (Name.size() != 0) { |
| OS.indent(Indent); |
| OS << "module " << Name << " {\n"; |
| Indent += 2; |
| } |
| |
| // Output submodules. |
| for (auto I = SubModules.begin(), E = SubModules.end(); I != E; ++I) { |
| if (!(*I)->output(OS, Indent)) |
| return false; |
| } |
| |
| // Output header files. |
| for (auto I = HeaderFileNames.begin(), E = HeaderFileNames.end(); I != E; |
| ++I) { |
| OS.indent(Indent); |
| if (IsProblem || strstr((*I).c_str(), ".inl")) |
| OS << "exclude header \"" << *I << "\"\n"; |
| else |
| OS << "header \"" << *I << "\"\n"; |
| } |
| |
| // If this module has header files, output export directive. |
| if (HeaderFileNames.size() != 0) { |
| OS.indent(Indent); |
| OS << "export *\n"; |
| } |
| |
| // If this is not the nameless root module, close the module definition. |
| if (Name.size() != 0) { |
| Indent -= 2; |
| OS.indent(Indent); |
| OS << "}\n"; |
| } |
| |
| return true; |
| } |
| |
| // Lookup a sub-module. |
| Module *Module::findSubModule(llvm::StringRef SubName) { |
| for (auto I = SubModules.begin(), E = SubModules.end(); I != E; ++I) { |
| if ((*I)->Name == SubName) |
| return *I; |
| } |
| return nullptr; |
| } |
| |
| // Implementation functions: |
| |
| // Reserved keywords in module.modulemap syntax. |
| // Keep in sync with keywords in module map parser in Lex/ModuleMap.cpp, |
| // such as in ModuleMapParser::consumeToken(). |
| static const char *const ReservedNames[] = { |
| "config_macros", "export", "module", "conflict", "framework", |
| "requires", "exclude", "header", "private", "explicit", |
| "link", "umbrella", "extern", "use", nullptr // Flag end. |
| }; |
| |
| // Convert module name to a non-keyword. |
| // Prepends a '_' to the name if and only if the name is a keyword. |
| static std::string |
| ensureNoCollisionWithReservedName(llvm::StringRef MightBeReservedName) { |
| std::string SafeName(MightBeReservedName); |
| for (int Index = 0; ReservedNames[Index] != nullptr; ++Index) { |
| if (MightBeReservedName == ReservedNames[Index]) { |
| SafeName.insert(0, "_"); |
| break; |
| } |
| } |
| return SafeName; |
| } |
| |
| // Convert module name to a non-keyword. |
| // Prepends a '_' to the name if and only if the name is a keyword. |
| static std::string |
| ensureVaidModuleName(llvm::StringRef MightBeInvalidName) { |
| std::string SafeName(MightBeInvalidName); |
| std::replace(SafeName.begin(), SafeName.end(), '-', '_'); |
| std::replace(SafeName.begin(), SafeName.end(), '.', '_'); |
| if (isdigit(SafeName[0])) |
| SafeName = "_" + SafeName; |
| return SafeName; |
| } |
| |
| // Add one module, given a header file path. |
| static bool addModuleDescription(Module *RootModule, |
| llvm::StringRef HeaderFilePath, |
| llvm::StringRef HeaderPrefix, |
| DependencyMap &Dependencies, |
| bool IsProblemFile) { |
| Module *CurrentModule = RootModule; |
| DependentsVector &FileDependents = Dependencies[HeaderFilePath]; |
| std::string FilePath; |
| // Strip prefix. |
| // HeaderFilePath should be compared to natively-canonicalized Prefix. |
| llvm::SmallString<256> NativePath, NativePrefix; |
| llvm::sys::path::native(HeaderFilePath, NativePath); |
| llvm::sys::path::native(HeaderPrefix, NativePrefix); |
| if (NativePath.startswith(NativePrefix)) |
| FilePath = std::string(NativePath.substr(NativePrefix.size() + 1)); |
| else |
| FilePath = std::string(HeaderFilePath); |
| int Count = FileDependents.size(); |
| // Headers that go into modules must not depend on other files being |
| // included first. If there are any dependents, warn user and omit. |
| if (Count != 0) { |
| llvm::errs() << "warning: " << FilePath |
| << " depends on other headers being included first," |
| " meaning the module.modulemap won't compile." |
| " This header will be omitted from the module map.\n"; |
| return true; |
| } |
| // Make canonical. |
| std::replace(FilePath.begin(), FilePath.end(), '\\', '/'); |
| // Insert module into tree, using subdirectories as submodules. |
| for (llvm::sys::path::const_iterator I = llvm::sys::path::begin(FilePath), |
| E = llvm::sys::path::end(FilePath); |
| I != E; ++I) { |
| if ((*I)[0] == '.') |
| continue; |
| std::string Stem(llvm::sys::path::stem(*I)); |
| Stem = ensureNoCollisionWithReservedName(Stem); |
| Stem = ensureVaidModuleName(Stem); |
| Module *SubModule = CurrentModule->findSubModule(Stem); |
| if (!SubModule) { |
| SubModule = new Module(Stem, IsProblemFile); |
| CurrentModule->SubModules.push_back(SubModule); |
| } |
| CurrentModule = SubModule; |
| } |
| // Add header file name to headers. |
| CurrentModule->HeaderFileNames.push_back(FilePath); |
| return true; |
| } |
| |
| // Create the internal module tree representation. |
| static Module *loadModuleDescriptions( |
| llvm::StringRef RootModuleName, llvm::ArrayRef<std::string> HeaderFileNames, |
| llvm::ArrayRef<std::string> ProblemFileNames, |
| DependencyMap &Dependencies, llvm::StringRef HeaderPrefix) { |
| |
| // Create root module. |
| auto *RootModule = new Module(RootModuleName, false); |
| |
| llvm::SmallString<256> CurrentDirectory; |
| llvm::sys::fs::current_path(CurrentDirectory); |
| |
| // If no header prefix, use current directory. |
| if (HeaderPrefix.size() == 0) |
| HeaderPrefix = CurrentDirectory; |
| |
| // Walk the header file names and output the module map. |
| for (llvm::ArrayRef<std::string>::iterator I = HeaderFileNames.begin(), |
| E = HeaderFileNames.end(); |
| I != E; ++I) { |
| std::string Header(*I); |
| bool IsProblemFile = false; |
| for (auto &ProblemFile : ProblemFileNames) { |
| if (ProblemFile == Header) { |
| IsProblemFile = true; |
| break; |
| } |
| } |
| // Add as a module. |
| if (!addModuleDescription(RootModule, Header, HeaderPrefix, Dependencies, IsProblemFile)) |
| return nullptr; |
| } |
| |
| return RootModule; |
| } |
| |
| // Kick off the writing of the module map. |
| static bool writeModuleMap(llvm::StringRef ModuleMapPath, |
| llvm::StringRef HeaderPrefix, Module *RootModule) { |
| llvm::SmallString<256> HeaderDirectory(ModuleMapPath); |
| llvm::sys::path::remove_filename(HeaderDirectory); |
| llvm::SmallString<256> FilePath; |
| |
| // Get the module map file path to be used. |
| if ((HeaderDirectory.size() == 0) && (HeaderPrefix.size() != 0)) { |
| FilePath = HeaderPrefix; |
| // Prepend header file name prefix if it's not absolute. |
| llvm::sys::path::append(FilePath, ModuleMapPath); |
| llvm::sys::path::native(FilePath); |
| } else { |
| FilePath = ModuleMapPath; |
| llvm::sys::path::native(FilePath); |
| } |
| |
| // Set up module map output file. |
| std::error_code EC; |
| llvm::ToolOutputFile Out(FilePath, EC, llvm::sys::fs::OF_TextWithCRLF); |
| if (EC) { |
| llvm::errs() << Argv0 << ": error opening " << FilePath << ":" |
| << EC.message() << "\n"; |
| return false; |
| } |
| |
| // Get output stream from tool output buffer/manager. |
| llvm::raw_fd_ostream &OS = Out.os(); |
| |
| // Output file comment. |
| OS << "// " << ModuleMapPath << "\n"; |
| OS << "// Generated by: " << CommandLine << "\n\n"; |
| |
| // Write module hierarchy from internal representation. |
| if (!RootModule->output(OS, 0)) |
| return false; |
| |
| // Tell ToolOutputFile that we want to keep the file. |
| Out.keep(); |
| |
| return true; |
| } |
| |
| // Global functions: |
| |
| // Module map generation entry point. |
| bool createModuleMap(llvm::StringRef ModuleMapPath, |
| llvm::ArrayRef<std::string> HeaderFileNames, |
| llvm::ArrayRef<std::string> ProblemFileNames, |
| DependencyMap &Dependencies, llvm::StringRef HeaderPrefix, |
| llvm::StringRef RootModuleName) { |
| // Load internal representation of modules. |
| std::unique_ptr<Module> RootModule( |
| loadModuleDescriptions( |
| RootModuleName, HeaderFileNames, ProblemFileNames, Dependencies, |
| HeaderPrefix)); |
| if (!RootModule.get()) |
| return false; |
| |
| // Write module map file. |
| return writeModuleMap(ModuleMapPath, HeaderPrefix, RootModule.get()); |
| } |