Unify build tool plugins with prebuilt artifact bundle and Linux support#231
Merged
Unify build tool plugins with prebuilt artifact bundle and Linux support#231
Conversation
…ndle support Replace the dual-plugin architecture (SafeDIGenerator + SafeDIPrebuiltGenerator) with a single SafeDIGenerator plugin that shells out to SafeDITool for scanning and generation. - Add `scan` and `generate` subcommands to SafeDITool (`generate` is default for backward compatibility) - Plugin calls `SafeDITool scan` via Process during createBuildCommands to build the manifest, then returns a `.buildCommand` for `SafeDITool generate` - Add `usePrebuiltBinary` flag in Package.swift to switch between artifact bundle (prebuilt) and source-built SafeDITool - Add artifact bundle binary target (placeholder checksum, updated by publish workflow) - Move output file naming and relative path utilities from SafeDIScannerCore to SafeDICore - Add `additionalInputFiles` field to SafeDIToolManifest for build input tracking - Delete SafeDIScannerCore, SafeDIScanner, SafeDIPrebuiltGenerator, InstallSafeDITool - Update publish workflow: workflow_dispatch with version/branch/dry-run inputs, Linux builds, artifact bundle assembly, auto-update Package.swift - Update documentation, examples, and CI Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #231 +/- ##
==========================================
Coverage 100.00% 100.00%
==========================================
Files 39 41 +2
Lines 6122 5752 -370
==========================================
- Hits 6122 5752 -370
🚀 New features to boost your workflow:
|
… Xcode
- Bump swift-argument-parser minimum from 1.2.0 to 1.4.0 (1.3.1+ has
CommandConfiguration: Sendable, needed for Swift 6 concurrency)
- Delete stale Package.resolved in example projects
- Use .prebuildCommand for Xcode plugin context since context.tool(named:)
returns paths with unresolved build variables (${BUILD_DIR}) that can't
be used with Process during createBuildCommands
- Add --output-directory and --mock-scoped-files to Generate command so
it can self-scan when invoked without --swift-manifest
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Extract scan logic into free `performScan(...)` function callable by both `Scan.run()` and `Generate.run()` — avoids ArgumentParser's uninitialized property issue when creating ParsableCommand structs directly - Update all test code to use `Generate.parse([...])` instead of `Generate()` which leaves @option properties uninitialized - Fix test function signatures to propagate throws from parse calls Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Cover the duplicate-basename disambiguation logic in OutputFileNaming.swift by testing through the full scan pipeline with files in subdirectories. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Xcode's plugin context returns tool paths with unresolved build variables
(${BUILD_DIR}/${CONFIGURATION}) that can't be used with Process during
createBuildCommands. Prebuild commands also don't work with source-built
executables in Xcode.
Solution: add a lightweight PluginScanner that determines output files
in-process (text-based, no SwiftSyntax), then return a .buildCommand
with --output-directory so SafeDITool does scan+generate at build time.
Verified all example projects build:
- Example Package Integration (SPM)
- ExampleProjectIntegration (Xcode)
- ExampleMultiProjectIntegration (Xcode)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ate, regex matching - Scan additional directory files for roots (P1: roots from additionalDirectoriesToInclude were missing from declared outputs) - Extract and declare outputs for additionalMocksToGenerate from #SafeDIConfiguration (P1: cross-module mock outputs were missing) - Use regex patterns instead of raw substring matching to reduce false positives from comments/strings (P2: @INSTANTIABLE in a comment could declare phantom outputs) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… branch) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace `usePrebuiltBinary` boolean with proper Package Traits: - `prebuilt` (default): uses artifact bundle binary target - `sourceBuild`: compiles SafeDITool from source The binaryTarget now always exists in the targets array, pointing to the alpha-10 release artifact bundle. This proves the trait plumbing works (SPM resolves the artifact, plugin uses it). The old binary doesn't have the new `scan` subcommand, so Xcode projects will get argument errors until a proper release with the new SafeDITool is published. Example Package Integration uses `traits: ["sourceBuild"]` since it's a local path dependency that needs the source-built tool. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Tests need sourceBuild trait to compile SafeDITool from source. Xcode project integration jobs use the prebuilt artifact bundle which contains the old SafeDITool without --output-directory support. Disabled until a release with the new SafeDITool is published. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…tBinary sed Artifact bundle is now always uploaded as a workflow artifact (not just dry-run), so if the release step fails the bundle can be downloaded and the release created manually. Removed the usePrebuiltBinary sed since we switched to Package Traits. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… dead code - README.md: Replace usePrebuiltBinary reference with sourceBuild trait - CLAUDE.md: Add --traits sourceBuild to build/test commands - OutputFileNaming.swift: Remove unused relativePath property from FileInfo - Delete stale ExamplePrebuiltPackageIntegration/Package.resolved Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… coverage - Delete ExamplePrebuiltPackageIntegration (prebuilt is now the default, no special example needed) - Add sourceBuild trait to Xcode example projects via UI - Add comment to Example Package Integration explaining sourceBuild trait - Update README example projects note about sourceBuild trait - Add mutual exclusivity guard: #if prebuilt && sourceBuild / #error - Extract update-version.sh script from publish workflow sed commands - Add CI job to test update-version.sh script - Publish workflow: always commit (even dry-run), only push/release on real runs. Use RELEASE_UPLOADER PAT for checkout to bypass branch protection. - Add prerelease input to publish workflow - Remove dead directoryBaseURL branch in ScanCommand - Add tests for --output-directory auto-scan, Scan.run(), and --output-directory without CSV error - Bump artifact bundle URL to alpha-11 (placeholder for next release) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
8342d34 to
2bb5227
Compare
# Conflicts: # .github/workflows/publish.yml
SPM resolves the full package graph (including binaryTarget) even when building a single product. Without the trait, builds fail trying to download the artifact bundle that doesn't exist yet. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This reverts commit 18e0ff2.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
gh release create fails if the tag/release already exists. Delete it first (with --cleanup-tag) so republishing a version works. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ba17930 to
2abd091
Compare
2abd091 to
b4e0df1
Compare
Deleting a release removes the artifact bundle, breaking package resolution for all consumers. Instead, create if new or edit if existing, then upload assets with --clobber to replace in-place. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
b4e0df1 to
4513c16
Compare
dfed
commented
Apr 9, 2026
dfed
commented
Apr 9, 2026
This reverts commit 69689c0.
- Remove safeDIVersion from Shared.swift (no longer needed) - Remove check-version-consistency.sh (no duplication to check) - Remove version-check CI job - Change currentVersion to "0.0.0-development" (stamped by publish jobs) - update-version.sh now only updates Package.swift (URL + checksum) - Publish build jobs stamp version via sed before building - Add else branches to relativePath functions - Upgrade upload-artifact to v7 - Address PR review comments Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
update-version.sh no longer modifies SafeDITool.swift (version is stamped at build time), so the CI check shouldn't look for it there. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The workflow always runs from the branch it should commit to, so the separate branch input is redundant. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The GitHub API can return 404 if the release is created too quickly after pushing the tag. Sleep 5s after push, retry create up to 3 times. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ip -9 - macOS: build with -Xswiftc -Osize -Xlinker -dead_strip, strip -rSTx after lipo (before codesigning) - Linux: build with -Xswiftc -Osize, strip -s after build - Use zip -9 for maximum compression on artifact bundle Expected ~50% size reduction based on SwiftLint/SwiftFormat precedent. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This reverts commit c209768.
…ip -9 - macOS: build with -Xswiftc -Osize -Xlinker -dead_strip, strip -rSTx after lipo (before codesigning) - Linux: build with -Xswiftc -Osize, strip -s after build - Use zip -9 for maximum compression on artifact bundle Expected ~50% size reduction based on SwiftLint/SwiftFormat precedent. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Stripping after lipo invalidated the codesignature. Move strip to the build jobs (before codesigning) so the signed binary is valid. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Parse all files once and filter the results for mock-scoped subset instead of parsing mock-scoped files a second time. ~20% scan speedup. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The previous regex `[^)]*` stopped matching at any `)`, including those inside string literal arguments like `mockAttributes: "@available(iOS 17, *)"`. This caused under-matching — files with `generateMock: true` after such arguments would not be detected. Use `(.|\n)*?` (non-greedy any character) instead, which matches through parentheses in strings while still finding the target argument. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
SafeDIGenerator+SafeDIPrebuiltGenerator) with a singleSafeDIGeneratorpluginscanandgeneratesubcommands toSafeDITool(generateis the default for backward compatibility)SafeDITool scanviaProcessduringcreateBuildCommands, then returns a.buildCommandfor code generationPluginScannerto discover output files, then returns a.buildCommandwith--output-directoryfor combined scan+generate at build timeprebuilt(default, uses artifact bundle) andsourceBuild(compiles from source), with mutual exclusivity guardSafeDIScannerCore,SafeDIScanner,SafeDIPrebuiltGenerator,InstallSafeDITool,ExamplePrebuiltPackageIntegrationsourceBuildtrait with comments explaining it's for local devcurrentVersionis"0.0.0-development"in source, stamped at build time by publish workflow-Osize, codesigned+notarized, Linux with--static-swift-stdlibworkflow_dispatchwith version/prerelease/dry-run inputs, artifact bundle always uploaded as workflow artifactMotivation
The in-process
SafeDIScannerruns in debug mode inside the plugin process (~15s) because SPM provides no way to build plugin code in release mode. Shelling out to a prebuiltSafeDIToolbinary eliminates this bottleneck entirely.Test plan
swift test --traits sourceBuild)swift build --traits sourceBuildsucceedsswift build(default prebuilt trait) succeeds when artifact bundle existsswift build --traits sourceBuild,prebuilterrors with mutual exclusivity message./CLI/lint.shpasses🤖 Generated with Claude Code