33b2824f8060b7b97b71334854dff943b26dce36
[WebKit-https.git] / Tools / gtk / generate-gtkdoc
1 #!/usr/bin/env python
2 # Copyright (C) 2011 Igalia S.L.
3 #
4 # This library is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU Lesser General Public
6 # License as published by the Free Software Foundation; either
7 # version 2 of the License, or (at your option) any later version.
8 #
9 # This library is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12 # Lesser General Public License for more details.
13 #
14 # You should have received a copy of the GNU Lesser General Public
15 # License along with this library; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
17
18 from __future__ import print_function
19 from ConfigParser import SafeConfigParser
20
21 import argparse
22 import codecs
23 import common
24 import glob
25 import gtkdoc
26 import logging
27 import os.path
28 import sys
29
30 sys.stdout = codecs.getwriter("utf-8")(sys.stdout)
31 sys.stderr = codecs.getwriter("utf-8")(sys.stderr)
32
33 def configure_logging(verbose):
34     level = logging.DEBUG if verbose else logging.INFO
35     logger = logging.getLogger('gtkdoc')
36     logger.setLevel(level)
37     handler = logging.StreamHandler()
38     handler.setLevel(level)
39     logger.addHandler(handler)
40     if level == logging.DEBUG:
41         handler.setFormatter(logging.Formatter('[%(asctime)s]  %(message)s'))
42     else:
43         handler.setFormatter(logging.Formatter('%(message)s'))
44
45 def get_gtkdoc_module_paths(cross_reference_deps):
46     dependent_packages = {
47         'glib-2.0' : ['glib', 'gobject', 'gio'],
48         'libsoup-2.4' : ['libsoup-2.4'],
49         'gdk-pixbuf-2.0': ['gdk-pixbuf'],
50         'gtk+-3.0' : ['gtk3', 'gdk3']
51     }
52
53     paths = []
54     html_dir = os.path.join('share', 'gtk-doc', 'html')
55     for package, modules in dependent_packages.iteritems():
56         prefix = common.prefix_of_pkg_config_file(package)
57         if prefix is None:
58             continue
59         for module in modules:
60             paths.append(os.path.join(prefix, html_dir, module))
61
62     for local_dep in cross_reference_deps:
63         paths.append(common.build_path('Documentation', local_dep, 'html'))
64     return paths
65
66 def print_missing_api(generator):
67     missing_api = generator.api_missing_documentation()
68     if not missing_api:
69         return
70     print("\nThe following API are missing documentation:")
71     for api in missing_api:
72         print("\t{0}".format(api))
73
74 def files_to_ignore(source_dirs, headers_with_gtkdoc):
75     """
76     Find files to ignore during documentation generation. We assume that if an
77     implementation file exists for a header with gtkdoc (say webkitfoo.cpp for
78     webkitfoo.h) we shouldn't ignore that file. Currently this holds true for all
79     of the WebKit project.
80     """
81     implementation_files = list(headers_with_gtkdoc)
82     for header in headers_with_gtkdoc:
83         def add_file_if_exists(file):
84             if os.path.isfile(file):
85                 implementation_files.append(os.path.abspath(file))
86         header_name_without_extension = os.path.splitext(header)[0]
87         add_file_if_exists(header_name_without_extension + ".cpp")
88         add_file_if_exists(header_name_without_extension + ".c")
89
90     def file_should_be_ignored(file):
91         if os.path.splitext(file)[1] not in ['.h', '.c', '.cpp', '.cc']:
92             return False # These files are ignored anyway.
93         if not os.path.isfile(file):
94             return True
95         return os.path.abspath(file) not in implementation_files
96
97     all_files = sum([[os.path.join(dir, file) for file in os.listdir(dir)] for dir in source_dirs], [])
98     return filter(file_should_be_ignored, all_files)
99
100 def get_generator_for_config(config_file, virtual_root, cross_reference_deps = []):
101     if not os.path.isfile(config_file):
102         return None
103
104     config = SafeConfigParser()
105     config.read(config_file)
106     module_name = config.sections()[0]
107     pkgconfig_file = config.get(module_name, 'pkgconfig_file')
108
109     if not os.path.isfile(pkgconfig_file):
110         return None
111
112     source_dirs = config.get(module_name, 'source_dirs').replace(';', ' ').split()
113     headers = [os.path.abspath(f) for f in config.get(module_name, 'headers').replace(';', ' ').split()]
114     return gtkdoc.PkgConfigGTKDoc(pkgconfig_file, {
115         'decorator': 'WEBKIT_API|WEBKIT_DEPRECATED|WEBKIT_DEPRECATED_FOR\(.+\)',
116         'deprecation_guard': 'WEBKIT_DISABLE_DEPRECATED',
117         'library_path': common.library_build_path(),
118         'virtual_root': virtual_root,
119         'module_name': module_name,
120         'namespace': config.get(module_name, 'namespace'),
121         'doc_dir': config.get(module_name, 'doc_dir'),
122         'output_dir': common.build_path('Documentation', module_name),
123         'main_sgml_file': config.get(module_name, 'main_sgml_file'),
124         'source_dirs': source_dirs,
125         'headers': headers,
126         'cflags': " ".join(config.get(module_name, 'cflags').split()),
127         'cross_reference_deps': get_gtkdoc_module_paths(cross_reference_deps),
128         'ignored_files': files_to_ignore(source_dirs, headers),
129     })
130
131 def generate_doc(generator, skip_html):
132     generator.generate(not skip_html)
133     if generator.saw_warnings:
134         print_missing_api(generator)
135     return generator.saw_warnings
136
137 def rebase_doc(generator):
138     try:
139         generator.rebase_installed_docs()
140     except Exception:
141         print("Rebase did not happen, likely no documentation is present.")
142
143 def generate_documentation(generator):
144     if not arguments.rebase:
145         return generate_doc(generator, arguments.skip_html)
146
147     rebase_doc(generator)
148     return False
149
150 def prepare_environment_for_gtkdoc_generation():
151     # We need to add the JavaScriptCore build directory to the PKG_CONFIG_PATH
152     # so that pkgconfig can properly resolve the libjavascriptcore dependency.
153     pkg_config_path = os.environ.get("PKG_CONFIG_PATH")
154     os.environ['PKG_CONFIG_PATH'] = common.build_path('Source', 'JavaScriptCore')
155     if pkg_config_path:
156         os.environ['PKG_CONFIG_PATH'] += ':' + pkg_config_path
157
158     # Newer versions of glib have deprecated g_type_init, so we need to disable
159     # that warning when running gtkdoc-scanobj by overriding the CFLAGS we use
160     # to compile it.
161     cflags = os.environ.get('CFLAGS', '')
162     cflags += ' -DGLIB_VERSION_MIN_REQUIRED=GLIB_VERSION_2_32'
163
164     # In non-x86 architectures, when a pointer is cast to (void*) and
165     # back, the compiler thinks that the alignment is random. Since
166     # gtkdoc build is broken at any warning message, it is better to
167     # silence these false positives.
168     cflags += ' -Wno-cast-align'
169     os.environ['CFLAGS'] = cflags
170
171     # Paths from the GNUmakefile generated configuration files are relative to the build directory.
172     os.chdir(common.build_path())
173
174 if __name__ == "__main__":
175     parser = argparse.ArgumentParser(description='Generate gtkdoc for WebKit.')
176     parser.add_argument('-v', '--verbose', action='store_true',
177                         help='Whether or not to run in verbose mode.')
178     parser.add_argument('--rebase', action='store_true',
179                         help='When specified, run the tool in rebase mode.')
180     parser.add_argument('--skip-html', action='store_true',
181                         help='Whether or not to skip HTML generation, which can be slow.')
182     parser.add_argument('--virtual-root', type=str, default='',
183                         help='A temporary installation directory which is used as the root ' + \
184                              'where the actual installation prefix lives; this is mostly ' + \
185                              'useful for packagers, and should be set to what is given to ' + \
186                              'make install as DESTDIR.')
187
188     arguments = parser.parse_args()
189     configure_logging(arguments.verbose)
190
191     prepare_environment_for_gtkdoc_generation()
192
193     webkitdom_generator = get_generator_for_config(common.build_path('gtkdoc-webkitdom.cfg'), arguments.virtual_root)
194     if not webkitdom_generator:
195         print("gtkdoc-webkitdom.cfg does not exist! Skipping that documentation")
196         sys.exit(1)
197     saw_warnings = generate_documentation(webkitdom_generator)
198     if saw_warnings:
199         sys.exit(saw_warnings)
200
201     webkit2_generator = get_generator_for_config(common.build_path('gtkdoc-webkit2gtk.cfg'), arguments.virtual_root, [webkitdom_generator.module_name])
202     if not webkit2_generator:
203         print("gtkdoc-webkit2gtk.cfg does not exist! Skipping that documentation")
204         sys.exit(1)
205     saw_warnings = generate_documentation(webkit2_generator)
206
207     sys.exit(saw_warnings)