mirror of
https://github.com/natnat-mc/moonbuild
synced 2026-05-12 20:01:13 +02:00
v2
This commit is contained in:
+2
-2
@@ -1,2 +1,2 @@
|
||||
/moonbuild/*.lua
|
||||
/bin/*.lua
|
||||
*.lua
|
||||
/out/*
|
||||
|
||||
-11
@@ -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"
|
||||
+40
-28
@@ -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 var 'MOONC', 'moonc'
|
||||
public var 'AMALG', 'amalg.lua'
|
||||
public var 'RM', 'rm', '-f', '--'
|
||||
public var 'LUA', 'lua5.3'
|
||||
|
||||
public target 'clean', fn: =>
|
||||
-rm '-f', OUT_LUA, BINARY_LUA
|
||||
var 'LIB_SRC', _.wildcard 'moonbuild/**.moon'
|
||||
var 'BIN_SRC', _.wildcard 'bin/*.moon'
|
||||
|
||||
public target 'info', fn: =>
|
||||
#echo "Moonscript sources:", SOURCES_MOON
|
||||
#echo "Compiled lua:", OUT_LUA
|
||||
var 'LIB_LUA', _.patsubst LIB_SRC, '%.moon', '%.lua'
|
||||
var 'BIN_LUA', _.patsubst BIN_SRC, '%.moon', '%.lua'
|
||||
var 'BIN', _.patsubst BIN_LUA, 'bin/%.lua', 'out/%'
|
||||
|
||||
public target 'compile', deps: OUT_AMALG
|
||||
var 'MODULES', _.foreach (_.patsubst LIB_LUA, '%.lua', '%'), => @gsub '/', '.'
|
||||
|
||||
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
|
||||
with public default target 'all'
|
||||
\after 'bin'
|
||||
\after 'lib'
|
||||
|
||||
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
|
||||
with public target 'clean'
|
||||
\fn => _.cmd RM, LIB_LUA
|
||||
\fn => _.cmd RM, BIN_LUA
|
||||
|
||||
target '%.lua', in: '%.moon', out: '%.lua', fn: =>
|
||||
-moonc @infile
|
||||
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
|
||||
|
||||
Executable → Regular
+77
-199
@@ -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'
|
||||
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
|
||||
|
||||
util = require 'moonbuild.util'
|
||||
import freezecache, invalidatecache from require 'moonbuild.fsutil'
|
||||
import exists, mtime, run, min, max, first, flatten, match, patsubst, sortedpairs from util
|
||||
-- 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!
|
||||
|
||||
import insert, concat from table
|
||||
|
||||
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, ...
|
||||
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]
|
||||
|
||||
-- command object
|
||||
-- represents a command that can be called
|
||||
class Command
|
||||
new: (@cmd, ...) =>
|
||||
@args = {...}
|
||||
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
|
||||
|
||||
__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, ', '})"
|
||||
|
||||
new: (@name, @outs={}, @ins={}, @deps={}, @fn= =>) =>
|
||||
@skip = false
|
||||
error "Duplicate build name #{@name}" if all[@name]
|
||||
all[@name] = @
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
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
|
||||
-- initialize the buildfile further
|
||||
ctx\init!
|
||||
print "Initialized buildfile" if args.verbose
|
||||
|
||||
buildtargets!
|
||||
-- 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
|
||||
|
||||
-1211
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
|
||||
Vendored
+18
@@ -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
|
||||
Vendored
+32
@@ -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
|
||||
Vendored
+31
@@ -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}
|
||||
Reference in New Issue
Block a user