alfons-task
Codinget 4 years ago
parent ceada11b0b
commit c8670ab903
  1. 4
      .gitignore
  2. 11
      Alfons.moon
  3. 80
      Build.moon
  4. 39
      Makefile
  5. 132
      README.md
  6. 282
      bin/moonbuild.moon
  7. 1211
      moonbuild.lua
  8. 15
      moonbuild/_.moon
  9. 21
      moonbuild/_cmd.moon
  10. 121
      moonbuild/_cmd/common.moon
  11. 29
      moonbuild/_cmd/lua.moon
  12. 46
      moonbuild/_cmd/posix.moon
  13. 162
      moonbuild/_common.moon
  14. 198
      moonbuild/_fs.moon
  15. 54
      moonbuild/_fs/cmd.moon
  16. 12
      moonbuild/_fs/lfs.moon
  17. 68
      moonbuild/_fs/posix.moon
  18. 47
      moonbuild/_util.moon
  19. 91
      moonbuild/compat/bit.moon
  20. 29
      moonbuild/compat/ctx.moon
  21. 10
      moonbuild/compat/execute.moon
  22. 12
      moonbuild/compat/pcall.moon
  23. 38
      moonbuild/context.moon
  24. 182
      moonbuild/core/DAG.moon
  25. 54
      moonbuild/core/Target.moon
  26. 11
      moonbuild/core/Variable.moon
  27. 61
      moonbuild/core/multiprocessexecutor.moon
  28. 16
      moonbuild/core/singleprocessexecutor.moon
  29. 18
      moonbuild/env/global.moon
  30. 32
      moonbuild/env/init.moon
  31. 31
      moonbuild/env/top.moon
  32. 52
      moonbuild/fscache.moon
  33. 124
      moonbuild/fsutil.moon
  34. 37
      moonbuild/stringutil.moon
  35. 69
      moonbuild/tableutil.moon
  36. 95
      moonbuild/util.moon
  37. 23
      rock.yml
  38. 111
      spec/fsutil_spec.moon
  39. 78
      spec/stringutil_spec.moon
  40. 82
      spec/tableutil_spec.moon

4
.gitignore vendored

@ -1,2 +1,2 @@
/moonbuild/*.lua
/bin/*.lua
*.lua
/out/*

@ -1,11 +0,0 @@
tasks:
build: =>
sh "moon bin/moonbuild.moon compile"
test: =>
sh "busted"
install: =>
sh "moon bin/moonbuild.moon install"
release: =>
error "no version provided" unless @v
tasks.build!
sh "rockbuild -m -t #{@v} upload"

@ -1,34 +1,46 @@
SOURCES_MOON = wildcard 'moonbuild/**.moon'
BINARY = 'bin/moonbuild.moon'
OUT_LUA = patsubst SOURCES_MOON, '%.moon', '%.lua'
BINARY_LUA = patsubst BINARY, '%.moon', '%.lua'
OUT_AMALG = 'moonbuild.lua'
public target 'clean', fn: =>
-rm '-f', OUT_LUA, BINARY_LUA
public target 'info', fn: =>
#echo "Moonscript sources:", SOURCES_MOON
#echo "Compiled lua:", OUT_LUA
public target 'compile', deps: OUT_AMALG
public target 'install', from: OUT_AMALG, out: '/usr/local/bin/moonbuild', fn: =>
dfd, err = io.open @outfile, 'w'
error err unless dfd
ifd, err = io.open @infile, 'r'
error err unless ifd
dfd\write '#!/usr/bin/env lua5.3\n'
for line in ifd\lines!
dfd\write line, '\n'
ifd\close!
dfd\close!
-chmod '+x', @outfile
#echo "Installed at:", @outfile
default target OUT_AMALG, from: {BINARY_LUA, OUT_LUA}, out: OUT_AMALG, fn: =>
modules = foreach (patsubst OUT_LUA, '%.lua', '%'), => @gsub '/', '.'
-Command 'amalg.lua', '-o', @outfile, '-s', 'bin/moonbuild.lua', modules
target '%.lua', in: '%.moon', out: '%.lua', fn: =>
-moonc @infile
public var 'MOONC', 'moonc'
public var 'AMALG', 'amalg.lua'
public var 'RM', 'rm', '-f', '--'
public var 'LUA', 'lua5.3'
var 'LIB_SRC', _.wildcard 'moonbuild/**.moon'
var 'BIN_SRC', _.wildcard 'bin/*.moon'
var 'LIB_LUA', _.patsubst LIB_SRC, '%.moon', '%.lua'
var 'BIN_LUA', _.patsubst BIN_SRC, '%.moon', '%.lua'
var 'BIN', _.patsubst BIN_LUA, 'bin/%.lua', 'out/%'
var 'MODULES', _.foreach (_.patsubst LIB_LUA, '%.lua', '%'), => @gsub '/', '.'
with public default target 'all'
\after 'bin'
\after 'lib'
with public target 'clean'
\fn => _.cmd RM, LIB_LUA
\fn => _.cmd RM, BIN_LUA
with public target 'mrproper'
\after 'clean'
\fn => _.cmd RM, BIN
with public target 'bin'
\depends BIN
with public target 'lib'
\depends LIB_LUA
with target BIN, pattern: 'out/%'
\depends 'bin/%.lua'
\depends LIB_LUA
\produces 'out/%'
\mkdirs!
\fn =>
_.cmd AMALG, '-o', @outfile, '-s', @infile, MODULES
_.writefile @outfile, "#!/usr/bin/env #{LUA}\n#{_.readfile @outfile}"
_.cmd 'chmod', '+x', @outfile
with target {LIB_LUA, BIN_LUA}, pattern: '%.lua'
\depends '%.moon'
\produces '%.lua'
\fn => _.moonc @infile, @outfile

@ -0,0 +1,39 @@
.PHONY: all clean mrproper bin lib
MOONC = moonc
AMALG = amalg.lua
RM = rm -f --
LUA = lua5.3
LIB_SRC = $(wildcard moonbuild/*.moon) $(wildcard moonbuild/*/*.moon) $(wildcard moonbuild/*/*/*.moon)
BIN_SRC = $(wildcard bin/*.moon)
LIB_LUA = $(foreach moon, $(LIB_SRC), $(patsubst %.moon, %.lua, $(moon)))
BIN_LUA = $(foreach moon, $(BIN_SRC), $(patsubst %.moon, %.lua, $(moon)))
BIN = $(foreach lua, $(BIN_LUA), $(patsubst bin/%.lua, out/%, $(lua)))
MODULES = $(shell echo $(foreach lib, $(LIB_LUA), $(patsubst %.lua, %, $(lib))) | sed 's|/|.|g')
all: bin lib
clean:
$(RM) $(LIB_LUA)
$(RM) $(BIN_LUA)
mrproper: clean
$(RM) $(BIN)
bin: $(BIN)
lib: $(LIB_LUA)
out/%: bin/%.lua $(LIB_LUA)
@mkdir -p `dirname $@`
$(AMALG) -o $@.body -s $< $(MODULES)
@printf '#!/usr/bin/env %s\n' $(LUA) > $@.headline
@cat $@.headline $@.body > $@
@rm $@.headline $@.body
chmod +x $@
%.lua: %.moon
moonc $^

@ -1,118 +1,34 @@
# Moonbuild
Because `make` is painful to use, and build scripts are too slow. Moonbuild aims to be a good compromise.
# moonbuild
**Now in v2 - complete rework from v1**
You should probably use [`tup`](http://gittup.org/tup/) instead if you want a good build system.
Because `make` is painful to use, and build scripts are too slow. Moonbuild aims to be a good compromise.
You should probably use [http://gittup.org/tup/](tup) instead if you want a good build system.
## How does it work?
Basically like `make`, but in [Moonscript](https://moonscript.org) and with explicit ordering. See its `Build.moon` for examples (and you can compare it with the `Makefile`, both do the same thing).
`moonbuild` reads a build file, usually `Build.moon` that is written in a Moonscript DSL to define its targets and variables.
It then builds a DAG for the dependancies of the targets you tell it to build.
Then, it tries building every target in the graph while respecting dependancies, possibly on multiple processes.
## Why moonscript?
It's fast, based on lua, and it's easy to write DSLs with it, so the build instructions can be readable. Also, it's a full programming language, so there are no arbitrary restrictions.
Essentially, it works the same way as `make`, just with a different language, you can compare the `Build.moon` and `Makefile` in this repo to see for yourself.
## How do I install it?
Any of these will work
- `luarocks install moonbuild` (probably as root)
- `moon bin/moonbuild.moon install` (probably as root, in the cloned repo)
- `alfons install` (probably as root, in the cloned repo, requires alfons)
## Why Moonscript?
Because it's fast, based on lua, and making DSLs with it is relatively easy.
It's also a language I like a lot, so I might have been biased when choosing it.
## Now, how do I use it?
First, you'll need a `Build.moon`, `Buildfile.moon`, `Build` or `Buildfile` in the root of your project.
Then, you'll need a few `target`s, and ideally a `default target` (or the default target will be `all`). `public target`s will be listed by `moonbuild -l`.
To execute a command, you can use either `-cmd` or `#cmd` (the former will print it before executing it, the later won't).
## Installing
It is available on luarocks with `luarocks install moonbuild`.
It is also recommended to install `luaposix` if you can, as it speeds it up a lot, or `luafilesystem` in case it isn't available.
### `[default] [public] target <name> [deps: <deps>] [in: <inputs>] [out: <outputs>] [from: <from>] [fn: <code>]`
Define a new target, and give it a list of depenancies, inputs, outputs and a function to run to build it.
## Building from source
You will need `argparse` and `moonscript` installed from luarocks, and `luaposix` or `luafilesystem` are recommended.
`deps`, `in` and `out` can be either strings or tables. `from` acts like both `in` and `deps`. `name` must be a string and `code` must be a function, that will be given a table with the following fields:
- `name`: the name of the target
- `ins`: the table of inputs
- `infile`: the first input
- `outs`: the table of outputs
- `outfile`: the first output
### Bootstrapping
You can build moonbuild with itself: `moon bin/moonbuild.moon -qjy`.
This will leave the binary ready to be used as `out/moonbuild`.
If `name` is a glob, the target becomes a glob target.
Glob targets can be used with name that matches them (with a limit of one glob target per name, and no ordering is specified).
Glob targets will have their name substituted for their inputs, outputs and dependancies.
### Using make
You can also build moonbuild with make: `make`.
This will leave the binary ready to be used as `out/moonbuild`.
### `-cmd [<args>...]`
Prints and executes the command `cmd` with the given args. See `run` for how `args` works.
### `#cmd [<args>...]`
Executes without printing the command `cmd` with the given args. See `run` for how `args` works.
### `wildcard <wc>`
Returns a table with all the matching files. Valid wildcards contain `**`, which can be expanded by any characters, including '/', and `*`, which cannot be expanded by `/`.
`wc` must be a string
### `exclude <list> [<exclusions>...]`
Removes all exclusions from the given list, and returns it.
`list` must be a table, and `exclusions` can be any type
### `patsubst <str> <patt> <subst>`
If the string matches `patt`, makes it match `subst` instead. If `str` is a table, it is recursively applied to all array values.
Patterns are in the format `[prefix]%[suffix]`, with the `%` representing any sequence of characters, including `/`.
`str`, `pat` and `subst` must be strings
### `foreach <table> <code>`
Applies `code` to every element of `table`, and returns the resulting table.
`table` must be a table, and `code` a function.
### `min|max <table>`
Returns either the min or max value of the given table.
`table` must be a table
### `first <table> <code>`
Returns the first value of the table that verifies the given condition.
`table` must be a table, and `code` a function
### `flatten <table>`
Flattens a table so that it has exactly one dimension.
`table` can be anything
### `insert|unpack|concat`
The functions, imported from the `table` library.
### `mtime <file>`
Returns the modification time of `file`, or `nil` if it doesn't exist.
`file` must be a string
### `exists <file>`
Returns `true` if the file exists, `false` otherwise.
`file` must be a string
### `isdir <file>`
Returns `true` if the file exists and is a directory, `false` otherwise.
`file` must be a string
### `run <cmd> [<args> [print: <print>] [error: <error>]]`
Runs the given command with the given arguments. If `print` is truthy, prints the command before executing it, and if `error` is truthy, crashes if the command fails. Returns a boolean which is true if the command ended with success, and a number which is the return code.
`cmd` must be a string, `args` must be a table, which can contain either strings or other tables. `raw: <val>` is a special kind of argument that will not be escaped. `print` and `error` can be anything, but booleans or nil are recommended
### `popen <cmd> [<args> [print: <print>]]`
Same as `run`, but returns a `io.popen` handle.
### `calccdeps <infile> [includesys]`
Computes the dependancies required to compile a C source file. Optionally include system libraries as sources.
`infile` must be a string and `includesys` must be a boolean or `nil`
### `findclib <lib> [what]`
Finds the compiler or linker flags required to use a given C library.
`lib` must be a string and `what` must be either `nil`, `'all'`, `'cc'` or `'ld'`
## License
MIT
## Docs
TODO

@ -1,210 +1,88 @@
#!/usr/bin/env moon
argparse = require 'argparse'
require 'moonscript'
-- load everything we need
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
import insert, concat from table
Context = require 'moonbuild.context'
Variable = require 'moonbuild.core.Variable'
DepGraph = require 'moonbuild.core.DAG'
import parseargs from require 'moonbuild._cmd.common'
import sort, concat from table
import exit from os
-- parse the arguments
argparse = require 'argparse'
parser = with argparse "moonbuild", "A build system in moonscript"
\option '-b --buildfile', "Build file to use", 'Build.moon'
\option '-j --parallel', "Sets the number of parallel tasks, 'y' to run as many as we have cores", '1'
\flag '-l --list', "List the targets", false
\flag '-V --list-variables', "List the variables", false
\flag '-q --quiet', "Don't print targets as they are being built", false
\flag '-f --force', "Always rebuild every target", false
\flag '-v --verbose', "Be verbose", false
(\option '-u --unset', "Unsets a variable")\count '*'
(\option '-s --set', "Sets a variable")\args(2)\count '*'
(\option '-S --set-list', "Sets a variable to a list")\args(2)\count '*'
(\argument 'targets', "Targets to build")\args '*'
\add_complete!
parser = argparse 'moonbuild'
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!
-- 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
load dumped, file, 'b', scope
pcall = (fn, ...) ->
rewrite = (err) ->
trace = debug.traceback '', 2
trunc = truncate_traceback trim trace
(rewrite_traceback trunc, err) or trace
xpcall fn, rewrite, ...
-- command object
-- represents a command that can be called
class Command
new: (@cmd, ...) =>
@args = {...}
__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}
@build: (name, upper) =>
target = (@find name) or error "No such target: #{name}"
target\build name, upper
__tostring: =>
"Target #{@name} (#{concat @deps, ', '})"
overrides = {}
for unset in *args.unset
overrides[unset] = Variable.NIL
for set in *args.set
overrides[set[1]] = set[2]
for set in *args.set_list
overrides[set[1]] = parseargs set[2]
new: (@name, @outs={}, @ins={}, @deps={}, @fn= =>) =>
@skip = false
error "Duplicate build name #{@name}" if all[@name]
all[@name] = @
args.parallel = args.parallel == 'y' and 'y' or ((tonumber args.parallel) or error "Invalid argument for -j: #{args.parallel}")
error "Invalid argument for -j: #{args.parallel}" if args.parallel != 'y' and (args.parallel<1 or args.parallel%1 != 0)
print "Parsed CLI args" if args.verbose
build: (name, upper={}) =>
return if skip[name]
error "Cycle detected on #{@name}" if upper[@]
upper = setmetatable {[@]: true}, __index: upper
if @name!=name
@@build (patsubst name, @name, dep), upper for dep in *@deps
else
@@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
error "Can't build #{@name}: lua error\n#{err}" unless ok
for f in *outs
error "Can't build #{@name}: output file #{f} not created" unless exists f
skip[name] = true
shouldbuild: (name) =>
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]
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]
for i=1, #@outs
return true if not otimes[i]
(max itimes)>(min otimes)
error "Need Lua >=5.2" if setfenv
local targets, defaulttarget
buildscope =
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
setmetatable buildscope,
__index: (k) =>
global = rawget _G, k
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
unless ok
if err
io.stderr\write "Error while loading build file: ", err, '\n'
else
io.stderr\write "Unknown error\n"
os.exit 1
-- load the buildfile
ctx = Context!
ctx\load (loadfile args.buildfile), overrides
print "Loaded buildfile" if args.verbose
-- handle -l and -V
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
io.write "\t\t#{name}"
if name!=dep.name
io.write " (#{dep.name})"
io.write "\n"
os.exit 0
buildtargets!
print "Public targets"
targets, n = {}, 1
for t in *ctx.targets
if t.public
targets[n], n = t.name, n+1
sort targets
print concat targets, ", "
print!
exit 0 unless args.list_variables
if args.list_variables
print "Public variables"
vars, n = {}, 1
for k, v in pairs ctx.variables
if v.public
vars[n], n = k, n+1
sort vars
print concat vars, ", "
print!
exit 0
-- initialize the buildfile further
ctx\init!
print "Initialized buildfile" if args.verbose
-- create the DAG
targets = #args.targets==0 and ctx.defaulttargets or args.targets
dag = DepGraph ctx, targets
print "Created dependancy graph" if args.verbose
-- execute the build
if args.parallel==1
Executor = require 'moonbuild.core.singleprocessexecutor'
executor = Executor dag, args.parallel
executor\execute args
else
ok, Executor = pcall -> require 'moonbuild.core.multiprocessexecutor'
Executor = require 'moonbuild.core.singleprocessexecutor' unless ok
nparallel = args.parallel == 'y' and Executor\getmaxparallel! or args.parallel
print "Building with #{nparallel} max parallel process#{nparallel>1 and "es" or ""}" if args.verbose
executor = Executor dag, nparallel
executor\execute args
print "Finished" if args.verbose

File diff suppressed because it is too large Load Diff

@ -0,0 +1,15 @@
import gmatch, match, gsub from string
import insert, remove, concat, sub, sort from table
_fs = require 'moonbuild._fs'
_cmd = require 'moonbuild._cmd'
_util = require 'moonbuild._util'
_common = require 'moonbuild._common'
_ = {}
for k, lib in pairs {:_fs, :_cmd, :_util, :_common}
_[k] = lib
for n in *lib!
_[n] = lib[n]
_

@ -0,0 +1,21 @@
import parseargs, escape from require 'moonbuild._cmd.common'
ok, cmd, backend = false, nil, nil
unless ok
ok, cmd = pcall -> require 'moonbuild._cmd.posix'
backend = 'posix'
unless ok
ok, cmd = pcall -> require 'moonbuild._cmd.lua'
backend = 'lua'
error "unable to load any cmd library, tried luaposix and posix commands" unless ok
-- from the backend
cmd = {k, v for k, v in pairs cmd}
cmd.backend = backend
-- common cmd function
cmd.parseargs = parseargs
cmd.escape = escape
-- the library itself
setmetatable cmd, __call: => {'cmd', 'cmdrst', 'sh'}

@ -0,0 +1,121 @@
import gsub, sub, match from string
import concat from table
specialchars =
'\"': '\\\"'
'\\': '\\\\'
'\'': '\\\''
'\n': '\\n'
'\r': '\\r'
'\t': '\\t'
replacespecialchar = (c) -> specialchars[c] or c
escape = (arg) ->
return arg if match arg, "^[a-zA-Z0-9_.-]+$"
'"'..(gsub arg, "([\"\\\n\r\t])", replacespecialchar)..'"'
parseargs = (argstr) ->
state = 'normal'
current, ci = {}, 1
args, ai = {}, 1
c = nil
i = 0
running = true
add = ->
current[ci], ci = c, ci+1
push = ->
args[ai], ai, current, ci = (concat current), ai+1, {}, 1 if ci!=1
addv = (v) ->
current[ci], ci = v, ci+1
fail = (msg) ->
error "failed to parse: #{msg} in state #{state} at pos #{i}", 2
finish = ->
running = false
EOF = ''
while running
i += 1
c = sub argstr, i, i
switch state
when 'normal'
switch c
when '\"'
state = 'doublequote'
when '\''
state = 'singlequote'
when ' '
push!
when '\n'
push!
when '\t'
push!
when '\\'
state = 'backslashnormal'
when EOF
push!
finish!
else
add!
when 'doublequote'
switch c
when '\"'
state = 'normal'
when '\\'
state = 'backslashdoublequote'
when EOF
fail "unexpected EOF"
else
add!
when 'singlequote'
switch c
when '\''
state = 'normal'
when EOF
fail "unexpected EOF"
else
add!
when 'backslashnormal'
switch c
when '\n'
state = 'normal'
when EOF
fail "unexpected EOF"
else
add!
state = 'normal'
when 'backslashdoublequote'
switch c
when '$'
add!
state = 'doublequote'
when '`'
add!
state = 'doublequote'
when '\"'
add!
state = 'doublequote'
when '\\'
add!
state = 'doublequote'
when '\n'
state = 'doublequote'
when EOF
fail "unexpected EOF"
else
addv '\\'
add!
state = 'doublequote'
args
{
:escape
:parseargs
}

@ -0,0 +1,29 @@
import escape from require 'moonbuild._cmd.common'
import flatten from require 'moonbuild._common'
import execute from require 'moonbuild.compat.execute'
import popen from io
import concat from table
cmdline = (...) ->
concat [escape arg for arg in *flatten ...], ' '
cmd = (...) ->
ok, ret, code = execute cmdline ...
error "command #{first ...} exited with #{code} (#{ret})" unless ok
cmdrst = (...) ->
fd, err = popen cmdline ...
error err unless fd
data = fd\read '*a'
fd\close!
data
sh = (cli) ->
ok, ret, code = execute cli
error "command '#{cli}' exited with #{code} (#{ret})" unless ok
{
:cmd
:cmdrst
:sh
}

@ -0,0 +1,46 @@
import spawn from require 'posix'
import fork, execp, pipe, dup2, _exit, close from require 'posix.unistd'
import fdopen from require 'posix.stdio'
import wait from require 'posix.sys.wait'
import flatten, first from require 'moonbuild._common'
import remove from table
cmd = (...) ->
code, ty = spawn flatten ...
error "command #{first ...} #{ty} with code #{code}" if ty!='exited' or code!=0
cmdrst = (...) ->
rd, wr = pipe!
pid, err = fork!
if pid == 0
dup2 wr, 1
close rd
args = flatten ...
c = remove args, 1
execp c, args
return _exit 1
if pid == nil
close rd
close wr
error "command #{first ...} failed to start: couldn't fork(): #{err}"
close wr
fd = fdopen rd, 'r'
data = fd\read '*a'
fd\close!
close rd
_, ty, code = wait pid
error "command #{first ...} #{ty} with code #{code}" if ty!='exited' or code!=0
data
sh = (cli) ->
cmd 'sh', '-c', cli
{
:cmd
:cmdrst
:sh
}

@ -0,0 +1,162 @@
import sort, concat from table
import huge from math
import match, sub from string
common = {}
flatten = (list, ...) ->
return flatten {list, ...} if (select '#', ...)!=0
t = type list
switch t
when 'nil'
{}
when 'string'
{list}
when 'number'
{tostring list}
when 'boolean'
{list}
when 'table'
keys = [k for k in pairs list]
sort keys
elements, i = {}, 1
for k in *keys
if (type k)=='number'
for e in *(flatten list[k])
elements[i], i = e, i+1
else
return {list}
setmetatable elements, __tostring: => concat @, ' '
else
error "can't flatten elements of type #{t}"
first = (list, ...) ->
t = type list
switch t
when 'nil'
if (select '#', ...)==0
nil
else
first ...
when 'string'
list
when 'number'
tostring list
when 'boolean'
list
when 'table'
min = huge
for k in pairs list
if (type k) == 'number'
min = k if k < min
else
return list
first list[min]
else
error "can't find first of type #{t}"
foreach = (list, fn) ->
[fn v for v in *flatten list]
filter = (list, fn) ->
[v for v in *flatten list when fn v]
includes = (list, v) ->
return true if list==v
if (type list) == 'table'
for k, e in pairs list
if (type k) == 'number'
return true if includes e, v
if (type list) == 'number'
return (tostring list) == (tostring v)
false
patget = (s, pat) ->
prefix, suffix = match pat, '^(.*)%%(.*)$'
return s==pat and s or nil unless prefix
if (sub s, 1, #prefix)==prefix and (suffix == '' or (sub s, -#suffix)==suffix)
sub s, #prefix+1, -#suffix-1
else
nil
patset = (s, rep) ->
prefix, suffix = match rep, '^(.*)%%(.*)$'
if prefix
prefix..s..suffix
else
rep
patsubst = (s, pat, rep) ->
prefix, suffix = match pat, '^(.*)%%(.*)$'
rprefix, rsuffix = match rep, '^(.*)%%(.*)$'
t = type s
f = false
if t=='nil'
return nil
if t=='number'
t = 'string'
s = tostring s
if t=='string'
t = 'table'
s = {s}
f = true
if t!='table'
error "can't substitute patterns on type #{t}"
r, i = {}, 1
for s in *flatten s
if not prefix
if s==pat
if rprefix
r[i], i = rprefix..s..rsuffix, i+1
else
r[i], i = rep, i+1
elseif (sub s, 1, #prefix)==prefix and (suffix == '' or (sub s, -#suffix)==suffix)
if rprefix
r[i], i = rprefix..(sub s, #prefix+1, -#suffix-1)..rsuffix, i+1
else
r[i], i = rep, i+1
f and r[1] or r
exclude = (list, ...) ->
exclusions = flatten ...
[v for v in *flatten list when not includes exclusions, v]
min = (list) ->
m = list[1]
for i=2, #list
e = list[i]
m = e if e<m
m
max = (list) ->
m = list[1]
for i=2, #list
e = list[i]
m = e if e>m
m
minmax = (list) ->
m = list[1]
M = list[1]
for i=2, #list
e = list[i]
m = e if e<m
M = e if e>M
m, M
common.flatten = flatten
common.first = first
common.foreach = foreach
common.filter = filter
common.includes = includes
common.patget = patget
common.patset = patset
common.patsubst = patsubst
common.min = min
common.max = max
common.minmax = minmax
setmetatable common, __call: => [k for k in pairs common]

@ -0,0 +1,198 @@
import remove, concat from table
import gmatch, match, gsub, sub from string
-- load backend
ok, fs, backend = false, nil, nil
unless ok
ok, fs = pcall -> require 'moonbuild._fs.posix'
backend = 'posix'
unless ok
ok, fs = pcall -> require 'moonbuild._fs.lfs'
backend = 'lfs'
unless ok
ok, fs = pcall -> require 'moonbuild._fs.cmd'
backend = 'cmd'
error "unable to load any fs library, tried luaposix, luafilesystem and posix commands" unless ok
-- caching mechanism
DISABLED = ( -> DISABLED )
NIL = ( -> NIL )
cacheenabled = true
caches = {}
clearcache = ->
v.clearcache! for k, v in pairs caches
clearentry = (entry) ->
v.clearentry entry for k, v in pairs caches
disableentry = (entry) ->
v.disableentry entry for k, v in pairs caches
disablecache = ->
cacheenabled = false
enablecache = ->
cacheenabled = true
withcache = (fn) ->
opts = {}
opts.cache = {}
opts.clearcache = ->
opts.cache = {}
opts.clearentry = (entry) ->
opts.cache[entry] = nil
opts.disableentry = (entry) ->
opts.cache[entry] = DISABLED
caches[fn] = opts
setmetatable opts,
__call: (arg) =>
return fn arg unless cacheenabled
cached = opts.cache[arg]
return fn arg if cached == DISABLED
return nil if cached == NIL
return cached if cached != nil
cached = fn arg
opts.cache[arg] = cached
opts.cache[arg] = NIL if cached == nil
return cached
fs = {
dir: withcache fs.dir
attributes: withcache fs.attributes
mkdir: fs.mkdir
}
import attributes, dir, mkdir from fs
-- actual functions
normalizepath = (file) ->
parts = [part for part in gmatch file, '[^/]+']
absolute = (sub file, 1, 1)=='/'
i = 1
while i<=#parts
if parts[i]=='.'
remove parts, i
continue
if parts[i]=='..' and i!=1 and parts[i-1]!='..'
remove parts, i
remove parts, i-1
i -= 1
continue
i += 1
if #parts==0
absolute and '/' or '.'
else
(absolute and '/' or '') .. concat parts, '/'
ls = (d) ->
[f for f in *dir normalizepath d when f!='.' and f!='..']
lswithpath = (d) ->
return ls '.' if d==''
[d..'/'..f for f in *dir normalizepath d when f!='.' and f!='..']
matchglob = (str, glob) ->
glob = gsub glob, '[%[%]%%+.?-]', => '%'..@
patt = '^'..(gsub glob, '%*%*?', => @=='**' and '.*' or '[^/]*')..'$'
rst = if (type str)=='table'
results, i = {}, 1
for s in *str
rst = (match s, patt) and s
results[i], i = rst, i+1 if rst
results
else
(match str, patt) and str
rst
exists = (f) ->
(attributes normalizepath f) != nil
isdir = (f) ->
((attributes normalizepath f) or {}).mode == 'directory'
wildcard = (glob) ->
parts = [part for part in gmatch glob, '[^/]+']
absolute = (sub glob, 1, 1)=='/'
for i, part in ipairs parts
prevpath = (absolute and '/' or '') .. concat parts, '/', 1, i-1
currpath = (i==1 and '' or (prevpath .. '/')) .. part
if match part, '%*%*.*%*%*'
error "Two '**' in the same path component in a wildcard"
if match part, '%*%*'
prefix = match currpath, '^(.*)%*%*'
suffix = (match part, '%*%*(.*)$') .. (i==#parts and '' or ('/'..concat parts, '/', i+1, #parts))
return {} unless exists prevpath
files = lswithpath prevpath
results, ri = {}, 1
for file in *files
if matchglob file, currpath
if i==#parts
results[ri], ri = file, ri+1
elseif isdir file
for result in *wildcard file .. '/' .. concat parts, '/', i+1, #parts
results[ri], ri = result, ri+1
if (matchglob file, prefix..'**') and isdir file
for result in *wildcard file .. '/**' .. suffix
results[ri], ri = result, ri+1
return results
if match part, '%*'
return {} unless exists prevpath
files = lswithpath prevpath
if i==#parts
return matchglob files, glob
results, ri = {}, 1
for file in *files
if (matchglob file, currpath) and isdir file
for result in *wildcard file .. '/' .. concat parts, '/', i+1, #parts
results[ri], ri = result, ri+1
return results
if exists glob
return {glob}
else
return {}
parent = (file) ->
normalizepath file..'/..'
actualmkdir = mkdir
mkdir = (dir) ->
actualmkdir dir
clearentry parent dir
mkdirs = (dir) ->
return if isdir dir
error "Can't mkdirs #{dir}: file exists" if exists dir
mkdirs parent dir
mkdir dir
-- from the backend
fs = {k, withcache fn for k, fn in pairs fs}
-- own functions
fs.normalizepath = normalizepath
fs.ls = ls
fs.lswithpath = lswithpath
fs.matchglob = matchglob
fs.exists = exists
fs.isdir = isdir
fs.wildcard = wildcard
fs.parent = parent
fs.mkdir = mkdir
fs.mkdirs = mkdirs
-- cache and backend
fs.clearcache = clearcache
fs.clearentry = clearentry
fs.disableentry = disableentry
fs.disablecache = disablecache
fs.enablecache = enablecache
fs.backend = backend
-- the library itself
setmetatable fs, __call: => {'dir', 'ls', 'normalizepath', 'exists', 'isdir', 'wildcard', 'mkdir', 'mkdirs'}

@ -0,0 +1,54 @@
import escape from require 'moonbuild._cmd'
import execute from require 'moonbuild.compat.execute'
import popen from io
import gmatch, match, sub from string
error "commands ls and stat aren't available" unless (execute "which ls >/dev/null 2>&1") and (execute "which stat >/dev/null 2>&1")
{
dir: (path) ->
[file for file in (popen "ls -1 #{escape path}")\lines!]
attributes: (path) ->
fd = popen "stat -c '%d %i %A %h %u %g %s %b %t %T %X %Y %Z' #{escape path}"
stat = [part for part in gmatch (fd\read '*a'), "%S+"]
fd\close!
fd = popen "stat -f -c '%S' #{escape path}"
blksize = match (fd\read '*a'), '%S+'
fd\close!
{
dev: tonumber stat[1]
ino: tonumber stat[2]
nlink: tonumber stat[4]
uid: tonumber stat[5]
gid: tonumber stat[6]
size: tonumber stat[7]
blocks: tonumber stat[8]
blksize: tonumber blksize
access: tonumber stat[11]
modification: tonumber stat[12]
change: tonumber stat[13]
permissions: do
sub stat[3], 2
mode: do
switch sub stat[3], 1, 1
when '-' then 'file'
when 'd' then 'directory'
when 'l' then 'link'
when 's' then 'socket'
when 'p' then 'named pipe'
when 'c' then 'char device'
when 'b' then 'block device'
else 'other'
rdev: do
(tonumber stat[9]) * 256 + (tonumber stat[10])
}
mkdir: (path) ->
error "Mkdir #{path} failed" unless execute "mkdir #{escape path}"
}

@ -0,0 +1,12 @@
import dir, attributes, mkdir from require 'lfs'
{
dir: (path) ->
[v for v in dir path]
attributes: attributes
mkdir: (path) ->
ok, err = mkdir path
error "Failed to mkdir #{path}: #{err}" unless ok
}

@ -0,0 +1,68 @@
import dir from require 'posix.dirent'
import stat, mkdir, S_IFMT, S_IFBLK, S_IFCHR, S_IFDIR, S_IFIFO, S_IFLINK, S_IFREG, S_IFSOCK from require 'posix.sys.stat'
import band, btest from require 'moonbuild.compat.bit'
import concat from table
{
dir: dir
attributes: (path) ->
st = stat path
return nil unless st
mode = st.st_mode
{
mode: do
ty = band mode, S_IFMT
switch ty
when S_IFREG then 'file'
when S_IFDIR then 'directory'
when S_IFLINK then 'link'
when S_IFSOCK then 'socket'
when S_IFIFO then 'named pipe'
when S_IFCHR then 'char device'
when S_IFBLK then 'block device'
else 'other'
permissions: do
_suid = btest mode, 2048
_sgid = btest mode, 1024
_stic = btest mode, 512
_ur = btest mode, 256
_uw = btest mode, 128
_ux = btest mode, 64
_gr = btest mode, 32
_gw = btest mode, 16
_gx = btest mode, 8
_or = btest mode, 4
_ow = btest mode, 2
_ox = btest mode, 1
concat {
_ur and 'r' or '-'
_uw and 'w' or '-'
_suid and 's' or (_ux and 'x' or '-')
_gr and 'r' or '-'
_gw and 'w' or '-'
_sgid and 's' or (_gx and 'x' or '-')
_or and 'r' or '-'
_ow and 'w' or '-'
_stic and 't' or (_ox and 'x' or '-')
}
dev: st.st_dev
ino: st.st_ino
nlink: st.st_nlink
uid: st.st_uid
gid: st.st_gid
rdev: st.st_rdev
access: st.st_atime
modification: st.st_mtime
change: st.st_ctime
size: st.st_size
blocks: st.st_blocks
blksize: st.st_blksize
}
mkdir: (path) ->
ok, err = mkdir path
error "Failed to mkdir #{path}: #{err}" unless ok
}

@ -0,0 +1,47 @@
import to_lua from require 'moonscript.base'
import parseargs, cmdrst from require 'moonbuild._cmd'
import gmatch, match, gsub from string
import open from io
util = {}
_pkgconfig = (mode, ...) ->
parseargs cmdrst 'pkg-config', "--#{mode}", ...
pkgconfig = setmetatable {}, __index: (mode) => (...) -> _pkgconfig mode, ...
_cdeps = (cc, cflags, path) ->
raw = cmdrst cc, cflags, '-M', path
rawlist = gsub (match raw, ':(.+)'), '\\\n', ' '
[v for v in gmatch rawlist, '%S+']
cdeps = setmetatable {},
__index: (cc) => (cflags, path) -> _cdeps cc, cflags, path
__call: (cflags, path) => _cdeps 'cc', cflags, path
readfile = (filename) ->
fd, err = open filename, 'rb'
error err unless fd
data, err = fd\read '*a'
error err unless data
fd\close!
data
writefile = (filename, data) ->
fd, err = open filename, 'wb'
error err unless fd
ok, err = fd\write data
error err unless ok
fd\close!
nil
moonc = (infile, outfile) ->
code, err = to_lua readfile infile
error "Failed to compile #{@infile}: #{err}" unless code
writefile outfile, code
util.pkgconfig = pkgconfig
util.cdeps = cdeps
util.readfile = readfile
util.writefile = writefile
util.moonc = moonc
setmetatable util, __call: => [k for k in pairs util]

@ -0,0 +1,91 @@
loadstring = loadstring or load
import floor, ceil, pow from math
band = loadstring [[local a, b = ...; return a & b ]]
bor = loadstring [[local a, b = ...; return a | b ]]
bxor = loadstring [[local a, b = ...; return a ~ b ]]
bnot = loadstring [[local a = ...; return ~a ]]
shl = loadstring [[local a, b = ...; return a << b]]
shr = loadstring [[local a, b = ...; return a >> b]]
unless band
_checkint = (n) ->
if n%1 == 0
n
else
error "not an int"
_shl = (a, b) ->
a * pow(2, b)
_shr = (a, b) ->
v = a / pow(2, b)
if v<0
ceil v
else
floor v
_shr1 = (n) ->
n /= 2
if n<0
ceil v
else
floor v
_band = (a, b) ->
v = 0
n = 1
for i=0, 63
if a%2 == 1 and b%2 == 1
v += n
if i!=63
a = _shr1 a
b = _shr1 b
n *= 2
v
_bor = (a, b) ->
v = 0
n = 1
for i=0, 63
if a%2 == 1 or b%2 == 1
v += n
if i!=63
a = _shr1 a
b = _shr1 b
n *= 2
v
_bxor = (a, b) ->
v = 0
n = 1
for i=0, 63
if a%2 != b%2
v += n
if i!=63
a = _shr1 a
b = _shr1 b
n *= 2
v
_bnot = (a) ->
v = 0
n = 1
for i=0, 63
if a%2 == 0
v += n
if i!=63
a = _shr1 a
n *= 2
v
band = (a, b) -> _band (_checkint a), (_checkint b)
bor = (a, b) -> _bor (_checkint a), (_checkint b)
bxor = (a, b) -> _bxor (_checkint a), (_checkint b)
bnot = (a) -> _bnot (_checkint a)
shl = (a, b) -> _shl (_checkint a), (_checkint b)
shr = (a, b) -> _shr (_checkint a), (_checkint b)
btest = (a, b) -> (band a, b) == b
{ :band, :bor, :bxor, :bnot, :shl, :shr, :btest }

@ -0,0 +1,29 @@
pcall = require 'moonbuild.compat.pcall'
runwithcontext = if setfenv
(fn, ctx, ...) ->
env = getfenv fn
setfenv fn, ctx
local data, ndata, ok
acc = (succ, ...) ->
ok = succ
if succ
data = {...}
ndata = select '#', ...
else
data = ...
acc pcall fn, ...
setfenv fn, env
if ok
unpack data, 1, ndata
else
error data
else
import dump from string
(fn, ctx, ...) ->
code = dump fn, false
fn = load code, 'runwithcontext', 'b', ctx
fn ...
{ :runwithcontext }

@ -0,0 +1,10 @@
import execute from os
{
execute: (cmd) ->
a, b, c = execute cmd
if (type a) == 'boolean'
a, b, c
else
a==0 or nil, 'exit', a
}

@ -0,0 +1,12 @@
pcall = _G.pcall
unpack = _G.unpack or table.unpack
testfn = (a, b) -> a == b and a == 1 and true or error!
testok, testrst = pcall testfn, 1, 1
unless testok and testrst
realpcall = pcall
pcall = (fn, ...) ->
args = { n: (select '#', ...), ... }
realpcall -> fn unpack args, 1, args.n
pcall

@ -0,0 +1,38 @@
import runwithcontext from require 'moonbuild.compat.ctx'
topenv = require 'moonbuild.env.top'
initenv = require 'moonbuild.env.init'
import includes from require 'moonbuild._common'
import insert from table
class Context
new: =>
@targets = {}
@defaulttargets = {}
@variables = {}
@inits = {}
addvar: (var) =>
@variables[var.name] = var
addinit: (fn) =>
insert @inits, fn
addtarget: (target) =>
insert @targets, target
resetexecuted: =>
@executedtargets = {}
adddefault: (target) =>
error "not a target of the current context: #{target}" unless includes @targets, target
error "not a named target" unless (type target.name) == 'string'
insert @defaulttargets, target.name
load: (code, overrides) =>
runwithcontext code, (topenv @, overrides)
init: =>
if @inits[1]
env = (initenv @)
for init in *@inits
runwithcontext code, env

@ -0,0 +1,182 @@
import filter, foreach, flatten, patsubst from require 'moonbuild._common'
import runwithcontext from require 'moonbuild.compat.ctx'
globalenv = require 'moonbuild.env.global'
import exists, parent, mkdirs, clearentry, disableentry, attributes from require 'moonbuild._fs'
import sort from table
import huge from math
local DepNode, FileTarget
nodepriority = (a, b) ->
ta = type a.name
tb = type b.name
da = #a.deps
db = #b.deps
if ta=='string' and tb!='string'
return true
elseif ta!='string' and tb=='string'
return false
elseif a.priority > b.priority
return true
elseif a.priority < b.priority
return false
else
return da < db
transclosure = (obj, prop) ->
elems = {}
i = 1
set = {}
imp = (e) ->
for v in *e[prop]
if not set[v]
elems[i], i = v, i+1
set[v] = i
imp v
imp obj
elems
mtime = (path) ->
attr = attributes path
attr and attr.modification
class DepGraph
new: (@ctx, names={}) =>
@nodes = {}
@env = globalenv @ctx
for name in *names
@addnode name
addnode: (name) =>
return if @nodes[name]
elected = @resolvedeps name
@nodes[name] = elected
for dep in *(transclosure elected, 'deps')
@nodes[dep.name] = dep
dep.deps = nil
elected.deps = nil
resolvedeps: (name) =>
do
node = @nodes[name]
return node, {} if node
candidates = filter {@ctx.targets, FileTarget!}, (target) -> target\matches name
nodes = foreach candidates, (candidate) -> a: {pcall -> DepNode @, candidate, name}
resolved = foreach (filter nodes, (node) -> node.a[1]), (node) -> node.a[2]
sort resolved, nodepriority
resolved[1] or error "Cannot resolve target #{name}"
buildablenodes: =>
[v for k, v in pairs @nodes when v\canbuild! and not v.built]
class DepNode
new: (@dag, target, @name) =>
@priority = target.priority
@buildfunctions = target.buildfunctions
@mkdirs = target._mkdirs
@sync = target._sync
@type = target._type
@outs = foreach target.outfiles, (name) -> patsubst @name, target.pattern, name
@type = 'virtual' if #@outs == 0
@built = false
resolve = (name) -> @dag\resolvedeps patsubst @name, target.pattern, name
after = flatten foreach target.needtargets, resolve
deps = flatten foreach target.infiles, resolve
if #target.depfunctions!=0
ctx = setmetatable {},
__index: (_, k) ->
switch k
when 'infile' then first deps
when 'infiles' then flatten deps
when 'outfile' then first @outs
when 'outfiles' then flatten @outs
when 'name' then @name
else error "No such field in TargetDepsContext: #{k}"
__newindex: (k) =>
error "Attempt to set field #{k} of TargetDepsContext"
for depfn in *target.depfunctions
deps = flatten deps, foreach depfn, (fn) -> resolve runwithcontext fn, @dag.env, ctx
@ins = foreach deps, (dep) -> dep.name
@after = foreach after, (dep) -> dep.name
@deps = flatten { deps, after }
@built = true if #@deps == 0 and #@buildfunctions == 0
canbuild: =>
for node in *flatten { @ins, @after }
if not @dag.nodes[node].built
return false
for file in *@ins
if not exists file
error "Node #{name} has ran all of its parents, but can't run since #{file} doesn't exist"
return true
build: (opts={}) =>
force = opts.force or false
quiet = opts.quiet or false
return if @built
return unless force or @shouldbuild!
print "#{@type == 'virtual' and "Running" or "Building"} #{@name}" unless quiet or #@buildfunctions == 0
@actuallybuild!
shouldbuild: =>
-- targets with no outputs / inputs and virtual targets *NEED* to be built
return true if #@outs == 0 or #@ins == 0 or @type == 'virtual'
-- check min mtime for outputs
minout = huge
for file in *@outs
time = mtime file
-- if an output file is missing, we *NEED* to build it
return true if not time
minout = time if time < minout
-- check max mtime for inputs
maxin = 0
for file in *@ins
time = mtime file
maxin = time if time > maxin
-- if any input file is more recent than any output file, we need to build
maxin > minout
actuallybuild: =>
if @mkdirs
mkdirs parent file for file in *@outs
disableentry file for file in *@outs
ctx = setmetatable {},
__index: (_, k) ->
switch k
when 'infile' then @ins[1]
when 'infiles' then @ins
when 'outfile' then @outs[1]
when 'outfiles' then @outs
when 'name' then @name
else error "No such field in TargetContext: #{k}"
__newindex: (k) =>
error "Attempt to set field #{k} of TargetContext"
for fn in *@buildfunctions
runwithcontext fn, @dag.env, ctx
updatecache: =>
clearentry file for file in *@outs
class FileTarget
new: =>
@priority = -huge
@buildfunctions = {}
@_mkdirs = false
@_sync = false
@_type = 'file'
@needtargets = {}
@infiles = {}
@depfunctions = {}
@outfiles = {'%'}
@pattern = '%'
matches: (name) =>
exists name
DepGraph

@ -0,0 +1,54 @@
import flatten, includes, patget from require 'moonbuild._common'
import insert from table
class Target
new: (@ctx, @name, opts={}) =>
@name = flatten @name if (type @name) != 'string'
@pattern = opts.pattern or ((type @name) == 'string' and @name or '%')
@priority = opts.priority or 0
error "pattern must be a string" unless (type @pattern) == 'string'
error "priority must be an int" unless (type @priority) == 'number' and @priority%1 == 0
@outfiles = {}
@infiles = {}
@needtargets = {}
@depfunctions = {}
@buildfunctions = {}
@_mkdirs = false
@_sync = false
@_type = 'normal'
@public = false
matches: (name) =>
if @name==name
return true
if (includes @name, name) and patget name, @pattern
return true
return false
produces: (...) =>
n = #@outfiles+1
for obj in *flatten ...
@outfiles[n], n = obj, n+1
depends: (...) =>
if (type ...) == 'function'
insert @depfunctions, (...)
else
n = #@infiles+1
for obj in *flatten ...
@infiles[n], n = obj, n+1
after: (...) =>
n = #@needtargets+1
for tgt in *flatten ...
@needtargets[n], n = tgt, n+1
fn: (fn) =>
insert @buildfunctions, fn
sync: =>
@_sync = true
mkdirs: =>
@_mkdirs = true

@ -0,0 +1,11 @@
import flatten from require 'moonbuild._common'
class Variable
@NIL: ->
new: (@name, ...) =>
@public = false
if (select '#', ...) !=1 or (type ...) == 'table'
@value = flatten ...
else
@value = ...

@ -0,0 +1,61 @@
import fork, _exit from require 'posix.unistd'
import wait from require 'posix.sys.wait'
import open, stderr from io
import match from string
class Executor
@getmaxparallel: =>
fd = open '/proc/cpuinfo', 'r'
return 1 unless fd
ncpu = 0
for line in fd\lines!
ncpu += 1 if match line, '^processor%s*:'
fd\close!
ncpu == 0 and 1 or ncpu
new: (@dag, @nparallel) =>
@processes = {}
@nprocesses = 0
@building = {}
execute: (opts) =>
block = @dag\buildablenodes!
while #block != 0
for node in *block
@addprocess node, opts
if @nprocesses == @nparallel
@waitprocess!
block = [node for node in *@dag\buildablenodes! when not @building[node]]
while #block == 0 and @nprocesses != 0
@waitprocess!
block = [node for node in *@dag\buildablenodes! when not @building[node]]
while @nprocesses !=0
@waitprocess!
for name, node in pairs @dag.nodes
error "Node #{name} wasn't built" unless node.built
addprocess: (node, opts) =>
pid = fork!
error "Failed to fork" unless pid
if pid!=0
@processes[pid] = node
@nprocesses += 1
@building[node] = true
else
ok, err = pcall -> node\build opts
if ok
_exit 0
else
stderr\write err
_exit 1
waitprocess: =>
pid, ty, status = wait!
error "Failed to wait" unless pid
error "Failed to build #{@processes[pid].name}" if ty != 'exited' or status != 0
@processes[pid].built = true
@processes[pid]\updatecache!
@processes[pid] = nil
@nprocesses -= 1

@ -0,0 +1,16 @@
class Executor
@getmaxparallel: => 1
new: (@dag, @nparallel) =>
execute: (opts) =>
block = @dag\buildablenodes!
while #block != 0
for node in *block
node\build opts
node\updatecache!
node.built = true
block = @dag\buildablenodes!
for name, node in pairs @dag.nodes
error "Node #{name} wasn't built" unless node.built

@ -0,0 +1,18 @@
_ = require 'moonbuild._'
(ctx) ->
varlayer = setmetatable {},
__index: _G
for name, var in pairs ctx.variables
rawset varlayer, name, var.value
env = setmetatable {},
__index: varlayer
__newindex: (k) => error "attempt to assign to global variable '#{k}', which is disabled in the global env"
rawset env, '_', _
rawset env, '_G', env
rawset env, '_ENV', env
env, varlayer

@ -0,0 +1,32 @@
Target = require 'moonbuild.core.Target'
Variable = require 'moonbuild.core.Variable'
_ = require 'moonbuild._'
import flatten from _
(ctx) ->
varlayer = setmetatable {},
__index: _G
for name, var in pairs ctx.variables
rawset varlayer, name, var.value
env = setmetatable {},
__index: varlayer
__newindex: (k) => error "attempt to assign to global variable '#{k}', use the function 'var' instead"
rawset env, '_', _
rawset env, '_G', env
rawset env, '_ENV', env
rawset env, 'var', (name, ...) ->
var = Variable name, ...
ctx\addvar var
rawset varlayer, name, var.value
var
rawset env, 'target', (name, opts) ->
target = Target ctx, name, opts
ctx\addtarget target
target
env, varlayer

@ -0,0 +1,31 @@
initenv = require 'moonbuild.env.init'
Target = require 'moonbuild.core.Target'
Variable = require 'moonbuild.core.Variable'
(ctx, overrides) ->
env, varlayer = initenv ctx
rawset env, 'default', (target) ->
ctx\adddefault target
target
rawset env, 'public', (e) ->
clazz = ((getmetatable e) or {}).__class
if clazz == Target
e.public = true
elseif clazz == Variable
e.public = true
override = overrides[e.name]
if override
override = nil if override == Variable.NIL
e.value = override
rawset varlayer, e.name, override
else
error "cannot set an object of type #{clazz and clazz.__name or type e} public"
e
rawset env, 'init', (fn) ->
error "you can only add functions to init" unless (type fn) == 'function'
ctx\addinit fn
env, varlayer

@ -1,52 +0,0 @@
import attributes, dir from require 'lfs'
unpack or= table.unpack
FROZEN = ->
makecached = (fn) ->
cache = {}
invalidate = (val) ->
cache[val] = nil
freeze = (val) ->
cache[val] = FROZEN
clear = ->
cache = {}
get = (val) ->
if cache == FROZEN
return fn val
cached = cache[val]
if cached!=FROZEN and cached!=nil
return unpack cached
ret = {fn val}
if cached!=FROZEN
cache[val] = ret
unpack ret
enable = ->
cache = {} if cache==FROZEN
disable = ->
cache = FROZEN
setmetatable { :get, :invalidate, :freeze, :clear, :enable, :disable },
__call: (val) => get val
cached = {
attributes: makecached attributes
dir: makecached (file) -> [k for k in dir file]
}
enable = ->
fn\enable! for _, fn in cached
disable = ->
fn\disable! for _, fn in cached
clear = ->
fn\clear! for _, fn in cached
setmetatable { :enable, :disable, :clear }, __index: cached

@ -1,124 +0,0 @@
import dir, attributes, clear, enable, disable from require 'moonbuild.fscache'
import gmatch, match, gsub, sub from string
import insert, remove, concat from table
normalizepath = (file) ->
parts = [part for part in gmatch file, '[^/]+']
absolute = (sub file, 1, 1)=='/'
i = 1
while i<=#parts
if parts[i]=='.'
remove parts, i
continue
if parts[i]=='..' and i!=1 and parts[i-1]!='..'
remove parts, i
remove parts, i-1
i -= 1
continue
i += 1
if #parts==0
absolute and '/' or '.'
else
(absolute and '/' or '') .. concat parts, '/'
ls = (d) ->
[f for f in *dir normalizepath d when f!='.' and f!='..']
lswithpath = (d) ->
return ls '.' if d==''
[d..'/'..f for f in *dir normalizepath d when f!='.' and f!='..']
exists = (f) ->
(attributes normalizepath f) != nil
isdir = (f) ->
a = attributes normalizepath f
a and a.mode == 'directory' or false
mtime = (f) ->
a = attributes normalizepath f
a and a.modification
matchglob = (str, glob) ->
glob = gsub glob, '[%[%]%%+.?-]', => '%'..@
patt = '^'..(gsub glob, '%*%*?', => @=='**' and '.*' or '[^/]*')..'$'
rst = if (type str)=='table'
results, i = {}, 1
for s in *str
rst = (match s, patt) and s
results[i], i = rst, i+1 if rst
results
else
(match str, patt) and str
rst
wildcard = (glob) ->
parts = [part for part in gmatch glob, '[^/]+']
absolute = (sub glob, 1, 1)=='/'
for i, part in ipairs parts
prevpath = (absolute and '/' or '') .. concat parts, '/', 1, i-1
currpath = (i==1 and '' or (prevpath .. '/')) .. part
if match part, '%*%*.*%*%*'
error "Two '**' in the same path component in a wildcard"
if match part, '%*%*'
prefix = match currpath, '^(.*)%*%*'
suffix = (match part, '%*%*(.*)$') .. (i==#parts and '' or ('/'..concat parts, '/', i+1, #parts))
files = lswithpath prevpath
results = {}
for file in *files
if matchglob file, currpath
if i==#parts
insert results, file
elseif isdir file
for result in *wildcard file .. '/' .. concat parts, '/', i+1, #parts
insert results, result
if (matchglob file, prefix..'**') and isdir file
for result in *wildcard file .. '/**' .. suffix
insert results, result
return results
if match part, '%*'
files = lswithpath prevpath
if i==#parts
return matchglob files, glob
results = {}
for file in *files
if (matchglob file, currpath) and isdir file
for result in *wildcard file .. '/' .. concat parts, '/', i+1, #parts
insert results, result
return results
if exists glob
return {glob}
else
return {}
parentdir = (file) ->
normalizepath file..'/..'
freezecache = (file) ->
dir.freeze file
dir.freeze parentdir file
attributes.invalidate file
invalidatecache = (file) ->
dir.invalidate file
dir.invalidate parentdir file
attributes.invalidate file
{
:wildcard
:exists, :isdir
:mtime
:normalizepath, :parentdir
:matchglob
:freezecache, :invalidatecache
clearcache: clear, enablecache: enable, disablecache: disable
}

@ -1,37 +0,0 @@
import match, gmatch, sub from string
import upper, lower from string
GLOB_PATT='^([^%%]*)%%([^%%]*)$'
patsubst = (str, pattern, replacement) ->
return [patsubst s, pattern, replacement for s in *str] if (type str)=='table'
if str==pattern
return replacement
prefix, suffix = match pattern, GLOB_PATT
if not (prefix or suffix)
return str
reprefix, resuffix = match replacement, GLOB_PATT
if not (reprefix or resuffix)
if (#prefix==0 or (sub str, 1, #prefix)==prefix) and (#suffix==0 or (sub str, -#suffix)==suffix)
return replacement
else
return str
if #prefix==0 or (sub str, 1, #prefix)==prefix
str = reprefix..(sub str, #prefix+1)
if #suffix==0 or (sub str, -#suffix)==suffix
str = (sub str, 1, -#suffix-1)..resuffix
str
splitsp = (str) ->
[elem for elem in gmatch str, '%S+']
{
:patsubst
:splitsp
:upper, :lower
}

@ -1,69 +0,0 @@
import insert, remove, concat, sort from table
unpack or= table.unpack
sortedpairs = (table, cmp) ->
keys = [k for k in pairs table]
sort keys, cmp
coroutine.wrap ->
for key in *keys
coroutine.yield key, table[key]
min = (table, cmp=(a, b) -> a<b) ->
val = table[1]
for i=2, #table
elem = table[i]
if cmp elem, val
val = elem
val
max = (table, cmp=(a, b) -> a<b) ->
val = table[1]
for i=2, #table
elem = table[i]
if not cmp elem, val
val = elem
val
foreach = (tab, fn) ->
[fn e for e in *tab]
first = (tab, fn) ->
for e in *tab
return e if fn e
exclude = (tab, ...) ->
i=1
while i<=#tab
removed=false
for j=1, select '#', ...
if tab[i]==select j, ...
remove tab, i
removed = true
break
i += 1 unless removed
tab
flatten = (tab) ->
return {tab} if (type tab)!='table'
out = {}
for e in *tab
if (type e)=='table'
if e[1] == nil and (next e)!=nil
insert out, e
else
insert out, v for v in *flatten e
else
insert out, e
out
{
:min, :max
:foreach
:first
:exclude
:flatten
:sortedpairs
:insert, :remove, :concat, :sort
:unpack
}

@ -1,95 +0,0 @@
import wildcard, exists, isdir, mtime from require 'moonbuild.fsutil'
import foreach, first, flatten, exclude, sortedpairs, min, max from require 'moonbuild.tableutil'
import patsubst, splitsp from require 'moonbuild.stringutil'
import insert, concat, sort, pairs from require 'moonbuild.tableutil'
import upper, lower from require 'moonbuild.stringutil'
GLOB_PATT='^([^%%]*)%%([^%%]*)$'
-- command functions
escapecmdpart= (p) ->
if (type p)=='table'
return p.raw if p.raw
return concat [escapecmdpart part for part in *p], ' '
return p if p\match '^[a-zA-Z0-9_./-]+$'
'"'..p\gsub('\\', '\\\\')\gsub('"', '\\"')..'"'
escapecmd= (c, args={}) ->
c=escapecmdpart c
for a in *flatten args
c..=' '..escapecmdpart a if a
c
run= (c, args, params={}) ->
escaped=escapecmd c, args
print escaped if params.print
ret, _, code=os.execute escaped
ret, code=ret==0, ret if (type ret)=='number'
error "#{c} failed with code #{code}" if params.error and not ret
ret, code
popen= (c, args, mode='r', params={}) ->
escaped=escapecmd c, args
print escaped if params.print
io.popen escaped, mode
calccdeps= (infile, includesys=false) ->
data=(popen 'cc', {includesys and '-M' or '-MM', infile})\read '*a'
rawdeps=data\gsub('\\\n', '')\match ':(.+)'
[dep for dep in rawdeps\gmatch '%S+' when dep!=infile]
findclib= (name, mode='all') ->
args={name}
insert args, '--cflags' if mode=='all' or mode=='cc'
insert args, '--libs' if mode=='all' or mode=='ld'
[arg for arg in (popen 'pkg-config', args)\read('*a')\gmatch '%S+']
-- glob match
match= (str, glob) ->
prefix, suffix=glob\match GLOB_PATT
return str==glob unless prefix
return str\sub #prefix+1, -#suffix-1 if (str\sub 1, #prefix)==prefix and (str\sub -#suffix)==suffix
false
-- is a valid glob
isglob= (glob) ->
return if glob\match GLOB_PATT
true
else
false
-- getenv
env= (key, def) ->
(os.getenv key) or def
{
-- table function
:min, :max
:foreach
:first
:exclude
:flatten
:sortedpairs
:insert, :remove, :concat, :sort
:unpack
-- file functions
:wildcard
:mtime
:exists, :isdir
-- command functions
:run, :popen
:calccdeps, :findclib
-- string functions
:patsubst
:splitsp
:upper, :lower
-- glob functions
:match, :isglob
-- env functions
:env
}

@ -1,23 +0,0 @@
package: moonbuild
source:
url: git://github.com/natnat-mc/moonbuild
description:
summary: Small build system in between make and a build.sh
detailed: >
moonbuild is a small build system that simplifies your
build definitions by allowing you to use declarative as
well as imperative rules.
It represents the build as a DAG with explicit ordering,
and doesn't give you any default confusing rules (unlike
make)
homepage: https://github.com/natnat-mc/moonbuild
dependencies:
- lua >= 5.3
- luafilesystem >= 1.7.0
- argparse >= 0.7.1-1
- moonscript >= 0.5.0-1
build:
type: builtin
install:
bin:
moonbuild: moonbuild.lua

@ -1,111 +0,0 @@
describe 'fsutil', ->
describe 'normalizepath', ->
import normalizepath from require 'moonbuild.fsutil'
test = (expected, source) ->
it "normalizes #{source} correctly", ->
assert.equal expected, normalizepath source
testall = (tab) ->
for a, b in pairs tab
test b, a
describe 'handles already normalized paths', ->
testall {
'.': '.'
'..': '..'
'../..': '../..'
'/': '/'
'/a': '/a'
'/a/b': '/a/b'
'a': 'a'
'a/b': 'a/b'
}
describe 'trims leading slashes', ->
testall {
'a/': 'a'
'a/b/': 'a/b'
'/a/': '/a'
'/a/b/': '/a/b'
}
describe 'normalizes absolute paths', ->
testall {
'/a/a/../b': '/a/b'
'/a/./b': '/a/b'
'/a/b/c/..': '/a/b'
'/./a/./b/././.': '/a/b'
}
describe 'normalizes relative paths', ->
testall {
'../x/../../a': '../../a'
'../x/../a': '../a'
'x/..': '.'
'../.': '..'
'./a': 'a'
}
describe 'matchglob', ->
import matchglob from require 'moonbuild.fsutil'
test = (expected, source, glob) ->
if expected
it "matches #{glob} on #{source}", ->
assert.equal source, matchglob source, glob
else
it "doesn't match #{glob} on #{source}", ->
assert.equal nil, matchglob source, glob
testall = (tab) ->
for a, b in pairs tab
test b, a[1], a[2]
describe 'handles literal names', ->
testall {
[{'a', 'a'}]: true
[{'a.b', 'a.b'}]: true
[{'a/b', 'a/b'}]: true
[{'..', '..'}]: true
}
describe 'doesn\'t treat things as special chars', ->
testall {
[{'a', '.'}]: false
[{'a.b.c', '%S+'}]: false
[{'%S+', '%S+'}]: true
[{'%d', '%d'}]: true
[{'a', '%S'}]: false
[{'aaa', 'a+'}]: false
}
describe 'only matches fully', ->
testall {
[{'abcdef', 'bcde'}]: false
[{'a/b/c', 'b/c'}]: false
[{'a/b/c', 'a/b'}]: false
}
describe 'handles *', ->
testall {
[{'abcde', '*'}]: true
[{'a/b/c/d', 'a/*/c/d'}]: true
[{'a/b/c/d', 'a/*/d'}]: false
[{'abcde', 'a*e'}]: true
[{'abcde', 'a*f'}]: false
[{'a/b/c/d/e', 'a/*/*/*/e'}]: true
[{'a/b/c/d/e', 'a*/*/*e'}]: false
}
describe 'handles **', ->
testall {
[{'abcde', '**'}]: true
[{'a/b/c/d', 'a/**/c/d'}]: true
[{'abcde', 'a**e'}]: true
[{'a/b/c/d/e', 'a/**/**/**/e'}]: true
[{'a/b/c/d/e', 'a**e'}]: true
[{'a/b/c/d/e', 'a/**/e'}]: true
[{'a/b/c/d/e', 'a**f'}]: false
[{'abcde', 'a**f'}]: false
}

@ -1,78 +0,0 @@
describe 'stringutil', ->
describe 'patsubst', ->
import patsubst from require 'moonbuild.stringutil'
test = (expected, source, patt, subst) ->
it "substitutes #{source} into #{expected} with #{patt} and #{subst}", ->
assert.equal expected, patsubst source, patt, subst
testall = (tab) ->
for a, b in pairs tab
test b, a[1], a[2], a[3]
describe 'handles just adding pre/suffix', ->
testall {
[{'a', '%', '_%'}]: '_a'
[{'tx', '%', 'a_%'}]: 'a_tx'
[{'a', '%', '%_'}]: 'a_'
[{'tx', '%', '%_a'}]: 'tx_a'
[{'a', '%', '_%_'}]: '_a_'
}
describe 'handles doing nothing', ->
for str in *({'a', 'aa', 'tx'})
test str, str, '%', '%'
describe 'handles literal change', ->
testall {
[{'a', 'a', 'b'}]: 'b'
[{'a', 'b', 'c'}]: 'a'
[{'aa', 'a', 'b'}]: 'aa'
}
describe 'handles match change', ->
testall {
[{'-a_', '-%_', 'b'}]: 'b'
[{'-a_', '-%', 'b'}]: 'b'
[{'-a_', '%_', 'b'}]: 'b'
[{'-a_', '_%-', 'b'}]: '-a_'
}
describe 'handles just removing pre/suffix', ->
testall {
[{'_a', '_%', '%'}]: 'a'
[{'a_', '%_', '%'}]: 'a'
[{'_a_', '_%_', '%'}]: 'a'
}
describe 'handles not matching', ->
testall {
[{'a-', '%_', '%'}]: 'a-'
[{'-a', '_%', '%'}]: '-a'
[{'-a-', '_%_', '%'}]: '-a-'
}
describe 'handles changing pre/suffix', ->
testall {
[{'a-', '%-', '%_'}]: 'a_'
[{'-a', '-%', '_%'}]: '_a'
[{'-a', '-%', '%_'}]: 'a_'
[{'_a-', '_%-', '-%_'}]: '-a_'
}
describe 'splitsp', ->
import splitsp from require 'moonbuild.stringutil'
test = (expected, source) ->
it "splits '#{source}' correctly", ->
assert.same expected, splitsp source
for source, expected in pairs {
'a b c': {'a', 'b', 'c'}
'abc': {'abc'}
'': {}
' a b c': {'a', 'b', 'c'}
' ': {}
' ab c': {'ab', 'c'}
}
test expected, source

@ -1,82 +0,0 @@
describe 'tableutil', ->
describe 'sortedpairs', ->
import sortedpairs from require 'moonbuild.tableutil'
for src, dst in pairs {
[{a: '1', c: 2, b: 3}]: {'a', '1', 'b', 3, 'c', 2}
[{5, 4, 3}]: {1, 5, 2, 4, 3, 3}
}
it "works for #{src}", ->
i = 1
for k, v in sortedpairs src
assert.equal k, dst[i]
assert.equal v, dst[i+1]
i += 2
describe 'min and max', ->
import min, max from require 'moonbuild.tableutil'
for src, dst in pairs {
[{1, 2, 3, 4, 5}]: {1, 5}
[{5, 4, 3, 2, 1}]: {1, 5}
[{2, 4, 5, 1, 3}]: {1, 5}
[{1, 1, 1, 1, 1}]: {1, 1}
[{1}]: {1, 1}
}
it "min of #{table.concat src, ','} is #{dst[1]}", ->
assert.equal dst[1], min src
it "max of #{table.concat src, ','} is #{dst[2]}", ->
assert.equal dst[2], max src
describe 'foreach', ->
import foreach from require 'moonbuild.tableutil'
src = {1, 2, 5, '79'}
testall = (name, rst, fn) ->
it name, ->
assert.same rst, foreach src, fn
testall 'works with tostring', {'1', '2', '5', '79'}, tostring
testall 'works with tonumber', {1, 2, 5, 79}, tonumber
testall 'works with some mix of tonumber and comparison', {false, false, true, true}, => 3<tonumber @
describe 'first', ->
import first from require 'moonbuild.tableutil'
test = (name, src, rst, fn) ->
it name, ->
assert.equal rst, (first src, fn)
test 'works with == for first of list', {1, 3, 5}, 1, => @==1
test 'works with == for something else', {1, 3, 5}, 3, => @==3
test 'works with == for absent element', {1, 3, 5}, nil, => @==2
describe 'exclude', ->
import exclude from require 'moonbuild.tableutil'
unpack or= table.unpack
test = (name, src, rst, ...) ->
rest = {...}
it name, ->
assert.equal src, exclude src, unpack rest
assert.same rst, src
test 'works with nothing', {1, 2, 3}, {1, 2, 3}
test 'works with absent elements', {1, 2, 3}, {1, 2, 3}, 4, 5, 6
test 'works with some elements', {1, 2, 3}, {1}, 2, 3
test 'works with all elements', {1, 2, 3}, {}, 1, 2, 3
test 'works with a mix', {1, 2, 3}, {1, 3}, 2, 4, 5
describe 'flatten', ->
import flatten from require 'moonbuild.tableutil'
test = (name, src, rst) ->
it name, ->
assert.same rst, flatten src
test 'works with empty table', {}, {}
test 'works with flat table', {1, 2, 3}, {1, 2, 3}
test 'works with one level', {1, {2}, {3}}, {1, 2, 3}
test 'works with multiple levels', {{{1, {{2}}}, 3}}, {1, 2, 3}
test 'skips maps', {1, {a: 2}, 3}, {1, {a: 2}, 3}
Loading…
Cancel
Save