initial commit

alfons-task
Nathan DECHER 4 years ago
commit bda639aec7
  1. 3
      .gitignore
  2. 33
      Build.moon
  3. 9
      LICENSE
  4. 33
      Makefile
  5. 92
      README.md
  6. 132
      moonbuild.moon
  7. 143
      util.moon

3
.gitignore vendored

@ -0,0 +1,3 @@
/*.lua
/*.lua.c
/moonbuild

@ -0,0 +1,33 @@
SOURCES_MOON = wildcard '*.moon'
exclude SOURCES_MOON, 'Build.moon'
OUT_LUA = patsubst SOURCES_MOON, '%.moon', '%.lua'
BINARY = 'moonbuild'
MAIN = "#{BINARY}.moon"
MAIN_LUA = patsubst MAIN, '%.moon', '%.lua'
OUT_C = patsubst MAIN, '%.moon', '%.lua.c'
PREFIX = env 'PREFIX', '/usr/local'
default public target 'all', deps: BINARY
public target 'install', deps: 'moonbuild', in: 'moonbuild', fn: =>
-install @infile, "#{PREFIX}/bin"
public target 'clean', fn: =>
-rm '-f', OUT_LUA
-rm '-f', OUT_C
public target 'mrproper', deps: 'clean', fn: =>
-rm '-f', BINARY
public target 'info', fn: =>
#echo "Moonscript sources:", SOURCES_MOON
#echo "Compiled lua:", OUT_LUA
#echo "Binary:", BINARY
target BINARY, out: {BINARY, OUT_C}, in: OUT_LUA, deps: OUT_LUA, fn: =>
-luastatic MAIN_LUA, OUT_LUA, '-I/usr/include/lua5.3', '-llua5.3'
foreach OUT_LUA, (file) ->
source=patsubst file, '%.lua', '%.moon'
target file, in: source, out: file, fn: =>
-moonc @infile

@ -0,0 +1,9 @@
MIT License
Copyright (c) 2020 Codinget
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

@ -0,0 +1,33 @@
SOURCES_MOON := $(wildcard *.moon)
SOURCES_MOON := $(filter-out Build.moon, $(SOURCES_MOON))
OUT_LUA := $(foreach source, $(SOURCES_MOON), $(patsubst %.moon, %.lua, $(source)))
BINARY := moonbuild
MAIN := $(BINARY).moon
MAIN_LUA := $(patsubst %.moon, %.lua, $(MAIN))
OUT_C := $(patsubst %.moon, %.lua.c, $(MAIN))
PREFIX ?= /usr/local
.PHONY: all install clean mrproper info
all: $(BINARY)
install: moonbuild
install $^ $(PREFIX)/bin
clean:
rm -f $(OUT_LUA)
rm -f $(OUT_C)
mrproper: clean
rm -f $(BINARY)
info:
@echo "Moonscript sources:" $(SOURCES_MOON)
@echo "Compiled lua:" $(OUT_LUA)
@echo "Binary:" $(BINARY)
$(BINARY): $(OUT_LUA)
luastatic $(MAIN_LUA) $(OUT_LUA) -I/usr/include/lua5.3 -llua5.3
%.lua: %.moon
moonc $^

@ -0,0 +1,92 @@
# Moonbuild
Because `make` is painful to use, and build scripts are too slow. Moonbuild aims to be a good compromise.
You should probably use [`tup`](http://gittup.org/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).
## 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.
## How do I install it?
- First, you'll need Lua 5.2 (untested) or 5.3 and [LuaRocks](https://luarocks.org)
- Then, you'll need `moonscript`, `argparse` and `luastatic`, which you can get from `luarocks`
- Now, you can simply `make` Moonbuild, or build it with itself with `moon moonbuild.moon`
- You're now ready to install it, with `sudo make install` or `sudo ./moonbuild install`
## 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).
### `[default] [public] target <name> [deps: <deps>] [in: <inputs>] [out: <outputs>] [fn: <code>]`
Define a new target, and give it a list of depenancies, inputs, outputs and a function to run to build it.
`deps`, `in` and `out` can be either strings or tables. `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
### `wildcard <wc>`
Returns a table with all the matching files. Valid wildcards contain either `**`, which can be expanded by any characters, including '/', or `*`, which cannot be expanded by `/`. Wildcards can only contain one `**` or `*`.
`wc` must be a string
### `exclude <list> [<exclusions>...]`
Removes all exclusions from the given list, and returns it.
`list` must be a table, and `exclusions` can be any type
### `patsubst <str> <patt> <subst>`
If the string matches `patt`, makes it match `subst` instead. If `str` is a table, it is recursively applied to all array values.
Patterns are in the format `[prefix]%[suffix]`, with the `%` representing any sequence of characters, including `/`.
`str`, `pat` and `subst` must be strings
### `foreach <table> <code>`
Applies `code` to every element of `table`, and returns the resulting table.
`table` must be a table, and `code` a function.
### `min|max <table>`
Returns either the min or max value of the given table.
`table` must be a table
### `first <table> <code>`
Returns the first value of the table that verifies the given condition.
`table` must be a table, and `code` a function
### `flatten <table>`
Flattens a table so that it has exactly one dimension.
`table` can be anything
### `insert|unpack|concat`
The functions, imported from the `table` library.
### `mtime <file>`
Returns the modification time of `file`, or `nil` if it doesn't exist.
`file` must be a string
### `exists <file>`
Returns `true` if the file exists, `false` otherwise.
`file` must be a string
### `run <cmd> [<args> [print: <print>] [error: <error>]]`
Runs the given command with the given arguments. If `print` is truthy, prints the command before executing it, and if `error` is truthy, crashes if the command fails. Returns a boolean which is true if the command ended with success, and a number which is the return code.
`cmd` must be a string, `args` must be a table, which can contain either strings or other tables. `raw: <val>` is a special kind of argument that will not be escaped. `print` and `error` can be anything, but booleans or nil are recommended
### `popen <cmd> [<args> [print: <print>]]`
Same as `run`, but returns a `io.popen` handle.
## License
MIT

@ -0,0 +1,132 @@
#!/usr/bin/env moon
argparse=require 'argparse'
require 'moonscript'
import loadfile from require 'moonscript.base'
import truncate_traceback, rewrite_traceback from require 'moonscript.errors'
import trim from require 'moonscript.util'
util=require 'util'
import exists, mtime, run, min, max, first, flatten from util
import insert, concat from table
parser=argparse 'moonbuild'
parser\argument('targets', "Targets to run")\args '*'
parser\flag '-a --noskip', "Always run targets"
parser\flag '-l --list', "List available targets"
args=parser\parse!
-- util functions
loadwithscope= (file, scope) ->
fn=loadfile file
dumped=string.dump fn
load dumped, file, 'b', scope
pcall= (fn, ...) ->
rewrite=(err) ->
trace=debug.traceback '', 2
trunc=truncate_traceback trim trace
rewrite_traceback trunc, err
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={}
@build: (name) =>
target=all[name] or error "No such target: #{name}"
target\build!
new: (@name, @outs={}, @ins={}, @deps={}, @fn= =>) =>
@skip=false
error "Duplicate build name #{@name}" if all[@name]
all[@name]=@
build: =>
return if @skip
error "Can't build #{@name}: cyclic dependancy" if @cycle
@cycle=true
for depname in *@deps
dep=all[depname] or error "Can't build #{@name}: missing dependancy #{depname}"
dep\build!
return unless @shouldbuild!
print "Building #{@name}"
ok, err=pcall ->
@.fn ins: @ins, outs: @outs, infile: @ins[1], outfile: @outs[1], name: @name
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=true
shouldbuild: =>
return true if args.noskip
return true if #@ins==0 or #@outs==0
itimes=[mtime f for f in *@ins]
for i=1, #@ins
error "Can't build #{@name}: missing inputs" unless itimes[i]
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
targets={}
defaulttarget='all'
buildscope=
default: (target) ->
defaulttarget=target.name
target
public: (target) ->
insert targets, target.name
target
target: (name, params) ->
BuildObject name, (flatten params.out), (flatten params.in), (flatten params.deps), params.fn
buildscope[k]=fn for k, fn in pairs util
setmetatable buildscope,
__index: (k) =>
global=rawget _G, k
return global if global
(...) -> Command k, ...
file=first {'Build.moon', 'Buildfile.moon', 'Build', 'Buildfile'}, exists
error "No Build.moon or Buildfile found" unless file
buildfn=loadwithscope file, buildscope
ok, err=pcall buildfn
unless ok
if err
io.stderr\write err, '\n'
else
io.stderr\write "Unknown error\n"
os.exit 1
if args.list
io.write "Available targets:\n"
io.write "\t#{concat targets, ', '}\n"
os.exit 0
if #args.targets==0
BuildObject\build defaulttarget
for target in *args.targets
BuildObject\build target

@ -0,0 +1,143 @@
import attributes, dir from require 'lfs'
import insert, concat from table
unpack or=table.unpack
-- min and max of table
max= (t) ->
m=t[1]
for i=2, #t
v=t[i]
m=v if v>m
m
min= (t) ->
m=t[1]
for i=2, #t
v=t[i]
m=v if v<m
m
-- simpler constructs
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, ...
table.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' and e[1]
insert out, v for v in *flatten e
else
insert out, e
out
-- file functions
mtime= (f) ->
a=attributes f
a and a.modification
exists= (f) ->
(attributes f)!=nil
-- 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
c..=' '..escapecmdpart a for a in *args
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
-- file matcher
wildcard= (pattern) ->
prefix, suffix=pattern\match '^(.*)%*%*(.*)$'
if prefix
fd=popen 'find', {(raw: '*'), '-name', "*#{suffix}"}
found={}
for line in fd\lines!
insert found, line if (line\sub 1, #prefix)==prefix
fd\close!
return found
directory, prefix, suffix=pattern\match '^([^/]*)/(.*)%*(.*)$'
if directory
found={}
for file in dir directory
if (file\sub 1, #prefix)==prefix and (file\sub -#suffix)==suffix
insert found, "#{directory}/#{file}"
return found
prefix, suffix=pattern\match '^(.*)%*(.*)$'
if prefix
found={}
for file in dir '.'
if (file\sub 1, #prefix)==prefix and (file\sub -#suffix)==suffix
insert found, file
return found
error "Invalid wildcard pattern: #{pattern}"
-- string pattern
patsubst= (str, pattern, replacement) ->
return [patsubst s, pattern, replacement for s in *str] if (type str)=='table'
prefix, suffix=pattern\match '^(.*)%%(.*)$'
error "Invalid pattern #{pattern}" unless prefix
reprefix, resuffix=replacement\match '^(.*)%%(.*)$'
error "Invalid replacement pattern #{pattern}" unless reprefix
if (str\sub 1, #prefix)==prefix and (str\sub -#suffix)==suffix
return reprefix..(str\sub #prefix+1, -#suffix-1)..resuffix
str
env= (key, def) ->
(os.getenv key) or def
{
-- table functions
:min, :max
:foreach
:first
:insert, :unpack, :concat
:exclude
:flatten
-- file functions
:wildcard
:mtime, :exists
-- command functions
:run, :popen
-- string functions
:patsubst
:env
}
Loading…
Cancel
Save