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