blob: 220924e7d1387d5f80199017421be813e5124273 [file] [log] [blame]
#!/usr/bin/python
# Copyright (c) 2009 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Docbuilder for extension docs."""
import os
import os.path
import shutil
import sys
import time
import urllib
from subprocess import Popen, PIPE
from optparse import OptionParser
_script_path = os.path.realpath(__file__)
_build_dir = os.path.dirname(_script_path)
_base_dir = os.path.normpath(_build_dir + "/..")
_static_dir = _base_dir + "/static"
_js_dir = _base_dir + "/js"
_template_dir = _base_dir + "/template"
_samples_dir = _base_dir + "/examples"
_extension_api_dir = os.path.normpath(_base_dir + "/../api")
_extension_api_json = _extension_api_dir + "/extension_api.json"
_api_template_html = _template_dir + "/api_template.html"
_page_shell_html = _template_dir + "/page_shell.html"
_generator_html = _build_dir + "/generator.html"
_samples_json = _base_dir + "/samples.json"
_expected_output_preamble = "#BEGIN"
_expected_output_postamble = "#END"
# HACK! This is required because we can only depend on python 2.4 and
# the calling environment may not be setup to set the PYTHONPATH
sys.path.append(os.path.normpath(_base_dir +
"/../../../../third_party"))
import simplejson as json
from directory import Sample
from directory import ApiManifest
from directory import SamplesManifest
def RenderPages(names, test_shell):
"""
Calls test_shell --layout-tests .../generator.html?<names> and writes the
results to .../docs/<name>.html
"""
if not names:
raise Exception("RenderPage called with empty names param")
generator_url = "file:" + urllib.pathname2url(_generator_html)
generator_url += "?" + ",".join(names)
# Start with a fresh copy of page shell for each file.
# Save the current contents so that we can look for changes later.
originals = {}
for name in names:
input_file = _base_dir + "/" + name + ".html"
if (os.path.isfile(input_file)):
originals[name] = open(input_file, 'rb').read()
os.remove(input_file)
else:
originals[name] = ""
shutil.copy(_page_shell_html, input_file)
# Run test_shell and capture result
test_shell_timeout = 1000 * 60 * 5 # five minutes
p = Popen(
[test_shell, "--layout-tests", "--time-out-ms=%s" % test_shell_timeout,
generator_url],
stdout=PIPE)
# The remaining output will be the content of the generated pages.
output = p.stdout.read()
# Parse out just the JSON part.
begin = output.find(_expected_output_preamble)
end = output.rfind(_expected_output_postamble)
if (begin < 0 or end < 0):
raise Exception ("test_shell returned invalid output:\n\n" + output)
begin += len(_expected_output_preamble)
try:
output_parsed = json.loads(output[begin:end])
except ValueError, msg:
raise Exception("Could not parse test_shell output as JSON. Error: " + msg +
"\n\nOutput was:\n" + output)
changed_files = []
for name in names:
result = output_parsed[name].encode("utf8") + '\n'
# Remove CRs that are appearing from captured test_shell output.
result = result.replace('\r', '')
# Remove page_shell
input_file = _base_dir + "/" + name + ".html"
os.remove(input_file)
# Write output
open(input_file, 'wb').write(result)
if (originals[name] and result != originals[name]):
changed_files.append(input_file)
return changed_files
def FindTestShell():
# This is hacky. It is used to guess the location of the test_shell
chrome_dir = os.path.normpath(_base_dir + "/../../../")
src_dir = os.path.normpath(chrome_dir + "/../")
search_locations = []
if (sys.platform in ('cygwin', 'win32')):
home_dir = os.path.normpath(os.getenv("HOMEDRIVE") + os.getenv("HOMEPATH"))
search_locations.append(chrome_dir + "/Release/test_shell.exe")
search_locations.append(chrome_dir + "/Debug/test_shell.exe")
search_locations.append(home_dir + "/bin/test_shell/" +
"test_shell.exe")
if (sys.platform in ('linux', 'linux2')):
search_locations.append(src_dir + "/sconsbuild/Release/test_shell")
search_locations.append(src_dir + "/out/Release/test_shell")
search_locations.append(src_dir + "/sconsbuild/Debug/test_shell")
search_locations.append(src_dir + "/out/Debug/test_shell")
search_locations.append(os.getenv("HOME") + "/bin/test_shell/test_shell")
if (sys.platform == 'darwin'):
search_locations.append(src_dir +
"/xcodebuild/Release/TestShell.app/Contents/MacOS/TestShell")
search_locations.append(src_dir +
"/xcodebuild/Debug/TestShell.app/Contents/MacOS/TestShell")
search_locations.append(os.getenv("HOME") + "/bin/test_shell/" +
"TestShell.app/Contents/MacOS/TestShell")
for loc in search_locations:
if os.path.isfile(loc):
return loc
raise Exception("Could not find test_shell executable\n" +
"**test_shell may need to be built**\n" +
"Searched: \n" + "\n".join(search_locations) + "\n" +
"To specify a path to test_shell use --test-shell-path")
def GetStaticFileNames():
static_files = os.listdir(_static_dir)
return set(os.path.splitext(file_name)[0]
for file_name in static_files
if file_name.endswith(".html") and not file_name.startswith("."))
def main():
# Prevent windows from using cygwin python.
if (sys.platform == "cygwin"):
sys.exit("Building docs not supported for cygwin python. Please run the "
"build.sh script instead, which uses depot_tools python.")
parser = OptionParser()
parser.add_option("--test-shell-path", dest="test_shell_path",
metavar="PATH",
help="path to test_shell executable")
parser.add_option("--page-name", dest="page_name", metavar="PAGE",
help="only generate docs for PAGE.html")
parser.add_option("--nozip", dest="zips", action="store_false",
help="do not generate zip files for samples",
default=True)
(options, args) = parser.parse_args()
if (options.test_shell_path and os.path.isfile(options.test_shell_path)):
test_shell = options.test_shell_path
else:
test_shell = FindTestShell()
# Load the manifest of existing API Methods
api_manifest = ApiManifest(_extension_api_json)
# Read static file names
static_names = GetStaticFileNames()
# Read module names
module_names = api_manifest.getModuleNames()
# All pages to generate
page_names = static_names | module_names
# Allow the user to render a single page if they want
if options.page_name:
if options.page_name in page_names:
page_names = [options.page_name]
else:
raise Exception("--page-name argument must be one of %s." %
', '.join(sorted(page_names)))
# Render a manifest file containing metadata about all the extension samples
samples_manifest = SamplesManifest(_samples_dir, _base_dir, api_manifest)
samples_manifest.writeToFile(_samples_json)
# Write zipped versions of the samples listed in the manifest to the
# filesystem, unless the user has disabled it
if options.zips:
modified_zips = samples_manifest.writeZippedSamples()
else:
modified_zips = []
modified_files = RenderPages(page_names, test_shell)
modified_files.extend(modified_zips)
if len(modified_files) == 0:
print "Output files match existing files. No changes made."
else:
print ("ATTENTION: EXTENSION DOCS HAVE CHANGED\n" +
"The following files have been modified and should be checked\n" +
"into source control (ideally in the same changelist as the\n" +
"underlying files that resulting in their changing).")
for f in modified_files:
print " * %s" % f
# Hack. Sleep here, otherwise windows doesn't properly close the debug.log
# and the os.remove will fail with a "Permission denied".
time.sleep(1)
debug_log = os.path.normpath(_build_dir + "/" + "debug.log")
if (os.path.isfile(debug_log)):
os.remove(debug_log)
if 'EX_OK' in dir(os):
return os.EX_OK
else:
return 0
if __name__ == '__main__':
sys.exit(main())