Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added new feature : 'put-resouce-bundle' #8

Open
wants to merge 1 commit into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ Actions:
* add-resources-bundle <bundle_path>
Add a bundle to the project and in the `Copy Bundle Resources` build phase

* put-resource-bundle <source_file_absolute_path_to_read> [<dest_dir_path> (default=Resources, relative or absolute)>]
Copy or rewrite a file, and then Add a bundle to target path in the project and in the `Copy Bundle Resources` build phase

* touch
Rewrite the project file
```
Expand Down
111 changes: 97 additions & 14 deletions Sources/Xcproj.m
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#import <objc/runtime.h>
#import "XCDUndocumentedChecker.h"
#import "XMLPlistDecoder.h"
#import "Xcproj+FileUtils.h"

@implementation Xcproj
{
Expand Down Expand Up @@ -203,25 +204,28 @@ - (void) application:(DDCliApplication *)app willParseOptions:(DDGetoptLongParse
[optionsParser addOptionsFromTable:optionTable];
}

- (void) setProject:(NSString *)projectName
- (void) setProject:(NSString *)projectPath
{
[self.class initializeXcproj];

if (![PBXProject isProjectWrapperExtension:[projectPath pathExtension]])
@throw [DDCliParseException parseExceptionWithReason:[NSString stringWithFormat:@"The project name %@ does not have a valid extension.", projectPath] exitCode:EX_USAGE];

if (![PBXProject isProjectWrapperExtension:[projectName pathExtension]])
@throw [DDCliParseException parseExceptionWithReason:[NSString stringWithFormat:@"The project name %@ does not have a valid extension.", projectName] exitCode:EX_USAGE];
NSString *projectPath = projectName;
if (![projectName isAbsolutePath])
projectPath = [[[NSFileManager defaultManager] currentDirectoryPath] stringByAppendingPathComponent:projectName];
if (![projectPath isAbsolutePath]){
projectPath = [[[NSFileManager defaultManager] currentDirectoryPath] stringByAppendingPathComponent:projectPath];
}else{
//change current path
[[NSFileManager defaultManager] changeCurrentDirectoryPath:[[projectPath stringByStandardizingPath] stringByDeletingLastPathComponent]];
}

if (![[NSFileManager defaultManager] fileExistsAtPath:projectPath])
@throw [DDCliParseException parseExceptionWithReason:[NSString stringWithFormat:@"The project %@ does not exist in this directory.", projectName] exitCode:EX_NOINPUT];
@throw [DDCliParseException parseExceptionWithReason:[NSString stringWithFormat:@"The project %@ does not exist in this directory.", projectPath] exitCode:EX_NOINPUT];

[_project release];
_project = [[PBXProject projectWithFile:projectPath] retain];

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

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

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

- (void) printUsage:(int)exitCode
Expand All @@ -341,8 +345,10 @@ - (void) printUsage:(int)exitCode
@" Assign a value to a build setting. If the build setting does not exist, it is added to the target\n\n"
@" * add-xcconfig <xcconfig_path>\n"
@" Add an xcconfig file to the project and base all configurations on it\n\n"
@" * add-resources-bundle <bundle_path>\n"
@" Add a bundle to the project and in the `Copy Bundle Resources` build phase\n\n"
@" * add-resources-bundle <bundle_paths> ...\n"
@" Add multiple bundles to default group in the project and in the `Copy Bundle Resources` build phase\n\n"
@" * put-resource-bundle <source_file_absolute_path_to_read> [<dest_dir_path> (default=Resources, relative or absolute)>]\n"
@" 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"
@" * touch\n"
@" Rewrite the project file\n");
exit(exitCode);
Expand Down Expand Up @@ -461,6 +467,83 @@ - (int) addResourcesBundle:(NSArray *)arguments
return [self writeProject];
}

- (int) putResourceBundle:(NSArray *)arguments
{
if(arguments.count<1 || arguments.count>2){
ddprintf(@"Wrong arguments input. See usage\n");
[self printUsage:EX_USAGE];
}

//check source file valid
NSString * sourceFilePath = [[arguments firstObject] stringByStandardizingPath];

if (![[NSFileManager defaultManager] fileExistsAtPath:sourceFilePath]){
@throw [DDCliParseException parseExceptionWithReason:[NSString stringWithFormat:@"A source file %@ does not exists.", sourceFilePath] exitCode:EX_OSFILE];
}
if (![[NSFileManager defaultManager] isReadableFileAtPath:sourceFilePath]){
@throw [DDCliParseException parseExceptionWithReason:[NSString stringWithFormat:@"A source file %@ can't read.", sourceFilePath] exitCode:EX_IOERR];
}
if (![sourceFilePath isAbsolutePath]){
@throw [DDCliParseException parseExceptionWithReason:[NSString stringWithFormat:@"A path of source file %@ must be absolute path.", sourceFilePath] exitCode:EX_IOERR];
}

NSString * projectTargetGroupName = _target.name;
NSString * projectRootPath = [[NSFileManager defaultManager] currentDirectoryPath];
NSString * targetPath = [projectRootPath stringByAppendingPathComponent:_target.name];
NSString * destFilePath = [targetPath stringByAppendingPathComponent:@"Resources"];
//set dest path
if(arguments.count==2){
destFilePath = [[arguments lastObject] stringByStandardizingPath];
if(![destFilePath isAbsolutePath]){
destFilePath = [projectRootPath stringByAppendingPathComponent:destFilePath];
}

if(![[destFilePath stringByDeletingLastPathComponent] hasPrefix:projectRootPath]){
@throw [DDCliParseException parseExceptionWithReason:[NSString stringWithFormat:@"A path of destination file '%@' must be locate inside of base path of project '%@'.", destFilePath, projectRootPath] exitCode:EX_IOERR];
}
}
destFilePath = [destFilePath stringByAppendingPathComponent:[sourceFilePath lastPathComponent]];

//resolving relative path
NSArray const * relativeDirs = [destFilePath pathComponents];
relativeDirs = [relativeDirs subarrayWithRange:NSMakeRange(projectRootPath.pathComponents.count, relativeDirs.count-projectRootPath.pathComponents.count-1)];

if([[relativeDirs firstObject] isNotEqualTo:projectTargetGroupName]){
ddprintf(@"[!] WARNING : A relative path of destination file '%@' is located outside of current TARGET path '%@'.\n", destFilePath, targetPath);
}

@try {
[[NSFileManager defaultManager] createDirectoryAtPath:destFilePath withIntermediateDirectories:YES attributes:nil error:NULL];

NSError *error = nil;
if([self atomicCopyItemAtURL:[NSURL fileURLWithPath:sourceFilePath] toURL:[NSURL fileURLWithPath:destFilePath] error:&error]){
[relativeDirs enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
if(idx==0){
[self addGroupNamed:obj beforeGroupNamed:_project.rootGroup.name];
}else{
[self addGroupNamed:obj inGroupNamed:relativeDirs[idx-1]];
}

if([[relativeDirs lastObject] isEqualTo:obj]){
id<PBXFileReference> bundleReference = [self addFileAtPath:destFilePath];
[self addFileReference:bundleReference inGroupNamed:obj];
[self addFileReference:bundleReference toBuildPhase:@"Resources"];
}
}];

}else{
ddprintf(@"[!] Resource file copy failed.%@\n");
}

}@catch (NSException * exc) {
ddprintf(@"File I/O Exception : %@\n", exc.reason);
return EX_IOERR;
}

return [self writeProject];
}


- (int) touch:(NSArray *)arguments
{
return [self writeProject];
Expand Down
11 changes: 11 additions & 0 deletions Xcproj+FileUtils.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
//
// Created by BLACKGENE on 15. 5. 29..
// Copyright (c) 2015 Cédric Luthi. All rights reserved.
//

#import <Foundation/Foundation.h>
#import "Xcproj.h"

@interface Xcproj (FileUtils)
- (BOOL)atomicCopyItemAtURL:(NSURL *)sourceURL toURL:(NSURL *)destinationURL error:(NSError **)outError;
@end
45 changes: 45 additions & 0 deletions Xcproj+FileUtils.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
//
// Created by BLACKGENE on 15. 5. 29..
// Copyright (c) 2015 Cédric Luthi. All rights reserved.
//

#import "Xcproj+FileUtils.h"


@implementation Xcproj (FileUtils)

- (BOOL)atomicCopyItemAtURL:(NSURL *)sourceURL toURL:(NSURL *)destinationURL error:(NSError **)outError {
NSFileManager *manager = [NSFileManager defaultManager];
NSURL *tempDir = [manager URLForDirectory:NSItemReplacementDirectory inDomain:NSUserDomainMask appropriateForURL:destinationURL create:YES error:outError];
if (!tempDir){
return NO;
}

NSURL *tempURL = [tempDir URLByAppendingPathComponent:[destinationURL lastPathComponent]];
BOOL result = [manager copyItemAtURL:sourceURL toURL:tempURL error:outError];
if (result){
NSURL *resultingURL;
result = [manager replaceItemAtURL:destinationURL
withItemAtURL:tempURL
backupItemName:nil
options:NSFileManagerItemReplacementUsingNewMetadataOnly
resultingItemURL:&resultingURL
error:outError];

if (result){
NSAssert([resultingURL.absoluteString isEqualToString:destinationURL.absoluteString],
@"URL unexpectedly changed during replacement from:\n%@\nto:\n%@",
destinationURL,
resultingURL);
}
}

// Clean up
NSError *error;
if (![manager removeItemAtURL:tempDir error:&error]){
NSLog(@"Failed to remove temp directory after atomic copy: %@", error);
}
return result;
}

@end
Binary file added xcproj
Binary file not shown.
23 changes: 23 additions & 0 deletions xcproj.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
objects = {

/* Begin PBXBuildFile section */
4F71D608E2B691F44F267F73 /* Xcproj+FileUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 4F71DFC078F05F4B47C98305 /* Xcproj+FileUtils.m */; };
8DD76F9A0486AA7600D96B5E /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 08FB7796FE84155DC02AAC07 /* main.m */; settings = {ATTRIBUTES = (); }; };
8DD76F9C0486AA7600D96B5E /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 08FB779EFE84155DC02AAC07 /* Foundation.framework */; };
C215308619DBE040002524A7 /* XMLPlistDecoder.m in Sources */ = {isa = PBXBuildFile; fileRef = C215308519DBE040002524A7 /* XMLPlistDecoder.m */; };
Expand All @@ -24,6 +25,8 @@
08FB7796FE84155DC02AAC07 /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = Sources/main.m; sourceTree = "<group>"; };
08FB779EFE84155DC02AAC07 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = /System/Library/Frameworks/Foundation.framework; sourceTree = "<absolute>"; };
32A70AAB03705E1F00C91783 /* xcproj_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = xcproj_Prefix.pch; path = Sources/xcproj_Prefix.pch; sourceTree = "<group>"; };
4F71D5B9A24F620C2593DF81 /* Xcproj+FileUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "Xcproj+FileUtils.h"; sourceTree = "<group>"; };
4F71DFC078F05F4B47C98305 /* Xcproj+FileUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "Xcproj+FileUtils.m"; sourceTree = "<group>"; };
8DD76FA10486AA7600D96B5E /* xcproj */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = xcproj; sourceTree = BUILT_PRODUCTS_DIR; };
C215308419DBE040002524A7 /* XMLPlistDecoder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = XMLPlistDecoder.h; path = Sources/XMLPlistDecoder.h; sourceTree = "<group>"; };
C215308519DBE040002524A7 /* XMLPlistDecoder.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = XMLPlistDecoder.m; path = Sources/XMLPlistDecoder.m; sourceTree = "<group>"; };
Expand Down Expand Up @@ -93,6 +96,8 @@
DAFF25CB1303ECB100584E0E /* XCDUndocumentedChecker.m */,
C215308419DBE040002524A7 /* XMLPlistDecoder.h */,
C215308519DBE040002524A7 /* XMLPlistDecoder.m */,
4F71DFC078F05F4B47C98305 /* Xcproj+FileUtils.m */,
4F71D5B9A24F620C2593DF81 /* Xcproj+FileUtils.h */,
);
name = Sources;
sourceTree = "<group>";
Expand Down Expand Up @@ -166,6 +171,7 @@
buildPhases = (
8DD76F990486AA7600D96B5E /* Sources */,
8DD76F9B0486AA7600D96B5E /* Frameworks */,
A05ABC2D1B17203A006D0115 /* ShellScript */,
);
buildRules = (
);
Expand Down Expand Up @@ -205,6 +211,22 @@
};
/* End PBXProject section */

/* Begin PBXShellScriptBuildPhase section */
A05ABC2D1B17203A006D0115 /* ShellScript */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "cp \"${BUILT_PRODUCTS_DIR}/${FULL_PRODUCT_NAME}\" \"${SRCROO}\"";
};
/* End PBXShellScriptBuildPhase section */

/* Begin PBXSourcesBuildPhase section */
8DD76F990486AA7600D96B5E /* Sources */ = {
isa = PBXSourcesBuildPhase;
Expand All @@ -219,6 +241,7 @@
C269E99117B3DCCC0099A0C5 /* DDGetoptLongParser.m in Sources */,
DAFF25CC1303ECB100584E0E /* XCDUndocumentedChecker.m in Sources */,
C269E98F17B3DCCC0099A0C5 /* DDCliParseException.m in Sources */,
4F71D608E2B691F44F267F73 /* Xcproj+FileUtils.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down