1#!/usr/bin/env python3
2#
3# Copyright 2017, Sam Thursfield <sam@afuera.me.uk>
4#
5# This library is free software; you can redistribute it and/or
6# modify it under the terms of the GNU General Public
7# License as published by the Free Software Foundation; either
8# version 2 of the License, or (at your option) any later version.
9#
10# This library is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13# General Public License for more details.
14#
15# You should have received a copy of the GNU General Public
16# License along with this library; if not, write to the
17# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18# Boston, MA  02110-1301, USA.
19
20
21'''g-ir-merge: combine multiple GObject Introspection namespaces together.
22
23This tool exists to solve a problem in Tracker: we have code for
24libtracker-sparql written in both C and Vala. It's not possible to generate
25a single .gir for all of this code as `g-ir-scanner` can't deal with Vala's
26generated .c code, and `valac` only handles .vala code.
27
28The only workable solution seems to be to generate two different .gir files
29and manually combine them. Thankfully it's not too difficult to do this.
30
31For more discussion, see: https://bugzilla.gnome.org/show_bug.cgi?id=782091
32
33'''
34
35
36import argparse
37import sys
38from xml.etree import ElementTree
39
40CORE_NAMESPACE = 'http://www.gtk.org/introspection/core/1.0'
41C_NAMESPACE = 'http://www.gtk.org/introspection/c/1.0'
42GLIB_NAMESPACE = 'http://www.gtk.org/introspection/glib/1.0'
43
44def argument_parser():
45    parser = argparse.ArgumentParser(
46        description="Merge .gir repositories together.")
47
48    parser.add_argument('--namespace', '-n', metavar='NAMESPACE_NAME', type=str, required=True,
49                        help="name for combined .gir namespace")
50    parser.add_argument('--nsversion', metavar='NAMESPACE_VERSION', type=str, required=True,
51                        help="version for combined .gir namespace")
52
53    parser.add_argument('--c-include', metavar='C_INCLUDES', type=list,
54                        help="override list of C includes")
55
56    parser.add_argument('files', metavar="GIR_FILE", type=str, nargs='+',
57                        help="input .gir file")
58    return parser
59
60
61def parse_inputs(files):
62    '''Read the important contents from one or more .gir files and return all.
63
64    This does no post-processing of the data, it simply returns everything
65    including any duplicate or contradictory info.
66
67    '''
68    ns = {
69        'core': CORE_NAMESPACE,
70        'c': C_NAMESPACE
71    }
72
73    includes = []
74    namespaces = []
75    c_includes = []
76
77    for file in files:
78        gi_repository = ElementTree.parse(file).getroot()
79
80        for gi_include in gi_repository.findall('core:include', ns):
81            includes.append(gi_include)
82
83        for gi_namespace in gi_repository.findall('core:namespace', ns):
84            namespaces.append(gi_namespace)
85
86        for gi_c_include in gi_repository.findall('c:include', ns):
87            c_includes.append(gi_c_include)
88
89    return includes, namespaces, c_includes
90
91
92def merge_includes(all_includes, namespace):
93    merged = {}
94    for element in all_includes:
95        name = element.get('name')
96        version = element.get('version')
97        if name not in merged and name != namespace:
98            merged[name] = element
99    return list(merged.values())
100
101
102def merge_namespaces(all_namespaces):
103    identifier_prefixes = set()
104    symbol_prefixes = set()
105    shared_libraries = set()
106
107    contents = []
108
109    for element in all_namespaces:
110        if element.get('c:identifier_prefixes', None):
111            identifier_prefixes.update(element.get('c:identifier_prefixes').split(','))
112        if element.get('c:symbol_prefixes', None):
113            symbol_prefixes.update(element.get('c:symbol_prefixes').split(','))
114        identifier_prefixes.update(element.get('c:prefix', []))
115        symbol_prefixes.update(element.get('c:prefix', []))
116
117        if element.get('shared-library', None):
118            shared_libraries.update(element.get('shared-library', '').split(','))
119
120        contents.extend(element)
121
122    return (contents,
123            ','.join(sorted(identifier_prefixes)),
124            ','.join(sorted(symbol_prefixes)),
125            ','.join(sorted(shared_libraries)))
126
127
128def create_repository(namespace_name, namespace_version, shared_libraries,
129                      c_identifier_prefixes, c_symbol_prefixes, includes, namespace_contents):
130    '''Create a new GI repository with a single namespace.'''
131    ElementTree.register_namespace('', CORE_NAMESPACE)
132    ElementTree.register_namespace('c', C_NAMESPACE)
133    ElementTree.register_namespace('glib', GLIB_NAMESPACE)
134    gir_version = '1.2'
135
136    repository = ElementTree.Element('repository', attrib={'version': gir_version})
137    repository.extend(includes)
138
139    namespace = ElementTree.SubElement(repository, 'namespace', attrib={
140            'name': namespace_name,
141            'version': namespace_version,
142            'shared-library': shared_libraries,
143            'c:identifier-prefixes': c_identifier_prefixes,
144            'c:symbol-prefixes': c_symbol_prefixes,
145        })
146    namespace.extend(namespace_contents)
147
148    return repository
149
150
151def main():
152    args = argument_parser().parse_args()
153
154    all_includes, all_namespaces, all_c_includes = parse_inputs(args.files)
155
156    includes = merge_includes(all_includes, args.namespace)
157
158    namespace_contents, identifier_prefixes, symbol_prefixes, shared_libraries \
159        = merge_namespaces(all_namespaces)
160
161    repository = create_repository(args.namespace, args.nsversion, shared_libraries,
162                                   identifier_prefixes, symbol_prefixes,
163                                   includes, namespace_contents)
164
165    print(ElementTree.tostring(repository, encoding="unicode"))
166
167
168try:
169    main()
170except RuntimeError as e:
171    sys.stderr.write("{}\n".format(e))
172    sys.exit(1)
173