Skip to content

Commit

Permalink
Launch latest from list of found julia variants
Browse files Browse the repository at this point in the history
  • Loading branch information
slarew authored and staticfloat committed Apr 16, 2019
1 parent 7796c51 commit 8dada1c
Show file tree
Hide file tree
Showing 3 changed files with 206 additions and 68 deletions.
230 changes: 172 additions & 58 deletions contrib/mac/frameworkapp/JuliaLauncher/AppDelegate.m
Original file line number Diff line number Diff line change
Expand Up @@ -8,84 +8,199 @@
static bool launchTerminalApp(void);
static void execJuliaInTerminal(NSURL *julia);

/// 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;
/// (major,minor,patch) components parsed from version.
@property(readonly, nullable) NSArray<NSNumber *> *versionComponents;
@end

@implementation JuliaVariant

- (instancetype)initWithJulia:(NSURL *)exe bundle:(NSBundle *)b {
self = [super init];
if (!self) {
return nil;
}
NSAssert(exe != nil, @"juliaexe cannot be nil.");
_juliaexe = exe;
_bundle = b;
if (_bundle == nil) {
// Try to locate the framework bundle.
NSURL *frameworkURL =
[[exe URLByDeletingLastPathComponent] URLByDeletingLastPathComponent];
NSBundle *bundle = [NSBundle bundleWithURL:frameworkURL];
if (bundle &&
[[bundle bundleIdentifier] isEqual:@"org.julialang.julia.lib"]) {
_bundle = bundle;
}
}
if (_bundle) {
// Extract version from framework bundle.
_version = _bundle.infoDictionary[(NSString *)kCFBundleVersionKey];
} else {
// TODO: shell out and make julia tell us its version.
_version = @"?";
}
return self;
}

- (NSArray<NSNumber *> *)versionComponents {
NSNumber *compsi[3];
NSArray<NSString *> *c = [self.version componentsSeparatedByString:@"."];
if (c.count < 3) {
return nil;
}
NSInteger i = 0;
for (NSString *vn in c) {
compsi[i++] = @(vn.integerValue);
}
if ([compsi[0] isEqual:@(0)] && [compsi[1] isEqual:@(0)] &&
[compsi[2] isEqual:@(0)]) {
return nil;
}
return [NSArray arrayWithObjects:compsi count:3];
}

@end

@interface AppDelegate ()
@property NSMetadataQuery *mdq;
@property NSMutableDictionary<NSURL *, JuliaVariant *> *juliaVariants;
@property JuliaVariant *latestKnownTaggedJulia;
@end

@implementation AppDelegate

- (instancetype)init {
self = [super init];
if (!self) {
return nil;
}
self.juliaVariants = [[NSMutableDictionary alloc] init];
return self;
}

- (void)dealloc {
[self stopFindJuliaWithSpotlight];
}

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
NSURL *juliaexe = [self findJuliaInLinkedFramework];
if (juliaexe != nil) {
execJuliaInTerminal(juliaexe);
} else {
[self findJuliaWithSpotlight];
}
[self findJuliaWithSpotlight];
}

- (NSURL *_Nullable)findJuliaInLinkedFramework {
// Try NSBundle's search by bundle ID for the Julia framework.
NSBundle *b = [NSBundle bundleWithIdentifier:@"org.julialang.julia.lib"];
if (b != nil) {
return [b URLForAuxiliaryExecutable:@"julia"];
- (void)addJuliaVariant:(JuliaVariant *)jv {
if ([self.juliaVariants objectForKey:jv.juliaexe]) {
// Don't overwrite.
return;
}
[self.juliaVariants setObject:jv forKey:jv.juliaexe];

// Track the latest known tagged variant.
if (self.latestKnownTaggedJulia == nil) {
self.latestKnownTaggedJulia = jv;
} else {
JuliaVariant *latestJv = self.latestKnownTaggedJulia;
NSArray<NSNumber *> *latestV = latestJv.versionComponents;
NSArray<NSNumber *> *vc = jv.versionComponents;
if (vc != nil) {
// Compare version tuple.
if (vc[0] > latestV[0] || (vc[0] == latestV[0] && vc[1] > latestV[1]) ||
(vc[0] == latestV[0] && vc[1] == latestV[1] && vc[2] > latestV[2])) {
latestJv = jv;
latestV = vc;
}
}
}
return nil;
}

- (void)findJuliaQueryDidUpdate:(NSNotification *)sender {
if (sender.object != self.mdq) {
return;
}

// Disable updates while enumerating results.
[self.mdq disableUpdates];

for (NSUInteger i = 0; i < self.mdq.resultCount; ++i) {
// Grab the path attribute from the item.
NSMetadataItem *item = [self.mdq resultAtIndex:i];
NSURL *frameworkPath = [[NSURL alloc]
initFileURLWithPath:[item valueForAttribute:NSMetadataItemPathKey]
isDirectory:true];
NSLog(@"Found Julia framework %@", frameworkPath);

NSURL *frameworkVersions =
[frameworkPath URLByAppendingPathComponent:@"Versions"];

NSFileManager *fm = NSFileManager.defaultManager;
NSArray<NSURL *> *versions =
[fm contentsOfDirectoryAtURL:frameworkVersions
includingPropertiesForKeys:@[ NSURLIsDirectoryKey ]
options:0
error:nil];

for (NSURL *frameworkVersion in versions) {
NSNumber *isDir;
if (![frameworkVersion getResourceValue:&isDir
forKey:NSURLIsDirectoryKey
error:nil] ||
!isDir.boolValue) {
continue;
// Grab the path attribute from the item.
NSString *itemPath = [item valueForAttribute:NSMetadataItemPathKey];
NSString *contentType =
[item valueForAttribute:(NSString *)kMDItemContentType];

if ([contentType isEqual:@"public.unix-executable"]) {
// TODO: Verify the executable is actually a Julia.
NSURL *juliaexe =
[[[NSURL alloc] initFileURLWithPath:itemPath
isDirectory:false] URLByStandardizingPath];
NSLog(@"Found Julia %@", juliaexe);
JuliaVariant *jv = [[JuliaVariant alloc] initWithJulia:juliaexe
bundle:nil];
[self addJuliaVariant:jv];
} else if ([contentType isEqual:@"com.apple.framework"]) {
NSURL *frameworkPath = [[NSURL alloc] initFileURLWithPath:itemPath
isDirectory:true];
NSLog(@"Found Julia framework %@", frameworkPath);

// Iterate over versions within the framework.

NSFileManager *fm = NSFileManager.defaultManager;
NSURL *frameworkVersions =
[frameworkPath URLByAppendingPathComponent:@"Versions"
isDirectory:true];
NSArray<NSURL *> *versions =
[fm contentsOfDirectoryAtURL:frameworkVersions
includingPropertiesForKeys:@[ NSURLIsDirectoryKey ]
options:0
error:nil];

for (NSURL *frameworkVersion in versions) {
NSNumber *isDir;
if (![frameworkVersion getResourceValue:&isDir
forKey:NSURLIsDirectoryKey
error:nil] ||
!isDir.boolValue) {
// Version is a symink (probably Current) so skip it.
continue;
}

NSBundle *bundle = [NSBundle bundleWithURL:frameworkVersion];

// Form the path to julia in the framework's Helpers directory.
NSURL *juliaexe = [[[[bundle.executableURL URLByStandardizingPath]
URLByDeletingLastPathComponent]
URLByAppendingPathComponent:@"Helpers"
isDirectory:true]
URLByAppendingPathComponent:@"julia"
isDirectory:false];

if (juliaexe == nil) {
continue;
}

NSLog(@"Found Julia %@ (%@) at %@",
bundle.infoDictionary[@"CFBundleShortVersionString"],
bundle.infoDictionary[(NSString *)kCFBundleVersionKey], juliaexe);

JuliaVariant *jv = [[JuliaVariant alloc] initWithJulia:juliaexe
bundle:bundle];
[self addJuliaVariant:jv];
}
} else {
NSLog(@"Ignoring Julia at %@ with content type %@", itemPath,
contentType);
}
}

NSBundle *bundle = [NSBundle bundleWithURL:frameworkVersion];

NSString *frameworkVersion =
bundle.infoDictionary[(NSString *)kCFBundleVersionKey];
NSString *frameworkShortVersion =
bundle.infoDictionary[@"CFBundleShortVersionString"];

// Form the path to julia in the framework's Helpers directory.
NSURL *juliaexe = [[[[bundle.executableURL URLByResolvingSymlinksInPath]
URLByDeletingLastPathComponent] URLByAppendingPathComponent:@"Helpers"
isDirectory:true]
URLByAppendingPathComponent:@"julia"
isDirectory:false];

if (juliaexe != nil) {
NSLog(@"Found Julia %@ (%@) at %@", frameworkShortVersion,
frameworkVersion, juliaexe.path);
execJuliaInTerminal(juliaexe);
}
if (sender.name == NSMetadataQueryDidFinishGatheringNotification) {
// Initial search is complete after app launch so exec julia.
NSURL *juliaexe = self.latestKnownTaggedJulia.juliaexe;
if (juliaexe) {
execJuliaInTerminal(juliaexe);
}
}

Expand All @@ -105,12 +220,11 @@ - (void)findJuliaWithSpotlight {
// Search for the framework bundle identifier.
NSPredicate *searchPredicate = [NSPredicate
predicateWithFormat:
@"kMDItemCFBundleIdentifier == 'org.julialang.julia.lib'"];
@"(kMDItemCFBundleIdentifier == 'org.julialang.julia.lib' && "
@"kMDItemContentType == 'com.apple.framework') || (kMDItemFSName == "
@"'julia' && kMDItemContentType == 'public.unix-executable')"];
self.mdq.predicate = searchPredicate;

// Include the framework's path as metadata.
self.mdq.valueListAttributes = @[ NSMetadataItemPathKey ];

// Observe the query's notifications.
[[NSNotificationCenter defaultCenter]
addObserver:self
Expand Down
20 changes: 10 additions & 10 deletions contrib/mac/frameworkapp/JuliaLauncher/Base.lproj/MainMenu.xib
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14460.31" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14460.31"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14490.70"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="NSApplication">
Expand All @@ -11,16 +11,16 @@
</connections>
</customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<customObject id="Voe-Tx-rLC" customClass="AppDelegate"/>
<customObject id="YLy-65-1bz" customClass="NSFontManager"/>
<menu title="Main Menu" systemMenu="main" id="AYu-sK-qS6">
<items>
<menuItem title="JuliaLauncher" id="1Xt-HY-uBw">
<menuItem title="Julia" id="1Xt-HY-uBw">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="JuliaLauncher" systemMenu="apple" id="uQy-DD-JDr">
<menu key="submenu" title="Julia" systemMenu="apple" id="uQy-DD-JDr">
<items>
<menuItem title="About JuliaLauncher" id="5kV-Vb-QxS">
<menuItem title="About Julia" id="5kV-Vb-QxS">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="orderFrontStandardAboutPanel:" target="-1" id="Exp-CZ-Vem"/>
Expand All @@ -34,7 +34,7 @@
<menu key="submenu" title="Services" systemMenu="services" id="hz9-B4-Xy5"/>
</menuItem>
<menuItem isSeparatorItem="YES" id="4je-JR-u6R"/>
<menuItem title="Hide JuliaLauncher" keyEquivalent="h" id="Olw-nP-bQN">
<menuItem title="Hide Julia" keyEquivalent="h" id="Olw-nP-bQN">
<connections>
<action selector="hide:" target="-1" id="PnN-Uc-m68"/>
</connections>
Expand All @@ -52,7 +52,7 @@
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="kCx-OE-vgT"/>
<menuItem title="Quit JuliaLauncher" keyEquivalent="q" id="4sb-4s-VLi">
<menuItem title="Quit Julia" keyEquivalent="q" id="4sb-4s-VLi">
<connections>
<action selector="terminate:" target="-1" id="Te7-pn-YzF"/>
</connections>
Expand Down Expand Up @@ -624,7 +624,7 @@
<menuItem title="Show Sidebar" keyEquivalent="s" id="kIP-vf-haE">
<modifierMask key="keyEquivalentModifierMask" control="YES" command="YES"/>
<connections>
<action selector="toggleSourceList:" target="-1" id="iwa-gc-5KM"/>
<action selector="toggleSidebar:" target="-1" id="iwa-gc-5KM"/>
</connections>
</menuItem>
<menuItem title="Enter Full Screen" keyEquivalent="f" id="4J7-dP-txa">
Expand Down Expand Up @@ -665,7 +665,7 @@
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Help" systemMenu="help" id="F2S-fz-NVQ">
<items>
<menuItem title="JuliaLauncher Help" keyEquivalent="?" id="FKE-Sm-Kum">
<menuItem title="Julia Help" keyEquivalent="?" id="FKE-Sm-Kum">
<connections>
<action selector="showHelp:" target="-1" id="y7X-2Q-9no"/>
</connections>
Expand Down
24 changes: 24 additions & 0 deletions contrib/mac/frameworkapp/JuliaLauncher/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,29 @@
<string>MainMenu</string>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
<key>UTExportedTypeDeclarations</key>
<array>
<dict>
<key>UTTypeConformsTo</key>
<array>
<string>public.script</string>
</array>
<key>UTTypeDescription</key>
<string>Julia source code</string>
<key>UTTypeIconFile</key>
<string>AppIcon.icns</string>
<key>UTTypeIdentifier</key>
<string>org.julialang.julia.text</string>
<key>UTTypeReferenceURL</key>
<string></string>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>jl</string>
</array>
</dict>
</dict>
</array>
</dict>
</plist>

0 comments on commit 8dada1c

Please sign in to comment.