wtapack 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/ext/wtapack/CodeSigner.h +14 -0
- data/ext/wtapack/CodeSigner.m +197 -0
- data/ext/wtapack/ErrorHandler.h +19 -0
- data/ext/wtapack/ErrorHandler.m +53 -0
- data/ext/wtapack/NSArray+WTAMap.h +15 -0
- data/ext/wtapack/NSArray+WTAMap.m +24 -0
- data/ext/wtapack/ProfileManager.h +18 -0
- data/ext/wtapack/ProfileManager.m +151 -0
- data/ext/wtapack/extconf.rb +8 -0
- data/ext/wtapack/main.h +7 -0
- data/ext/wtapack/main.m +250 -0
- data/ext/wtapack/updateBundleID.h +11 -0
- data/ext/wtapack/updateBundleID.m +54 -0
- data/ext/wtapack/wtapack.c +32 -0
- data/lib/wtapack.rb +33 -0
- metadata +74 -0
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)
|
data/ext/wtapack/main.h
ADDED
data/ext/wtapack/main.m
ADDED
@@ -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,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: []
|