summaryrefslogtreecommitdiffstats
path: root/chrome/common/extensions/docs/build/build.py
blob: 67957549c03f79b35584d0826c7593ed5dc8df77 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
#!/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"
_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"

_expected_output_preamble = "<!DOCTYPE html>"
_expected_output_postamble = "</body></html>"

# 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

def RenderPage(name, test_shell):
  """
  Calls test_shell --layout-tests .../generator.html?<name> and writes the
  result to .../docs/<name>.html
  """
  if not name:
    raise Exception("RenderPage called with empty name")

  generator_url = "file:" + urllib.pathname2url(_generator_html) + "?" + name
  input_file = _base_dir + "/" + name + ".html"

  # Copy page_shell to destination output and move aside original, if it exists.
  original = None
  if (os.path.isfile(input_file)):
    original = open(input_file, 'rb').read()
    os.remove(input_file)

  shutil.copy(_page_shell_html, input_file)

  # Run test_shell and capture result
  p = Popen([test_shell, "--layout-tests", generator_url],
      stdout=PIPE)

  # the remaining output will be the content of the generated page.
  result = p.stdout.read()

  content_start = result.find(_expected_output_preamble)
  content_end = result.find(_expected_output_postamble)
  if (content_start < 0):
    if (result.startswith("#TEST_TIMED_OUT")):
      raise Exception("test_shell returned TEST_TIMED_OUT.\n" +
                        "Their was probably a problem with generating the " +
                        "page\n" +
                        "Try copying template/page_shell.html to:\n" +
                        input_file +
                        "\nAnd open it in chrome using the file: scheme.\n" +
                        "Look from javascript errors via the inspector.")
    raise Exception("test_shell returned unexpected output: " + result)
  result = result[content_start:content_end + len(_expected_output_postamble)] + "\n"

  # remove the trailing #EOF that test shell appends to the output.
  result = result.replace('#EOF', '')

  # Remove page_shell
  os.remove(input_file)

  # Remove CRs that are appearing from captured test_shell output.
  result = result.replace('\r', '')

  # Write output
  open(input_file, 'wb').write(result)
  if (original and result == original):
    return None
  else:
    return input_file

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 + "/Debug/test_shell.exe")
    search_locations.append(chrome_dir + "/Release/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/Debug/test_shell")
    search_locations.append(src_dir + "/out/Debug/test_shell")
    search_locations.append(src_dir + "/sconsbuild/Release/test_shell")
    search_locations.append(src_dir + "/out/Release/test_shell")
    search_locations.append(os.getenv("HOME") + "/bin/test_shell/test_shell")

  if (sys.platform == 'darwin'):
    search_locations.append(src_dir +
        "/xcodebuild/Debug/TestShell.app/Contents/MacOS/TestShell")
    search_locations.append(src_dir +
        "/xcodebuild/Release/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 GetAPIModuleNames():
  try:
    contents = open(_extension_api_json, 'r').read()
  except IOError, msg:
    raise Exception("Failed to read the file that defines the extensions API.  "
                    "The specific error was: %s." % msg)

  try:
    extension_api = json.loads(contents, encoding="ASCII")
  except ValueError, msg:
    raise Exception("File %s has a syntax error: %s" %
                    (_extension_api_json, msg))
  # Exclude modules with a "nodoc" property.
  return set(module['namespace'].encode() for module in extension_api
             if "nodoc" not in module)

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"))

def main():
  # Prevent windows from using cygwin python.
  if (sys.platform == "cygwin"):
    raise Exception("Building docs not supported for cygwin python.\n"
                    "Please run the build.bat script.")

  parser = OptionParser()
  parser.add_option("--test-shell-path", dest="test_shell_path")
  (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()

  # Read static file names
  static_names = GetStaticFileNames()

  # Read module names
  module_names = GetAPIModuleNames()

  # All pages to generate
  page_names = static_names | module_names

  modified_files = []
  for page in page_names:
    modified_file = RenderPage(page, test_shell)
    if (modified_file):
      modified_files.append(modified_file)

  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 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)

  return os.EX_OK

if __name__ == '__main__':
  sys.exit(main())