# depend.awk -- awk script used to construct makefile dependencies
# for nethack's source files (`make depend' support for Makefile.src).
# $NHDT-Date: 1709577497 2024/03/04 18:38:17 $  $NHDT-Branch: NetHack-5.0 $:$NHDT-Revision: 1.18 $
#
# usage:
#   awk -f depend.awk ../include/*.h list-of-.c/.cpp-files
#   (might need nawk or gawk instead of plain awk if it is really old)
# meta usage:
#   ( cd src ; make all ; cp ../sys/unix/Makefile.src ./Makefile ; \
#     make depend ; cp ./Makefile ../sys/unix/Makefile.src ; \
#     cd .. ; sh sys/unix/setup.sh [sys/unix/hints/FOO] )
# newer usage:
#   cd sys/unix ; make -f Makefile.src updatedepend
#
# This awk program scans each file in sequence, looking for lines beginning
# with `#include "' and recording the name inside the quotes.  For .h files,
# that's all it does.  For each .c file, it writes out a make rule for the
# corresponding .o file; dependencies in nested header files are propagated
# to the .o target.
#
# Variables that can be set on the command line:
#  -v dontsortdeps=1               do not sort the dependencies
#  -v dontsortfules=1              do not sort the rules
#
# config.h and hack.h get special handling because of their heavy use;
#	timestamps for them allow make to avoid rechecking dates on
#	subsidiary headers for every source file;
# extern.h gets special handling to avoid excessive recompilation
#	during development;
# patchlev.h gets special handling because it only exists on systems
#	which consider filename patchlevel.h to be too long;
# amiconf.h moved from ../include/ to ../outdated/include/ so skip it
# interp.c gets special handling because it usually doesn't exist; it's
#	assumed to be the last #include in the file where it occurs.
# win32api.h gets special handling because it only exists for some ports;
#	it's assumed to be the last #include in the file where it occurs
# zlib.h ditto
#
BEGIN		{ FS = "\""			#for `#include "X"', $2 is X
		  special[++sp_cnt] = "../include/config.h"
		  special[++sp_cnt] = "../include/hack.h"
		  alt_deps["../include/extern.h"] = ""
		  alt_deps["../include/patchlev.h"] = ""
		  alt_deps["../include/amiconf.h"] = ""
		  alt_deps["interp.c"] = " #interp.c"	#comment it out
		  alt_deps["../include/win32api.h"] = " #../include/win32api.h"
		  alt_deps["../include/zlib.h"] = " #zlib.h"	#comment it out
		}
FNR == 1	{ output_dep()			#finish previous file
		  file = FILENAME		#setup for current file
		}
/^[#][ \t]*include[ \t]+["]/  {			#find `#include "X"'
		  incl = $2
		  #[3.4.0: gnomehack headers currently aren't in include]
		  #[3.6.2: Qt4 headers aren't in include either]
		  #[3.6.2: curses headers likewise]
		  #[NetHack 5.0.0: Qt headers have moved; process 'moc' files]
		  if (incl ~ /[.]h$/) {
		    if (incl ~ "curses[.]h")
		      incl = ""	# skip "curses.h"; it should be <curses.h>
		    else if (incl ~ /^..\/lib\/lua-.*\/src\/l/)
		      incl = ""	# skip lua headers
		    else if (incl ~ /^curs/)	# curses special case
		      incl = "../win/curses/" incl
		    else if (incl ~ /(.*\/)*qt_/) {	# Qt special cases
		      # Qt v3 headers are in ../win/Qt3
		      # Qt v4/v5/v6 headers are in ../win/Qt
		      # *.moc files have path in their #include
		      if (file ~ /[.]moc$/)
			; # keep 'incl' as-is
		      else if (file ~ /^[.][.]\/win\/Qt3\/.*/)
			incl = "../win/Qt3/" incl
		      else			# Qt v4/v5/v6
			incl = "../win/Qt/" incl
		    } else if (incl ~ /^gn/)	# gnomehack special case
		      incl = "../win/gnome/" incl
		    else
		      incl = "../include/" incl
		  }
		  deps[file] = deps[file] " " incl
		}
END		{
		    output_dep()		#finish the last file
		    output_final()		#write output
		}


#
# `file' has been fully scanned, so process it now; for .h files,
# don't do anything (we've just been collecting their dependencies);
# for .c files, output the `make' rule for corresponding .o file
#

function output_dep(){
  if(!dontsortrules){
    worklist[++worklistctr] = file
  } else {
    output_final2()
  }
}

function output_final(				x)
{
  if(!dontsortrules){
    nhsort(worklist, 1, worklistctr, 1)
    for(x=1;x<=worklistctr;x++){
      file = worklist[x]
      output_final2()
    }
  } else {
    return
  }
}

function output_final2(				base, targ, moc)
{
  #get the file's base name (including suffix)
  base = file;  sub("^.+/", "", base)
  #for qt source files, add qt timestamp file as extra dependency
  moc = (base ~ /[.]moc$/)
  if (moc || base ~ /(.+\/)*qt_.*[.]cpp$/) {
    deps[file] = deps[file] " $(QTn_H)"
  }
  if ((base ~ /[.]cp*$/ || moc) && !(file in filedone)) {
    #prior to very first .c|.cpp file, handle some special header file cases
    if (!c_count++)
      output_specials()
    #construct object filename from source filename
    targ = base;  sub("[.]cp*$", ".o", targ)
    #format and write the collected dependencies
    format_dep(targ, file)
    #generated file tile.c can appear more than once in the list of files
    #so track which files have already been handled; can't reuse done[] here
    filedone[file]++;
  }
}

#
# handle some targets (config.h, hack.h) via special timestamping rules
#
function output_specials(			i, sp, alt_sp)
{
  for (i = 1; i <= sp_cnt; i++) {
    sp = special[i]
    #change "../include/foo.h" first to "foo.h", then ultimately to "$(FOO_H)"
    alt_sp = sp;  sub("^.+/", "", alt_sp)
    print "#", alt_sp, "timestamp"	#output a `make' comment
 #- sub("[.]", "_", alt_sp);  alt_sp = "$(" toupper(alt_sp) ")"
 #+ Some nawks don't have toupper(), so hardwire these instead.
    sub("config.h", "$(CONFIG_H)", alt_sp);  sub("hack.h", "$(HACK_H)", alt_sp)
    format_dep(alt_sp, sp)		#output the target
    print "\ttouch " alt_sp		#output a build command
    alt_deps[sp] = alt_sp		#alternate dependency for depend()
  }
  print "#"
}

#
# write a target and its dependency list in pretty-printed format;
# if target's primary source file has a path prefix, also write build command
#
function format_dep(target, source,		col, n, i, list, prefix, moc)
{
  split("", done)			#``for (x in done) delete done[x]''
  moc = (target ~ /[.]moc$/)
  prefix = (moc || substr(target,1,1) == "$") ? "" : "$(TARGETPFX)"
  printf("%s%s:", prefix, target);  col = length(target) + 1 + length(prefix)
  #- printf("\t");  col += 8 - (col % 8);
  #- if (col == 8) { printf("\t"); col += 8 }
  source = depend("", source, 0)
  sub(" +$", "", source)                #strip trailing spaces, if any
  n = split(source, list, " +")
  #first: leading whitespace yields empty 1st element; not sure why moc
  #files duplicate the target as next element but we need to skip that too
  first = moc ? 3 : 2
  source = list[first]
  if (!dontsortdeps){
    nhsort(list, first, n, 0)
  }
  for (i = first; i <= n; i++) {
    if (col + length(list[i]) >= (i < n ? 78 : 80) - 1) {
      printf(" \\\n\t\t");  col = 16	#make a backslash+newline split
    } else {
      printf(" ");  col++;
    }
    printf("%s", list[i]);  col += length(list[i])
  }
  printf("\n")				#terminate
  #write build command if first source entry has non-include path prefix
  if (moc) {
    print "\t$(MOCPATH) -o $@ " source
  } else if (source ~ /\// && substr(source, 1, 11) != "../include/") {
    if (source ~ /[.]cpp$/ )
      print "\t$(TARGET_CXX) $(TARGET_CXXFLAGS) -c -o $@ " source
    else if (source ~ /\/X11\//)	# "../win/X11/foo.c"
      print "\t$(TARGET_CC) $(TARGET_CFLAGS) $(X11CFLAGS) -c -o $@ " source
    else if (source ~ /\/gnome\//)	# "../win/gnome/foo.c"
      print "\t$(TARGET_CC) $(TARGET_CFLAGS) $(GNOMEINC) -c -o $@ " source
    else
      print "\t$(TARGET_CC) $(TARGET_CFLAGS) -c -o $@ " source
  }
}

#
# recursively add the dependencies for file `name' to string `inout'
# (unless `skip', in which case we're only marking files as already done)
#
function depend(inout, name, skip,		n, i, list)
{
  if (!done[name]++) {
    if (name in alt_deps) {	#some names have non-conventional dependencies
      if (!skip) inout = inout " " alt_deps[name]
      skip = 1
    } else {			#ordinary name
      if (!skip) inout = inout " " name
    }
    if (name in deps) {
 #-   n = split(deps[name], list, " +")
 #-   for (i = 2; i <= n; i++)	#(leading whitespace yields empty 1st element)
 #-	inout = depend(inout, list[i], skip)
 #+  At least one implementation of nawk handles the local array `list' wrong,
 #+  so the clumsier substitute code below is used as a workaround.
      list = deps[name];  sub("^ +", "", list)
      while (list) {
	match((list " "), " +");  i = RSTART;  n = RLENGTH
	inout = depend(inout, substr(list, 1, i-1), skip)
	list = substr(list, i+n)
      }
    }
  }
  return inout
}

#
# sort list[first]..list[last]
# Derived from: https://www.baeldung.com/linux/awk-begin-and-end-rules
#
function nhsort(list, first, last, cmpid,		i,j,temp)
{
  for (i = first; i <= last-1; i++) {
    for (j = i+1; j <= last; j++) {
      if (nhcmp(list[i], list[j], cmpid)) {
	temp = list[i]
	list[i] = list[j]
	list[j] = temp
      }
    }
  }
}

function nhcmp(a,b,cmpid)
{
  if(cmpid == 0){		  # sort dependencies
      # commented out entry (there can be only one) MUST be last
    if (a ~ /^#/){ return 1}
    if (b ~ /^#/){ return 0}
      # 2 .c or .cpp files
    if (a ~ /\.c(pp)?$/ && b ~ /\.c(pp)?$/ ){ return a > b }
      # a .c or .cpp file and anything else
    if (a ~ /\.c(pp)?$/){ return 0 }
    if (b ~ /\.c(pp)?$/){ return 1 }
      # default
    return a > b
  } else if(cmpid == 1){	  # sort rules
      # 2 .h files
    if (a ~ /\.h$/ && b ~ /\/.h$/){ return a > b }
      # a .h and anything else
    if (a ~ /\.h$/){ return 0 }
    if (b ~ /\.h$/){ return 1 }
      # 2 .c or .cpp files
    if (a ~ /\.c(pp)?$/ && b ~ /\.c(pp)?$/ ){ return a > b }
      # a .c or .cpp file and anything else
    if (a ~ /\.c(pp)?$/){ return 0 }
    if (b ~ /\.c(pp)?$/){ return 1 }
      # 2 .moc files
    if (a ~ /\.moc$/ && b ~ /\.moc$/){ return a > b }
      # a .moc and anything else
    if (a ~ /\.moc$/){ return 0 }
    if (b ~ /\.moc$/){ return 1 }
      # default
    return a > b
  } else {
    print "internal error cmpid=" cmpid
    exit 1
  }
}

#depend.awk#
