Skip to content
This repository was archived by the owner on Apr 21, 2023. It is now read-only.

Commit ab86a24

Browse files
committed
[PythonKit] Better Windows pre-linked Python support
1 parent e49b245 commit ab86a24

File tree

1 file changed

+91
-72
lines changed

1 file changed

+91
-72
lines changed

PythonKit/PythonLibrary.swift

Lines changed: 91 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,9 @@ import WinSDK
2828
//===----------------------------------------------------------------------===//
2929

3030
public struct PythonLibrary {
31-
private static let shared = PythonLibrary()
3231
private static let pythonInitializeSymbolName = "Py_Initialize"
3332
private static let pythonLegacySymbolName = "PyString_AsString"
34-
private static var librarySymbolsLoaded = false
33+
private static var isPythonLibraryLoaded = false
3534

3635
#if canImport(Darwin)
3736
private static let defaultLibraryHandle = UnsafeMutableRawPointer(bitPattern: -2) // RTLD_DEFAULT
@@ -41,60 +40,61 @@ public struct PythonLibrary {
4140
private static let defaultLibraryHandle: UnsafeMutableRawPointer? = nil // Unsupported
4241
#endif
4342

44-
private let pythonLibraryHandle: UnsafeMutableRawPointer?
45-
private let isLegacyPython: Bool
46-
47-
private init() {
48-
self.pythonLibraryHandle = PythonLibrary.loadPythonLibrary()
49-
50-
// Check if Python is legacy (Python 2)
51-
self.isLegacyPython = Self.loadSymbol(pythonLibraryHandle, PythonLibrary.pythonLegacySymbolName) != nil
43+
private static let pythonLibraryHandle: UnsafeMutableRawPointer? = {
44+
let pythonLibraryHandle = Self.loadPythonLibrary()
45+
guard Self.isPythonLibraryLoaded(at: pythonLibraryHandle) else {
46+
fatalError("""
47+
Python library not found. Set the \(Environment.library.key) \
48+
environment variable with the path to a Python library.
49+
""")
50+
}
51+
Self.isPythonLibraryLoaded = true
52+
return pythonLibraryHandle
53+
}()
54+
private static let isLegacyPython: Bool = {
55+
let isLegacyPython = Self.loadSymbol(Self.pythonLibraryHandle, Self.pythonLegacySymbolName) != nil
5256
if isLegacyPython {
53-
PythonLibrary.log("Loaded legacy Python library, using legacy symbols...")
57+
Self.log("Loaded legacy Python library, using legacy symbols...")
5458
}
55-
PythonLibrary.librarySymbolsLoaded = true
56-
}
57-
58-
static func loadSymbol(
59-
_ libraryHandle: UnsafeMutableRawPointer?, _ name: String) -> UnsafeMutableRawPointer? {
60-
#if canImport(Darwin) || canImport(Glibc)
61-
return dlsym(libraryHandle, name)
62-
#elseif os(Windows)
63-
guard let libraryHandle = libraryHandle else { return nil }
64-
let moduleHandle = libraryHandle
65-
.assumingMemoryBound(to: HINSTANCE__.self)
66-
let moduleSymbol = GetProcAddress(moduleHandle, name)
67-
return unsafeBitCast(moduleSymbol, to: UnsafeMutableRawPointer?.self)
68-
#endif
69-
}
59+
return isLegacyPython
60+
}()
7061

71-
static func loadSymbol<T>(
62+
internal static func loadSymbol<T>(
7263
name: String, legacyName: String? = nil, type: T.Type = T.self) -> T {
7364
var name = name
74-
if let legacyName = legacyName, PythonLibrary.shared.isLegacyPython {
65+
if let legacyName = legacyName, self.isLegacyPython {
7566
name = legacyName
7667
}
7768

7869
log("Loading symbol '\(name)' from the Python library...")
79-
return unsafeBitCast(loadSymbol(PythonLibrary.shared.pythonLibraryHandle, name), to: type)
70+
return unsafeBitCast(self.loadSymbol(self.pythonLibraryHandle, name), to: type)
8071
}
8172
}
8273

8374
// Methods of `PythonLibrary` required to set a given Python version.
84-
public extension PythonLibrary {
85-
static func useVersion(_ major: Int, _ minor: Int? = nil) {
86-
precondition(!librarySymbolsLoaded, """
87-
Error: \(#function) should not be called after any Python library \
75+
extension PythonLibrary {
76+
private static func enforceNonLoadedPythonLibrary(function: String = #function) {
77+
precondition(!self.isPythonLibraryLoaded, """
78+
Error: \(function) should not be called after any Python library \
8879
has already been loaded.
8980
""")
81+
}
82+
83+
public static func useVersion(_ major: Int, _ minor: Int? = nil) {
84+
self.enforceNonLoadedPythonLibrary()
9085
let version = PythonVersion(major: major, minor: minor)
9186
PythonLibrary.Environment.version.set(version.versionString)
9287
}
88+
89+
public static func usePythonLibrary(at path: String) {
90+
self.enforceNonLoadedPythonLibrary()
91+
PythonLibrary.Environment.library.set(path)
92+
}
9393
}
9494

9595
// `PythonVersion` struct that defines a given Python version.
96-
private extension PythonLibrary {
97-
struct PythonVersion {
96+
extension PythonLibrary {
97+
private struct PythonVersion {
9898
let major: Int
9999
let minor: Int?
100100

@@ -116,8 +116,8 @@ private extension PythonLibrary {
116116
}
117117

118118
// `PythonLibrary.Environment` enum used to read and set environment variables.
119-
private extension PythonLibrary {
120-
enum Environment: String {
119+
extension PythonLibrary {
120+
private enum Environment: String {
121121
private static let keyPrefix = "PYTHON"
122122
private static let keySeparator = "_"
123123

@@ -145,34 +145,30 @@ private extension PythonLibrary {
145145
}
146146

147147
// Methods of `PythonLibrary` required to load the Python library.
148-
private extension PythonLibrary {
149-
static let supportedMajorVersions: [Int] = [3, 2]
150-
static let supportedMinorVersions: [Int] = Array(0...30).reversed()
148+
extension PythonLibrary {
149+
private static let supportedMajorVersions: [Int] = [3, 2]
150+
private static let supportedMinorVersions: [Int] = Array(0...30).reversed()
151151

152-
static let libraryPathVersionCharacter: Character = ":"
152+
private static let libraryPathVersionCharacter: Character = ":"
153153

154154
#if canImport(Darwin)
155-
static var libraryNames = ["Python.framework/Versions/:/Python"]
156-
static var libraryPathExtensions = [""]
157-
static var librarySearchPaths = ["", "/usr/local/Frameworks/"]
158-
static var libraryVersionSeparator = "."
155+
private static var libraryNames = ["Python.framework/Versions/:/Python"]
156+
private static var libraryPathExtensions = [""]
157+
private static var librarySearchPaths = ["", "/usr/local/Frameworks/"]
158+
private static var libraryVersionSeparator = "."
159159
#elseif os(Linux)
160-
static var libraryNames = ["libpython:", "libpython:m"]
161-
static var libraryPathExtensions = [".so"]
162-
static var librarySearchPaths = [""]
163-
static var libraryVersionSeparator = "."
160+
private static var libraryNames = ["libpython:", "libpython:m"]
161+
private static var libraryPathExtensions = [".so"]
162+
private static var librarySearchPaths = [""]
163+
private static var libraryVersionSeparator = "."
164164
#elseif os(Windows)
165-
static var libraryNames = ["python:"]
166-
static var libraryPathExtensions = [".dll"]
167-
static var librarySearchPaths = [""]
168-
static var libraryVersionSeparator = ""
165+
private static var libraryNames = ["python:"]
166+
private static var libraryPathExtensions = [".dll"]
167+
private static var librarySearchPaths = [""]
168+
private static var libraryVersionSeparator = ""
169169
#endif
170170

171-
private static var isPythonLibraryPreloaded: Bool {
172-
return self.loadSymbol(self.defaultLibraryHandle, self.pythonInitializeSymbolName) != nil
173-
}
174-
175-
static let libraryPaths: [String] = {
171+
private static let libraryPaths: [String] = {
176172
var libraryPaths: [String] = []
177173
for librarySearchPath in librarySearchPaths {
178174
for libraryName in libraryNames {
@@ -185,14 +181,40 @@ private extension PythonLibrary {
185181
}
186182
return libraryPaths
187183
}()
184+
185+
private static func loadSymbol(
186+
_ libraryHandle: UnsafeMutableRawPointer?, _ name: String) -> UnsafeMutableRawPointer? {
187+
#if canImport(Darwin) || canImport(Glibc)
188+
return dlsym(libraryHandle, name)
189+
#elseif os(Windows)
190+
guard let libraryHandle = libraryHandle else { return nil }
191+
let moduleHandle = libraryHandle
192+
.assumingMemoryBound(to: HINSTANCE__.self)
193+
let moduleSymbol = GetProcAddress(moduleHandle, name)
194+
return unsafeBitCast(moduleSymbol, to: UnsafeMutableRawPointer?.self)
195+
#endif
196+
}
197+
198+
private static func isPythonLibraryLoaded(at pythonLibraryHandle: UnsafeMutableRawPointer? = nil) -> Bool {
199+
let pythonLibraryHandle = pythonLibraryHandle ?? self.defaultLibraryHandle
200+
return self.loadSymbol(pythonLibraryHandle, self.pythonInitializeSymbolName) != nil
201+
}
188202

189-
static func loadPythonLibrary() -> UnsafeMutableRawPointer? {
190-
if self.isPythonLibraryPreloaded {
191-
return self.defaultLibraryHandle
203+
private static func loadPythonLibrary() -> UnsafeMutableRawPointer? {
204+
let pythonLibraryHandle: UnsafeMutableRawPointer?
205+
if self.isPythonLibraryLoaded() {
206+
pythonLibraryHandle = self.defaultLibraryHandle
192207
}
193208
else if let pythonLibraryPath = Environment.library.value {
194-
return self.loadPythonLibrary(at: pythonLibraryPath)
209+
pythonLibraryHandle = self.loadPythonLibrary(at: pythonLibraryPath)
195210
}
211+
else {
212+
pythonLibraryHandle = self.findAndLoadExternalPythonLibrary()
213+
}
214+
return pythonLibraryHandle
215+
}
216+
217+
private static func findAndLoadExternalPythonLibrary() -> UnsafeMutableRawPointer? {
196218
for majorVersion in supportedMajorVersions {
197219
for minorVersion in supportedMinorVersions {
198220
for libraryPath in libraryPaths {
@@ -205,13 +227,10 @@ private extension PythonLibrary {
205227
}
206228
}
207229
}
208-
fatalError("""
209-
Python library not found. Set the \(Environment.library.key) \
210-
environment variable with the path to a Python library.
211-
""")
230+
return nil
212231
}
213232

214-
static func loadPythonLibrary(
233+
private static func loadPythonLibrary(
215234
at path: String, version: PythonVersion) -> UnsafeMutableRawPointer? {
216235
let versionString = version.versionString
217236

@@ -231,8 +250,8 @@ private extension PythonLibrary {
231250
return self.loadPythonLibrary(at: path)
232251
}
233252

234-
static func loadPythonLibrary(at path: String) -> UnsafeMutableRawPointer? {
235-
log("Trying to load library at '\(path)'...")
253+
private static func loadPythonLibrary(at path: String) -> UnsafeMutableRawPointer? {
254+
self.log("Trying to load library at '\(path)'...")
236255
#if canImport(Darwin) || canImport(Glibc)
237256
// Must be RTLD_GLOBAL because subsequent .so files from the imported python
238257
// modules may depend on this .so file.
@@ -242,15 +261,15 @@ private extension PythonLibrary {
242261
#endif
243262

244263
if pythonLibraryHandle != nil {
245-
log("Library at '\(path)' was sucessfully loaded.")
264+
self.log("Library at '\(path)' was sucessfully loaded.")
246265
}
247266
return pythonLibraryHandle
248267
}
249268
}
250269

251270
// Methods of `PythonLibrary` used for logging messages.
252-
private extension PythonLibrary {
253-
static func log(_ message: String) {
271+
extension PythonLibrary {
272+
private static func log(_ message: String) {
254273
guard Environment.loaderLogging.value != nil else { return }
255274
fputs(message + "\n", stderr)
256275
}

0 commit comments

Comments
 (0)