[GLIB] User data not correctly passed to callback of functions and constructors with...
[WebKit-https.git] / Tools / gtkdoc / 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
20 import argparse
21 import codecs
22 import glob
23 import gtkdoc
24 import logging
25 import os.path
26 import sys
27
28 if sys.version_info < (3, 2):
29     from ConfigParser import SafeConfigParser as ConfigParser
30 else:
31     from configparser import ConfigParser
32
33 top_level_directory = os.path.normpath(os.path.join(os.path.dirname(__file__), '..', '..'))
34 sys.path.insert(0, os.path.join(top_level_directory, 'Tools', 'glib'))
35 import common
36
37 if sys.version_info.major == 2:
38     sys.stdout = codecs.getwriter("utf-8")(sys.stdout)
39     sys.stderr = codecs.getwriter("utf-8")(sys.stderr)
40
41 def configure_logging(verbose):
42     level = logging.DEBUG if verbose else logging.INFO
43     logger = logging.getLogger('gtkdoc')
44     logger.setLevel(level)
45     handler = logging.StreamHandler()
46     handler.setLevel(level)
47     logger.addHandler(handler)
48     if level == logging.DEBUG:
49         handler.setFormatter(logging.Formatter('[%(asctime)s]  %(message)s'))
50     else:
51         handler.setFormatter(logging.Formatter('%(message)s'))
52
53 def get_gtkdoc_module_paths(cross_reference_deps):
54     dependent_packages = {
55         'glib-2.0' : ['glib', 'gobject', 'gio'],
56         'libsoup-2.4' : ['libsoup-2.4']
57     }
58
59     if arguments.gtk:
60         dependent_packages['gdk-pixbuf-2.0'] = ['gdk-pixbuf']
61         dependent_packages['gtk+-3.0'] = ['gtk3', 'gdk3']
62
63     paths = []
64     html_dir = os.path.join('share', 'gtk-doc', 'html')
65     for package, modules in dependent_packages.items():
66         prefix = common.prefix_of_pkg_config_file(package)
67         if prefix is None:
68             continue
69         for module in modules:
70             paths.append(os.path.join(prefix, html_dir, module))
71
72     for local_dep in cross_reference_deps:
73         paths.append(common.build_path('Documentation', local_dep, 'html'))
74     return paths
75
76 def print_missing_api(generator):
77     missing_api = generator.api_missing_documentation()
78     if not missing_api:
79         return
80     print("\nThe following API are missing documentation:")
81     for api in missing_api:
82         print("\t{0}".format(api))
83
84 def files_to_ignore(source_dirs, headers_with_gtkdoc):
85     """
86     Find files to ignore during documentation generation. We assume that if an
87     implementation file exists for a header with gtkdoc (say webkitfoo.cpp for
88     webkitfoo.h) we shouldn't ignore that file. Currently this holds true for all
89     of the WebKit project.
90     """
91     implementation_files = list(headers_with_gtkdoc)
92     for header in headers_with_gtkdoc:
93         def add_file_if_exists(filename):
94             for dir in source_dirs:
95                 file = os.path.join(dir, filename)
96                 if os.path.isfile(file):
97                     implementation_files.append(os.path.abspath(file))
98         header_basename_without_extension = os.path.splitext(os.path.basename(header))[0]
99         add_file_if_exists(header_basename_without_extension + ".cpp")
100         add_file_if_exists(header_basename_without_extension + "Gtk.cpp")
101         add_file_if_exists(header_basename_without_extension + ".c")
102
103     def file_should_be_ignored(file):
104         if os.path.splitext(file)[1] not in ['.h', '.c', '.cpp', '.cc']:
105             return False # These files are ignored anyway.
106         if not os.path.isfile(file):
107             return True
108         return os.path.abspath(file) not in implementation_files
109
110     all_files = sum([[os.path.join(dir, file) for file in os.listdir(dir)] for dir in source_dirs], [])
111     return filter(file_should_be_ignored, all_files)
112
113 def get_generator_for_config(config_file, virtual_root, cross_reference_deps = []):
114     if not os.path.isfile(config_file):
115         return None
116
117     config = ConfigParser()
118     config.read(config_file)
119     module_name = config.sections()[0]
120     pkgconfig_file = config.get(module_name, 'pkgconfig_file')
121
122     if not os.path.isfile(pkgconfig_file):
123         return None
124
125     source_dirs = config.get(module_name, 'source_dirs').replace(';', ' ').split()
126     headers = [os.path.abspath(f) for f in config.get(module_name, 'headers').replace(';', ' ').split()]
127     return gtkdoc.PkgConfigGTKDoc(pkgconfig_file, {
128         'decorator': config.get(module_name, 'decorator'),
129         'deprecation_guard': config.get(module_name, 'deprecation_guard'),
130         'library_path': common.library_build_path(),
131         'virtual_root': virtual_root,
132         'module_name': module_name,
133         'namespace': config.get(module_name, 'namespace'),
134         'doc_dir': config.get(module_name, 'doc_dir'),
135         'output_dir': common.build_path('Documentation', module_name),
136         'main_sgml_file': config.get(module_name, 'main_sgml_file'),
137         'source_dirs': source_dirs,
138         'headers': headers,
139         'cflags': " ".join(config.get(module_name, 'cflags').split()),
140         'cross_reference_deps': get_gtkdoc_module_paths(cross_reference_deps),
141         'ignored_files': files_to_ignore(source_dirs, headers),
142     })
143
144 def generate_doc(generator, skip_html):
145     generator.generate(not skip_html)
146     if generator.saw_warnings:
147         print_missing_api(generator)
148     return generator.saw_warnings
149
150 def rebase_doc(generator):
151     try:
152         generator.rebase_installed_docs()
153     except Exception:
154         print("Rebase did not happen, likely no documentation is present.")
155
156 def generate_documentation(generator):
157     if not arguments.rebase:
158         return generate_doc(generator, arguments.skip_html)
159
160     rebase_doc(generator)
161     return False
162
163 def prepare_environment_for_gtkdoc_generation():
164     # We need to add the JavaScriptCore build directory to the PKG_CONFIG_PATH
165     # so that pkgconfig can properly resolve the libjavascriptcore dependency.
166     pkg_config_path = os.environ.get("PKG_CONFIG_PATH")
167     os.environ['PKG_CONFIG_PATH'] = common.build_path('Source', 'JavaScriptCore')
168     if pkg_config_path:
169         os.environ['PKG_CONFIG_PATH'] += ':' + pkg_config_path
170
171     # Newer versions of glib have deprecated g_type_init, so we need to disable
172     # that warning when running gtkdoc-scanobj by overriding the CFLAGS we use
173     # to compile it.
174     cflags = os.environ.get('CFLAGS', '')
175     cflags += ' -DGLIB_VERSION_MIN_REQUIRED=GLIB_VERSION_2_32'
176
177     # In non-x86 architectures, when a pointer is cast to (void*) and
178     # back, the compiler thinks that the alignment is random. Since
179     # gtkdoc build is broken at any warning message, it is better to
180     # silence these false positives.
181     cflags += ' -Wno-cast-align'
182     os.environ['CFLAGS'] = cflags
183
184     # Paths from the GNUmakefile generated configuration files are relative to the build directory.
185     os.chdir(common.build_path())
186
187 def build_gtkdoc_for_wpe(arguments):
188     webextensions_generator = get_generator_for_config(common.build_path('gtkdoc-webextensions.cfg'), arguments.virtual_root)
189     if not webextensions_generator:
190         print("gtkdoc-webextensions.cfg does not exist! Skipping that documentation")
191         sys.exit(1)
192     saw_warnings = generate_documentation(webextensions_generator)
193     if saw_warnings:
194         sys.exit(saw_warnings)
195
196     wpe_generator = get_generator_for_config(common.build_path('gtkdoc-wpe.cfg'), arguments.virtual_root)
197     if not wpe_generator:
198         print("gtkdoc-wpe.cfg does not exist! Skipping that documentation")
199         sys.exit(1)
200     saw_warnings = generate_documentation(wpe_generator)
201     sys.exit(saw_warnings)
202
203 def build_gtkdoc_for_wkgtk(arguments):
204     jsc_generator = get_generator_for_config(common.build_path('gtkdoc-jsc-glib.cfg'), arguments.virtual_root)
205     if not jsc_generator:
206         print("gtkdoc-jsc-glib.cfg does not exist! Skipping that documentation")
207         sys.exit(1)
208     saw_warnings = generate_documentation(jsc_generator)
209     if saw_warnings:
210         sys.exit(saw_warnings)
211
212     webkitdom_generator = get_generator_for_config(common.build_path('gtkdoc-webkitdom.cfg'), arguments.virtual_root, [jsc_generator.module_name])
213     if not webkitdom_generator:
214         print("gtkdoc-webkitdom.cfg does not exist! Skipping that documentation")
215         sys.exit(1)
216     saw_warnings = generate_documentation(webkitdom_generator)
217     if saw_warnings:
218         sys.exit(saw_warnings)
219
220     webkit2_generator = get_generator_for_config(common.build_path('gtkdoc-webkit2gtk.cfg'), arguments.virtual_root, [webkitdom_generator.module_name, jsc_generator.module_name])
221     if not webkit2_generator:
222         print("gtkdoc-webkit2gtk.cfg does not exist! Skipping that documentation")
223         sys.exit(1)
224     saw_warnings = generate_documentation(webkit2_generator)
225     sys.exit(saw_warnings)
226
227 if __name__ == "__main__":
228     parser = argparse.ArgumentParser(description='Generate gtkdoc for WebKit.')
229     parser.add_argument('-v', '--verbose', action='store_true',
230                         help='Whether or not to run in verbose mode.')
231     parser.add_argument('--rebase', action='store_true',
232                         help='When specified, run the tool in rebase mode.')
233     parser.add_argument('--skip-html', action='store_true',
234                         help='Whether or not to skip HTML generation, which can be slow.')
235     parser.add_argument('--virtual-root', type=str, default='',
236                         help='A temporary installation directory which is used as the root ' + \
237                              'where the actual installation prefix lives; this is mostly ' + \
238                              'useful for packagers, and should be set to what is given to ' + \
239                              'make install as DESTDIR.')
240
241     parser.add_argument('--gtk', action='store_true',
242                         help='Build documentation for WebKitGTK+')
243     parser.add_argument('--wpe', action='store_true',
244                         help='Build documentation for WPE')
245
246     arguments = parser.parse_args()
247     configure_logging(arguments.verbose)
248
249     prepare_environment_for_gtkdoc_generation()
250
251     if arguments.wpe:
252         build_gtkdoc_for_wpe(arguments)
253     elif arguments.gtk:
254         build_gtkdoc_for_wkgtk(arguments)
255     else:
256         print('Please choose a WebKit port with --gtk or --wpe')
257         sys.exit(1)