Browse Source

Build tests for ccan.
More sophisticated skipping: skip dependencies when one fails as well.
Allow tests to change their total_score; only access it after running (other than to check it's non-zero).

Rusty Russell 16 years ago
parent
commit
61088f5c75

+ 2 - 2
tools/Makefile

@@ -3,13 +3,13 @@ ALL_TOOLS = tools/ccan_depends tools/run_tests tools/doc_extract tools/namespaci
 .PHONY: tools
 tools: $(ALL_TOOLS)
 
-tools/ccan_depends: tools/ccan_depends.o tools/depends.o ccan/str_talloc/str_talloc.o ccan/grab_file/grab_file.o ccan/talloc/talloc.o ccan/noerr/noerr.o
+tools/ccan_depends: tools/ccan_depends.o tools/depends.o tools/tools.o ccan/str_talloc/str_talloc.o ccan/grab_file/grab_file.o ccan/talloc/talloc.o ccan/noerr/noerr.o
 
 tools/run_tests: tools/run_tests.o tools/depends.o ccan/str_talloc/str_talloc.o ccan/grab_file/grab_file.o ccan/tap/tap.o ccan/noerr/noerr.o ccan/talloc/talloc.o
 
 tools/doc_extract: tools/doc_extract.o tools/doc_extract-core.o ccan/str_talloc/str_talloc.o ccan/grab_file/grab_file.o ccan/noerr/noerr.o ccan/talloc/talloc.o
 
-tools/namespacize: tools/namespacize.o tools/depends.o ccan/str_talloc/str_talloc.o ccan/grab_file/grab_file.o ccan/noerr/noerr.o ccan/talloc/talloc.o
+tools/namespacize: tools/namespacize.o tools/depends.o tools/tools.o ccan/str_talloc/str_talloc.o ccan/grab_file/grab_file.o ccan/noerr/noerr.o ccan/talloc/talloc.o
 
 tools/run_tests.o tools/namespacize.o tools/depends.o: tools/tools.h
 

+ 1 - 0
tools/ccanlint/Makefile

@@ -6,6 +6,7 @@ CORE_OBJS := tools/ccanlint/ccanlint.o \
 	tools/ccanlint/file_analysis.o \
 	tools/doc_extract-core.o \
 	tools/depends.o \
+	tools/tools.o \
 	ccan/str_talloc/str_talloc.o ccan/grab_file/grab_file.o \
 	ccan/talloc/talloc.o ccan/noerr/noerr.o
 

+ 39 - 8
tools/ccanlint/ccanlint.c

@@ -68,6 +68,19 @@ bool ask(const char *question)
 		&& toupper(reply[0]) == 'Y';
 }
 
+static const char *should_skip(struct manifest *m, struct ccanlint *i)
+{
+	if (i->skip_fail)
+		return "dependency failed";
+
+	if (i->skip)
+		return "dependency was skipped";
+
+	if (i->can_run)
+		return i->can_run(m);
+	return NULL;
+}
+
 static bool run_test(struct ccanlint *i,
 		     bool summary,
 		     unsigned int *score,
@@ -77,18 +90,38 @@ static bool run_test(struct ccanlint *i,
 	void *result;
 	unsigned int this_score;
 	const struct dependent *d;
+	const char *skip;
 
-	if (i->total_score)
-		*total_score += i->total_score;
 	//one less test to run through
 	list_for_each(&i->dependencies, d, node)
 		d->dependent->num_depends--;
+
+	skip = should_skip(m, i);
+	if (skip) {
+		if (verbose)
+			printf("  %s: skipped (%s)\n", i->name, skip);
+
+		/* If we're skipping this because a prereq failed, we fail. */
+		if (i->skip_fail)
+			*total_score += i->total_score;
+			
+		list_del(&i->list);
+		list_add_tail(&finished_tests, &i->list);
+		list_for_each(&i->dependencies, d, node) {
+			d->dependent->skip = true;
+			d->dependent->skip_fail = i->skip_fail;
+		}
+		return true;
+	}
+
 	result = i->check(m);
 	if (!result) {
 		if (verbose)
 			printf("  %s: OK\n", i->name);
-		if (i->total_score)
+		if (i->total_score) {
 			*score += i->total_score;
+			*total_score += i->total_score;
+		}
 
 		list_del(&i->list);
 		list_add_tail(&finished_tests, &i->list);
@@ -103,6 +136,7 @@ static bool run_test(struct ccanlint *i,
 	list_del(&i->list);
 	list_add_tail(&finished_tests, &i->list);
 
+	*total_score += i->total_score;
 	*score += this_score;
 	if (summary) {
 		printf("%s FAILED (%u/%u)\n",
@@ -119,11 +153,8 @@ static bool run_test(struct ccanlint *i,
 
 	/* Skip any tests which depend on this one. */
 	list_for_each(&i->dependencies, d, node) {
-		list_del(&d->dependent->list);
-		list_add(&finished_tests, &d->dependent->list);
-		if (verbose)
-			printf("  -> skipping %s\n", d->dependent->name);
-		*total_score += d->dependent->total_score;
+		d->dependent->skip = true;
+		d->dependent->skip_fail = true;
 	}
 
 	return false;

+ 11 - 2
tools/ccanlint/ccanlint.h

@@ -26,8 +26,10 @@ struct manifest {
 
 	struct list_head other_files;
 
-	/* From tests/check_depends.c */
-	struct list_head dep_obj_files;
+	/* From tests/check_depends_exist.c */
+	struct list_head dep_dirs;
+	/* From tests/check_depends_built.c */
+	struct list_head dep_objs;
 };
 
 struct manifest *get_manifest(const void *ctx);
@@ -41,6 +43,9 @@ struct ccanlint {
 	/* Total score that this test is worth.  0 means compulsory tests. */
 	unsigned int total_score;
 
+	/* Can we run this test?  Return string explaining why, if not. */
+	const char *(*can_run)(struct manifest *m);
+
 	/* If this returns non-NULL, it means the check failed. */
 	void *(*check)(struct manifest *m);
 
@@ -60,6 +65,10 @@ struct ccanlint {
 	struct list_head dependencies;
 	/* How many things do we (still) depend on? */
 	unsigned int num_depends;
+	/* Did we skip a dependency?  If so, must skip this, too. */
+	bool skip;
+	/* Did we fail a dependency?  If so, skip and mark as fail. */
+	bool skip_fail;
 };
 
 /* Ask the user a yes/no question: the answer is NO if there's an error. */

+ 2 - 0
tools/ccanlint/file_analysis.c

@@ -164,6 +164,8 @@ struct manifest *get_manifest(const void *ctx)
 	list_head_init(&m->compile_fail_tests);
 	list_head_init(&m->other_test_files);
 	list_head_init(&m->other_files);
+	list_head_init(&m->dep_dirs);
+	list_head_init(&m->dep_objs);
 
 	m->basename = talloc_getcwd(m);
 	if (!m->basename)

+ 60 - 0
tools/ccanlint/tests/build.c

@@ -0,0 +1,60 @@
+#include <tools/ccanlint/ccanlint.h>
+#include <tools/tools.h>
+#include <ccan/talloc/talloc.h>
+#include <ccan/str/str.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <limits.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <err.h>
+#include <string.h>
+#include <ctype.h>
+
+static const char *can_build(struct manifest *m)
+{
+	if (list_empty(&m->c_files))
+		return "No C files in module";
+	if (safe_mode)
+		return "Safe mode enabled";
+	return NULL;
+}
+
+static char *obj_list(const struct manifest *m)
+{
+	char *list = talloc_strdup(m, "");
+	struct ccan_file *i;
+
+	/* Object from all the C files. */
+	list_for_each(&m->c_files, i, list)
+		list = talloc_asprintf_append(list, "%.*s.o ",
+					      strlen(i->name) - 2, i->name);
+
+	return list;
+}
+
+/* We leave this object file around after ccanlint runs, all built. */
+static void *do_build(struct manifest *m)
+{
+	return run_command(m, "ld -r -o ../%s.o %s", m->basename, obj_list(m));
+}
+
+static const char *describe_build(struct manifest *m, void *check_result)
+{
+	return talloc_asprintf(check_result, 
+			       "The object file for the module didn't build:\n"
+			       "%s", (char *)check_result);
+}
+
+struct ccanlint build = {
+	.name = "Module can be built",
+	.total_score = 1,
+	.check = do_build,
+	.describe = describe_build,
+	.can_run = can_build,
+};
+
+REGISTER_TEST(build, &depends_built, NULL);

+ 82 - 0
tools/ccanlint/tests/build_objs.c

@@ -0,0 +1,82 @@
+#include <tools/ccanlint/ccanlint.h>
+#include <tools/tools.h>
+#include <ccan/talloc/talloc.h>
+#include <ccan/str/str.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <limits.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <err.h>
+#include <string.h>
+#include <ctype.h>
+
+static const char *can_build(struct manifest *m)
+{
+	if (list_empty(&m->c_files))
+		return "No C files in module";
+	if (safe_mode)
+		return "Safe mode enabled";
+	return NULL;
+}
+
+static bool compile_obj(struct ccan_file *c_file, char *objfile, char **report)
+{
+	char *contents;
+
+	contents = run_command(objfile, "cc " CFLAGS " -o %s -c %s",
+			       objfile, c_file->name);
+	if (contents) {
+		if (*report)
+			*report = talloc_append_string(*report, contents);
+		else
+			*report = contents;
+		return false;
+	}
+	return true;
+}
+
+static int cleanup_obj(char *objfile)
+{
+	unlink(objfile);
+	return 0;
+}
+
+static void *check_objs_build(struct manifest *m)
+{
+	char *report = NULL;
+	struct ccan_file *i;
+
+	/* One point for each obj file. */
+	build_objs.total_score = 0;
+	list_for_each(&m->c_files, i, list)
+		build_objs.total_score++;
+
+	list_for_each(&m->c_files, i, list) {
+		char *objfile = talloc_strdup(m, i->name);
+		objfile[strlen(objfile)-1] = 'o';
+
+		if (compile_obj(i, objfile, &report))
+			talloc_set_destructor(objfile, cleanup_obj);
+	}
+	return report;
+}
+
+static const char *describe_objs_build(struct manifest *m, void *check_result)
+{
+	return talloc_asprintf(check_result, 
+			       "%s", (char *)check_result);
+}
+
+struct ccanlint build_objs = {
+	.name = "Module object files can be built",
+	.total_score = 1,
+	.check = check_objs_build,
+	.describe = describe_objs_build,
+	.can_run = can_build,
+};
+
+REGISTER_TEST(build_objs, &depends_exist, NULL);

+ 101 - 0
tools/ccanlint/tests/check_build.c

@@ -0,0 +1,101 @@
+#include <tools/ccanlint/ccanlint.h>
+#include <tools/tools.h>
+#include <ccan/talloc/talloc.h>
+#include <ccan/str/str.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <limits.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <err.h>
+#include <string.h>
+#include <ctype.h>
+
+static const char *can_build(struct manifest *m)
+{
+	if (safe_mode)
+		return "Safe mode enabled";
+	return NULL;
+}
+
+static int cleanup_testfile(char *testfile)
+{
+	unlink(testfile);
+	return 0;
+}
+
+static char *obj_list(const struct manifest *m)
+{
+	char *list = talloc_strdup(m, "");
+	struct ccan_file *i;
+
+	/* Other CCAN deps. */
+	list_for_each(&m->dep_objs, i, list)
+		list = talloc_asprintf_append(list, "%s ", i->name);
+
+	return list;
+}
+
+static char *lib_list(const struct manifest *m)
+{
+	unsigned int i, num;
+	char **libs = get_libs(m, ".", ".", &num);
+	char *ret = talloc_strdup(m, "");
+
+	for (i = 0; i < num; i++)
+		ret = talloc_asprintf_append(ret, "-l %s ", libs[i]);
+	return ret;
+}
+
+static void *check_use_build(struct manifest *m)
+{
+	char *contents;
+	char *tmpfile, *outfile;
+	int fd;
+
+	tmpfile = talloc_strdup(m, tempnam("/tmp", "ccanlint"));
+	talloc_set_destructor(tmpfile, cleanup_testfile);
+	outfile = talloc_strdup(m, tempnam("/tmp", "ccanlint"));
+	talloc_set_destructor(outfile, cleanup_testfile);
+
+	fd = open(tmpfile, O_WRONLY | O_CREAT | O_EXCL, 0600);
+	if (fd < 0)
+		return talloc_asprintf(m, "Creating temporary file: %s",
+				       strerror(errno));
+
+	contents = talloc_asprintf(tmpfile,
+				   "#include <ccan/%s/%s.h>\n"
+				   "int main(void)\n"
+				   "{\n"
+				   "	return 0;\n"
+				   "}\n",
+				   m->basename, m->basename);
+	if (write(fd, contents, strlen(contents)) != strlen(contents)) {
+		close(fd);
+		return "Failure writing to temporary file";
+	}
+	close(fd);
+
+	return run_command(m, "cc " CFLAGS " -o %s -x c %s -x none %s %s",
+			   outfile, tmpfile, obj_list(m), lib_list(m));
+}
+
+static const char *describe_use_build(struct manifest *m, void *check_result)
+{
+	return talloc_asprintf(check_result, 
+			       "Linking against module:\n"
+			       "%s", (char *)check_result);
+}
+
+struct ccanlint check_build = {
+	.name = "Module can be used",
+	.total_score = 1,
+	.check = check_use_build,
+	.describe = describe_use_build,
+	.can_run = can_build,
+};
+
+REGISTER_TEST(check_build, &build, NULL);

+ 0 - 109
tools/ccanlint/tests/check_depends.c

@@ -1,109 +0,0 @@
-#include <tools/ccanlint/ccanlint.h>
-#include <tools/tools.h>
-#include <ccan/talloc/talloc.h>
-#include <ccan/str/str.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <fcntl.h>
-#include <unistd.h>
-#include <limits.h>
-#include <errno.h>
-#include <stdlib.h>
-#include <stdio.h>
-#include <err.h>
-#include <string.h>
-#include <ctype.h>
-
-static bool expect_obj_file(const char *dir)
-{
-	char *olddir;
-	struct manifest *dep_man;
-	bool has_c_files;
-
-	olddir = talloc_getcwd(dir);
-	if (!olddir)
-		err(1, "Getting current directory");
-
-	/* We will fail below if this doesn't exist. */
-	if (chdir(dir) != 0)
-		return false;
-
-	dep_man = get_manifest(dir);
-	if (chdir(olddir) != 0)
-		err(1, "Returning to original directory '%s'", olddir);
-	talloc_free(olddir);
-
-	/* If it has C files, we expect an object file built from them. */
-	has_c_files = !list_empty(&dep_man->c_files);
-	talloc_free(dep_man);
-	return has_c_files;
-}
-
-/* FIXME: recursive ccanlint if they ask for it. */
-static char *add_dep(char *sofar, struct manifest *m, const char *dep)
-{
-	char *file, *dir;
-	struct stat st;
-	bool need_obj;
-
-	dir = talloc_asprintf(m, "../%s", dep);
-	need_obj = expect_obj_file(dir);
-
-	if (need_obj) {
-		file = talloc_asprintf(m, "../%s.o", dep);
-		if (stat(file, &st) == 0) {
-			struct ccan_file *f = new_ccan_file(m, file);
-			list_add_tail(&m->dep_obj_files, &f->list);
-			return sofar;
-		}
-	}
-
-	if (stat(dir, &st) == 0) {
-		if (!need_obj)
-			return sofar;
-
-		return talloc_asprintf_append(sofar,
-					      "ccan/%s: isn't built (no %s)\n",
-					      dep, file);
-	}
-
-	return talloc_asprintf_append(sofar,
-				      "ccan/%s: could not find directory %s\n",
-				      dep, dir);
-}
-
-static void *check_depends(struct manifest *m)
-{
-	unsigned int i;
-	char *report = NULL;
-	char **deps;
-
-	if (safe_mode)
-		deps = get_safe_ccan_deps(m, "..", m->basename, true);
-	else
-		deps = get_deps(m, "..", m->basename, true);
-
-	for (i = 0; deps[i]; i++) {
-		if (!strstarts(deps[i], "ccan/"))
-			continue;
-
-		report = add_dep(report, m, deps[i] + strlen("ccan/"));
-	}
-	return report;
-}
-
-static const char *describe_depends(struct manifest *m, void *check_result)
-{
-	return talloc_asprintf(check_result, 
-			       "The following dependencies are needed:\n"
-			       "%s\n", (char *)check_result);
-}
-
-struct ccanlint depends = {
-	.name = "CCAN dependencies are built",
-	.total_score = 1,
-	.check = check_depends,
-	.describe = describe_depends,
-};
-
-REGISTER_TEST(depends, NULL);

+ 92 - 0
tools/ccanlint/tests/check_depends_built.c

@@ -0,0 +1,92 @@
+#include <tools/ccanlint/ccanlint.h>
+#include <tools/tools.h>
+#include <ccan/talloc/talloc.h>
+#include <ccan/str/str.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <limits.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <err.h>
+#include <string.h>
+#include <ctype.h>
+
+static const char *can_build(struct manifest *m)
+{
+	if (safe_mode)
+		return "Safe mode enabled";
+	return NULL;
+}
+
+/* FIXME: recursive ccanlint if they ask for it. */
+static bool expect_obj_file(const char *dir)
+{
+	char *olddir;
+	struct manifest *dep_man;
+	bool has_c_files;
+
+	olddir = talloc_getcwd(dir);
+	if (!olddir)
+		err(1, "Getting current directory");
+
+	/* We will fail below if this doesn't exist. */
+	if (chdir(dir) != 0)
+		return false;
+
+	dep_man = get_manifest(dir);
+	if (chdir(olddir) != 0)
+		err(1, "Returning to original directory '%s'", olddir);
+	talloc_free(olddir);
+
+	/* If it has C files, we expect an object file built from them. */
+	has_c_files = !list_empty(&dep_man->c_files);
+	talloc_free(dep_man);
+	return has_c_files;
+}
+
+static void *check_depends_built(struct manifest *m)
+{
+	struct ccan_file *i;
+	char *report = NULL;
+
+	list_for_each(&m->dep_dirs, i, list) {
+		char *objfile;
+		struct stat st;
+
+		if (!expect_obj_file(i->name))
+			continue;
+
+		objfile = talloc_asprintf(m, "%s.o", i->name);
+		if (stat(objfile, &st) != 0) {
+			report = talloc_asprintf_append(report,
+							"object file %s\n",
+							objfile);
+		} else {
+			struct ccan_file *f = new_ccan_file(m, objfile);
+			list_add_tail(&m->dep_objs, &f->list);
+		}
+			
+	}
+	return talloc_steal(m, report);
+}
+
+static const char *describe_depends_built(struct manifest *m,
+					  void *check_result)
+{
+	return talloc_asprintf(check_result, 
+			       "The following dependencies are not built:\n"
+			       "%s", (char *)check_result);
+}
+
+struct ccanlint depends_built = {
+	.name = "CCAN dependencies are built",
+	.total_score = 1,
+	.check = check_depends_built,
+	.describe = describe_depends_built,
+	.can_run = can_build,
+};
+
+REGISTER_TEST(depends_built, &depends_exist, NULL);

+ 71 - 0
tools/ccanlint/tests/check_depends_exist.c

@@ -0,0 +1,71 @@
+#include <tools/ccanlint/ccanlint.h>
+#include <tools/tools.h>
+#include <ccan/talloc/talloc.h>
+#include <ccan/str/str.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <limits.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <err.h>
+#include <string.h>
+#include <ctype.h>
+
+static char *add_dep(char *sofar, struct manifest *m, const char *dep)
+{
+	char *dir;
+	struct stat st;
+	struct ccan_file *f;
+
+	dir = talloc_asprintf(m, "../%s", dep);
+	if (stat(dir, &st) != 0) {
+		return talloc_asprintf_append(sofar,
+					      "ccan/%s: expected it in"
+					      " directory %s\n",
+					      dep, dir);
+	}
+
+	f = new_ccan_file(m, dir);
+	list_add_tail(&m->dep_dirs, &f->list);
+	return sofar;
+}
+
+static void *check_depends_exist(struct manifest *m)
+{
+	unsigned int i;
+	char *report = NULL;
+	char **deps;
+
+	if (safe_mode)
+		deps = get_safe_ccan_deps(m, "..", m->basename, true);
+	else
+		deps = get_deps(m, "..", m->basename, true);
+
+	for (i = 0; deps[i]; i++) {
+		if (!strstarts(deps[i], "ccan/"))
+			continue;
+
+		report = add_dep(report, m, deps[i] + strlen("ccan/"));
+	}
+	return report;
+}
+
+static const char *describe_depends_exist(struct manifest *m,
+					  void *check_result)
+{
+	return talloc_asprintf(check_result,
+			       "The following dependencies are are expected:\n"
+			       "%s", (char *)check_result);
+}
+
+struct ccanlint depends_exist = {
+	.name = "CCAN dependencies are present",
+	.total_score = 1,
+	.check = check_depends_exist,
+	.describe = describe_depends_exist,
+};
+
+REGISTER_TEST(depends_exist, NULL);

+ 75 - 0
tools/ccanlint/tests/check_includes_build.c

@@ -0,0 +1,75 @@
+#include <tools/ccanlint/ccanlint.h>
+#include <tools/tools.h>
+#include <ccan/talloc/talloc.h>
+#include <ccan/grab_file/grab_file.h>
+#include <ccan/str/str.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <limits.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <err.h>
+#include <string.h>
+#include <ctype.h>
+
+static const char *can_build(struct manifest *m)
+{
+	if (safe_mode)
+		return "Safe mode enabled";
+	return NULL;
+}
+
+static int cleanup_testfile(char *testfile)
+{
+	unlink(testfile);
+	return 0;
+}
+
+static void *check_includes_build(struct manifest *m)
+{
+	char *contents;
+	char *tmpfile, *objfile;
+	int fd;
+
+	tmpfile = talloc_strdup(m, tempnam("/tmp", "ccanlint"));
+	talloc_set_destructor(tmpfile, cleanup_testfile);
+	objfile = talloc_strdup(m, tempnam("/tmp", "ccanlint"));
+	talloc_set_destructor(objfile, cleanup_testfile);
+
+	fd = open(tmpfile, O_WRONLY | O_CREAT | O_EXCL, 0600);
+	if (fd < 0)
+		return talloc_asprintf(m, "Creating temporary file: %s",
+				       strerror(errno));
+
+	contents = talloc_asprintf(tmpfile, "#include <ccan/%s/%s.h>\n",
+				   m->basename, m->basename);
+	if (write(fd, contents, strlen(contents)) != strlen(contents)) {
+		close(fd);
+		return "Failure writing to temporary file";
+	}
+	close(fd);
+
+	return run_command(m, "cc " CFLAGS " -o %s -c -x c %s",
+			   objfile, tmpfile);
+}
+
+static const char *describe_includes_build(struct manifest *m,
+					   void *check_result)
+{
+	return talloc_asprintf(check_result, 
+			       "#include of the main header file:\n"
+			       "%s", (char *)check_result);
+}
+
+struct ccanlint includes_build = {
+	.name = "Can compile against main header",
+	.total_score = 1,
+	.check = check_includes_build,
+	.describe = describe_includes_build,
+	.can_run = can_build,
+};
+
+REGISTER_TEST(includes_build, &depends_exist, NULL);

+ 16 - 37
tools/depends.c

@@ -185,6 +185,22 @@ get_all_deps(const void *ctx, const char *dir, const char *name,
 	return deps;
 }
 
+char **get_libs(const void *ctx, const char *dir,
+		const char *name, unsigned int *num)
+{
+	char **libs, *cmd, *infofile;
+
+	infofile = compile_info(ctx, dir, name);
+	if (!infofile)
+		errx(1, "Could not compile _info for '%s'", name);
+
+	cmd = talloc_asprintf(ctx, "%s libs", infofile);
+	libs = lines_from_cmd(cmd, num, "%s", cmd);
+	if (!libs)
+		err(1, "Could not run '%s'", cmd);
+	return libs;
+}
+
 char **get_deps(const void *ctx, const char *dir, const char *name,
 		bool recurse)
 {
@@ -204,40 +220,3 @@ char **get_safe_ccan_deps(const void *ctx, const char *dir,
 	}
 	return get_all_deps(ctx, dir, name, get_one_safe_deps);
 }
-	
-char *talloc_basename(const void *ctx, const char *dir)
-{
-	char *p = strrchr(dir, '/');
-
-	if (!p)
-		return (char *)dir;
-	return talloc_strdup(ctx, p+1);
-}
-
-char *talloc_dirname(const void *ctx, const char *dir)
-{
-	char *p = strrchr(dir, '/');
-
-	if (!p)
-		return talloc_strdup(ctx, ".");
-	return talloc_strndup(ctx, dir, p - dir);
-}
-
-char *talloc_getcwd(const void *ctx)
-{
-	unsigned int len;
-	char *cwd;
-
-	/* *This* is why people hate C. */
-	len = 32;
-	cwd = talloc_array(ctx, char, len);
-	while (!getcwd(cwd, len)) {
-		if (errno != ERANGE) {
-			talloc_free(cwd);
-			return NULL;
-		}
-		cwd = talloc_realloc(ctx, cwd, char, len *= 2);
-	}
-	return cwd;
-}
-

+ 69 - 0
tools/tools.c

@@ -0,0 +1,69 @@
+#include <ccan/talloc/talloc.h>
+#include <ccan/grab_file/grab_file.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdarg.h>
+#include <errno.h>
+#include "tools.h"
+
+char *talloc_basename(const void *ctx, const char *dir)
+{
+	char *p = strrchr(dir, '/');
+
+	if (!p)
+		return (char *)dir;
+	return talloc_strdup(ctx, p+1);
+}
+
+char *talloc_dirname(const void *ctx, const char *dir)
+{
+	char *p = strrchr(dir, '/');
+
+	if (!p)
+		return talloc_strdup(ctx, ".");
+	return talloc_strndup(ctx, dir, p - dir);
+}
+
+char *talloc_getcwd(const void *ctx)
+{
+	unsigned int len;
+	char *cwd;
+
+	/* *This* is why people hate C. */
+	len = 32;
+	cwd = talloc_array(ctx, char, len);
+	while (!getcwd(cwd, len)) {
+		if (errno != ERANGE) {
+			talloc_free(cwd);
+			return NULL;
+		}
+		cwd = talloc_realloc(ctx, cwd, char, len *= 2);
+	}
+	return cwd;
+}
+
+char *run_command(const void *ctx, const char *fmt, ...)
+{
+	va_list ap;
+	char *cmd, *contents;
+	FILE *pipe;
+
+	va_start(ap, fmt);
+	cmd = talloc_vasprintf(ctx, fmt, ap);
+	va_end(ap);
+
+	/* Ensure stderr gets to us too. */
+	cmd = talloc_asprintf_append(cmd, " 2>&1");
+	
+	pipe = popen(cmd, "r");
+	if (!pipe)
+		return talloc_asprintf(ctx, "Failed to run '%s'", cmd);
+
+	contents = grab_fd(cmd, fileno(pipe), NULL);
+	if (pclose(pipe) != 0)
+		return talloc_asprintf(ctx, "Running '%s':\n%s",
+				       cmd, contents);
+
+	talloc_free(cmd);
+	return NULL;
+}

+ 6 - 0
tools/tools.h

@@ -19,7 +19,13 @@ char **get_deps(const void *ctx, const char *dir, const char *name,
 char **get_safe_ccan_deps(const void *ctx, const char *dir, const char *name,
 			  bool recurse);
 
+/* This also needs to compile the info file. */
+char **get_libs(const void *ctx, const char *dir,
+		const char *name, unsigned int *num);
+
+/* From tools.c */
 char *talloc_basename(const void *ctx, const char *dir);
 char *talloc_dirname(const void *ctx, const char *dir);
 char *talloc_getcwd(const void *ctx);
+char *run_command(const void *ctx, const char *fmt, ...);
 #endif /* CCAN_TOOLS_H */