Skip to content

Commit

Permalink
devenv: Only define targets when tool is not available
Browse files Browse the repository at this point in the history
An issue with the previous approach is that if the target exists and
happened to be older than the tool used to bootstrap it, `make` would
try to re-install it!

Instead when falling back to an existing install we don't define a
target at all, sidestepping the issue.

This commit also introduces some `define` rules allowing us to write a
template rule once and initialize it multiple times with different
parameters - useful for defining multiple versions of Python!
  • Loading branch information
alcarney committed May 3, 2024
1 parent 62bd518 commit 76aab71
Showing 1 changed file with 72 additions and 52 deletions.
124 changes: 72 additions & 52 deletions .devcontainer/tools.mk
Original file line number Diff line number Diff line change
@@ -1,91 +1,105 @@
BIN ?= $(HOME)/.local/bin

PY38 ?= $(or $(shell command -v python3.8), $(BIN)/python3.8)
PY39 ?= $(or $(shell command -v python3.9), $(BIN)/python3.9)
PY310 ?= $(or $(shell command -v python3.10), $(BIN)/python3.10)
PY311 ?= $(or $(shell command -v python3.11), $(BIN)/python3.11)
PY312 ?= $(or $(shell command -v python3.12), $(BIN)/python3.12)
HATCH_VERSION = 1.10.0
NODE_VERSION := 18.20.2

# Set a default python
PY ?= $(or $(shell command -v python), $(BIN)/python)
PY_INTERPRETERS := $(PY) $(PY38) $(PY39) $(PY310) $(PY311) $(PY312)
# The versions of Python we support
PYXX_versions := 3.8 3.9 3.10 3.11 3.12
PY_INTERPRETERS =

# Hatch is not only used for building packages, but bootstrapping any missing
# interpreters
HATCH ?= $(or $(shell command -v hatch), $(BIN)/hatch)
HATCH_VERSION = 1.9.7
PRE_COMMIT ?= $(or $(shell command -v pre-commit), $(BIN)/pre-commit)
TOWNCRIER ?= $(or $(shell command -v towncrier), $(BIN)/towncrier)

PY_TOOLS := $(HATCH) $(PRE_COMMIT) $(TOWNCRIER)

# Node JS
NPM ?= $(or $(shell command -v npm), $(BIN)/npm)
NODE ?= $(or $(shell command -v node), $(BIN)/node)
NODE_VERSION := 18.20.2
NODE_DIR := $(HOME)/.local/node

tools: $(PY_INTERPRETERS) $(PY_TOOLS) $(NPM) $(NODE)
for prog in $^ ; do echo -n "$${prog}\t" ; $${prog} --version; done

$(HATCH):
curl -L --output /tmp/hatch.tar.gz https://github.com/pypa/hatch/releases/download/hatch-v$(HATCH_VERSION)/hatch-$(HATCH_VERSION)-x86_64-unknown-linux-gnu.tar.gz
tar -xf /tmp/hatch.tar.gz -C /tmp
rm /tmp/hatch.tar.gz

[ -d $(BIN) ] || mkdir -p $(BIN)
test -d $(BIN) || mkdir -p $(BIN)
mv /tmp/hatch-$(HATCH_VERSION)-x86_64-unknown-linux-gnu $(HATCH)

$@ --version
touch $@

$(PY38): $(HATCH)
$(HATCH) python find 3.8 || $(HATCH) python install 3.8
ln -s $$($(HATCH) python find 3.8) $@
# This effectively defines a function `PYXX` that takes a Python version number
# (e.g. 3.8) and expands it out into a common block of code that will ensure a
# verison of that interpreter is available to be used.
#
# The is perhaps a bit more complicated than I'd like, but it should mean that
# the project's makefiles are useful both inside and outside of a devcontainer.
#
# `PYXX` has the following behavior:
# - If possible, it will reuse the user's existing version of Python
# i.e. $(shell command -v pythonX.X)
#
# - The user may force a specific interpreter to be used by setting the
# variable when running make e.g. PYXX=/path/to/pythonX.X make ...
#
# - Otherwise, `make` will use `$(HATCH)` to install the given version of
# Python under `$(BIN)`
#
# See: https://www.gnu.org/software/make/manual/html_node/Eval-Function.html
define PYXX =

$@ --version
touch $@
PY$(subst .,,$1) ?= $$(shell command -v python$1)

$(PY39): $(HATCH)
$(HATCH) python find 3.9 || $(HATCH) python install 3.9
ln -s $$($(HATCH) python find 3.9) $@
ifeq ($$(strip $$(PY$(subst .,,$1))),)

$@ --version
touch $@
PY$(subst .,,$1) := $$(BIN)/python$1

$(PY310): $(HATCH)
$(HATCH) python find 3.10 || $(HATCH) python install 3.10
ln -s $$($(HATCH) python find 3.10) $@
$$(PY$(subst .,,$1)): $$(HATCH)
$$(HATCH) python find $1 || $$(HATCH) python install $1
ln -s $$$$($$(HATCH) python find $1) $$@

$@ --version
touch $@
$$@ --version
touch $$@

$(PY311): $(HATCH)
$(HATCH) python find 3.11 || $(HATCH) python install 3.11
ln -s $$($(HATCH) python find 3.11) $@
endif

$@ --version
touch $@
PY_INTERPRETERS += $$(PY$(subst .,,$1))
endef

$(PY312): $(HATCH)
$(HATCH) python find 3.12 || $(HATCH) python install 3.12
ln -s $$($(HATCH) python find 3.12) $@
# Uncomment the following line to see what this expands into.
#$(foreach version,$(PYXX_versions),$(info $(call PYXX,$(version))))
$(foreach version,$(PYXX_versions),$(eval $(call PYXX,$(version))))

$@ --version
touch $@
# Set a default `python` command if there is not one already
PY ?= $(shell command -v python)

ifeq ($(strip $(PY)),)
PY := $(BIN)/python

$(PY): $(PY312)
ln -s $< $@
$@ --version
touch $@
endif

PY_INTERPRETERS += $(PY)
#$(info $(PY_INTERPRETERS))

PRE_COMMIT ?= $(shell command -v pre-commit)

ifeq ($(strip $(PRE_COMMIT)),)
PRE_COMMIT := $(BIN)/pre-commit

$(PRE_COMMIT): $(PY)
$(PY) -m pip install --user pre-commit
$@ --version
touch $@
endif

$(TOWNCRIER): $(PY)
$(PY) -m pip install --user towncrier
$@ --version
touch $@
PY_TOOLS := $(HATCH) $(PRE_COMMIT)

# Node JS
NPM ?= $(shell command -v npm)

ifeq ($(strip $(NPM)),)

NPM := $(BIN)/npm
NODE := $(BIN)/node
NODE_DIR := $(HOME)/.local/node

$(NPM):
curl -L --output /tmp/node.tar.xz https://nodejs.org/dist/v$(NODE_VERSION)/node-v$(NODE_VERSION)-linux-x64.tar.xz
Expand All @@ -101,3 +115,9 @@ $(NPM):

$(NODE) --version
PATH=$(BIN) $(NPM) --version

endif

# One command to bootstrap all tools and check their versions
tools: $(PY_INTERPRETERS) $(PY_TOOLS) $(NPM)
for prog in $^ ; do echo -n "$${prog}\t" ; PATH=$(BIN) $${prog} --version; done

0 comments on commit 76aab71

Please sign in to comment.