//===--- ClangModuleDependencyScanner.cpp - Dependency Scanning -----------===// // // This source file is part of the Swift.org open source project // // Copyright (c) 2019 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See https://swift.org/LICENSE.txt for license information // See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // //===----------------------------------------------------------------------===// // // This file implements dependency scanning for Clang modules. // //===----------------------------------------------------------------------===// #include "ImporterImpl.h" #include "swift/AST/ModuleDependencies.h" #include "swift/ClangImporter/ClangImporter.h" #include "swift/ClangImporter/ClangImporterOptions.h" #include "clang/Tooling/DependencyScanning/DependencyScanningService.h" #include "clang/Tooling/DependencyScanning/DependencyScanningTool.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/Signals.h" using namespace swift; using namespace clang::tooling; using namespace clang::tooling::dependencies; class swift::ClangModuleDependenciesCacheImpl { /// The name of the file used for the "import hack" to compute module /// dependencies. /// FIXME: This should go away once Clang's dependency scanning library /// can scan by module name. std::string importHackFile; public: /// Set containing all of the Clang modules that have already been seen. llvm::StringSet<> alreadySeen; DependencyScanningService service; DependencyScanningTool tool; ClangModuleDependenciesCacheImpl() : service(ScanningMode::MinimizedSourcePreprocessing, ScanningOutputFormat::Full), tool(service) { } ~ClangModuleDependenciesCacheImpl(); /// Retrieve the name of the file used for the "import hack" that is /// used to scan the dependencies of a Clang module. llvm::ErrorOr getImportHackFile(); }; ClangModuleDependenciesCacheImpl::~ClangModuleDependenciesCacheImpl() { if (!importHackFile.empty()) { llvm::sys::fs::remove(importHackFile); } } llvm::ErrorOr ClangModuleDependenciesCacheImpl::getImportHackFile() { if (!importHackFile.empty()) return importHackFile; // Create a temporary file. int resultFD; SmallString<128> resultPath; if (auto error = llvm::sys::fs::createTemporaryFile( "import-hack", "m", resultFD, resultPath)) return error; llvm::raw_fd_ostream out(resultFD, /*shouldClose=*/true); out << "@import HACK_MODULE_NAME;\n"; llvm::sys::RemoveFileOnSignal(resultPath); importHackFile = resultPath.str().str(); return importHackFile; } namespace { class SingleCommandCompilationDatabase : public CompilationDatabase { public: SingleCommandCompilationDatabase(CompileCommand Cmd) : Command(std::move(Cmd)) {} virtual std::vector getCompileCommands(StringRef FilePath) const { return {Command}; } virtual std::vector getAllCompileCommands() const { return {Command}; } private: CompileCommand Command; }; } // Add search paths. // Note: This is handled differently for the Clang importer itself, which // adds search paths to Clang's data structures rather than to its // command line. static void addSearchPathInvocationArguments( std::vector &invocationArgStrs, ASTContext &ctx, const ClangImporterOptions &importerOpts) { SearchPathOptions &searchPathOpts = ctx.SearchPathOpts; for (const auto &framepath : searchPathOpts.FrameworkSearchPaths) { invocationArgStrs.push_back(framepath.IsSystem ? "-iframework" : "-F"); invocationArgStrs.push_back(framepath.Path); } for (auto path : searchPathOpts.ImportSearchPaths) { invocationArgStrs.push_back("-I"); invocationArgStrs.push_back(path); } } /// Create the command line for Clang dependency scanning. static std::vector getClangDepScanningInvocationArguments( ASTContext &ctx, const ClangImporterOptions &importerOpts, StringRef sourceFileName) { std::vector commandLineArgs; // Form the basic command line. commandLineArgs.push_back("clang"); importer::getNormalInvocationArguments(commandLineArgs, ctx, importerOpts); importer::addCommonInvocationArguments(commandLineArgs, ctx, importerOpts); addSearchPathInvocationArguments(commandLineArgs, ctx, importerOpts); auto sourceFilePos = std::find( commandLineArgs.begin(), commandLineArgs.end(), ""); assert(sourceFilePos != commandLineArgs.end()); *sourceFilePos = sourceFileName; // HACK! Drop the -fmodule-format= argument and the one that // precedes it. { auto moduleFormatPos = std::find_if(commandLineArgs.begin(), commandLineArgs.end(), [](StringRef arg) { return arg.startswith("-fmodule-format="); }); assert(moduleFormatPos != commandLineArgs.end()); assert(moduleFormatPos != commandLineArgs.begin()); commandLineArgs.erase(moduleFormatPos-1, moduleFormatPos+1); } // HACK: No -fsyntax-only here? { auto syntaxOnlyPos = std::find(commandLineArgs.begin(), commandLineArgs.end(), "-fsyntax-only"); assert(syntaxOnlyPos != commandLineArgs.end()); *syntaxOnlyPos = "-c"; } // HACK: Stolen from ClangScanDeps.cpp commandLineArgs.push_back("-o"); commandLineArgs.push_back("/dev/null"); commandLineArgs.push_back("-M"); commandLineArgs.push_back("-MT"); commandLineArgs.push_back("import-hack.o"); commandLineArgs.push_back("-Xclang"); commandLineArgs.push_back("-Eonly"); commandLineArgs.push_back("-Xclang"); commandLineArgs.push_back("-sys-header-deps"); commandLineArgs.push_back("-Wno-error"); return commandLineArgs; } /// Get or create the Clang-specific static ClangModuleDependenciesCacheImpl *getOrCreateClangImpl( ModuleDependenciesCache &cache) { auto clangImpl = cache.getClangImpl(); if (!clangImpl) { clangImpl = new ClangModuleDependenciesCacheImpl(); cache.setClangImpl(clangImpl, [](ClangModuleDependenciesCacheImpl *ptr) { delete ptr; }); } return clangImpl; } /// Record the module dependencies we found by scanning Clang modules into /// the module dependencies cache. static void recordModuleDependencies( ModuleDependenciesCache &cache, const FullDependenciesResult &clangDependencies) { for (const auto &clangModuleDep : clangDependencies.DiscoveredModules) { // If we've already cached this information, we're done. if (cache.hasDependencies(clangModuleDep.ModuleName, ModuleDependenciesKind::Clang)) continue; // File dependencies for this module. std::vector fileDeps; for (const auto &fileDep : clangModuleDep.FileDeps) { fileDeps.push_back(fileDep.getKey()); } // Module-level dependencies. llvm::StringSet<> alreadyAddedModules; auto dependencies = ModuleDependencies::forClangModule( clangModuleDep.ImplicitModulePCMPath, clangModuleDep.ClangModuleMapFile, fileDeps); for (const auto &moduleName : clangModuleDep.ClangModuleDeps) { dependencies.addModuleDependency(moduleName.ModuleName, alreadyAddedModules); } cache.recordDependencies(clangModuleDep.ModuleName, std::move(dependencies), ModuleDependenciesKind::Clang); } } Optional ClangImporter::getModuleDependencies( StringRef moduleName, ModuleDependenciesCache &cache) { // Check whether there is already a cached result. if (auto found = cache.findDependencies( moduleName, ModuleDependenciesKind::Clang)) return found; // Retrieve or create the shared state. auto clangImpl = getOrCreateClangImpl(cache); // HACK! Replace the module import buffer name with the source file hack. auto importHackFile = clangImpl->getImportHackFile(); if (!importHackFile) { // FIXME: Emit a diagnostic here. return None; } // Reform the Clang importer options. // FIXME: Just save a reference or copy so we can get this back. ClangImporterOptions importerOpts; // Determine the command-line arguments for dependency scanning. auto &ctx = Impl.SwiftContext; std::vector commandLineArgs = getClangDepScanningInvocationArguments( ctx, importerOpts, *importHackFile); // HACK! Trick out a .m file to use to import the module we name. std::string moduleNameHackDefine = ("-DHACK_MODULE_NAME=" + moduleName).str(); commandLineArgs.push_back(moduleNameHackDefine); commandLineArgs.push_back("-fmodules-ignore-macro=HACK_MODULE_NAME"); std::string workingDir = ctx.SourceMgr.getFileSystem()->getCurrentWorkingDirectory().get(); CompileCommand command(workingDir, *importHackFile, commandLineArgs, "-"); SingleCommandCompilationDatabase database(command); auto clangDependencies = clangImpl->tool.getFullDependencies( database, workingDir, clangImpl->alreadySeen); if (!clangDependencies) { // FIXME: Route this to a normal diagnostic. llvm::logAllUnhandledErrors(clangDependencies.takeError(), llvm::errs()); return None; } // Record module dependencies for each module we found. recordModuleDependencies(cache, *clangDependencies); return cache.findDependencies(moduleName, ModuleDependenciesKind::Clang); } bool ClangImporter::addBridgingHeaderDependencies( StringRef moduleName, ModuleDependenciesCache &cache) { auto targetModule = *cache.findDependencies( moduleName, ModuleDependenciesKind::Swift); // If we've already recorded bridging header dependencies, we're done. auto swiftDeps = targetModule.getAsSwiftModule(); if (!swiftDeps->bridgingSourceFiles.empty() || !swiftDeps->bridgingModuleDependencies.empty()) return false; // Retrieve or create the shared state. auto clangImpl = getOrCreateClangImpl(cache); // Reform the Clang importer options. // FIXME: Just save a reference or copy so we can get this back. ClangImporterOptions importerOpts; // Retrieve the bridging header. std::string bridgingHeader = *targetModule.getBridgingHeader(); // Determine the command-line arguments for dependency scanning. auto &ctx = Impl.SwiftContext; std::vector commandLineArgs = getClangDepScanningInvocationArguments( ctx, importerOpts, bridgingHeader); std::string workingDir = ctx.SourceMgr.getFileSystem()->getCurrentWorkingDirectory().get(); CompileCommand command(workingDir, bridgingHeader, commandLineArgs, "-"); SingleCommandCompilationDatabase database(command); auto clangDependencies = clangImpl->tool.getFullDependencies( database, workingDir, clangImpl->alreadySeen); if (!clangDependencies) { // FIXME: Route this to a normal diagnostic. llvm::logAllUnhandledErrors(clangDependencies.takeError(), llvm::errs()); return true; } // Record module dependencies for each module we found. recordModuleDependencies(cache, *clangDependencies); // Record dependencies for the source files the bridging header includes. for (const auto &fileDep : clangDependencies->FullDeps.FileDeps) targetModule.addBridgingSourceFile(fileDep); // ... and all module dependencies. llvm::StringSet<> alreadyAddedModules; for (const auto &moduleDep : clangDependencies->FullDeps.ClangModuleDeps) { targetModule.addBridgingModuleDependency( moduleDep.ModuleName, alreadyAddedModules); } // Update the cache with the new information for the module. cache.updateDependencies( {moduleName, ModuleDependenciesKind::Swift}, std::move(targetModule)); return false; }