# Makefile for beastie
#
# Important functional targets:
#
#   all         : the program
#   check       : build the program and run tests
#   docs        : build documentation in doc/
#   clean       : tidy up
#
# and interesting executable targets:
#
#   beastie     : the program
#   repl        : a link to `beastie`; calling ./repl is similar to `beastie -r`
#   lex-aux,... : programs lex-bib2, lex-bst, lex-authors, ...
#                 (one per .lex file, plus lex-bib2.c) will lex an input file
#                 or a string argument and list the lexemes, for the sake
#                 of debugging or curiosity.
#
# The _distributed_ Makefile.in is edited slightly, compared to the
# repository one, so that it doesn't contain the 'dist' target.
#
# Some of the source files -- specifically util-extra.c and mycu.c --
# are generated from the corresponding .in files using
# scheme-macro-filter.scm and the bare-bones program beastie0.
#
# SPDX-FileCopyrightText: 2023 Norman Gray <https://nxg.me.uk>
# SPDX-License-Identifier: BSD-2-Clause

# Copyright years and repo URL are set in configure.ac

# The 'install' target installs things here
prefix=@prefix@
bindir=$(prefix)/bin
mandir=$(prefix)/share/man/man1

# there is a parse-mdblock lexer, but no parser, therefore mdblock is
# not listed here, but separately in LIBOBJS below
PARSERS=authors aux bst fmtstring markdown mdinline json

SRC=	beastie.c beastie0.c core.c s7.c util-extra.c.in util.c \
	beastie.h core.h version.h s7.h util.h util-extra.h yycommon.h \
	unicode.c unicode.h uniprops.h unidefs.h misc/unicode/ucd/ucd_version.h \
	unicode-scm.c unicode-scm.h \
	authors.scm bibtex.scm bst.scm config.scm.in \
	parse-mdblock.lex parse-mdblock.h lex-bib2.c lex-bib2.h \
	klipspringer.scm unicode.scm \
	parse-aux.scm parse-markdown.scm parse-mdblock.scm \
	parse-subtex.scm parse-bib2.scm parse-json.scm \
	runtime.scm xexpr.scm \
	scheme-macro-filter.scm scheme-utils.scm readermacros.scm readermacros.inc \
	$(PARSERS:%=parse-%.lex) \
	$(PARSERS:%=parse-%.y) \
	$(PARSERS:%=parse-%.h)

GEN_C=util-extra.c
HAVE_ICU=@HAVE_ICU@
ifeq ($(HAVE_ICU), YES)
SRC += icu-shim.c
else
GEN_C += mycu.c
endif

DISTFILES=	$(SRC) mycu.c.in icu-shim.c depend.mk \
		configure.ac configure config.h.in version.sh.in Makefile.in doc/Makefile.in \
		bison2.sh release-notes.md LICENCE.txt
# distfiles which are edited during distribution
DISTFILES_MOD=README.md

# This list doesn't include core.o, for no terribly good reason beyond
# it being essentially part of beastie.c, rather than library code as such.
#
# But, hmm: there's now an inconsistent mix of C code in core.o, which
# I think of as somehow different from this, and C code in
# unicode-scm.o and parse-mdblock.o.
LIBOBJS=util.o $(GEN_C:.c=.o) unicode.o unicode-scm.o \
	parse-mdblock.o s7.o lex-bib2.o \
	$(PARSERS:%=parse-%.o) $(PARSERS:%=parse-%.tab.o)

ifeq ($(HAVE_ICU), YES)
LIBOBJS += icu-shim.o
else
LIBOBJS += mycu.o
endif

CC=@CC@
LIBS=@LIBS@

# clang (possibly v14+) warns about variables set but never used,
# which happens in .tab.c files.
# (the -Wno-unused-but-set-variable option turns off a warning about
# a variable being set but not used, in generated Bison code;
# this option seems to be acceptable to both clang and gcc,
# but not older version of clang
CPPFLAGS=@CPPFLAGS@ -MMD
CFLAGS=@CFLAGS@ -Wall -std=c99
LDFLAGS=@LDFLAGS@
NOUNUSEDCFLAG=@NOUNUSEDCFLAG@

FLEX=@FLEX@
BISON_ARGS=
#BISON_ARGS=--report=state
#BISON_ARGS=--report=state -Wcounterexamples
BISON = @BISON@ -t -d $(BISON_ARGS)

REDTEXT=printf "[37;41m %s [0m\n"


all: beastie

beastie: beastie.o core.o libbeastie.a
	$(CC) -o $@ -g $(LDFLAGS) beastie.o core.o -L. -lbeastie $(LIBS)

# If beastie is started with the program name 'repl', then it starts
# in REPL mode by default.  This is a basic REPL.  If BEASTIE_LOAD_PATH
# is pointing to a colon-separated list of directories, in one of
# which is libc_s7.so (see below), then this library is loaded and we
# get a better repl.  But this one is good enough for Emacs.
repl: beastie
	rm -f repl
	ln -s beastie repl

libbeastie.a: $(LIBOBJS)
	ar rv $@ $?
	ranlib $@

parse-%.tab.c parse-%.tab.h: parse-%.y
	$(BISON) --name-prefix=$* $<
parse-%.tab.o: parse-%.tab.c
	$(CC) $(CPPFLAGS) $(CFLAGS) $(NOUNUSEDCFLAG) -c -o $@ $<
parse-%.c: parse-%.lex
	$(FLEX) -o $@ $<

# The programs lex-bib2, lex-bst, lex-authors, ... (one per .lex file,
# plus lex-bib2.c) will lex an input file or a string argument and
# list the lexemes.
#
# Most of these are built from a flex-based .lex, and a yacc/bison .y source.
build/lex-%: build/lex-%.o libbeastie.a
	$(CC) -o $@ -g $< -L. -lbeastie $(LDFLAGS) $(LIBS)
build/lex-%.o: parse-%.c parse-%.tab.c
	test -d build || mkdir build
	$(CC) $(CPPFLAGS) $(CFLAGS) -c -g -o $@ -DWITH_MAIN=1 $<

# parse-mdblock doesn't have a corresponding .y grammar, so no .tab.c
build/lex-mdblock.o: parse-mdblock.c
	test -d build || mkdir build
	$(CC) $(CPPFLAGS) $(CFLAGS) -c -g -o $@ -DWITH_MAIN=1 $<

# bib2 has neither a .lex nor a .y source
# (there was once a parse-bib.{lex,y}, hence the '2', here)
build/lex-bib2: build/lex-bib2-main.o libbeastie.a
	$(CC) -o $@ -g $< -L. $(LDFLAGS) -lbeastie $(LIBS)
build/lex-bib2-main.o: lex-bib2.c
	test -d build || mkdir build
	$(CC) $(CPPFLAGS) $(CFLAGS) -c -g -o $@ -DWITH_MAIN=1 $<

# The following is how to build a bison parser which reports state in .output
# (this is retained here only for reference).
# See also -Wcounterexamples in recent versions of Bison.
# parse-aux.tab.c parse-aux.tab.h: parse-aux.y
# 	bison -d --name-prefix=aux --report=state -t parse-aux.y
# parse-aux: parse-aux.tab.c parse-aux.o util.o s7.o
# 	$(CC) -o $@ -DWITH_MAIN=1 parse-aux.tab.c parse-aux.o util.o s7.o

# There is code in scheme-macro-filter.scm which generates a dependency file
# util-extra.c.d.
util-extra.c: util-extra.c.in beastie0 scheme-macro-filter.scm
	./beastie0 scheme-macro-filter.scm $< $@

# To turn off debugging support in s7.o (faster),
# simply comment this out and use the default rule.
@S7DEBUGGING@s7.o: s7.c
@S7DEBUGGING@	$(CC) $(CFLAGS) -DS7_DEBUGGING -c -o $@ $<

# Coverage support: GCOV1 is enabled when ./configure is run without
# arguments, and GCOV2 when it's run with --enable-altgcov
#
# The following may be mildly specific to llvm gcov, but is at least
# close to the usage of the GCC gcov tool.
#
# Before building beastie-gcov, do `make; rm *.o *.gcovo`.
#
# The usual way to do this is as below.  Then
#
#    % (cd test; make BEASTIE=../heastie-gcov test)`,
#    % gcov foo.c
#
# That produces `foo.c.gcov`, which is supposed to add up the number
# of times each line of foo.c was called, but... doesn't seem to in
# fact.  Bah: this is wrong, but don't sweat it now.  At least I have
# finally worked out how to build these.
#
@GCOV1@%.gcno: %.c
@GCOV1@	$(CC) --coverage -c -O0 -o $*.o $<
@GCOV1@beastie-gcov: beastie.gcno core.gcno $(LIBOBJS:.o=.gcno)
@GCOV1@	$(CC) --coverage -o $@ beastie.o core.o $(LIBOBJS)
#
# The above doesn't work when s7 terminates in an
# abrupt way which means that the gcda data, done the usual way, isn't
# written.  This is (I think) because s7.c chooses to terminate using
# _exit(2) rather than exit(3).
#
# So we do it the alternative way, using llvm-profdata: the
# %c in the LLVM_PROFILE_FILE is important, since it triggers a mode
# where this is streamed directly to beastie.profraw.
#
# See https://clang.llvm.org/docs/SourceBasedCodeCoverage.html
#
# Build beastie.profraw as below, then
#
#    PATH="$XPATH:$PATH" llvm-profdata merge -sparse beastie.profraw -o beastie.profdata
#    PATH="$XPATH:$PATH" llvm-cov show ./beastie-gcov -instr-profile=beastie.profdata >beastie.gcov
#
# Alternatively, use `llvm-cov report`.  It may be necessary to
# rebuild beastie0 with `cc -fprofile-instr-generate -o beastie0
# beastie0.o s7.o`, for some dependency-related reason I haven't got
# to the bottom of.
#
# macOS note: on macOS it's necessary to explicitly place llvm-cov in
# the path, as here (XPATH is the path to the Xcode tools, such as
# /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin;
# it slightly unexpectedly isn't in the path by default).  I'd expect
# it to be OK to add this directory to the PATH before building, but
# it seems not, and the cc then in the path needs some extra
# environment variables, which... I'll work out some other time.
#
# The .gcovo file extension is a dummy one -- that is, the useful
# product of this rule is the .o file, and the .gcovo is just for
# dependency tracking.
@GCOV2@%.gcovo: %.c
@GCOV2@	$(CC) -fprofile-instr-generate -fcoverage-mapping -c -O0 -o $*.o $<
@GCOV2@	touch $@
@GCOV2@beastie-gcov: beastie.gcovo core.gcovo $(LIBOBJS:.o=.gcovo)
@GCOV2@	$(CC) -fprofile-instr-generate -o $@ beastie.o core.o $(LIBOBJS)
@GCOV2@beastie.profraw: beastie-gcov
@GCOV2@	LLVM_PROFILE_FILE='%cbeastie.profraw' BEASTIE_LOAD_PATH=$$PWD/test ./beastie-gcov test/test-misc.scm

# The targets for version.h and version.sed are OMITTED from the
# Makefile.in included in the distribution tarball.
# This is in order to freeze the version number in the distributed version.h.
# See the source repository.

include depend.mk
# when (occasionally) rebuilding depend.mk, do so in the nonicu case
depend.mk:
	@echo "# GENERATED FILE"							>$@
	@echo "# This file is part of Beastie <https://purl.org/nxg/dist/beastie>"	>>$@
	@echo "# SPDX-FileCopyrightText: 2024 Norman Gray <https://nxg.me.uk>"		>>$@
	@echo "# SPDX-License-Identifier: BSD-2-Clause"					>>$@
	cat *.d										>>$@
# the following line will remove all of the absolute paths in the depend files,
# but it isn't necessary, if this is rebuilt in the nonicu case
#	sed -e 's,/[^ ]*,,' -e '/^ *\\/d' *.d						>>$@

### REUSE-IgnoreEnd

# See https://tkurtbond.github.io/posts/2020/08/03/compiling-s7-scheme-on-macos/
# then ./s7_repl libc.scm to build libc_s7.so
# ...but this has to be done in the s7 distribution directory.
# This target exists for maintenance and debugging:
# the s7 source is _not_ routinely downloaded during building
S7DIR=s7
s7repl: $(S7DIR)/s7repl $(S7DIR)/libc_s7.so
$(S7DIR)/s7repl: $(S7DIR)/s7.c
	cd $(S7DIR); $(CC) s7.c -o s7repl -DWITH_MAIN -I. -O2 -g -ldl -lm -Wl,-dynamic
$(S7DIR)/s7.o: $(S7DIR)/s7.c
	cd $(S7DIR); $(CC) -c -g -o s7.o s7.c
$(S7DIR)/libc_s7.so: $(S7DIR)/s7repl $(S7DIR)/s7.o
	cd $(S7DIR); ./s7repl libc.scm
# s7.tar.gz expands into s7/
s7/s7.c: s7.tar.gz
	tar xmzf s7.tar.gz
s7.tar.gz:
	curl -O https://ccrma.stanford.edu/software/s7/s7.tar.gz

mycu.c: mycu.c.in beastie0 misc/unicode/ucd/UnicodeData.txt
	./beastie0 scheme-macro-filter.scm mycu.c.in >$@

core0.o: core.c
	$(CC) -c $(CPPFLAGS) $(CFLAGS) -MF ${@:.o=.d} -DALL_FUNCTIONS=0 -o $@ $<
unicode-scm0.o: unicode-scm.c
	$(CC) -c $(CPPFLAGS) $(CFLAGS) -MF ${@:.o=.d} -DALL_FUNCTIONS=0 -o $@ $<

beastie0.o: readermacros.inc version.h

# beastie0 is a version of beastie which uses no utility functions,
# and only a cut-down runtime.  It's used for bootstrapping
# some parts of the compiled source.
beastie0: beastie0.o core0.o util.o s7.o unicode-scm0.o unicode.o
	$(CC) -o $@ $(LDFLAGS) beastie0.o core0.o unicode-scm0.o util.o unicode.o s7.o $(LIBS)

### Non-ICU testing
#
# We also want to build a non-ICU version of the code,
# when the configured version is the ICU version,
# to confirm that this also still works.
nonicu/%: % nonicu/Makefile.in
	cp $< $@
# nonicu/Makefile.in is also the target which creates the nonicu directory
nonicu/Makefile.in: Makefile.in
	test -d nonicu || mkdir -p nonicu
	mkdir nonicu/doc nonicu/test nonicu/examples
	sed -e '/^version.sed:/,/^$$/d' -e '/^version.h:/,/^$$/d' Makefile.in >$@
nonicu/examples/Makefile: nonicu/Makefile.in
	cp -R examples nonicu
nonicu/misc/unicode/ucd/ucd_version.h: misc/unicode/ucd/ucd_version.h nonicu/Makefile.in
	cp -R misc nonicu
nonicu/test: test/build/dist-test.tar nonicu/Makefile.in
	test -d nonicu/test || mkdir nonicu/test
	cd nonicu/test; tar xf ../../test/build/dist-test.tar
nonicu/config.h: $(DISTFILES:%=nonicu/%) nonicu/test
	cd nonicu; ./configure --without-icu
nonicu/beastie: nonicu/config.h nonicu/examples/Makefile
	@echo "[30;103m\n Building a non-ICU version... [0m"
	cd nonicu; make -j

### Assemble the distribution(s)

docs: doc/dist/beastie.xhtml
doc/dist/beastie.xhtml: beastie doc/beastie.md
	cd doc; $(MAKE) dist/beastie.xhtml

test/build/dist-test.tar: test
	cd test; $(MAKE) build/dist-test.tar
examples/build/dist-examples.tar: examples
	cd examples; $(MAKE) build/dist-examples.tar
doc/build/dist-doc.tar: doc
	cd doc; $(MAKE) build/dist-doc.tar

# Distribution: the targets here produce a 'distribution kit' in build/,
# with pre-built documentation, and a slightly cut-down Makefile.in which
# doesn't include the targets version.{h,sed}.
#
# The dist target depends on version.sed, which isn't included in the
# version of this file included in the distribution tarball.
# This is semi-deliberate, since I want to discourage any
# distributions happening from such a tarball.
DISTLABEL=use-make-dist-XXX
dist: version.sed
	d=`echo beastie-@BEASTIE_VERSION@ | sed -f version.sed`; \
	  $(MAKE) DISTLABEL=$$d build/dist-manifest
# CTAN wants the unpacked directory not to contain a version number,
# but to be just beastie/
dist-ctan: version.sed
	$(MAKE) DISTLABEL=beastie build/dist-manifest

# the CI in .gitlab-ci.yml uses this file
build/dist-manifest: build/$(DISTLABEL).tar.gz build/$(DISTLABEL).zip
	for f in $(^:build/%=%); do echo $$f; done >$@

# The macOS tar has a --no-xattrs option, which stops it inserting
# extended attributes, it finds in files it's bundling, and the same
# behaviour is triggered by the (famously undocumented)
# COPYFILE_DISABLE=1 environment variable.
# I expect in fact to build distribution tarballs during CI, so there
# may be no need for this, but include it anyway, just in case.
build/$(DISTLABEL).tar.gz: build/$(DISTLABEL)/Makefile.in
	cd build; \
	  COPYFILE_DISABLE=1 tar -cf $(DISTLABEL).tar $(DISTLABEL); \
	  gzip --best $(DISTLABEL).tar

build/$(DISTLABEL).zip: build/$(DISTLABEL)/Makefile.in
	cd build; zip -q -r $(DISTLABEL).zip $(DISTLABEL)

# Assemble the distribution.
# The contents of test, examples, and doc are assembled into tarballs
# by Makefiles in the corresponding directories -- this avoids us
# inadvertently including built files from those directories.
#
# We include doc/dist, so that the distributed tarball has readable
# docs immediately, without the recipient having to build them.
build/$(DISTLABEL)/Makefile.in: $(DISTFILES) $(DISTFILES_MOD) \
		doc/dist/beastie.xhtml version.sed \
		test/build/dist-test.tar examples/build/dist-examples.tar doc/build/dist-doc.tar
	mkdir -p build/$(DISTLABEL)
	tar cf - $(DISTFILES) | (cd build/$(DISTLABEL); tar xBpf -)
	for f in $(DISTFILES_MOD); do sed -f version.sed $$f >build/$(DISTLABEL)/$$f; done
	top=`pwd`; for d in test examples doc; do \
	  mkdir -p build/$(DISTLABEL)/$$d; \
	  (cd build/$(DISTLABEL)/$$d; tar xf $$top/$$d/build/dist-$$d.tar); \
	done
	cp -R misc LICENSES build/$(DISTLABEL)
	cp -R doc/dist build/$(DISTLABEL)/doc
	echo @PACKAGE_VERSION@ >build/$(DISTLABEL)/VERSION
	sed '/^#NODIST/,/^#DIST/d' Makefile.in >build/$(DISTLABEL)/Makefile.in

# Attempt to build and test the distribution tarball
dist-check: dist
	cd build; \
	  d=$$PWD; tar=`grep tar.gz dist-manifest`; name=`basename $$tar .tar.gz`\
	  mkdir install-test-build; cd install-test-build \
	  && tar xzf $$d/$$tar && cd * \
	  && ./configure --prefix=$$d/install-test-local \
	  && make -j check

# The install target is very crude!
install: beastie
	test -d "$(bindir)" || { echo "Create $(bindir) first" >&2; false; }
	test -d "$(mandir)" || { echo "Create $(mandir) first" >&2; false; }
	install -m 755 beastie "$(bindir)"
	install -m 644 doc/beastie.1 "$(mandir)"

# If we have ICU, then check the non-ICU case as well
ifeq ($(HAVE_ICU), YES)
check: check0 check-nonicu
else
check: check0
endif

check-nonicu: nonicu/beastie
	@echo "[30;103m Repeating the tests for the non-ICU case... [0m"
	cd nonicu; $(MAKE) check
	@echo "[30;103m ...non-ICU check OK[0m"

check0: beastie
	cd test; $(MAKE) check
	cd examples; $(MAKE) check


Makefile: ./config.status Makefile.in
	./config.status Makefile
config.h config.scm: ./config.status
	./config.status
	touch config.h config.scm
version.sh: ./config.status version.sh.in
	./config.status version.sh

clean:
	rm -f beastie beastie0 repl \
		*.o *.d \
		$(PARSERS:%=parse-%.c) \
		$(PARSERS:%=parse-%.tab.c) \
		$(PARSERS:%=parse-%.tab.h) \
		$(PARSERS:%=lex-%) \
		$(GEN_C) \
		libbeastie.a libc_s7.*
	rm -Rf build
# these directory tests are redundant _except_ when running this in the nonicu case
	if test -d doc; then		cd doc; $(MAKE) clean; fi
	if test -d test; then		cd test; $(MAKE) clean; fi
	if test -d examples; then	cd examples; $(MAKE) clean; fi
	if test -d nonicu; then		rm -Rf nonicu; fi
