#define _POSIX_C_SOURCE 200809L #include "minimake.h" #include #include #include #include #include #include #include #include #include "files/files.h" #include "hash_maps/hash_maps.h" #include "lines/lines.h" #include "lists/lists.h" // Static variables static char *program_name; static struct hash_map *variables = NULL; static struct hash_map *rules = NULL; // Keeps track of variables and rules order static struct list *variables_list = NULL; static struct list *rules_list = NULL; // ==== Misc ==== static void hashmap_deep_free(struct hash_map *hash_map) { if (hash_map == NULL) return; if (hash_map->data == NULL) return; for (size_t i = 0; i < hash_map->size; i++) { struct pair_list *entry = hash_map->data[i]; while (entry != NULL) { struct pair_list *next = entry->next; // Ok c moche mais eh, ça fonctionne if (hash_map == rules) { struct rule *r = entry->value; free(r->name); list_deep_destroy(r->dependencies); list_deep_destroy(r->recipe); free(r); } else if (hash_map == variables) { // printf("Att to free: %s\n", entry->key); free(entry->key); free(entry->value); } else { printf("DEBUG: attempting to free a generic hashmap\n"); free(entry->key); free(entry->value); } free(entry); entry = next; } } free(hash_map->data); free(hash_map); } static void free_all(void) { list_destroy(rules_list); list_destroy(variables_list); hashmap_deep_free(rules); hashmap_deep_free(variables); } // ==== Parsing ==== // Registers a new rule in the hashmap // WARNING Allocates memory on the heap, // the rules hashmap should be freed before exit static void register_rule(char *name, struct list *dependencies, struct list *recipe) { struct rule *rule = malloc(sizeof(struct rule)); rule->name = name; rule->dependencies = dependencies; rule->recipe = recipe; int err = hashmap_insert(rules, name, rule, NULL); if (!err) { drop(GENERIC_ERR, "Internal Error: Couln't add entry for '%s' in the hashmap", name); } rules_list = list_append(rules_list, name); } // Registers a new variable in the hashmap // WARNING Allocates memory on the heap, // the variables hashmap should be freed before exit static void register_variable(char *name, char *value) { int err = hashmap_insert(variables, name, value, NULL); if (!err) { drop(GENERIC_ERR, "Internal Error: Couln't add entry for '%s' in the hashmap", name); } variables_list = list_append(variables_list, name); } // Parse dependencies from buf and returns them inside a chained list static struct list *read_deps(struct line *l, size_t offset) { size_t i = offset; size_t buf_size = l->length; char *buf = l->buffer; struct list *res = NULL; while (i < buf_size) { i += skipblanks(buf + i, buf_size - i); // Read word char *dep_name; i += readword(buf + i, buf_size - i, &dep_name); if (dep_name != NULL) // Add to list res = list_append(res, dep_name); // Comments if (buf[i] == '#') return res; // Unknown chars if (!ischar(buf[i]) && !isblank(buf[i]) && buf[i] != '\0' && buf[i] != '\n') { drop(GENERIC_ERR, "Unexpected character '%c' after rule declaration at %lu:%lu", buf[i], l->number, i); } } return res; } // Searches the following lines for recipes and returns them in the form of a // list // WARNING begins to read the following line static struct list *read_recipe(struct line *l) { FILE *stream = l->file_stream; char *buf = l->buffer; size_t buf_size = BUFFER_SIZE; struct list *res = NULL; // Getline while ((l->length = getline(&buf, &buf_size, stream)) != -1) { l->number++; // Skip blank lines and comments if (isblankline(l)) continue; else if (buf[0] != '\t') // Not a recipe { l->buffer = buf; // Update buffer ! return res; } else // Add recipe to list { size_t offset = 1 + skipblanks(buf + 1, l->length - 1); char *command = strdup(buf + offset); if (command == NULL) { drop(GENERIC_ERR, "Internal error: couldn't duplicate string (%lu:1)", l->number); } command[l->length - offset - 1] = '\0'; res = list_append(res, command); } } l->buffer = buf; return res; } // === Variable Expansion === // Reads the value after a variable declaration static char *read_variable_value(char *buf, size_t buf_len) { size_t i = 0; size_t str_buf_size = STRING_BUFFER_SIZE; char *str_buf = malloc(sizeof(char) * str_buf_size); while (i < buf_len && buf[i] != '\n' && buf[i] != '#' && buf[i] != '\0') { // Reallocate more space if necessary if (i >= str_buf_size - 1) { str_buf_size += STRING_BUFFER_SIZE; str_buf = realloc(str_buf, str_buf_size); if (str_buf == NULL) drop(GENERIC_ERR, "Could not realloc"); } str_buf[i] = buf[i]; i++; } str_buf[i] = '\0'; return str_buf; } // Reads variable name from buf until ')' and stores it into *result // Returns its length on success and -1 on fail // WARNING allocates memory on the heap, free *result after use static size_t read_variable_name(struct line *line, size_t index, char **result) { size_t i = index; char *buf = line->buffer; // Alloc var_buf size_t var_buf_size = STRING_BUFFER_SIZE; char *var_buf = malloc(sizeof(char) * var_buf_size); if (var_buf == NULL) drop(GENERIC_ERR, "Could not allocate more memory"); size_t var_i = 0; while (buf[i] != '\n' && buf[i] != '\0' && buf[i] != '#' && buf[i] != ')') { // Reallocate more space if necessary var_buf_size = resize_buf(&var_buf, var_buf_size, i); // Copy var_buf[var_i] = buf[i]; i++; var_i++; } var_buf[var_i] = '\0'; // Mismatched parenthesis if (buf[i] != ')') { free(var_buf); *result = NULL; return 0; } i += 2; *result = var_buf; return i - index; } // Searches for variable in database and returns its value // if no value is found, it drops an error and exit the program // WARNING variable must be allocated on the heap as this function may attempt // to free it // NOTE Takes line_number to print an helpful error message static char *get_variable_value(char *variable_name, size_t line_number) { char *value = hashmap_get(variables, variable_name); if (value == NULL) { // Adds tmp_buf to the list of items to free variables_list = list_append(variables_list, variable_name); drop(GENERIC_ERR, "Could not find specified variable '%s' at line %lu", variable_name, line_number); } free(variable_name); return value; } // Gets the corresponding value of the variable and stores it in *value // Returns the number of skipped characters size_t expand_variable(struct line *line, size_t index, char **value) { size_t i = index; char *buf = line->buffer; // Name in parenthesis if (buf[i] == '(') { // Read variable char *var_name; i++; i += read_variable_name(line, i, &var_name); if (var_name == NULL) drop(GENERIC_ERR, "Mismatched parenthesis at %lu:%lu", i, line->number); // Get corresponding value *value = get_variable_value(var_name, line->number); // Return return i - index; } else // Single char variable { // '$ ' if (isblank(buf[i]) || isspace(buf[i])) drop(2, "Unauthorized character '%c' at %lu:%lu", buf[i], line->number, i); else { // Allocate temporary buffer to hold variable name char *var_name = malloc(2 * sizeof(char)); if (var_name == NULL) // Seriously, not even two bytes drop(GENERIC_ERR, "Could not allocate memory"); var_name[0] = buf[i]; var_name[1] = '\0'; // Escape '$$' if (buf[i] == '$') { *value = var_name; return 2; } // Get corresponding value *value = get_variable_value(var_name, line->number); free(var_name); } return 2; } } // ================= // Takes a buffer containing the line to parse and it length // As well as the line number in the file for error handling static void parse_line(struct line *current_line) { char *buf = current_line->buffer; size_t line_size = current_line->length; size_t i = 0; i += skipblanks(buf + i, line_size - i); // Read name char *name; // i += readword(buf + i, line_size - i, &name); i += read_declaration(current_line, i, &name); // if (name == NULL) // errx(1, "Il s'est passé quoi là ? \nUnexpected character at %lu:%lu", // current_line->number, i); i += skipblanks(buf + i, line_size - i); // Potential elements struct list *dependencies; struct list *recipe; char *value; // Definition type switch (buf[i]) { // Variable case '=': i += skipblanks(buf + i + 1, line_size - i) + 1; value = read_variable_value(buf + i, line_size - i); register_variable(name, value); break; // Rule case ':': dependencies = read_deps(current_line, i + 1); recipe = read_recipe(current_line); register_rule(name, dependencies, recipe); // Check for EOF if (current_line->length == -1) return; parse_line(current_line); // TODO: check for loooooops break; // Blank line case '\0': case '#': if (name != NULL) { drop(GENERIC_ERR, "Unexpected character '%c' after declaration '%s' at line %lu", buf[i], name, current_line->number); } break; default: drop(GENERIC_ERR, "Unexpected character '%c' after declaration '%s' at line %lu", buf[i], name, current_line->number); } // free(name); } void make_parse(char *path) { // Open file FILE *stream = fopen(path, "r"); if (stream == 0) errx(2, "Could not open file: %s", path); // Init hash maps variables = hashmap_init(HASHMAP_SIZE); rules = hashmap_init(HASHMAP_SIZE); if (variables == NULL || rules == NULL) errx(1, "Internal error: Failed to initiate hash maps"); // Allocate line buffer size_t buf_size = BUFFER_SIZE; char *buf = malloc(sizeof(char) * buf_size); if (buf == NULL) drop(GENERIC_ERR, "Could not allocate more memory"); // Parse line by line ssize_t nread; struct line current_line; current_line.number = 1; current_line.file_stream = stream; while ((nread = getline(&buf, &buf_size, stream)) != -1) { if (nread == -1) drop(GENERIC_ERR, "Could not get line %lu", current_line.number); current_line.buffer = buf; current_line.length = nread; parse_line(¤t_line); current_line.number += 1; } free(buf); fclose(stream); return; } // ==== Runtime ==== // Return 1 if rule is up to date, 0 otherwise static int uptodate(struct rule *rule) { struct stat path_stat; if (lstat(rule->name, &path_stat) != 0) // target doesn't exists return 0; struct list *dependencies = rule->dependencies; while (dependencies != NULL) { char *depname = dependencies->data; if (lstat(depname, &path_stat) != 0) // Dependecy doesn't exists return 0; } return 1; } // Expands and run a specified rule static int run_rule(struct rule *rule) { // Check if p to date if (uptodate(rule)) { printf("%s: '%s' is up to date.", program_name, rule->name); return 0; } struct list *commands = rule->recipe; struct list *dependencies = rule->dependencies; // Build dependencies while (dependencies != NULL) { // Expand variable // TODO // Check file existence if (!file_exists(dependencies->data)) { // Check rule existence instead struct rule *dep_rule = hashmap_get(rules, dependencies->data); if (dep_rule != NULL) { int res = run_rule(dep_rule); if (res != 0) // Exit on error return res; } else { drop(GENERIC_ERR, "No rule to make target '%s'", dependencies->data); } } dependencies = dependencies->next; } // Empty recipe if (commands == NULL) { printf("%s: Nothing to be done for '%s'.\n", program_name, rule->name); return 0; } while (commands != NULL) { // Expand command variables // TODO // Run int res = run_command(commands->data); if (res != 0) // Exit on error return res; commands = commands->next; } return 0; } // Run the given rules after parsing int make_run(struct list *given_rules) { if (given_rules == NULL) { // No rule specified => run first rule found in the Makefile if (rules_list == NULL) drop(GENERIC_ERR, "No targets"); struct rule *full_rule = hashmap_get(rules, rules_list->data); if (full_rule == NULL) drop(GENERIC_ERR, "Internal error: Could not retrieve target '%s' in database", rules_list->data); return run_rule(full_rule); } else { while (given_rules != NULL) { struct rule *full_rule = hashmap_get(rules, given_rules->data); if (full_rule == NULL) drop(GENERIC_ERR, "No rule to make target '%s'", given_rules->data); int res = run_rule(full_rule); if (res != 0) return res; given_rules = given_rules->next; } return 0; } } // ==== MAKE ==== int make(char *path, struct list *rules, char *exec_name, int flags) { program_name = exec_name; make_parse(path); // Print if (flags & FLAGS_PRINT) { dump_database(); free_all(); return 0; } // Run int status = make_run(rules); free_all(); return status; } // Prints an error message before exiting gracefully (by freeing all variables) void drop(int status, char *format, ...) { // Print va_list args; va_start(args, format); fprintf(stderr, "%s: *** ", program_name); vfprintf(stderr, format, args); fprintf(stderr, ". Stop.\n"); va_end(args); free_all(); exit(status); } void dump_database(void) { struct list *elt; // Dump variables elt = variables_list; puts("# variables"); while (elt != NULL) { // Get var char *val = hashmap_get(variables, elt->data); if (val == NULL) drop(GENERIC_ERR, "Could not get variable '%s' in database", elt->data); // Print char *key = elt->data; printf("'%s' = '%s'\n", key, val); elt = elt->next; } // Dump rules elt = rules_list; puts("# rules"); while (elt != NULL) { // Get rule struct rule *rule = hashmap_get(rules, elt->data); if (rule == NULL) drop(GENERIC_ERR, "Could not get rule '%s' in database", elt->data); // Print name printf("(%s):", rule->name); // Print dependencies struct list *dep = rule->dependencies; while (dep != NULL) { char *dep_str = dep->data; printf(" [%s]", dep_str); dep = dep->next; } putchar('\n'); // Print recipe struct list *rcp = rule->recipe; while (rcp != NULL) { char *rcp_str = rcp->data; printf("\t'%s'\n", rcp_str); rcp = rcp->next; } elt = elt->next; } }