Xcode 8 扩展执行 NSTask

Posted

技术标签:

【中文标题】Xcode 8 扩展执行 NSTask【英文标题】:Xcode 8 extension executing NSTask 【发布时间】:2017-01-17 17:47:22 【问题描述】:

我的目标是创建一个执行 clang-format 的扩展。我的代码如下所示:

- (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);

我需要 try-catch 块的原因是,当我尝试运行此代码时会引发异常。异常原因是:

错误:启动路径不可访问

我的 clang-format 的路径是 /usr/local/bin/clang-format。我发现它不喜欢我尝试访问 /usr/local/bin 中的应用程序,但是 /bin 是可以的(例如,如果我尝试执行 /bin/ls 没有问题)。

我尝试的另一个解决方案是通过设置启动路径和参数来运行 /bin/bash:

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

这成功启动了任务,但失败并显示以下错误输出:

/bin/bash: /etc/profile: 不允许操作 /bin/bash: /usr/local/bin/clang-format: 不允许操作

第一个错误消息是由于尝试在bash中调用-l参数,它试图以用户身份登录。

知道如何启用对其他文件夹的访问吗?我需要启用某种沙盒环境设置吗?

【问题讨论】:

【参考方案1】:

我猜由于沙盒,这是不可能的。 您可以捆绑 clang-format 可执行文件并从那里使用它。

【讨论】:

【参考方案2】:

就个人而言,我认为你完全错了。扩展应该很快(如果你在 Xcode 扩展上观看视频,他会重复多次进出)。而且它们受到严格限制。

但是,还有另一个 - 容器应用程序可能能够为您的扩展程序执行此处理,而无需所有黑客攻击。缺点是您必须将缓冲区传入和传出扩展。

这并不容易,但可以做到。让您的容器运行的简单方法。首先,修改容器应用的 Info.plist(不是扩展名 Info.plist),使其具有 URL 类型。

在您的扩展程序中,您可以通过运行以下命令“唤醒”容器应用:

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

至于两者之间的沟通,Apple 有很多方法。我,我是老派,所以我目前正在使用 DistributedNotificationCenter。

虽然我没有尝试过,但我不明白为什么容器应用程序会出现与 clang 聊天的问题(我正在使用容器应用程序进行设置)。

【讨论】:

以上是关于Xcode 8 扩展执行 NSTask的主要内容,如果未能解决你的问题,请参考以下文章

NSTask/NSAppleScript执行pod反馈env: ruby_executable_hooks: No such file or directory

如何在主线程上安全地使用[NSTask waitUntilExit]?

NSTask 启动路径错误 [重复]

在 Mac 应用程序中嵌入可执行文件

从 Cocoa 应用程序(通过 NSTask)静默运行 xcodebuild 两次失败

Swift NStask 函数