Revert 32-bit building since it errors on travis-ci
[appimagekit/gsi.git] / LibcWrapGenerator / LibcWrapGenerator.vala
blob10d4a65965a5cfc0cbd8f9e84bf1106e0c2b5dbb
1 /*
2 * Copyright (C) 2011 Jan Niklas Hasse <jhasse@gmail.com>
3 * Copyright (C) 2013 Upstairs Laboratories Inc.
5 * This library is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU Lesser General Public License as
7 * published by the Free Software Foundation; either version 2.1 of
8 * the License, or (at your option) any later version.
10 * This library is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 * Authors:
20 * Jan Niklas Hasse <jhasse@gmail.com>
21 * Tristan Van Berkom <tristan@upstairslabs.com>
23 using GLib;
25 static const string DEFAULT_TARGET = "2.7";
26 static const string DEFAULT_TARGET_HELP = "Target glibc ABI (Default 2.7)";
28 /***************************************************************
29 * Debugging Facilities *
30 ***************************************************************/
31 [Flags]
32 public enum DF {
33 COLLECT,
34 FILTER,
35 VERSION_PARSE,
36 VERSION_COMPARE,
37 DUMP_FILES
40 static const GLib.DebugKey[] libcwrap_debug_keys = {
41 { "collect", DF.COLLECT },
42 { "filter", DF.FILTER },
43 { "version-parse", DF.VERSION_PARSE },
44 { "version-compare", DF.VERSION_COMPARE },
45 { "dump-files", DF.DUMP_FILES }
48 private uint libcwrap_debug_mask = 0;
49 public delegate void DebugFunc ();
51 public void libcwrap_note (int domain, DebugFunc debug_func)
53 if ((libcwrap_debug_mask & domain) != 0)
54 debug_func ();
57 /***************************************************************
58 * VersionNumber class *
59 ***************************************************************/
60 class VersionNumber : Object
62 private int major { get; set; } // x.0.0
63 private int minor { get; set; } // 0.x.0
64 private int revision { get; set; } // 0.0.x
65 private string originalString;
67 public VersionNumber (string version) {
69 originalString = version;
71 try {
72 var regex = new Regex("([[:digit:]]*)\\.([[:digit:]]*)\\.*([[:digit:]]*)");
73 var split = regex.split(version);
75 assert (split.length > 1); // TODO: Don't use assert, print a nice error message instead
76 major = int.parse (split[1]);
78 if (split.length > 2)
79 minor = int.parse (split[2]);
80 else
81 minor = 0;
83 if (split.length > 3)
84 revision = int.parse (split[3]);
85 else
86 revision = 0;
87 } catch (GLib.RegexError e) {
88 stdout.printf ("Error compiling regular expression: %s", e.message);
89 Posix.exit (-1);
92 libcwrap_note (DF.VERSION_PARSE, () =>
93 stdout.printf ("Version string '%s' parsed as major: %d minor: %d rev: %d\n",
94 originalString, major, minor, revision));
97 public bool newerThan(VersionNumber other) {
98 bool newerThanOther = false;
100 if (major > other.major) {
101 newerThanOther = true;
102 } else if (major == other.major) {
104 if (minor > other.minor) {
105 newerThanOther = true;
106 } else if (minor == other.minor) {
108 if(revision > other.revision) {
109 newerThanOther = true;
114 libcwrap_note (DF.VERSION_COMPARE, () =>
115 stdout.printf ("Version '%s' is %s than version '%s'\n",
116 originalString,
117 newerThanOther ? "newer" : "older",
118 other.getString()));
120 return newerThanOther;
123 public string getString() {
124 return originalString;
128 /***************************************************************
129 * Main *
130 ***************************************************************/
131 public class Main : Object {
133 /* Command line options */
134 private static string? libdir = null;
135 private static string? output = null;
136 private static string? target = null;
138 private const GLib.OptionEntry[] options = {
139 { "libdir", 'l', 0, OptionArg.FILENAME, ref libdir, "Library directory", "<DIRECTORY>" },
140 { "output", 'o', 0, OptionArg.STRING, ref output, "Header to create", "<FILENAME>" },
141 { "target", 't', 0, OptionArg.STRING, ref target, DEFAULT_TARGET_HELP, "<MAJOR.MINOR[.MICRO]>" },
142 { null }
145 /* Local variables */
146 private static VersionNumber minimumVersion;
147 private static Gee.HashMap<string, VersionNumber>symbolMap;
148 private static Gee.HashSet<string>filterMap;
150 public static int main (string[] args) {
152 /* Initialize debugging */
153 string libcwrap_debug_env = GLib.Environment.get_variable ("LIBCWRAP_DEBUG");
154 libcwrap_debug_mask = GLib.parse_debug_string (libcwrap_debug_env, libcwrap_debug_keys);
156 /* Initialize the default here */
157 target = DEFAULT_TARGET;
159 try {
160 var opt_context = new OptionContext ("- Libc compatibility header generator");
161 opt_context.set_help_enabled (true);
162 opt_context.add_main_entries (options, null);
163 opt_context.parse (ref args);
164 } catch (OptionError e) {
165 stdout.printf ("error: %s\n", e.message);
166 stdout.printf ("Run '%s --help' for a list of available command line options.\n", args[0]);
167 return 0;
170 if (libdir == null) {
171 stdout.printf ("Must specify --libdir\n");
172 stdout.printf ("Run '%s --help' for a list of available command line options.\n", args[0]);
173 return 0;
176 if (output == null) {
177 stdout.printf ("Must specify --output\n");
178 stdout.printf ("Run '%s --help' for a list of available command line options.\n", args[0]);
179 return 0;
182 /* Initialize local resources */
183 minimumVersion = new VersionNumber (target);
185 /* All symbols, containing the newest possible version before 'minimumVersion' if possible */
186 symbolMap = new Gee.HashMap<string, VersionNumber>((Gee.HashDataFunc<string>)GLib.str_hash,
187 (Gee.EqualDataFunc<string>)GLib.str_equal);
189 /* All symbols which did not have any version > minimumVersion */
190 filterMap = new Gee.HashSet<string>((Gee.HashDataFunc<string>)GLib.str_hash,
191 (Gee.EqualDataFunc<string>)GLib.str_equal);
193 try {
195 stdout.printf ("Generating %s (glibc %s) from libs at '%s' .", output, minimumVersion.getString(), libdir);
197 parseLibraries ();
198 generateHeader ();
200 } catch (Error e) {
202 warning("%s", e.message);
203 return 1;
206 stdout.printf(" OK\n");
208 return 0;
211 private static void parseLibrary (Regex regex, FileInfo fileinfo) throws Error {
212 string commandToUse = "objdump -T";
213 string output, errorOutput;
214 int returnCode;
216 /* For testing on objdump files which might be collected on other systems,
217 * run the generator with --libdir /path/to/objdump/files/
219 if ((libcwrap_debug_mask & DF.DUMP_FILES) != 0)
220 commandToUse = "cat";
222 Process.spawn_command_line_sync (commandToUse + " " + libdir + "/" + fileinfo.get_name(),
223 out output, out errorOutput, out returnCode);
225 if (returnCode != 0)
226 return;
228 foreach (var line in output.split ("\n")) {
230 if (regex.match (line) && !("PRIVATE" in line)) {
231 var version = new VersionNumber (regex.split (line)[3]);
232 var symbolName = regex.split (line)[7];
233 var versionInMap = symbolMap.get (symbolName);
235 /* Some versioning symbols exist in the objdump output, let's skip those */
236 if (symbolName.has_prefix ("GLIBC"))
237 continue;
239 libcwrap_note (DF.COLLECT, () =>
240 stdout.printf ("Selected symbol '%s' version '%s' from objdump line %s\n",
241 symbolName, version.getString(), line));
243 if (versionInMap == null) {
245 symbolMap.set (symbolName, version);
247 /* First occurance of the symbol, if it's older
248 * than the minimum required, put it in that table also
250 if (minimumVersion.newerThan (version)) {
251 filterMap.add (symbolName);
252 libcwrap_note (DF.FILTER, () =>
253 stdout.printf ("Adding symbol '%s %s' to the filter\n",
254 symbolName, version.getString()));
257 } else {
259 /* We want the newest possible version of a symbol which is older than the
260 * minimum glibc version specified (or the only version of the symbol if
261 * it's newer than the minimum version)
264 /* This symbol is <= minimumVersion
266 if (!version.newerThan (minimumVersion)) {
268 /* What we have in the map is > minimumVersion, so we need this version */
269 if (versionInMap.newerThan (minimumVersion))
270 symbolMap.set (symbolName, version);
272 /* What we have in the map is already <= minimumVersion, but we want
273 * the most recent acceptable symbol
275 else if (version.newerThan (versionInMap))
276 symbolMap.set (symbolName, version);
279 } else { /* This symbol is > minimumVersion */
281 /* If there are only versions > minimumVersion, then we want
282 * the lowest possible version, this is because we try to provide
283 * information in the linker warning about what version the symbol
284 * was initially introduced in.
286 if (versionInMap.newerThan (minimumVersion) &&
287 versionInMap.newerThan (version))
288 symbolMap.set (symbolName, version);
291 /* While trucking along through the huge symbol list, remove symbols from
292 * the 'safe to exclude' if there is a version found which is newer
293 * than the minimum requirement
295 if (version.newerThan (minimumVersion)) {
296 filterMap.remove(symbolName);
297 libcwrap_note (DF.FILTER, () =>
298 stdout.printf ("Removing symbol '%s' from the filter, found version '%s'\n",
299 symbolName, version.getString()));
303 } else {
304 libcwrap_note (DF.COLLECT, () => stdout.printf ("Rejected objdump line %s\n", line));
309 private static void parseLibraries () throws Error {
310 var libPath = File.new_for_path (libdir);
311 var enumerator = libPath.enumerate_children (FileAttribute.STANDARD_NAME, 0, null);
312 var regex = new Regex ("(.*)(GLIBC_)([0-9]+\\.([0-9]+\\.)*[0-9]+)(\\)?)([ ]*)(.+)");
314 var counter = 0;
315 FileInfo fileinfo;
317 while ((fileinfo = enumerator.next_file(null)) != null) {
319 if (++counter % 50 == 0) {
320 stdout.printf(".");
321 stdout.flush();
324 parseLibrary (regex, fileinfo);
328 private static void appendSymbols (StringBuilder headerFile, bool unavailableSymbols) {
330 if (unavailableSymbols)
331 headerFile.append("\n/* Symbols introduced in newer glibc versions, which must not be used */\n");
332 else
333 headerFile.append("\n/* Symbols redirected to earlier glibc versions */\n");
335 foreach (var it in symbolMap.keys) {
336 var version = symbolMap.get (it);
337 string versionToUse;
339 /* If the symbol only has occurrences older than the minimum required glibc version,
340 * then there is no need to output anything for this symbol
342 if (filterMap.contains (it))
343 continue;
345 /* If the only available symbol is > minimumVersion, then redirect it
346 * to a comprehensible linker error, otherwise redirect the symbol
347 * to it's existing version <= minimumVersion.
349 if (version.newerThan (minimumVersion)) {
351 versionToUse = "DONT_USE_THIS_VERSION_%s".printf (version.getString());
352 if (!unavailableSymbols)
353 continue;
355 } else {
357 versionToUse = version.getString ();
358 if (unavailableSymbols)
359 continue;
362 headerFile.append("__asm__(\".symver %s, %s@GLIBC_%s\");\n".printf(it, it, versionToUse));
366 private static void generateHeader () throws Error {
367 var headerFile = new StringBuilder ();
369 /* FIXME: Currently we do:
371 * if !defined (__OBJC__) && !defined (__ASSEMBLER__)
373 * But what we want is a clause which accepts any form of C including C++ and probably
374 * also including ObjC. That said, the generated header works fine for C and C++ sources.
376 headerFile.append ("/* glibc bindings for target ABI version glibc " + minimumVersion.getString() + " */\n");
377 headerFile.append ("#if !defined (__LIBC_CUSTOM_BINDINGS_H__)\n");
378 headerFile.append ("\n");
379 headerFile.append ("# if !defined (__OBJC__) && !defined (__ASSEMBLER__)\n");
380 headerFile.append ("# if defined (__cplusplus)\n");
381 headerFile.append ("extern \"C\" {\n");
382 headerFile.append ("# endif\n");
384 /* First generate the available redirected symbols, then the unavailable symbols */
385 appendSymbols (headerFile, false);
386 appendSymbols (headerFile, true);
388 headerFile.append ("\n");
389 headerFile.append ("# if defined (__cplusplus)\n");
390 headerFile.append ("}\n");
391 headerFile.append ("# endif\n");
392 headerFile.append ("# endif /* !defined (__OBJC__) && !defined (__ASSEMBLER__) */\n");
393 headerFile.append ("#endif\n");
395 FileUtils.set_contents (output, headerFile.str);