diff --git a/Plugins/JExtractSwiftPlugin/JExtractSwiftPlugin.swift b/Plugins/JExtractSwiftPlugin/JExtractSwiftPlugin.swift index 8d0be455..0647e8c5 100644 --- a/Plugins/JExtractSwiftPlugin/JExtractSwiftPlugin.swift +++ b/Plugins/JExtractSwiftPlugin/JExtractSwiftPlugin.swift @@ -93,7 +93,7 @@ struct JExtractSwiftBuildToolPlugin: SwiftJavaPluginProtocol, BuildToolPlugin { $0.pathExtension == "swift" } - // Output Swift files are just Java filename based converted to Swift files one-to-one + // Output files are flattened filenames of the inputs, with the appended +SwiftJava suffix. var outputSwiftFiles: [URL] = swiftFiles.compactMap { sourceFileURL in guard sourceFileURL.isFileURL else { return nil as URL? @@ -104,7 +104,6 @@ struct JExtractSwiftBuildToolPlugin: SwiftJavaPluginProtocol, BuildToolPlugin { fatalError("Could not get relative path for source file \(sourceFilePath)") } let outputURL = outputSwiftDirectory - .appending(path: String(sourceFilePath.dropFirst(sourceDir.count).dropLast(sourceFileURL.lastPathComponent.count + 1))) let inputFileName = sourceFileURL.deletingPathExtension().lastPathComponent return outputURL.appending(path: "\(inputFileName)+SwiftJava.swift") diff --git a/Plugins/SwiftJavaPlugin/SwiftJavaPlugin.swift b/Plugins/SwiftJavaPlugin/SwiftJavaPlugin.swift index 7908932d..147bfc03 100644 --- a/Plugins/SwiftJavaPlugin/SwiftJavaPlugin.swift +++ b/Plugins/SwiftJavaPlugin/SwiftJavaPlugin.swift @@ -175,6 +175,10 @@ struct SwiftJavaBuildToolPlugin: SwiftJavaPluginProtocol, BuildToolPlugin { let displayName = "Wrapping \(classes.count) Java classes in Swift target '\(sourceModule.name)'" log("Prepared: \(displayName)") + + for f in outputSwiftFiles { + log("Swift output file: \(f)") + } commands += [ .buildCommand( displayName: displayName, diff --git a/Samples/SwiftJavaExtractFFMSampleApp/Sources/MySwiftLibrary/LibrarySubDirectory/SwiftTypeInSubDirectory.swift b/Samples/SwiftJavaExtractFFMSampleApp/Sources/MySwiftLibrary/LibrarySubDirectory/SwiftTypeInSubDirectory.swift new file mode 100644 index 00000000..e03eab0b --- /dev/null +++ b/Samples/SwiftJavaExtractFFMSampleApp/Sources/MySwiftLibrary/LibrarySubDirectory/SwiftTypeInSubDirectory.swift @@ -0,0 +1,21 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +public final class SwiftTypeInSubDirectory { + public init() {} + + public func hello() -> Int { + 12 + } +} \ No newline at end of file diff --git a/Samples/SwiftJavaExtractFFMSampleApp/Sources/MySwiftLibrary/swift-java.config b/Samples/SwiftJavaExtractFFMSampleApp/Sources/MySwiftLibrary/swift-java.config index 6e5bc2af..3a76a8d8 100644 --- a/Samples/SwiftJavaExtractFFMSampleApp/Sources/MySwiftLibrary/swift-java.config +++ b/Samples/SwiftJavaExtractFFMSampleApp/Sources/MySwiftLibrary/swift-java.config @@ -1,3 +1,4 @@ { - "javaPackage": "com.example.swift" + "javaPackage": "com.example.swift", + "logLevel": "trace" } diff --git a/Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/SwiftTypeInSubDirectoryTest.java b/Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/SwiftTypeInSubDirectoryTest.java new file mode 100644 index 00000000..87bcfde8 --- /dev/null +++ b/Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/SwiftTypeInSubDirectoryTest.java @@ -0,0 +1,38 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +package com.example.swift; + +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.swift.swiftkit.core.SwiftLibraries; +import org.swift.swiftkit.ffm.AllocatingSwiftArena; + +import java.io.File; +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.*; + +public class SwiftTypeInSubDirectoryTest { + + @Test + void test_MySwiftClass_voidMethod() { + try (var arena = AllocatingSwiftArena.ofConfined()) { + SwiftTypeInSubDirectory o = SwiftTypeInSubDirectory.init(arena); + var num = o.hello(); + assertEquals(12, num); + } + } + +} diff --git a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/LibrarySubDirectory/SwiftTypeInSubDirectory.swift b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/LibrarySubDirectory/SwiftTypeInSubDirectory.swift new file mode 100644 index 00000000..520c8c54 --- /dev/null +++ b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/LibrarySubDirectory/SwiftTypeInSubDirectory.swift @@ -0,0 +1,21 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +public final class SwiftTypeInSubDirectory { + public init() {} + + public func hello() -> Int { + 12 + } +} diff --git a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/swift-java.config b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/swift-java.config index be44c2fd..3d6a1201 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/swift-java.config +++ b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/swift-java.config @@ -1,5 +1,5 @@ { "javaPackage": "com.example.swift", "mode": "jni", - "logLevel": ["debug"] + "logLevel": "debug" } diff --git a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/SwiftTypeInSubDirectoryTest.java b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/SwiftTypeInSubDirectoryTest.java new file mode 100644 index 00000000..0debf8f8 --- /dev/null +++ b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/SwiftTypeInSubDirectoryTest.java @@ -0,0 +1,33 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +package com.example.swift; + +import org.junit.jupiter.api.Test; +import org.swift.swiftkit.core.SwiftArena; + +import static org.junit.jupiter.api.Assertions.*; + +public class SwiftTypeInSubDirectoryTest { + + @Test + void test_SwiftTypeInSubDirectory_hello() { + try (var arena = SwiftArena.ofConfined()) { + SwiftTypeInSubDirectory o = SwiftTypeInSubDirectory.init(arena); + var num = o.hello(); + assertEquals(12, num); + } + } + +} diff --git a/Sources/JExtractSwiftLib/CodePrinter.swift b/Sources/JExtractSwiftLib/CodePrinter.swift index 1497a61a..ebbe83db 100644 --- a/Sources/JExtractSwiftLib/CodePrinter.swift +++ b/Sources/JExtractSwiftLib/CodePrinter.swift @@ -14,7 +14,11 @@ import Foundation -let PATH_SEPARATOR = "/" // TODO: Windows +#if os(Windows) +let PATH_SEPARATOR = "\\" +#else +let PATH_SEPARATOR = "/" +#endif public struct CodePrinter { var contents: String = "" diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift index f233ef00..ce70db0e 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift @@ -22,16 +22,15 @@ extension FFMSwift2JavaGenerator { } package func writeSwiftExpectedEmptySources() throws { - let pendingFileCount = self.expectedOutputSwiftFiles.count + let pendingFileCount = self.expectedOutputSwiftFileNames.count guard pendingFileCount > 0 else { return // no need to write any empty files, yay } - print("[swift-java] Write empty [\(self.expectedOutputSwiftFiles.count)] 'expected' files in: \(swiftOutputDirectory)/") + log.info("[swift-java] Write empty [\(self.expectedOutputSwiftFileNames.count)] 'expected' files in: \(swiftOutputDirectory)/") - for expectedFileName in self.expectedOutputSwiftFiles { - log.debug("Write SwiftPM-'expected' empty file: \(expectedFileName.bold)") - + for expectedFileName in self.expectedOutputSwiftFileNames { + log.info("Write SwiftPM-'expected' empty file: \(expectedFileName.bold)") var printer = CodePrinter() printer.print("// Empty file generated on purpose") @@ -55,7 +54,7 @@ extension FFMSwift2JavaGenerator { javaPackagePath: nil, filename: moduleFilename) { log.info("Generated: \(moduleFilenameBase.bold).swift (at \(outputFile.absoluteString))") - self.expectedOutputSwiftFiles.remove(moduleFilename) + self.expectedOutputSwiftFileNames.remove(moduleFilename) } } catch { log.warning("Failed to write to Swift thunks: \(moduleFilename)") @@ -93,7 +92,9 @@ extension FFMSwift2JavaGenerator { javaPackagePath: nil, filename: filename) { log.info("Done writing Swift thunks to: \(outputFile.absoluteString)") - self.expectedOutputSwiftFiles.remove(filename) + // log.info("REMOVE FROM: \(expectedOutputSwiftFileNames)") + // log.info("REMOVE FROM THE: \(filename)") + self.expectedOutputSwiftFileNames.remove(filename) } } catch { log.warning("Failed to write to Swift thunks: \(filename), error: \(error)") diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift index c445d5c9..33208450 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift @@ -39,7 +39,10 @@ package class FFMSwift2JavaGenerator: Swift2JavaGenerator { /// Because we need to write empty files for SwiftPM, keep track which files we didn't write yet, /// and write an empty file for those. - var expectedOutputSwiftFiles: Set + /// + /// Since Swift files in SwiftPM builds needs to be unique, we use this fact to flatten paths into plain names here. + /// For uniqueness checking "did we write this file already", just checking the name should be sufficient. + var expectedOutputSwiftFileNames: Set package init( config: Configuration, @@ -57,19 +60,26 @@ package class FFMSwift2JavaGenerator: Swift2JavaGenerator { self.javaOutputDirectory = javaOutputDirectory self.lookupContext = translator.lookupContext - // If we are forced to write empty files, construct the expected outputs + // If we are forced to write empty files, construct the expected outputs. + // It is sufficient to use file names only, since SwiftPM requires names to be unique within a module anyway. if translator.config.writeEmptyFiles ?? false { - self.expectedOutputSwiftFiles = Set(translator.inputs.compactMap { (input) -> String? in - guard let filePathPart = input.path.split(separator: "/\(translator.swiftModuleName)/").last else { + self.expectedOutputSwiftFileNames = Set(translator.inputs.compactMap { (input) -> String? in + // guard let filePathPart = input.path.split(separator: "/\(translator.swiftModuleName)/").last else { + // return nil + // } + // return String(filePathPart.replacing(".swift", with: "+SwiftJava.swift")) + guard let fileName = input.path.split(separator: PATH_SEPARATOR).last else { return nil } - - return String(filePathPart.replacing(".swift", with: "+SwiftJava.swift")) + guard fileName.hasSuffix(".swift") else { + return nil + } + return String(fileName.replacing(".swift", with: "+SwiftJava.swift")) }) - self.expectedOutputSwiftFiles.insert("\(translator.swiftModuleName)Module+SwiftJava.swift") - self.expectedOutputSwiftFiles.insert("Foundation+SwiftJava.swift") + self.expectedOutputSwiftFileNames.insert("\(translator.swiftModuleName)Module+SwiftJava.swift") + self.expectedOutputSwiftFileNames.insert("Foundation+SwiftJava.swift") } else { - self.expectedOutputSwiftFiles = [] + self.expectedOutputSwiftFileNames = [] } } diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift index 91a0b0e7..da3936a7 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift @@ -26,15 +26,15 @@ extension JNISwift2JavaGenerator { } package func writeSwiftExpectedEmptySources() throws { - let pendingFileCount = self.expectedOutputSwiftFiles.count + let pendingFileCount = self.expectedOutputSwiftFileNames.count guard pendingFileCount > 0 else { return // no need to write any empty files, yay } - - print("[swift-java] Write empty [\(self.expectedOutputSwiftFiles.count)] 'expected' files in: \(swiftOutputDirectory)/") - - for expectedFileName in self.expectedOutputSwiftFiles { - logger.debug("Write SwiftPM-'expected' empty file: \(expectedFileName.bold)") + + logger.info("[swift-java] Write empty [\(self.expectedOutputSwiftFileNames.count)] 'expected' files in: \(swiftOutputDirectory)/") + + for expectedFileName in self.expectedOutputSwiftFileNames { + logger.info("Write SwiftPM-'expected' empty file: \(expectedFileName.bold)") var printer = CodePrinter() @@ -61,7 +61,7 @@ extension JNISwift2JavaGenerator { filename: moduleFilename ) { logger.info("Generated: \(moduleFilenameBase.bold).swift (at \(outputFile.absoluteString))") - self.expectedOutputSwiftFiles.remove(moduleFilename) + self.expectedOutputSwiftFileNames.remove(moduleFilename) } // === All types @@ -96,7 +96,7 @@ extension JNISwift2JavaGenerator { javaPackagePath: nil, filename: filename) { logger.info("Done writing Swift thunks to: \(outputFile.absoluteString)") - self.expectedOutputSwiftFiles.remove(filename) + self.expectedOutputSwiftFileNames.remove(filename) } } catch { logger.warning("Failed to write to Swift thunks: \(filename), error: \(error)") diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift index 3b84cfb9..7f8a36b9 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift @@ -44,7 +44,10 @@ package class JNISwift2JavaGenerator: Swift2JavaGenerator { /// Because we need to write empty files for SwiftPM, keep track which files we didn't write yet, /// and write an empty file for those. - var expectedOutputSwiftFiles: Set + /// + /// Since Swift files in SwiftPM builds needs to be unique, we use this fact to flatten paths into plain names here. + /// For uniqueness checking "did we write this file already", just checking the name should be sufficient. + var expectedOutputSwiftFileNames: Set package init( config: Configuration, @@ -64,19 +67,26 @@ package class JNISwift2JavaGenerator: Swift2JavaGenerator { self.javaClassLookupTable = javaClassLookupTable self.lookupContext = translator.lookupContext - // If we are forced to write empty files, construct the expected outputs + // If we are forced to write empty files, construct the expected outputs. + // It is sufficient to use file names only, since SwiftPM requires names to be unique within a module anyway. if translator.config.writeEmptyFiles ?? false { - self.expectedOutputSwiftFiles = Set(translator.inputs.compactMap { (input) -> String? in - guard let filePathPart = input.path.split(separator: "/\(translator.swiftModuleName)/").last else { + self.expectedOutputSwiftFileNames = Set(translator.inputs.compactMap { (input) -> String? in + // guard let filePathPart = input.path.split(separator: "/\(translator.swiftModuleName)/").last else { + // return nil + // } + // return String(filePathPart.replacing(".swift", with: "+SwiftJava.swift")) + guard let fileName = input.path.split(separator: PATH_SEPARATOR).last else { return nil } - - return String(filePathPart.replacing(".swift", with: "+SwiftJava.swift")) + guard fileName.hasSuffix(".swift") else { + return nil + } + return String(fileName.replacing(".swift", with: "+SwiftJava.swift")) }) - self.expectedOutputSwiftFiles.insert("\(translator.swiftModuleName)Module+SwiftJava.swift") - self.expectedOutputSwiftFiles.insert("Foundation+SwiftJava.swift") + self.expectedOutputSwiftFileNames.insert("\(translator.swiftModuleName)Module+SwiftJava.swift") + self.expectedOutputSwiftFileNames.insert("Foundation+SwiftJava.swift") } else { - self.expectedOutputSwiftFiles = [] + self.expectedOutputSwiftFileNames = [] } } @@ -84,7 +94,7 @@ package class JNISwift2JavaGenerator: Swift2JavaGenerator { try writeSwiftThunkSources() try writeExportedJavaSources() - let pendingFileCount = self.expectedOutputSwiftFiles.count + let pendingFileCount = self.expectedOutputSwiftFileNames.count if pendingFileCount > 0 { print("[swift-java] Write empty [\(pendingFileCount)] 'expected' files in: \(swiftOutputDirectory)/") try writeSwiftExpectedEmptySources() diff --git a/Sources/JExtractSwiftLib/Swift2Java.swift b/Sources/JExtractSwiftLib/Swift2Java.swift index ed76483e..43e168d5 100644 --- a/Sources/JExtractSwiftLib/Swift2Java.swift +++ b/Sources/JExtractSwiftLib/Swift2Java.swift @@ -17,6 +17,7 @@ import SwiftSyntax import SwiftSyntaxBuilder import SwiftJavaShared import SwiftJavaConfigurationShared +import OrderedCollections public struct SwiftToJava { let config: Configuration @@ -34,6 +35,7 @@ public struct SwiftToJava { let translator = Swift2JavaTranslator(config: config) translator.log.logLevel = config.logLevel ?? .info + let log = translator.log if config.javaPackage == nil || config.javaPackage!.isEmpty { translator.log.warning("Configured java package is '', consider specifying concrete package for generated sources.") @@ -43,28 +45,17 @@ public struct SwiftToJava { fatalError("Missing '--swift-input' directory!") } - translator.log.info("Input swift = \(inputSwift)") + log.info("Input swift = \(inputSwift)") let inputPaths = inputSwift.split(separator: ",").map { URL(string: String($0))! } - translator.log.info("Input paths = \(inputPaths)") - - var allFiles: [URL] = [] - let fileManager = FileManager.default - let log = translator.log + log.info("Input paths = \(inputPaths)") - for path in inputPaths { - log.info("Input path: \(path)") - if isDirectory(url: path) { - if let enumerator = fileManager.enumerator(at: path, includingPropertiesForKeys: nil) { - for case let fileURL as URL in enumerator { - allFiles.append(fileURL) - } - } - } else if path.isFileURL { - allFiles.append(path) - } + let allFiles = collectAllFiles(suffix: ".swift", in: inputPaths, log: translator.log) + for f in allFiles { + log.warning("INPUT FILE: \(f) ->>>") } // Register files to the translator. + let fileManager = FileManager.default for file in allFiles { guard canExtract(from: file) else { continue @@ -137,8 +128,82 @@ public struct SwiftToJava { } -func isDirectory(url: URL) -> Bool { - var isDirectory: ObjCBool = false - _ = FileManager.default.fileExists(atPath: url.path, isDirectory: &isDirectory) - return isDirectory.boolValue +extension URL { + var isDirectory: Bool { + var isDir: ObjCBool = false + _ = FileManager.default.fileExists(atPath: self.path, isDirectory: &isDir) + return isDir.boolValue + } +} + +/// Collect all files with given 'suffix', will explore directories recursively. +public func collectAllFiles(suffix: String, in inputPaths: [URL], log: Logger) -> OrderedSet { + guard !inputPaths.isEmpty else { + return [] + } + + let fileManager = FileManager.default + var allFiles: OrderedSet = [] + allFiles.reserveCapacity(32) // rough guesstimate + + let resourceKeys: [URLResourceKey] = [ + .isRegularFileKey, + .isDirectoryKey, + .nameKey + ] + + for path in inputPaths { + do { + try collectFilesFromPath( + path, + suffix: suffix, + fileManager: fileManager, + resourceKeys: resourceKeys, + into: &allFiles, + log: log + ) + } catch { + log.trace("Failed to collect paths in: \(path), skipping.") + } + } + + return allFiles +} + +private func collectFilesFromPath( + _ path: URL, + suffix: String, + fileManager: FileManager, + resourceKeys: [URLResourceKey], + into allFiles: inout OrderedSet, + log: Logger +) throws { + guard fileManager.fileExists(atPath: path.path) else { + return + } + + if path.isDirectory { + let enumerator = fileManager.enumerator( + at: path, + includingPropertiesForKeys: resourceKeys, + options: [.skipsHiddenFiles], + errorHandler: { url, error in + return true + }) + guard let enumerator else { + return + } + + for case let fileURL as URL in enumerator { + try? collectFilesFromPath(fileURL, suffix: suffix, fileManager: fileManager, resourceKeys: resourceKeys, into: &allFiles, log: log) + } + } + + guard path.isFileURL else { + return + } + guard path.lastPathComponent.hasSuffix(suffix) else { + return + } + allFiles.append(path) } diff --git a/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift b/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift index 60b99a63..212a8e46 100644 --- a/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift +++ b/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift @@ -78,7 +78,7 @@ extension Swift2JavaTranslator { } package func add(filePath: String, text: String) { - log.trace("Adding: \(filePath)") + log.info("Adding: \(filePath)") let sourceFileSyntax = Parser.parse(source: text) self.inputs.append(SwiftJavaInputFile(syntax: sourceFileSyntax, path: filePath)) } diff --git a/Sources/SwiftJavaConfigurationShared/Configuration.swift b/Sources/SwiftJavaConfigurationShared/Configuration.swift index 493ee4bc..133b76fd 100644 --- a/Sources/SwiftJavaConfigurationShared/Configuration.swift +++ b/Sources/SwiftJavaConfigurationShared/Configuration.swift @@ -183,7 +183,10 @@ public func readConfiguration(string: String, configPath: URL?, file: String = # decoder.allowsJSON5 = true return try decoder.decode(Configuration.self, from: configData) } catch { - throw ConfigurationError(message: "Failed to parse SwiftJava configuration at '\(configPath.map({ $0.absoluteURL.description }) ?? "")'! \(#fileID):\(#line)", error: error, + throw ConfigurationError( + message: "Failed to parse SwiftJava configuration at '\(configPath.map({ $0.absoluteURL.description }) ?? "")'! \(#fileID):\(#line)", + error: error, + text: string, file: file, line: line) } } @@ -276,19 +279,21 @@ extension Configuration { public struct ConfigurationError: Error { let message: String let error: any Error + let text: String? let file: String let line: UInt - init(message: String, error: any Error, file: String = #fileID, line: UInt = #line) { + init(message: String, error: any Error, text: String?, file: String = #fileID, line: UInt = #line) { self.message = message self.error = error + self.text = text self.file = file self.line = line } } -public enum LogLevel: String, Codable, Hashable { +public enum LogLevel: String, ExpressibleByStringLiteral, Codable, Hashable { case trace = "trace" case debug = "debug" case info = "info" @@ -296,11 +301,15 @@ public enum LogLevel: String, Codable, Hashable { case warning = "warning" case error = "error" case critical = "critical" + + public init(stringLiteral value: String) { + self = LogLevel(rawValue: value) ?? .info + } } extension LogLevel { public init(from decoder: any Decoder) throws { - var container = try decoder.unkeyedContainer() + let container = try decoder.singleValueContainer() let string = try container.decode(String.self) switch string { case "trace": self = .trace