generate embedded files automatically

This commit is contained in:
wellenvogel 2021-11-21 12:37:53 +01:00
parent 724e661396
commit 6e38a5250a
3 changed files with 144 additions and 114 deletions

View File

@ -10,10 +10,18 @@ Import("env")
GEN_DIR='generated'
CFG_FILE='web/config.json'
XDR_FILE='web/xdrconfig.json'
FILES=['web/index.html',CFG_FILE,XDR_FILE,'web/index.js','web/index.css']
CFG_INCLUDE='GwConfigDefinitions.h'
XDR_INCLUDE='GwXdrTypeMappings.h'
EMBEDDED_INCLUDE="GwEmbeddedFiles.h"
def getEmbeddedFiles(env):
rt=[]
efiles=env.GetProjectOption("board_build.embed_files")
for f in efiles.split("\n"):
if f == '':
continue
rt.append(f)
return rt
def basePath():
#see: https://stackoverflow.com/questions/16771894/python-nameerror-global-name-file-is-not-defined
@ -38,126 +46,150 @@ def isCurrent(infile,outfile):
print("%s is newer then %s, no need to recreate"%(outfile,infile))
return True
return False
def compressFile(inFile):
outfile=os.path.basename(inFile)+".gz"
inFile=os.path.join(basePath(),inFile)
outfile=os.path.join(outPath(),outfile)
def compressFile(inFile,outfile):
if isCurrent(inFile,outfile):
return
with open(inFile, 'rb') as f_in:
with gzip.open(outfile, 'wb') as f_out:
shutil.copyfileobj(f_in, f_out)
def generateCfg():
outfile=os.path.join(outPath(),CFG_INCLUDE)
infile=os.path.join(basePath(),CFG_FILE)
def generateFile(infile,outfile,callback,inMode='rb',outMode='w'):
if isCurrent(infile,outfile):
return
print("creating %s"%CFG_INCLUDE)
print("creating %s"%outfile)
oh=None
with open(CFG_FILE,'rb') as ch:
config=json.load(ch)
try:
with open(outfile,'w') as oh:
oh.write("//generated from %s\n"%CFG_FILE)
oh.write('#include "GwConfigItem.h"\n')
l=len(config)
oh.write('class GwConfigDefinitions{\n')
oh.write(' public:\n')
oh.write(' int getNumConfig() const{return %d;}\n'%(l))
for item in config:
n=item.get('name')
if n is None:
continue
if len(n) > 15:
raise Exception("%s: config names must be max 15 caracters"%n)
oh.write(' const String %s=F("%s");\n'%(n,n))
oh.write(' protected:\n')
oh.write(' GwConfigItem *configs[%d]={\n'%(l))
first=True
for item in config:
if not first:
oh.write(',\n')
first=False
oh.write(" new GwConfigItem(%s,\"%s\")"%(item.get('name'),item.get('default')))
oh.write('};\n')
oh.write('};\n')
oh.close()
except Exception as e:
if oh is not None:
with open(infile,inMode) as ch:
with open(outfile,'w') as oh:
try:
callback(ch,oh,inFile=infile)
oh.close()
except Exception as e:
try:
oh.close()
except:
pass
os.unlink(outfile)
raise
os.unlink(outfile)
raise
def generateXdrMappings():
outfile=os.path.join(outPath(),XDR_INCLUDE)
infile=os.path.join(basePath(),XDR_FILE)
if isCurrent(infile,outfile):
return
print("creating %s"%XDR_INCLUDE)
oh=None
def writeFileIfChanged(fileName,data):
if os.path.exists(fileName):
with open(fileName,"r") as ih:
old=ih.read()
ih.close()
if old == data:
return
print("#generating %s"%fileName)
with open(fileName,"w") as oh:
oh.write(data)
with open(infile,"rb") as fp:
jdoc=json.load(fp)
try:
with open(outfile,"w") as oh:
oh.write("static GwXDRTypeMapping* typeMappings[]={\n")
first=True
for cat in jdoc:
item=jdoc[cat]
cid=item.get('id')
if cid is None:
continue
tc=item.get('type')
if tc is not None:
if first:
first=False
else:
oh.write(",\n")
oh.write(" new GwXDRTypeMapping(%d,%d,0) /*%s*/"%(cid,tc,cat))
fields=item.get('fields')
if fields is None:
continue
idx=0
for fe in fields:
if not isinstance(fe,dict):
continue
tc=fe.get('t')
id=fe.get('v')
if id is None:
id=idx
idx+=1
l=fe.get('l') or ''
if tc is None or id is None:
continue
if first:
first=False
else:
oh.write(",\n")
oh.write(" new GwXDRTypeMapping(%d,%d,%d) /*%s:%s*/"%(cid,tc,id,cat,l))
oh.write("\n")
oh.write("};\n")
except Exception as e:
if oh:
try:
oh.close()
except:
pass
os.unlink(outfile)
raise
def generateCfg(ch,oh,inFile=''):
config=json.load(ch)
oh.write("//generated from %s\n"%inFile)
oh.write('#include "GwConfigItem.h"\n')
l=len(config)
oh.write('class GwConfigDefinitions{\n')
oh.write(' public:\n')
oh.write(' int getNumConfig() const{return %d;}\n'%(l))
for item in config:
n=item.get('name')
if n is None:
continue
if len(n) > 15:
raise Exception("%s: config names must be max 15 caracters"%n)
oh.write(' const String %s=F("%s");\n'%(n,n))
oh.write(' protected:\n')
oh.write(' GwConfigItem *configs[%d]={\n'%(l))
first=True
for item in config:
if not first:
oh.write(',\n')
first=False
oh.write(" new GwConfigItem(%s,\"%s\")"%(item.get('name'),item.get('default')))
oh.write('};\n')
oh.write('};\n')
def generateXdrMappings(fp,oh,inFile=''):
jdoc=json.load(fp)
oh.write("static GwXDRTypeMapping* typeMappings[]={\n")
first=True
for cat in jdoc:
item=jdoc[cat]
cid=item.get('id')
if cid is None:
continue
tc=item.get('type')
if tc is not None:
if first:
first=False
else:
oh.write(",\n")
oh.write(" new GwXDRTypeMapping(%d,%d,0) /*%s*/"%(cid,tc,cat))
fields=item.get('fields')
if fields is None:
continue
idx=0
for fe in fields:
if not isinstance(fe,dict):
continue
tc=fe.get('t')
id=fe.get('v')
if id is None:
id=idx
idx+=1
l=fe.get('l') or ''
if tc is None or id is None:
continue
if first:
first=False
else:
oh.write(",\n")
oh.write(" new GwXDRTypeMapping(%d,%d,%d) /*%s:%s*/"%(cid,tc,id,cat,l))
oh.write("\n")
oh.write("};\n")
def generateEmbedded(elist,outFile):
content=""
for entry in elist:
content+="EMBED_GZ_FILE(\"%s\",%s,\"%s\");\n"%entry
writeFileIfChanged(outFile,content)
if not checkDir():
sys.exit(1)
for f in FILES:
print("compressing %s"%f)
compressFile(f)
generateCfg()
generateXdrMappings()
version="dev"+datetime.now().strftime("%Y%m%d")
env.Append(CPPDEFINES=[('GWDEVVERSION',version)])
def getContentType(fn):
if (fn.endswith('.gz')):
fn=fn[0:-3]
if (fn.endswith('html')):
return "text/html"
if (fn.endswith('json')):
return "application/json"
if (fn.endswith('js')):
return "text/javascript"
return "application/octet-stream"
def prebuild(env):
print("#prebuild running")
if not checkDir():
sys.exit(1)
embedded=getEmbeddedFiles(env)
filedefs=[]
for ef in embedded:
print("#checking embedded file %s"%ef)
(dn,fn)=os.path.split(ef)
pureName=fn
if pureName.endswith('.gz'):
pureName=pureName[0:-3]
ct=getContentType(pureName)
usname=ef.replace('/','_').replace('.','_')
filedefs.append((pureName,usname,ct))
inFile=os.path.join(basePath(),"web",pureName)
if os.path.exists(inFile):
print("compressing %s"%inFile)
compressFile(inFile,ef)
else:
print("#WARNING: infile %s for %s not found"%(inFile,ef))
generateEmbedded(filedefs,os.path.join(outPath(),EMBEDDED_INCLUDE))
generateFile(os.path.join(basePath(),CFG_FILE),os.path.join(outPath(),CFG_INCLUDE),generateCfg)
generateFile(os.path.join(basePath(),XDR_FILE),os.path.join(outPath(),XDR_INCLUDE),generateXdrMappings)
version="dev"+datetime.now().strftime("%Y%m%d")
env.Append(CPPDEFINES=[('GWDEVVERSION',version)])
prebuild(env)

View File

@ -16,15 +16,12 @@ class EmbeddedFile {
embeddedFiles[name]=this;
}
} ;
#define EMBED_GZ_FILE(fileName, fileExt, contentType) \
extern const uint8_t fileName##_##fileExt##_File[] asm("_binary_generated_" #fileName "_" #fileExt "_gz_start"); \
extern const uint8_t fileName##_##fileExt##_FileLen[] asm("_binary_generated_" #fileName "_" #fileExt "_gz_size"); \
const EmbeddedFile fileName##_##fileExt##_Config(#fileName "." #fileExt,contentType,(const uint8_t*)fileName##_##fileExt##_File,(int)fileName##_##fileExt##_FileLen);
#define EMBED_GZ_FILE(fileName, binName, contentType) \
extern const uint8_t binName##_File[] asm("_binary_" #binName "_start"); \
extern const uint8_t binName##_FileLen[] asm("_binary_" #binName "_size"); \
const EmbeddedFile binName##_Config(fileName,contentType,(const uint8_t*)binName##_File,(int)binName##_FileLen);
EMBED_GZ_FILE(index,html,"text/html")
EMBED_GZ_FILE(config,json,"application/json")
EMBED_GZ_FILE(index,js,"text/javascript")
EMBED_GZ_FILE(index,css,"text/css")
#include "GwEmbeddedFiles.h"
void sendEmbeddedFile(String name,String contentType,AsyncWebServerRequest *request){
std::map<String,EmbeddedFile*>::iterator it=embeddedFiles.find(name);

View File

@ -23,6 +23,7 @@ board_build.embed_files =
generated/index.js.gz
generated/index.css.gz
generated/config.json.gz
generated/xdrconfig.json.gz
board_build.partitions = partitions_custom.csv
extra_scripts =
pre:extra_script.py