Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
24 changes: 22 additions & 2 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -118,12 +118,12 @@ 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"),
.package(url: "https://github.com/apple/swift-collections", .upToNextMinor(from: "1.3.0")), // primarily for ordered collections
.package(url: "https://github.com/swiftlang/swift-subprocess.git", from: "0.2.1", traits: ["SubprocessFoundation"]),
.package(url: "https://github.com/swiftlang/swift-subprocess.git", from: "0.4.0", traits: ["SubprocessFoundation"]),

// Benchmarking
.package(url: "https://github.com/ordo-one/package-benchmark", .upToNextMajor(from: "1.4.0")),
Expand Down Expand Up @@ -320,6 +320,7 @@ let package = Package(
dependencies: [
.product(name: "SwiftBasicFormat", package: "swift-syntax"),
.product(name: "SwiftLexicalLookup", package: "swift-syntax"),
.product(name: "SwiftIfConfig", package: "swift-syntax"),
.product(name: "SwiftSyntax", package: "swift-syntax"),
.product(name: "SwiftSyntaxBuilder", package: "swift-syntax"),
.product(name: "ArgumentParser", package: "swift-argument-parser"),
Expand All @@ -331,6 +332,25 @@ let package = Package(
],
swiftSettings: [
.swiftLanguageMode(.v5)
],
plugins: [
.plugin(name: "_StaticBuildConfigPlugin")
]
),

.executableTarget(
name: "StaticBuildConfigPluginExecutable",
dependencies: [
.product(name: "Subprocess", package: "swift-subprocess"),
.product(name: "SwiftIfConfig", package: "swift-syntax"),
]
),

.plugin(
name: "_StaticBuildConfigPlugin",
capability: .buildTool(),
dependencies: [
"StaticBuildConfigPluginExecutable"
]
),

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: \(staticBuildConfig)")
}
arguments += ["--static-build-config", resolvedURL.absoluteURL.path(percentEncoded: false)]
}

let dependentConfigFilesArguments = dependentConfigFiles.flatMap { moduleAndConfigFile in
let (moduleName, configFile) = moduleAndConfigFile
return [
Expand Down
33 changes: 33 additions & 0 deletions Plugins/_StaticBuildConfigPlugin/_StaticBuildConfigPlugin.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
//===----------------------------------------------------------------------===//
//
// 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 PackagePlugin

@main
struct _StaticBuildConfigPlugin: BuildToolPlugin {
func createBuildCommands(context: PluginContext, target: any Target) async throws -> [Command] {
let outSwift = context.pluginWorkDirectoryURL.appending(path: "StaticBuildConfiguration+embedded.swift")
return [
.buildCommand(
displayName: "Run -print-static-build-config",
executable: try context.tool(named: "StaticBuildConfigPluginExecutable").url,
arguments: [outSwift.absoluteURL.path(percentEncoded: false)],
environment: [:],
inputFiles: [],
outputFiles: [outSwift.absoluteURL]
)
]
}
}
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
//===----------------------------------------------------------------------===//
//
// 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 {
static let shared = JExtractDefaultBuildConfiguration()

private var base: StaticBuildConfiguration

init() {
let decoder = JSONDecoder()
do {
base = try decoder.decode(StaticBuildConfiguration.self, from: StaticBuildConfiguration.embedded)
} catch {
fatalError("Embedded StaticBuildConfiguration is broken! data: \(String(data: StaticBuildConfiguration.embedded, encoding: .utf8) ?? "")")
}
}

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
}
}

extension BuildConfiguration where Self == JExtractDefaultBuildConfiguration {
static var jextractDefault: JExtractDefaultBuildConfiguration {
.shared
}
}
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
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
Loading
Loading