jbsrc/lib/src/core/jb-tests.c (14366B) - raw
1 /*
2 * JB, the Jean-Yves Lefort's Build System
3 * Copyright (C) 2008 Jean-Yves Lefort <jylefort@brutele.be>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program 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
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19
20 #include <stdio.h>
21 #include <string.h>
22 #include <unistd.h>
23 #include <glib.h>
24 #include "jb-util.h"
25 #include "jb-variable.h"
26 #include "jb-compile-options.h"
27 #include "jb-tests.h"
28 #include "jb-feature.h"
29
30 void
31 jb_check_host_system (void)
32 {
33 char *output;
34 gboolean status = FALSE;
35
36 if (jb_variable_get_variable("host-cpu") != NULL)
37 return; /* already checked */
38
39 jb_message_checking("the host system type");
40
41 if (jb_exec(&output, NULL, "jbsrc/tools/config.sub `jbsrc/tools/config.guess`"))
42 {
43 char **fields;
44 int len;
45
46 fields = g_strsplit(output, "-", 0);
47 len = g_strv_length(fields);
48
49 if (len == 3)
50 {
51 jb_variable_set_string("host-cpu", fields[0]);
52 jb_variable_set_string("host-manufacturer", fields[1]);
53 jb_variable_set_string("host-kernel", NULL);
54 jb_variable_set_string("host-os", fields[2]);
55 status = TRUE;
56 }
57 else if (len == 4)
58 {
59 jb_variable_set_string("host-cpu", fields[0]);
60 jb_variable_set_string("host-manufacturer", fields[1]);
61 jb_variable_set_string("host-kernel", fields[2]);
62 jb_variable_set_string("host-os", fields[3]);
63 status = TRUE;
64 }
65
66 g_strfreev(fields);
67 }
68
69 if (status)
70 jb_message_result_string(output);
71 else
72 jb_message_result_string("unknown");
73
74 g_free(output);
75
76 if (! status)
77 jb_error("unable to determine host system type");
78 }
79
80 void
81 jb_register_program (const char *name, JBVariableFlags flags)
82 {
83 char *description;
84 char *program;
85
86 g_return_if_fail(name != NULL);
87
88 description = g_strdup_printf("%s program", name);
89
90 program = g_find_program_in_path(name);
91
92 jb_variable_add_string(name,
93 description,
94 jb_variable_group_external_programs,
95 flags,
96 program);
97
98 g_free(description);
99 g_free(program);
100 }
101
102 /* the program must have been registered with jb_register_program() */
103 gboolean
104 jb_check_program (const char *name)
105 {
106 static GHashTable *checked_programs = NULL;
107 JBVariable *variable;
108 const char *program;
109 gboolean result = FALSE;
110
111 g_return_val_if_fail(name != NULL, FALSE);
112
113 /* do not check for the same program twice */
114 if (checked_programs != NULL)
115 {
116 gpointer checked_result;
117
118 if (g_hash_table_lookup_extended(checked_programs, name, NULL, &checked_result))
119 return GPOINTER_TO_INT(checked_result);
120 }
121 else
122 checked_programs = g_hash_table_new(g_str_hash, g_str_equal);
123
124 jb_message_checking("for %s", name);
125
126 variable = jb_variable_get_variable_or_error(name);
127
128 program = g_value_get_string(&variable->value);
129
130 if (variable->user_set)
131 {
132 char *absolute_program;
133
134 absolute_program = g_find_program_in_path(program);
135 if (absolute_program != NULL)
136 {
137 jb_message_result_string(absolute_program);
138 g_value_take_string(&variable->value, absolute_program);
139 result = TRUE;
140 }
141 else
142 {
143 jb_message_result_string_format("not found (\"%s\" was specified but was not found, is a directory or is not executable)", program);
144 g_value_set_string(&variable->value, NULL);
145 }
146 }
147 else
148 {
149 if (program != NULL)
150 {
151 jb_message_result_string(program);
152 result = TRUE;
153 }
154 else
155 jb_message_result_string("not found");
156 }
157
158 g_hash_table_insert(checked_programs, g_strdup(name), GINT_TO_POINTER(result));
159
160 return result;
161 }
162
163 void
164 jb_require_program (const char *name)
165 {
166 g_return_if_fail(name != NULL);
167
168 if (! jb_check_program(name))
169 jb_error("required program \"%s\" not found", name);
170 }
171
172 static void
173 log_c_test (const char *filename, const char *action)
174 {
175 char *source;
176 char **lines;
177 int i;
178
179 source = jb_read_file_or_exit(filename);
180 lines = g_strsplit(source, "\n", 0);
181 g_free(source);
182
183 jb_log("attempting to %s program %s:", action, filename);
184
185 for (i = 0; lines[i] != NULL; i++)
186 {
187 const char *line = lines[i];
188
189 /* do not output a spurious empty last line */
190 if (*line == '\0' && lines[i + 1] == NULL)
191 break;
192
193 jb_log("%4i %s", i + 1, lines[i]);
194 }
195
196 jb_log(JB_SEPARATOR);
197
198 g_strfreev(lines);
199 }
200
201 gboolean
202 jb_test_compile (const char *filename,
203 const char *cflags,
204 const char *cppflags)
205 {
206 g_return_val_if_fail(filename != NULL, FALSE);
207
208 log_c_test(filename, "compile");
209
210 return jb_exec_expand(NULL, NULL,
211 "$cc -c -o build/test.o"
212 " $cflags $extra-cflags"
213 " $cppflags $extra-cppflags"
214 " $filename",
215 "extra-cflags", cflags,
216 "extra-cppflags", cppflags,
217 "filename", filename,
218 NULL);
219 }
220
221 gboolean
222 jb_test_compile_string (const char *str,
223 const char *cflags,
224 const char *cppflags)
225 {
226 g_return_val_if_fail(str != NULL, FALSE);
227
228 jb_write_file_or_exit("build/test.c", str);
229
230 return jb_test_compile("build/test.c", cflags, cppflags);
231 }
232
233 gboolean
234 jb_test_link (const char *filename,
235 const char *cflags,
236 const char *cppflags,
237 const char *ldflags,
238 const char *libs)
239 {
240 g_return_val_if_fail(filename != NULL, FALSE);
241
242 log_c_test(filename, "link");
243
244 return jb_exec_expand(NULL, NULL,
245 "$cc -o build/test"
246 " $cflags $extra-cflags"
247 " $cppflags $extra-cppflags"
248 " $ldflags $extra-ldflags"
249 " $filename"
250 " $libs $extra-libs",
251 "filename", filename,
252 "extra-cflags", cflags,
253 "extra-cppflags", cppflags,
254 "extra-ldflags", ldflags,
255 "extra-libs", libs,
256 NULL);
257 }
258
259 gboolean
260 jb_test_link_string (const char *str,
261 const char *cflags,
262 const char *cppflags,
263 const char *ldflags,
264 const char *libs)
265 {
266 g_return_val_if_fail(str != NULL, FALSE);
267
268 jb_write_file_or_exit("build/test.c", str);
269
270 return jb_test_link("build/test.c", cflags, cppflags, ldflags, libs);
271 }
272
273 gboolean
274 jb_test_run (const char *filename,
275 const char *cflags,
276 const char *cppflags,
277 const char *ldflags,
278 const char *libs)
279 {
280 g_return_val_if_fail(filename != NULL, FALSE);
281
282 if (! jb_test_link(filename, cflags, cppflags, ldflags, libs))
283 return FALSE;
284
285 return jb_exec(NULL, NULL, "build/test");
286 }
287
288 gboolean
289 jb_test_run_string (const char *str,
290 const char *cflags,
291 const char *cppflags,
292 const char *ldflags,
293 const char *libs)
294 {
295 g_return_val_if_fail(str != NULL, FALSE);
296
297 jb_write_file_or_exit("build/test.c", str);
298
299 return jb_test_run("build/test.c", cflags, cppflags, ldflags, libs);
300 }
301
302 gboolean
303 jb_check_functions (const char *functions, const char *libname)
304 {
305 char **functions_array;
306 int i;
307 GString *checking_message;
308 GString *program;
309 char *libs = NULL;
310 gboolean result;
311
312 g_return_val_if_fail(functions != NULL, FALSE);
313
314 functions_array = g_strsplit(functions, " ", 0);
315
316 checking_message = g_string_new("for ");
317
318 for (i = 0; functions_array[i] != NULL; i++)
319 {
320 if (i != 0)
321 g_string_append_printf(checking_message, ", ");
322
323 g_string_append_printf(checking_message, "%s()", functions_array[i]);
324 }
325
326 if (libname)
327 g_string_append_printf(checking_message, " in -l%s", libname);
328 else
329 g_string_append(checking_message, " in libc");
330
331 jb_message_checking("%s", checking_message->str);
332 g_string_free(checking_message, TRUE);
333
334 program = g_string_new(NULL);
335
336 /*
337 * Quoting c.m4 in autoconf: "Override any GCC internal prototype to
338 * avoid an error. Use char because int might match the return type
339 * of a GCC builtin and then its argument prototype would still
340 * apply."
341 */
342 for (i = 0; functions_array[i] != NULL; i++)
343 g_string_append_printf(program, "char %s ();\n", functions_array[i]);
344
345 g_string_append(program, "int main () {");
346
347 for (i = 0; functions_array[i] != NULL; i++)
348 g_string_append_printf(program, " %s();", functions_array[i]);
349
350 g_string_append(program, " }\n");
351
352 if (libname)
353 libs = g_strdup_printf("-l%s", libname);
354
355 result = jb_test_link_string(program->str, NULL, NULL, NULL, libs);
356
357 g_string_free(program, TRUE);
358 g_free(libs);
359
360 jb_message_result_bool(result);
361
362 return result;
363 }
364
365 gboolean
366 jb_check_packages (const char *group_name,
367 const char *varprefix,
368 const char *packages)
369 {
370 char *quoted_packages;
371 char *cflags = NULL;
372 char *libs = NULL;
373 char *error = NULL;
374 gboolean status;
375
376 g_return_val_if_fail(group_name != NULL, FALSE);
377 g_return_val_if_fail(varprefix != NULL, FALSE);
378 g_return_val_if_fail(packages != NULL, FALSE);
379 g_return_val_if_fail(jb_feature_is_enabled(&jb_pkg_config_feature), FALSE);
380
381 jb_require_program("pkg-config");
382
383 jb_message_checking("for %s", group_name);
384
385 quoted_packages = g_shell_quote(packages);
386
387 status = jb_exec_expand(&cflags, NULL, "$pkg-config --cflags $packages",
388 "packages", quoted_packages,
389 NULL)
390 && jb_exec_expand(&libs, NULL, "$pkg-config --libs $packages",
391 "packages", quoted_packages,
392 NULL);
393
394 if (status)
395 jb_variable_set_package_flags(varprefix, cflags, NULL, NULL, libs);
396 else
397 jb_exec_expand(NULL, &error, "$pkg-config --print-errors $packages",
398 "packages", quoted_packages,
399 NULL);
400
401 g_free(quoted_packages);
402 g_free(cflags);
403 g_free(libs);
404
405 jb_message_result_bool(status);
406
407 if (error != NULL)
408 {
409 jb_warning("%s", error);
410 g_free(error);
411 }
412
413 return status;
414 }
415
416 /*
417 * If one or more options (bool variables) in the provided
418 * NULL-terminated list are enabled, checks for the specified
419 * packages. If the packages are not found, disable the provided
420 * options.
421 */
422 void
423 jb_check_packages_for_options (const char *group_name,
424 const char *varprefix,
425 const char *packages,
426 ...)
427 {
428 GSList *options = NULL;
429 va_list args;
430 const char *option;
431 gboolean needs_packages = FALSE;
432
433 g_return_if_fail(group_name != NULL);
434 g_return_if_fail(varprefix != NULL);
435 g_return_if_fail(packages != NULL);
436 g_return_if_fail(jb_feature_is_enabled(&jb_pkg_config_feature));
437
438 va_start(args, packages);
439 while ((option = va_arg(args, const char *)) != NULL)
440 {
441 options = g_slist_append(options, (gpointer) option);
442
443 if (jb_variable_get_bool(option))
444 needs_packages = TRUE;
445 }
446 va_end(args);
447
448 if (needs_packages && ! jb_check_packages(group_name, varprefix, packages))
449 {
450 GSList *l;
451
452 JB_LIST_FOREACH(l, options)
453 {
454 option = l->data;
455
456 if (jb_variable_get_bool(option))
457 {
458 jb_warning("disabling option \"%s\" since %s was not found", option, group_name);
459 jb_variable_set_bool(option, FALSE);
460 }
461 }
462 }
463
464 g_slist_free(options);
465 }
466
467 void
468 jb_require_packages (const char *group_name,
469 const char *varprefix,
470 const char *packages)
471 {
472 g_return_if_fail(group_name != NULL);
473 g_return_if_fail(varprefix != NULL);
474 g_return_if_fail(packages != NULL);
475 g_return_if_fail(jb_feature_is_enabled(&jb_pkg_config_feature));
476
477 if (! jb_check_packages(group_name, varprefix, packages))
478 jb_error("unable to find %s", group_name);
479 }
480
481 char *
482 jb_get_package_variable (const char *package, const char *variable)
483 {
484 char *value;
485
486 g_return_val_if_fail(package != NULL, NULL);
487 g_return_val_if_fail(variable != NULL, NULL);
488 g_return_val_if_fail(jb_feature_is_enabled(&jb_pkg_config_feature), FALSE);
489
490 jb_require_program("pkg-config");
491
492 if (! jb_exec_expand(&value, NULL, "$pkg-config --variable=$variable $package",
493 "variable", variable,
494 "package", package,
495 NULL))
496 {
497 g_free(value);
498 value = NULL;
499 }
500
501 return value;
502 }
503
504 void
505 jb_check_cc_dependency_style (void)
506 {
507 if (! jb_variable_get_bool("cc-dependency-tracking"))
508 return; /* we don't need to know since we will not use it */
509
510 jb_message_checking("the C compiler dependency style");
511
512 /* make sure they do not exist beforehand */
513 unlink("build/test.o");
514 unlink("build/test.o.deps");
515
516 if (jb_test_compile_string("#include <stdio.h>\n"
517 "int main () {}\n",
518 "-MT build/test.o -MD -MP -MF build/test.o.deps",
519 NULL)
520 && g_file_test("build/test.o", G_FILE_TEST_IS_REGULAR)
521 && g_file_test("build/test.o.deps", G_FILE_TEST_IS_REGULAR))
522 jb_message_result_string("GCC");
523 else
524 {
525 jb_message_result_string("unknown, disabling dependency tracking");
526 jb_variable_set_bool("cc-dependency-tracking", FALSE);
527 }
528 }
529
530 void
531 jb_check_glibc (void)
532 {
533 gboolean result;
534
535 jb_message_checking("for the GNU C library");
536 result = jb_test_compile_string("#include <features.h>\n"
537 "int main() {\n"
538 "#ifndef __GLIBC__\n"
539 "#error \"glibc not found\"\n"
540 "#endif\n"
541 "}\n",
542 NULL,
543 NULL);
544 jb_message_result_bool(result);
545
546 jb_variable_set_bool("glibc", result);
547 }
548
549 static gboolean
550 check_reentrant_resolver_real (gboolean *result)
551 {
552 const char *os;
553 int freebsd_major;
554 int freebsd_minor;
555
556 if (! strcmp(jb_variable_get_string("host-kernel"), "linux"))
557 {
558 *result = TRUE;
559 return TRUE;
560 }
561
562 os = jb_variable_get_string("host-os");
563
564 if (sscanf(os, "freebsd%d.%d", &freebsd_major, &freebsd_minor) == 2)
565 {
566 /* FreeBSD >= 5.3 */
567 *result = (freebsd_major == 5 && freebsd_minor >= 3) || freebsd_major >= 6;
568 return TRUE;
569 }
570
571 if (g_str_has_prefix(os, "netbsd") || g_str_has_prefix(os, "openbsd"))
572 {
573 *result = FALSE;
574 return TRUE;
575 }
576
577 return FALSE; /* unknown */
578 }
579
580 void
581 jb_check_reentrant_dns_resolver (void)
582 {
583 gboolean result;
584
585 jb_check_host_system();
586
587 jb_message_checking("if the DNS resolver is reentrant");
588
589 if (check_reentrant_resolver_real(&result))
590 {
591 jb_message_result_bool(result);
592 if (result)
593 jb_compile_options_add_cppflags(jb_compile_options, "-DHAVE_REENTRANT_RESOLVER");
594 }
595 else
596 jb_message_result_string("unknown, assuming it is not");
597 }