Index: modularize/CMakeLists.txt =================================================================== --- modularize/CMakeLists.txt +++ modularize/CMakeLists.txt @@ -7,6 +7,7 @@ add_clang_executable(modularize Modularize.cpp + ModuleAssistant.cpp PreprocessorTracker.cpp ) Index: modularize/Modularize.h =================================================================== --- modularize/Modularize.h +++ modularize/Modularize.h @@ -0,0 +1,52 @@ +//===--- Modularize.h - Common definitions for Modularize -*- C++ -*-----===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===--------------------------------------------------------------------===// +/// +/// \file +/// \brief Common definitions for Modularize. +/// +//===--------------------------------------------------------------------===// + +#ifndef MODULARIZE_H +#define MODULARIZE_H + +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/SmallString.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/ADT/StringMap.h" +#include "llvm/ADT/StringRef.h" +#include +#include + +// Save the program name for error messages. +extern const char *Argv0; +// Save the command line for comments. +extern std::string CommandLine; + +// Dependency types. +typedef llvm::SmallVector DependentsVector; +typedef llvm::StringMap DependencyMap; + +// Global function declarations. + +/// Create the module map file. +/// \param ModuleMapPath The path to the module map file to be generated. +/// \param HeaderFileNames The list of header files, absolute native paths. +/// \param Dependencies Map of headers that depend on other headers. +/// \param HeaderPrefix Tells the code where the headers are, if they +/// aren's in the current directory, allowing the generator to strip +/// the leading, non-relative beginning of the header paths. +/// \brief RootModuleName If not empty, specifies that a root module +/// should be created with this name. +/// \returns True if successful. +bool createModuleMap(llvm::StringRef ModuleMapPath, + llvm::ArrayRef HeaderFileNames, + DependencyMap &Dependencies, llvm::StringRef HeaderPrefix, + llvm::StringRef RootModuleName); + +#endif // MODULARIZE_H Index: modularize/Modularize.cpp =================================================================== --- modularize/Modularize.cpp +++ modularize/Modularize.cpp @@ -90,6 +90,25 @@ // // See PreprocessorTracker.cpp for additional details. // +// Modularize also has an option ("-module-map-path=module.map") that will +// skip the checks, and instead act as a module.map generation assistant, +// generating a module map file based on the header list. An optional +// "-root-module=(rootName)" argument can specify a root module to be +// created in the generated module.map file. Note that you will likely +// need to edit this file to suit the needs of your headers. +// +// An example command line for generating a module.map file: +// +// modularize -module-map-path=module.map -root-module=myroot headerlist.txt +// +// Note that if the headers in the header list have partial paths, sub-modules +// will be created for the subdirectires involved, assuming that the +// subdirectories contain headers to be grouped into a module, but still with +// individual modules for the headers in the subdirectory. +// +// See the ModuleAssistant.cpp file comments for additional details about the +// implementation of the assistant mode. +// // Future directions: // // Basically, we want to add new checks for whatever we can check with respect @@ -134,8 +153,6 @@ #include "clang/Tooling/CompilationDatabase.h" #include "clang/Tooling/Tooling.h" #include "llvm/ADT/OwningPtr.h" -#include "llvm/ADT/StringRef.h" -#include "llvm/ADT/StringMap.h" #include "llvm/Config/config.h" #include "llvm/Option/Arg.h" #include "llvm/Option/ArgList.h" @@ -150,6 +167,7 @@ #include #include #include +#include "Modularize.h" #include "PreprocessorTracker.h" using namespace clang; @@ -178,8 +196,24 @@ " If not specified," " the files are considered to be relative to the header list file.")); -typedef SmallVector DependentsVector; -typedef StringMap DependencyMap; +// Option for assistant mode, telling modularize to output a module map +// based on the headers list, and where to put it. +cl::opt ModuleMapPath( + "module-map-path", cl::init(""), + cl::desc("Turn on module map output and specify output path or file name." + " If no path is specified and if prefix option is specified," + " use prefix for file path.")); + +// Option for assistant mode, telling modularize to output a module map +// based on the headers list, and where to put it. +cl::opt +RootModule("root-module", cl::init(""), + cl::desc("Specify the name of the root module.")); + +// Save the program name for error messages. +const char *Argv0; +// Save the command line for comments. +std::string CommandLine; // Read the header list file and collect the header file names and // optional dependencies. @@ -651,6 +685,16 @@ int main(int Argc, const char **Argv) { + // Save program name for error messages. + Argv0 = Argv[0]; + + // Save program arguments for use in module.map comment. + CommandLine = sys::path::stem(sys::path::filename(Argv0)); + for (int ArgIndex = 1; ArgIndex < Argc; ArgIndex++) { + CommandLine.append(" "); + CommandLine.append(Argv[ArgIndex]); + } + // This causes options to be parsed. cl::ParseCommandLineOptions(Argc, Argv, "modularize.\n"); @@ -670,6 +714,14 @@ return 1; } + // If we are in assistant mode, output the module map and quit. + if (ModuleMapPath[0]) { + if (!createModuleMap(ModuleMapPath, Headers, Dependencies, HeaderPrefix, + RootModule)) + return 1; // Failed. + return 0; // Success - Skip checks in assistant mode. + } + // Create the compilation database. SmallString<256> PathBuf; sys::fs::current_path(PathBuf); Index: modularize/ModuleAssistant.cpp =================================================================== --- modularize/ModuleAssistant.cpp +++ modularize/ModuleAssistant.cpp @@ -0,0 +1,287 @@ +//===--- ModuleAssistant.cpp - Module map generation manager -*- C++ -*---===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===---------------------------------------------------------------------===// +// +// 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::tool_output_file object. +// +//===---------------------------------------------------------------------===// + +#include "Modularize.h" +#include "llvm/ADT/OwningPtr.h" +#include "llvm/ADT/SmallString.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/Path.h" +#include "llvm/Support/ToolOutputFile.h" +#include + +// Local definitions: + +namespace { + +// Internal class definitions: + +// Represents a module. +class Module { +public: + Module(llvm::StringRef Name); + Module(); + ~Module(); + bool output(llvm::raw_fd_ostream &OS, int Indent); + Module *findSubModule(llvm::StringRef SubName); + +public: + std::string Name; + std::vector HeaderFileNames; + std::vector SubModules; +}; + +} // end anonymous namespace. + +// Module functions: + +// Constructors. +Module::Module(llvm::StringRef Name) : Name(Name) {} +Module::Module() {} + +// Destructor. +Module::~Module() { + // Free submodules. + while (SubModules.size()) { + 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 (std::vector::iterator I = SubModules.begin(), + E = SubModules.end(); + I != E; ++I) { + if (!(*I)->output(OS, Indent)) + return false; + } + + // Output header files. + for (std::vector::iterator I = HeaderFileNames.begin(), + E = HeaderFileNames.end(); + I != E; ++I) { + OS.indent(Indent); + 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 (std::vector::iterator I = SubModules.begin(), + E = SubModules.end(); + I != E; ++I) { + if ((*I)->Name == SubName) + return *I; + } + return 0; +} + +// Implementation functions: + +// Reserved keywords in module.map syntax. +// Keep in sync with keywords in module map parser in Lex/ModuleMap.cpp, +// such as in ModuleMapParser::consumeToken(). +static const char *ReservedNames[] = { + "config_macros", "export", "module", "conflict", "framework", + "requires", "exclude", "header", "private", "explicit", + "link", "umbrella", "extern", "use", 0 // 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] != 0; ++Index) { + if (MightBeReservedName == ReservedNames[Index]) { + SafeName.insert(0, "_"); + break; + } + } + return SafeName; +} + +// Add one module, given a header file path. +static bool addModuleDescription(Module *RootModule, + llvm::StringRef HeaderFilePath, + llvm::StringRef HeaderPrefix, + DependencyMap &Dependencies) { + Module *CurrentModule = RootModule; + DependentsVector &FileDependents = Dependencies[HeaderFilePath]; + std::string FilePath; + // Strip prefix. + if (HeaderFilePath.startswith(HeaderPrefix)) + FilePath = HeaderFilePath.substr(HeaderPrefix.size() + 1); + else + FilePath = 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.map 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); + Module *SubModule = CurrentModule->findSubModule(Stem); + if (SubModule == 0) { + SubModule = new Module(Stem); + 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 HeaderFileNames, + DependencyMap &Dependencies, llvm::StringRef HeaderPrefix) { + + // Create root module. + Module *RootModule = new Module(RootModuleName); + + 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::iterator I = HeaderFileNames.begin(), + E = HeaderFileNames.end(); + I != E; ++I) { + // Add as a module. + if (!addModuleDescription(RootModule, *I, HeaderPrefix, Dependencies)) + return false; + } + + 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::string Error; + llvm::tool_output_file Out(FilePath.c_str(), Error); + if (!Error.empty()) { + llvm::errs() << Argv0 << ": error opening " << FilePath << ":" << Error + << "\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 tool_output_file 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 HeaderFileNames, + DependencyMap &Dependencies, llvm::StringRef HeaderPrefix, + llvm::StringRef RootModuleName) { + // Load internal representation of modules. + llvm::OwningPtr RootModule(loadModuleDescriptions( + RootModuleName, HeaderFileNames, Dependencies, HeaderPrefix)); + if (!RootModule.get()) + return false; + + // Write module map file. + return writeModuleMap(ModuleMapPath, HeaderPrefix, RootModule.get()); +} Index: test/modularize/Inputs/SubModule1/Header1.h =================================================================== --- test/modularize/Inputs/SubModule1/Header1.h +++ test/modularize/Inputs/SubModule1/Header1.h @@ -0,0 +1 @@ +// Header1.h - Empty. Index: test/modularize/Inputs/SubModule1/Header2.h =================================================================== --- test/modularize/Inputs/SubModule1/Header2.h +++ test/modularize/Inputs/SubModule1/Header2.h @@ -0,0 +1 @@ +// Header2.h - Empty. Index: test/modularize/Inputs/SubModule2/Header3.h =================================================================== --- test/modularize/Inputs/SubModule2/Header3.h +++ test/modularize/Inputs/SubModule2/Header3.h @@ -0,0 +1 @@ +// Header3.h - Empty. Index: test/modularize/Inputs/SubModule2/Header4.h =================================================================== --- test/modularize/Inputs/SubModule2/Header4.h +++ test/modularize/Inputs/SubModule2/Header4.h @@ -0,0 +1 @@ +// Header4.h - Empty. Index: test/modularize/NoProblemsAssistant.modularize =================================================================== --- test/modularize/NoProblemsAssistant.modularize +++ test/modularize/NoProblemsAssistant.modularize @@ -0,0 +1,45 @@ +# RUN: modularize -module-map-path=Output/NoProblemsAssistant.txt -root-module=Root -prefix=%S/Input %s +# RUN: FileCheck --input-file=%T/NoProblemsAssistant.txt %s + +SomeTypes.h +SomeDecls.h +SubModule1/Header1.h +SubModule1/Header2.h +SubModule2/Header3.h +SubModule2/Header4.h +SubModule2.h + +# CHECK: // Output/NoProblemsAssistant.txt +# CHECK-NEXT: // Generated by: modularize -module-map-path=Output/NoProblemsAssistant.txt -root-module=Root -prefix={{.*}}{{[/\\]}}{{.*}} {{.*}}{{[/\\]}}NoProblemsAssistant.modularize +# CHECK: module Root { +# CHECK-NEXT: module SomeTypes { +# CHECK-NEXT: header "SomeTypes.h" +# CHECK-NEXT: export * +# CHECK-NEXT: } +# CHECK-NEXT: module SomeDecls { +# CHECK-NEXT: header "SomeDecls.h" +# CHECK-NEXT: export * +# CHECK-NEXT: } +# CHECK-NEXT: module SubModule1 { +# CHECK-NEXT: module Header1 { +# CHECK-NEXT: header "SubModule1/Header1.h" +# CHECK-NEXT: export * +# CHECK-NEXT: } +# CHECK-NEXT: module Header2 { +# CHECK-NEXT: header "SubModule1/Header2.h" +# CHECK-NEXT: export * +# CHECK-NEXT: } +# CHECK-NEXT: } +# CHECK-NEXT: module SubModule2 { +# CHECK-NEXT: module Header3 { +# CHECK-NEXT: header "SubModule2/Header3.h" +# CHECK-NEXT: export * +# CHECK-NEXT: } +# CHECK-NEXT: module Header4 { +# CHECK-NEXT: header "SubModule2/Header4.h" +# CHECK-NEXT: export * +# CHECK-NEXT: } +# CHECK-NEXT: header "SubModule2.h" +# CHECK-NEXT: export * +# CHECK-NEXT: } +# CHECK-NEXT: } Index: test/modularize/SubModule2.h =================================================================== --- test/modularize/SubModule2.h +++ test/modularize/SubModule2.h @@ -0,0 +1,3 @@ +// SubModule2.h - Master header with same name as directory. +#include "SubModule2/Header3.h" +#include "SubModule2/Header4.h"