diff --git a/.gitignore b/.gitignore index d7756c2..d535e3a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,10 @@ -a.out +*.out +*~ +*.swp *.o +*.a +*.so +*.class +*.log +*.core +minimake/minimake diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..d5e33a8 --- /dev/null +++ b/AUTHORS @@ -0,0 +1,6 @@ +guillem.george + +Special thanks to + +- Timothée Battefort for the parser +- Amadéo Heaulme for the error handling diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..db29234 --- /dev/null +++ b/Makefile @@ -0,0 +1,34 @@ +CC = gcc +CFLAGS = -std=c99 -pedantic -Werror -Wall -Wextra -Wvla +LDFLAGS= + +DBG_CFLAGS = -fsanitize=address -g +DBG_LDFLAGS= -fsanitize=address + + +SRC_DIR = src +LIB_SRCS = lines/lines.c hash_maps/hash_maps.c lists/lists.c files/files.c +MAIN_SRCS = main.c minimake.c + +# SRCS = $(patsubst %,$(SRC_DIR)/%, $(MAIN_SRCS)) +SRCS = $(MAIN_SRCS:%=$(SRC_DIR)/%) $(LIB_SRCS:%=$(SRC_DIR)/%) +OBJS = $(SRCS:.c=.o) + +TARGET= minimake +# DBG_TARGET = minimake-dbg + +$(TARGET): $(OBJS) + $(CC) -o $@ $^ $(LDFLAGS) $(LDLIBS) + @echo $(OBJS) + +debug: CFLAGS += $(DBG_CFLAGS) +debug: LDFLAGS += $(DBG_LDFLAGS) +debug: $(OBJS) + $(CC) -o $(TARGET) $^ $(LDFLAGS) $(LDLIBS) + +check: + dash ./tests/run.sh + +clean: + $(RM) $(TARGET) + $(RM) $(OBJS) diff --git a/README.md b/README.md new file mode 100644 index 0000000..7cc185b --- /dev/null +++ b/README.md @@ -0,0 +1,73 @@ +# Minimake + +Minimake is a small project written in about a week in C99. Its goal was to reproduce the main features of the well known GNU Make utility. + +> **Note** This is a school project, therefore it probably won't interest you if you are looking for something useful. + +## Build + +```sh +make +``` +or even better +```sh +minimake +``` + +## How it works + +If you're not familiar with Make, what it does is that it reads a file named +`Makefile` in the current directory which contains instructions to build a +project in the form of recipes. It includes support for variables, dependencies +, implicit rules and more. + +Then depending on the user input it automatically executes a recipe with its dependencies. + + +Here is what a basic one can look like +```make +BENCH_FLAGS = --all + +test_and_bench: + bash ./runtests.sh + bash ./runbenchs.sh $(BENCH_FLAGS) +``` + +But it will more realisticly look like that + +```make +CC = gcc +CFLAGS = -std=c99 -pedantic -Werror -Wall -Wextra -Wvla +LDFLAGS= + +DBG_CFLAGS = -fsanitize=address -g +DBG_LDFLAGS= -fsanitize=address + + +SRC_DIR = src +LIB_SRCS = lines/lines.c hash_maps/hash_maps.c lists/lists.c files/files.c +MAIN_SRCS = main.c minimake.c + +SRCS = $(MAIN_SRCS:%=$(SRC_DIR)/%) $(LIB_SRCS:%=$(SRC_DIR)/%) +OBJS = $(SRCS:.c=.o) + +TARGET= minimake +DBG_TARGET = minimake-dbg + +$(TARGET): $(OBJS) + $(CC) -o $@ $^ $(LDFLAGS) $(LDLIBS) + @echo $(OBJS) + +debug: CFLAGS += $(DBG_CFLAGS) +debug: LDFLAGS += $(DBG_LDFLAGS) +debug: $(OBJS) + $(CC) -o $(DBG_TARGET) $^ $(LDFLAGS) $(LDLIBS) + +check: + dash ./tests/run.sh + +clean: + $(RM) $(TARGET) + $(RM) $(OBJS) + +``` diff --git a/micromake/src/micromake.c b/micromake/src/micromake.c deleted file mode 100644 index fda3fe2..0000000 --- a/micromake/src/micromake.c +++ /dev/null @@ -1,119 +0,0 @@ -#define _POSIX_C_SOURCE 200809L -#define BUFFER_SIZE 1024 -#define STRING_BUFFER_SIZE 32 - -#include -#include -#include -#include - -// Helps to match a string (excludes blanks and special characters) -static int is_char(char c) -{ - return c != '\0' && !isblank(c) && c != ':' && c != '=' && c != '#'; -} - -// Returns the next word from buf until buf_len, -// and the numbers of read characters in *read_chars -// WARNING allocates the result on the heap -static char *readWord(char *buf, size_t buf_len, size_t *read_chars) -{ - 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 && is_char(buf[i])) - { - // 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) - errx(2, "Could not realloc"); - } - - str_buf[i] = buf[i]; - i++; - } - - str_buf[i] = '\0'; - *read_chars = i; - - return str_buf; -} - -int skip_blanks(char *buf, size_t buf_len) -{ - size_t i = 0; - while (i < buf_len && isblank(buf[i])) - i++; - return i; -} - -int main(int argc, char **argv) -{ - if (argc < 2) - errx(2, "Not enough arguments"); - if (argc > 2) - errx(2, "Not enough arguments"); - - // Open file - FILE *stream = fopen(argv[1], "r"); - if (stream == 0) - errx(2, "Could not open file"); - - // Allocate buffer - size_t buf_size = BUFFER_SIZE; - char *buf = malloc(sizeof(char) * buf_size); - if (buf == NULL) - errx(2, "Could not allocate more memory"); - - // Read - ssize_t nread; - while ((nread = getline(&buf, &buf_size, stream)) != -1) - { - size_t u_nread = nread; - size_t i = 0; - // Skip blanks - i += skip_blanks(buf + i, nread - i); - - // Read target name - size_t skipped_chars = 0; - char *rule_name = readWord(buf + i, nread - i, &skipped_chars); - if (skipped_chars != 0) - printf("%s:", rule_name); - i += skipped_chars; - - // Skip until ':' - i += skip_blanks(buf + i, nread - i); - if (buf[i] != ':') - errx(2, "Unexpected character '%c' after rule declaration '%s'", - buf[i], rule_name); - i++; - - // Read deps - while (i < u_nread) - { - // Skip blanks - i += skip_blanks(buf + i, nread - i); - - // Read word - size_t skipped_chars = 0; - char *dep_name = readWord(buf + i, nread - i, &skipped_chars); - if (skipped_chars != 0) - printf(" %s", dep_name); - free(dep_name); - i += skipped_chars; - - if (!is_char(buf[i]) && !isblank(buf[i]) && buf[i] != '\0') - errx(2, "Unexpected character '%c'", buf[i]); - } - - i++; - free(rule_name); - } - - free(buf); - fclose(stream); - return 0; -} diff --git a/microshell/microshell.c b/microshell/microshell.c deleted file mode 100644 index 4a7b661..0000000 --- a/microshell/microshell.c +++ /dev/null @@ -1,30 +0,0 @@ -#include -#include -#include -#include -#include - -int main(int argc, char **argv) -{ - if (argc < 2) - err(1, "Not enough args"); - - int id = fork(); - if (id < 0) - err(1, "Cannot fork"); - - if (id == 0) - { - int res = 0; - wait(&res); - // printf("%d\n", res); - return res; - } - else - { - int res = execl("/bin/sh", "supershell", "-c", argv[1], NULL); - printf("process exit status: %d\n", res); - // exit(res); - return res; - } -} diff --git a/src/files/files.c b/src/files/files.c new file mode 100644 index 0000000..b27627d --- /dev/null +++ b/src/files/files.c @@ -0,0 +1,94 @@ +#include "files.h" + +#include +#include +#include +#include +#include + +// #include "../lines/lines.h" + +char *get_makefile(struct list *files) +{ + struct list *file = files; + int found = 0; + + // Test file presence + while (file != NULL && !found) + { + FILE *test = fopen(file->data, "r"); + if (test != NULL) + { + fclose(test); + found = 1; + } + else + { + file = file->next; + } + } + + char *res = NULL; + if (found) + res = file->data; + + return res; +} + +ssize_t get_file_lastmodiftime(char *filename) +{ + struct stat stat; + if (lstat(filename, &stat) == -1) + return -1; + return stat.st_mtime; +} + +// Calls a shell that will run the given command +// It will first print the command to run unless the it starts with '@' +// Returns the return code o the command or -1 if a syscall fails +int run_command(char *command) +{ + if (command[0] == '@') + command++; + else + { + puts(command); + } + + // Just in case + fflush(stdout); + fflush(stderr); + + int id = fork(); + if (id < 0) + { + perror("fork"); + return -1; + } + + if (id == 0) + { + int res = execl("/bin/sh", "(make)", "-c", command, NULL); + exit(res); + } + else + { + int res = 0; + wait(&res); + return res; + } +} + +// Returns 1 if file exists (and is readable), 0 otherwise +int file_exists(char *filename) +{ + if (filename == NULL) + return 0; + + FILE *test = fopen(filename, "r"); + if (test == NULL) + return 0; + + fclose(test); + return 1; +} diff --git a/src/files/files.h b/src/files/files.h new file mode 100644 index 0000000..d4893c3 --- /dev/null +++ b/src/files/files.h @@ -0,0 +1,15 @@ +#ifndef FILES_H +#define FILES_H + +#define _XOPEN_SOURCE 500 + +#include + +#include "../lists/lists.h" + +char *get_makefile(struct list *files); +ssize_t get_file_lastmodiftime(char *filename); +int run_command(char *command); +int file_exists(char *filename); + +#endif // FILES_H diff --git a/src/hash_maps/hash_maps.c b/src/hash_maps/hash_maps.c new file mode 100644 index 0000000..405a0f9 --- /dev/null +++ b/src/hash_maps/hash_maps.c @@ -0,0 +1,177 @@ +#include "hash_maps.h" + +#include +#include +#include +#include +#include + +/* +** Hash the key using FNV-1a 32 bits hash algorithm. +*/ +size_t hash(const char *key) +{ + if (!key) + return 0; + + uint32_t hash = 2166136261; // FNV offset basis + uint32_t prime = 16777619; // FNV prime + + while (*key) + { + hash ^= *key; + hash *= prime; + key++; + } + + return hash; +} + +struct hash_map *hashmap_init(size_t size) +{ + struct pair_list **data = calloc(size, sizeof(struct pair_list)); + struct hash_map *map = malloc(sizeof(struct hash_map)); + map->data = data; + map->size = size; + return map; +} + +bool hashmap_insert(struct hash_map *hash_map, char *key, void *value, + bool *updated) +{ + if (hash_map == NULL || hash_map->data == NULL || hash_map->size == 0) + { + if (updated) + *updated = false; + return false; + } + + // Insert into hashmap (head) + + size_t entry_hash = hash(key) % hash_map->size; + // Search if key is already present + struct pair_list *elt = hash_map->data[entry_hash]; + while (elt != NULL) + { + if (strcmp(elt->key, key) == 0) + { + elt->value = value; + if (updated) + *updated = true; + return true; + } + elt = elt->next; + } + + // Create entry + struct pair_list *new_entry = malloc(sizeof(struct pair_list)); + if (new_entry == NULL) + return false; + new_entry->key = key; + new_entry->value = value; + + new_entry->next = hash_map->data[entry_hash]; + hash_map->data[entry_hash] = new_entry; + + if (updated) + *updated = false; + + return true; +} + +void hashmap_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; + free(entry); + entry = next; + } + } + free(hash_map->data); + free(hash_map); +} + +// WARNING +// Will only work with string values +// DO NOT attempt to call it with anything else +void hashmap_dump(struct hash_map *hash_map) +{ + for (size_t i = 0; i < hash_map->size; i++) + { + struct pair_list *entry = hash_map->data[i]; + bool is_null = true; + if (entry != NULL) + { + is_null = false; + char *val = entry->value; + printf("%s: %s", entry->key, val); + entry = entry->next; + } + while (entry != NULL) + { + char *val = entry->value; + printf(", %s: %s", entry->key, val); + entry = entry->next; + } + if (!is_null) + putchar('\n'); + } +} + +void *hashmap_get(const struct hash_map *hash_map, char *key) +{ + if (hash_map == NULL || hash_map->data == NULL || hash_map->size == 0) + return NULL; + + size_t entry_hash = hash(key) % hash_map->size; + struct pair_list *entry = hash_map->data[entry_hash]; + while (entry != NULL && strcmp(entry->key, key) != 0) + { + entry = entry->next; + } + if (entry == NULL) + return NULL; + + return entry->value; +} + +bool hashmap_remove(struct hash_map *hash_map, char *key) +{ + if (hash_map == NULL || hash_map->size == 0 || hash_map->data == NULL) + return false; + + size_t entry_hash = hash(key) % hash_map->size; + struct pair_list *entry = hash_map->data[entry_hash]; + + if (entry == NULL) + return false; + if (strcmp(entry->key, key) == 0) + { + hash_map->data[entry_hash] = entry->next; + free(entry); + return true; + } + + if (entry->next == NULL) + return false; + while (strcmp(entry->next->key, key) != 0) + { + entry = entry->next; + if (entry->next == NULL) + return false; + } + + struct pair_list *elt_to_free = entry->next; + entry->next = entry->next->next; + free(elt_to_free); + return true; +} diff --git a/src/hash_maps/hash_maps.h b/src/hash_maps/hash_maps.h new file mode 100644 index 0000000..634c993 --- /dev/null +++ b/src/hash_maps/hash_maps.h @@ -0,0 +1,33 @@ +#ifndef HASH_MAPS_H +#define HASH_MAPS_H + +#include +#include + +// WARNING I made the choice to discard the const qualifier of the key argument +// To add the possibility to free it. +// Note that key should *NEVER* be changed once it has been added to the hashmap + +struct pair_list +{ + char *key; + void *value; + struct pair_list *next; +}; + +struct hash_map +{ + struct pair_list **data; + size_t size; +}; + +size_t hash(const char *str); +struct hash_map *hashmap_init(size_t size); +bool hashmap_insert(struct hash_map *hash_map, char *key, void *value, + bool *updated); +void hashmap_free(struct hash_map *hash_map); +void hashmap_dump(struct hash_map *hash_map); +void *hashmap_get(const struct hash_map *hash_map, char *key); +bool hashmap_remove(struct hash_map *hash_map, char *key); + +#endif /* ! HASH_MAPS_H */ diff --git a/src/lines/lines.c b/src/lines/lines.c new file mode 100644 index 0000000..238b576 --- /dev/null +++ b/src/lines/lines.c @@ -0,0 +1,263 @@ +#include "lines.h" + +#include +#include +#include +#include +#include +#include + +#include "../minimake.h" + +// Helps to match a string (excludes blanks and special characters) +int ischar(char c) +{ + return c != '\0' && !isblank(c) && c != ':' && c != '=' && c != '#' + && c != '\n'; +} + +int skipblanks(char *buf, size_t buf_len) +{ + size_t i = 0; + while (i < buf_len && (isblank(buf[i]) || buf[i] == '\n')) + i++; + return i; +} + +// Returns 1 if line is blank +int isblankline(struct line *l) +{ + size_t line_size = l->length; + char *buf = l->buffer; + + size_t i = 0; + while (i < line_size) + { + if (!isblank(buf[i]) && buf[i] != '\n') + return 0; + if (buf[i] == '#') // Comments + return 1; + i++; + } + return 1; +} + +// Returns the next word from buf until buf_len, +// and the numbers of read characters in *read_chars +// WARNING allocates the result on the heap +size_t readword(char *buf, size_t buf_len, char **word) +{ + size_t i = 0; + size_t res_size = STRING_BUFFER_SIZE; + char *res = malloc(sizeof(char) * res_size); + while (i < buf_len && ischar(buf[i])) + { + // Reallocate more space if necessary + resize_buf(&res, res_size, i); + + res[i] = buf[i]; + i++; + } + res[i] = '\0'; + + if (i == 0) + { + free(res); + *word = NULL; + return i; + } + + *word = res; + return i; +} + +// Resizes buf if index is greater or equal than buf_size-1 +// Returns new buffer size +size_t resize_buf(char **buf, size_t buf_size, size_t index) +{ + if (index >= buf_size - 1) + { + buf_size += BUFFER_SIZE; + *buf = realloc(*buf, buf_size); + if (buf == NULL) + drop(2, "Could not realloc"); + } + return buf_size; +} + +// Inserts str into base at index and returns the resulting string +// WARNING allocates the result on the heap, do not forget to free it +char *insert_str(char *base, char *str, size_t index) +{ + size_t res_size = BUFFER_SIZE; + char *res = malloc(res_size * sizeof(char)); + if (res == NULL) + drop(GENERIC_ERR, "Could not allocate more memory"); + + // Copy until index + size_t i = 0; + while (i < index) + { + // Reallocate more space if necessary + res_size = resize_buf(&res, res_size, i); + + res[i] = base[i]; + i++; + } + + // Copy str + size_t str_i = 0; + while (str[str_i] != '\0') + { + // Reallocate more space if necessary + res_size = resize_buf(&res, res_size, i); + + res[i] = str[str_i]; + i++; + str_i++; + } + + // Copy rest + while (base[i] != '\0') + { + // Reallocate more space if necessary + res_size = resize_buf(&res, res_size, i); + + res[i] = base[i]; + i++; + } + + res[i] = '\0'; + return res; +} + +// Appends str into base at index and stores the resulting string in *result +// Returns the size of the resulting buffer +// WARNING allocates the result on the heap, do not forget to free it +size_t append_str(char *base, char *str, size_t index, char **result) +{ + size_t res_size = BUFFER_SIZE; + char *res = malloc(res_size * sizeof(char)); + if (res == NULL) + drop(GENERIC_ERR, "Could not allocate more memory"); + + // Copy until index + size_t i = 0; + while (i < index) + { + // Reallocate more space if necessary + res_size = resize_buf(&res, res_size, i); + + res[i] = base[i]; + i++; + } + + // Copy str + size_t str_i = 0; + while (str[str_i] != '\0') + { + // Reallocate more space if necessary + res_size = resize_buf(&res, res_size, i); + + res[i] = str[str_i]; + i++; + str_i++; + } + res[i] = '\0'; + + *result = res; + return res_size; +} + +// Read word until ':', '=' and blanks and expands variable if any is found +// Returns the number of skipped chars and stores the resulting string in word +size_t read_declaration(struct line *line, size_t index, char **word) +{ + size_t i = index; + size_t buf_len = line->length; + char *buf = line->buffer; + + size_t res_size = STRING_BUFFER_SIZE; + char *res = malloc(sizeof(char) * res_size); + if (res == NULL) + drop(GENERIC_ERR, "Could not allocate more memory"); + + size_t res_i = 0; + while (i < buf_len && ischar(buf[i])) + { + // Reallocate more space if necessary + res_size = resize_buf(&res, res_size, i); + + // Expand variable + if (buf[i] == '$') + { + char *expanded_var; + char *tmp_res_buf; + i += expand_variable(line, i + 1, &expanded_var); + res_size = append_str(res, expanded_var, i, &tmp_res_buf); + // Replace res with res + expanded_variable + res_i = strlen(tmp_res_buf); + free(res); + // free(expanded_var); + res = tmp_res_buf; + continue; + } + + res[res_i] = buf[i]; + i++; + res_i++; + } + res[res_i] = '\0'; + + if (i == index) + { + free(res); + *word = NULL; + return i - index; + } + + *word = res; + return i - index; +} + +// Takes a string, expands its variables and returns the resulting buffer +// WARNING allocates the result on the heap, free result after use +char *expand_str(char *str) +{ + size_t str_i = 0; + size_t buf_i = 0; + size_t buf_size = BUFFER_SIZE; + char *buf = malloc(BUFFER_SIZE * sizeof(char)); + while (str[str_i] != '\0') + { + // Reallocate more space if necessary + resize_buf(&buf, buf_size, buf_i); + + // Expand variable + if (buf[buf_i] == '$') + { + // Temporary variables + char *expanded_var; + char *tmp_res_buf; + struct line false_line = { + .buffer = buf, .file_stream = NULL, .length = 0, .number = 0 + }; + str_i += expand_variable(&false_line, str_i + 1, &expanded_var); + buf_size = append_str(buf, expanded_var, buf_i, &tmp_res_buf); + // Replace buf with buf + expanded_variable + buf_i = strlen(tmp_res_buf); + free(buf); + // free(expanded_var); + buf = tmp_res_buf; + continue; + } + + buf[buf_i] = str[str_i]; + buf_i++; + str_i++; + } + + buf[buf_i] = '\0'; + + return buf; +} diff --git a/src/lines/lines.h b/src/lines/lines.h new file mode 100644 index 0000000..5782327 --- /dev/null +++ b/src/lines/lines.h @@ -0,0 +1,30 @@ +#ifndef LINES_H +#define LINES_H + +#define STRING_BUFFER_SIZE 32 + +#include +#include +#include + +// Holds line information +// Exists only because of EPITA's annoying 4 parameters limit +// WARNING buffer must be freed after use +struct line +{ + char *buffer; // Line content + ssize_t length; // Line length + size_t number; // Line number in file + FILE *file_stream; // Full file stream +}; + +int ischar(char c); +int isblankline(struct line *l); +int skipblanks(char *buf, size_t buf_len); +size_t readword(char *buf, size_t buf_len, char **word); +size_t resize_buf(char **buf, size_t buf_size, size_t index); +char *insert_str(char *base, char *str, size_t index); +size_t read_declaration(struct line *line, size_t index, char **word); +size_t append_str(char *base, char *str, size_t index, char **res); + +#endif // LINES_H diff --git a/src/lists/lists.c b/src/lists/lists.c new file mode 100644 index 0000000..e8a681b --- /dev/null +++ b/src/lists/lists.c @@ -0,0 +1,346 @@ +#include "lists.h" + +#include +#include +#include + +struct list *list_prepend(struct list *list, void *value) +{ + struct list *new_elt = malloc(sizeof(struct list)); + if (new_elt == NULL) + { + return NULL; + } + new_elt->next = list; + new_elt->data = value; + + return new_elt; +} + +size_t list_length(struct list *list) +{ + size_t len = 0; + while (list != NULL) + { + len++; + list = list->next; + } + return len; +} + +// void list_print(struct list *list) +// { +// if (list == NULL) +// { +// return; +// } + +// while (list != NULL) +// { +// if (list->next != NULL) +// { +// printf("%p ", list->data); +// } +// else +// { +// printf("%p\n", list->data); +// } +// list = list->next; +// } +// } + +void list_destroy(struct list *list) +{ + struct list *elt = list; + struct list *next_elt; + while (elt != NULL) + { + next_elt = elt->next; + free(elt); + elt = next_elt; + } +} + +struct list *list_append(struct list *list, void *value) +{ + if (list == NULL) + { + struct list *new_elt = malloc(sizeof(struct list)); + new_elt->data = value; + new_elt->next = NULL; + return new_elt; + } + + struct list *elt = list; + + while (elt->next != NULL) + { + elt = elt->next; + } + + struct list *new_elt = malloc(sizeof(struct list)); + new_elt->data = value; + new_elt->next = NULL; + elt->next = new_elt; + + return list; +} + +/* + * + ******************* + * Advanced * + ******************* + * + */ + +struct list *list_insert(struct list *list, void *value, size_t index) +{ + if (list == NULL || index == 0) + { + struct list *new_elt = malloc(sizeof(struct list)); + new_elt->data = value; + new_elt->next = list; + return new_elt; + } + + struct list *elt = list; + + for (size_t i = 0; i < index - 1; i++) + { + if (elt->next == NULL) + { + break; + } + elt = elt->next; + } + + struct list *new_elt = malloc(sizeof(struct list)); + new_elt->data = value; + new_elt->next = elt->next; + elt->next = new_elt; + + return list; +} + +struct list *list_remove(struct list *list, size_t index) +{ + struct list *elt = list; + struct list *prev_elt; + + if (index == 0) + { + struct list *res = elt->next; + free(elt); + return res; + } + + for (size_t i = 0; i < index; i++) + { + if (elt == NULL) + { + return list; + } + prev_elt = elt; + elt = elt->next; + } + if (elt == NULL) + { + return list; + } + + prev_elt->next = elt->next; + free(elt); + + return list; +} + +int list_find(struct list *list, void *value) +{ + if (list == NULL) + { + return -1; + } + + int res = 0; + while (list->data != value) + { + list = list->next; + res++; + if (list == NULL) + { + return -1; + } + } + return res; +} + +struct list *list_concat(struct list *list, struct list *list2) +{ + if (list == NULL) + { + return list2; + } + struct list *elt = list; + while (elt->next != NULL) + { + elt = elt->next; + } + elt->next = list2; + return list; +} + +/* + * + ****************** + * Ultimate * + ****************** + * + */ + +static void swap_next(struct list *elt) +{ + void *c = elt->next->data; + elt->next->data = elt->data; + elt->data = c; +} +struct list *list_sort(struct list *list) +{ + // Bubble sort go ! + if (list == NULL) + { + return list; + } + struct list *elt = list; + int len = 0; + while (elt->next != NULL) + { + if (elt->data > elt->next->data) + { + swap_next(elt); + } + elt = elt->next; + len++; + } + + for (int i = 1; i < len; i++) + { + elt = list; + while (elt->next != NULL) + { + if (elt->data > elt->next->data) + { + swap_next(elt); + } + elt = elt->next; + } + } + + return list; +} + +// Old proto +// WARNING no malloc/free allowed (moulinette issue) +struct list *list_reverse(struct list *list) +{ + if (list == NULL) + { + return list; + } + + // Get len + struct list *elt = list; + int len = 0; + while (elt->next != NULL) + { + len++; + elt = elt->next; + } + elt = list; + + // Bring each elt to end + for (int i = 0; i < len; i++) + { + elt = list; + for (int j = 0; j < len - i; j++) + { + swap_next(elt); + elt = elt->next; + } + } + + return list; +} + +// Doesn't work neither +// struct list *list_reverse(struct list *list) +// { +// struct list *new_list = NULL; +// struct list *elt = list; +// while (elt != NULL) +// { +// struct list *new_elt = malloc(sizeof(struct list)); +// if (new_elt == NULL) +// { +// return NULL; +// } +// new_elt->data = elt->data; +// new_elt->next = new_list; + +// struct list *next_elt = elt->next; +// free(elt); +// elt = next_elt; +// } +// return new_list; +// } + +// Moulinette issue +// struct list *list_reverse(struct list *list) +// { +// if (list == NULL) +// { +// return list; +// } + +// struct list *new_list = NULL; +// while (list != NULL) +// { +// new_list = list_prepend(new_list, list->data); +// list = list_remove(list, 0); +// } + +// return new_list; +// } + +struct list *list_split(struct list *list, size_t index) +{ + struct list *elt = list; + for (size_t i = 0; i < index; i++) + { + if (elt == NULL) + { + return NULL; + } + elt = elt->next; + } + if (elt == NULL) + { + return NULL; + } + struct list *res = elt->next; + elt->next = NULL; + return res; +} + +void list_deep_destroy(struct list *l) +{ + struct list *elt = l; + struct list *next_elt; + while (elt != NULL) + { + next_elt = elt->next; + free(elt->data); + free(elt); + elt = next_elt; + } +} diff --git a/src/lists/lists.h b/src/lists/lists.h new file mode 100644 index 0000000..116bd1f --- /dev/null +++ b/src/lists/lists.h @@ -0,0 +1,110 @@ +#ifndef LISTS_H +#define LISTS_H + +#include + +struct list +{ + void *data; + struct list *next; +}; + +/* +** Insert a node containing `value` at the beginning of the list. +** Return `NULL` if an error occured. +*/ +struct list *list_prepend(struct list *list, void *value); + +/* +** Return the lenght of the list. +** Return `0` if the list is empty. +*/ +size_t list_length(struct list *list); + +/* +** Display the list contents on `stdout`. +** Nothing is displayed if the list is empty. +*/ +void list_print(struct list *list); + +/* +** Release the memory used by the list. +** Does nothing if `list` is `NULL`. +*/ +void list_destroy(struct list *list); + +/* +** Release the memory used by the list and its content +** Does nothing if `list` is `NULL`. +*/ +void list_deep_destroy(struct list *l); + +/* +** Append a node containing `value` at the end of the list. +** Return `NULL` if an error occured. +*/ +// START PROTO list_append +struct list *list_append(struct list *list, void *value); +// END PROTO list_append + +/* +** Insert a node containing `value` at the index `index` in the list. +** If the index is greater than the length of the list, the behaviour is the +** same as `list_append`. +** Return `NULL` if an error occured. +*/ +// START PROTO list_insert +struct list *list_insert(struct list *list, void *value, size_t index); +// END PROTO list_insert + +/* +** Remove the element at the index `index`. +** Return `NULL` if an error occured. +*/ +// START PROTO list_remove +struct list *list_remove(struct list *list, size_t index); +// END PROTO list_remove + +/* +** Return the position of the first node containing `value`. +** Return `-1` if nothing is found. +*/ +// START PROTO list_find +int list_find(struct list *list, void *value); +// END PROTO list_find + +/* +** Concatenate the list `list2` at the end of the list `list`. +** Return `list2` if `list` is `NULL`. +*/ +// START PROTO list_concat +// struct list *list_concat(struct list *list, struct list *list2); +// END PROTO list_concat + +/* +** Sort the elements of the list in ascending order. +** Return the new list. +*/ +// START PROTO list_sort +// struct list *list_sort(struct list *list); +// END PROTO list_sort + +/* +** Invert the order of the elements of the list. +** Return the new list. +*/ +// START PROTO list_reverse +// struct list *list_reverse(struct list *list); +// END PROTO list_reverse + +/* +** Split the list at index `index`. +** First part goes in `list` and contains the element at `index`. +** Second part is returned. +** Return `NULL` if `list` is `NULL` or `index` is invalid. +*/ +// START PROTO list_split +// struct list *list_split(struct list *list, size_t index); +// END PROTO list_split + +#endif /* ! LISTS_H */ diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..ec0473f --- /dev/null +++ b/src/main.c @@ -0,0 +1,112 @@ +#include +#include +#include +// #include +#include +#include + +#include "files/files.h" +#include "lists/lists.h" +#include "minimake.h" + +// TODO: +// Look at perror for stdlib functions +// Create an enum for error handling + +static void print_help(char *program_name) +{ + printf("Usage: %s [options] [target]... ", program_name); + puts("Options:"); + puts(" -f FILE Reads FILE as a makefile"); + puts(" -h Prints this message and exit"); + puts(" -p Prints minimake's internal database"); + puts(""); + puts("Pas mal non ? C'est français"); +} + +static int handle_args(int argc, char **argv, struct list **minimake_files, + struct list **minimake_rules) +{ + int flags = 0; + struct list *files = NULL; + struct list *rules = NULL; + for (int i = 1; i < argc; i++) + { + // Options + if (argv[i][0] == '-') + { + // No opt + if (strcmp(argv[i], "--") == 0) + { + // Treat as rules + for (int j = i; j < argc; j++) + { + rules = list_append(rules, argv[i]); + } + *minimake_files = files; + *minimake_rules = rules; + return flags; + } + // Help + else if (strcmp(argv[i], "-h") == 0) + { + print_help(argv[0]); + list_destroy(files); + list_destroy(rules); + exit(0); + } + // Custom file + else if (strcmp(argv[i], "-f") == 0) + { + flags |= FLAGS_CUSTOM_FILE; + i++; + + if (i == argc || argv[i][0] == '-') + errx(INVALID_ARG, "No file specified after '-f'"); + + files = list_prepend(files, argv[i]); + } + // Print + else if (strcmp(argv[i], "-p") == 0) + { + flags |= FLAGS_PRINT; + } + // Unknown option + else + { + printf("Unknown option '%s'", argv[i]); + print_help(argv[0]); + list_destroy(files); + list_destroy(rules); + exit(INVALID_ARG); + } + } + else // Rules + { + rules = list_append(rules, argv[i]); + } + } + + files = list_append(files, DEFAULT_MAKEFILE); + files = list_append(files, DEFAULT_MAKEFILE_2); + + *minimake_files = files; + *minimake_rules = rules; + return flags; +} + +int main(int argc, char **argv) +{ + struct list *files; + struct list *rules; + int flags = handle_args(argc, argv, &files, &rules); + + char *filename = get_makefile(files); + if (filename == NULL) + errx(GENERIC_ERR, "No Makefile found"); + + int res = make(filename, rules, argv[0], flags); + list_destroy(files); + list_destroy(rules); + return res; +} diff --git a/src/minimake.c b/src/minimake.c new file mode 100644 index 0000000..4368296 --- /dev/null +++ b/src/minimake.c @@ -0,0 +1,674 @@ +#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; + } +} diff --git a/src/minimake.h b/src/minimake.h new file mode 100644 index 0000000..7bb5f39 --- /dev/null +++ b/src/minimake.h @@ -0,0 +1,48 @@ +#ifndef MINIMAKE_H +#define MINIMAKE_H + +// Error Codes +#define INVALID_ARG 2 +#define GENERIC_ERR 2 + +// Flags +#define FLAGS_CUSTOM_FILE 1 +#define FLAGS_PRINT 2 + +// Default values +#define DEFAULT_MAKEFILE "makefile" +#define DEFAULT_MAKEFILE_2 "Makefile" +#define BUFFER_SIZE 1024 +#define HASHMAP_SIZE 32 + +#include +#include + +#include "lines/lines.h" +#include "lists/lists.h" + +// Holds variable information +// WARNING its values must be freed after use +struct variable +{ + char *name; + char *value; +}; + +// Holds rule information +// WARNING its values must be freed after use +struct rule +{ + char *name; + struct list *dependencies; + struct list *recipe; +}; + +int make(char *path, struct list *rules, char *argv0, int flags); +void make_parse(char *path); +int make_run(struct list *rules); +void drop(int status, char *format, ...); +void dump_database(void); +size_t expand_variable(struct line *line, size_t index, char **value); + +#endif // ! MINIMAKE_H diff --git a/tests/Makefile.empty b/tests/Makefile.empty new file mode 100644 index 0000000..e9228f2 --- /dev/null +++ b/tests/Makefile.empty @@ -0,0 +1 @@ +empty_target: diff --git a/tests/Makefile.syntax-test b/tests/Makefile.syntax-test new file mode 100644 index 0000000..fff0427 --- /dev/null +++ b/tests/Makefile.syntax-test @@ -0,0 +1,35 @@ +SIMPLE_VAR = coucou +SIMPLE_VAR_COMMENT = the comment is gone # comment + +# the following line starts with a space then a tab + SPACES_BEFORE_TAB = var_beginning var_end + +sparse_rule: depa depb + + command 1 + + command 2 + + B = B_var_beginning B_var_end + +packed_rule: depa depb + command 1 + command 2 + +silent_rule: depa depb + @ command 1 + @command 2 + +rule_comment: depa depb # comment + +command_space_rule: depa depb + echo spaces before + echo spaces after + echo this is a # comment + +simple_rule: simple_dep + +no_dep_rule: + +variable_rule: beginning $(SIMPLE_VAR) end + echo "shouldn't be expanded: $(SIMPLE_VAR)" diff --git a/tests/Makefile.test3 b/tests/Makefile.test3 new file mode 100644 index 0000000..7a42836 --- /dev/null +++ b/tests/Makefile.test3 @@ -0,0 +1 @@ +VAR = 1 diff --git a/tests/Makefile.test4 b/tests/Makefile.test4 new file mode 100644 index 0000000..4da97dd --- /dev/null +++ b/tests/Makefile.test4 @@ -0,0 +1,7 @@ +rule1: + echo Toto + echo Tata + echo Tutu + +rule2: + @echo Toto encore diff --git a/tests/Makefile.vars b/tests/Makefile.vars new file mode 100644 index 0000000..b5ce417 --- /dev/null +++ b/tests/Makefile.vars @@ -0,0 +1,4 @@ +V=2 + +main: + echo $V diff --git a/tests/Makefile2.syntax-test b/tests/Makefile2.syntax-test new file mode 100644 index 0000000..ac3c606 --- /dev/null +++ b/tests/Makefile2.syntax-test @@ -0,0 +1,36 @@ +SIMPLE_VAR = coucou +SIMPLE_VAR_COMMENT = the comment is gone # comment +$(SIMPLE_VAR) = 1 + +# the following line starts with a space then a tab + SPACES_BEFORE_TAB = var_beginning var_end + +sparse_rule: depa depb + + command 1 + + command 2 + + B = B_var_beginning B_var_end + +packed_rule: depa depb + command 1 + command 2 + +silent_rule: depa depb + @ command 1 + @command 2 + +rule_comment: depa depb # comment + +command_space_rule: depa depb + echo spaces before + echo spaces after + echo this is a # comment + +simple_rule: simple_dep + +no_dep_rule: + +variable_rule: beginning $(SIMPLE_VAR) end + echo "shouldn't be expanded: $(SIMPLE_VAR)" diff --git a/tests/run.sh b/tests/run.sh new file mode 100644 index 0000000..0fe1e07 --- /dev/null +++ b/tests/run.sh @@ -0,0 +1,77 @@ +#!/bin/sh + +########################################### +# PIERRE PAGNOUX EXPERIENCE TESTSUITE # +########################################### + +#----------------- COLOR -----------------# +# 0 - No style | 1 - Bold +RED="\e[0;31m" +BRED="\e[1;31m" +GRN="\e[0;32m" +BGRN="\e[1;32m" +YEL="\e[0;33m" +BYEL="\e[1;33m" +BLU="\e[0;34m" +BBLU="\e[1;34m" +PUR="\e[0;35m" +BPUR="\e[1;35m" +CYA="\e[0;36m" +BCYA="\e[1;36m" +WHI="\e[0;37m" +BWHI="\e[1;37m" +GRE="\e[2;37m" + +#----------------- GLOBV -----------------# +ref_out=/tmp/ref_out.out +my_out=/tmp/my_out.out + +#----------------- TESTS -----------------# + +tit_wrap() +{ + echo -e $@$WHI +} + +func_test() +{ + tit_wrap - $PUR $1 + shift + printf $@ > $ref_out + ../../tinyprintf $@ > $my_out + + diff $ref_out $my_out > /tmp/null + + if [ $? -eq 0 ]; then + tit_wrap $GRN GOOD + else + tit_wrap $RED EXPECTED: $BRED$(cat $ref_out) \|$RED GOT: $BRED$(cat $my_out) + fi +} + +func_file() +{ + for file in *; do + if [ -f $file ]; then + func_test $file $(cat $file) + fi + done +} + +func_dir() +{ + cd tests + for dir in *; do + if [ -d $dir ]; then + tit_wrap == $dir == + cd $dir + func_file + cd .. + fi + done + cd .. +} + +# func_dir + +echo "Tests should be here"