#!/usr/bin/env python """PList utility""" import argparse import os import subprocess import re import unicodedata def normalize_char(c): try: UNICODE_EXISTS = bool(type(unicode)) except NameError: unicode = lambda s: str(s) try: cname = unicodedata.name( unicode(c) ) cname = cname[:cname.index( ' WITH' )] return unicodedata.lookup( cname ) except ( ValueError, KeyError ): return c def normalize_string(s): return ''.join( normalize_char(c) for c in s ) def replace_var( str, var, val ): if str.find( '$(' + var + ')' ) != -1: return str.replace( '$(' + var + ')', val ) if str.find( '${' + var + '}' ) != -1: return str.replace( '${' + var + '}', val ) return str parser = argparse.ArgumentParser( description = 'PList utility for Ninja builds' ) parser.add_argument( 'files', metavar = 'file', type=open, nargs='+', help = 'Source plist file' ) parser.add_argument( '--exename', type=str, help = 'Executable name', default = [] ) parser.add_argument( '--prodname', type=str, help = 'Product name', default = [] ) parser.add_argument( '--bundle', type=str, help = 'Bundle identifier', default = [] ) parser.add_argument( '--output', type=str, help = 'Output path', default = [] ) parser.add_argument( '--target', type=str, help = 'Target OS', default = [] ) parser.add_argument( '--deploymenttarget', type=str, help = 'Target OS version', default = [] ) options = parser.parse_args() if not options.exename: options.exename = 'unknown' if not options.prodname: options.prodname = 'unknown' if not options.target: options.target = 'macos' if not options.deploymenttarget: if options.target == 'macos': options.deploymenttarget = '12.0' else: options.deploymenttarget = '10.0' buildversion = subprocess.check_output( [ 'sw_vers', '-buildVersion' ] ).decode().strip() #Merge input plists using first file as base lines = [] for f in options.files: _, extension = os.path.splitext(f.name) if extension != '.plist': continue if lines == []: lines += [ line.strip( '\n\r' ) for line in f ] else: mergelines = [ line.strip( '\n\r' ) for line in f ] for i in range( 0, len( mergelines ) ): if re.match( '^$', mergelines[i] ): break if re.match( '^$', mergelines[i] ): for j in range( 0, len( lines ) ): if re.match( '^$', lines[j] ): for k in range( i+1, len( mergelines ) ): if re.match( '^$', mergelines[k] ): break lines.insert( j+(k-(i+1)), mergelines[k] ) break break #Parse input plist to get package type and signature bundle_package_type = 'APPL' bundle_signature = '????' for i in range( 0, len( lines ) ): if 'CFBundlePackageType' in lines[i]: match = re.match( '^.*>(.*)<.*$', lines[i+1] ) if match: bundle_package_type = match.group(1) if 'CFBundleSignature' in lines[i]: match = re.match( '^.*>(.*)<.*$', lines[i+1] ) if match: bundle_signature = match.group(1) #Write package type and signature to PkgInfo in output path with open( os.path.join( os.path.dirname( options.output ), 'PkgInfo' ), 'w' ) as pkginfo_file: pkginfo_file.write( bundle_package_type + bundle_signature ) pkginfo_file.close() #insert os version for i in range( 0, len( lines ) ): if re.match( '^$', lines[i] ): lines.insert( i+1, '\tBuildMachineOSBuild' ) lines.insert( i+2, '\t' + buildversion + '' ) break #replace build variables name for i in range( 0, len( lines ) ): lines[i] = replace_var( lines[i], 'EXECUTABLE_NAME', options.exename ) lines[i] = replace_var( lines[i], 'PRODUCT_NAME', options.prodname ) lines[i] = replace_var( lines[i], 'PRODUCT_NAME:rfc1034identifier', normalize_string( options.exename ).lower() ) lines[i] = replace_var( lines[i], 'PRODUCT_NAME:c99extidentifier', normalize_string( options.exename ).lower().replace( '-', '_' ).replace( '.', '_' ) ) lines[i] = replace_var( lines[i], 'IOS_DEPLOYMENT_TARGET', options.deploymenttarget ) lines[i] = replace_var( lines[i], 'MACOSX_DEPLOYMENT_TARGET', options.deploymenttarget ) #replace bundle identifier if given if not options.bundle is None and options.bundle != '': for i in range( 0, len( lines ) ): if lines[i].find( 'CFBundleIdentifier' ) != -1: lines[i+1] = '\t' + normalize_string( options.bundle ) + '' break #add supported platform, minimum os version and requirements if options.target == 'ios': for i in range( 0, len( lines ) ): if 'CFBundleSignature' in lines[i]: lines.insert( i+2, '\tCFBundleSupportedPlatforms' ) lines.insert( i+3, '\t' ) lines.insert( i+4, '\t\tiPhoneOS' ) lines.insert( i+5, '\t' ) lines.insert( i+6, '\tMinimumOSVersion' ) lines.insert( i+7, '\t6.0' ) lines.insert( i+8, '\tUIDeviceFamily' ) lines.insert( i+9, '\t' ) lines.insert( i+10, '\t\t1' ) lines.insert( i+11, '\t\t2' ) lines.insert( i+12, '\t' ) break #add build info #DTCompiler #com.apple.compilers.llvm.clang.1_0 #DTPlatformBuild #12B411 #DTPlatformName #iphoneos #DTPlatformVersion #8.1 #DTSDKBuild #12B411 #DTSDKName #iphoneos8.1 #DTXcode #0611 #DTXcodeBuild #6A2008a #write final Info.plist in output path with open( options.output, 'w' ) as plist_file: for line in lines: #print lines[i] plist_file.write( line + '\n' ) plist_file.close() #run plutil -convert binary1 sdk = 'iphoneos' platformpath = subprocess.check_output( [ 'xcrun', '--sdk', sdk, '--show-sdk-platform-path' ] ).decode().strip() localpath = platformpath + "/Developer/usr/bin:/Applications/Xcode.app/Contents/Developer/usr/bin:/usr/bin:/bin:/usr/sbin:/sbin" plutil = "PATH=" + localpath + " " + subprocess.check_output( [ 'xcrun', '--sdk', sdk, '-f', 'plutil' ] ).decode().strip() plcommand = plutil + ' -convert binary1 ' + options.output os.system( plcommand )