1#!/usr/bin/env python
2
3"""PList utility"""
4
5import argparse
6import os
7import subprocess
8import re
9import unicodedata
10
11def normalize_char(c):
12  try:
13    cname = unicodedata.name( unicode(c) )
14    cname = cname[:cname.index( ' WITH' )]
15    return unicodedata.lookup( cname )
16  except ( ValueError, KeyError ):
17    return c
18
19def normalize_string(s):
20    return ''.join( normalize_char(c) for c in s )
21
22def replace_var( str, var, val ):
23  if str.find( '$(' + var + ')' ) != -1:
24    return str.replace( '$(' + var + ')', val )
25  if str.find( '${' + var + '}' ) != -1:
26    return str.replace( '${' + var + '}', val )
27  return str
28
29
30parser = argparse.ArgumentParser( description = 'PList utility for Ninja builds' )
31parser.add_argument( 'files',
32                     metavar = 'file', type=file, nargs='+',
33                     help = 'Source plist file' )
34parser.add_argument( '--exename', type=str,
35                     help = 'Executable name',
36                     default = [] )
37parser.add_argument( '--prodname', type=str,
38                     help = 'Product name',
39                     default = [] )
40parser.add_argument( '--bundle', type=str,
41                     help = 'Bundle identifier',
42                     default = [] )
43parser.add_argument( '--output', type=str,
44                     help = 'Output path',
45                     default = [] )
46parser.add_argument( '--target', type=str,
47                     help = 'Target OS',
48                     default = [] )
49parser.add_argument( '--deploymenttarget', type=str,
50                     help = 'Target OS version',
51                     default = [] )
52options = parser.parse_args()
53
54if not options.exename:
55  options.exename = 'unknown'
56if not options.prodname:
57  options.prodname = 'unknown'
58if not options.target:
59  options.target = 'macos'
60if not options.deploymenttarget:
61  if options.target == 'macos':
62    options.deploymenttarget = '10.7'
63  else:
64    options.deploymenttarget = '6.0'
65
66buildversion = subprocess.check_output( [ 'sw_vers', '-buildVersion' ] ).strip()
67
68#Merge inputs using first file as base
69lines = []
70for f in options.files:
71  if lines == []:
72    lines += [ line.strip( '\n\r' ) for line in f ]
73  else:
74    mergelines = [ line.strip( '\n\r' ) for line in f ]
75    for i in range( 0, len( mergelines ) ):
76      if re.match( '^<dict/>$', mergelines[i] ):
77        break
78      if re.match( '^<dict>$', mergelines[i] ):
79        for j in range( 0, len( lines ) ):
80          if re.match( '^</dict>$', lines[j] ):
81            for k in range( i+1, len( mergelines ) ):
82              if re.match( '^</dict>$', mergelines[k] ):
83                break
84              lines.insert( j+(k-(i+1)), mergelines[k] )
85            break
86        break
87
88#Parse input plist to get package type and signature
89bundle_package_type = 'APPL'
90bundle_signature = '????'
91
92for i in range( 0, len( lines ) ):
93  if 'CFBundlePackageType' in lines[i]:
94    match = re.match( '^.*>(.*)<.*$', lines[i+1] )
95    if match:
96      bundle_package_type = match.group(1)
97  if 'CFBundleSignature' in lines[i]:
98    match = re.match( '^.*>(.*)<.*$', lines[i+1] )
99    if match:
100      bundle_signature = match.group(1)
101
102#Write package type and signature to PkgInfo in output path
103with open( os.path.join( os.path.dirname( options.output ), 'PkgInfo' ), 'w' ) as pkginfo_file:
104  pkginfo_file.write( bundle_package_type + bundle_signature )
105  pkginfo_file.close()
106
107#insert os version
108for i in range( 0, len( lines ) ):
109  if re.match( '^<dict>$', lines[i] ):
110    lines.insert( i+1, '\t<key>BuildMachineOSBuild</key>' )
111    lines.insert( i+2, '\t<string>' + buildversion + '</string>' )
112    break
113
114#replace build variables name
115for i in range( 0, len( lines ) ):
116  lines[i] = replace_var( lines[i], 'EXECUTABLE_NAME', options.exename )
117  lines[i] = replace_var( lines[i], 'PRODUCT_NAME', options.prodname )
118  lines[i] = replace_var( lines[i], 'PRODUCT_NAME:rfc1034identifier', normalize_string( options.exename ).lower() )
119  lines[i] = replace_var( lines[i], 'PRODUCT_NAME:c99extidentifier', normalize_string( options.exename ).lower().replace( '-', '_' ).replace( '.', '_' ) )
120  lines[i] = replace_var( lines[i], 'IOS_DEPLOYMENT_TARGET', options.deploymenttarget )
121  lines[i] = replace_var( lines[i], 'MACOSX_DEPLOYMENT_TARGET', options.deploymenttarget )
122
123#replace bundle identifier if given
124if not options.bundle is None and options.bundle != '':
125  for i in range( 0, len( lines ) ):
126    if lines[i].find( 'CFBundleIdentifier' ) != -1:
127      lines[i+1] = '\t<string>' + normalize_string( options.bundle ) + '</string>'
128      break
129
130#add supported platform, minimum os version and requirements
131if options.target == 'ios':
132  for i in range( 0, len( lines ) ):
133    if 'CFBundleSignature' in lines[i]:
134      lines.insert( i+2,  '\t<key>CFBundleSupportedPlatforms</key>' )
135      lines.insert( i+3,  '\t<array>' )
136      lines.insert( i+4,  '\t\t<string>iPhoneOS</string>' )
137      lines.insert( i+5,  '\t</array>' )
138      lines.insert( i+6,  '\t<key>MinimumOSVersion</key>' )
139      lines.insert( i+7,  '\t<string>6.0</string>' )
140      lines.insert( i+8,  '\t<key>UIDeviceFamily</key>' )
141      lines.insert( i+9,  '\t<array>' )
142      lines.insert( i+10, '\t\t<integer>1</integer>' )
143      lines.insert( i+11, '\t\t<integer>2</integer>' )
144      lines.insert( i+12, '\t</array>' )
145      break
146
147#add build info
148#<key>DTCompiler</key>
149#<string>com.apple.compilers.llvm.clang.1_0</string>
150#<key>DTPlatformBuild</key>
151#<string>12B411</string>
152#<key>DTPlatformName</key>
153#<string>iphoneos</string>
154#<key>DTPlatformVersion</key>
155#<string>8.1</string>
156#<key>DTSDKBuild</key>
157#<string>12B411</string>
158#<key>DTSDKName</key>
159#<string>iphoneos8.1</string>
160#<key>DTXcode</key>
161#<string>0611</string>
162#<key>DTXcodeBuild</key>
163#<string>6A2008a</string>
164
165#write final Info.plist in output path
166with open( options.output, 'w' ) as plist_file:
167  for line in lines:
168    #print lines[i]
169    plist_file.write( line + '\n' )
170  plist_file.close()
171
172#run plutil -convert binary1
173sdk = 'iphoneos'
174platformpath = subprocess.check_output( [ 'xcrun', '--sdk', sdk, '--show-sdk-platform-path' ] ).strip()
175localpath = platformpath + "/Developer/usr/bin:/Applications/Xcode.app/Contents/Developer/usr/bin:/usr/bin:/bin:/usr/sbin:/sbin"
176plutil = "PATH=" + localpath + " " + subprocess.check_output( [ 'xcrun', '--sdk', sdk, '-f', 'plutil' ] ).strip()
177os.system( plutil + ' -convert binary1 ' + options.output )
178
179