-
Notifications
You must be signed in to change notification settings - Fork 184
/
Copy pathdeps.mk
284 lines (252 loc) · 9.44 KB
/
deps.mk
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
## Installed dependencies
#
# This framework will automatically install build-time dependencies as a
# prerequisite for build targets. This installation uses local directories
# (usually under lib/) to store all installed software.
#
# Some dependencies are defined in the framework and will always be installed.
# This includes xml2rfc and kramdown-rfc. Other dependencies are specific to a
# particular project and will be driven from files that are in the project
# repository.
#
# Currently, this supports three different package installation frameworks:
# * pip for python, specified in requirements.txt
# * gem for ruby, specified in Gemfile
# * npm for nodejs, specified in package.json
# Each system has its own format for specifying dependencies. What you need to
# know is that if you include any of the above files, you don't need to worry
# about ensuring that these tools are available when a build runs.
#
# This also works in CI runs, with caching, so your builds won't run too slowly.
# The prerequsites that are installed by default are installed globally in a CI
# docker image, which avoids an expensive installation step in CI. This makes
# CI runs slightly different than local runs.
#
## Configuration
#
# For python, if you have some extra tools, just add them to requirements.txt
# and they will be installed into a virtual environment.
#
# For ruby, listing tools in a `Gemfile` will ensure that files are installed.
# You should add `Gemfile.lock` to your .gitignore file if you do this.
#
# For nodejs, new dependencies can be added to `package.json`. Use `npm install
# -s <package>` to add files. You should add `package-lock.json` and
# `node_modules/` to your `.gitignore` file if you do this.
#
# Tools are added to the path, so you should have no problem running them.
#
## Using Tools
#
# Makefile rules can be written to use a new tool. Ensure that the variable
# `$(DEPS_FILES)` is a dependency of any target that relies on tools being
# available. For example, for a linter:
#
# lint:: example-lint
# .PHONY: example-lint
# example-lint: $(drafts_xml) $(DEPS_FILES)
# $(example-linter) $(filter-out $(DEPS_FILES),$^)
#
# Note the filtering that is used here to avoid linting those files.
#
## Manual Additions
#
# To manually add dependencies, edit your `Makefile` as follows.
#
# 1. Choose a file you will use as a marker to track installation and add that
# to `$(DEPS_FILES)`:
# DEPS_FILES := .example.dep
# This change needs to appear *before* the `include $(LIBDIR)/main.mk`
# line of the `Makefile`; subsequent changes can be put below.
# 2. Add the marker file to your `.gitignore`
# 3. Add a recipe for the marker file that installs the tool. If your
# installation depends on local files, add those as dependencies. Make
# sure to touch the marker file when you do this.
# .example.dep: example.cfg
# @install-example --config $^
# @touch $@
# 4. If necessary, add the tool to `$(PATH)`.
# 5. (Optionally) Add a dependency to `update-deps` that updates the tool.
# This allows people to update the tool periodically to catch changes in
# the tool outside of the local repository (such as new releases). No
# need to do this if you are only installing in CI builds (as below).
# 6. (Optionally) Add a dependency to `clean-deps` to remove the tool.
# 7. (Optionally) Add steps to the workflow files so that the tool is
# cached between builds in CI.
#
# Generally, it is better to install tools in a subdirectory as that does
# not require alterations to the system that might be disruptive. However,
# that can mean that you can't use prebuilt binaries (such as those that
# are included in an OS distribution).
#
## Using OS Package Manager in CI
#
# If you use the OS packagage manager, you should only do that in CI by making
# the installation conditional on `$(CI)`. This isn't simple as you won't have
# access to this variable when you set `$(DEPS_FILES)`. So rather than making
# that addition conditional, make it unconditional and change the recipe:
# .example.dep: example.cfg
# ifeq(true,$(CI))
# @install-example --config $^
# else
# # maybe test if the tool is present
# # then print a warning and fail if it isn't
# endif
# @touch $@
#
# The additions to `update-deps` and `clean-deps` are unnecessary if you only
# make changes in CI.
#
# CI images are based on Alpine Linux, which uses `apk`:
# https://wiki.alpinelinux.org/wiki/Alpine_Package_Keeper
# and the "main" and "community" package repositories (by default):
# https://pkgs.alpinelinux.org/packages
.PHONY: deps clean-deps update-deps
# Make really doesn't handle spaces in filenames well.
# Using $(realpath) exposes make to spaces in directory names above this one.
# Though we might prefer to use $(realpath), this function operates a fallback
# so that the full path is not used if there are spaces in directory names.
ifeq ($(words $(realpath $(LIBDIR))),1)
safe-realpath = $(realpath $(1))
relative-paths := false
else
ifneq (,$(DISABLE_SPACES_WARNING))
$(warning Your $$LIBDIR ($(LIBDIR)) contains spaces; some things might break.)
endif
safe-realpath = $(1)
relative-paths := true
endif
ifeq (true,$(DISABLE_CACHE))
no-cache := --no-cache
no-cache-dir := --no-cache-dir
bundle-update-all := --all
endif
## Python
ifeq (true,$(CI))
# Override VENVDIR so we can use caching in CI.
VENVDIR = $(call safe-realpath,.)/.venv
endif
VENVDIR ?= $(call safe-realpath,$(LIBDIR))/.venv
REQUIREMENTS_TXT := $(wildcard requirements.txt)
ifneq (,$(strip $(REQUIREMENTS_TXT)))
# Need to maintain a local marker file in case the lib/ directory is shared.
LOCAL_VENV := .requirements.txt
DEPS_FILES += $(LOCAL_VENV)
endif
ifneq (true,$(CI))
# Don't install from lib/requirements.txt in CI; these are in the docker image.
REQUIREMENTS_TXT += $(LIBDIR)/requirements.txt
endif
## Install from requirements.txt.
ifneq (,$(strip $(REQUIREMENTS_TXT)))
ifeq (true,$(CI))
# Under CI, install from the local requirements.txt, but install globally (no venv).
pip ?= pip3
$(LOCAL_VENV):
"$(pip)" install --no-user $(no-cache-dir) $(foreach path,$(REQUIREMENTS_TXT),-r $(path))
@touch $@
# No clean-deps target in CI..
else # CI
# We have something to install in a venv, so include venv.mk.
include $(LIBDIR)/venv.mk
export VENV
pip := $(VENV)/pip
python := $(VENV)/python
xml2rfc := $(VENV)/xml2rfc $(XML2RFC_OPTS)
rfc-tidy := $(VENV)/rfc-tidy
export PATH := $(VENV):$(PATH)
ifneq (,$(LOCAL_VENV))
$(LOCAL_VENV): $(VENV)/$(MARKER)
@touch $@
else
DEPS_FILES += $(VENV)/$(MARKER)
endif
clean-deps:: clean-venv
endif # CI
update-deps::
"$(pip)" install --no-user $(no-cache-dir) --upgrade --upgrade-strategy eager \
$(foreach path,$(REQUIREMENTS_TXT),-r "$(path)")
endif # -e requirements.txt
# Variable defaults for CI
python ?= python3
xml2rfc ?= xml2rfc $(XML2RFC_OPTS)
rfc-tidy ?= rfc-tidy
## Ruby
ifeq (,$(shell which bundle)$(filter true,$(NO_RUBY)))
$(warning ruby bundler not installed; skipping bundle install)
NO_RUBY := true
endif
ifneq (true,$(NO_RUBY))
BUNDLE_IGNORE_MESSAGES := true
export BUNDLE_IGNORE_MESSAGES
ifeq (true,$(CI))
# Override BUNDLE_PATH so we can use caching in CI.
BUNDLE_PATH := $(call safe-realpath,.)/.gems
BUNDLE_DISABLE_VERSION_CHECK := true
export BUNDLE_DISABLE_VERSION_CHECK
endif
BUNDLE_PATH ?= $(call safe-realpath,$(LIBDIR))/.gems
# Install binaries to somewhere sensible instead of .../ruby/$v/bin where $v
# doesn't even match the current ruby version.
BUNDLE_BIN := $(BUNDLE_PATH)/bin
export PATH := $(BUNDLE_BIN):$(PATH)
ifeq (true,$(relative-paths))
# This means that BUNDLE_PATH is relative, which bundler will interpret
# as being relative to the Gemfile, not the current directory, so tweak
# the two path settings for bundler. After setting $PATH.
# Thankfully, we don't install from $(LIBDIR)/Gemfile in CI, which
# would mean that this would need to be "../.gems" there.
$(warning Using a relative path for bundler)
bundle-path-override-lib := BUNDLE_PATH=$(LIBDIR)/.gems BUNDLE_BIN=$(LIBDIR)/.gems/bin
bundle-path-override := BUNDLE_PATH=.gems BUNDLE_BIN=.gems/bin
endif
export BUNDLE_PATH
export BUNDLE_BIN
ifneq (,$(wildcard Gemfile))
# A local Gemfile exists.
DEPS_FILES += Gemfile.lock
Gemfile.lock: Gemfile
$(bundle-path-override-lib) bundle install $(no-cache) --gemfile="$(call safe-realpath,$<)"
@touch $@
update-deps:: Gemfile
$(bundle-path-override-lib) bundle update $(bundle-update-all) --gemfile="$(call safe-realpath,$<)"
clean-deps::
-rm -rf "$(BUNDLE_PATH)"
endif # Gemfile
ifneq (true,$(CI))
# Install kramdown-rfc.
DEPS_FILES += $(LIBDIR)/Gemfile.lock
$(LIBDIR)/Gemfile.lock: $(LIBDIR)/Gemfile
$(bundle-path-override) bundle install $(no-cache) --gemfile="$(call safe-realpath,$<)"
@touch $@
update-deps:: $(LIBDIR)/Gemfile
$(bundle-path-override) bundle update $(bundle-update-all) --gemfile="$(call safe-realpath,$<)"
clean-deps::
-rm -rf "$(BUNDLE_PATH)"
endif # !CI
endif # !NO_RUBY
## Nodejs
ifeq (,$(shell which npm))
ifneq (,$(wildcard package.json))
$(warning package.json exists, but npm not available; npm packages not installed)
endif
NO_NODEJS := true
endif
ifneq (true,$(NO_NODEJS))
export PATH := $(abspath node_modules/.bin):$(PATH)
ifneq (,$(wildcard package.json))
DEPS_FILES += package-lock.json
package-lock.json: package.json
npm install
@touch $@
update-deps::
npm update --no-save --dev
clean-deps::
-rm -rf package-lock.json
endif # package.json
endif # !NO_NODEJS
## Link everything up
deps:: $(DEPS_FILES)
update-deps::
clean-deps::
@-rm -f $(DEPS_FILES)