Swift build tool plugin unable to write to pluginWorkDirectory using Xcode Cloud

I have created a Swift package build tool plugin for colour generation. The plugin takes two input files from a project or swift package and from them generates a new Swift file containing references to all of the generated UIColors. When building locally and when using Github Actions the plugin is able to generate the required file within the pluginWorkDirectory and the project that references it can then access it as an input file during its build process.

However, when building my project using Xcode Cloud, the plugin appears to be unable to create the file, it fails with the following error:

Error while generating colours Files encountered an error at '/Volumes/workspace/DerivedData/SourcePackages/plugins/GLA.output/GLA/ColorGeneratorPlugin/GeneratedColors/Colors.swift'.

2022-11-22T15:46:11.344345498Z    Reason: fileCreationFailed
2022-11-22T15:46:11.344390826Z    LLVM Profile Error: Failed to write file "default.profraw": Operation not permitted`

This then means that the build of the project fails with this error:

Error opening input file '/Volumes/workspace/DerivedData/SourcePackages/plugins/GLA.output/GLA/ColorGeneratorPlugin/GeneratedColors/Colors.swift' (No such file or directory)

It appears that Xcode Cloud is blocking the creation of files in the pluginWorkDirectory, even though locally it can.

Has anyone faced a similar issue? Is there anything I can do to make it work in Xcode Cloud?

I've encountered a similar issue building an app with a package, MyPackage, that has a single MyPackage target using a FooPlugin build tool plugin from the Foo package. Everything works as expected when building locally, but I encounter an error on Xcode Cloud due to insufficient permissions:

=== BUILD TARGET MyPackage OF PROJECT MyPackage WITH CONFIGURATION Debug ===

...

WriteAuxiliaryFile /Volumes/workspace/DerivedData/Build/Intermediates.noindex/MyPackage.build/Debug-iphonesimulator/MyPackage.build/Script-8495445145755076753.sh (in target 'MyPackage' from project 'MyPackage')
    cd /Volumes/workspace/repository/Packages/MyPackage
    write-file /Volumes/workspace/DerivedData/Build/Intermediates.noindex/MyPackage.build/Debug-iphonesimulator/MyPackage.build/Script-8495445145755076753.sh

PhaseScriptExecution Foo /Volumes/workspace/DerivedData/Build/Intermediates.noindex/MyPackage.build/Debug-iphonesimulator/MyPackage.build/Script-8495445145755076753.sh (in target 'MyPackage' from project 'MyPackage')
    cd /Volumes/workspace/repository/Packages/MyPackage
    /bin/sh -c /Volumes/workspace/DerivedData/Build/Intermediates.noindex/MyPackage.build/Debug-iphonesimulator/MyPackage.build/Script-8495445145755076753.sh
Error: You don’t have permissio
n to save the file “Foo.swift” in the folder “FooPlugin”
.
Command PhaseScriptExecution failed with a nonzero exit code

Result bundle written to path:
	/Volumes/workspace/resultbundle.xcresult

** TEST BUILD FAILED **

For reference, the output from the earlier Process build tool plug-in results is:

Apply build tool plug-in “FooPlugin” to target “MyPackage” in package “mypackage”

Process build tool plug-in results
package: <IDESwiftPackageCore.IDESwiftPackage:0x7f9c1c2069e0 path:'/Volumes/workspace/repository/Packages/mypackage'>
    target: MyPackage
        prebuildOutputPaths: []
        buildCommands: [SwiftPM.SPMCustomBuildCommand(displayName: Optional("Foo"), executable: "/${BUILD_DIR}/${CONFIGURATION}/Foo", arguments: ["--input", "/Volumes/workspace/repository/Packages/MyPackage/Sources/MyPackage/Resources/en.lproj/Localizable.strings", "--output", "/Volumes/workspace/DerivedData/SourcePackages/plugins/mypackage.output/MyPackage/FooPlugin/Foo.swift"], environment: [:], workingDir: nil, inputPaths: ["/Volumes/workspace/repository/Packages/MyPackage/Sources/MyPackage/Resources/en.lproj/Localizable.strings"], outputPaths: ["/Volumes/workspace/DerivedData/SourcePackages/plugins/mypackage.output/MyPackage/FooPlugin/Foo.swift"], sandboxProfile: Optional(SwiftPM.SPMSandboxProfile(strictness: Basics.Sandbox.Strictness.writableTemporaryDirectory, writableDirectories: [<AbsolutePath:"/Volumes/workspace/DerivedData/SourcePackages/plugins/mypackage.output/MyPackage/FooPlugin">], readOnlyDirectories: [<AbsolutePath:"/Volumes/workspace/repository/Packages/MyPackage">])))]
        allDerivedOutputPaths: ["/Volumes/workspace/DerivedData/SourcePackages/plugins/mypackage.output/MyPackage/FooPlugin/Foo.swift"]

I have been unable to fix this.

We also see this issue, interestingly when Xcode cloud shows the call to run the generated script, the surrounding sandbox-exec does seem to say that writing to this directory should be allowed, yet it still fails:


Swift/ErrorType.swift:200: Fatal error: Error raised at top level: Error Domain=NSCocoaErrorDomain Code=513 "You don’t have permission to save the file “String+TransactionsAccessors.swift” in the folder “StringsGeneratorPlugin”." UserInfo={NSURL=file:///Volumes/workspace/DerivedData/SourcePackages/plugins/SampleApp.output/SampleApp/StringsGeneratorPlugin/String+TransactionsAccessors.swift, NSUserStringVariant=Folder, NSUnderlyingError=0x600003104330 {Error Domain=NSPOSIXErrorDomain Code=1 "Operation not permitted"}}

then in the call to sandbox-exec


/Volumes/workspace/DerivedData/Build/Intermediates.noindex/SampleApp.build/Debug-iphonesimulator/SampleApp.build/Script-7981050304838536995.sh: line 17: 12085 Illegal instruction: 4  /usr/bin/sandbox-exec -p "(version 1)
(deny default)
(import \"system.sb\")
(allow file-read*)
(allow process*)
(allow file-write*
    (subpath \"/private/tmp\")
    (subpath \"/private/var/folders/22/cm0mxjnn4zg5l2rs2l3b51qr0000gn/T\")
)
(deny file-write*
    (subpath \"/Volumes/workspace/repository/SampleApp\")
)
(allow file-write*
    (subpath \"/Volumes/workspace/DerivedData/SourcePackages/plugins/SampleApp.output/SampleApp/StringsGeneratorPlugin\")
)

Does anyone have a good solution?

After months of fighting with permission on Xcode Cloud, by chance I found what was the issue on my case!

In my plugin implementation I write the file through

"""
// auto generated swift file

// ... the content
""".write(to: output, atomically: true, encoding: .utf8)

Using atomically: true is the issue: this means that swift writes the entire content into a temporary file placed somewhere else and Xcode Cloud doesn't have permission in this moment!

Using atomically: false the file is written directly into our output file, which is the context.pluginWorkDirectory.appending("GeneratedImageAssets.swift") and permissions here hare granted!!

Swift build tool plugin unable to write to pluginWorkDirectory using Xcode Cloud
 
 
Q