Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Plugins can no longer access output locations on an external drive #6948

Open
tomas-bat opened this issue Sep 25, 2023 · 14 comments
Open

Plugins can no longer access output locations on an external drive #6948

tomas-bat opened this issue Sep 25, 2023 · 14 comments
Labels

Comments

@tomas-bat
Copy link

Description

After updating to Xcode 15 & Swift 5.9, all plugins that generate code or update source code (SwiftGenPlugin, Swiftlint, ...) can no longer access output locations, if DerivedData is manually set to a custom location in Xcode settings (on an external drive).

If DerivedData is set to a default location, it works correctly.

Expected behavior

Plugins should be able to access output locations even if DerivedData is on an external drive.

Actual behavior

Plugins cannot access the output locations and fail with

Error Domain=NSCocoaErrorDomain Code=513 "You don’t have permission to save the file (...) in the folder (...)." UserInfo={NSURL=file:///Volumes/TSD/develop/Xcode/DerivedData/<app-directory>/SourcePackages/plugins/uitoolkit.output/UIToolkit/SwiftGenPlugin/Localizable.generated.swift, NSUserStringVariant=Folder, NSUnderlyingError=0x600000cb2be0 {Error Domain=NSPOSIXErrorDomain Code=1 "Operation not permitted"}}

Steps to reproduce

  1. Use a plugin that generates code or updates source files in a project
  2. Set a custom DerivedData location to an external drive (Xcode -> Settings -> Locations)
  3. Try to run the plugin

Swift Package Manager version/commit hash

Swift Package Manager, Swift 5.9

Swift & OS version (output of swift --version ; uname -a)

swift-driver version: 1.87.1 Apple Swift version 5.9 (swiftlang-5.9.0.128.108 clang-1500.0.40.1)
Target: arm64-apple-macosx13.0

Darwin Tom---MacBook-Pro.local 22.6.0 Darwin Kernel Version 22.6.0: Wed Jul  5 22:22:52 PDT 2023; root:xnu-8796.141.3~6/RELEASE_ARM64_T8103 arm64
@Kyle-Ye
Copy link
Contributor

Kyle-Ye commented Sep 27, 2023

Could you provide a minimal Xcode Project / Swift Package to reproduce the issue you report? Thanks @tomas-bat

@neonichu
Copy link
Contributor

If I understand this issue correctly, you're only seeing this in Xcode? Or also when using the SwiftPM CLI, e.g. with --scratch-path?

@tomas-bat
Copy link
Author

Could you provide a minimal Xcode Project / Swift Package to reproduce the issue you report?

@Kyle-Ye Sure, here's the Swift Package: https://github.com/tomas-bat/spm-derived-data-issue

If I understand this issue correctly, you're only seeing this in Xcode? Or also when using the SwiftPM CLI, e.g. with --scratch-path?

@neonichu It also fails when using the SwiftPM CLI:

$ swift build --scratch-path /Volumes/<external-drive>/<some-path>
Executing configuration file <path-to-project>/SwiftPackage/Sources/SwiftPackage/swiftgen.yml
 $ swiftgen xcassets --templatePath swiftgen-xcassets.stencil --param publicAccess --output /Volumes/<external-drive>/<some-path>/plugins/outputs/swiftpackage/SwiftPackage/SwiftGenPlugin/Assets.generated.swift Resources/Assets.xcassets
Error: You don’t have permission to save the file “Assets.generated.swift” in the folder “SwiftGenPlugin”.

This error does not appear when the scratch path is set to a location on the main drive, e.g. ~/Desktop/build

@neonichu
Copy link
Contributor

I was wondering whether #6910 would be the culprit here, but it is actually not part of 5.9 yet, it has been merged to the branch to end up in the next 5.9 update. So now I am wondering instead whether that is a fix, I'll try to validate that soon.

@Kyle-Ye
Copy link
Contributor

Kyle-Ye commented Oct 4, 2023

I have successfully reproduced the issue locally.

The sandbox profile seems fine. And I tried it with my demo CLI app.

So I suspect there was a bug in "SwiftGen" binary.

/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/hs/g5q9gn6d5tx6_3bxmq1pj0rw0000gp/T\")
)
(deny file-write*
    (subpath \"/Users/kyle/tmp/spm-derived-data-issue/SwiftPackage\")
)
(allow file-write*
    (subpath \"/Volumes/Resource/Tmp/SwiftPackage-avihhefbofskanfegeumqytjvufm/SourcePackages/plugins/swiftpackage.output/SwiftPackage/SwiftGenPlugin\")
)
" /Volumes/Resource/Tmp/SwiftPackage-avihhefbofskanfegeumqytjvufm/SourcePackages/artifacts/swiftgenplugin/swiftgen/swiftgen.artifactbundle/swiftgen/bin/swiftgen config run --verbose --config /Users/kyle/tmp/spm-derived-data-issue/SwiftPackage/Sources/SwiftPackage/swiftgen.yml

I made a clone locally and replaced the swiftgen path in the command to try to debug it.

Also remember to replace ${DERIVED_SOURCES_DIR} to the actual dir path.

After some debugging, I found the problem is an implementation detail of the write method.

SwiftGen currently use String.write(toFile:atomically:encoding) API which in this case will fail. Using Data.write(to:) is fine in such case.

try path.write(content)
extension Path {
  public func write(_ string: String, encoding: String.Encoding = String.Encoding.utf8) throws {
    try string.write(toFile: normalize().path, atomically: true, encoding: encoding)
  }
}

Following the existing API of SwiftGen, we may try to use the Path.write(_ data: Data) API. However this will also fail due to the .atomic option.

try path.write(content.data(using: .utf8)!)
extension Path {
  public func write(_ data: Data) throws {
    try data.write(to: normalize().url, options: .atomic)
  }
}

In conclusion, for external Volume sandbox file writing:

  • ✅ data.write(to: url)
  • ✅ string.write(toFile: path, atomically: false, encoding: .utf8)
  • ❌ data.write(to: url, options: .atomic)
  • ❌ string.write(toFile: path, atomically: true, encoding: .utf8)

A short fix for this issue is a PR for SwiftGen.

A long fix is digging why such API is not working on such case. But Sandbox implementation on macOS and Foundation on Darwin Platform is not open sourced. Maybe file a Feedback issue to Apple to the only thing we can do.

Anyway this should not be an issue of SwiftPM.

@neonichu
Copy link
Contributor

neonichu commented Oct 4, 2023

Thanks for the investigation, sounds like the problem should indeed be fixed by #6910 then.

The issue is that the atomic versions of these APIs write to a temporary side location on the external volume that isn't part of the sandbox profile in 5.9, so it gets denied.

@Kyle-Ye
Copy link
Contributor

Kyle-Ye commented Oct 4, 2023

Since this is already fixed by the upstream. I think I do not need to create a PR to do the temporary fix. (A PR for SwiftGen -> merged -> Release a new SwiftGen version -> A update version PR for SwiftGenPlugin -> merged -> You issue is fixed)

Rather let's wait for the next Xcode version which will bring the fix in Swift 5.9.1 cc @tomas-bat

@Kyle-Ye
Copy link
Contributor

Kyle-Ye commented Oct 4, 2023

Thanks for the investigation, sounds like the problem should indeed be fixed by #6910 then.

The issue is that the atomic versions of these APIs write to a temporary side location on the external volume that isn't part of the sandbox profile in 5.9, so it gets denied.

Tested it with Xcode 15.1 Beta - Swift 5.9 (5.9.2.1.6) and still fails. Looks like the fix commit is not included yet. Hope to see it landing on the release of Xcode 15.1.

@mrmacete
Copy link

tested on Xcode 15.1 beta 3 and still fails

@Kyle-Ye
Copy link
Contributor

Kyle-Ye commented Dec 7, 2023

Xcode 15.1 RC use the same Swift version as Xcode 15.1 Beta 3 - Swift 5.9.2 (5.9.2.2.56)

Looks like we'll have to wait for the next Xcode releasing 😂

@shadone
Copy link

shadone commented Jan 16, 2024

Is the fix still not released? I hit the same issue with latest Xcode 15.2.0 (swift 5.9.2).

Xcode Cloud seems to use DerivedData folder on an external drive, so this issue here makes it impossible to use e.g. SwiftGenPlugin in projects build on Xcode Cloud

@Kyle-Ye
Copy link
Contributor

Kyle-Ye commented Jan 16, 2024

Is the fix still not released? I hit the same issue with latest Xcode 15.2.0 (swift 5.9.2).

Xcode Cloud seems to use DerivedData folder on an external drive, so this issue here makes it impossible to use e.g. SwiftGenPlugin in projects build on Xcode Cloud

Expected. Xcode 15.2 use the same version of Swift as Xcode 15.1 - Swift 5.9.2 (5.9.2.2.56).

@Kyle-Ye
Copy link
Contributor

Kyle-Ye commented Jan 16, 2024

Is the fix still not released? I hit the same issue with latest Xcode 15.2.0 (swift 5.9.2).

Xcode Cloud seems to use DerivedData folder on an external drive, so this issue here makes it impossible to use e.g. SwiftGenPlugin in projects build on Xcode Cloud

If anyone is eager is for a fix. You can try fork SwiftGenPlugin and apply the patch I mentation in #6948 (comment)

@shadone
Copy link

shadone commented Jan 16, 2024

Thanks, I tried fixing it like you said - instead of using String.write(toFile: file, atomically: true, encoding: encoding) I use atomically: false
That seems to have fixed my problems - now I can use SwiftGenPlugin (SPM Plugin for SwiftGen) in Xcode Cloud

SwiftGen/SwiftGenPlugin#18 (comment)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

5 participants