From aefc524f57ef9156012fe9c6621e056dfe166d8c Mon Sep 17 00:00:00 2001 From: Pat Pannuto Date: Sun, 20 Oct 2013 20:31:14 -0400 Subject: [PATCH 1/2] Add option parsing to Tupfile.ini Any `Tupfile.ini` encountered along the way to the root is parsed as a configuration file (such as the `.tup/options` or `/etc/tup/options` files). The ini files are added to the configuration search in the order they are found, that is a `Tupfile.ini` in the directory `tup` is run from has the highest precedence. Note: This has the interesting property that things in `Tupfile.ini` files are explicitly not project-global unless they are in the root directory of the project. The contents of `Tupfile.ini` files are not tracked in tup's database, rather they are re-parsed on every invocation of tup. This is a design choice, as the configuration elements in the ini files are designed to affect the behavoir of tup as a program, not tup as a build system. In a correct system, the options exposed by the ini files should not be able to affect net result of a build. --- src/tup/option.c | 152 ++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 124 insertions(+), 28 deletions(-) diff --git a/src/tup/option.c b/src/tup/option.c index 920c6f5a4..cb4ef706f 100644 --- a/src/tup/option.c +++ b/src/tup/option.c @@ -49,20 +49,26 @@ static char home_loc[PATH_MAX]; -static struct { +static struct config_file { const char *file; struct vardb root; int exists; -} locations[] = { +} static_configs[] = { { .file = TUP_OPTIONS_FILE }, { .file = home_loc }, #ifndef _WIN32 { .file = "/etc/tup/options" }, #endif }; -#define NUM_OPTION_LOCATIONS (sizeof(locations) / sizeof(locations[0])) +#define NUM_OPTION_LOCATIONS (sizeof(static_configs) / sizeof(static_configs[0])) + +static struct dynamic_config_file_list { + struct dynamic_config_file_list *next; + struct config_file dynamic_config; +} *dynamic_configs = NULL; static int parse_option_file(int x); +static void parse_ini_file(FILE *f); static const char *cpu_number(void); static const char *stdout_isatty(void); static const char *get_console_width(void); @@ -133,6 +139,8 @@ int tup_option_process_ini(void) { exit(1); } } else { + parse_ini_file(f); + if (best_root != -1) close(best_root); /* open can never fail as we have @@ -238,7 +246,7 @@ int tup_option_init(void) return -1; for(x=0; xdynamic_config.file); + c = c->next; + free(temp); + } + dynamic_configs = NULL; +} + +static int tup_boolify_flag(const char *value) { + if(strcasecmp(value, "true") == 0) return 1; - if(strcmp(value, "yes") == 0) + if(strcasecmp(value, "yes") == 0) return 1; - if(strcmp(value, "false") == 0) + if(strcasecmp(value, "false") == 0) return 0; - if(strcmp(value, "no") == 0) + if(strcasecmp(value, "no") == 0) return 0; return atoi(value); } +int tup_option_get_flag(const char *opt) +{ + const char *value = tup_option_get_string(opt); + return tup_boolify_flag(value); +} + int tup_option_get_int(const char *opt) { return atoi(tup_option_get_string(opt)); @@ -284,13 +306,25 @@ const char *tup_option_get_string(const char *opt) { unsigned int x; int len = strlen(opt); + struct dynamic_config_file_list *c = dynamic_configs; + if(!inited) { fprintf(stderr, "tup internal error: Called tup_option_get_string(%s) before the options were initialized.\n", opt); exit(1); } + + while(c != NULL) { + struct var_entry *ve; + ve = vardb_get(&c->dynamic_config.root, opt, len); + if(ve) { + return ve->value; + } + c = c->next; + } + for(x=0; xvalue; } @@ -319,9 +353,9 @@ const char *tup_option_get_location(const char *opt) } for(x=0; xdynamic_config.root, options[x].name, len); if(ve) { - location = locations[y].file; + location = c->dynamic_config.file; value = ve->value; - break; + found_option = 1; + } + c = c->next; + } + + y = 0; + while (!found_option && (y < NUM_OPTION_LOCATIONS)) { + struct var_entry *ve; + ve = vardb_get(&static_configs[y].root, options[x].name, len); + if(ve) { + location = static_configs[y].file; + value = ve->value; + found_option = 1; } + y++; } printf("%s = '%s' from %s\n", options[x].name, value, location); } @@ -389,20 +439,66 @@ static int parse_option_file(int x) FILE *f; int rc; - f = fopen(locations[x].file, "r"); + f = fopen(static_configs[x].file, "r"); if(!f) { /* Don't care if the file's not there or we can't read it */ - locations[x].exists = 0; + static_configs[x].exists = 0; return 0; } - locations[x].exists = 1; - rc = ini_parse_file(f, parse_callback, &locations[x].root); + static_configs[x].exists = 1; + rc = ini_parse_file(f, parse_callback, &static_configs[x].root); fclose(f); if(rc == 0) return 0; return -1; } +static void parse_ini_file(FILE *f) { + struct dynamic_config_file_list *c = dynamic_configs; + int rc; + const char* ini_name = "Tupfile.ini"; + /* strlen("Tupfile.ini") == 11. Some compilers don't grok const ints */ + char file_path[PATH_MAX + 1 + 11] = {0}; + + if (c == NULL) { + dynamic_configs = malloc(sizeof(struct dynamic_config_file_list)); + c = dynamic_configs; + } else { + while (c->next != NULL) + c = c->next; + c->next = malloc(sizeof(struct dynamic_config_file_list)); + c = c->next; + } + assert(c && "malloc config struct"); + c->next = NULL; + + if (NULL == getcwd(file_path, PATH_MAX)) { + perror("getcwd"); + fprintf(stderr, "tup error: Unexpected error getting current directory\n"); + exit(1); + } + file_path[strlen(file_path)] = PATH_SEP; // note: still \0-terminated from array init + strcpy(file_path+strlen(file_path), ini_name); + c->dynamic_config.file = strdup(file_path); + if (c->dynamic_config.file == NULL) { + perror("strdup"); + fprintf(stderr, "tup error: Unexpected error allocating memory for file path\n"); + exit(1); + } + + if(vardb_init(&c->dynamic_config.root) < 0) { + fprintf(stderr, "tup internal error: Failed to initialize vardb for .ini config entry\n"); + exit(1); + } + + rc = ini_parse_file(f, parse_callback, &c->dynamic_config.root); + + if (rc != 0) { + fprintf(stderr, "tup error: Failed to parse ini file\n"); + exit(1); + } +} + static const char *cpu_number(void) { static char buf[10]; From 8d09099756be09ef2f2d4e17e8ea5b843aa5baf8 Mon Sep 17 00:00:00 2001 From: Pat Pannuto Date: Thu, 22 Aug 2013 17:29:23 -0700 Subject: [PATCH 2/2] Add post_init.varaints hook for default variants This commit adds support for default variants. It adds an option entry called post_init.variants that takes a list of variants to be run. The hook is called any time that `init_command()` is run, whether implicitly or by an explicit tup init. The init() process is re-arranged slightly so that tup is fully init'd when the post_init hooks run. For similar reasons, find_tup_dir is converted to a singleton. --- src/tup/config.c | 4 ++ src/tup/hooks.c | 92 ++++++++++++++++++++++++++++++++++++++ src/tup/hooks.h | 27 ++++++++++++ src/tup/init.c | 42 +++++++++++++++--- src/tup/init.h | 2 + src/tup/option.c | 107 ++++++++++++++++++++++++++++++--------------- src/tup/option.h | 3 +- src/tup/tup/main.c | 45 ++++++++++--------- tup.1 | 3 ++ 9 files changed, 260 insertions(+), 65 deletions(-) create mode 100644 src/tup/hooks.c create mode 100644 src/tup/hooks.h diff --git a/src/tup/config.c b/src/tup/config.c index 522e152c9..cd2f45522 100644 --- a/src/tup/config.c +++ b/src/tup/config.c @@ -43,6 +43,9 @@ static int top_fd = -1; int find_tup_dir(void) { struct stat st; + static int found_tup_dir = 0; + if (found_tup_dir) + return 0; if(getcwd(tup_wd, sizeof(tup_wd)) == NULL) { perror("getcwd"); @@ -77,6 +80,7 @@ int find_tup_dir(void) return -1; } } + found_tup_dir = 1; return 0; } diff --git a/src/tup/hooks.c b/src/tup/hooks.c new file mode 100644 index 000000000..f3632434e --- /dev/null +++ b/src/tup/hooks.c @@ -0,0 +1,92 @@ +/* vim: set ts=8 sw=8 sts=8 noet tw=78: + * + * tup - A file-based build system + * + * Copyright (C) 2011-2012 Mike Shal + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include +#include +#include + +#include "hooks.h" +#include "config.h" +#include "option.h" + +int run_pre_init_hooks(void) +{ + return 0; +} + +/* "private" export from tup/main.c */ +int variant_command(int argc, char **argv); +static int post_init_variant_hook(void) +{ + char argv0[] = "post_init_variants"; + char *argv[3]; + const char* variants; + char *variants_copy; + char* v; + + argv[0] = argv0; + argv[2] = NULL; + + variants = tup_option_get_string("post_init.variants"); + if (variants == NULL) + return 0; + + variants_copy = strdup(variants); + if (variants_copy == NULL) { + fprintf(stderr, "tup error: Failed to allocate memory for variants string\n"); + return -1; + } + + /* The create_variant command expects find_tup_dir to have + run already, but as a post-init hook we're too early, run it now */ + if (find_tup_dir() != 0) { + fprintf(stderr, "tup internal error: Failed to find .tup dir in post-init hook\n"); + free(variants_copy); + return -1; + } + + v = strtok(variants_copy, " ,"); + while (v != NULL) { + argv[1] = v; + if (variant_command(2, argv) != 0) { + fprintf(stderr, "tup error: Post-init hook failed creating variant\n"); + fprintf(stderr, "tup error: Failed variant was: %s\n", v); + break; + } + v = strtok(NULL, " ,"); + } + + free(variants_copy); + + if (v == NULL) + return 0; + else + return -1; +} + +int run_post_init_hooks(void) +{ + printf("Running post-init hooks...\n"); + + if (post_init_variant_hook() != 0) + return -1; + + return 0; +} diff --git a/src/tup/hooks.h b/src/tup/hooks.h new file mode 100644 index 000000000..86d6ced3d --- /dev/null +++ b/src/tup/hooks.h @@ -0,0 +1,27 @@ +/* vim: set ts=8 sw=8 sts=8 noet tw=78: + * + * tup - A file-based build system + * + * Copyright (C) 2011-2012 Mike Shal + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef tup_hooks_h +#define tup_hooks_h + +int run_pre_init_hooks(void); +int run_post_init_hooks(void); + +#endif diff --git a/src/tup/init.c b/src/tup/init.c index ba7322729..830140659 100644 --- a/src/tup/init.c +++ b/src/tup/init.c @@ -21,6 +21,7 @@ #include "init.h" #include "config.h" #include "db.h" +#include "hooks.h" #include "lock.h" #include "entry.h" #include "server.h" @@ -40,14 +41,35 @@ #define mkdir(a,b) mkdir(a) #endif -int tup_init(void) +/* Returns < 0 on error, >= 0 on success. Returns 1 if init_command ran */ +int tup_early_init(const char* cmd, int argc, char **argv) { + int ini_ret; + + if (tup_option_init() != 0) { + return -1; + } + + tup_temporarily_drop_privs(); + ini_ret = tup_option_process_ini(cmd, argc, argv); + tup_restore_privs(); + + if(ini_ret < 0) { + return ini_ret; + } + if(find_tup_dir() != 0) { fprintf(stderr, "tup %s usage: tup [args]\n", tup_version()); fprintf(stderr, "For information on Tupfiles and other commands, see the tup(1) man page.\n"); fprintf(stderr, "No .tup directory found. Either create a Tupfile.ini file at the top of your project, or manually run 'tup init' there.\n"); return -1; } + + return ini_ret; +} + +int tup_init(void) +{ if(tup_entry_init() < 0) { return -1; } @@ -63,9 +85,6 @@ int tup_init(void) if(tup_lock_init() < 0) { goto out_err; } - if(tup_option_init() < 0) { - goto out_unlock; - } color_init(); if(tup_db_open() != 0) { goto out_unlock; @@ -82,7 +101,6 @@ int tup_init(void) int tup_cleanup(void) { tup_db_close(); - tup_option_exit(); tup_lock_exit(); if(close(tup_top_fd()) < 0) perror("close(tup_top_fd())"); @@ -161,7 +179,7 @@ static int mkdirtree(const char *dirname) return 0; } -int init_command(int argc, char **argv) +static int _init_command(int argc, char **argv) { int x; int db_sync = 1; @@ -252,3 +270,15 @@ int init_command(int argc, char **argv) } return 0; } + +int init_command(int argc, char **argv) +{ + if (run_pre_init_hooks() != 0) { + fprintf(stderr, "tup error: pre_init hooks failed\n"); + return -1; + } + if (_init_command(argc, argv) != 0) { + return -1; + } + return 0; +} diff --git a/src/tup/init.h b/src/tup/init.h index f7c0518b9..efbfb61af 100644 --- a/src/tup/init.h +++ b/src/tup/init.h @@ -18,6 +18,8 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +int tup_early_init(const char* cmd, int argc, char **argv); +void tup_early_cleanup(void); int tup_init(void); int tup_cleanup(void); void tup_valgrind_cleanup(void); diff --git a/src/tup/option.c b/src/tup/option.c index cb4ef706f..f68c12aef 100644 --- a/src/tup/option.c +++ b/src/tup/option.c @@ -97,6 +97,7 @@ static struct option { {"graph.ghosts", "0", NULL}, {"graph.environment", "0", NULL}, {"graph.combine", "0", NULL}, + {"post_init.variants", NULL, NULL}, }; #define NUM_OPTIONS (sizeof(options) / sizeof(options[0])) @@ -114,16 +115,41 @@ static struct sigaction sigact = { }; #endif -int tup_option_process_ini(void) { +/* atexit handler */ +static void tup_option_ini_exit(void) +{ + struct dynamic_config_file_list *c = dynamic_configs; + + while (c != NULL) { + struct dynamic_config_file_list *temp = c; + + free((char *) c->dynamic_config.file); + c = c->next; + free(temp); + } + dynamic_configs = NULL; +} + +/* Returns < 0 on error, >= 0 on success. Returns 1 if init_command ran */ +int tup_option_process_ini(const char* cmd, int real_argc, char **real_argv) +{ + int error = 0; int cur_dir; int best_root = -1; // file descriptor -> best root candidate int found_tup_dir = 0; + int command_was_init = (strcmp(cmd, "init") == 0); + int ran_init = 0; cur_dir = open(".", 0); if (cur_dir < 0) { perror("open(\".\", 0)"); fprintf(stderr, "tup error: Could not get reference to current directory?\n"); - exit(1); + return -1; + } + + if (atexit(tup_option_ini_exit) != 0) { + fprintf(stderr, "tup error: atexit registration failed?\n"); + return -1; } for(;;) { @@ -136,7 +162,8 @@ int tup_option_process_ini(void) { if (errno != ENOENT) { perror("fopen"); fprintf(stderr, "tup error: Unexpected error opening ini file\n"); - exit(1); + error = -1; + break; } } else { parse_ini_file(f); @@ -159,7 +186,8 @@ int tup_option_process_ini(void) { if (chdir("..")) { perror("chdir"); fprintf(stderr, "tup error: Unexpected error traversing directory tree\n"); - exit(1); + error = -1; + break; } if (NULL == getcwd(path_buf, sizeof(path_buf))) { @@ -175,41 +203,43 @@ int tup_option_process_ini(void) { } } + if (error != 0) + goto ini_cleanup; + if (best_root == -1) { goto ini_cleanup; } - if (!found_tup_dir) { - int rc; - int argc = 1; - char argv0[] = "init"; - char *argv[] = {argv0, NULL}; + if (!found_tup_dir && !command_was_init) { + int fake_argc = 1; + char fake_argv0[] = "init"; + char *fake_argv[] = {fake_argv0, NULL}; char root_path[PATH_MAX]; if (fchdir(best_root) < 0) { perror("fchdir(best_root)"); fprintf(stderr, "tup error: Could not chdir to root candidate?\n"); - exit(1); + error = -1; + goto best_root_cleanup; } if (NULL == getcwd(root_path, sizeof(root_path))) { if (errno != ERANGE) { perror("getcwd"); fprintf(stderr, "tup error: Unexpected error getting root path\n"); - exit(1); + error = -1; + goto best_root_cleanup; } printf("Initilizing .tup directory\n"); } else { printf("Initilizing .tup in %s\n", root_path); } - rc = init_command(argc, argv); - if (0 != rc) { - fprintf(stderr, "tup error: `tup init' failed unexpectedly\n"); - exit(rc); - } + error = init_command(fake_argc, fake_argv); + ran_init = 1; } +best_root_cleanup: if(close(best_root) < 0) { perror("close(best_root"); } @@ -225,7 +255,25 @@ int tup_option_process_ini(void) { fprintf(stderr, "tup error: Unexpected error closing current directory file descriptor\n"); exit(1); } - return 0; + + if ((error == 0) && command_was_init) { + error = init_command(real_argc, real_argv); + ran_init = 1; + } + + if (error != 0) + return error; + return ran_init; +} + +/* atexit handler */ +static void tup_option_exit(void) +{ + unsigned int x; + + for(x=0; xdynamic_config.file); - c = c->next; - free(temp); - } - dynamic_configs = NULL; -} - static int tup_boolify_flag(const char *value) { if(strcasecmp(value, "true") == 0) return 1; diff --git a/src/tup/option.h b/src/tup/option.h index 9498effe1..0d27d919b 100644 --- a/src/tup/option.h +++ b/src/tup/option.h @@ -23,9 +23,8 @@ #include "compat.h" -int tup_option_process_ini(void); +int tup_option_process_ini(const char* cmd, int real_argc, char **real_argv); int tup_option_init(void); -void tup_option_exit(void); int tup_option_get_int(const char *opt); int tup_option_get_flag(const char *opt); const char *tup_option_get_string(const char *opt); diff --git a/src/tup/tup/main.c b/src/tup/tup/main.c index 8e63d90ad..ba2f28d4f 100644 --- a/src/tup/tup/main.c +++ b/src/tup/tup/main.c @@ -42,6 +42,7 @@ #include "tup/option.h" #include "tup/privs.h" #include "tup/flist.h" +#include "tup/hooks.h" #ifdef _WIN32 #define mkdir(a,b) mkdir(a) @@ -51,7 +52,7 @@ static int graph_cb(void *arg, struct tup_entry *tent); static int graph(int argc, char **argv); /* Testing commands */ static int mlink(int argc, char **argv); -static int variant(int argc, char **argv); + int variant_command(int argc, char **argv); static int node_exists(int argc, char **argv); static int link_exists(int argc, char **argv); static int touch(int argc, char **argv); @@ -124,41 +125,28 @@ int main(int argc, char **argv) } /* Commands that should run before running an implicit `tup init' */ - if(strcmp(cmd, "init") == 0) { - if(tup_drop_privs() < 0) - return 1; - return init_command(argc, argv); - } else if(strcmp(cmd, "version") == 0) { + if(strcmp(cmd, "version") == 0) { if(tup_drop_privs() < 0) return 1; version(); return 0; } - /* Process all of the Tupfile.ini files. Runs `tup init' if necessary */ - tup_temporarily_drop_privs(); - if(tup_option_process_ini() != 0) - return 1; - tup_restore_privs(); + /* Tupfile.ini parsing, `tup init', and find_tup_dir */ + rc = tup_early_init(cmd, argc, argv); + if (rc < 0) + return rc; /* Commands that don't use a normal tup_init() */ if(strcmp(cmd, "stop") == 0) { if(tup_drop_privs() < 0) return 1; - if(find_tup_dir() < 0) { - fprintf(stderr, "No .tup directory found - unable to stop the file monitor.\n"); - return -1; - } if(open_tup_top() < 0) return -1; return stop_monitor(TUP_MONITOR_SHUTDOWN); } else if(strcmp(cmd, "waitmon") == 0) { if(tup_drop_privs() < 0) return 1; - if(find_tup_dir() < 0) { - fprintf(stderr, "No .tup directory found - unable to stop the file monitor.\n"); - return -1; - } if(open_tup_top() < 0) return -1; if(waitmon() < 0) @@ -169,6 +157,19 @@ int main(int argc, char **argv) if(tup_init() < 0) return 1; + if (rc > 0) { + if (run_post_init_hooks() != 0) { + fprintf(stderr, "tup error: post_init hooks failed\n"); + rc = -1; + goto main_cleanup; + } + + if(strcmp(cmd, "init") == 0) { + rc = 0; + goto main_cleanup; + } + } + if(strcmp(cmd, "monitor") == 0) { rc = monitor(argc, argv); } else if(strcmp(cmd, "entry") == 0) { @@ -210,7 +211,7 @@ int main(int argc, char **argv) } else if(strcmp(cmd, "todo") == 0) { rc = todo(argc, argv); } else if(strcmp(cmd, "variant") == 0) { - rc = variant(argc, argv); + rc = variant_command(argc, argv); } else if(strcmp(cmd, "node_exists") == 0) { rc = node_exists(argc, argv); } else if(strcmp(cmd, "normal_exists") == 0 || @@ -266,6 +267,7 @@ int main(int argc, char **argv) return -1; } +main_cleanup: if(tup_cleanup() < 0) rc = 1; tup_valgrind_cleanup(); @@ -568,7 +570,8 @@ static int create_variant(const char *config_path) return 0; } -static int variant(int argc, char **argv) +/* Symbol is exported so that post-init hook can call it to automatically */ +int variant_command(int argc, char **argv) { int x; diff --git a/tup.1 b/tup.1 index 5f8a52dae..eeb1f5347 100644 --- a/tup.1 +++ b/tup.1 @@ -303,6 +303,9 @@ Set to '1' to show the environment nodes (such as PATH) and their dependencies. .TP .B graph.combine (default '0') Set to '1' to try to combine similar nodes in the graph. For example, instead of showing 10 separate compilation commands that all have one .c file input and one .o file output, this will combine them into one command to more easily see the whole structure of the graph. By default all nodes are shown separately. +.TP +.B post_init.variants (default ) +A post-init hook that will automatically set up default variants after init is run. This hook is triggered whenever init runs sucessfully, whether implicitly or from an explicit call to \fBtup init\fR. Multiple variants may be listed as a space-separated list. The value of this entry is passed directly to the \fBtup variant\fR command as its arguments. .RE .SH "TUPFILES" You must create a file called "Tupfile" anywhere in the tup hierarchy that you want to create an output file based on the input files. The input files can be anywhere else in the tup hierarchy, but the output file(s) must be written in the same directory as the Tupfile.