Commits

Alexey Petruchik committed c8a85da

XcodeProject initial implementation

  • Participants
  • Parent commits ed4c689

Comments (0)

Files changed (1)

File src/engine/SCons/Tool/xcodeproj.py

 
 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
 
-projectBuilder = None
+import os
+
+import SCons.Builder
+import SCons.Node.FS
+
+contents_xcworkspacedata = """\
+<?xml version="1.0" encoding="UTF-8"?>
+<Workspace
+   version = "1.0">
+   <FileRef
+      location = "self:%s">
+   </FileRef>
+</Workspace>
+"""
+
+project_pbxproj_header = """\
+// !$*UTF8*$!
+{
+    archiveVersion = 1;
+    classes = {
+    };
+    objectVersion = 46;
+    objects = {
+
+"""
+
+project_pbxproj_footer = """\
+    };
+    rootObject = %s;
+}
+"""
+
+MAP_EXT = {
+    '.h' :  "sourcecode.c.h",
+
+    '.hh':  "sourcecode.cpp.h",
+    '.inl': "sourcecode.cpp.h",
+    '.hpp': "sourcecode.cpp.h",
+
+    '.c':   "sourcecode.c.c",
+
+    '.m':   "sourcecode.c.objc",
+
+    '.mm':  "sourcecode.cpp.objcpp",
+
+    '.cc':  "sourcecode.cpp.cpp",
+
+    '.cpp': "sourcecode.cpp.cpp",
+    '.C':   "sourcecode.cpp.cpp",
+    '.cxx': "sourcecode.cpp.cpp",
+    '.c++': "sourcecode.cpp.cpp",
+
+    '.l':   "sourcecode.lex", # luthor
+    '.ll':  "sourcecode.lex",
+
+    '.y':   "sourcecode.yacc",
+    '.yy':  "sourcecode.yacc",
+
+    '.plist': "text.plist.xml",
+    ".nib":   "wrapper.nib",
+    ".xib":   "text.xib",
+    "": "",
+}
+
+class XcodeProjectBuildContext(object):
+    def __init__(self):
+        self.id = 562000999
+        self.nodes = {}
+        self.generate_codenav_targets = True
+
+    def newid(self):
+        self.id = self.id + 1
+        return "%04X%04X%04X%012d" % (0, 10000, 0, self.id)
+
+    def regNode(self, node):
+        type_name = node.__class__.__name__
+
+        if not self.nodes.has_key(type_name):
+            self.nodes[type_name] = []
+
+        self.nodes[type_name].append(node)
+
+    def print_section(self, file, t):
+        if self.nodes.has_key(t.__name__):
+            file.write('/* Begin %s section */\n' % t.__name__)
+            for n in self.nodes[t.__name__]:
+                n.write(file)
+            file.write('/* End %s section */\n\n' % t.__name__)
+
+class XcodeNode(object):
+    def __init__(self, context, description):
+        context.regNode(self)
+
+        self._context = context
+        self._id = context.newid()
+        self._name = description
+        
+        self.isa = self.__class__.__name__
+
+    def tostring(self, value):
+        if isinstance(value, list):
+            result = '(\n'
+            for i in value:
+                result = result + '\t\t\t\t%s,\n' % self.tostring(i)
+            result = result + '\t\t\t)'
+            return result
+        elif isinstance(value, dict):
+            result = '{\n'
+            for k, v in value.iteritems():
+                result = result + ('\t\t\t\t%s = %s;\n' % (self.tostring(k), self.tostring(v)))
+            result = result + '\t\t\t}'
+            return result
+        elif isinstance(value, XcodeNode):
+            return value._id + ' /* ' + value._name + ' */' if value._name != '' else value._id
+        else:
+            return str(value)
+
+    def write(self, f):
+        if self._name != '':
+            f.write('\t\t%s /* %s */ = {\n' % (self._id, self._name))
+        else:
+            f.write('\t\t%s = {\n' % (self._id))
+
+        for attribute,value in self.__dict__.items():
+            if attribute[0] != '_':
+                f.write('\t\t\t%s = %s;\n' % (attribute, self.tostring(value)))
+
+        f.write('\t\t};\n')
+
+class PBXFileReference(XcodeNode):
+    def __init__(self, context, path, executable = False):
+        XcodeNode.__init__(self, context, os.path.basename(path))
+
+        if not executable:
+            self.fileEncoding = 4
+            self.name = self._name
+            self.path = path
+            self.sourceTree = '"<group>"'
+
+            _, filetype = os.path.splitext(self._name)
+            self.lastKnownFileType = MAP_EXT[filetype]
+        else:
+            self.explicitFileType = '"compiled.mach-o.executable"'
+            self.includeInIndex = 0
+            self.path = path
+            self.sourceTree = 'BUILT_PRODUCTS_DIR'
+
+    def is_header_file(self):
+        _, ext = os.path.splitext(self._name)
+        return ext in ['.h', '.hh', '.hpp']
+
+    def is_inline_file(self):
+        _, ext = os.path.splitext(self._name)
+        return ext in ['.inl']
+
+    def is_source_file(self):
+        _, ext = os.path.splitext(self._name)
+        return ext in ['.c', '.cpp', '.cc', '.m', '.mm', '.cxx']
+
+class PBXLegacyTarget(XcodeNode):
+    def __init__(self, context, description):
+        XcodeNode.__init__(self, context, description)
+
+        self.buildArgumentsString = '""'#'"${ACTION)"'
+        self.buildConfigurationList = XCConfigurationList(context, 'Build configuration list for PBXLegacyTarget "%s"' % description)
+        self.buildPhases = []
+        self.buildToolPath = 'scons'
+        self.dependencies = []
+        self.productName = '"%s"' % description
+        self.name = '"%s"' % description
+        self.passBuildSettingsInEnvironment = 1
+        self.productType = '"com.apple.product-type.tool"'
+        #self.productType = '"com.apple.product-type.library.static"'
+        #self.productType = '"com.apple.product-type.library.dynamic"'
+
+class PBXNativeTarget(XcodeNode):
+    def __init__(self, context, description):
+        XcodeNode.__init__(self, context, description)
+
+        self.buildConfigurationList = XCConfigurationList(context, 'Build configuration list for PBXNativeTarget "%s"' % description)
+        self.buildPhases = [PBXSourcesBuildPhase(context, 'Sources')]
+        self.buildRules = []
+        self.dependencies = []
+        self.name = self._name
+        self.productName = self._name
+        self.productType = '"com.apple.product-type.tool"'
+
+class PBXProject(XcodeNode):
+    def __init__(self, context, description, project_name):
+        XcodeNode.__init__(self, context, description)
+
+        self.targets = [PBXLegacyTarget(context, project_name.split('.')[0])]
+        if context.generate_codenav_targets:
+            self.targets.append(PBXNativeTarget(context, project_name.split('.')[0] + '-codenav'))
+
+        self.buildConfigurationList = XCConfigurationList(context, 'Build configuration list for PBXProject "%s"' % project_name)
+        self.compatibilityVersion = '"Xcode 3.2"'
+        self.projectDirPath = '""';
+        self.projectRoot = '""'
+        self.mainGroup = PBXGroup(context, '')
+
+class PBXGroup(XcodeNode):
+    def __init__(self, context, description):
+        XcodeNode.__init__(self, context, description)
+
+        self.sourceTree = '"<group>"'
+        self.children = []
+
+        if description != '':
+            self.name = description
+
+        self._subgroups = {}
+
+    def add_source(self, source, executable = False):
+        l = source.split(os.path.sep)
+
+        currGroup = self
+        path = ''
+        while len(l) > 1:
+            dir_name = l.pop(0)
+            path = os.path.join(path, dir_name)
+
+            if not currGroup._subgroups.has_key(dir_name):
+                newGroup = PBXGroup(self._context, dir_name)
+                currGroup._subgroups[dir_name] = newGroup
+                currGroup.children.append(newGroup)
+            
+            currGroup = currGroup._subgroups[dir_name]
+
+        p = PBXFileReference(self._context, source, executable)
+        currGroup.children.append(p)
+        return p
+
+class PBXSourcesBuildPhase(XcodeNode):
+    def __init__(self, context, description):
+        XcodeNode.__init__(self, context, description)
+        self.buildActionMask = 2147483647
+        self.runOnlyForDeploymentPostprocessing = 0
+        self.files = []
+
+class PBXBuildFile(XcodeNode):
+    def __init__(self, context, ref):
+        XcodeNode.__init__(self, context, ref._name)
+        self.fileRef = ref
+
+class XCBuildConfiguration(XcodeNode):
+    def __init__(self, context, description):
+        XcodeNode.__init__(self, context, description)
+
+        self.buildSettings = {
+            'BUILT_PRODUCTS_DIR': '"/Users/stopiccot/code/other/scons-xcode"', 
+            'ARCHS': '"$(ARCHS_STANDARD_64_BIT)"',
+            'HEADER_SEARCH_PATHS': '/Users/stopiccot/code/other/scons-xcode/uber_lib',
+            'USER_HEADER_SEARCH_PATHS': '/Users/stopiccot/code/other/scons-xcode/uber_lib',
+            'PRODUCT_NAME': '"$(TARGET_NAME)"'
+        }
+
+        self.name = self._name
+
+class XCConfigurationList(XcodeNode):
+    def __init__(self, context, description):
+        XcodeNode.__init__(self, context, description)
+
+        self.buildConfigurations = [XCBuildConfiguration(context, 'Debug'), XCBuildConfiguration(context, 'Release')]
+        self.defaultConfigurationIsVisible = 0
+        self.defaultConfigurationName = 'Release'
+
+def XcodeProjectAction(target, source, env):
+    project_name = str(target[0].srcnode())
+    os.mkdir(project_name)
+
+    # Generate xxx.xcodeproj/project/project.xcworkspace/contents.xcworkspacedata
+    os.mkdir(os.path.join(project_name, 'project.xcworkspace'))
+    fw = open(os.path.join(project_name, 'project.xcworkspace', 'contents.xcworkspacedata'), 'w')
+    fw.write(contents_xcworkspacedata % project_name)
+
+    fp = open(os.path.join(project_name, 'project.pbxproj'), 'w')
+
+    fp.write(project_pbxproj_header)
+
+    def custom_sort(x):
+        if isinstance(x, PBXGroup):
+            return '0' + x._name
+        elif isinstance(x, PBXFileReference):
+            if x.is_header_file(): return '1' + x._name
+            if x.is_inline_file(): return '2' + x._name
+            if x.is_source_file(): return '3' + x._name
+            return '4' + x._name
+        else:
+            return '5' + x._name
+
+    context = XcodeProjectBuildContext()
+    context.generate_codenav_targets = env.get('generate_codenav_targets', 1)
+    p = PBXProject(context, 'Project object', project_name)
+    
+    for i in env['srcs']:
+        p.mainGroup.add_source(i)
+
+    for i in env['incs']:
+        p.mainGroup.add_source(i)
+
+    for g in context.nodes['PBXGroup']:
+        g.children.sort(key = custom_sort)
+
+    if context.generate_codenav_targets:
+        for f in context.nodes['PBXFileReference']:
+            if f.is_source_file():
+                p.targets[1].buildPhases[0].files.append(PBXBuildFile(context, f))
+
+    pp = PBXFileReference(context, 'scons-xcode', True)
+    p.targets[0].productReference = pp
+    p.targets[1].productReference = pp
+
+    context.print_section(fp, PBXFileReference)
+    context.print_section(fp, PBXGroup)
+    context.print_section(fp, PBXSourcesBuildPhase)
+    context.print_section(fp, PBXLegacyTarget)
+    context.print_section(fp, PBXNativeTarget)
+    context.print_section(fp, PBXProject)
+    context.print_section(fp, PBXBuildFile)
+    context.print_section(fp, XCBuildConfiguration)
+    context.print_section(fp, XCConfigurationList)
+
+    fp.write(project_pbxproj_footer % p.tostring(p))
+
+def XcodeProjectEmitter(target, source, env):
+    project_name = str(target[0].srcnode())
+    target.append(os.path.join(project_name, 'project.pbxproj'))
+
+    if env.get('generate_workspace', 1):
+        target.append(os.path.join(project_name, 'project.xcworkspace'))
+        target.append(os.path.join(project_name, 'project.xcworkspace', 'contents.xcworkspacedata'))
+
+    return target, source
+
+projectBuilder = SCons.Builder.Builder(
+#    emitter = XcodeProjectEmitter,
+    action = XcodeProjectAction,
+    suffix = '.xcodeproj',
+    target_factory = SCons.Node.FS.Dir
+)
 
 def generate(env):
     """Add Builders and construction variables for Xcode project files to an Environment."""
         env['BUILDERS']['XcodeProject']
     except KeyError:
         env['BUILDERS']['XcodeProject'] = projectBuilder
-
-    pass 
     
 def exists(env):
     return True