diff --git a/CHANGELOG.md b/CHANGELOG.md index d8f0527c7..35c6f0f4d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,7 @@ ##### Bug Fixes -- None. +- Fix indexing of xib/storyboard files in SPM projects. ## 3.3.0 (2025-12-13) diff --git a/Package.swift b/Package.swift index 70e12700f..248420aff 100644 --- a/Package.swift +++ b/Package.swift @@ -137,7 +137,7 @@ var targets: [PackageDescription.Target] = [ .target(name: "TestShared"), .target(name: "PeripheryKit"), ], - exclude: ["SPMProject"] + exclude: ["SPMProject", "SPMProjectMacOS"] ), .testTarget( name: "AccessibilityTests", diff --git a/Sources/ProjectDrivers/SPM.swift b/Sources/ProjectDrivers/SPM.swift index cfd465c6f..57ebfbd19 100644 --- a/Sources/ProjectDrivers/SPM.swift +++ b/Sources/ProjectDrivers/SPM.swift @@ -31,14 +31,7 @@ public enum SPM { try shell.exec(["swift", "build", "--build-tests"] + additionalArguments) } - public func testTargetNames() throws -> Set { - let description = try load() - return description.targets.filter(\.isTestTarget).mapSet(\.name) - } - - // MARK: - Private - - private func load() throws -> PackageDescription { + public func load() throws -> PackageDescription { logger.contextualized(with: "spm:package").debug("Loading \(FilePath.current)") let jsonData: Data @@ -62,15 +55,21 @@ public enum SPM { } } -struct PackageDescription: Decodable { - let targets: [Target] +public struct PackageDescription: Decodable { + public let targets: [Target] } -struct Target: Decodable { - let name: String - let type: String +public struct Target: Decodable { + public let name: String + public let type: String + public let path: String + public let resources: [Resource]? - var isTestTarget: Bool { + public var isTestTarget: Bool { type == "test" } } + +public struct Resource: Decodable { + public let path: String +} diff --git a/Sources/ProjectDrivers/SPMProjectDriver.swift b/Sources/ProjectDrivers/SPMProjectDriver.swift index 3cbcbe892..cf1630518 100644 --- a/Sources/ProjectDrivers/SPMProjectDriver.swift +++ b/Sources/ProjectDrivers/SPMProjectDriver.swift @@ -50,7 +50,10 @@ extension SPMProjectDriver: ProjectDriver { [pkg.path.appending(".build/debug/index/store")] } - let excludedTestTargets = configuration.excludeTests ? try pkg.testTargetNames() : [] + // Load package description once and reuse it + let description = try pkg.load() + + let excludedTestTargets = configuration.excludeTests ? testTargetNames(from: description) : [] let collector = SourceFileCollector( indexStorePaths: indexStorePaths, excludedTestTargets: excludedTestTargets, @@ -58,7 +61,43 @@ extension SPMProjectDriver: ProjectDriver { configuration: configuration ) let sourceFiles = try collector.collect() + let xibPaths = interfaceBuilderFiles(from: description) + + return IndexPlan( + sourceFiles: sourceFiles, + xibPaths: xibPaths + ) + } + + // MARK: - Private + + private func testTargetNames(from description: PackageDescription) -> Set { + description.targets.filter(\.isTestTarget).mapSet(\.name) + } + + private func interfaceBuilderFiles(from description: PackageDescription) -> Set { + var xibFiles: Set = [] + + for target in description.targets { + let targetPath = pkg.path.appending(target.path) + + guard let resources = target.resources else { continue } + + for resource in resources { + // Resource.path is always a single file path + let resourceFilePath = FilePath(resource.path) + let resourcePath: FilePath = resourceFilePath.isAbsolute + ? resourceFilePath + : targetPath.appending(resource.path) + + // Check if the resource path exists and is a xib/storyboard file + guard resourcePath.exists else { continue } + guard let ext = resourcePath.extension?.lowercased(), ["xib", "storyboard"].contains(ext) else { continue } + + xibFiles.insert(resourcePath) + } + } - return IndexPlan(sourceFiles: sourceFiles) + return xibFiles } } diff --git a/Tests/SPMTests/Helper.swift b/Tests/SPMTests/Helper.swift index 2693cbb39..8ca967ca2 100644 --- a/Tests/SPMTests/Helper.swift +++ b/Tests/SPMTests/Helper.swift @@ -5,3 +5,9 @@ import SystemPackage var SPMProjectPath: FilePath { ProjectRootPath.appending("Tests/SPMTests/SPMProject") } + +#if os(macOS) + var SPMProjectMacOSPath: FilePath { + ProjectRootPath.appending("Tests/SPMTests/SPMProjectMacOS") + } +#endif diff --git a/Tests/SPMTests/SPMProjectMacOS/.gitignore b/Tests/SPMTests/SPMProjectMacOS/.gitignore new file mode 100644 index 000000000..95c432091 --- /dev/null +++ b/Tests/SPMTests/SPMProjectMacOS/.gitignore @@ -0,0 +1,5 @@ +.DS_Store +/.build +/Packages +/*.xcodeproj +xcuserdata/ diff --git a/Tests/SPMTests/SPMProjectMacOS/Package.swift b/Tests/SPMTests/SPMProjectMacOS/Package.swift new file mode 100644 index 000000000..762e837b1 --- /dev/null +++ b/Tests/SPMTests/SPMProjectMacOS/Package.swift @@ -0,0 +1,24 @@ +// swift-tools-version: 6.0 +import PackageDescription + +let package = Package( + name: "SPMProjectMacOS", + platforms: [.macOS(.v13)], + products: [ + .executable( + name: "frontend", + targets: ["Frontend"] + ), + ], + dependencies: [], + targets: [ + .executableTarget( + name: "Frontend", + dependencies: ["SPMProjectMacOSKit"] + ), + .target( + name: "SPMProjectMacOSKit", + resources: [.process("Resources")] + ), + ] +) diff --git a/Tests/SPMTests/SPMProjectMacOS/Sources/Frontend/main.swift b/Tests/SPMTests/SPMProjectMacOS/Sources/Frontend/main.swift new file mode 100644 index 000000000..e3995240e --- /dev/null +++ b/Tests/SPMTests/SPMProjectMacOS/Sources/Frontend/main.swift @@ -0,0 +1,9 @@ +import Foundation +import SPMProjectMacOSKit + +func main() { + // Reference the view controller class (it's also referenced from the xib) + _ = SPMXibViewController.self +} + +main() diff --git a/Tests/SPMTests/SPMProjectMacOS/Sources/SPMProjectMacOSKit/Resources/SPMXibViewController.xib b/Tests/SPMTests/SPMProjectMacOS/Sources/SPMProjectMacOSKit/Resources/SPMXibViewController.xib new file mode 100644 index 000000000..58359794f --- /dev/null +++ b/Tests/SPMTests/SPMProjectMacOS/Sources/SPMProjectMacOSKit/Resources/SPMXibViewController.xib @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Tests/SPMTests/SPMProjectMacOS/Sources/SPMProjectMacOSKit/SPMXibViewController.swift b/Tests/SPMTests/SPMProjectMacOS/Sources/SPMProjectMacOSKit/SPMXibViewController.swift new file mode 100644 index 000000000..6c318232b --- /dev/null +++ b/Tests/SPMTests/SPMProjectMacOS/Sources/SPMProjectMacOSKit/SPMXibViewController.swift @@ -0,0 +1,6 @@ +import AppKit + +public class SPMXibViewController: NSViewController { + @IBOutlet var button: NSButton! + @IBAction func buttonTapped(_: Any) {} +} diff --git a/Tests/SPMTests/SPMProjectMacOSTest.swift b/Tests/SPMTests/SPMProjectMacOSTest.swift new file mode 100644 index 000000000..160107aa4 --- /dev/null +++ b/Tests/SPMTests/SPMProjectMacOSTest.swift @@ -0,0 +1,21 @@ +#if os(macOS) + import Configuration + @testable import TestShared + import XCTest + + final class SPMProjectMacOSTest: SPMSourceGraphTestCase { + override static func setUp() { + super.setUp() + + build(projectPath: SPMProjectMacOSPath) + index(configuration: Configuration()) + } + + func testRetainsInterfaceBuilderDeclarations() { + assertReferenced(.class("SPMXibViewController")) { + self.assertReferenced(.functionMethodInstance("buttonTapped(_:)")) + self.assertReferenced(.varInstance("button")) + } + } + } +#endif