-- subfile for 'hamster_core.lua' (returns 'MakeHook') local m= assert( arg[1], "called via 'hamster_core.lua' only" ) local Loc_SkipHash= ASSUME( m.SkipHash ) local Loc_PathAsTable= ASSUME( m.PathAsTable ) local Loc_TblFlatten= ASSUME( m.TblFlatten ) local Loc_FindFileInPath= ASSUME( m.FindFileInPath ) local Loc_RemoveDuplicates= ASSUME( m.RemoveDuplicates ) local Loc_TargetName= ASSUME( m._TargetName ) local Loc_IsFlatTableWithoutNils= ASSUME( m.IsFlatTableWithoutNils ) local Loc_PathOnly= ASSUME( m.PathOnly ) local Loc_MakeSlashTerminated= ASSUME( m.MakeSlashTerminated ) local Loc_IsSlashTerminated= ASSUME( m.IsSlashTerminated ) local Loc_TblCopy= ASSUME( m.TblCopy ) local Loc_TblToStr= ASSUME( m.TblToStr ) local Loc_EscSpaces= ASSUME( m.EscSpaces ) --local Loc_IsEmptyTable= ASSUME( m.IsEmptyTable ) local Loc_CompileCmd= ASSUME( m._CompileCmd ) local Loc_TmpName= ASSUME( m.TmpName ) --local Loc_ValidateTags= ASSUME( m._ValidateTags ) local Loc_CreateFile= ASSUME( m.CreateFile ) local _OPEN_RT= ASSUME( m._OPEN_RT ) local Loc_FilenameOnly= ASSUME( m.FilenameOnly ) --... --== SCRIPT GENERATION (make hook) ==-- -- This is a hook towards the standard 'make' tools, allowing time-stamp -- based dependency checking (only compiling files that actually need to -- be compiled). -- -- Also include file dependencies are handled (by us) so changes in local -- include files (those named with "", not <>) will be detected. ----- -- [tbl]= Loc_FindHeaders( fname_str, inc_paths [,flatten_bool [,parent_str] ] ) -- -- Params: 'fname' may be either an assembler or C/C++ source file. -- -- 'inc_paths' is either a table of directory names or a string path. -- -- 'flatten' returns a flat table, without header tree (and no duplicates) -- -- Returns: A table of include files (with full paths) that a certain source -- file is dependent on. 'nil' if no includes. -- -- Note: System include files (using <> brackets) are _not_ listed, or -- recursed to. -- -- Internal lookup table: tbl[header_filename]= { true / dependency_tbl } -- local dependency_cache= {} -- Speed optimization: build script can set certain source/header names for -- exclusion from dependency check (if they never change) --local sys_sources= {} local function Loc_FindHeaders( fname, inc_paths, beautify, parent ) -- local fh local ret= nil local suffix, asm -- local type_c= { c=1, h=1, lch=1 } local type_cpp= { ["c++"]=1, cpp=1, cxx=1, cc=1, hpp=1 } local type_asm= { asm=1, inc=1, src=1 } if type(fname)=="number" then fname= Loc_SkipHash( node_stack[fname].target ) end ASSUME( type(fname)=="string" ) fh= io.open( fname, _OPEN_RT ) if not fh then error( "Unable to open '"..fname.."'! ".. (parent and ("(from '"..parent.."')") or "" ) ) end ASSUME( inc_paths ) if type(inc_paths)=="string" then inc_paths= Loc_PathAsTable( inc_paths ) end inc_paths= Loc_TblFlatten( inc_paths ) -- remove nils suffix= third( string.find( fname, "%.(%w+)$" ) ) suffix= string.lower(suffix) if type_c[suffix] or type_cpp[suffix] then asm= false elseif type_asm[suffix] then asm= true else print( "WARN: Unknown file type '."..suffix.."' (header search skipped)" ) return nil end --[[ -- Skip specific source files (speed optimization) that are regarded as -- non-changing system stuff. The list of patterns is build project specific. -- for v in sys_sources do if string.find( fname, v ) then return nil --match end end ]]-- -- Loop lines within a source file -- while true do local line, header line= fh:read() -- "*l" if not line then break end if not asm then -- C/C++ -- speed optimization: avoid 'string.find' if possible -- if string.sub( line, 1,8 ) ~= "#include" then header= nil else header= third( string.find( line, '^%#include%s+%"(.+)%"' ) ) -- Performance optimization: Treat certain well-known headers -- as 'system' even if they're included as "" (and not <>). -- if header then local skip_these= { "paragui", "pg.+", "SDL", "SDL_.+" } local tmp -- Skip dot & suffix, if any: tmp= third( string.find( header, "^(.+)%." ) ) tmp= tmp or header for _,v in skip_these do -- if string.find( tmp, "^"..v.."$" ) then header= nil -- found on the skip list! break end end end end else -- hc11 asm: "$filename.inc" header= third( string.find( line, '^%$(%w+%.inc)"' ) ) end if header then ret= ret or {} -- If this header has already been scanned (for another source file) -- use those results: -- local node= dependency_cache[header] if node then if node==true then -- do nothing (loop prevention) else table.insert( ret, node[1] ) -- header full name if node[2] then table.insert( ret, node[2] ) -- dependencies end ASSUME( not node[3] ) end else -- Studying this header for the first (and only!) :) time. -- local header2= Loc_FindFileInPath( header, inc_paths ) if not header2 then -- There may be header files we don't find, because of conditional -- compilations etc. (we don't do full preprocessing) -- -- Normally these are system headers (within '<>') that we -- would skip anyhow. -- --print( "WARN: Unable to find '"..header.."'" ) else local node= { header2 } -- with dependencies added later dependency_cache[header]= true -- prevents inclusion loops local more= Loc_FindHeaders( header2, inc_paths, false, fname ) if more then -- subheaders as a subtable table.insert( node, more ) end -- Print dependency info: (debug) -- --print( "\tScanning '"..header2.."':" ) -- Remember the dependencies for later visitors: -- dependency_cache[header]= node table.insert( ret, node[1] ) -- header full name if node[2] then table.insert( ret, node[2] ) -- dependencies end ASSUME( not node[3] ) end end end end fh:close() if beautify then ret= Loc_RemoveDuplicates( Loc_TblFlatten(ret) ) end return ret end ----- -- rc_int [,err_str]= MakeHook( env_stack, node_stack, opts_tbl ) -- local function MakeHook( envs_tbl, nodes_tbl, opts_tbl ) -- local rules="" local all_targets= {} print "Creating makefile..\n" -- Craft the makefile contents -- for i,node in ipairs(nodes_tbl) do --1,table.getn(nodes_tbl) do -- --local node= nodes_tbl[i] local env= envs_tbl[ node.env_id ] local cmd, rc local deps= nil local cfg= ASSUME( m.CompilerConfig( env ) ) local target= Loc_TargetName( node.target, node.type, cfg ) local sources= node.sources ASSUME( Loc_IsFlatTableWithoutNils(sources) ) -- Several source nodes exist only for linkage (program, library etc.) -- not for compiling objects. -- if string.find( node.type, "Object" ) then -- ASSUME( sources[1] and (not sources[2]) ) local src= Loc_SkipHash(sources[1]) local inc_paths= Loc_SkipHash( env.CPPPATH ) if type(inc_paths)=="string" then inc_paths= { inc_paths } -- make it always a table elseif not inc_paths then inc_paths= {} end -- The source dir seems to be needed as an implicit header path -- table.insert( inc_paths, Loc_PathOnly(src) ) deps= Loc_FindHeaders( src, Loc_MakeSlashTerminated(inc_paths), true ) or {} -- Also the source itself wants to jump in! :) -- table.insert( deps, 1, src ) elseif (node.type=="Install") or (node.type=="Custom") then -- ASSUME( type(sources[1]) == "string" ) ASSUME( sources[2] == nil ) local src= Loc_SkipHash( sources[1] ) -- Install uses dir names as target (same filename as source) -- if Loc_IsSlashTerminated(target) then target= target..Loc_FilenameOnly(src) end deps= src table.insert( all_targets, target ) else -- 'Program', 'Library' etc... -- deps= Loc_TblCopy( sources, function(v) return Loc_TargetName( nodes_tbl[v].target, nodes_tbl[v].type, cfg ) end ) table.insert( all_targets, target ) end deps= Loc_TblFlatten(deps) -- make sure it's a table if env.depends then -- explicit dependencies (filenames & node id's) -- ASSUME(deps) for _,v in Loc_TblFlatten( env.depends ) do -- if type(v)=="number" then -- node id table.insert( deps, ASSUME( node_stack[v].target ) ) else ASSUME( type(v)=="string", "Unexpected 'depends=' type! "..type(v) ) table.insert( deps, v ) -- filename itself end end end cmd= Loc_CompileCmd( node, env, nodes_tbl ) if type(cmd)=="string" then cmd= {cmd} end -- Note: We rely on forced rebuild to happen if there are no pre-requisites -- listed, is this right?!? (gnu make manual was sort of.. unclear) -- local use_nmake= (CC=="msc") or (CC=="evc") local tmp= Loc_TblToStr( Loc_SkipHash( deps ), ' ', not use_nmake, use_nmake ) rules= rules.. Loc_EscSpaces(target)..": "..(tmp or "").."\n" for _,v in ipairs(cmd) do rules= rules.. ("\t"..v.." \n") -- space avoids '\' at line end end rules= rules.."\n" end -- Create the (temporary) makefile -- local tmpfn= Loc_TmpName( opts_tbl.tmpdir ) local targets= Loc_RemoveDuplicates(all_targets) --local targets= (opts_tbl.targets) or Loc_RemoveDuplicates(all_targets) local prefix= [[ # # Temporary makefile created by Hamster rel.]]..(m._info.RELEASE)..[[ # ]].. "\nall: "..Loc_TblToStr(targets,' ',true).."\n\t\n" if not Loc_CreateFile( tmpfn, prefix..rules ) then return -99, "Unable to create temporary makefile!" end -- Note: BSD make is quite different from Gnu make, but we won't be -- using the advanced features so it should be fine.. -- local tmp= ASSUME( m._make ) -- "nmake"/"make"/... local make_f= tmp.." ".. (string.find(tmp,"nmake") and "/f" or "-f") .." " STDERR_PRINT( make_f..tmpfn ) local rc, err= os.execute( make_f..tmpfn ) if rc~=0 then -- (leave tmpfile there, for debugging) return rc, "Makefile failed" end os.remove( tmpfn ) return 0 -- ok :) end return MakeHook