1
0
mirror of https://github.com/natnat-mc/moonbuild synced 2026-07-07 02:07:12 +02:00
This commit is contained in:
Codinget
2020-11-07 21:01:26 +01:00
parent ceada11b0b
commit c8670ab903
40 changed files with 1540 additions and 2230 deletions
+15
View File
@@ -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]
_
+21
View File
@@ -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'}
+121
View File
@@ -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
}
+29
View File
@@ -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
}
+46
View File
@@ -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
}
+162
View File
@@ -0,0 +1,162 @@
import sort, concat from table
import huge from math
import match, sub from string
common = {}
flatten = (list, ...) ->
return flatten {list, ...} if (select '#', ...)!=0
t = type list
switch t
when 'nil'
{}
when 'string'
{list}
when 'number'
{tostring list}
when 'boolean'
{list}
when 'table'
keys = [k for k in pairs list]
sort keys
elements, i = {}, 1
for k in *keys
if (type k)=='number'
for e in *(flatten list[k])
elements[i], i = e, i+1
else
return {list}
setmetatable elements, __tostring: => concat @, ' '
else
error "can't flatten elements of type #{t}"
first = (list, ...) ->
t = type list
switch t
when 'nil'
if (select '#', ...)==0
nil
else
first ...
when 'string'
list
when 'number'
tostring list
when 'boolean'
list
when 'table'
min = huge
for k in pairs list
if (type k) == 'number'
min = k if k < min
else
return list
first list[min]
else
error "can't find first of type #{t}"
foreach = (list, fn) ->
[fn v for v in *flatten list]
filter = (list, fn) ->
[v for v in *flatten list when fn v]
includes = (list, v) ->
return true if list==v
if (type list) == 'table'
for k, e in pairs list
if (type k) == 'number'
return true if includes e, v
if (type list) == 'number'
return (tostring list) == (tostring v)
false
patget = (s, pat) ->
prefix, suffix = match pat, '^(.*)%%(.*)$'
return s==pat and s or nil unless prefix
if (sub s, 1, #prefix)==prefix and (suffix == '' or (sub s, -#suffix)==suffix)
sub s, #prefix+1, -#suffix-1
else
nil
patset = (s, rep) ->
prefix, suffix = match rep, '^(.*)%%(.*)$'
if prefix
prefix..s..suffix
else
rep
patsubst = (s, pat, rep) ->
prefix, suffix = match pat, '^(.*)%%(.*)$'
rprefix, rsuffix = match rep, '^(.*)%%(.*)$'
t = type s
f = false
if t=='nil'
return nil
if t=='number'
t = 'string'
s = tostring s
if t=='string'
t = 'table'
s = {s}
f = true
if t!='table'
error "can't substitute patterns on type #{t}"
r, i = {}, 1
for s in *flatten s
if not prefix
if s==pat
if rprefix
r[i], i = rprefix..s..rsuffix, i+1
else
r[i], i = rep, i+1
elseif (sub s, 1, #prefix)==prefix and (suffix == '' or (sub s, -#suffix)==suffix)
if rprefix
r[i], i = rprefix..(sub s, #prefix+1, -#suffix-1)..rsuffix, i+1
else
r[i], i = rep, i+1
f and r[1] or r
exclude = (list, ...) ->
exclusions = flatten ...
[v for v in *flatten list when not includes exclusions, v]
min = (list) ->
m = list[1]
for i=2, #list
e = list[i]
m = e if e<m
m
max = (list) ->
m = list[1]
for i=2, #list
e = list[i]
m = e if e>m
m
minmax = (list) ->
m = list[1]
M = list[1]
for i=2, #list
e = list[i]
m = e if e<m
M = e if e>M
m, M
common.flatten = flatten
common.first = first
common.foreach = foreach
common.filter = filter
common.includes = includes
common.patget = patget
common.patset = patset
common.patsubst = patsubst
common.min = min
common.max = max
common.minmax = minmax
setmetatable common, __call: => [k for k in pairs common]
+198
View File
@@ -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'}
+54
View File
@@ -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}"
}
+12
View File
@@ -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
}
+68
View File
@@ -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
}
+47
View File
@@ -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]
+91
View File
@@ -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 }
+29
View File
@@ -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 }
+10
View File
@@ -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
}
+12
View File
@@ -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
+38
View File
@@ -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
+182
View File
@@ -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
+54
View File
@@ -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
+11
View File
@@ -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 = ...
+61
View File
@@ -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
+16
View File
@@ -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
+18
View File
@@ -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
+32
View File
@@ -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
+31
View File
@@ -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
-52
View File
@@ -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
-124
View File
@@ -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
}
-37
View File
@@ -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
}
-69
View File
@@ -1,69 +0,0 @@
import insert, remove, concat, sort from table
unpack or= table.unpack
sortedpairs = (table, cmp) ->
keys = [k for k in pairs table]
sort keys, cmp
coroutine.wrap ->
for key in *keys
coroutine.yield key, table[key]
min = (table, cmp=(a, b) -> a<b) ->
val = table[1]
for i=2, #table
elem = table[i]
if cmp elem, val
val = elem
val
max = (table, cmp=(a, b) -> a<b) ->
val = table[1]
for i=2, #table
elem = table[i]
if not cmp elem, val
val = elem
val
foreach = (tab, fn) ->
[fn e for e in *tab]
first = (tab, fn) ->
for e in *tab
return e if fn e
exclude = (tab, ...) ->
i=1
while i<=#tab
removed=false
for j=1, select '#', ...
if tab[i]==select j, ...
remove tab, i
removed = true
break
i += 1 unless removed
tab
flatten = (tab) ->
return {tab} if (type tab)!='table'
out = {}
for e in *tab
if (type e)=='table'
if e[1] == nil and (next e)!=nil
insert out, e
else
insert out, v for v in *flatten e
else
insert out, e
out
{
:min, :max
:foreach
:first
:exclude
:flatten
:sortedpairs
:insert, :remove, :concat, :sort
:unpack
}
-95
View File
@@ -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
}