#!/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 )