wtapack 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 3dc6a0f4020613af0a67a7cc7276e689b595fe57
4
+ data.tar.gz: 662ebe71614a404c1893add26c317bce10dbb7e5
5
+ SHA512:
6
+ metadata.gz: e72e8366278bf4bf09a64fc7f0a9790681ff2fa65a7e30757d6e72326503c4beb9ccccd6b123e68002f15aaa2694f531717d7582b354774ec2ad0624c61476ea
7
+ data.tar.gz: da4c0a2f0fcdd50bf9ece15e093cd64fb6232c7abf224597148c24adc632bb77b371a24052acfe6b592b55e3b11b9c21d8fdeee1ae34c7a1953312dc4b5a1c12
@@ -0,0 +1,14 @@
1
+ //
2
+ // CodeSigner.h
3
+ // WTAPackageApplication
4
+ //
5
+ // Created by Robert Thompson on 10/13/14.
6
+ // Copyright (c) 2014 WillowTree Apps. All rights reserved.
7
+ //
8
+
9
+ @import Foundation;
10
+
11
+ @interface CodeSigner : NSObject
12
+ + (void)signBinary:(NSURL*)binaryURL;
13
+ + (void)signFramework:(NSURL*)frameworkURL;
14
+ @end
@@ -0,0 +1,197 @@
1
+ //
2
+ // CodeSigner.m
3
+ // WTAPackageApplication
4
+ //
5
+ // Created by Robert Thompson on 10/13/14.
6
+ // Copyright (c) 2014 WillowTree Apps. All rights reserved.
7
+ //
8
+
9
+ #import "CodeSigner.h"
10
+ #import <objc/runtime.h>
11
+ #import "ErrorHandler.h"
12
+
13
+ void validateSigningIdentity(NSString* identity);
14
+
15
+ @implementation CodeSigner
16
+ + (NSString*)signingIdentity
17
+ {
18
+ NSString* result = objc_getAssociatedObject(self, @selector(signingIdentity));
19
+ if (!result)
20
+ {
21
+ result = [[NSUserDefaults standardUserDefaults] stringForKey:@"sign"];
22
+ //validateSigningIdentity(result); /* Rakefile does this */
23
+ objc_setAssociatedObject(self, @selector(signingIdentity), result, OBJC_ASSOCIATION_COPY_NONATOMIC);
24
+ }
25
+
26
+ return result;
27
+ }
28
+
29
+ + (NSString*)codesignFormatString
30
+ {
31
+ NSString* result = objc_getAssociatedObject(self, @selector(codesignFormatString));
32
+ if (!result)
33
+ {
34
+ NSString* signingIdentity = [self signingIdentity];
35
+ result = [NSString stringWithFormat:@"/usr/bin/codesign --force --preserve-metadata=identifier,entitlements,resource-rules --sign %@ --resource-rules=\"%%@\" --entitlements \"%%@\" \"%%@\"", signingIdentity];
36
+
37
+ objc_setAssociatedObject(self, @selector(codesignFormatString), result, OBJC_ASSOCIATION_COPY_NONATOMIC);
38
+ }
39
+
40
+ return result;
41
+ }
42
+
43
+ + (void)signBinary:(NSURL *)binaryURL
44
+ {
45
+ NSString* codesign = [NSString stringWithFormat:[self codesignFormatString], [binaryURL URLByAppendingPathComponent:@"ResourceRules.plist"].path, [binaryURL URLByAppendingPathComponent:@"Entitlements.plist"].path, binaryURL.path];
46
+
47
+ printf("%s\n", codesign.UTF8String);
48
+
49
+ [self codesign:codesign failureMessage:@"Codesigning Failed!"];
50
+ }
51
+
52
+ + (void)signFramework:(NSURL *)frameworkURL
53
+ {
54
+ NSString* codesign = [NSString stringWithFormat:@"/usr/bin/codesign --force -v --sign \"%@\" \"%@\"",
55
+ [self signingIdentity],
56
+ frameworkURL.path];
57
+
58
+ printf("%s\n", codesign.UTF8String);
59
+
60
+ [self codesign:codesign failureMessage:@"Framework Codesigning Failed!"];
61
+ }
62
+
63
+ + (void)codesign:(NSString*)codesignString failureMessage:(NSString*)message
64
+ {
65
+ // Lots of nonsense here so we can capture the output of the codesign command to display as
66
+ // we please instead of as it pleases.
67
+
68
+ const char* template = "/tmp/codesignOut.XXXXXXX";
69
+ size_t length = strlen(template);
70
+ char* temp = calloc(1, length);
71
+ strncpy(temp, template, length);
72
+
73
+ int fileno = mkstemp(temp);
74
+ int retVal = system([codesignString stringByAppendingString:[NSString stringWithFormat:@">& %s", temp]].UTF8String);
75
+
76
+ close(fileno);
77
+ NSString* tempFilePath = [NSString stringWithUTF8String:temp];
78
+ NSString* codesignOuput = [[NSString alloc] initWithContentsOfFile:tempFilePath
79
+ encoding:NSUTF8StringEncoding
80
+ error:NULL];
81
+ [[NSFileManager defaultManager] removeItemAtPath:tempFilePath
82
+ error:NULL];
83
+ free(temp);
84
+
85
+ if (retVal != 0)
86
+ {
87
+ [ErrorHandler fatalErrorWithMessage:[NSString stringWithFormat:@"%@\ncodesign output: %@",
88
+ message,
89
+ codesignOuput]
90
+ exitCode:retVal];
91
+ }
92
+ else
93
+ {
94
+ fprintf(stdout, "%s", codesignOuput.UTF8String);
95
+ }
96
+ }
97
+ @end
98
+
99
+ // It turned out the Rakefile already does the below, so no point in doing it twice.
100
+
101
+ // So the lesson of this function is that the keychain API is very confusing and difficult
102
+ // to work with. This function loops through all the certificates in the keychain, and checks the
103
+ // SHA1 digest (which has to be calculated, it cannot be gotten from the keychain API directly)
104
+ // against the one passed in. If it finds a match, it verifies that the certificate is trusted.
105
+ //void validateSigningIdentity(NSString* identity)
106
+ //{
107
+ // NSDictionary* query = @{ (__bridge id)kSecClass : (__bridge id)kSecClassCertificate,
108
+ // (__bridge id)kSecReturnRef : (__bridge id)kCFBooleanTrue,
109
+ // (__bridge id)kSecMatchLimit : (__bridge id)kSecMatchLimitAll };
110
+ //
111
+ // CFTypeRef result = NULL;
112
+ // OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)(query), &result);
113
+ // if (status)
114
+ // {
115
+ // CFStringRef errorString = SecCopyErrorMessageString(status, 0);
116
+ // NSLog(@"status is %@, result is %@", errorString, (__bridge id)result);
117
+ // CFRelease(errorString);
118
+ // [ErrorHandler fatalErrorWithMessage:@"Could not find signing identity"
119
+ // exitCode:status];
120
+ // }
121
+ //
122
+ // BOOL certificateFound = NO;
123
+ //
124
+ // for (id certificate in (__bridge NSArray*)result)
125
+ // {
126
+ // CFErrorRef error = NULL;
127
+ // SecCertificateRef certificateRef = (__bridge SecCertificateRef)certificate;
128
+ // SecTransformRef transformRef = SecDigestTransformCreate(kSecDigestSHA1, 0, &error);
129
+ // CFDataRef certData = SecCertificateCopyData(certificateRef);
130
+ // SecTransformSetAttribute(transformRef, kSecTransformInputAttributeName, certData, &error);
131
+ // if (error)
132
+ // {
133
+ // NSLog(@"%@", (__bridge NSError*)error);
134
+ // }
135
+ // CFDataRef output = SecTransformExecute(transformRef, &error);
136
+ // if (error)
137
+ // {
138
+ // NSLog(@"%@", (__bridge NSError*)error);
139
+ // }
140
+ // CFRelease(certData);
141
+ //
142
+ // NSData* outputData = (__bridge NSData*)output;
143
+ //
144
+ // NSCharacterSet* greaterLessSet = [NSCharacterSet characterSetWithCharactersInString:@"<>"];
145
+ // NSString* description = [[outputData.description stringByTrimmingCharactersInSet:greaterLessSet] stringByReplacingOccurrencesOfString:@" "
146
+ // withString:@""];
147
+ // CFRelease(output);
148
+ // CFRelease(transformRef);
149
+ // if(error)
150
+ // {
151
+ // CFRelease(error);
152
+ // }
153
+ //
154
+ // if ([description compare:identity options:NSCaseInsensitiveSearch] == NSOrderedSame)
155
+ // {
156
+ // SecTrustRef trustRef = NULL;
157
+ // SecPolicyRef policyRef = SecPolicyCreateBasicX509();
158
+ // status = SecTrustCreateWithCertificates(certificateRef, policyRef, &trustRef);
159
+ // if (status)
160
+ // {
161
+ // CFStringRef errorString = SecCopyErrorMessageString(status, 0);
162
+ // NSLog(@"%@", errorString);
163
+ // CFRelease(errorString);
164
+ // }
165
+ // SecTrustResultType trustResult;
166
+ // status = SecTrustEvaluate(trustRef, &trustResult);
167
+ // if (status)
168
+ // {
169
+ // CFStringRef errorString = SecCopyErrorMessageString(status, 0);
170
+ // NSLog(@"%@", errorString);
171
+ // CFRelease(errorString);
172
+ // }
173
+ //
174
+ // if ((trustResult != kSecTrustResultDeny) &&
175
+ // (trustResult != kSecTrustResultFatalTrustFailure) &&
176
+ // (trustResult != kSecTrustResultInvalid) &&
177
+ // (trustResult != kSecTrustResultOtherError) &&
178
+ // (trustResult != kSecTrustResultRecoverableTrustFailure))
179
+ // {
180
+ // certificateFound = YES;
181
+ // }
182
+ // CFRelease(certificateRef);
183
+ // CFRelease(trustRef);
184
+ // CFRelease(policyRef);
185
+ // break;
186
+ // }
187
+ //
188
+ // CFRelease(certificateRef);
189
+ //
190
+ // }
191
+ //
192
+ // if (!certificateFound)
193
+ // {
194
+ // [ErrorHandler fatalErrorWithMessage:[NSString stringWithFormat:@"No valid certificate with SHA1 %@ found", identity]
195
+ // exitCode:EX_DATAERR];
196
+ // }
197
+ //}
@@ -0,0 +1,19 @@
1
+ //
2
+ // ErrorHandler.h
3
+ // WTAPackageApplication
4
+ //
5
+ // Created by Robert Thompson on 10/13/14.
6
+ // Copyright (c) 2014 WillowTree Apps. All rights reserved.
7
+ //
8
+
9
+ @import Foundation;
10
+ #import <sysexits.h>
11
+
12
+ @interface ErrorHandler : NSObject
13
+ // Used for cleaning up after ourselves in the event of an error
14
+ + (void)setTemporaryDirectory:(NSURL*)tempDir;
15
+
16
+ // Display an error meesage to stderr then exit cleanly.
17
+ + (void)fatalErrorWithMessage:(NSString*)message exitCode:(int)code;
18
+ + (void)fatalErrorWithError:(NSError*)error additionalMessage:(NSString*)message;
19
+ @end
@@ -0,0 +1,53 @@
1
+ //
2
+ // ErrorHandler.m
3
+ // WTAPackageApplication
4
+ //
5
+ // Created by Robert Thompson on 10/13/14.
6
+ // Copyright (c) 2014 WillowTree Apps. All rights reserved.
7
+ //
8
+
9
+ #import "ErrorHandler.h"
10
+ #import <objc/runtime.h>
11
+
12
+ @implementation ErrorHandler
13
+ + (NSURL*)temporaryDirectory
14
+ {
15
+ return objc_getAssociatedObject(self, @selector(temporaryDirectory));
16
+ }
17
+
18
+ + (void)setTemporaryDirectory:(NSURL *)tempDir
19
+ {
20
+ NSURL* result = [self temporaryDirectory];
21
+ if ([result isEqual:tempDir])
22
+ {
23
+ return;
24
+ }
25
+
26
+ objc_setAssociatedObject(self, @selector(temporaryDirectory), tempDir, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
27
+ }
28
+
29
+ + (void)fatalErrorWithMessage:(NSString *)message exitCode:(int)code
30
+ {
31
+ fprintf(stderr, "%s\n", message.UTF8String);
32
+
33
+ [self cleanupAndExitWithCode:code];
34
+ }
35
+
36
+ + (void)fatalErrorWithError:(NSError *)error additionalMessage:(NSString *)message
37
+ {
38
+ fprintf(stderr, "%s with error: %s", message.UTF8String, [NSString stringWithFormat:@"%@", error].UTF8String);
39
+ [self cleanupAndExitWithCode:error.code];
40
+ }
41
+
42
+ + (void)cleanupAndExitWithCode:(NSInteger)code
43
+ {
44
+ NSURL* tempDir = [self temporaryDirectory];
45
+ if (tempDir)
46
+ {
47
+ [[NSFileManager defaultManager] removeItemAtURL:tempDir
48
+ error:NULL];
49
+ }
50
+
51
+ exit((int)code);
52
+ }
53
+ @end
@@ -0,0 +1,15 @@
1
+ //
2
+ // NSArray+WTAMap.h
3
+ // WTAPackageApplication
4
+ //
5
+ // Created by Robert Thompson on 10/13/14.
6
+ // Copyright (c) 2014 WillowTree Apps. All rights reserved.
7
+ //
8
+
9
+ @import Foundation;
10
+
11
+ typedef id (^arrayObjectFunc)(id obj);
12
+
13
+ @interface NSArray (WTAMap)
14
+ - (NSArray*)wta_map:(arrayObjectFunc)func;
15
+ @end
@@ -0,0 +1,24 @@
1
+ //
2
+ // NSArray+WTAMap.m
3
+ // WTAPackageApplication
4
+ //
5
+ // Created by Robert Thompson on 10/13/14.
6
+ // Copyright (c) 2014 WillowTree Apps. All rights reserved.
7
+ //
8
+
9
+ #import "NSArray+WTAMap.h"
10
+
11
+ @implementation NSArray (WTAMap)
12
+ - (NSArray *)wta_map:(arrayObjectFunc)func
13
+ {
14
+ NSMutableArray* newArray = [NSMutableArray arrayWithCapacity:self.count];
15
+
16
+ dispatch_apply(self.count, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
17
+ ^(size_t i) {
18
+ id obj = self[i];
19
+ [newArray addObject:func(obj)];
20
+ });
21
+
22
+ return [NSArray arrayWithArray:newArray];
23
+ }
24
+ @end
@@ -0,0 +1,18 @@
1
+ //
2
+ // ProfileManager.h
3
+ // WTAPackageApplication
4
+ //
5
+ // Created by Robert Thompson on 10/13/14.
6
+ // Copyright (c) 2014 WillowTree Apps. All rights reserved.
7
+ //
8
+
9
+ @import Foundation;
10
+
11
+ @interface ProfileManager : NSObject
12
+ - (instancetype)initWithKey:(NSString*)profileUUIDKey bundleURL:(NSURL*)bundleURL;
13
+
14
+ @property (strong, nonatomic, readonly) NSDictionary* provisioningProfile;
15
+
16
+ - (void)replaceProfile;
17
+ - (void)replaceEntitlements;
18
+ @end
@@ -0,0 +1,151 @@
1
+ //
2
+ // ProfileManager.m
3
+ // WTAPackageApplication
4
+ //
5
+ // Created by Robert Thompson on 10/13/14.
6
+ // Copyright (c) 2014 WillowTree Apps. All rights reserved.
7
+ //
8
+
9
+ #import "ProfileManager.h"
10
+ #import "ErrorHandler.h"
11
+ @import Security;
12
+
13
+ @interface ProfileManager ()
14
+ {
15
+ __strong NSDictionary* _provisioningProfile;
16
+ }
17
+ @property (strong, nonatomic) NSURL* profileURL;
18
+ @property (strong, nonatomic) NSURL* bundleURL;
19
+ @end
20
+
21
+ @implementation ProfileManager
22
+ #pragma mark Initializers
23
+ - (instancetype)initWithKey:(NSString *)profileUUIDKey bundleURL:(NSURL *)bundleURL
24
+ {
25
+ self = [super init];
26
+ if (self)
27
+ {
28
+ NSString* profileUUID = [[NSUserDefaults standardUserDefaults] stringForKey:profileUUIDKey];
29
+ if (!profileUUID)
30
+ {
31
+ NSString* errorString = [NSString stringWithFormat:@"You must specify a profile for key \"%@\".",
32
+ profileUUIDKey];
33
+ [ErrorHandler fatalErrorWithMessage:errorString
34
+ exitCode:EX_USAGE];
35
+ }
36
+
37
+ NSString* provisioningProfilePath = [[NSString stringWithFormat:@"~/Library/MobileDevice/Provisioning Profiles/%@.mobileprovision",
38
+ profileUUID] stringByExpandingTildeInPath];
39
+
40
+ if ([[NSFileManager defaultManager] fileExistsAtPath:provisioningProfilePath])
41
+ {
42
+ _profileURL = [NSURL fileURLWithPath:provisioningProfilePath];
43
+ }
44
+ else
45
+ {
46
+ NSString* errorString = [NSString stringWithFormat:@"Specifed provisioning profile %@ does not exist",
47
+ profileUUID];
48
+
49
+ [ErrorHandler fatalErrorWithMessage:errorString
50
+ exitCode:EX_NOINPUT];
51
+ }
52
+
53
+ _bundleURL = bundleURL;
54
+ }
55
+
56
+ return self;
57
+ }
58
+
59
+ #pragma mark Instance Methods
60
+ - (void)replaceProfile
61
+ {
62
+ NSFileManager* fm = [NSFileManager defaultManager];
63
+
64
+ NSError* error = nil;
65
+
66
+ NSURL* destinationPPURL = [self.bundleURL URLByAppendingPathComponent:@"embedded.mobileprovision"];
67
+ // Delete the old one (we don't care if this fails)
68
+ [fm removeItemAtURL:destinationPPURL
69
+ error:NULL];
70
+
71
+ // Copy in the new one
72
+ BOOL success = [fm copyItemAtURL:self.profileURL
73
+ toURL:destinationPPURL
74
+ error:&error];
75
+
76
+ if (!success)
77
+ {
78
+ [ErrorHandler fatalErrorWithError:error
79
+ additionalMessage:@"Failed to copy new profile into place."];
80
+ }
81
+ }
82
+
83
+ - (void)replaceEntitlements
84
+ {
85
+ NSMutableDictionary* entitlementsDict = self.provisioningProfile[@"Entitlements"];
86
+
87
+ entitlementsDict[@"get-task-allow"] = @NO;
88
+
89
+ NSError* error = nil;
90
+ NSData* entitlementsData = [NSPropertyListSerialization dataWithPropertyList:entitlementsDict
91
+ format:NSPropertyListXMLFormat_v1_0
92
+ options:0
93
+ error:&error];
94
+ if (error)
95
+ {
96
+ [ErrorHandler fatalErrorWithError:error
97
+ additionalMessage:@"Failed to serialize entitlements plist"];
98
+ }
99
+
100
+ BOOL success = [entitlementsData writeToURL:[self.bundleURL URLByAppendingPathComponent:@"Entitlements.plist"]
101
+ atomically:YES];
102
+ if (!success)
103
+ {
104
+ NSString* errorString = [NSString stringWithFormat:@"Failed to write Entitlements.plist to %@",
105
+ self.bundleURL.path];
106
+ [ErrorHandler fatalErrorWithMessage:errorString
107
+ exitCode:EX_CANTCREAT];
108
+ }
109
+ }
110
+
111
+ #pragma mark Getters/Setters
112
+ - (NSDictionary*)provisioningProfile
113
+ {
114
+ if (!_provisioningProfile)
115
+ {
116
+ _provisioningProfile = [self.class getProvisioningProfile:self.profileURL.path];
117
+ }
118
+
119
+ return _provisioningProfile;
120
+ }
121
+
122
+ #pragma mark Utility Methods
123
+ + (NSDictionary *)getProvisioningProfile:(NSString *)profilePath
124
+ {
125
+ CMSDecoderRef decoder;
126
+ CMSDecoderCreate(&decoder);
127
+ NSData* encodedData = [NSData dataWithContentsOfFile:profilePath];
128
+ CMSDecoderUpdateMessage(decoder, encodedData.bytes, encodedData.length);
129
+ CMSDecoderFinalizeMessage(decoder);
130
+
131
+ CFDataRef decodedDataRef;
132
+ CMSDecoderCopyContent(decoder, &decodedDataRef);
133
+
134
+ NSPropertyListFormat format;
135
+ NSError* error = nil;
136
+ NSDictionary* profile = [NSPropertyListSerialization propertyListWithData:(__bridge NSData*)decodedDataRef
137
+ options:NSPropertyListMutableContainersAndLeaves
138
+ format:&format
139
+ error:&error];
140
+ CFRelease(decoder);
141
+ CFRelease(decodedDataRef);
142
+
143
+ if (error)
144
+ {
145
+ [ErrorHandler fatalErrorWithError:error
146
+ additionalMessage:@"Failed to deserialize provisioning profile."];
147
+ }
148
+
149
+ return profile;
150
+ }
151
+ @end
@@ -0,0 +1,8 @@
1
+ require 'mkmf'
2
+ extension_name = 'wtapack'
3
+ dir_config(extension_name)
4
+ MODULE_CACHE = "#{ENV['HOME']}/Library/Developer/Xcode/DerivedData/ModuleCache"
5
+ ISYSROOT_PATH = `xcrun -sdk macosx10.9 --show-sdk-path`
6
+ $CFLAGS = "-fmessage-length=0 -fdiagnostics-show-note-include-stack -fmacro-backtrace-limit=0 -std=gnu99 -fobjc-arc -fmodules -fmodules-cache-path=#{MODULE_CACHE} -fmodules-prune-interval=86400 -fmodules-prune-after=345600 -isysroot #{ISYSROOT_PATH} -mmacosx-version-min=10.9 -fasm-blocks -fstrict-aliasing -Os -march=native -flto -Wall -Wpedantic -Werror"
7
+ $LFLAGS = "-isysroot $(ISYSROOT_PATH) -mmacosx-version-min=10.9 -flto -framework Foundation"
8
+ create_makefile(extension_name)
@@ -0,0 +1,7 @@
1
+ #ifndef MAIN_H
2
+ #define MAIN_H
3
+
4
+ int rb_main(int argc, char** argv);
5
+
6
+ #endif
7
+
@@ -0,0 +1,250 @@
1
+ //
2
+ // main.m
3
+ // WTAPackageApplication
4
+ //
5
+ // Created by Robert Thompson on 10/10/14.
6
+ // Copyright (c) 2014 WillowTree Apps. All rights reserved.
7
+ //
8
+
9
+ @import Foundation;
10
+ #import "ProfileManager.h"
11
+ #import "CodeSigner.h"
12
+ #import "updateBundleID.h"
13
+ #import "NSArray+WTAMap.h"
14
+ #import "ErrorHandler.h"
15
+ #import <ruby.h>
16
+
17
+ static NSString* const usageString = @"Usage: %s -input <path to original .app> \
18
+ -profile <provisioning profile UUID> -sign <SHA1 for valid certificate> \
19
+ [-bundleID <new bundleID>] [-extensions <comma-separated list of extensions>] \
20
+ [-<extension name> <extension provisioning profile UUID> ...] -output <path to output ipa>";
21
+
22
+ void showUsageAndExit(void);
23
+
24
+ VALUE rb_main(int argc, const char * argv[]) {
25
+ @autoreleasepool {
26
+
27
+ NSMutableArray* arguments = [NSMutableArray arrayWithCapacity: argc];
28
+ for (int i = 0; i < argc; i++)
29
+ {
30
+ NSString* string = [[NSString stringWithUTF8String:argv[i]] stringByReplacingOccurrencesOfString:@"\"" withString:@""];
31
+ NSCharacterSet* comma = [NSCharacterSet characterSetWithCharactersInString:@","];
32
+ string = [string stringByTrimmingCharactersInSet:comma];
33
+ [arguments addObject:string];
34
+ }
35
+
36
+ for (NSString* argument in arguments)
37
+ {
38
+ if ([argument isEqualToString:@"-help"])
39
+ {
40
+ showUsageAndExit();
41
+ }
42
+ }
43
+
44
+ NSMutableDictionary* registrationDict = [NSMutableDictionary dictionary];
45
+ for (int i = 1; i < arguments.count; i+=2)
46
+ {
47
+ NSString* key = [arguments[i] substringFromIndex:1];
48
+ registrationDict[key] = arguments[i+1];
49
+ }
50
+
51
+ NSLog(@"%@", registrationDict);
52
+ NSFileManager* fm = [NSFileManager defaultManager];
53
+ NSUserDefaults* standardDefaults = [NSUserDefaults standardUserDefaults];
54
+ [standardDefaults registerDefaults: registrationDict];
55
+
56
+ NSError* error = nil;
57
+
58
+ // Read in the input directory, bail if it doesn't exist
59
+ NSString* origAppName = [standardDefaults stringForKey:@"input"];
60
+ if (!origAppName)
61
+ {
62
+ showUsageAndExit();
63
+ }
64
+
65
+ // Check to see if output key exists
66
+ NSString* outputPathString = [standardDefaults valueForKey:@"output"];
67
+ if (!outputPathString)
68
+ {
69
+ showUsageAndExit();
70
+ }
71
+
72
+ NSString* newBundleID = [standardDefaults stringForKey:@"bundleID"];
73
+
74
+ // Create a temporary location to do our work
75
+ NSURL* tmpDirURL = [fm URLForDirectory:NSItemReplacementDirectory
76
+ inDomain:NSUserDomainMask
77
+ appropriateForURL:[NSURL fileURLWithPath:@"PackageApplicationDir" isDirectory:YES]
78
+ create:YES
79
+ error:&error];
80
+ if (error)
81
+ {
82
+ [ErrorHandler fatalErrorWithMessage:@"Failed to create temporary Directory!"
83
+ exitCode:EX_CANTCREAT];
84
+ }
85
+
86
+ // Set up our error handler so we don't leave files laying around in the case of an error
87
+ [ErrorHandler setTemporaryDirectory:tmpDirURL];
88
+
89
+ NSString* appName = [origAppName lastPathComponent];
90
+ NSURL* destAppDirURL = [tmpDirURL URLByAppendingPathComponent:@"Payload" isDirectory:YES];
91
+ NSURL* destApp = [destAppDirURL URLByAppendingPathComponent:appName isDirectory:YES];
92
+
93
+ // Make sure the app is actually there and display an error message if it isn't
94
+ BOOL origAppIsDirectory = NO;
95
+ BOOL origAppExists = [fm fileExistsAtPath:origAppName isDirectory:&origAppIsDirectory];
96
+ if (!origAppExists || !origAppIsDirectory)
97
+ {
98
+ [ErrorHandler fatalErrorWithMessage:[NSString stringWithFormat:@"%@ does not exist!\n", origAppName]
99
+ exitCode:EX_NOINPUT];
100
+ }
101
+
102
+ NSURL* origAppURL = [NSURL fileURLWithPath:origAppName isDirectory:YES];
103
+
104
+ // Create destination directory
105
+ [fm createDirectoryAtURL:destAppDirURL
106
+ withIntermediateDirectories:YES
107
+ attributes:nil
108
+ error:&error];
109
+ if (error)
110
+ {
111
+ [ErrorHandler fatalErrorWithError:error
112
+ additionalMessage:@"Failed to create destination Payload directory."];
113
+ }
114
+
115
+ // Copy original app to work directory
116
+ [fm copyItemAtURL:origAppURL
117
+ toURL:destApp
118
+ error:&error];
119
+
120
+ if (error)
121
+ {
122
+ [ErrorHandler fatalErrorWithError:error
123
+ additionalMessage:@"Failed to copy original app to work directory."];
124
+ }
125
+
126
+ // Set up the object that handles provisioning profiles and the entitlements contained
127
+ // therein.
128
+ ProfileManager* mainProfileManager = [[ProfileManager alloc] initWithKey:@"profile"
129
+ bundleURL:destApp];
130
+ if (newBundleID)
131
+ {
132
+ // Sometimes we may not be changing the bundle ID
133
+ updateBundleID(destApp, newBundleID);
134
+ }
135
+
136
+ [mainProfileManager replaceEntitlements];
137
+
138
+ [mainProfileManager replaceProfile];
139
+
140
+ // Extensions handling starts here
141
+ NSString* extensionsString = [standardDefaults stringForKey:@"extensions"];
142
+ NSArray* extensions = [[extensionsString componentsSeparatedByString:@","] wta_map: ^(NSString* string) {
143
+ return [string stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
144
+ }];
145
+
146
+ NSURL* pluginPath = [destApp URLByAppendingPathComponent:@"PlugIns"];
147
+
148
+ dispatch_apply(extensions.count,
149
+ dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,
150
+ 0),
151
+ ^(size_t i) {
152
+ NSString* extension = extensions[i];
153
+ NSString* extensionName = [NSString stringWithFormat:@"%@.appex", extension];
154
+ NSURL* extensionURL = [pluginPath URLByAppendingPathComponent:extensionName];
155
+
156
+ ProfileManager* extensionProfileManager = [[ProfileManager alloc] initWithKey:extension
157
+ bundleURL:extensionURL];
158
+ if (newBundleID)
159
+ {
160
+ NSString* extensionBundleName = [extension stringByReplacingOccurrencesOfString:@" " withString:@"-"];
161
+ NSString* newExtensionBundleID = [newBundleID stringByAppendingString:[NSString stringWithFormat:@".%@", extensionBundleName]];
162
+
163
+ updateBundleID(extensionURL, newExtensionBundleID);
164
+ }
165
+
166
+ [extensionProfileManager replaceEntitlements];
167
+
168
+ [extensionProfileManager replaceProfile];
169
+
170
+ // Code signing
171
+ [CodeSigner signBinary:extensionURL];
172
+ });
173
+
174
+ // Code signing of frameworks. We don't care if this call fails, because many apps don't
175
+ // even have Framewoks.
176
+ NSArray* frameworks = [fm contentsOfDirectoryAtURL:[destApp URLByAppendingPathComponent:@"Frameworks"]
177
+ includingPropertiesForKeys:nil
178
+ options:NSDirectoryEnumerationSkipsHiddenFiles
179
+ error:NULL];
180
+
181
+ dispatch_apply(frameworks.count,
182
+ dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,
183
+ 0),
184
+ ^(size_t i) {
185
+ [CodeSigner signFramework:frameworks[i]];
186
+ });
187
+
188
+ // Finally sign whole app.
189
+ [CodeSigner signBinary:destApp];
190
+
191
+ // Remove whatever is at the output path, if it exists
192
+ // (or else the zip command will just append)
193
+ [fm removeItemAtPath:outputPathString
194
+ error:NULL];
195
+
196
+ NSString* currentWorkingDirectory = [fm currentDirectoryPath];
197
+ [fm changeCurrentDirectoryPath:tmpDirURL.path];
198
+ // Create the final ipa
199
+ int retVal = system([NSString stringWithFormat:@"/usr/bin/zip --symlinks --recurse-paths --verbose -9 \"%@\" .",
200
+ outputPathString].UTF8String);
201
+ if (retVal != 0)
202
+ {
203
+ [ErrorHandler fatalErrorWithMessage:[NSString stringWithFormat:@"Failed to create ipa at %@",
204
+ outputPathString]
205
+ exitCode:retVal];
206
+ }
207
+
208
+ // Clean up after ourselves
209
+ [fm changeCurrentDirectoryPath:currentWorkingDirectory];
210
+ [fm removeItemAtURL:tmpDirURL error:NULL];
211
+ }
212
+ return rb_int2inum(0);
213
+ }
214
+
215
+ void showUsageAndExit(void)
216
+ {
217
+ [ErrorHandler fatalErrorWithMessage:[NSString stringWithFormat:usageString, [[NSProcessInfo processInfo].arguments.firstObject UTF8String]]
218
+ exitCode:EX_USAGE];
219
+ }
220
+ // This is left here for historical interest, in case anyone wants to know how to do this.
221
+ //NSMutableDictionary* readRawEntitlements(NSURL* rawEntitlementsPath)
222
+ //{
223
+ // NSData* rawFileData = [[NSFileManager defaultManager] contentsAtPath:rawEntitlementsPath.path];
224
+ //
225
+ // uint32_t formatTag;
226
+ // [rawFileData getBytes:&formatTag length:4];
227
+ //
228
+ // if (CFSwapInt32(formatTag) != 0xfade7171)
229
+ // {
230
+ // exit(1);
231
+ // }
232
+ //
233
+ // int length;
234
+ // [rawFileData getBytes:&length range:NSMakeRange(4, 4)];
235
+ //
236
+ // length = CFSwapInt32(length);
237
+ //
238
+ // NSData* plistData = [rawFileData subdataWithRange:NSMakeRange(8, length - 8)];
239
+ // NSPropertyListFormat format;
240
+ // NSMutableDictionary* result = [NSPropertyListSerialization propertyListWithData:plistData
241
+ // options:NSPropertyListMutableContainersAndLeaves
242
+ // format:&format
243
+ // error:NULL];
244
+ //
245
+ // [[NSFileManager defaultManager] removeItemAtURL:rawEntitlementsPath
246
+ // error:NULL];
247
+ //
248
+ // return result;
249
+ //}
250
+
@@ -0,0 +1,11 @@
1
+ //
2
+ // updateBundleID.h
3
+ // WTAPackageApplication
4
+ //
5
+ // Created by Robert Thompson on 10/13/14.
6
+ // Copyright (c) 2014 WillowTree Apps. All rights reserved.
7
+ //
8
+
9
+ @import Foundation;
10
+
11
+ void updateBundleID(NSURL* appURL, NSString* newBundleID);
@@ -0,0 +1,54 @@
1
+ //
2
+ // updateBundleID.m
3
+ // WTAPackageApplication
4
+ //
5
+ // Created by Robert Thompson on 10/13/14.
6
+ // Copyright (c) 2014 WillowTree Apps. All rights reserved.
7
+ //
8
+
9
+ #import "updateBundleID.h"
10
+ #import "ErrorHandler.h"
11
+
12
+ void updateBundleID(NSURL* appURL, NSString* newBundleID)
13
+ {
14
+ NSFileManager* fm = [NSFileManager defaultManager];
15
+
16
+ NSPropertyListFormat format;
17
+ NSURL* infoPlistURL = [appURL URLByAppendingPathComponent:@"Info.plist"];
18
+ NSData* infoPlistData = [fm contentsAtPath:infoPlistURL.path];
19
+ NSError* error = nil;
20
+ NSMutableDictionary* infoPlist = [NSPropertyListSerialization propertyListWithData:infoPlistData
21
+ options:NSPropertyListMutableContainersAndLeaves
22
+ format:&format
23
+ error:&error];
24
+ if (error)
25
+ {
26
+ [ErrorHandler fatalErrorWithError:error
27
+ additionalMessage:[NSString stringWithFormat:@"Failed to deserialize Info.plist in %@",
28
+ appURL.path]];
29
+ }
30
+
31
+ infoPlist[@"CFBundleIdentifier"] = newBundleID;
32
+
33
+ infoPlistData = [NSPropertyListSerialization dataWithPropertyList:infoPlist
34
+ format:format
35
+ options:0
36
+ error:&error];
37
+ if (error)
38
+ {
39
+ [ErrorHandler fatalErrorWithError:error
40
+ additionalMessage:[NSString stringWithFormat:@"Failed to serialize new Info.plist for %@",
41
+ appURL.path]];
42
+ }
43
+
44
+ BOOL success = [infoPlistData writeToURL:infoPlistURL
45
+ atomically:YES];
46
+
47
+ if (!success)
48
+ {
49
+ NSString* errorString = [NSString stringWithFormat:@"Failed to write new Info.plist in %@",
50
+ appURL.path];
51
+ [ErrorHandler fatalErrorWithMessage:errorString
52
+ exitCode:EX_CANTCREAT];
53
+ }
54
+ }
@@ -0,0 +1,32 @@
1
+ #include <ruby.h>
2
+ #include "main.h"
3
+
4
+ static VALUE hello_world(VALUE mod)
5
+ {
6
+ return rb_str_new2("hello world");
7
+ }
8
+
9
+ static VALUE native_pack(VALUE mod, VALUE argc, VALUE argv)
10
+ {
11
+ struct RArray *argv_array = RARRAY(argv);
12
+
13
+ char** real_argv = ALLOC_N(char*, RARRAY_LEN(argv_array));
14
+ for (int i = 0; i < RARRAY_LEN(argv_array); i++)
15
+ {
16
+ struct RString* string = RSTRING(RARRAY_CONST_PTR(argv_array)[i]);
17
+ int length = RSTRING_LEN(string);
18
+ real_argv[i] = ALLOC_N(char, length + 1);
19
+ memcpy(real_argv[i], RSTRING_PTR(string), length);
20
+ real_argv[i][length] = '\0';
21
+ }
22
+
23
+ return rb_main(NUM2INT(argc), real_argv);
24
+ }
25
+
26
+ void Init_wtapack()
27
+ {
28
+ VALUE mWtapack = rb_define_module("Wtapack");
29
+ rb_define_singleton_method(mWtapack, "hello_world", hello_world, 0);
30
+ rb_define_singleton_method(mWtapack, "native_pack", native_pack, 2);
31
+ }
32
+
data/lib/wtapack.rb ADDED
@@ -0,0 +1,33 @@
1
+ require File.expand_path('../wtapack.bundle', __FILE__)
2
+
3
+ module Wtapack
4
+ class Packer
5
+ attr_accessor :profile, :extensions, :input, :sign, :bundleID, :extension_profiles, :output
6
+
7
+ def pack
8
+ argv = ["wtapack", "-profile", "#{self.profile}", "-sign", "#{self.sign}", "-input", "\"#{self.input}\"", "-output", "\"#{self.output}\"", "-bundleID", "#{self.bundleID}"]
9
+ unless self.extensions.nil?
10
+ extensions = ["-extensions"]
11
+ extension_list = ""
12
+ self.extensions.each do |ex|
13
+ extension_list = extension_list + "\"#{ex}\"" + ","
14
+ end
15
+ argv = argv + extensions + [extension_list]
16
+ end
17
+ unless self.extension_profiles.nil?
18
+ self.extension_profiles.each do |extension, profile|
19
+
20
+ argv = argv + ["\"-#{extension}\"", "#{profile}"]
21
+
22
+ end
23
+ end
24
+ if self.extensions.nil?
25
+ self.extensions = []
26
+ end
27
+
28
+ argc = argv.count
29
+
30
+ Wtapack::native_pack(argc, argv)
31
+ end
32
+ end
33
+ end
metadata ADDED
@@ -0,0 +1,74 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: wtapack
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Robert Thompson
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-10-15 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rake-compiler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ description: Will re-sign, re-bundle-identifier, and re-provision an iOS app, and
28
+ then package the result as an ipa.
29
+ email: robert.thompson@willowtreeapps.com
30
+ executables: []
31
+ extensions:
32
+ - ext/wtapack/extconf.rb
33
+ extra_rdoc_files: []
34
+ files:
35
+ - ext/wtapack/CodeSigner.h
36
+ - ext/wtapack/CodeSigner.m
37
+ - ext/wtapack/ErrorHandler.h
38
+ - ext/wtapack/ErrorHandler.m
39
+ - ext/wtapack/NSArray+WTAMap.h
40
+ - ext/wtapack/NSArray+WTAMap.m
41
+ - ext/wtapack/ProfileManager.h
42
+ - ext/wtapack/ProfileManager.m
43
+ - ext/wtapack/extconf.rb
44
+ - ext/wtapack/main.h
45
+ - ext/wtapack/main.m
46
+ - ext/wtapack/updateBundleID.h
47
+ - ext/wtapack/updateBundleID.m
48
+ - ext/wtapack/wtapack.c
49
+ - lib/wtapack.rb
50
+ homepage: https://github.com/willowtreeapps/wtapack
51
+ licenses:
52
+ - MIT
53
+ metadata: {}
54
+ post_install_message:
55
+ rdoc_options: []
56
+ require_paths:
57
+ - lib
58
+ required_ruby_version: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
63
+ required_rubygems_version: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
68
+ requirements: []
69
+ rubyforge_project:
70
+ rubygems_version: 2.4.2
71
+ signing_key:
72
+ specification_version: 4
73
+ summary: Codesigning and ipa packaging
74
+ test_files: []