Compare commits
No commits in common. "master" and "minimake-dementor2-1" have entirely different histories.
master
...
minimake-d
27 changed files with 871 additions and 1233 deletions
73
README.md
73
README.md
|
|
@ -1,73 +0,0 @@
|
||||||
# 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)
|
|
||||||
|
|
||||||
```
|
|
||||||
3
micromake/src/Microfile
Normal file
3
micromake/src/Microfile
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
qwertyu:
|
||||||
|
cbejw: a bc d
|
||||||
|
dmwq :d wdwd
|
||||||
119
micromake/src/micromake.c
Normal file
119
micromake/src/micromake.c
Normal file
|
|
@ -0,0 +1,119 @@
|
||||||
|
#define _POSIX_C_SOURCE 200809L
|
||||||
|
#define BUFFER_SIZE 1024
|
||||||
|
#define STRING_BUFFER_SIZE 32
|
||||||
|
|
||||||
|
#include <ctype.h>
|
||||||
|
#include <err.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
30
microshell/microshell.c
Normal file
30
microshell/microshell.c
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
#include <err.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <sys/wait.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -7,7 +7,7 @@ DBG_LDFLAGS= -fsanitize=address
|
||||||
|
|
||||||
|
|
||||||
SRC_DIR = src
|
SRC_DIR = src
|
||||||
LIB_SRCS = lines/lines.c hash_maps/hash_maps.c lists/lists.c files/files.c
|
LIB_SRCS = lines/lines.c hash_maps/hash_maps.c lists/lists.c
|
||||||
MAIN_SRCS = main.c minimake.c
|
MAIN_SRCS = main.c minimake.c
|
||||||
|
|
||||||
# SRCS = $(patsubst %,$(SRC_DIR)/%, $(MAIN_SRCS))
|
# SRCS = $(patsubst %,$(SRC_DIR)/%, $(MAIN_SRCS))
|
||||||
75
minimake/src/lines/lines.c
Normal file
75
minimake/src/lines/lines.c
Normal file
|
|
@ -0,0 +1,75 @@
|
||||||
|
#include "lines.h"
|
||||||
|
|
||||||
|
#include <ctype.h>
|
||||||
|
#include <err.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <unistd.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
|
||||||
|
if (i >= res_size - 1)
|
||||||
|
{
|
||||||
|
res_size += STRING_BUFFER_SIZE;
|
||||||
|
res = realloc(res, res_size);
|
||||||
|
if (res == NULL)
|
||||||
|
errx(2, "Could not realloc");
|
||||||
|
}
|
||||||
|
|
||||||
|
res[i] = buf[i];
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
res[i] = '\0';
|
||||||
|
|
||||||
|
if (i == 0)
|
||||||
|
{
|
||||||
|
free(res);
|
||||||
|
*word = NULL;
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
|
||||||
|
*word = res;
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
|
@ -4,8 +4,8 @@
|
||||||
#define STRING_BUFFER_SIZE 32
|
#define STRING_BUFFER_SIZE 32
|
||||||
|
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
#include <stdio.h>
|
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
// Holds line information
|
// Holds line information
|
||||||
// Exists only because of EPITA's annoying 4 parameters limit
|
// Exists only because of EPITA's annoying 4 parameters limit
|
||||||
|
|
@ -22,9 +22,5 @@ int ischar(char c);
|
||||||
int isblankline(struct line *l);
|
int isblankline(struct line *l);
|
||||||
int skipblanks(char *buf, size_t buf_len);
|
int skipblanks(char *buf, size_t buf_len);
|
||||||
size_t readword(char *buf, size_t buf_len, char **word);
|
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
|
#endif // LINES_H
|
||||||
1
minimake/src/lists/.gitignore
vendored
Normal file
1
minimake/src/lists/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
list.c
|
||||||
|
|
@ -331,16 +331,3 @@ struct list *list_split(struct list *list, size_t index)
|
||||||
elt->next = NULL;
|
elt->next = NULL;
|
||||||
return res;
|
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
#ifndef LISTS_H
|
#ifndef LIST_H
|
||||||
#define LISTS_H
|
#define LIST_H
|
||||||
|
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
|
|
||||||
|
|
@ -33,12 +33,6 @@ void list_print(struct list *list);
|
||||||
*/
|
*/
|
||||||
void list_destroy(struct list *list);
|
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.
|
** Append a node containing `value` at the end of the list.
|
||||||
** Return `NULL` if an error occured.
|
** Return `NULL` if an error occured.
|
||||||
|
|
@ -78,7 +72,7 @@ int list_find(struct list *list, void *value);
|
||||||
** Return `list2` if `list` is `NULL`.
|
** Return `list2` if `list` is `NULL`.
|
||||||
*/
|
*/
|
||||||
// START PROTO list_concat
|
// START PROTO list_concat
|
||||||
// struct list *list_concat(struct list *list, struct list *list2);
|
struct list *list_concat(struct list *list, struct list *list2);
|
||||||
// END PROTO list_concat
|
// END PROTO list_concat
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
@ -86,7 +80,7 @@ int list_find(struct list *list, void *value);
|
||||||
** Return the new list.
|
** Return the new list.
|
||||||
*/
|
*/
|
||||||
// START PROTO list_sort
|
// START PROTO list_sort
|
||||||
// struct list *list_sort(struct list *list);
|
struct list *list_sort(struct list *list);
|
||||||
// END PROTO list_sort
|
// END PROTO list_sort
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
@ -94,7 +88,7 @@ int list_find(struct list *list, void *value);
|
||||||
** Return the new list.
|
** Return the new list.
|
||||||
*/
|
*/
|
||||||
// START PROTO list_reverse
|
// START PROTO list_reverse
|
||||||
// struct list *list_reverse(struct list *list);
|
struct list *list_reverse(struct list *list);
|
||||||
// END PROTO list_reverse
|
// END PROTO list_reverse
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
@ -104,7 +98,7 @@ int list_find(struct list *list, void *value);
|
||||||
** Return `NULL` if `list` is `NULL` or `index` is invalid.
|
** Return `NULL` if `list` is `NULL` or `index` is invalid.
|
||||||
*/
|
*/
|
||||||
// START PROTO list_split
|
// START PROTO list_split
|
||||||
// struct list *list_split(struct list *list, size_t index);
|
struct list *list_split(struct list *list, size_t index);
|
||||||
// END PROTO list_split
|
// END PROTO list_split
|
||||||
|
|
||||||
#endif /* ! LISTS_H */
|
#endif /* ! LIST_H */
|
||||||
|
|
@ -5,7 +5,6 @@
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#include "files/files.h"
|
|
||||||
#include "lists/lists.h"
|
#include "lists/lists.h"
|
||||||
#include "minimake.h"
|
#include "minimake.h"
|
||||||
|
|
||||||
|
|
@ -41,7 +40,7 @@ static int handle_args(int argc, char **argv, struct list **minimake_files,
|
||||||
// Treat as rules
|
// Treat as rules
|
||||||
for (int j = i; j < argc; j++)
|
for (int j = i; j < argc; j++)
|
||||||
{
|
{
|
||||||
rules = list_append(rules, argv[i]);
|
files = list_append(rules, argv[i]);
|
||||||
}
|
}
|
||||||
*minimake_files = files;
|
*minimake_files = files;
|
||||||
*minimake_rules = rules;
|
*minimake_rules = rules;
|
||||||
|
|
@ -51,17 +50,13 @@ static int handle_args(int argc, char **argv, struct list **minimake_files,
|
||||||
else if (strcmp(argv[i], "-h") == 0)
|
else if (strcmp(argv[i], "-h") == 0)
|
||||||
{
|
{
|
||||||
print_help(argv[0]);
|
print_help(argv[0]);
|
||||||
list_destroy(files);
|
|
||||||
list_destroy(rules);
|
|
||||||
exit(0);
|
exit(0);
|
||||||
}
|
}
|
||||||
// Custom file
|
// Custom file
|
||||||
else if (strcmp(argv[i], "-f") == 0)
|
else if (strcmp(argv[i], "-f") == 0)
|
||||||
{
|
{
|
||||||
flags |= FLAGS_CUSTOM_FILE;
|
flags |= FLAGS_CUSTOM_FILE;
|
||||||
i++;
|
if (i + 1 == argc || argv[i + 1][0] == '-')
|
||||||
|
|
||||||
if (i == argc || argv[i][0] == '-')
|
|
||||||
errx(INVALID_ARG, "No file specified after '-f'");
|
errx(INVALID_ARG, "No file specified after '-f'");
|
||||||
|
|
||||||
files = list_prepend(files, argv[i]);
|
files = list_prepend(files, argv[i]);
|
||||||
|
|
@ -76,14 +71,12 @@ static int handle_args(int argc, char **argv, struct list **minimake_files,
|
||||||
{
|
{
|
||||||
printf("Unknown option '%s'", argv[i]);
|
printf("Unknown option '%s'", argv[i]);
|
||||||
print_help(argv[0]);
|
print_help(argv[0]);
|
||||||
list_destroy(files);
|
|
||||||
list_destroy(rules);
|
|
||||||
exit(INVALID_ARG);
|
exit(INVALID_ARG);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else // Rules
|
else // Rules
|
||||||
{
|
{
|
||||||
rules = list_append(rules, argv[i]);
|
files = list_append(rules, argv[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -95,6 +88,34 @@ static int handle_args(int argc, char **argv, struct list **minimake_files,
|
||||||
return flags;
|
return flags;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
list_destroy(files);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
int main(int argc, char **argv)
|
int main(int argc, char **argv)
|
||||||
{
|
{
|
||||||
struct list *files;
|
struct list *files;
|
||||||
|
|
@ -105,8 +126,5 @@ int main(int argc, char **argv)
|
||||||
if (filename == NULL)
|
if (filename == NULL)
|
||||||
errx(GENERIC_ERR, "No Makefile found");
|
errx(GENERIC_ERR, "No Makefile found");
|
||||||
|
|
||||||
int res = make(filename, rules, argv[0], flags);
|
make(filename, flags, argv[0]);
|
||||||
list_destroy(files);
|
|
||||||
list_destroy(rules);
|
|
||||||
return res;
|
|
||||||
}
|
}
|
||||||
551
minimake/src/minimake.c
Normal file
551
minimake/src/minimake.c
Normal file
|
|
@ -0,0 +1,551 @@
|
||||||
|
#define _POSIX_C_SOURCE 200809L
|
||||||
|
|
||||||
|
#include "minimake.h"
|
||||||
|
|
||||||
|
#include <ctype.h>
|
||||||
|
#include <err.h>
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "hash_maps/hash_maps.h"
|
||||||
|
#include "lines/lines.h"
|
||||||
|
#include "lists/lists.h"
|
||||||
|
|
||||||
|
// Static variables
|
||||||
|
|
||||||
|
char *program_name;
|
||||||
|
|
||||||
|
struct hash_map *variables = NULL;
|
||||||
|
struct hash_map *rules = NULL;
|
||||||
|
|
||||||
|
// Keeps track of variables and rules order
|
||||||
|
struct list *variables_list = NULL;
|
||||||
|
struct list *rules_list = NULL;
|
||||||
|
|
||||||
|
// ==== Misc ====
|
||||||
|
|
||||||
|
#define FLAGS_FREE_RULES 1
|
||||||
|
#define FLAGS_FREE_VARIABLES 2
|
||||||
|
|
||||||
|
static 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
// printf("DEBUG: %s\n", entry->key);
|
||||||
|
struct rule *r = entry->value;
|
||||||
|
free(r->name);
|
||||||
|
list_deep_destroy(r->dependencies);
|
||||||
|
list_deep_destroy(r->recipe);
|
||||||
|
free(r);
|
||||||
|
// free(entry->key);
|
||||||
|
}
|
||||||
|
else if (hash_map == variables)
|
||||||
|
{
|
||||||
|
// struct variable *v = entry->value;
|
||||||
|
// free(v->name);
|
||||||
|
// free(v->value);
|
||||||
|
|
||||||
|
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()
|
||||||
|
{
|
||||||
|
list_destroy(rules_list);
|
||||||
|
list_destroy(variables_list);
|
||||||
|
hashmap_deep_free(rules);
|
||||||
|
hashmap_deep_free(variables);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void exit_on_error(int status, char *format, ...)
|
||||||
|
{
|
||||||
|
// Print
|
||||||
|
va_list args;
|
||||||
|
va_start(args, format);
|
||||||
|
fprintf(stderr, "%s: ", program_name);
|
||||||
|
vfprintf(stderr, format, args);
|
||||||
|
fprintf(stderr, "\n");
|
||||||
|
va_end(args);
|
||||||
|
|
||||||
|
free_all();
|
||||||
|
exit(status);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void dump_database()
|
||||||
|
{
|
||||||
|
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)
|
||||||
|
exit_on_error(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)
|
||||||
|
exit_on_error(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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==== 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)
|
||||||
|
{
|
||||||
|
exit_on_error(
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
exit_on_error(
|
||||||
|
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')
|
||||||
|
{
|
||||||
|
exit_on_error(
|
||||||
|
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
|
||||||
|
{
|
||||||
|
char *command = strdup(buf + 1);
|
||||||
|
if (command == NULL)
|
||||||
|
{
|
||||||
|
exit_on_error(
|
||||||
|
GENERIC_ERR,
|
||||||
|
"Internal error: couldn't duplicate string (%lu:1)",
|
||||||
|
l->number);
|
||||||
|
}
|
||||||
|
|
||||||
|
command[l->length - 2] = '\0';
|
||||||
|
res = list_append(res, command);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
l->buffer = buf;
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reads the value after a variable declaration
|
||||||
|
static char *read_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)
|
||||||
|
exit_on_error(GENERIC_ERR, "Could not realloc");
|
||||||
|
}
|
||||||
|
|
||||||
|
str_buf[i] = buf[i];
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
str_buf[i] = '\0';
|
||||||
|
|
||||||
|
return str_buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gets the corresponding value of the variable and stores it in *value
|
||||||
|
// Returns the number of skipped characters
|
||||||
|
// static size_t expand_variable(char *buf, size_t line_number, char **value)
|
||||||
|
// {
|
||||||
|
// size_t i = 0;
|
||||||
|
// if (buf[i] == '(')
|
||||||
|
// {
|
||||||
|
// // Read Value
|
||||||
|
|
||||||
|
// size_t tmp_buf_size = STRING_BUFFER_SIZE;
|
||||||
|
// char *tmp_buf = malloc(sizeof(char) * tmp_buf_size);
|
||||||
|
// // TODO free on err
|
||||||
|
|
||||||
|
// while (buf[i] != '\n' && buf[i] != '\0' && buf[i] != '#'
|
||||||
|
// && buf[i] != ')')
|
||||||
|
// {
|
||||||
|
// // Reallocate more space if necessary
|
||||||
|
// if (i >= tmp_buf_size - 1)
|
||||||
|
// {
|
||||||
|
// tmp_buf_size += STRING_BUFFER_SIZE;
|
||||||
|
// tmp_buf = realloc(tmp_buf, tmp_buf_size);
|
||||||
|
// if (tmp_buf == NULL)
|
||||||
|
// exit_on_error(GENERIC_ERR, "Could not realloc");
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // Copy
|
||||||
|
// tmp_buf[i] = buf[i];
|
||||||
|
// i++;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // End
|
||||||
|
// tmp_buf[i] = '\0';
|
||||||
|
|
||||||
|
// // Check for mismatched parenthesis
|
||||||
|
// if (buf[i] == ')')
|
||||||
|
// {
|
||||||
|
// // Get corresponding value
|
||||||
|
// char *val = hashmap_get(variables, tmp_buf);
|
||||||
|
// if (val == NULL)
|
||||||
|
// {
|
||||||
|
// // Adds tmp_buf to the list of items to free
|
||||||
|
// variables_list = list_append(variables_list, tmp_buf);
|
||||||
|
// exit_on_error(GENERIC_ERR,
|
||||||
|
// "Could not find specified variable '%s' at
|
||||||
|
// line", tmp_buf, line_number);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// free(tmp_buf);
|
||||||
|
// *value = val;
|
||||||
|
// return i + 1;
|
||||||
|
// }
|
||||||
|
// else
|
||||||
|
// {
|
||||||
|
// free(tmp_buf);
|
||||||
|
// exit_on_error(GENERIC_ERR, "Mismatched parenthesis");
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// else
|
||||||
|
// {
|
||||||
|
// if (isblank(buf[i]) || isspace(buf[i]))
|
||||||
|
// {
|
||||||
|
// exit_on_error(GENERIC_ERR,
|
||||||
|
// "Special character '$' cannot be used alone");
|
||||||
|
// }
|
||||||
|
// else if (buf[i] == '$')
|
||||||
|
// {
|
||||||
|
// *value = "$";
|
||||||
|
// return 2;
|
||||||
|
// }
|
||||||
|
// else
|
||||||
|
// {
|
||||||
|
// // Get corresponding value
|
||||||
|
// char *tmp_buf = malloc(2 * sizeof(char));
|
||||||
|
// if (tmp_buf == NULL)
|
||||||
|
// exit_on_error(GENERIC_ERR,
|
||||||
|
// "Could not allocate memory (seriously, not even
|
||||||
|
// " "two bytes)");
|
||||||
|
// tmp_buf[0] = buf[i];
|
||||||
|
// tmp_buf[1] = '\0';
|
||||||
|
// char *val = hashmap_get(variables, tmp_buf);
|
||||||
|
// if (val == NULL)
|
||||||
|
// {
|
||||||
|
// // Adds tmp_buf to the list of items to free
|
||||||
|
// variables_list = list_append(variables_list, tmp_buf);
|
||||||
|
// exit_on_error(GENERIC_ERR,
|
||||||
|
// "Could not find specified variable '%s' at
|
||||||
|
// line", tmp_buf, line_number);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// free(tmp_buf);
|
||||||
|
// *value = val;
|
||||||
|
// return 2;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return 1; // Discard warnings
|
||||||
|
// }
|
||||||
|
// return 1; // Discard warnings
|
||||||
|
// }
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
// 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_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)
|
||||||
|
{
|
||||||
|
exit_on_error(
|
||||||
|
GENERIC_ERR,
|
||||||
|
"Unexpected character '%c' after declaration '%s' at line %lu",
|
||||||
|
buf[i], name, current_line->number);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
exit_on_error(
|
||||||
|
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)
|
||||||
|
exit_on_error(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)
|
||||||
|
exit_on_error(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 ====
|
||||||
|
|
||||||
|
// ==== MAKE ====
|
||||||
|
|
||||||
|
void make(char *path, int flags, char *argv0)
|
||||||
|
{
|
||||||
|
program_name = argv0;
|
||||||
|
|
||||||
|
make_parse(path);
|
||||||
|
|
||||||
|
if (flags & FLAGS_PRINT)
|
||||||
|
// errx(GENERIC_ERR, "Not Implemented");
|
||||||
|
dump_database();
|
||||||
|
|
||||||
|
free_all();
|
||||||
|
}
|
||||||
|
|
@ -10,15 +10,14 @@
|
||||||
#define FLAGS_PRINT 2
|
#define FLAGS_PRINT 2
|
||||||
|
|
||||||
// Default values
|
// Default values
|
||||||
#define DEFAULT_MAKEFILE "makefile"
|
#define DEFAULT_MAKEFILE "Makefile"
|
||||||
#define DEFAULT_MAKEFILE_2 "Makefile"
|
#define DEFAULT_MAKEFILE_2 "makefile"
|
||||||
#define BUFFER_SIZE 1024
|
#define BUFFER_SIZE 1024
|
||||||
#define HASHMAP_SIZE 32
|
#define HASHMAP_SIZE 32
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
|
|
||||||
#include "lines/lines.h"
|
|
||||||
#include "lists/lists.h"
|
#include "lists/lists.h"
|
||||||
|
|
||||||
// Holds variable information
|
// Holds variable information
|
||||||
|
|
@ -38,11 +37,7 @@ struct rule
|
||||||
struct list *recipe;
|
struct list *recipe;
|
||||||
};
|
};
|
||||||
|
|
||||||
int make(char *path, struct list *rules, char *argv0, int flags);
|
void make(char *path, int flags, char* program_name);
|
||||||
void make_parse(char *path);
|
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
|
#endif // ! MINIMAKE_H
|
||||||
37
simple_ls/simple_ls.c
Normal file
37
simple_ls/simple_ls.c
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
#include <dirent.h>
|
||||||
|
#include <err.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
static void simple_ls(char *path)
|
||||||
|
{
|
||||||
|
if (path == NULL)
|
||||||
|
errx(1, "Internal error: Passed NULL path");
|
||||||
|
|
||||||
|
DIR *dir = opendir(path);
|
||||||
|
if (dir == NULL)
|
||||||
|
errx(1, "Internal error: cannot open directory");
|
||||||
|
|
||||||
|
struct dirent *element;
|
||||||
|
while ((element = readdir(dir)))
|
||||||
|
{
|
||||||
|
puts(element->d_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
int res = closedir(dir);
|
||||||
|
if (res == -1)
|
||||||
|
errx(1, "Could not close dir");
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
if (argc < 2)
|
||||||
|
simple_ls(".");
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (int i = 1; i < argc; i++)
|
||||||
|
{
|
||||||
|
simple_ls(argv[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
@ -1,94 +0,0 @@
|
||||||
#include "files.h"
|
|
||||||
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <sys/stat.h>
|
|
||||||
#include <sys/wait.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
|
|
||||||
// #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;
|
|
||||||
}
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
#ifndef FILES_H
|
|
||||||
#define FILES_H
|
|
||||||
|
|
||||||
#define _XOPEN_SOURCE 500
|
|
||||||
|
|
||||||
#include <sys/types.h>
|
|
||||||
|
|
||||||
#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
|
|
||||||
|
|
@ -1,263 +0,0 @@
|
||||||
#include "lines.h"
|
|
||||||
|
|
||||||
#include <ctype.h>
|
|
||||||
#include <err.h>
|
|
||||||
#include <stddef.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
|
|
||||||
#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;
|
|
||||||
}
|
|
||||||
674
src/minimake.c
674
src/minimake.c
|
|
@ -1,674 +0,0 @@
|
||||||
#define _POSIX_C_SOURCE 200809L
|
|
||||||
|
|
||||||
#include "minimake.h"
|
|
||||||
|
|
||||||
#include <ctype.h>
|
|
||||||
#include <err.h>
|
|
||||||
#include <stdarg.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <sys/stat.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
|
|
||||||
#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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
empty_target:
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
VAR = 1
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
rule1:
|
|
||||||
echo Toto
|
|
||||||
echo Tata
|
|
||||||
echo Tutu
|
|
||||||
|
|
||||||
rule2:
|
|
||||||
@echo Toto encore
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
V=2
|
|
||||||
|
|
||||||
main:
|
|
||||||
echo $V
|
|
||||||
|
|
@ -1,36 +0,0 @@
|
||||||
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)"
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue