def filter_log(last_commit): commit_valid = call( 'git -C {} cat-file -e '.format(config.config_dic['project_path']) + last_commit)[0] if commit_valid != 0: return '无' git_logs_cmd = '''git -C {} log --pretty=\"%s\" {}..HEAD --reverse'''.format( config.config_dic['project_path'], last_commit) logs = call(git_logs_cmd) log_has_prefix = [] prefix = config.config_dic['filter_log']['prefix'] if not prefix: prefix = '[' for line in logs[1].split("\n"): if line.startswith(prefix): log_has_prefix.append(line) if not log_has_prefix: return '无' log_text = '' i = 0 while i < len(log_has_prefix): log_text += '{}.{}'.format(i + 1, log_has_prefix[i]) if i < len(log_has_prefix) - 1: log_text += '\n' i += 1 return log_text
def get_subject(build): build_info = config.config_dic['build'][build] version = call( '''/usr/libexec/PlistBuddy -c "Print CFBundleShortVersionString" {}'''. format(config.config_dic['project_path'] + build_info['info_plist']))[1] build_version = call( '''/usr/libexec/PlistBuddy -c "Print CFBundleVersion" {}'''.format( config.config_dic['project_path'] + build_info['info_plist']))[1] subject = build_info['app_name'] + ' ' + str.strip( version) + ' ' + 'Build' + ' ' + str.strip(build_version) return subject
def upload(archive=None, build_info=None): bugly_info = config.config_dic['bugly'] dSYM_path = "{}/dSYMs/{}.app.dSYM".format(archive, build_info['scheme']) version = call( '''/usr/libexec/PlistBuddy -c "Print CFBundleShortVersionString" {}'''. format(config.config_dic['project_path'] + build_info['info_plist']))[1] cmd = "java -jar {} -i {} -u -id {} -key {} -package {} -version {}".format( bugly_info['jar_file'], dSYM_path, build_info['bugly_id'], build_info['bugly_key'], build_info['bundle_id'], str.strip(version)) return call(cmd, None)
def build_if_need(): git_info = config.config_dic['git'] print('Pulling latest code...') if git_info['pull_before_build']: if git_info['branch']: call('git -C {} checkout {}'.format( config.config_dic['project_path'], git_info['branch'])) call('git -C {} pull origin {}'.format( config.config_dic['project_path'], git_info['branch'])) else: call('git -C {} pull'.format(config.config_dic['project_path'])) print('Pull code complete!') current_commit = call( '''git -C {} --no-pager log --format="%H" -n 1'''.format( config.config_dic['project_path']))[1] last_try_file = config.config_dic['log_path'] + 'last_try_build.txt' last_build_file = config.config_dic['log_path'] + 'last_build.txt' if not os.path.exists(last_try_file): with open(last_try_file, 'w') as f: f.write('0') if not os.path.exists(last_build_file): with open(last_build_file, 'w') as f: f.write('0') with open(last_try_file, 'r') as f: last_try_commit = f.read() with open(last_build_file, 'r') as f: last_build_commit = f.read() if last_try_commit == current_commit: print('Build have tried, exit!') return (False, None) commit_msg = call('''git -C {} --no-pager log --format="%s" -n 1'''.format( config.config_dic['project_path']))[1] build_target = None for key in config.config_dic['build']: if config.config_dic['build'][key]['build_identifier'] in commit_msg: build_target = key break if not build_target: print('No build identifier, exit!') return (False, None) if last_build_commit == current_commit: print('This build has been builded, exit!') return (False, None) print('Build identifier detect, build start...') print('Build info {}'.format(config.config_dic['build'][build_target])) return (True, build_target)
def get_current_branch(project_path): git_branches = call( 'git -C {} branch'.format(project_path))[1].splitlines() for line in git_branches: if line.startswith('*'): return line.split()[1] return None
def filter_log(last_commit): commit_valid = call( 'git -C {} cat-file -e '.format(config.config_dic['project_path']) + last_commit)[1] if commit_valid != '0': return '无' git_logs_cmd = '''git -C {} log --pretty=\"%s\" {}..HEAD'''.format( config.config_dic['project_path'], last_commit) logs = call(git_logs_cmd) log_has_prefix = [] prefix = config.config_dic['filter_log']['prefix'] if not prefix: prefix = '[' for line in logs[1].split("\n"): if line.startswith(prefix): log_has_prefix.append(line) if log_has_prefix.count == 0: return '无' log_file = '{}log.txt'.format(config.config_dic['builds_path']) with open(log_file, 'w') as f: for line in log_has_prefix: f.write('{}\n'.format(line)) with open(log_file, 'r+') as f: flip_cmd = "sed '1!G;h;$!d' " + log_file res = call(flip_cmd) f.write(res[1]) with open(log_file, 'r+') as f: add_num_cmd = """awk '{printf NR"."" "}1' """ + log_file res = call(add_num_cmd) f.write(res[1]) with open(log_file, 'r') as f: return f.read()
def resign(ipa=None, build_info=None): if os.path.exists('Payload'): shutil.rmtree('Payload') resign_info = build_info['resign'] tmp_dir = call('mktemp -d')[1] call('unzip -q {} -d {}'.format(ipa, tmp_dir)) extentions_info = None if 'extentions' in resign_info: extentions_info = resign_info['extentions'] if extentions_info: for extention in extentions_info: resign_app('{}/Payload/{}.app/PlugIns/{}.appex'.format(tmp_dir, build_info['scheme'], extention['scheme']), resign_info['certificate'], extention['provisioning_profile'], None, extention['bundle_id']) resign_app('{}/Payload/{}.app'.format(tmp_dir, build_info['scheme']), resign_info['certificate'], resign_info['provisioning_profile'], resign_info['app_name'], resign_info['bundle_id']) file_name_pieces = ipa.split('/')[-1].split('.ipa') resign_ipa_name = file_name_pieces[0] + '-resign.ipa' resign_ipa = ipa.replace(ipa.split('/')[-1], resign_ipa_name) zipDir(tmp_dir, resign_ipa) shutil.rmtree(tmp_dir) os.remove(ipa) return (0, resign_ipa)
def read_profile_attribute(profile=None, attribute=None): if not profile: return None if not attribute: return None key_value_res = call('egrep -a -A 1 ' + attribute + ' ' + profile) if key_value_res[0] != 0: return None all_keys = re.findall(".*</(.*)>.*", key_value_res[1]) value = re.findall(".*<{}>(.*)</{}>.*".format(all_keys[1], all_keys[1]), key_value_res[1]) if value: return value[0] else: return None
def get_latest_commit_msg(project_path): commit_msg = call('''git -C {} --no-pager log --format="%s" -n 1'''.format( project_path))[1] return commit_msg
def get_current_commit(project_path): current_commit = call( '''git -C {} --no-pager log --format="%H" -n 1'''.format( project_path))[1] return current_commit
def checkout_and_pull(project_path, repo='origin', branch='master'): call('git -C {} fetch {} {}'.format(project_path, repo, branch)) call('git -C {} checkout {}'.format(project_path, branch)) pull(project_path, repo, branch)
def pull(project_path, repo='origin', branch='master'): call('git -C {} pull {} {}'.format(project_path, repo, branch))
def build(build_target, send_msg=True): last_try_file = config.config_dic['log_path'] + 'last_try_build.txt' last_build_file = config.config_dic['log_path'] + 'last_build.txt' if not os.path.exists(last_try_file): with open(last_try_file, 'w') as f: f.write('0') if not os.path.exists(last_build_file): with open(last_build_file, 'w') as f: f.write('0') with open(last_build_file, 'r') as f: last_build_commit = f.read() current_commit = call('''git -C {} log --format="%H" -n 1'''.format( config.config_dic['project_path']))[1] with open(last_try_file, 'w') as f: f.write(current_commit) build_info = config.config_dic['build'][build_target] print('Building...') build_res = build_ipa.build_ipa(build_target) if build_res[0] != 0: print('Build failure!') failture_mail_info = config.config_dic['email_after_failure'] if failture_mail_info['enable']: mail.send_failture_msg('Build failure!', build_target) else: print('Build success!') cp_info = config.config_dic['copy_to'] if cp_info['enable']: path = cp_info['path'] if not os.path.exists(path): os.mkdir(path) shutil.copy(build_res[2], path) print('Copy to {}'.format(path)) fir_info = config.config_dic['upload_to_fir'] if fir_info['enable']: print('Upload to fir.im...') fir.upload(fir_info['path'], build_res[2], fir_info['token']) print('Upload complete!') bugly_info = config.config_dic['bugly'] if bugly_info['enable']: print('Upload symbol file to bugly...') bugly.upload(build_res[1], build_info) print('Upload complete!') if send_msg: mail_info = config.config_dic['email_after_build'] if mail_info['enable']: print('Send email...') if mail_info['send_filter_log']: log = filter_log.msg_with_intall_info( last_build_commit, build_target) mail.send_success_msg(log, build_target) else: mail.send_success_msg("Build success!", build_target) print('Send complete!') ding_info = config.config_dic['send_ding_msg_after_build'] if ding_info['enable']: print('Send dingtalk message...') tokens = ding_info['tokens'] if ding_info['send_filter_log']: log = filter_log.msg_with_intall_info( last_build_commit, build_target) dingtalk_bot.sendMessage(log, tokens) else: dingtalk_bot.sendMessage('打包成功!', tokens) print('Send complete!') with open(last_build_file, 'w') as f: f.write(current_commit) print('Build complete!')
def build_ipa(target=None): build_info = config.config_dic['build'][target] scheme_name = build_info['scheme'] archive_path = config.config_dic['builds_path'] + scheme_name + '.xcarchive' archive_cmd = "xcodebuild archive -workspace {} -scheme {} -archivePath {} ONLY_ACTIVE_ARCH=NO TARGETED_DEVICE_FAMILY=1 -allowProvisioningUpdates".format(config.config_dic['project_path'] + config.config_dic['worspace_name'], scheme_name, archive_path) log_file = config.config_dic['log_path'] + config.config_dic['builg_log'] file = open(log_file, 'w+') res = call(archive_cmd, file) if res[0] != 0: return (1, None, None) sign_certificate = 'iPhone Distribution' if build_info['export_mothod'] == 'development': sign_certificate = 'iPhone Developer' export_plist_template = """ <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>compileBitcode</key> <false/> <key>method</key> <string>{}</string> <key>provisioningProfiles</key> <dict> <key>{}</key> <string>{}</string> </dict> <key>signingCertificate</key> <string>{}</string> <key>signingStyle</key> <string>manual</string> <key>stripSwiftSymbols</key> <true/> <key>teamID</key> <string>{}</string> <key>thinning</key> <string><none></string> </dict> </plist> """ export_plist = export_plist_template.format(build_info['export_mothod'], build_info['bundle_id'], build_info['provisioning_profile'], sign_certificate, build_info['team_id']) export_plist_path = config.config_dic["builds_path"] + "export.plist" with open(export_plist_path, 'w') as f: f.write(export_plist) export_cmd = "xcodebuild -exportArchive -archivePath {} -exportOptionsPlist {} -exportPath {} -allowProvisioningUpdates".format(archive_path, export_plist_path, config.config_dic['builds_path']) res = call(export_cmd, file) if res[0] != 0: return [1, None, None ] version = call('''/usr/libexec/PlistBuddy -c "Print CFBundleShortVersionString" {}'''.format(config.config_dic['project_path'] + build_info['info_plist']))[1] build_version = call('''/usr/libexec/PlistBuddy -c "Print CFBundleVersion" {}'''.format(config.config_dic['project_path'] + build_info['info_plist']))[1] ipa_name = '{}-{}-Build-{}.ipa'.format(scheme_name, str.strip(version), str.strip(build_version)) ipa_path = config.config_dic["builds_path"] + ipa_name shutil.move(config.config_dic['builds_path'] + scheme_name + '.ipa', ipa_path) if not os.path.exists(ipa_path): return [1, None, None ] file.close return [0, archive_path, ipa_path]
def upload(fir_path=None, ipa=None, token=None): upload_cmd = "{} p {} -T {}".format(fir_path, ipa, token) return call(upload_cmd, None)
def resign_app(app=None, cert=None, profile=None, app_name=None, bundle_id=None): signature = app + '/_CodeSignature' if os.path.exists(signature): shutil.rmtree(signature) shutil.copyfile(profile, app + '/embedded.mobileprovision') tmp_entitlements = '/tmp/entitlements.plist' with open(tmp_entitlements, 'w+') as f: call('codesign -d --entitlements - ' + app, f) with open(tmp_entitlements, 'r+', encoding='ISO-8859-1') as f: lines = f.read().split('\n') to_delete = lines[0] f.seek(0) for line in lines: if line != to_delete: if line == lines[1]: words = line.split('xml') line = line.replace(words[0], '<?') f.write(line + '\n') f.truncate() team_id = read_profile_attribute(profile, 'com.apple.developer.team-identifier') if bundle_id: call("/usr/libexec/PlistBuddy -c \"Set :application-identifier {}\" {}".format('{}.{}'.format(team_id, bundle_id), tmp_entitlements)) call("/usr/libexec/PlistBuddy -c \"Set :com.apple.developer.team-identifier {}\" {}".format(team_id, tmp_entitlements)) call("/usr/libexec/PlistBuddy -c \"Set :CFBundleIdentifier {}\" {}/Info.plist".format(bundle_id, app)) else: entitlements_bundle_id = read_profile_attribute(profile, 'application-identifier') pure_bundle_id = entitlements_bundle_id.replace('{}.'.format(team_id), '') call("/usr/libexec/PlistBuddy -c \"Set :application-identifier {}\" {}".format(entitlements_bundle_id, tmp_entitlements)) call("/usr/libexec/PlistBuddy -c \"Set :com.apple.developer.team-identifier {}\" {}".format(team_id, tmp_entitlements)) call("/usr/libexec/PlistBuddy -c \"Set :CFBundleIdentifier {}\" {}/Info.plist".format(pure_bundle_id, app)) if app_name: call("/usr/libexec/PlistBuddy -c \"Set :CFBundleName {}\" {}/Info.plist".format(app_name, app)) aps_env = read_profile_attribute(profile, 'aps-environment') if aps_env: call("/usr/libexec/PlistBuddy -c \"Set :aps-environment {}\" {}".format(aps_env, tmp_entitlements)) call('codesign -f -s \"{}\" \'--entitlements\' \'{}\' {}'.format(cert, tmp_entitlements, app)) os.remove(tmp_entitlements)