VERSION=0.16

BIN_OPT:= \
  create-graph.native \
  print-stats.native \
  bin2src.native \
  src2bin.native \
  build-fixpoint.native \
  clean-repository.native \
  buildgraph2srcgraph.native \
  annotate-strong.native \
  partial-order.native \
  find-fvs.native \
  collapse-srcgraph.native \
  optuniv.native \
  calculate-fas.native \
  buildcheck-more-problems.native \
  distcheck-more-problems.native

BIN_BYTE:=$(patsubst %.native, %.d.byte, $(BIN_OPT))
BIN_PROF:=$(patsubst %.native, %.p.native, $(BIN_OPT))

BIN_PYTOOLS:=$(patsubst tools/%.py, %, $(filter-out tools/util.py tools/debarch.py,$(wildcard tools/*.py)))
BIN_OCTOOLS:=$(patsubst %.native, %, $(BIN_OPT))
BIN_SHTOOLS:=$(patsubst tools/%.sh, %, $(wildcard tools/*.sh))

BIN_TOOLS:=$(BIN_PYTOOLS) $(BIN_OCTOOLS) $(BIN_SHTOOLS)
MANPAGES:=$(patsubst %, doc/man/botch-%.1, $(BIN_TOOLS))

OCAML_BEST ?= $(if $(wildcard /usr/bin/ocamlopt),native,d.byte)

PWD := $(shell pwd)
BUILD = $(PWD)/dose/_build
DOSELIBS = $(PWD)/dose/_build/dose3
CUDFLIBS = $(PWD)/dose/_build/cudf
DOSEBYTELIBS = \
  _build/doselibs/common.cma \
  _build/doselibs/debian.cma \
  _build/doselibs/versioning.cma \
  _build/doselibs/csw.cma \
  _build/doselibs/pef.cma \
  _build/doselibs/algo.cma \
  _build/doselibs/doseparse.cma \
  _build/doselibs/doseparseNoRpm.cma
CFLAGS=-cflags "-w +a-4-9"
bindir=/usr/bin
datadir=/usr/share/botch

# we have to export PYTHONHASHSEED because otherwise networkx will create
# content with random output order
#   https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=749710
#   https://github.com/networkx/networkx/issues/1181
# on the other hand, the generated hashes are not the same on 32 vs 64 arches
#   http://bugs.python.org/issue22621
#   http://bugs.debian.org/778373
export PYTHONHASHSEED=0
# to make the sort order in the tests locale independent
export LC_COLLATE=C.UTF8
export LC_ALL=C.UTF8
# because dose2html outputs utf8 on standard output
export PYTHONIOENCODING=utf-8

.PHONY: all
all: $(OCAML_BEST) doc

.PHONY: native
native: atdgen
	OCAMLPATH=$(BUILD) ocamlbuild -classic-display -use-ocamlfind $(CFLAGS) $(BIN_OPT)

.PHONY: d.byte
d.byte: atdgen
	OCAMLPATH=$(BUILD) ocamlbuild -classic-display -use-ocamlfind $(CFLAGS) $(BIN_BYTE)

# profiling for use of native executables with gprof
.PHONY: profile
profile: atdgen
	OCAMLPATH=$(BUILD) ocamlbuild -classic-display -use-ocamlfind $(CFLAGS) $(BIN_PROF)

doc/man/botch.1: $(MANPAGES)
	./doc/man/generate-botch-manpage.py | pod2man --section 1 --center="botch tools" --name botch > $@

.PHONY: doc
doc: $(MANPAGES) doc/man/botch.1
	$(MAKE) -C doc/wiki

doc/man/%.1: doc/man/%.pod
	pod2man --section 1 --center="botch tools" $< > $@

.PHONY: doselib
doselib:
	(cd dose && ./configure --with-zip --with-bz2 --with-ocamlgraph --without-libcudf && make clean libs $(DOSEBYTELIBS))
	rm -Rf $(DOSELIBS)
	mkdir -p $(DOSELIBS)
	cp dose/META $(DOSELIBS)
	for i in \
		dose/_build/*/common.* \
		dose/_build/*/versioning.* \
		dose/_build/*/debian.* \
		dose/_build/*/algo.* \
		dose/_build/*/csw.* \
		dose/_build/*/pef.* \
		dose/_build/*/doseparseNoRpm.* \
		dose/_build/*/doseparse.*; do \
	  if [ -e $$i ]; then \
	  cp $$i $(DOSELIBS) ; \
	  rm -f $(DOSELIBS)/*.mlpack $(DOSELIBS)/*.cmx ; \
	  fi ; \
	done
	rm -Rf $(CUDFLIBS)
	mkdir -p $(CUDFLIBS)
	cp dose/cudf/META $(CUDFLIBS)
	cp dose/_build/doselibs/cudf*.cmi $(CUDFLIBS)
	for i in dose/_build/doselibs/cudf.*; do \
	  if [ -e $$i ]; then \
	    cp $$i $(CUDFLIBS) ; \
	    rm -f $(CUDFLIBS)/*.mlpack $(CUDFLIBS)/*.cmx ; \
	  fi ; \
	done

%_j.ml: %.atd
	atdgen -j -j-std $<

%_t.ml: %.atd
	atdgen -t $<

.PHONY: atdgen
atdgen: datatypes_j.ml datatypes_t.ml

# the "while : ; do" loop around graph-difference is a temporary fix to work
# around random segmentation faults. See http://bugs.python.org/issue24605
.PHONY: test-default
test-default: $(OCAML_BEST)
	rm -rf tmp out
	./tools/native.sh --debug --verbose --develop --output out --tmp tmp --jobs=2 --drop-b-d-indep amd64 tests/sid-amd64-packages-20140101T000000Z tests/sid-sources-20140101T000000Z
	for f in tests/default/tmp/* tmp/*; do basename "$$f"; done | sort | uniq | while read f; do \
		echo checking $$f; \
		case "$$f" in \
		*.xml) while : ; do ./tools/graph-difference.py "tests/default/tmp/$$f" "tmp/$$f"; exit=$$?; if [ $$exit -eq 139 ]; then echo segfault; continue; fi; if [ $$exit -ne 0 ]; then exit 1; else break; fi; done;; \
			*) diff -u "tests/default/tmp/$$f" "tmp/$$f" || exit 1;; \
		esac; \
	done
	for f in tests/default/out/* out/*; do basename "$$f"; done | sort | uniq | while read f; do \
		echo checking $$f; \
		case "$$f" in \
			*.xml) while : ; do ./tools/graph-difference.py "tests/default/out/$$f" "out/$$f"; exit=$$?; if [ $$exit -eq 139 ]; then echo segfault; continue; fi; if [ $$exit -ne 0 ]; then exit 1; else break; fi; done;; \
			*) diff -u "tests/default/out/$$f" "out/$$f" || exit 1;; \
		esac; \
	done

.PHONY: test-selfcontained
test-selfcontained: $(OCAML_BEST)
	rm -rf tmp out
	./tools/native.sh --debug --verbose --develop --output out --tmp tmp --drop-b-d-indep --optgraph --latest --clean --self-contained --optuniv --sapsb --strong --no-drop amd64 tests/sid-amd64-packages-20140101T000000Z tests/sid-sources-20140101T000000Z
	for f in tests/selfcontained/tmp/* tmp/*; do basename "$$f"; done | sort | uniq | while read f; do \
		echo checking $$f; \
		case "$$f" in \
			*.xml) while : ; do ./tools/graph-difference.py "tests/selfcontained/tmp/$$f" "tmp/$$f"; exit=$$?; if [ $$exit -eq 139 ]; then echo segfault; continue; fi; if [ $$exit -ne 0 ]; then exit 1; else break; fi; done;; \
			*) diff -u "tests/selfcontained/tmp/$$f" "tmp/$$f" || exit 1;; \
		esac; \
	done
	for f in tests/selfcontained/out/* out/*; do basename "$$f"; done | sort | uniq | while read f; do \
		echo checking $$f; \
		case "$$f" in \
			*.xml) while : ; do ./tools/graph-difference.py "tests/selfcontained/out/$$f" "out/$$f"; exit=$$?; if [ $$exit -eq 139 ]; then echo segfault; continue; fi; if [ $$exit -ne 0 ]; then exit 1; else break; fi; done;; \
			*) diff -u "tests/selfcontained/out/$$f" "out/$$f" || exit 1;; \
		esac; \
	done

.PHONY: test-cross
test-cross: $(OCAML_BEST)
	rm -rf tmp out
	mkdir out
	cp tests/cross-ma.diff out/ma.diff
	./tools/cross.sh --debug --verbose --develop --output=out --tmp=tmp --drop-b-d-indep --optgraph --jobs=2 amd64 armhf tests/sid-amd64-packages-20140101T000000Z tests/sid-sources-20140101T000000Z
	for f in tests/cross/tmp/* tmp/*; do basename "$$f"; done | sort | uniq | while read f; do \
		echo checking $$f; \
		case "$$f" in \
			*.xml) while : ; do ./tools/graph-difference.py "tests/cross/tmp/$$f" "tmp/$$f"; exit=$$?; if [ $$exit -eq 139 ]; then echo segfault; continue; fi; if [ $$exit -ne 0 ]; then exit 1; else break; fi; done;; \
			*) diff -u "tests/cross/tmp/$$f" "tmp/$$f" || exit 1;; \
		esac; \
	done
	for f in tests/cross/out/* out/*; do basename "$$f"; done | sort | uniq | while read f; do \
		echo checking $$f; \
		case "$$f" in \
			*.xml) while : ; do ./tools/graph-difference.py "tests/cross/out/$$f" "out/$$f"; exit=$$?; if [ $$exit -eq 139 ]; then echo segfault; continue; fi; if [ $$exit -ne 0 ]; then exit 1; else break; fi; done;; \
			*) diff -u "tests/cross/out/$$f" "out/$$f" || exit 1;; \
		esac; \
	done

.PHONY: test-man
test-man: all
	# test whether there is no manpage without a program by
	# normalizing program and manpage names and only printing
	# those names which are not duplicate but unique
	ls tools/*.py *.$(OCAML_BEST) tools/*.sh doc/man/botch-*.pod \
		| grep -v tools/util.py \
		| grep -v tools/debarch.py \
		| while read pkg; do \
			pkg=`basename $$pkg .$(OCAML_BEST)`; \
			pkg=`basename $$pkg .py`; \
			pkg=`basename $$pkg .sh`; \
			pkg=`basename $$pkg .pod`; \
			echo $${pkg#botch-}; \
		done | sort | uniq -u | while read pkg; do \
			echo doc/man/botch-$${pkg}.pod is superfluous >&2; \
			exit 1; \
		done
	# now that we know that there is one man page for every program, check
	# whether the man page documents all the program options
	ls tools/*.py *.$(OCAML_BEST) tools/*.sh \
		| grep -v tools/util.py \
		| grep -v tools/debarch.py \
		| grep -v buildcheck-more-problems | grep -v distcheck-more-problems \
		| while read pkg; do \
			shortopts=`./$$pkg --help 2>&1 | sed -ne 's/^ \+-\([^ =-]\).*$$/\1/p' | sort`; \
			longopts=`./$$pkg --help 2>&1 | sed -ne 's/^ \+\(-[^ =-]\+,\)\? --\([^ =]\+\)\([ =].*\)\?$$/\2/p' | sort`; \
			echo "compare \`./$$pkg --help\` with man page..."; \
			man=`basename $$pkg .$(OCAML_BEST)`; \
			man=`basename $$man .py`; \
			man=`basename $$man .sh`; \
			man=`basename $$man .pod`; \
			longman=`sed -ne 's/=item B<[^>]*--\([^>=]\+\)=\?>.*$$/\1/p' doc/man/botch-$${man}.pod | sort`; \
			shortman=`sed -ne 's/=item B<-\([^>,-]\)[^>]*>.*$$/\1/p' doc/man/botch-$${man}.pod | sort`; \
			if [ "$$shortopts" != "$$shortman" ]; then \
				echo "\`./$$pkg --help\` and man page ./$$man disagree:"; \
				echo "$$shortopts != $$shortman"; \
				exit 1; \
			fi; \
			if [ "$$longopts" != "$$longman" ]; then \
				echo "\`./$$pkg --help\` and man page ./$$man disagree:"; \
				echo "$$longopts != $$longman"; \
				exit 1; \
			fi; \
		done

.PHONY: test-python
test-python:
	# FIXME: add more tests
	#./tests.py
	#OCAMLPATH=$(BUILD) ocamlbuild -classic-display -use-ocamlfind $(CFLAGS) tests.$(OCAML_BEST)
	#./tests.$(OCAML_BEST)
	pyflakes3 tools/*.py
	# E402 is triggered by the sys.path.append() statement in front of import of utils
	# see https://github.com/PyCQA/pep8/issues/264
	pep8 --ignore=E402 tools/*.py

.PHONY: test
test: test-python test-man test-default test-selfcontained test-cross

.PHONY: install
install: all
	$(MAKE) -C doc/wiki install
	mkdir -p $(DESTDIR)$(bindir)
	for octool in $(BIN_OCTOOLS); do \
		cp _build/$$octool.$(OCAML_BEST) $(DESTDIR)$(bindir)/botch-$$octool; \
	done
	for shtool in $(BIN_SHTOOLS); do \
		cp tools/$$shtool.sh $(DESTDIR)$(bindir)/botch-$$shtool; \
	done
	for pytool in $(BIN_PYTOOLS); do \
		cp tools/$$pytool.py $(DESTDIR)$(bindir)/botch-$$pytool; \
	done
	mkdir -p $(DESTDIR)$(datadir)
	cp -r droppable $(DESTDIR)$(datadir)
	cp tools/util.py $(DESTDIR)$(datadir)
	cp tools/debarch.py $(DESTDIR)$(datadir)

.PHONY: clean
clean:
	ocamlbuild -clean
	rm -f datatypes_j.ml datatypes_j.mli datatypes.ml datatypes.mli datatypes_t.ml datatypes_t.mli
	rm -f tools/*.pyc
	rm -f *.native *.d.byte
	rm -f doc/man/*.1
	$(MAKE) -C doc/wiki clean

.PHONY: distclean
distclean: clean
	rm -rf tools/__pycache__ tmp out

.PHONY: doseclean
doseclean:
	(cd dose && ocamlbuild -clean)

.PHONY: tarball
tarball:
	$(eval tmpdir := $(shell mktemp --directory))
	git archive --worktree-attributes --prefix=botch-$(VERSION)/ -o $(tmpdir)/botch-$(VERSION).tar HEAD
	git -C doc/wiki archive --worktree-attributes --prefix=botch-$(VERSION)/doc/wiki/ -o $(tmpdir)/botch-doc.tar HEAD
	git -C tests archive --worktree-attributes --prefix=botch-$(VERSION)/tests/ -o $(tmpdir)/botch-tests.tar HEAD
	# tar --concatenate seems to only take two files as input so we can
	# neither replace the temporary files with a pipe nor combine both of
	# the below commands into one... :(
	tar --concatenate -f $(tmpdir)/botch-$(VERSION).tar $(tmpdir)/botch-doc.tar
	tar --concatenate -f $(tmpdir)/botch-$(VERSION).tar $(tmpdir)/botch-tests.tar
	# testing shows that in this case, -9e compresses better than -9
	xz --verbose -c9e $(tmpdir)/botch-$(VERSION).tar > ../botch-$(VERSION).tar.xz
	rm -rf $(tmpdir)

.PHONY: upload
upload: tarball
	gpg -u 8FBD83E1 --armor --detach-sig ../botch-$(VERSION).tar.xz
	scp ../botch-$(VERSION).tar.xz.asc ../botch-$(VERSION).tar.xz mmfn:/var/www/botch/
	ssh mmfn tree -s --noreport -P "botch-*.tar.xz*" -o /var/www/botch/index.html -v -L 1 -H "." -T "botch-releases" /var/www/botch/
