Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sage
Path: blob/develop/build/make/Makefile.in
4052 views
# Makefile template for Sage packages: This Makefile is filled by the
# ./configure script with information all of Sage's dependent packages (SPKGs),
# including their names, their current versions, their dependencies, and some
# classifications according to their installation priority ("standard",
# "optional") and installation method ("normal", "pip", "script").
#
# Finally, install and clean rules for each package are generated from the
# templates at the end of this file.  Because the templates may slightly
# obscure the substance of the actual rules, this file can be debugged by
# running:
#
#     $ make -f build/make/Makefile -n DEBUG_RULES=1
#
# This will not actually run any rules (the -n flag) but will print all the
# rules generated from the templates.

# Always use bash for make rules
SHELL = @SHELL@

ifndef DEBUG_RULES
# Check a variable that is only set in build/make/install, but not in sage-env, for example
ifndef SAGE_PKGCONFIG
# Set by build/bin/sage-sdist, which invokes the Makefile directly in
# order to download upstream packages for distribution.
ifndef SAGE_SPKG_COPY_UPSTREAM
$(error This Makefile needs to be invoked by build/make/install)
endif
endif
endif

# Directory to keep track of which packages are installed - relative to installation prefix
SPKG_INST_RELDIR = var/lib/sage/installed

# Aliases for mutually exclusive standard packages selected at configure time
TOOLCHAIN = @SAGE_TOOLCHAIN@
PYTHON = python3
MP_LIBRARY = gmp
BLAS = openblas

# pkgconfig files generated/installed at build time
PCFILES = @SAGE_SYSTEM_FACADE_PC_FILES@

LN = ln
SED = sed

# In recursive invocations of make, remove "-jNUMJOBS" options that may
# be in $(MAKE) when users follow the recommendations in our manuals.
# We also get rid of excessive "Entering directory" messages.
MAKE_REC = $(MAKE:-j%=) --no-print-directory

# We need to be able to override this to support ./sage -i -c PKG
SAGE_SPKG = sage-spkg

# These are added to SAGE_SPKG in the call
SAGE_SPKG_OPTIONS = @SAGE_SPKG_OPTIONS@

# Where the Sage distribution installs documentation.
# set to empty if --disable-doc is used
SAGE_DOCS = @SAGE_DOCS@
SAGE_DOCS_DISABLED_MESSAGE = This Sage build is configured with "configure --disable-doc", so building the documentation will not work.

# Where the Sage distribution installs Python packages.
# This can be overridden by 'make SAGE_VENV=/some/venv'.
SAGE_VENV = @SAGE_VENV@

# Generate/install sage-specific .pc files.
# see build/pkgs/gsl/spkg-configure.m4
$(SAGE_PKGCONFIG)/gsl.pc:
	-rm -f $@
	@SAGE_GSL_PC_COMMAND@

# see build/pkgs/openblas/spkg-configure.m4
$(SAGE_PKGCONFIG)/openblas.pc $(SAGE_PKGCONFIG)/blas.pc $(SAGE_PKGCONFIG)/cblas.pc $(SAGE_PKGCONFIG)/lapack.pc:
	-rm -f $@
	@SAGE_OPENBLAS_PC_COMMAND@

# Files to track installation of packages
BUILT_PACKAGES = @SAGE_BUILT_PACKAGES@
DUMMY_PACKAGES = @SAGE_DUMMY_PACKAGES@

# Set to the path to Sage's GCC (if GCC is installed) to force rebuilds
# of packages if GCC changed.
# See m4/sage_spkg_collect.m4 and https://github.com/sagemath/sage/issues/24703
GCC_DEP = @SAGE_GCC_DEP@

# Versions of all the packages, in the format
#
# vers_<pkgname> = <pkgvers>

@SAGE_PACKAGE_VERSIONS@

# Dependencies for all packages, in the format
#
# deps_<pkgname> = <dep1> <dep2> etc...

@SAGE_PACKAGE_DEPENDENCIES@

# Installation trees for all packages, in the format:
#
# - for a non-Python package:
#
#   trees_<pkgname1> = SAGE_LOCAL
#
# - for a Python package:
#
#   trees_<pkgname2> = SAGE_VENV

@SAGE_PACKAGE_TREES@

# All standard/optional/experimental installed packages (triggers the auto-update)
OPTIONAL_INSTALLED_PACKAGES = @SAGE_OPTIONAL_INSTALLED_PACKAGES@
INSTALLED_PACKAGES = $(OPTIONAL_INSTALLED_PACKAGES)
INSTALLED_PACKAGE_INSTS = \
    $(foreach pkgname,$(INSTALLED_PACKAGES),$(inst_$(pkgname)))

# All previously installed standard/optional/experimental packages that are to be uninstalled
OPTIONAL_UNINSTALLED_PACKAGES = @SAGE_OPTIONAL_UNINSTALLED_PACKAGES@
UNINSTALLED_PACKAGES = $(OPTIONAL_UNINSTALLED_PACKAGES)
UNINSTALLED_PACKAGES_UNINSTALLS = $(UNINSTALLED_PACKAGES:%=%-uninstall)

# All packages which should be downloaded
SDIST_PACKAGES = @SAGE_SDIST_PACKAGES@

# Packages that use the 'normal' build rules
NORMAL_PACKAGES = @SAGE_NORMAL_PACKAGES@

# Packages that use the 'pip' package build rules
PIP_PACKAGES = @SAGE_PIP_PACKAGES@

# Packages that use the 'script' package build rules
SCRIPT_PACKAGES = @SAGE_SCRIPT_PACKAGES@

# Packages for which we build platform-independent wheels for PyPI
PYPI_NOARCH_WHEEL_PACKAGES =			\
	sage_setup				\
	sagemath_environment			\

# Packages for which we build wheels for PyPI
PYPI_WHEEL_PACKAGES = $(PYPI_NOARCH_WHEEL_PACKAGES) \
	sagemath_objects			\
	sagemath_repl				\
	sagemath_categories			\
	sagemath_bliss 				\
	sagemath_mcqd 				\
	sagemath_tdlib				\
	sagemath_coxeter3 			\
	sagemath_sirocco			\
	sagemath_meataxe

# sage_docbuild is here, not in PYPI_WHEEL_PACKAGES, because it depends on sagelib
WHEEL_PACKAGES = $(PYPI_WHEEL_PACKAGES)		\
	sage_conf				\
	sagelib					\
	sage_docbuild

# Packages for which build sdists for PyPI
PYPI_SDIST_PACKAGES = $(WHEEL_PACKAGES)

# Generate the actual inst_<pkgname> variables; for each package that is
# actually built this generates a line like:
#
# inst_<pkgname> = $(INST)/<pkgname>-<pkgvers>
#
# And for 'dummy' package that are not actually built/installed (e.g. because
# configure determined we can use the package from the system):
#
# inst_<pkgname> = $(INST)/.dummy
#
# For example:
#
# inst_python3 = $(INST)/python3-$(vers_python3)
#
# inst_git = $(INST)/.dummy

$(foreach pkgname,$(BUILT_PACKAGES),\
	$(eval inst_$(pkgname) = $(foreach tree, $(trees_$(pkgname)), $(and $($(tree)), $($(tree))/$(SPKG_INST_RELDIR)/$(pkgname)-$(vers_$(pkgname))))))
$(foreach pkgname,$(DUMMY_PACKAGES),\
	$(eval inst_$(pkgname) = $(SAGE_LOCAL)/$(SPKG_INST_RELDIR)/.dummy))

# Override this for pip packages, for which we do not keep an installation record
# in addition to what pip is already doing.
$(foreach pkgname,$(PIP_PACKAGES),\
	$(eval inst_$(pkgname) = $(pkgname)))

# Dummy target for packages which are not installed
$(SAGE_LOCAL)/$(SPKG_INST_RELDIR)/.dummy:
	touch $@


# Filtered by installation tree
$(foreach tree,SAGE_LOCAL SAGE_VENV SAGE_DOCS, \
    $(eval $(tree)_INSTALLED_PACKAGE_INSTS = \
               $(foreach pkgname,$(INSTALLED_PACKAGES), \
                         $(if $(findstring $(tree),$(trees_$(pkgname))), \
                              $(inst_$(pkgname))))) \
    $(eval $(tree)_UNINSTALLED_PACKAGE_UNINSTALLS = \
               $(foreach pkgname,$(INSTALLED_PACKAGES), \
                         $(if $(findstring $(tree),$(trees_$(pkgname))), \
                              $(inst_$(pkgname))))))


###############################################################################

# Silent rules
# https://www.gnu.org/software/automake/manual/html_node/Automake-Silent-Rules.html
ifeq ($(V), 0)
AM_V_at = @
else
AM_V_at =
endif

# Issue #33125: Handle make options -n, -t, -q
ifeq ($(strip $(foreach flag,n t q,$(findstring $(flag),$(filter-out --%,$(MAKEFLAGS))))),)
PLUS = +
else
PLUS =
endif

# List of targets that can be run (in addition to names of packages) using `sage -i` or `sage -f`
# These should generally have an associated -uninstall target for `sage -f` to
# work correctly
SAGE_I_TARGETS = sagelib doc

# Tell make not to look for files with these names:
.PHONY: all all-sage all-toolchain all-build all-sageruntime \
	all-start build-start toolchain toolchain-deps base-toolchain \
	pypi-sdists pypi-noarch-wheels pypi-wheels wheels \
	sagelib \
	doc doc-html doc-html-jsmath doc-html-mathjax doc-pdf \
	doc-uninstall \
	python3_venv _clean-broken-gcc

PYTHON_FOR_VENV = @PYTHON_FOR_VENV@
PYTHON_MINOR = @PYTHON_MINOR@
SAGE_VENV_FLAGS = @SAGE_VENV_FLAGS@

ifneq ($(PYTHON_FOR_VENV),)
# Special rule for making the Python virtualenv from the system Python (Python
# 3 only).  $(PYTHON) is set in Makefile to python3_venv.
# Thus $(inst_python3_venv) will an (order-only) dependency of every Python package.
#
# TODO: If we reconfigure to build our own Python after having used the system
# Python, files installed to create the virtualenv should be *removed*.  That
# could either be done here by the makefile, or in an spkg-preinst for python3
ifeq ($(PYTHON),python3)
PYTHON = python3_venv
endif
inst_python3_venv = $(SAGE_VENV)/$(SPKG_INST_RELDIR)/python3_venv-3.$(PYTHON_MINOR)-$(subst /,-,$(PYTHON_FOR_VENV))$(findstring --system-site-packages,$(SAGE_VENV_FLAGS))

$(SAGE_VENV)/$(SPKG_INST_RELDIR):
	mkdir -p "$@"

$(inst_python3_venv): | $(SAGE_VENV)/$(SPKG_INST_RELDIR)
	$(PYTHON_FOR_VENV) $(SAGE_ROOT)/build/bin/sage-venv $(SAGE_VENV_FLAGS) "$(SAGE_VENV)"
	rm -f "$(SAGE_VENV)/$(SPKG_INST_RELDIR)"/python3_venv-*
	touch "$@"
endif

# Build everything and start Sage.
# Note that we put the "doc" target first in the rule below because
# the doc build takes the most time and should be started as soon as
# possible.
all-start: toolchain-deps
	+$(MAKE_REC) all-sage-docs all-sage

# Build everything except the documentation
all-build: toolchain-deps
	+$(MAKE_REC) all-sage

# This used to do run "sage-starts" script, now it's just an alias
build-start: all-build

# The preliminary build phase: toolchain.
base-toolchain: _clean-broken-gcc
	+$(MAKE_REC) toolchain

# All targets except for the documentation
all-sage: \
		$(SAGE_LOCAL_INSTALLED_PACKAGE_INSTS) $(SAGE_LOCAL_UNINSTALLED_PACKAGES_UNINSTALLS) \
		$(SAGE_VENV_INSTALLED_PACKAGE_INSTS)  $(SAGE_VENV_UNINSTALLED_PACKAGES_UNINSTALLS)

# Same but filtered by installation trees:
all-build-local: toolchain-deps
	+$(MAKE_REC) all-sage-local

all-sage-local: $(SAGE_LOCAL_INSTALLED_PACKAGE_INSTS) $(SAGE_LOCAL_UNINSTALLED_PACKAGES_UNINSTALLS)

all-build-venv: toolchain-deps
	+$(MAKE_REC) all-sage-venv

all-sage-venv:  $(SAGE_VENV_INSTALLED_PACKAGE_INSTS)  $(SAGE_VENV_UNINSTALLED_PACKAGES_UNINSTALLS)

all-build-docs: toolchain-deps
	+$(MAKE_REC) all-sage-docs

all-sage-docs:  $(SAGE_DOCS_INSTALLED_PACKAGE_INSTS) $(SAGE_DOCS_UNINSTALLED_PACKAGES_UNINSTALLS)

# Download all packages which should be inside an sdist tarball (the -B
# option to make forces all targets to be built unconditionally)
download-for-sdist:
	+env SAGE_INSTALL_FETCH_ONLY=yes $(MAKE_REC) -B SAGERUNTIME= \
		$(SDIST_PACKAGES)

# TOOLCHAIN consists of dependencies determined by configure.
# These are built before anything else.
toolchain: $(foreach pkgname,$(TOOLCHAIN),$(inst_$(pkgname))) $(PCFILES)

# Build all packages that GCC links against serially, otherwise this
# leads to race conditions where some library which is used by GCC gets
# reinstalled. Since system GCCs might use Sage's libraries, we do this
# unconditionally. We still use the dependency checking from $(MAKE),
# so this will not trigger useless rebuilds.
# See #14168 and #14232.
#
# Note: This list consists of only the *runtime* dependencies of the toolchain.
TOOLCHAIN_DEPS = $(MP_LIBRARY) mpfr mpc
TOOLCHAIN_DEP_INSTS = \
	$(foreach pkgname,$(TOOLCHAIN_DEPS),$(inst_$(pkgname)))

toolchain-deps:
	+@for target in $(TOOLCHAIN_DEP_INSTS); do \
	    echo $(MAKE_REC) $$target; \
	    $(MAKE_REC) $$target; \
	done

all-toolchain: base-toolchain
	+$(MAKE_REC) toolchain-deps

# Shorthand for a list of packages sufficient for building and installing
# typical Python packages from source. Wheel packages only need pip.
PYTHON_TOOLCHAIN = setuptools pip setuptools_scm wheel flit_core hatchling python_build

# Issue #32056: Avoid installed setuptools leaking into the build of python3 by uninstalling it.
# It will have to be reinstalled anyway because of its dependency on $(PYTHON).
python3-SAGE_LOCAL-no-deps: setuptools-clean
python3-SAGE_VENV-no-deps: setuptools-clean

# Everything needed to start up Sage using "./sage".  Of course, not
# every part of Sage will work.  It does not include Maxima for example.
SAGERUNTIME = sagelib $(inst_ipython) $(inst_pexpect)

all-sageruntime: toolchain-deps
	+$(MAKE_REC) $(SAGERUNTIME)


###############################################################################
# Building the documentation
###############################################################################

# You can choose to have the built HTML version of the documentation link to
# the PDF version. To do so, you need to build both the HTML and PDF versions.
# To have the HTML version link to the PDF version, do
#
# $ ./sage --docbuild all html
# $ ./sage --docbuild all pdf
#
# For more information on the docbuild utility, do
#
# $ ./sage --docbuild -H

doc: doc-html

# All doc-building is delegated to the script packages
# sagemath_doc_html, sagemath_doc_pdf
doc-html: sagemath_doc_html

# 'doc-html-no-plot': build docs without building the graphics coming
# from the '.. plot' directive, in case you want to save a few
# megabytes of disk space. Run 'make doc-clean' first because the
# presence of graphics is cached in the built documentation.
doc-html-no-plot:
	(cd $(SAGE_ROOT) && $(MAKE) doc-clean)
	+$(MAKE_REC) SAGE_DOCBUILD_OPTS="$(SAGE_DOCBUILD_OPTS) --no-plot" doc-html

# Using mathjax is actually the only options, but we keep
# this target for backwards compatibility.
doc-html-mathjax: doc-html

# Also Keep target 'doc-html-jsmath' for backwards compatibility.
doc-html-jsmath: doc-html-mathjax

doc-pdf: sagemath_doc_pdf

doc-uninstall:
	rm -rf "$(SAGE_SHARE)/doc/sage"

# Special target for cleaning up a broken GCC install detected by configure
# This should check for the .clean-broken-gcc stamp, and if found clean
# everything up along with the stamp file itself.  This target is then run
# as a prerequisite to installing any other packages.
_clean-broken-gcc:
	@if [ -f "$(SAGE_ROOT)/build/make/.clean-broken-gcc" ]; then \
	   rm -f "$(SAGE_LOCAL)/bin/gcc"; \
	   rm -f "$(SAGE_LOCAL)/gcc-"*; \
	   rm -f "$(SAGE_LOCAL)/bin/g++"; \
	   rm -f "$(SAGE_SPKG_INST)/gcc-"*; \
	   rm -f "$(SAGE_ROOT)/build/make/.clean-broken-gcc"; \
	   echo "Cleaned up old broken GCC install"; \
	fi

# Implicit rules for uninstalling packages that no longer exist in the source tree.
%-SAGE_LOCAL-uninstall:
	@package=$@; \
	package=$${package%%-*}; \
	if [ -d '$(SAGE_LOCAL)' ]; then \
	    sage-spkg-uninstall $$package; \
	fi

%-SAGE_VENV-uninstall:
	@package=$@; \
	package=$${package%%-*}; \
	if [ -d '$(SAGE_VENV)' ]; then \
	    sage-spkg-uninstall $$package $(SAGE_VENV); \
	fi

%-uninstall:
	@package=$@; \
	package=$${package%%-*}; \
	$(MAKE) $$package-SAGE_LOCAL-uninstall $$package-SAGE_VENV-uninstall

%-installcheck:
	@stampfile=$@; \
	stampfile=$${stampfile%-installcheck}; \
	if [ -s $$stampfile ]; then \
	    echo >&2 "# Checking $$stampfile"; \
            tree=$${stampfile%%/$(SPKG_INST_RELDIR)/*}; \
	    package_with_version=$${stampfile##*/}; \
	    package=$${package_with_version%%-*}; \
	    if ! $(SAGE_VENV)/bin/python3 $(SAGE_ROOT)/build/bin/sage-spkg-installcheck --verbose $$package $$tree; then \
		case "$$tree" in \
		    "$(SAGE_LOCAL)") echo "    make $$package-SAGE_LOCAL-uninstall;";; \
		    "$(SAGE_VENV)")  echo "    make $$package-SAGE_VENV-uninstall;";; \
		    *)               echo "    ./sage --buildsh -c \"sage-spkg-uninstall $$package $$tree\";";; \
		esac; \
	    fi; \
	fi;

list-broken-packages: auditwheel_or_delocate
	@fix_broken_packages=$$($(MAKE) -s $(patsubst %,%-installcheck,$(wildcard $(SAGE_LOCAL)/$(SPKG_INST_RELDIR)/* $(SAGE_VENV)/$(SPKG_INST_RELDIR)/*))); \
	if [ -n "$$fix_broken_packages" ]; then \
	    echo >&2 ; \
	    echo >&2 "Uninstall broken packages by typing:"; \
	    echo >&2 ; \
	    echo >&2 "$$fix_broken_packages"; \
	fi

pypi-sdists: $(PYPI_SDIST_PACKAGES:%=%-sdist)
	@echo "Built sdists are in upstream/"

# Ensuring wheels are present, even for packages that may have been installed
# as editable. Until we have better uninstallation of script packages, we
# just remove the timestamps, which will lead to rebuilds of the packages.
pypi-noarch-wheels:
	for a in $(PYPI_NOARCH_WHEEL_PACKAGES); do \
	    rm -f $(SAGE_VENV)/var/lib/sage/installed/$$a-*; \
	done
	$(MAKE_REC) SAGE_EDITABLE=no SAGE_WHEELS=yes $(PYPI_NOARCH_WHEEL_PACKAGES)
	@echo "Built wheels are in venv/var/lib/sage/wheels/"

pypi-wheels:
	for a in $(PYPI_WHEEL_PACKAGES); do \
	    rm -f $(SAGE_VENV)/var/lib/sage/installed/$$a-*; \
	done
	$(MAKE_REC) SAGE_EDITABLE=no SAGE_WHEELS=yes $(PYPI_WHEEL_PACKAGES)
	@echo "Built wheels are in venv/var/lib/sage/wheels/"

wheels:
	for a in $(WHEEL_PACKAGES); do \
	    rm -f $(SAGE_VENV)/var/lib/sage/installed/$$a-*; \
	done
	$(MAKE_REC) SAGE_EDITABLE=no SAGE_WHEELS=yes $(WHEEL_PACKAGES)
	@echo "Built wheels are in venv/var/lib/sage/wheels/"

pypi-wheels-check: $(PYPI_WHEEL_PACKAGES:%=%-check)

#==============================================================================
# Setting SAGE_CHECK... variables
#==============================================================================
ifeq "$(origin SAGE_CHECK)" "undefined"
SAGE_CHECK := no
endif

define SET_SAGE_CHECK
$(eval SAGE_CHECK_$(1) := $(2))
endef
# Set defaults
$(foreach pkgname, $(NORMAL_PACKAGES) $(SCRIPT_PACKAGES),\
	$(eval $(call SET_SAGE_CHECK,$(pkgname),$(SAGE_CHECK))))

# Parsing the SAGE_CHECK_PACKAGES variable:
# - if this contains "!pkg", set SAGE_CHECK_pkg=no.
# - if this contains "?pkg", set SAGE_CHECK_pkg=warn.
# - if this contains "pkg",  set SAGE_CHECK_pkg=yes.
#
# We check this now and export SAGE_CHECK_pkg for
# dependencies and the Makefile rules.
#
# Since Python's self-tests seem to fail on all platforms, we disable
# its test suite by default.
# meson_python 0.10.0 fails on some platforms, so we reduce it to warnings.
# However, if SAGE_CHECK=warn, we do not do that.
SAGE_CHECK_PACKAGES_DEFAULT_yes := !python3,?meson_python
SAGE_CHECK_PACKAGES_DEFAULT_warn :=
SAGE_CHECK_PACKAGES_DEFAULT_no :=
comma := ,
ifeq "$(origin SAGE_CHECK_PACKAGES)" "undefined"
SAGE_CHECK_PACKAGES := $(SAGE_CHECK_PACKAGES_DEFAULT_$(SAGE_CHECK))
endif
SAGE_CHECK_PACKAGES_sep := $(subst $(comma), ,$(SAGE_CHECK_PACKAGES))
SAGE_CHECK_PACKAGES_sep := $(subst :, ,$(SAGE_CHECK_PACKAGES_sep))
$(foreach clause, $(SAGE_CHECK_PACKAGES_sep),					\
     $(if $(findstring !,$(clause)),						\
	  $(eval $(call SET_SAGE_CHECK,$(subst !,,$(clause)),no)),		\
	  $(if $(findstring ?,$(clause)),					\
	       $(eval $(call SET_SAGE_CHECK,$(subst ?,,$(clause)),warn)),	\
	       $(eval $(call SET_SAGE_CHECK,$(clause),yes)))))
debug-check:
	@echo $(foreach pkgname, $(NORMAL_PACKAGES) $(SCRIPT_PACKAGES), SAGE_CHECK_$(pkgname) = $(SAGE_CHECK_$(pkgname)))


#==============================================================================
# Rules generated from pkgs/<package>/dependencies files
#==============================================================================

# Define a function for generating the list of a package's dependencies
# as $(inst_<pkgname>) variables.  For example, takes:
#
#     deps_cysignals = python3 cython pari | pip
#
# to:
#
#     $(inst_python3) $(inst_cython) $(inst_pari) | $(inst_pip)
#
# If some value in the dependencies list is not a package name (e.g. it is
# the name of some arbitrary file, or it is the '|' symbol) then it is just
# used verbatim.
#
# As a special case, also adds a special variable GCC_DEP for all packages
# except for gcc itself.  See the definition of GCC_DEP above
#
# Positional arguments:
#     $(1): package name
pkg_deps = \
	$(if $(filter gcc,$(1)),,$$(GCC_DEP))\
	$(foreach dep,$(deps_$(1)),\
        $(if $(value inst_$(dep)),$$(inst_$(dep)),$(dep)))

# ============================= normal packages ==============================
# Generate build rules for 'normal' packages; this template is used to generate
# rules in the form:
#
# $(INST)/<pkgname>-<pkgvers>: <dependencies>
#     $(MAKE) $(1)-no-deps
#
# <pkgname>: $(INST)/<pkgname>-<pkgvers>
#
# <pkgname>-build-deps: <dependencies>
#
# <pkgname>-no-deps:
#     +$(AM_V_at)sage-logger -p '$(SAGE_SPKG) <pkgname>-<pkgvers>' '$(SAGE_LOGS)/<pkgname>-<pkgvers>.log'
#
# <pkgname>-uninstall:
#     sage-spkg-uninstall <pkgname> '$(SAGE_LOCAL)'
#
# So <pkgname>-build-deps installs just the dependencies, while
# <pkgname>-no-deps tries to install the package without its
# dependencies. This is currently used in SAGE_SRC/bin/sage when
# running 'sage -b' to build the Sage library.
#
# For example, for python3 this will expand to:
#
# $(INST)/python3-3.7.3: $(inst_readline) $(inst_sqlite) $(inst_libpng) $(inst_xz) $(inst_libffi)
#     +$(AM_V_at)sage-logger -p '$(SAGE_SPKG) python3-3.7.3' '$(SAGE_LOGS)/python3-3.7.3.log'
#
# python3: $(INST)/python3-3.7.3
#
# python3-uninstall:
#     sage-spkg-uninstall python3 '$(SAGE_LOCAL)'
#
# Note: In these rules the $(INST)/<pkgname>-<pkgvers> target is used
# explicitly, rather than expanding the $(inst_<pkgname>) variable, since
# it may expand to $(INST)/.dummy for packages that were not configured
# for installation by default.  However, we wish to be able to manually
# install those packages later.
#
# For packages listed in $(TOOLCHAIN_DEPS) we also pass --keep-existing to
# sage-spkg, and --keep-files to sage-spkg-uninstall since those packages can
# have a recursive self-dependency, and should not be deleted while upgrading.
# See Issue #25857

# Positional arguments:
#     $(1): package name
#     $(2): package version
#     $(3): package dependencies
#     $(4): package tree variable

define NORMAL_PACKAGE_templ ##########################################

$(1)-build-deps: $(3)

$$($(4))/$(SPKG_INST_RELDIR)/$(1)-$(2): $(3)
	+$(MAKE_REC) $(1)-$(4)-no-deps

$(1): $$($(4))/$(SPKG_INST_RELDIR)/$(1)-$(2)

$(1)-ensure: $(inst_$(1))

$(1)-$(4)-no-deps:
	+@if [ -z '$$($(4))' ]; then \
	    echo "Error: The installation tree $(4) has been disabled" 2>&1; \
	    echo "$$($(4)_DISABLED_MESSAGE)" 2>&1; \
	    exit 1; \
	else \
	    sage-logger -p 'SAGE_CHECK=$$(SAGE_CHECK_$(1)) PATH=$$(SAGE_SRC)/bin:$$($(4))/bin:$$$$PATH $$(SAGE_SPKG) $$(SAGE_SPKG_OPTIONS) \
		$(if $(filter $(1),$(TOOLCHAIN_DEPS)),--keep-existing) \
		$(1)-$(2) $$($(4))' '$$(SAGE_LOGS)/$(1)-$(2).log'; \
	fi

$(1)-no-deps: $(1)-$(4)-no-deps

$(1)-$(4)-check:
	$(PLUS)@sage-logger -p 'PATH=$$(SAGE_SRC)/bin:$$($(4))/bin:$$$$PATH $$(SAGE_SPKG) --check-only $(1)-$(2) $$($(4))' '$$(SAGE_LOGS)/$(1)-$(2).log'

$(1)-check: $(1)-$(4)-check

$(1)-$(4)-uninstall:
	if [ -d '$$($(4))' ]; then \
	    sage-spkg-uninstall $(if $(filter $(1),$(TOOLCHAIN_DEPS)),--keep-files) \
		$(1) '$$($(4))'; \
	fi

$(1)-uninstall: $(1)-$(4)-uninstall

$(1)-clean: $(1)-uninstall

.PHONY: $(1) $(1)-$(4)-uninstall $(1)-uninstall $(1)-clean $(1)-build-deps $(1)-no-deps
endef #################################################################

$(foreach pkgname, $(NORMAL_PACKAGES),\
    $(foreach tree, $(trees_$(pkgname)), \
	$(eval $(call NORMAL_PACKAGE_templ,$(pkgname),$(vers_$(pkgname)),\
	                                   $(call pkg_deps,$(pkgname)),$(tree)))))

ifdef DEBUG_RULES
$(info # Rules for standard packages)
$(foreach pkgname, $(NORMAL_PACKAGES),\
    $(foreach tree, $(trees_$(pkgname)), \
	$(info $(call NORMAL_PACKAGE_templ,$(pkgname),$(vers_$(pkgname)),\
	                                   $(call pkg_deps,$(pkgname)),$(tree)))))
endif

# ================================ pip packages ===============================
# Generate build rules for 'pip' packages; this template is used to generate
# two rules in the form:
#
# <pkgname>: <dependencies>
#     $(AM_V_at)sage-logger -p 'sage --pip install ...' '$(SAGE_LOGS)/<pkgname>.log'
#
# <pkgname>-uninstall:
#     -sage --pip uninstall -y ...

# Positional arguments:
#     $(1): package name
#     $(2): package dependencies
define PIP_PACKAGE_templ
$(1)-build-deps: $(2)

$(1): $(2)
	+$(MAKE_REC) $(1)-no-deps

$(1)-ensure: $(inst_$(1))

$(1)-no-deps:
	$(AM_V_at)sage-logger -p 'sage --pip install -r "$$(SAGE_ROOT)/build/pkgs/$(1)/requirements.txt"' '$$(SAGE_LOGS)/$(1).log'

$(1)-uninstall:
	-sage --pip uninstall --isolated --yes --no-input -r '$$(SAGE_ROOT)/build/pkgs/$(1)/requirements.txt'

$(1)-clean: $(1)-uninstall

.PHONY: $(1) $(1)-uninstall $(1)-clean $(1)-build-deps $(1)-no-deps
endef

$(foreach pkgname,$(PIP_PACKAGES),\
	$(eval $(call PIP_PACKAGE_templ,$(pkgname),$(call pkg_deps,$(pkgname)))))

ifdef DEBUG_RULES
$(info # Rules for pip packages)
$(foreach pkgname,$(PIP_PACKAGES),\
	$(info $(call PIP_PACKAGE_templ,$(pkgname),$(call pkg_deps,$(pkgname)))))
endif

# ============================= script packages ==============================
# Generate build rules for 'script' packages; this template is used to generate
# three rules in the form:
#
# $(INST)/<pkgname>-<pkgvers>: <dependencies>
#     $(AM_V_at)cd '$SAGE_ROOT' && \\
#         . '$SAGE_ROOT/src/bin/sage-env-config' && \\
#         . '$SAGE_ROOT/src/bin/sage-env' && \\
#         . '$SAGE_ROOT/build/bin/sage-build-env-config' && \\
#         . '$SAGE_ROOT/build/bin/sage-build-env' && \\
#         sage-logger -p '$SAGE_ROOT/build/pkgs/<pkgname>/spkg-install' '$(SAGE_LOGS)/<pkgname>.log'
#
# <pkgname>: $(INST)/<pkgname>-<pkgvers>
#
# <pkgname>-uninstall:
#     -$(AM_V_at)cd '$SAGE_ROOT' && \\
#         . '$SAGE_ROOT/src/bin/sage-env-config' && \\
#         . '$SAGE_ROOT/src/bin/sage-env' && \\
#         . '$SAGE_ROOT/build/bin/sage-build-env-config' && \\
#         . '$SAGE_ROOT/build/bin/sage-build-env' && \\
#         '$SAGE_ROOT/build/pkgs/$PKG_NAME/spkg-uninstall'

# Positional arguments:
#     $(1): package name
#     $(2): package version
#     $(3): package dependencies
#     $(4): package tree variable

define SCRIPT_PACKAGE_templ
$(1)-build-deps: $(3)

$$($(4))/$(SPKG_INST_RELDIR)/$(1)-$(2): $(3)
	+$(MAKE_REC) $(1)-$(4)-no-deps

$(1): $$($(4))/$(SPKG_INST_RELDIR)/$(1)-$(2)

$(1)-ensure: $(inst_$(1))

$(1)-$(4)-no-deps:
	$(PLUS)@if [ -z '$$($(4))' ]; then \
	    echo "Error: The installation tree $(4) has been disabled" 2>&1; \
	    echo "$$($(4)_DISABLED_MESSAGE)" 2>&1; \
	    exit 1; \
	else \
	    rm -rf '$$($(4))/var/lib/sage/scripts/$(1)'; \
	    cd '$$(SAGE_ROOT)/build/pkgs/$(1)' && \
		. '$$(SAGE_ROOT)/src/bin/sage-src-env-config' && \
		. '$$(SAGE_ROOT)/src/bin/sage-env-config' && \
		. '$$(SAGE_ROOT)/src/bin/sage-env' && \
		. '$$(SAGE_ROOT)/build/bin/sage-build-env-config' && \
		. '$$(SAGE_ROOT)/build/bin/sage-build-env' && \
		SAGE_CHECK=$$(SAGE_CHECK_$(1)) \
		sage-logger -p 'SAGE_CHECK=$$(SAGE_CHECK_$(1)) PATH=$$(SAGE_SRC)/bin:$$($(4))/bin:$$$$PATH $$(SAGE_SPKG) $$(SAGE_SPKG_OPTIONS) \
		$(if $(filter $(1),$(TOOLCHAIN_DEPS)),--keep-existing) \
		$(1)-$(2) $$($(4))' '$$(SAGE_LOGS)/$(1)-$(2).log'; \
	fi

$(1)-no-deps: $(1)-$(4)-no-deps

$(1)-$(4)-check:
	$(PLUS)@sage-logger -p 'PATH=$$(SAGE_SRC)/bin:$$($(4))/bin:$$$$PATH $$(SAGE_SPKG) --check-only $(1)-$(2) $$($(4))' '$$(SAGE_LOGS)/$(1)-$(2).log'

$(1)-check: $(1)-$(4)-check

$(1)-$(4)-uninstall:
	if [ -d '$$($(4))' ]; then \
	    sage-spkg-uninstall $(if $(filter $(1),$(TOOLCHAIN_DEPS)),--keep-files) \
		$(1) '$$($(4))'; \
	fi

$(1)-uninstall: $(1)-$(4)-uninstall

$(1)-clean: $(1)-uninstall

$(1)-sdist: FORCE python_build sage_setup cython
	$(AM_V_at) cd '$$(SAGE_ROOT)' && \
		. '$$(SAGE_ROOT)/src/bin/sage-src-env-config' && \
		. '$$(SAGE_ROOT)/src/bin/sage-env-config' && \
		. '$$(SAGE_ROOT)/src/bin/sage-env' && \
		'$$(SAGE_ROOT)/build/pkgs/$(1)/spkg-src'

# Recursive tox invocation
# Setting SAGE_SPKG_WHEELS is for the benefit of sagelib's tox.ini
$(1)-tox-%: FORCE
	$(AM_V_at)cd '$$(SAGE_ROOT)/build/pkgs/$(1)/src' && \
		. '$$(SAGE_ROOT)/src/bin/sage-src-env-config' && \
		. '$$(SAGE_ROOT)/src/bin/sage-env-config' && \
		. '$$(SAGE_ROOT)/src/bin/sage-env' && \
		SAGE_SPKG_WHEELS=$$(SAGE_VENV)/var/lib/sage/wheels \
		tox -v -v -v -e $$*

.PHONY: $(1) $(1)-uninstall $(1)-clean $(1)-build-deps $(1)-no-deps $(1)-clean

endef

$(foreach pkgname,$(SCRIPT_PACKAGES),\
    $(foreach tree, $(trees_$(pkgname)), \
	$(eval $(call SCRIPT_PACKAGE_templ,$(pkgname),$(vers_$(pkgname)),$(call pkg_deps,$(pkgname)),$(tree)))))

ifdef DEBUG_RULES
$(info # Rules for script packages)
$(foreach pkgname,$(SCRIPT_PACKAGES),\
    $(foreach tree, $(trees_$(pkgname)), \
	$(info $(call SCRIPT_PACKAGE_templ,$(pkgname),$(vers_$(pkgname)),$(call pkg_deps,$(pkgname)),$(tree)))))
endif

# sagelib depends on this so that its install script is always executed
FORCE:

# Use this target to list common targets of this Makefile (in particular for
# installation with `sage -i`.
list:
	@for pkg in $(SAGE_I_TARGETS) $(NORMAL_PACKAGES) $(PIP_PACKAGES) $(SCRIPT_PACKAGES); do\
		echo $$pkg;\
	done


.PHONY: list