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;