OLD | NEW |
1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
2 # Copyright (c) 2012 Google Inc. All rights reserved. | 2 # Copyright (c) 2012 Google Inc. All rights reserved. |
3 # Use of this source code is governed by a BSD-style license that can be | 3 # Use of this source code is governed by a BSD-style license that can be |
4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
5 | 5 |
6 """Utility functions to perform Xcode-style build steps. | 6 """Utility functions to perform Xcode-style build steps. |
7 | 7 |
8 These functions are executed via gyp-mac-tool when using the Makefile generator. | 8 These functions are executed via gyp-mac-tool when using the Makefile generator. |
9 """ | 9 """ |
10 | 10 |
(...skipping 27 matching lines...) Expand all Loading... |
38 if len(args) < 1: | 38 if len(args) < 1: |
39 raise Exception("Not enough arguments") | 39 raise Exception("Not enough arguments") |
40 | 40 |
41 method = "Exec%s" % self._CommandifyName(args[0]) | 41 method = "Exec%s" % self._CommandifyName(args[0]) |
42 return getattr(self, method)(*args[1:]) | 42 return getattr(self, method)(*args[1:]) |
43 | 43 |
44 def _CommandifyName(self, name_string): | 44 def _CommandifyName(self, name_string): |
45 """Transforms a tool name like copy-info-plist to CopyInfoPlist""" | 45 """Transforms a tool name like copy-info-plist to CopyInfoPlist""" |
46 return name_string.title().replace('-', '') | 46 return name_string.title().replace('-', '') |
47 | 47 |
48 def ExecCopyBundleResource(self, source, dest): | 48 def ExecCopyBundleResource(self, source, dest, convert_to_binary): |
49 """Copies a resource file to the bundle/Resources directory, performing any | 49 """Copies a resource file to the bundle/Resources directory, performing any |
50 necessary compilation on each resource.""" | 50 necessary compilation on each resource.""" |
51 extension = os.path.splitext(source)[1].lower() | 51 extension = os.path.splitext(source)[1].lower() |
52 if os.path.isdir(source): | 52 if os.path.isdir(source): |
53 # Copy tree. | 53 # Copy tree. |
54 # TODO(thakis): This copies file attributes like mtime, while the | 54 # TODO(thakis): This copies file attributes like mtime, while the |
55 # single-file branch below doesn't. This should probably be changed to | 55 # single-file branch below doesn't. This should probably be changed to |
56 # be consistent with the single-file branch. | 56 # be consistent with the single-file branch. |
57 if os.path.exists(dest): | 57 if os.path.exists(dest): |
58 shutil.rmtree(dest) | 58 shutil.rmtree(dest) |
59 shutil.copytree(source, dest) | 59 shutil.copytree(source, dest) |
60 elif extension == '.xib': | 60 elif extension == '.xib': |
61 return self._CopyXIBFile(source, dest) | 61 return self._CopyXIBFile(source, dest) |
62 elif extension == '.storyboard': | 62 elif extension == '.storyboard': |
63 return self._CopyXIBFile(source, dest) | 63 return self._CopyXIBFile(source, dest) |
64 elif extension == '.strings': | 64 elif extension == '.strings': |
65 self._CopyStringsFile(source, dest) | 65 self._CopyStringsFile(source, dest, convert_to_binary) |
66 else: | 66 else: |
67 shutil.copy(source, dest) | 67 shutil.copy(source, dest) |
68 | 68 |
69 def _CopyXIBFile(self, source, dest): | 69 def _CopyXIBFile(self, source, dest): |
70 """Compiles a XIB file with ibtool into a binary plist in the bundle.""" | 70 """Compiles a XIB file with ibtool into a binary plist in the bundle.""" |
71 | 71 |
72 # ibtool sometimes crashes with relative paths. See crbug.com/314728. | 72 # ibtool sometimes crashes with relative paths. See crbug.com/314728. |
73 base = os.path.dirname(os.path.realpath(__file__)) | 73 base = os.path.dirname(os.path.realpath(__file__)) |
74 if os.path.relpath(source): | 74 if os.path.relpath(source): |
75 source = os.path.join(base, source) | 75 source = os.path.join(base, source) |
76 if os.path.relpath(dest): | 76 if os.path.relpath(dest): |
77 dest = os.path.join(base, dest) | 77 dest = os.path.join(base, dest) |
78 | 78 |
79 args = ['xcrun', 'ibtool', '--errors', '--warnings', '--notices', | 79 args = ['xcrun', 'ibtool', '--errors', '--warnings', '--notices', |
80 '--output-format', 'human-readable-text', '--compile', dest, source] | 80 '--output-format', 'human-readable-text', '--compile', dest, source] |
81 ibtool_section_re = re.compile(r'/\*.*\*/') | 81 ibtool_section_re = re.compile(r'/\*.*\*/') |
82 ibtool_re = re.compile(r'.*note:.*is clipping its content') | 82 ibtool_re = re.compile(r'.*note:.*is clipping its content') |
83 ibtoolout = subprocess.Popen(args, stdout=subprocess.PIPE) | 83 ibtoolout = subprocess.Popen(args, stdout=subprocess.PIPE) |
84 current_section_header = None | 84 current_section_header = None |
85 for line in ibtoolout.stdout: | 85 for line in ibtoolout.stdout: |
86 if ibtool_section_re.match(line): | 86 if ibtool_section_re.match(line): |
87 current_section_header = line | 87 current_section_header = line |
88 elif not ibtool_re.match(line): | 88 elif not ibtool_re.match(line): |
89 if current_section_header: | 89 if current_section_header: |
90 sys.stdout.write(current_section_header) | 90 sys.stdout.write(current_section_header) |
91 current_section_header = None | 91 current_section_header = None |
92 sys.stdout.write(line) | 92 sys.stdout.write(line) |
93 return ibtoolout.returncode | 93 return ibtoolout.returncode |
94 | 94 |
95 def _CopyStringsFile(self, source, dest): | 95 def _ConvertToBinary(self, dest): |
| 96 subprocess.check_call([ |
| 97 'xcrun', 'plutil', '-convert', 'binary1', '-o', dest, dest]) |
| 98 |
| 99 def _CopyStringsFile(self, source, dest, convert_to_binary): |
96 """Copies a .strings file using iconv to reconvert the input into UTF-16.""" | 100 """Copies a .strings file using iconv to reconvert the input into UTF-16.""" |
97 input_code = self._DetectInputEncoding(source) or "UTF-8" | 101 input_code = self._DetectInputEncoding(source) or "UTF-8" |
98 | 102 |
99 # Xcode's CpyCopyStringsFile / builtin-copyStrings seems to call | 103 # Xcode's CpyCopyStringsFile / builtin-copyStrings seems to call |
100 # CFPropertyListCreateFromXMLData() behind the scenes; at least it prints | 104 # CFPropertyListCreateFromXMLData() behind the scenes; at least it prints |
101 # CFPropertyListCreateFromXMLData(): Old-style plist parser: missing | 105 # CFPropertyListCreateFromXMLData(): Old-style plist parser: missing |
102 # semicolon in dictionary. | 106 # semicolon in dictionary. |
103 # on invalid files. Do the same kind of validation. | 107 # on invalid files. Do the same kind of validation. |
104 import CoreFoundation | 108 import CoreFoundation |
105 s = open(source, 'rb').read() | 109 s = open(source, 'rb').read() |
106 d = CoreFoundation.CFDataCreate(None, s, len(s)) | 110 d = CoreFoundation.CFDataCreate(None, s, len(s)) |
107 _, error = CoreFoundation.CFPropertyListCreateFromXMLData(None, d, 0, None) | 111 _, error = CoreFoundation.CFPropertyListCreateFromXMLData(None, d, 0, None) |
108 if error: | 112 if error: |
109 return | 113 return |
110 | 114 |
111 fp = open(dest, 'wb') | 115 fp = open(dest, 'wb') |
112 fp.write(s.decode(input_code).encode('UTF-16')) | 116 fp.write(s.decode(input_code).encode('UTF-16')) |
113 fp.close() | 117 fp.close() |
114 | 118 |
| 119 if convert_to_binary == 'True': |
| 120 self._ConvertToBinary(dest) |
| 121 |
115 def _DetectInputEncoding(self, file_name): | 122 def _DetectInputEncoding(self, file_name): |
116 """Reads the first few bytes from file_name and tries to guess the text | 123 """Reads the first few bytes from file_name and tries to guess the text |
117 encoding. Returns None as a guess if it can't detect it.""" | 124 encoding. Returns None as a guess if it can't detect it.""" |
118 fp = open(file_name, 'rb') | 125 fp = open(file_name, 'rb') |
119 try: | 126 try: |
120 header = fp.read(3) | 127 header = fp.read(3) |
121 except e: | 128 except e: |
122 fp.close() | 129 fp.close() |
123 return None | 130 return None |
124 fp.close() | 131 fp.close() |
125 if header.startswith("\xFE\xFF"): | 132 if header.startswith("\xFE\xFF"): |
126 return "UTF-16" | 133 return "UTF-16" |
127 elif header.startswith("\xFF\xFE"): | 134 elif header.startswith("\xFF\xFE"): |
128 return "UTF-16" | 135 return "UTF-16" |
129 elif header.startswith("\xEF\xBB\xBF"): | 136 elif header.startswith("\xEF\xBB\xBF"): |
130 return "UTF-8" | 137 return "UTF-8" |
131 else: | 138 else: |
132 return None | 139 return None |
133 | 140 |
134 def ExecCopyInfoPlist(self, source, dest, *keys): | 141 def ExecCopyInfoPlist(self, source, dest, convert_to_binary, *keys): |
135 """Copies the |source| Info.plist to the destination directory |dest|.""" | 142 """Copies the |source| Info.plist to the destination directory |dest|.""" |
136 # Read the source Info.plist into memory. | 143 # Read the source Info.plist into memory. |
137 fd = open(source, 'r') | 144 fd = open(source, 'r') |
138 lines = fd.read() | 145 lines = fd.read() |
139 fd.close() | 146 fd.close() |
140 | 147 |
141 # Insert synthesized key/value pairs (e.g. BuildMachineOSBuild). | 148 # Insert synthesized key/value pairs (e.g. BuildMachineOSBuild). |
142 plist = plistlib.readPlistFromString(lines) | 149 plist = plistlib.readPlistFromString(lines) |
143 if keys: | 150 if keys: |
144 plist = dict(plist.items() + json.loads(keys[0]).items()) | 151 plist = dict(plist.items() + json.loads(keys[0]).items()) |
(...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
178 | 185 |
179 # Write out the file with variables replaced. | 186 # Write out the file with variables replaced. |
180 fd = open(dest, 'w') | 187 fd = open(dest, 'w') |
181 fd.write(lines) | 188 fd.write(lines) |
182 fd.close() | 189 fd.close() |
183 | 190 |
184 # Now write out PkgInfo file now that the Info.plist file has been | 191 # Now write out PkgInfo file now that the Info.plist file has been |
185 # "compiled". | 192 # "compiled". |
186 self._WritePkgInfo(dest) | 193 self._WritePkgInfo(dest) |
187 | 194 |
| 195 if convert_to_binary == 'True': |
| 196 self._ConvertToBinary(dest) |
| 197 |
188 def _WritePkgInfo(self, info_plist): | 198 def _WritePkgInfo(self, info_plist): |
189 """This writes the PkgInfo file from the data stored in Info.plist.""" | 199 """This writes the PkgInfo file from the data stored in Info.plist.""" |
190 plist = plistlib.readPlist(info_plist) | 200 plist = plistlib.readPlist(info_plist) |
191 if not plist: | 201 if not plist: |
192 return | 202 return |
193 | 203 |
194 # Only create PkgInfo for executable types. | 204 # Only create PkgInfo for executable types. |
195 package_type = plist['CFBundlePackageType'] | 205 package_type = plist['CFBundlePackageType'] |
196 if package_type != 'APPL': | 206 if package_type != 'APPL': |
197 return | 207 return |
(...skipping 393 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
591 data = data.replace('$(%s)' % key, value) | 601 data = data.replace('$(%s)' % key, value) |
592 return data | 602 return data |
593 if isinstance(data, list): | 603 if isinstance(data, list): |
594 return [self._ExpandVariables(v, substitutions) for v in data] | 604 return [self._ExpandVariables(v, substitutions) for v in data] |
595 if isinstance(data, dict): | 605 if isinstance(data, dict): |
596 return {k: self._ExpandVariables(data[k], substitutions) for k in data} | 606 return {k: self._ExpandVariables(data[k], substitutions) for k in data} |
597 return data | 607 return data |
598 | 608 |
599 if __name__ == '__main__': | 609 if __name__ == '__main__': |
600 sys.exit(main(sys.argv[1:])) | 610 sys.exit(main(sys.argv[1:])) |
OLD | NEW |