OLD | NEW |
1 #!/usr/bin/python | 1 #!/usr/bin/python |
2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | 2 # Copyright (c) 2012 The Chromium Authors. 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 """Docbuilder for extension docs.""" | |
7 | |
8 import glob | |
9 import os | |
10 import os.path | |
11 import shutil | |
12 import sys | |
13 import time | |
14 import urllib | |
15 | |
16 from subprocess import Popen, PIPE | |
17 from optparse import OptionParser | |
18 | |
19 _script_path = os.path.realpath(__file__) | |
20 _build_dir = os.path.dirname(_script_path) | |
21 _base_dir = os.path.normpath(_build_dir + "/..") | |
22 _static_dir = _base_dir + "/static" | |
23 _js_dir = _base_dir + "/js" | |
24 _template_dir = _base_dir + "/template" | |
25 _samples_dir = _base_dir + "/examples" | |
26 _extension_api_dir = os.path.normpath(_base_dir + "/../api") | |
27 | |
28 _extension_api_json_schemas = glob.glob(_extension_api_dir + | |
29 '/[a-zA-Z0-9]*.json') | |
30 _extension_api_json_schemas += glob.glob(_extension_api_dir + | |
31 '/*/[a-zA-Z0-9]*.json') | |
32 _extension_api_idl_schemas = glob.glob(_extension_api_dir + | |
33 '/[a-zA-Z0-9]*.idl') | |
34 _extension_api_idl_schemas += glob.glob(_extension_api_dir + | |
35 '/*/[a-zA-Z0-9]*.idl') | |
36 _api_template_html = _template_dir + "/api_template.html" | |
37 _page_shell_html = _template_dir + "/page_shell.html" | |
38 _generator_html = _build_dir + "/generator.html" | |
39 _samples_json = _base_dir + "/samples.json" | |
40 | |
41 _expected_output_preamble = "#BEGIN" | |
42 _expected_output_postamble = "#END" | |
43 | |
44 # HACK! This is required because we can only depend on python 2.4 and | |
45 # the calling environment may not be setup to set the PYTHONPATH | |
46 # Insert at the front so that we pick up our simplejson even if there's a | |
47 # system simplejson installed, for predictable escaping. | |
48 sys.path.insert(0, os.path.normpath(_base_dir + | |
49 "/../../../../third_party")) | |
50 import simplejson as json | |
51 from directory import Sample | |
52 from directory import ApiManifest | |
53 from directory import SamplesManifest | |
54 | |
55 def RenderPages(family, dump_render_tree, single_page_name): | |
56 output_dir = os.path.join(_base_dir, family) | |
57 names = set(os.path.splitext(name)[0] for name in os.listdir(output_dir) | |
58 if not name.startswith(".") and name.endswith(".html")) | |
59 | |
60 # Allow the user to render a single page if they want | |
61 if single_page_name: | |
62 if single_page_name in names: | |
63 names = [single_page_name] | |
64 else: | |
65 return [] | |
66 | |
67 generator_url = "file:" + urllib.pathname2url(_generator_html) | |
68 generator_url += "?" + family + "|" + ",".join(names) | |
69 | |
70 # Start with a fresh copy of page shell for each file. | |
71 # Save the current contents so that we can look for changes later. | |
72 originals = {} | |
73 for name in names: | |
74 input_file = os.path.join(output_dir, name + ".html") | |
75 | |
76 if (os.path.isfile(input_file)): | |
77 originals[name] = open(input_file, 'rb').read() | |
78 os.remove(input_file) | |
79 else: | |
80 originals[name] = "" | |
81 | |
82 shutil.copy(_page_shell_html, input_file) | |
83 | |
84 print generator_url | |
85 | |
86 # Run DumpRenderTree and capture result | |
87 p = Popen([dump_render_tree, "--no-timeout", generator_url], stdout=PIPE) | |
88 | |
89 # The remaining output will be the content of the generated pages. | |
90 output = p.stdout.read() | |
91 | |
92 # Parse out just the JSON part. | |
93 begin = output.find(_expected_output_preamble) | |
94 end = output.rfind(_expected_output_postamble) | |
95 | |
96 if (begin < 0 or end < 0): | |
97 raise Exception("%s returned invalid output:\n\n%s" % | |
98 (dump_render_tree, output)) | |
99 | |
100 begin += len(_expected_output_preamble) | |
101 | |
102 try: | |
103 output_parsed = json.loads(output[begin:end]) | |
104 except ValueError, msg: | |
105 raise Exception("Could not parse DumpRenderTree output as JSON. Error: " + | |
106 msg + "\n\nOutput was:\n" + output) | |
107 | |
108 changed_files = [] | |
109 for name in names: | |
110 result = output_parsed[name].encode("utf8") + '\n' | |
111 | |
112 # Remove CRs that are appearing from captured DumpRenderTree output. | |
113 result = result.replace('\r', '') | |
114 | |
115 # Remove empty style attributes. | |
116 result = result.replace(' style=""', '') | |
117 | |
118 # Remove page_shell | |
119 input_file = os.path.join(output_dir, name + ".html") | |
120 os.remove(input_file) | |
121 | |
122 # Write output | |
123 open(input_file, 'wb').write(result) | |
124 if (originals[name] and result != originals[name]): | |
125 changed_files.append(input_file) | |
126 | |
127 return changed_files | |
128 | |
129 | |
130 def FindDumpRenderTree(): | |
131 # This is hacky. It is used to guess the location of the DumpRenderTree | |
132 chrome_dir = os.path.normpath(_base_dir + "/../../../") | |
133 src_dir = os.path.normpath(chrome_dir + "/../") | |
134 | |
135 search_locations = [] | |
136 | |
137 if (sys.platform in ('cygwin', 'win32')): | |
138 home_dir = os.path.normpath(os.getenv("HOMEDRIVE") + os.getenv("HOMEPATH")) | |
139 search_locations.append(src_dir + "/build/Release/DumpRenderTree.exe") | |
140 search_locations.append(src_dir + "/build/Debug/DumpRenderTree.exe") | |
141 search_locations.append(home_dir + "/bin/DumpRenderTree/" | |
142 "DumpRenderTree.exe") | |
143 | |
144 if (sys.platform.startswith('linux')): | |
145 search_locations.append(src_dir + "/sconsbuild/Release/DumpRenderTree") | |
146 search_locations.append(src_dir + "/out/Release/DumpRenderTree") | |
147 search_locations.append(src_dir + "/sconsbuild/Debug/DumpRenderTree") | |
148 search_locations.append(src_dir + "/out/Debug/DumpRenderTree") | |
149 search_locations.append(os.getenv("HOME") + "/bin/DumpRenderTree/" | |
150 "DumpRenderTree") | |
151 | |
152 if (sys.platform == 'darwin'): | |
153 search_locations.append(src_dir + | |
154 "/xcodebuild/Release/DumpRenderTree.app/Contents/MacOS/DumpRenderTree") | |
155 search_locations.append(src_dir + | |
156 "/xcodebuild/Debug/DumpRenderTree.app/Contents/MacOS/DumpRenderTree") | |
157 search_locations.append(src_dir + | |
158 "/out/Release/DumpRenderTree.app/Contents/MacOS/DumpRenderTree") | |
159 search_locations.append(src_dir + | |
160 "/out/Debug/DumpRenderTree.app/Contents/MacOS/DumpRenderTree") | |
161 search_locations.append(os.getenv("HOME") + "/bin/DumpRenderTree/" + | |
162 "DumpRenderTree.app/Contents/MacOS/DumpRenderTree") | |
163 | |
164 for loc in search_locations: | |
165 if os.path.isfile(loc): | |
166 return loc | |
167 | |
168 raise Exception("Could not find DumpRenderTree executable\n" | |
169 "**DumpRenderTree may need to be built**\n" | |
170 "Searched: \n" + "\n".join(search_locations) + "\n" | |
171 "To specify a path to DumpRenderTree use " | |
172 "--dump-render-tree-path") | |
173 | |
174 def main(): | 6 def main(): |
175 # Prevent windows from using cygwin python. | 7 print("build.py is now DEAD. Don't torture yourself with it any more.") |
176 if (sys.platform == "cygwin"): | 8 print("") |
177 sys.exit("Building docs not supported for cygwin python. Please run the " | 9 print("Please see server2/README for the new way to update docs.") |
178 "build.sh script instead, which uses depot_tools python.") | |
179 | |
180 parser = OptionParser() | |
181 parser.add_option("--dump-render-tree-path", dest="dump_render_tree_path", | |
182 metavar="PATH", | |
183 help="path to DumpRenderTree executable") | |
184 parser.add_option("--page-name", dest="page_name", metavar="PAGE", | |
185 help="only generate docs for PAGE.html") | |
186 parser.add_option("--nozip", dest="zips", action="store_false", | |
187 help="do not generate zip files for samples", | |
188 default=True) | |
189 options, args = parser.parse_args() | |
190 | |
191 # This is a script that converts the documentation from the old style (using | |
192 # build.py, etc.) to the new style. The new docs and docs server can be found | |
193 # in the ../server2 directory. | |
194 Popen(['python', | |
195 os.path.join(_base_dir, 'server2', 'converter.py'), | |
196 os.path.join(_base_dir, 'static'), | |
197 os.path.join(_base_dir, os.pardir, 'api'), | |
198 os.path.join(_base_dir, 'server2', 'templates', 'articles'), | |
199 os.path.join(_base_dir, 'server2', 'templates', 'intros'), | |
200 os.path.join(_base_dir, 'server2', 'templates', 'public'), | |
201 os.path.join(_base_dir, 'server2', 'static', 'images'), | |
202 '-r']) | |
203 | |
204 if (options.dump_render_tree_path and | |
205 os.path.isfile(options.dump_render_tree_path)): | |
206 dump_render_tree = options.dump_render_tree_path | |
207 else: | |
208 dump_render_tree = FindDumpRenderTree() | |
209 | |
210 # Load the manifest of existing API Methods | |
211 api_manifest = ApiManifest(_extension_api_json_schemas, | |
212 _extension_api_idl_schemas) | |
213 | |
214 # Write temporary JSON files based on the IDL inputs | |
215 api_manifest.generateJSONFromIDL() | |
216 | |
217 # Render a manifest file containing metadata about all the extension samples | |
218 samples_manifest = SamplesManifest(_samples_dir, _base_dir, api_manifest) | |
219 samples_manifest.writeToFile(_samples_json) | |
220 | |
221 # Write zipped versions of the samples listed in the manifest to the | |
222 # filesystem, unless the user has disabled it | |
223 modified_files = [] | |
224 if options.zips: | |
225 modified_files.extend(samples_manifest.writeZippedSamples()) | |
226 | |
227 doc_families = ["extensions", "apps"] | |
228 for family in doc_families: | |
229 modified_files.extend( | |
230 RenderPages(family, dump_render_tree, options.page_name)) | |
231 | |
232 if len(modified_files) == 0: | |
233 print "Output files match existing files. No changes made." | |
234 else: | |
235 print ("ATTENTION: EXTENSION DOCS HAVE CHANGED\n" + | |
236 "The following files have been modified and should be checked\n" + | |
237 "into source control (ideally in the same changelist as the\n" + | |
238 "underlying files that resulting in their changing).") | |
239 for f in modified_files: | |
240 print " * %s" % f | |
241 | |
242 # Hack. Sleep here, otherwise windows doesn't properly close the debug.log | |
243 # and the os.remove will fail with a "Permission denied". | |
244 time.sleep(1) | |
245 debug_log = os.path.normpath(_build_dir + "/" + "debug.log") | |
246 if (os.path.isfile(debug_log)): | |
247 os.remove(debug_log) | |
248 | |
249 # Cleanup our temporary IDL->JSON files | |
250 api_manifest.cleanupGeneratedFiles() | |
251 | |
252 if 'EX_OK' in dir(os): | |
253 return os.EX_OK | |
254 else: | |
255 return 0 | |
256 | 10 |
257 if __name__ == '__main__': | 11 if __name__ == '__main__': |
258 sys.exit(main()) | 12 main() |
OLD | NEW |