1 # Copyright (C) 2011 Igalia S.L.
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.
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.
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
27 """Class that controls a gtkdoc run.
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.
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
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.
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 '')
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.
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)
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.
84 def __init__(self, args):
86 # Parameters specific to scanning.
89 self.ignored_files = []
91 self.deprecation_guard = ''
93 # Parameters specific to gtkdoc-scanobj.
96 self.library_path = ''
98 # Parameters specific to generation.
101 self.main_sgml_file = ''
103 # Parameters specific to gtkdoc-fixxref.
104 self.cross_reference_deps = []
106 self.interactive = False
108 self.logger = logging.getLogger('gtkdoc')
110 for key, value in args.iteritems():
111 setattr(self, key, value)
113 def raise_error_if_not_specified(key):
114 if not getattr(self, key):
115 raise Exception('%s not specified.' % key)
117 raise_error_if_not_specified('output_dir')
118 raise_error_if_not_specified('source_dirs')
119 raise_error_if_not_specified('module_name')
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)
128 if not self.main_sgml_file:
129 self.main_sgml_file = self.module_name + "-docs.sgml"
131 def generate(self, html=True):
132 self.saw_warnings = False
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()
144 self._run_gtkdoc_mkhtml()
145 self._run_gtkdoc_fixxref()
147 def _delete_file_if_exists(self, path):
148 if not os.access(path, os.F_OK | os.R_OK):
150 self.logger.debug('deleting %s', path)
153 def _create_directory_if_nonexistent(self, path):
156 except OSError as error:
157 if error.errno != errno.EEXIST:
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)
164 def _output_has_warnings(self, outputs):
165 for output in outputs:
166 if output and output.find('warning'):
170 def _ask_yes_or_no_question(self, question):
171 if not self.interactive:
174 question += ' [y/N] '
176 while answer != 'y' and answer != 'n' and answer != '':
177 answer = raw_input(question).lower()
180 def _run_command(self, args, env=None, cwd=None, print_output=True, ignore_warnings=False):
182 self.logger.info("Running %s", args[0])
183 self.logger.debug("Full command args: %s", str(args))
185 process = subprocess.Popen(args, env=env, cwd=cwd,
186 stdout=subprocess.PIPE,
187 stderr=subprocess.PIPE)
188 stdout, stderr = process.communicate()
192 sys.stdout.write(stdout)
194 sys.stderr.write(stderr)
196 if process.returncode != 0:
197 raise Exception('%s produced a non-zero return code %i'
198 % (args[0], process.returncode))
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])
206 return stdout.strip()
208 def _copy_doc_files_to_output_dir(self, html=True):
210 self.logger.info('Not copying any files from doc directory,'
211 ' because no doc directory given.')
214 def copy_file_replacing_existing(src, dest):
215 if os.path.isdir(src):
216 self.logger.debug('skipped directory %s', src)
218 if not os.access(src, os.F_OK | os.R_OK):
219 self.logger.debug('skipped unreadable %s', src)
222 self._delete_file_if_exists(dest)
224 self.logger.debug('created %s', dest)
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))
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)
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)
244 if os.path.exists(html_src_dir):
245 copy_all_files_in_directory(html_src_dir, html_dest_dir)
247 def _write_version_xml(self):
249 self.logger.info('No version specified, so not writing version.xml')
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')
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):
260 output_file = open(version_xml_path, 'w')
261 output_file.write(self.version)
264 def _ignored_files_basenames(self):
265 return ' '.join([os.path.basename(x) for x in self.ignored_files])
267 def _run_gtkdoc_scan(self):
268 args = ['gtkdoc-scan',
269 '--module=%s' % self.module_name,
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])
276 args.append('--ignore-decorators=%s' % self.decorator)
277 if self.deprecation_guard:
278 args.append('--deprecated-guards=%s' % self.deprecation_guard)
280 args.append('--output-dir=%s' % self.output_dir)
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)
289 self._run_command(args)
291 def _run_gtkdoc_scangobj(self):
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)
300 env['RUN'] = 'LD_LIBRARY_PATH="%s" ' % self.library_path
303 env['LDFLAGS'] = '%s %s' % (ldflags, env.get('LDFLAGS', ''))
305 env['CFLAGS'] = '%s %s' % (self.cflags, env.get('CFLAGS', ''))
308 self.logger.debug('CFLAGS=%s', env['CFLAGS'])
310 self.logger.debug('LDFLAGS %s', env['LDFLAGS'])
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)
316 def _run_gtkdoc_mktmpl(self):
317 args = ['gtkdoc-mktmpl', '--module=%s' % self.module_name]
318 self._run_command(args, cwd=self.output_dir)
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)
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',
331 ignored_files_basenames = self._ignored_files_basenames()
332 if ignored_files_basenames:
333 args.append('--ignore-files=%s' % ignored_files_basenames)
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)
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"
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"
348 # gtkdoc-mkhtml expects the SGML path to be absolute.
349 sgml_file = os.path.join(os.path.abspath(self.output_dir),
351 self._raise_exception_if_file_inaccessible(sgml_file)
353 self._run_command(['gtkdoc-mkhtml', self.module_name, sgml_file],
356 def _run_gtkdoc_fixxref(self):
357 args = ['gtkdoc-fixxref',
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)
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):
367 args = ['gtkdoc-rebase',
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)
376 class PkgConfigGTKDoc(GTKDoc):
378 """Class reads a library's pkgconfig file to guess gtkdoc parameters.
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.
386 pkg_config_path -- Path to the pkgconfig file for the library. Required.
389 def __init__(self, pkg_config_path, args):
390 super(PkgConfigGTKDoc, self).__init__(args)
392 if not os.path.exists(pkg_config_path):
393 raise Exception('Could not find pkg-config file at: %s'
396 self.cflags += " " + self._run_command(['pkg-config',
398 '--cflags'], print_output=False)
399 self.ldflags += " " + self._run_command(['pkg-config',
401 '--libs'], print_output=False)
402 self.version = self._run_command(['pkg-config',
404 '--modversion'], print_output=False)
405 self.prefix = self._run_command(['pkg-config',
407 '--variable=prefix'], print_output=False)