Plugin command not running when package is indirectly included in Xcode project

I've been experimenting with the new plugin feature introduced in Swift 5.6 and have run into a strange issue.

My project uses a single Package.swift which contains ~20 local package targets. I have a target that is using an experimental SwiftGenPlugin which includes a pre-build command. I am using the plugin along with a swiftgen.yml file to generate a file to help with localization.

The following scenarios work fine:

  • Build that package directly
  • Build a package that depends on that package

I can monitor that location in derived-data and see that it is built almost immediately.

However, when I include one of the top level packages that depends on the package using SwiftGen inside an iOS project as a normal dependency, I will get build failures from other packages that depend on SwiftGen already having run. It's like it stops building the graph correctly once it's included in an actual Xcode project.

Is this expected? Does anyone have a workaround?

4 Likes

Here is the relevant portion of the build log for the iOS Xcode project target:
Screen Shot 2022-05-05 at 3.44.39 PM

And here is the relevant portion of the build log for the package that the iOS Xcode project target depends on (which includes the BTLocalization package as a dependency):
Screen Shot 2022-05-05 at 4.10.54 PM

Notice how the failed build never mentions SwiftGen while the successful case has it as the first step. The failed build also makes no reference to "prebuild commands".

Are these plugins intended to run when triggered from building an actual project?

Here is a project that reproduces the issue.

Notice that it fails if you try to build the "iOS App" target alone. Once you build either the "iOSAppFeature" or "Localization" package, future attempts to build the "iOS App" target will succeed.

I have been unable to make any progress on this issue. Can anyone tell me if this is a bug or functioning as designed?

I have tested this with Xcode 13.4 and have confirmed that the issue is still present.

I created the experimental plugin @rzulkoski mentioned in the original post.

I created this plugin in preparation for integration with our app's SPM micro-feature architecture where we are currently running SwiftGen from commit hooks.

I can also replicate the issue described using Ryan's simplified example, and I'll admit I hadn't gotten this far with integration on the plugin that I built - for reasons I'll explain.

I forked the above reproducing example to play with this a bit myself, and in doing so I stripped down the example as much as possible. To make sure the issue is not in the SwiftGen executable or anywhere else I just created a simple executable binary with the sole purpose of creating a file with the current reference time interval as it's filename in Derived Data.

It is incredibly difficult to debug anything for these plugins.

Sometimes swift build will give an error when Xcode does not
Xcode will say

:white_check_mark: Applying plugin "TouchPlugin" to target "iOSAppFeature"

But if you run swift build you will receive

error: failed: PrebuildCommand(configuration: SPMBuildCore.BuildToolPluginInvocationResult.CommandConfiguration(displayName: Optional("Create a file"), executable: AbsolutePath:"/Users/nicolas.richard/Projects/PluginNotRunning/SwiftPackages/.build/arm64-apple-macosx/debug/Touch", arguments: ["674575448.053521"], environment: [:], workingDirectory: nil), outputFilesDirectory: AbsolutePath:"/Users/nicolas.richard/Projects/PluginNotRunning/SwiftPackages/.build/plugins/outputs/swiftpackages/iOSAppFeature/TouchPlugin")

No output from the executable is included anywhere in Xcode
It would obviously be extremely useful to have the executable's output for debugging

You are allowed to violate the rules of the package plugins and there are no errors at all
SE-0305 proposes that plugins cannot depend on an executable target inside the package for technical reasons. But there are no errors or warnings if you do so.

Just try pointing your plugin to an executableTarget instead of a binaryTarget. There are no errors or warnings. But the command does not run.

You can do this in my example fork by just switching out the comments from binaryTarget to executableTarget.

There is some kind of synchronization issue with FileManager for plugins
Related to why I haven't continued integration of the plugin into our apps.

I've tried to do some things like clear out the plugin folder before writing new files to it and the behaviour seems to be that the deletion can occur at any time before/after the write.

This causes issues if a filename changes, or if a generated file is meant to be deleted. Extra garbage is left in DerivedData and developers have an inconsistent experience.

I know that one potential solution is to work on a build command which explicitly states its inputs and outputs but I haven't had the time to try this yet.


I hope my perspective helps in looking at this issue / improving the functionality.

A big help to me and my pursuit would be information for how to obtain the output from the command my plugin is running and/or any additional debug information for the build tool plugin.

4 Likes

Good news, this has been fixed in Xcode 14 beta 4! :tada: