1## Copyright 2015-2016 Carnë Draug
2## Copyright 2015-2016 Oliver Heimlich
3## Copyright 2017 Julien Bect <jbect@users.sf.net>
4## Copyright 2017 Olaf Till <i7tiol@t-online.de>
5## Copyright 2019 John Donoghue <john.donoghue@ieee.org>
6##
7## Copying and distribution of this file, with or without modification,
8## are permitted in any medium without royalty provided the copyright
9## notice and this notice are preserved.  This file is offered as-is,
10## without any warranty.
11
12TOPDIR := $(shell pwd)
13
14## Some basic tools (can be overriden using environment variables)
15SED ?= sed
16TAR ?= tar
17GREP ?= grep
18CUT ?= cut
19TR ?= tr
20TEXI2PDF  ?= texi2pdf -q
21
22## Note the use of ':=' (immediate set) and not just '=' (lazy set).
23## http://stackoverflow.com/a/448939/1609556
24package := $(shell $(GREP) "^Name: " DESCRIPTION | $(CUT) -f2 -d" " | \
25$(TR) '[:upper:]' '[:lower:]')
26version := $(shell $(GREP) "^Version: " DESCRIPTION | $(CUT) -f2 -d" ")
27
28## These are the paths that will be created for the releases.
29target_dir       := target
30release_dir      := $(target_dir)/$(package)-$(version)
31release_tarball  := $(target_dir)/$(package)-$(version).tar.gz
32html_dir         := $(target_dir)/$(package)-html
33html_tarball     := $(target_dir)/$(package)-html.tar.gz
34## Using $(realpath ...) avoids problems with symlinks due to bug
35## #50994 in Octaves scripts/pkg/private/install.m.  But at least the
36## release directory above is needed in the relative form, for 'git
37## archive --format=tar --prefix=$(release_dir).
38real_target_dir  := $(realpath .)/$(target_dir)
39installation_dir := $(real_target_dir)/.installation
40package_list     := $(installation_dir)/.octave_packages
41install_stamp    := $(installation_dir)/.install_stamp
42
43## These can be set by environment variables which allow to easily
44## test with different Octave versions.
45ifndef OCTAVE
46OCTAVE := octave
47endif
48OCTAVE := $(OCTAVE) --no-gui --silent --norc
49MKOCTFILE ?= mkoctfile
50
51## Command used to set permissions before creating tarballs
52FIX_PERMISSIONS ?= chmod -R a+rX,u+w,go-w,ug-s
53
54HG           := hg
55HG_CMD        = $(HG) --config alias.$(1)=$(1) --config defaults.$(1)= $(1)
56HG_ID        := $(shell $(call HG_CMD,identify) --id | sed -e 's/+//' )
57HG_TIMESTAMP := $(firstword $(shell $(call HG_CMD,log) --rev $(HG_ID) --template '{date|hgdate}'))
58
59
60## Detect which VCS is used
61vcs := $(if $(wildcard .hg),hg,$(if $(wildcard .git),git,unknown))
62ifeq ($(vcs),hg)
63release_dir_dep := .hg/dirstate
64endif
65ifeq ($(vcs),git)
66release_dir_dep := .git/index
67endif
68
69TAR_REPRODUCIBLE_OPTIONS := --sort=name --mtime="@$(HG_TIMESTAMP)" --owner=0 --group=0 --numeric-owner
70TAR_OPTIONS  := --format=ustar $(TAR_REPRODUCIBLE_OPTIONS)
71
72## .PHONY indicates targets that are not filenames
73## (https://www.gnu.org/software/make/manual/html_node/Phony-Targets.html)
74.PHONY: help
75
76## make will display the command before runnning them.  Use @command
77## to not display it (makes specially sense for echo).
78help:
79	@echo "Targets:"
80	@echo "   dist    - Create $(release_tarball) for release."
81	@echo "   html    - Create $(html_tarball) for release."
82	@echo "   release - Create both of the above and show md5sums."
83	@echo "   install - Install the package in $(installation_dir), where it is not visible in a normal Octave session."
84	@echo "   check   - Execute package tests."
85	@echo "   doctest - Test the help texts with the doctest package."
86	@echo "   run     - Run Octave with the package installed in $(installation_dir) in the path."
87	@echo "   clean   - Remove everything made with this Makefile."
88
89
90##
91## Recipes for release tarballs (package + html)
92##
93
94.PHONY: release dist html clean-tarballs clean-unpacked-release
95
96## To make a release, build the distribution and html tarballs.
97release: dist html
98	md5sum $(release_tarball) $(html_tarball)
99	@echo "Upload @ https://sourceforge.net/p/octave/package-releases/new/"
100	@echo "    and note the changeset the release corresponds to"
101
102## dist and html targets are only PHONY/alias targets to the release
103## and html tarballs.
104dist: $(release_tarball)
105html: $(html_tarball)
106
107## An implicit rule with a recipe to build the tarballs correctly.
108%.tar.gz: %
109	$(TAR) -cf - $(TAR_OPTIONS) -C "$(target_dir)/" "$(notdir $<)" | gzip -9n > "$@"
110
111clean-tarballs:
112	@echo "## Cleaning release tarballs (package + html)..."
113	-$(RM) $(release_tarball) $(html_tarball)
114	@echo
115
116## Create the unpacked package.
117##
118## Notes:
119##    * having ".hg/dirstate" (or ".git/index") as a prerequesite means it is
120##      only rebuilt if we are at a different commit.
121##    * the variable RM usually defaults to "rm -f"
122##    * having this recipe separate from the one that makes the tarball
123##      makes it easy to have packages in alternative formats (such as zip)
124##    * note that if a commands needs to be run in a specific directory,
125##      the command to "cd" needs to be on the same line.  Each line restores
126##      the original working directory.
127$(release_dir): $(release_dir_dep)
128	-$(RM) -r "$@"
129ifeq (${vcs},hg)
130	hg archive --exclude ".hg*" --type files "$@"
131endif
132ifeq (${vcs},git)
133	git archive --format=tar --prefix="$@/" HEAD | $(TAR) -x
134	$(RM) "$@/.gitignore"
135endif
136## Don't fall back to run the supposed necessary contents of
137## 'bootstrap' here. Users are better off if they provide
138## 'bootstrap'. Administrators, checking build reproducibility, can
139## put in the missing 'bootstrap' file if they feel they know its
140## necessary contents.
141ifneq (,$(wildcard src/bootstrap))
142	cd "$@/src" && ./bootstrap && $(RM) -r "autom4te.cache"
143endif
144## Uncomment this if your src/Makefile.in has these targets for
145## pre-building something for the release (e.g. documentation).
146#	cd "$@/src" && ./configure && $(MAKE) prebuild && \
147#	  $(MAKE) clean && $(RM) Makefile
148##
149	$(MAKE) -C "$@" docs
150#	cd "$@" && $(MAKE) tests
151	# remove dev stuff
152	cd "$@" && $(RM) -rf "devel" && $(RM) -f doc/mkfuncdocs.py
153	${FIX_PERMISSIONS} "$@"
154
155.PHONY: docs
156docs: doc/$(package).pdf
157
158clean-docs:
159	$(RM) -f doc/$(package).info
160	$(RM) -f doc/$(package).pdf
161	$(RM) -f doc/functions.texi
162
163doc/$(package).pdf: doc/$(package).texi doc/functions.texi
164	cd doc && SOURCE_DATE_EPOCH=$(HG_TIMESTAMP) $(TEXI2PDF) $(package).texi
165	# remove temp files
166	cd doc && $(RM) -f $(package).aux  $(package).cp  $(package).cps  $(package).fn  $(package).fns  $(package).log  $(package).toc
167
168doc/functions.texi:
169	cd doc && ./mkfuncdocs.py --src-dir=../inst/ ../INDEX | $(SED) 's/@seealso/@xseealso/g' > functions.texi
170
171
172run_in_place = $(OCTAVE) --eval ' pkg ("local_list", "$(package_list)"); ' \
173                         --eval ' pkg ("load", "$(package)"); '
174
175#html_options = --eval 'options = get_html_options ("octave-forge");'
176## Uncomment this for package documentation.
177html_options = --eval 'options = get_html_options ("octave-forge");' \
178               --eval 'options.package_doc = "$(package).texi";'
179$(html_dir): $(install_stamp)
180	$(RM) -r "$@";
181	$(run_in_place)                    \
182        --eval ' pkg load generate_html; ' \
183	$(html_options)                    \
184        --eval ' generate_package_html ("$(package)", "$@", options); ';
185	$(FIX_PERMISSIONS) "$@";
186
187clean-unpacked-release:
188	@echo "## Cleaning unpacked release tarballs (package + html)..."
189	-$(RM) -r $(release_dir) $(html_dir)
190	@echo
191
192##
193## Recipes for installing the package.
194##
195
196.PHONY: install clean-install
197
198octave_install_commands = \
199' llist_path = pkg ("local_list"); \
200  mkdir ("$(installation_dir)"); \
201  load (llist_path); \
202  local_packages(cellfun (@ (x) strcmp ("$(package)", x.name), local_packages)) = []; \
203  save ("$(package_list)", "local_packages"); \
204  pkg ("local_list", "$(package_list)"); \
205  pkg ("prefix", "$(installation_dir)", "$(installation_dir)"); \
206  pkg ("install", "-local", "-verbose", "$(release_tarball)"); '
207
208## Install unconditionally. Maybe useful for testing installation with
209## different versions of Octave.
210install: $(release_tarball)
211	@echo "Installing package under $(installation_dir) ..."
212	$(OCTAVE) --eval $(octave_install_commands)
213	touch $(install_stamp)
214
215## Install only if installation (under target/...) is not current.
216$(install_stamp): $(release_tarball)
217	@echo "Installing package under $(installation_dir) ..."
218	$(OCTAVE) --eval $(octave_install_commands)
219	touch $(install_stamp)
220
221clean-install:
222	@echo "## Cleaning installation under $(installation_dir) ..."
223	-$(RM) -r $(installation_dir)
224	@echo
225
226
227##
228## Recipes for testing purposes
229##
230
231.PHONY: run doctest check
232
233## Start an Octave session with the package directories on the path for
234## interactice test of development sources.
235run: $(install_stamp)
236	$(run_in_place) --persist
237
238## Test example blocks in the documentation.  Needs doctest package
239##  https://octave.sourceforge.io/doctest/index.html
240doctest: $(install_stamp)
241	$(run_in_place) --eval 'pkg load doctest;'                                                          \
242	  --eval "targets = pkg('list', '$(package)'){1}.dir;" \
243	  --eval "doctest (targets);"
244
245## Test package.
246octave_test_commands = \
247' pkgs = pkg("list", "$(package)"); \
248  dirs = {pkgs{1}.dir}; \
249  __run_test_suite__ (dirs, {}); '
250## the following works, too, but provides no overall summary output as
251## __run_test_suite__ does:
252##
253##    else cellfun (@runtests, horzcat (cellfun (@ (dir) ostrsplit (([~, dirs] = system (sprintf ("find %s -type d", dir))), "\n\r", true), dirs, "UniformOutput", false){:})); endif '
254check: $(install_stamp)
255	$(run_in_place) --eval $(octave_test_commands)
256
257
258##
259## CLEAN
260##
261
262.PHONY: clean
263
264clean: clean-tarballs clean-unpacked-release clean-install clean-docs
265	test -e inst/test && rmdir inst/test || true
266	test -e fntests.log && rm -f fntests.log || true
267	@echo "## Removing target directory (if empty)..."
268	test -e $(target_dir) && rmdir $(target_dir) || true
269	@echo
270	@echo "## Cleaning done"
271	@echo
272
273.PHONY: tests
274
275CC_TST_SOURCES := $(shell $(GREP) --files-with-matches '^%!' src/*.cc)
276TST_SOURCES := $(patsubst src/%.cc,inst/test/%.cc-tst,$(CC_TST_SOURCES))
277
278inst/test:
279	@mkdir -p "$@"
280
281$(TST_SOURCES): inst/test/%.cc-tst: src/%.cc | inst/test
282	@echo "Extracting tests from $< ..."
283	@$(RM) -f "$@" "$@-t"
284	@(      echo "## Generated from $<"; \
285		$(GREP) '^%!' "$<") > "$@"
286
287tests: $(TST_SOURCES)
288