Marcel Telka
2024-03-23 4325c82a563491d25a1f48db6e125ec04b750554
commit | author | age
9c75c0 1 #
NJ 2 # CDDL HEADER START
3 #
4 # The contents of this file are subject to the terms of the
5 # Common Development and Distribution License (the "License").
6 # You may not use this file except in compliance with the License.
7 #
8 # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 # or http://www.opensolaris.org/os/licensing.
10 # See the License for the specific language governing permissions
11 # and limitations under the License.
12 #
13 # When distributing Covered Code, include this CDDL HEADER in each
14 # file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 # If applicable, add the following below this CDDL HEADER, with the
16 # fields enclosed by brackets "[]" replaced with your own identifying
17 # information: Portions Copyright [yyyy] [name of copyright owner]
18 #
19 # CDDL HEADER END
20 #
8beffa 21 # Copyright (c) 2010, 2016, Oracle and/or its affiliates. All rights reserved.
9c75c0 22 #
NJ 23
7c8106 24 #
MT 25 # The Python build infrastructure in setup.py.mk and pyproject.mk files uses
26 # several Python projects to work properly.  Since we cannot use these projects
27 # until they are actually built and installed we need to bootstrap.
28 #
29 # We do have several sequential bootstrap checkpoints during the process:
30 #
31 # (0)    Nothing works yet.
32 #
33 #     Just core Python runtime is available (with no additional projects).
34 #     While here almost nothing works.  We cannot do following tasks with
35 #     regular Python projects:
36 #         - detect their requirements,
37 #         - build and publish them,
38 #         - test them.
39 #
40 # (1)     The bootstrapper is ready.
41 #
42 #     The bootstrapper is special tool that requires just core Python with no
43 #     dependency on other Python projects and it is able to build and publish
44 #     itself and other Python projects.
45 #
46 #     For projects using the 'setup.py' build style we do not need any
47 #     special bootstrapper because such projects are built using their own
48 #     'setup.py' script.  The only issue with the 'setup.py' build style
49 #     projects is that their 'setup.py' script usually depends on some other
50 #     projects (typically setuptools) to get successfully built.
51 #
52 #     For 'pyproject'-style projects we use pyproject_installer as the
53 #     bootstrapper.
54 #
55 #     To achieve this checkpoint we just need to build pyproject_installer
56 #     using pyproject_installer without detecting its requirements (they are
6b1855 57 #     none anyway) and without testing it (since no testing infrastructure is
7c8106 58 #     ready yet).
MT 59 #
60 # (2)    The python-requires script works.
61 #
62 #     Once the python-requires script works we can start to detect runtime
63 #     dependencie of other Python projects automatically.
64 #
65 #     To achieve this checkpoint we need to build the packaging project
66 #     (directly needed by the python-requires script) and all projects
67 #     required by packaging.  During this all projects' dependencies needs to
68 #     be manually evaluated to make sure they are correct.
69 #
70 # (3)    The build infrastructure is fully working.
71 #
72 #    Once we are here we can build any Python project, but we cannot test it
73 #    yet.
74 #
75 #     For projects using the 'setup.py' build style we do not need any
76 #     special build infrastructure.  See checkpoint (1) above for detialed
77 #     discussion about 'setup.py' build style projects.
78 #
79 #     For 'pyproject'-style projects we need to build both 'build' and
80 #     'installer' projects and all projects they depends on.
81 #
82 # (4)    The testing infrastructure is fully working.
83 #
84 #     Once we are here we can finally use all features of the Python build
85 #     framework.  Including testing.
86 #
87 #     To achieve this we need to build tox, tox-current-env, and pytest
88 #     projects together with their dependencies.
89 #
90 # All projects needed to achieve checkpoints (1), (2), and (3) should set
91 # PYTHON_BOOTSTRAP to 'yes' in their Makefile to make sure the regular build
92 # infrastructure is not used for them and special set of build rules is applied
93 # instead.
94 #
95 # All projects needed to go from checkpoint (3) to checkpoint (4) should set
96 # PYTHON_TEST_BOOTSTRAP to 'yes' in their Makefile to let the build
97 # infrastructure know that testing for such projects might not work properly.
98 #
99 # The PYTHON_BOOTSTRAP set to 'yes' implies PYTHON_TEST_BOOTSTRAP set to 'yes'
100 # too.
101 #
102 ifeq ($(strip $(PYTHON_BOOTSTRAP)),yes)
103 PYTHON_TEST_BOOTSTRAP = yes
104 endif
6b1855 105
MT 106 #
107 # Lists of Python projects needed to achieve particular bootstrap checkpoint.
2172bd 108 # Indentation shows project dependencies (e.g. packaging requires flit_core).
6b1855 109 #
MT 110 PYTHON_BOOTSTRAP_CHECKPOINT_1 +=    pyproject_installer
111 #
112 PYTHON_BOOTSTRAP_CHECKPOINT_2 +=    $(PYTHON_BOOTSTRAP_CHECKPOINT_1)
113 PYTHON_BOOTSTRAP_CHECKPOINT_2 +=    packaging
2172bd 114 PYTHON_BOOTSTRAP_CHECKPOINT_2 +=        flit_core
7c8106 115
792915 116 # Particular python runtime is always required (at least to run setup.py)
MT 117 PYTHON_REQUIRED_PACKAGES += runtime/python
118
668bcb 119 define python-rule
AL 120 $(BUILD_DIR)/%-$(1)/.built:        PYTHON_VERSION=$(1)
121 $(BUILD_DIR)/%-$(1)/.installed:        PYTHON_VERSION=$(1)
122 $(BUILD_DIR)/%-$(1)/.tested:        PYTHON_VERSION=$(1)
123 $(BUILD_DIR)/%-$(1)/.tested-and-compared:    PYTHON_VERSION=$(1)
124 endef
125
126 $(foreach pyver, $(PYTHON_VERSIONS), $(eval $(call python-rule,$(pyver))))
127
74300c 128 $(BUILD_DIR)/$(MACH32)-%/.built:    BITS=32
NJ 129 $(BUILD_DIR)/$(MACH64)-%/.built:    BITS=64
130 $(BUILD_DIR)/$(MACH32)-%/.installed:    BITS=32
131 $(BUILD_DIR)/$(MACH64)-%/.installed:    BITS=64
8d70f8 132 $(BUILD_DIR)/$(MACH32)-%/.tested:    BITS=32
RB 133 $(BUILD_DIR)/$(MACH64)-%/.tested:    BITS=64
134 $(BUILD_DIR)/$(MACH32)-%/.tested-and-compared:    BITS=32
135 $(BUILD_DIR)/$(MACH64)-%/.tested-and-compared:    BITS=64
224ba0 136
44f1c2 137 PYTHON_32_VERSIONS = $(filter-out $(PYTHON_64_ONLY_VERSIONS), $(PYTHON_VERSIONS))
AP 138
139 BUILD_32 = $(PYTHON_32_VERSIONS:%=$(BUILD_DIR)/$(MACH32)-%/.built)
74300c 140 BUILD_64 = $(PYTHON_VERSIONS:%=$(BUILD_DIR)/$(MACH64)-%/.built)
ad2fa3 141 BUILD_NO_ARCH = $(PYTHON_VERSIONS:%=$(BUILD_DIR)/$(MACH)-%/.built)
224ba0 142
668bcb 143 ifeq ($(filter-out $(PYTHON_64_ONLY_VERSIONS), $(PYTHON_VERSION)),)
44f1c2 144 BUILD_32_and_64 = $(BUILD_64)
AP 145 endif
146
147 INSTALL_32 = $(PYTHON_32_VERSIONS:%=$(BUILD_DIR)/$(MACH32)-%/.installed)
74300c 148 INSTALL_64 = $(PYTHON_VERSIONS:%=$(BUILD_DIR)/$(MACH64)-%/.installed)
ad2fa3 149 INSTALL_NO_ARCH = $(PYTHON_VERSIONS:%=$(BUILD_DIR)/$(MACH)-%/.installed)
9c75c0 150
35a012 151 PYTHON_ENV =    CC="$(CC)"
NJ 152 PYTHON_ENV +=    CFLAGS="$(CFLAGS)"
0d76b8 153 PYTHON_ENV +=    CXX="$(CXX)"
MT 154 PYTHON_ENV +=    CXXFLAGS="$(CXXFLAGS)"
fd5873 155 PYTHON_ENV +=    LDFLAGS="$(LDFLAGS)"
ba2ab2 156 PYTHON_ENV +=    PKG_CONFIG_PATH="$(PKG_CONFIG_PATH)"
ad2fa3 157
NJ 158 COMPONENT_BUILD_ENV += $(PYTHON_ENV)
159 COMPONENT_INSTALL_ENV += $(PYTHON_ENV)
160 COMPONENT_TEST_ENV += $(PYTHON_ENV)
fa74c0 161
ea207c 162 # Set CARGO_HOME to make sure projects built using rust (for example via
MT 163 # setuptools-rust) do not pollute user's home directory with cargo bits.
164 COMPONENT_BUILD_ENV += CARGO_HOME=$(@D)/.cargo
165
6aefa3 166 # Make sure the default Python version is installed last and so is the
MT 167 # canonical version.  This is needed for components that keep PYTHON_VERSIONS
168 # set to more than single value, but deliver unversioned binaries in usr/bin or
169 # other overlapping files.
170 define python-order-rule
171 $(BUILD_DIR)/%-$(PYTHON_VERSION)/.installed:    $(BUILD_DIR)/%-$(1)/.installed
172 endef
173 $(foreach pyver,$(filter-out $(PYTHON_VERSION),$(PYTHON_VERSIONS)),$(eval $(call python-order-rule,$(pyver))))
174
381aab 175 # We need to copy the source dir to avoid its modification by install target
MT 176 # where egg-info is re-generated
177 CLONEY_ARGS = CLONEY_MODE="copy"
a7216a 178
115d4d 179 COMPONENT_BUILD_CMD = $(PYTHON) setup.py --no-user-cfg build $(COMPONENT_BUILD_SETUP_PY_ARGS)
7589de 180
9c75c0 181 # build the configured source
381aab 182 $(BUILD_DIR)/%/.built:    $(SOURCE_DIR)/.prep
9c75c0 183     $(RM) -r $(@D) ; $(MKDIR) $(@D)
381aab 184     $(ENV) $(CLONEY_ARGS) $(CLONEY) $(SOURCE_DIR) $(@D)
9c75c0 185     $(COMPONENT_PRE_BUILD_ACTION)
b3a428 186     (cd $(@D)$(COMPONENT_SUBDIR:%=/%) ; $(ENV) $(COMPONENT_BUILD_ENV) \
7589de 187         $(COMPONENT_BUILD_CMD) $(COMPONENT_BUILD_ARGS))
9c75c0 188     $(COMPONENT_POST_BUILD_ACTION)
NJ 189     $(TOUCH) $@
190
7589de 191
MT 192 COMPONENT_INSTALL_CMD = $(PYTHON) setup.py --no-user-cfg install
ff17eb 193
e83e52 194 COMPONENT_INSTALL_ARGS +=    --root $(PROTO_DIR) 
NJ 195 COMPONENT_INSTALL_ARGS +=    --install-lib=$(PYTHON_LIB)
fa74c0 196 COMPONENT_INSTALL_ARGS +=    --install-data=$(PYTHON_DATA)
381aab 197 COMPONENT_INSTALL_ARGS +=    --skip-build
6aefa3 198 COMPONENT_INSTALL_ARGS +=    --force
e83e52 199
9c75c0 200 # install the built source into a prototype area
381aab 201 $(BUILD_DIR)/%/.installed:    $(BUILD_DIR)/%/.built
9c75c0 202     $(COMPONENT_PRE_INSTALL_ACTION)
b3a428 203     (cd $(@D)$(COMPONENT_SUBDIR:%=/%) ; $(ENV) $(COMPONENT_INSTALL_ENV) \
7589de 204         $(COMPONENT_INSTALL_CMD) $(COMPONENT_INSTALL_ARGS))
9c75c0 205     $(COMPONENT_POST_INSTALL_ACTION)
NJ 206     $(TOUCH) $@
e83e52 207
e537d3 208 ifeq ($(strip $(SINGLE_PYTHON_VERSION)),no)
053684 209 # Rename binaries in /usr/bin to contain version number
MT 210 COMPONENT_POST_INSTALL_ACTION += \
211     for f in $(PROTOUSRBINDIR)/* ; do \
9b979e 212         [ -f $$f ] || continue ; \
053684 213         for v in $(PYTHON_VERSIONS) ; do \
9b979e 214             [ "$$f" == "$${f%%$$v}" ] || continue 2 ; \
053684 215         done ; \
MT 216         $(MV) $$f $$f-$(PYTHON_VERSION) ; \
217     done ;
e537d3 218 endif
053684 219
dfab6b 220 # Remove any previous dependency files
MT 221 COMPONENT_POST_INSTALL_ACTION +=    $(RM) $(@D)/.depend-runtime $(@D)/.depend-test ;
06c6b6 222
17a961 223 # Define Python version specific filenames for tests.
30eb16 224 ifeq ($(strip $(USE_COMMON_TEST_MASTER)),no)
17a961 225 COMPONENT_TEST_MASTER =    $(COMPONENT_TEST_RESULTS_DIR)/results-$(PYTHON_VERSION).master
30eb16 226 endif
5d7921 227 COMPONENT_TEST_BUILD_DIR = $(BUILD_DIR)/test-$(PYTHON_VERSION)
17a961 228 COMPONENT_TEST_OUTPUT =    $(COMPONENT_TEST_BUILD_DIR)/test-$(PYTHON_VERSION)-results
MT 229 COMPONENT_TEST_DIFFS =    $(COMPONENT_TEST_BUILD_DIR)/test-$(PYTHON_VERSION)-diffs
230 COMPONENT_TEST_SNAPSHOT = $(COMPONENT_TEST_BUILD_DIR)/results-$(PYTHON_VERSION).snapshot
231 COMPONENT_TEST_TRANSFORM_CMD = $(COMPONENT_TEST_BUILD_DIR)/transform-$(PYTHON_VERSION)-results
8d70f8 232
34664f 233 # Generic transforms for Python test results.
MT 234 # See below for test style specific transforms.
235 COMPONENT_TEST_TRANSFORMS += "-e 's|$(PYTHON_DIR)|\$$(PYTHON_DIR)|g'"
848948 236
dcf6a8 237 # Testing depends on install target because we want to test installed modules
MT 238 COMPONENT_TEST_DEP +=    $(BUILD_DIR)/%/.installed
239 # Point Python to the proto area so it is able to find installed modules there
240 COMPONENT_TEST_ENV +=    PYTHONPATH=$(PROTO_DIR)/$(PYTHON_LIB)
9b979e 241 # Make sure testing is able to find own installed executables (if any)
MT 242 COMPONENT_TEST_ENV +=    PATH=$(PROTOUSRBINDIR):$(PATH)
59c102 243
8d70f8 244 # determine the type of tests we want to run.
RB 245 ifeq ($(strip $(wildcard $(COMPONENT_TEST_RESULTS_DIR)/results-*.master)),)
44f1c2 246 TEST_32 = $(PYTHON_32_VERSIONS:%=$(BUILD_DIR)/$(MACH32)-%/.tested)
8d70f8 247 TEST_64 = $(PYTHON_VERSIONS:%=$(BUILD_DIR)/$(MACH64)-%/.tested)
RB 248 TEST_NO_ARCH = $(PYTHON_VERSIONS:%=$(BUILD_DIR)/$(MACH)-%/.tested)
249 else
44f1c2 250 TEST_32 = $(PYTHON_32_VERSIONS:%=$(BUILD_DIR)/$(MACH32)-%/.tested-and-compared)
8d70f8 251 TEST_64 = $(PYTHON_VERSIONS:%=$(BUILD_DIR)/$(MACH64)-%/.tested-and-compared)
RB 252 TEST_NO_ARCH = $(PYTHON_VERSIONS:%=$(BUILD_DIR)/$(MACH)-%/.tested-and-compared)
253 endif
254
381aab 255 #
792915 256 # Testing in the Python world is complex.  Python projects usually do not
MT 257 # support Makefile with common 'check' or 'test' target to get built bits
258 # tested.
381aab 259 #
792915 260 # De facto standard way to test Python projects these days is tox which is
MT 261 # designed and used primarily for release testing; to make sure the released
262 # python project runs on all supported Python versions, platforms, etc.  tox
263 # does so using virtualenv and creates isolated test environments where the
264 # tested package together with all its dependencies is automatically installed
265 # (using pip) and tested.  This is great for Python projects developers but it
266 # is hardly usable for operating system distributions like OpenIndiana.
381aab 267 #
792915 268 # We do not need such release testing.  Instead we need something closer to
MT 269 # integration testing: we need to test the built component in our real
270 # environment without automatic installation of any dependencies using pip.  In
271 # addition, we need to run tests only for Python versions we actually support
272 # and the component is built for.
381aab 273 #
792915 274 # To achieve that we do few things.  First, to avoid isolated environments
MT 275 # (virtualenv) we run tox with the tox-current-env plugin.  Second, to test
276 # only Python versions we are interested in we use -e option for tox to select
277 # single Python version only.  Since we run separate test target per Python
278 # version this will make sure we test all needed Python versions.
279 #
25e842 280 # The tox tool itself uses some other tools under the hood to run tests, for
MT 281 # example pytest.  Some projects could even support pytest testing directly
282 # without support for tox.  For such projects we offer separate "pytest"-style
283 # testing.
284 #
285 # For projects that do not support testing using neither tox nor pytest we
855acc 286 # offer either unittest or (deprecated) "setup.py test" testing too.
25e842 287 #
MT 288 # The TEST_STYLE variable is used to select (or force) particular test style
289 # for Python projects.  Valid values are:
290 #
291 #     tox        - "tox"-style testing
292 #     pytest        - "pytest"-style testing
855acc 293 #     unittest    - "unittest"-style testing
25e842 294 #     setup.py    - "setup.py test"-style testing
7c8106 295 #     none        - no testing is supported (or desired) at all
792915 296 #
MT 297
298 TEST_STYLE ?= tox
299 ifeq ($(strip $(TEST_STYLE)),tox)
9b979e 300 # tox needs PATH environment variable - see https://github.com/tox-dev/tox/issues/2538
MT 301 # We already added it to the test environment - see above
3db057 302 COMPONENT_TEST_ENV +=        PYTEST_ADDOPTS="$(PYTEST_ADDOPTS)"
dd736d 303 COMPONENT_TEST_ENV +=        NOSE_VERBOSE=2
792915 304 COMPONENT_TEST_CMD =        $(TOX)
81789e 305 COMPONENT_TEST_ARGS =        --current-env --no-provision
e7d6fe 306 COMPONENT_TEST_ARGS +=        --recreate
b621ca 307 COMPONENT_TEST_ARGS +=        $(TOX_TESTENV)
46ab01 308 COMPONENT_TEST_TARGETS =    $(if $(strip $(TOX_POSARGS)),-- $(TOX_POSARGS))
b621ca 309
ccec59 310 TOX_TESTENV = -e py$(subst .,,$(PYTHON_VERSION))
792915 311
83badd 312 # Make sure following tools are called indirectly to properly support tox-current-env
573ee5 313 TOX_CALL_INDIRECTLY += py.test
051a62 314 TOX_CALL_INDIRECTLY += pytest
MT 315 TOX_CALL_INDIRECTLY += coverage
1eb2df 316 TOX_CALL_INDIRECTLY += zope-testrunner
MT 317 TOX_CALL_INDIRECTLY.zope-testrunner = zope.testrunner
318 TOX_CALL_INDIRECTLY += sphinx-build
319 TOX_CALL_INDIRECTLY.sphinx-build = sphinx.cmd.build
dd736d 320 TOX_CALL_INDIRECTLY += nosetests
MT 321 TOX_CALL_INDIRECTLY.nosetests = nose
1eb2df 322 $(foreach indirectly, $(TOX_CALL_INDIRECTLY), $(eval TOX_CALL_INDIRECTLY.$(indirectly) ?= $(indirectly)))
MT 323 COMPONENT_PRE_TEST_ACTION += COMPONENT_TEST_DIR=$(COMPONENT_TEST_DIR) ;
950890 324 COMPONENT_PRE_TEST_ACTION += \
1eb2df 325     $(foreach indirectly, $(TOX_CALL_INDIRECTLY), \
MT 326         [ -f $$COMPONENT_TEST_DIR/tox.ini ] && \
a67d39 327             $(GSED) -i -e '/^commands *=/,/^$$/{ \
MT 328                 s/^\(\(commands *=\)\{0,1\}[ \t]*\)'$(indirectly)'\([ \t]\{1,\}.*\)\{0,1\}$$/\1python -m '$(TOX_CALL_INDIRECTLY.$(indirectly))'\3/ \
329             }' $$COMPONENT_TEST_DIR/tox.ini ; \
1eb2df 330     )
MT 331 COMPONENT_PRE_TEST_ACTION += true ;
ae4a72 332
792915 333 # Normalize tox test results.
ccec59 334 COMPONENT_TEST_TRANSFORMS += "-e 's/py$(subst .,,$(PYTHON_VERSION))/py\$$(PYV)/g'"    # normalize PYV
34664f 335 COMPONENT_TEST_TRANSFORMS += "-e '/^py\$$(PYV) installed:/d'"        # depends on set of installed packages
MT 336 COMPONENT_TEST_TRANSFORMS += "-e '/PYTHONHASHSEED/d'"            # this is random
792915 337
a3f365 338 # Normalize zope.testrunner test results
8937be 339 COMPONENT_TEST_TRANSFORMS += \
MT 340     "-e 's/ in \([0-9]\{1,\} minutes \)\{0,1\}[0-9]\{1,\}\.[0-9]\{3\} seconds//'"    # timing
a3f365 341
5cfe42 342 # Remove timing for tox 4 test results
MT 343 COMPONENT_TEST_TRANSFORMS += "-e 's/^\(  py\$$(PYV): OK\) (.* seconds)$$/\1/'"
344 COMPONENT_TEST_TRANSFORMS += "-e 's/^\(  congratulations :)\) (.* seconds)$$/\1/'"
345
22f684 346 # Remove useless lines from the "coverage combine" output
e91f55 347 COMPONENT_TEST_TRANSFORMS += "-e '/^Combined data file .*\.coverage/d'"
MT 348 COMPONENT_TEST_TRANSFORMS += "-e '/^Skipping duplicate data .*\.coverage/d'"
22f684 349
f86079 350 # sort list of Sphinx doctest results
MT 351 COMPONENT_TEST_TRANSFORMS += \
352     "| ( \
353         $(GSED) -u -e '/^running tests\.\.\.$$/q' ; \
354         $(GSED) -u -e '/^Doctest summary/Q' \
355             | $(NAWK) '/^$$/{\$$0=\"\\\\n\"}1' ORS='|' \
356             | $(GNU_GREP) -v '^|$$' \
a99182 357             | $(SORT) \
f86079 358             | tr -d '\\\\n' | tr '|' '\\\\n' \
MT 359             | $(NAWK) '{print}END{if(NR>0)printf(\"\\\\nDoctest summary\\\\n\")}' ; \
360         $(CAT) \
361     ) | $(COMPONENT_TEST_TRANSFORMER)"
362
792915 363 # tox package together with the tox-current-env plugin is needed
b1d234 364 USERLAND_TEST_REQUIRED_PACKAGES += library/python/tox
MT 365 USERLAND_TEST_REQUIRED_PACKAGES += library/python/tox-current-env
06c6b6 366
MT 367 # Generate raw lists of test dependencies per Python version
1eea40 368 # Please note we set PATH below four times for $(COMPONENT_TEST_CMD) (aka tox)
MT 369 # to workaround https://github.com/tox-dev/tox/issues/2538
06c6b6 370 COMPONENT_POST_INSTALL_ACTION += \
b1d234 371     if [ -x "$(COMPONENT_TEST_CMD)" ] ; then \
b3a428 372         cd $(@D)$(COMPONENT_SUBDIR:%=/%) ; \
b1d234 373         echo "Testing dependencies:" ; \
b621ca 374         PATH=$(PATH) $(COMPONENT_TEST_CMD) -qq --no-provision --print-deps-to=- $(TOX_TESTENV) || exit 1 ; \
b1d234 375         echo "Testing extras:" ; \
b621ca 376         PATH=$(PATH) $(COMPONENT_TEST_CMD) -qq --no-provision --print-extras-to=- $(TOX_TESTENV) || exit 1 ; \
MT 377         ( PATH=$(PATH) $(COMPONENT_TEST_CMD) -qq --no-provision --print-deps-to=- $(TOX_TESTENV) \
b1d234 378             | $(WS_TOOLS)/python-resolve-deps \
MT 379                 PYTHONPATH=$(PROTO_DIR)/$(PYTHON_DIR)/site-packages:$(PROTO_DIR)/$(PYTHON_LIB) \
380                 $(PYTHON) $(WS_TOOLS)/python-requires $(COMPONENT_NAME) \
381             | $(PYTHON) $(WS_TOOLS)/python-requires - ; \
b621ca 382         for e in $$(PATH=$(PATH) $(COMPONENT_TEST_CMD) -qq --no-provision --print-extras-to=- $(TOX_TESTENV)) ; do \
7416d1 383             PYTHONPATH=$(PROTO_DIR)/$(PYTHON_DIR)/site-packages:$(PROTO_DIR)/$(PYTHON_LIB) \
b1d234 384                 $(PYTHON) $(WS_TOOLS)/python-requires $(COMPONENT_NAME) $$e ; \
dfab6b 385         done ) | $(GSED) -e '/^tox\(-current-env\)\?$$/d' >> $(@D)/.depend-test ; \
b1d234 386     fi ;
25e842 387 else ifeq ($(strip $(TEST_STYLE)),pytest)
MT 388 COMPONENT_TEST_CMD =        $(PYTHON) -m pytest
3db057 389 COMPONENT_TEST_ARGS =        $(PYTEST_ADDOPTS)
25e842 390 COMPONENT_TEST_TARGETS =
3db057 391
b1d234 392 USERLAND_TEST_REQUIRED_PACKAGES += library/python/pytest
855acc 393 else ifeq ($(strip $(TEST_STYLE)),unittest)
MT 394 COMPONENT_TEST_CMD =        $(PYTHON) -m unittest
395 COMPONENT_TEST_ARGS =
396 COMPONENT_TEST_ARGS +=        --verbose
397 COMPONENT_TEST_TARGETS =
25e842 398 else ifeq ($(strip $(TEST_STYLE)),setup.py)
MT 399 # Old and deprecated "setup.py test"-style testing
792915 400 COMPONENT_TEST_CMD =        $(PYTHON) setup.py
MT 401 COMPONENT_TEST_ARGS =        --no-user-cfg
402 COMPONENT_TEST_TARGETS =    test
25e842 403 else ifeq ($(strip $(TEST_STYLE)),none)
MT 404 TEST_TARGET = $(NO_TESTS)
792915 405 endif
381aab 406
3db057 407 # Run pytest verbose to get separate line per test in results output
MT 408 PYTEST_ADDOPTS += --verbose
409
808d8a 410 # Force pytest to not use colored output so the results normalization is unaffected
MT 411 PYTEST_ADDOPTS += --color=no
412
ed465b 413 # Avoid loading of unexpected pytest plugins.
40cc3a 414 define disable-pytest-plugin
ed465b 415 PYTEST_ADDOPTS += $$(if $$(filter library/python/$(2)-$$(subst .,,$$(PYTHON_VERSION)), $$(REQUIRED_PACKAGES) $$(TEST_REQUIRED_PACKAGES) $$(COMPONENT_FMRI)-$$(subst .,,$$(PYTHON_VERSION))),,-p 'no:$(1)')
40cc3a 416 endef
ed465b 417 $(eval $(call disable-pytest-plugin,anyio,anyio))
1595bc 418 $(eval $(call disable-pytest-plugin,asyncio,pytest-asyncio))        # adds line to test report header
MT 419 $(eval $(call disable-pytest-plugin,benchmark,pytest-benchmark))    # adds line to test report header; adds benchmark report
0f943a 420 $(eval $(call disable-pytest-plugin,black,pytest-black))        # runs extra test(s)
ed465b 421 $(eval $(call disable-pytest-plugin,check,pytest-check))
0f943a 422 $(eval $(call disable-pytest-plugin,checkdocs,pytest-checkdocs))    # runs extra test(s)
ed465b 423 $(eval $(call disable-pytest-plugin,console-scripts,pytest-console-scripts))
40cc3a 424 $(eval $(call disable-pytest-plugin,cov,pytest-cov))
ed465b 425 $(eval $(call disable-pytest-plugin,custom_exit_code,pytest-custom-exit-code))
3380cb 426 $(eval $(call disable-pytest-plugin,enabler,pytest-enabler))
ed465b 427 $(eval $(call disable-pytest-plugin,env,pytest-env))
MT 428 $(eval $(call disable-pytest-plugin,faker,faker))
429 $(eval $(call disable-pytest-plugin,flake8,pytest-flake8))
40cc3a 430 $(eval $(call disable-pytest-plugin,flaky,flaky))
ed465b 431 $(eval $(call disable-pytest-plugin,freezer,pytest-freezer))
MT 432 $(eval $(call disable-pytest-plugin,helpers_namespace,pytest-helpers-namespace))
1595bc 433 $(eval $(call disable-pytest-plugin,hypothesispytest,hypothesis))    # adds line to test report header
ed465b 434 $(eval $(call disable-pytest-plugin,jaraco.test.http,jaraco-test))
MT 435 $(eval $(call disable-pytest-plugin,kgb,kgb))
140fab 436 $(eval $(call disable-pytest-plugin,metadata,pytest-metadata))        # adds line to test report header
0f943a 437 $(eval $(call disable-pytest-plugin,mypy,pytest-mypy))            # runs extra test(s)
MT 438 $(eval $(call disable-pytest-plugin,perf,pytest-perf))            # https://github.com/jaraco/pytest-perf/issues/9
e7470e 439 $(eval $(call disable-pytest-plugin,pytest home,pytest-home))
ed465b 440 $(eval $(call disable-pytest-plugin,pytest-datadir,pytest-datadir))
a38a08 441 $(eval $(call disable-pytest-plugin,pytest-mypy-plugins,pytest-mypy-plugins))    # could cause tests to fail
ed465b 442 $(eval $(call disable-pytest-plugin,pytest-teamcity,teamcity-messages))
MT 443 $(eval $(call disable-pytest-plugin,pytest_expect,pytest-expect))
444 $(eval $(call disable-pytest-plugin,pytest_fakefs,pyfakefs))
445 $(eval $(call disable-pytest-plugin,pytest_forked,pytest-forked))
446 $(eval $(call disable-pytest-plugin,pytest_httpserver,pytest-httpserver))
447 $(eval $(call disable-pytest-plugin,pytest_ignore_flaky,pytest-ignore-flaky))
818557 448 $(eval $(call disable-pytest-plugin,pytest_lazyfixture,pytest-lazy-fixtures))
ed465b 449 $(eval $(call disable-pytest-plugin,pytest_mock,pytest-mock))
0f943a 450 $(eval $(call disable-pytest-plugin,randomly,pytest-randomly))        # reorders tests
ed465b 451 $(eval $(call disable-pytest-plugin,regressions,pytest-regressions))
95dfd1 452 $(eval $(call disable-pytest-plugin,relaxed,pytest-relaxed))        # runs extra test(s); produces different test report
40cc3a 453 $(eval $(call disable-pytest-plugin,reporter,pytest-reporter))        # https://github.com/christiansandberg/pytest-reporter/issues/8
ed465b 454 $(eval $(call disable-pytest-plugin,rerunfailures,pytest-rerunfailures))
af147f 455 $(eval $(call disable-pytest-plugin,salt-factories,pytest-salt-factories))            # requires salt
MT 456 $(eval $(call disable-pytest-plugin,salt-factories-event-listener,pytest-salt-factories))    # requires salt
457 $(eval $(call disable-pytest-plugin,salt-factories-factories,pytest-salt-factories))        # requires salt
458 $(eval $(call disable-pytest-plugin,salt-factories-loader-mock,pytest-salt-factories))        # requires salt
459 $(eval $(call disable-pytest-plugin,salt-factories-log-server,pytest-salt-factories))        # requires salt
460 $(eval $(call disable-pytest-plugin,salt-factories-markers,pytest-salt-factories))        # requires salt
461 $(eval $(call disable-pytest-plugin,salt-factories-sysinfo,pytest-salt-factories))        # requires salt
462 $(eval $(call disable-pytest-plugin,salt-factories-sysstats,pytest-salt-factories))        # requires salt
ed465b 463 $(eval $(call disable-pytest-plugin,shell-utilities,pytest-shell-utilities))
MT 464 $(eval $(call disable-pytest-plugin,skip-markers,pytest-skip-markers))
465 $(eval $(call disable-pytest-plugin,socket,pytest-socket))
466 $(eval $(call disable-pytest-plugin,subprocess,pytest-subprocess))
467 $(eval $(call disable-pytest-plugin,subtests,pytest-subtests))
1a8beb 468 $(eval $(call disable-pytest-plugin,tempdir,pytest-tempdir))        # adds line to test report header
ed465b 469 $(eval $(call disable-pytest-plugin,time_machine,time-machine))
MT 470 $(eval $(call disable-pytest-plugin,timeout,pytest-timeout))
471 $(eval $(call disable-pytest-plugin,travis-fold,pytest-travis-fold))
472 $(eval $(call disable-pytest-plugin,typeguard,typeguard))
473 $(eval $(call disable-pytest-plugin,unittest_mock,backports-unittest-mock))
474 $(eval $(call disable-pytest-plugin,xdist,pytest-xdist))
475 $(eval $(call disable-pytest-plugin,xdist.looponfail,pytest-xdist))
af147f 476 $(eval $(call disable-pytest-plugin,xprocess,pytest-xprocess))        # adds a reminder line to test output
7d6724 477
18da4f 478 # By default we are not interested in full list of test failures so exit on
MT 479 # first failure to save time.  This could be easily overridden from environment
480 # if needed (for example to debug test failures) or in per-component Makefile.
481 PYTEST_FASTFAIL = -x
482 PYTEST_ADDOPTS += $(PYTEST_FASTFAIL)
483
45ab77 484 # By default we are not interested to see the default long tracebacks.
MT 485 # Detailed tracebacks are shown either for failures or xfails.  We aim to see
486 # testing passed so there should be no failures.  Since xfails are expected
487 # failures we are not interested in detailed tracebacks here at all since they
488 # could contain random data, like pointers, temporary file names, etc.
489 PYTEST_TRACEBACK = --tb=line
490 PYTEST_ADDOPTS += $(PYTEST_TRACEBACK)
491
ae8210 492 # Normalize pytest test results.  The pytest framework could be used either
MT 493 # directly or via tox or setup.py so add these transforms for all test styles
494 # unconditionally.
34664f 495 COMPONENT_TEST_TRANSFORMS += \
MT 496     "-e 's/^\(platform sunos5 -- Python \)$(shell echo $(PYTHON_VERSION) | $(GSED) -e 's/\./\\./g')\.[0-9]\{1,\}.*\( -- .*\)/\1\$$(PYTHON_VERSION).X\2/'"
ae8210 497 COMPONENT_TEST_TRANSFORMS += "-e '/^Using --randomly-seed=[0-9]\{1,\}$$/d'"    # this is random
MT 498 COMPONENT_TEST_TRANSFORMS += "-e '/^plugins: /d'"                # order of listed plugins could vary
c44dc0 499 COMPONENT_TEST_TRANSFORMS += "-e '/^-\{1,\} coverage: /,/^$$/d'"        # remove coverage report
27b18a 500 # sort list of pytest unit tests and drop percentage
MT 501 COMPONENT_TEST_TRANSFORMS += \
08f372 502     "| ( \
MT 503         $(GSED) -u -e '/^=\{1,\} test session starts /q' ; \
504         $(GSED) -u -e '/^$$/q' ; \
a99182 505         $(GSED) -u -e 's/ *\[...%\]$$//' -e '/^$$/Q' | $(SORT) | $(NAWK) '{print}END{if(NR>0)printf(\"\\\\n\")}' ; \
08f372 506         $(CAT) \
MT 507     ) | $(COMPONENT_TEST_TRANSFORMER)"
6dfcfa 508 COMPONENT_TEST_TRANSFORMS += \
b4e51a 509     "-e 's/^=\{1,\} \(.*\) in [0-9]\{1,\}\.[0-9]\{1,\}s \(([^)]*) \)\?=\{1,\}$$/======== \1 ========/'"    # remove timing
a033e0 510 # Remove slowest durations report for projects that run pytest with --durations option
2f67f4 511 COMPONENT_TEST_TRANSFORMS += "-e '/^=\{1,\} slowest [0-9 ]*durations =\{1,\}$$/,/^=/{/^=/!d}'"
0abb3a 512 # Remove short test summary info for projects that run pytest with -r option
MT 513 COMPONENT_TEST_TRANSFORMS += "-e '/^=\{1,\} short test summary info =\{1,\}$$/,/^=/{/^=/!d}'"
ae8210 514
3d923b 515 # Normalize test results produced by pytest-benchmark
MT 516 COMPONENT_TEST_TRANSFORMS += \
517     $(if $(filter library/python/pytest-benchmark-$(subst .,,$(PYTHON_VERSION)), $(REQUIRED_PACKAGES) $(TEST_REQUIRED_PACKAGES)),"| ( \
518         $(GSED) -e '/^-\{1,\} benchmark/,/^=/{/^=/!d}' \
519     ) | $(COMPONENT_TEST_TRANSFORMER) -e ''")
520
d381c2 521 # Normalize test results produced by pytest-xdist
MT 522 COMPONENT_TEST_TRANSFORMS += \
523     $(if $(filter library/python/pytest-xdist-$(subst .,,$(PYTHON_VERSION)), $(REQUIRED_PACKAGES) $(TEST_REQUIRED_PACKAGES)),"| ( \
524         $(GSED) -u \
525             -e '/^created: .* workers$$/d' \
526             -e 's/^[0-9]\{1,\}\( workers \[[0-9]\{1,\} items\]\)$$/X\1/' \
527             -e '/^scheduling tests via /q' ; \
528         $(GSED) -u -e '/^$$/q' ; \
529         $(GSED) -u -n -e '/^\[gw/p' -e '/^$$/Q' | ( $(GSED) \
530             -e 's/^\[gw[0-9]\{1,\}\] \[...%\] //' \
531             -e 's/ *$$//' \
532             -e 's/\([^ ]\{1,\}\) \(.*\)$$/\2 \1/' \
533             | $(SORT) | $(NAWK) '{print}END{if(NR>0)printf(\"\\\\n\")}' ; \
534         ) ; \
535         $(CAT) \
536     ) | $(COMPONENT_TEST_TRANSFORMER) -e ''")
537
b50750 538 # Normalize stestr test results
MT 539 USE_STESTR = $(filter library/python/stestr-$(subst .,,$(PYTHON_VERSION)), $(REQUIRED_PACKAGES) $(TEST_REQUIRED_PACKAGES))
540 COMPONENT_TEST_TRANSFORMS += \
541     $(if $(strip $(USE_STESTR)),"| ( \
542             $(GSED) -e '0,/^{[0-9]\{1,\}}/{//i\'\$$'\\\n{0}\\\n}' \
543                 -e 's/^\(Ran: [0-9]\{1,\} tests\{0,1\}\) in .*\$$/\1/' \
544                 -e '/^Sum of execute time for each test/d' \
545                 -e '/^ - Worker /d' \
546         ) | ( \
547             $(GSED) -u -e '/^{0}\$$/Q' ; \
548             $(GSED) -u -e 's/^{[0-9]\{1,\}} //' \
549                 -e 's/\[[.0-9]\{1,\}s\] \.\.\./.../' \
550                 -e '/^\$$/Q' | $(SORT) | $(GSED) -e '\$$a\'\$$'\\\n\\\n' ; \
551             $(CAT) \
552         ) | $(COMPONENT_TEST_TRANSFORMER) -e ''")
553
ce8823 554 # Normalize setup.py test results.  The setup.py testing could be used either
MT 555 # directly or via tox so add these transforms for all test styles
556 # unconditionally.
557 COMPONENT_TEST_TRANSFORMS += "-e '/SetuptoolsDeprecationWarning:/,+1d'"        # depends on Python version and is useless
9f6a07 558 COMPONENT_TEST_TRANSFORMS += "-e 's/^\(Ran [0-9]\{1,\} tests\{0,1\}\) in .*$$/\1/'"    # delete timing from test results
b3a428 559
MT 560 COMPONENT_TEST_DIR = $(@D)$(COMPONENT_SUBDIR:%=/%)
ce8823 561
59c102 562 # test the built source
8d70f8 563 $(BUILD_DIR)/%/.tested-and-compared:    $(COMPONENT_TEST_DEP)
237543 564     $(RM) -rf $(COMPONENT_TEST_BUILD_DIR)
AP 565     $(MKDIR) $(COMPONENT_TEST_BUILD_DIR)
59c102 566     $(COMPONENT_PRE_TEST_ACTION)
8d70f8 567     -(cd $(COMPONENT_TEST_DIR) ; \
381aab 568         $(COMPONENT_TEST_ENV_CMD) $(COMPONENT_TEST_ENV) \
792915 569         $(COMPONENT_TEST_CMD) \
MT 570         $(COMPONENT_TEST_ARGS) $(COMPONENT_TEST_TARGETS)) \
8d70f8 571         &> $(COMPONENT_TEST_OUTPUT)
59c102 572     $(COMPONENT_POST_TEST_ACTION)
8d70f8 573     $(COMPONENT_TEST_CREATE_TRANSFORMS)
RB 574     $(COMPONENT_TEST_PERFORM_TRANSFORM)
575     $(COMPONENT_TEST_COMPARE)
576     $(COMPONENT_TEST_CLEANUP)
577     $(TOUCH) $@
578
89aae0 579 $(BUILD_DIR)/%/.tested:    SHELLOPTS=pipefail
8d70f8 580 $(BUILD_DIR)/%/.tested:    $(COMPONENT_TEST_DEP)
89aae0 581     $(RM) -rf $(COMPONENT_TEST_BUILD_DIR)
MT 582     $(MKDIR) $(COMPONENT_TEST_BUILD_DIR)
8d70f8 583     $(COMPONENT_PRE_TEST_ACTION)
RB 584     (cd $(COMPONENT_TEST_DIR) ; \
381aab 585         $(COMPONENT_TEST_ENV_CMD) $(COMPONENT_TEST_ENV) \
792915 586         $(COMPONENT_TEST_CMD) \
89aae0 587         $(COMPONENT_TEST_ARGS) $(COMPONENT_TEST_TARGETS)) \
MT 588         |& $(TEE) $(COMPONENT_TEST_OUTPUT)
8d70f8 589     $(COMPONENT_POST_TEST_ACTION)
89aae0 590     $(COMPONENT_TEST_CREATE_TRANSFORMS)
MT 591     $(COMPONENT_TEST_PERFORM_TRANSFORM)
8d70f8 592     $(COMPONENT_TEST_CLEANUP)
59c102 593     $(TOUCH) $@
7999b2 594
9b979e 595 ifeq ($(strip $(SINGLE_PYTHON_VERSION)),no)
MT 596 # Temporarily create symlinks for renamed binaries
597 COMPONENT_PRE_TEST_ACTION += \
598     for f in $(PROTOUSRBINDIR)/*-$(PYTHON_VERSION) ; do \
599         [ -f $$f ] || continue ; \
0c5dad 600         [ -L $${f%%-$(PYTHON_VERSION)} ] && $(RM) $${f%%-$(PYTHON_VERSION)} ; \
9b979e 601         [ -e $${f%%-$(PYTHON_VERSION)} ] && continue ; \
MT 602         $(SYMLINK) $$(basename $$f) $${f%%-$(PYTHON_VERSION)} ; \
603     done ;
604
605 # Cleanup of temporary symlinks
606 COMPONENT_POST_TEST_ACTION += \
607     for f in $(PROTOUSRBINDIR)/*-$(PYTHON_VERSION) ; do \
608         [ -f $$f ] || continue ; \
609         [ ! -L $${f%%-$(PYTHON_VERSION)} ] || $(RM) $${f%%-$(PYTHON_VERSION)} ; \
610     done ;
611 endif
612
053684 613
e537d3 614 ifeq ($(strip $(SINGLE_PYTHON_VERSION)),no)
053684 615 # We need to add -$(PYV) to package fmri
MT 616 GENERATE_EXTRA_CMD += | \
e537d3 617     $(GSED) -e 's/^\(set name=pkg.fmri [^@]*\)\(.*\)$$/\1-$$(PYV)\2/'
MT 618 endif
053684 619
095208 620 # Add runtime dependencies from project metadata to generated manifest
MT 621 GENERATE_EXTRA_DEPS += $(BUILD_DIR)/META.depend-runtime.res
622 GENERATE_EXTRA_CMD += | \
623     $(CAT) - <( \
624         echo "" ; \
82fd1e 625         echo "\# python modules are unusable without python runtime binary" ; \
MT 626         echo "depend type=require fmri=__TBD pkg.debug.depend.file=python\$$(PYVER) \\" ; \
627         echo "    pkg.debug.depend.path=usr/bin" ; \
628         echo "" ; \
095208 629         echo "\# Automatically generated dependencies based on distribution metadata" ; \
MT 630         $(CAT) $(BUILD_DIR)/META.depend-runtime.res \
631     )
632
633 # Add runtime dependencies from project metadata to REQUIRED_PACKAGES
634 REQUIRED_PACKAGES_RESOLVED += $(BUILD_DIR)/META.depend-runtime.res
635
636
637 # Generate raw lists of runtime dependencies per Python version
638 COMPONENT_POST_INSTALL_ACTION += \
06c6b6 639     PYTHONPATH=$(PROTO_DIR)/$(PYTHON_DIR)/site-packages:$(PROTO_DIR)/$(PYTHON_LIB) \
dfab6b 640         $(PYTHON) $(WS_TOOLS)/python-requires $(COMPONENT_NAME) >> $(@D)/.depend-runtime ;
095208 641
MT 642 # Convert raw per version lists of runtime dependencies to single resolved
06c6b6 643 # runtime dependency list.  The dependency on META.depend-test.required here is
MT 644 # purely to get the file created as a side effect of this target.
645 $(BUILD_DIR)/META.depend-runtime.res:    $(INSTALL_$(MK_BITS)) $(BUILD_DIR)/META.depend-test.required
a99182 646     $(CAT) $(INSTALL_$(MK_BITS):%.installed=%.depend-runtime) | $(SORT) -u \
095208 647         | $(GSED) -e 's/.*/depend type=require fmri=pkg:\/library\/python\/&-$$(PYV)/' > $@
MT 648
dfab6b 649 # Generate raw lists of test dependencies per Python version
MT 650 COMPONENT_POST_INSTALL_ACTION += \
b3a428 651     cd $(@D)$(COMPONENT_SUBDIR:%=/%) ; \
9d43f1 652     ( for f in $(TEST_REQUIREMENTS) ; do \
ace7a9 653         $(CAT) $$f | $(DOS2UNIX) -ascii ; \
9d43f1 654     done ; \
MT 655     for e in $(TEST_REQUIREMENTS_EXTRAS) ; do \
656         PYTHONPATH=$(PROTO_DIR)/$(PYTHON_DIR)/site-packages:$(PROTO_DIR)/$(PYTHON_LIB) \
657             $(PYTHON) $(WS_TOOLS)/python-requires $(COMPONENT_NAME) $$e ; \
658     done ) | $(WS_TOOLS)/python-resolve-deps \
dfab6b 659         PYTHONPATH=$(PROTO_DIR)/$(PYTHON_DIR)/site-packages:$(PROTO_DIR)/$(PYTHON_LIB) \
MT 660         $(PYTHON) $(WS_TOOLS)/python-requires $(COMPONENT_NAME) \
661     | $(PYTHON) $(WS_TOOLS)/python-requires - >> $(@D)/.depend-test ;
662
06c6b6 663 # Convert raw per version lists of test dependencies to single list of
d2a1ce 664 # TEST_REQUIRED_PACKAGES entries.  Some Python projects lists their own project
MT 665 # as a test dependency so filter this out here too.
06c6b6 666 $(BUILD_DIR)/META.depend-test.required:    $(INSTALL_$(MK_BITS))
a99182 667     $(CAT) $(INSTALL_$(MK_BITS):%.installed=%.depend-test) | $(SORT) -u \
d2a1ce 668         | $(GSED) -e 's/.*/TEST_REQUIRED_PACKAGES.python += library\/python\/&/' \
4325c8 669         | ( $(GNU_GREP) -v ' $(COMPONENT_FMRI)$$' || true ) \
d2a1ce 670         > $@
06c6b6 671
MT 672 # Add META.depend-test.required to the generated list of REQUIRED_PACKAGES
673 REQUIRED_PACKAGES_TRANSFORM += -e '$$r $(BUILD_DIR)/META.depend-test.required'
674
c49523 675 # The python-requires script requires packaging to provide useful output but
6b1855 676 # packaging might be unavailable during bootstrap until we reach bootstrap
MT 677 # checkpoint 2.  So require it conditionally.
678 ifeq ($(filter $(strip $(COMPONENT_NAME)),$(PYTHON_BOOTSTRAP_CHECKPOINT_2)),)
eb37ff 679 USERLAND_REQUIRED_PACKAGES.python += library/python/packaging
c49523 680 endif
MT 681
053684 682
35a012 683 clean::
NJ 684     $(RM) -r $(SOURCE_DIR) $(BUILD_DIR)
6005c4 685
c437ed 686 # Make it easy to construct a URL for a pypi source download.
8beffa 687 pypi_url_multi = pypi:///$(COMPONENT_NAME_$(1))==$(COMPONENT_VERSION_$(1))
DD 688 pypi_url_single = pypi:///$(COMPONENT_NAME)==$(COMPONENT_VERSION)
689 pypi_url = $(if $(COMPONENT_NAME_$(1)),$(pypi_url_multi),$(pypi_url_single))