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