New: XcodeCapp 3, a major overhaul.

- Updated project to Xcode 4.
- Reorganized files into preferred Xcode 4 hierarchy.
- Updated code to use properties.
- Eliminated unused/redundant code.
- Significantly optimized project scans.
- Added support for outlets, actions and resources in user frameworks.
- Now using bindings throughout.
- New application icon, Retina ready.
- New status menu icons.
- Reorganized menu.
- Added Preferences… menu item.
- Parsing errors and nib2cib errors are actually logged to the errors panel now.
- When an error occurs, the errors panel automatically opens, unless the "Automatically open Errors & Warnings panel" preference is off.
- Double-clicking an error or selecting an error and clicking Open opens the error in the preferred editor for .j files (or Xcode for .xibs). The cursor jumps to the offending line in the following editors: Sublime Text, TextWrangler, BBEdit, TextMate, Chocolate, MacVim.
- The Errors panel now remembers its position/size.
- When a project is loaded, the errors panel is cleared.
- When a file is modified, any pre-existing errors for that file are removed.
- Updated to Growl framework 2.0.1.
- If Notification Center is available (OS X 10.8+), that is used instead of Growl.
- .xcodecapp-ignore now supports ignoring a directory by suffixing the name with "/".
- .xcodecapp-ignore now supports include expressions prefixed with "!".
- Significantly optimized filename matching against ignored paths.
- .XcodeSupport has been renamed XcodeSupport to make it visible, so that trashing it is easier if things get out of sync.
- The template Xcode project now contains no frameworks or targets, so Xcode does not show any warnings or errors, and the user cannot accidentally try to build.
- Removing a file now removes the file from the Xcode project.
- Shadow files replace forward slash with U+2215 (DIVISION SLASH), which looks like forward slash but is a character that is extremely unlikely to be in a filename.
This commit is contained in:
Aparajita Fishman
2013-04-24 10:57:59 -04:00
parent 0b7e92aa53
commit 606205b58d
95 changed files with 4516 additions and 6792 deletions
+103 -83
View File
@@ -1,16 +1,16 @@
# Copyright 2012 Calvin Rien
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# A pbxproj file is an OpenStep format plist
# {} represents dictionary of key=value pairs delimited by ;
@@ -143,39 +143,39 @@ class PBXFileReference(PBXType):
self.build_phase = None
types = {
'.a':('archive.ar', 'PBXFrameworksBuildPhase'),
'.app': ('wrapper.application', None),
'.s': ('sourcecode.asm', 'PBXSourcesBuildPhase'),
'.c': ('sourcecode.c.c', 'PBXSourcesBuildPhase'),
'.cpp': ('sourcecode.cpp.cpp', 'PBXSourcesBuildPhase'),
'.framework': ('wrapper.framework','PBXFrameworksBuildPhase'),
'.h': ('sourcecode.c.h', None),
'.icns': ('image.icns','PBXResourcesBuildPhase'),
'.m': ('sourcecode.c.objc', 'PBXSourcesBuildPhase'),
'.j': ('sourcecode.c.objc', 'PBXSourcesBuildPhase'),
'.mm': ('sourcecode.cpp.objcpp', 'PBXSourcesBuildPhase'),
'.nib': ('wrapper.nib', 'PBXResourcesBuildPhase'),
'.plist': ('text.plist.xml', 'PBXResourcesBuildPhase'),
'.json': ('text.json', 'PBXResourcesBuildPhase'),
'.png': ('image.png', 'PBXResourcesBuildPhase'),
'.rtf': ('text.rtf', 'PBXResourcesBuildPhase'),
'.tiff': ('image.tiff', 'PBXResourcesBuildPhase'),
'.txt': ('text', 'PBXResourcesBuildPhase'),
'.xcodeproj': ('wrapper.pb-project', None),
'.xib': ('file.xib', 'PBXResourcesBuildPhase'),
'.strings': ('text.plist.strings', 'PBXResourcesBuildPhase'),
'.bundle': ('wrapper.plug-in', 'PBXResourcesBuildPhase'),
'.dylib': ('compiled.mach-o.dylib', 'PBXFrameworksBuildPhase')
'.a':('archive.ar', 'PBXFrameworksBuildPhase'),
'.app': ('wrapper.application', None),
'.s': ('sourcecode.asm', 'PBXSourcesBuildPhase'),
'.c': ('sourcecode.c.c', 'PBXSourcesBuildPhase'),
'.cpp': ('sourcecode.cpp.cpp', 'PBXSourcesBuildPhase'),
'.framework': ('wrapper.framework','PBXFrameworksBuildPhase'),
'.h': ('sourcecode.c.h', None),
'.icns': ('image.icns','PBXResourcesBuildPhase'),
'.m': ('sourcecode.c.objc', 'PBXSourcesBuildPhase'),
'.j': ('sourcecode.c.objc', 'PBXSourcesBuildPhase'),
'.mm': ('sourcecode.cpp.objcpp', 'PBXSourcesBuildPhase'),
'.nib': ('wrapper.nib', 'PBXResourcesBuildPhase'),
'.plist': ('text.plist.xml', 'PBXResourcesBuildPhase'),
'.json': ('text.json', 'PBXResourcesBuildPhase'),
'.png': ('image.png', 'PBXResourcesBuildPhase'),
'.rtf': ('text.rtf', 'PBXResourcesBuildPhase'),
'.tiff': ('image.tiff', 'PBXResourcesBuildPhase'),
'.txt': ('text', 'PBXResourcesBuildPhase'),
'.xcodeproj': ('wrapper.pb-project', None),
'.xib': ('file.xib', 'PBXResourcesBuildPhase'),
'.strings': ('text.plist.strings', 'PBXResourcesBuildPhase'),
'.bundle': ('wrapper.plug-in', 'PBXResourcesBuildPhase'),
'.dylib': ('compiled.mach-o.dylib', 'PBXFrameworksBuildPhase')
}
trees = [
'<absolute>',
'<group>',
'BUILT_PRODUCTS_DIR',
'DEVELOPER_DIR',
'SDKROOT',
'SOURCE_ROOT',
]
'<absolute>',
'<group>',
'BUILT_PRODUCTS_DIR',
'DEVELOPER_DIR',
'SDKROOT',
'SOURCE_ROOT',
]
def guess_file_type(self):
self.remove('explicitFileType')
@@ -355,6 +355,10 @@ class PBXVariantGroup(PBXType):
pass
class PBXTargetDependency(PBXType):
pass
class PBXBuildPhase(PBXType):
def add_build_file(self, bf):
if bf.get('isa') != 'PBXBuildFile':
@@ -590,7 +594,7 @@ class XcodeProject(PBXDict):
if b.add_library_search_paths(paths, recursive):
self.modified = True
# TODO: need to return value if project has been modified
# TODO: need to return value if project has been modified
def get_obj(self, id):
return self.objects.get(id)
@@ -600,36 +604,36 @@ class XcodeProject(PBXDict):
def get_files_by_os_path(self, os_path, tree='SOURCE_ROOT'):
files = [f for f in self.objects.values() if f.get('isa') == 'PBXFileReference'
and f.get('path') == os_path
and f.get('sourceTree') == tree]
and f.get('path') == os_path
and f.get('sourceTree') == tree]
return files
def get_files_by_name(self, name, parent=None):
if parent:
files = [f for f in self.objects.values() if f.get('isa') == 'PBXFileReference'
and f.get(name) == name
and parent.has_child(f)]
and f.get(name) == name
and parent.has_child(f)]
else:
files = [f for f in self.objects.values() if f.get('isa') == 'PBXFileReference'
and f.get(name) == name]
and f.get(name) == name]
return files
def get_build_files(self, id):
files = [f for f in self.objects.values() if f.get('isa') == 'PBXBuildFile'
and f.get('fileRef') == id]
and f.get('fileRef') == id]
return files
def get_groups_by_name(self, name, parent=None):
if parent:
groups = [g for g in self.objects.values() if g.get('isa') == 'PBXGroup'
and g.get_name() == name
and parent.has_child(g)]
and g.get_name() == name
and parent.has_child(g)]
else:
groups = [g for g in self.objects.values() if g.get('isa') == 'PBXGroup'
and g.get_name() == name]
and g.get_name() == name]
return groups
@@ -662,7 +666,7 @@ class XcodeProject(PBXDict):
path = os.path.abspath(path)
groups = [g for g in self.objects.values() if g.get('isa') == 'PBXGroup'
and os.path.abspath(g.get('path','/dev/null')) == path]
and os.path.abspath(g.get('path','/dev/null')) == path]
return groups
@@ -739,9 +743,9 @@ class XcodeProject(PBXDict):
continue
kwds = {
'create_build_files': create_build_files,
'parent': grp,
'name': f
'create_build_files': create_build_files,
'parent': grp,
'name': f
}
f_path = os.path.join(grp_path, f)
@@ -812,12 +816,12 @@ class XcodeProject(PBXDict):
results.append(build_file)
if abs_path and tree == 'SOURCE_ROOT' and os.path.isfile(abs_path)\
and file_ref.build_phase == 'PBXFrameworksBuildPhase':
and file_ref.build_phase == 'PBXFrameworksBuildPhase':
library_path = os.path.join('$(SRCROOT)', os.path.split(f_path)[0])
self.add_library_search_paths([library_path], recursive=False)
if abs_path and tree == 'SOURCE_ROOT' and not os.path.isfile(abs_path)\
and file_ref.build_phase == 'PBXFrameworksBuildPhase':
and file_ref.build_phase == 'PBXFrameworksBuildPhase':
framework_path = os.path.join('$(SRCROOT)', os.path.split(f_path)[0])
self.add_framework_search_paths([framework_path,'$(inherited)'], recursive=False)
@@ -849,12 +853,24 @@ class XcodeProject(PBXDict):
if(not os.path.exists(finalLib)):
os.symlink(srcLib, finalLib);
def remove_group(self, grp):
pass
def remove_file(self, id):
pass
def remove_file(self, id, recursive=True):
if not PBXType.IsGuid(id):
id = id.id
if id in self.objects:
self.objects.remove(id)
if recursive:
groups = [g for g in self.objects.values() if g.get('isa') == 'PBXGroup']
for group in groups:
if id in group['children']:
group.remove_child(id)
self.modified = True
def move_file(self, id, dest_grp=None):
pass
@@ -1023,11 +1039,11 @@ class XcodeProject(PBXDict):
for f in v:
filerefs.extend([fr.id for fr in self.objects.values() if fr.get('isa') == 'PBXFileReference'
and fr.get('name') == f])
and fr.get('name') == f])
buildfiles = [bf for bf in self.objects.values() if bf.get('isa') == 'PBXBuildFile'
and bf.get('fileRef') in filerefs]
and bf.get('fileRef') in filerefs]
for bf in buildfiles:
if bf.add_compiler_flag(k):
@@ -1137,26 +1153,30 @@ class XcodeProject(PBXDict):
#root.remove('objects') #remove it to avoid problems
sections = [
('PBXBuildFile',False),
('PBXCopyFilesBuildPhase',True),
('PBXFileReference',False),
('PBXFrameworksBuildPhase',True),
('PBXGroup',True),
('PBXNativeTarget',True),
('PBXProject',True),
('PBXResourcesBuildPhase',True),
('PBXShellScriptBuildPhase',True),
('PBXSourcesBuildPhase',True),
('XCBuildConfiguration',True),
('XCConfigurationList',True)]
('PBXBuildFile',False),
('PBXCopyFilesBuildPhase',True),
('PBXFileReference',False),
('PBXFrameworksBuildPhase',True),
('PBXGroup',True),
('PBXNativeTarget',True),
('PBXProject',True),
('PBXResourcesBuildPhase',True),
('PBXShellScriptBuildPhase',True),
('PBXSourcesBuildPhase',True),
('XCBuildConfiguration',True),
('XCConfigurationList',True),
('PBXTargetDependency', True),
('PBXVariantGroup', True),
('PBXReferenceProxy', True),
('PBXContainerItemProxy', True)]
for section in sections: #iterate over the sections
for section in sections: #iterate over the sections
if(self.sections.get(section[0]) == None):
continue;
out.write('\n/* Begin %s section */'%section[0]);
self.sections.get(section[0]).sort(cmp=lambda x,y: cmp(x[0],y[0]))
#if(self.sections.get(section[0])=='PBXGroup' and ): //add the patch to add the missing but existing files.
#if(self.sections.get(section[0])=='PBXGroup' and ): //add the patch to add the missing but existing files.
for pair in self.sections.get(section[0]):
key = pair[0]
@@ -1206,7 +1226,7 @@ class XcodeProject(PBXDict):
out.write('"'+XcodeProject.addslashes(root)+'"')
if(root in self.uuids):
out.write(" /* "+self.uuids[root]+" */");
@classmethod
def getJSONFromXML(cls, root):
result = ''
@@ -1232,7 +1252,7 @@ class XcodeProject(PBXDict):
for child in root.childNodes:
if child.nodeType != Node.ELEMENT_NODE:
continue;
if(i>0):
result += ","
result += XcodeProject.getJSONFromXML(child);
@@ -1242,28 +1262,28 @@ class XcodeProject(PBXDict):
data = '""'
for node in root.childNodes:
if node.nodeType == node.TEXT_NODE:
data = '"'+XcodeProject.addslashes(node.data).replace('\n','\\n')+'"'
data = '"'+XcodeProject.addslashes(node.data).replace('\n','\\n').replace('\\\'', '\'')+'"'
break
result += data
return result;
@classmethod
def Load(cls, path):
cls.plutil_path = os.path.join(os.path.split(__file__)[0], 'plutil')
if not os.path.isfile(XcodeProject.plutil_path):
cls.plutil_path = 'plutil'
if subprocess.call([XcodeProject.plutil_path,'-lint','-s',path]):
print 'ERROR: not a valid .pbxproj file'
return None
# load project by converting to JSON and parse
p = subprocess.Popen([XcodeProject.plutil_path, '-convert', 'xml1', '-o', '-', path], stdout=subprocess.PIPE)
rawXML = p.communicate()[0]
xml = parseString(rawXML);
jsonStr = XcodeProject.getJSONFromXML(xml.getElementsByTagName('dict')[0]);
tree = json.loads(jsonStr)
return XcodeProject(tree, path)
+54 -33
View File
@@ -2,7 +2,7 @@
* parser.j
*
* Created by Francisco Tolmasky.
* Modified by Antoine Mercadal, with great help of Martin Carlberg
* Modified by Antoine Mercadal, with great help from Martin Carlberg
* Copyright 2008-2013, 280 North, Inc.
*
* This library is free software; you can redistribute it and/or
@@ -23,7 +23,9 @@
@import <Foundation/Foundation.j>
var FILE = require("file"),
OS = require("os");
OS = require("os"),
SLASH_REPLACEMENT = ""; // DIVISION SLASH, Unicode: U+2215
// Debug function to print some JS objects
function dump(obj)
@@ -35,22 +37,23 @@ function raise(pos, message)
{
var syntaxError = new SyntaxError(message);
syntaxError.line = pos.line;
syntaxError.column = pos.column;
syntaxError.lineStart = pos.lineStart;
syntaxError.lineEnd = pos.lineEnd;
throw syntaxError;
}
var hasWarnings = false,
errors = [],
xcc = ObjectiveJ.acorn.walk.make(
{
ClassDeclarationStatement: function(node, st, c)
{
if (node.categoryname)
{
print("Line " + node.loc.start.line + ", " + node.loc.source);
print("Categories are not supported yet. Ignoring it.");
[errors addObject:@{
@"message": "Categories are not supported yet, ignoring it.",
@"path": node.loc.source,
@"line": node.loc.start.line
}];
hasWarnings = true;
return;
}
@@ -135,18 +138,25 @@ function compile(node, state, visitor)
c(node, state);
};
function shadowBaseNameForPath(path)
{
// strip the extension and replace slashes
return path.substring(0, path.length - 2).replace(/[/]/g, SLASH_REPLACEMENT);
}
function main(args)
{
try
{
var fileURL = new CFURL(args[1]),
outputBaseURL = new CFURL(args[2]),
outputHeaderURL = new CFURL(outputBaseURL.path() + ".h"),
outputSourceURL = new CFURL(outputBaseURL.path() + ".m"),
outputDirectory = args[2],
baseFilename = shadowBaseNameForPath(fileURL.path()),
outputHeaderURL = new CFURL([outputDirectory stringByAppendingPathComponent:baseFilename + ".h"]),
outputImplementationURL = new CFURL([outputDirectory stringByAppendingPathComponent:baseFilename + ".m"]),
source = FILE.read(fileURL, { charset: "UTF-8" }),
flags = ObjectiveJ.Preprocessor.Flags.IncludeDebugSymbols | ObjectiveJ.Preprocessor.Flags.IncludeTypeSignatures,
tokens = ObjectiveJ.acorn.parse(source, { locations:true, sourceFile:fileURL.path() }),
sourceFile = fileURL.path(),
tokens = ObjectiveJ.acorn.parse(source, { locations:true, sourceFile:sourceFile }),
classesInformation = [],
ObjectiveCSource = "",
ObjectiveCHeader = "";
@@ -158,54 +168,65 @@ function main(args)
ObjectiveCHeader +=
"#import <Foundation/Foundation.h>\n" +
"#import <Cocoa/Cocoa.h>\n" +
"#import \"xcc_general_include.h\"\n\n";
'#import "xcc_general_include.h"\n';
ObjectiveCSource += "#import \"" + outputHeaderURL.lastPathComponent() + "\"\n\n";
ObjectiveCSource += "#import \"" + outputHeaderURL.lastPathComponent() + "\"\n";
// Traverse each found classes
classesInformation.forEach(function(aClass)
{
// add new class definition
ObjectiveCHeader += "@interface " + aClass.name + " : " + NSCompatibleClassName(aClass.superClass) + "\n\n";
ObjectiveCHeader += [CPString stringWithFormat:@"\n@interface %@ : %@", aClass.name, NSCompatibleClassName(aClass.superClass)];
// add each outlet in header
if (aClass.outlets.length > 0)
ObjectiveCHeader += "\n";
// Add each outlets in header
aClass.outlets.forEach(function(anOutlet)
{
ObjectiveCHeader += "@property (assign) IBOutlet " + NSCompatibleClassName(anOutlet.type, YES) + " " + anOutlet.name + ";\n";
ObjectiveCHeader += [CPString stringWithFormat:@"\n@property (assign) IBOutlet %@ %@;", NSCompatibleClassName(anOutlet.type, YES), anOutlet.name];
});
ObjectiveCHeader += "\n";
if (aClass.actions.length > 0)
ObjectiveCHeader += "\n";
// Add each actions in header
// add each action in header
aClass.actions.forEach(function(anAction)
{
ObjectiveCHeader += "- (IBAction)" + anAction.name + ":(" + anAction.arguments[0].type + ")" + anAction.arguments[0].name + ";\n";
ObjectiveCHeader += [CPString stringWithFormat:@"\n- (IBAction)%@:(%@)%@;", anAction.name, anAction.arguments[0].type, anAction.arguments[0].name];
});
ObjectiveCHeader += "\n@end\n\n\n";
if (aClass.outlets.length > 0 || aClass.actions.length > 0)
ObjectiveCHeader += "\n";
ObjectiveCHeader += "\n@end\n";
// fill up the implementation file
ObjectiveCSource += "@implementation " + aClass.name + "\n@end\n\n";
ObjectiveCSource += "\n@implementation " + aClass.name + "\n@end\n";
});
// write files
if (ObjectiveCSource.length)
FILE.write(outputSourceURL, ObjectiveCSource, { charset:"UTF-8" });
if (ObjectiveCSource.length)
FILE.write(outputImplementationURL, ObjectiveCSource, { charset:"UTF-8" });
if (ObjectiveCHeader.length)
FILE.write(outputHeaderURL, ObjectiveCHeader, { charset:"UTF-8" });
if (ObjectiveCHeader.length)
FILE.write(outputHeaderURL, ObjectiveCHeader, { charset:"UTF-8" });
}
catch (e)
{
if (e instanceof SyntaxError)
print("Line " + e.line + ", " + fileURL.path());
print(e.name + ": " + e.message);
OS.exit(1);
[errors addObject:@{
@"message": e.message,
@"path": sourceFile,
@"line": e.line
}];
}
if (hasWarnings)
OS.exit(2);
if ([errors count])
{
var plist = [CPPropertyListSerialization dataFromPropertyList:errors format:CPPropertyListXMLFormat_v1_0];
print([plist rawString]);
OS.exit(1);
}
}
function NSCompatibleClassName(aClassName, asPointer)
@@ -4,40 +4,64 @@ import re
import sys
from mod_pbxproj import XcodeProject
XCODESUPPORTFOLDER = ".XcodeSupport"
XCODE_SUPPORT_FOLDER = "XcodeSupport"
SLASH_REPLACEMENT = u"" # DIVISION SLASH Unicode U+2215
STRING_RE = re.compile(ur"^\s*<string>(.*)</string>\s*$", re.MULTILINE)
FRAMEWORKS_RE = re.compile(ur"^(.+/Frameworks/Debug/([^/]+))/.+$")
XCC_GENERAL_INCLUDE = u"xcc_general_include.h"
def update_general_include(project, projectBasePath):
xcc_general_include_file = os.path.join(projectBasePath, XCODESUPPORTFOLDER, u"xcc_general_include.h")
def update_general_include(project, projectBasePath, shadowGroup):
xcc_general_include_path = os.path.join(projectBasePath, XCODE_SUPPORT_FOLDER, XCC_GENERAL_INCLUDE)
content = u""
for file in os.listdir(os.path.join(projectBasePath, XCODESUPPORTFOLDER)):
if file.endswith(".h"):
content += u'#include "{0}"\n'.format(os.path.basename(unicode(file)))
for path in os.listdir(os.path.join(projectBasePath, XCODE_SUPPORT_FOLDER)):
filename = unicode(os.path.basename(path))
f = open(xcc_general_include_file, "w")
if filename.endswith(".h") and filename != XCC_GENERAL_INCLUDE:
content += u'#include "{0}"\n'.format(filename)
f = open(xcc_general_include_path, "w")
f.write(content.encode("utf-8"))
f.close()
if len(project.get_files_by_os_path(os.path.join(XCODESUPPORTFOLDER, os.path.basename(xcc_general_include_file)))) == 0:
project.add_file(xcc_general_include_file, parent=shadowGroup)
if len(project.get_files_by_os_path(os.path.join(XCODE_SUPPORT_FOLDER, XCC_GENERAL_INCLUDE))) == 0:
project.add_file(xcc_general_include_path, parent=shadowGroup)
def add_file(project, shadowGroup, sourceGroup, shadowHeaderPath, shadowImplementationFilePath, sourcePath, projectBasePath):
project.add_file(shadowHeaderPath, parent=shadowGroup)
project.add_file(shadowImplementationFilePath, parent=shadowGroup)
def file_with_path(path, projectPath, project):
relPath = os.path.relpath(path, projectPath)
if sourcePath in project.get_files_by_os_path(os.path.relpath(sourcePath, projectBasePath)):
return
for fileRef in [f for f in project.objects.values() if f.get("isa") == "PBXFileReference"]:
filePath = path if fileRef.get("sourceTree") == "<absolute>" else relPath
project.add_file(sourcePath, parent=sourceGroup)
if fileRef.get("path") == filePath:
return fileRef
def remove_file(project, shadowGroup, sourceGroup, shadowHeaderPath, shadowImplementationFilePath, sourcePath, projectBasePath):
project.remove_file(os.path.join(XCODESUPPORTFOLDER, os.path.basename(shadowHeaderPath)), parent=shadowGroup)
project.remove_file(os.path.join(XCODESUPPORTFOLDER, os.path.basename(shadowImplementationFilePath)), parent=shadowGroup)
project.remove_file(os.path.relpath(sourcePath, projectBasePath), parent=sourceGroup)
return None
def add_file(project, shadowGroup, sourceGroup, shadowHeaderPath, shadowImplementationPath, sourcePath, projectBasePath):
# Shadow files are always project-relative
if not file_with_path(shadowHeaderPath, projectBasePath, project):
project.add_file(shadowHeaderPath, parent=shadowGroup, tree="SOURCE_ROOT", create_build_files=False)
if not file_with_path(shadowImplementationPath, projectBasePath, project):
project.add_file(shadowImplementationPath, parent=shadowGroup, tree="SOURCE_ROOT", create_build_files=False)
# If the file is within the project directory, the file reference will be project-relative, otherwise absolute
if sourcePath.startswith(projectBasePath):
tree = "SOURCE_ROOT"
else:
tree = "<absolute>"
if not file_with_path(sourcePath, projectBasePath, project):
project.add_file(sourcePath, parent=sourceGroup, tree=tree, create_build_files=False)
def remove_file(project, shadowGroup, sourceGroup, shadowHeaderPath, shadowImplementationPath, sourcePath, projectBasePath):
for path in (shadowHeaderPath, shadowImplementationPath, sourcePath):
fileRef = file_with_path(path, projectBasePath, project)
if fileRef:
project.remove_file(fileRef)
def xml_converter(matchObj):
return "<string>{0}</string>".format(matchObj.group(1).encode('ascii', 'xmlcharrefreplace'))
@@ -66,6 +90,10 @@ def add_framework_resources(project, resourcesPath):
framework = os.path.basename(os.path.dirname(resourcesPath))
files[0]['name'] = framework + " Resources"
def save_project(project, pbxPath):
project.save()
convert_unicode_to_xml(pbxPath)
if __name__ == "__main__":
@@ -76,10 +104,10 @@ if __name__ == "__main__":
projectSourcePath = unicode(sys.argv[3])
sourcePath = os.path.realpath(projectSourcePath)
shadowBasePath = os.path.join(projectBasePath, ".XcodeSupport")
shadowBasename = os.path.splitext(sourcePath)[0].replace(u"/", SLASH_REPLACEMENT)
shadowHeaderPath = os.path.join(shadowBasePath, shadowBasename + ".h")
shadowImplementationPath = os.path.join(shadowBasePath, shadowBasename + ".m")
shadowBasePath = os.path.join(projectBasePath, XCODE_SUPPORT_FOLDER)
shadowBaseName = os.path.splitext(sourcePath)[0].replace(u"/", SLASH_REPLACEMENT)
shadowHeaderPath = os.path.join(shadowBasePath, shadowBaseName + ".h")
shadowImplementationPath = os.path.join(shadowBasePath, shadowBaseName + ".m")
projectName = os.path.basename(projectBasePath)
pbxPath = os.path.join(projectBasePath, projectName + ".xcodeproj", "project.pbxproj")
@@ -88,11 +116,11 @@ if __name__ == "__main__":
shadowGroup = project.get_or_create_group("Classes")
sourceGroup = project.get_or_create_group("Sources")
files = project.get_files_by_os_path(os.path.join(XCODESUPPORTFOLDER, os.path.basename(shadowHeaderPath)))
if action == "add":
if len(files) == 0:
update_general_include(project, projectBasePath)
fileRef = file_with_path(shadowHeaderPath, projectBasePath, project)
if not fileRef:
update_general_include(project, projectBasePath, shadowGroup)
add_file(project, shadowGroup, sourceGroup, shadowHeaderPath, shadowImplementationPath, sourcePath, projectBasePath)
match = FRAMEWORKS_RE.match(projectSourcePath)
@@ -104,10 +132,9 @@ if __name__ == "__main__":
if os.path.isdir(resourcesPath):
add_framework_resources(project, resourcesPath)
project.save()
convert_unicode_to_xml(pbxPath)
save_project(project, pbxPath)
elif action == "remove" and len(files) == 1:
update_general_include(project, projectBasePath)
elif action == "remove":
update_general_include(project, projectBasePath, shadowGroup)
remove_file(project, shadowGroup, sourceGroup, shadowHeaderPath, shadowImplementationPath, sourcePath, projectBasePath)
project.save()
save_project(project, pbxPath)