A compromise between the speed of make and the ease of use of a build script
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
moonbuild/bin/moonbuild.moon

211 lines
5.2 KiB

4 years ago
#!/usr/bin/env moon
argparse = require 'argparse'
4 years ago
require 'moonscript'
import loadfile from require 'moonscript.base'
import truncate_traceback, rewrite_traceback from require 'moonscript.errors'
import trim from require 'moonscript.util'
util = require 'moonbuild.util'
import freezecache, invalidatecache from require 'moonbuild.fsutil'
import exists, mtime, run, min, max, first, flatten, match, patsubst, sortedpairs from util
4 years ago
import insert, concat from table
parser = argparse 'moonbuild'
4 years ago
parser\argument('targets', "Targets to run")\args '*'
parser\flag '-a --noskip', "Always run targets"
parser\flag '-l --list', "List available targets"
parser\flag '-d --deps', "List targets and their dependancies"
args = parser\parse!
4 years ago
-- util functions
loadwithscope = (file, scope) ->
fn, err = loadfile file
error err or "failed to load code" unless fn
dumped, err = string.dump fn
error err or "failed to dump function" unless dumped
4 years ago
load dumped, file, 'b', scope
pcall = (fn, ...) ->
rewrite = (err) ->
trace = debug.traceback '', 2
trunc = truncate_traceback trim trace
(rewrite_traceback trunc, err) or trace
4 years ago
xpcall fn, rewrite, ...
-- command object
-- represents a command that can be called
class Command
new: (@cmd, ...) =>
@args = {...}
4 years ago
__unm: => @run error: true, print: true
__len: => @run error: true
__tostring: => @cmd
run: (params) => run @cmd, @args, params
@run: (...) => -@ ...
-- build object
-- represents a target
class BuildObject
all = {}
skip = {}
@find: (name) =>
target = all[name]
return target if target
for glob, tgt in pairs all
return tgt if match name, glob
nil
@list: =>
{target, {dep, @find dep for dep in *target.deps} for name, target in pairs all}
4 years ago
4 years ago
@build: (name, upper) =>
target = (@find name) or error "No such target: #{name}"
4 years ago
target\build name, upper
__tostring: =>
"Target #{@name} (#{concat @deps, ', '})"
4 years ago
new: (@name, @outs={}, @ins={}, @deps={}, @fn= =>) =>
@skip = false
4 years ago
error "Duplicate build name #{@name}" if all[@name]
all[@name] = @
4 years ago
4 years ago
build: (name, upper={}) =>
return if skip[name]
4 years ago
error "Cycle detected on #{@name}" if upper[@]
upper = setmetatable {[@]: true}, __index: upper
if @name!=name
4 years ago
@@build (patsubst name, @name, dep), upper for dep in *@deps
else
4 years ago
@@build dep, upper for dep in *@deps
return unless @shouldbuild name
ins = @ins
outs = @outs
if @name!=name
ins = [patsubst name, @name, elem for elem in *@ins]
outs = [patsubst name, @name, elem for elem in *@outs]
print "Building #{@name} as #{name}"
else
print "Building #{name}"
freezecache file for file in *outs
ok, err = pcall ->
@.fn
ins: ins
outs: outs
infile: ins[1]
outfile: outs[1]
name: name
invalidatecache file for file in *outs
4 years ago
error "Can't build #{@name}: lua error\n#{err}" unless ok
for f in *outs
4 years ago
error "Can't build #{@name}: output file #{f} not created" unless exists f
skip[name] = true
4 years ago
shouldbuild: (name) =>
4 years ago
return true if args.noskip
return true if #@ins==0 or #@outs==0
ins = if @name!=name
[patsubst name, @name, elem for elem in *@ins]
else
@ins
itimes = [mtime f for f in *ins]
4 years ago
for i=1, #@ins
error "Can't build #{@name}: missing inputs" unless itimes[i]
outs = if @name!=name
[patsubst name, @name, elem for elem in *@outs]
else
@outs
otimes = [mtime f for f in *outs]
4 years ago
for i=1, #@outs
return true if not otimes[i]
(max itimes)>(min otimes)
4 years ago
error "Need Lua >=5.2" if setfenv
local targets, defaulttarget
4 years ago
buildscope =
4 years ago
default: (target) ->
defaulttarget=target.name
target
public: (target) ->
insert targets, target.name
target
target: (name, params) ->
tout = flatten params.out
tin = flatten params.in
tdeps = flatten params.deps
for f in *flatten params.from
insert tin, f
insert tdeps, f
BuildObject name, tout, tin, tdeps, params.fn
:Command
buildscope[k] = fn for k, fn in pairs util
4 years ago
setmetatable buildscope,
__index: (k) =>
global = rawget _G, k
4 years ago
return global if global
(...) -> Command k, ...
loadtargets = ->
targets = {}
defaulttarget = 'all'
file = first {'Build.moon', 'Buildfile.moon', 'Build', 'Buildfile'}, exists
error "No Build.moon or Buildfile found" unless file
buildfn = loadwithscope file, buildscope
error "Failed to load build function" unless buildfn
buildfn!
buildtargets = ->
if #args.targets==0
BuildObject\build defaulttarget
for target in *args.targets
BuildObject\build target
ok, err = pcall loadtargets
4 years ago
unless ok
if err
io.stderr\write "Error while loading build file: ", err, '\n'
4 years ago
else
io.stderr\write "Unknown error\n"
os.exit 1
if args.list
io.write "Available targets:\n"
io.write "\t#{concat targets, ', '}\n"
os.exit 0
if args.deps
io.write "Targets:\n"
for target, deps in sortedpairs BuildObject\list!, (a, b) -> a.name<b.name
io.write "\t#{target.name} "
if #target.ins==0
if #target.outs==0
io.write "[no in/out]"
else
io.write "[spontaneous generation]"
else
if #target.outs==0
io.write "[consumer]"
else
io.write "(#{concat target.ins, ', '} -> #{concat target.outs, ', '})"
io.write "\n"
for name, dep in sortedpairs deps
4 years ago
io.write "\t\t#{name}"
if name!=dep.name
io.write " (#{dep.name})"
io.write "\n"
os.exit 0
buildtargets!