Skip to content

Commit a40f22e

Browse files
committed
ClangImporter: check version specified in canImport with the project version specified in .tbd files
Project versions are embedded in the .tbd files of Clang frameworks. This patch teaches the compiler to check the desired version specified in `canImport` against the project version in .tbd file. The condition returns true if the Clang module on disk has a version number greater or equal to the one from `canImport` condition; it returns false otherwise. Part of rdar://73992299
1 parent c40a083 commit a40f22e

File tree

3 files changed

+126
-1
lines changed

3 files changed

+126
-1
lines changed

lib/ClangImporter/ClangImporter.cpp

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@
6565
#include "llvm/Support/FileCollector.h"
6666
#include "llvm/Support/Memory.h"
6767
#include "llvm/Support/Path.h"
68+
#include "llvm/Support/YAMLTraits.h"
69+
#include "llvm/Support/YAMLParser.h"
6870
#include <algorithm>
6971
#include <memory>
7072

@@ -1731,6 +1733,11 @@ bool ClangImporter::isModuleImported(const clang::Module *M) {
17311733
return M->NameVisibility == clang::Module::NameVisibilityKind::AllVisible;
17321734
}
17331735

1736+
static std::string getScalaNodeText(llvm::yaml::Node *N) {
1737+
SmallString<32> Buffer;
1738+
return cast<llvm::yaml::ScalarNode>(N)->getValue(Buffer).str();
1739+
}
1740+
17341741
bool ClangImporter::canImportModule(ImportPath::Element moduleID,
17351742
llvm::VersionTuple version,
17361743
bool underlyingVersion) {
@@ -1748,7 +1755,66 @@ bool ClangImporter::canImportModule(ImportPath::Element moduleID,
17481755
clang::Module::UnresolvedHeaderDirective mh;
17491756
clang::Module *m;
17501757
auto &ctx = Impl.getClangASTContext();
1751-
return clangModule->isAvailable(ctx.getLangOpts(), getTargetInfo(), r, mh, m);
1758+
auto available = clangModule->isAvailable(ctx.getLangOpts(), getTargetInfo(),
1759+
r, mh, m);
1760+
if (!available)
1761+
return false;
1762+
if (version.empty())
1763+
return true;
1764+
assert(available);
1765+
assert(!version.empty());
1766+
llvm::VersionTuple currentVersion;
1767+
StringRef path = getClangASTContext().getSourceManager()
1768+
.getFilename(clangModule->DefinitionLoc);
1769+
// Look for the .tbd file inside .framework dir to get the project version
1770+
// number.
1771+
std::string fwName = (llvm::Twine(moduleID.Item.str()) + ".framework").str();
1772+
auto pos = path.find(fwName);
1773+
while (pos != StringRef::npos) {
1774+
llvm::SmallString<256> buffer(path.substr(0, pos + fwName.size()));
1775+
llvm::sys::path::append(buffer, llvm::Twine(moduleID.Item.str()) + ".tbd");
1776+
auto tbdPath = buffer.str();
1777+
llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> tbdBufOrErr =
1778+
llvm::MemoryBuffer::getFile(tbdPath);
1779+
// .tbd file doesn't exist, break.
1780+
if (!tbdBufOrErr) {
1781+
break;
1782+
}
1783+
StringRef tbdBuffer = tbdBufOrErr->get()->getBuffer();
1784+
1785+
// Use a new source manager instead of the one from ASTContext because we
1786+
// don't want the Json file to be persistent.
1787+
SourceManager SM;
1788+
llvm::yaml::Stream Stream(llvm::MemoryBufferRef(tbdBuffer, tbdPath),
1789+
SM.getLLVMSourceMgr());
1790+
auto DI = Stream.begin();
1791+
assert(DI != Stream.end() && "Failed to read a document");
1792+
llvm::yaml::Node *N = DI->getRoot();
1793+
assert(N && "Failed to find a root");
1794+
auto *pairs = dyn_cast_or_null<llvm::yaml::MappingNode>(N);
1795+
if (!pairs)
1796+
break;
1797+
for (auto &keyValue: *pairs) {
1798+
auto key = getScalaNodeText(keyValue.getKey());
1799+
// Look for field "current-version" in the .tbd file.
1800+
if (key == "current-version") {
1801+
auto ver = getScalaNodeText(keyValue.getValue());
1802+
currentVersion.tryParse(ver);
1803+
break;
1804+
}
1805+
}
1806+
break;
1807+
}
1808+
// Diagnose unable to checking the current version.
1809+
if (currentVersion.empty()) {
1810+
Impl.diagnose(moduleID.Loc, diag::cannot_find_project_version, "Clang",
1811+
moduleID.Item.str());
1812+
return true;
1813+
}
1814+
assert(!currentVersion.empty());
1815+
// Give a green light if the version on disk is greater or equal to the version
1816+
// specified in the canImport condition.
1817+
return currentVersion >= version;
17521818
}
17531819

17541820
ModuleDecl *ClangImporter::Implementation::loadModuleClang(
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// REQUIRES: VENDOR=apple
2+
// RUN: %empty-directory(%t)
3+
// RUN: %empty-directory(%t/overlaydir)
4+
// RUN: %empty-directory(%t/frameworks)
5+
6+
// RUN: cp -rf %S/Inputs/frameworks/Simple.framework %t/frameworks/
7+
8+
// RUN: echo "current-version: 1830.100" > %t/frameworks/Simple.framework/Simple.tbd
9+
// RUN: echo "@_exported import Simple" > %t.overlay.swift
10+
// RUN: echo "public func additional() {}" >> %t.overlay.swift
11+
12+
// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk) -enable-objc-interop -disable-implicit-concurrency-module-import -module-name Simple -F %t/frameworks/ %t.overlay.swift -emit-module-path %t/overlaydir/Simple.swiftmodule
13+
// RUN: %target-typecheck-verify-swift -disable-implicit-concurrency-module-import -I %t/overlaydir/ -F %t/frameworks
14+
15+
import Simple
16+
17+
func canImportVersioned() {
18+
#if canImport(Simple, underlyingVersion: 3.3)
19+
let a = 1 // expected-warning {{initialization of immutable value 'a' was never used; consider replacing with assignment to '_' or removing it}}
20+
#endif
21+
22+
#if canImport(Simple, underlyingVersion: 1830.100)
23+
let b = 1 // expected-warning {{initialization of immutable value 'b' was never used; consider replacing with assignment to '_' or removing it}}
24+
#endif
25+
26+
#if canImport(Simple, underlyingVersion: 1830.11)
27+
let c = 1 // expected-warning {{initialization of immutable value 'c' was never used; consider replacing with assignment to '_' or removing it}}
28+
#endif
29+
}
30+
31+
func canNotImportVersioned() {
32+
#if canImport(Simple, underlyingVersion: 1831)
33+
let a = 1
34+
#endif
35+
36+
#if canImport(Simple, underlyingVersion: 1830.101)
37+
let b = 1
38+
#endif
39+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// REQUIRES: VENDOR=apple
2+
// RUN: %empty-directory(%t)
3+
// RUN: %empty-directory(%t/overlaydir)
4+
// RUN: %empty-directory(%t/frameworks)
5+
6+
// RUN: cp -rf %S/Inputs/frameworks/Simple.framework %t/frameworks/
7+
8+
// RUN: echo "" > %t/frameworks/Simple.framework/Simple.tbd
9+
// RUN: echo "@_exported import Simple" > %t.overlay.swift
10+
// RUN: echo "public func additional() {}" >> %t.overlay.swift
11+
12+
// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk) -enable-objc-interop -disable-implicit-concurrency-module-import -module-name Simple -F %t/frameworks/ %t.overlay.swift -emit-module-path %t/overlaydir/Simple.swiftmodule
13+
// RUN: %target-typecheck-verify-swift -disable-implicit-concurrency-module-import -I %t/overlaydir/ -F %t/frameworks
14+
15+
import Simple
16+
17+
func canImportVersioned() {
18+
#if canImport(Simple, underlyingVersion: 3.3) // expected-warning {{cannot find user version number for Clang module 'Simple'; version number ignored}}
19+
#endif
20+
}

0 commit comments

Comments
 (0)