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 +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: []
|