# This file is part of the open-lmake distribution (git@github.com:cesar-douady/open-lmake.git)
# Copyright (c) 2023-2025 Doliam
# This program is free software: you can redistribute/modify under the terms of the GPL-v3 (https://www.gnu.org/licenses/gpl-3.0.html).
# This program is distributed WITHOUT ANY WARRANTY, without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
import json
import os
import re
import sys
import textwrap
import lmake
from lmake.rules import Rule,DirRule,PyRule,TraceRule
exe_lst = ('hello_world',) # executables for generation of compile_commands.json (for IDE integration)
class Dir(DirRule) : pass # create file '...' in any directory
class Base(Rule) :
stems = {
'File' : r'.+'
, 'Exe' : r'.+'
, 'Test' : r'[^-]+'
}
# for IDE integration as per : https://clang.llvm.org/docs/JSONCompilationDatabase.html
class CompCmds(Base,PyRule) :
target = 'compile_commands.json'
def cmd() :
first = True
print('[')
for i,exe in enumerate(exe_lst) :
if first : first = False
else : print(',')
cmds = open(f'{exe}.compile_commands.json').read()
assert cmds[:2]=='[\n' and cmds[-2:]==']\n' # suppress leading and trailing []
cmds = cmds[2:-2] # .
print(cmds,end='')
print(']')
# suppress comments, so that editing source files is more comfortable
class NoComment(Base) :
target = '{File}.nc'
dep = '{File}'
cmd = "sed -e 's: *#.*::' -e '/^$/ d' "
flags = '-O0' # updated by run script to simulate a modification of cmd
for ext in ('c','cc') :
class Compile(Base,PyRule) :
name = f'compile {ext}'
targets = {
'OBJ' : '{File}.o'
, 'LST' : '{File}.d' # contains actually included .h files
}
deps = { 'SRC':f'{{File}}.{ext}' }
def cmd() :
tf = f"{os.environ['TMPDIR']}/lst"
lmake.run_cc( 'gcc' , '-MMD' , '-MF' , tf , flags , '-c' , '-o' , OBJ , SRC )
lst = open(tf).read()
lst = re.sub(r'\\\n','',lst) # suppress multi-line
lst = lst.split(':',1)[1] # suppress target: prefix
with open(LST,'w') as fd :
for f in lst.split() :
if f.endswith('.h' ) : print(f[:-2],file=fd)
elif f.endswith('.hh') : print(f[:-3],file=fd)
# generate fragment of compile_commands.json for IDE integration
class CompCmdObj(Base,PyRule) :
name = f'gen compile_command {ext}'
target = '{File}.compile_command.json'
deps = { 'SRC':f'{{File}}.{ext}' }
def cmd() :
print(json.dumps(
{ 'directory' : '.'
, 'arguments' : ('gcc','-c','-o',f'{File}.o',SRC)
, 'output' : f'{File}.o'
, 'file' : SRC
}
, indent = 4
))
# generate transitive closure of included .h files
class ObjLst(Base,PyRule) :
target = '{File}.lst'
deps = { 'LST':'{File}.d' }
def cmd() :
lst = {} # dont care of val, just need a set with order preserved as in dict
def handle(d) :
try : l = open(d).read().split()
except : return # if d does not exist, this must be a '.h only' lib and there is no object to link
for o in l :
if o in lst : continue
lst[o] = None # insert o in object list
handle(f'{o}.d') # recurse
handle(LST)
print(f'{File}')
for o in lst : print(o)
class Link(Base,PyRule) :
targets = { 'EXE' : '{Exe}.exe' }
deps = { 'LST' : ('{Exe}.lst.nc','critical') }
def cmd() :
lmake.run_cc('gcc',f'-o{EXE}',*(f'{obj.strip()}.o' for obj in open(LST)))
# generate fragment of compile_commands.json for IDE integration
class CompCmdLnk(Base,PyRule) :
target = '{Exe}.compile_commands.json'
deps = { 'LST' : ('{Exe}.lst.nc','critical') }
def cmd() :
first = True
print('[')
for obj in open(LST) :
if first : first = False
else : print(',')
obj = obj.strip()
print(textwrap.indent(open(f'{obj}.compile_command.json').read(),'\t'),end='')
print(']')
class Run(Base) :
target = r'{Exe}-{Test}.out'
deps = {
'EXE' : '{Exe}.exe'
, 'IN' : '{Exe}-{Test}.scn.nc'
}
cmd = './{EXE} "$(cat {IN})"'
class Chk(Base) : # generic comparison rule
target = '{File}.ok'
deps = {
'DUT' : '{File}'
, 'REF' : '{File}.ref.nc'
}
cmd = 'diff {REF} {DUT}>&2'
# Here we scatter all the tests from a single, simple, regression description
# Each line contains a test, a scenario and and expected output, separated with ':'
class Scenarios(Base,PyRule) :
targets = {
'LST' : '{Exe}.tlst'
, 'SCN' : '{Exe}-{Test*}.scn' # this is a star target : it means all tests are generated in a single job execution
, 'REF' : '{Exe}-{Test*}.out.ref' # idem
}
dep = '{Exe}.regr.nc'
def cmd() :
with open(LST,'w') as lst :
for l in sys.stdin.readlines() :
test,scn,ref = (x.strip() for x in l.split(':'))
print(scn ,file=open(SCN(test),'w'))
print(ref ,file=open(REF(test),'w'))
print(test,file=lst )
# here we gather results of scattered tests
class Regr(Base) :
target = '{Exe}.tok'
deps = { 'LST' : ( '{Exe}.tlst.nc' , 'critical' ) }
cmd = r'''
ldepend $(sed 's:.*:{Exe}-\0.out.ok:' {LST})
'''