Goto sanos source index
//
// make.c
//
// Make utility
//
// Copyright (C) 2011 Michael Ringgaard. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
//
// 1. Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
// 3. Neither the name of the project nor the names of its contributors
// may be used to endorse or promote products derived from this software
// without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
// OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
// OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
// SUCH DAMAGE.
//
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include <sys/stat.h>
struct buffer {
char *start;
char *end;
char *limit;
};
struct item {
char *value;
struct item *next;
};
struct list {
struct item *head;
struct item *tail;
};
#define UNCHECKED 0
#define PENDING 1
#define CLEAN 2
#define DIRTY 3
struct rule {
char *target;
struct list dependencies;
int status;
time_t timestamp;
struct list commands;
struct rule *next;
struct rule *build_next;
};
struct project {
struct rule *rules_head;
struct rule *rules_tail;
struct list targets;
int dry_run;
int silent;
int debug;
int always_build;
char oldcwd[FILENAME_MAX];
struct rule *build_first;
struct rule *build_last;
struct rule *phony;
struct buffer line;
struct buffer name;
struct buffer value;
};
char *stralloc(char *str, int size) {
char *buffer = (char *) malloc(size + 1);
memcpy(buffer, str, size);
buffer[size] = 0;
return buffer;
}
void buffer_init(struct buffer *b) {
b->start = b->end = b->limit = NULL;
}
void buffer_free(struct buffer *b) {
if (b->start) free(b->start);
}
void buffer_clear(struct buffer *b) {
b->end = b->start;
}
void buffer_expand(struct buffer *b, int minfree) {
char *p;
int size;
int minsize;
if (b->limit - b->end >= minfree) return;
size = b->limit - b->start;
minsize = b->end - b->start + minfree;
while (size < minsize) {
if (size == 0) {
size = 128;
} else {
size *= 2;
}
}
p = (char *) realloc(b->start, size);
b->end = p + (b->end - b->start);
b->limit = p + size;
b->start = p;
}
void buffer_append(struct buffer *b, char *data, int size) {
buffer_expand(b, size);
memcpy(b->end, data, size);
b->end += size;
}
void buffer_add(struct buffer *b, char ch) {
buffer_expand(b, 1);
*b->end++ = ch;
}
void buffer_concat(struct buffer *b, char *str) {
buffer_append(b, str, strlen(str));
}
void buffer_set(struct buffer *b, char *data, int size) {
buffer_clear(b);
buffer_append(b, data, size);
}
char *buffer_dup(struct buffer *b) {
return stralloc(b->start, b->end - b->start);
}
void list_init(struct list *l) {
l->head = l->tail = NULL;
}
void list_append(struct list *l, char *value) {
struct item *itm = (struct item *) malloc(sizeof(struct item));
itm->value = value;
itm->next = NULL;
if (l->tail) l->tail->next = itm;
if (!l->head) l->head = itm;
l->tail = itm;
}
void list_free(struct list *l) {
struct item *next;
struct item *i = l->head;
while (i != NULL) {
if (i->value) free(i->value);
next = i->next;
free(i);
i = next;
}
}
void project_init(struct project *prj) {
memset(prj, 0, sizeof(struct project));
getcwd(prj->oldcwd, FILENAME_MAX);
}
void project_free(struct project *prj) {
struct rule *r;
list_free(&prj->targets);
r = prj->rules_head;
while (r != NULL) {
struct rule *next;
if (r->target) free(r->target);
list_free(&r->dependencies);
list_free(&r->commands);
next = r->next;
free(r);
r = next;
}
buffer_free(&prj->line);
buffer_free(&prj->name);
buffer_free(&prj->value);
chdir(prj->oldcwd);
}
struct rule *add_rule(struct project *prj, char *target) {
struct rule *rule = (struct rule *) malloc(sizeof(struct rule));
rule->target = target;
rule->build_next = NULL;
rule->status = UNCHECKED;
rule->timestamp = -1;
list_init(&rule->dependencies);
list_init(&rule->commands);
rule->next = NULL;
if (prj->rules_tail) prj->rules_tail->next = rule;
if (!prj->rules_head) prj->rules_head = rule;
prj->rules_tail = rule;
return rule;
}
struct rule *find_rule(struct project *prj, char *name) {
struct rule *r = prj->rules_head;
while (r) {
if (strcmp(r->target, name) == 0) return r;
r = r->next;
}
return NULL;
}
void setup_predefined_variables() {
setenv("AR", "ar", 0);
setenv("AS", "as", 0);
setenv("CC", "cc", 0);
setenv("MAKE", "make", 0);
}
void replace_pattern(char *p, char *pattern, char *replace, struct buffer *b) {
int pattern_len = strlen(pattern);
while (*p) {
if (strncmp(p, pattern, pattern_len) == 0) {
buffer_concat(b, replace);
p += pattern_len;
} else {
buffer_add(b, *p++);
}
}
}
int expand_macros(char *p, struct project *prj, struct rule *rule, struct buffer *b) {
buffer_clear(b);
while (*p) {
if (*p == '$') {
// Parse macro
char *start = ++p;
char *end = NULL;
if (*p == '@') {
// Expand to target name
if (!rule) {
fprintf(stderr, "Special macro $@ only allowed in commands\n");
return -1;
}
buffer_concat(b, rule->target);
p++;
} else if (*p == '<') {
// Expand to first name in dependencies
if (!rule) {
fprintf(stderr, "Special macro $< only allowed in commands\n");
return -1;
}
if (rule->dependencies.head != NULL) {
buffer_concat(b, rule->dependencies.head->value);
}
p++;
} else if (*p == '^' || *p == '*' && *(p + 1) == '*') {
struct item *item;
int first;
// Expand to list of dependencies
if (!rule) {
fprintf(stderr, "Special macro $^ only allowed in commands\n");
return -1;
}
item = rule->dependencies.head;
first = 1;
while (item) {
if (!first) buffer_add(b, ' ');
buffer_concat(b, item->value);
item = item->next;
first = 0;
}
if (*p == '*') p++;
p++;
} else if (*p == '?') {
// Expand to list of dependencies more recent than target
if (!rule) {
fprintf(stderr, "Special macro $? only allowed in commands\n");
return -1;
}
buffer_concat(b, "#NOTIMPL#");
p++;
} else if (*p == '(') {
start = ++p;
while (*p && *p != ')') p++;
if (*p != ')') return -1;
end = p++;
} else {
while (isalnum(*p) || *p == '_') p++;
end = p;
}
if (end != NULL) {
// Substitute variable.
char *pattern = NULL;
char *replace = NULL;
char *name = stralloc(start, end - start);
char *value;
// Check for $(VAR:pattern=replace)
char *colon = strchr(name, ':');
if (colon) {
char *equal = strchr(colon, '=');
if (equal) {
*colon = 0;
pattern = colon + 1;
*equal = 0;
replace = equal + 1;
}
}
// Expand variable
value = getenv(name);
if (value != NULL) {
if (pattern != NULL) {
replace_pattern(value, pattern, replace, b);
} else {
buffer_concat(b, value);
}
}
free(name);
}
} else {
// Just add character.
buffer_add(b, *p++);
}
}
buffer_add(b, 0);
return 0;
}
void parse_list(char *p, struct list *list) {
char *start;
while (*p) {
// Skip whitespace
if (isspace(*p)) {
p++;
continue;
}
// Parse name
start = p;
while (*p && !isspace(*p)) p++;
list_append(list, stralloc(start, p - start));
}
}
int parse_makefile(struct project *prj, FILE *f) {
int line_num = 1;
struct rule *current_rule = NULL;
struct buffer *line = &prj->line;
struct buffer *name = &prj->name;
struct buffer *value = &prj->value;
while (!feof(f)) {
int ch;
char *p;
int skipws;
buffer_clear(line);
skipws = 0;
while ((ch = getc(f)) != EOF) {
if (ch == '\r') continue;
if (ch == '\n') {
// Increment line counter.
line_num++;
// Check for line continuation.
if (line->end > line->start && *(line->end - 1) == '\\') {
line->end--;
skipws = 1;
continue;
} else {
break;
}
}
if (skipws) {
if (isspace(ch)) {
skipws = 2;
continue;
} else {
if (skipws == 2) buffer_add(line, ' ');
skipws = 0;
}
}
buffer_add(line, ch);
}
buffer_add(line, 0);
p = line->start;
// Skip empty lines.
if (!*p) continue;
// Skip comments.
if (*p == '#') continue;
// Skip directives for now.
if (*p == '!') continue;
// If the line starts with whitespace it must be a command.
if (isspace(*p)) {
// Skip trailing whitespace.
while (isspace(*p)) p++;
// Is it just a blank line?
if (!*p) continue;
if (!current_rule) {
fprintf(stderr, "line %d: command with no target\n", line_num);
return -1;
}
list_append(¤t_rule->commands, strdup(p));
} else {
char *name_start;
char *name_end;
char type;
// Expand macros
if (expand_macros(line->start, prj, NULL, value) < 0) {
fprintf(stderr, "line %d: expansion failed\n", line_num);
return -1;
}
// Parse target or variable name
p = value->start;
name_start = p;
while (*p && *p != ':' && *p != '=' && !isspace(*p)) p++;
name_end = p;
if (!*p || name_start == name_end) {
fprintf(stderr, "line %d: syntax errror\n", line_num);
return -1;
}
while (isspace(*p)) p++;
type = *p++;
while (isspace(*p)) p++;
if (type != ':' && type != '=') {
fprintf(stderr, "line %d: syntax errror, rule or variable expected\n", line_num);
return -1;
}
buffer_set(name, name_start, name_end - name_start);
buffer_add(name, 0);
if (type == '=') {
// Set variable
setenv(name->start, p, 1);
if (prj->debug) printf("set %s=%s\n", name->start, p);
} else {
// Find existing rule or create a new one
struct rule *rule = find_rule(prj, name->start);
if (!rule) rule = add_rule(prj, buffer_dup(name));
parse_list(p, &rule->dependencies);
current_rule = rule;
}
}
}
return 0;
}
time_t get_timestamp(char *filename) {
struct stat st;
if (stat(filename, &st) < 0) return -1;
return st.st_mtime;
}
void mark_for_build(struct project *prj, struct rule *rule) {
if (prj->build_last) prj->build_last->build_next = rule;
if (!prj->build_first) prj->build_first = rule;
prj->build_last = rule;
}
int check_rule(struct project *prj, struct rule *rule) {
struct item *item;
struct rule *dep;
int dirty = 0;
// Check for cyclic dependencies
if (rule->status == PENDING) {
fprintf(stderr, "Cyclic dependency on %s\n", rule->target);
return -1;
}
// If this has already been checked we are done.
if (rule->status != UNCHECKED) return 0;
// Get timestamp for target
rule->timestamp = get_timestamp(rule->target);
// If this target does not exist we need to build this rule
if (rule->timestamp == -1) {
if (prj->debug) printf("build %s because it does not exist\n", rule->target);
dirty = 1;
}
// Mark this target as pending
rule->status = PENDING;
// Check dependencies.
item = rule->dependencies.head;
while (item) {
dep = find_rule(prj, item->value);
if (dep) {
// Check dependent rule
if (check_rule(prj, dep) < 0) return -1;
// If dependent rule is dirty we also need to build this rule
if (dep->status == DIRTY) {
if (prj->debug) printf("build %s because %s needs to be built\n", rule->target, item->value);
dirty = 1;
}
// If this target is older than dependent we need to build this rule
if (rule->timestamp < dep->timestamp) {
if (prj->debug) printf("build %s because it is older than %s\n", rule->target, item->value);
dirty = 1;
}
} else {
// No rule for dependent, just check the timestamp.
time_t t = get_timestamp(item->value);
if (rule->timestamp < t) {
if (prj->debug) printf("build %s because it is older than %s\n", rule->target, item->value);
dirty = 1;
}
}
item = item->next;
}
// Always build phony targets
if (prj->phony) {
item = prj->phony->dependencies.head;
while (item) {
if (strcmp(item->value, rule->target) == 0) {
if (prj->debug) printf("build %s because it is a phony target\n", rule->target);
dirty = 1;
break;
}
item = item->next;
}
}
// Mark as dirty if we are in always-build mode
if (prj->always_build) dirty = 1;
// Add rule to build list if it is dirty.
if (dirty) {
mark_for_build(prj, rule);
rule->status = DIRTY;
} else {
rule->status = CLEAN;
}
return 0;
}
int check_targets(struct project *prj) {
struct item *target = prj->targets.head;
prj->phony = find_rule(prj, ".PHONY");
while (target) {
struct rule *r = find_rule(prj, target->value);
if (!r) {
// This is a classic!
fprintf(stderr, "Don't know how to make %s\n", target->value);
return -1;
}
if (check_rule(prj, r) < 0) return -1;
target = target->next;
}
return 0;
}
int build_targets(struct project *prj) {
struct buffer *cmd = &prj->value;
struct rule *r = prj->build_first;
while (r) {
// Run commands in rule.
struct item *command = r->commands.head;
while (command) {
// Expand macros in command.
if (expand_macros(command->value, prj, r, cmd) < 0) return -1;
if (!prj->silent) printf("%s\n", cmd->start);
if (!prj->dry_run) {
if (system(cmd->start) != 0) return -1;
}
command = command->next;
}
r = r->build_next;
}
return 0;
}
void usage() {
fprintf(stderr, "usage: make [ -f <makefile> ] [ options ] ... [ targets ] ... \n\n");
fprintf(stderr, " -B Unconditionally build all targets.\n");
fprintf(stderr, " -C <dir> Change current directory to <dir> before building.\n");
fprintf(stderr, " -d Output debug messages.\n");
fprintf(stderr, " -f <file> Read <file> as makefile.\n");
fprintf(stderr, " -h Print this message.\n");
fprintf(stderr, " -n Display commands but do not build.\n");
fprintf(stderr, " -s Do not print commands as they are executed.\n");
}
int main(int argc, char *argv[]) {
int c;
int i;
int rc;
char *makefile = "Makefile";
char *dir = NULL;
FILE *mf;
struct project prj;
char curdir[FILENAME_MAX];
// Initialize project
setup_predefined_variables();
project_init(&prj);
// Parse command line options
while ((c = getopt(argc, argv, "BC:df:hns")) != EOF) {
switch (c) {
case 'B':
prj.always_build = 1;
break;
case 'C':
dir = optarg;
break;
case 'd':
prj.debug = 1;
break;
case 'f':
makefile = optarg;
break;
case 'h':
usage();
return 1;
case 'n':
prj.dry_run = 1;
break;
case 's':
prj.silent = 1;
break;
default:
fprintf(stderr, "use -h for help\n");
return 1;
}
}
// Add targets and variables from command line
for (i = optind; i < argc; ++i) {
char *eq = strchr(argv[i], '=');
if (eq) {
// Set variable
putenv(argv[i]);
} else {
// Add target.
list_append(&prj.targets, strdup(argv[i]));
}
}
// Change directory if requested
if (dir) {
if (chdir(dir) < 0) {
perror(dir);
project_free(&prj);
return 1;
}
}
// Set current directory variable.
setenv("CURDIR", getcwd(curdir, FILENAME_MAX), 0);
// Read and parse makefile.
mf = fopen(makefile, "r");
if (mf) {
rc = parse_makefile(&prj, mf);
fclose(mf);
if (rc < 0) {
project_free(&prj);
return 1;
}
}
// Use the first rule if no targets were specified on the command line
if (!prj.targets.head && prj.rules_head) {
list_append(&prj.targets, strdup(prj.rules_head->target));
}
// Add and expand build targets
rc = check_targets(&prj);
if (rc < 0) {
project_free(&prj);
return 1;
}
// Build targets
rc = build_targets(&prj);
if (rc < 0) {
project_free(&prj);
return 1;
}
project_free(&prj);
return 0;
}