Skip to content

Commit 7c007bb

Browse files
authored
Merge pull request swiftlang#2083 from pvieito/bundle-tests-extended
2 parents 1f8378b + 656760e commit 7c007bb

File tree

4 files changed

+183
-37
lines changed

4 files changed

+183
-37
lines changed

CoreFoundation/Base.subproj/ForSwiftFoundationOnly.h

+1
Original file line numberDiff line numberDiff line change
@@ -393,6 +393,7 @@ CF_EXPORT _Nullable CFErrorRef CFWriteStreamCopyError(CFWriteStreamRef _Null_uns
393393

394394
CF_CROSS_PLATFORM_EXPORT CFStringRef _Nullable _CFBundleCopyExecutablePath(CFBundleRef bundle);
395395
CF_CROSS_PLATFORM_EXPORT Boolean _CFBundleSupportsFHSBundles(void);
396+
CF_CROSS_PLATFORM_EXPORT Boolean _CFBundleSupportsFreestandingBundles(void);
396397
CF_CROSS_PLATFORM_EXPORT CFStringRef __CFTimeZoneCopyDataVersionString(void);
397398

398399
// https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html

CoreFoundation/PlugIn.subproj/CFBundle.c

+8
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,14 @@ CF_CROSS_PLATFORM_EXPORT Boolean _CFBundleSupportsFHSBundles() {
170170
#endif
171171
}
172172

173+
CF_CROSS_PLATFORM_EXPORT Boolean _CFBundleSupportsFreestandingBundles() {
174+
#if !DEPLOYMENT_RUNTIME_OBJC
175+
return true;
176+
#else
177+
return false;
178+
#endif
179+
}
180+
173181
#pragma mark -
174182

175183
CF_PRIVATE os_log_t _CFBundleResourceLogger(void) {

Foundation/Bundle.swift

+23-7
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,21 @@ import CoreFoundation
1111

1212
open class Bundle: NSObject {
1313
private var _bundle : CFBundle!
14-
15-
internal static var _supportsFHSStyle: Bool {
16-
#if DEPLOYMENT_RUNTIME_OBJC
17-
return false
18-
#else
19-
return _CFBundleSupportsFHSBundles()
20-
#endif
14+
15+
public static var _supportsFHSBundles: Bool {
16+
#if DEPLOYMENT_RUNTIME_OBJC
17+
return false
18+
#else
19+
return _CFBundleSupportsFHSBundles()
20+
#endif
21+
}
22+
23+
public static var _supportsFreestandingBundles: Bool {
24+
#if DEPLOYMENT_RUNTIME_OBJC
25+
return false
26+
#else
27+
return _CFBundleSupportsFreestandingBundles()
28+
#endif
2129
}
2230

2331
private static var _mainBundle : Bundle = {
@@ -106,6 +114,14 @@ open class Bundle: NSObject {
106114
_bundle = result
107115
}
108116

117+
public convenience init?(_executableURL: URL) {
118+
guard let bundleURL = _CFBundleCopyBundleURLForExecutableURL(_executableURL._cfObject)?.takeRetainedValue() else {
119+
return nil
120+
}
121+
122+
self.init(url: bundleURL._swiftObject)
123+
}
124+
109125
override open var description: String {
110126
return "\(String(describing: Bundle.self)) <\(bundleURL.path)> (\(isLoaded ? "loaded" : "not yet loaded"))"
111127
}

TestFoundation/TestBundle.swift

+151-30
Original file line numberDiff line numberDiff line change
@@ -44,26 +44,110 @@ internal func xdgTestHelperURL() -> URL {
4444

4545

4646
class BundlePlayground {
47+
enum ExecutableType: CaseIterable {
48+
case library
49+
case executable
50+
51+
var pathExtension: String {
52+
switch self {
53+
case .library:
54+
#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS)
55+
return "dylib"
56+
#elseif os(Windows)
57+
return "dll"
58+
#else
59+
return "so"
60+
#endif
61+
case .executable:
62+
#if os(Windows)
63+
return "exe"
64+
#else
65+
return ""
66+
#endif
67+
}
68+
}
69+
70+
var flatPathExtension: String {
71+
#if os(Windows)
72+
return self.pathExtension
73+
#else
74+
return ""
75+
#endif
76+
}
77+
78+
var fhsPrefix: String {
79+
switch self {
80+
case .executable:
81+
return "bin"
82+
case .library:
83+
return "lib"
84+
}
85+
}
86+
87+
var nonFlatFilePrefix: String {
88+
switch self {
89+
case .executable:
90+
return ""
91+
case .library:
92+
return "lib"
93+
}
94+
}
95+
}
96+
4797
enum Layout {
48-
case flat
49-
case fhsInstalled
50-
case fhsFreestanding
98+
case flat(ExecutableType)
99+
case fhs(ExecutableType)
100+
case freestanding(ExecutableType)
51101

52102
static var allApplicable: [Layout] {
53-
let layouts: [Layout] = [ .flat, .fhsInstalled, .fhsFreestanding ]
54-
55-
#if NS_FOUNDATION_ALLOWS_TESTABLE_IMPORT
56-
if Bundle._supportsFHSStyle {
57-
return layouts
58-
} else {
59-
return layouts.filter { !$0.isFHS }
103+
let layouts: [Layout] = [
104+
.flat(.library),
105+
.flat(.executable),
106+
.fhs(.library),
107+
.fhs(.executable),
108+
.freestanding(.library),
109+
.freestanding(.executable),
110+
]
111+
112+
return layouts.filter { $0.isSupported }
113+
}
114+
115+
var isFreestanding: Bool {
116+
switch self {
117+
case .freestanding(_):
118+
return true
119+
default:
120+
return false
60121
}
61-
#else
62-
return layouts.filter { !$0.isFHS }
63-
#endif
64122
}
123+
65124
var isFHS: Bool {
66-
return self == .fhsInstalled || self == .fhsFreestanding
125+
switch self {
126+
case .fhs(_):
127+
return true
128+
default:
129+
return false
130+
}
131+
}
132+
133+
var isFlat: Bool {
134+
switch self {
135+
case .flat(_):
136+
return true
137+
default:
138+
return false
139+
}
140+
}
141+
142+
var isSupported: Bool {
143+
switch self {
144+
case .flat(_):
145+
return true
146+
case .freestanding(_):
147+
return Bundle._supportsFreestandingBundles
148+
case .fhs(_):
149+
return Bundle._supportsFHSBundles
150+
}
67151
}
68152
}
69153

@@ -76,6 +160,7 @@ class BundlePlayground {
76160
let layout: Layout
77161

78162
private(set) var bundlePath: String!
163+
private(set) var mainExecutableURL: URL!
79164
private var playgroundPath: String?
80165

81166
init?(bundleName: String,
@@ -104,8 +189,8 @@ class BundlePlayground {
104189

105190
let temporaryDirectory = FileManager.default.temporaryDirectory.appendingPathComponent("TestFoundation_Playground_" + UUID().uuidString)
106191

107-
switch (layout) {
108-
case .flat:
192+
switch layout {
193+
case .flat(let executableType):
109194
do {
110195
try FileManager.default.createDirectory(atPath: temporaryDirectory.path, withIntermediateDirectories: false, attributes: nil)
111196

@@ -114,10 +199,18 @@ class BundlePlayground {
114199
try FileManager.default.createDirectory(atPath: bundleURL.path, withIntermediateDirectories: false, attributes: nil)
115200

116201
// Make a main and an auxiliary executable:
117-
guard FileManager.default.createFile(atPath: bundleURL.appendingPathComponent(bundleName).path, contents: nil) else {
202+
self.mainExecutableURL = bundleURL
203+
.appendingPathComponent(bundleName)
204+
.appendingPathExtension(executableType.flatPathExtension)
205+
206+
guard FileManager.default.createFile(atPath: mainExecutableURL.path, contents: nil) else {
118207
return false
119208
}
120-
guard FileManager.default.createFile(atPath: bundleURL.appendingPathComponent(auxiliaryExecutableName).path, contents: nil) else {
209+
210+
let auxiliaryExecutableURL = bundleURL
211+
.appendingPathComponent(auxiliaryExecutableName)
212+
.appendingPathExtension(executableType.flatPathExtension)
213+
guard FileManager.default.createFile(atPath: auxiliaryExecutableURL.path, contents: nil) else {
121214
return false
122215
}
123216

@@ -143,7 +236,7 @@ class BundlePlayground {
143236
return false
144237
}
145238

146-
case .fhsInstalled:
239+
case .fhs(let executableType):
147240
do {
148241

149242
// Create a FHS /usr/local-style hierarchy:
@@ -152,17 +245,18 @@ class BundlePlayground {
152245
try FileManager.default.createDirectory(atPath: temporaryDirectory.appendingPathComponent("lib").path, withIntermediateDirectories: false, attributes: nil)
153246

154247
// Make a main and an auxiliary executable:
155-
#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS)
156-
let pathExtension = "dylib"
157-
#else
158-
let pathExtension = "so"
159-
#endif
160-
161-
guard FileManager.default.createFile(atPath: temporaryDirectory.appendingPathComponent("lib").appendingPathComponent("lib\(bundleName).\(pathExtension)").path, contents: nil) else { return false }
248+
self.mainExecutableURL = temporaryDirectory
249+
.appendingPathComponent(executableType.fhsPrefix)
250+
.appendingPathComponent(executableType.nonFlatFilePrefix + bundleName)
251+
.appendingPathExtension(executableType.pathExtension)
252+
guard FileManager.default.createFile(atPath: mainExecutableURL.path, contents: nil) else { return false }
162253

163254
let executablesDirectory = temporaryDirectory.appendingPathComponent("libexec").appendingPathComponent("\(bundleName).executables")
164255
try FileManager.default.createDirectory(atPath: executablesDirectory.path, withIntermediateDirectories: true, attributes: nil)
165-
guard FileManager.default.createFile(atPath: executablesDirectory.appendingPathComponent(auxiliaryExecutableName).path, contents: nil) else { return false }
256+
let auxiliaryExecutableURL = executablesDirectory
257+
.appendingPathComponent(executableType.nonFlatFilePrefix + auxiliaryExecutableName)
258+
.appendingPathExtension(executableType.pathExtension)
259+
guard FileManager.default.createFile(atPath: auxiliaryExecutableURL.path, contents: nil) else { return false }
166260

167261
// Make a .resources directory in …/share:
168262
let resourcesDirectory = temporaryDirectory.appendingPathComponent("share").appendingPathComponent("\(bundleName).resources")
@@ -186,21 +280,27 @@ class BundlePlayground {
186280
return false
187281
}
188282

189-
case .fhsFreestanding:
283+
case .freestanding(let executableType):
190284
do {
191285
let bundleName = URL(string:self.bundleName)!.deletingPathExtension().path
192286

193287
try FileManager.default.createDirectory(atPath: temporaryDirectory.path, withIntermediateDirectories: false, attributes: nil)
194288

195289
// Make a main executable:
196-
guard FileManager.default.createFile(atPath: temporaryDirectory.appendingPathComponent(bundleName).path, contents: nil) else { return false }
290+
self.mainExecutableURL = temporaryDirectory
291+
.appendingPathComponent(executableType.nonFlatFilePrefix + bundleName)
292+
.appendingPathExtension(executableType.pathExtension)
293+
guard FileManager.default.createFile(atPath: mainExecutableURL.path, contents: nil) else { return false }
197294

198295
// Make a .resources directory:
199296
let resourcesDirectory = temporaryDirectory.appendingPathComponent("\(bundleName).resources")
200297
try FileManager.default.createDirectory(atPath: resourcesDirectory.path, withIntermediateDirectories: false, attributes: nil)
201298

202299
// Make an auxiliary executable:
203-
guard FileManager.default.createFile(atPath: resourcesDirectory.appendingPathComponent(auxiliaryExecutableName).path, contents: nil) else { return false }
300+
let auxiliaryExecutableURL = resourcesDirectory
301+
.appendingPathComponent(executableType.nonFlatFilePrefix + auxiliaryExecutableName)
302+
.appendingPathExtension(executableType.pathExtension)
303+
guard FileManager.default.createFile(atPath: auxiliaryExecutableURL.path, contents: nil) else { return false }
204304

205305
// Put some resources in the bundle
206306
for resourceName in resourceFilenames {
@@ -252,6 +352,7 @@ class TestBundle : XCTestCase {
252352
("test_bundlePreflight", test_bundlePreflight),
253353
("test_bundleFindExecutable", test_bundleFindExecutable),
254354
("test_bundleFindAuxiliaryExecutables", test_bundleFindAuxiliaryExecutables),
355+
("test_bundleReverseBundleLookup", test_bundleReverseBundleLookup),
255356
("test_mainBundleExecutableURL", test_mainBundleExecutableURL),
256357
]
257358
}
@@ -438,6 +539,26 @@ class TestBundle : XCTestCase {
438539
XCTAssertNil(bundle.url(forAuxiliaryExecutable: "does_not_exist_at_all"))
439540
}
440541
}
542+
543+
func test_bundleReverseBundleLookup() {
544+
_withEachPlaygroundLayout { (playground) in
545+
#if !os(Windows)
546+
if playground.layout.isFreestanding {
547+
// TODO: Freestanding bundles reverse lookup pending to be implemented on non-Windows platforms.
548+
return
549+
}
550+
#endif
551+
552+
if playground.layout.isFHS {
553+
// TODO: FHS bundles reverse lookup pending to be implemented on all platforms.
554+
return
555+
}
556+
557+
let bundle = Bundle(_executableURL: playground.mainExecutableURL)
558+
XCTAssertNotNil(bundle)
559+
XCTAssertEqual(bundle?.bundlePath, playground.bundlePath)
560+
}
561+
}
441562

442563
func test_mainBundleExecutableURL() {
443564
#if !DARWIN_COMPATIBILITY_TESTS // _CFProcessPath() is unavailable on native Foundation

0 commit comments

Comments
 (0)