Move luac.cross build into standard make hierarchy
This commit is contained in:
parent
3d3eebfd67
commit
4141e69003
|
@ -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,4 +1,4 @@
|
|||
#ifndef __MODULE_H__
|
||||
#if !defined(__MODULE_H_) && !defined(LUA_CROSS_COMPILER)
|
||||
#define __MODULE_H__
|
||||
|
||||
#include "user_modules.h"
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
# a generated lib/image xxx.a ()
|
||||
#
|
||||
ifndef PDIR
|
||||
SUBDIRS = luac_cross
|
||||
GEN_LIBS = liblua.a
|
||||
endif
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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'; \
|
||||
|
|
@ -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;
|
||||
}
|
|
@ -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");
|
||||
|
|
|
@ -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
|
||||
|
|
848
tools/build.lua
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
|
||||
|
|
@ -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()
|
||||
|
425
tools/utils.lua
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
|
||||
|
Loading…
Reference in New Issue