Permalink
Find file
Fetching contributors…
Cannot retrieve contributors at this time
executable file 160 lines (145 sloc) 5.08 KB
#!/usr/bin/env awk -f
# A little Make descended from the make.awk in Aho, Kernighan, & Weinberger,
# _The Awk Programming Language_. Embed this file in a project to declare its
# independence from any external 'make' program. Get the latest version at
# http://github.com/darius/ake
# (Submit any broadly-useful patches to there, not here.)
BEGIN {
if (ARGC == 1) the_target = ""
else if (ARGC == 2) the_target = ARGV[1]
else error("usage: ake [target]")
parse("Akefile")
add_auto_targets()
rank_ages()
if (!(the_target in targets))
error(the_target " is not in Akefile")
if (update(the_target) == 0)
print the_target " is up to date"
}
# Data structures:
#
# targets[t] 1 for every target named t
# ndeps[t] the # of dependencies of target t
# deps[t, i] the name of the ith dependency of t
# cmd[t] the command to use to update target t
#
# progress[t] 1 if we're working on updating t's dependencies;
# 2 if we've finished that
#
# age[t] age ranking: smaller means younger
# (t in age[t] may be either a target or a file)
# Parse an akefile.
function parse(filename, target,i) {
while (getline <filename > 0)
if ($0 ~ /^#/) # a comment
;
else if ($0 ~ /^[ \t]/) { # a command
if (target == "")
error("command with no target: " $0)
cmd[target] = cmd[target] $0 "\n"
} else if ($0 ~ /^[A-Za-z$].*:/) { # $1: $2 $3 ...
sub(/:/, "")
target = $1
if (++targets[target] > 1)
error(target " is multiply defined")
for (i = 2; i <= NF; i++)
deps[target, ++ndeps[target]] = $i
if (the_target == "")
the_target = target
} else if (0 < NF)
error("illegal line in " filename ": " $0)
}
# Add rules like "foo.o: foo.c" for targets like foo.o that need them.
# This is kind of sleazy; probably we should check for these at update
# time instead, and only supply the default rule if foo.c exists.
function add_auto_targets( t, i, dep) {
for (t in targets)
for (i = 1; i <= ndeps[t]; ++i) {
dep = deps[t, i]
if (dep ~ /[.]o$/ && !(dep in targets)) {
targets[dep] = 1 # XXX is this legal inside the for loop?
deps[dep, ++ndeps[dep]] = change_extension(dep, ".c")
}
}
}
function change_extension(filename, ext) {
sub(/[.][^.]+/, ext, filename)
return filename
}
# Make target t up-to-date; return true iff we had to update it.
function update(t) {
if (!(t in age))
error(t " does not exist")
if (!(t in targets))
return 0
if (update_dependencies(t)) {
printf("%s", get_update_command(t))
# XXX what if one line in the command fails?
# do subsequent lines get run?
if (system(get_update_command(t)))
error("failed")
rank_ages()
check_up_to_date(t)
return 1
}
return 0
}
# Return the shell command to run to update t from its dependencies.
function get_update_command(t) {
if (cmd[t] ~ /[^ \t\n]/)
return cmd[t]
# XXX sleazy...:
if (t ~ /[.]o$/ && 1 <= ndeps[t] && deps[t, 1] ~ /[.]c$/)
return "\tcc $CFLAGS -o " t " -c " deps[t, 1] "\n"
if (t !~ /[.]/ && 1 <= ndeps[t] && deps[t, 1] ~ /[.]o$/)
return "\tcc $CFLAGS -o " t " " join_deps(t) "\n"
return ""
}
# Return t's dependencies concatenated in order, with spaces between each.
function join_deps(t, i,result) {
for (i = 1; i <= ndeps[t]; ++i)
result = (i == 1 ? "" : result " ") deps[t, i]
return result
}
# Panic if t is not up-to-date wrt its direct dependencies.
function check_up_to_date(t, i,dep) {
for (i = 1; i <= ndeps[t]; ++i) {
dep = deps[t, i]
# XXX this test can give false alarms because our age-ranking
# code, given two files of the same age, ranks them arbitrarily
# (I think by alphabetical order). What to do?
if (age[dep] < age[t])
error(t "'s update rule failed to make it younger than " dep)
}
}
# Update t's dependencies; return true iff t needs updating afterwards.
function update_dependencies(t, dirty,i,dep) {
dirty = (ndeps[t] == 0)
progress[t] = 1
for (i = 1; i <= ndeps[t]; ++i) {
dep = deps[t, i]
if (progress[dep] == 0)
update(dep)
else if (progress[dep] == 1)
error(dep " and " t " are circularly defined")
if (age[dep] <= age[t])
dirty = 1
}
progress[t] = 2
return dirty
}
# Compute the ages of all targets.
function rank_ages( t,rank,filename) {
delete age
# Non-file targets are really old unless they've been updated:
for (t in targets)
age[t] = (progress[t] == 2 ? 0 : 999999)
# File ages override that default:
for (rank = 1; ("ls -t" | getline filename) > 0; ++rank)
age[filename] = rank
close("ls -t")
}
function error(s) {
print "ake: " s
exit 1
}