Skip to content

Commit 451cf27

Browse files
author
Harlan Haskins
authored
[5.1] Cherry-pick some more module interface fixes (#24051)
* [ParseableInterface] Normalize paths before comparison when serializing dependencies Dependencies in the SDK have their paths serialized relative to it to allow the produced swift module to stay valid when the SDK moves. In the windows build mixed slashes were coming through in these paths and breaking the check for whether a dependency was in the SDK or not. This patch ensures both paths are using native path separators prior to the comparison to hopefuly fix the Windows build. * [ParseableInterfaces] Improvements to forwarding module dependencies - Fix a typo in the debug output for finding a prebuilt module - Don't add the prebuilt module path to the dependency tracker - Ensure we're registering the _forwarding module_'s dependencies in cached modules, not the prebuilt module's dependencies. - Check for the existence of a prebuilt module before doing the is-up-to-date check (which will have failed anyway if it didn't exist). - Test that forwarding module dependency collection works rdar://48659199
1 parent 3f96b5f commit 451cf27

File tree

2 files changed

+159
-54
lines changed

2 files changed

+159
-54
lines changed

lib/Frontend/ParseableInterfaceModuleLoader.cpp

Lines changed: 103 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,32 @@ static Optional<llvm::vfs::Status> getStatusOfDependency(
247247
return status.get();
248248
}
249249

250+
/// If the file dependency in \p FullDepPath is inside the \p Base directory,
251+
/// this returns its path relative to \p Base. Otherwise it returns None.
252+
static Optional<StringRef> getRelativeDepPath(StringRef DepPath,
253+
StringRef Base) {
254+
// If Base is the root directory, or DepPath does not start with Base, bail.
255+
if (Base.size() <= 1 || !DepPath.startswith(Base)) {
256+
return None;
257+
}
258+
259+
assert(DepPath.size() > Base.size() &&
260+
"should never depend on a directory");
261+
262+
// Is the DepName something like ${Base}/foo.h"?
263+
if (path::is_separator(DepPath[Base.size()]))
264+
return DepPath.substr(Base.size() + 1);
265+
266+
// Is the DepName something like "${Base}foo.h", where Base
267+
// itself contains a trailing slash?
268+
if (path::is_separator(Base.back()))
269+
return DepPath.substr(Base.size());
270+
271+
// We have something next to Base, like "Base.h", that's somehow
272+
// become a dependency.
273+
return None;
274+
}
275+
250276
#pragma mark - Module Building
251277

252278
/// Builds a parseable module interface into a .swiftmodule at the provided
@@ -365,52 +391,33 @@ class swift::ParseableInterfaceBuilder {
365391
bool collectDepsForSerialization(CompilerInstance &SubInstance,
366392
SmallVectorImpl<FileDependency> &Deps,
367393
bool IsHashBased) {
368-
StringRef SDKPath = SubInstance.getASTContext().SearchPathOpts.SDKPath;
369-
StringRef ResourcePath =
370-
SubInstance.getASTContext().SearchPathOpts.RuntimeResourcePath;
394+
auto &Opts = SubInstance.getASTContext().SearchPathOpts;
395+
SmallString<128> SDKPath(Opts.SDKPath);
396+
path::native(SDKPath);
397+
SmallString<128> ResourcePath(Opts.RuntimeResourcePath);
398+
path::native(ResourcePath);
399+
371400
auto DTDeps = SubInstance.getDependencyTracker()->getDependencies();
372401
SmallVector<StringRef, 16> InitialDepNames(DTDeps.begin(), DTDeps.end());
373402
InitialDepNames.push_back(interfacePath);
374403
llvm::StringSet<> AllDepNames;
404+
SmallString<128> Scratch;
405+
406+
for (const auto &InitialDepName : InitialDepNames) {
407+
path::native(InitialDepName, Scratch);
408+
StringRef DepName = Scratch.str();
375409

376-
for (auto const &DepName : InitialDepNames) {
377410
assert(moduleCachePath.empty() || !DepName.startswith(moduleCachePath));
378-
assert(prebuiltCachePath.empty() || !DepName.startswith(prebuiltCachePath));
379411

380-
/// Lazily load the dependency buffer if we need it. If we're not
381-
/// dealing with a hash-based dependencies, and if the dependency is
382-
/// not a .swiftmodule, we can avoid opening the buffer.
383-
std::unique_ptr<llvm::MemoryBuffer> DepBuf = nullptr;
384-
auto getDepBuf = [&]() -> llvm::MemoryBuffer * {
385-
if (DepBuf) return DepBuf.get();
386-
if (auto Buf = getBufferOfDependency(fs, DepName, interfacePath,
387-
diags, diagnosticLoc)) {
388-
DepBuf = std::move(Buf);
389-
return DepBuf.get();
390-
}
391-
return nullptr;
392-
};
412+
// Serialize the paths of dependencies in the SDK relative to it.
413+
Optional<StringRef> SDKRelativePath = getRelativeDepPath(DepName, SDKPath);
414+
StringRef DepNameToStore = SDKRelativePath.getValueOr(DepName);
415+
bool IsSDKRelative = SDKRelativePath.hasValue();
393416

394-
// Adjust the paths of dependences in the SDK to be relative to it
395-
bool IsSDKRelative = false;
396-
StringRef DepNameToStore = DepName;
397-
if (SDKPath.size() > 1 && DepName.startswith(SDKPath)) {
398-
assert(DepName.size() > SDKPath.size() &&
399-
"should never depend on a directory");
400-
if (llvm::sys::path::is_separator(DepName[SDKPath.size()])) {
401-
// Is the DepName something like ${SDKPath}/foo.h"?
402-
DepNameToStore = DepName.substr(SDKPath.size() + 1);
403-
IsSDKRelative = true;
404-
} else if (llvm::sys::path::is_separator(SDKPath.back())) {
405-
// Is the DepName something like "${SDKPath}foo.h", where SDKPath
406-
// itself contains a trailing slash?
407-
DepNameToStore = DepName.substr(SDKPath.size());
408-
IsSDKRelative = true;
409-
} else {
410-
// We have something next to an SDK, like "Foo.sdk.h", that's somehow
411-
// become a dependency.
412-
}
413-
}
417+
// Forwarding modules add the underlying prebuilt module to their
418+
// dependency list -- don't serialize that.
419+
if (!prebuiltCachePath.empty() && DepName.startswith(prebuiltCachePath))
420+
continue;
414421

415422
if (AllDepNames.insert(DepName).second && dependencyTracker) {
416423
dependencyTracker->addDependency(DepName, /*isSystem*/IsSDKRelative);
@@ -425,6 +432,20 @@ class swift::ParseableInterfaceBuilder {
425432
if (!Status)
426433
return true;
427434

435+
/// Lazily load the dependency buffer if we need it. If we're not
436+
/// dealing with a hash-based dependencies, and if the dependency is
437+
/// not a .swiftmodule, we can avoid opening the buffer.
438+
std::unique_ptr<llvm::MemoryBuffer> DepBuf = nullptr;
439+
auto getDepBuf = [&]() -> llvm::MemoryBuffer * {
440+
if (DepBuf) return DepBuf.get();
441+
if (auto Buf = getBufferOfDependency(fs, DepName, interfacePath,
442+
diags, diagnosticLoc)) {
443+
DepBuf = std::move(Buf);
444+
return DepBuf.get();
445+
}
446+
return nullptr;
447+
};
448+
428449
if (IsHashBased) {
429450
auto buf = getDepBuf();
430451
if (!buf) return true;
@@ -683,8 +704,7 @@ class ParseableInterfaceModuleLoaderImpl {
683704
if (!dep.isSDKRelative())
684705
return dep.getPath();
685706

686-
StringRef SDKPath = ctx.SearchPathOpts.SDKPath;
687-
scratch.assign(SDKPath.begin(), SDKPath.end());
707+
path::native(ctx.SearchPathOpts.SDKPath, scratch);
688708
llvm::sys::path::append(scratch, dep.getPath());
689709
return StringRef(scratch.data(), scratch.size());
690710
}
@@ -812,6 +832,10 @@ class ParseableInterfaceModuleLoaderImpl {
812832
}
813833
path::append(scratch, path::filename(modulePath));
814834

835+
// If there isn't a file at this location, skip returning a path.
836+
if (!fs.exists(scratch))
837+
return None;
838+
815839
return scratch.str();
816840
}
817841

@@ -898,7 +922,7 @@ class ParseableInterfaceModuleLoaderImpl {
898922
return DiscoveredModule::prebuilt(*path, std::move(moduleBuffer));
899923
} else {
900924
LLVM_DEBUG(llvm::dbgs() << "Found out-of-date prebuilt module at "
901-
<< modulePath << "\n");
925+
<< path->str() << "\n");
902926
}
903927
}
904928
}
@@ -935,40 +959,62 @@ class ParseableInterfaceModuleLoaderImpl {
935959

936960
/// Writes the "forwarding module" that will forward to a module in the
937961
/// prebuilt cache.
962+
///
938963
/// Since forwarding modules track dependencies separately from the module
939964
/// they point to, we'll need to grab the up-to-date file status while doing
940-
/// this.
941-
bool writeForwardingModule(const DiscoveredModule &mod,
942-
StringRef outputPath,
943-
ArrayRef<FileDependency> deps) {
965+
/// this. If the write was successful, it also updates the
966+
/// list of dependencies to match what was written to the forwarding file.
967+
bool writeForwardingModuleAndUpdateDeps(
968+
const DiscoveredModule &mod, StringRef outputPath,
969+
SmallVectorImpl<FileDependency> &deps) {
944970
assert(mod.isPrebuilt() &&
945971
"cannot write forwarding file for non-prebuilt module");
946972
ForwardingModule fwd(mod.path);
947973

974+
SmallVector<FileDependency, 16> depsAdjustedToMTime;
975+
948976
// FIXME: We need to avoid re-statting all these dependencies, otherwise
949977
// we may record out-of-date information.
950-
auto addDependency = [&](StringRef path) {
978+
auto addDependency = [&](StringRef path) -> FileDependency {
951979
auto status = fs.status(path);
952980
uint64_t mtime =
953981
status->getLastModificationTime().time_since_epoch().count();
954982
fwd.addDependency(path, status->getSize(), mtime);
983+
984+
// Construct new FileDependency matching what we've added to the
985+
// forwarding module. This is no longer SDK-relative because the absolute
986+
// path has already been resolved.
987+
return FileDependency::modTimeBased(path, /*isSDKRelative*/false,
988+
status->getSize(), mtime);
955989
};
956990

957-
// Add the prebuilt module as a dependency of the forwarding module.
958-
addDependency(fwd.underlyingModulePath);
991+
// Add the prebuilt module as a dependency of the forwarding module, but
992+
// don't add it to the outer dependency list.
993+
(void)addDependency(fwd.underlyingModulePath);
959994

960-
// Add all the dependencies from the prebuilt module.
995+
// Add all the dependencies from the prebuilt module, and update our list
996+
// of dependencies to reflect what's recorded in the forwarding module.
961997
SmallString<128> SDKRelativeBuffer;
962998
for (auto dep : deps) {
963-
addDependency(getFullDependencyPath(dep, SDKRelativeBuffer));
999+
auto adjustedDep =
1000+
addDependency(getFullDependencyPath(dep, SDKRelativeBuffer));
1001+
depsAdjustedToMTime.push_back(adjustedDep);
9641002
}
9651003

966-
return withOutputFile(diags, outputPath,
1004+
auto hadError = withOutputFile(diags, outputPath,
9671005
[&](llvm::raw_pwrite_stream &out) {
9681006
llvm::yaml::Output yamlWriter(out);
9691007
yamlWriter << fwd;
9701008
return false;
9711009
});
1010+
1011+
if (hadError)
1012+
return true;
1013+
1014+
// If and only if we succeeded writing the forwarding file, update the
1015+
// provided list of dependencies.
1016+
deps = depsAdjustedToMTime;
1017+
return false;
9721018
}
9731019

9741020
/// Looks up the best module to load for a given interface, and returns a
@@ -1017,17 +1063,20 @@ class ParseableInterfaceModuleLoaderImpl {
10171063
if (moduleOrErr) {
10181064
auto module = std::move(moduleOrErr.get());
10191065

1020-
// If it's prebuilt, use this time to generate a forwarding module.
1066+
// If it's prebuilt, use this time to generate a forwarding module and
1067+
// update the dependencies to use modification times.
10211068
if (module.isPrebuilt())
1022-
if (writeForwardingModule(module, cachedOutputPath, allDeps))
1069+
if (writeForwardingModuleAndUpdateDeps(module, cachedOutputPath,
1070+
allDeps))
10231071
return std::make_error_code(std::errc::not_supported);
10241072

10251073
// Report the module's dependencies to the dependencyTracker
10261074
if (dependencyTracker) {
10271075
SmallString<128> SDKRelativeBuffer;
10281076
for (auto &dep: allDeps) {
10291077
StringRef fullPath = getFullDependencyPath(dep, SDKRelativeBuffer);
1030-
dependencyTracker->addDependency(fullPath, dep.isSDKRelative());
1078+
dependencyTracker->addDependency(fullPath,
1079+
/*IsSystem=*/dep.isSDKRelative());
10311080
}
10321081
}
10331082

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// RUN: %empty-directory(%t)
2+
// RUN: %empty-directory(%t/mock-sdk)
3+
// RUN: %empty-directory(%t/ModuleCache)
4+
// RUN: %empty-directory(%t/PrebuiltModuleCache)
5+
6+
// This test makes sure that we propagate the right set of dependencies from a
7+
// prebuilt module to its corresponding forwarding module.
8+
9+
// Setup. Copy the mock SDK into the tmp directory.
10+
// RUN: cp -r %S/Inputs/mock-sdk/* %t/mock-sdk/.
11+
12+
// 1. Compile ExportedLib.swiftinterface, which a) is in the SDK, and b) depends
13+
// on a C module with headers that should be in the dependency list.
14+
// Put it in the prebuilt cache.
15+
16+
// RUN: %target-swift-frontend -build-module-from-parseable-interface %t/mock-sdk/ExportedLib.swiftinterface -sdk %t/mock-sdk -o %t/PrebuiltModuleCache/ExportedLib.swiftmodule -serialize-parseable-module-interface-dependency-hashes -track-system-dependencies
17+
18+
// 2. Make sure the prebuilt module we built has SomeCModule.h as a dependency.
19+
20+
// RUN: llvm-bcanalyzer -dump %t/PrebuiltModuleCache/ExportedLib.swiftmodule | grep 'FILE_DEPENDENCY.*SomeCModule.h'
21+
22+
// 3. Typecheck this file, which imports SdkLib, which imports ExportedLib.
23+
// Because ExportedLib is prebuilt, we expect a forwarding module for
24+
// ExportedLib in the module cache, and a serialized module for SdkLib in
25+
// the cache.
26+
27+
// RUN: %target-swift-frontend -typecheck %s -prebuilt-module-cache-path %t/PrebuiltModuleCache -module-cache-path %t/ModuleCache -sdk %t/mock-sdk -I %t/mock-sdk -track-system-dependencies
28+
29+
// 4. Make sure the forwarding module is installed in the cache.
30+
31+
// RUN: %{python} %S/Inputs/check-is-forwarding-module.py %t/ModuleCache/ExportedLib-*.swiftmodule
32+
33+
// 5. Make sure the forwarding module depends on the prebuilt module and the C
34+
// header.
35+
36+
// RUN: grep ' *path:.*ExportedLib.swiftmodule' %t/ModuleCache/ExportedLib-*.swiftmodule
37+
// RUN: grep ' *path:.*SomeCModule.h' %t/ModuleCache/ExportedLib-*.swiftmodule
38+
39+
// 6. Make sure the dependencies from the forwarding module make it into the
40+
// cached module.
41+
42+
// RUN: llvm-bcanalyzer -dump %t/ModuleCache/SdkLib-*.swiftmodule | grep 'FILE_DEPENDENCY.*SomeCModule.h'
43+
44+
// 7. Make sure the prebuilt ExportedLib module did NOT get propagated to SdkLib.
45+
46+
// RUN: llvm-bcanalyzer -dump %t/ModuleCache/SdkLib-*.swiftmodule | not grep 'FILE_DEPENDENCY.*PrebuiltModuleCache'
47+
48+
// 8. Make sure we re-build the SdkLib module if one of the dependencies changes its mtime (but not its hash).
49+
// This will ensure we recorded the forwarding module's dependencies, not the prebuilt.
50+
51+
// RUN: %{python} %S/Inputs/make-old.py %t/ModuleCache/SdkLib-*.swiftmodule
52+
// RUN: %{python} %S/Inputs/make-old.py %t/mock-sdk/usr/include/SomeCModule.h
53+
// RUN: %target-swift-frontend -typecheck %s -prebuilt-module-cache-path %t/PrebuiltModuleCache -module-cache-path %t/ModuleCache -sdk %t/mock-sdk -I %t/mock-sdk -track-system-dependencies
54+
// RUN: %{python} %S/Inputs/check-is-new.py %t/ModuleCache/SdkLib-*.swiftmodule
55+
56+
import SdkLib

0 commit comments

Comments
 (0)