Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
21 changes: 21 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]

### Added
- **Swift Build Tool Plugin Integration**
- Automatic dependency validation during builds with Swift Package Manager build tool plugin
- Zero-configuration integration - works automatically when applied to package targets
- Seamless Xcode integration with native build system error and warning reporting
- Target-specific analysis validates dependencies for each target individually
- Prebuild command execution ensures validation before compilation begins
- IDE-friendly output with Xcode-compatible error messages and navigation
- Support for both Swift Package Manager command-line builds and Xcode workspace builds
- Plugin automatically excludes test dependencies when analyzing non-test targets
- Operates in quiet mode by default to focus on dependency issues during builds
- **Product-Level Dependency Detection**
- Analyzes external package products from `.build/checkouts` directory after `swift resolve`
- Detects imports satisfied by product dependencies to prevent false "missing dependency" reports
Expand Down Expand Up @@ -68,6 +78,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Enhanced dependency parsing to capture exact line numbers where dependencies are declared

### Technical Details
- **Swift Build Tool Plugin Architecture**
- Added `DependencyAuditPlugin` conforming to `BuildToolPlugin` protocol with `@main` annotation
- Implemented `createBuildCommands(context:target:)` method for prebuild command generation
- Plugin executes `swift run swift-dependency-audit` with target-specific arguments
- Uses modern PackagePlugin API with `pluginWorkDirectoryURL` and `directoryURL` properties
- Target filtering logic analyzes all targets and lets the tool handle inappropriate target types
- Automatic test exclusion for non-test targets via `--exclude-tests` flag
- Xcode-compatible output format via `--output-format xcode` for seamless IDE integration
- Quiet mode operation via `--quiet` flag to focus on issues during builds
- Plugin target definition in Package.swift with `.buildTool()` capability

- **Product-Level Dependency Analysis**
- Added `Product`, `ExternalPackage`, `ExternalPackageDependency`, and `ProductSatisfiedDependency` models
- New `ExternalPackageResolver` actor for discovering and parsing external packages from `.build/checkouts`
Expand Down
7 changes: 6 additions & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ let package = Package(
.watchOS(.v11),
],
products: [
.executable(name: "swift-dependency-audit", targets: ["SwiftDependencyAudit"])
.executable(name: "swift-dependency-audit", targets: ["SwiftDependencyAudit"]),
.plugin(name: "DependencyAuditPlugin", targets: ["DependencyAuditPlugin"])
],
dependencies: [
.package(url: "https://github.com/apple/swift-argument-parser.git", from: "1.6.1")
Expand All @@ -31,5 +32,9 @@ let package = Package(
name: "SwiftDependencyAuditTests",
dependencies: ["SwiftDependencyAuditLib"]
),
.plugin(
name: "DependencyAuditPlugin",
capability: .buildTool()
),
]
)
69 changes: 69 additions & 0 deletions Plugins/DependencyAuditPlugin/DependencyAuditPlugin.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import PackagePlugin
import Foundation

/// Swift Package Manager build tool plugin that automatically validates dependencies during builds
@main
struct DependencyAuditPlugin: BuildToolPlugin {

/// Creates build commands to run dependency validation
func createBuildCommands(context: PluginContext, target: Target) throws -> [Command] {
// Only run on source-based targets that could have dependencies
guard shouldAnalyzeTarget(target) else {
return []
}

// Create output directory for plugin results
let outputDir = context.pluginWorkDirectoryURL.appendingPathComponent("DependencyAudit")

// Build script content that will execute swift run with our tool
var scriptContent = """
#!/bin/bash
set -e

# Change to package directory
cd "\(context.package.directoryURL.path)"

# Run dependency audit for specific target
swift run swift-dependency-audit \\
Copy link

Copilot AI Jul 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Invoking the audit tool via swift run can be slower and less robust; consider using the PackagePlugin API (context.tool(named:)) to locate and execute the swift-dependency-audit executable directly.

Suggested change
swift run swift-dependency-audit \\
"\(try context.tool(named: "swift-dependency-audit").path)" \\

Copilot uses AI. Check for mistakes.
"\(context.package.directoryURL.path)" \\
--target "\(target.name)" \\
--output-format xcode \\
--quiet
"""

// Add additional arguments based on target type
// Check if target name contains "Test" to identify test targets
if !target.name.lowercased().contains("test") {
Copy link

Copilot AI Jul 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Detecting test targets by checking the name substring is brittle; use the target’s type (e.g., TestTarget) or another metadata property to differentiate test and non-test targets.

Suggested change
// Check if target name contains "Test" to identify test targets
if !target.name.lowercased().contains("test") {
// Check if the target is not a TestTarget to identify non-test targets
if !(target is TestTarget) {

Copilot uses AI. Check for mistakes.
// For non-test targets, exclude test dependencies from analysis
scriptContent += " \\\n --exclude-tests"
}

// Create the prebuild command that runs our script
// Using prebuild because dependency state can change between builds
// and we want to validate on every build
let command = Command.prebuildCommand(
displayName: "Dependency Audit for \(target.name)",
executable: URL(fileURLWithPath: "/bin/bash"),
arguments: [
"-c",
"""
# Create output directory
mkdir -p "\(outputDir.path)"

# Execute dependency audit directly
\(scriptContent)
"""
],
outputFilesDirectory: outputDir
)

return [command]
}

/// Determines if a target should be analyzed for dependencies
private func shouldAnalyzeTarget(_ target: Target) -> Bool {
// For now, analyze all targets and let the tool itself filter appropriately
// The tool will skip targets that don't have source directories
return true
Copy link

Copilot AI Jul 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Analyzing all targets can lead to unnecessary executions on non-source modules; consider filtering to SourceModuleTarget instances within shouldAnalyzeTarget to reduce overhead.

Suggested change
// For now, analyze all targets and let the tool itself filter appropriately
// The tool will skip targets that don't have source directories
return true
// Only analyze source-based targets
return target is SourceModuleTarget

Copilot uses AI. Check for mistakes.
}
}
70 changes: 70 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,76 @@ swift run swift-dependency-audit --output-format github-actions
swift run swift-dependency-audit --output-format xcode --quiet
```

## Build Tool Plugin Integration

SwiftDependencyAudit includes a Swift Package Manager build tool plugin that automatically validates dependencies during builds, providing seamless integration with both Swift Package Manager and Xcode.

### Automatic Build Integration

When you add SwiftDependencyAudit as a dependency to your package, you can enable automatic dependency validation during builds by applying the build tool plugin to your targets:

```swift
// Package.swift
let package = Package(
name: "MyPackage",
dependencies: [
.package(url: "https://github.com/tonyarnold/swift-dependency-audit.git", from: "1.0.0")
],
targets: [
.target(
name: "MyLibrary",
dependencies: ["SomeOtherDependency"],
plugins: [
.plugin(name: "DependencyAuditPlugin", package: "swift-dependency-audit")
]
),
.testTarget(
name: "MyLibraryTests",
dependencies: ["MyLibrary"],
plugins: [
.plugin(name: "DependencyAuditPlugin", package: "swift-dependency-audit")
]
)
]
)
```

### Plugin Features

- **Automatic Execution**: Runs dependency validation before every build
- **Target-Specific Analysis**: Validates dependencies for each target individually
- **Xcode Integration**: Seamless integration with Xcode's build system and error reporting
- **Zero Configuration**: Works automatically once applied to targets
- **Build Performance**: Uses prebuild commands to validate before compilation begins
- **IDE-Friendly Output**: Generates Xcode-compatible error and warning messages

### Plugin Benefits

- **Early Detection**: Catches dependency issues before compilation
- **Developer Workflow**: Immediate feedback in Xcode and command-line builds
- **CI/CD Ready**: Automatic validation in continuous integration environments
- **No Manual Steps**: Eliminates need to manually run dependency audits
- **Build Integration**: Leverages Swift Package Manager's plugin architecture

### Plugin Behavior

The build tool plugin:
1. Runs before each target compilation
2. Analyzes the specific target being built
3. Uses Xcode-compatible output format for IDE integration
4. Excludes test dependencies when analyzing non-test targets
5. Operates in quiet mode to focus on issues
6. Integrates with Xcode's build log and error reporting

### Xcode Integration

When using the plugin in Xcode:
- Dependency issues appear as build errors and warnings
- Click on errors to navigate directly to the problematic import
- Warnings for unused dependencies link to Package.swift
- Full integration with Xcode's issue navigator
- Works with both workspace and package projects

## Sample Output

### Terminal Output (Default)
Expand Down