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
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

##### Bug Fixes

- None.
- Fix indexing of xib/storyboard files in SPM projects.

## 3.3.0 (2025-12-13)

Expand Down
2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ var targets: [PackageDescription.Target] = [
.target(name: "TestShared"),
.target(name: "PeripheryKit"),
],
exclude: ["SPMProject"]
exclude: ["SPMProject", "SPMProjectMacOS"]
),
.testTarget(
name: "AccessibilityTests",
Expand Down
27 changes: 13 additions & 14 deletions Sources/ProjectDrivers/SPM.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,7 @@ public enum SPM {
try shell.exec(["swift", "build", "--build-tests"] + additionalArguments)
}

public func testTargetNames() throws -> Set<String> {
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
Expand All @@ -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
}
43 changes: 41 additions & 2 deletions Sources/ProjectDrivers/SPMProjectDriver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -50,15 +50,54 @@ 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,
logger: logger,
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<String> {
description.targets.filter(\.isTestTarget).mapSet(\.name)
}

private func interfaceBuilderFiles(from description: PackageDescription) -> Set<FilePath> {
var xibFiles: Set<FilePath> = []

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
}
}
6 changes: 6 additions & 0 deletions Tests/SPMTests/Helper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
5 changes: 5 additions & 0 deletions Tests/SPMTests/SPMProjectMacOS/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.DS_Store
/.build
/Packages
/*.xcodeproj
xcuserdata/
24 changes: 24 additions & 0 deletions Tests/SPMTests/SPMProjectMacOS/Package.swift
Original file line number Diff line number Diff line change
@@ -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")]
),
]
)
9 changes: 9 additions & 0 deletions Tests/SPMTests/SPMProjectMacOS/Sources/Frontend/main.swift
Original file line number Diff line number Diff line change
@@ -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()
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="18122" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="18093"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="SPMXibViewController" customModule="SPMProjectMacOSKit" customModuleProvider="target">
<connections>
<outlet property="button" destination="OO3-gz-PhK" id="63X-9t-63W"/>
<outlet property="view" destination="iN0-l3-epB" id="qyc-wr-FpV"/>
</connections>
</customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customView id="iN0-l3-epB">
<rect key="frame" x="0.0" y="0.0" width="480" height="270"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<subviews>
<button id="OO3-gz-PhK">
<rect key="frame" x="217" y="120" width="46" height="30"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<buttonCell key="cell" type="push" title="Button" bezelStyle="rounded" alignment="center" borderStyle="border" imagePosition="overlaps" inset="2" id="button-cell">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<action selector="buttonTapped:" target="-2" id="jLb-bl-k6b"/>
</connections>
</button>
</subviews>
</customView>
</objects>
</document>
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import AppKit

public class SPMXibViewController: NSViewController {
@IBOutlet var button: NSButton!
@IBAction func buttonTapped(_: Any) {}
}
21 changes: 21 additions & 0 deletions Tests/SPMTests/SPMProjectMacOSTest.swift
Original file line number Diff line number Diff line change
@@ -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
Loading