itsycal: build from source (#506430)

This commit is contained in:
Oleksii Filonenko
2026-07-13 10:58:17 +00:00
committed by GitHub
6 changed files with 570 additions and 33 deletions

View File

@@ -0,0 +1,29 @@
From 9ce5e368c00ed887f6d2628d4e8eff664bff5522 Mon Sep 17 00:00:00 2001
From: 4evy <git@evy.pink>
Date: Sat, 11 Jul 2026 01:12:36 +0300
Subject: [PATCH 1/4] Allow running from the Nix store
Upstream normally requires the bundle to live in /Applications because its
Sparkle updater needs a stable, writable installation path. Nix applications
instead live in the immutable Nix store and are updated by Nix.
Use upstream's existing kAllowOutsideApplicationsFolder escape hatch as the
registered default. This preserves upstream's check and keeps the downstream
change to one explicit policy setting.
---
Itsycal/AppDelegate.m | 2 ++
1 file changed, 2 insertions(+)
diff --git a/Itsycal/AppDelegate.m b/Itsycal/AppDelegate.m
index 5cc187a..ee256d9 100644
--- a/Itsycal/AppDelegate.m
+++ b/Itsycal/AppDelegate.m
@@ -42,6 +42,8 @@
kThemePreference: @0, // System=0, Light=1, Dark=2
kHideIcon: @(NO),
kShowLocation: @(NO),
+ // Nix installs the app in the immutable Nix store, not /Applications.
+ kAllowOutsideApplicationsFolder: @(YES),
kEnableTahoeMenuIcons: @(NO),
kDoNotDrawOutlineAroundCurrentMonth: @(NO)
}];

View File

@@ -0,0 +1,124 @@
From f1474c9b296a3fbef30ca577dbd9a92224290b97 Mon Sep 17 00:00:00 2001
From: 4evy <git@evy.pink>
Date: Sat, 11 Jul 2026 01:12:47 +0300
Subject: [PATCH 2/4] Remove self-update controls for Nix builds
Sparkle cannot replace an application inside the immutable Nix store. Leaving
its menu item and preference checkbox backed by a no-op stub is misleading.
Remove the updater object, imports, controls, action, and matching Tahoe menu
symbol so the Nix build has no Sparkle runtime dependency or non-functional
update UI.
---
Itsycal/Base.lproj/MainMenu.xib | 1 -
Itsycal/PrefsGeneralVC.m | 11 ++---------
Itsycal/ViewController.m | 9 ---------
3 files changed, 2 insertions(+), 19 deletions(-)
diff --git a/Itsycal/Base.lproj/MainMenu.xib b/Itsycal/Base.lproj/MainMenu.xib
index aa2067e..1a25048 100644
--- a/Itsycal/Base.lproj/MainMenu.xib
+++ b/Itsycal/Base.lproj/MainMenu.xib
@@ -89,6 +89,5 @@
</menuItem>
</items>
</menu>
- <customObject id="9Zs-J5-87v" customClass="SUUpdater"/>
</objects>
</document>
diff --git a/Itsycal/PrefsGeneralVC.m b/Itsycal/PrefsGeneralVC.m
index 7c19942..86f6be0 100644
--- a/Itsycal/PrefsGeneralVC.m
+++ b/Itsycal/PrefsGeneralVC.m
@@ -9,7 +9,6 @@
#import "MoVFLHelper.h"
#import "EventCenter.h"
#import "MASShortcut/Shortcut.h"
-#import "Sparkle/SUUpdater.h"
static NSString * const kSourceCellId = @"SourceCell";
static NSString * const kCalendarCellId = @"CalendarCell";
@@ -33,7 +32,6 @@ static NSString * const kCalendarCellId = @"CalendarCell";
{
NSTextField *_title;
NSButton *_login;
- NSButton *_checkUpdates;
NSButton *_beepBeep;
NSPopUpButton *_firstDayPopup;
NSTableView *_calendarsTV;
@@ -66,7 +64,6 @@ static NSString * const kCalendarCellId = @"CalendarCell";
// Checkboxes
_login = chkbx(NSLocalizedString(@"Launch at login", @""));
_login.action = @selector(launchAtLogin:);
- _checkUpdates = chkbx(NSLocalizedString(@"Automatically check for updates", @""));
_beepBeep = chkbx(NSLocalizedString(@"Beep beep on the hour", @""));
// First day of week label
@@ -130,10 +127,9 @@ static NSString * const kCalendarCellId = @"CalendarCell";
NSLocalizedString(@"31 days", @"")]];
[v addSubview:_agendaDaysPopup];
- MoVFLHelper *vfl = [[MoVFLHelper alloc] initWithSuperview:v metrics:@{@"m": @20} views:NSDictionaryOfVariableBindings(_login, _checkUpdates, _beepBeep, firstDayLabel, _firstDayPopup, shortcutLabel, shortcutView, tvContainer, agendaDaysLabel, _agendaDaysPopup)];
- [vfl :@"V:|-m-[_login]-[_checkUpdates]-[_beepBeep]-20-[_firstDayPopup]-20-[shortcutLabel]-3-[shortcutView(25)]-20-[tvContainer(170)]-[_agendaDaysPopup]-m-|"];
+ MoVFLHelper *vfl = [[MoVFLHelper alloc] initWithSuperview:v metrics:@{@"m": @20} views:NSDictionaryOfVariableBindings(_login, _beepBeep, firstDayLabel, _firstDayPopup, shortcutLabel, shortcutView, tvContainer, agendaDaysLabel, _agendaDaysPopup)];
+ [vfl :@"V:|-m-[_login]-[_beepBeep]-20-[_firstDayPopup]-20-[shortcutLabel]-3-[shortcutView(25)]-20-[tvContainer(170)]-[_agendaDaysPopup]-m-|"];
[vfl :@"H:|-m-[_login]-(>=m)-|"];
- [vfl :@"H:|-m-[_checkUpdates]-(>=m)-|"];
[vfl :@"H:|-m-[_beepBeep]-(>=m)-|"];
[vfl :@"H:|-m-[firstDayLabel]-[_firstDayPopup]-(>=m)-|" :NSLayoutFormatAlignAllFirstBaseline];
[vfl :@"H:|-(>=m)-[shortcutLabel]-(>=m)-|"];
@@ -144,9 +140,6 @@ static NSString * const kCalendarCellId = @"CalendarCell";
// Center shortcutLabel
[v addConstraint:[NSLayoutConstraint constraintWithItem:shortcutLabel attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:v attribute:NSLayoutAttributeCenterX multiplier:1 constant:0]];
- // Binding for Sparkle automatic update checks
- [_checkUpdates bind:@"value" toObject:[SUUpdater sharedUpdater] withKeyPath:@"automaticallyChecksForUpdates" options:@{NSContinuouslyUpdatesValueBindingOption: @(YES)}];
-
// Binding for hourly beep
[_beepBeep bind:@"value" toObject:[NSUserDefaultsController sharedUserDefaultsController] withKeyPath:[@"values." stringByAppendingString:kBeepBeepOnTheHour] options:@{NSContinuouslyUpdatesValueBindingOption: @(YES)}];
diff --git a/Itsycal/ViewController.m b/Itsycal/ViewController.m
index 1d55c4f..0bd303b 100644
--- a/Itsycal/ViewController.m
+++ b/Itsycal/ViewController.m
@@ -21,7 +21,6 @@
#import "MoButton.h"
#import "MoVFLHelper.h"
#import "MoUtils.h"
-#import "Sparkle/SUUpdater.h"
@implementation ViewController
{
@@ -378,7 +377,6 @@
NSInteger i = 0;
[optMenu insertItemWithTitle:NSLocalizedString(@"About Itsycal", @"") action:@selector(showAbout:) keyEquivalent:@"" atIndex:i++];
- [optMenu insertItemWithTitle:NSLocalizedString(@"Check for Updates…", @"") action:@selector(checkForUpdates:) keyEquivalent:@"" atIndex:i++];
[optMenu insertItem:[NSMenuItem separatorItem] atIndex:i++];
[optMenu insertItemWithTitle:NSLocalizedString(@"Go to Date…", @"") action:@selector(showDatePickerPopover:) keyEquivalent:@"T" atIndex:i++];
[optMenu insertItem:[NSMenuItem separatorItem] atIndex:i++];
@@ -394,7 +392,6 @@
NSInteger index = 0;
NSArray *symbolNames = @[
@"info.circle",
- @"arrow.trianglehead.2.clockwise",
@"21.calendar",
@"gear",
@"calendar.badge.clock",
@@ -462,11 +459,6 @@
[self.prefsWC showWindow:self];
}
-- (void)checkForUpdates:(id)sender
-{
- [[SUUpdater sharedUpdater] checkForUpdates:self];
-}
-
- (void)openDateAndTimePrefs:(id)sender
{
NSURL *url = nil;
@@ -1514,4 +1506,3 @@
}
@end
-

View File

@@ -0,0 +1,116 @@
From d3d2dd9ce8b566496503c38158199432a5ae87bb Mon Sep 17 00:00:00 2001
From: 4evy <git@evy.pink>
Date: Sat, 11 Jul 2026 01:12:47 +0300
Subject: [PATCH 3/4] Use modern ServiceManagement login items
LSSharedFileList was deprecated in macOS 10.11 and its status lookup can fail
when disconnected network volumes are present. Nixpkgs now targets macOS 14,
so no legacy fallback is needed.
Use SMAppService.mainAppService, available since macOS 13. Treat
RequiresApproval as registered so users can still turn the pending login item
off. The package signs the complete app bundle after fixup, as required by
ServiceManagement.
---
Itsycal/MoLoginItem.m | 58 +++++++---------------------------------
Itsycal/PrefsGeneralVC.m | 12 +--------
2 files changed, 11 insertions(+), 59 deletions(-)
diff --git a/Itsycal/MoLoginItem.m b/Itsycal/MoLoginItem.m
index 7efa602..0dc4960 100644
--- a/Itsycal/MoLoginItem.m
+++ b/Itsycal/MoLoginItem.m
@@ -4,60 +4,22 @@
//
#import "MoLoginItem.h"
-
-// LSSharedFileList API was deprecated in macOS 10.11
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wdeprecated-declarations"
+#import <ServiceManagement/ServiceManagement.h>
BOOL MOIsLoginItemEnabled(void)
{
- BOOL isEnabled = NO;
- NSString *appPath = [[NSBundle mainBundle] bundlePath];
- LSSharedFileListRef loginItemsRef = LSSharedFileListCreate(NULL, kLSSharedFileListSessionLoginItems, NULL);
-
- if (loginItemsRef) {
- UInt32 seedValue;
- NSArray *loginItems = CFBridgingRelease(LSSharedFileListCopySnapshot(loginItemsRef, &seedValue));
- for (id item in loginItems) {
- LSSharedFileListItemRef itemRef = (__bridge LSSharedFileListItemRef)item;
- NSURL *pathURL = CFBridgingRelease(LSSharedFileListItemCopyResolvedURL(itemRef, 0, NULL));
- if (pathURL && [pathURL.path hasPrefix:appPath]) {
- isEnabled = YES;
- break;
- }
- }
- CFRelease(loginItemsRef);
- }
- return isEnabled;
+ SMAppServiceStatus status = SMAppService.mainAppService.status;
+ return status == SMAppServiceStatusEnabled || status == SMAppServiceStatusRequiresApproval;
}
void MOEnableLoginItem(BOOL enable)
{
- NSString *appPath = [[NSBundle mainBundle] bundlePath];
- LSSharedFileListRef loginItemsRef = LSSharedFileListCreate(NULL, kLSSharedFileListSessionLoginItems, NULL);
-
- if (loginItemsRef) {
- if (enable) {
- // We call LSSharedFileListInsertItemURL to insert the item at the bottom of Login Items list.
- CFURLRef url = (__bridge CFURLRef)[NSURL fileURLWithPath:appPath];
- LSSharedFileListItemRef item = LSSharedFileListInsertItemURL(loginItemsRef, kLSSharedFileListItemLast, NULL, NULL, url, NULL, NULL);
- if (item != NULL) CFRelease(item);
- }
- else {
- // Grab the contents of the shared file list (LSSharedFileListItemRef objects)
- // and pop it in an array so we can iterate through it to find our item.
- UInt32 seedValue;
- NSArray *loginItems = CFBridgingRelease(LSSharedFileListCopySnapshot(loginItemsRef, &seedValue));
- for (id item in loginItems) {
- LSSharedFileListItemRef itemRef = (__bridge LSSharedFileListItemRef)item;
- NSURL *pathURL = CFBridgingRelease(LSSharedFileListItemCopyResolvedURL(itemRef, 0, NULL));
- if (pathURL && [pathURL.path hasPrefix:appPath]) {
- LSSharedFileListItemRemove(loginItemsRef, itemRef); // Deleting the item
- }
- }
- }
- CFRelease(loginItemsRef);
+ SMAppService *service = SMAppService.mainAppService;
+ NSError *error = nil;
+ BOOL succeeded = enable
+ ? [service registerAndReturnError:&error]
+ : [service unregisterAndReturnError:&error];
+ if (!succeeded) {
+ NSLog(@"Unable to %@ launch at login: %@", enable ? @"enable" : @"disable", error);
}
}
-
-#pragma clang diagnostic pop
diff --git a/Itsycal/PrefsGeneralVC.m b/Itsycal/PrefsGeneralVC.m
index 86f6be0..ecfd30d 100644
--- a/Itsycal/PrefsGeneralVC.m
+++ b/Itsycal/PrefsGeneralVC.m
@@ -158,17 +158,7 @@ static NSString * const kCalendarCellId = @"CalendarCell";
_sourcesAndCalendars = [self.ec sourcesAndCalendars];
- // The API used to check the login item's state (LSSharedFileList) causes
- // errors for users who have network drives but are not connected to their
- // network (github.com/sfsam/Itsycal/issues/15). Give them an option to
- // disable this check.
- if ([[NSUserDefaults standardUserDefaults] boolForKey:@"DoNotCheckLoginItemStatus"] == NO) {
- _login.hidden = NO;
- _login.state = MOIsLoginItemEnabled() ? NSControlStateValueOn : NSControlStateValueOff;
- }
- else {
- _login.hidden = YES;
- }
+ _login.state = MOIsLoginItemEnabled() ? NSControlStateValueOn : NSControlStateValueOff;
_calendarsTV.enabled = self.ec.calendarAccessGranted;
_agendaDaysPopup.enabled = self.ec.calendarAccessGranted;

View File

@@ -0,0 +1,90 @@
From 6fb6ac13a8deee492449442e1ab3d7429aa9fff8 Mon Sep 17 00:00:00 2001
From: 4evy <git@evy.pink>
Date: Sat, 11 Jul 2026 01:12:55 +0300
Subject: [PATCH 4/4] Use APIs available on the Nixpkgs macOS baseline
The package metadata and Mach-O deployment target are both macOS 14. Remove
compatibility branches for older systems and use the corresponding current
APIs:
* request EventKit full access directly;
* discover the calendar handler through NSWorkspace and
UniformTypeIdentifiers;
* use the renamed CoreAudio main-element constant.
---
Itsycal/EventCenter.m | 20 ++------------------
Itsycal/ViewController.m | 9 ++++-----
2 files changed, 6 insertions(+), 23 deletions(-)
diff --git a/Itsycal/EventCenter.m b/Itsycal/EventCenter.m
index 148c631..3862630 100644
--- a/Itsycal/EventCenter.m
+++ b/Itsycal/EventCenter.m
@@ -59,15 +59,7 @@ static NSString *kSelectedCalendars = @"SelectedCalendars";
_queueIsol = dispatch_queue_create("com.mowglii.Itsycal.queueIsol", DISPATCH_QUEUE_SERIAL);
_queueIsol2 = dispatch_queue_create("com.mowglii.Itsycal.queueIsol2", DISPATCH_QUEUE_SERIAL);
_store = [EKEventStore new];
-#if MAC_OS_X_VERSION_MAX_ALLOWED >= 140000
- if (@available(macOS 14.0, *)) {
- [_store requestFullAccessToEventsWithCompletion:requestCompletionHandler];
- } else {
- [_store requestAccessToEntityType:EKEntityTypeEvent completion:requestCompletionHandler];
- }
-#else
- [_store requestAccessToEntityType:EKEntityTypeEvent completion:requestCompletionHandler];
-#endif
+ [_store requestFullAccessToEventsWithCompletion:requestCompletionHandler];
// Refetch everything when the event store has changed.
__weak __typeof(self) weakSelf = self;
@@ -85,15 +77,7 @@ static NSString *kSelectedCalendars = @"SelectedCalendars";
#pragma mark - Public (main thread)
- (BOOL)calendarAccessGranted {
-#if MAC_OS_X_VERSION_MAX_ALLOWED >= 140000
- if (@available(macOS 14.0, *)) {
- return [EKEventStore authorizationStatusForEntityType:EKEntityTypeEvent] == EKAuthorizationStatusFullAccess;
- } else {
- return [EKEventStore authorizationStatusForEntityType:EKEntityTypeEvent] == EKAuthorizationStatusAuthorized;
- }
-#else
- return [EKEventStore authorizationStatusForEntityType:EKEntityTypeEvent] == EKAuthorizationStatusAuthorized;
-#endif
+ return [EKEventStore authorizationStatusForEntityType:EKEntityTypeEvent] == EKAuthorizationStatusFullAccess;
}
- (NSString *)defaultCalendarIdentifier {
diff --git a/Itsycal/ViewController.m b/Itsycal/ViewController.m
index 0bd303b..a887682 100644
--- a/Itsycal/ViewController.m
+++ b/Itsycal/ViewController.m
@@ -21,6 +21,7 @@
#import "MoButton.h"
#import "MoVFLHelper.h"
#import "MoUtils.h"
+#import <UniformTypeIdentifiers/UniformTypeIdentifiers.h>
@implementation ViewController
{
@@ -319,10 +320,8 @@
// Determine the default calendar app.
// See: support.busymac.com/help/21535-busycal-url-handler
- CFStringRef strRef = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, CFSTR("ics"), kUTTypeData);
- CFStringRef bundleID = LSCopyDefaultRoleHandlerForContentType(strRef, kLSRolesEditor);
- CFRelease(strRef);
- NSString *defaultCalendarAppBundleID = CFBridgingRelease(bundleID);
+ NSURL *defaultCalendarAppURL = [NSWorkspace.sharedWorkspace URLForApplicationToOpenContentType:UTTypeCalendarEvent];
+ NSString *defaultCalendarAppBundleID = [NSBundle bundleWithURL:defaultCalendarAppURL].bundleIdentifier;
// Use URL scheme to open BusyCal or Fantastical2 on the
// date selected in our calendar.
@@ -1263,7 +1262,7 @@
AudioObjectPropertyAddress propertyAddress;
propertyAddress.mSelector = kAudioHardwarePropertyDefaultOutputDevice;
propertyAddress.mScope = kAudioObjectPropertyScopeGlobal;
- propertyAddress.mElement = kAudioObjectPropertyElementMaster;
+ propertyAddress.mElement = kAudioObjectPropertyElementMain;
OSStatus result = AudioObjectGetPropertyData(kAudioObjectSystemObject, &propertyAddress, 0, NULL, &dataSize, &deviceID);

View File

@@ -1,42 +1,240 @@
{
lib,
fetchzip,
stdenvNoCC,
stdenv,
fetchFromGitHub,
actool,
ibtool,
lld,
makeWrapper,
rcodesign,
re-plistbuddy,
nix-update-script,
}:
stdenvNoCC.mkDerivation (finalAttrs: {
let
# Upstream ships MASShortcut only as a prebuilt framework. Build the
# maintained Rectangle fork instead; it adds F20 and macOS Tahoe fixes while
# retaining the API and bundle identity used by Itsycal.
masShortcutSrc = fetchFromGitHub {
owner = "rxhanson";
repo = "MASShortcut";
rev = "2f9fbb3f959b7a683c6faaf9638d22afad37a235";
hash = "sha256-EZLt7ph24L1wwFEMlltuPutId09RBug/y9OtDhixIig=";
};
masShortcutSources = [
"Model/MASShortcut.m"
"Model/MASShortcutValidator.m"
"Monitoring/MASHotKey.m"
"Monitoring/MASShortcutMonitor.m"
"UI/MASLocalization.m"
"UI/MASShortcutView.m"
"UI/MASShortcutView+Bindings.m"
"UI/MASShortcutViewButtonCell.m"
"User Defaults Storage/MASDictionaryTransformer.m"
"User Defaults Storage/MASShortcutBinder.m"
];
masShortcutInfoPlist = lib.generators.toPlist { escape = true; } {
CFBundleDevelopmentRegion = "English";
CFBundleExecutable = "MASShortcut";
CFBundleIdentifier = "com.github.shpakovski.MASShortcut";
CFBundleInfoDictionaryVersion = "6.0";
CFBundleName = "MASShortcut";
CFBundlePackageType = "FMWK";
CFBundleShortVersionString = "2.4.0";
CFBundleVersion = "2.4.0";
NSHumanReadableCopyright = "Copyright © Vadim Shpakovski. All rights reserved.";
};
in
stdenv.mkDerivation (finalAttrs: {
pname = "itsycal";
version = "0.15.12";
src = fetchzip {
url = "https://itsycal.s3.amazonaws.com/Itsycal-${finalAttrs.version}.zip";
hash = "sha256-2Xu1ZQnNl0o2/AYOIjxZPDnc0TxMXrqKej7CCZEVV9I=";
stripRoot = false;
src = fetchFromGitHub {
owner = "sfsam";
repo = "Itsycal";
tag = finalAttrs.version;
hash = "sha256-K25oG8d+OauNHLkatLLskfcWCqOmM2WEw2Ygd3v1uqE=";
};
patches = [
# Use upstream's hidden escape hatch for installations outside /Applications.
./0001-Allow-running-from-the-Nix-store.patch
# Nix owns updates, so remove the otherwise non-functional Sparkle UI.
./0002-Remove-self-update-controls-for-Nix-builds.patch
# Replace the deprecated LSSharedFileList implementation with SMAppService.
./0003-Use-modern-ServiceManagement-login-items.patch
# The Nixpkgs Darwin baseline is macOS 14; use its non-deprecated APIs.
./0004-Use-APIs-available-on-the-Nixpkgs-macOS-baseline.patch
];
strictDeps = true;
nativeBuildInputs = [
actool
ibtool
lld
makeWrapper
rcodesign
re-plistbuddy
];
# The classic open-source ld64 crashes while linking MASShortcut on arm64.
# Keep lld until the cctools linker can link this framework reliably.
env.NIX_CFLAGS_LINK = "-fuse-ld=lld";
dontConfigure = true;
postPatch = ''
# Both upstream frameworks contain native code. MASShortcut is rebuilt
# below and Sparkle is removed by patch 0002, so make accidental reuse fail.
rm -rf Itsycal/_frameworks
'';
buildPhase = ''
runHook preBuild
buildDir="$PWD/build"
mkdir -p "$buildDir/module-cache"
# actool accepts one catalog, while upstream keeps images and colors in
# separate catalogs. Merge them without changing their contents.
cp -R Itsycal/Images.xcassets "$buildDir/Assets.xcassets"
cp -R Itsycal/Colors.xcassets/*.colorset "$buildDir/Assets.xcassets/"
# Compile .m -> .o; flags before --, sources after. Sets _objects array
compile_objc() {
local prefix="$1"; shift
local -a flags=()
while [[ "$1" != "--" ]]; do flags+=("$1"); shift; done; shift
_objects=()
for src in "$@"; do
local obj="$buildDir/''${prefix}_$(basename "$src" .m).o"
clang -fobjc-arc "''${flags[@]}" -c "$src" -o "$obj"
_objects+=("$obj")
done
}
link_dylib() {
local name="$1"; shift
clang -dynamiclib -fobjc-arc \
-install_name "@rpath/$name.framework/$name" "$@"
}
# Build MASShortcut as a real framework. The explicit source list avoids
# pulling its demo or test targets into the application.
masHeaders="$buildDir/MAS_headers/MASShortcut"
mkdir -p "$masHeaders"
cp ${masShortcutSrc}/Framework/include/*.h "$masHeaders/"
masSources=( ${
lib.escapeShellArgs (map (src: "${masShortcutSrc}/Framework/${src}") masShortcutSources)
} )
compile_objc mas -fmodules -fmodules-cache-path="$buildDir/module-cache" \
-I"$masHeaders" -include AppKit/AppKit.h -- "''${masSources[@]}"
link_dylib MASShortcut -framework AppKit -framework Carbon \
-compatibility_version 1 -current_version 2.4.0 \
-o "$buildDir/MASShortcut.dylib" "''${_objects[@]}"
# Itsycal
compile_objc app -fmodules -fmodules-cache-path="$buildDir/module-cache" \
-I"$buildDir/MAS_headers" -IItsycal -- Itsycal/*.m
clang -fobjc-arc \
-framework AppKit -framework EventKit -framework ScriptingBridge \
-framework Carbon -framework ServiceManagement -framework UniformTypeIdentifiers \
-Wl,-rpath,@executable_path/../Frameworks \
"$buildDir/MASShortcut.dylib" \
-o "$buildDir/Itsycal" "''${_objects[@]}"
runHook postBuild
'';
installPhase = ''
runHook preInstall
mkdir -p "$out/Applications"
cp -R Itsycal.app "$out/Applications/"
appDir="$out/Applications/Itsycal.app/Contents"
mkdir -p "$appDir"/{MacOS,Frameworks,Resources}
cp "$buildDir/Itsycal" "$appDir/MacOS/Itsycal"
# Assemble the framework bundle metadata normally generated by Xcode.
mkdir -p "$appDir/Frameworks/MASShortcut.framework/Resources"
cp "$buildDir/MASShortcut.dylib" "$appDir/Frameworks/MASShortcut.framework/MASShortcut"
cp -r ${masShortcutSrc}/Framework/Resources/*.lproj "$appDir/Frameworks/MASShortcut.framework/Resources/"
printf '%s' ${lib.escapeShellArg masShortcutInfoPlist} \
> "$appDir/Frameworks/MASShortcut.framework/Resources/Info.plist"
# Resources
ibtool --compile "$appDir/Resources/MainMenu.nib" Itsycal/Base.lproj/MainMenu.xib
actool --compile "$appDir/Resources" \
--platform macosx \
--minimum-deployment-target ${stdenv.hostPlatform.darwinMinVersion} \
--app-icon AppIcon \
--output-partial-info-plist "$buildDir/asset-info.plist" \
"$buildDir/Assets.xcassets"
cp Itsycal/beep.mp3 "$appDir/Resources/"
mkdir -p "$appDir/Resources/Fonts"
cp Itsycal/_fonts/Mow.otf "$appDir/Resources/Fonts/"
for lproj in Itsycal/*.lproj; do
mkdir -p "$appDir/Resources/$(basename "$lproj")"
cp "$lproj"/*.strings "$appDir/Resources/$(basename "$lproj")/" 2>/dev/null || true
done
# Resolve the Xcode variables in upstream's plist. Keeping that plist as
# the source of truth means new privacy and bundle metadata is preserved.
bundleVersion=$(sed -n 's/.*CURRENT_PROJECT_VERSION = \([0-9]*\);/\1/p' \
Itsycal.xcodeproj/project.pbxproj | head -1)
cp Itsycal/Info.plist "$appDir/Info.plist"
substituteInPlace "$appDir/Info.plist" \
--replace-fail '$(EXECUTABLE_NAME)' 'Itsycal' \
--replace-fail '$(PRODUCT_BUNDLE_IDENTIFIER)' 'com.mowglii.ItsycalApp' \
--replace-fail '$(PRODUCT_NAME)' 'Itsycal' \
--replace-fail '$(MARKETING_VERSION)' '${finalAttrs.version}' \
--replace-fail '$(CURRENT_PROJECT_VERSION)' "$bundleVersion" \
--replace-fail '$(MACOSX_DEPLOYMENT_TARGET)' '${stdenv.hostPlatform.darwinMinVersion}'
# Merge actool's icon metadata and remove the Sparkle configuration that
# no longer has a consumer in this immutable Nix build.
PlistBuddy -c 'Set :CFBundleIconFile AppIcon' \
-c 'Add :CFBundleIconName string AppIcon' \
-c 'Delete :SUAllowsAutomaticUpdates' \
-c 'Delete :SUEnableAutomaticChecks' \
-c 'Delete :SUFeedURL' \
-c 'Delete :SUPublicEDKey' \
-c 'Delete :SUScheduledCheckInterval' \
"$appDir/Info.plist"
makeWrapper "$out/Applications/Itsycal.app/Contents/MacOS/Itsycal" "$out/bin/itsycal"
runHook postInstall
'';
passthru = {
updateScript = ./update.sh;
# Signing only Mach-O files leaves app resources unsealed. Sign the complete
# bundle after fixup so ServiceManagement accepts it and nested code verifies.
postFixup = ''
rcodesign sign "$out/Applications/Itsycal.app"
'';
passthru.updateScript = nix-update-script {
# Ignore upstream's non-version "help" tag.
extraArgs = [ "--version-regex=^([0-9]+\\.[0-9]+\\.[0-9]+)$" ];
};
meta = {
changelog = "https://www.mowglii.com/itsycal/versionhistory.html";
description = "Tiny menu bar calendar";
homepage = "https://www.mowglii.com/itsycal/";
license = lib.licenses.mit;
license = with lib.licenses; [
bsd2
mit
];
mainProgram = "itsycal";
maintainers = with lib.maintainers; [
eclairevoyant
_4evy
];
platforms = lib.platforms.darwin;
sourceProvenance = with lib.sourceTypes; [ binaryNativeCode ];
sourceProvenance = with lib.sourceTypes; [ fromSource ];
};
})

View File

@@ -1,20 +0,0 @@
#!/usr/bin/env nix-shell
#!nix-shell -i bash -p curl xq-xml common-updater-scripts
set -eu
ROOT="$(dirname "$(readlink -f "$0")")"
NIX_DRV="$ROOT/package.nix"
if [ ! -f "$NIX_DRV" ]; then
echo "ERROR: cannot find package.nix in $ROOT"
exit 1
fi
LATEST_VERSION="$(curl -Ls https://www.mowglii.com/itsycal/versionhistory.html | xq -m -q 'h4' -a 'id' | head -n1)"
if [ -z "$LATEST_VERSION" ]; then
echo "ERROR: Failed to scrape the latest version."
exit 1
fi
update-source-version itsycal "$LATEST_VERSION" --file="$NIX_DRV"