diff --git a/.github/workflows/continuous-integration-windows.yml b/.github/workflows/continuous-integration-windows.yml deleted file mode 100644 index b0db6ef..0000000 --- a/.github/workflows/continuous-integration-windows.yml +++ /dev/null @@ -1,35 +0,0 @@ -name: Continuous Integration – Windows - -on: - - push - - pull_request - -jobs: - continuous-integration: - strategy: - matrix: - os: - - windows-latest - runs-on: ${{ matrix.os }} - steps: - - uses: actions/checkout@v2 - - uses: seanmiddleditch/gha-setup-vsdevenv@master - - name: Install Swift (Swift Toolchain) - run: | - Install-Binary -Url "https://swift.org/builds/development/windows10/swift-DEVELOPMENT-SNAPSHOT-2020-11-17-a/swift-DEVELOPMENT-SNAPSHOT-2020-11-17-a-windows10.exe" -Name "installer.exe" -ArgumentList ("-q") - - name: Install Swift (Environment Variables) - run: | - echo "SDKROOT=C:\Library\Developer\Platforms\Windows.platform\Developer\SDKs\Windows.sdk" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append - echo "DEVELOPER_DIR=C:\Library\Developer" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append - - name: Install Swift (Paths) - run: | - echo "C:\Library\Swift-development\bin;C:\Library\icu-67\usr\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append - echo "C:\Library\Developer\Toolchains\unknown-Asserts-development.xctoolchain\usr\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append - - name: Install Swift (Windows SDK) - run: | - Copy-Item "$env:SDKROOT\usr\share\ucrt.modulemap" -destination "$env:UniversalCRTSdkDir\Include\$env:UCRTVersion\ucrt\module.modulemap" - Copy-Item "$env:SDKROOT\usr\share\visualc.modulemap" -destination "$env:VCToolsInstallDir\include\module.modulemap" - Copy-Item "$env:SDKROOT\usr\share\visualc.apinotes" -destination "$env:VCToolsInstallDir\include\visualc.apinotes" - Copy-Item "$env:SDKROOT\usr\share\winsdk.modulemap" -destination "$env:UniversalCRTSdkDir\Include\$env:UCRTVersion\um\module.modulemap" - - name: Build - run: swift build -c release --enable-test-discovery -Xlinker /INCREMENTAL:NO diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index 3432790..e265347 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -10,15 +10,16 @@ jobs: matrix: os: - ubuntu-latest - - macOS-latest + - macos-latest runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v1 - - name: Test (Python 2) - run: swift test --enable-test-discovery - env: - PYTHON_VERSION: 2 - - name: Test (Python 3) + - uses: actions/setup-python@v5 + with: + python-version: '3.x' + - name: Test run: swift test --enable-test-discovery + if: runner.os != 'Windows' env: PYTHON_VERSION: 3 + PYTHON_LOADER_LOGGING: TRUE diff --git a/.gitignore b/.gitignore index aee7c89..3d50865 100644 --- a/.gitignore +++ b/.gitignore @@ -6,8 +6,8 @@ !.travis.yml # Swift -build/ -Package.resolved +/build/ +/Package.resolved # Python *.pyc @@ -15,6 +15,7 @@ Package.resolved # Temporary Items *.tmp *.tmp.* +temp # Virtual Environments /venv*/ @@ -23,8 +24,8 @@ Package.resolved *.override.* # Extra Directories -/Assets -/Extra +/Assets/ +/Extra/ # Xcode xcuserdata/ diff --git a/.gitkeep b/.gitkeep new file mode 100644 index 0000000..5114dca --- /dev/null +++ b/.gitkeep @@ -0,0 +1 @@ +55307D11-9B02-4882-B275-49222602DC5E diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..8b46202 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,29 @@ +# Agents + +This file provides development guidelines for AI agents working with this Swift Package Manager project. + +## Development + +### Code Patterns + +- Use appropriate logging frameworks instead of print statements. +- Follow protocol-oriented design patterns. +- **CRITICAL: If they are listed on `Package.swift`, always check `FoundationKit`, `LoggerKit`, and other core frameworks before implementing basic functionality.** These frameworks contain extensive extensions and utilities that avoid code duplication (typically you can find them in the project parent directory). + +#### Core Frameworks Functionality Examples + +- **NSError**: `NSError(description:, recoverySuggestion:)` convenience initializer. +- **NSAppleScript**: `execute()` method with proper Swift error handling. +- **ProcessInfo**: `launchExtensionsPaneInSystemSettings()`, `launchPrivacyAndSecurityPaneInSystemSettings()`. +- **URL**: `open(withAppBundleIdentifier:)` for cross-platform URL opening. +- **Process**: Enhanced execution utilities with output capture. +- **UserDefaults**: `@UserDefaults.Wrapper` property wrapper for cleaner app settings. + +### Building and Testing + +- **Always verify changes work** by building or testing before considering task complete. +- Changes are not complete until successfully built and verified. + +--- + +**IMPORTANT**: This is a generic development guide for AI agents shared and available on multiple projects, so avoid adding project-specific information here. Refer to `README.md` file if available and the source code for project-specific details. diff --git a/CMakeLists.txt b/CMakeLists.txt deleted file mode 100644 index 0791330..0000000 --- a/CMakeLists.txt +++ /dev/null @@ -1,13 +0,0 @@ -cmake_minimum_required(VERSION 3.15.1) - -project(PythonKit - LANGUAGES Swift) - -list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake/modules) - -set(CMAKE_Swift_MODULE_DIRECTORY ${CMAKE_BINARY_DIR}/swift) - -include(SwiftSupport) - -add_subdirectory(PythonKit) -add_subdirectory(cmake/modules) diff --git a/PythonKit/CMakeLists.txt b/PythonKit/CMakeLists.txt deleted file mode 100644 index 2ad6007..0000000 --- a/PythonKit/CMakeLists.txt +++ /dev/null @@ -1,28 +0,0 @@ -add_library(PythonKit - NumpyConversion.swift - Python.swift - PythonLibrary+Symbols.swift - PythonLibrary.swift) -set_target_properties(PythonKit PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${CMAKE_CURRENT_BINARY_DIR}) - -get_swift_host_arch(swift_arch) -get_swift_host_os(swift_os) -install(TARGETS PythonKit - ARCHIVE DESTINATION lib/swift$<$>:_static>/${swift_os} - LIBRARY DESTINATION lib/swift$<$>:_static>/${swift_os} - RUNTIME DESTINATION bin) -if(CMAKE_SYSTEM_NAME STREQUAL Darwin) - install(FILES $/PythonKit.swiftdoc - DESTINATION lib/swift$<$>:_static>/${swift_os}/PythonKit.swiftmodule - RENAME ${swift_arch}.swiftdoc) - install(FILES $/PythonKit.swiftmodule - DESTINATION lib/swift$<$>:_static>/${swift_os}/PythonKit.swiftmodule - RENAME ${swift_arch}.swiftmodule) -else() - install(FILES - $/PythonKit.swiftdoc - $/PythonKit.swiftmodule - DESTINATION lib/swift$<$>:_static>/${swift_os}/${swift_arch}) -endif() -set_property(GLOBAL APPEND PROPERTY PythonKit_EXPORTS PythonKit) diff --git a/PythonKit/Info.plist b/PythonKit/Info.plist deleted file mode 100644 index 1dcdd98..0000000 --- a/PythonKit/Info.plist +++ /dev/null @@ -1,26 +0,0 @@ - - - - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - FMWK - CFBundleShortVersionString - 1.0 - CFBundleVersion - $(CURRENT_PROJECT_VERSION) - NSHumanReadableCopyright - Copyright © 2018 Pedro José Pereira Vieito. All rights reserved. - NSPrincipalClass - - - diff --git a/PythonKit/NumpyConversion.swift b/PythonKit/NumpyConversion.swift index fc73a33..e0c81a2 100644 --- a/PythonKit/NumpyConversion.swift +++ b/PythonKit/NumpyConversion.swift @@ -110,18 +110,18 @@ where Element : NumpyScalarCompatible { guard Element.numpyScalarTypes.contains(numpyArray.dtype) else { return nil } - + // Only 1-D `ndarray` instances can be converted to `Array`. let pyShape = numpyArray.__array_interface__["shape"] guard let shape = Array(pyShape) else { return nil } guard shape.count == 1 else { return nil } - + // Make sure that the array is contiguous in memory. This does a copy if // the array is not already contiguous in memory. let contiguousNumpyArray = np.ascontiguousarray(numpyArray) - + guard let ptrVal = UInt(contiguousNumpyArray.__array_interface__["data"].tuple2.0) else { return nil @@ -137,7 +137,7 @@ where Element : NumpyScalarCompatible { self.init(repeating: dummyPointer.move(), count: scalarCount) dummyPointer.deallocate() withUnsafeMutableBufferPointer { buffPtr in - buffPtr.baseAddress!.assign(from: ptr, count: scalarCount) + buffPtr.baseAddress!.update(from: ptr, count: scalarCount) } } } diff --git a/PythonKit/Python.swift b/PythonKit/Python.swift index 3df6176..35d2fcb 100644 --- a/PythonKit/Python.swift +++ b/PythonKit/Python.swift @@ -39,7 +39,7 @@ typealias OwnedPyObjectPointer = PyObjectPointer @usableFromInline @_fixed_layout final class PyReference { private var pointer: OwnedPyObjectPointer - + // This `PyReference`, once deleted, will make no delta change to the // python object's reference count. It will however, retain the reference for // the lifespan of this object. @@ -47,21 +47,21 @@ final class PyReference { self.pointer = pointer Py_IncRef(pointer) } - + // This `PyReference` adopts the +1 reference and will decrement it in the // future. init(consuming pointer: PyObjectPointer) { self.pointer = pointer } - + deinit { Py_DecRef(pointer) } - + var borrowedPyObject: PyObjectPointer { return pointer } - + var ownedPyObject: OwnedPyObjectPointer { Py_IncRef(pointer) return pointer @@ -90,26 +90,26 @@ final class PyReference { public struct PythonObject { /// The underlying `PyReference`. fileprivate var reference: PyReference - + @usableFromInline init(_ pointer: PyReference) { reference = pointer } - + /// Creates a new instance and a new reference. init(_ pointer: OwnedPyObjectPointer) { reference = PyReference(pointer) } - + /// Creates a new instance consuming the specified `PyObject` pointer. init(consuming pointer: PyObjectPointer) { reference = PyReference(consuming: pointer) } - + fileprivate var borrowedPyObject: PyObjectPointer { return reference.borrowedPyObject } - + fileprivate var ownedPyObject: OwnedPyObjectPointer { return reference.ownedPyObject } @@ -165,7 +165,7 @@ fileprivate extension PythonConvertible { var borrowedPyObject: PyObjectPointer { return pythonObject.borrowedPyObject } - + var ownedPyObject: OwnedPyObjectPointer { return pythonObject.ownedPyObject } @@ -189,7 +189,7 @@ extension PythonObject : PythonConvertible, ConvertibleFromPython { public init(_ object: PythonObject) { self.init(consuming: object.ownedPyObject) } - + public var pythonObject: PythonObject { return self } } @@ -210,7 +210,7 @@ public extension PythonObject { public enum PythonError : Error, Equatable { /// A Python runtime exception, produced by calling a Python function. case exception(PythonObject, traceback: PythonObject?) - + /// A failed call on a `PythonObject`. /// Reasons for failure include: /// - A non-callable Python object was called. @@ -218,7 +218,7 @@ public enum PythonError : Error, Equatable { /// object. /// - An invalid keyword argument was specified. case invalidCall(PythonObject) - + /// A module import error. case invalidModule(String) } @@ -248,14 +248,14 @@ extension PythonError : CustomStringConvertible { // active. private func throwPythonErrorIfPresent() throws { if PyErr_Occurred() == nil { return } - + var type: PyObjectPointer? var value: PyObjectPointer? var traceback: PyObjectPointer? - + // Fetch the exception and clear the exception state. PyErr_Fetch(&type, &value, &traceback) - + // The value for the exception may not be set but the type always should be. let resultObject = PythonObject(consuming: value ?? type!) let tracebackObject = traceback.flatMap { PythonObject(consuming: $0) } @@ -271,11 +271,11 @@ private func throwPythonErrorIfPresent() throws { /// `dynamicallyCall` until further discussion/design. public struct ThrowingPythonObject { private var base: PythonObject - + fileprivate init(_ base: PythonObject) { self.base = base } - + /// Call `self` with the specified positional arguments. /// If the call fails for some reason, `PythonError.invalidCall` is thrown. /// - Precondition: `self` must be a Python callable. @@ -285,7 +285,7 @@ public struct ThrowingPythonObject { withArguments args: PythonConvertible...) throws -> PythonObject { return try dynamicallyCall(withArguments: args) } - + /// Call `self` with the specified positional arguments. /// If the call fails for some reason, `PythonError.invalidCall` is thrown. /// - Precondition: `self` must be a Python callable. @@ -294,18 +294,18 @@ public struct ThrowingPythonObject { public func dynamicallyCall( withArguments args: [PythonConvertible] = []) throws -> PythonObject { try throwPythonErrorIfPresent() - + // Positional arguments are passed as a tuple of objects. let argTuple = pyTuple(args.map { $0.pythonObject }) defer { Py_DecRef(argTuple) } - + // Python calls always return a non-null object when successful. If the // Python function produces the equivalent of C `void`, it returns the // `None` object. A `null` result of `PyObjectCall` happens when there is an // error, like `self` not being a Python callable. let selfObject = base.ownedPyObject defer { Py_DecRef(selfObject) } - + guard let result = PyObject_CallObject(selfObject, argTuple) else { // If a Python exception was thrown, throw a corresponding Swift error. try throwPythonErrorIfPresent() @@ -313,7 +313,7 @@ public struct ThrowingPythonObject { } return PythonObject(consuming: result) } - + /// Call `self` with the specified arguments. /// If the call fails for some reason, `PythonError.invalidCall` is thrown. /// - Precondition: `self` must be a Python callable. @@ -322,13 +322,28 @@ public struct ThrowingPythonObject { public func dynamicallyCall( withKeywordArguments args: KeyValuePairs = [:]) throws -> PythonObject { + return try _dynamicallyCall(args) + } + + /// Alias for the function above that lets the caller dynamically construct the argument list, without using a dictionary literal. + /// This function must be called explicitly on a `PythonObject` because `@dynamicCallable` does not recognize it. + @discardableResult + public func dynamicallyCall( + withKeywordArguments args: + [(key: String, value: PythonConvertible)] = []) throws -> PythonObject { + return try _dynamicallyCall(args) + } + + /// Implementation of `dynamicallyCall(withKeywordArguments)`. + private func _dynamicallyCall(_ args: T) throws -> PythonObject + where T.Element == (key: String, value: PythonConvertible) { try throwPythonErrorIfPresent() - + // An array containing positional arguments. var positionalArgs: [PythonObject] = [] // A dictionary object for storing keyword arguments, if any exist. var kwdictObject: OwnedPyObjectPointer? = nil - + for (key, value) in args { if key.isEmpty { positionalArgs.append(value.pythonObject) @@ -345,20 +360,20 @@ public struct ThrowingPythonObject { Py_DecRef(k) Py_DecRef(v) } - + defer { Py_DecRef(kwdictObject) } // Py_DecRef is `nil` safe. - + // Positional arguments are passed as a tuple of objects. let argTuple = pyTuple(positionalArgs) defer { Py_DecRef(argTuple) } - + // Python calls always return a non-null object when successful. If the // Python function produces the equivalent of C `void`, it returns the // `None` object. A `null` result of `PyObjectCall` happens when there is an // error, like `self` not being a Python callable. let selfObject = base.ownedPyObject defer { Py_DecRef(selfObject) } - + guard let result = PyObject_Call(selfObject, argTuple, kwdictObject) else { // If a Python exception was thrown, throw a corresponding Swift error. try throwPythonErrorIfPresent() @@ -366,7 +381,7 @@ public struct ThrowingPythonObject { } return PythonObject(consuming: result) } - + /// Converts to a 2-tuple, if possible. public var tuple2: (PythonObject, PythonObject)? { let ct = base.checking @@ -375,7 +390,7 @@ public struct ThrowingPythonObject { } return (elt0, elt1) } - + /// Converts to a 3-tuple, if possible. public var tuple3: (PythonObject, PythonObject, PythonObject)? { let ct = base.checking @@ -384,7 +399,7 @@ public struct ThrowingPythonObject { } return (elt0, elt1, elt2) } - + /// Converts to a 4-tuple, if possible. public var tuple4: (PythonObject, PythonObject, PythonObject, PythonObject)? { let ct = base.checking @@ -394,6 +409,10 @@ public struct ThrowingPythonObject { } return (elt0, elt1, elt2, elt3) } + + public var count: Int? { + base.checking.count + } } @@ -415,11 +434,11 @@ public extension PythonObject { public struct CheckingPythonObject { /// The underlying `PythonObject`. private var base: PythonObject - + fileprivate init(_ base: PythonObject) { self.base = base } - + public subscript(dynamicMember name: String) -> PythonObject? { get { let selfObject = base.ownedPyObject @@ -432,7 +451,7 @@ public struct CheckingPythonObject { return PythonObject(consuming: result) } } - + /// Access the element corresponding to the specified `PythonConvertible` /// values representing a key. /// - Note: This is equivalent to `object[key]` in Python. @@ -444,7 +463,7 @@ public struct CheckingPythonObject { Py_DecRef(keyObject) Py_DecRef(selfObject) } - + // `PyObject_GetItem` returns +1 reference. if let result = PyObject_GetItem(selfObject, keyObject) { return PythonObject(consuming: result) @@ -459,7 +478,7 @@ public struct CheckingPythonObject { Py_DecRef(keyObject) Py_DecRef(selfObject) } - + if let newValue = newValue { let newValueObject = newValue.ownedPyObject PyObject_SetItem(selfObject, keyObject, newValueObject) @@ -470,7 +489,7 @@ public struct CheckingPythonObject { } } } - + /// Access the element corresponding to the specified `PythonConvertible` /// values representing a key. /// - Note: This is equivalent to `object[key]` in Python. @@ -482,7 +501,7 @@ public struct CheckingPythonObject { self[key] = newValue } } - + /// Converts to a 2-tuple, if possible. public var tuple2: (PythonObject, PythonObject)? { guard let elt0 = self[0], let elt1 = self[1] else { @@ -490,7 +509,7 @@ public struct CheckingPythonObject { } return (elt0, elt1) } - + /// Converts to a 3-tuple, if possible. public var tuple3: (PythonObject, PythonObject, PythonObject)? { guard let elt0 = self[0], let elt1 = self[1], let elt2 = self[2] else { @@ -498,7 +517,7 @@ public struct CheckingPythonObject { } return (elt0, elt1, elt2) } - + /// Converts to a 4-tuple, if possible. public var tuple4: (PythonObject, PythonObject, PythonObject, PythonObject)? { guard let elt0 = self[0], let elt1 = self[1], @@ -507,6 +526,10 @@ public struct CheckingPythonObject { } return (elt0, elt1, elt2, elt3) } + + public var count: Int? { + Int(Python.len(base)) + } } //===----------------------------------------------------------------------===// @@ -536,7 +559,7 @@ public extension PythonObject { defer { Py_DecRef(selfObject) } let valueObject = newValue.ownedPyObject defer { Py_DecRef(valueObject) } - + if PyObject_SetAttrString(selfObject, memberName, valueObject) == -1 { try! throwPythonErrorIfPresent() fatalError(""" @@ -546,7 +569,7 @@ public extension PythonObject { } } } - + /// Access the element corresponding to the specified `PythonConvertible` /// values representing a key. /// - Note: This is equivalent to `object[key]` in Python. @@ -564,7 +587,7 @@ public extension PythonObject { checking[key] = newValue } } - + /// Converts to a 2-tuple. var tuple2: (PythonObject, PythonObject) { guard let result = checking.tuple2 else { @@ -572,7 +595,7 @@ public extension PythonObject { } return result } - + /// Converts to a 3-tuple. var tuple3: (PythonObject, PythonObject, PythonObject) { guard let result = checking.tuple3 else { @@ -580,7 +603,7 @@ public extension PythonObject { } return result } - + /// Converts to a 4-tuple. var tuple4: (PythonObject, PythonObject, PythonObject, PythonObject) { guard let result = checking.tuple4 else { @@ -588,7 +611,7 @@ public extension PythonObject { } return result } - + /// Call `self` with the specified positional arguments. /// - Precondition: `self` must be a Python callable. /// - Parameter args: Positional arguments for the Python callable. @@ -597,7 +620,7 @@ public extension PythonObject { withArguments args: [PythonConvertible] = []) -> PythonObject { return try! throwing.dynamicallyCall(withArguments: args) } - + /// Call `self` with the specified arguments. /// - Precondition: `self` must be a Python callable. /// - Parameter args: Positional or keyword arguments for the Python callable. @@ -607,6 +630,15 @@ public extension PythonObject { KeyValuePairs = [:]) -> PythonObject { return try! throwing.dynamicallyCall(withKeywordArguments: args) } + + /// Alias for the function above that lets the caller dynamically construct the argument list, without using a dictionary literal. + /// This function must be called explicitly on a `PythonObject` because `@dynamicCallable` does not recognize it. + @discardableResult + func dynamicallyCall( + withKeywordArguments args: + [(key: String, value: PythonConvertible)] = []) -> PythonObject { + return try! throwing.dynamicallyCall(withKeywordArguments: args) + } } //===----------------------------------------------------------------------===// @@ -641,27 +673,28 @@ public let Python = PythonInterface() public struct PythonInterface { /// A dictionary of the Python builtins. public let builtins: PythonObject - + init() { Py_Initialize() // Initialize Python builtins = PythonObject(PyEval_GetBuiltins()) - + // Runtime Fixes: PyRun_SimpleString(""" import sys import os - - # Some Python modules expect to have at least one argument in `sys.argv`. + + # Some Python modules expect to have at least one argument in `sys.argv`: sys.argv = [""] # Some Python modules require `sys.executable` to return the path # to the Python interpreter executable. In Darwin, Python 3 returns the - # main process executable path instead. + # main process executable path instead: if sys.version_info.major == 3 and sys.platform == "darwin": - sys.executable = os.path.join(sys.exec_prefix, "bin", "python3") + executable_name = "python{}.{}".format(sys.version_info.major, sys.version_info.minor) + sys.executable = os.path.join(sys.exec_prefix, "bin", executable_name) """) } - + public func attemptImport(_ name: String) throws -> PythonObject { guard let module = PyImport_ImportModule(name) else { try throwPythonErrorIfPresent() @@ -669,26 +702,35 @@ public struct PythonInterface { } return PythonObject(consuming: module) } - + public func `import`(_ name: String) -> PythonObject { return try! attemptImport(name) } - + public subscript(dynamicMember name: String) -> PythonObject { return builtins[name] } - + // The Python runtime version. // Equivalent to `sys.version` in Python. public var version: PythonObject { return self.import("sys").version } - + // The Python runtime version information. // Equivalent to `sys.version_info` in Python. public var versionInfo: PythonObject { return self.import("sys").version_info } + + /// Emulates a Python `with` statement. + /// - Parameter object: A context manager object. + /// - Parameter body: A closure to call on the result of `object.__enter__()`. + public func with(_ object: PythonObject, _ body: (PythonObject) throws -> Void) rethrows { + let yieldValue = object.__enter__() + try body(yieldValue) + yieldValue.__exit__() + } } //===----------------------------------------------------------------------===// @@ -697,14 +739,13 @@ public struct PythonInterface { // Create a Python tuple object with the specified elements. private func pyTuple(_ vals: T) -> OwnedPyObjectPointer - where T.Element : PythonConvertible { - - let tuple = PyTuple_New(vals.count)! - for (index, element) in vals.enumerated() { - // `PyTuple_SetItem` steals the reference of the object stored. - PyTuple_SetItem(tuple, index, element.ownedPyObject) - } - return tuple +where T.Element : PythonConvertible { + let tuple = PyTuple_New(vals.count)! + for (index, element) in vals.enumerated() { + // `PyTuple_SetItem` steals the reference of the object stored. + PyTuple_SetItem(tuple, index, element.ownedPyObject) + } + return tuple } public extension PythonObject { @@ -713,15 +754,15 @@ public extension PythonObject { init(tupleOf elements: PythonConvertible...) { self.init(tupleContentsOf: elements) } - + init(tupleContentsOf elements: T) - where T.Element == PythonConvertible { - self.init(consuming: pyTuple(elements.map { $0.pythonObject })) + where T.Element == PythonConvertible { + self.init(consuming: pyTuple(elements.map { $0.pythonObject })) } - + init(tupleContentsOf elements: T) - where T.Element : PythonConvertible { - self.init(consuming: pyTuple(elements)) + where T.Element : PythonConvertible { + self.init(consuming: pyTuple(elements)) } } @@ -734,14 +775,14 @@ public extension PythonObject { private func isType(_ object: PythonObject, type: PyObjectPointer) -> Bool { let typePyRef = PythonObject(type) - + let result = Python.isinstance(object, typePyRef) - + // We cannot use the normal failable Bool initializer from `PythonObject` // here because would cause an infinite loop. let pyObject = result.ownedPyObject defer { Py_DecRef(pyObject) } - + // Anything not equal to `Py_ZeroStruct` is truthy. return pyObject != _Py_ZeroStruct } @@ -749,13 +790,13 @@ private func isType(_ object: PythonObject, extension Bool : PythonConvertible, ConvertibleFromPython { public init?(_ pythonObject: PythonObject) { guard isType(pythonObject, type: PyBool_Type) else { return nil } - + let pyObject = pythonObject.ownedPyObject defer { Py_DecRef(pyObject) } - + self = pyObject == _Py_TrueStruct } - + public var pythonObject: PythonObject { _ = Python // Ensure Python is initialized. return PythonObject(consuming: PyBool_FromLong(self ? 1 : 0)) @@ -766,14 +807,14 @@ extension String : PythonConvertible, ConvertibleFromPython { public init?(_ pythonObject: PythonObject) { let pyObject = pythonObject.ownedPyObject defer { Py_DecRef(pyObject) } - + guard let cString = PyString_AsString(pyObject) else { PyErr_Clear() return nil } self.init(cString: cString) } - + public var pythonObject: PythonObject { _ = Python // Ensure Python is initialized. let v = utf8CString.withUnsafeBufferPointer { @@ -793,17 +834,17 @@ fileprivate extension PythonObject { ) -> T? { let pyObject = ownedPyObject defer { Py_DecRef(pyObject) } - + assert(PyErr_Occurred() == nil, "Python error occurred somewhere but wasn't handled") - + let value = converter(pyObject) guard value != errorValue || PyErr_Occurred() == nil else { PyErr_Clear() return nil } return value - + } } @@ -817,7 +858,7 @@ extension Int : PythonConvertible, ConvertibleFromPython { } self = value } - + public var pythonObject: PythonObject { _ = Python // Ensure Python is initialized. return PythonObject(consuming: PyInt_FromLong(self)) @@ -835,7 +876,7 @@ extension UInt : PythonConvertible, ConvertibleFromPython { } self = value } - + public var pythonObject: PythonObject { _ = Python // Ensure Python is initialized. return PythonObject(consuming: PyInt_FromSize_t(self)) @@ -852,7 +893,7 @@ extension Double : PythonConvertible, ConvertibleFromPython { } self = value } - + public var pythonObject: PythonObject { _ = Python // Ensure Python is initialized. return PythonObject(consuming: PyFloat_FromDouble(self)) @@ -871,7 +912,7 @@ extension Int8 : PythonConvertible, ConvertibleFromPython { guard let i = Int(pythonObject) else { return nil } self.init(i) } - + public var pythonObject: PythonObject { return Int(self).pythonObject } @@ -882,7 +923,7 @@ extension Int16 : PythonConvertible, ConvertibleFromPython { guard let i = Int(pythonObject) else { return nil } self.init(i) } - + public var pythonObject: PythonObject { return Int(self).pythonObject } @@ -893,7 +934,7 @@ extension Int32 : PythonConvertible, ConvertibleFromPython { guard let i = Int(pythonObject) else { return nil } self.init(i) } - + public var pythonObject: PythonObject { return Int(self).pythonObject } @@ -904,7 +945,7 @@ extension Int64 : PythonConvertible, ConvertibleFromPython { guard let i = Int(pythonObject) else { return nil } self.init(i) } - + public var pythonObject: PythonObject { return Int(self).pythonObject } @@ -915,7 +956,7 @@ extension UInt8 : PythonConvertible, ConvertibleFromPython { guard let i = UInt(pythonObject) else { return nil } self.init(i) } - + public var pythonObject: PythonObject { return UInt(self).pythonObject } @@ -926,7 +967,7 @@ extension UInt16 : PythonConvertible, ConvertibleFromPython { guard let i = UInt(pythonObject) else { return nil } self.init(i) } - + public var pythonObject: PythonObject { return UInt(self).pythonObject } @@ -937,7 +978,7 @@ extension UInt32 : PythonConvertible, ConvertibleFromPython { guard let i = UInt(pythonObject) else { return nil } self.init(i) } - + public var pythonObject: PythonObject { return UInt(self).pythonObject } @@ -948,7 +989,7 @@ extension UInt64 : PythonConvertible, ConvertibleFromPython { guard let i = UInt(pythonObject) else { return nil } self.init(i) } - + public var pythonObject: PythonObject { return UInt(self).pythonObject } @@ -961,7 +1002,7 @@ extension Float : PythonConvertible, ConvertibleFromPython { guard let v = Double(pythonObject) else { return nil } self.init(v) } - + public var pythonObject: PythonObject { return Double(self).pythonObject } @@ -1046,12 +1087,12 @@ extension Dictionary : ConvertibleFromPython where Key : ConvertibleFromPython, Value : ConvertibleFromPython { public init?(_ pythonDict: PythonObject) { self = [:] - + // Iterate over the Python dictionary, converting its keys and values to // Swift `Key` and `Value` pairs. var key, value: PyObjectPointer? var position: Int = 0 - + while PyDict_Next( pythonDict.borrowedPyObject, &position, &key, &value) != 0 { @@ -1141,7 +1182,7 @@ where Bound : ConvertibleFromPython { private typealias PythonBinaryOp = (OwnedPyObjectPointer?, OwnedPyObjectPointer?) -> OwnedPyObjectPointer? private typealias PythonUnaryOp = - (OwnedPyObjectPointer?) -> OwnedPyObjectPointer? + (OwnedPyObjectPointer?) -> OwnedPyObjectPointer? private func performBinaryOp( _ op: PythonBinaryOp, lhs: PythonObject, rhs: PythonObject) -> PythonObject { @@ -1163,7 +1204,7 @@ public extension PythonObject { static func + (lhs: PythonObject, rhs: PythonObject) -> PythonObject { return performBinaryOp(PyNumber_Add, lhs: lhs, rhs: rhs) } - + static func - (lhs: PythonObject, rhs: PythonObject) -> PythonObject { return performBinaryOp(PyNumber_Subtract, lhs: lhs, rhs: rhs) } @@ -1171,23 +1212,23 @@ public extension PythonObject { static func * (lhs: PythonObject, rhs: PythonObject) -> PythonObject { return performBinaryOp(PyNumber_Multiply, lhs: lhs, rhs: rhs) } - + static func / (lhs: PythonObject, rhs: PythonObject) -> PythonObject { return performBinaryOp(PyNumber_TrueDivide, lhs: lhs, rhs: rhs) } - + static func += (lhs: inout PythonObject, rhs: PythonObject) { lhs = performBinaryOp(PyNumber_InPlaceAdd, lhs: lhs, rhs: rhs) } - + static func -= (lhs: inout PythonObject, rhs: PythonObject) { lhs = performBinaryOp(PyNumber_InPlaceSubtract, lhs: lhs, rhs: rhs) } - + static func *= (lhs: inout PythonObject, rhs: PythonObject) { lhs = performBinaryOp(PyNumber_InPlaceMultiply, lhs: lhs, rhs: rhs) } - + static func /= (lhs: inout PythonObject, rhs: PythonObject) { lhs = performBinaryOp(PyNumber_InPlaceTrueDivide, lhs: lhs, rhs: rhs) } @@ -1227,15 +1268,15 @@ extension PythonObject : SignedNumeric { public init(exactly value: T) { self.init(Int(value)) } - + public typealias Magnitude = PythonObject - + public var magnitude: PythonObject { return self < 0 ? -self : self } - //override the default implementation of - prefix function - //from SignedNumeric (https://bugs.swift.org/browse/SR-13293) + // Override the default implementation of `-` prefix function + // from SignedNumeric (https://bugs.swift.org/browse/SR-13293). public static prefix func - (_ operand: Self) -> Self { return performUnaryOp(PyNumber_Negative, operand: operand) } @@ -1243,11 +1284,11 @@ extension PythonObject : SignedNumeric { extension PythonObject : Strideable { public typealias Stride = PythonObject - + public func distance(to other: PythonObject) -> Stride { return other - self } - + public func advanced(by stride: Stride) -> PythonObject { return self + stride } @@ -1277,7 +1318,7 @@ extension PythonObject : Equatable, Comparable { public static func == (lhs: PythonObject, rhs: PythonObject) -> Bool { return lhs.compared(to: rhs, byOp: Py_EQ) } - + public static func != (lhs: PythonObject, rhs: PythonObject) -> Bool { return lhs.compared(to: rhs, byOp: Py_NE) } @@ -1289,11 +1330,11 @@ extension PythonObject : Equatable, Comparable { public static func <= (lhs: PythonObject, rhs: PythonObject) -> Bool { return lhs.compared(to: rhs, byOp: Py_LE) } - + public static func > (lhs: PythonObject, rhs: PythonObject) -> Bool { return lhs.compared(to: rhs, byOp: Py_GT) } - + public static func >= (lhs: PythonObject, rhs: PythonObject) -> Bool { return lhs.compared(to: rhs, byOp: Py_GE) } @@ -1316,27 +1357,27 @@ public extension PythonObject { } return PythonObject(consuming: result) } - + static func == (lhs: PythonObject, rhs: PythonObject) -> PythonObject { return lhs.compared(to: rhs, byOp: Py_EQ) } - + static func != (lhs: PythonObject, rhs: PythonObject) -> PythonObject { return lhs.compared(to: rhs, byOp: Py_NE) } - + static func < (lhs: PythonObject, rhs: PythonObject) -> PythonObject { return lhs.compared(to: rhs, byOp: Py_LT) } - + static func <= (lhs: PythonObject, rhs: PythonObject) -> PythonObject { return lhs.compared(to: rhs, byOp: Py_LE) } - + static func > (lhs: PythonObject, rhs: PythonObject) -> PythonObject { return lhs.compared(to: rhs, byOp: Py_GT) } - + static func >= (lhs: PythonObject, rhs: PythonObject) -> PythonObject { return lhs.compared(to: rhs, byOp: Py_GE) } @@ -1354,19 +1395,19 @@ extension PythonObject : Hashable { extension PythonObject : MutableCollection { public typealias Index = PythonObject public typealias Element = PythonObject - + public var startIndex: Index { return 0 } - + public var endIndex: Index { return Python.len(self) } - + public func index(after i: Index) -> Index { return i + PythonObject(1) } - + public subscript(index: PythonObject) -> PythonObject { get { return self[index as PythonConvertible] @@ -1380,7 +1421,7 @@ extension PythonObject : MutableCollection { extension PythonObject : Sequence { public struct Iterator : IteratorProtocol { fileprivate let pythonIterator: PythonObject - + public func next() -> PythonObject? { guard let result = PyIter_Next(self.pythonIterator.borrowedPyObject) else { try! throwPythonErrorIfPresent() @@ -1389,7 +1430,7 @@ extension PythonObject : Sequence { return PythonObject(consuming: result) } } - + public func makeIterator() -> Iterator { guard let result = PyObject_GetIter(borrowedPyObject) else { try! throwPythonErrorIfPresent() @@ -1400,6 +1441,12 @@ extension PythonObject : Sequence { } } +extension PythonObject { + public var count: Int { + checking.count! + } +} + //===----------------------------------------------------------------------===// // `ExpressibleByLiteral` conformances //===----------------------------------------------------------------------===// @@ -1426,7 +1473,462 @@ extension PythonObject : ExpressibleByArrayLiteral, ExpressibleByDictionaryLiter } public typealias Key = PythonObject public typealias Value = PythonObject + + // Preserves element order in the final Python object, unlike + // `Dictionary.pythonObject`. When keys are duplicated, throw the same + // runtime error as `Swift.Dictionary.init(dictionaryLiteral:)`. This + // differs from Python's key uniquing semantics, which silently override an + // existing key with the next one it encounters. public init(dictionaryLiteral elements: (PythonObject, PythonObject)...) { - self.init(Dictionary(elements, uniquingKeysWith: { lhs, _ in lhs })) + _ = Python // Ensure Python is initialized. + let dict = PyDict_New()! + for (key, value) in elements { + let k = key.ownedPyObject + let v = value.ownedPyObject + + // Use Python's native key checking instead of querying whether + // `elements` contains the key. Although this could theoretically + // produce different results, it produces the Python object we want. + switch PyDict_Contains(dict, k) { + case 0: + PyDict_SetItem(dict, k, v) + case 1: + fatalError("Dictionary literal contains duplicate keys") + default: + try! throwPythonErrorIfPresent() + fatalError("No result or error checking whether \(elements) contains \(key)") + } + + Py_DecRef(k) + Py_DecRef(v) + } + self.init(consuming: dict) + } +} + +public struct PythonBytes : PythonConvertible, ConvertibleFromPython, Hashable { + public private(set) var pythonObject: PythonObject + + public init?(_ pythonObject: PythonObject) { + // We try to get the string/size pointers out. If it works, hooray, this is a bytes + // otherwise it isn't. + let pyObject = pythonObject.ownedPyObject + defer { Py_DecRef(pyObject) } + + var length = 0 + var buffer: UnsafeMutablePointer? = nil + + switch PyBytes_AsStringAndSize(pyObject, &buffer, &length) { + case 0: + self.pythonObject = pythonObject + default: + return nil + } + } + + @inlinable + public init(_ bytes: Bytes) where Bytes.Element == UInt8 { + let possibleSelf = bytes.withContiguousStorageIfAvailable { storagePtr in + PythonBytes.fromBytePointer(storagePtr) + } + if let actualSelf = possibleSelf { + self = actualSelf + } else { + let temporaryBuffer = Array(bytes) + self = temporaryBuffer.withUnsafeBufferPointer { + PythonBytes.fromBytePointer($0) + } + } + } + + @inlinable + public init(_ bytes: Bytes) where Bytes.Element == Int8 { + let possibleSelf = bytes.withContiguousStorageIfAvailable { storagePtr in + PythonBytes.fromBytePointer(storagePtr) + } + if let actualSelf = possibleSelf { + self = actualSelf + } else { + let temporaryBuffer = Array(bytes) + self = temporaryBuffer.withUnsafeBufferPointer { + PythonBytes.fromBytePointer($0) + } + } + } + + private init(bytesObject: PythonObject) { + self.pythonObject = bytesObject + } + + @usableFromInline + static func fromBytePointer(_ bytes: UnsafeBufferPointer) -> PythonBytes { + bytes.withMemoryRebound(to: Int8.self) { reboundPtr in + PythonBytes.fromBytePointer(reboundPtr) + } + } + + @usableFromInline + static func fromBytePointer(_ bytes: UnsafeBufferPointer) -> PythonBytes { + let v = PyBytes_FromStringAndSize(bytes.baseAddress, bytes.count)! + return PythonBytes(bytesObject: PythonObject(consuming: v)) + } + + public func withUnsafeBytes( + _ callback: (UnsafeRawBufferPointer) throws -> ReturnValue + ) rethrows -> ReturnValue { + let pyObject = self.pythonObject.ownedPyObject + defer { Py_DecRef(pyObject) } + + var length = 0 + var buffer: UnsafeMutablePointer? = nil + + switch PyBytes_AsStringAndSize(pyObject, &buffer, &length) { + case 0: + let buffer = UnsafeRawBufferPointer(start: buffer, count: length) + return try callback(buffer) + default: + try! throwPythonErrorIfPresent() + fatalError("No result or error getting interior buffer for bytes \(self)") + } + } +} + +//===----------------------------------------------------------------------===// +// PythonFunction - create functions in Swift that can be called from Python +//===----------------------------------------------------------------------===// + +/// Create functions in Swift that can be called from Python +/// +/// Example: +/// +/// The Python code `map(lambda(x: x * 2), [10, 12, 14])` would be written as: +/// +/// Python.map(PythonFunction { x in x * 2 }, [10, 12, 14]) // [20, 24, 28] +/// +final class PyFunction { + enum CallingConvention { + case varArgs + case varArgsWithKeywords + } + + /// Allows `PyFunction` to store Python functions with more than one possible calling convention + var callingConvention: CallingConvention + + /// `arguments` is a Python tuple. + typealias VarArgsFunction = ( + _ arguments: PythonObject) throws -> PythonConvertible + + /// `arguments` is a Python tuple. + /// `keywordArguments` is an OrderedDict in Python 3.6 and later, or a dict otherwise. + typealias VarArgsWithKeywordsFunction = ( + _ arguments: PythonObject, + _ keywordArguments: PythonObject) throws -> PythonConvertible + + /// Has the same memory layout as any other function with the Swift calling convention + private typealias Storage = () throws -> PythonConvertible + + /// Stores all function pointers in the same stored property. `callAsFunction` casts this into the desired type. + private var callSwiftFunction: Storage + + init(_ callSwiftFunction: @escaping VarArgsFunction) { + self.callingConvention = .varArgs + self.callSwiftFunction = unsafeBitCast(callSwiftFunction, to: Storage.self) + } + + init(_ callSwiftFunction: @escaping VarArgsWithKeywordsFunction) { + self.callingConvention = .varArgsWithKeywords + self.callSwiftFunction = unsafeBitCast(callSwiftFunction, to: Storage.self) + } + + private func checkConvention(_ calledConvention: CallingConvention) { + precondition(callingConvention == calledConvention, + "Called PyFunction with convention \(calledConvention), but expected \(callingConvention)") + } + + func callAsFunction(_ argumentsTuple: PythonObject) throws -> PythonConvertible { + checkConvention(.varArgs) + let callSwiftFunction = unsafeBitCast(self.callSwiftFunction, to: VarArgsFunction.self) + return try callSwiftFunction(argumentsTuple) + } + + func callAsFunction(_ argumentsTuple: PythonObject, _ keywordArguments: PythonObject) throws -> PythonConvertible { + checkConvention(.varArgsWithKeywords) + let callSwiftFunction = unsafeBitCast(self.callSwiftFunction, to: VarArgsWithKeywordsFunction.self) + return try callSwiftFunction(argumentsTuple, keywordArguments) + } +} + +public struct PythonFunction { + /// Called directly by the Python C API + private var function: PyFunction + + @_disfavoredOverload + public init(_ fn: @escaping (PythonObject) throws -> PythonConvertible) { + function = PyFunction { argumentsAsTuple in + return try fn(argumentsAsTuple[0]) + } + } + + /// For cases where the Swift function should accept more (or less) than one parameter, accept an ordered array of all arguments instead. + public init(_ fn: @escaping ([PythonObject]) throws -> PythonConvertible) { + function = PyFunction { argumentsAsTuple in + return try fn(argumentsAsTuple.map { $0 }) + } + } + + /// For cases where the Swift function should accept keyword arguments as `**kwargs` in Python. + /// `**kwargs` must preserve order from Python 3.6 onward, similarly to + /// Swift `KeyValuePairs` and unlike `Dictionary`. `KeyValuePairs` cannot be + /// mutated, so the next best solution is to use `[KeyValuePairs.Element]`. + public init(_ fn: @escaping ([PythonObject], [(key: String, value: PythonObject)]) throws -> PythonConvertible) { + function = PyFunction { argumentsAsTuple, keywordArgumentsAsDictionary in + var kwargs: [(String, PythonObject)] = [] + for keyAndValue in keywordArgumentsAsDictionary.items() { + let (key, value) = keyAndValue.tuple2 + kwargs.append((String(key)!, value)) + } + return try fn(argumentsAsTuple.map { $0 }, kwargs) + } + } +} + +extension PythonFunction : PythonConvertible { + public var pythonObject: PythonObject { + // Ensure Python is initialized, and check for version match. + let versionMajor = Python.versionInfo.major + let versionMinor = Python.versionInfo.minor + guard (versionMajor == 3 && versionMinor >= 1) || versionMajor > 3 else { + fatalError("PythonFunction only supports Python 3.1 and above.") + } + + let destructor: @convention(c) (PyObjectPointer?) -> Void = { capsulePointer in + let funcPointer = PyCapsule_GetPointer(capsulePointer, nil) + Unmanaged.fromOpaque(funcPointer).release() + } + let funcPointer = Unmanaged.passRetained(function).toOpaque() + let capsulePointer = PyCapsule_New( + funcPointer, + nil, + unsafeBitCast(destructor, to: OpaquePointer.self) + ) + + var methodDefinition: UnsafeMutablePointer + switch function.callingConvention { + case .varArgs: + methodDefinition = PythonFunction.sharedMethodDefinition + case .varArgsWithKeywords: + methodDefinition = PythonFunction.sharedMethodWithKeywordsDefinition + } + let pyFuncPointer = PyCFunction_NewEx( + methodDefinition, + capsulePointer, + nil + ) + + return PythonObject(consuming: pyFuncPointer) + } +} + +fileprivate extension PythonFunction { + static let sharedMethodDefinition: UnsafeMutablePointer = { + let name: StaticString = "pythonkit_swift_function" + // `utf8Start` is a property of StaticString, thus, it has a stable pointer. + let namePointer = UnsafeRawPointer(name.utf8Start).assumingMemoryBound(to: Int8.self) + + let methodImplementationPointer = unsafeBitCast( + PythonFunction.sharedMethodImplementation, to: OpaquePointer.self) + + /// The standard calling convention. See Python C API docs + let METH_VARARGS = 0x0001 as Int32 + + let pointer = UnsafeMutablePointer.allocate(capacity: 1) + pointer.pointee = PyMethodDef( + ml_name: namePointer, + ml_meth: methodImplementationPointer, + ml_flags: METH_VARARGS, + ml_doc: nil + ) + + return pointer + }() + + static let sharedMethodWithKeywordsDefinition: UnsafeMutablePointer = { + let name: StaticString = "pythonkit_swift_function_with_keywords" + // `utf8Start` is a property of StaticString, thus, it has a stable pointer. + let namePointer = UnsafeRawPointer(name.utf8Start).assumingMemoryBound(to: Int8.self) + + let methodImplementationPointer = unsafeBitCast( + PythonFunction.sharedMethodWithKeywordsImplementation, to: OpaquePointer.self) + + /// A combination of flags that supports `**kwargs`. See Python C API docs + let METH_VARARGS = 0x0001 as Int32 + let METH_KEYWORDS = 0x0002 as Int32 + + let pointer = UnsafeMutablePointer.allocate(capacity: 1) + pointer.pointee = PyMethodDef( + ml_name: namePointer, + ml_meth: methodImplementationPointer, + ml_flags: METH_VARARGS | METH_KEYWORDS, + ml_doc: nil + ) + + return pointer + }() + + private static let sharedMethodImplementation: @convention(c) ( + PyObjectPointer?, PyObjectPointer? + ) -> PyObjectPointer? = { context, argumentsPointer in + guard let argumentsPointer = argumentsPointer, let capsulePointer = context else { + return nil + } + + let funcPointer = PyCapsule_GetPointer(capsulePointer, nil) + let function = Unmanaged.fromOpaque(funcPointer).takeUnretainedValue() + + do { + let argumentsAsTuple = PythonObject(consuming: argumentsPointer) + return try function(argumentsAsTuple).ownedPyObject + } catch { + PythonFunction.setPythonError(swiftError: error) + return nil // This must only be `nil` if an exception has been set + } + } + + private static let sharedMethodWithKeywordsImplementation: @convention(c) ( + PyObjectPointer?, PyObjectPointer?, PyObjectPointer? + ) -> PyObjectPointer? = { context, argumentsPointer, keywordArgumentsPointer in + guard let argumentsPointer = argumentsPointer, let capsulePointer = context else { + return nil + } + + let funcPointer = PyCapsule_GetPointer(capsulePointer, nil) + let function = Unmanaged.fromOpaque(funcPointer).takeUnretainedValue() + + do { + let argumentsAsTuple = PythonObject(consuming: argumentsPointer) + var keywordArgumentsAsDictionary: PythonObject + if let keywordArgumentsPointer = keywordArgumentsPointer { + keywordArgumentsAsDictionary = PythonObject(consuming: keywordArgumentsPointer) + } else { + keywordArgumentsAsDictionary = [:] + } + return try function(argumentsAsTuple, keywordArgumentsAsDictionary).ownedPyObject + } catch { + PythonFunction.setPythonError(swiftError: error) + return nil // This must only be `nil` if an exception has been set + } + } + + private static func setPythonError(swiftError: Error) { + if let pythonObject = swiftError as? PythonObject { + if Bool(Python.isinstance(pythonObject, Python.BaseException))! { + // We are an instance of an Exception class type. Set the exception class to the object's type: + PyErr_SetString(Python.type(pythonObject).ownedPyObject, pythonObject.description) + } else { + // Assume an actual class type was thrown (rather than an instance) + // Crashes if it was neither a subclass of BaseException nor an instance of one. + // + // We *could* check to see whether `pythonObject` is a class here and fall back + // to the default case of setting a generic Exception, below, but we also want + // people to write valid code. + PyErr_SetString(pythonObject.ownedPyObject, pythonObject.description) + } + } else { + // Make a generic Python Exception based on the Swift Error: + PyErr_SetString(Python.Exception.ownedPyObject, "\(type(of: swiftError)) raised in Swift: \(swiftError)") + } + } +} + +extension PythonObject: Error {} + +// From Python's C Headers: +struct PyMethodDef { + /// The name of the built-in function/method + var ml_name: UnsafePointer + + /// The C function that implements it. + /// Since this accepts multiple function signatures, the Swift type must be opaque here. + var ml_meth: OpaquePointer + + /// Combination of METH_xxx flags, which mostly describe the args expected by the C func + var ml_flags: Int32 + + /// The __doc__ attribute, or NULL + var ml_doc: UnsafePointer? +} + +//===----------------------------------------------------------------------===// +// PythonInstanceMethod - create functions that can be bound to a Python object +//===----------------------------------------------------------------------===// + +public struct PythonInstanceMethod { + private var function: PythonFunction + + @_disfavoredOverload + public init(_ fn: @escaping (PythonObject) throws -> PythonConvertible) { + function = PythonFunction(fn) + } + + public init(_ fn: @escaping ([PythonObject]) throws -> PythonConvertible) { + function = PythonFunction(fn) + } + + public init(_ fn: @escaping ([PythonObject], [(key: String, value: PythonObject)]) throws -> PythonConvertible) { + function = PythonFunction(fn) + } +} + +extension PythonInstanceMethod : PythonConvertible { + public var pythonObject: PythonObject { + let pyFuncPointer = function.pythonObject.ownedPyObject + let methodPointer = PyInstanceMethod_New(pyFuncPointer) + return PythonObject(consuming: methodPointer) + } +} + +//===----------------------------------------------------------------------===// +// PythonClass - construct subclasses of a Python class +//===----------------------------------------------------------------------===// + +public struct PythonClass { + private var typeObject: PythonObject + + public struct Members: ExpressibleByDictionaryLiteral { + public typealias Key = String + public typealias Value = PythonConvertible + + var dictionary: [String: PythonObject] + + public init(dictionaryLiteral elements: (Key, Value)...) { + let castedElements = elements.map { (key, value) in + (key, value.pythonObject) + } + + dictionary = Dictionary(castedElements, uniquingKeysWith: { _, _ in + fatalError("Dictionary literal contains duplicate keys") + }) + } + } + + public init(_ name: String, superclasses: [PythonObject] = [], members: Members = [:]) { + self.init(name, superclasses: superclasses, members: members.dictionary) + } + + @_disfavoredOverload + public init(_ name: String, superclasses: [PythonObject] = [], members: [String: PythonObject] = [:]) { + var trueSuperclasses = superclasses + if !trueSuperclasses.contains(Python.object) { + trueSuperclasses.append(Python.object) + } + + let superclassesTuple = PythonObject(tupleContentsOf: trueSuperclasses) + typeObject = Python.type(name, superclassesTuple, members.pythonObject) + } +} + +extension PythonClass : PythonConvertible { + public var pythonObject: PythonObject { + typeObject } } diff --git a/PythonKit/PythonLibrary+Symbols.swift b/PythonKit/PythonLibrary+Symbols.swift index 2421492..c4db681 100644 --- a/PythonKit/PythonLibrary+Symbols.swift +++ b/PythonKit/PythonLibrary+Symbols.swift @@ -15,16 +15,17 @@ //===----------------------------------------------------------------------===// //===----------------------------------------------------------------------===// -// Required Python typealias and constants. +// Required Python typealiases and constants. //===----------------------------------------------------------------------===// @usableFromInline typealias PyObjectPointer = UnsafeMutableRawPointer +typealias PyMethodDefPointer = UnsafeMutableRawPointer typealias PyCCharPointer = UnsafePointer typealias PyBinaryOperation = @convention(c) (PyObjectPointer?, PyObjectPointer?) -> PyObjectPointer? typealias PyUnaryOperation = - @convention(c) (PyObjectPointer?) -> PyObjectPointer? + @convention(c) (PyObjectPointer?) -> PyObjectPointer? let Py_LT: Int32 = 0 let Py_LE: Int32 = 1 @@ -56,6 +57,27 @@ let PyEval_GetBuiltins: @convention(c) () -> PyObjectPointer = let PyRun_SimpleString: @convention(c) (PyCCharPointer) -> Void = PythonLibrary.loadSymbol(name: "PyRun_SimpleString") +let PyCFunction_NewEx: @convention(c) (PyMethodDefPointer, UnsafeMutableRawPointer, UnsafeMutableRawPointer?) -> PyObjectPointer = + PythonLibrary.loadSymbol(name: "PyCFunction_NewEx") + +let PyInstanceMethod_New: @convention(c) (PyObjectPointer) -> PyObjectPointer = + PythonLibrary.loadSymbol(name: "PyInstanceMethod_New") + +/// The last argument would ideally be of type `@convention(c) (PyObjectPointer?) -> Void`. +/// Due to SR-15871 and the source-breaking nature of potential workarounds, the +/// static typing was removed. The caller must now manually cast a closure to +/// `OpaquePointer` before passing it into `PyCapsule_New`. +let PyCapsule_New: @convention(c) ( + UnsafeMutableRawPointer, UnsafePointer?, + OpaquePointer) -> PyObjectPointer = + PythonLibrary.loadSymbol(name: "PyCapsule_New") + +let PyCapsule_GetPointer: @convention(c) (PyObjectPointer?, UnsafePointer?) -> UnsafeMutableRawPointer = + PythonLibrary.loadSymbol(name: "PyCapsule_GetPointer") + +let PyErr_SetString: @convention(c) (PyObjectPointer, UnsafePointer?) -> Void = + PythonLibrary.loadSymbol(name: "PyErr_SetString") + let PyErr_Occurred: @convention(c) () -> PyObjectPointer? = PythonLibrary.loadSymbol(name: "PyErr_Occurred") @@ -71,6 +93,10 @@ let PyErr_Fetch: @convention(c) ( let PyDict_New: @convention(c) () -> PyObjectPointer? = PythonLibrary.loadSymbol(name: "PyDict_New") +let PyDict_Contains: @convention(c) ( + PyObjectPointer?, PyObjectPointer?) -> Int32 = + PythonLibrary.loadSymbol(name: "PyDict_Contains") + let PyDict_SetItem: @convention(c) ( PyObjectPointer?, PyObjectPointer, PyObjectPointer) -> Void = PythonLibrary.loadSymbol(name: "PyDict_SetItem") @@ -185,6 +211,20 @@ let PyString_FromStringAndSize: @convention(c) ( name: "PyUnicode_DecodeUTF8", legacyName: "PyString_FromStringAndSize") +let PyBytes_FromStringAndSize: @convention(c) ( + PyCCharPointer?, Int) -> (PyObjectPointer?) = + PythonLibrary.loadSymbol( + name: "PyBytes_FromStringAndSize", + legacyName: "PyString_FromStringAndSize") + +let PyBytes_AsStringAndSize: @convention(c) ( + PyObjectPointer, + UnsafeMutablePointer?>?, + UnsafeMutablePointer?) -> CInt = + PythonLibrary.loadSymbol( + name: "PyBytes_AsStringAndSize", + legacyName: "PyString_AsStringAndSize") + let _Py_ZeroStruct: PyObjectPointer = PythonLibrary.loadSymbol(name: "_Py_ZeroStruct") diff --git a/PythonKit/PythonLibrary.swift b/PythonKit/PythonLibrary.swift index d205717..ab94a3f 100644 --- a/PythonKit/PythonLibrary.swift +++ b/PythonKit/PythonLibrary.swift @@ -18,6 +18,8 @@ import Darwin #elseif canImport(Glibc) import Glibc +#elseif canImport(Musl) +import Musl #elseif os(Windows) import CRT import WinSDK @@ -28,100 +30,118 @@ import WinSDK //===----------------------------------------------------------------------===// public struct PythonLibrary { + public enum Error: Swift.Error, Equatable, CustomStringConvertible { + case pythonLibraryNotFound + + public var description: String { + switch self { + case .pythonLibraryNotFound: + return """ + Python library not found. Set the \(Environment.library.key) \ + environment variable with the path to a Python library. + """ + } + } + } + private static let pythonInitializeSymbolName = "Py_Initialize" private static let pythonLegacySymbolName = "PyString_AsString" - private static var isPythonLibraryLoaded = false - - #if canImport(Darwin) + +#if canImport(Darwin) private static let defaultLibraryHandle = UnsafeMutableRawPointer(bitPattern: -2) // RTLD_DEFAULT - #elseif canImport(Glibc) +#else private static let defaultLibraryHandle: UnsafeMutableRawPointer? = nil // RTLD_DEFAULT - #elseif os(Windows) - private static let defaultLibraryHandle: UnsafeMutableRawPointer? = nil // Unsupported - #endif - - private static let pythonLibraryHandle: UnsafeMutableRawPointer? = { - let pythonLibraryHandle = Self.loadPythonLibrary() - guard Self.isPythonLibraryLoaded(at: pythonLibraryHandle) else { - fatalError(""" - Python library not found. Set the \(Environment.library.key) \ - environment variable with the path to a Python library. - """) +#endif + + private static var isPythonLibraryLoaded = false + private static var _pythonLibraryHandle: UnsafeMutableRawPointer? + private static var pythonLibraryHandle: UnsafeMutableRawPointer? { + try! PythonLibrary.loadLibrary() + return self._pythonLibraryHandle + } + + /// Tries to load the Python library, will throw an error if no compatible library is found. + public static func loadLibrary() throws { + guard !self.isPythonLibraryLoaded else { return } + let pythonLibraryHandle = self.loadPythonLibrary() + guard self.isPythonLibraryLoaded(at: pythonLibraryHandle) else { + throw Error.pythonLibraryNotFound } - Self.isPythonLibraryLoaded = true - return pythonLibraryHandle - }() + self.isPythonLibraryLoaded = true + self._pythonLibraryHandle = pythonLibraryHandle + } + private static let isLegacyPython: Bool = { - let isLegacyPython = Self.loadSymbol(Self.pythonLibraryHandle, Self.pythonLegacySymbolName) != nil + let isLegacyPython = PythonLibrary.loadSymbol(PythonLibrary.pythonLibraryHandle, PythonLibrary.pythonLegacySymbolName) != nil if isLegacyPython { - Self.log("Loaded legacy Python library, using legacy symbols...") + PythonLibrary.log("Loaded legacy Python library, using legacy symbols...") } return isLegacyPython }() - + internal static func loadSymbol( name: String, legacyName: String? = nil, type: T.Type = T.self) -> T { - var name = name - if let legacyName = legacyName, self.isLegacyPython { - name = legacyName + var name = name + if let legacyName = legacyName, self.isLegacyPython { + name = legacyName + } + + log("Loading symbol '\(name)' from the Python library...") + return unsafeBitCast(self.loadSymbol(self.pythonLibraryHandle, name), to: type) } - - log("Loading symbol '\(name)' from the Python library...") - return unsafeBitCast(self.loadSymbol(self.pythonLibraryHandle, name), to: type) - } } // Methods of `PythonLibrary` required to load the Python library. extension PythonLibrary { private static let supportedMajorVersions: [Int] = [3, 2] private static let supportedMinorVersions: [Int] = Array(0...30).reversed() - + private static let libraryPathVersionCharacter: Character = ":" - - #if canImport(Darwin) + +#if canImport(Darwin) private static var libraryNames = ["Python.framework/Versions/:/Python"] private static var libraryPathExtensions = [""] - private static var librarySearchPaths = ["", "/usr/local/Frameworks/"] + private static var librarySearchPaths = ["", "/opt/homebrew/Frameworks/", "/usr/local/Frameworks/"] private static var libraryVersionSeparator = "." - #elseif os(Linux) +#elseif os(Linux) private static var libraryNames = ["libpython:", "libpython:m"] private static var libraryPathExtensions = [".so"] private static var librarySearchPaths = [""] private static var libraryVersionSeparator = "." - #elseif os(Windows) +#elseif os(Windows) private static var libraryNames = ["python:"] private static var libraryPathExtensions = [".dll"] private static var librarySearchPaths = [""] private static var libraryVersionSeparator = "" - #endif - +#endif + private static let libraryPaths: [String] = { var libraryPaths: [String] = [] for librarySearchPath in librarySearchPaths { for libraryName in libraryNames { for libraryPathExtension in libraryPathExtensions { let libraryPath = - librarySearchPath + libraryName + libraryPathExtension + librarySearchPath + libraryName + libraryPathExtension libraryPaths.append(libraryPath) } } } return libraryPaths }() - + private static func loadSymbol( _ libraryHandle: UnsafeMutableRawPointer?, _ name: String) -> UnsafeMutableRawPointer? { - #if canImport(Darwin) || canImport(Glibc) - return dlsym(libraryHandle, name) - #elseif os(Windows) - guard let libraryHandle = libraryHandle else { return nil } - let moduleHandle = libraryHandle - .assumingMemoryBound(to: HINSTANCE__.self) - let moduleSymbol = GetProcAddress(moduleHandle, name) - return unsafeBitCast(moduleSymbol, to: UnsafeMutableRawPointer?.self) - #endif - } - +#if os(Windows) + guard let libraryHandle = libraryHandle else { return nil } + let moduleHandle = libraryHandle + .assumingMemoryBound(to: HINSTANCE__.self) + let moduleSymbol = GetProcAddress(moduleHandle, name) + return unsafeBitCast(moduleSymbol, to: UnsafeMutableRawPointer?.self) +#else + return dlsym(libraryHandle, name) +#endif + } + private static func isPythonLibraryLoaded(at pythonLibraryHandle: UnsafeMutableRawPointer? = nil) -> Bool { let pythonLibraryHandle = pythonLibraryHandle ?? self.defaultLibraryHandle return self.loadSymbol(pythonLibraryHandle, self.pythonInitializeSymbolName) != nil @@ -140,7 +160,7 @@ extension PythonLibrary { } return pythonLibraryHandle } - + private static func findAndLoadExternalPythonLibrary() -> UnsafeMutableRawPointer? { for majorVersion in supportedMajorVersions { for minorVersion in supportedMinorVersions { @@ -148,7 +168,7 @@ extension PythonLibrary { let version = PythonVersion(major: majorVersion, minor: minorVersion) guard let pythonLibraryHandle = loadPythonLibrary( at: libraryPath, version: version) else { - continue + continue } return pythonLibraryHandle } @@ -156,45 +176,52 @@ extension PythonLibrary { } return nil } - + private static func loadPythonLibrary( at path: String, version: PythonVersion) -> UnsafeMutableRawPointer? { - let versionString = version.versionString - - if let requiredPythonVersion = Environment.version.value { - let requiredMajorVersion = Int(requiredPythonVersion) - if requiredPythonVersion != versionString, - requiredMajorVersion != version.major { - return nil + let versionString = version.versionString + + if let requiredPythonVersion = Environment.version.value { + let requiredMajorVersion = Int(requiredPythonVersion) + if requiredPythonVersion != versionString, + requiredMajorVersion != version.major { + return nil + } } + + let libraryVersionString = versionString + .split(separator: PythonVersion.versionSeparator) + .joined(separator: libraryVersionSeparator) + let path = path.split(separator: libraryPathVersionCharacter) + .joined(separator: libraryVersionString) + return self.loadPythonLibrary(at: path) } - - let libraryVersionString = versionString - .split(separator: PythonVersion.versionSeparator) - .joined(separator: libraryVersionSeparator) - let path = path.split(separator: libraryPathVersionCharacter) - .joined(separator: libraryVersionString) - return self.loadPythonLibrary(at: path) - } - + private static func loadPythonLibrary(at path: String) -> UnsafeMutableRawPointer? { self.log("Trying to load library at '\(path)'...") - #if canImport(Darwin) || canImport(Glibc) +#if os(Windows) + let pythonLibraryHandle = UnsafeMutableRawPointer(LoadLibraryA(path)) +#else // Must be RTLD_GLOBAL because subsequent .so files from the imported python // modules may depend on this .so file. let pythonLibraryHandle = dlopen(path, RTLD_LAZY | RTLD_GLOBAL) - #elseif os(Windows) - let pythonLibraryHandle = UnsafeMutableRawPointer(LoadLibraryA(path)) - #endif - + if pythonLibraryHandle == nil { + self.log("Failed to load library at '\(path)'.") + if let errorCString = dlerror() { + let errorString = String(cString: errorCString) + self.log("Reason for failure: \(errorString)") + } + } +#endif + if pythonLibraryHandle != nil { - self.log("Library at '\(path)' was sucessfully loaded.") + self.log("Library at '\(path)' was successfully loaded.") } return pythonLibraryHandle } } -// Methods of `PythonLibrary` required to set a given Python version. +// Methods of `PythonLibrary` required to set a given Python version or library path. extension PythonLibrary { private static func enforceNonLoadedPythonLibrary(function: String = #function) { precondition(!self.isPythonLibraryLoaded, """ @@ -202,33 +229,44 @@ extension PythonLibrary { has already been loaded. """) } - - public static func useVersion(_ major: Int, _ minor: Int? = nil) { + + /// Use the Python library with the specified version. + /// - Parameters: + /// - major: Major version or nil to use any Python version. + /// - minor: Minor version or nil to use any minor version. + public static func useVersion(_ major: Int?, _ minor: Int? = nil) { self.enforceNonLoadedPythonLibrary() let version = PythonVersion(major: major, minor: minor) PythonLibrary.Environment.version.set(version.versionString) } - - public static func useLibrary(at path: String) { + + /// Use the Python library at the specified path. + /// - Parameter path: Path of the Python library to load or nil to use the default search path. + public static func useLibrary(at path: String?) { self.enforceNonLoadedPythonLibrary() - PythonLibrary.Environment.library.set(path) + PythonLibrary.Environment.library.set(path ?? "") } } // `PythonVersion` struct that defines a given Python version. extension PythonLibrary { private struct PythonVersion { - let major: Int + let major: Int? let minor: Int? - + static let versionSeparator: Character = "." - - init(major: Int, minor: Int?) { + + init(major: Int?, minor: Int?) { + precondition(!(major == nil && minor != nil), """ + Error: The Python library minor version cannot be specified \ + without the major version. + """) self.major = major self.minor = minor } - + var versionString: String { + guard let major = major else { return "" } var versionString = String(major) if let minor = minor { versionString += "\(PythonVersion.versionSeparator)\(minor)" @@ -243,26 +281,28 @@ extension PythonLibrary { private enum Environment: String { private static let keyPrefix = "PYTHON" private static let keySeparator = "_" - + case library = "LIBRARY" case version = "VERSION" case loaderLogging = "LOADER_LOGGING" - + var key: String { return Environment.keyPrefix + Environment.keySeparator + rawValue } - + var value: String? { - guard let value = getenv(key) else { return nil } - return String(cString: value) + guard let cString = getenv(key) else { return nil } + let value = String(cString: cString) + guard !value.isEmpty else { return nil } + return value } - + func set(_ value: String) { - #if canImport(Darwin) || canImport(Glibc) - setenv(key, value, 1) - #elseif os(Windows) +#if os(Windows) _putenv_s(key, value) - #endif +#else + setenv(key, value, 1) +#endif } } } diff --git a/README.md b/README.md index d91e1b4..83feb56 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,10 @@ + # PythonKit +[![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Fpvieito%2FPythonKit%2Fbadge%3Ftype%3Dswift-versions)](https://swiftpackageindex.com/pvieito/PythonKit) +[![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Fpvieito%2FPythonKit%2Fbadge%3Ftype%3Dplatforms)](https://swiftpackageindex.com/pvieito/PythonKit) Swift framework to interact with Python. -## Requirements - -**PythonKit** requires [**Swift 5**](https://swift.org/download/) or higher and has been tested on macOS, Linux and Windows. - ## Usage Some Python code like this: @@ -23,7 +22,7 @@ Can be implemented in Swift through PythonKit with the following code: ```swift import PythonKit -let sys = try Python.import("sys") +let sys = Python.import("sys") print("Python \(sys.version_info.major).\(sys.version_info.minor)") print("Python Version: \(sys.version)") @@ -35,7 +34,7 @@ print("Python Encoding: \(sys.getdefaultencoding().upper())") Add the following dependency to your `Package.swift` manifest: ```swift -.package(url: "https://github.com/pvieito/PythonKit.git", .branch("master")), +.package(url: "https://github.com/pvieito/PythonKit.git", branch: "master"), ``` ## Environment Variables diff --git a/Tests/PythonKitTests/NumpyConversionTests.swift b/Tests/PythonKitTests/NumpyConversionTests.swift index 7f47946..4189470 100644 --- a/Tests/PythonKitTests/NumpyConversionTests.swift +++ b/Tests/PythonKitTests/NumpyConversionTests.swift @@ -3,25 +3,25 @@ import PythonKit class NumpyConversionTests: XCTestCase { static var numpyModule = try? Python.attemptImport("numpy") - + func testArrayConversion() { guard let np = NumpyConversionTests.numpyModule else { return } - + let numpyArrayEmpty = np.array([] as [Float], dtype: np.float32) XCTAssertEqual([], Array(numpy: numpyArrayEmpty)) - + let numpyArrayBool = np.array([true, false, false, true]) XCTAssertEqual([true, false, false, true], Array(numpy: numpyArrayBool)) - + let numpyArrayFloat = np.ones([6], dtype: np.float32) XCTAssertEqual(Array(repeating: 1, count: 6), Array(numpy: numpyArrayFloat)) - + let numpyArrayInt32 = np.array([-1, 4, 25, 2018], dtype: np.int32) XCTAssertEqual([-1, 4, 25, 2018], Array(numpy: numpyArrayInt32)) - + let numpyArray2D = np.ones([2, 3]) XCTAssertNil(Array(numpy: numpyArray2D)) - + let numpyArrayStrided = np.array([[1, 2], [1, 2]], dtype: np.int32)[ Python.slice(Python.None), 1] // Assert that the array has a stride, so that we're certainly testing a diff --git a/Tests/PythonKitTests/PythonFunctionTests.swift b/Tests/PythonKitTests/PythonFunctionTests.swift new file mode 100644 index 0000000..f11ad57 --- /dev/null +++ b/Tests/PythonKitTests/PythonFunctionTests.swift @@ -0,0 +1,343 @@ +import XCTest +import PythonKit + +class PythonFunctionTests: XCTestCase { + private var canUsePythonFunction: Bool { + let versionMajor = Python.versionInfo.major + let versionMinor = Python.versionInfo.minor + return (versionMajor == 3 && versionMinor >= 1) || versionMajor > 3 + } + + func testPythonFunction() { + guard canUsePythonFunction else { + return + } + + let pythonAdd = PythonFunction { args in + let lhs = args[0] + let rhs = args[1] + return lhs + rhs + }.pythonObject + + let pythonSum = pythonAdd(2, 3) + XCTAssertNotNil(Double(pythonSum)) + XCTAssertEqual(pythonSum, 5) + + // Test function with keyword arguments + + // Since there is no alternative function signature, `args` and `kwargs` + // can be used without manually stating their type. This differs from + // the behavior when there are no keywords. + let pythonSelect = PythonFunction { args, kwargs in + // NOTE: This may fail on Python versions before 3.6 because they do + // not preserve order of keyword arguments + XCTAssertEqual(args[0], true) + XCTAssertEqual(kwargs[0].key, "y") + XCTAssertEqual(kwargs[0].value, 2) + XCTAssertEqual(kwargs[1].key, "x") + XCTAssertEqual(kwargs[1].value, 3) + + let conditional = Bool(args[0])! + let xIndex = kwargs.firstIndex(where: { $0.key == "x" })! + let yIndex = kwargs.firstIndex(where: { $0.key == "y" })! + + return kwargs[conditional ? xIndex : yIndex].value + }.pythonObject + + let pythonSelectOutput = pythonSelect(true, y: 2, x: 3) + XCTAssertEqual(pythonSelectOutput, 3) + } + + // From https://www.geeksforgeeks.org/create-classes-dynamically-in-python + func testPythonClassConstruction() { + guard canUsePythonFunction else { + return + } + + let constructor = PythonInstanceMethod { args in + let `self` = args[0] + `self`.constructor_arg = args[1] + return Python.None + } + + // Instead of calling `print`, use this to test what would be output. + var printOutput: String? + + // Example of function using an alternative syntax for `args`. + let displayMethod = PythonInstanceMethod { (args: [PythonObject]) in + // let `self` = args[0] + printOutput = String(args[1]) + return Python.None + } + + let classMethodOriginal = PythonInstanceMethod { args in + // let cls = args[0] + printOutput = String(args[1]) + return Python.None + } + + // Did not explicitly convert `constructor` or `displayMethod` to + // PythonObject. This is intentional, as the `PythonClass` initializer + // should take any `PythonConvertible` and not just `PythonObject`. + let classMethod = Python.classmethod(classMethodOriginal.pythonObject) + + let Geeks = PythonClass("Geeks", members: [ + // Constructor + "__init__": constructor, + + // Data members + "string_attribute": "Geeks 4 geeks!", + "int_attribute": 1706256, + + // Member functions + "func_arg": displayMethod, + "class_func": classMethod, + ]).pythonObject + + let obj = Geeks("constructor argument") + XCTAssertEqual(obj.constructor_arg, "constructor argument") + XCTAssertEqual(obj.string_attribute, "Geeks 4 geeks!") + XCTAssertEqual(obj.int_attribute, 1706256) + + obj.func_arg("Geeks for Geeks") + XCTAssertEqual(printOutput, "Geeks for Geeks") + + Geeks.class_func("Class Dynamically Created!") + XCTAssertEqual(printOutput, "Class Dynamically Created!") + } + + // Previously, there was a build error where passing a simple + // `PythonClass.Members` literal made the literal's type ambiguous. It was + // confused with `[String: PythonObject]`. The solution was adding a + // `@_disfavoredOverload` attribute to the more specific initializer. + func testPythonClassInitializer() { + guard canUsePythonFunction else { + return + } + + let MyClass = PythonClass( + "MyClass", + superclasses: [Python.object], + members: [ + "memberName": "memberValue", + ] + ).pythonObject + + let memberValue = MyClass().memberName + XCTAssertEqual(String(memberValue), "memberValue") + } + + func testPythonClassInheritance() { + guard canUsePythonFunction else { + return + } + + var helloOutput: String? + var helloWorldOutput: String? + + // Declare subclasses of `Python.Exception` + + let HelloException = PythonClass( + "HelloException", + superclasses: [Python.Exception], + members: [ + "str_prefix": "HelloException-prefix ", + + "__init__": PythonInstanceMethod { args in + let `self` = args[0] + let message = "hello \(args[1])" + helloOutput = String(message) + + // Conventional `super` syntax does not work; use this instead. + Python.Exception.__init__(`self`, message) + return Python.None + }, + + // Example of function using the `self` convention instead of `args`. + "__str__": PythonInstanceMethod { (`self`: PythonObject) in + return `self`.str_prefix + Python.repr(`self`) + } + ] + ).pythonObject + + let HelloWorldException = PythonClass( + "HelloWorldException", + superclasses: [HelloException], + members: [ + "str_prefix": "HelloWorldException-prefix ", + + "__init__": PythonInstanceMethod { args in + let `self` = args[0] + let message = "world \(args[1])" + helloWorldOutput = String(message) + + `self`.int_param = args[2] + + // Conventional `super` syntax does not work; use this instead. + HelloException.__init__(`self`, message) + return Python.None + }, + + // Example of function using the `self` convention instead of `args`. + "custom_method": PythonInstanceMethod { (`self`: PythonObject) in + return `self`.int_param + } + ] + ).pythonObject + + // Test that inheritance works as expected + + let error1 = HelloException("test 1") + XCTAssertEqual(helloOutput, "hello test 1") + XCTAssertEqual(Python.str(error1), "HelloException-prefix HelloException('hello test 1')") + XCTAssertEqual(Python.repr(error1), "HelloException('hello test 1')") + + let error2 = HelloWorldException("test 1", 123) + XCTAssertEqual(helloOutput, "hello world test 1") + XCTAssertEqual(helloWorldOutput, "world test 1") + XCTAssertEqual(Python.str(error2), "HelloWorldException-prefix HelloWorldException('hello world test 1')") + XCTAssertEqual(Python.repr(error2), "HelloWorldException('hello world test 1')") + XCTAssertEqual(error2.custom_method(), 123) + XCTAssertNotEqual(error2.custom_method(), "123") + + // Test that subclasses behave like Python exceptions + + // Example of function with no named parameters, which can be stated + // ergonomically using an underscore. The ignored input is a [PythonObject]. + let _ = PythonFunction { _ in + throw HelloWorldException("EXAMPLE ERROR MESSAGE", 2) + }.pythonObject + + /* + do { + try testFunction.throwing.dynamicallyCall(withArguments: []) + XCTFail("testFunction did not throw an error.") + } catch PythonError.exception(let error, _) { + guard let description = String(error) else { + XCTFail("A string could not be created from a HelloWorldException.") + return + } + + XCTAssertTrue(description.contains("EXAMPLE ERROR MESSAGE")) + XCTAssertTrue(description.contains("HelloWorldException")) + } catch { + XCTFail("Got error that was not a Python exception: \(error.localizedDescription)") + } + */ + } + + // Tests the ability to dynamically construct an argument list with keywords + // and instantiate a `PythonInstanceMethod` with keywords. + func testPythonClassInheritanceWithKeywords() { + guard canUsePythonFunction else { + return + } + + func getValue(key: String, kwargs: [(String, PythonObject)]) -> PythonObject { + let index = kwargs.firstIndex(where: { $0.0 == key })! + return kwargs[index].1 + } + + // Base class has the following arguments: + // __init__(): + // - 1 unnamed argument + // - param1 + // - param2 + // + // test_method(): + // - param1 + // - param2 + + let BaseClass = PythonClass( + "BaseClass", + superclasses: [], + members: [ + "__init__": PythonInstanceMethod { args, kwargs in + let `self` = args[0] + `self`.arg1 = args[1] + `self`.param1 = getValue(key: "param1", kwargs: kwargs) + `self`.param2 = getValue(key: "param2", kwargs: kwargs) + return Python.None + }, + + "test_method": PythonInstanceMethod { args, kwargs in + let `self` = args[0] + `self`.param1 += getValue(key: "param1", kwargs: kwargs) + `self`.param2 += getValue(key: "param2", kwargs: kwargs) + return Python.None + } + ] + ).pythonObject + + // Derived class accepts the following arguments: + // __init__(): + // - param2 + // - param3 + // + // test_method(): + // - param1 + // - param2 + // - param3 + + let DerivedClass = PythonClass( + "DerivedClass", + superclasses: [], + members: [ + "__init__": PythonInstanceMethod { args, kwargs in + let `self` = args[0] + `self`.param3 = getValue(key: "param3", kwargs: kwargs) + + // Lists the arguments in an order different than they are + // specified (self, param2, param3, param1, arg1). The + // correct order is (self, arg1, param1, param2, param3). + let newKeywordArguments = args.map { + ("", $0) + } + kwargs + [ + ("param1", 1), + ("", 0) + ] + + BaseClass.__init__.dynamicallyCall( + withKeywordArguments: newKeywordArguments) + return Python.None + }, + + "test_method": PythonInstanceMethod { args, kwargs in + let `self` = args[0] + `self`.param3 += getValue(key: "param3", kwargs: kwargs) + + BaseClass.test_method.dynamicallyCall( + withKeywordArguments: args.map { ("", $0) } + kwargs) + return Python.None + } + ] + ).pythonObject + + let derivedInstance = DerivedClass(param2: 2, param3: 3) + XCTAssertEqual(derivedInstance.arg1, 0) + XCTAssertEqual(derivedInstance.param1, 1) + XCTAssertEqual(derivedInstance.param2, 2) + XCTAssertEqual(derivedInstance.checking.param3, 3) + + derivedInstance.test_method(param1: 1, param2: 2, param3: 3) + XCTAssertEqual(derivedInstance.arg1, 0) + XCTAssertEqual(derivedInstance.param1, 2) + XCTAssertEqual(derivedInstance.param2, 4) + XCTAssertEqual(derivedInstance.checking.param3, 6) + + // Validate that subclassing and instantiating the derived class does + // not affect behavior of the parent class. + + let baseInstance = BaseClass(0, param1: 10, param2: 20) + XCTAssertEqual(baseInstance.arg1, 0) + XCTAssertEqual(baseInstance.param1, 10) + XCTAssertEqual(baseInstance.param2, 20) + XCTAssertEqual(baseInstance.checking.param3, nil) + + baseInstance.test_method(param1: 10, param2: 20) + XCTAssertEqual(baseInstance.arg1, 0) + XCTAssertEqual(baseInstance.param1, 20) + XCTAssertEqual(baseInstance.param2, 40) + XCTAssertEqual(baseInstance.checking.param3, nil) + } +} diff --git a/Tests/PythonKitTests/PythonRuntimeTests.swift b/Tests/PythonKitTests/PythonRuntimeTests.swift index 29d74ae..90f1418 100644 --- a/Tests/PythonKitTests/PythonRuntimeTests.swift +++ b/Tests/PythonKitTests/PythonRuntimeTests.swift @@ -6,99 +6,136 @@ class PythonRuntimeTests: XCTestCase { XCTAssertGreaterThanOrEqual(Python.versionInfo.major, 2) XCTAssertGreaterThanOrEqual(Python.versionInfo.minor, 0) } - + func testPythonList() { let list: PythonObject = [0, 1, 2] XCTAssertEqual("[0, 1, 2]", list.description) XCTAssertEqual(3, Python.len(list)) XCTAssertEqual("[0, 1, 2]", Python.str(list)) - + let polymorphicList = PythonObject(["a", 2, true, 1.5]) XCTAssertEqual("a", polymorphicList[0]) XCTAssertEqual(2, polymorphicList[1]) XCTAssertEqual(true, polymorphicList[2]) XCTAssertEqual(1.5, polymorphicList[3]) XCTAssertEqual(1.5, polymorphicList[-1]) - + + XCTAssertEqual(4, polymorphicList.count as Int) + XCTAssertEqual(4, polymorphicList.checking.count!) + XCTAssertEqual(4, polymorphicList.throwing.count!) + polymorphicList[2] = 2 XCTAssertEqual(2, polymorphicList[2]) } - + + #if !os(Windows) func testPythonDict() { let dict: PythonObject = ["a": 1, 1: 0.5] XCTAssertEqual(2, Python.len(dict)) XCTAssertEqual(1, dict["a"]) XCTAssertEqual(0.5, dict[1]) - + + XCTAssertEqual(2, dict.count as Int) + XCTAssertEqual(2, dict.checking.count!) + XCTAssertEqual(2, dict.throwing.count!) + dict["b"] = "c" XCTAssertEqual("c", dict["b"]) dict["b"] = "d" XCTAssertEqual("d", dict["b"]) + + // Dictionary initializer patch does not work on Python 2, but that + // version is no longer being actively supported. + guard Python.versionInfo.major >= 3 else { + return + } + + // Pandas DataFrame regression test spotted in Jupyter. This is + // non-deterministic, so repeat it several times to ensure the bug does + // not appear. + for _ in 0..<15 { + let records: [PythonObject] = [ + ["col 1": 3, "col 2": 5], + ["col 1": 8, "col 2": 2] + ] + let records_description = + "[{'col 1': 3, 'col 2': 5}, {'col 1': 8, 'col 2': 2}]" + XCTAssertEqual(String(describing: records), records_description) + + let records_alt: [PythonObject] = [ + ["col 1": 3, "col 2": 5, "col 3": 4], + ["col 1": 8, "col 2": 2, "col 3": 4] + ] + let records_alt_description = + "[{'col 1': 3, 'col 2': 5, 'col 3': 4}, {'col 1': 8, 'col 2': 2, 'col 3': 4}]" + XCTAssertEqual(String(describing: records_alt), records_alt_description) + } } - + func testRange() { let slice = PythonObject(5..<10) XCTAssertEqual(Python.slice(5, 10), slice) XCTAssertEqual(5, slice.start) XCTAssertEqual(10, slice.stop) - + let range = Range(slice) XCTAssertNotNil(range) XCTAssertEqual(5, range?.lowerBound) XCTAssertEqual(10, range?.upperBound) - + XCTAssertNil(Range(PythonObject(5...))) } - + func testPartialRangeFrom() { let slice = PythonObject(5...) XCTAssertEqual(Python.slice(5, Python.None), slice) XCTAssertEqual(5, slice.start) - + let range = PartialRangeFrom(slice) XCTAssertNotNil(range) XCTAssertEqual(5, range?.lowerBound) - + XCTAssertNil(PartialRangeFrom(PythonObject(..<5))) } - + func testPartialRangeUpTo() { let slice = PythonObject(..<5) XCTAssertEqual(Python.slice(5), slice) XCTAssertEqual(5, slice.stop) - + let range = PartialRangeUpTo(slice) XCTAssertNotNil(range) XCTAssertEqual(5, range?.upperBound) - + XCTAssertNil(PartialRangeUpTo(PythonObject(5...))) } - + #endif + func testStrideable() { let strideTo = stride(from: PythonObject(0), to: 100, by: 2) XCTAssertEqual(0, strideTo.min()!) XCTAssertEqual(98, strideTo.max()!) XCTAssertEqual([0, 2, 4, 6, 8], Array(strideTo.prefix(5))) XCTAssertEqual([90, 92, 94, 96, 98], Array(strideTo.suffix(5))) - + let strideThrough = stride(from: PythonObject(0), through: 100, by: 2) XCTAssertEqual(0, strideThrough.min()!) XCTAssertEqual(100, strideThrough.max()!) XCTAssertEqual([0, 2, 4, 6, 8], Array(strideThrough.prefix(5))) XCTAssertEqual([92, 94, 96, 98, 100], Array(strideThrough.suffix(5))) } - + func testBinaryOps() { XCTAssertEqual(42, PythonObject(42)) XCTAssertEqual(42, PythonObject(2) + PythonObject(40)) XCTAssertEqual(2, PythonObject(2) * PythonObject(3) + PythonObject(-4)) - + XCTAssertEqual("abcdef", PythonObject("ab") + PythonObject("cde") + PythonObject("") + PythonObject("f")) XCTAssertEqual("ababab", PythonObject("ab") * 3) - + var x = PythonObject(2) x += 3 XCTAssertEqual(5, x) @@ -113,12 +150,12 @@ class PythonRuntimeTests: XCTestCase { } func testUnaryOps() { - var x = PythonObject(5) - x = -x - XCTAssertEqual(-5, x) - x = PythonObject(-5) - x = -x - XCTAssertEqual(5, x) + var x = PythonObject(5) + x = -x + XCTAssertEqual(-5, x) + x = PythonObject(-5) + x = -x + XCTAssertEqual(5, x) } func testComparable() { @@ -127,38 +164,41 @@ class PythonRuntimeTests: XCTestCase { let list: PythonObject = [-1, 10, 1, 0, 0] XCTAssertEqual([-1, 0, 0, 1, 10], list.sorted()) } - + + #if !os(Windows) func testHashable() { func compareHashValues(_ x: PythonConvertible) { let a = x.pythonObject let b = x.pythonObject XCTAssertEqual(a.hashValue, b.hashValue) } - + compareHashValues(1) compareHashValues(3.14) compareHashValues("asdf") compareHashValues(PythonObject(tupleOf: 1, 2, 3)) } - + #endif + func testRangeIteration() { for (index, val) in Python.range(5).enumerated() { XCTAssertEqual(PythonObject(index), val) } } - + func testErrors() { XCTAssertThrowsError( try PythonObject(1).__truediv__.throwing.dynamicallyCall(withArguments: 0) ) { - guard let pythonError = $0 as? PythonError else { + guard case let PythonError.exception(exception, _) = $0 else { XCTFail("non-Python error: \($0)") return } - XCTAssertEqual(pythonError, PythonError.exception("division by zero", traceback: nil)) + XCTAssertEqual(exception.__class__.__name__, "ZeroDivisionError") } } - + + #if !os(Windows) func testTuple() { let element1: PythonObject = 0 let element2: PythonObject = "abc" @@ -168,33 +208,34 @@ class PythonRuntimeTests: XCTestCase { let (pair1, pair2) = pair.tuple2 XCTAssertEqual(element1, pair1) XCTAssertEqual(element2, pair2) - + let triple = PythonObject(tupleOf: element1, element2, element3) let (triple1, triple2, triple3) = triple.tuple3 XCTAssertEqual(element1, triple1) XCTAssertEqual(element2, triple2) XCTAssertEqual(element3, triple3) - + let quadruple = PythonObject(tupleOf: element1, element2, element3, element4) let (quadruple1, quadruple2, quadruple3, quadruple4) = quadruple.tuple4 XCTAssertEqual(element1, quadruple1) XCTAssertEqual(element2, quadruple2) XCTAssertEqual(element3, quadruple3) XCTAssertEqual(element4, quadruple4) - + XCTAssertEqual(element2, quadruple[1]) } - + #endif + func testMethodCalling() { let list: PythonObject = [1, 2] list.append(3) XCTAssertEqual([1, 2, 3], list) - + // Check method binding. let append = list.append append(4) XCTAssertEqual([1, 2, 3, 4], list) - + // Check *args/**kwargs behavior: `str.format(*args, **kwargs)`. let greeting: PythonObject = "{0} {first} {last}!" XCTAssertEqual("Hi John Smith!", @@ -202,7 +243,7 @@ class PythonRuntimeTests: XCTestCase { XCTAssertEqual("Hey Jane Doe!", greeting.format("Hey", first: "Jane", last: "Doe")) } - + func testConvertibleFromPython() { // Ensure that we cover the -1 case as this is used by Python // to signal conversion errors. @@ -211,18 +252,20 @@ class PythonRuntimeTests: XCTestCase { let five: PythonObject = 5 let half: PythonObject = 0.5 let string: PythonObject = "abc" - + + #if !os(Windows) XCTAssertEqual(-1, Int(minusOne)) XCTAssertEqual(-1, Int8(minusOne)) XCTAssertEqual(-1, Int16(minusOne)) XCTAssertEqual(-1, Int32(minusOne)) XCTAssertEqual(-1, Int64(minusOne)) + #endif XCTAssertEqual(-1.0, Float(minusOne)) XCTAssertEqual(-1.0, Double(minusOne)) - + XCTAssertEqual(0, Int(zero)) XCTAssertEqual(0.0, Double(zero)) - + XCTAssertEqual(5, UInt(five)) XCTAssertEqual(5, UInt8(five)) XCTAssertEqual(5, UInt16(five)) @@ -230,23 +273,25 @@ class PythonRuntimeTests: XCTestCase { XCTAssertEqual(5, UInt64(five)) XCTAssertEqual(5.0, Float(five)) XCTAssertEqual(5.0, Double(five)) - + XCTAssertEqual(0.5, Float(half)) XCTAssertEqual(0.5, Double(half)) // Python rounds down in this case. - XCTAssertEqual(0, Int(half)) - + // XCTAssertEqual(0, Int(half)) + XCTAssertEqual("abc", String(string)) - + XCTAssertNil(String(zero)) + #if !os(Windows) XCTAssertNil(Int(string)) + #endif XCTAssertNil(Double(string)) } - + func testPythonConvertible() { let minusOne: PythonObject = -1 let five: PythonObject = 5 - + XCTAssertEqual(minusOne, Int(-1).pythonObject) XCTAssertEqual(minusOne, Int8(-1).pythonObject) XCTAssertEqual(minusOne, Int16(-1).pythonObject) @@ -254,7 +299,7 @@ class PythonRuntimeTests: XCTestCase { XCTAssertEqual(minusOne, Int64(-1).pythonObject) XCTAssertEqual(minusOne, Float(-1).pythonObject) XCTAssertEqual(minusOne, Double(-1).pythonObject) - + XCTAssertEqual(five, UInt(5).pythonObject) XCTAssertEqual(five, UInt8(5).pythonObject) XCTAssertEqual(five, UInt16(5).pythonObject) @@ -263,13 +308,13 @@ class PythonRuntimeTests: XCTestCase { XCTAssertEqual(five, Float(5).pythonObject) XCTAssertEqual(five, Double(5).pythonObject) } - + // SR-9230: https://bugs.swift.org/browse/SR-9230 func testSR9230() { let pythonDict = Python.dict(a: "a", b: "b") XCTAssertEqual(Python.len(pythonDict), 2) } - + // TF-78: isType() consumed refcount for type objects like `PyBool_Type`. func testPythonRefCount() { let b: PythonObject = true @@ -279,4 +324,60 @@ class PythonRuntimeTests: XCTestCase { _ = Bool.init(b) } } + + func testPythonBytes() { + let bytes = PythonBytes([UInt8(1), UInt8(2), UInt8(3), UInt8(4)]) + bytes.withUnsafeBytes { + XCTAssertEqual(Array($0), [1, 2, 3, 4]) + } + } + + func testPythonBytesInt8() { + let bytes = PythonBytes([Int8(1), Int8(2), Int8(3), Int8(4)]) + bytes.withUnsafeBytes { + XCTAssertEqual(Array($0), [1, 2, 3, 4]) + } + } + + func testPythonBytesNonContiguousSequence() { + let bytes = PythonBytes(CollectionOfOne(UInt8(1))) + bytes.withUnsafeBytes { + XCTAssertEqual(Array($0), [1]) + } + } + + func testPythonBytesNonContiguousSequenceInt8() { + let bytes = PythonBytes(CollectionOfOne(Int8(1))) + bytes.withUnsafeBytes { + XCTAssertEqual(Array($0), [1]) + } + } + + func testBytesConversion() { + let bytes = PythonBytes(CollectionOfOne(UInt8(1))) + let otherBytes = PythonBytes(bytes.pythonObject) + otherBytes?.withUnsafeBytes { + XCTAssertEqual(Array($0), [1]) + } + XCTAssertEqual(bytes, otherBytes) + } + + /// Tests an emulation of the Python `with` statement. + /// + /// Mirrors: + /// ```python + /// with open('temp', 'w') as opened_file: + /// opened_file.write('Contents') + /// with open('temp', 'r') as opened_file: + /// contents = opened_file.read() + /// ``` + func testWith() { + Python.with(Python.open("temp", "w")) { opened_file in + opened_file.write("Contents") + } + Python.with(Python.open("temp", "r")) { opened_file in + let contents = opened_file.read() + XCTAssertEqual("Contents", String(contents)!) + } + } } diff --git a/cmake/modules/CMakeLists.txt b/cmake/modules/CMakeLists.txt deleted file mode 100644 index 8f37a5b..0000000 --- a/cmake/modules/CMakeLists.txt +++ /dev/null @@ -1,6 +0,0 @@ -set(PythonKit_EXPORTS_FILE ${CMAKE_CURRENT_BINARY_DIR}/PythonKitExports.cmake) -configure_file(PythonKitConfig.cmake.in - ${CMAKE_CURRENT_BINARY_DIR}/PythonKitConfig.cmake) - -get_property(PythonKit_EXPORTS GLOBAL PROPERTY PythonKit_EXPORTS) -export(TARGETS ${PythonKit_EXPORTS} FILE ${PythonKit_EXPORTS_FILE}) diff --git a/cmake/modules/PythonKitConfig.cmake.in b/cmake/modules/PythonKitConfig.cmake.in deleted file mode 100644 index ebf9ff8..0000000 --- a/cmake/modules/PythonKitConfig.cmake.in +++ /dev/null @@ -1,3 +0,0 @@ -if(NOT TARGET PythonKit) - include(@PythonKit_EXPORTS_FILE@) -endif() diff --git a/cmake/modules/SwiftSupport.cmake b/cmake/modules/SwiftSupport.cmake deleted file mode 100644 index cfaf135..0000000 --- a/cmake/modules/SwiftSupport.cmake +++ /dev/null @@ -1,45 +0,0 @@ -# Returns the current architecture name in a variable -# -# Usage: -# get_swift_host_arch(result_var_name) -# -# If the current architecture is supported by Swift, sets ${result_var_name} -# with the sanitized host architecture name derived from CMAKE_SYSTEM_PROCESSOR. -function(get_swift_host_arch result_var_name) - if("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "x86_64") - set("${result_var_name}" "x86_64" PARENT_SCOPE) - elseif("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "aarch64") - set("${result_var_name}" "aarch64" PARENT_SCOPE) - elseif("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "ppc64") - set("${result_var_name}" "powerpc64" PARENT_SCOPE) - elseif("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "ppc64le") - set("${result_var_name}" "powerpc64le" PARENT_SCOPE) - elseif("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "s390x") - set("${result_var_name}" "s390x" PARENT_SCOPE) - elseif("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "armv6l") - set("${result_var_name}" "armv6" PARENT_SCOPE) - elseif("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "armv7l") - set("${result_var_name}" "armv7" PARENT_SCOPE) - elseif("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "armv7-a") - set("${result_var_name}" "armv7" PARENT_SCOPE) - elseif("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "AMD64") - set("${result_var_name}" "x86_64" PARENT_SCOPE) - elseif("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "IA64") - set("${result_var_name}" "itanium" PARENT_SCOPE) - elseif("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "x86") - set("${result_var_name}" "i686" PARENT_SCOPE) - elseif("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "i686") - set("${result_var_name}" "i686" PARENT_SCOPE) - else() - message(FATAL_ERROR "Unrecognized architecture on host system: ${CMAKE_SYSTEM_PROCESSOR}") - endif() -endfunction() - -function(get_swift_host_os result_var_name) - if(CMAKE_SYSTEM_NAME STREQUAL Darwin) - set(${result_var_name} macosx PARENT_SCOPE) - else() - string(TOLOWER ${CMAKE_SYSTEM_NAME} cmake_system_name_lc) - set(${result_var_name} ${cmake_system_name_lc} PARENT_SCOPE) - endif() -endfunction()