diff --git a/contrib/mac/frameworkapp/ExecSandbox/ExecSandbox.entitlements b/contrib/mac/frameworkapp/ExecSandbox/ExecSandbox.entitlements new file mode 100644 index 0000000000000..a22bc37478f9f --- /dev/null +++ b/contrib/mac/frameworkapp/ExecSandbox/ExecSandbox.entitlements @@ -0,0 +1,12 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.temporary-exception.files.absolute-path.read-only + + / + + + diff --git a/contrib/mac/frameworkapp/ExecSandbox/ExecSandbox.h b/contrib/mac/frameworkapp/ExecSandbox/ExecSandbox.h new file mode 100644 index 0000000000000..391a3b7c5f7c8 --- /dev/null +++ b/contrib/mac/frameworkapp/ExecSandbox/ExecSandbox.h @@ -0,0 +1,7 @@ +// This file is a part of Julia. License is MIT: https://julialang.org/license + +@import Foundation; +#import "ExecSandboxProtocol.h" + +@interface ExecSandbox : NSObject +@end diff --git a/contrib/mac/frameworkapp/ExecSandbox/ExecSandbox.m b/contrib/mac/frameworkapp/ExecSandbox/ExecSandbox.m new file mode 100644 index 0000000000000..a8c4a2cbfd75c --- /dev/null +++ b/contrib/mac/frameworkapp/ExecSandbox/ExecSandbox.m @@ -0,0 +1,149 @@ +// This file is a part of Julia. License is MIT: https://julialang.org/license + +#import "ExecSandbox.h" + +@class JuliaTask; + +@interface ExecSandbox () { + NSMutableArray *_Nonnull _tasks; +} +- (void)taskTerminated:(JuliaTask *_Nonnull)jt; +@end + +@interface JuliaTask : NSObject { + ExecSandbox *__weak _delegate; + dispatch_block_t _Nullable _onCleanup; + NSTask *_Nonnull _task; +} +@end + +@implementation JuliaTask + +- (instancetype)initWithTask:(NSTask *_Nonnull)task + delegate:(ExecSandbox *)d + cleanup:(dispatch_block_t)onCleanup { + self = [super init]; + if (self == nil) { + return nil; + } + _delegate = d; + _onCleanup = onCleanup; + _task = task; + return self; +} + +- (void)launch:(void (^_Nullable)(int status))onTermination { + dispatch_block_t onCleanup = _onCleanup; + JuliaTask __weak *weakSelf = self; + _task.terminationHandler = ^(NSTask *_Nonnull t) { + if (onTermination != nil) { + onTermination(t.terminationStatus); + } + if (onCleanup != nil) { + onCleanup(); + } + JuliaTask *strongSelf = weakSelf; + if (strongSelf) { + [strongSelf->_delegate taskTerminated:strongSelf]; + } + }; + @try { + [_task launch]; + } @catch (NSException *exception) { + NSLog(@"NSTask launch exception: %@", exception); + } +} + +- (void)terminate { + @try { + [_task terminate]; + } @catch (NSException *exception) { + NSLog(@"NSTask terminate exception: %@", exception); + } +} + +@end + +@implementation ExecSandbox + +- (instancetype)init { + self = [super init]; + if (self == nil) { + return nil; + } + _tasks = [[NSMutableArray alloc] init]; + return self; +} + +- (void)eval:(NSString *)p + withJulia:(NSData *)executableBookmark + arguments:(NSArray *)baseArgs + task:(void (^)(id task, NSFileHandle *stdIn, + NSFileHandle *stdOut, NSFileHandle *stdErr))reply { + + NSURL *executableURL = + [NSURL URLByResolvingBookmarkData:executableBookmark + options:NSURLBookmarkResolutionWithoutUI + relativeToURL:nil + bookmarkDataIsStale:nil + error:nil]; + if (executableURL == nil) { + reply(nil, nil, nil, nil); + return; + } + + for (NSString *arg in baseArgs) { + if ([arg isEqual:@"--"]) { + reply(nil, nil, nil, nil); + return; + } + } + + NSURL *temporaryDirectoryURL = [NSURL fileURLWithPath:NSTemporaryDirectory() + isDirectory:YES]; + NSString *temporaryFilename = + [[NSProcessInfo processInfo] globallyUniqueString]; + NSURL *temporaryFileURL = + [temporaryDirectoryURL URLByAppendingPathComponent:temporaryFilename + isDirectory:false]; + + [[p dataUsingEncoding:NSUTF8StringEncoding] writeToURL:temporaryFileURL + atomically:false]; + + NSMutableArray *args = [[NSMutableArray alloc] init]; + [args addObjectsFromArray:baseArgs]; + [args addObjectsFromArray:@[ @"--", temporaryFileURL.path ]]; + + NSPipe *stdIn = [NSPipe pipe], *stdOut = [NSPipe pipe], + *stdErr = [NSPipe pipe]; + + NSTask *t = [[NSTask alloc] init]; + if (@available(macOS 10.13, *)) { + t.executableURL = executableURL; + } else { + t.launchPath = executableURL.path; + } + t.arguments = args; + t.standardInput = stdIn; + t.standardOutput = stdOut; + t.standardError = stdErr; + + JuliaTask *jt = + [[JuliaTask alloc] initWithTask:t + delegate:self + cleanup:^() { + [[NSFileManager defaultManager] + removeItemAtURL:temporaryDirectoryURL + error:nil]; + }]; + [_tasks addObject:jt]; + + reply(jt, stdIn.fileHandleForWriting, stdOut.fileHandleForReading, + stdErr.fileHandleForReading); +} + +- (void)taskTerminated:(JuliaTask *_Nonnull)jt { + [_tasks removeObject:jt]; +} + +@end diff --git a/contrib/mac/frameworkapp/ExecSandbox/ExecSandboxProtocol.h b/contrib/mac/frameworkapp/ExecSandbox/ExecSandboxProtocol.h new file mode 100644 index 0000000000000..5dd16e74f74c0 --- /dev/null +++ b/contrib/mac/frameworkapp/ExecSandbox/ExecSandboxProtocol.h @@ -0,0 +1,31 @@ +// This file is a part of Julia. License is MIT: https://julialang.org/license + +@import Foundation; + +@protocol TaskProtocol +/// Launch the task and upon termination receive the exit status. +- (void)launch:(void (^_Nullable)(int status))onTermination; +/// Terminate (SIGTERM) the task. +- (void)terminate; +@end + +@protocol ExecSandboxProtocol +/** + Evaluate a Julia program with a Julia executable. + + @param juliaProgram Julia source code to be evaluated. + @param executableBookmark NSURL file bookmark for the julia executable to run. + @param args Arguments to pass to julia. + @param reply Async result with task and standard in, out, and error. An error + occured if task is nil. + */ +- (void)eval:(NSString *_Nonnull)juliaProgram + withJulia:(NSData *_Nonnull)executableBookmark + arguments:(NSArray *_Nullable)args + task:(void (^_Nonnull)(id _Nullable task, + NSFileHandle *_Nullable stdIn, + NSFileHandle *_Nullable stdOut, + NSFileHandle *_Nullable stdErr))reply; +@end + +NSXPCInterface *_Nonnull CreateExecSandboxXPCInterface(void); diff --git a/contrib/mac/frameworkapp/ExecSandbox/ExecSandboxProtocol.m b/contrib/mac/frameworkapp/ExecSandbox/ExecSandboxProtocol.m new file mode 100644 index 0000000000000..a7e13f60df54c --- /dev/null +++ b/contrib/mac/frameworkapp/ExecSandbox/ExecSandboxProtocol.m @@ -0,0 +1,14 @@ +// This file is a part of Julia. License is MIT: https://julialang.org/license + +#import "ExecSandboxProtocol.h" + +NSXPCInterface *CreateExecSandboxXPCInterface(void) { + NSXPCInterface *i = + [NSXPCInterface interfaceWithProtocol:@protocol(ExecSandboxProtocol)]; + /// Reply sends a task proxy: + [i setInterface:[NSXPCInterface interfaceWithProtocol:@protocol(TaskProtocol)] + forSelector:@selector(eval:withJulia:arguments:task:) + argumentIndex:0 + ofReply:true]; + return i; +} diff --git a/contrib/mac/frameworkapp/ExecSandbox/Info.plist b/contrib/mac/frameworkapp/ExecSandbox/Info.plist new file mode 100644 index 0000000000000..4339a0b268edf --- /dev/null +++ b/contrib/mac/frameworkapp/ExecSandbox/Info.plist @@ -0,0 +1,31 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + ExecSandbox + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + XPC! + CFBundleShortVersionString + $(APP_SHORT_VERSION_STRING) + CFBundleVersion + $(APP_VERSION) + XPCService + + RunLoopType + NSRunLoop + ServiceType + Application + + + diff --git a/contrib/mac/frameworkapp/ExecSandbox/main.m b/contrib/mac/frameworkapp/ExecSandbox/main.m new file mode 100644 index 0000000000000..3ca1ff1d1dbff --- /dev/null +++ b/contrib/mac/frameworkapp/ExecSandbox/main.m @@ -0,0 +1,28 @@ +// This file is a part of Julia. License is MIT: https://julialang.org/license + +@import Foundation; +#import "ExecSandbox.h" + +@interface ServiceDelegate : NSObject +@end + +@implementation ServiceDelegate + +- (BOOL)listener:(NSXPCListener *)listener + shouldAcceptNewConnection:(NSXPCConnection *)newConnection { + newConnection.exportedInterface = CreateExecSandboxXPCInterface(); + ExecSandbox *exportedObject = [[ExecSandbox alloc] init]; + newConnection.exportedObject = exportedObject; + [newConnection resume]; + return YES; +} + +@end + +int main(int argc, const char *argv[]) { + ServiceDelegate *delegate = [[ServiceDelegate alloc] init]; + NSXPCListener *listener = [NSXPCListener serviceListener]; + listener.delegate = delegate; + [listener resume]; + return 0; +} diff --git a/contrib/mac/frameworkapp/JuliaLauncher.xcodeproj/project.pbxproj b/contrib/mac/frameworkapp/JuliaLauncher.xcodeproj/project.pbxproj index 62124f9f54839..31dcbbded687e 100644 --- a/contrib/mac/frameworkapp/JuliaLauncher.xcodeproj/project.pbxproj +++ b/contrib/mac/frameworkapp/JuliaLauncher.xcodeproj/project.pbxproj @@ -7,13 +7,50 @@ objects = { /* Begin PBXBuildFile section */ + DC69B3E6226152320004C981 /* ExecSandbox.m in Sources */ = {isa = PBXBuildFile; fileRef = DC69B3E5226152320004C981 /* ExecSandbox.m */; }; + DC69B3E8226152320004C981 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = DC69B3E7226152320004C981 /* main.m */; }; + DC69B3EC226152320004C981 /* ExecSandbox.xpc in Embed XPC Services */ = {isa = PBXBuildFile; fileRef = DC69B3E1226152320004C981 /* ExecSandbox.xpc */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + DC69B3F322628ABC0004C981 /* ExecSandboxProtocol.m in Sources */ = {isa = PBXBuildFile; fileRef = DC69B3F222628ABC0004C981 /* ExecSandboxProtocol.m */; }; + DC69B3F422628ABC0004C981 /* ExecSandboxProtocol.m in Sources */ = {isa = PBXBuildFile; fileRef = DC69B3F222628ABC0004C981 /* ExecSandboxProtocol.m */; }; DCECD36721B6461B0099A8C3 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = DCECD36621B6461B0099A8C3 /* AppDelegate.m */; }; DCECD36921B6461C0099A8C3 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DCECD36821B6461C0099A8C3 /* Assets.xcassets */; }; DCECD36C21B6461C0099A8C3 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = DCECD36A21B6461C0099A8C3 /* MainMenu.xib */; }; DCECD36F21B6461C0099A8C3 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = DCECD36E21B6461C0099A8C3 /* main.m */; }; /* End PBXBuildFile section */ +/* Begin PBXContainerItemProxy section */ + DC69B3EA226152320004C981 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = DCECD35A21B6461B0099A8C3 /* Project object */; + proxyType = 1; + remoteGlobalIDString = DC69B3E0226152320004C981; + remoteInfo = ExecSandbox; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + DC69B3F0226152320004C981 /* Embed XPC Services */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = "$(CONTENTS_FOLDER_PATH)/XPCServices"; + dstSubfolderSpec = 16; + files = ( + DC69B3EC226152320004C981 /* ExecSandbox.xpc in Embed XPC Services */, + ); + name = "Embed XPC Services"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + /* Begin PBXFileReference section */ + DC69B3E1226152320004C981 /* ExecSandbox.xpc */ = {isa = PBXFileReference; explicitFileType = "wrapper.xpc-service"; includeInIndex = 0; path = ExecSandbox.xpc; sourceTree = BUILT_PRODUCTS_DIR; }; + DC69B3E3226152320004C981 /* ExecSandboxProtocol.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ExecSandboxProtocol.h; sourceTree = ""; }; + DC69B3E4226152320004C981 /* ExecSandbox.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ExecSandbox.h; sourceTree = ""; }; + DC69B3E5226152320004C981 /* ExecSandbox.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ExecSandbox.m; sourceTree = ""; }; + DC69B3E7226152320004C981 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; + DC69B3E9226152320004C981 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + DC69B3F1226152410004C981 /* ExecSandbox.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = ExecSandbox.entitlements; sourceTree = ""; }; + DC69B3F222628ABC0004C981 /* ExecSandboxProtocol.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ExecSandboxProtocol.m; sourceTree = ""; }; DCECD36221B6461B0099A8C3 /* Julia.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Julia.app; sourceTree = BUILT_PRODUCTS_DIR; }; DCECD36521B6461B0099A8C3 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; DCECD36621B6461B0099A8C3 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; @@ -25,6 +62,13 @@ /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ + DC69B3DE226152320004C981 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; DCECD35F21B6461B0099A8C3 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -35,10 +79,25 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + DC69B3E2226152320004C981 /* ExecSandbox */ = { + isa = PBXGroup; + children = ( + DC69B3E3226152320004C981 /* ExecSandboxProtocol.h */, + DC69B3F222628ABC0004C981 /* ExecSandboxProtocol.m */, + DC69B3E4226152320004C981 /* ExecSandbox.h */, + DC69B3E5226152320004C981 /* ExecSandbox.m */, + DC69B3E7226152320004C981 /* main.m */, + DC69B3E9226152320004C981 /* Info.plist */, + DC69B3F1226152410004C981 /* ExecSandbox.entitlements */, + ); + path = ExecSandbox; + sourceTree = ""; + }; DCECD35921B6461B0099A8C3 = { isa = PBXGroup; children = ( DCECD36421B6461B0099A8C3 /* JuliaLauncher */, + DC69B3E2226152320004C981 /* ExecSandbox */, DCECD36321B6461B0099A8C3 /* Products */, ); sourceTree = ""; @@ -47,6 +106,7 @@ isa = PBXGroup; children = ( DCECD36221B6461B0099A8C3 /* Julia.app */, + DC69B3E1226152320004C981 /* ExecSandbox.xpc */, ); name = Products; sourceTree = ""; @@ -68,6 +128,23 @@ /* End PBXGroup section */ /* Begin PBXNativeTarget section */ + DC69B3E0226152320004C981 /* ExecSandbox */ = { + isa = PBXNativeTarget; + buildConfigurationList = DC69B3EF226152320004C981 /* Build configuration list for PBXNativeTarget "ExecSandbox" */; + buildPhases = ( + DC69B3DD226152320004C981 /* Sources */, + DC69B3DE226152320004C981 /* Frameworks */, + DC69B3DF226152320004C981 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = ExecSandbox; + productName = ExecSandbox; + productReference = DC69B3E1226152320004C981 /* ExecSandbox.xpc */; + productType = "com.apple.product-type.xpc-service"; + }; DCECD36121B6461B0099A8C3 /* JuliaLauncher */ = { isa = PBXNativeTarget; buildConfigurationList = DCECD37321B6461C0099A8C3 /* Build configuration list for PBXNativeTarget "JuliaLauncher" */; @@ -75,10 +152,12 @@ DCECD35E21B6461B0099A8C3 /* Sources */, DCECD35F21B6461B0099A8C3 /* Frameworks */, DCECD36021B6461B0099A8C3 /* Resources */, + DC69B3F0226152320004C981 /* Embed XPC Services */, ); buildRules = ( ); dependencies = ( + DC69B3EB226152320004C981 /* PBXTargetDependency */, ); name = JuliaLauncher; productName = JuliaLauncher; @@ -94,6 +173,17 @@ LastUpgradeCheck = 1010; ORGANIZATIONNAME = JuliaLang; TargetAttributes = { + DC69B3E0226152320004C981 = { + CreatedOnToolsVersion = 10.2; + SystemCapabilities = { + com.apple.HardenedRuntime = { + enabled = 1; + }; + com.apple.Sandbox = { + enabled = 1; + }; + }; + }; DCECD36121B6461B0099A8C3 = { CreatedOnToolsVersion = 10.1; SystemCapabilities = { @@ -121,11 +211,19 @@ projectRoot = ""; targets = ( DCECD36121B6461B0099A8C3 /* JuliaLauncher */, + DC69B3E0226152320004C981 /* ExecSandbox */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ + DC69B3DF226152320004C981 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; DCECD36021B6461B0099A8C3 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -138,17 +236,36 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ + DC69B3DD226152320004C981 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + DC69B3E8226152320004C981 /* main.m in Sources */, + DC69B3E6226152320004C981 /* ExecSandbox.m in Sources */, + DC69B3F422628ABC0004C981 /* ExecSandboxProtocol.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; DCECD35E21B6461B0099A8C3 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( DCECD36F21B6461C0099A8C3 /* main.m in Sources */, DCECD36721B6461B0099A8C3 /* AppDelegate.m in Sources */, + DC69B3F322628ABC0004C981 /* ExecSandboxProtocol.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ +/* Begin PBXTargetDependency section */ + DC69B3EB226152320004C981 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = DC69B3E0226152320004C981 /* ExecSandbox */; + targetProxy = DC69B3EA226152320004C981 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + /* Begin PBXVariantGroup section */ DCECD36A21B6461C0099A8C3 /* MainMenu.xib */ = { isa = PBXVariantGroup; @@ -161,6 +278,32 @@ /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ + DC69B3ED226152320004C981 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_ENTITLEMENTS = ExecSandbox/ExecSandbox.entitlements; + CODE_SIGN_STYLE = Automatic; + ENABLE_HARDENED_RUNTIME = YES; + INFOPLIST_FILE = ExecSandbox/Info.plist; + PRODUCT_BUNDLE_IDENTIFIER = org.julialang.ExecSandbox; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + }; + name = Debug; + }; + DC69B3EE226152320004C981 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_ENTITLEMENTS = ExecSandbox/ExecSandbox.entitlements; + CODE_SIGN_STYLE = Automatic; + ENABLE_HARDENED_RUNTIME = YES; + INFOPLIST_FILE = ExecSandbox/Info.plist; + PRODUCT_BUNDLE_IDENTIFIER = org.julialang.ExecSandbox; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + }; + name = Release; + }; DCECD37121B6461C0099A8C3 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -311,6 +454,15 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + DC69B3EF226152320004C981 /* Build configuration list for PBXNativeTarget "ExecSandbox" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + DC69B3ED226152320004C981 /* Debug */, + DC69B3EE226152320004C981 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; DCECD35D21B6461B0099A8C3 /* Build configuration list for PBXProject "JuliaLauncher" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/contrib/mac/frameworkapp/JuliaLauncher/AppDelegate.h b/contrib/mac/frameworkapp/JuliaLauncher/AppDelegate.h index 4441cd97cfd7f..4c08646448c3c 100644 --- a/contrib/mac/frameworkapp/JuliaLauncher/AppDelegate.h +++ b/contrib/mac/frameworkapp/JuliaLauncher/AppDelegate.h @@ -3,5 +3,4 @@ @import AppKit; @interface AppDelegate : NSObject - @end diff --git a/contrib/mac/frameworkapp/JuliaLauncher/AppDelegate.m b/contrib/mac/frameworkapp/JuliaLauncher/AppDelegate.m index 4ac281118f45e..a9b63ff402a07 100644 --- a/contrib/mac/frameworkapp/JuliaLauncher/AppDelegate.m +++ b/contrib/mac/frameworkapp/JuliaLauncher/AppDelegate.m @@ -1,19 +1,64 @@ // This file is a part of Julia. License is MIT: https://julialang.org/license +@import AppKit; #import "AppDelegate.h" +#import "ExecSandboxProtocol.h" /// Terminal's bundle ID. NSString static const *const terminalBundleID = @"com.apple.Terminal"; static bool launchTerminalApp(void); -static void execJuliaInTerminal(NSURL *julia); +static void execJuliaInTerminal(NSURL *_Nonnull julia); + +/// Controller for an XPC connection to the ExecSandbox service. +/// +/// The ExecSandbox service allows Julia code to be run within a restricted App +/// Sandbox environment. +@interface ExecSandboxController : NSObject { + NSXPCConnection *_Nonnull _execSandboxCnx; +} +- (id _Nonnull)remoteObjectProxyWithErrorHandler: + (void (^_Nonnull)(NSError *_Nullable error))handler; ++ (ExecSandboxController *_Nonnull)sharedController; +@end + +@implementation ExecSandboxController + +- (instancetype)init { + self = [super init]; + if (self == nil) + return nil; + _execSandboxCnx = [[NSXPCConnection alloc] + initWithServiceName:@"org.julialang.ExecSandbox"]; + _execSandboxCnx.remoteObjectInterface = CreateExecSandboxXPCInterface(); + [_execSandboxCnx resume]; + return self; +} + +- (id)remoteObjectProxyWithErrorHandler: + (void (^_Nonnull)(NSError *_Nullable error))handler { + return [_execSandboxCnx remoteObjectProxyWithErrorHandler:handler]; +} + ++ (ExecSandboxController *)sharedController { + static ExecSandboxController *s = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + s = [[ExecSandboxController alloc] init]; + }); + return s; +} + +@end /// Location of an installed variant of Julia (frameowrk or nix hier). @interface JuliaVariant : NSObject @property(readonly, nullable) NSBundle *bundle; @property(readonly, nonnull) NSURL *juliaexe; -@property(readonly, nonnull) NSString *version; -- (instancetype)initWithJulia:(NSURL *)exe bundle:(NSBundle *)b; +@property(readonly, nullable) NSString *version; +@property(readonly) BOOL updatingVersion; +- (instancetype)initWithJulia:(NSURL *_Nonnull)exe + bundle:(NSBundle *_Nullable)b; /// (major,minor,patch) components parsed from version. @property(readonly, nullable) NSArray *versionComponents; @end @@ -28,6 +73,7 @@ - (instancetype)initWithJulia:(NSURL *)exe bundle:(NSBundle *)b { NSAssert(exe != nil, @"juliaexe cannot be nil."); _juliaexe = exe; _bundle = b; + _version = nil; if (_bundle == nil) { // Try to locate the framework bundle. NSURL *frameworkURL = @@ -42,8 +88,48 @@ - (instancetype)initWithJulia:(NSURL *)exe bundle:(NSBundle *)b { // Extract version from framework bundle. _version = _bundle.infoDictionary[(NSString *)kCFBundleVersionKey]; } else { - // TODO: shell out and make julia tell us its version. - _version = @"?"; + // Exec the julia and have it tell us its version. + + NSData *juliaexeBookmark = [_juliaexe bookmarkDataWithOptions:0 + includingResourceValuesForKeys:nil + relativeToURL:nil + error:nil]; + + _updatingVersion = true; + + id remote = [[ExecSandboxController sharedController] + remoteObjectProxyWithErrorHandler:^(NSError *error) { + [self willChangeValueForKey:@"updatingVersion"]; + self->_updatingVersion = false; + [self didChangeValueForKey:@"updatingVersion"]; + }]; + + [remote eval:@"print(\"$(Base.VERSION.major).$(Base.VERSION.minor).$(Base." + @"VERSION.patch)\")" + withJulia:juliaexeBookmark + arguments:nil + task:^(id task, NSFileHandle *stdIn, + NSFileHandle *stdOut, NSFileHandle *stdErr) { + [task launch:^(int status) { + NSString *vout = + [[NSString alloc] initWithData:[stdOut readDataToEndOfFile] + encoding:NSUTF8StringEncoding]; + if (status == 0 && vout) { + [self willChangeValueForKey:@"version"]; + [self willChangeValueForKey:@"updatingVersion"]; + self->_version = vout; + self->_updatingVersion = false; + [self didChangeValueForKey:@"updatingVersion"]; + [self didChangeValueForKey:@"version"]; + + } else { + [self willChangeValueForKey:@"updatingVersion"]; + self->_updatingVersion = false; + [self didChangeValueForKey:@"updatingVersion"]; + } + }]; + }]; + NSLog(@"Getting version by execing %@", exe); } return self; } @@ -67,10 +153,11 @@ - (instancetype)initWithJulia:(NSURL *)exe bundle:(NSBundle *)b { @end -@interface AppDelegate () -@property NSMetadataQuery *mdq; -@property NSMutableDictionary *juliaVariants; -@property JuliaVariant *latestKnownTaggedJulia; +@interface AppDelegate () { + NSMetadataQuery *_Nullable _mdq; +} +@property NSMutableDictionary *_Nonnull juliaVariants; +@property JuliaVariant *_Nullable latestKnownTaggedJulia; @end @implementation AppDelegate @@ -80,7 +167,7 @@ - (instancetype)init { if (!self) { return nil; } - self.juliaVariants = [[NSMutableDictionary alloc] init]; + _juliaVariants = [[NSMutableDictionary alloc] init]; return self; } @@ -118,15 +205,15 @@ - (void)addJuliaVariant:(JuliaVariant *)jv { } - (void)findJuliaQueryDidUpdate:(NSNotification *)sender { - if (sender.object != self.mdq) { + if (sender.object != _mdq) { return; } // Disable updates while enumerating results. - [self.mdq disableUpdates]; + [_mdq disableUpdates]; - for (NSUInteger i = 0; i < self.mdq.resultCount; ++i) { - NSMetadataItem *item = [self.mdq resultAtIndex:i]; + for (NSUInteger i = 0; i < _mdq.resultCount; ++i) { + NSMetadataItem *item = [_mdq resultAtIndex:i]; // Grab the path attribute from the item. NSString *itemPath = [item valueForAttribute:NSMetadataItemPathKey]; NSString *contentType = @@ -205,17 +292,17 @@ - (void)findJuliaQueryDidUpdate:(NSNotification *)sender { } // Safe to enable updates now. - [self.mdq enableUpdates]; + [_mdq enableUpdates]; } /// Start a Spotlight query for Julia frameworks. - (void)findJuliaWithSpotlight { - if (self.mdq != nil) { + if (_mdq != nil) { // Query exists so return. return; } - self.mdq = [[NSMetadataQuery alloc] init]; + _mdq = [[NSMetadataQuery alloc] init]; // Search for the framework bundle identifier. NSPredicate *searchPredicate = [NSPredicate @@ -223,21 +310,21 @@ - (void)findJuliaWithSpotlight { @"(kMDItemCFBundleIdentifier == 'org.julialang.julia.lib' && " @"kMDItemContentType == 'com.apple.framework') || (kMDItemFSName == " @"'julia' && kMDItemContentType == 'public.unix-executable')"]; - self.mdq.predicate = searchPredicate; + _mdq.predicate = searchPredicate; // Observe the query's notifications. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(findJuliaQueryDidUpdate:) name:NSMetadataQueryDidUpdateNotification - object:self.mdq]; + object:_mdq]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(findJuliaQueryDidUpdate:) name:NSMetadataQueryDidFinishGatheringNotification - object:self.mdq]; + object:_mdq]; - if (![self.mdq startQuery]) { + if (![_mdq startQuery]) { NSAlert *a = [[NSAlert alloc] init]; a.alertStyle = NSAlertStyleCritical; a.messageText = NSLocalizedString(@"Cannot find the Julia framework.", ); @@ -253,27 +340,27 @@ - (void)findJuliaWithSpotlight { } - (void)stopFindJuliaWithSpotlight { - if (self.mdq == nil) { + if (_mdq == nil) { return; } - [self.mdq stopQuery]; + [_mdq stopQuery]; [[NSNotificationCenter defaultCenter] removeObserver:self name:NSMetadataQueryDidUpdateNotification - object:self.mdq]; + object:_mdq]; [[NSNotificationCenter defaultCenter] removeObserver:self name:NSMetadataQueryDidFinishGatheringNotification - object:self.mdq]; + object:_mdq]; - self.mdq = nil; + _mdq = nil; } @end -void execJuliaInTerminal(NSURL *julia) { +void execJuliaInTerminal(NSURL *_Nonnull julia) { OSStatus s; NSAlert *a = [[NSAlert alloc] init]; a.alertStyle = NSAlertStyleCritical;