summaryrefslogtreecommitdiffstats
path: root/chrome/common/extensions/docs/build/build.py
blob: 220924e7d1387d5f80199017421be813e5124273 (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
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
#!/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())