* $Id: setuputil.sno,v 1.51 2015/01/02 04:50:19 phil Exp $
* setuputil.sno - help build CSNOBOL4 LOAD()able libraries
* (on POSIX and Win32)
* Phil Budne November 3, 2013
* from sqlite3 build.sno, in imitation of Python setup/distutils

* NOTE! Not careful about local variables!
* This is NOT meant for general inclusion into programs!!

-INCLUDE 'host.sno'
-INCLUDE 'snopea.sno'
-INCLUDE 'config.sno'
-INCLUDE 'basename.sno'

* TODO
*   Honor more vars:
*	name
*	author -- write into POD "AUTHOR" section!!
*	version
*	description
*	url
*	download_url
*	extra_compile_args

**=pea
**=sect NAME
**snobol4setup \- SNOBOL4 Loadable module setup utility
**=sect SYNOPSIS
**B<snobol4 setup.sno> I<command>
**=sect DESCRIPTION
**The example program below (canonically called B<setup.sno>) can be invoked
**(portably across different operating systems)
**with the following commands:
**
**=sect OPTIONS
**=item build
**Compiles and links a dynamicly loadable executable file
**named I<modulename> (with the appropriate extension for the local system).
**I<modulename>B<.sno> will be created from
**B<pre.sno> (if it exists),
**any I<LOAD()> present in comments 
**and any lines starting with B<*=pea> and ending with B<*=cut>
**in I<sourcefiles>,
**and finally B<funcs.sno> (if it exists).
**
**=item install
**installs binary, include and man page files.
**
**=item clean
**cleans up.
**
**=sect EXAMPLE
**=code
**-INCLUDE 'setuputil.sno'
**        t = TABLE()
**        t['module'] = 'I<modulename>'
**        t['sources'] = 'I<sourcefile>.c,I<othername.c>'
**        t['author'] = t['maintainer'] = 'I<Yourname Here>'
**        t['author_email'] = t['maintainer_email'] = 'I<name@doma.in>'
**        t['include_dirs'] = 'I<list,of,dirs,to,search>'
**        t['library_dirs'] = 'I<list,of,dirs,to,search>'
**        t['libraries'] = 'I<list,of,library,name,to,link,against>'
**        t['license'] = 'BSD'
**        setup(t)
**end
**=ecode
**=sect NOT CONFORMING TO
**Inspired by Python setuptools, easyinstall, et.al.
**=sect AUTHOR
**Philip L. Budne
**=sect BUGS
**Only tested under OS X, FreeBSD, Linux, CYGWIN, MINGW, Visual Studio.
**=sect SEE ALSO
**B<snobol4>(1), B<snopea>(1)
**=cut
****************
* make code less confusing!
	setup.c = ','
	setup.s = ' '
*	tab
	setup.t = '	'


	DEFINE('setup(t)')			:(setup.end)
setup	TERMINAL = DIFFER(DATATYPE(t),'TABLE') 'setup needs TABLE'
+						:s(setup.fatal)
	podseen =

	objext = HOST(HOST_OBJ_EXT)
	sys = HOST(HOST_SETUP_SYS)

	cygwin = DIFFER(HOST() ? POS(0) BREAK(':') ':CYGWIN') 1
	windows = DIFFER(HOST() ? POS(0) BREAK(':') ':Win') 1
	mingw = DIFFER(windows) IDENT(sys, "posix") 1

* build against distribution include files
	dist_build = FILE("../../snolib/setuputil.sno") FILE("../../config.h") 1
	dist_build = 
+		FILE("..\..\snolib\setuputil.sno") FILE("..\..\config\win32\config.h") 1

*	XXX parse options!!
	i = HOST(HOST_FIRSTARG)
setup.switches
	cmd = HOST(HOST_ARGN, i)		:f(setup.usage)
	i = i + 1
	IDENT(cmd)				:s(setup.usage)
	cmd POS(0) '-'				:f(setup.cmd)
	setup.verbose = IDENT(cmd, '-v') 1	:s(setup.switches)
	setup.debug = IDENT(cmd, '-d') ' -g'	:s(setup.switches)
	:(setup.usage)

setup.cmd
	IDENT(cmd, 'help')			:s(setup.usage)

	lbl = 'setup.' sys '.' cmd 'cmd'
	FUNCTION(lbl)				:f(setup.not.found)
	TERMINAL = APPLY(lbl, t) t['module'] ' ' cmd ' succeeded.' :s(return)
	TERMINAL = t['module'] ' ' cmd ' failed.' :(setup.fatal)

setup.not.found
+	TERMINAL = sys ' ' cmd ' not found'
setup.usage
+	TERMINAL = 'Usage: snobol4 setup.sno [-v] [-d] COMMAND'
	TERMINAL = 'Commands: build install clean help'
setup.fatal
+	&CODE = 1				:(freturn)
setup.end

****************************************************************
*	utilities

*	replace all instances of string "from" with "to"
	DEFINE('setup.repl(s,from,to)pat,pre,suf,x') :(setup.repl.end)
setup.repl
+	pat = (POS(0) BREAKX(SUBSTR(from,1,1))) . pre from REM . suf
setup.repl.loop
	s pat = pre to suf			:s(setup.repl.loop)
	setup.repl = s				:(RETURN)
setup.repl.end

*	return name of Dynamic Library file
	DEFINE('setup.dlname(t)')		:(setup.dlname.end)
setup.dlname
	setup.dlname = t['module'] HOST(HOST_DL_EXT) :(return)
setup.dlname.end

*	return name of snobol include file
	DEFINE('setup.sno(t)')			:(setup.sno.end)
setup.sno
	setup.sno = t['module'] '.sno'		:(return)
setup.sno.end

****************
*	generate -INCLUDE file from .c files
	DEFINE('setup.makeinclude(t)'
+		'modname,files,lines,line,runpea,inpea,extras')
+						:(setup.makeinclude.end)
setup.makeinclude
	ou = IO_FINDUNIT()
	of = setup.sno(t)
	OUTPUT(.out, ou,, of)			:f(freturn)
	out = "* Generated by setuputil.sno setup.makeinclude() " DATE()
	modname = REPLACE(t['module'], &LCASE, &UCASE) '_DL'
	out = setup.t modname ' = "' t['module'] '"'
	iu = IO_FINDUNIT()

	runpea =	
***** copy pre.sno, if it exists
	INPUT(.in, iu,, 'pre.sno')		:f(smi.nopre)
	out = '**** pre.sno:'
smi.copy.pre
	lines = lines + 1
	line = in				:f(smi.copy.pre.eof)
	line '=pea'				:f(smi.copy.pre.out)
	runpea = 1
smi.copy.pre.out
	out = line				:s(smi.copy.pre)
smi.copy.pre.eof
	ENDFILE(iu)
	DETACH(.in)
	out = '******** end pre.sno'
smi.nopre

***** loop for each C source file
* look at each source line for LOAD(....) prototypes, POD docs

	files = t['sources'] setup.c
	lines = 0
smi.floop
	files pos(0) breakx(setup.c) . file setup.c = :f(smi.close)
	iu = IO_FINDUNIT()
	INPUT(.in, iu,, file)			:f(smi.openerr)
	inpea =
smi.iloop
	line = in				:f(smi.eof)

* copy SNOPOD sections to output
	differ(inpea)				:s(smi.inpea)
	line POS(0) span('*') '='		:f(smi.lloop)
	runpea = 1
	inpea = 1
smi.inpea
	out = line
	line POS(0) span('*') '=cut'		:f(smi.iloop)
	inpea =

* look for multiple LOAD()s on a line??!
smi.lloop
	line BREAKX('L') ('LOAD' BAL) . load =	:f(smi.iloop)
	lines = lines + 1
	out = setup.t load    	 		:(smi.lloop)
smi.eof

***** copy funcs.sno, if it exists
	iu = IO_FINDUNIT()
	INPUT(.in, iu,, 'funcs.sno')		:f(smi.nofuncs)
	out = '**** funcs.sno:'
smi.copy.funcs
	line = in				:f(smi.copy.funcs.eof)
	line '=pea'				:f(smi.copy.funcs.out)
	runpea = 1
smi.copy.funcs.out
	out = line				:s(smi.copy.funcs)
smi.copy.funcs.eof

	lines = lines + 1
	out = in				:s(smi.copy.funcs)
	ENDFILE(iu)
	DETACH(.in)
	out = '******** end funcs.sno'
smi.nofuncs
	out = '**************** end ' t['module'] '.sno'
	ENDFILE(iu)
	DETACH(.in)

smi.close
	ENDFILE(ou)
	DETACH(.out)
	TERMINAL = GT(lines,0) 'created ' of
* delete if EQ(lines,0) ???
	IDENT(runpea)				:s(smi.endman)

* man file
	mf = pn = 'snobol4' of
	mf '.sno' = '.3'
	htmlmf = mf '.html'

* page name
	pn '.sno' =

	TERMINAL = snopea(of, mf, pn, '3', 'roff') 'created ' mf
	TERMINAL = snopea(of, htmlmf, pn, '3', 'html') 'created ' htmlmf
smi.endman

*** process 'extras' files for 'snopea' docs
	extras = t['extras'] ','
smi.extras
	extras break(',') . fn ',' =		:f(smi.done)
	ident(fn)				:s(smi.done)

	iu = IO_FINDUNIT()
	INPUT(.in, iu,, fn)			:s(smi.checkpea)
	TERMINAL = 'could not open "extras": ' fn :(smi.extras)

	runpea =
smi.checkpea
	line = in				:f(smi.checkpea.eof)
	line POS(0) SPAN('*') '=pea'		:f(smi.checkpea)
	runpea = 1
smi.checkpea.eof
	DETACH(.in)
	ENDFILE(iu)
	IDENT(runpea)				:s(smi.done)

	pn = 'snobol4' fn
	pn '.sno' =
	mf = pn '.3'
	htmlmf = mf '.html'

	TERMINAL = snopea(fn, mf, pn, '3', 'roff') 'created ' mf
	TERMINAL = snopea(fn, htmlmf, pn, '3', 'html') 'created ' htmlmf
	:(smi.extras)

smi.done
	:(return)

smi.openerr
	TERMINAL = "Could not open " file	:(freturn)
setup.makeinclude.end
*
****************************************************************
*	POSIX

* can be used on any system where zero status code means success
* (ie; NOT VMS!!!)
	DEFINE('setup.posix.system(command)')	:(setup.posix.system.end)
setup.posix.system
	TERMINAL = DIFFER(setup.verbose) 'command: ' command
	IDENT(command)				:s(FRETURN)
	&CODE = HOST(HOST_SYSCMD, command)	:f(FRETURN)
	TERMINAL = DIFFER(setup.verbose) 'status: ' &CODE
	EQ(&CODE, 0)		  		:s(RETURN)f(FRETURN)
setup.posix.system.end

	DEFINE('setup.posix.objs(t)srcs,file')	:(setup.posix.objs.end)
setup.posix.objs
	srcs = setup.repl(t['sources'] setup.c, '.c,', objext ' ')
setup.posix.objs.loop
	IDENT(srcs)				:s(RETURN)
	srcs (break(' ') ' ') . file =		:f(RETURN)
	file = basename(file)
	setup.posix.objs = setup.posix.objs file :(setup.posix.objs.loop)
setup.posix.objs.end

****************
*	POSIX build command
*		requires C compiler have -c and -I options
*		and linker/loader with -o option

	DEFINE('setup.posix.buildcmd(t)')	:(setup.posix.buildcmd.end)
setup.posix.buildcmd
  	iflags = ' -I' HOST(HOST_INCLUDE_DIR)
	iflags = DIFFER(dist_build) ' -I../.. -I../../include'
	iflags = DIFFER(dist_build) DIFFER(mingw) iflags ' -I../../config/win32'

setup.posix.buildcmd2
	idirs = t['include_dirs']
	iflags = DIFFER(idirs) iflags setup.repl(setup.c idirs, setup.c, ' -I')

	sources = setup.repl(t['sources'], setup.c, setup.s)

* build "dynamic library" (DL) -- something attached at runtime
	compile = HOST(HOST_CC) ' -c ' HOST(HOST_COPT)
+		' ' HOST(HOST_DL_CFLAGS) setup.debug
+		' ' t['cflags']
+		' -DHAVE_CONFIG_H' iflags
+		' ' sources

	setup.posix.system(compile)		 :f(FRETURN)

	libdirs = t['library_dirs']
	libdirs = DIFFER(libdirs) setup.repl(setup.c libdirs, setup.c, ' -L')
	libdirs = DIFFER(mingw cygwin) libdirs ' -L' (DIFFER(dist_build) '../..',
+							HOST(HOST_SNOLIB_VERS))

	libs = t['libraries']
	libs = DIFFER(libs) setup.repl(setup.c libs, setup.c, ' -l')
	libs = DIFFER(mingw cygwin) libs ' -lsnobol4'

	link = HOST(HOST_DL_LD)
+		' ' HOST(HOST_DL_LDFLAGS)
+		' -o ' setup.dlname(t) ' ' setup.posix.objs(t) libdirs libs
+		' ' t['ldflags']

	setup.posix.system(link)		:f(FRETURN)
	setup.makeinclude(t)			:(RETURN)
setup.posix.buildcmd.end

****************
*	POSIX install command
*		uses "cp -f"

	DEFINE('setup.posix.installcmd(t)')	:(setup.posix.installcmd.end)
setup.posix.installcmd

* if no CVS directory-- install as a "local" file
	dest = (FILE_ISDIR('CVS') HOST(HOST_SNOLIB_VLIB), 
+			HOST(HOST_SNOLIB_VLOCAL))

	destdir = HOST(HOST_GETENV, 'DESTDIR')
	libfile = setup.dlname(t)
	shared = (HOST(HOST_SHARED_OBJ_SUBDIR), 'shared')
	command = 'cp -f ' libfile ' ' dest '/' shared
	setup.posix.system(command)		:f(FRETURN)

	sno = setup.sno(t)
	snofiles = sno

* get man files:
	man = 'snobol4' sno
	man '.sno' = '.3'
	manfiles = (FILE(man) man, '')

	x = t['extras'] ','
	xfiles =
setup.posix.instcmd.xloop
	x break(',') . fn ',' =			:f(setup.posix.instcmd.xdone)
	ident(fn)				:s(setup.posix.instcmd.xdone)
	snofiles = FILE(fn) snofiles ' ' fn
	fn '.sno' = '.3'
	fn = 'snobol4' fn
	manfiles = FILE(fn) manfiles ' ' fn 	:(setup.posix.instcmd.xloop)
setup.posix.instcmd.xdone

	command = 'cp -f ' snofiles ' ' dest
	setup.posix.system(command)		:f(FRETURN)

	IDENT(manfiles)				:s(RETURN)
	command = 'cp -f ' manfiles ' ' destdir MAN3DIR
	setup.posix.system(command)		:f(FRETURN)s(RETURN)
setup.posix.installcmd.end

****************
*	POSIX clean command
*		uses "rm -f"

	DEFINE('setup.posix.cleancmd(t)sno,man,x') :(setup.posix.cleancmd.end)
setup.posix.cleancmd
* XXX move path generation into a function???
	sno = setup.sno(t)
	man = 'snobol4' sno
	man '.sno' = '.3'
	man = man ' ' man '.html'

	x = t['extras'] ','
	xfiles =
setup.posix.cleancmd.xloop
	x break(',') . fn ',' =			:f(setup.posix.cleancmd.xdone)
	ident(fn)				:s(setup.posix.cleancmd.xdone)
	fn '.sno' = '.3'
	fn = 'snobol4' fn
	xfiles = xfiles ' ' fn ' ' fn '.html'	(setup.posix.cleancmd.xloop)
setup.posix.cleancmd.xdone
	command = 'rm -f ' setup.posix.objs(t) setup.dlname(t) ' ' sno ' ' man
+				xfiles
	setup.posix.system(command)		:s(RETURN)f(FRETURN)
setup.posix.cleancmd.end
*
****************************************************************
* Windows with MicroSoft C

	DEFINE('setup.win.msc.basename(fname)') :(setup.win.msc.basename.end)
setup.win.msc.basename
	setup.win.msc.basename = basename(fname, '/\')	:(RETURN)
setup.win.msc.basename.end

	DEFINE('setup.win.msc.objs(t)srcs,file') :(setup.win.msc.objs.end)
setup.win.msc.objs
	srcs = setup.repl(t['sources'] setup.c, '.c,', objext ' ')
	srcs = setup.repl(srcs setup.c, '.cpp,', objext ' ')

setup.win.msc.objs.loop
	IDENT(srcs)				:s(RETURN)
	srcs (break(' ') ' ') . file =		:f(RETURN)
	file = setup.win.msc.basename(file)
	setup.win.msc.objs = setup.win.msc.objs file :(setup.win.msc.objs.loop)
setup.win.msc.objs.end

****************
*	Windows build command using MSC

	DEFINE('setup.win.msc.buildcmd(t)')	:(setup.win.msc.buildcmd.end)
setup.win.msc.buildcmd
  	iflags = (IDENT(dist_build) ' -I' HOST(HOST_INCLUDE_DIR),
+			' -I..\.. -I..\..\include -I..\..\config\win32')

setup.win.msc.buildcmd2
	idirs = t['include_dirs']
	iflags = DIFFER(idirs) iflags setup.repl(setup.c idirs, setup.c, ' -I')

	sources = setup.repl(t['sources'], setup.c, setup.s)

* build "dynamic library" (DL) -- something attached at runtime
	compile = HOST(HOST_CC) ' -c -DHAVE_CONFIG_H ' HOST(HOST_COPT)
+		' ' HOST(HOST_DL_CFLAGS)
+		' ' t['cflags']
+		' ' iflags
+		' ' sources

	setup.posix.system(compile)		 :f(FRETURN)

	libdirs = t['library_dirs']
	libdirs = DIFFER(libdirs)
+		setup.repl(setup.c libdirs, setup.c, ' /LIBPATH:')

* XXX might need quotes on HOST_SNOLIB_VERS????
	snobol4lib_dir = ' /LIBPATH:' (DIFFER(dist_build) '..\..',
+					HOST(HOST_SNOLIB_VERS))

	libs = t['libraries']
	libs = DIFFER(libs) setup.repl(libs setup.c, setup.c, '.lib ')

	link = HOST(HOST_DL_LD)
+		' ' HOST(HOST_DL_LDFLAGS)
+		' /OUT:' setup.dlname(t)
+		' ' t['ldflags']
+		' ' setup.win.msc.objs(t)
+		' ' libdirs snobol4lib_dir
+		' ' libs 'snobol4.lib'

	setup.posix.system(link)		:f(FRETURN)
	setup.makeinclude(t)			:(RETURN)
setup.win.msc.buildcmd.end

****************
*	Windows install command w/o MINGW
*		uses "copy /y"

	DEFINE('setup.win.msc.installcmd(t)')	:(setup.win.msc.installcmd.end)
setup.win.msc.installcmd

* if no CVS directory-- install as a "local" file
	dest = (FILE_ISDIR('CVS') HOST(HOST_SNOLIB_VLIB), 
+			HOST(HOST_SNOLIB_VLOCAL))
	htmldir = HOST(HOST_SNOLIB_VERS) '\doc'
	destdir = HOST(HOST_GETENV, 'DESTDIR')

	libfile = setup.dlname(t)
	FILE(libfile)				:F(FRETURN)
	shared = (HOST(HOST_SHARED_OBJ_SUBDIR), 'shared')
	command = 'copy /y ' libfile ' "' destdir dest '\' shared '"'
	setup.posix.system(command)

* snobol4<module>.sno, snobol4<module>.html
	fn = setup.sno(t)
	FILE(fn) setup.posix.system('copy /y ' fn ' "' destdir dest '"')

	fn = 'snobol4' fn
	fn '.sno' = '.3.html'
	FILE(fn) setup.posix.system('copy /y ' fn ' "' destdir htmldir '"')

	x = t['extras'] ','
setup.win.msc.instcmd.xloop
	x break(',') . fn ',' =			:f(setup.win.msc.instcmd.xdone)
	ident(fn)				:s(setup.win.msc.instcmd.xdone)

	FILE(fn) setup.posix.system('copy /y ' fn ' "' destdir dest '"')

	fn = 'snobol4' fn
	fn '.sno' = '.3.html'
	FILE(fn) setup.posix.system('copy /y ' fn ' "' destdir htmldir '"')
	:(setup.win.msc.instcmd.xloop)
setup.win.msc.instcmd.xdone			:(RETURN)

setup.win.msc.installcmd.end

****************************************************************
* more systems here


