Skip to content

Commit f7679c1

Browse files
committed
Added new action : 'put-resource-bundle'
1 parent fe9ff51 commit f7679c1

File tree

6 files changed

+179
-14
lines changed

6 files changed

+179
-14
lines changed

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,9 @@ Actions:
5858
* add-resources-bundle <bundle_path>
5959
Add a bundle to the project and in the `Copy Bundle Resources` build phase
6060
61+
* put-resource-bundle <source_file_absolute_path_to_read> [<dest_dir_path> (default=Resources, relative or absolute)>]
62+
Copy or rewrite a file, and then Add a bundle to target path in the project and in the `Copy Bundle Resources` build phase
63+
6164
* touch
6265
Rewrite the project file
6366
```

Sources/Xcproj.m

Lines changed: 97 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#import <objc/runtime.h>
1313
#import "XCDUndocumentedChecker.h"
1414
#import "XMLPlistDecoder.h"
15+
#import "Xcproj+FileUtils.h"
1516

1617
@implementation Xcproj
1718
{
@@ -203,25 +204,28 @@ - (void) application:(DDCliApplication *)app willParseOptions:(DDGetoptLongParse
203204
[optionsParser addOptionsFromTable:optionTable];
204205
}
205206

206-
- (void) setProject:(NSString *)projectName
207+
- (void) setProject:(NSString *)projectPath
207208
{
208209
[self.class initializeXcproj];
210+
211+
if (![PBXProject isProjectWrapperExtension:[projectPath pathExtension]])
212+
@throw [DDCliParseException parseExceptionWithReason:[NSString stringWithFormat:@"The project name %@ does not have a valid extension.", projectPath] exitCode:EX_USAGE];
209213

210-
if (![PBXProject isProjectWrapperExtension:[projectName pathExtension]])
211-
@throw [DDCliParseException parseExceptionWithReason:[NSString stringWithFormat:@"The project name %@ does not have a valid extension.", projectName] exitCode:EX_USAGE];
212-
213-
NSString *projectPath = projectName;
214-
if (![projectName isAbsolutePath])
215-
projectPath = [[[NSFileManager defaultManager] currentDirectoryPath] stringByAppendingPathComponent:projectName];
216-
214+
if (![projectPath isAbsolutePath]){
215+
projectPath = [[[NSFileManager defaultManager] currentDirectoryPath] stringByAppendingPathComponent:projectPath];
216+
}else{
217+
//change current path
218+
[[NSFileManager defaultManager] changeCurrentDirectoryPath:[[projectPath stringByStandardizingPath] stringByDeletingLastPathComponent]];
219+
}
220+
217221
if (![[NSFileManager defaultManager] fileExistsAtPath:projectPath])
218-
@throw [DDCliParseException parseExceptionWithReason:[NSString stringWithFormat:@"The project %@ does not exist in this directory.", projectName] exitCode:EX_NOINPUT];
219-
222+
@throw [DDCliParseException parseExceptionWithReason:[NSString stringWithFormat:@"The project %@ does not exist in this directory.", projectPath] exitCode:EX_NOINPUT];
223+
220224
[_project release];
221225
_project = [[PBXProject projectWithFile:projectPath] retain];
222226

223227
if (!_project)
224-
@throw [DDCliParseException parseExceptionWithReason:[NSString stringWithFormat:@"The '%@' project is corrupted.", projectName] exitCode:EX_DATAERR];
228+
@throw [DDCliParseException parseExceptionWithReason:[NSString stringWithFormat:@"The '%@' project is corrupted.", projectPath] exitCode:EX_DATAERR];
225229
}
226230

227231
- (void) setTarget:(NSString *)targetName
@@ -319,7 +323,7 @@ - (int) application:(DDCliApplication *)app runWithArguments:(NSArray *)argument
319323

320324
- (NSArray *) allowedActions
321325
{
322-
return [NSArray arrayWithObjects:@"list-targets", @"list-headers", @"read-build-setting", @"write-build-setting", @"add-xcconfig", @"add-resources-bundle", @"touch", nil];
326+
return [NSArray arrayWithObjects:@"list-targets", @"list-headers", @"read-build-setting", @"write-build-setting", @"add-xcconfig", @"add-resources-bundle", @"put-resource-bundle", @"touch", nil];
323327
}
324328

325329
- (void) printUsage:(int)exitCode
@@ -341,8 +345,10 @@ - (void) printUsage:(int)exitCode
341345
@" Assign a value to a build setting. If the build setting does not exist, it is added to the target\n\n"
342346
@" * add-xcconfig <xcconfig_path>\n"
343347
@" Add an xcconfig file to the project and base all configurations on it\n\n"
344-
@" * add-resources-bundle <bundle_path>\n"
345-
@" Add a bundle to the project and in the `Copy Bundle Resources` build phase\n\n"
348+
@" * add-resources-bundle <bundle_paths> ...\n"
349+
@" Add multiple bundles to default group in the project and in the `Copy Bundle Resources` build phase\n\n"
350+
@" * put-resource-bundle <source_file_absolute_path_to_read> [<dest_dir_path> (default=Resources, relative or absolute)>]\n"
351+
@" Copy or rewrite a file, and then Add a bundle to target path in the project and in the `Copy Bundle Resources` build phase\n\n"
346352
@" * touch\n"
347353
@" Rewrite the project file\n");
348354
exit(exitCode);
@@ -461,6 +467,83 @@ - (int) addResourcesBundle:(NSArray *)arguments
461467
return [self writeProject];
462468
}
463469

470+
- (int) putResourceBundle:(NSArray *)arguments
471+
{
472+
if(arguments.count<1 || arguments.count>2){
473+
ddprintf(@"Wrong arguments input. See usage\n");
474+
[self printUsage:EX_USAGE];
475+
}
476+
477+
//check source file valid
478+
NSString * sourceFilePath = [[arguments firstObject] stringByStandardizingPath];
479+
480+
if (![[NSFileManager defaultManager] fileExistsAtPath:sourceFilePath]){
481+
@throw [DDCliParseException parseExceptionWithReason:[NSString stringWithFormat:@"A source file %@ does not exists.", sourceFilePath] exitCode:EX_OSFILE];
482+
}
483+
if (![[NSFileManager defaultManager] isReadableFileAtPath:sourceFilePath]){
484+
@throw [DDCliParseException parseExceptionWithReason:[NSString stringWithFormat:@"A source file %@ can't read.", sourceFilePath] exitCode:EX_IOERR];
485+
}
486+
if (![sourceFilePath isAbsolutePath]){
487+
@throw [DDCliParseException parseExceptionWithReason:[NSString stringWithFormat:@"A path of source file %@ must be absolute path.", sourceFilePath] exitCode:EX_IOERR];
488+
}
489+
490+
NSString * projectTargetGroupName = _target.name;
491+
NSString * projectRootPath = [[NSFileManager defaultManager] currentDirectoryPath];
492+
NSString * targetPath = [projectRootPath stringByAppendingPathComponent:_target.name];
493+
NSString * destFilePath = [targetPath stringByAppendingPathComponent:@"Resources"];
494+
//set dest path
495+
if(arguments.count==2){
496+
destFilePath = [[arguments lastObject] stringByStandardizingPath];
497+
if(![destFilePath isAbsolutePath]){
498+
destFilePath = [projectRootPath stringByAppendingPathComponent:destFilePath];
499+
}
500+
501+
if(![[destFilePath stringByDeletingLastPathComponent] hasPrefix:projectRootPath]){
502+
@throw [DDCliParseException parseExceptionWithReason:[NSString stringWithFormat:@"A path of destination file '%@' must be locate inside of base path of project '%@'.", destFilePath, projectRootPath] exitCode:EX_IOERR];
503+
}
504+
}
505+
destFilePath = [destFilePath stringByAppendingPathComponent:[sourceFilePath lastPathComponent]];
506+
507+
//resolving relative path
508+
NSArray const * relativeDirs = [destFilePath pathComponents];
509+
relativeDirs = [relativeDirs subarrayWithRange:NSMakeRange(projectRootPath.pathComponents.count, relativeDirs.count-projectRootPath.pathComponents.count-1)];
510+
511+
if([[relativeDirs firstObject] isNotEqualTo:projectTargetGroupName]){
512+
ddprintf(@"[!] WARNING : A relative path of destination file '%@' is located outside of current TARGET path '%@'.\n", destFilePath, targetPath);
513+
}
514+
515+
@try {
516+
[[NSFileManager defaultManager] createDirectoryAtPath:destFilePath withIntermediateDirectories:YES attributes:nil error:NULL];
517+
518+
NSError *error = nil;
519+
if([self atomicCopyItemAtURL:[NSURL fileURLWithPath:sourceFilePath] toURL:[NSURL fileURLWithPath:destFilePath] error:&error]){
520+
[relativeDirs enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
521+
if(idx==0){
522+
[self addGroupNamed:obj beforeGroupNamed:_project.rootGroup.name];
523+
}else{
524+
[self addGroupNamed:obj inGroupNamed:relativeDirs[idx-1]];
525+
}
526+
527+
if([[relativeDirs lastObject] isEqualTo:obj]){
528+
id<PBXFileReference> bundleReference = [self addFileAtPath:destFilePath];
529+
[self addFileReference:bundleReference inGroupNamed:obj];
530+
[self addFileReference:bundleReference toBuildPhase:@"Resources"];
531+
}
532+
}];
533+
534+
}else{
535+
ddprintf(@"[!] Resource file copy failed.%@\n");
536+
}
537+
538+
}@catch (NSException * exc) {
539+
ddprintf(@"File I/O Exception : %@\n", exc.reason);
540+
return EX_IOERR;
541+
}
542+
543+
return [self writeProject];
544+
}
545+
546+
464547
- (int) touch:(NSArray *)arguments
465548
{
466549
return [self writeProject];

Xcproj+FileUtils.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
//
2+
// Created by BLACKGENE on 15. 5. 29..
3+
// Copyright (c) 2015 Cédric Luthi. All rights reserved.
4+
//
5+
6+
#import <Foundation/Foundation.h>
7+
#import "Xcproj.h"
8+
9+
@interface Xcproj (FileUtils)
10+
- (BOOL)atomicCopyItemAtURL:(NSURL *)sourceURL toURL:(NSURL *)destinationURL error:(NSError **)outError;
11+
@end

Xcproj+FileUtils.m

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
//
2+
// Created by BLACKGENE on 15. 5. 29..
3+
// Copyright (c) 2015 Cédric Luthi. All rights reserved.
4+
//
5+
6+
#import "Xcproj+FileUtils.h"
7+
8+
9+
@implementation Xcproj (FileUtils)
10+
11+
- (BOOL)atomicCopyItemAtURL:(NSURL *)sourceURL toURL:(NSURL *)destinationURL error:(NSError **)outError {
12+
NSFileManager *manager = [NSFileManager defaultManager];
13+
NSURL *tempDir = [manager URLForDirectory:NSItemReplacementDirectory inDomain:NSUserDomainMask appropriateForURL:destinationURL create:YES error:outError];
14+
if (!tempDir){
15+
return NO;
16+
}
17+
18+
NSURL *tempURL = [tempDir URLByAppendingPathComponent:[destinationURL lastPathComponent]];
19+
BOOL result = [manager copyItemAtURL:sourceURL toURL:tempURL error:outError];
20+
if (result){
21+
NSURL *resultingURL;
22+
result = [manager replaceItemAtURL:destinationURL
23+
withItemAtURL:tempURL
24+
backupItemName:nil
25+
options:NSFileManagerItemReplacementUsingNewMetadataOnly
26+
resultingItemURL:&resultingURL
27+
error:outError];
28+
29+
if (result){
30+
NSAssert([resultingURL.absoluteString isEqualToString:destinationURL.absoluteString],
31+
@"URL unexpectedly changed during replacement from:\n%@\nto:\n%@",
32+
destinationURL,
33+
resultingURL);
34+
}
35+
}
36+
37+
// Clean up
38+
NSError *error;
39+
if (![manager removeItemAtURL:tempDir error:&error]){
40+
NSLog(@"Failed to remove temp directory after atomic copy: %@", error);
41+
}
42+
return result;
43+
}
44+
45+
@end

xcproj

109 KB
Binary file not shown.

xcproj.xcodeproj/project.pbxproj

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
objects = {
88

99
/* Begin PBXBuildFile section */
10+
4F71D608E2B691F44F267F73 /* Xcproj+FileUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 4F71DFC078F05F4B47C98305 /* Xcproj+FileUtils.m */; };
1011
8DD76F9A0486AA7600D96B5E /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 08FB7796FE84155DC02AAC07 /* main.m */; settings = {ATTRIBUTES = (); }; };
1112
8DD76F9C0486AA7600D96B5E /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 08FB779EFE84155DC02AAC07 /* Foundation.framework */; };
1213
C215308619DBE040002524A7 /* XMLPlistDecoder.m in Sources */ = {isa = PBXBuildFile; fileRef = C215308519DBE040002524A7 /* XMLPlistDecoder.m */; };
@@ -24,6 +25,8 @@
2425
08FB7796FE84155DC02AAC07 /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = Sources/main.m; sourceTree = "<group>"; };
2526
08FB779EFE84155DC02AAC07 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = /System/Library/Frameworks/Foundation.framework; sourceTree = "<absolute>"; };
2627
32A70AAB03705E1F00C91783 /* xcproj_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = xcproj_Prefix.pch; path = Sources/xcproj_Prefix.pch; sourceTree = "<group>"; };
28+
4F71D5B9A24F620C2593DF81 /* Xcproj+FileUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "Xcproj+FileUtils.h"; sourceTree = "<group>"; };
29+
4F71DFC078F05F4B47C98305 /* Xcproj+FileUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "Xcproj+FileUtils.m"; sourceTree = "<group>"; };
2730
8DD76FA10486AA7600D96B5E /* xcproj */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = xcproj; sourceTree = BUILT_PRODUCTS_DIR; };
2831
C215308419DBE040002524A7 /* XMLPlistDecoder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = XMLPlistDecoder.h; path = Sources/XMLPlistDecoder.h; sourceTree = "<group>"; };
2932
C215308519DBE040002524A7 /* XMLPlistDecoder.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = XMLPlistDecoder.m; path = Sources/XMLPlistDecoder.m; sourceTree = "<group>"; };
@@ -93,6 +96,8 @@
9396
DAFF25CB1303ECB100584E0E /* XCDUndocumentedChecker.m */,
9497
C215308419DBE040002524A7 /* XMLPlistDecoder.h */,
9598
C215308519DBE040002524A7 /* XMLPlistDecoder.m */,
99+
4F71DFC078F05F4B47C98305 /* Xcproj+FileUtils.m */,
100+
4F71D5B9A24F620C2593DF81 /* Xcproj+FileUtils.h */,
96101
);
97102
name = Sources;
98103
sourceTree = "<group>";
@@ -166,6 +171,7 @@
166171
buildPhases = (
167172
8DD76F990486AA7600D96B5E /* Sources */,
168173
8DD76F9B0486AA7600D96B5E /* Frameworks */,
174+
A05ABC2D1B17203A006D0115 /* ShellScript */,
169175
);
170176
buildRules = (
171177
);
@@ -205,6 +211,22 @@
205211
};
206212
/* End PBXProject section */
207213

214+
/* Begin PBXShellScriptBuildPhase section */
215+
A05ABC2D1B17203A006D0115 /* ShellScript */ = {
216+
isa = PBXShellScriptBuildPhase;
217+
buildActionMask = 2147483647;
218+
files = (
219+
);
220+
inputPaths = (
221+
);
222+
outputPaths = (
223+
);
224+
runOnlyForDeploymentPostprocessing = 0;
225+
shellPath = /bin/sh;
226+
shellScript = "cp \"${BUILT_PRODUCTS_DIR}/${FULL_PRODUCT_NAME}\" \"${SRCROO}\"";
227+
};
228+
/* End PBXShellScriptBuildPhase section */
229+
208230
/* Begin PBXSourcesBuildPhase section */
209231
8DD76F990486AA7600D96B5E /* Sources */ = {
210232
isa = PBXSourcesBuildPhase;
@@ -219,6 +241,7 @@
219241
C269E99117B3DCCC0099A0C5 /* DDGetoptLongParser.m in Sources */,
220242
DAFF25CC1303ECB100584E0E /* XCDUndocumentedChecker.m in Sources */,
221243
C269E98F17B3DCCC0099A0C5 /* DDCliParseException.m in Sources */,
244+
4F71D608E2B691F44F267F73 /* Xcproj+FileUtils.m in Sources */,
222245
);
223246
runOnlyForDeploymentPostprocessing = 0;
224247
};

0 commit comments

Comments
 (0)