diff --git a/Tools/XcodeCapp/XcodeCapp/Debug.xcconfig b/Tools/XcodeCapp/XcodeCapp/Debug.xcconfig new file mode 100644 index 000000000..9ec35ffbf --- /dev/null +++ b/Tools/XcodeCapp/XcodeCapp/Debug.xcconfig @@ -0,0 +1,41 @@ +// +// Debug.xcconfig +// XcodeCapp +// +// Created by Aparajita on 4/18/13. +// +// + +//:configuration = Debug +ARCHS = $(ARCHS_STANDARD_64_BIT) +SDKROOT = macosx +ONLY_ACTIVE_ARCH = YES +DEBUG_INFORMATION_FORMAT = dwarf-with-dsym +COMBINE_HIDPI_IMAGES = YES +INSTALL_PATH = $(LOCAL_APPS_DIR) +MACOSX_DEPLOYMENT_TARGET = 10.6.8 +COPY_PHASE_STRIP = NO +INFOPLIST_FILE = XcodeCapp/Info.plist +PRODUCT_NAME = XcodeCapp +ALWAYS_SEARCH_USER_PATHS = NO +FRAMEWORK_SEARCH_PATHS = $(inherited) "$(SRCROOT)/XcodeCapp" +GCC_OPTIMIZATION_LEVEL = 0 +CLANG_ENABLE_OBJC_ARC = YES +GCC_PRECOMPILE_PREFIX_HEADER = YES +GCC_PREFIX_HEADER = XcodeCapp/XcodeCapp-Prefix.pch +GCC_PREPROCESSOR_DEFINITIONS = DEBUG=1 +GCC_WARN_ABOUT_RETURN_TYPE = YES +GCC_WARN_UNUSED_VARIABLE = YES +WRAPPER_EXTENSION = app +GCC_C_LANGUAGE_STANDARD = gnu99 +CLANG_CXX_LANGUAGE_STANDARD = gnu++0x +CLANG_CXX_LIBRARY = libc++ +CLANG_WARN_EMPTY_BODY = YES +CLANG_WARN_CONSTANT_CONVERSION = YES +GCC_WARN_64_TO_32_BIT_CONVERSION = YES +CLANG_WARN_ENUM_CONVERSION = YES +CLANG_WARN_INT_CONVERSION = YES +GCC_WARN_ABOUT_RETURN_TYPE = YES +GCC_WARN_UNINITIALIZED_AUTOS = YES +GCC_WARN_UNUSED_VARIABLE = YES +CLANG_WARN__DUPLICATE_METHOD_MATCH = YES diff --git a/Tools/XcodeCapp/XcodeCapp/Release.xcconfig b/Tools/XcodeCapp/XcodeCapp/Release.xcconfig new file mode 100644 index 000000000..047a9cf0d --- /dev/null +++ b/Tools/XcodeCapp/XcodeCapp/Release.xcconfig @@ -0,0 +1,41 @@ +// +// Release.xcconfig +// XcodeCapp +// +// Created by Aparajita on 4/18/13. +// +// + +//:configuration = Release +ARCHS = $(ARCHS_STANDARD_64_BIT) +SDKROOT = macosx +ONLY_ACTIVE_ARCH = YES +DEBUG_INFORMATION_FORMAT = dwarf +COMBINE_HIDPI_IMAGES = YES +INSTALL_PATH = $(LOCAL_APPS_DIR) +MACOSX_DEPLOYMENT_TARGET = 10.6.8 +COPY_PHASE_STRIP = YES +INFOPLIST_FILE = XcodeCapp/Info.plist +PRODUCT_NAME = XcodeCapp +ALWAYS_SEARCH_USER_PATHS = NO +FRAMEWORK_SEARCH_PATHS = $(inherited) "$(SRCROOT)/XcodeCapp" +GCC_OPTIMIZATION_LEVEL = s +CLANG_ENABLE_OBJC_ARC = YES +GCC_PRECOMPILE_PREFIX_HEADER = YES +GCC_PREFIX_HEADER = XcodeCapp/XcodeCapp-Prefix.pch +GCC_PREPROCESSOR_DEFINITIONS = +GCC_WARN_ABOUT_RETURN_TYPE = YES +GCC_WARN_UNUSED_VARIABLE = YES +WRAPPER_EXTENSION = app +GCC_C_LANGUAGE_STANDARD = gnu99 +CLANG_CXX_LANGUAGE_STANDARD = gnu++0x +CLANG_CXX_LIBRARY = libc++ +CLANG_WARN_EMPTY_BODY = YES +CLANG_WARN_CONSTANT_CONVERSION = YES +GCC_WARN_64_TO_32_BIT_CONVERSION = YES +CLANG_WARN_ENUM_CONVERSION = YES +CLANG_WARN_INT_CONVERSION = YES +GCC_WARN_ABOUT_RETURN_TYPE = YES +GCC_WARN_UNINITIALIZED_AUTOS = YES +GCC_WARN_UNUSED_VARIABLE = YES +CLANG_WARN__DUPLICATE_METHOD_MATCH = YES diff --git a/Tools/XcodeCapp/XcodeCapp/Scripts/mod_pbxproj.py b/Tools/XcodeCapp/XcodeCapp/Scripts/mod_pbxproj.py new file mode 100755 index 000000000..6af471f01 --- /dev/null +++ b/Tools/XcodeCapp/XcodeCapp/Scripts/mod_pbxproj.py @@ -0,0 +1,1269 @@ +# 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 +# +# 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. + +# A pbxproj file is an OpenStep format plist +# {} represents dictionary of key=value pairs delimited by ; +# () represents list of values delimited by , +# file starts with a comment specifying the character type +# // !$*UTF8*$! + +# when adding a file to a project, create the PBXFileReference +# add the PBXFileReference's guid to a group +# create a PBXBuildFile with the PBXFileReference's guid +# add the PBXBuildFile to the appropriate build phase + +# when adding a header search path add +# HEADER_SEARCH_PATHS = "path/**"; +# to each XCBuildConfiguration object + +# Xcode4 will read either a OpenStep or XML plist. +# this script uses `plutil` to validate, read and write +# the pbxproj file. Plutil is available in OS X 10.2 and higher +# Plutil can't write OpenStep plists, so I save as XML + +import re, uuid, sys, os, shutil, subprocess, datetime, json, types, re, ntpath + +from UserDict import IterableUserDict +from UserList import UserList +from xml.dom.minidom import * + +regex = '[a-zA-Z0-9\\._/-]*' + +class PBXEncoder(json.JSONEncoder): + + def default(self, obj): + """Tests the input object, obj, to encode as JSON.""" + + if isinstance(obj, (PBXList, PBXDict)): + return obj.data + + return json.JSONEncoder.default(self, obj) + + +class PBXDict(IterableUserDict): + def __init__(self, d=None): + if d: + d = dict([(PBXType.Convert(k),PBXType.Convert(v)) for k,v in d.items()]) + + IterableUserDict.__init__(self, d) + + def __setitem__(self, key, value): + IterableUserDict.__setitem__(self, PBXType.Convert(key), PBXType.Convert(value)) + + def remove(self, key): + self.data.pop(PBXType.Convert(key), None) + + + +class PBXList(UserList): + def __init__(self, l=None): + if isinstance(l, basestring): + UserList.__init__(self) + self.add(l) + return + elif l: + l = [PBXType.Convert(v) for v in l] + + UserList.__init__(self, l) + + def add(self, value): + value = PBXType.Convert(value) + + if value in self.data: + return False + + self.data.append(value) + return True + + def remove(self, value): + value = PBXType.Convert(value) + + if value in self.data: + self.data.remove(value) + + def __setitem__(self, key, value): + UserList.__setitem__(self, PBXType.Convert(key), PBXType.Convert(value)) + + +class PBXType(PBXDict): + def __init__(self, d=None): + PBXDict.__init__(self, d) + + if not self.has_key('isa'): + self['isa'] = self.__class__.__name__ + self.id = None + + @staticmethod + def Convert(o): + if isinstance(o, list): + return PBXList(o) + elif isinstance(o, dict): + isa = o.get('isa') + + if not isa: + return PBXDict(o) + + cls = globals().get(isa) + + if cls and issubclass(cls, PBXType): + return cls(o) + + print 'warning: unknown PBX type: %s' % isa + return PBXDict(o) + else: + return o + + @staticmethod + def IsGuid(o): + return re.match('^[A-F0-9]{24}$', str(o)) + + @classmethod + def GenerateId(cls): + return ''.join(str(uuid.uuid4()).upper().split('-')[1:]) + + @classmethod + def Create(cls, *args, **kwargs): + return cls(*args, **kwargs) + + +class PBXFileReference(PBXType): + def __init__(self, d=None): + PBXType.__init__(self, d) + 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') + } + + trees = [ + '', + '', + 'BUILT_PRODUCTS_DIR', + 'DEVELOPER_DIR', + 'SDKROOT', + 'SOURCE_ROOT', + ] + + def guess_file_type(self): + self.remove('explicitFileType') + self.remove('lastKnownFileType') + ext = os.path.splitext(self.get('name', ''))[1] + + f_type, build_phase = PBXFileReference.types.get(ext, ('?', None)) + + self['lastKnownFileType'] = f_type + self.build_phase = build_phase + + if f_type == '?': + print 'unknown file extension: %s' % ext + print 'please add extension and Xcode type to PBXFileReference.types' + + return f_type + + def set_file_type(self, ft): + self.remove('explicitFileType') + self.remove('lastKnownFileType') + + self['explicitFileType'] = ft + + @classmethod + def Create(cls, os_path, tree='SOURCE_ROOT'): + if tree not in cls.trees: + print 'Not a valid sourceTree type: %s' % tree + return None + + fr = cls() + fr.id = cls.GenerateId() + fr['path'] = os_path + fr['name'] = os.path.split(os_path)[1] + fr['sourceTree'] = '' if os.path.isabs(os_path) else tree + fr.guess_file_type() + + return fr + +class PBXBuildFile(PBXType): + def set_weak_link(self, weak=False): + k_settings = 'settings' + k_attributes = 'ATTRIBUTES' + + s = self.get(k_settings) + + if not s: + if weak: + self[k_settings] = PBXDict({k_attributes:PBXList(['Weak'])}) + + return True + + atr = s.get(k_attributes) + + if not atr: + if weak: + atr = PBXList() + else: + return False + + if weak: + atr.add('Weak') + else: + atr.remove('Weak') + + self[k_settings][k_attributes] = atr + + return True + + def add_compiler_flag(self, flag): + k_settings = 'settings' + k_attributes = 'COMPILER_FLAGS' + + if not self.has_key(k_settings): + self[k_settings] = PBXDict() + + if not self[k_settings].has_key(k_attributes): + self[k_settings][k_attributes] = flag + return True + + flags = self[k_settings][k_attributes].split(' ') + + if flag in flags: + return False + + flags.append(flag) + + self[k_settings][k_attributes] = ' '.join(flags) + + @classmethod + def Create(cls, file_ref, weak=False): + if isinstance(file_ref, PBXFileReference): + file_ref = file_ref.id + + bf = cls() + bf.id = cls.GenerateId() + bf['fileRef'] = file_ref + + if weak: + bf.set_weak_link(True) + + return bf + +class PBXGroup(PBXType): + def add_child(self, ref): + if not isinstance(ref, PBXDict): + return None + + isa = ref.get('isa') + + if isa != 'PBXFileReference' and isa != 'PBXGroup': + return None + + if not self.has_key('children'): + self['children'] = PBXList() + + self['children'].add(ref.id) + + return ref.id + + def remove_child(self, id): + if not self.has_key('children'): + self['children'] = PBXList() + return + + if not PBXType.IsGuid(id): + id = id.id + + self['children'].remove(id) + + def has_child(self, id): + if not self.has_key('children'): + self['children'] = PBXList() + return False + + if not PBXType.IsGuid(id): + id = id.id + + return id in self['children'] + + def get_name(self): + path_name = os.path.split(self.get('path',''))[1] + return self.get('name', path_name) + + @classmethod + def Create(cls, name, path=None, tree='SOURCE_ROOT'): + grp = cls() + grp.id = cls.GenerateId() + grp['name'] = name + grp['children'] = PBXList() + + if path: + grp['path'] = path + grp['sourceTree'] = tree + else: + grp['sourceTree'] = '' + + return grp + + +class PBXNativeTarget(PBXType): + pass + + +class PBXProject(PBXType): + pass + + +class PBXContainerItemProxy(PBXType): + pass + + +class PBXReferenceProxy(PBXType): + pass + + +class PBXVariantGroup(PBXType): + pass + + +class PBXBuildPhase(PBXType): + def add_build_file(self, bf): + if bf.get('isa') != 'PBXBuildFile': + return False + + if not self.has_key('files'): + self['files'] = PBXList() + + self['files'].add(bf.id) + + return True + + def remove_build_file(self, id): + if not self.has_key('files'): + self['files'] = PBXList() + return + + self['files'].remove(id) + + def has_build_file(self, id): + if not self.has_key('files'): + self['files'] = PBXList() + return False + + if not PBXType.IsGuid(id): + id = id.id + + return id in self['files'] + + +class PBXFrameworksBuildPhase(PBXBuildPhase): + pass + + +class PBXResourcesBuildPhase(PBXBuildPhase): + pass + + +class PBXShellScriptBuildPhase(PBXBuildPhase): + pass + + +class PBXSourcesBuildPhase(PBXBuildPhase): + pass + + +class PBXCopyFilesBuildPhase(PBXBuildPhase): + pass + + +class XCBuildConfiguration(PBXType): + def add_search_paths(self, paths, base, key, recursive=True, escape=True): + modified = False + + if not isinstance(paths, list): + paths = [paths] + + if not self.has_key(base): + self[base] = PBXDict() + + for path in paths: + if recursive and not path.endswith('/**'): + path = os.path.join(path, '**') + + if not self[base].has_key(key): + self[base][key] = PBXList() + elif isinstance(self[base][key], basestring): + self[base][key] = PBXList(self[base][key]) + + if escape : + if self[base][key].add('\\"%s\\"' % path):#'\\"%s\\"' % path + modified = True + else: + if self[base][key].add(path):#'\\"%s\\"' % path + modified = True + + return modified + + def add_header_search_paths(self, paths, recursive=True): + return self.add_search_paths(paths, 'buildSettings', 'HEADER_SEARCH_PATHS', recursive=recursive) + + def add_library_search_paths(self, paths, recursive=True): + return self.add_search_paths(paths, 'buildSettings', 'LIBRARY_SEARCH_PATHS', recursive=recursive) + + def add_framework_search_paths(self, paths, recursive=True): + return self.add_search_paths(paths, 'buildSettings', 'FRAMEWORK_SEARCH_PATHS', recursive=recursive, escape = False) + + def add_other_cflags(self, flags): + modified = False + + base = 'buildSettings' + key = 'OTHER_CFLAGS' + + if isinstance(flags, basestring): + flags = PBXList(flags) + + if not self.has_key(base): + self[base] = PBXDict() + + for flag in flags: + + if not self[base].has_key(key): + self[base][key] = PBXList() + elif isinstance(self[base][key], basestring): + self[base][key] = PBXList(self[base][key]) + + if self[base][key].add(flag): + self[base][key] = [e for e in self[base][key] if e] + modified = True + + return modified + + def add_other_ldflags(self, flags): + modified = False + + base = 'buildSettings' + key = 'OTHER_LDFLAGS' + + if isinstance(flags, basestring): + flags = PBXList(flags) + + if not self.has_key(base): + self[base] = PBXDict() + + for flag in flags: + + if not self[base].has_key(key): + self[base][key] = PBXList() + elif isinstance(self[base][key], basestring): + self[base][key] = PBXList(self[base][key]) + + if self[base][key].add(flag): + self[base][key] = [e for e in self[base][key] if e] + modified = True + + return modified + + def remove_other_ldflags(self, flags): + modified = False + + base = 'buildSettings' + key = 'OTHER_LDFLAGS' + + if isinstance(flags, basestring): + flags = PBXList(flags) + + if self.has_key(base): #there is flags, so we can "remove" something + for flag in flags: + if not self[base].has_key(key): + return False + elif isinstance(self[base][key], basestring): + self[base][key] = PBXList(self[base][key]) + + if self[base][key].remove(flag): + self[base][key] = [e for e in self[base][key] if e] + mofified = True + + return modified + +class XCConfigurationList(PBXType): + pass + + +class XcodeProject(PBXDict): + plutil_path = 'plutil' + special_folders = ['.bundle', '.framework', '.xcodeproj'] + + def __init__(self, d=None, path=None): + if not path: + path = os.path.join(os.getcwd(), 'project.pbxproj') + + self.pbxproj_path =os.path.abspath(path) + self.source_root = os.path.abspath(os.path.join(os.path.split(path)[0], '..')) + + IterableUserDict.__init__(self, d) + + self.data = PBXDict(self.data) + self.objects = self.get('objects') + self.modified = False + + root_id = self.get('rootObject') + if root_id: + self.root_object = self.objects[root_id] + root_group_id = self.root_object.get('mainGroup') + self.root_group = self.objects[root_group_id] + else: + print "error: project has no root object" + self.root_object = None + self.root_group = None + + for k,v in self.objects.iteritems(): + v.id = k + + def add_other_cflags(self, flags): + build_configs = [b for b in self.objects.values() if b.get('isa') == 'XCBuildConfiguration'] + + for b in build_configs: + if b.add_other_cflags(flags): + self.modified = True + + def add_other_ldflags(self, flags): + build_configs = [b for b in self.objects.values() if b.get('isa') == 'XCBuildConfiguration'] + + for b in build_configs: + if b.add_other_ldflags(flags): + self.modified = True + + def remove_other_ldflags(self, flags): + build_configs = [b for b in self.objects.values() if b.get('isa') == 'XCBuildConfiguration'] + + for b in build_configs: + if b.remove_other_ldflags(flags): + self.modified = True + + def add_header_search_paths(self, paths, recursive=True): + build_configs = [b for b in self.objects.values() if b.get('isa') == 'XCBuildConfiguration'] + + for b in build_configs: + if b.add_header_search_paths(paths, recursive): + self.modified = True + + def add_framework_search_paths(self, paths, recursive=True): + build_configs = [b for b in self.objects.values() if b.get('isa') == 'XCBuildConfiguration'] + + for b in build_configs: + if b.add_framework_search_paths(paths, recursive): + self.modified = True + + def add_library_search_paths(self, paths, recursive=True): + build_configs = [b for b in self.objects.values() if b.get('isa') == 'XCBuildConfiguration'] + + for b in build_configs: + if b.add_library_search_paths(paths, recursive): + self.modified = True + + # TODO: need to return value if project has been modified + + def get_obj(self, id): + return self.objects.get(id) + + def get_ids(self): + return self.objects.keys() + + 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] + + 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)] + else: + files = [f for f in self.objects.values() if f.get('isa') == 'PBXFileReference' + 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] + + 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)] + else: + groups = [g for g in self.objects.values() if g.get('isa') == 'PBXGroup' + and g.get_name() == name] + + return groups + + def get_or_create_group(self, name, path=None, parent=None): + if not name: + return None + + if not parent: + parent = self.root_group + elif not isinstance(parent, PBXGroup): + # assume it's an id + parent = self.objects.get(parent, self.root_group) + + groups = self.get_groups_by_name(name) + + for grp in groups: + if parent.has_child(grp.id): + return grp + + grp = PBXGroup.Create(name, path) + parent.add_child(grp) + + self.objects[grp.id] = grp + + self.modified = True + + return grp + + def get_groups_by_os_path(self, path): + 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] + + return groups + + def get_build_phases(self, phase_name): + phases = [p for p in self.objects.values() if p.get('isa') == phase_name] + + return phases + + def get_relative_path(self, os_path): + return os.path.relpath(os_path, self.source_root) + + def verify_files(self, file_list, parent=None): + # returns list of files not in the current project. + if not file_list: + return [] + + if parent: + exists_list = [f.get('name') for f in self.objects.values() if f.get('isa') == 'PBXFileReference' and f.get('name') in file_list and parent.has_child(f)] + else: + exists_list = [f.get('name') for f in self.objects.values() if f.get('isa') == 'PBXFileReference' and f.get('name') in file_list] + + return set(file_list).difference(exists_list) + + def add_folder(self, os_path, parent=None, excludes=None, recursive=True, create_build_files=True): + if not os.path.isdir(os_path): + return [] + + if not excludes: + excludes = [] + + results = [] + + if not parent: + parent = self.root_group + elif not isinstance(parent, PBXGroup): + # assume it's an id + parent = self.objects.get(parent, self.root_group) + + path_dict = {os.path.split(os_path)[0]:parent} + special_list = [] + + for (grp_path, subdirs, files) in os.walk(os_path): + parent_folder, folder_name = os.path.split(grp_path) + parent = path_dict.get(parent_folder, parent) + + if [sp for sp in special_list if parent_folder.startswith(sp)]: + continue + + if folder_name.startswith('.'): + special_list.append(grp_path) + continue + + if os.path.splitext(grp_path)[1] in XcodeProject.special_folders: + # if this file has a special extension (bundle or framework mainly) treat it as a file + special_list.append(grp_path) + + new_files = self.verify_files([folder_name], parent=parent) + + if new_files: + results.extend(self.add_file(grp_path, parent, create_build_files=create_build_files)) + + continue + + # create group + grp = self.get_or_create_group(folder_name, path=self.get_relative_path(grp_path) , parent=parent) + path_dict[grp_path] = grp + + results.append(grp) + + file_dict = {} + + for f in files: + if f[0] == '.' or [m for m in excludes if re.match(m,f)]: + continue + + kwds = { + 'create_build_files': create_build_files, + 'parent': grp, + 'name': f + } + + f_path = os.path.join(grp_path, f) + + file_dict[f_path] = kwds + + new_files = self.verify_files([n.get('name') for n in file_dict.values()], parent=grp) + + add_files = [(k,v) for k,v in file_dict.items() if v.get('name') in new_files] + + for path, kwds in add_files: + kwds.pop('name', None) + + self.add_file(path, **kwds) + + if not recursive: + break + + for r in results: + self.objects[r.id] = r + + return results + + def path_leaf(self,path): + head, tail = ntpath.split(path) + return tail or ntpath.basename(head) + + def add_file_if_doesnt_exist(self, f_path, parent=None, tree='SOURCE_ROOT', create_build_files=True, weak=False): + for obj in self.objects.values() : + if 'path' in obj : + if self.path_leaf(f_path) == self.path_leaf(obj.get('path')): + return [] + + return self.add_file(f_path, parent, tree, create_build_files, weak) + + def add_file(self, f_path, parent=None, tree='SOURCE_ROOT', create_build_files=True, weak=False): + results = [] + + abs_path = '' + + if os.path.isabs(f_path): + abs_path = f_path + + if not os.path.exists(f_path): + return results + elif tree == 'SOURCE_ROOT': + f_path = os.path.relpath(f_path, self.source_root) + else: + tree = '' + + if not parent: + parent = self.root_group + elif not isinstance(parent, PBXGroup): + # assume it's an id + parent = self.objects.get(parent, self.root_group) + + file_ref = PBXFileReference.Create(f_path, tree) + parent.add_child(file_ref) + results.append(file_ref) + # create a build file for the file ref + if file_ref.build_phase and create_build_files: + phases = self.get_build_phases(file_ref.build_phase) + + for phase in phases: + build_file = PBXBuildFile.Create(file_ref, weak=weak) + + phase.add_build_file(build_file) + results.append(build_file) + + if abs_path and tree == 'SOURCE_ROOT' and os.path.isfile(abs_path)\ + 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': + + framework_path = os.path.join('$(SRCROOT)', os.path.split(f_path)[0]) + self.add_framework_search_paths([framework_path,'$(inherited)'], recursive=False) + + for r in results: + self.objects[r.id] = r + + if results: + self.modified = True + + return results + + def check_and_repair_framework(self, base): + name = os.path.basename(base); + if(".framework" in name): + basename = name[:-len(".framework")] + + finalHeaders = os.path.join(base, "Headers"); + finalCurrent = os.path.join(base, "Versions/Current"); + finalLib = os.path.join(base, basename); + srcHeaders = "Versions/A/Headers"; + srcCurrent = "A"; + srcLib = "Versions/A/"+basename; + + if(not os.path.exists(finalHeaders)): + os.symlink(srcHeaders, finalHeaders); + if(not os.path.exists(finalCurrent)): + os.symlink(srcCurrent, finalCurrent); + if(not os.path.exists(finalLib)): + os.symlink(srcLib, finalLib); + + + def remove_group(self, grp): + pass + + def remove_file(self, id): + pass + + def move_file(self, id, dest_grp=None): + pass + + def apply_patch(self, patch_path, xcode_path): + if not os.path.isfile(patch_path) or not os.path.isdir(xcode_path): + print 'ERROR: couldn\'t apply "%s" to "%s"' % (patch_path, xcode_path) + return + + print 'applying "%s" to "%s"' % (patch_path, xcode_path) + + return subprocess.call(['patch', '-p1', '--forward', '--directory=%s'%xcode_path, '--input=%s'%patch_path]) + + def apply_mods(self, mod_dict, default_path=None): + if not default_path: + default_path = os.getcwd() + + keys = mod_dict.keys() + + for k in keys: + v = mod_dict.pop(k) + + mod_dict[k.lower()] = v + + parent = mod_dict.pop('group', None) + + if parent: + parent = self.get_or_create_group(parent) + + excludes = mod_dict.pop('excludes', []) + + if excludes: + excludes = [re.compile(e) for e in excludes] + + compiler_flags = mod_dict.pop('compiler_flags', {}) + + for k,v in mod_dict.items(): + if k == 'patches': + for p in v: + if not os.path.isabs(p): + p = os.path.join(default_path, p) + + self.apply_patch(p, self.source_root) + elif k == 'folders': + # get and compile excludes list + # do each folder individually + for folder in v: + kwds = {} + + # if path contains ':' remove it and set recursive to False + if ':' in folder: + args = folder.split(':') + kwds['recursive'] = False + folder = args.pop(0) + + if os.path.isabs(folder) and os.path.isdir(folder): + pass + else: + folder = os.path.join(default_path, folder) + if not os.path.isdir(folder): + continue + + if parent: + kwds['parent'] = parent + + if excludes: + kwds['excludes'] = excludes + + self.add_folder(folder, **kwds) + elif k == 'headerpaths' or k == 'librarypaths': + paths = [] + + for p in v: + if p.endswith('/**'): + p = os.path.split(p)[0] + + if not os.path.isabs(p): + p = os.path.join(default_path, p) + + if not os.path.exists(p): + continue + + p = self.get_relative_path(p) + + paths.append(os.path.join('$(SRCROOT)', p, "**")) + + if k == 'headerpaths': + self.add_header_search_paths(paths) + else: + self.add_library_search_paths(paths) + elif k == 'other_cflags': + self.add_other_cflags(v) + elif k == 'other_ldflags': + self.add_other_ldflags(v) + elif k == 'libs' or k == 'frameworks' or k == 'files': + paths = {} + + for p in v: + kwds = {} + + if ':' in p: + args = p.split(':') + p = args.pop(0) + + if 'weak' in args: + kwds['weak'] = True + + file_path = os.path.join(default_path, p) + search_path, file_name = os.path.split(file_path) + + if [m for m in excludes if re.match(m,file_name)]: + continue + + try: + expr = re.compile(file_name) + except re.error: + expr = None + + if expr and os.path.isdir(search_path): + file_list = os.listdir(search_path) + + for f in file_list: + if [m for m in excludes if re.match(m,f)]: + continue + + if re.search(expr,f): + kwds['name'] = f + paths[os.path.join(search_path, f)] = kwds + p = None + + if k == 'libs': + kwds['parent'] = self.get_or_create_group('Libraries', parent=parent) + elif k == 'frameworks': + kwds['parent'] = self.get_or_create_group('Frameworks', parent=parent) + + if p: + kwds['name'] = file_name + + if k == 'libs': + p = os.path.join('usr','lib',p) + kwds['tree'] = 'SDKROOT' + elif k == 'frameworks': + p = os.path.join('System','Library','Frameworks',p) + kwds['tree'] = 'SDKROOT' + elif k == 'files' and not os.path.exists(file_path): + # don't add non-existent files to the project. + continue + + paths[p] = kwds + + new_files = self.verify_files([n.get('name') for n in paths.values()]) + + add_files = [(k,v) for k,v in paths.items() if v.get('name') in new_files] + + for path, kwds in add_files: + kwds.pop('name', None) + + if not kwds.has_key('parent') and parent: + kwds['parent'] = parent + + self.add_file(path, **kwds) + + if compiler_flags: + for k,v in compiler_flags.items(): + filerefs = [] + + for f in v: + filerefs.extend([fr.id for fr in self.objects.values() if fr.get('isa') == 'PBXFileReference' + and fr.get('name') == f]) + + + buildfiles = [bf for bf in self.objects.values() if bf.get('isa') == 'PBXBuildFile' + and bf.get('fileRef') in filerefs] + + for bf in buildfiles: + if bf.add_compiler_flag(k): + self.modified = True + + + def backup(self, file_name=None): + if not file_name: + file_name = self.pbxproj_path + + backup_name = "%s.%s.backup" % (file_name, datetime.datetime.now().strftime('%d%m%y-%H%M%S')) + + shutil.copy2(file_name, backup_name) + + #old format + def save(self, file_name=None): + if not file_name: + file_name = self.pbxproj_path + + # JSON serialize the project and convert that json to an xml plist + p = subprocess.Popen([XcodeProject.plutil_path, '-convert', 'xml1', '-o', file_name, '-'], stdin=subprocess.PIPE) + p.communicate(PBXEncoder().encode(self.data)) + + #save Xcode 3.2 compatible format + def saveFormat3_2(self, file_name=None): + if not file_name: + file_name = self.pbxproj_path + + #process to get the section's info and names + objs = self.data.get('objects'); + sections = dict(); + uuids = dict(); + for key in objs: + l = list(); + if objs.get(key).get('isa') in sections: + l = sections.get(objs.get(key).get('isa')) + l.append(tuple([key, objs.get(key)])) + sections[objs.get(key).get('isa')] = l; + + if('name' in objs.get(key)): + uuids[key] = objs.get(key).get('name') + elif('path' in objs.get(key)): + uuids[key] = objs.get(key).get('path') + else: + if(objs.get(key).get('isa')=='PBXProject'): + uuids[objs.get(key).get('buildConfigurationList')] = 'Build configuration list for PBXProject "Unity-iPhone"' + elif(objs.get(key).get('isa')[0:3] == 'PBX'): + uuids[key] = objs.get(key).get('isa')[3:-10] + else: + uuids[key] = 'Build configuration list for PBXNativeTarget "TARGET_NAME"' + + ro = self.data.get('rootObject') + uuids[ro] = 'Project Object' + + for key in objs: + #transitive references (used in the BuildFile section) + if('fileRef' in objs.get(key) and objs.get(key).get('fileRef') in uuids): + uuids[key] = uuids[objs.get(key).get('fileRef')] + #transitive reference to the target name (used in the Native target section) + if(objs.get(key).get('isa') == 'PBXNativeTarget'): + uuids[objs.get(key).get('buildConfigurationList')] = uuids[objs.get(key).get('buildConfigurationList')].replace('TARGET_NAME',uuids[key]) + + self.uuids = uuids; + self.sections = sections; + + out = open(file_name,'w') + out.write('// !$*UTF8*$!\n'); + self._printNewXCodeFormat(out, self.data, '', enters=True) + out.close() + + @classmethod + def addslashes(cls,s): + d = {'"':'\\"', "'":"\\'", "\0":"\\\0", "\\":"\\\\"} + return ''.join(d.get(c, c) for c in s) + + def _printNewXCodeFormat(self, out, root, deep, enters = True): + if(isinstance(root,IterableUserDict)): + out.write('{') + if enters: + out.write('\n') + + isa = root.pop('isa','') + if(isa!=''): #keep the isa in the first spot + if enters: + out.write('\t'+deep) + out.write('isa = '); + self._printNewXCodeFormat(out,isa,'\t'+deep, enters=enters); + out.write(';') + if enters: + out.write('\n') + else: + out.write(' '); + + for key in sorted(root.iterkeys()): #keep the same order as Apple. + if enters: + out.write('\t'+deep) + + if re.match(regex,key).group(0) == key: + out.write(key+' = '); + else: + out.write('"'+key+'" = '); + + if key == 'objects': + out.write('{');#open the objects section + if enters: + out.write('\n') + #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)] + + 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. + + for pair in self.sections.get(section[0]): + key = pair[0] + value = pair[1] + out.write('\n') + if enters: + out.write('\t\t'+deep) + out.write(key); + if(key in self.uuids): + out.write(" /* "+self.uuids[key]+" */"); + out.write(" = "); + self._printNewXCodeFormat(out,value,'\t\t'+deep,enters=section[1]) + out.write(';') + out.write('\n/* End %s section */\n'%section[0]) + out.write(deep+'\t}');#close of the objects section + else: + self._printNewXCodeFormat(out,root[key],'\t'+deep,enters=enters) + + out.write(';') + if enters: + out.write('\n') + else: + out.write(' ') + root['isa']=isa; #restore the isa for further calls + + if enters: + out.write(deep) + out.write('}') + elif isinstance(root,UserList): + out.write('(') + if enters: + out.write('\n') + for value in (root): + if enters: + out.write('\t'+deep) + self._printNewXCodeFormat(out,value,'\t'+deep,enters=enters) + out.write(',') + if enters: + out.write('\n') + if enters: + out.write(deep) + out.write(')') + else: + if len(root) > 0 and re.match(regex,root).group(0) == root: + out.write(root) + else: + out.write('"'+XcodeProject.addslashes(root)+'"') + if(root in self.uuids): + out.write(" /* "+self.uuids[root]+" */"); + + @classmethod + def getJSONFromXML(cls, root): + result = '' + if(root.nodeName == 'dict'): + result += '{' + i=0; + for child in root.childNodes: + if child.nodeType != Node.ELEMENT_NODE: + continue; + if(child.nodeName=='key' and i>0): + result +=',' + result += XcodeProject.getJSONFromXML(child); + i = i + 1 + result += '}' + elif root.nodeName == 'key': + for node in root.childNodes: + if node.nodeType == node.TEXT_NODE: + result += '"'+node.data+'":' + break + elif root.nodeName == 'array': + result += '[' + i=0; + for child in root.childNodes: + if child.nodeType != Node.ELEMENT_NODE: + continue; + + if(i>0): + result += "," + result += XcodeProject.getJSONFromXML(child); + i = i+1; + result += ']' + elif root.nodeName == 'string': + data = '""' + for node in root.childNodes: + if node.nodeType == node.TEXT_NODE: + data = '"'+XcodeProject.addslashes(node.data).replace('\n','\\n')+'"' + 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) diff --git a/Tools/XcodeCapp/XcodeCapp/Scripts/parser.j b/Tools/XcodeCapp/XcodeCapp/Scripts/parser.j new file mode 100644 index 000000000..43040dab9 --- /dev/null +++ b/Tools/XcodeCapp/XcodeCapp/Scripts/parser.j @@ -0,0 +1,584 @@ +/* + * parser.j + * + * Created by Francisco Tolmasky. + * Modified by Antoine Mercadal, with great help of Martin Carlberg + * Copyright 2008-2013, 280 North, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +@import + +var FILE = require("file"), + OS = require("os"); + +// Debug function to print some JS objects +function dump(obj) +{ + print(JSON.stringify(obj)); +} + +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, + 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."); + hasWarnings = true; + return; + } + + var className = node.classname.name, + superclassname = node.superclassname.name, + declaredOutletsName = [], + classInfo = { + "name": className, + "superClass": superclassname, + "outlets": [], + "actions": [], + "actionNames": [] + }; + + if (node.ivardeclarations) + { + for (var i = 0; i < node.ivardeclarations.length; ++i) + { + var ivarDecl = node.ivardeclarations[i], + ivarType = ivarDecl.ivartype ? ivarDecl.ivartype.name : null, + ivarName = ivarDecl.id.name, + ivarHasOutlet = ivarDecl.outlet ? "@outlet" : null; + + if (ivarHasOutlet) + { + if (declaredOutletsName.indexOf(ivarName) !== -1) + raise(ivarDecl.loc.start, "Outlet named '" + ivarName + "' is declared multiple times."); + + declaredOutletsName.push(ivarName); + classInfo.outlets.push({"type": ivarType, "name": ivarName}); + } + } + } + + st.push(classInfo) + + for (var i = 0; i < node.body.length; ++i) + c(node.body[i], classInfo, "Statement"); + }, + + MethodDeclarationStatement: function(node, st, c) + { + var selectors = node.selectors, + arguments = node.arguments, + methodReturnType = [node.returntype ? node.returntype.name : "id"], + methodHasAction = node.action ? "IBAction" : null, + selector = selectors[0].name, + actionInformations = {"name": selector, "arguments":[]}; + + if (methodHasAction && arguments.length == 1) + { + if (st.actionNames.indexOf(selector) !== -1) + raise(node.loc.start, "Action named '" + selector + "' is declared multiple times."); + + st.actionNames.push(selector); + + for (var i = 0; i < arguments.length; i++) + { + var argument = arguments[i], + argumentName = argument.identifier.name, + argumentType = argument.type ? argument.type.name : null; + + actionInformations.arguments.push({"type": argumentType, "name": argumentName}); + } + + st.actions.push(actionInformations) + } + else if (methodHasAction) + raise(node.loc.start, "Method '" + selector + "' is an action but has more than one parameter."); + } + } +); + +function compile(node, state, visitor) +{ + function c(node, st, override) + { + visitor[override || node.type](node, st, c); + } + + c(node, state); +}; + + +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"), + 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() }), + classesInformation = [], + ObjectiveCSource = "", + ObjectiveCHeader = ""; + + compile(tokens, classesInformation, xcc); + + // dump(classesInformation) + + ObjectiveCHeader += + "#import \n" + + "#import \n" + + "#import \"xcc_general_include.h\"\n\n"; + + ObjectiveCSource += "#import \"" + outputHeaderURL.lastPathComponent() + "\"\n\n"; + + // Traverse each found classes + classesInformation.forEach(function(aClass) + { + // add new class definition + ObjectiveCHeader += "@interface " + aClass.name + " : " + NSCompatibleClassName(aClass.superClass) + "\n\n"; + + // Add each outlets in header + aClass.outlets.forEach(function(anOutlet) + { + ObjectiveCHeader += "@property (assign) IBOutlet " + NSCompatibleClassName(anOutlet.type, YES) + " " + anOutlet.name + ";\n"; + }); + + ObjectiveCHeader += "\n"; + + // Add each actions in header + aClass.actions.forEach(function(anAction) + { + ObjectiveCHeader += "- (IBAction)" + anAction.name + ":(" + anAction.arguments[0].type + ")" + anAction.arguments[0].name + ";\n"; + }); + + ObjectiveCHeader += "\n@end\n\n\n"; + + // fill up the implementation file + ObjectiveCSource += "@implementation " + aClass.name + "\n@end\n\n"; + }); + + // write files + if (ObjectiveCSource.length) + FILE.write(outputSourceURL, ObjectiveCSource, { 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); + } + + if (hasWarnings) + OS.exit(2); +} + +function NSCompatibleClassName(aClassName, asPointer) +{ + if (aClassName === "var" || aClassName === "id") + return "id"; + + var prefix = aClassName.substr(0, 2), + asterisk = asPointer ? "*" : ""; + + if (prefix !== "CP") + return aClassName + asterisk; + + var NSClassName = "NS" + aClassName.substr(2); + + if (NSClasses[NSClassName]) + return NSClassName + asterisk; + + if (ReplacementClasses[aClassName]) + return ReplacementClasses[aClassName] + asterisk; + + return aClassName + asterisk; +} + +var ReplacementClasses = { + "CPWebView": "WebView", + "CPRadio": "NSButtonCell", + "CPRadioGroup": "NSMatrix" + }; + +var NSClasses = { + "NSAffineTransform" : YES, + "NSAppleEventDescriptor" : YES, + "NSAppleEventManager" : YES, + "NSAppleScript" : YES, + "NSArchiver" : YES, + "NSArray" : YES, + "NSAssertionHandler" : YES, + "NSAttributedString" : YES, + "NSAutoreleasePool" : YES, + "NSBlockOperation" : YES, + "NSBundle" : YES, + "NSCache" : YES, + "NSCachedURLResponse" : YES, + "NSCalendar" : YES, + "NSCharacterSet" : YES, + "NSClassDescription" : YES, + "NSCloneCommand" : YES, + "NSCloseCommand" : YES, + "NSCoder" : YES, + "NSComparisonPredicate" : YES, + "NSCompoundPredicate" : YES, + "NSCondition" : YES, + "NSConditionLock" : YES, + "NSConnection" : YES, + "NSCountCommand" : YES, + "NSCountedSet" : YES, + "NSCreateCommand" : YES, + "NSData" : YES, + "NSDate" : YES, + "NSDateComponents" : YES, + "NSDateFormatter" : YES, + "NSDecimalNumber" : YES, + "NSDecimalNumberHandler" : YES, + "NSDeleteCommand" : YES, + "NSDeserializer" : YES, + "NSDictionary" : YES, + "NSDirectoryEnumerator" : YES, + "NSDistantObject" : YES, + "NSDistantObjectRequest" : YES, + "NSDistributedLock" : YES, + "NSDistributedNotificationCenter" : YES, + "NSEnumerator" : YES, + "NSError" : YES, + "NSException" : YES, + "NSExistsCommand" : YES, + "NSExpression" : YES, + "NSFileHandle" : YES, + "NSFileManager" : YES, + "NSFileWrapper" : YES, + "NSFormatter" : YES, + "NSGarbageCollector" : YES, + "NSGetCommand" : YES, + "NSHashTable" : YES, + "NSHost" : YES, + "NSHTTPCookie" : YES, + "NSHTTPCookieStorage" : YES, + "NSHTTPURLResponse" : YES, + "NSIndexPath" : YES, + "NSIndexSet" : YES, + "NSIndexSpecifier" : YES, + "NSInputStream" : YES, + "NSInvocation" : YES, + "NSInvocationOperation" : YES, + "NSKeyedArchiver" : YES, + "NSKeyedUnarchiver" : YES, + "NSLocale" : YES, + "NSLock" : YES, + "NSLogicalTest" : YES, + "NSMachBootstrapServer" : YES, + "NSMachPort" : YES, + "NSMapTable" : YES, + "NSMessagePort" : YES, + "NSMessagePortNameServer" : YES, + "NSMetadataItem" : YES, + "NSMetadataQuery" : YES, + "NSMetadataQueryAttributeValueTuple" : YES, + "NSMetadataQueryResultGroup" : YES, + "NSMethodSignature" : YES, + "NSMiddleSpecifier" : YES, + "NSMoveCommand" : YES, + "NSMutableArray" : YES, + "NSMutableAttributedString" : YES, + "NSMutableCharacterSet" : YES, + "NSMutableData" : YES, + "NSMutableDictionary" : YES, + "NSMutableIndexSet" : YES, + "NSMutableSet" : YES, + "NSMutableString" : YES, + "NSMutableURLRequest" : YES, + "NSNameSpecifier" : YES, + "NSNetService" : YES, + "NSNetServiceBrowser" : YES, + "NSNotification" : YES, + "NSNotificationCenter" : YES, + "NSNotificationQueue" : YES, + "NSNull" : YES, + "NSNumber" : YES, + "NSNumberFormatter" : YES, + "NSObject" : YES, + "NSOperation" : YES, + "NSOperationQueue" : YES, + "NSOrthography" : YES, + "NSOutputStream" : YES, + "NSPipe" : YES, + "NSPointerArray" : YES, + "NSPointerFunctions" : YES, + "NSPort" : YES, + "NSPortCoder" : YES, + "NSPortMessage" : YES, + "NSPortNameServer" : YES, + "NSPositionalSpecifier" : YES, + "NSPredicate" : YES, + "NSProcessInfo" : YES, + "NSPropertyListSerialization" : YES, + "NSPropertySpecifier" : YES, + "NSProtocolChecker" : YES, + "NSProxy" : YES, + "NSPurgeableData" : YES, + "NSQuitCommand" : YES, + "NSRandomSpecifier" : YES, + "NSRangeSpecifier" : YES, + "NSRecursiveLock" : YES, + "NSRelativeSpecifier" : YES, + "NSRunLoop" : YES, + "NSScanner" : YES, + "NSScriptClassDescription" : YES, + "NSScriptCoercionHandler" : YES, + "NSScriptCommand" : YES, + "NSScriptCommandDescription" : YES, + "NSScriptExecutionContext" : YES, + "NSScriptObjectSpecifier" : YES, + "NSScriptSuiteRegistry" : YES, + "NSScriptWhoseTest" : YES, + "NSSerializer" : YES, + "NSSet" : YES, + "NSSetCommand" : YES, + "NSSocketPort" : YES, + "NSSocketPortNameServer" : YES, + "NSSortDescriptor" : YES, + "NSSpecifierTest" : YES, + "NSSpellServer" : YES, + "NSStream" : YES, + "NSString" : YES, + "NSTask" : YES, + "NSTextCheckingResult" : YES, + "NSThread" : YES, + "NSTimer" : YES, + "NSTimeZone" : YES, + "NSUnarchiver" : YES, + "NSUndoManager" : YES, + "NSUniqueIDSpecifier" : YES, + "NSURL" : YES, + "NSURLAuthenticationChallenge" : YES, + "NSURLCache" : YES, + "NSURLConnection" : YES, + "NSURLCredential" : YES, + "NSURLCredentialStorage" : YES, + "NSURLDownload" : YES, + "NSURLHandle" : YES, + "NSURLProtectionSpace" : YES, + "NSURLProtocol" : YES, + "NSURLRequest" : YES, + "NSURLResponse" : YES, + "NSUserDefaults" : YES, + "NSValue" : YES, + "NSValueTransformer" : YES, + "NSWhoseSpecifier" : YES, + "NSXMLDocument" : YES, + "NSXMLDTD" : YES, + "NSXMLDTDNode" : YES, + "NSXMLElement" : YES, + "NSXMLNode" : YES, + "NSXMLParser" : YES, + "NSActionCell" : YES, + "NSAffineTransform Additions" : YES, + "NSAlert" : YES, + "NSAnimation" : YES, + "NSAnimationContext" : YES, + "NSAppleScript Additions" : YES, + "NSApplication" : YES, + "NSArrayController" : YES, + "NSATSTypesetter" : YES, + "NSAttributedString Application Kit Additions" : YES, + "NSBezierPath" : YES, + "NSBitmapImageRep" : YES, + "NSBox" : YES, + "NSBrowser" : YES, + "NSBrowserCell" : YES, + "NSBundle Additions" : YES, + "NSButton" : YES, + "NSButtonCell" : YES, + "NSCachedImageRep" : YES, + "NSCell" : YES, + "NSCIImageRep" : YES, + "NSClipView" : YES, + "NSCoder Application Kit Additions" : YES, + "NSCollectionView" : YES, + "NSCollectionViewItem" : YES, + "NSColor" : YES, + "NSColorList" : YES, + "NSColorPanel" : YES, + "NSColorPicker" : YES, + "NSColorSpace" : YES, + "NSColorWell" : YES, + "NSComboBox" : YES, + "NSComboBoxCell" : YES, + "NSControl" : YES, + "NSController" : YES, + "NSCursor" : YES, + "NSCustomImageRep" : YES, + "NSDatePicker" : YES, + "NSDatePickerCell" : YES, + "NSDictionaryController" : YES, + "NSDockTile" : YES, + "NSDocument" : YES, + "NSDocumentController" : YES, + "NSDrawer" : YES, + "NSEPSImageRep" : YES, + "NSEvent" : YES, + "NSFileWrapper" : YES, + "NSFont" : YES, + "NSFontDescriptor" : YES, + "NSFontManager" : YES, + "NSFontPanel" : YES, + "NSForm" : YES, + "NSFormCell" : YES, + "NSGlyphGenerator" : YES, + "NSGlyphInfo" : YES, + "NSGradient" : YES, + "NSGraphicsContext" : YES, + "NSHelpManager" : YES, + "NSImage" : YES, + "NSImageCell" : YES, + "NSImageRep" : YES, + "NSImageView" : YES, + "NSLayoutManager" : YES, + "NSLevelIndicator" : YES, + "NSLevelIndicatorCell" : YES, + "NSMatrix" : YES, + "NSMenu" : YES, + "NSMenuItem" : YES, + "NSMenuItemCell" : YES, + "NSMenuView" : YES, + "NSMutableAttributedString Additions" : YES, + "NSMutableParagraphStyle" : YES, + "NSNib" : YES, + "NSNibConnector" : YES, + "NSNibControlConnector" : YES, + "NSNibOutletConnector" : YES, + "NSObjectController" : YES, + "NSOpenGLContext" : YES, + "NSOpenGLLayer" : YES, + "NSOpenGLPixelBuffer" : YES, + "NSOpenGLPixelFormat" : YES, + "NSOpenGLView" : YES, + "NSOpenPanel" : YES, + "NSOutlineView" : YES, + "NSPageLayout" : YES, + "NSPanel" : YES, + "NSParagraphStyle" : YES, + "NSPasteboard" : YES, + "NSPasteboardItem" : YES, + "NSPathCell" : YES, + "NSPathComponentCell" : YES, + "NSPathControl" : YES, + "NSPDFImageRep" : YES, + "NSPersistentDocument" : YES, + "NSPICTImageRep" : YES, + "NSPopUpButton" : YES, + "NSPopUpButtonCell" : YES, + "NSPredicateEditor" : YES, + "NSPredicateEditorRowTemplate" : YES, + "NSPrinter" : YES, + "NSPrintInfo" : YES, + "NSPrintOperation" : YES, + "NSPrintPanel" : YES, + "NSProgressIndicator" : YES, + "NSResponder" : YES, + "NSRuleEditor" : YES, + "NSRulerMarker" : YES, + "NSRulerView" : YES, + "NSRunningApplication" : YES, + "NSSavePanel" : YES, + "NSScreen" : YES, + "NSScroller" : YES, + "NSScrollView" : YES, + "NSSearchField" : YES, + "NSSearchFieldCell" : YES, + "NSSecureTextField" : YES, + "NSSecureTextFieldCell" : YES, + "NSSegmentedCell" : YES, + "NSSegmentedControl" : YES, + "NSShadow" : YES, + "NSSlider" : YES, + "NSSliderCell" : YES, + "NSSound" : YES, + "NSSpeechRecognizer" : YES, + "NSSpeechSynthesizer" : YES, + "NSSpellChecker" : YES, + "NSSplitView" : YES, + "NSStatusBar" : YES, + "NSStatusItem" : YES, + "NSStepper" : YES, + "NSStepperCell" : YES, + "NSString Application Kit Additions" : YES, + "NSTableColumn" : YES, + "NSTableHeaderCell" : YES, + "NSTableHeaderView" : YES, + "NSTableView" : YES, + "NSTabView" : YES, + "NSTabViewItem" : YES, + "NSText" : YES, + "NSTextAttachment" : YES, + "NSTextAttachmentCell" : YES, + "NSTextBlock" : YES, + "NSTextContainer" : YES, + "NSTextField" : YES, + "NSTextFieldCell" : YES, + "NSTextInputContext" : YES, + "NSTextList" : YES, + "NSTextStorage" : YES, + "NSTextTab" : YES, + "NSTextTable" : YES, + "NSTextTableBlock" : YES, + "NSTextView" : YES, + "NSTokenField" : YES, + "NSTokenFieldCell" : YES, + "NSToolbar" : YES, + "NSToolbarItem" : YES, + "NSToolbarItemGroup" : YES, + "NSTouch" : YES, + "NSTrackingArea" : YES, + "NSTreeController" : YES, + "NSTreeNode" : YES, + "NSTypesetter" : YES, + "NSURL Additions" : YES, + "NSUserDefaultsController" : YES, + "NSView" : YES, + "NSViewAnimation" : YES, + "NSViewController" : YES, + "NSWindow" : YES, + "NSWindowController" : YES, + "NSWorkspace" : YES, + "NSPopover": YES + }; diff --git a/Tools/XcodeCapp/XcodeCapp/Scripts/pbxprojModifier.py b/Tools/XcodeCapp/XcodeCapp/Scripts/pbxprojModifier.py new file mode 100755 index 000000000..09137f9d4 --- /dev/null +++ b/Tools/XcodeCapp/XcodeCapp/Scripts/pbxprojModifier.py @@ -0,0 +1,113 @@ +# -*- coding: utf-8 -*- +import os.path +import re +import sys +from mod_pbxproj import XcodeProject + +XCODESUPPORTFOLDER = ".XcodeSupport" +SLASH_REPLACEMENT = u"∕" # DIVISION SLASH Unicode U+2215 +STRING_RE = re.compile(ur"^\s*(.*)\s*$", re.MULTILINE) +FRAMEWORKS_RE = re.compile(ur"^(.+/Frameworks/Debug/([^/]+))/.+$") + + +def update_general_include(project, projectBasePath): + xcc_general_include_file = os.path.join(projectBasePath, XCODESUPPORTFOLDER, u"xcc_general_include.h") + 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))) + + f = open(xcc_general_include_file, "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) + +def add_file(project, shadowGroup, sourceGroup, shadowHeaderPath, shadowImplementationFilePath, sourcePath, projectBasePath): + project.add_file(shadowHeaderPath, parent=shadowGroup) + project.add_file(shadowImplementationFilePath, parent=shadowGroup) + + if sourcePath in project.get_files_by_os_path(os.path.relpath(sourcePath, projectBasePath)): + return + + project.add_file(sourcePath, parent=sourceGroup) + +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) + +def xml_converter(matchObj): + return "{0}".format(matchObj.group(1).encode('ascii', 'xmlcharrefreplace')) + +def convert_unicode_to_xml(path): + """ + mod_pbxproj writes unicode data as utf-8, but since it's an xml plist + all non-ascii characters should be converted to xml character references. + So we do that as a post-processing phase. + """ + with open(path, 'rb') as f: + content = f.read().decode('utf-8') + + with open(path, "wb") as f: + content = STRING_RE.sub(xml_converter, content) + f.write(content) + +def add_framework_resources(project, resourcesPath): + realPath = os.path.realpath(resourcesPath) + files = project.get_files_by_os_path(realPath, tree="") + + if not files: + files = project.add_file(realPath, parent=None, tree="", create_build_files=False) + + if files: + framework = os.path.basename(os.path.dirname(resourcesPath)) + files[0]['name'] = framework + " Resources" + + +if __name__ == "__main__": + + action = sys.argv[1] + + if action in ("add", "remove"): + projectBasePath = unicode(sys.argv[2]) + 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") + projectName = os.path.basename(projectBasePath) + pbxPath = os.path.join(projectBasePath, projectName + ".xcodeproj", "project.pbxproj") + + project = XcodeProject.Load(pbxPath) + + 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) + add_file(project, shadowGroup, sourceGroup, shadowHeaderPath, shadowImplementationPath, sourcePath, projectBasePath) + + match = FRAMEWORKS_RE.match(projectSourcePath) + + if match: + framework = match.group(2) + resourcesPath = os.path.join(match.group(1), "Resources") + + if os.path.isdir(resourcesPath): + add_framework_resources(project, resourcesPath) + + project.save() + convert_unicode_to_xml(pbxPath) + + elif action == "remove" and len(files) == 1: + update_general_include(project, projectBasePath) + remove_file(project, shadowGroup, sourceGroup, shadowHeaderPath, shadowImplementationPath, sourcePath, projectBasePath) + project.save() diff --git a/Tools/nib2cib/Converter.j b/Tools/nib2cib/Converter.j index ed9857fa1..549a32bdf 100644 --- a/Tools/nib2cib/Converter.j +++ b/Tools/nib2cib/Converter.j @@ -102,7 +102,7 @@ ConverterConversionException = @"ConverterConversionException"; if ([outputPath length]) FILE.write(outputPath, [convertedData rawString], { charset:"UTF-8" }); - CPLog.info(CPLogColorize("Conversion successful", "warn")); + CPLog.info("Conversion successful"); return convertedData; } diff --git a/Tools/nib2cib/Nib2Cib.j b/Tools/nib2cib/Nib2Cib.j index e3080b9f1..fbe1fa765 100644 --- a/Tools/nib2cib/Nib2Cib.j +++ b/Tools/nib2cib/Nib2Cib.j @@ -31,6 +31,9 @@ @import "Converter.j" @import "Converter+Mac.j" + +Nib2CibColorizeOutput = YES; + var FILE = require("file"), OS = require("os"), SYS = require("system"), @@ -51,6 +54,7 @@ var FILE = require("file"), { CPArray commandLineArgs; JSObject parser; + JSObject commandLineOptions; JSObject nibInfo; CPString appDirectory @accessors(readonly); CPDictionary frameworks @accessors(readonly); @@ -97,19 +101,25 @@ var FILE = require("file"), { try { - var options = [self parseOptionsFromArgs:commandLineArgs]; + commandLineOptions = [self parseOptionsFromArgs:commandLineArgs]; - [self setLogLevel:options.quiet ? -1 : options.verbosity]; + Nib2CibColorizeOutput = !commandLineOptions.noColors; + [self setLogLevel:commandLineOptions.quiet ? -1 : commandLineOptions.verbosity]; [self checkPrerequisites]; - if (options.watch) - [self watchWithOptions:options]; + if (commandLineOptions.watch) + [self watchWithOptions:commandLineOptions]; else - [self convertWithOptions:options inputPath:nil]; + { + var success = [self convertWithOptions:commandLineOptions inputPath:nil]; + + if (!success) + OS.exit(1); + } } catch (anException) { - CPLog.fatal([self exceptionReason:anException]); + [self logError:[self exceptionReason:anException]]; OS.exit(1); } } @@ -229,7 +239,7 @@ var FILE = require("file"), } catch (anException) { - CPLog.fatal([self exceptionReason:anException]); + [self logError:[self exceptionReason:anException]]; return NO; } } @@ -311,7 +321,7 @@ var FILE = require("file"), if (verbosity > 0) stream.print(); else - CPLog.warn("Conversion successful"); + CPLog.info("Conversion successful"); } } @@ -359,6 +369,11 @@ var FILE = require("file"), .def(true) .help("Do not read stored options"); + parser.option("--no-colors", "noColors") + .set(true) + .def(false) + .help("Don't colorize output"); + parser.option("--version", "showVersion") .action(function() { [self printVersionAndExit]; }) .help("Show the version of nib2cib and quit"); @@ -987,11 +1002,19 @@ var FILE = require("file"), [CPException raise:ConverterConversionException reason:message]; } +- (void)logError:(CPString)message +{ + if (Nib2CibColorizeOutput) + message = CPLogColorize(message, "fatal"); + + stream.printError(message); +} + @end function logFormatter(aString, aLevel, aTitle) { - if (aLevel === "info") + if (!Nib2CibColorizeOutput || aLevel === "info") return aString; else return CPLogColorize(aString, aLevel);