Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ let package = Package(
],
dependencies: [
swiftJavaJNICoreDep,
.package(url: "https://github.com/swiftlang/swift-syntax", from: "602.0.0"),
.package(url: "https://github.com/swiftlang/swift-syntax", from: "603.0.0"),
.package(url: "https://github.com/apple/swift-argument-parser", from: "1.5.0"),
.package(url: "https://github.com/apple/swift-system", from: "1.4.0"),
.package(url: "https://github.com/apple/swift-log", from: "1.2.0"),
Expand Down
9 changes: 8 additions & 1 deletion Plugins/JExtractSwiftPlugin/JExtractSwiftPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ struct JExtractSwiftBuildToolPlugin: SwiftJavaPluginProtocol, BuildToolPlugin {
// The name of the configuration file SwiftJava.config from the target for
// which we are generating Swift wrappers for Java classes.
let configFile = sourceDir.appending(path: "swift-java.config")
let configuration = try readConfiguration(sourceDir: sourceDir)
let configuration = try readConfiguration(configPath: configFile)

// We use the the usual maven-style structure of "src/[generated|main|test]/java/..."
// that is common in JVM ecosystem
Expand All @@ -71,6 +71,13 @@ struct JExtractSwiftBuildToolPlugin: SwiftJavaPluginProtocol, BuildToolPlugin {
// We'll have to make up some caching inside the tool so we don't re-parse files which have not changed etc.
]

if let staticBuildConfig = configuration?.staticBuildConfigurationFile {
guard let resolvedURL = URL(string: staticBuildConfig, relativeTo: configFile) else {
fatalError("Could not resolve 'staticBuildConfigurationFile' url")
Comment thread
sidepelican marked this conversation as resolved.
Outdated
}
arguments += ["--static-build-config", resolvedURL.absoluteURL.path(percentEncoded: false)]
}

let dependentConfigFilesArguments = dependentConfigFiles.flatMap { moduleAndConfigFile in
let (moduleName, configFile) = moduleAndConfigFile
return [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -229,8 +229,10 @@ extension DeclSyntaxProtocol {
} else {
"var"
}
case .unexpectedCodeDecl(let node):
node.trimmedDescription
case .usingDecl(let node):
node.nameForDebug
node.trimmedDescription
}
}

Expand Down
101 changes: 101 additions & 0 deletions Sources/JExtractSwiftLib/JExtractDefaultBuildConfiguration.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2026 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
//
//===----------------------------------------------------------------------===//

import Foundation
import SwiftIfConfig
import SwiftSyntax

/// A default, fixed build configuration during static analysis for interface extraction.
struct JExtractDefaultBuildConfiguration: BuildConfiguration {
private var base: StaticBuildConfiguration

init() {
let decoder = JSONDecoder()
base = try! decoder.decode(StaticBuildConfiguration.self, from: printStaticBuildConfigOutput)
}

func isCustomConditionSet(name: String) throws -> Bool {
base.isCustomConditionSet(name: name)
}

func hasFeature(name: String) throws -> Bool {
base.hasFeature(name: name)
}

func hasAttribute(name: String) throws -> Bool {
base.hasAttribute(name: name)
}

func canImport(importPath: [(TokenSyntax, String)], version: CanImportVersion) throws -> Bool {
try base.canImport(importPath: importPath, version: version)
}

func isActiveTargetOS(name: String) throws -> Bool {
true
}

func isActiveTargetArchitecture(name: String) throws -> Bool {
true
}

func isActiveTargetEnvironment(name: String) throws -> Bool {
true
}

func isActiveTargetRuntime(name: String) throws -> Bool {
true
}

func isActiveTargetPointerAuthentication(name: String) throws -> Bool {
true
}

func isActiveTargetObjectFormat(name: String) throws -> Bool {
true
}

var targetPointerBitWidth: Int {
base.targetPointerBitWidth
}

var targetAtomicBitWidths: [Int] {
base.targetAtomicBitWidths
}

var endianness: Endianness {
base.endianness
}

var languageVersion: VersionTuple {
base.languageVersion
}

var compilerVersion: VersionTuple {
base.compilerVersion
}
}

private let shared = JExtractDefaultBuildConfiguration()
Comment thread
sidepelican marked this conversation as resolved.
Outdated

extension BuildConfiguration where Self == JExtractDefaultBuildConfiguration {
static var jextractDefault: JExtractDefaultBuildConfiguration {
shared
}
}

// $ swift frontend -print-static-build-config -target aarch64-unknown-linux-gnu
private let printStaticBuildConfigOutput = Data(
Comment thread
sidepelican marked this conversation as resolved.
Outdated
#"{"attributes":["GKInspectable","noDerivative","objcMembers","discardableResult","const","available","usableFromInline","preconcurrency","nonisolated","retroactive","frozen","unsafe","propertyWrapper","lifetime","_extern","inline","abi","storageRestrictions","_opaqueReturnTypeOf","objc","constInitialized","autoclosure","escaping","unchecked","requires_stored_property_inits","convention","attached","nonexhaustive","dynamicCallable","reasync","dynamicMemberLookup","NSCopying","transpose","warn_unqualified_access","c","globalActor","isolated","_local","rethrows","exclusivity","backDeployed","UIApplicationMain","main","nonobjc","resultBuilder","Sendable","_noMetadata","IBDesignable","IBOutlet","export","IBSegueAction","IBAction","derivative","NSApplicationMain","inlinable","concurrent","IBInspectable","NSManaged","_addressable","differentiable"],"compilerVersion":{"components":[6,3]},"customConditions":[],"endianness":"little","features":["BuiltinCreateAsyncTaskWithExecutor","BuiltinCreateAsyncTaskName","Macros","ValueGenericsNameLookup","FreestandingExpressionMacros","AssociatedTypeAvailability","NoncopyableGenerics2","BuiltinInterleave","OptionalIsolatedParameters","BuiltinAddressOfRawLayout","InoutLifetimeDependence","ExtensionMacros","BuiltinBuildMainExecutor","BuiltinStoreRaw","InheritActorContext","IsolatedAny","BuiltinExecutor","LayoutPrespecialization","NonexhaustiveAttribute","InlineAlways","BuiltinCreateTaskGroupWithFlags","ValueGenerics","InlineArrayTypeSugar","BuiltinCreateTask","NonescapableTypes","RetroactiveAttribute","BuiltinTaskRunInline","BuiltinBuildComplexEqualityExecutor","MemorySafetyAttributes","BuiltinBuildExecutor","ModuleSelector","ParameterPacks","MarkerProtocol","ConformanceSuppression","BitwiseCopyable","AddressOfProperty2","UnsafeInheritExecutor","BuiltinCreateAsyncTaskOwnedTaskExecutor","SpecializeAttributeWithAvailability","BuiltinJob","AlwaysInheritActorContext","AsyncExecutionBehaviorAttributes","AsyncSequenceFailure","BorrowingSwitch","PrimaryAssociatedTypes2","NewCxxMethodSafetyHeuristics","ExtensionMacroAttr","BuiltinStackAlloc","TypedThrows","BuiltinContinuation","BuiltinUnprotectedAddressOf","BuiltinSelect","ImplicitSelfCapture","MoveOnlyPartialConsumption","Actors","GeneralizedIsSameMetaTypeBuiltin","MoveOnly","GlobalActors","BuiltinCreateAsyncDiscardingTaskInGroupWithExecutor","NonfrozenEnumExhaustivity","RethrowsProtocol","IsolatedDeinit","BuiltinUnprotectedStackAlloc","BuiltinCreateAsyncTaskInGroupWithExecutor","BuiltinTaskGroupWithArgument","NoAsyncAvailability","ObjCImplementation","AttachedMacros","FreestandingMacros","AsyncAwait","EffectfulProp","BuiltinConcurrencyStackNesting","BuiltinCreateAsyncTaskInGroup","ConcurrentFunctions","BuiltinHopToActor","BuiltinIntLiteralAccessors","BodyMacros","BuiltinVectorsExternC","BuiltinCreateAsyncDiscardingTaskInGroup","RawIdentifiers","NonescapableAccessorOnTrivial","UnavailableFromAsync","IsolatedAny2","LexicalLifetimes","SendableCompletionHandlers","BuiltinAssumeAlignment","AssociatedTypeImplements","Sendable","ABIAttributeSE0479","BuiltinBuildTaskExecutorRef","LifetimeDependenceMutableAccessors","BitwiseCopyable2","IsolatedConformances","ExpressionMacroDefaultArguments","NoncopyableGenerics","SendingArgsAndResults","MoveOnlyResilientTypes","BuiltinEmplaceTypedThrows"],"languageMode":{"components":[5,10]},"targetArchitectures":["arm64"],"targetAtomicBitWidths":[128,64,32,16,8],"targetEnvironments":[],"targetOSs":["Linux"],"targetObjectFileFormats":["ELF"],"targetPointerAuthenticationSchemes":["_none"],"targetPointerBitWidth":64,"targetRuntimes":["_Native","_multithreaded"]}"#
.utf8
)
1 change: 0 additions & 1 deletion Sources/JExtractSwiftLib/Swift2Java.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ public struct SwiftToJava {
}

let translator = Swift2JavaTranslator(config: config)
translator.log.logLevel = config.logLevel ?? .info
Comment thread
sidepelican marked this conversation as resolved.
let log = translator.log

if config.javaPackage == nil || config.javaPackage!.isEmpty {
Expand Down
18 changes: 18 additions & 0 deletions Sources/JExtractSwiftLib/Swift2JavaTranslator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

import Foundation
import SwiftBasicFormat
import SwiftIfConfig
import SwiftJavaConfigurationShared
import SwiftJavaJNICore
import SwiftParser
Expand All @@ -27,6 +28,9 @@ public final class Swift2JavaTranslator {

let config: Configuration

/// The build configuration used to resolve #if conditional compilation blocks.
let buildConfig: any BuildConfiguration

/// The name of the Swift module being translated.
let swiftModuleName: String

Expand Down Expand Up @@ -70,6 +74,19 @@ public final class Swift2JavaTranslator {
self.log = Logger(label: "translator", logLevel: config.logLevel ?? .info)
self.config = config
self.swiftModuleName = swiftModule

if let staticBuildConfigPath = config.staticBuildConfigurationFile {
do {
let data = try Data(contentsOf: URL(fileURLWithPath: staticBuildConfigPath))
let decoder = JSONDecoder()
self.buildConfig = try decoder.decode(StaticBuildConfiguration.self, from: data)
self.log.info("Using custom static build configuration from: \(staticBuildConfigPath)")
} catch {
fatalError("Failed to load static build configuration from '\(staticBuildConfigPath)': \(error)")
}
} else {
self.buildConfig = .jextractDefault
}
}
}

Expand Down Expand Up @@ -152,6 +169,7 @@ extension Swift2JavaTranslator {
moduleName: self.swiftModuleName,
inputs + [dependenciesSource],
config: self.config,
buildConfig: self.buildConfig,
log: self.log,
)
self.lookupContext = SwiftTypeLookupContext(symbolTable: symbolTable)
Expand Down
28 changes: 27 additions & 1 deletion Sources/JExtractSwiftLib/Swift2JavaVisitor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
//===----------------------------------------------------------------------===//

import Foundation
import SwiftIfConfig
import SwiftJavaConfigurationShared
import SwiftParser
import SwiftSyntax
Expand Down Expand Up @@ -70,7 +71,8 @@ final class Swift2JavaVisitor {
self.visit(subscriptDecl: node, in: parent)
case .enumCaseDecl(let node):
self.visit(enumCaseDecl: node, in: parent)

case .ifConfigDecl(let node):
self.visit(ifConfigDecl: node, in: parent, sourceFilePath: sourceFilePath)
default:
break
}
Expand Down Expand Up @@ -366,6 +368,30 @@ final class Swift2JavaVisitor {
}
}

private func visit(
ifConfigDecl node: IfConfigDeclSyntax,
in parent: ImportedNominalType?,
sourceFilePath: String
) {
let (clause, _) = node.activeClause(in: translator.buildConfig)
if let clause, let elements = clause.elements {
switch elements {
case .statements(let codeBlock):
for codeItem in codeBlock {
if let declNode = codeItem.item.as(DeclSyntax.self) {
self.visit(decl: declNode, in: parent, sourceFilePath: sourceFilePath)
}
}
case .decls(let memberBlock):
for memberItem in memberBlock {
self.visit(decl: memberItem.decl, in: parent, sourceFilePath: sourceFilePath)
}
default:
break
}
}
}

private func importAccessor(
from node: DeclSyntax,
in typeContext: ImportedNominalType?,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
//
//===----------------------------------------------------------------------===//

import SwiftIfConfig
import SwiftSyntax

struct SwiftParsedModuleSymbolTableBuilder {
Expand All @@ -23,6 +24,9 @@ struct SwiftParsedModuleSymbolTableBuilder {
/// Imported modules to resolve type syntax.
let importedModules: [String: SwiftModuleSymbolTable]

/// The build configuration used to resolve #if conditional compilation blocks.
let buildConfig: any BuildConfiguration

/// Extension decls their extended type hasn't been resolved.
var unresolvedExtensions: [ExtensionDeclSyntax]

Expand All @@ -31,6 +35,7 @@ struct SwiftParsedModuleSymbolTableBuilder {
requiredAvailablityOfModuleWithName: String? = nil,
alternativeModules: SwiftModuleSymbolTable.AlternativeModuleNamesData? = nil,
importedModules: [String: SwiftModuleSymbolTable],
buildConfig: any BuildConfiguration = .jextractDefault,
log: Logger? = nil
) {
self.log = log
Expand All @@ -40,6 +45,7 @@ struct SwiftParsedModuleSymbolTableBuilder {
alternativeModules: alternativeModules
)
self.importedModules = importedModules
self.buildConfig = buildConfig
self.unresolvedExtensions = []
}

Expand All @@ -56,17 +62,25 @@ extension SwiftParsedModuleSymbolTableBuilder {
) {
// Find top-level type declarations.
for statement in sourceFile.statements {
// We only care about declarations.
guard case .decl(let decl) = statement.item else {
continue
}
self.handle(codeBlockItem: statement.item, sourceFilePath: sourceFilePath)
}
}

if let nominalTypeNode = decl.asNominal {
self.handle(sourceFilePath: sourceFilePath, nominalTypeDecl: nominalTypeNode, parent: nil)
}
if let extensionNode = decl.as(ExtensionDeclSyntax.self) {
self.handle(extensionDecl: extensionNode, sourceFilePath: sourceFilePath)
}
mutating func handle(
codeBlockItem node: CodeBlockItemSyntax.Item,
sourceFilePath: String
) {
// We only care about declarations.
guard case .decl(let decl) = node else {
return
}

if let nominalTypeNode = decl.asNominal {
self.handle(sourceFilePath: sourceFilePath, nominalTypeDecl: nominalTypeNode, parent: nil)
} else if let extensionNode = decl.as(ExtensionDeclSyntax.self) {
self.handle(extensionDecl: extensionNode, sourceFilePath: sourceFilePath)
} else if let ifConfigNode = decl.as(IfConfigDeclSyntax.self) {
self.handle(ifConfig: ifConfigNode, sourceFilePath: sourceFilePath)
}
}

Expand Down Expand Up @@ -152,6 +166,23 @@ extension SwiftParsedModuleSymbolTableBuilder {
return true
}

mutating func handle(
ifConfig node: IfConfigDeclSyntax,
sourceFilePath: String
) {
let (clause, _) = node.activeClause(in: buildConfig)
if let clause, let elements = clause.elements {
switch elements {
case .statements(let codeBlock):
for codeItem in codeBlock {
self.handle(codeBlockItem: codeItem.item, sourceFilePath: sourceFilePath)
}
default:
break
}
}
}

/// Finalize the symbol table and return it.
mutating func finalize() -> SwiftModuleSymbolTable {
// Handle the unresolved extensions.
Expand Down
4 changes: 4 additions & 0 deletions Sources/JExtractSwiftLib/SwiftTypes/SwiftSymbolTable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
//===----------------------------------------------------------------------===//

import CodePrinting
import SwiftIfConfig
import SwiftJavaConfigurationShared
import SwiftParser
import SwiftSyntax
Expand Down Expand Up @@ -69,6 +70,7 @@ extension SwiftSymbolTable {
moduleName: String,
_ inputFiles: some Collection<SwiftJavaInputFile>,
config: Configuration?,
buildConfig: any BuildConfiguration = .jextractDefault,
log: Logger,
) -> SwiftSymbolTable {

Expand Down Expand Up @@ -104,6 +106,7 @@ extension SwiftSymbolTable {
var stubBuilder = SwiftParsedModuleSymbolTableBuilder(
moduleName: stubModuleName,
importedModules: ["Swift": importedModules["Swift"]!],
buildConfig: buildConfig,
)
stubBuilder.handle(sourceFile: sourceFile, sourceFilePath: "\(stubModuleName)_stub.swift")
let stubModule = stubBuilder.finalize()
Expand All @@ -122,6 +125,7 @@ extension SwiftSymbolTable {
var builder = SwiftParsedModuleSymbolTableBuilder(
moduleName: moduleName,
importedModules: importedModules,
buildConfig: buildConfig,
log: log,
)
// First, register top-level and nested nominal types to the symbol table.
Expand Down
4 changes: 4 additions & 0 deletions Sources/SwiftJavaConfigurationShared/Configuration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,10 @@ public struct Configuration: Codable {
/// ```
public var specialize: [String: SpecializationConfigEntry]?

/// If set, use this JSON file as the static build configuration for jextract.
/// This allows users to provide a custom StaticBuildConfiguration for #if resolution.
public var staticBuildConfigurationFile: String?

// ==== wrap-java ---------------------------------------------------------

/// The Java class path that should be passed along to the swift-java tool.
Expand Down
Loading
Loading