2abd95b12499a9554215e36b0592000991b5200d
[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     print("\nGenerating {0} documentation...".format(generator.module_name))
133     generator.generate(not skip_html)
134     if generator.saw_warnings:
135         print_missing_api(generator)
136     return generator.saw_warnings
137
138 def rebase_doc(generator):
139     print("\nRebasing {0} documentation...".format(generator.module_name))
140     try:
141         generator.rebase_installed_docs()
142     except Exception:
143         print("Rebase did not happen, likely no documentation is present.")
144
145 def generate_documentation(generator):
146     if not arguments.rebase:
147         return generate_doc(generator, arguments.skip_html)
148
149     rebase_doc(generator)
150     return False
151
152 def prepare_environment_for_gtkdoc_generation():
153     # We need to add the JavaScriptCore build directory to the PKG_CONFIG_PATH
154     # so that pkgconfig can properly resolve the libjavascriptcore dependency.
155     pkg_config_path = os.environ.get("PKG_CONFIG_PATH")
156     os.environ['PKG_CONFIG_PATH'] = common.build_path('Source', 'JavaScriptCore')
157     if pkg_config_path:
158         os.environ['PKG_CONFIG_PATH'] += ':' + pkg_config_path
159
160     # Newer versions of glib have deprecated g_type_init, so we need to disable
161     # that warning when running gtkdoc-scanobj by overriding the CFLAGS we use
162     # to compile it.
163     cflags = os.environ.get('CFLAGS', '')
164     cflags += ' -DGLIB_VERSION_MIN_REQUIRED=GLIB_VERSION_2_32'
165
166     # In non-x86 architectures, when a pointer is cast to (void*) and
167     # back, the compiler thinks that the alignment is random. Since
168     # gtkdoc build is broken at any warning message, it is better to
169     # silence these false positives.
170     cflags += ' -Wno-cast-align'
171     os.environ['CFLAGS'] = cflags
172
173     # Paths from the GNUmakefile generated configuration files are relative to the build directory.
174     os.chdir(common.build_path())
175
176 if __name__ == "__main__":
177     parser = argparse.ArgumentParser(description='Generate gtkdoc for WebKit.')
178     parser.add_argument('-v', '--verbose', action='store_true',
179                         help='Whether or not to run in verbose mode.')
180     parser.add_argument('--rebase', action='store_true',
181                         help='When specified, run the tool in rebase mode.')
182     parser.add_argument('--skip-html', action='store_true',
183                         help='Whether or not to skip HTML generation, which can be slow.')
184     parser.add_argument('--virtual-root', type=str, default='',
185                         help='A temporary installation directory which is used as the root ' + \
186                              'where the actual installation prefix lives; this is mostly ' + \
187                              'useful for packagers, and should be set to what is given to ' + \
188                              'make install as DESTDIR.')
189
190     arguments = parser.parse_args()
191     configure_logging(arguments.verbose)
192
193     prepare_environment_for_gtkdoc_generation()
194
195     webkitdom_generator = get_generator_for_config(common.build_path('gtkdoc-webkitdom.cfg'), arguments.virtual_root)
196     if not webkitdom_generator:
197         print("gtkdoc-webkitdom.cfg does not exist! Skipping that documentation")
198         sys.exit(1)
199     saw_warnings = generate_documentation(webkitdom_generator)
200     if saw_warnings:
201         sys.exit(saw_warnings)
202
203     webkit2_generator = get_generator_for_config(common.build_path('gtkdoc-webkit2gtk.cfg'), arguments.virtual_root, [webkitdom_generator.module_name])
204     if not webkit2_generator:
205         print("gtkdoc-webkit2gtk.cfg does not exist! Skipping that documentation")
206         sys.exit(1)
207     saw_warnings = generate_documentation(webkit2_generator)
208
209     sys.exit(saw_warnings)