VERSION=0.17

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
define diff_tmp_out
	for f in tests/$(1)/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/$(1)/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/$(1)/tmp/$$f" "tmp/$$f" || exit 1;; \
		esac; \
	done
	for f in tests/$(1)/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/$(1)/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/$(1)/out/$$f" "out/$$f" || exit 1;; \
		esac; \
	done
endef

.PHONY: test-misc
test-misc: $(OCAML_BEST)
	rm -rf tmp out
	mkdir -p tmp out
	$(eval packages := tests/sid-amd64-packages-20140101T000000Z)
	$(eval sources := tests/sid-sources-20140101T000000Z)
	grep-dctrl --exact-match --field Package build-essential $(packages) \
		| ./tools/latest-version.py - - > tmp/build-essential
	./bin2src.$(OCAML_BEST) --deb-native-arch=amd64 tmp/build-essential $(sources) \
		> tmp/build-essential-src
	./create-graph.$(OCAML_BEST) --deb-native-arch=amd64 --bg $(sources) $(packages) tmp/build-essential-src \
		> tmp/selfcontained_repo.xml
	./tools/buildgraph2packages.py tmp/selfcontained_repo.xml $(packages) \
		> tmp/packages
	./bin2src.$(OCAML_BEST) --deb-native-arch=amd64 tmp/packages $(sources) \
		> tmp/sources
	# test botch-calcportsmetric
	./create-graph.$(OCAML_BEST) --deb-native-arch=amd64 tmp/packages tmp/sources --strongtype > tmp/strongbuildgraph.xml
	./buildgraph2srcgraph.$(OCAML_BEST) tmp/strongbuildgraph.xml --deb-native-arch=amd64 tmp/packages tmp/sources > tmp/strongsrcgraph.xml
	./create-graph.$(OCAML_BEST) --deb-native-arch=amd64 tmp/packages tmp/sources --closuretype > tmp/closurebuildgraph.xml
	./buildgraph2srcgraph.$(OCAML_BEST) tmp/closurebuildgraph.xml --deb-native-arch=amd64 tmp/packages tmp/sources > tmp/closuresrcgraph.xml
	./tools/calcportsmetric.py tmp/strongsrcgraph.xml tmp/closuresrcgraph.xml > out/importance_metric.txt
	# test botch-multiarch-interpreter-problem
	dose-ceve --deb-native-arch=amd64 -G pkg -T grml deb://tmp/packages debsrc://tmp/sources > tmp/ma_interpreter.pkggraph
	./tools/multiarch-interpreter-problem.py --packages tmp/packages tmp/ma_interpreter.pkggraph > out/ma_interpreter.txt
	# test y-u-b-d-transitive-essential
	./tools/y-u-b-d-transitive-essential.sh --debug --verbose --develop --output out amd64 tmp/packages tmp/sources acl
	# test botch-buildcheck-more-problems
	./buildcheck-more-problems.$(OCAML_BEST) --checkonly=aster,bustle --deb-native-arch=amd64 --explain --failures $(packages) $(sources) > out/buildcheck.yaml || [ $$? -eq 1 ]
	# test botch-distcheck-more-problems
	./distcheck-more-problems.$(OCAML_BEST) --checkonly=arista:amd64,chef:amd64 --deb-native-arch=amd64 --explain --failures deb://$(packages) > out/distcheck.yaml || [ $$? -eq 1 ]
	# test dose2html
	./tools/dose2html.py --srcsdir=out --wwwroot=out/ --packages=$(packages) out/distcheck.yaml out/distcheck.html
	./tools/dose2html.py --srcsdir=out --wwwroot=out/ --packages=$(packages) out/buildcheck.yaml out/buildcheck.html
	# verify results
	$(call diff_tmp_out,misc)

.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
	$(call diff_tmp_out,default)

.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
	$(call diff_tmp_out,selfcontained)

.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
	$(call diff_tmp_out,cross)

.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 test-misc

.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/
