Xcode 8 extension executing NSTask
Asked Answered
P

2

10

My goal is to create an extension that executes clang-format. My code looks something like this:

- (void)performCommandWithInvocation:(XCSourceEditorCommandInvocation *)invocation completionHandler:(void (^)(NSError * _Nullable nilOrError))completionHandler
{
    NSError *error = nil;

    NSURL *executableURL = [[self class] executableURL];

    if (!executableURL)
    {
          NSString *errorDescription = [NSString stringWithFormat:@"Failed to find clang-format. Ensure it is installed at any of these locations\n%@", [[self class] clangFormatUrls]];
              completionHandler([NSError errorWithDomain:SourceEditorCommandErrorDomain
              code:1
              userInfo:@{NSLocalizedDescriptionKey: errorDescription}]);
          return;
    }

    NSMutableArray *args = [NSMutableArray array];
    [args addObject:@"-style=LLVM"];
    [args addObject:@"someFile.m"];
    NSPipe *outputPipe = [NSPipe pipe];
    NSPipe *errorPipe = [NSPipe pipe];

    NSTask *task = [[NSTask alloc] init];
    task.launchPath = executableURL.path;
    task.arguments = args;

    task.standardOutput = outputPipe;
    task.standardError = errorPipe;

    @try
    {
          [task launch];
    }
    @catch (NSException *exception)
    {
          completionHandler([NSError errorWithDomain:SourceEditorCommandErrorDomain
              code:2
              userInfo:@{NSLocalizedDescriptionKey: [NSString stringWithFormat:@"Failed to run clang-format: %@", exception.reason]}]);
          return;
    }

    [task waitUntilExit];

    NSString *output = [[NSString alloc] initWithData:[[outputPipe fileHandleForReading] readDataToEndOfFile]
          encoding:NSUTF8StringEncoding];
    NSString *errorOutput = [[NSString alloc] initWithData:[[errorPipe fileHandleForReading] readDataToEndOfFile]
          encoding:NSUTF8StringEncoding];
    [[outputPipe fileHandleForReading] closeFile];
    [[errorPipe fileHandleForReading] closeFile];

    int status = [task terminationStatus];
    if (status == 0)
    {
          NSLog(@"Success: %@", output);
    }
    else
    {
          error = [NSError errorWithDomain:SourceEditorCommandErrorDomain
              code:3
              userInfo:@{NSLocalizedDescriptionKey: errorOutput}];
    }

    completionHandler(error);
}

The reason I need that try-catch block is because an exception is thrown when I try to run this code. The exception reason is:

Error: launch path not accessible

The path for my clang-format is /usr/local/bin/clang-format. What I discovered is that it doesn't like me trying to access an application in /usr/local/bin, but /bin is ok (e.g. If I try to execute /bin/ls there is no problem).

Another solution I tried was to run /bin/bash by setting the launch path and arguments like this:

task.launchPath = [[[NSProcessInfo processInfo] environment] objectForKey:@"SHELL"];
task.arguments = @[@"-l", @"-c", @"/usr/local/bin/clang-format -style=LLVM someFile.m"];

This successfully launches the task, but it fails with the following error output:

/bin/bash: /etc/profile: Operation not permitted /bin/bash: /usr/local/bin/clang-format: Operation not permitted

The first error message is due to trying to call the -l parameter in bash, which tries to log in as the user.

Any idea how I can enable access to those other folders? Is there some kind of sandbox environment setting I need to enable?

Papst answered 9/9, 2016 at 14:26 Comment(0)
B
2

I guess that because of the sandboxing this is not possible. You could bundle the clang-format executable and use it from there.

Bugs answered 16/9, 2016 at 15:34 Comment(0)
J
0

Personally, I think you are going at it all wrong. Extensions are supposed to be quick (if you watch the video on Xcode extensions he repeats multiple times to get in and get out). And they are severely limited.

However, there is another - the container app may be able to do this processing for your extension without all the hacks. The downside is that you have to pass the buffer to and from the extension.

It’s not easy, but it can be done. Easy peasy way to get your container to run. First, modify the container app’s Info.plist (not the extension Info.plist) so that it has a URL type.

Info.plist

In your extension you can “wake up” the container app by running the following:

let customurl = NSURL.init(string: “yoururlschemehere://")
NSWorkspace.shared().open(customurl as! URL)

As for communication between the two, Apple has a plethora of methods. Me, I’m old-school, so I’m using DistributedNotificationCenter - for the moment.

Although I haven’t tried it, I do not see why the container app should have an issue chatting with clang (I’m using the container app for settings).

Jura answered 22/9, 2016 at 18:13 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.