Add CuckooPluginPerModule for per-module mock generation#595
Add CuckooPluginPerModule for per-module mock generation#595SylvainRX wants to merge 4 commits intoBrightify:masterfrom
Conversation
- Create new CuckooPluginPerModule plugin that generates separate mock files per module dependency - Add content-equality guards in GenerateCommand to prevent unnecessary file rewrites - Skip writing output files when content unchanged This allows test targets to depend only on specific module mocks rather than all mocks in a single file, while maintaining the original plugin for existing users.
Enables test targets to override shared module mock generation by introducing CUCKOO_COMPOUND_MODULE_NAME (TARGET/MODULE format). - Plugin: - Pass CUCKOO_COMPOUND_MODULE_NAME env var for each dependency - Generate build command for test target itself (not just deps) - Generator: - Prioritize compound module key over plain module name - Support suppressor pattern (empty sources = empty output) This allows Cuckoofile.toml to specify different mock sources for the same module when used in different test targets, fixing issues where shared dependencies generate unwanted mocks.
| var modules: [Module] | ||
| if let compoundModuleName { | ||
| let compoundMatches = allModules.filter { $0.name == compoundModuleName } | ||
| if !compoundMatches.isEmpty { | ||
| // Compound key (TARGET/MODULE) found – use it exclusively. | ||
| // An entry with empty sources acts as a suppressor, producing an empty output file. | ||
| modules = compoundMatches | ||
| } else if let requestedModuleName { | ||
| // No compound override – fall back to the plain module name. | ||
| modules = allModules.filter { $0.name == requestedModuleName } | ||
| } else { | ||
| modules = [] | ||
| } | ||
| } else if let requestedModuleName { | ||
| modules = allModules.filter { $0.name == requestedModuleName } | ||
| } else { | ||
| modules = allModules | ||
| } |
There was a problem hiding this comment.
Since the allModules aren't used (and I can't think of why they would be), I'd like this filtering logic moved there instead, keeping this high-level implementation easy to read.
There was a problem hiding this comment.
I ended up getting rid of the concept of compound modules altogether, I don't think it was bringing that much value compared to what I have now. I updated the PR description accordingly
Package.swift
Outdated
| targets: ["CuckooPluginSingleFile"] | ||
| ), | ||
| .plugin( | ||
| name: "CuckooPluginPerModule", |
There was a problem hiding this comment.
I'd use "CuckooPluginModular" instead, does that make sense for this implementation?
There was a problem hiding this comment.
I'm not sure comparing potentially large strings on every generation run is the way to go here. I'd rather cache the mocked file names and their latest modification date which is then compared to the generated file modification date. I'm not asking you to implement that, buut I'd rather leave the logic as it is until that is implemented.
There was a problem hiding this comment.
Right, I reverted this part above
MatyasKriz
left a comment
There was a problem hiding this comment.
Looks great! I appreciate you taking the time to improve the project like this. Especially the automatic source detection might be nice for small projects.
One more thing, though – please mention this new modular build tool in README, so that users don't miss it. 🙂
|
@MatyasKriz Thank you for the review and feedbacks! I'll address them as soon as I have some time. |
Rename CuckooPluginPerModule plugin to CuckooPluginModular Refactor module filtering Revert the write logic for mock generation (get rid of the large string comparison)
I deemed the compound module concept as useless for now. If we want to import protocol with an identical name from different targets, we should work on prefixing them with their target name in the generated mocks.
f517ecb to
e63d7f8
Compare
SylvainRX
left a comment
There was a problem hiding this comment.
Hello @MatyasKriz !
I pushed changes addressing your comments, and updated the readme as well.
Please let me know if there's thing I can keep improving on.
Thanks!
Package.swift
Outdated
| targets: ["CuckooPluginSingleFile"] | ||
| ), | ||
| .plugin( | ||
| name: "CuckooPluginPerModule", |
| var modules: [Module] | ||
| if let compoundModuleName { | ||
| let compoundMatches = allModules.filter { $0.name == compoundModuleName } | ||
| if !compoundMatches.isEmpty { | ||
| // Compound key (TARGET/MODULE) found – use it exclusively. | ||
| // An entry with empty sources acts as a suppressor, producing an empty output file. | ||
| modules = compoundMatches | ||
| } else if let requestedModuleName { | ||
| // No compound override – fall back to the plain module name. | ||
| modules = allModules.filter { $0.name == requestedModuleName } | ||
| } else { | ||
| modules = [] | ||
| } | ||
| } else if let requestedModuleName { | ||
| modules = allModules.filter { $0.name == requestedModuleName } | ||
| } else { | ||
| modules = allModules | ||
| } |
There was a problem hiding this comment.
I ended up getting rid of the concept of compound modules altogether, I don't think it was bringing that much value compared to what I have now. I updated the PR description accordingly
There was a problem hiding this comment.
Right, I reverted this part above
Summary
Adds a new
CuckooPluginModularbuild tool plugin that generates separate mock files per module dependency, improving modularity for multi-target Swift Packages.Related issue: #555
Motivation
The existing
CuckooPluginSingleFilegenerates all mocks into a singleGeneratedMocks.swiftfile in derived data. In a Swift Package with multiple targets, each test target compiles independently and may not have visibility into types from unrelated modules, making a single shared mock file problematic.Changes
New Plugin: CuckooPluginModular
Generator/Plugin/Modular/CUCKOO_MODULE_NAMEenvironment variableGeneratedMocks_<ModuleName>.swiftCuckoofile.tomlto have a[modules.<TestTargetName>]entry to control which files are mocked and which imports are addedCuckoofile.tomlentry is required for each module; modules without a matching entry produce an empty fileUsage
Package.swift:
Cuckoofile.toml:
Breaking Changes
None. All changes are backward compatible with existing plugins and configurations.