Package Manager executable not available when building for platforms other than macOS

Consider the following Package manifest file:

let package = Package(
    name: "FooPackage",
    products: [
        .executable(
            name: "FooExecutable",
            targets: ["FooExecutable"]
        ),
        .plugin(
            name: "FooPlugin",
            targets: ["FooPlugin"]
        ),
        .library(
            name: "FooLibrary",
            targets: ["FooLibrary"]
        )
    ],
    targets: [
        .executableTarget(name: "FooExecutable"),
        .plugin(
            name: "FooPlugin",
            capability: .buildTool(),
            dependencies: ["FooExecutable"]
        ),
        .target(
            name: "FooLibrary",
            dependencies: [],
            plugins: ["FooPlugin"]
        )
    ]
)

And a pretty basic build tool plugin:

@main struct FooPlugin: BuildToolPlugin {
    func createBuildCommands(context: PluginContext, target: Target) async throws -> [Command] {
        let tool = try context.tool(named: "FooExecutable")
        return [
            .buildCommand(
                displayName: "Running FooExecutable",
                executable: tool.path,
                arguments: []
            )
        ]
    }
}

The library target declares its dependency on the plugin, which in turn declares its dependency on an executable target, all contained within the same manifest.

When building for macOS, the executable command declared by the plugin runs successfully and the package build itself also succeeds. However if I change the destination to any other platform (i.e. iOS) , the build fails with the following error:

sandbox-exec: execvp() of '//Users/rogerio.assis/Library/Developer/Xcode/DerivedData/z-fppyndgqmxtxktbgiugzikvzesui/Build/Products/Debug-iphoneos/FooExecutable' failed: No such file or directory

It looks like the generated build script is looking for the executable inside Debug-iphoneos however the executable is located inside the Debug folder.

Is this expected behavior?
Thanks!
Rog

1 Like

I'm using Swift 5.7 and Xcode 14.2 and seeing this too. In my case, it works when doing a regular "Build" from Xcode and fails when doing an "Archive". I have more details below, but the "No such file or directory" error is interesting because the path to the executable that the Plugin command product shell script uses, is just a soft linked file to where the executable should be, but is not.


Structure of the project (very similar to the above package):

  • The Xcode Target has a SPM dependency to a local Package (Package A).
  • The local Package A, has dependancies on remote Package B
  • In Local Package A, a target uses a plugin defined by remote Package B.
  • The Plugin from remote Package B uses an .executableTarget defined in the same remote Package B.

I am writing all of these packages and have full source control.

The Important Build Steps:

  1. When I do a clean “Build”, the build system will:
  2. Compile the .executableTarget in Package B for macOS.
  3. Execute the Plugin from Package B, applied to library target in Package A - producing a command that uses the .executableTarget from above.
  4. Runs this command when compiling the library target in Package A. (The plugin outputs plists for the bundle)
  5. Compiles the Xcode target, linking all the stuff, etc…

The difference between a “Build” and “Archive” is that step 2 outputs the executable to different paths, and step 3 fails with the error that the executable can’t be found. :boom:

The name of this executable is cbt. Full paths below are truncated, … represents the DerivedData dir for the Xcode project.

During a “Build” the executable is written to:
.../Build/Products/cbt

During an “Archive” the executable appears to be written to:
…/Build/Intermediates.noindex/ArchiveIntermediates/Staging/BuildProductsPath/Debug/cbt

However, that file is just a soft link to another path:
.../Build/Intermediates.noindex/ArchiveIntermediates/Staging/IntermediateBuildFilesPath/UninstalledProducts/macosx/cbt
When the shell script produced by the plugin is executed, it fails with this error because there is no cbt at the last path above:

sandbox-exec: execvp() of '.../Build/Intermediates.noindex/ArchiveIntermediates/Staging/BuildProductsPath/Debug/cbt' failed: No such file or directory
Command PhaseScriptExecution failed with a nonzero exit code

Possible Solution:

I’d love to understand why this is happening. At this point my one solution I am going to try is to change Package B to vend a .binaryTarget of cbt similar to how the SwiftLint package is doing it.

Maybe that will get around the problem? I'll post again if I get that working.

At this point my one solution I am going to try is to change Package B to vend a .binaryTarget of cbt similar to how the SwiftLint package is doing it.

This did work. It has the advantage that the executable doesn't have to be recompiled during a clean build unless I am actively developing it. (This is controlled via manually changing a variable in its Package.swift) However this does have the downside that any changes have to be published as a release to Github. For a private project like our this requires some extra configuration. We setup a GitHub personal access token for each engineer and a machine user to authenticate:

Engineer's Mac: Add a Keychain item

Name: api.github.com
Kind: Internet password
Account: <engineer's account name>
Where: https://api.github.com
Password: <engineer's personal access token>

CI: We configure the CI to write a ~/.netrc file that includes the token as a password:

cat > /Users/distiller/.netrc \<< EOF
machine api.github.com
    login <MachineUserName>
    password ${MACHINE_USER_PERSONAL_API_TOKEN}
EOF