Including/excluding entire groups from targets

2019-03-04 17:25发布

My project has a group with a few hundred files (organized into a couple dozen subgroups two levels deep). The files in that group are themselves being changed reasonably often. I want those files to be included in some targets, but not others.

In Xcode 3.x, after each change to the group, I would just Get Info on the group itself, go to the Targets tab, and (re-)select the targets I wanted. (This was, in fact, the answer to a nearly-identical question from 2010, Xcode — groups and targets.)

In Xcode 5, the equivalent File Inspector panel doesn't have a Target Membership section if you have a group selected (and, even if selecting a group were the same as selecting all of its files, the Target Membership checkboxes are disabled if you select more than one file).

So, is this functionality still there, but hidden somewhere I haven't been able to find it?

If not, obviously there are other ways I can do what I want—script Xcode, parse the .pbxproj file, or abstract the group into a subproject or an entirely separate project that builds a static lib, etc. But I'd love to be able to work with Xcode here, the way I did in 3.x, instead of have to fight against it.


Actually, scripting Xcode doesn't seem to work. Any attempt to get the build files of a build phase fails with a generic -10000 error. For example:

tell application "Xcode"
    set theproject to project "SampleProject"
    set thetarget to target "SampleTarget" of theproject
    set thephase to build phase "Compile Sources" of thetarget
    build files of phase
end tell

… fails on the last line with:

error "Xcode got an error: AppleEvent handler failed." number -10000

标签: xcode xcode5
1条回答
劫难
2楼-- · 2019-03-04 17:39

Here's the hack I ended up using—I'd obviously still appreciate a better solution.

#!/usr/bin/env python3

import os
import plistlib
import sys

pbxproj = os.path.join(sys.argv[1], 'project.pbxproj')
groupname = sys.argv[2]
extensions = 'm mm c cc cpp'.split()

with open(pbxproj, 'rb') as f:
    p = plistlib.load(f)

objs = p['objects']

groupid, group = next((k, v) for k, v in objs.items()
                      if v.get('path') == groupname)

def descendants(id):
    obj = objs[id]
    if obj['isa'] == 'PBXFileReference':
        yield (id, obj)
    for child in obj.get('children', []):
        yield from descendants(child)

mdict = {id: obj for id, obj in descendants(group_id)
         if os.path.splitext(obj['path'])[-1] in extensions}

proj_id, proj = next((k, v) for k, v in objs.items()
                     if v['isa'] == 'PBXProject')

for target_id in proj['targets']:
    target = objs[target_id]
    phase_ids = target['buildPhases']
    phases = [(phase_id, objs[phase_id]) for phase_id in phase_ids]
    phase_id, phase = next((phase_id, phase)
                           for phase_id, phase in phases
                           if phase['isa'] == 'PBXSourcesBuildPhase')
    fileref_ids = [i
                   for i, buildfile_id in enumerate(phase['files'])
                   if objs[buildfile_id]['fileRef'] in mdict]
    fileref_ids.sort(reverse=True)
    for i in fileref_ids:
        del phase['files'][i]

with open(pbxproj + '.new', 'wb') as f:
    plistlib.dump(p, f)
os.rename(pbxproj, pbxproj + '.bak')
os.rename(pbxproj + '.new', pbxproj)
查看更多
登录 后发表回答