AudioSessionManger is applied to getUserMedia.
[framework/web/webkit-efl.git] / Tools / gtk / gtkdoc.py
1 # Copyright (C) 2011 Igalia S.L.
2 #
3 # This library is free software; you can redistribute it and/or
4 # modify it under the terms of the GNU Lesser General Public
5 # License as published by the Free Software Foundation; either
6 # version 2 of the License, or (at your option) any later version.
7 #
8 # This library is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
11 # Lesser General Public License for more details.
12 #
13 # You should have received a copy of the GNU Lesser General Public
14 # License along with this library; if not, write to the Free Software
15 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
16
17 import errno
18 import logging
19 import os
20 import os.path
21 import subprocess
22 import sys
23
24
25 class GTKDoc(object):
26
27     """Class that controls a gtkdoc run.
28
29     Each instance of this class represents one gtkdoc configuration
30     and set of documentation. The gtkdoc package is a series of tools
31     run consecutively which converts inline C/C++ documentation into
32     docbook files and then into HTML. This class is suitable for
33     generating documentation or simply verifying correctness.
34
35     Keyword arguments:
36     output_dir         -- The path where gtkdoc output should be placed. Generation
37                           may overwrite file in this directory. Required.
38     module_name        -- The name of the documentation module. For libraries this
39                           is typically the library name. Required if not library path
40                           is given.
41     source_dirs        -- A list of paths to the source code to be scanned. Required.
42     ignored_files      -- A list of filenames to ignore in the source directory. It is
43                           only necessary to provide the basenames of these files.
44                           Typically it is important to provide an updated list of
45                           ignored files to prevent warnings about undocumented symbols.
46     decorator          -- If a decorator is used to unhide certain symbols in header
47                           files this parameter is required for successful scanning.
48                           (default '')
49     deprecation_guard  -- gtkdoc tries to ensure that symbols marked as deprecated
50                           are encased in this C preprocessor define. This is required
51                           to avoid gtkdoc warnings. (default '')
52     cflags             -- This parameter specifies any preprocessor flags necessary for
53                           building the scanner binary during gtkdoc-scanobj. Typically
54                           this includes all absolute include paths necessary to resolve
55                           all header dependencies. (default '')
56     ldflags            -- This parameter specifies any linker flags necessary for
57                           building the scanner binary during gtkdoc-scanobj. Typically
58                           this includes "-lyourlibraryname". (default '')
59     library_path       -- This parameter specifies the path to the directory where you
60                           library resides used for building the scanner binary during
61                           gtkdoc-scanobj. (default '')
62
63     doc_dir            -- The path to other documentation files necessary to build
64                           the documentation. This files in this directory as well as
65                           the files in the 'html' subdirectory will be copied
66                           recursively into the output directory. (default '')
67     main_sgml_file     -- The path or name (if a doc_dir is given) of the SGML file
68                           that is the considered the main page of your documentation.
69                           (default: <module_name>-docs.sgml)
70     version            -- The version number of the module. If this is provided,
71                           a version.xml file containing the version will be created
72                           in the output directory during documentation generation.
73
74     interactive        -- Whether or not errors or warnings should prompt the user
75                           to continue or not. When this value is false, generation
76                           will continue despite warnings. (default False)
77
78     virtual_root       -- A temporary installation directory which is used as the root
79                           where the actual installation prefix lives; this is mostly
80                           useful for packagers, and should be set to what is given to
81                           make install as DESTDIR.
82     """
83
84     def __init__(self, args):
85
86         # Parameters specific to scanning.
87         self.module_name = ''
88         self.source_dirs = []
89         self.ignored_files = []
90         self.decorator = ''
91         self.deprecation_guard = ''
92
93         # Parameters specific to gtkdoc-scanobj.
94         self.cflags = ''
95         self.ldflags = ''
96         self.library_path = ''
97
98         # Parameters specific to generation.
99         self.output_dir = ''
100         self.doc_dir = ''
101         self.main_sgml_file = ''
102
103         # Parameters specific to gtkdoc-fixxref.
104         self.cross_reference_deps = []
105
106         self.interactive = False
107
108         self.logger = logging.getLogger('gtkdoc')
109
110         for key, value in args.iteritems():
111             setattr(self, key, value)
112
113         def raise_error_if_not_specified(key):
114             if not getattr(self, key):
115                 raise Exception('%s not specified.' % key)
116
117         raise_error_if_not_specified('output_dir')
118         raise_error_if_not_specified('source_dirs')
119         raise_error_if_not_specified('module_name')
120
121         # Make all paths absolute in case we were passed relative paths, since
122         # we change the current working directory when executing subcommands.
123         self.output_dir = os.path.abspath(self.output_dir)
124         self.source_dirs = [os.path.abspath(x) for x in self.source_dirs]
125         if self.library_path:
126             self.library_path = os.path.abspath(self.library_path)
127
128         if not self.main_sgml_file:
129             self.main_sgml_file = self.module_name + "-docs.sgml"
130
131     def generate(self, html=True):
132         self.saw_warnings = False
133
134         self._copy_doc_files_to_output_dir(html)
135         self._write_version_xml()
136         self._run_gtkdoc_scan()
137         self._run_gtkdoc_scangobj()
138         self._run_gtkdoc_mktmpl()
139         self._run_gtkdoc_mkdb()
140
141         if not html:
142             return
143
144         self._run_gtkdoc_mkhtml()
145         self._run_gtkdoc_fixxref()
146
147     def _delete_file_if_exists(self, path):
148         if not os.access(path, os.F_OK | os.R_OK):
149             return
150         self.logger.debug('deleting %s', path)
151         os.unlink(path)
152
153     def _create_directory_if_nonexistent(self, path):
154         try:
155             os.makedirs(path)
156         except OSError as error:
157             if error.errno != errno.EEXIST:
158                 raise
159
160     def _raise_exception_if_file_inaccessible(self, path):
161         if not os.path.exists(path) or not os.access(path, os.R_OK):
162             raise Exception("Could not access file at: %s" % path)
163
164     def _output_has_warnings(self, outputs):
165         for output in outputs:
166             if output and output.find('warning'):
167                 return True
168         return False
169
170     def _ask_yes_or_no_question(self, question):
171         if not self.interactive:
172             return True
173
174         question += ' [y/N] '
175         answer = None
176         while answer != 'y' and answer != 'n' and answer != '':
177             answer = raw_input(question).lower()
178         return answer == 'y'
179
180     def _run_command(self, args, env=None, cwd=None, print_output=True, ignore_warnings=False):
181         if print_output:
182             self.logger.info("Running %s", args[0])
183         self.logger.debug("Full command args: %s", str(args))
184
185         process = subprocess.Popen(args, env=env, cwd=cwd,
186                                    stdout=subprocess.PIPE,
187                                    stderr=subprocess.PIPE)
188         stdout, stderr = process.communicate()
189
190         if print_output:
191             if stdout:
192                 sys.stdout.write(stdout)
193             if stderr:
194                 sys.stderr.write(stderr)
195
196         if process.returncode != 0:
197             raise Exception('%s produced a non-zero return code %i'
198                              % (args[0], process.returncode))
199
200         if not ignore_warnings and ('warning' in stderr or 'warning' in stdout):
201             self.saw_warnings = True
202             if not self._ask_yes_or_no_question('%s produced warnings, '
203                                                 'try to continue?' % args[0]):
204                 raise Exception('%s step failed' % args[0])
205
206         return stdout.strip()
207
208     def _copy_doc_files_to_output_dir(self, html=True):
209         if not self.doc_dir:
210             self.logger.info('Not copying any files from doc directory,'
211                              ' because no doc directory given.')
212             return
213
214         def copy_file_replacing_existing(src, dest):
215             if os.path.isdir(src):
216                 self.logger.debug('skipped directory %s',  src)
217                 return
218             if not os.access(src, os.F_OK | os.R_OK):
219                 self.logger.debug('skipped unreadable %s', src)
220                 return
221
222             self._delete_file_if_exists(dest)
223
224             self.logger.debug('created %s', dest)
225             os.link(src, dest)
226
227         def copy_all_files_in_directory(src, dest):
228             for path in os.listdir(src):
229                 copy_file_replacing_existing(os.path.join(src, path),
230                                              os.path.join(dest, path))
231
232         self.logger.info('Copying template files to output directory...')
233         self._create_directory_if_nonexistent(self.output_dir)
234         copy_all_files_in_directory(self.doc_dir, self.output_dir)
235
236         if not html:
237             return
238
239         self.logger.info('Copying HTML files to output directory...')
240         html_src_dir = os.path.join(self.doc_dir, 'html')
241         html_dest_dir = os.path.join(self.output_dir, 'html')
242         self._create_directory_if_nonexistent(html_dest_dir)
243
244         if os.path.exists(html_src_dir):
245             copy_all_files_in_directory(html_src_dir, html_dest_dir)
246
247     def _write_version_xml(self):
248         if not self.version:
249             self.logger.info('No version specified, so not writing version.xml')
250             return
251
252         version_xml_path = os.path.join(self.output_dir, 'version.xml')
253         src_version_xml_path = os.path.join(self.doc_dir, 'version.xml')
254
255         # Don't overwrite version.xml if it was in the doc directory.
256         if os.path.exists(version_xml_path) and \
257            os.path.exists(src_version_xml_path):
258             return
259
260         output_file = open(version_xml_path, 'w')
261         output_file.write(self.version)
262         output_file.close()
263
264     def _ignored_files_basenames(self):
265         return ' '.join([os.path.basename(x) for x in self.ignored_files])
266
267     def _run_gtkdoc_scan(self):
268         args = ['gtkdoc-scan',
269                 '--module=%s' % self.module_name,
270                 '--rebuild-types']
271
272         # Each source directory should be have its own "--source-dir=" prefix.
273         args.extend(['--source-dir=%s' % path for path in self.source_dirs])
274
275         if self.decorator:
276             args.append('--ignore-decorators=%s' % self.decorator)
277         if self.deprecation_guard:
278             args.append('--deprecated-guards=%s' % self.deprecation_guard)
279         if self.output_dir:
280             args.append('--output-dir=%s' % self.output_dir)
281
282         # gtkdoc-scan wants the basenames of ignored headers, so strip the
283         # dirname. Different from "--source-dir", the headers should be
284         # specified as one long string.
285         ignored_files_basenames = self._ignored_files_basenames()
286         if ignored_files_basenames:
287             args.append('--ignore-headers=%s' % ignored_files_basenames)
288
289         self._run_command(args)
290
291     def _run_gtkdoc_scangobj(self):
292         env = os.environ
293         ldflags = self.ldflags
294         if self.library_path:
295             ldflags = ' "-L%s" ' % self.library_path + ldflags
296             current_ld_library_path = env.get('LD_LIBRARY_PATH')
297             if current_ld_library_path:
298                 env['RUN'] = 'LD_LIBRARY_PATH="%s:%s" ' % (self.library_path, current_ld_library_path)
299             else:
300                 env['RUN'] = 'LD_LIBRARY_PATH="%s" ' % self.library_path
301
302         if ldflags:
303             env['LDFLAGS'] = '%s %s' % (ldflags, env.get('LDFLAGS', ''))
304         if self.cflags:
305             env['CFLAGS'] = '%s %s' % (self.cflags, env.get('CFLAGS', ''))
306
307         if 'CFLAGS' in env:
308             self.logger.debug('CFLAGS=%s', env['CFLAGS'])
309         if 'LDFLAGS' in env:
310             self.logger.debug('LDFLAGS %s', env['LDFLAGS'])
311         if 'RUN' in env:
312             self.logger.debug('RUN=%s', env['RUN'])
313         self._run_command(['gtkdoc-scangobj', '--module=%s' % self.module_name],
314                           env=env, cwd=self.output_dir)
315
316     def _run_gtkdoc_mktmpl(self):
317         args = ['gtkdoc-mktmpl', '--module=%s' % self.module_name]
318         self._run_command(args, cwd=self.output_dir)
319
320     def _run_gtkdoc_mkdb(self):
321         sgml_file = os.path.join(self.output_dir, self.main_sgml_file)
322         self._raise_exception_if_file_inaccessible(sgml_file)
323
324         args = ['gtkdoc-mkdb',
325                 '--module=%s' % self.module_name,
326                 '--main-sgml-file=%s' % sgml_file,
327                 '--source-suffixes=h,c,cpp,cc',
328                 '--output-format=xml',
329                 '--sgml-mode']
330
331         ignored_files_basenames = self._ignored_files_basenames()
332         if ignored_files_basenames:
333             args.append('--ignore-files=%s' % ignored_files_basenames)
334
335         # Each directory should be have its own "--source-dir=" prefix.
336         args.extend(['--source-dir=%s' % path for path in self.source_dirs])
337         self._run_command(args, cwd=self.output_dir)
338
339     def _run_gtkdoc_mkhtml(self):
340         html_dest_dir = os.path.join(self.output_dir, 'html')
341         if not os.path.isdir(html_dest_dir):
342             raise Exception("%s is not a directory, could not generate HTML"
343                             % html_dest_dir)
344         elif not os.access(html_dest_dir, os.X_OK | os.R_OK | os.W_OK):
345             raise Exception("Could not access %s to generate HTML"
346                             % html_dest_dir)
347
348         # gtkdoc-mkhtml expects the SGML path to be absolute.
349         sgml_file = os.path.join(os.path.abspath(self.output_dir),
350                                  self.main_sgml_file)
351         self._raise_exception_if_file_inaccessible(sgml_file)
352
353         self._run_command(['gtkdoc-mkhtml', self.module_name, sgml_file],
354                           cwd=html_dest_dir)
355
356     def _run_gtkdoc_fixxref(self):
357         args = ['gtkdoc-fixxref',
358                 '--module-dir=html',
359                 '--html-dir=html']
360         args.extend(['--extra-dir=%s' % extra_dir for extra_dir in self.cross_reference_deps])
361         self._run_command(args, cwd=self.output_dir, ignore_warnings=True)
362
363     def rebase_installed_docs(self):
364         html_dir = os.path.join(self.virtual_root + self.prefix, 'share', 'gtk-doc', 'html', self.module_name)
365         if not os.path.isdir(html_dir):
366             return
367         args = ['gtkdoc-rebase',
368                 '--relative',
369                 '--html-dir=%s' % html_dir]
370         args.extend(['--other-dir=%s' % extra_dir for extra_dir in self.cross_reference_deps])
371         if self.virtual_root:
372             args.extend(['--dest-dir=%s' % self.virtual_root])
373         self._run_command(args, cwd=self.output_dir)
374
375
376 class PkgConfigGTKDoc(GTKDoc):
377
378     """Class reads a library's pkgconfig file to guess gtkdoc parameters.
379
380     Some gtkdoc parameters can be guessed by reading a library's pkgconfig
381     file, including the cflags, ldflags and version parameters. If you
382     provide these parameters as well, they will be appended to the ones
383     guessed via the pkgconfig file.
384
385     Keyword arguments:
386       pkg_config_path -- Path to the pkgconfig file for the library. Required.
387     """
388
389     def __init__(self, pkg_config_path, args):
390         super(PkgConfigGTKDoc, self).__init__(args)
391
392         if not os.path.exists(pkg_config_path):
393             raise Exception('Could not find pkg-config file at: %s'
394                             % pkg_config_path)
395
396         self.cflags += " " + self._run_command(['pkg-config',
397                                                 pkg_config_path,
398                                                 '--cflags'], print_output=False)
399         self.ldflags += " " + self._run_command(['pkg-config',
400                                                 pkg_config_path,
401                                                 '--libs'], print_output=False)
402         self.version = self._run_command(['pkg-config',
403                                           pkg_config_path,
404                                           '--modversion'], print_output=False)
405         self.prefix = self._run_command(['pkg-config',
406                                          pkg_config_path,
407                                          '--variable=prefix'], print_output=False)