1#!/usr/bin/python
2
3# Copyright (c) 2014 The Chromium Authors. All rights reserved.
4# Use of this source code is governed by a BSD-style license that can be
5# found in the LICENSE file.
6
7import collections
8import json
9import optparse
10import os
11import shutil
12import subprocess
13import sys
14import tempfile
15import urllib2
16
17
18SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
19DOC_DIR = os.path.dirname(SCRIPT_DIR)
20
21
22ChannelInfo = collections.namedtuple('ChannelInfo', ['branch', 'version'])
23
24
25def Trace(msg):
26  if Trace.verbose:
27    sys.stderr.write(str(msg) + '\n')
28
29Trace.verbose = False
30
31
32def GetChannelInfo():
33  url = 'http://omahaproxy.appspot.com/json'
34  u = urllib2.urlopen(url)
35  try:
36    data = json.loads(u.read())
37  finally:
38    u.close()
39
40  channel_info = {}
41  for os_row in data:
42    osname = os_row['os']
43    if osname not in ('win', 'mac', 'linux'):
44      continue
45    for version_row in os_row['versions']:
46      channel = version_row['channel']
47      # We don't display canary docs.
48      if channel == 'canary':
49        continue
50
51      version = version_row['version'].split('.')[0]  # Major version
52      branch = version_row['true_branch']
53      if branch is None:
54        branch = 'trunk'
55
56      if channel in channel_info:
57        existing_info = channel_info[channel]
58        if branch != existing_info.branch:
59          sys.stderr.write('Warning: found different branch numbers for '
60              'channel %s: %s vs %s. Using %s.\n' % (
61              channel, branch, existing_info.branch, existing_info.branch))
62      else:
63        channel_info[channel] = ChannelInfo(branch, version)
64
65  return channel_info
66
67
68def RemoveFile(filename):
69  if os.path.exists(filename):
70    os.remove(filename)
71
72
73def RemoveDir(dirname):
74  if os.path.exists(dirname):
75    shutil.rmtree(dirname)
76
77
78def GetSVNRepositoryRoot(branch):
79  if branch == 'trunk':
80    return 'http://src.chromium.org/chrome/trunk/src'
81  return 'http://src.chromium.org/chrome/branches/%s/src' % branch
82
83
84def CheckoutPepperDocs(branch, doc_dirname):
85  Trace('Removing directory %s' % doc_dirname)
86  RemoveDir(doc_dirname)
87
88  svn_root_url = GetSVNRepositoryRoot(branch)
89
90  for subdir in ('api', 'generators', 'cpp', 'utility'):
91    url = svn_root_url + '/ppapi/%s' % subdir
92    cmd = ['svn', 'co', url, os.path.join(doc_dirname, subdir)]
93    Trace('Checking out docs into %s:\n  %s' % (doc_dirname, ' '.join(cmd)))
94    subprocess.check_call(cmd)
95
96  # The IDL generator needs PLY (a python lexing library); check it out into
97  # generators.
98  url = svn_root_url + '/third_party/ply'
99  ply_dirname = os.path.join(doc_dirname, 'generators', 'ply')
100  cmd = ['svn', 'co', url, ply_dirname]
101  Trace('Checking out PLY into %s:\n  %s' % (ply_dirname, ' '.join(cmd)))
102  subprocess.check_call(cmd)
103
104
105def FixPepperDocLinks(doc_dirname):
106  # TODO(binji): We can remove this step when the correct links are in the
107  # stable branch.
108  Trace('Looking for links to fix in Pepper headers...')
109  for root, dirs, filenames in os.walk(doc_dirname):
110    # Don't recurse into .svn
111    if '.svn' in dirs:
112      dirs.remove('.svn')
113
114    for filename in filenames:
115      header_filename = os.path.join(root, filename)
116      Trace('  Checking file %r...' % header_filename)
117      replacements = {
118        '<a href="/native-client/{{pepperversion}}/devguide/coding/audio">':
119            '<a href="/native-client/devguide/coding/audio.html">',
120        '<a href="/native-client/devguide/coding/audio">':
121            '<a href="/native-client/devguide/coding/audio.html">',
122        '<a href="/native-client/{{pepperversion}}/pepperc/globals_defs"':
123            '<a href="globals_defs.html"',
124        '<a href="../pepperc/ppb__image__data_8h.html">':
125            '<a href="../c/ppb__image__data_8h.html">'}
126
127      with open(header_filename) as f:
128        lines = []
129        replaced = False
130        for line in f:
131          for find, replace in replacements.iteritems():
132            pos = line.find(find)
133            if pos != -1:
134              Trace('    Found %r...' % find)
135              replaced = True
136              line = line[:pos] + replace + line[pos + len(find):]
137          lines.append(line)
138
139      if replaced:
140        Trace('  Writing new file.')
141        with open(header_filename, 'w') as f:
142          f.writelines(lines)
143
144
145def GenerateCHeaders(pepper_version, doc_dirname):
146  script = os.path.join(os.pardir, 'generators', 'generator.py')
147  cwd = os.path.join(doc_dirname, 'api')
148  out_dirname = os.path.join(os.pardir, 'c')
149  cmd = [sys.executable, script, '--cgen', '--release', 'M' + pepper_version,
150         '--wnone', '--dstroot', out_dirname]
151  Trace('Generating C Headers for version %s\n  %s' % (
152      pepper_version, ' '.join(cmd)))
153  subprocess.check_call(cmd, cwd=cwd)
154
155
156def GenerateDoxyfile(template_filename, out_dirname, doc_dirname, doxyfile):
157  Trace('Writing Doxyfile "%s" (from template %s)' % (
158    doxyfile, template_filename))
159
160  with open(template_filename) as f:
161    data = f.read()
162
163  with open(doxyfile, 'w') as f:
164    f.write(data % {
165      'out_dirname': out_dirname,
166      'doc_dirname': doc_dirname,
167      'script_dirname': SCRIPT_DIR})
168
169
170def RunDoxygen(out_dirname, doxyfile):
171  Trace('Removing old output directory %s' % out_dirname)
172  RemoveDir(out_dirname)
173
174  Trace('Making new output directory %s' % out_dirname)
175  os.makedirs(out_dirname)
176
177  cmd = ['doxygen', doxyfile]
178  Trace('Running Doxygen:\n  %s' % ' '.join(cmd))
179  subprocess.check_call(cmd)
180
181
182def RunDoxyCleanup(out_dirname):
183  script = os.path.join(SCRIPT_DIR, 'doxy_cleanup.py')
184  cmd = [sys.executable, script, out_dirname]
185  if Trace.verbose:
186    cmd.append('-v')
187  Trace('Running doxy_cleanup:\n  %s' % ' '.join(cmd))
188  subprocess.check_call(cmd)
189
190
191def RunRstIndex(kind, channel, pepper_version, out_dirname, out_rst_filename):
192  assert kind in ('root', 'c', 'cpp')
193  script = os.path.join(SCRIPT_DIR, 'rst_index.py')
194  cmd = [sys.executable, script,
195         '--' + kind,
196         '--channel', channel,
197         '--version', pepper_version,
198         out_dirname,
199         '-o', out_rst_filename]
200  Trace('Running rst_index:\n  %s' % ' '.join(cmd))
201  subprocess.check_call(cmd)
202
203
204def GenerateDocs(root_dirname, channel, pepper_version, branch):
205  Trace('Generating docs for %s (branch %s)' % (channel, branch))
206  pepper_dirname = 'pepper_%s' % channel
207  out_dirname = os.path.join(root_dirname, pepper_dirname)
208
209  try:
210    svn_dirname = tempfile.mkdtemp(prefix=pepper_dirname)
211    doxyfile_dirname = tempfile.mkdtemp(prefix='%s_doxyfiles' % pepper_dirname)
212
213    CheckoutPepperDocs(branch, svn_dirname)
214    FixPepperDocLinks(svn_dirname)
215    GenerateCHeaders(pepper_version, svn_dirname)
216
217    doxyfile_c = ''
218    doxyfile_cpp = ''
219
220    # Generate Root index
221    rst_index_root = os.path.join(DOC_DIR, pepper_dirname, 'index.rst')
222    RunRstIndex('root', channel, pepper_version, out_dirname, rst_index_root)
223
224    # Generate C docs
225    out_dirname_c = os.path.join(out_dirname, 'c')
226    doxyfile_c = os.path.join(doxyfile_dirname, 'Doxyfile.c.%s' % channel)
227    doxyfile_c_template = os.path.join(SCRIPT_DIR, 'Doxyfile.c.template')
228    rst_index_c = os.path.join(DOC_DIR, pepper_dirname, 'c', 'index.rst')
229    GenerateDoxyfile(doxyfile_c_template, out_dirname_c, svn_dirname,
230                     doxyfile_c)
231    RunDoxygen(out_dirname_c, doxyfile_c)
232    RunDoxyCleanup(out_dirname_c)
233    RunRstIndex('c', channel, pepper_version, out_dirname_c, rst_index_c)
234
235    # Generate C++ docs
236    out_dirname_cpp = os.path.join(out_dirname, 'cpp')
237    doxyfile_cpp = os.path.join(doxyfile_dirname, 'Doxyfile.cpp.%s' % channel)
238    doxyfile_cpp_template = os.path.join(SCRIPT_DIR, 'Doxyfile.cpp.template')
239    rst_index_cpp = os.path.join(DOC_DIR, pepper_dirname, 'cpp', 'index.rst')
240    GenerateDoxyfile(doxyfile_cpp_template, out_dirname_cpp, svn_dirname,
241                     doxyfile_cpp)
242    RunDoxygen(out_dirname_cpp, doxyfile_cpp)
243    RunDoxyCleanup(out_dirname_cpp)
244    RunRstIndex('cpp', channel, pepper_version, out_dirname_cpp, rst_index_cpp)
245  finally:
246    # Cleanup
247    RemoveDir(svn_dirname)
248    RemoveDir(doxyfile_dirname)
249
250
251def main(argv):
252  parser = optparse.OptionParser(usage='Usage: %prog [options] <out_directory>')
253  parser.add_option('-v', '--verbose',
254                    help='Verbose output', action='store_true')
255  options, dirs = parser.parse_args(argv)
256
257  if options.verbose:
258    Trace.verbose = True
259
260  if len(dirs) != 1:
261    parser.error('Expected an output directory')
262
263  channel_info = GetChannelInfo()
264  for channel, info in channel_info.iteritems():
265    GenerateDocs(dirs[0], channel, info.version, info.branch)
266
267  return 0
268
269
270if __name__ == '__main__':
271  try:
272    rtn = main(sys.argv[1:])
273  except KeyboardInterrupt:
274    sys.stderr.write('%s: interrupted\n' % os.path.basename(__file__))
275    rtn = 1
276  sys.exit(rtn)
277