Skip to content

Commit 29e655b

Browse files
committed
DependenciesScanner: implement protocol for batch module dependencies scan
This scanning mode allows swift-driver to query module dependencies in a batch and in a more granular way. In short term, it could help solve a problem that clang module dependencies may vary if target triple changes. In a longer term, we could break a holistic dependencies graph into smaller pieces for better caching and reusing. This change doesn't include the implementation of using the specified scanner arguments to set up Clang dependencies scanner. It will come in later commits.
1 parent 5c4c4dc commit 29e655b

File tree

10 files changed

+225
-13
lines changed

10 files changed

+225
-13
lines changed

include/swift/AST/ASTContext.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -739,6 +739,12 @@ class ASTContext final {
739739
ModuleDependenciesCache &cache,
740740
InterfaceSubContextDelegate &delegate);
741741

742+
/// Retrieve the module dependencies for the Swift module with the given name.
743+
Optional<ModuleDependencies> getSwiftModuleDependencies(
744+
StringRef moduleName,
745+
ModuleDependenciesCache &cache,
746+
InterfaceSubContextDelegate &delegate);
747+
742748
/// Load extensions to the given nominal type from the external
743749
/// module loaders.
744750
///

include/swift/AST/DiagnosticsFrontend.def

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,14 @@ ERROR(placeholder_dependency_module_map_corrupted,none,
274274
"Swift placeholder dependency module map from %0 is malformed",
275275
(StringRef))
276276

277+
ERROR(batch_scan_input_file_missing,none,
278+
"cannot open batch dependencies scan input file from %0",
279+
(StringRef))
280+
281+
ERROR(batch_scan_input_file_corrupted,none,
282+
"batch dependencies scan input file from %0 is malformed",
283+
(StringRef))
284+
277285
REMARK(default_previous_install_name, none,
278286
"default previous install name for %0 is %1", (StringRef, StringRef))
279287

include/swift/AST/SearchPathOptions.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,9 @@ class SearchPathOptions {
9393

9494
/// A map of placeholder Swift module dependency information.
9595
std::string PlaceholderDependencyModuleMap;
96+
97+
/// A file containing modules we should perform batch scanning.
98+
std::string BatchScanInputFilePath;
9699
private:
97100
static StringRef
98101
pathStringFromFrameworkSearchPath(const FrameworkSearchPath &next) {

include/swift/Option/FrontendOptions.td

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,10 @@ def explict_swift_module_map
228228
def placeholder_dependency_module_map
229229
: Separate<["-"], "placeholder-dependency-module-map-file">, MetaVarName<"<path>">,
230230
HelpText<"Specify a JSON file containing information of external Swift module dependencies">;
231+
232+
def batch_scan_input_file
233+
: Separate<["-"], "batch-scan-input-file">, MetaVarName<"<path>">,
234+
HelpText<"Specify a JSON file containing modules to perform batch dependencies scanning">;
231235
}
232236

233237

lib/AST/ASTContext.cpp

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1516,6 +1516,21 @@ Optional<ModuleDependencies> ASTContext::getModuleDependencies(
15161516
return None;
15171517
}
15181518

1519+
Optional<ModuleDependencies>
1520+
ASTContext::getSwiftModuleDependencies(StringRef moduleName,
1521+
ModuleDependenciesCache &cache,
1522+
InterfaceSubContextDelegate &delegate) {
1523+
for (auto &loader : getImpl().ModuleLoaders) {
1524+
if (loader.get() == getImpl().TheClangModuleLoader)
1525+
continue;
1526+
1527+
if (auto dependencies = loader->getModuleDependencies(moduleName, cache,
1528+
delegate))
1529+
return dependencies;
1530+
}
1531+
return None;
1532+
}
1533+
15191534
void ASTContext::loadExtensions(NominalTypeDecl *nominal,
15201535
unsigned previousGeneration) {
15211536
PrettyStackTraceDecl stackTrace("loading extensions for", nominal);

lib/Frontend/CompilerInvocation.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -926,7 +926,8 @@ static bool ParseSearchPathArgs(SearchPathOptions &Opts,
926926
}
927927
if (const Arg *A = Args.getLastArg(OPT_placeholder_dependency_module_map))
928928
Opts.PlaceholderDependencyModuleMap = A->getValue();
929-
929+
if (const Arg *A = Args.getLastArg(OPT_batch_scan_input_file))
930+
Opts.BatchScanInputFilePath = A->getValue();
930931
// Opts.RuntimeIncludePath is set by calls to
931932
// setRuntimeIncludePath() or setMainExecutablePath().
932933
// Opts.RuntimeImportPath is set by calls to

lib/FrontendTool/FrontendTool.cpp

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1833,8 +1833,14 @@ static bool performCompile(CompilerInstance &Instance,
18331833
return finishPipeline(Context.hadError());
18341834
}
18351835

1836-
if (Action == FrontendOptions::ActionType::ScanDependencies)
1837-
return finishPipeline(scanDependencies(Instance));
1836+
if (Action == FrontendOptions::ActionType::ScanDependencies) {
1837+
auto batchScanInput = Instance.getASTContext().SearchPathOpts.BatchScanInputFilePath;
1838+
if (batchScanInput.empty())
1839+
return finishPipeline(scanDependencies(Instance));
1840+
else
1841+
return finishPipeline(batchScanModuleDependencies(Instance,
1842+
batchScanInput));
1843+
}
18381844

18391845
if (Action == FrontendOptions::ActionType::ScanClangDependencies)
18401846
return finishPipeline(scanClangDependencies(Instance));

lib/FrontendTool/ScanDependencies.cpp

Lines changed: 135 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,101 @@
3232
#include "llvm/ADT/StringRef.h"
3333
#include "llvm/ADT/StringSet.h"
3434
#include "llvm/Support/FileSystem.h"
35+
#include "llvm/Support/StringSaver.h"
36+
#include "llvm/Support/YAMLTraits.h"
37+
#include "llvm/Support/YAMLParser.h"
3538
#include <set>
3639

3740
using namespace swift;
41+
using namespace llvm::yaml;
3842

3943
namespace {
44+
struct BatchScanInput {
45+
StringRef moduleName;
46+
StringRef arguments;
47+
StringRef outputPath;
48+
};
49+
50+
static std::string getScalaNodeText(Node *N) {
51+
SmallString<32> Buffer;
52+
return cast<ScalarNode>(N)->getValue(Buffer).str();
53+
}
54+
55+
/// Parse an entry like this, where the "platforms" key-value pair is optional:
56+
/// {
57+
/// "module": "Foo.pcm",
58+
/// "arguments": "-target 10.15",
59+
/// "output": "../Foo.json"
60+
/// },
61+
static bool parseBatchInputEntries(ASTContext &Ctx, llvm::StringSaver &saver,
62+
Node *Node, std::vector<BatchScanInput> &result) {
63+
auto *SN = cast<SequenceNode>(Node);
64+
if (!SN)
65+
return true;
66+
for (auto It = SN->begin(); It != SN->end(); ++It) {
67+
auto *MN = cast<MappingNode>(&*It);
68+
BatchScanInput entry;
69+
Optional<std::set<int8_t>> Platforms;
70+
for (auto &Pair: *MN) {
71+
auto Key = getScalaNodeText(Pair.getKey());
72+
auto* Value = Pair.getValue();
73+
if (Key == "module") {
74+
entry.moduleName = saver.save(getScalaNodeText(Value));
75+
} else if (Key == "arguments") {
76+
entry.arguments = saver.save(getScalaNodeText(Value));
77+
} else if (Key == "output") {
78+
entry.outputPath = saver.save(getScalaNodeText(Value));
79+
} else {
80+
// Future proof.
81+
continue;
82+
}
83+
}
84+
if (entry.moduleName.empty())
85+
return true;
86+
if (entry.outputPath.empty())
87+
return true;
88+
auto ext = llvm::sys::path::extension(entry.moduleName);
89+
if (ext != ".swiftmodule" && ext != ".pcm")
90+
return true;
91+
result.emplace_back(std::move(entry));
92+
}
93+
return false;
94+
}
4095

96+
static Optional<std::vector<BatchScanInput>>
97+
parseBatchScanInputFile(ASTContext &ctx, StringRef batchInputPath,
98+
llvm::StringSaver &saver) {
99+
assert(!batchInputPath.empty());
100+
namespace yaml = llvm::yaml;
101+
std::vector<BatchScanInput> result;
102+
103+
// Load the input file.
104+
llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> FileBufOrErr =
105+
llvm::MemoryBuffer::getFile(batchInputPath);
106+
if (!FileBufOrErr) {
107+
ctx.Diags.diagnose(SourceLoc(), diag::batch_scan_input_file_missing,
108+
batchInputPath);
109+
return None;
110+
}
111+
StringRef Buffer = FileBufOrErr->get()->getBuffer();
112+
113+
// Use a new source manager instead of the one from ASTContext because we
114+
// don't want the Json file to be persistent.
115+
SourceManager SM;
116+
yaml::Stream Stream(llvm::MemoryBufferRef(Buffer, batchInputPath),
117+
SM.getLLVMSourceMgr());
118+
for (auto DI = Stream.begin(); DI != Stream.end(); ++ DI) {
119+
assert(DI != Stream.end() && "Failed to read a document");
120+
yaml::Node *N = DI->getRoot();
121+
assert(N && "Failed to find a root");
122+
if (parseBatchInputEntries(ctx, saver, N, result)) {
123+
ctx.Diags.diagnose(SourceLoc(), diag::batch_scan_input_file_corrupted,
124+
batchInputPath);
125+
return None;
126+
}
127+
}
128+
return result;
129+
}
41130
}
42131

43132
/// Find all of the imported Clang modules starting with the given module name.
@@ -533,15 +622,17 @@ static bool diagnoseCycle(CompilerInstance &instance,
533622
return false;
534623
}
535624

536-
bool swift::scanClangDependencies(CompilerInstance &instance) {
625+
static bool scanModuleDependencies(CompilerInstance &instance,
626+
StringRef moduleName,
627+
StringRef arguments,
628+
bool isClang,
629+
StringRef outputPath) {
537630
ASTContext &ctx = instance.getASTContext();
538-
ModuleDecl *mainModule = instance.getMainModule();
539631
auto &FEOpts = instance.getInvocation().getFrontendOptions();
540632
ModuleInterfaceLoaderOptions LoaderOpts(FEOpts);
541633
auto ModuleCachePath = getModuleCachePathFromClang(ctx
542634
.getClangModuleLoader()->getClangInstance());
543635

544-
StringRef mainModuleName = mainModule->getNameStr();
545636
llvm::SetVector<ModuleDependencyID, std::vector<ModuleDependencyID>,
546637
std::set<ModuleDependencyID>> allModules;
547638
// Create the module dependency cache.
@@ -555,16 +646,23 @@ bool swift::scanClangDependencies(CompilerInstance &instance) {
555646
FEOpts.PrebuiltModuleCachePath,
556647
FEOpts.SerializeModuleInterfaceDependencyHashes,
557648
FEOpts.shouldTrackSystemDependencies());
558-
// Loading the clang module using Clang importer.
559-
// This action will populate the cache with the main module's dependencies.
560-
auto rootDeps = static_cast<ClangImporter*>(ctx.getClangModuleLoader())
561-
->getModuleDependencies(mainModuleName, cache, ASTDelegate);
649+
Optional<ModuleDependencies> rootDeps;
650+
if (isClang) {
651+
// Loading the clang module using Clang importer.
652+
// This action will populate the cache with the main module's dependencies.
653+
rootDeps = ctx.getModuleDependencies(moduleName, /*IsClang*/true, cache,
654+
ASTDelegate);
655+
} else {
656+
// Loading the swift module's dependencies.
657+
rootDeps = ctx.getSwiftModuleDependencies(moduleName, cache, ASTDelegate);
658+
}
562659
if (!rootDeps.hasValue()) {
563660
// We cannot find the clang module, abort.
564661
return true;
565662
}
566663
// Add the main module.
567-
allModules.insert({mainModuleName.str(), ModuleDependenciesKind::Clang});
664+
allModules.insert({moduleName.str(), isClang ? ModuleDependenciesKind::Clang:
665+
ModuleDependenciesKind::Swift});
568666

569667
// Explore the dependencies of every module.
570668
for (unsigned currentModuleIdx = 0;
@@ -576,13 +674,40 @@ bool swift::scanClangDependencies(CompilerInstance &instance) {
576674
allModules.insert(discoveredModules.begin(), discoveredModules.end());
577675
}
578676
// Write out the JSON description.
579-
std::string path = FEOpts.InputsAndOutputs.getSingleOutputFilename();
580677
std::error_code EC;
581-
llvm::raw_fd_ostream out(path, EC, llvm::sys::fs::F_None);
678+
llvm::raw_fd_ostream out(outputPath, EC, llvm::sys::fs::F_None);
582679
writeJSON(out, instance, cache, ASTDelegate, allModules.getArrayRef());
583680
return false;
584681
}
585682

683+
bool swift::scanClangDependencies(CompilerInstance &instance) {
684+
return scanModuleDependencies(instance,
685+
instance.getMainModule()->getNameStr(),
686+
StringRef(),
687+
/*isClang*/true,
688+
instance.getInvocation().getFrontendOptions()
689+
.InputsAndOutputs.getSingleOutputFilename());
690+
}
691+
692+
bool swift::batchScanModuleDependencies(CompilerInstance &instance,
693+
llvm::StringRef batchInputFile) {
694+
(void)instance.getMainModule();
695+
llvm::BumpPtrAllocator alloc;
696+
llvm::StringSaver saver(alloc);
697+
auto results = parseBatchScanInputFile(instance.getASTContext(),
698+
batchInputFile, saver);
699+
if (!results.hasValue())
700+
return true;
701+
for (auto &entry: *results) {
702+
auto moduleName = llvm::sys::path::stem(entry.moduleName);
703+
auto isClang = llvm::sys::path::extension(entry.moduleName) == ".pcm";
704+
if (scanModuleDependencies(instance, moduleName, entry.arguments, isClang,
705+
entry.outputPath))
706+
return true;
707+
}
708+
return false;
709+
}
710+
586711
bool swift::scanDependencies(CompilerInstance &instance) {
587712
ASTContext &Context = instance.getASTContext();
588713
ModuleDecl *mainModule = instance.getMainModule();

lib/FrontendTool/ScanDependencies.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,15 @@
1313
#ifndef SWIFT_FRONTENDTOOL_SCANDEPENDENCIES_H
1414
#define SWIFT_FRONTENDTOOL_SCANDEPENDENCIES_H
1515

16+
#include "llvm/ADT/StringRef.h"
17+
1618
namespace swift {
1719

1820
class CompilerInstance;
1921

22+
/// Batch scan the dependencies for modules specified in \c batchInputFile.
23+
bool batchScanModuleDependencies(CompilerInstance &instance, llvm::StringRef batchInputFile);
24+
2025
/// Scans the dependencies of the main module of \c instance.
2126
bool scanDependencies(CompilerInstance &instance);
2227

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// RUN: %empty-directory(%t)
2+
// RUN: %empty-directory(%t/inputs)
3+
// RUN: %empty-directory(%t/outputs)
4+
// RUN: mkdir -p %t/clang-module-cache
5+
6+
// RUN: echo "[{" > %/t/inputs/input.json
7+
// RUN: echo "\"module\": \"F.swiftmodule\"," >> %/t/inputs/input.json
8+
// RUN: echo "\"arguments\": \"-target x86_64-apple-macosx10.9\"," >> %/t/inputs/input.json
9+
// RUN: echo "\"output\": \"%/t/outputs/F.swiftmodule.json\"" >> %/t/inputs/input.json
10+
// RUN: echo "}," >> %/t/inputs/input.json
11+
// RUN: echo "{" >> %/t/inputs/input.json
12+
// RUN: echo "\"module\": \"F.pcm\"," >> %/t/inputs/input.json
13+
// RUN: echo "\"arguments\": \"-target x86_64-apple-macosx10.9\"," >> %/t/inputs/input.json
14+
// RUN: echo "\"output\": \"%/t/outputs/F.pcm.json\"" >> %/t/inputs/input.json
15+
// RUN: echo "}]" >> %/t/inputs/input.json
16+
17+
// RUN: %target-swift-frontend -scan-dependencies -module-cache-path %t/clang-module-cache %s -o %t/deps.json -I %S/Inputs/CHeaders -I %S/Inputs/Swift -emit-dependencies -emit-dependencies-path %t/deps.d -import-objc-header %S/Inputs/CHeaders/Bridging.h -swift-version 4 -batch-scan-input-file %/t/inputs/input.json
18+
19+
// Check the contents of the JSON output
20+
// RUN: %FileCheck %s -check-prefix=CHECK-PCM < %t/outputs/F.pcm.json
21+
// RUN: %FileCheck %s -check-prefix=CHECK-SWIFT < %t/outputs/F.swiftmodule.json
22+
23+
// CHECK-PCM: {
24+
// CHECK-PCM-NEXT: "mainModuleName": "F",
25+
// CHECK-PCM-NEXT: "modules": [
26+
// CHECK-PCM-NEXT: {
27+
// CHECK-PCM-NEXT: "clang": "F"
28+
// CHECK-PCM-NEXT: },
29+
// CHECK-PCM-NEXT: {
30+
// CHECK-PCM-NEXT: "modulePath": "F.pcm",
31+
32+
// CHECK-SWIFT: {
33+
// CHECK-SWIFT-NEXT: "mainModuleName": "F",
34+
// CHECK-SWIFT-NEXT: "modules": [
35+
// CHECK-SWIFT-NEXT: {
36+
// CHECK-SWIFT-NEXT: "swift": "F"
37+
// CHECK-SWIFT-NEXT: },
38+
// CHECK-SWIFT-NEXT: {
39+
// CHECK-SWIFT-NEXT: "modulePath": "F.swiftmodule",

0 commit comments

Comments
 (0)