Compare commits

..

10 commits

Author SHA1 Message Date
Gu://em_
9731ba9a7a New presentation and organization 2026-06-19 11:11:29 +02:00
Gu://em_
018bae577a et on dis ciao aux clang format 2025-12-19 20:13:58 +01:00
Gu://em_
57c7743ba0 si ca passe pas jsui cuit 2025-12-19 20:12:03 +01:00
Gu://em_
bb1591a96a nothing to be done reload 2025-12-18 13:17:48 +01:00
Gu://em_
7acbfd6c9f hmmmm 2025-10-31 23:14:34 +01:00
Gu://em_
db4bf470ff even more fixes 2025-10-31 23:12:01 +01:00
Gu://em_
4b3df88e8a fixed print 2025-10-31 22:48:30 +01:00
Gu://em_
1efc35a59e JUSTOCASOUUUU 2025-10-31 22:31:08 +01:00
Gu://em_
a8ca407247 JUSTOCASOU 2025-10-31 21:34:09 +01:00
Guillem George
c358562ecc EXPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAnd 2025-10-30 21:14:53 +01:00
27 changed files with 1233 additions and 871 deletions

View file

@ -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 LIB_SRCS = lines/lines.c hash_maps/hash_maps.c lists/lists.c files/files.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))

73
README.md Normal file
View file

@ -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)
```

View file

@ -1,3 +0,0 @@
qwertyu:
cbejw: a bc d
dmwq :d wdwd

View file

@ -1,119 +0,0 @@
#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;
}

View file

@ -1,30 +0,0 @@
#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;
}
}

View file

@ -1,75 +0,0 @@
#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;
}

View file

@ -1 +0,0 @@
list.c

View file

@ -1,551 +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 <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(&current_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();
}

View file

@ -1,37 +0,0 @@
#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;
}

94
src/files/files.c Normal file
View file

@ -0,0 +1,94 @@
#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;
}

15
src/files/files.h Normal file
View file

@ -0,0 +1,15 @@
#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

View file

@ -24,7 +24,7 @@ struct hash_map
size_t hash(const char *str); size_t hash(const char *str);
struct hash_map *hashmap_init(size_t size); struct hash_map *hashmap_init(size_t size);
bool hashmap_insert(struct hash_map *hash_map, char *key, void *value, bool hashmap_insert(struct hash_map *hash_map, char *key, void *value,
bool *updated); bool *updated);
void hashmap_free(struct hash_map *hash_map); void hashmap_free(struct hash_map *hash_map);
void hashmap_dump(struct hash_map *hash_map); void hashmap_dump(struct hash_map *hash_map);
void *hashmap_get(const struct hash_map *hash_map, char *key); void *hashmap_get(const struct hash_map *hash_map, char *key);

263
src/lines/lines.c Normal file
View file

@ -0,0 +1,263 @@
#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;
}

View file

@ -4,23 +4,27 @@
#define STRING_BUFFER_SIZE 32 #define STRING_BUFFER_SIZE 32
#include <stddef.h> #include <stddef.h>
#include <sys/types.h>
#include <stdio.h> #include <stdio.h>
#include <sys/types.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
// WARNING buffer must be freed after use // WARNING buffer must be freed after use
struct line struct line
{ {
char* buffer; // Line content char *buffer; // Line content
ssize_t length; // Line length ssize_t length; // Line length
size_t number; // Line number in file size_t number; // Line number in file
FILE* file_stream; // Full file stream FILE *file_stream; // Full file stream
}; };
int ischar(char c); 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

View file

@ -331,3 +331,16 @@ 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;
}
}

View file

@ -1,11 +1,11 @@
#ifndef LIST_H #ifndef LISTS_H
#define LIST_H #define LISTS_H
#include <stddef.h> #include <stddef.h>
struct list struct list
{ {
void* data; void *data;
struct list *next; struct list *next;
}; };
@ -13,7 +13,7 @@ struct list
** Insert a node containing `value` at the beginning of the list. ** Insert a node containing `value` at the beginning of the list.
** Return `NULL` if an error occured. ** Return `NULL` if an error occured.
*/ */
struct list *list_prepend(struct list *list, void* value); struct list *list_prepend(struct list *list, void *value);
/* /*
** Return the lenght of the list. ** Return the lenght of the list.
@ -33,12 +33,18 @@ 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.
*/ */
// START PROTO list_append // START PROTO list_append
struct list *list_append(struct list *list, void* value); struct list *list_append(struct list *list, void *value);
// END PROTO list_append // END PROTO list_append
/* /*
@ -48,7 +54,7 @@ struct list *list_append(struct list *list, void* value);
** Return `NULL` if an error occured. ** Return `NULL` if an error occured.
*/ */
// START PROTO list_insert // START PROTO list_insert
struct list *list_insert(struct list *list, void* value, size_t index); struct list *list_insert(struct list *list, void *value, size_t index);
// END PROTO list_insert // END PROTO list_insert
/* /*
@ -64,7 +70,7 @@ struct list *list_remove(struct list *list, size_t index);
** Return `-1` if nothing is found. ** Return `-1` if nothing is found.
*/ */
// START PROTO list_find // START PROTO list_find
int list_find(struct list *list, void* value); int list_find(struct list *list, void *value);
// END PROTO list_find // END PROTO list_find
/* /*
@ -72,7 +78,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
/* /*
@ -80,7 +86,7 @@ struct list *list_concat(struct list *list, struct list *list2);
** 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
/* /*
@ -88,7 +94,7 @@ struct list *list_sort(struct list *list);
** 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
/* /*
@ -98,7 +104,7 @@ struct list *list_reverse(struct list *list);
** 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 /* ! LIST_H */ #endif /* ! LISTS_H */

View file

@ -5,6 +5,7 @@
#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"
@ -40,7 +41,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++)
{ {
files = list_append(rules, argv[i]); rules = list_append(rules, argv[i]);
} }
*minimake_files = files; *minimake_files = files;
*minimake_rules = rules; *minimake_rules = rules;
@ -50,13 +51,17 @@ 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;
if (i + 1 == argc || argv[i + 1][0] == '-') i++;
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]);
@ -71,12 +76,14 @@ 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
{ {
files = list_append(rules, argv[i]); rules = list_append(rules, argv[i]);
} }
} }
@ -88,34 +95,6 @@ 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;
@ -126,5 +105,8 @@ int main(int argc, char **argv)
if (filename == NULL) if (filename == NULL)
errx(GENERIC_ERR, "No Makefile found"); errx(GENERIC_ERR, "No Makefile found");
make(filename, flags, argv[0]); int res = make(filename, rules, argv[0], flags);
list_destroy(files);
list_destroy(rules);
return res;
} }

674
src/minimake.c Normal file
View file

@ -0,0 +1,674 @@
#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(&current_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;
}
}

View file

@ -10,14 +10,15 @@
#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
@ -32,12 +33,16 @@ struct variable
// WARNING its values must be freed after use // WARNING its values must be freed after use
struct rule struct rule
{ {
char* name; char *name;
struct list *dependencies; struct list *dependencies;
struct list *recipe; struct list *recipe;
}; };
void make(char *path, int flags, char* program_name); int make(char *path, struct list *rules, char *argv0, int flags);
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

1
tests/Makefile.empty Normal file
View file

@ -0,0 +1 @@
empty_target:

1
tests/Makefile.test3 Normal file
View file

@ -0,0 +1 @@
VAR = 1

7
tests/Makefile.test4 Normal file
View file

@ -0,0 +1,7 @@
rule1:
echo Toto
echo Tata
echo Tutu
rule2:
@echo Toto encore

4
tests/Makefile.vars Normal file
View file

@ -0,0 +1,4 @@
V=2
main:
echo $V

View file

@ -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)"