Update Installer to support app notarization
Soon notarization will be required for Developer ID apps. This change allows the Installer to run in two modes. The "dev mode" still builds the IME as the prerequisite of the Installer and places the IME app bundle inside the Installer's resources folder. That has been so since the beginning of this project, and this continues to allow IME developers to test the input method. On the other hand, if "McBopomofo-r$rev.zip" is placed in the NotarizedArchives folder and McBopomofo is not built as a dependency of the Installer and the app bundle is not copied to the resources folder, the Installer then can be built as a notarizable app (otherwise Xcode wouldn't even let you submit it for notarization). To build the distributable Installer, notarize the IME app first, then zip the app as McBopomofo-r$rev.zip and place that to the NotarizedArchives folder under Source/Installer. Then build and submit the Installer for notarization. This is in line with Apple's guideline in https://developer.apple.com/documentation/xcode/notarizing_your_app_before_distribution/customizing_the_notarization_workflow ("If you distribute your software via a custom third-party installer, you need two rounds of notarization.") We don't expect that we make new Installers often enough, and therefore we don't intend to automate this process via scripting.
This commit is contained in:
parent
7978a9efba
commit
4f6d1acc43
|
@ -29,6 +29,8 @@
|
|||
6A0D4F5715FC0EF900ABF4B3 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 6A0D4F4815FC0EE100ABF4B3 /* InfoPlist.strings */; };
|
||||
6A0D4F5815FC0EF900ABF4B3 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 6A0D4F4A15FC0EE100ABF4B3 /* Localizable.strings */; };
|
||||
6A187E2616004C5900466B2E /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6A187E2816004C5900466B2E /* MainMenu.xib */; };
|
||||
6A225A1F23679F2600F685C6 /* NotarizedArchives in Resources */ = {isa = PBXBuildFile; fileRef = 6A225A1E23679F2600F685C6 /* NotarizedArchives */; };
|
||||
6A225A232367A1D700F685C6 /* ArchiveUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = 6A225A222367A1D700F685C6 /* ArchiveUtil.m */; };
|
||||
6A38BC1515FC117A00A8A51F /* data.txt in Resources */ = {isa = PBXBuildFile; fileRef = 6A38BBF615FC117A00A8A51F /* data.txt */; };
|
||||
6A38BC1D15FC11C700A8A51F /* UpdateNotificationController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6A38BC1F15FC11C700A8A51F /* UpdateNotificationController.xib */; };
|
||||
6A38BC2815FC158A00A8A51F /* InputMethodKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6A38BC2715FC158A00A8A51F /* InputMethodKit.framework */; };
|
||||
|
@ -156,6 +158,9 @@
|
|||
6A15B32621A51F2300B92CD3 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/UpdateNotificationController.xib; sourceTree = "<group>"; };
|
||||
6A15B32721A51F2300B92CD3 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Source/Base.lproj/preferences.xib; sourceTree = "<group>"; };
|
||||
6A187E2916004C7300466B2E /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = "zh-Hant"; path = "zh-Hant.lproj/MainMenu.xib"; sourceTree = "<group>"; };
|
||||
6A225A1E23679F2600F685C6 /* NotarizedArchives */ = {isa = PBXFileReference; lastKnownFileType = folder; path = NotarizedArchives; sourceTree = "<group>"; };
|
||||
6A225A212367A1D700F685C6 /* ArchiveUtil.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ArchiveUtil.h; sourceTree = "<group>"; };
|
||||
6A225A222367A1D700F685C6 /* ArchiveUtil.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ArchiveUtil.m; sourceTree = "<group>"; };
|
||||
6A38BBDE15FC117A00A8A51F /* 4_in_5.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = 4_in_5.txt; sourceTree = "<group>"; };
|
||||
6A38BBDF15FC117A00A8A51F /* 4_in_6.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = 4_in_6.txt; sourceTree = "<group>"; };
|
||||
6A38BBE015FC117A00A8A51F /* 5_in_6.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = 5_in_6.txt; sourceTree = "<group>"; };
|
||||
|
@ -471,6 +476,9 @@
|
|||
6ACA41E715FC1D9000935EF6 /* Installer */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
6A225A212367A1D700F685C6 /* ArchiveUtil.h */,
|
||||
6A225A222367A1D700F685C6 /* ArchiveUtil.m */,
|
||||
6A225A1E23679F2600F685C6 /* NotarizedArchives */,
|
||||
6ACA41E815FC1D9000935EF6 /* AppDelegate.h */,
|
||||
6ACA41E915FC1D9000935EF6 /* AppDelegate.m */,
|
||||
6ACA41EA15FC1D9000935EF6 /* InfoPlist.strings */,
|
||||
|
@ -529,6 +537,7 @@
|
|||
6ACA41C715FC1D7500935EF6 /* Sources */,
|
||||
6ACA41C815FC1D7500935EF6 /* Frameworks */,
|
||||
6ACA41C915FC1D7500935EF6 /* Resources */,
|
||||
6A225A2023679F5F00F685C6 /* ShellScript */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
|
@ -553,6 +562,7 @@
|
|||
developmentRegion = English;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
English,
|
||||
en,
|
||||
"zh-Hant",
|
||||
Base,
|
||||
|
@ -598,6 +608,7 @@
|
|||
files = (
|
||||
6ACA420215FC1E5200935EF6 /* McBopomofo.app in Resources */,
|
||||
6ACA41FA15FC1D9000935EF6 /* InfoPlist.strings in Resources */,
|
||||
6A225A1F23679F2600F685C6 /* NotarizedArchives in Resources */,
|
||||
6ACA41FB15FC1D9000935EF6 /* License.rtf in Resources */,
|
||||
6ACA41FC15FC1D9000935EF6 /* Localizable.strings in Resources */,
|
||||
6ACA41FD15FC1D9000935EF6 /* MainMenu.xib in Resources */,
|
||||
|
@ -607,6 +618,26 @@
|
|||
};
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXShellScriptBuildPhase section */
|
||||
6A225A2023679F5F00F685C6 /* ShellScript */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
outputFileListPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "# Remove the README.md in the NotarizedArchives directory\nrm -f \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/NotarizedArchives/README.md\"\n";
|
||||
};
|
||||
/* End PBXShellScriptBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
6A0D4E9E15FC0D2D00ABF4B3 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
|
@ -634,6 +665,7 @@
|
|||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
6ACA41F915FC1D9000935EF6 /* AppDelegate.m in Sources */,
|
||||
6A225A232367A1D700F685C6 /* ArchiveUtil.m in Sources */,
|
||||
6ACA41FF15FC1D9000935EF6 /* main.m in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
|
|
|
@ -26,10 +26,12 @@
|
|||
//
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#import "ArchiveUtil.h"
|
||||
|
||||
@interface AppDelegate : NSWindowController <NSApplicationDelegate>
|
||||
{
|
||||
@protected
|
||||
ArchiveUtil *_archiveUtil;
|
||||
NSString *_installingVersion;
|
||||
BOOL _upgrading;
|
||||
NSButton *_installButton;
|
||||
|
|
|
@ -58,6 +58,7 @@ void RunAlertPanel(NSString *title, NSString *message, NSString *buttonTitle) {
|
|||
|
||||
- (void)dealloc
|
||||
{
|
||||
[_archiveUtil release];
|
||||
[_installingVersion release];
|
||||
[_translocationRemovalStartTime release];
|
||||
[super dealloc];
|
||||
|
@ -65,6 +66,12 @@ void RunAlertPanel(NSString *title, NSString *message, NSString *buttonTitle) {
|
|||
|
||||
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
|
||||
{
|
||||
_installingVersion = [[[[NSBundle mainBundle] infoDictionary] objectForKey:(id)kCFBundleVersionKey] retain];
|
||||
NSString *versionString = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleShortVersionString"];
|
||||
|
||||
_archiveUtil = [[ArchiveUtil alloc] initWithAppName:kTargetBin targetAppBundleName:kTargetBundle];
|
||||
[_archiveUtil validateIfNotarizedArchiveExists];
|
||||
|
||||
[self.cancelButton setNextKeyView:self.installButton];
|
||||
[self.installButton setNextKeyView:self.cancelButton];
|
||||
[[self window] setDefaultButtonCell:[self.installButton cell]];
|
||||
|
@ -75,11 +82,7 @@ void RunAlertPanel(NSString *title, NSString *message, NSString *buttonTitle) {
|
|||
[mutableAttrStr addAttribute:NSForegroundColorAttributeName value:[NSColor controlTextColor] range:NSMakeRange(0, [mutableAttrStr length])];
|
||||
[[self.textView textStorage] setAttributedString:mutableAttrStr];
|
||||
[self.textView setSelectedRange:NSMakeRange(0, 0)];
|
||||
|
||||
NSBundle *installingBundle = [NSBundle bundleWithPath:[[NSBundle mainBundle] pathForResource:kTargetBin ofType:kTargetType]];
|
||||
_installingVersion = [[[installingBundle infoDictionary] objectForKey:(id)kCFBundleVersionKey] retain];
|
||||
NSString *versionString = [[installingBundle infoDictionary] objectForKey:@"CFBundleShortVersionString"];
|
||||
|
||||
|
||||
[[self window] setTitle:[NSString stringWithFormat:NSLocalizedString(@"%@ (for version %@, r%@)", nil), [[self window] title], versionString, _installingVersion]];
|
||||
|
||||
if ([[NSFileManager defaultManager] fileExistsAtPath:[kTargetPartialPath stringByExpandingTildeInPath]]) {
|
||||
|
@ -170,7 +173,13 @@ void RunAlertPanel(NSString *title, NSString *message, NSString *buttonTitle) {
|
|||
|
||||
- (void)installInputMethodWithWarning:(BOOL)warning
|
||||
{
|
||||
NSTask *cpTask = [NSTask launchedTaskWithLaunchPath:@"/bin/cp" arguments:[NSArray arrayWithObjects:@"-R", [[NSBundle mainBundle] pathForResource:kTargetBin ofType:kTargetType], [kDestinationPartial stringByExpandingTildeInPath], nil]];
|
||||
// If the unzipped archive does not exist, this must be a dev-mode installer.
|
||||
NSString *targetBundle = [_archiveUtil unzipNotarizedArchive];
|
||||
if (!targetBundle) {
|
||||
targetBundle = [[NSBundle mainBundle] pathForResource:kTargetBin ofType:kTargetType];
|
||||
}
|
||||
|
||||
NSTask *cpTask = [NSTask launchedTaskWithLaunchPath:@"/bin/cp" arguments:[NSArray arrayWithObjects:@"-R", targetBundle, [kDestinationPartial stringByExpandingTildeInPath], nil]];
|
||||
[cpTask waitUntilExit];
|
||||
if ([cpTask terminationStatus] != 0) {
|
||||
RunAlertPanel(NSLocalizedString(@"Install Failed", nil), NSLocalizedString(@"Cannot copy the file to the destination.", nil), NSLocalizedString(@"Cancel", nil));
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
// Copyright (c) 2011-2019 The McBopomofo Project.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person
|
||||
// obtaining a copy of this software and associated documentation
|
||||
// files (the "Software"), to deal in the Software without
|
||||
// restriction, including without limitation the rights to use,
|
||||
// copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the
|
||||
// Software is furnished to do so, subject to the following
|
||||
// conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
// OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@interface ArchiveUtil : NSObject {
|
||||
NSString *_appName;
|
||||
NSString *_targetAppBundleName;
|
||||
}
|
||||
- (instancetype _Nonnull)initWithAppName:(NSString *_Nonnull)name
|
||||
targetAppBundleName:(NSString *_Nonnull)invalidAppBundleName;
|
||||
|
||||
// Returns YES if (1) a zip file under
|
||||
// Resources/NotarizedArchives/$_appName-$bundleVersion.zip exists, and (2) if
|
||||
// Resources/$_invalidAppBundleName does not exist.
|
||||
- (BOOL)validateIfNotarizedArchiveExists;
|
||||
|
||||
- (NSString *_Nullable)unzipNotarizedArchive;
|
||||
@end
|
|
@ -0,0 +1,136 @@
|
|||
// Copyright (c) 2011-2019 The McBopomofo Project.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person
|
||||
// obtaining a copy of this software and associated documentation
|
||||
// files (the "Software"), to deal in the Software without
|
||||
// restriction, including without limitation the rights to use,
|
||||
// copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the
|
||||
// Software is furnished to do so, subject to the following
|
||||
// conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
// OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
#import "ArchiveUtil.h"
|
||||
|
||||
@implementation ArchiveUtil
|
||||
- (instancetype)initWithAppName:(NSString *)name
|
||||
targetAppBundleName:(NSString *)targetAppBundleName {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_appName = [name retain];
|
||||
_targetAppBundleName = [targetAppBundleName retain];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)delloc {
|
||||
[_appName release];
|
||||
[_targetAppBundleName release];
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (BOOL)validateIfNotarizedArchiveExists {
|
||||
NSString *resourePath = [[NSBundle mainBundle] resourcePath];
|
||||
NSString *devModeAppBundlePath =
|
||||
[resourePath stringByAppendingPathComponent:_targetAppBundleName];
|
||||
|
||||
NSArray<NSString *> *notarizedArchivesContent =
|
||||
[[NSFileManager defaultManager] subpathsAtPath:[self notarizedArchivesPath]];
|
||||
NSInteger count = [notarizedArchivesContent count];
|
||||
BOOL notarizedArchiveExists =
|
||||
[[NSFileManager defaultManager] fileExistsAtPath:[self notarizedArchive]];
|
||||
BOOL devModeAppBundleExists =
|
||||
[[NSFileManager defaultManager] fileExistsAtPath:devModeAppBundlePath];
|
||||
|
||||
if (count > 0) {
|
||||
// Not a valid distribution package.
|
||||
if (count != 1 || !notarizedArchiveExists || devModeAppBundleExists) {
|
||||
NSAlert *alert = [[NSAlert alloc] init];
|
||||
[alert setAlertStyle:NSAlertStyleInformational];
|
||||
[alert setMessageText:@"Internal Error"];
|
||||
[alert
|
||||
setInformativeText:
|
||||
[NSString stringWithFormat:@"devMode installer, expected archive name: %@, "
|
||||
@"archive exists: %d, devMode app bundle exists: %d",
|
||||
[self notarizedArchive], notarizedArchiveExists,
|
||||
devModeAppBundleExists]];
|
||||
[alert addButtonWithTitle:@"Terminate"];
|
||||
[alert runModal];
|
||||
[alert autorelease];
|
||||
|
||||
[[NSApplication sharedApplication] terminate:nil];
|
||||
} else {
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
|
||||
if (!devModeAppBundleExists) {
|
||||
NSAlert *alert = [[NSAlert alloc] init];
|
||||
[alert setAlertStyle:NSAlertStyleInformational];
|
||||
[alert setMessageText:@"Internal Error"];
|
||||
[alert
|
||||
setInformativeText:[NSString stringWithFormat:@"Dev target bundle does not exist: %@",
|
||||
devModeAppBundlePath]];
|
||||
[alert addButtonWithTitle:@"Terminate"];
|
||||
[alert runModal];
|
||||
[alert autorelease];
|
||||
|
||||
[[NSApplication sharedApplication] terminate:nil];
|
||||
}
|
||||
|
||||
// Notarized archive does not exist, but it's ok.
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (NSString *)unzipNotarizedArchive {
|
||||
if (![self validateIfNotarizedArchiveExists]) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSString *tempFilePath =
|
||||
[NSTemporaryDirectory() stringByAppendingPathComponent:[[NSUUID UUID] UUIDString]];
|
||||
NSArray *arguments = @[ [self notarizedArchive], @"-d", tempFilePath ];
|
||||
|
||||
NSTask *unzipTask = [[NSTask alloc] init];
|
||||
[unzipTask setLaunchPath:@"/usr/bin/unzip"];
|
||||
[unzipTask setCurrentDirectoryPath:[[NSBundle mainBundle] resourcePath]];
|
||||
[unzipTask setArguments:arguments];
|
||||
[unzipTask launch];
|
||||
[unzipTask waitUntilExit];
|
||||
|
||||
NSAssert(unzipTask.terminationStatus == 0, @"Must successfully unzipped");
|
||||
|
||||
NSString *result = [tempFilePath stringByAppendingPathComponent:_targetAppBundleName];
|
||||
NSAssert([[NSFileManager defaultManager] fileExistsAtPath:result],
|
||||
@"App bundle must be unzipped at %@", result);
|
||||
return result;
|
||||
}
|
||||
|
||||
- (NSString *)notarizedArchivesPath {
|
||||
NSString *resourePath = [[NSBundle mainBundle] resourcePath];
|
||||
NSString *notarizedArchivesPath =
|
||||
[resourePath stringByAppendingPathComponent:@"NotarizedArchives"];
|
||||
return notarizedArchivesPath;
|
||||
}
|
||||
|
||||
- (NSString *)notarizedArchive {
|
||||
NSString *bundleVersion =
|
||||
[[[NSBundle mainBundle] infoDictionary] objectForKey:(id)kCFBundleVersionKey];
|
||||
NSString *notarizedArchiveBasename =
|
||||
[NSString stringWithFormat:@"%@-r%@.zip", _appName, bundleVersion];
|
||||
NSString *notarizedArchive =
|
||||
[[self notarizedArchivesPath] stringByAppendingPathComponent:notarizedArchiveBasename];
|
||||
return notarizedArchive;
|
||||
}
|
||||
@end
|
|
@ -22,6 +22,8 @@
|
|||
<string>MBIN</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>805</string>
|
||||
<key>LSApplicationCategoryType</key>
|
||||
<string>public.app-category.utilities</string>
|
||||
<key>LSHasLocalizedDisplayName</key>
|
||||
<true/>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
Place the notarized archive here for producing the release installer.
|
Loading…
Reference in New Issue