Swift macOS app: cannot run executable in /usr/local/bin

Viewed 411

Context

I tried writing a macOS Swift app that launches an executable in /usr/local/bin (e.g. Homebrew-installed /usr/local/bin/cowsay).

However, this does not work because /usr/local/bin is not in the PATH environment variable.

Within the app, I tried adding /usr/local/bin to PATH via setenv, which also doesn't seem to work.

Code

Here is a code reproducer with Xcode 12.5 beta 3 (12E5244e):

import Foundation
import SwiftUI

func addPathComponentIfNotSet(_ pathComponent: String) {
  // If path component already exists in PATH, return.
  let path = ProcessInfo.processInfo.environment["PATH"]!
  let pathComponents = path.split(separator: ":")
  if pathComponents.contains(Substring(pathComponent)) {
    return
  }
  // Otherwise, prepend path component to PATH via setenv.
  let newPath = "\(pathComponent):\(path)"
  setenv("PATH", newPath, 1)
  // Print new PATH.
  print("Actual new PATH:", ProcessInfo.processInfo.environment["PATH"]!)
}

@discardableResult
func shell(_ args: String...) -> Int32 {
  let task = Process()
  task.launchPath = "/usr/bin/env"
  task.arguments = args
  task.launch()
  task.waitUntilExit()
  return task.terminationStatus
}

struct DummyContentView: View {
  var body: some View {
    Button("Press me") {
      // Add "/usr/local/bin" to PATH if it doesn't exist.
      // Prints: "Actual new PATH: /usr/local/bin:/Applications/Xcode-beta.app/Contents/Developer/usr/bin:/usr/bin:/bin:/usr/sbin:/sbin"
      addPathComponentIfNotSet("/usr/local/bin")

      // Test: running "ls" works.
      shell("ls")

      // Error 1: running "cowsay" here fails with "env: cowsay: Operation not permitted".
      shell("cowsay", "Test this")

      // Error 2: running "cowsay" here fails with "The file “cowsay” doesn’t exist".
      let executableURL = URL(fileURLWithPath: "/usr/local/bin/cowsay")
      try! Process.run(
        executableURL,
        arguments: ["Test this too"],
        terminationHandler: nil)
    }
  }
}

Questions

  1. Why do attempts to invoke /usr/local/bin/cowsay fail?
    • I suspect it is related to security sandboxing.
  2. In a Swift macOS app, is it possible to invoke executables within /usr/local/bin?
    • Perhaps it's possible if security measures like System Integrity Protection are disabled – other approaches would be ideal.
  3. How can I adapt the app to achieve my goal of invoking a local Homebrew-installed binary?
    • One idea is to (a) create a server that can invoke the binary and (b) change the app to be a client sending requests to the server. I wonder if there are more lightweight solutions.

Accepted solution notes

Bill's solution worked for me. Here's a screenshot showing the fix – making Build Settings > Signing > Code Signing Entitlements empty:

Fix: make Code Signing Entitlements empty

1 Answers

The main problem is sandboxing. If this is just for your own use, you could disable sandbox for this application. To do so, go to target / build settings / signing. Remove the name of the entitlements file from code signing entitlements.

Related