最近因为在给项目接入第三方SDK,遇到了一个比较烦人的事情就是,每次出包都要重新根据第三方SDK说明设置xcode,每次最少花20分钟来设置,如果出错的话就不一定是20分钟的事了,所以我决定要做一个自动化处理脚本。
在网上查了好多资料,用比对工具一步一步的比较project.pbxproj文件的不同,真是眼睛盯瞎了的感觉。
在伯乐在线上看到了一篇关于project.pbxproj文件的说明。
关于操作project.pbxproj文件有一下几个第三方库来参考
- Xcodeproj CocoaPods 写的 Ruby 解析库,用于修改引入 CocoaPods 的工程文件并保存为 XML 格式。CocoaPods 本身是很强大的,还可以用来操作 Xcode workspaces (.xcworkspace), configuration files (.xcconfig) 和 Xcode Scheme files (.xcscheme).
- mod-pbxproj 强大的 Python 解析库,支持一定的修改操作,可输出 OpenStep 格式,但是顺序和注释内容无法完美还原,有些鸡肋。
- xUnique 用 Python 写的统一多设备生成的 UUID 的工具,主要用途是统一工程在多设备上生成的 UUID,避免工程文件冲突。
- pbxplorer Ruby 写的解析库。
- node-xcode Cordova 基于它管理 Xcode 工程
我用到了mod-pbxproj库里面的提供的一些方法来实现项目需求
由于个人python水平还在初级阶段,不过也正在补充自己,代码写的可能比较烂,但是以解决实际问题为主
#!/usr/bin/env python # -*- coding: utf-8 -*- # @Author: fasthro # @Date: 2016-11-15 11:21:33 # @Last Modified by: fasthro # @Last Modified time: 2016-11-15 11:21:57 import shutil import os import json from mod_pbxproj import XcodeProject import re import platform _channel = "ios_yh" _bundleId = "com.ebo.ball" _version = "1.0.0" # 设置编码 import sys reload(sys) sys.setdefaultencoding("utf-8") # 解析 class ParseChannelParam: def __init__(self, jp): self.json_data = None self.json_file_data = None self.json_resource_data = None self.json_base_framework_data = None self.json_other_framework_data = None self.bundle_id = None self.version = None # 文件列表 self.file_list = [] # xcode 文件目录 self.file_path_list = [] # 文件类型 self.file_type_list = [] # xcode 文件列表 self.xcode_file_list = [] # 文件夹列表 self.folder_list = [] # xcode 文件夹目录 self.folder_path_list = [] # 目录类型 self.folder_type_list = [] # xcode 文件夹列表 self.xcode_folder_list = [] # framework self.base_frameworks = [] self.base_weak_frameworks = [] self.other_frameworks = [] self.other_weak_frameworks = [] # 解析 self.parse(jp) # 写入文件列表 self.writfilelist() # 写入文件夹列表 self.writfolderlist() # 写入other self.writeother() def parse(self, jp): try: with open(jp) as jf: self.json_data = json.load(jf) self.json_file_data = self.json_data["file"] self.json_resource_data = self.json_data["resource"] self.json_base_framework_data = self.json_data["base_framework"] self.json_other_framework_data = self.json_data["other_framework"] except: print "parse json error" pass def writfilelist(self): if self.json_file_data is not None: l = len(self.json_file_data) if l > 0: for index in range(0, l): self.file_list.append(self.json_file_data[index]["file_name"]) self.file_path_list.append(self.json_file_data[index]["file_path"]) self.file_type_list.append(self.json_file_data[index]["type"]) for index in range(len(self.file_path_list)): if self.file_type_list[index] == 'copy': self.xcode_file_list.append(os.path.join(self.file_path_list[index], self.file_list[index])) def writfolderlist(self): if self.json_resource_data is not None: l = len(self.json_resource_data) if l > 0: for index in range(0, l): self.folder_list.append(self.json_resource_data[index]["dir_name"]) self.folder_path_list.append(self.json_resource_data[index]["dir_path"]) self.folder_type_list.append(self.json_resource_data[index]["type"]) for index in range(len(self.folder_path_list)): if self.folder_type_list[index] == 'copy': self.xcode_folder_list.append(os.path.join(self.folder_path_list[index], self.folder_list[index])) def writeother(self): self.bundle_id = self.json_data["bundleId"] self.version = self.json_data["version"] if self.json_base_framework_data is not None: for index in range(len(self.json_base_framework_data)): self.base_frameworks.append(self.json_base_framework_data[index]["path"]) self.base_weak_frameworks.append(self.json_base_framework_data[index]["weak"]) if self.json_other_framework_data is not None: for index in range(len(self.json_other_framework_data)): self.other_frameworks.append(self.json_other_framework_data[index]["path"]) self.other_weak_frameworks.append(self.json_other_framework_data[index]["weak"]) def __str__(self): return "ParseChannelParam : bundle_id = %s version = %s xcode_file_list = %s xcode_folder_list = %s " % (self.bundle_id, self.version, str(self.xcode_file_list), str(self.xcode_folder_list)) # 打包之前准备工作 class PreparatoryWork: def __init__(self,frompath, topath, filefromls, filetols, folderfromls, foldertols): # from 根目录 self.from_path = frompath # to 根目录 self.to_path = topath # 需要 copy 的文件 self.file_from_path_list = filefromls self.file_to_path_list = filetols # 需要 copy 的目录 self.folder_from_list = folderfromls self.folder_to_path_list = foldertols # copy self.copy(filefromls, filetols) self.copy(folderfromls, foldertols) def copy(self, fs, ts): for index in range(len(fs)): frompath = os.path.join(self.from_path, fs[index]) topath_temp = os.path.join(self.to_path, ts[index]) topath = os.path.join(topath_temp, fs[index]) # 如果已经存在就删除 if os.path.exists(topath): if os.path.isdir(topath): shutil.rmtree(topath) else: os.remove(topath) if os.path.isfile(frompath): print "copy %s -> %s" % (frompath, topath) shutil.copy(frompath, topath) else: print "copy %s -> %s" % (frompath, topath) shutil.copytree(frompath, topath) # Xcode *.pbxproj 相关设置 class Xcode: """ ·xpath : xcode 根目录 ·folders : 需要添加的文件夹列表 ·files : 需要添加的文件列表 """ def __init__(self, xpath=None, folders=[], files=[]): # xcode project path self.xcode_project_path = xpath # xcode pbxproj path if platform.system() == "Windows": self.xcode_pbxproj_path = os.path.join(xpath, 'Unity-iPhone.xcodeproj/project.pbxproj.xml') else: self.xcode_pbxproj_path = os.path.join(xpath, 'Unity-iPhone.xcodeproj/project.pbxproj') # need add folders self.folders = folders #need add files self.files = files self.project = None if self.xcode_pbxproj_path is not None: pstr_xml = self.xcode_pbxproj_path[len(self.xcode_pbxproj_path) - 4: len(self.xcode_pbxproj_path)] pstr_proj = self.xcode_pbxproj_path[len(self.xcode_pbxproj_path) - 8: len(self.xcode_pbxproj_path)] if pstr_xml == '.xml': self.project = XcodeProject.LoadFromXML(self.xcode_pbxproj_path) elif pstr_proj == '.pbxproj': self.project = XcodeProject.Load(self.xcode_pbxproj_path) else: print "xcode load error path = [%s]" % self.xcode_pbxproj_path if self.project is None: print "Xcode load error" else: pass # temp file list self.temp_files = None self.temp_folder = None def addfileToXcode(self): self.addfiles(self.files) def addfolderToXcode(self): self.addfolders(self.folders) # 导入文件设置 -fno-objc-arc def set_file_seting(self, f_path, flag): if self.project: f_id = self.project.get_file_id_by_path(f_path) files = self.project.get_build_files(f_id) for f in files: f.add_compiler_flag(flag) # 添加文件夹 def addfolders(self, folders): if self.project: self.temp_files = [] for dpp in folders: dp = os.path.join(self.xcode_project_path, dpp) if os.path.exists(dp): print "add folder to xcode path = [%s]" % dp self.project.add_folder(dp) # add folder file to xcode self.getfilesdir(dp) else: print "add folder path = [%s] is not exist!" % dp print "add folder file : " if len(self.temp_files) > 0: self.addfiles(self.temp_files) def getfilesdir(self, dp): for f in os.listdir(dp): f_p = os.path.join(dp, f) if os.path.isfile(f_p): self.temp_files.append(f_p) else: cp = re.compile(r".bundle|.framework") gp = cp.search(f_p) if gp is not None: self.temp_files.append(f_p) else: self.getfilesdir(f_p) def addfiles(self, files): if self.project: for fpp in files: fp = os.path.join(self.xcode_project_path, fpp) if os.path.exists(fp): print "add file to xcode path = [%s]" % fp self.project.add_file_if_doesnt_exist(fp) else: print "add file path = [%s] is not exist!" % fp for fp in files: comp = re.compile('.m$|.mm$') match = comp.search(fp) if match: print "file [ *.m or *.mm ] seting flag set -fno-objc-arc path [ %s ]" % fp self.set_file_seting(fp, '-fno-objc-arc') def addframework(self, frameworks=[], weaks=[], isbase=True): if self.project: framework_parent = self.project.get_or_create_group('Frameworks') for index in range(len(frameworks)): fw = frameworks[index] we = weaks[index] comp = re.compile('.framework$') match = comp.search(fw) tree = None sr = "other" if isbase == True: tree = "SDKROOT" sr = "base" weak = we == "True" if match: print "add %s framework [ %s ] weak = %s" % (sr, fw, we) self.project.add_file_if_doesnt_exist(fw, parent=framework_parent, weak=weak, tree=tree) else: print "add %s libraries [ %s ]" % (sr,fw) self.project.add_file_if_doesnt_exist(fw, parent=framework_parent, weak=False, tree=tree) def save(self, fp=None): if self.project: if fp is not None: self.project.save(fp) else: self.project.save() print "save project" if __name__ == "__main__": # path # _path = os.getcwd() #项目路径(需要配置的路径) _path = "/Users/admin/Desktop/ballClent" if platform.system() == "Windows": _path = "D:/work/ballClient/trunk" # 其他路径 _channel_path = "%s/%s/%s" % (_path, "iosChannel/", _channel) _channel_param_path = "%s/%s.json" % (_channel_path, _channel) _xcode_path = "%s/%s" % (_path, "Build/Output/IOS") # 解析配置文件 parseJson = ParseChannelParam(_channel_param_path) print parseJson print "Preparatory Work : " # 根据配置拷贝和替换文件 preparat = PreparatoryWork(_channel_path, _xcode_path, parseJson.file_list, parseJson.file_path_list, parseJson.folder_list, parseJson.folder_path_list) # 根据配置文件设置xcode xcode = Xcode(_xcode_path, parseJson.xcode_folder_list, parseJson.xcode_file_list) print " add file to xcode : " xcode.addfileToXcode() print " add folder to xcode : " xcode.addfolderToXcode() print " add system framework to xcode : " print parseJson.base_frameworks xcode.addframework(parseJson.base_frameworks, parseJson.base_weak_frameworks, True) xcode.addframework(parseJson.other_frameworks, parseJson.other_weak_frameworks, False) if platform.system() == "Windows": xcode.save("project.pbxproj") else: xcode.save()
通过下面Json来自动化处理每个渠道SDK的导入
{ "bundleId": "com.*.*", "version": "1.0.0", "file": [ { "file_name": "UnitySdkInterface.mm", "file_path": "Libraries/Plugins/iOS", "type": "replace" }, { "file_name": "UnityAppController.mm", "file_path": "Classes", "type": "replace" }, { "file_name": "YHGameSdk.h", "file_path": "", "type": "copy" }, { "file_name": "YHGameSdk.m", "file_path": "", "type": "copy" }, { "file_name": "Info.plist", "file_path": "", "type": "replace" } ], "resource": [ { "dir_name": "galaxyJointSDK", "dir_path": "", "type": "copy" } ], "base_framework": [ { "path": "System/Library/Frameworks/MessageUI.framework", "weak": "True" }, { "path": "usr/lib/libc++.tbd", "weak": "False" }, { "path": "usr/lib/libz.tbd", "weak": "False" }, { "path": "usr/lib/libsqlite3.tbd", "weak": "False" } ], "other_framework": [ ] }
Unity 导出xcode之后在执行此脚本,传递参数channel之后即可根据配置自动化处理。目录是我们项目自己的目录。
在此记录一下自己成果,为跟我一样迷茫的小伙伴们一起分享。如果有问题请留言。
支持原创,转载请注明作者出处我的博客http://home.cnblogs.com/u/fastHro/