From c8670ab90382af45217cc5623b6f58e863d74943 Mon Sep 17 00:00:00 2001 From: Codinget Date: Sat, 7 Nov 2020 21:01:26 +0100 Subject: [PATCH] v2 --- .gitignore | 4 +- Alfons.moon | 11 - Build.moon | 80 +- Makefile | 39 + README.md | 132 +-- bin/moonbuild.moon | 282 ++--- moonbuild.lua | 1211 --------------------- moonbuild/_.moon | 15 + moonbuild/_cmd.moon | 21 + moonbuild/_cmd/common.moon | 121 ++ moonbuild/_cmd/lua.moon | 29 + moonbuild/_cmd/posix.moon | 46 + moonbuild/_common.moon | 162 +++ moonbuild/_fs.moon | 198 ++++ moonbuild/_fs/cmd.moon | 54 + moonbuild/_fs/lfs.moon | 12 + moonbuild/_fs/posix.moon | 68 ++ moonbuild/_util.moon | 47 + moonbuild/compat/bit.moon | 91 ++ moonbuild/compat/ctx.moon | 29 + moonbuild/compat/execute.moon | 10 + moonbuild/compat/pcall.moon | 12 + moonbuild/context.moon | 38 + moonbuild/core/DAG.moon | 182 ++++ moonbuild/core/Target.moon | 54 + moonbuild/core/Variable.moon | 11 + moonbuild/core/multiprocessexecutor.moon | 61 ++ moonbuild/core/singleprocessexecutor.moon | 16 + moonbuild/env/global.moon | 18 + moonbuild/env/init.moon | 32 + moonbuild/env/top.moon | 31 + moonbuild/fscache.moon | 52 - moonbuild/fsutil.moon | 124 --- moonbuild/stringutil.moon | 37 - moonbuild/tableutil.moon | 69 -- moonbuild/util.moon | 95 -- rock.yml | 23 - spec/fsutil_spec.moon | 111 -- spec/stringutil_spec.moon | 78 -- spec/tableutil_spec.moon | 82 -- 40 files changed, 1549 insertions(+), 2239 deletions(-) delete mode 100644 Alfons.moon create mode 100644 Makefile mode change 100755 => 100644 bin/moonbuild.moon delete mode 100644 moonbuild.lua create mode 100644 moonbuild/_.moon create mode 100644 moonbuild/_cmd.moon create mode 100644 moonbuild/_cmd/common.moon create mode 100644 moonbuild/_cmd/lua.moon create mode 100644 moonbuild/_cmd/posix.moon create mode 100644 moonbuild/_common.moon create mode 100644 moonbuild/_fs.moon create mode 100644 moonbuild/_fs/cmd.moon create mode 100644 moonbuild/_fs/lfs.moon create mode 100644 moonbuild/_fs/posix.moon create mode 100644 moonbuild/_util.moon create mode 100644 moonbuild/compat/bit.moon create mode 100644 moonbuild/compat/ctx.moon create mode 100644 moonbuild/compat/execute.moon create mode 100644 moonbuild/compat/pcall.moon create mode 100644 moonbuild/context.moon create mode 100644 moonbuild/core/DAG.moon create mode 100644 moonbuild/core/Target.moon create mode 100644 moonbuild/core/Variable.moon create mode 100644 moonbuild/core/multiprocessexecutor.moon create mode 100644 moonbuild/core/singleprocessexecutor.moon create mode 100644 moonbuild/env/global.moon create mode 100644 moonbuild/env/init.moon create mode 100644 moonbuild/env/top.moon delete mode 100644 moonbuild/fscache.moon delete mode 100644 moonbuild/fsutil.moon delete mode 100644 moonbuild/stringutil.moon delete mode 100644 moonbuild/tableutil.moon delete mode 100644 moonbuild/util.moon delete mode 100644 rock.yml delete mode 100644 spec/fsutil_spec.moon delete mode 100644 spec/stringutil_spec.moon delete mode 100644 spec/tableutil_spec.moon diff --git a/.gitignore b/.gitignore index 47307da..7c99134 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,2 @@ -/moonbuild/*.lua -/bin/*.lua +*.lua +/out/* diff --git a/Alfons.moon b/Alfons.moon deleted file mode 100644 index 0591b7f..0000000 --- a/Alfons.moon +++ /dev/null @@ -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" diff --git a/Build.moon b/Build.moon index c998169..16a2444 100644 --- a/Build.moon +++ b/Build.moon @@ -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 diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..73e540a --- /dev/null +++ b/Makefile @@ -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 $^ diff --git a/README.md b/README.md index aeee5cb..250b8fa 100644 --- a/README.md +++ b/README.md @@ -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 [deps: ] [in: ] [out: ] [from: ] [fn: ]` -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 [...]` -Prints and executes the command `cmd` with the given args. See `run` for how `args` works. - -### `#cmd [...]` -Executes without printing the command `cmd` with the given args. See `run` for how `args` works. - -### `wildcard ` -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 [...]` -Removes all exclusions from the given list, and returns it. - -`list` must be a table, and `exclusions` can be any type - -### `patsubst ` -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 ` -Applies `code` to every element of `table`, and returns the resulting table. - -`table` must be a table, and `code` a function. - -### `min|max
` -Returns either the min or max value of the given table. - -`table` must be a table - -### `first
` -Returns the first value of the table that verifies the given condition. - -`table` must be a table, and `code` a function - -### `flatten
` -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 ` -Returns the modification time of `file`, or `nil` if it doesn't exist. - -`file` must be a string - -### `exists ` -Returns `true` if the file exists, `false` otherwise. - -`file` must be a string - - -### `isdir ` -Returns `true` if the file exists and is a directory, `false` otherwise. - -`file` must be a string - -### `run [ [print: ] [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: ` is a special kind of argument that will not be escaped. `print` and `error` can be anything, but booleans or nil are recommended - -### `popen [ [print: ]]` -Same as `run`, but returns a `io.popen` handle. - -### `calccdeps [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 [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 diff --git a/bin/moonbuild.moon b/bin/moonbuild.moon old mode 100755 new mode 100644 index 51b99cc..3fd336b --- a/bin/moonbuild.moon +++ b/bin/moonbuild.moon @@ -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 #{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 diff --git a/moonbuild.lua b/moonbuild.lua deleted file mode 100644 index 251d6db..0000000 --- a/moonbuild.lua +++ /dev/null @@ -1,1211 +0,0 @@ -do - -do -local _ENV = _ENV -package.preload[ "moonbuild.fscache" ] = function( ... ) local arg = _G.arg; -local attributes, dir -do - local _obj_0 = require('lfs') - attributes, dir = _obj_0.attributes, _obj_0.dir -end -local unpack = unpack or table.unpack -local FROZEN -FROZEN = function() end -local makecached -makecached = function(fn) - local cache = { } - local invalidate - invalidate = function(val) - cache[val] = nil - end - local freeze - freeze = function(val) - cache[val] = FROZEN - end - local clear - clear = function() - cache = { } - end - local get - get = function(val) - if cache == FROZEN then - return fn(val) - end - local cached = cache[val] - if cached ~= FROZEN and cached ~= nil then - return unpack(cached) - end - local ret = { - fn(val) - } - if cached ~= FROZEN then - cache[val] = ret - end - return unpack(ret) - end - local enable - enable = function() - if cache == FROZEN then - cache = { } - end - end - local disable - disable = function() - cache = FROZEN - end - return setmetatable({ - get = get, - invalidate = invalidate, - freeze = freeze, - clear = clear, - enable = enable, - disable = disable - }, { - __call = function(self, val) - return get(val) - end - }) -end -local cached = { - attributes = makecached(attributes), - dir = makecached(function(file) - local _accum_0 = { } - local _len_0 = 1 - for k in dir(file) do - _accum_0[_len_0] = k - _len_0 = _len_0 + 1 - end - return _accum_0 - end) -} -local enable -enable = function() - for _, fn in cached do - fn:enable() - end -end -local disable -disable = function() - for _, fn in cached do - fn:disable() - end -end -local clear -clear = function() - for _, fn in cached do - fn:clear() - end -end -return setmetatable({ - enable = enable, - disable = disable, - clear = clear -}, { - __index = cached -}) - -end -end - -do -local _ENV = _ENV -package.preload[ "moonbuild.fsutil" ] = function( ... ) local arg = _G.arg; -local dir, attributes, clear, enable, disable -do - local _obj_0 = require('moonbuild.fscache') - dir, attributes, clear, enable, disable = _obj_0.dir, _obj_0.attributes, _obj_0.clear, _obj_0.enable, _obj_0.disable -end -local gmatch, match, gsub, sub -do - local _obj_0 = string - gmatch, match, gsub, sub = _obj_0.gmatch, _obj_0.match, _obj_0.gsub, _obj_0.sub -end -local insert, remove, concat -do - local _obj_0 = table - insert, remove, concat = _obj_0.insert, _obj_0.remove, _obj_0.concat -end -local normalizepath -normalizepath = function(file) - local parts - do - local _accum_0 = { } - local _len_0 = 1 - for part in gmatch(file, '[^/]+') do - _accum_0[_len_0] = part - _len_0 = _len_0 + 1 - end - parts = _accum_0 - end - local absolute = (sub(file, 1, 1)) == '/' - local i = 1 - while i <= #parts do - local _continue_0 = false - repeat - if parts[i] == '.' then - remove(parts, i) - _continue_0 = true - break - end - if parts[i] == '..' and i ~= 1 and parts[i - 1] ~= '..' then - remove(parts, i) - remove(parts, i - 1) - i = i - 1 - _continue_0 = true - break - end - i = i + 1 - _continue_0 = true - until true - if not _continue_0 then - break - end - end - if #parts == 0 then - return absolute and '/' or '.' - else - return (absolute and '/' or '') .. concat(parts, '/') - end -end -local ls -ls = function(d) - local _accum_0 = { } - local _len_0 = 1 - local _list_0 = dir(normalizepath(d)) - for _index_0 = 1, #_list_0 do - local f = _list_0[_index_0] - if f ~= '.' and f ~= '..' then - _accum_0[_len_0] = f - _len_0 = _len_0 + 1 - end - end - return _accum_0 -end -local lswithpath -lswithpath = function(d) - if d == '' then - return ls('.') - end - local _accum_0 = { } - local _len_0 = 1 - local _list_0 = dir(normalizepath(d)) - for _index_0 = 1, #_list_0 do - local f = _list_0[_index_0] - if f ~= '.' and f ~= '..' then - _accum_0[_len_0] = d .. '/' .. f - _len_0 = _len_0 + 1 - end - end - return _accum_0 -end -local exists -exists = function(f) - return (attributes(normalizepath(f))) ~= nil -end -local isdir -isdir = function(f) - local a = attributes(normalizepath(f)) - return a and a.mode == 'directory' or false -end -local mtime -mtime = function(f) - local a = attributes(normalizepath(f)) - return a and a.modification -end -local matchglob -matchglob = function(str, glob) - glob = gsub(glob, '[%[%]%%+.?-]', function(self) - return '%' .. self - end) - local patt = '^' .. (gsub(glob, '%*%*?', function(self) - return self == '**' and '.*' or '[^/]*' - end)) .. '$' - local rst - if (type(str)) == 'table' then - local results, i = { }, 1 - for _index_0 = 1, #str do - local s = str[_index_0] - rst = (match(s, patt)) and s - if rst then - results[i], i = rst, i + 1 - end - end - rst = results - else - rst = (match(str, patt)) and str - end - return rst -end -local wildcard -wildcard = function(glob) - local parts - do - local _accum_0 = { } - local _len_0 = 1 - for part in gmatch(glob, '[^/]+') do - _accum_0[_len_0] = part - _len_0 = _len_0 + 1 - end - parts = _accum_0 - end - local absolute = (sub(glob, 1, 1)) == '/' - for i, part in ipairs(parts) do - local prevpath = (absolute and '/' or '') .. concat(parts, '/', 1, i - 1) - local currpath = (i == 1 and '' or (prevpath .. '/')) .. part - if match(part, '%*%*.*%*%*') then - error("Two '**' in the same path component in a wildcard") - end - if match(part, '%*%*') then - local prefix = match(currpath, '^(.*)%*%*') - local suffix = (match(part, '%*%*(.*)$')) .. (i == #parts and '' or ('/' .. concat(parts, '/', i + 1, #parts))) - local files = lswithpath(prevpath) - local results = { } - for _index_0 = 1, #files do - local file = files[_index_0] - if matchglob(file, currpath) then - if i == #parts then - insert(results, file) - elseif isdir(file) then - local _list_0 = wildcard(file .. '/' .. concat(parts, '/', i + 1, #parts)) - for _index_1 = 1, #_list_0 do - local result = _list_0[_index_1] - insert(results, result) - end - end - end - if (matchglob(file, prefix .. '**')) and isdir(file) then - local _list_0 = wildcard(file .. '/**' .. suffix) - for _index_1 = 1, #_list_0 do - local result = _list_0[_index_1] - insert(results, result) - end - end - end - return results - end - if match(part, '%*') then - local files = lswithpath(prevpath) - if i == #parts then - return matchglob(files, glob) - end - local results = { } - for _index_0 = 1, #files do - local file = files[_index_0] - if (matchglob(file, currpath)) and isdir(file) then - local _list_0 = wildcard(file .. '/' .. concat(parts, '/', i + 1, #parts)) - for _index_1 = 1, #_list_0 do - local result = _list_0[_index_1] - insert(results, result) - end - end - end - return results - end - end - if exists(glob) then - return { - glob - } - else - return { } - end -end -local parentdir -parentdir = function(file) - return normalizepath(file .. '/..') -end -local freezecache -freezecache = function(file) - dir.freeze(file) - dir.freeze(parentdir(file)) - return attributes.invalidate(file) -end -local invalidatecache -invalidatecache = function(file) - dir.invalidate(file) - dir.invalidate(parentdir(file)) - return attributes.invalidate(file) -end -return { - wildcard = wildcard, - exists = exists, - isdir = isdir, - mtime = mtime, - normalizepath = normalizepath, - parentdir = parentdir, - matchglob = matchglob, - freezecache = freezecache, - invalidatecache = invalidatecache, - clearcache = clear, - enablecache = enable, - disablecache = disable -} - -end -end - -do -local _ENV = _ENV -package.preload[ "moonbuild.stringutil" ] = function( ... ) local arg = _G.arg; -local match, gmatch, sub -do - local _obj_0 = string - match, gmatch, sub = _obj_0.match, _obj_0.gmatch, _obj_0.sub -end -local upper, lower -do - local _obj_0 = string - upper, lower = _obj_0.upper, _obj_0.lower -end -local GLOB_PATT = '^([^%%]*)%%([^%%]*)$' -local patsubst -patsubst = function(str, pattern, replacement) - if (type(str)) == 'table' then - local _accum_0 = { } - local _len_0 = 1 - for _index_0 = 1, #str do - local s = str[_index_0] - _accum_0[_len_0] = patsubst(s, pattern, replacement) - _len_0 = _len_0 + 1 - end - return _accum_0 - end - if str == pattern then - return replacement - end - local prefix, suffix = match(pattern, GLOB_PATT) - if not (prefix or suffix) then - return str - end - local reprefix, resuffix = match(replacement, GLOB_PATT) - if not (reprefix or resuffix) then - if (#prefix == 0 or (sub(str, 1, #prefix)) == prefix) and (#suffix == 0 or (sub(str, -#suffix)) == suffix) then - return replacement - else - return str - end - end - if #prefix == 0 or (sub(str, 1, #prefix)) == prefix then - str = reprefix .. (sub(str, #prefix + 1)) - end - if #suffix == 0 or (sub(str, -#suffix)) == suffix then - str = (sub(str, 1, -#suffix - 1)) .. resuffix - end - return str -end -local splitsp -splitsp = function(str) - local _accum_0 = { } - local _len_0 = 1 - for elem in gmatch(str, '%S+') do - _accum_0[_len_0] = elem - _len_0 = _len_0 + 1 - end - return _accum_0 -end -return { - patsubst = patsubst, - splitsp = splitsp, - upper = upper, - lower = lower -} - -end -end - -do -local _ENV = _ENV -package.preload[ "moonbuild.tableutil" ] = function( ... ) local arg = _G.arg; -local insert, remove, concat, sort -do - local _obj_0 = table - insert, remove, concat, sort = _obj_0.insert, _obj_0.remove, _obj_0.concat, _obj_0.sort -end -local unpack = unpack or table.unpack -local sortedpairs -sortedpairs = function(table, cmp) - local keys - do - local _accum_0 = { } - local _len_0 = 1 - for k in pairs(table) do - _accum_0[_len_0] = k - _len_0 = _len_0 + 1 - end - keys = _accum_0 - end - sort(keys, cmp) - return coroutine.wrap(function() - for _index_0 = 1, #keys do - local key = keys[_index_0] - coroutine.yield(key, table[key]) - end - end) -end -local min -min = function(table, cmp) - if cmp == nil then - cmp = function(a, b) - return a < b - end - end - local val = table[1] - for i = 2, #table do - local elem = table[i] - if cmp(elem, val) then - val = elem - end - end - return val -end -local max -max = function(table, cmp) - if cmp == nil then - cmp = function(a, b) - return a < b - end - end - local val = table[1] - for i = 2, #table do - local elem = table[i] - if not cmp(elem, val) then - val = elem - end - end - return val -end -local foreach -foreach = function(tab, fn) - local _accum_0 = { } - local _len_0 = 1 - for _index_0 = 1, #tab do - local e = tab[_index_0] - _accum_0[_len_0] = fn(e) - _len_0 = _len_0 + 1 - end - return _accum_0 -end -local first -first = function(tab, fn) - for _index_0 = 1, #tab do - local e = tab[_index_0] - if fn(e) then - return e - end - end -end -local exclude -exclude = function(tab, ...) - local i = 1 - while i <= #tab do - local removed = false - for j = 1, select('#', ...) do - if tab[i] == select(j, ...) then - remove(tab, i) - removed = true - break - end - end - if not (removed) then - i = i + 1 - end - end - return tab -end -local flatten -flatten = function(tab) - if (type(tab)) ~= 'table' then - return { - tab - } - end - local out = { } - for _index_0 = 1, #tab do - local e = tab[_index_0] - if (type(e)) == 'table' then - if e[1] == nil and (next(e)) ~= nil then - insert(out, e) - else - local _list_0 = flatten(e) - for _index_1 = 1, #_list_0 do - local v = _list_0[_index_1] - insert(out, v) - end - end - else - insert(out, e) - end - end - return out -end -return { - min = min, - max = max, - foreach = foreach, - first = first, - exclude = exclude, - flatten = flatten, - sortedpairs = sortedpairs, - insert = insert, - remove = remove, - concat = concat, - sort = sort, - unpack = unpack -} - -end -end - -do -local _ENV = _ENV -package.preload[ "moonbuild.util" ] = function( ... ) local arg = _G.arg; -local wildcard, exists, isdir, mtime -do - local _obj_0 = require('moonbuild.fsutil') - wildcard, exists, isdir, mtime = _obj_0.wildcard, _obj_0.exists, _obj_0.isdir, _obj_0.mtime -end -local foreach, first, flatten, exclude, sortedpairs, min, max -do - local _obj_0 = require('moonbuild.tableutil') - foreach, first, flatten, exclude, sortedpairs, min, max = _obj_0.foreach, _obj_0.first, _obj_0.flatten, _obj_0.exclude, _obj_0.sortedpairs, _obj_0.min, _obj_0.max -end -local patsubst, splitsp -do - local _obj_0 = require('moonbuild.stringutil') - patsubst, splitsp = _obj_0.patsubst, _obj_0.splitsp -end -local insert, concat, sort, pairs -do - local _obj_0 = require('moonbuild.tableutil') - insert, concat, sort, pairs = _obj_0.insert, _obj_0.concat, _obj_0.sort, _obj_0.pairs -end -local upper, lower -do - local _obj_0 = require('moonbuild.stringutil') - upper, lower = _obj_0.upper, _obj_0.lower -end -local GLOB_PATT = '^([^%%]*)%%([^%%]*)$' -local escapecmdpart -escapecmdpart = function(p) - if (type(p)) == 'table' then - if p.raw then - return p.raw - end - return concat((function() - local _accum_0 = { } - local _len_0 = 1 - for _index_0 = 1, #p do - local part = p[_index_0] - _accum_0[_len_0] = escapecmdpart(part) - _len_0 = _len_0 + 1 - end - return _accum_0 - end)(), ' ') - end - if p:match('^[a-zA-Z0-9_./-]+$') then - return p - end - return '"' .. p:gsub('\\', '\\\\'):gsub('"', '\\"') .. '"' -end -local escapecmd -escapecmd = function(c, args) - if args == nil then - args = { } - end - c = escapecmdpart(c) - local _list_0 = flatten(args) - for _index_0 = 1, #_list_0 do - local a = _list_0[_index_0] - if a then - c = c .. (' ' .. escapecmdpart(a)) - end - end - return c -end -local run -run = function(c, args, params) - if params == nil then - params = { } - end - local escaped = escapecmd(c, args) - if params.print then - print(escaped) - end - local ret, _, code = os.execute(escaped) - if (type(ret)) == 'number' then - ret, code = ret == 0, ret - end - if params.error and not ret then - error(tostring(c) .. " failed with code " .. tostring(code)) - end - return ret, code -end -local popen -popen = function(c, args, mode, params) - if mode == nil then - mode = 'r' - end - if params == nil then - params = { } - end - local escaped = escapecmd(c, args) - if params.print then - print(escaped) - end - return io.popen(escaped, mode) -end -local calccdeps -calccdeps = function(infile, includesys) - if includesys == nil then - includesys = false - end - local data = (popen('cc', { - includesys and '-M' or '-MM', - infile - })):read('*a') - local rawdeps = data:gsub('\\\n', ''):match(':(.+)') - local _accum_0 = { } - local _len_0 = 1 - for dep in rawdeps:gmatch('%S+') do - if dep ~= infile then - _accum_0[_len_0] = dep - _len_0 = _len_0 + 1 - end - end - return _accum_0 -end -local findclib -findclib = function(name, mode) - if mode == nil then - mode = 'all' - end - local args = { - name - } - if mode == 'all' or mode == 'cc' then - insert(args, '--cflags') - end - if mode == 'all' or mode == 'ld' then - insert(args, '--libs') - end - local _accum_0 = { } - local _len_0 = 1 - for arg in (popen('pkg-config', args)):read('*a'):gmatch('%S+') do - _accum_0[_len_0] = arg - _len_0 = _len_0 + 1 - end - return _accum_0 -end -local match -match = function(str, glob) - local prefix, suffix = glob:match(GLOB_PATT) - if not (prefix) then - return str == glob - end - if (str:sub(1, #prefix)) == prefix and (str:sub(-#suffix)) == suffix then - return str:sub(#prefix + 1, -#suffix - 1) - end - return false -end -local isglob -isglob = function(glob) - if glob:match(GLOB_PATT) then - return true - else - return false - end -end -local env -env = function(key, def) - return (os.getenv(key)) or def -end -return { - min = min, - max = max, - foreach = foreach, - first = first, - exclude = exclude, - flatten = flatten, - sortedpairs = sortedpairs, - insert = insert, - remove = remove, - concat = concat, - sort = sort, - unpack = unpack, - wildcard = wildcard, - mtime = mtime, - exists = exists, - isdir = isdir, - run = run, - popen = popen, - calccdeps = calccdeps, - findclib = findclib, - patsubst = patsubst, - splitsp = splitsp, - upper = upper, - lower = lower, - match = match, - isglob = isglob, - env = env -} - -end -end - -end - -local argparse = require('argparse') -require('moonscript') -local loadfile -loadfile = require('moonscript.base').loadfile -local truncate_traceback, rewrite_traceback -do - local _obj_0 = require('moonscript.errors') - truncate_traceback, rewrite_traceback = _obj_0.truncate_traceback, _obj_0.rewrite_traceback -end -local trim -trim = require('moonscript.util').trim -local util = require('moonbuild.util') -local freezecache, invalidatecache -do - local _obj_0 = require('moonbuild.fsutil') - freezecache, invalidatecache = _obj_0.freezecache, _obj_0.invalidatecache -end -local exists, mtime, run, min, max, first, flatten, match, patsubst, sortedpairs -exists, mtime, run, min, max, first, flatten, match, patsubst, sortedpairs = util.exists, util.mtime, util.run, util.min, util.max, util.first, util.flatten, util.match, util.patsubst, util.sortedpairs -local insert, concat -do - local _obj_0 = table - insert, concat = _obj_0.insert, _obj_0.concat -end -local 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") -local args = parser:parse() -local loadwithscope -loadwithscope = function(file, scope) - local fn, err = loadfile(file) - if not (fn) then - error(err or "failed to load code") - end - local dumped - dumped, err = string.dump(fn) - if not (dumped) then - error(err or "failed to dump function") - end - return load(dumped, file, 'b', scope) -end -local pcall -pcall = function(fn, ...) - local rewrite - rewrite = function(err) - local trace = debug.traceback('', 2) - local trunc = truncate_traceback(trim(trace)) - return (rewrite_traceback(trunc, err)) or trace - end - return xpcall(fn, rewrite, ...) -end -local Command -do - local _class_0 - local _base_0 = { - __unm = function(self) - return self:run({ - error = true, - print = true - }) - end, - __len = function(self) - return self:run({ - error = true - }) - end, - __tostring = function(self) - return self.cmd - end, - run = function(self, params) - return run(self.cmd, self.args, params) - end - } - _base_0.__index = _base_0 - _class_0 = setmetatable({ - __init = function(self, cmd, ...) - self.cmd = cmd - self.args = { - ... - } - end, - __base = _base_0, - __name = "Command" - }, { - __index = _base_0, - __call = function(cls, ...) - local _self_0 = setmetatable({}, _base_0) - cls.__init(_self_0, ...) - return _self_0 - end - }) - _base_0.__class = _class_0 - local self = _class_0 - self.run = function(self, ...) - return -self(...) - end - Command = _class_0 -end -local BuildObject -do - local _class_0 - local all, skip - local _base_0 = { - __tostring = function(self) - return "Target " .. tostring(self.name) .. " (" .. tostring(concat(self.deps, ', ')) .. ")" - end, - build = function(self, name, upper) - if upper == nil then - upper = { } - end - if skip[name] then - return - end - if upper[self] then - error("Cycle detected on " .. tostring(self.name)) - end - upper = setmetatable({ - [self] = true - }, { - __index = upper - }) - if self.name ~= name then - local _list_0 = self.deps - for _index_0 = 1, #_list_0 do - local dep = _list_0[_index_0] - self.__class:build((patsubst(name, self.name, dep)), upper) - end - else - local _list_0 = self.deps - for _index_0 = 1, #_list_0 do - local dep = _list_0[_index_0] - self.__class:build(dep, upper) - end - end - if not (self:shouldbuild(name)) then - return - end - local ins = self.ins - local outs = self.outs - if self.name ~= name then - do - local _accum_0 = { } - local _len_0 = 1 - local _list_0 = self.ins - for _index_0 = 1, #_list_0 do - local elem = _list_0[_index_0] - _accum_0[_len_0] = patsubst(name, self.name, elem) - _len_0 = _len_0 + 1 - end - ins = _accum_0 - end - do - local _accum_0 = { } - local _len_0 = 1 - local _list_0 = self.outs - for _index_0 = 1, #_list_0 do - local elem = _list_0[_index_0] - _accum_0[_len_0] = patsubst(name, self.name, elem) - _len_0 = _len_0 + 1 - end - outs = _accum_0 - end - print("Building " .. tostring(self.name) .. " as " .. tostring(name)) - else - print("Building " .. tostring(name)) - end - for _index_0 = 1, #outs do - local file = outs[_index_0] - freezecache(file) - end - local ok, err = pcall(function() - return self.fn({ - ins = ins, - outs = outs, - infile = ins[1], - outfile = outs[1], - name = name - }) - end) - for _index_0 = 1, #outs do - local file = outs[_index_0] - invalidatecache(file) - end - if not (ok) then - error("Can't build " .. tostring(self.name) .. ": lua error\n" .. tostring(err)) - end - for _index_0 = 1, #outs do - local f = outs[_index_0] - if not (exists(f)) then - error("Can't build " .. tostring(self.name) .. ": output file " .. tostring(f) .. " not created") - end - end - skip[name] = true - end, - shouldbuild = function(self, name) - if args.noskip then - return true - end - if #self.ins == 0 or #self.outs == 0 then - return true - end - local ins - if self.name ~= name then - do - local _accum_0 = { } - local _len_0 = 1 - local _list_0 = self.ins - for _index_0 = 1, #_list_0 do - local elem = _list_0[_index_0] - _accum_0[_len_0] = patsubst(name, self.name, elem) - _len_0 = _len_0 + 1 - end - ins = _accum_0 - end - else - ins = self.ins - end - local itimes - do - local _accum_0 = { } - local _len_0 = 1 - for _index_0 = 1, #ins do - local f = ins[_index_0] - _accum_0[_len_0] = mtime(f) - _len_0 = _len_0 + 1 - end - itimes = _accum_0 - end - for i = 1, #self.ins do - if not (itimes[i]) then - error("Can't build " .. tostring(self.name) .. ": missing inputs") - end - end - local outs - if self.name ~= name then - do - local _accum_0 = { } - local _len_0 = 1 - local _list_0 = self.outs - for _index_0 = 1, #_list_0 do - local elem = _list_0[_index_0] - _accum_0[_len_0] = patsubst(name, self.name, elem) - _len_0 = _len_0 + 1 - end - outs = _accum_0 - end - else - outs = self.outs - end - local otimes - do - local _accum_0 = { } - local _len_0 = 1 - for _index_0 = 1, #outs do - local f = outs[_index_0] - _accum_0[_len_0] = mtime(f) - _len_0 = _len_0 + 1 - end - otimes = _accum_0 - end - for i = 1, #self.outs do - if not otimes[i] then - return true - end - end - return (max(itimes)) > (min(otimes)) - end - } - _base_0.__index = _base_0 - _class_0 = setmetatable({ - __init = function(self, name, outs, ins, deps, fn) - if outs == nil then - outs = { } - end - if ins == nil then - ins = { } - end - if deps == nil then - deps = { } - end - if fn == nil then - fn = function(self) end - end - self.name, self.outs, self.ins, self.deps, self.fn = name, outs, ins, deps, fn - self.skip = false - if all[self.name] then - error("Duplicate build name " .. tostring(self.name)) - end - all[self.name] = self - end, - __base = _base_0, - __name = "BuildObject" - }, { - __index = _base_0, - __call = function(cls, ...) - local _self_0 = setmetatable({}, _base_0) - cls.__init(_self_0, ...) - return _self_0 - end - }) - _base_0.__class = _class_0 - local self = _class_0 - all = { } - skip = { } - self.find = function(self, name) - local target = all[name] - if target then - return target - end - for glob, tgt in pairs(all) do - if match(name, glob) then - return tgt - end - end - return nil - end - self.list = function(self) - local _tbl_0 = { } - for name, target in pairs(all) do - do - local _tbl_1 = { } - local _list_0 = target.deps - for _index_0 = 1, #_list_0 do - local dep = _list_0[_index_0] - _tbl_1[dep] = self:find(dep) - end - _tbl_0[target] = _tbl_1 - end - end - return _tbl_0 - end - self.build = function(self, name, upper) - local target = (self:find(name)) or error("No such target: " .. tostring(name)) - return target:build(name, upper) - end - BuildObject = _class_0 -end -if setfenv then - error("Need Lua >=5.2") -end -local targets, defaulttarget -local buildscope = { - default = function(target) - defaulttarget = target.name - return target - end, - public = function(target) - insert(targets, target.name) - return target - end, - target = function(name, params) - local tout = flatten(params.out) - local tin = flatten(params["in"]) - local tdeps = flatten(params.deps) - local _list_0 = flatten(params.from) - for _index_0 = 1, #_list_0 do - local f = _list_0[_index_0] - insert(tin, f) - insert(tdeps, f) - end - return BuildObject(name, tout, tin, tdeps, params.fn) - end, - Command = Command -} -for k, fn in pairs(util) do - buildscope[k] = fn -end -setmetatable(buildscope, { - __index = function(self, k) - local global = rawget(_G, k) - if global then - return global - end - return function(...) - return Command(k, ...) - end - end -}) -local loadtargets -loadtargets = function() - targets = { } - defaulttarget = 'all' - local file = first({ - 'Build.moon', - 'Buildfile.moon', - 'Build', - 'Buildfile' - }, exists) - if not (file) then - error("No Build.moon or Buildfile found") - end - local buildfn = loadwithscope(file, buildscope) - if not (buildfn) then - error("Failed to load build function") - end - return buildfn() -end -local buildtargets -buildtargets = function() - if #args.targets == 0 then - BuildObject:build(defaulttarget) - end - local _list_0 = args.targets - for _index_0 = 1, #_list_0 do - local target = _list_0[_index_0] - BuildObject:build(target) - end -end -local ok, err = pcall(loadtargets) -if not (ok) then - if err then - io.stderr:write("Error while loading build file: ", err, '\n') - else - io.stderr:write("Unknown error\n") - end - os.exit(1) -end -if args.list then - io.write("Available targets:\n") - io.write("\t" .. tostring(concat(targets, ', ')) .. "\n") - os.exit(0) -end -if args.deps then - io.write("Targets:\n") - for target, deps in sortedpairs(BuildObject:list(), function(a, b) - return a.name < b.name - end) do - io.write("\t" .. tostring(target.name) .. " ") - if #target.ins == 0 then - if #target.outs == 0 then - io.write("[no in/out]") - else - io.write("[spontaneous generation]") - end - else - if #target.outs == 0 then - io.write("[consumer]") - else - io.write("(" .. tostring(concat(target.ins, ', ')) .. " -> " .. tostring(concat(target.outs, ', ')) .. ")") - end - end - io.write("\n") - for name, dep in sortedpairs(deps) do - io.write("\t\t" .. tostring(name)) - if name ~= dep.name then - io.write(" (" .. tostring(dep.name) .. ")") - end - io.write("\n") - end - end - os.exit(0) -end -return buildtargets() diff --git a/moonbuild/_.moon b/moonbuild/_.moon new file mode 100644 index 0000000..1efd1a2 --- /dev/null +++ b/moonbuild/_.moon @@ -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] +_ diff --git a/moonbuild/_cmd.moon b/moonbuild/_cmd.moon new file mode 100644 index 0000000..9645aed --- /dev/null +++ b/moonbuild/_cmd.moon @@ -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'} diff --git a/moonbuild/_cmd/common.moon b/moonbuild/_cmd/common.moon new file mode 100644 index 0000000..a947658 --- /dev/null +++ b/moonbuild/_cmd/common.moon @@ -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 +} diff --git a/moonbuild/_cmd/lua.moon b/moonbuild/_cmd/lua.moon new file mode 100644 index 0000000..8ee0dbc --- /dev/null +++ b/moonbuild/_cmd/lua.moon @@ -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 +} diff --git a/moonbuild/_cmd/posix.moon b/moonbuild/_cmd/posix.moon new file mode 100644 index 0000000..6e10966 --- /dev/null +++ b/moonbuild/_cmd/posix.moon @@ -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 +} diff --git a/moonbuild/_common.moon b/moonbuild/_common.moon new file mode 100644 index 0000000..57c9123 --- /dev/null +++ b/moonbuild/_common.moon @@ -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 = 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 eM + 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] diff --git a/moonbuild/_fs.moon b/moonbuild/_fs.moon new file mode 100644 index 0000000..386508d --- /dev/null +++ b/moonbuild/_fs.moon @@ -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'} diff --git a/moonbuild/_fs/cmd.moon b/moonbuild/_fs/cmd.moon new file mode 100644 index 0000000..fc084fa --- /dev/null +++ b/moonbuild/_fs/cmd.moon @@ -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}" +} diff --git a/moonbuild/_fs/lfs.moon b/moonbuild/_fs/lfs.moon new file mode 100644 index 0000000..f84d37d --- /dev/null +++ b/moonbuild/_fs/lfs.moon @@ -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 +} diff --git a/moonbuild/_fs/posix.moon b/moonbuild/_fs/posix.moon new file mode 100644 index 0000000..d74d477 --- /dev/null +++ b/moonbuild/_fs/posix.moon @@ -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 +} diff --git a/moonbuild/_util.moon b/moonbuild/_util.moon new file mode 100644 index 0000000..842c10b --- /dev/null +++ b/moonbuild/_util.moon @@ -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] diff --git a/moonbuild/compat/bit.moon b/moonbuild/compat/bit.moon new file mode 100644 index 0000000..2909eb9 --- /dev/null +++ b/moonbuild/compat/bit.moon @@ -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 } diff --git a/moonbuild/compat/ctx.moon b/moonbuild/compat/ctx.moon new file mode 100644 index 0000000..8aa1e91 --- /dev/null +++ b/moonbuild/compat/ctx.moon @@ -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 } diff --git a/moonbuild/compat/execute.moon b/moonbuild/compat/execute.moon new file mode 100644 index 0000000..4775e14 --- /dev/null +++ b/moonbuild/compat/execute.moon @@ -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 +} diff --git a/moonbuild/compat/pcall.moon b/moonbuild/compat/pcall.moon new file mode 100644 index 0000000..a1d7068 --- /dev/null +++ b/moonbuild/compat/pcall.moon @@ -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 diff --git a/moonbuild/context.moon b/moonbuild/context.moon new file mode 100644 index 0000000..917cec5 --- /dev/null +++ b/moonbuild/context.moon @@ -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 diff --git a/moonbuild/core/DAG.moon b/moonbuild/core/DAG.moon new file mode 100644 index 0000000..44302a2 --- /dev/null +++ b/moonbuild/core/DAG.moon @@ -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 diff --git a/moonbuild/core/Target.moon b/moonbuild/core/Target.moon new file mode 100644 index 0000000..deae83f --- /dev/null +++ b/moonbuild/core/Target.moon @@ -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 diff --git a/moonbuild/core/Variable.moon b/moonbuild/core/Variable.moon new file mode 100644 index 0000000..b1c0e4a --- /dev/null +++ b/moonbuild/core/Variable.moon @@ -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 = ... + diff --git a/moonbuild/core/multiprocessexecutor.moon b/moonbuild/core/multiprocessexecutor.moon new file mode 100644 index 0000000..e54fb6c --- /dev/null +++ b/moonbuild/core/multiprocessexecutor.moon @@ -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 diff --git a/moonbuild/core/singleprocessexecutor.moon b/moonbuild/core/singleprocessexecutor.moon new file mode 100644 index 0000000..2b93f9c --- /dev/null +++ b/moonbuild/core/singleprocessexecutor.moon @@ -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 diff --git a/moonbuild/env/global.moon b/moonbuild/env/global.moon new file mode 100644 index 0000000..d01609f --- /dev/null +++ b/moonbuild/env/global.moon @@ -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 diff --git a/moonbuild/env/init.moon b/moonbuild/env/init.moon new file mode 100644 index 0000000..588fbc0 --- /dev/null +++ b/moonbuild/env/init.moon @@ -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 diff --git a/moonbuild/env/top.moon b/moonbuild/env/top.moon new file mode 100644 index 0000000..827b0f8 --- /dev/null +++ b/moonbuild/env/top.moon @@ -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 diff --git a/moonbuild/fscache.moon b/moonbuild/fscache.moon deleted file mode 100644 index d32ae06..0000000 --- a/moonbuild/fscache.moon +++ /dev/null @@ -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 diff --git a/moonbuild/fsutil.moon b/moonbuild/fsutil.moon deleted file mode 100644 index 7c0214f..0000000 --- a/moonbuild/fsutil.moon +++ /dev/null @@ -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 -} diff --git a/moonbuild/stringutil.moon b/moonbuild/stringutil.moon deleted file mode 100644 index f5d07f3..0000000 --- a/moonbuild/stringutil.moon +++ /dev/null @@ -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 -} diff --git a/moonbuild/tableutil.moon b/moonbuild/tableutil.moon deleted file mode 100644 index ea4f687..0000000 --- a/moonbuild/tableutil.moon +++ /dev/null @@ -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 - val = table[1] - for i=2, #table - elem = table[i] - if cmp elem, val - val = elem - val - -max = (table, cmp=(a, b) -> a - 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 -} diff --git a/moonbuild/util.moon b/moonbuild/util.moon deleted file mode 100644 index 3b39c1b..0000000 --- a/moonbuild/util.moon +++ /dev/null @@ -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 -} diff --git a/rock.yml b/rock.yml deleted file mode 100644 index 3b58b28..0000000 --- a/rock.yml +++ /dev/null @@ -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 diff --git a/spec/fsutil_spec.moon b/spec/fsutil_spec.moon deleted file mode 100644 index fe24c1c..0000000 --- a/spec/fsutil_spec.moon +++ /dev/null @@ -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 - } diff --git a/spec/stringutil_spec.moon b/spec/stringutil_spec.moon deleted file mode 100644 index c49c6d1..0000000 --- a/spec/stringutil_spec.moon +++ /dev/null @@ -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 diff --git a/spec/tableutil_spec.moon b/spec/tableutil_spec.moon deleted file mode 100644 index e9d6ea7..0000000 --- a/spec/tableutil_spec.moon +++ /dev/null @@ -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 - 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}