Browse Source

Move luac.cross build into standard make hierarchy

TerryE 6 years ago
parent
commit
4141e69003

+ 0 - 8
.travis.yml

@@ -5,21 +5,13 @@ addons:
     packages:
     - python-serial
     - srecord
-    - lua5.1
 cache:
-  - pip
   - directories:
     - cache
-before_install:
-- pip install --user hererocks esptool
-- hererocks env --lua 5.1 -rlatest
-- source env/bin/activate
-- luarocks install luafilesystem
 install:
 - tar -Jxvf tools/esp-open-sdk.tar.xz
 - export PATH=$PATH:$PWD/esp-open-sdk/xtensa-lx106-elf/bin
 script:
-- lua tools/cross-lua.lua || exit 1
 - export BUILD_DATE=$(date +%Y%m%d)
 - make EXTRA_CCFLAGS="-DBUILD_DATE='\"'$BUILD_DATE'\"'" all
 - cd bin/

+ 1 - 1
app/include/module.h

@@ -1,4 +1,4 @@
-#ifndef __MODULE_H__
+#if !defined(__MODULE_H_) && !defined(LUA_CROSS_COMPILER)
 #define __MODULE_H__
 
 #include "user_modules.h"

+ 1 - 0
app/lua/Makefile

@@ -12,6 +12,7 @@
 #     a generated lib/image xxx.a ()
 #
 ifndef PDIR
+SUBDIRS = luac_cross
 GEN_LIBS = liblua.a
 endif
 

+ 33 - 0
app/lua/lflash.h

@@ -0,0 +1,33 @@
+/*
+** lflashe.h
+** See Copyright Notice in lua.h
+*/
+
+#if defined(LUA_FLASH_STORE) && !defined(lflash_h)
+#define lflash_h
+
+#include "lobject.h"
+#include "lstate.h"
+#include "lzio.h"
+
+#define FLASH_SIG          0xfafaaf00
+
+typedef lu_int32 FlashAddr;
+typedef struct {
+  lu_int32  flash_sig;      /* a stabdard fingerprint identifying an LFS image */
+  lu_int32  flash_size;     /* Size of LFS image */
+  FlashAddr mainProto;      /* address of main Proto in Proto hierarchy */
+  FlashAddr pROhash;        /* address of ROstrt hash */
+  lu_int32  nROuse;         /* number of elements in ROstrt */
+  int       nROsize;        /* size of ROstrt */
+  lu_int32  fill1;          /* reserved */
+  lu_int32  fill2;          /* reserved */
+} FlashHeader;
+
+void luaN_user_init(void);
+LUAI_FUNC void luaN_init (lua_State *L);
+LUAI_FUNC int  luaN_flashSetup (lua_State *L);
+LUAI_FUNC int  luaN_reload_reboot (lua_State *L);
+LUAI_FUNC int  luaN_index (lua_State *L);
+#endif
+

+ 2 - 0
app/lua/lrotable.c

@@ -18,6 +18,7 @@ extern const luaR_table lua_rotable[];
 
 /* Find a global "read only table" in the constant lua_rotable array */
 void* luaR_findglobal(const char *name, unsigned len) {
+#ifndef LUA_CROSS_COMPILER
   unsigned i;
 
   if (c_strlen(name) > LUA_MAX_ROTABLE_NAME)
@@ -26,6 +27,7 @@ void* luaR_findglobal(const char *name, unsigned len) {
     if (*lua_rotable[i].name != '\0' && c_strlen(lua_rotable[i].name) == len && !c_strncmp(lua_rotable[i].name, name, len)) {
       return (void*)(lua_rotable[i].pentries);
     }
+#endif
   return NULL;
 }
 

+ 3 - 2
app/lua/luac_cross.h

@@ -56,9 +56,10 @@
 #define c_strrchr strrchr
 #define c_strstr strstr
 double	c_strtod(const char *__n, char **__end_PTR);
-#define c_strtoul strtoul
 #define c_ungetc ungetc
-
+#define c_strtol strtol
+#define c_strtoul strtoul
+#define dbg_printf printf
 #else
 
 #define C_HEADER_ASSERT "c_assert.h"

+ 88 - 0
app/lua/luac_cross/Makefile

@@ -0,0 +1,88 @@
+# 
+# This Make file is called from the core Makefile hierarchy with is a hierarchical
+# make wwhich uses parent callbacks to implement inheritance.  However is luac_cross
+# build stands outside this and uses the host toolchain to implement a separate
+# host build of the luac.cross image. 
+#
+.NOTPARALLEL:
+
+CCFLAGS:= -I.. -I../../include -I../../../include -I ../../libc
+LDFLAGS:= -L$(SDK_DIR)/lib -L$(SDK_DIR)/ld -lm
+
+CCFLAGS += -Wall
+
+DEFINES += -DLUA_CROSS_COMPILER
+
+TARGET = host
+
+ifeq ($(FLAVOR),release)
+    CCFLAGS        += -O2
+    TARGET_LDFLAGS += -O2
+else
+    FLAVOR         =  debug
+    CCFLAGS        += -O2 -g
+    TARGET_LDFLAGS += -O2 -g
+endif
+
+LUACSRC := luac.c     lflashimg.c loslib.c    print.c
+LUASRC  := lapi.c     lauxlib.c   lbaselib.c  lcode.c    ldblib.c \
+           ldebug.c   ldo.c       ldump.c     lfunc.c    lgc.c \
+           llex.c     lmathlib.c  lmem.c      loadlib.c  lobject.c \
+           lopcodes.c lparser.c   lrotable.c  lstate.c   lstring.c \
+           lstrlib.c  ltable.c    ltablib.c   ltm.c      lundump.c \
+           lvm.c      lzio.c
+LIBCSRC := c_stdlib.c
+
+#
+# This relies on the files being unique on the vpath
+#
+SRC      := $(LUACSRC) $(LUASRC) $(LIBCSRC)
+vpath %.c .:..:../../libc
+
+
+ODIR   := .output/$(TARGET)/$(FLAVOR)/obj
+
+OBJS   := $(SRC:%.c=$(ODIR)/%.o)
+DEPS   := $(SRC:%.c=$(ODIR)/%.d)
+
+CFLAGS = $(CCFLAGS) $(DEFINES)  $(EXTRA_CCFLAGS) $(STD_CFLAGS) $(INCLUDES)
+DFLAGS = $(CCFLAGS) $(DDEFINES) $(EXTRA_CCFLAGS) $(STD_CFLAGS) $(INCLUDES)
+
+CC := gcc
+
+ECHO := echo
+
+IMAGE  := ../../../luac.cross
+
+.PHONY: test clean all
+
+all: $(DEPS) $(IMAGE)
+
+$(IMAGE) : $(OBJS)
+	$(CC) $(OBJS) -o $@ $(LDFLAGS)
+
+test :
+	@echo CC: $(CC)
+	@echo SRC: $(SRC)
+	@echo OBJS: $(OBJS)
+	@echo DEPS: $(DEPS)
+
+clean :
+	$(RM) -r $(ODIR)
+
+-include $(DEPS)
+
+$(ODIR)/%.o: %.c
+	@mkdir -p $(ODIR);
+	$(CC) $(if $(findstring $<,$(DSRCS)),$(DFLAGS),$(CFLAGS)) $(COPTS_$(*F)) -o $@ -c $<
+
+$(ODIR)/%.d: %.c
+	@mkdir -p $(ODIR);
+	@echo DEPEND: $(CC) -M $(CFLAGS) $<
+	@set -e; rm -f $@; \
+	$(CC) -M $(CFLAGS) $< > $@.$$$$; \
+	sed 's,\($*\.o\)[ :]*,$(ODIR)/\1 $@ : ,g' < $@.$$$$ > $@; \
+	rm -f $@.$$$$
+
+#  echo 's,\($*\.o\)[ :]*,$(ODIR)/\1 $@ : ,g'; \ 
+

+ 394 - 0
app/lua/luac_cross/lflashimg.c

@@ -0,0 +1,394 @@
+/*
+** lflashimg.c
+** Dump a compiled Proto hiearchy to a RO (FLash) image file
+** See Copyright Notice in lua.h
+*/
+
+#define LUAC_CROSS_FILE
+
+#include "luac_cross.h"
+#include C_HEADER_CTYPE
+#include C_HEADER_STDIO
+#include C_HEADER_STDLIB
+#include C_HEADER_STRING
+
+#define lflashimg_c
+#define LUA_CORE
+#include "lobject.h"
+#include "lstring.h"
+#undef LUA_FLASH_STORE
+#define LUA_FLASH_STORE
+#include "lflash.h"
+
+//#define LOCAL_DEBUG
+
+#if INT_MAX != 2147483647
+# error "luac.cross requires C toolchain with 4 byte word size"
+#endif
+#define WORDSIZE ((int) sizeof(int))
+#define ALIGN(s) (((s)+(WORDSIZE-1)) & (-(signed) WORDSIZE))
+#define WORDSHIFT 2
+typedef unsigned int uint;
+#define FLASH_WORDS(t) (sizeof(t)/sizeof(FlashAddr))
+/*
+ *
+ * This dumper is a variant of the standard ldump, in that instead of producing a
+ * binary loader format that lundump can load, it produced an image file that can be
+ * directly mapped or copied into addressable memory. The typical application is on
+ * small memory IoT devices which support programmable flash storage such as the
+ * ESP8266. A 64 Kb LFS image has 16Kb words and will enable all program-related
+ * storage to be accessed directly from flash, leaving the RAM for true R/W application
+ * data.
+ *
+ * The start address of the Lua Flash Store (LFS) is build-dependent,. However, by
+ * adopting a position independent image format, cross compilation can leave this
+ * detail to the on-device image loader. As all objects in the LFS can be treated as
+ * multiples of 4-byte words. Although some record field are byte-size and can be byte
+ * packed, all other fields are word aligned, and in particular any address references
+ * within the LFS are word-aligned and also refer to word-aligned addresses within the
+ * LFS.
+ *
+ * In order to make the LFS position independent, such addresses are stored in a
+ * special format, where each PIC address is two 16-bit unsigned offsets:
+ *
+ *   Bits 0-15 is the offset into the LFS that this address refers to
+ *   Bits 16-31 is the offset linking to the PIC next address.
+ *
+ * Hence the LFS can be up to 256Kb in length and the flash loader can use the forward
+ * links to chain down from the mainProto address at offet 3 to all image addresses
+ * during load and convert them to the corresponding correct absolute memory addresses.
+ *
+ * The flash image has a standard header detailed in lflash.h
+ *
+ * Note that luac.cross may be compiled on any little-endian machine with 32 or 64 bit
+ * word length so Flash addresses cant be handled as standard C pointers as size_t and
+ * int may not have the same size. Hence addresses with the must be declared as the
+ * FlashAddr type rather than typed C pointers and must be accessed through macros.
+ *
+ * The Flash image is assembled up by first building the RO stringtable containing
+ * all strings used in the compiled proto hierarchy.  This is followed by the Protos.
+ *
+ * The storage is allocated bottom up using a serial allocator and the algortihm for
+ * building the image essentially does a bottom-uo serial enumeration so that any
+ * referenced storage has already been allocated in the image, and therefore (with the
+ * exception of the Flash Header) all pointer references are backwards.
+ *
+ * As addresses are 4 byte on the target and either 4 or (typically) 8 bytes on the
+ * host so any structures containing address fields (TStrings, TValues, Protos, other
+ * address vectors) need repacking.
+ */
+
+typedef struct flashts {       /* This is the fixed 32-bit equivalent of TString */
+  FlashAddr next;
+  lu_byte   tt;
+  lu_byte   marked;
+  int       hash;
+  int       len;
+} FlashTS;
+
+#ifndef LUA_MAX_FLASH_SIZE
+#define LUA_MAX_FLASH_SIZE  0x10000  //in words
+#endif
+
+static uint curOffset = 0;
+static uint flashImage[LUA_MAX_FLASH_SIZE];
+static unsigned char flashAddrTag[LUA_MAX_FLASH_SIZE/WORDSIZE];
+
+#define fatal luac_fatal
+extern void __attribute__((noreturn)) luac_fatal(const char* message);
+
+#ifdef LOCAL_DEBUG
+#define DBG_PRINT(...) printf(__VA_ARGS__)
+#else
+#define DBG_PRINT(...) ((void)0)
+#endif
+
+#define FLASH_SIG 0xfafaaf00
+/*
+ *  Serial allocator.  Throw a luac-style out of memory error is allocaiton fails.
+ */
+static void *flashAlloc(lua_State* L, size_t n) {
+  void *p = (void *)(flashImage + curOffset);
+  curOffset += ALIGN(n)>>WORDSHIFT;
+  if (curOffset > LUA_MAX_FLASH_SIZE) {
+    fatal("Out of Flash memmory");
+  }
+  return p;
+}
+
+/*
+ * Convert an absolute address pointing inside the flash image to offset form.
+ * This macro form also takes the lvalue destination so that this can be tagged
+ * as a relocatable address.
+ */
+#define toFlashAddr(l, pd, s) _toFlashAddr(l, &(pd), s)
+static void _toFlashAddr(lua_State* L, FlashAddr *a, void *p) {
+  uint doffset = cast(char *, a) - cast(char *,flashImage);
+  lua_assert(!(doffset & (WORDSIZE-1)));
+  doffset >>= WORDSHIFT;
+  lua_assert(doffset <= curOffset);
+  if (p) {
+    uint poffset = cast(char *, p) - cast(char *,flashImage);
+    lua_assert(!(poffset & (WORDSIZE-1)));
+    poffset >>= WORDSHIFT;
+    lua_assert(poffset <= curOffset);
+    flashImage[doffset] = poffset;     // Set the pointer to the offset
+    flashAddrTag[doffset] = 1;         // And tag as an address
+  } else {                             // Special case for NULL pointer
+    flashImage[doffset] = 0;
+  }
+}
+
+/*
+ * Convert an image address in offset form back to (host) absolute form
+ */
+static void *fromFashAddr(FlashAddr a) {
+  return a ? cast(void *, flashImage + a) : NULL;
+}
+
+
+/*
+ * Add a TS found in the Proto Load to the table at the ToS
+ */
+static void addTS(lua_State *L, TString *ts) {
+  lua_assert(ttisstring(&(ts->tsv)));
+  lua_pushnil(L);
+  setsvalue(L, L->top-1, ts);
+  lua_pushinteger(L, 1);
+  lua_rawset(L, -3);
+  DBG_PRINT("Adding string: %s\n",getstr(ts));
+}
+
+
+/*
+ * Enumerate all of the Protos in the Proto hiearchy and scan contents to collect
+ * all referenced strings in a Lua Array at ToS.
+ */
+static void scanProtoStrings(lua_State *L, const Proto* f) {
+  /* Table at L->Top[-1] is used to collect the strings */
+  int i;
+
+  if (f->source)
+    addTS(L, f->source);
+
+#ifdef LUA_OPTIMIZE_DEBUG
+  if (f->packedlineinfo)
+    addTS(L, luaS_new(L, cast(const char *, f->packedlineinfo)));
+#endif
+
+  for (i = 0; i < f->sizek; i++) {
+    if (ttisstring(f->k + i))
+      addTS(L, rawtsvalue(f->k + i));
+  }
+  for (i = 0; i < f->sizeupvalues; i++) addTS(L, f->upvalues[i]);
+  for (i = 0; i < f->sizelocvars; i++)  addTS(L, f->locvars[i].varname);
+  for (i = 0; i < f->sizep; i++)        scanProtoStrings(L, f->p[i]);
+}
+
+
+/*
+ * Use the collected strings table to build the new ROstrt in the Flash Image
+ *
+ * The input is an array of {"SomeString" = 1, ...} on the ToS.
+ * The output is an array of {"SomeString" = FlashOffset("SomeString"), ...} on ToS
+ */
+static void createROstrt(lua_State *L, FlashHeader *fh) {
+
+  /* Table at L->Top[-1] on input is hash used to collect the strings */
+  /* Count the number of strings.  Can't use objlen as this is a hash */
+  fh->nROuse  = 0;
+  lua_pushnil(L);  /* first key */
+  while (lua_next(L, -2) != 0) {
+    fh->nROuse++;
+    DBG_PRINT("Found: %s\n",getstr(rawtsvalue(L->top-2)));
+    lua_pop(L, 1);                           // dump the value
+  }
+  fh->nROsize = 2<<luaO_log2(fh->nROuse);
+  FlashAddr *hashTab = flashAlloc(L, fh->nROsize * WORDSIZE);
+  toFlashAddr(L, fh->pROhash, hashTab);
+
+  /* Now iterate over the strings to be added to the RO string table and build it */
+  lua_newtable(L);                           // add output table
+  lua_pushnil(L);                            // First key
+  while (lua_next(L, -3) != 0) {             // replaces key, pushes value
+    TString *ts   = rawtsvalue(L->top - 2);  // key.ts
+    const char *p = getstr(ts);              // C string of key
+    uint hash     = ts->tsv.hash;            // hash of key
+    size_t len  = ts->tsv.len;               // and length
+
+    DBG_PRINT("2nd pass: %s\n",p);
+
+    FlashAddr *e  = hashTab + lmod(hash, fh->nROsize);
+    FlashTS *last = cast(FlashTS *, fromFashAddr(*e));
+    FlashTS *fts  = cast(FlashTS *, flashAlloc(L, sizeof(FlashTS)));
+    toFlashAddr(L, *e, fts);                 // add reference to TS to lookup vector
+    toFlashAddr(L, fts->next, last);         // and chain to previous entry if any
+    fts->tt     = LUA_TSTRING;               // Set as String
+    fts->marked = bitmask(FIXEDBIT);         // Fixed with no Whitebits set
+    fts->hash   = hash;                      // add hash
+    fts->len    = len;                       // and length
+    memcpy(flashAlloc(L, ALIGN(len+1)), p, ALIGN(len+1)); // copy string
+                                             // include the trailing null char
+    lua_pop(L, 1);                           // Junk the value
+    lua_pushvalue(L, -1);                    // Dup the key as rawset dumps its copy
+    lua_pushinteger(L, cast(FlashAddr*,fts)-flashImage);  // Value is new TS offset.
+    lua_rawset(L, -4);                       // Add to new table
+  }
+  /* At this point the old hash is done to derefence for GC */
+  lua_remove(L, -2);
+}
+
+/*
+ * Convert a TString reference in the host G(L)->strt entry into the corresponding
+ * TString address in the flashImage using the lookup table at ToS
+ */
+static void *resolveTString(lua_State* L, TString *s) {
+  if (!s)
+    return NULL;
+  lua_pushnil(L);
+  setsvalue(L, L->top-1, s);
+  lua_rawget(L, -2);
+  lua_assert(!lua_isnil(L, -1));
+  void *ts = fromFashAddr(lua_tointeger(L, -1));
+  lua_pop(L, 1);
+  return ts;
+}
+
+/*
+ * In order to simplify repacking of structures from the host format to that target
+ * format, this simple copy routine is data-driven by a simple format specifier.
+ *   n       Number of consecutive records to be processed
+ *   fmt     A string of A,I, S, V specifiers spanning the record.
+ *   src     Source of record
+ *   returns Address of destination record
+ */
+static void *flashCopy(lua_State* L, int n, const char *fmt, void *src) {
+  /* ToS is the string address mapping table */
+  if (n == 0)
+    return NULL;
+  int i;
+  void *newts;
+  // A bit of a botch because fmt is either "V" or a string of WORDSIZE specifiers */
+  int recsize = fmt[0]=='V' ? 16 : WORDSIZE * strlen(fmt);
+
+  uint *d  = cast(uint *, flashAlloc(L, n * recsize));
+  uint *dest = d;
+  uint *s  = cast(uint *, src);
+
+  for (i = 0; i < n; i++) {
+    const char *p = fmt;
+    while (*p) {
+      /* All input address types (A,S,V) are aligned to size_t boundaries */
+      if (*p != 'I' && ((size_t)s)&(sizeof(size_t)-1))
+        s++;
+      switch (*p++) {
+        case 'A':
+          toFlashAddr(L, *d, *cast(void**, s));
+          d++;
+          s += FLASH_WORDS(size_t);
+          break;
+        case 'I':
+          *d++ = *s++;
+          break;
+        case 'S':
+          newts = resolveTString(L, *cast(TString **, s));
+          toFlashAddr(L, *d, newts);
+          d++;
+          s += FLASH_WORDS(size_t);
+          break;
+        case 'V':
+          memcpy(d, s, sizeof(TValue));
+          TValue *sv = cast(TValue *, s);
+          if (ttisstring(sv)) {
+            newts = resolveTString(L, rawtsvalue(sv));
+            toFlashAddr(L, *d, newts);
+            d[1] = 0;
+          } else {
+            lua_assert(!iscollectable(sv));
+          }
+          d += FLASH_WORDS(TValue);
+          s += FLASH_WORDS(TValue);
+          break;
+        default:
+          lua_assert (0);
+      }
+    }
+  }
+  return dest;
+}
+
+/* The debug optimised version has a different Proto layout */
+#ifdef LUA_OPTIMIZE_DEBUG
+#define PROTO_COPY_MASK  "AIAAAAAASIIIIIIIAI"
+#else
+#define PROTO_COPY_MASK  "AIAAAAAASIIIIIIIIAI"
+#endif
+
+/*
+ * Do the actual prototype copy.
+ */
+static void *functionToFlash(lua_State* L, const Proto* orig) {
+  Proto f;
+  int i;
+
+  memcpy (&f, orig, sizeof(Proto));
+  f.gclist = NULL;
+  f.next = NULL;
+  l_setbit(f.marked, FIXEDBIT);
+
+  if (f.sizep) {                /* clone included Protos */
+    Proto **p = luaM_newvector(L, f.sizep, Proto *);
+    for (i=0; i<f.sizep; i++)
+      p[i] = cast(Proto *, functionToFlash(L, f.p[i]));
+    f.p = cast(Proto **, flashCopy(L, f.sizep, "A", p));
+    luaM_freearray(L, p, f.sizep, Proto *);
+  }
+  f.k = cast(TValue *, flashCopy(L, f.sizek, "V", f.k));
+  f.code = cast(Instruction *, flashCopy(L, f.sizecode, "I", f.code));
+
+#ifdef LUA_OPTIMIZE_DEBUG
+  if (f.packedlineinfo) {
+    TString *ts=luaS_new(L, cast(const char *,f.packedlineinfo));
+    f.packedlineinfo = cast(unsigned char *, resolveTString(L, ts)) + sizeof (FlashTS);
+  }
+#else
+  f.lineinfo = cast(int *, flashCopy(L, f.sizelineinfo, "I", f.lineinfo));
+#endif
+  f.locvars = cast(struct LocVar *, flashCopy(L, f.sizelocvars, "SII", f.locvars));
+  f.upvalues = cast(TString **, flashCopy(L, f.sizeupvalues, "S", f.upvalues));
+  return cast(void *, flashCopy(L, 1, PROTO_COPY_MASK, &f));
+}
+
+/*
+ * Scan through the tagged address to form linked chain.  Do the scan backwards
+ * from at last octaword including curOffset, as this makes the 'last' address
+ * forward reference for the on-chip LFS loader.
+ */
+void  linkPICaddresses(void){
+  int i, last = 0;
+  for (i = curOffset-1 ; i >= 0; i--) {
+    if (flashAddrTag[i]) {
+      lua_assert(flashImage[i]<curOffset);
+      flashImage[i] |= last<<16;
+      last = i;
+    }
+  }
+}
+
+
+uint dumpToFlashImage (lua_State* L, const Proto *main, lua_Writer w, void* data, int strip)
+{
+// parameter strip is ignored for now
+  lua_newtable(L);
+  FlashHeader *fh = cast(FlashHeader *, flashAlloc(L, sizeof(FlashHeader)));
+  scanProtoStrings(L, main);
+  createROstrt(L,  fh);
+  toFlashAddr(L, fh->mainProto, functionToFlash(L, main));
+  fh->flash_sig = FLASH_SIG;
+  fh->flash_size = curOffset*WORDSIZE;
+  linkPICaddresses();
+  lua_unlock(L);
+  int status = w(L, flashImage, curOffset * sizeof(uint), data);
+  lua_lock(L);
+  return status;
+}

+ 100 - 48
app/lua/luac_cross/luac.c

@@ -11,6 +11,7 @@
 #include C_HEADER_STDIO
 #include C_HEADER_STDLIB
 #include C_HEADER_STRING
+#include <time.h>
 
 #define luac_c
 #define LUA_CORE
@@ -31,17 +32,21 @@
 
 static int listing=0;			/* list bytecodes? */
 static int dumping=1;			/* dump bytecodes? */
-static int stripping=0;			/* strip debug information? */
+static int stripping=0;	  /* strip debug information? */
+static int flash=0;	  		/* output flash image */
+static int lookup=0;	  		/* output lookup-style master combination header */
 static char Output[]={ OUTPUT };	/* default output file name */
 static const char* output=Output;	/* actual output file name */
 static const char* progname=PROGNAME;	/* actual program name */
 static DumpTargetInfo target;
 
-static void fatal(const char* message)
+void luac_fatal(const char* message)
 {
  fprintf(stderr,"%s: %s\n",progname,message);
  exit(EXIT_FAILURE);
 }
+#define fatal(s) luac_fatal(s)
+
 
 static void cannot(const char* what)
 {
@@ -61,12 +66,11 @@ static void usage(const char* message)
  "  -        process stdin\n"
  "  -l       list\n"
  "  -o name  output to file " LUA_QL("name") " (default is \"%s\")\n"
+ "  -f       output a flash image file\n"
+ "  -i       generate lookup combination master (default with option -f)\n" 
  "  -p       parse only\n"
  "  -s       strip debug information\n"
  "  -v       show version information\n"
- "  -cci bits       cross-compile with given integer size\n"
- "  -ccn type bits  cross-compile with given lua_Number type and size\n"
- "  -cce endian     cross-compile with given endianness ('big' or 'little')\n"
  "  --       stop handling options\n",
  progname,Output);
  exit(EXIT_FAILURE);
@@ -91,6 +95,13 @@ static int doargs(int argc, char* argv[])
   }
   else if (IS("-"))			/* end of options; use stdin */
    break;
+  else if (IS("-f"))			/* Flash image file */
+  {
+   flash=1;
+   lookup=1;
+  }
+  else if (IS("-i"))			/* lookup */
+   lookup = 1;
   else if (IS("-l"))			/* list */
    ++listing;
   else if (IS("-o"))			/* output file */
@@ -99,42 +110,17 @@ static int doargs(int argc, char* argv[])
    if (output==NULL || *output==0) usage(LUA_QL("-o") " needs argument");
    if (IS("-")) output=NULL;
   }
+
   else if (IS("-p"))			/* parse only */
-   dumping=0;
+   dumping=0;   
   else if (IS("-s"))			/* strip debug information */
    stripping=1;
   else if (IS("-v"))			/* show version */
    ++version;
-  else if (IS("-cci")) /* target integer size */
-  {
-   int s = target.sizeof_int = atoi(argv[++i])/8;
-   if (!(s==1 || s==2 || s==4)) fatal(LUA_QL("-cci") " must be 8, 16 or 32");
-  }
-  else if (IS("-ccn")) /* target lua_Number type and size */
-  {
-   const char *type=argv[++i];
-   if (strcmp(type,"int")==0) target.lua_Number_integral=1;
-   else if (strcmp(type,"float")==0) target.lua_Number_integral=0;
-   else if (strcmp(type,"float_arm")==0)
-   {
-     target.lua_Number_integral=0;
-     target.is_arm_fpa=1;
-   }
-   else fatal(LUA_QL("-ccn") " type must be " LUA_QL("int") " or " LUA_QL("float") " or " LUA_QL("float_arm"));
-   int s = target.sizeof_lua_Number = atoi(argv[++i])/8;
-   if (target.lua_Number_integral && !(s==1 || s==2 || s==4)) fatal(LUA_QL("-ccn") " size must be 8, 16, or 32 for int");
-   if (!target.lua_Number_integral && !(s==4 || s==8)) fatal(LUA_QL("-ccn") " size must be 32 or 64 for float");
-  }
-  else if (IS("-cce")) /* target endianness */
-  {
-   const char *val=argv[++i];
-   if (strcmp(val,"big")==0) target.little_endian=0;
-   else if (strcmp(val,"little")==0) target.little_endian=1;
-   else fatal(LUA_QL("-cce") " must be " LUA_QL("big") " or " LUA_QL("little"));
-  }
   else					/* unknown option */
-   usage(argv[i]);
+  usage(argv[i]);
  }
+
  if (i==argc && (listing || !dumping))
  {
   dumping=0;
@@ -150,30 +136,87 @@ static int doargs(int argc, char* argv[])
 
 #define toproto(L,i) (clvalue(L->top+(i))->l.p)
 
-static const Proto* combine(lua_State* L, int n)
+static TString *corename(lua_State *L, const TString *filename) 
 {
- if (n==1)
+ const char *fn = getstr(filename)+1;
+ const char *s = strrchr(fn, '/');
+ s = s ? s + 1 : fn;
+ while (*s == '.') s++;
+ const char *e = strchr(s, '.');
+ int l = e ? e - s: strlen(s);
+ return l ? luaS_newlstr (L, s, l) : luaS_new(L, fn);
+} 
+/*
+ * If the luac command line includes multiple files or has the -f option 
+ * then luac generates a main function to reference all sub-main prototypes.
+ * This is one of two types:
+ *   Type 0   The standard luac combination main
+ *   Type 1   A lookup wrapper that facilitates indexing into the gernated protos 
+ */
+static const Proto* combine(lua_State* L, int n, int type)
+{
+ if (n==1 && type == 0)
   return toproto(L,-1);
  else
  {
-  int i,pc;
+  int i,pc,stacksize;
+  Instruction *code;
   Proto* f=luaF_newproto(L);
   setptvalue2s(L,L->top,f); incr_top(L);
   f->source=luaS_newliteral(L,"=(" PROGNAME ")");
-  f->maxstacksize=1;
-  pc=2*n+1;
-  f->code=luaM_newvector(L,pc,Instruction);
-  f->sizecode=pc;
   f->p=luaM_newvector(L,n,Proto*);
   f->sizep=n;
+  for (i=0; i<n; i++) f->p[i]=toproto(L,i-n-1);
   pc=0;
-  for (i=0; i<n; i++)
+
+  if (type == 0)
   {
-   f->p[i]=toproto(L,i-n-1);
-   f->code[pc++]=CREATE_ABx(OP_CLOSURE,0,i);
-   f->code[pc++]=CREATE_ABC(OP_CALL,0,1,1);
+  /*
+   * Type 0 is as per the standard luac, which is just a main routine which 
+   * invokes all of the compiled functions sequentially.  This is fine if 
+   * they are self registering modules, but useless otherwise.
+   */
+   stacksize = 1;
+   code=luaM_newvector(L,2*n+1,Instruction);
+   for (i=0; i<n; i++)
+   {
+    code[pc++]=CREATE_ABx(OP_CLOSURE,0,i);
+    code[pc++]=CREATE_ABC(OP_CALL,0,1,1);
+   }
+   code[pc++]=CREATE_ABC(OP_RETURN,0,1,0);
   }
-  f->code[pc++]=CREATE_ABC(OP_RETURN,0,1,0);
+  else
+  {
+  /*
+   * The Type 1 main() is a lookup which takes a single argument, the name to  
+   * be resolved. If this matches root name of one of the compiled files then
+   * a closure to this file main is returned.  If the name is "list": then the
+   * list of root names is returned, else a nil return.
+   */
+   lua_assert(n<LFIELDS_PER_FLUSH);
+   stacksize = n+2;
+   code=luaM_newvector(L,5*n+3  ,Instruction);
+   f->k=luaM_newvector(L,n+1,TValue);
+   f->sizek=n+1;
+   setnvalue(f->k, (lua_Number) time(NULL));
+   for (i=0; i<n; i++)  
+   {
+    /* if arg1 == FnameA then return function (...) -- funcA -- end end */
+    setsvalue2n(L,f->k+i+1,corename(L, f->p[i]->source));
+    code[pc++]=CREATE_ABC(OP_EQ,0,0,RKASK(i+1)); 
+    code[pc++]=CREATE_ABx(OP_JMP,0,MAXARG_sBx+2);
+    code[pc++]=CREATE_ABx(OP_CLOSURE,1,i);
+    code[pc++]=CREATE_ABC(OP_RETURN,1,2,0);
+   }
+   for (i=0; i<=n; i++) code[pc++]=CREATE_ABx(OP_LOADK,i+1,i);
+   /* should be a block loop */
+   code[pc++]=CREATE_ABC(OP_RETURN,1,2+n,0);
+   code[pc++]=CREATE_ABC(OP_RETURN,0,1,0);
+  }
+  f->numparams=1;
+  f->maxstacksize=stacksize;
+  f->code=code;
+  f->sizecode=pc;
   return f;
  }
 }
@@ -189,6 +232,8 @@ struct Smain {
  char** argv;
 };
 
+extern uint dumpToFlashImage (lua_State* L,const Proto *main, lua_Writer w, void* data, int strip);
+
 static int pmain(lua_State* L)
 {
  struct Smain* s = (struct Smain*)lua_touserdata(L, 1);
@@ -202,14 +247,21 @@ static int pmain(lua_State* L)
   const char* filename=IS("-") ? NULL : argv[i];
   if (luaL_loadfile(L,filename)!=0) fatal(lua_tostring(L,-1));
  }
- f=combine(L,argc);
+ f=combine(L,argc,lookup);
  if (listing) luaU_print(f,listing>1);
  if (dumping)
  {
+  int result;
   FILE* D= (output==NULL) ? stdout : fopen(output,"wb");
   if (D==NULL) cannot("open");
   lua_lock(L);
-  int result=luaU_dump_crosscompile(L,f,writer,D,stripping,target);
+  if (flash) 
+  {
+    result=dumpToFlashImage(L,f,writer, D, stripping);
+  } else
+  {
+    result=luaU_dump_crosscompile(L,f,writer,D,stripping,target);
+  }  
   lua_unlock(L);
   if (result==LUA_ERR_CC_INTOVERFLOW) fatal("value too big or small for target integer type");
   if (result==LUA_ERR_CC_NOTINTEGER) fatal("target lua_Number is integral but fractional value found");

+ 21 - 5
tools/Makefile

@@ -3,6 +3,7 @@
 #
 
 FSSOURCE ?= ../local/fs/
+LUASOURCE ?= ../local/lua/
 FLASHSIZE ?= 4mb 32mb 8mb
 SUBDIRS = 
 HOSTCC ?= gcc
@@ -16,10 +17,22 @@ OBJDUMP = $(or $(shell which objdump),xtensa-lx106-elf-objdump)
 SPIFFSFILES ?= $(patsubst $(FSSOURCE)%,%,$(shell find $(FSSOURCE) -name '*' '!' -name .gitignore ))
 
 #################################################################
-# Get the filesize of /bin/0x10000.bin
+# Get the filesize of /bin/0x10000.bin and SPIFFS sizing
 #
 
-FLASH_USED_END = $$((0x`$(OBJDUMP) -t ../app/.output/eagle/debug/image/eagle.app.v6.out |grep _flash_used_end |cut -f1 -d" "` - 0x40200000))
+FLASH_USED_END := $$((0x`$(OBJDUMP) -t ../app/.output/eagle/debug/image/eagle.app.v6.out |grep _flash_used_end |cut -f1 -d" "` - 0x40200000))
+FLASH_FS_SIZE := $(shell $(HOSTCC) -E -dM - <../app/include/user_config.h | grep SPIFFS_MAX_FILESYSTEM_SIZE| cut -d ' ' -f 3)
+FLASH_FS_LOC := $(shell $(HOSTCC) -E -dM - <../app/include/user_config.h | grep SPIFFS_FIXED_LOCATION| cut -d ' ' -f 3)
+
+ifneq (FLASH_FS_SIZE,'')
+FLASHSIZE = $(shell printf "0x%x" $(FLASH_FS_SIZE))
+endif 
+
+ifeq (FLASH_FS_LOC,'')
+FLASH_FS_LOC := $(FLASH_USED_END)
+else 
+FLASH_FS_LOC := $(shell printf "0x%x" $(FLASH_FS_LOC))
+endif 
 
 
 #############################################################
@@ -39,13 +52,16 @@ spiffsimg: spiffsimg/spiffsimg
 spiffsimg/spiffsimg: 
 	@$(MAKE) -C spiffsimg CC=$(HOSTCC)
 
-spiffsscript: remove-image spiffsimg/spiffsimg
+spiffsscript: remove-image LFSimage spiffsimg/spiffsimg
 	rm -f ./spiffsimg/spiffs.lst
-	echo "" >> ./spiffsimg/spiffs.lst
+	@echo "" >> ./spiffsimg/spiffs.lst
 	@$(foreach f, $(SPIFFSFILES), echo "import $(FSSOURCE)$(f) $(f)" >> ./spiffsimg/spiffs.lst ;)
-	@$(foreach sz, $(FLASHSIZE), spiffsimg/spiffsimg -U $(FLASH_USED_END) -o ../bin/spiffs-$(sz).dat -f ../bin/0x%x-$(sz).bin -S $(sz) -r ./spiffsimg/spiffs.lst -d; )
+	$(foreach sz, $(FLASHSIZE), spiffsimg/spiffsimg -f ../bin/0x%x-$(sz).img  -c $(sz) -U $(FLASH_FS_LOC) -r ./spiffsimg/spiffs.lst -d; )
 	@$(foreach sz, $(FLASHSIZE), if [ -r ../bin/spiffs-$(sz).dat ]; then echo Built $$(cat ../bin/spiffs-$(sz).dat)-$(sz).bin; fi; )
 	
+LFSimage: $(LUASOURCE)*.lua
+	../luac.cross -f -o $(FSSOURCE)flash.img $(LUASOURCE)*.lua
+
 remove-image:
 	$(foreach sz, $(FLASHSIZE), if [ -r ../bin/spiffs-$(sz).dat ]; then rm -f ../bin/$$(cat ../bin/spiffs-$(sz).dat)-$(sz).bin; fi; )
 	rm -f ../bin/spiffs*.dat

+ 0 - 848
tools/build.lua

@@ -1,848 +0,0 @@
--- eLua build system
-
-module( ..., package.seeall )
-
-local lfs = require "lfs"
-local sf = string.format
-utils = require "tools.utils"
-
--------------------------------------------------------------------------------
--- Various helpers
-
--- Return the time of the last modification of the file
-local function get_ftime( path )
-  local t = lfs.attributes( path, 'modification' )
-  return t or -1
-end
-
--- Check if a given target name is phony
-local function is_phony( target )
-  return target:find( "#phony" ) == 1
-end
-
--- Return a string with $(key) replaced with 'value'
-local function expand_key( s, key, value )
-  if not value then return s end
-  local fmt = sf( "%%$%%(%s%%)", key )
-  return ( s:gsub( fmt, value ) )
-end
-
--- Return a target name considering phony targets
-local function get_target_name( s )
-  if not is_phony( s ) then return s end
-end
-
--- 'Liniarize' a file name by replacing its path separators indicators with '_'
-local function linearize_fname( s )
-  return ( s:gsub( "[\\/]", "__" ) )
-end
-
--- Helper: transform a table into a string if needed
-local function table_to_string( t )
-  if not t then return nil end
-  if type( t ) == "table" then t = table.concat( t, " " ) end
-  return t
-end
-
--- Helper: return the extended type of an object (takes into account __type)
-local function exttype( o )
-  local t = type( o )
-  if t == "table" and o.__type then t = o:__type() end
-  return t
-end
-
----------------------------------------
--- Table utils 
--- (from http://lua-users.org/wiki/TableUtils)
-
-function table.val_to_str( v )
-  if "string" == type( v ) then
-    v = string.gsub( v, "\n", "\\n" )
-    if string.match( string.gsub(v,"[^'\"]",""), '^"+$' ) then
-      return "'" .. v .. "'"
-    end
-    return '"' .. string.gsub(v,'"', '\\"' ) .. '"'
-  else
-    return "table" == type( v ) and table.tostring( v ) or tostring( v )
-  end
-end
-
-function table.key_to_str ( k )
-  if "string" == type( k ) and string.match( k, "^[_%a][_%a%d]*$" ) then
-    return k
-  else
-    return "[" .. table.val_to_str( k ) .. "]"
-  end
-end
-
-function table.tostring( tbl )
-  local result, done = {}, {}
-  for k, v in ipairs( tbl ) do
-    table.insert( result, table.val_to_str( v ) )
-    done[ k ] = true
-  end
-  for k, v in pairs( tbl ) do
-    if not done[ k ] then
-      table.insert( result,
-        table.key_to_str( k ) .. "=" .. table.val_to_str( v ) )
-    end
-  end
-  return "{" .. table.concat( result, "," ) .. "}"
-end
-
--------------------------------------------------------------------------------
--- Dummy 'builder': simply checks the date of a file
-
-local _fbuilder = {}
-
-_fbuilder.new = function( target, dep )
-  local self = {}
-  setmetatable( self, { __index = _fbuilder } )
-  self.target = target
-  self.dep = dep
-  return self
-end
-
-_fbuilder.build = function( self )
- -- Doesn't build anything but returns 'true' if the dependency is newer than
- -- the target
- if is_phony( self.target ) then
-   return true
- else
-   return get_ftime( self.dep ) > get_ftime( self.target )
- end
-end
-
-_fbuilder.target_name = function( self )
-  return get_target_name( self.dep )
-end
-
--- Object type
-_fbuilder.__type = function()
-  return "_fbuilder"
-end
-
--------------------------------------------------------------------------------
--- Target object
-
-local _target = {}
-
-_target.new = function( target, dep, command, builder, ttype )
-  local self = {}
-  setmetatable( self, { __index = _target } )
-  self.target = target
-  self.command = command
-  self.builder = builder
-  builder:register_target( target, self )
-  self:set_dependencies( dep )
-  self.dep = self:_build_dependencies( self.origdep )
-  self.dont_clean = false
-  self.can_substitute_cmdline = false
-  self._force_rebuild = #self.dep == 0
-  builder.runlist[ target ] = false
-  self:set_type( ttype )
-  return self
-end
-
--- Set dependencies as a string; actual dependencies are computed by _build_dependencies
--- (below) when 'build' is called
-_target.set_dependencies = function( self, dep )
-  self.origdep = dep
-end
-
--- Set the target type
--- This is only for displaying actions
-_target.set_type = function( self, ttype )
-  local atable = { comp = { "[COMPILE]", 'blue' } , dep = { "[DEPENDS]", 'magenta' }, link = { "[LINK]", 'yellow' }, asm = { "[ASM]", 'white' } }
-  local tdata = atable[ ttype ]
-  if not tdata then
-    self.dispstr = is_phony( self.target ) and "[PHONY]" or "[TARGET]"
-    self.dispcol = 'green'
-  else
-    self.dispstr = tdata[ 1 ]
-    self.dispcol = tdata[ 2 ]
-  end
-end
-
--- Set dependencies
--- This uses a proxy table and returns string deps dynamically according
--- to the targets currently registered in the builder
-_target._build_dependencies = function( self, dep )
-  -- Step 1: start with an array
-  if type( dep ) == "string" then dep = utils.string_to_table( dep ) end
-  -- Step 2: linearize "dep" array keeping targets
-  local filter = function( e )
-    local t = exttype( e )
-    return t ~= "_ftarget" and t ~= "_target"
-  end
-  dep = utils.linearize_array( dep, filter )
-  -- Step 3: strings are turned into _fbuilder objects if not found as targets;
-  -- otherwise the corresponding target object is used
-  for i = 1, #dep do
-    if type( dep[ i ] ) == 'string' then
-      local t = self.builder:get_registered_target( dep[ i ] )
-      dep[ i ] = t or _fbuilder.new( self.target, dep[ i ] )
-    end
-  end
-  return dep
-end
-
--- Set pre-build function
-_target.set_pre_build_function = function( self, f )
-  self._pre_build_function = f
-end
-
--- Set post-build function
-_target.set_post_build_function = function( self, f )
-  self._post_build_function = f
-end
-
--- Force rebuild
-_target.force_rebuild = function( self, flag )
-  self._force_rebuild = flag
-end
-
--- Set additional arguments to send to the builder function if it is a callable
-_target.set_target_args = function( self, args )
-  self._target_args = args
-end
-
--- Function to execute in clean mode
-_target._cleaner = function( target, deps, tobj, disp_mode )
-  -- Clean the main target if it is not a phony target
-  local dprint = function( ... )
-    if disp_mode ~= "minimal" then print( ... ) end
-  end
-  if not is_phony( target ) then 
-    if tobj.dont_clean then
-      dprint( sf( "[builder] Target '%s' will not be deleted", target ) )
-      return 0
-    end
-    if disp_mode ~= "minimal" then io.write( sf( "[builder] Removing %s ... ", target ) ) end
-    if os.remove( target ) then dprint "done." else dprint "failed!" end
-  end
-  return 0
-end
-
--- Build the given target
-_target.build = function( self )
-  if self.builder.runlist[ self.target ] then return end
-  local docmd = self:target_name() and lfs.attributes( self:target_name(), "mode" ) ~= "file"
-  docmd = docmd or self.builder.global_force_rebuild
-  local initdocmd = docmd
-  self.dep = self:_build_dependencies( self.origdep )
-  local depends, dep, previnit = '', self.dep, self.origdep
-  -- Iterate through all dependencies, execute each one in turn
-  local deprunner = function()
-    for i = 1, #dep do
-      local res = dep[ i ]:build()
-      docmd = docmd or res
-      local t = dep[ i ]:target_name()
-      if exttype( dep[ i ] ) == "_target" and t and not is_phony( self.target ) then
-        docmd = docmd or get_ftime( t ) > get_ftime( self.target )
-      end
-      if t then depends = depends .. t .. " " end
-    end
-  end
-  deprunner()
-  -- Execute the preb-build function if needed
-  if self._pre_build_function then self._pre_build_function( self, docmd ) end
-  -- If the dependencies changed as a result of running the pre-build function
-  -- run through them again
-  if previnit ~= self.origdep then
-    self.dep = self:_build_dependencies( self.origdep )
-    depends, dep, docmd = '', self.dep, initdocmd
-    deprunner()
-  end
-  -- If at least one dependency is new rebuild the target
-  docmd = docmd or self._force_rebuild or self.builder.clean_mode
-  local keep_flag = true
-  if docmd and self.command then
-    if self.builder.disp_mode ~= 'all' and self.builder.disp_mode ~= "minimal" and not self.builder.clean_mode then
-      io.write( utils.col_funcs[ self.dispcol ]( self.dispstr ) .. " " )
-    end
-    local cmd, code = self.command
-    if self.builder.clean_mode then cmd = _target._cleaner end
-    if type( cmd ) == 'string' then
-      cmd = expand_key( cmd, "TARGET", self.target )
-      cmd = expand_key( cmd, "DEPENDS", depends )
-      cmd = expand_key( cmd, "FIRST", dep[ 1 ]:target_name() )
-      if self.builder.disp_mode == 'all' then
-        print( cmd )
-      elseif self.builder.disp_mode ~= "minimal" then
-        print( self.target )
-      end
-      code = self:execute( cmd )
-    else
-      if not self.builder.clean_mode and self.builder.disp_mode ~= "all" and self.builder.disp_mode ~= "minimal" then
-        print( self.target )
-      end
-      code = cmd( self.target, self.dep, self.builder.clean_mode and self or self._target_args, self.builder.disp_mode )
-      if code == 1 then -- this means "mark target as 'not executed'"
-        keep_flag = false
-        code = 0
-      end
-    end
-    if code ~= 0 then 
-      print( utils.col_red( "[builder] Error building target" ) )
-      if self.builder.disp_mode ~= 'all' and type( cmd ) == "string" then
-        print( utils.col_red( "[builder] Last executed command was: " ) )
-        print( cmd )
-      end
-      os.exit( 1 ) 
-    end
-  end
-  -- Execute the post-build function if needed
-  if self._post_build_function then self._post_build_function( self, docmd ) end
-  -- Marked target as "already ran" so it won't run again
-  self.builder.runlist[ self.target ] = true
-  return docmd and keep_flag
-end
-
--- Return the actual target name (taking into account phony targets)
-_target.target_name = function( self )
-  return get_target_name( self.target )
-end
-
--- Restrict cleaning this target
-_target.prevent_clean = function( self, flag )
-  self.dont_clean = flag
-end
-
--- Object type
-_target.__type = function()
-  return "_target"
-end
-
-_target.execute = function( self, cmd )
-  local code
-  if utils.is_windows() and #cmd > 8190 and self.can_substitute_cmdline then
-    -- Avoid cmd's maximum command line length limitation
-    local t = cmd:find( " " )
-    f = io.open( "tmpcmdline", "w" )
-    local rest = cmd:sub( t + 1 )
-    f:write( ( rest:gsub( "\\", "/" ) ) )
-    f:close()
-    cmd = cmd:sub( 1, t - 1 ) .. " @tmpcmdline"
-  end
-  local code = os.execute( cmd )
-  os.remove( "tmpcmdline" )
-  return code
-end
-
-_target.set_substitute_cmdline = function( self, flag )
-  self.can_substitute_cmdline = flag
-end
-
--------------------------------------------------------------------------------
--- Builder public interface
-
-builder = { KEEP_DIR = 0, BUILD_DIR_LINEARIZED = 1 }
-
----------------------------------------
--- Initialization and option handling
-
--- Create a new builder object with the output in 'build_dir' and with the 
--- specified compile, dependencies and link command
-builder.new = function( build_dir )
-  self = {}
-  setmetatable( self, { __index = builder } )
-  self.build_dir = build_dir or ".build"
-  self.exe_extension = utils.is_windows() and "exe" or ""
-  self.clean_mode = false
-  self.opts = utils.options_handler()
-  self.args = {}
-  self.user_args = {}
-  self.build_mode = self.KEEP_DIR
-  self.targets = {}
-  self.targetargs = {}
-  self._tlist = {}
-  self.runlist = {}
-  self.disp_mode = 'all'
-  self.cmdline_macros = {}
-  self.c_targets = {}
-  self.preprocess_mode = false
-  self.asm_mode = false
-  return self
-end
-
--- Helper: create the build output directory
-builder._create_build_dir = function( self )
-  if self.build_dir_created then return end
-  if self.build_mode ~= self.KEEP_DIR then
-     -- Create builds directory if needed
-    local mode = lfs.attributes( self.build_dir, "mode" )
-    if not mode or mode ~= "directory" then
-      if not utils.full_mkdir( self.build_dir ) then
-        print( "[builder] Unable to create directory " .. self.build_dir )
-        os.exit( 1 )
-      end
-    end
-  end
-  self.build_dir_created = true
-end
-
--- Add an options to the builder
-builder.add_option = function( self, name, help, default, data )
-  self.opts:add_option( name, help, default, data )
-end
-
--- Initialize builder from the given command line
-builder.init = function( self, args )
-  -- Add the default options
-  local opts = self.opts
-  opts:add_option( "build_mode", 'choose location of the object files', self.KEEP_DIR,
-                   { keep_dir = self.KEEP_DIR, build_dir_linearized = self.BUILD_DIR_LINEARIZED } )
-  opts:add_option( "build_dir", 'choose build directory', self.build_dir )
-  opts:add_option( "disp_mode", 'set builder display mode', 'summary', { 'all', 'summary', 'minimal' } )
-  -- Apply default values to all options
-  for i = 1, opts:get_num_opts() do
-    local o = opts:get_option( i )
-    self.args[ o.name:upper() ] = o.default
-  end
-  -- Read and interpret command line
-  for i = 1, #args do
-    local a = args[ i ]
-    if a:upper() == "-C" then                   -- clean option (-c)
-      self.clean_mode = true
-    elseif a:upper() == '-H' then               -- help option (-h)
-      self:_show_help()
-      os.exit( 1 )
-    elseif a:upper() == "-E" then               -- preprocess
-      self.preprocess_mode = true
-    elseif a:upper() == "-S" then               -- generate assembler
-      self.asm_mode = true
-    elseif a:find( '-D' ) == 1 and #a > 2 then  -- this is a macro definition that will be auomatically added to the compiler flags
-      table.insert( self.cmdline_macros, a:sub( 3 ) ) 
-    elseif a:find( '=' ) then                   -- builder argument (key=value)
-      local k, v = opts:handle_arg( a )
-      if not k then
-        self:_show_help()
-        os.exit( 1 )
-      end
-      self.args[ k:upper() ] = v
-      self.user_args[ k:upper() ] = true
-    else                                        -- this must be the target name / target arguments
-      if self.targetname == nil then            
-        self.targetname = a
-      else
-        table.insert( self.targetargs, a )
-      end
-    end
-  end
-  -- Read back the default options
-  self.build_mode = self.args.BUILD_MODE
-  self.build_dir = self.args.BUILD_DIR
-  self.disp_mode = self.args.DISP_MODE
-end
-
--- Return the value of the option with the given name
-builder.get_option = function( self, optname )
-  return self.args[ optname:upper() ]
-end
-
--- Returns true if the given option was specified by the user on the command line, false otherwise
-builder.is_user_option = function( self, optname )
-  return self.user_args[ optname:upper() ]
-end
-
--- Show builder help
-builder._show_help = function( self )
-  print( "[builder] Valid options:" )
-  print( "  -h: help (this text)" )
-  print( "  -c: clean target" )
-  print( "  -E: generate preprocessed output for single file targets" )
-  print( "  -S: generate assembler output for single file targets" )
-  self.opts:show_help()
-end
-
----------------------------------------
--- Builder configuration
-
--- Set the compile command
-builder.set_compile_cmd = function( self, cmd )
-  self.comp_cmd = cmd
-end
-
--- Set the link command
-builder.set_link_cmd = function( self, cmd )
-  self.link_cmd = cmd
-end
-
--- Set the assembler command
-builder.set_asm_cmd = function( self, cmd )
-  self._asm_cmd = cmd
-end
-
--- Set (actually force) the object file extension
-builder.set_object_extension = function( self, ext )
-  self.obj_extension = ext
-end
-
--- Set (actually force) the executable file extension
-builder.set_exe_extension = function( self, ext )
-  self.exe_extension = ext
-end
-
--- Set the clean mode
-builder.set_clean_mode = function( self, isclean )
-  self.clean_mode = isclean
-end
-
--- Sets the build mode
-builder.set_build_mode = function( self, mode )
-  self.build_mode = mode
-end
-
--- Set the build directory
-builder.set_build_dir = function( self, dir )
-  if self.build_dir_created then
-    print "[builder] Error: build directory already created"
-    os.exit( 1 )
-  end
-  self.build_dir = dir
-  self:_create_build_dir()
-end
-
--- Return the current build directory
-builder.get_build_dir = function( self )
-  return self.build_dir
-end
-
--- Return the target arguments
-builder.get_target_args = function( self )
-  return self.targetargs
-end
-
--- Set a specific dependency generation command for the assembler
--- Pass 'false' to skip dependency generation for assembler files
-builder.set_asm_dep_cmd = function( self, asm_dep_cmd ) 
-  self.asm_dep_cmd = asm_dep_cmd
-end
-
--- Set a specific dependency generation command for the compiler
--- Pass 'false' to skip dependency generation for C files
-builder.set_c_dep_cmd = function( self, c_dep_cmd )
-  self.c_dep_cmd = c_dep_cmd
-end
-
--- Save the builder configuration for a given component to a string
-builder._config_to_string = function( self, what )
-  local ctable = {}
-  local state_fields 
-  if what == 'comp' then
-    state_fields = { 'comp_cmd', '_asm_cmd', 'c_dep_cmd', 'asm_dep_cmd', 'obj_extension' }
-  elseif what == 'link' then
-    state_fields = { 'link_cmd' }
-  else
-    print( sf( "Invalid argument '%s' to _config_to_string", what ) )
-    os.exit( 1 )
-  end
-  utils.foreach( state_fields, function( k, v ) ctable[ v ] = self[ v ] end )
-  return table.tostring( ctable )
-end
-
--- Check the configuration of the given component against the previous one
--- Return true if the configuration has changed
-builder._compare_config = function( self, what )
-  local res = false
-  local crtstate = self:_config_to_string( what )
-  if not self.clean_mode then
-    local fconf = io.open( self.build_dir .. utils.dir_sep .. ".builddata." .. what, "rb" )
-    if fconf then
-      local oldstate = fconf:read( "*a" )
-      fconf:close()
-      if oldstate:lower() ~= crtstate:lower() then res = true end
-    end
-  end
-  -- Write state to build dir
-  fconf = io.open( self.build_dir .. utils.dir_sep .. ".builddata." .. what, "wb" )
-  if fconf then
-    fconf:write( self:_config_to_string( what ) )
-    fconf:close()
-  end
-  return res
-end
-
--- Sets the way commands are displayed
-builder.set_disp_mode = function( self, mode )
-  mode = mode:lower()
-  if mode ~= 'all' and mode ~= 'summary' and mode ~= "minimal" then
-    print( sf( "[builder] Invalid display mode '%s'", mode ) )
-    os.exit( 1 )
-  end
-  self.disp_mode = mode
-end
-
----------------------------------------
--- Command line builders
-
--- Internal helper
-builder._generic_cmd = function( self, args )
-  local compcmd = args.compiler or "gcc"
-  compcmd = compcmd .. " "
-  local flags = type( args.flags ) == 'table' and table_to_string( utils.linearize_array( args.flags ) ) or args.flags
-  local defines = type( args.defines ) == 'table' and table_to_string( utils.linearize_array( args.defines ) ) or args.defines
-  local includes = type( args.includes ) == 'table' and table_to_string( utils.linearize_array( args.includes ) ) or args.includes
-  local comptype = table_to_string( args.comptype ) or "-c"
-  compcmd = compcmd .. utils.prepend_string( defines, "-D" )
-  compcmd = compcmd .. utils.prepend_string( includes, "-I" )
-  return compcmd .. flags .. " " .. comptype .. " -o $(TARGET) $(FIRST)"
-end
-
--- Return a compile command based on the specified args
-builder.compile_cmd = function( self, args )
-  args.defines = { args.defines, self.cmdline_macros }
-  if self.preprocess_mode then
-    args.comptype = "-E"
-  elseif self.asm_mode then
-    args.comptype = "-S"
-  else
-    args.comptype = "-c"
-  end
-  return self:_generic_cmd( args )
-end
-
--- Return an assembler command based on the specified args
-builder.asm_cmd = function( self, args )
-  args.defines = { args.defines, self.cmdline_macros }
-  args.compiler = args.assembler
-  args.comptype = self.preprocess_mode and "-E" or "-c"
-  return self:_generic_cmd( args )
-end
-
--- Return a link command based on the specified args
-builder.link_cmd = function( self, args )
-  local flags = type( args.flags ) == 'table' and table_to_string( utils.linearize_array( args.flags ) ) or args.flags
-  local libraries = type( args.libraries ) == 'table' and table_to_string( utils.linearize_array( args.libraries ) ) or args.libraries
-  local linkcmd = args.linker or "gcc"
-  linkcmd = linkcmd .. " " .. flags .. " -o $(TARGET) $(DEPENDS)"
-  linkcmd = linkcmd .. " " .. utils.prepend_string( libraries, "-l" )
-  return linkcmd
-end
-
----------------------------------------
--- Target handling
-
--- Create a return a new C to object target
-builder.c_target = function( self, target, deps, comp_cmd )
-  return _target.new( target, deps, comp_cmd or self.comp_cmd, self, 'comp' )
-end
-
--- Create a return a new ASM to object target
-builder.asm_target = function( self, target, deps, asm_cmd )
-  return _target.new( target, deps, asm_cmd or self._asm_cmd, self, 'asm' )
-end
-
--- Return the name of a dependency file name corresponding to a C source
-builder.get_dep_filename = function( self, srcname )
-  return utils.replace_extension( self.build_dir .. utils.dir_sep .. linearize_fname( srcname ), "d" )
-end
-
--- Create a return a new C dependency target
-builder.dep_target = function( self, dep, depdeps, dep_cmd )
-  local depname = self:get_dep_filename( dep )
-  return _target.new( depname, depdeps, dep_cmd, self, 'dep' )
-end
-
--- Create and return a new link target
-builder.link_target = function( self, out, dep, link_cmd )
-  local path, ext = utils.split_ext( out )
-  if not ext and self.exe_extension and #self.exe_extension > 0 then
-    out = out .. self.exe_extension
-  end
-  local t = _target.new( out, dep, link_cmd or self.link_cmd, self, 'link' )
-  if self:_compare_config( 'link' ) then t:force_rebuild( true ) end
-  t:set_substitute_cmdline( true )
-  return t
-end
-
--- Create and return a new generic target
-builder.target = function( self, dest_target, deps, cmd )
-  return _target.new( dest_target, deps, cmd, self )
-end
-
--- Register a target (called from _target.new)
-builder.register_target = function( self, name, obj )
-  self._tlist[ name:gsub( "\\", "/" ) ] = obj
-end
-
--- Returns a registered target (nil if not found)
-builder.get_registered_target = function( self, name )
-  return self._tlist[ name:gsub( "\\", "/" ) ] 
-end
-
----------------------------------------
--- Actual building functions
-
--- Return the object name corresponding to a source file name
-builder.obj_name = function( self, name, ext )
-  local r = ext or self.obj_extension
-  if not r then
-    r = utils.is_windows() and "obj" or "o"
-  end
-  local objname = utils.replace_extension( name, r )
-  -- KEEP_DIR: object file in the same directory as source file
-  -- BUILD_DIR_LINEARIZED: object file in the build directory, linearized filename
-  if self.build_mode == self.KEEP_DIR then 
-    return objname
-  elseif self.build_mode == self.BUILD_DIR_LINEARIZED then
-    return self.build_dir .. utils.dir_sep .. linearize_fname( objname )
-  end
-end
-
--- Read and interpret dependencies for each file specified in "ftable"
--- "ftable" is either a space-separated string with all the source files or an array
-builder.read_depends = function( self, ftable )
-  if type( ftable ) == 'string' then ftable = utils.string_to_table( ftable ) end
-  -- Read dependency data
-  local dtable = {}
-  for i = 1, #ftable do
-    local f = io.open( self:get_dep_filename( ftable[ i ] ), "rb" )
-    local lines = ftable[ i ]
-    if f then
-      lines = f:read( "*a" )
-      f:close()
-      lines = lines:gsub( "\n", " " ):gsub( "\\%s+", " " ):gsub( "%s+", " " ):gsub( "^.-: (.*)", "%1" )
-    end
-    dtable[ ftable[ i ] ] = lines
-  end
-  return dtable
-end
-
--- Create and return compile targets for the given sources
-builder.create_compile_targets = function( self, ftable, res )
-  if type( ftable ) == 'string' then ftable = utils.string_to_table( ftable ) end
-  res = res or {}
-  ccmd, oname = "-c", "o"
-  if self.preprocess_mode then
-    ccmd, oname = '-E', "pre"
-  elseif self.asm_mode then
-    ccmd, oname = '-S', 's'
-  end
-  -- Build dependencies for all targets
-  for i = 1, #ftable do
-    local isasm = ftable[ i ]:find( "%.c$" ) == nil
-    -- Skip assembler targets if 'asm_dep_cmd' is set to 'false'
-    -- Skip C targets if 'c_dep_cmd' is set to 'false'
-    local skip = isasm and self.asm_dep_cmd == false
-    skip = skip or ( not isasm and self.c_dep_cmd == false )
-    local deps = self:get_dep_filename( ftable[ i ] )
-    local target
-    if not isasm then
-      local depcmd = skip and self.comp_cmd or ( self.c_dep_cmd or self.comp_cmd:gsub( ccmd .. " ", sf( ccmd .. " -MD -MF %s ", deps ) ) )
-      target = self:c_target( self:obj_name( ftable[ i ], oname ), { self:get_registered_target( deps ) or ftable[ i ] }, depcmd )
-    else
-      local depcmd = skip and self._asm_cmd or ( self.asm_dep_cmd or self._asm_cmd:gsub( ccmd .. " ", sf( ccmd .. " -MD -MF %s ", deps ) ) )
-      target = self:asm_target( self:obj_name( ftable[ i ], oname ), { self:get_registered_target( deps ) or ftable[ i ] }, depcmd )
-    end
-    -- Pre build step: replace dependencies with the ones from the compiler generated dependency file
-    local dprint = function( ... ) if self.disp_mode ~= "minimal" then print( ... ) end end
-    if not skip then
-      target:set_pre_build_function( function( t, _ )
-        if not self.clean_mode then
-          local fres = self:read_depends( ftable[ i ] )
-          local fdeps = fres[ ftable[ i ] ]
-          if #fdeps:gsub( "%s+", "" ) == 0 then fdeps = ftable[ i ] end
-          t:set_dependencies( fdeps )
-        else
-          if self.disp_mode ~= "minimal" then io.write( sf( "[builder] Removing %s ... ", deps ) ) end
-          if os.remove( deps ) then dprint "done." else dprint "failed!" end
-        end
-      end )
-    end
-    target.srcname = ftable[ i ]
-    -- TODO: check clean mode?
-    if not isasm then self.c_targets[ #self.c_targets + 1 ] = target end
-    table.insert( res, target )
-  end
-  return res
-end
-
--- Add a target to the list of builder targets
-builder.add_target = function( self, target, help, alias )
-  self.targets[ target.target ] = { target = target, help = help }
-  alias = alias or {}
-  for _, v in ipairs( alias ) do
-    self.targets[ v ] = { target = target, help = help }
-  end
-  return target
-end
-
--- Make a target the default one
-builder.default = function( self, target )
-  self.deftarget = target.target
-  self.targets.default = { target = target, help = "default target" }
-end
-
--- Build everything
-builder.build = function( self, target )
-  local t = self.targetname or self.deftarget
-  if not t then
-    print( utils.col_red( "[builder] Error: build target not specified" ) )
-    os.exit( 1 )
-  end
-  local trg 
-  -- Look for single targets (C source files)
-  for _, ct in pairs( self.c_targets ) do
-    if ct.srcname == t then
-      trg = ct
-      break
-    end
-  end
-  if not trg then
-    if not self.targets[ t ] then
-      print( sf( "[builder] Error: target '%s' not found", t ) )
-      print( "Available targets: " )
-      print( "  all source files" )
-      for k, v in pairs( self.targets ) do
-        if not is_phony( k ) then 
-          print( sf( "  %s - %s", k, v.help or "(no help available)" ) )
-        end
-      end
-      if self.deftarget and not is_phony( self.deftarget ) then
-        print( sf( "Default target is '%s'", self.deftarget ) )
-      end
-      os.exit( 1 )
-    else
-      if self.preprocess_mode or self.asm_mode then
-        print( "[builder] Error: preprocess (-E) or asm (-S) works only with single file targets." )
-        os.exit( 1 )
-      end
-      trg = self.targets[ t ].target
-    end
-  end
-  self:_create_build_dir()
-  -- At this point check if we have a change in the state that would require a rebuild
-  if self:_compare_config( 'comp' ) then
-    print( utils.col_yellow( "[builder] Forcing rebuild due to configuration change." ) )
-    self.global_force_rebuild = true
-  else
-    self.global_force_rebuild = false
-  end
-  -- Do the actual build
-  local res = trg:build()
-  if not res then print( utils.col_yellow( sf( '[builder] %s: up to date', t ) ) ) end
-  if self.clean_mode then 
-    os.remove( self.build_dir .. utils.dir_sep .. ".builddata.comp" ) 
-    os.remove( self.build_dir .. utils.dir_sep .. ".builddata.link" ) 
-  end
-  print( utils.col_yellow( "[builder] Done building target." ) )
-  return res
-end
-
--- Create dependencies, create object files, link final object
-builder.make_exe_target = function( self, target, file_list )
-  local odeps = self:create_compile_targets( file_list )
-  local exetarget = self:link_target( target, odeps )
-  self:default( self:add_target( exetarget ) )
-  return exetarget
-end
-
--------------------------------------------------------------------------------
--- Other exported functions
-
-function new_builder( build_dir )
-  return builder.new( build_dir )
-end
-

+ 0 - 42
tools/cross-lua.lua

@@ -1,42 +0,0 @@
-local args = { ... }
-local b = require "tools.build"
-local builder = b.new_builder( ".build/cross-lua" )
-local utils = b.utils
-local sf = string.format
- 
-if not (_VERSION == "Lua 5.1" and pcall(require,"lfs")) then
-  print  [[
-
-cross_lua.lua must be run within Lua 5.1 and it requires the Lua Filesystem to be installed. 
-On most *nix distrubitions youwill find a packages lua-5.1 and lua-filesystem, or 
-alternalively you can install lua-rocks and use the Rocks package manager to install lfs.
-]]
-  os.exit(1)
-end
-builder:init( args )
-builder:set_build_mode( builder.BUILD_DIR_LINEARIZED )
-local output = 'luac.cross'
-local cdefs = '-DLUA_CROSS_COMPILER -Ddbg_printf=printf'
-
--- Lua source files and include path
-local lua_files = [[
-    lapi.c lauxlib.c lbaselib.c lcode.c ldblib.c ldebug.c ldo.c ldump.c 
-    lfunc.c lgc.c llex.c lmathlib.c lmem.c loadlib.c lobject.c lopcodes.c  
-    lparser.c lrotable.c lstate.c lstring.c lstrlib.c ltable.c ltablib.c 
-    ltm.c  lundump.c lvm.c lzio.c 
-    luac_cross/luac.c luac_cross/loslib.c luac_cross/print.c
-    ../modules/linit.c
-    ../libc/c_stdlib.c
-  ]]
-lua_files = lua_files:gsub( "\n" , "" )
-local lua_full_files = utils.prepend_path( lua_files, "app/lua" )
-local local_include = "-Iapp/include -Iinclude -Iapp/lua"
-
--- Compiler/linker options
-builder:set_compile_cmd( sf( "gcc -O2 %s -Wall %s -c $(FIRST) -o $(TARGET)", local_include, cdefs ) )
-builder:set_link_cmd( "gcc -o $(TARGET) $(DEPENDS) -lm" )
-
--- Build everything
-builder:make_exe_target( output, lua_full_files )
-builder:build()
-

+ 0 - 425
tools/utils.lua

@@ -1,425 +0,0 @@
--- Generic utility functions
-
-module( ..., package.seeall )
-
-local lfs = require "lfs"
-local sf = string.format
--- Taken from Lake
-dir_sep = package.config:sub( 1, 1 )
-is_os_windows = dir_sep == '\\'
-
--- Converts a string with items separated by 'sep' into a table
-string_to_table = function( s, sep )
-  if type( s ) ~= "string" then return end
-  sep = sep or ' '
-  if s:sub( -1, -1 ) ~= sep then s = s .. sep end
-  s = s:gsub( sf( "^%s*", sep ), "" )
-  local t = {}
-  local fmt = sf( "(.-)%s+", sep )
-  for w in s:gmatch( fmt ) do table.insert( t, w ) end
-  return t
-end
-
--- Split a file name into 'path part' and 'extension part'
-split_ext = function( s )
-  local pos
-  for i = #s, 1, -1 do
-    if s:sub( i, i ) == "." then
-      pos = i
-      break
-    end
-  end
-  if not pos or s:find( dir_sep, pos + 1 ) then return s end
-  return s:sub( 1, pos - 1 ), s:sub( pos )
-end
-
--- Replace the extension of a given file name
-replace_extension = function( s, newext )
-  local p, e = split_ext( s )
-  if e then 
-    if newext and #newext > 0 then 
-      s = p .. "." .. newext
-    else
-      s = p
-    end
-  end
-  return s
-end
-
--- Return 'true' if building from Windows, false otherwise
-is_windows = function()
-  return is_os_windows
-end
-
--- Prepend each component of a 'pat'-separated string with 'prefix'
-prepend_string = function( s, prefix, pat )  
-  if not s or #s == 0 then return "" end
-  pat = pat or ' '
-  local res = ''
-  local st = string_to_table( s, pat )
-  foreach( st, function( k, v ) res = res .. prefix .. v .. " " end )
-  return res
-end
-
--- Like above, but consider 'prefix' a path
-prepend_path = function( s, prefix, pat )
-  return prepend_string( s, prefix .. dir_sep, pat )
-end
-
--- full mkdir: create all the paths needed for a multipath
-full_mkdir = function( path )
-  local ptables = string_to_table( path, dir_sep )
-  local p, res = ''
-  for i = 1, #ptables do
-    p = ( i ~= 1 and p .. dir_sep or p ) .. ptables[ i ]
-    res = lfs.mkdir( p )
-  end
-  return res
-end
-
--- Concatenate the given paths to form a complete path
-concat_path = function( paths )
-  return table.concat( paths, dir_sep )
-end
-
--- Return true if the given array contains the given element, false otherwise
-array_element_index = function( arr, element )
-  for i = 1, #arr do
-    if arr[ i ] == element then return i end
-  end
-end
-
--- Linearize an array with (possibly) embedded arrays into a simple array
-_linearize_array = function( arr, res, filter )
-  if type( arr ) ~= "table" then return end
-  for i = 1, #arr do
-    local e = arr[ i ]
-    if type( e ) == 'table' and filter( e ) then
-      _linearize_array( e, res, filter )
-    else
-      table.insert( res, e )
-    end
-  end 
-end
-
-linearize_array = function( arr, filter )
-  local res = {}
-  filter = filter or function( v ) return true end
-  _linearize_array( arr, res, filter )
-  return res
-end
-
--- Return an array with the keys of a table
-table_keys = function( t )
-  local keys = {}
-  foreach( t, function( k, v ) table.insert( keys, k ) end )
-  return keys
-end
-
--- Return an array with the values of a table
-table_values = function( t )
-  local vals = {}
-  foreach( t, function( k, v ) table.insert( vals, v ) end )
-  return vals
-end
-
--- Returns true if 'path' is a regular file, false otherwise
-is_file = function( path )
-  return lfs.attributes( path, "mode" ) == "file"
-end
-
--- Returns true if 'path' is a directory, false otherwise
-is_dir = function( path )
-  return lfs.attributes( path, "mode" ) == "directory"
-end
-
--- Return a list of files in the given directory matching a given mask
-get_files = function( path, mask, norec, level )
-  local t = ''
-  level = level or 0
-  for f in lfs.dir( path ) do
-    local fname = path .. dir_sep .. f
-    if lfs.attributes( fname, "mode" ) == "file" then
-      local include
-      if type( mask ) == "string" then
-        include = fname:find( mask )
-      else
-        include = mask( fname )
-      end
-      if include then t = t .. ' ' .. fname end
-    elseif lfs.attributes( fname, "mode" ) == "directory" and not fname:find( "%.+$" ) and not norec then
-      t = t .. " " .. get_files( fname, mask, norec, level + 1 )
-    end
-  end
-  return level > 0 and t or t:gsub( "^%s+", "" )
-end
-
--- Check if the given command can be executed properly
-check_command = function( cmd )
-  local res = os.execute( cmd .. " > .build.temp 2>&1" )
-  os.remove( ".build.temp" )
-  return res
-end
-
--- Execute a command and capture output
--- From: http://stackoverflow.com/a/326715/105950
-exec_capture = function( cmd, raw )
-  local f = assert(io.popen(cmd, 'r'))
-  local s = assert(f:read('*a'))
-   f:close()
-   if raw then return s end
-   s = string.gsub(s, '^%s+', '')
-   s = string.gsub(s, '%s+$', '')
-   s = string.gsub(s, '[\n\r]+', ' ')
-   return s
-end
-
--- Execute the given command for each value in a table
-foreach = function ( t, cmd )
-  if type( t ) ~= "table" then return end
-  for k, v in pairs( t ) do cmd( k, v ) end
-end
-
--- Generate header with the given #defines, return result as string
-gen_header_string = function( name, defines )
-  local s = "// eLua " .. name:lower() .. " definition\n\n"
-  s = s .. "#ifndef __" .. name:upper() .. "_H__\n"
-  s = s .. "#define __" .. name:upper() .. "_H__\n\n"
-
-  for key,value in pairs(defines) do 
-     s = s .. string.format("#define   %-25s%-19s\n",key:upper(),value)
-  end
-
-  s = s .. "\n#endif\n"
-  return s
-end
-
--- Generate header with the given #defines, save result to file
-gen_header_file = function( name, defines )
-  local hname = concat_path{ "inc", name:lower() .. ".h" }
-  local h = assert( io.open( hname, "w" ) )
-  h:write( gen_header_string( name, defines ) )
-  h:close()
-end
-
--- Remove the given elements from an array
-remove_array_elements = function( arr, del )
-  del = istable( del ) and del or { del }
-  foreach( del, function( k, v )
-    local pos = array_element_index( arr, v )
-    if pos then table.remove( arr, pos ) end
-  end )
-end
-
--- Remove a directory recusively
--- USE WITH CARE!! Doesn't do much checks :)
-rmdir_rec = function ( dirname )
-  if lfs.attributes( dirname, "mode" ) ~= "directory" then return end
-  for f in lfs.dir( dirname ) do
-    local ename = string.format( "%s/%s", dirname, f )
-    local attrs = lfs.attributes( ename )
-    if attrs.mode == 'directory' and f ~= '.' and f ~= '..' then
-      rmdir_rec( ename ) 
-    elseif attrs.mode == 'file' or attrs.mode == 'named pipe' or attrs.mode == 'link' then
-      os.remove( ename )
-    end
-  end
-  lfs.rmdir( dirname )
-end
-
--- Concatenates the second table into the first one
-concat_tables = function( dst, src )
-  foreach( src, function( k, v ) dst[ k ] = v end )
-end
-
--------------------------------------------------------------------------------
--- Color-related funtions
--- Currently disabled when running in Windows
--- (they can be enabled by setting WIN_ANSI_TERM)
-
-local dcoltable = { 'black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white' }
-local coltable = {}
-foreach( dcoltable, function( k, v ) coltable[ v ] = k - 1 end )
-
-local _col_builder = function( col )
-  local _col_maker = function( s )
-    if is_os_windows and not os.getenv( "WIN_ANSI_TERM" ) then
-      return s
-    else
-      return( sf( "\027[%d;1m%s\027[m", coltable[ col ] + 30, s ) )
-    end
-  end
-  return _col_maker
-end
-
-col_funcs = {}
-foreach( coltable, function( k, v ) 
-  local fname = "col_" .. k
-  _G[ fname ] = _col_builder( k ) 
-  col_funcs[ k ] = _G[ fname ]
-end )
-
--------------------------------------------------------------------------------
--- Option handling
-
-local options = {}
-
-options.new = function()
-  local self = {}
-  self.options = {}
-  setmetatable( self, { __index = options } )
-  return self
-end
-
--- Argument validator: boolean value
-options._bool_validator = function( v )
-  if v == '0' or v:upper() == 'FALSE' then
-    return false
-  elseif v == '1' or v:upper() == 'TRUE' then
-    return true
-  end
-end
-
--- Argument validator: choice value
-options._choice_validator = function( v, allowed )
-  for i = 1, #allowed do
-    if v:upper() == allowed[ i ]:upper() then return allowed[ i ] end
-  end
-end
-
--- Argument validator: choice map (argument value maps to something)
-options._choice_map_validator = function( v, allowed )
-  for k, value in pairs( allowed ) do
-    if v:upper() == k:upper() then return value end
-  end
-end
-
--- Argument validator: string value (no validation)
-options._string_validator = function( v )
-  return v
-end
-
--- Argument printer: boolean value
-options._bool_printer = function( o )
-  return "true|false", o.default and "true" or "false"
-end
-
--- Argument printer: choice value
-options._choice_printer = function( o )
-  local clist, opts  = '', o.data
-  for i = 1, #opts do
-    clist = clist .. ( i ~= 1 and "|" or "" ) .. opts[ i ]
-  end
-  return clist, o.default
-end
-
--- Argument printer: choice map printer
-options._choice_map_printer = function( o )
-  local clist, opts, def = '', o.data
-  local i = 1
-  for k, v in pairs( opts ) do
-    clist = clist .. ( i ~= 1 and "|" or "" ) .. k
-    if o.default == v then def = k end
-    i = i + 1
-  end
-  return clist, def
-end
-
--- Argument printer: string printer
-options._string_printer = function( o )
-  return nil, o.default
-end
-
--- Add an option of the specified type
-options._add_option = function( self, optname, opttype, help, default, data )
-  local validators = 
-  { 
-    string = options._string_validator, choice = options._choice_validator, 
-    boolean = options._bool_validator, choice_map = options._choice_map_validator
-  }
-  local printers = 
-  { 
-    string = options._string_printer, choice = options._choice_printer, 
-    boolean = options._bool_printer, choice_map = options._choice_map_printer
-  }
-  if not validators[ opttype ] then
-    print( sf( "[builder] Invalid option type '%s'", opttype ) )
-    os.exit( 1 )
-  end
-  table.insert( self.options, { name = optname, help = help, validator = validators[ opttype ], printer = printers[ opttype ], data = data, default = default } )
-end
-
--- Find an option with the given name
-options._find_option = function( self, optname )
-  for i = 1, #self.options do
-    local o = self.options[ i ]
-    if o.name:upper() == optname:upper() then return self.options[ i ] end
-  end
-end
-
--- 'add option' helper (automatically detects option type)
-options.add_option = function( self, name, help, default, data )
-  local otype
-  if type( default ) == 'boolean' then
-    otype = 'boolean'
-  elseif data and type( data ) == 'table' and #data == 0 then
-    otype = 'choice_map'
-  elseif data and type( data ) == 'table' then
-    otype = 'choice'
-    data = linearize_array( data )
-  elseif type( default ) == 'string' then
-    otype = 'string'
-  else
-    print( sf( "Error: cannot detect option type for '%s'", name ) )
-    os.exit( 1 )
-  end
-  self:_add_option( name, otype, help, default, data )
-end
-
-options.get_num_opts = function( self )
-  return #self.options
-end
-
-options.get_option = function( self, i )
-  return self.options[ i ]
-end
-
--- Handle an option of type 'key=value'
--- Returns both the key and the value or nil for error
-options.handle_arg = function( self, a )
-  local si, ei, k, v = a:find( "([^=]+)=(.*)$" )
-  if not k or not v then 
-    print( sf( "Error: invalid syntax in '%s'", a ) )
-    return
-  end
-  local opt = self:_find_option( k )
-  if not opt then
-    print( sf( "Error: invalid option '%s'", k ) )
-    return
-  end
-  local optv = opt.validator( v, opt.data )
-  if optv == nil then
-    print( sf( "Error: invalid value '%s' for option '%s'", v, k ) )
-    return
-  end
-  return k, optv
-end
-
--- Show help for all the registered options
-options.show_help = function( self )
-  for i = 1, #self.options do
-    local o = self.options[ i ]
-    print( sf( "\n  %s: %s", o.name, o.help ) )
-    local values, default = o.printer( o )
-    if values then
-      print( sf( "    Possible values: %s", values ) )
-    end
-    print( sf( "    Default value: %s", default or "none (changes at runtime)" ) )
-  end
-end
-
--- Create a new option handler
-function options_handler()
-  return options.new()
-end
-