New presentation and organization
This commit is contained in:
parent
018bae577a
commit
9731ba9a7a
24 changed files with 73 additions and 189 deletions
674
src/minimake.c
Normal file
674
src/minimake.c
Normal 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(¤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;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue