diff --git a/.travis.yml b/.travis.yml index 703f4a125..b172e849e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,12 +9,8 @@ before_script: - bundle exec hatchet ci:setup script: - - docker build --pull --tag travis-build-cedar-14 --file $(pwd)/builds/cedar-14.Dockerfile . - - docker run --rm -e "STACK=cedar-14" -e "USE_STAGING_BINARIES=$USE_STAGING_BINARIES" travis-build-cedar-14 bash $TESTFOLDER - - docker build --pull --tag travis-build-heroku-16 --file $(pwd)/builds/heroku-16.Dockerfile . - - docker run --rm -e "STACK=heroku-16" -e "USE_STAGING_BINARIES=$USE_STAGING_BINARIES" travis-build-heroku-16 bash $TESTFOLDER - - docker build --pull --tag travis-build-heroku-18 --file $(pwd)/builds/heroku-18.Dockerfile . - - docker run --rm -e "STACK=heroku-18" -e "USE_STAGING_BINARIES=$USE_STAGING_BINARIES" travis-build-heroku-18 bash $TESTFOLDER + - docker build --pull --tag travis-build-$STACK --file $(pwd)/builds/$STACK.Dockerfile . + - docker run --rm -e "STACK=$STACK" -e "USE_STAGING_BINARIES=$USE_STAGING_BINARIES" travis-build-$STACK bash $TESTFOLDER jobs: include: @@ -27,16 +23,28 @@ jobs: script: "bundle exec rspec" env: matrix: - - TESTFOLDER=test/run-deps - - TESTFOLDER=test/run-deps USE_STAGING_BINARIES=https://lang-python.s3.amazonaws.com/staging - - TESTFOLDER=test/run-versions - - TESTFOLDER=test/run-versions USE_STAGING_BINARIES=https://lang-python.s3.amazonaws.com/staging - - TESTFOLDER=test/run-features - - TESTFOLDER=test/run-features USE_STAGING_BINARIES=https://lang-python.s3.amazonaws.com/staging + - STACK=cedar-14 TESTFOLDER=test/run-deps + - STACK=cedar-14 TESTFOLDER=test/run-deps USE_STAGING_BINARIES=https://lang-python.s3.amazonaws.com/staging + - STACK=cedar-14 TESTFOLDER=test/run-versions + - STACK=cedar-14 TESTFOLDER=test/run-versions USE_STAGING_BINARIES=https://lang-python.s3.amazonaws.com/staging + - STACK=cedar-14 TESTFOLDER=test/run-features + - STACK=cedar-14 TESTFOLDER=test/run-features USE_STAGING_BINARIES=https://lang-python.s3.amazonaws.com/staging + - STACK=heroku-16 TESTFOLDER=test/run-deps + - STACK=heroku-16 TESTFOLDER=test/run-deps USE_STAGING_BINARIES=https://lang-python.s3.amazonaws.com/staging + - STACK=heroku-16 TESTFOLDER=test/run-versions + - STACK=heroku-16 TESTFOLDER=test/run-versions USE_STAGING_BINARIES=https://lang-python.s3.amazonaws.com/staging + - STACK=heroku-16 TESTFOLDER=test/run-features + - STACK=heroku-16 TESTFOLDER=test/run-features USE_STAGING_BINARIES=https://lang-python.s3.amazonaws.com/staging + - STACK=heroku-18 TESTFOLDER=test/run-deps + - STACK=heroku-18 TESTFOLDER=test/run-deps USE_STAGING_BINARIES=https://lang-python.s3.amazonaws.com/staging + - STACK=heroku-18 TESTFOLDER=test/run-versions + - STACK=heroku-18 TESTFOLDER=test/run-versions USE_STAGING_BINARIES=https://lang-python.s3.amazonaws.com/staging + - STACK=heroku-18 TESTFOLDER=test/run-features + - STACK=heroku-18 TESTFOLDER=test/run-features USE_STAGING_BINARIES=https://lang-python.s3.amazonaws.com/staging global: - HATCHET_RETRIES=3 - IS_RUNNING_ON_CI=true - HATCHET_APP_LIMIT=5 - HATCHET_DEPLOY_STRATEGY=git - - secure: yjtlPE5FbVxTKnjUy/tZUBgSEf4qADD3QOxtgziuid73S0U/1IEXlMGFULsQzIjtlHKmHeywZqpVVEpthIH4RuT7uoX1Pb7SSM/g0T8fT3VoEFbFK1uYl0oZQbUS4Klxv9tPiumj8if3m6ULEGIz1X0wZcMOC0tMLwVCnwmap0E= - - secure: ZeFTHWwnpIKE9nAqs88ocmiQh7bKce84lilGm5J23nf3N6V4wNyLwqlkvsM008WGBCaOg9AUx7ZunasT0ANsR5gLP3eV2UUg7ILdRgV2Gy13eNRFheC4PHdN92RqQ3aKoqlIv2K999xlhVjod0NzhkQQXB6PddfQINbuU7ks6As= + - secure: aICPFKFCmu+OWynYd0QRJVU37Rw3GTCxy3PePj5DSDx3/2S2i0BXXLhn/LECdMY98h24O0xL8k860+gy646Mjq8Cpaf16BUNJhWH5HWS50paymREhnrYd/twSh0i6M2dnlbCjXDHWc8n+64/vu4FmS1UhLw2nQBMCrcIRcxHBpPPHIy3F4pctGBb1LEaExmA3JQRDUoX6uzgt/68gjMDPfvUwM5lzBwRLOdpUwNOvIlYxKioxcqjhMAFuHNo3na50QzPlNE2dqt84iDRj4N7/7v6NzWhG/9O1TxkcVCjn8ZhRIsFQoIEW32X0tqGCsSMpKiR+h6PjGrt+y07xjiRKQPNoFdhjpASO7sGpPrENsOxN263XJPmea9iGJrAvLBYePHdhblzwrXfcM0285RxRJTZg6o7BFW16+PkurpuS7dGvX/7SYAv3IXCksWBbDk+RtpS5KVmYrv6MI+fuGdSFVRq+RS1zmWwaXD1zpc2N4hpV2ZnwI/uwuDwrTd60ou2pXLxttrIw9dqq44CmY5vAu69Fu4dw2BkmUhGUf8ApG6TTgqrt5rdO2W11D4qr/YNZ9Jydh3m+Fa2//JRAGSq0SSW+eqIjCU7D1kqR2lgQD5TdC7mYRFyWGF4YQ8V573Yo0tlL/zQzt07YuyfwIWkb2+AQyZh90cFs+O7T8vg99s= + - secure: cu0TCp7HFTCOJTOwQRm1ZhWL86JawLeaUR0/Jg9ThutphfGWNV8nOfgHUG/PEwgb0jwJz34c0DdfpwCdVuDmMsqptmA9I2Lu59BZidi87dqi+PuvfaUvSM69mPABPxaTE34iFtzpWtab+KsnDd/TG87KVk/1AmK1OjA4LKBkJOqo2XkkSFgE94QM04JRSaRiRD8qiLrXTlzkRWsMh1gGnj5kOP0jozBBWbjIMU42XCd9liei7eHoh3ZmFDLOatfIwhCkadG8vBMD5nV0UDCHt2+U6+YGC8cVI+nyjXNAMuOUXZ3GowQRCtIxokVgzRINVAcmDOc40i/gfy9p7HbU3G2FC9wNo98EKecF2GjYtbQdjEgBfijN4nFYi1R/D5CMl4Qo5GtGDkRPO1E626AoHTTtfpIP6MDtjLWTrriiobW8ZuV852jyl/lWJc/O2wYho/34kSER47r5+4398F+9BUbySLzJ9f3/8eNtPIsQunrgO93/PyfkS/2O+7Amov+NPMQ0JoUQuVJt/aHsrFCgbcHR+tOeT7eYYi7mxhLHsteMbRLfCHIIkg3FDEfPITOLgArKj6l9faTJiawLMG9xMMp3Wi6SYPvosDz6gwYDr+8CHeEn0UoUltYM/msMLiGZ0t8oi2DWjvRdLQe9qfC5N77mtcsI1pWEcyc9SOwrIBs= diff --git a/Makefile b/Makefile index bf7c5c7cc..e3da5678f 100644 --- a/Makefile +++ b/Makefile @@ -10,17 +10,17 @@ check: test-cedar-14: @echo "Running tests in docker (cedar-14)..." - @docker run -v $(shell pwd):/buildpack:ro --rm -it -e "STACK=cedar-14" heroku/cedar:14 bash -c 'cp -r /buildpack /buildpack_test; cd /buildpack_test/; test/run-deps; test/run-features; test/run-versions;' + @docker run -v $(shell pwd):/buildpack:ro --rm -it -e "STACK=cedar-14" -e TESTS=$(TESTS) heroku/cedar:14 bash -c 'cp -r /buildpack /buildpack_test; cd /buildpack_test/; test/run-deps; test/run-features; test/run-versions;' @echo "" test-heroku-16: @echo "Running tests in docker (heroku-16)..." - @docker run -v $(shell pwd):/buildpack:ro --rm -it -e "STACK=heroku-16" heroku/heroku:16-build bash -c 'cp -r /buildpack /buildpack_test; cd /buildpack_test/; test/run-deps; test/run-features; test/run-versions;' + @docker run -v $(shell pwd):/buildpack:ro --rm -it -e "STACK=heroku-16" -e TESTS=$(TESTS) heroku/heroku:16-build bash -c 'cp -r /buildpack /buildpack_test; cd /buildpack_test/; test/run-deps; test/run-features; test/run-versions;' @echo "" test-heroku-18: @echo "Running tests in docker (heroku-18)..." - @docker run -v $(shell pwd):/buildpack:ro --rm -it -e "STACK=heroku-18" heroku/heroku:18-build bash -c 'cp -r /buildpack /buildpack_test; cd /buildpack_test/; test/run-deps; test/run-features; test/run-versions;' + @docker run -v $(shell pwd):/buildpack:ro --rm -it -e "STACK=heroku-18" -e TESTS=$(TESTS) heroku/heroku:18-build bash -c 'cp -r /buildpack /buildpack_test; cd /buildpack_test/; test/run-deps; test/run-features; test/run-versions;' @echo "" buildenv-heroku-16: diff --git a/README.md b/README.md index e3ea62a25..4a6fdb8d1 100644 --- a/README.md +++ b/README.md @@ -83,6 +83,12 @@ make test-heroku-18 make test-heroku-16 ``` +You can also specify which tests to run: + +``` +make test TESTS="testPython3_7 testGitEgg" +``` + The tests are run via the vendored [shunit2](https://github.com/kward/shunit2) test framework. diff --git a/bin/compile b/bin/compile index 9319463af..6e30856b0 100755 --- a/bin/compile +++ b/bin/compile @@ -62,7 +62,25 @@ PY27="python-2.7" # Which stack is used (for binary downloading), if none is provided (e.g. outside of Heroku)? DEFAULT_PYTHON_STACK="cedar-14" # If pip doesn't match this version (the version we install), run the installer. -PIP_UPDATE="9.0.2" +PIP_UPDATE="19.2.3" + +for file in "$BUILD_DIR/runtime.txt" "$CACHE_DIR/.heroku/python-version" ; do + [ -f "$file" ] || continue + + version=$(tr -d '[:space:]' < "$file") + + case "$version" in "$PY34"*) + # Python 3.4 support was dropped in pip >= 19.2. + PIP_UPDATE="19.1.1" + break + ;; + esac +done + +if [[ -f "$BUILD_DIR/Pipfile" ]]; then + # Do not force pipenv users to re-install pipenv locally. + PIP_UPDATE="9.0.2" +fi export DEFAULT_PYTHON_STACK PIP_UPDATE export PY37 PY36 PY35 PY27 PY34 @@ -251,8 +269,13 @@ mtime "pip.uninstall.time" "${start}" # This allows for people to ship a setup.py application to Heroku # (which is rare, but I vouch that it should work!) -if [ ! -f requirements.txt ] && [ ! -f Pipfile ]; then - echo "-e ." > requirements.txt +if [ ! -f requirements.txt ] && [ ! -f Pipfile ] ; then + if [ -f pyproject.toml ] ; then + # Editable installs are not supported for pyproject.toml-style projects. + echo "." > requirements.txt + else + echo "-e ." > requirements.txt + fi fi # Fix egg-links. diff --git a/bin/detect b/bin/detect index eeb965b0f..682585fc5 100755 --- a/bin/detect +++ b/bin/detect @@ -15,7 +15,7 @@ BUILD_DIR=$1 # Exit early if app is clearly not Python. -if [ ! -f "$BUILD_DIR/requirements.txt" ] && [ ! -f "$BUILD_DIR/setup.py" ] && [ ! -f "$BUILD_DIR/Pipfile" ]; then +if [ ! -f "$BUILD_DIR/requirements.txt" ] && [ ! -f "$BUILD_DIR/setup.py" ] && [ ! -f "$BUILD_DIR/Pipfile" ] && [ ! -f "$BUILD_DIR/pyproject.toml" ]; then exit 1 fi diff --git a/test/fixtures/flit-requires/foobar.py b/test/fixtures/flit-requires/foobar.py new file mode 100644 index 000000000..4b4c67818 --- /dev/null +++ b/test/fixtures/flit-requires/foobar.py @@ -0,0 +1,3 @@ +"""An amazing sample package!""" + +__version__ = '0.1' diff --git a/test/fixtures/flit-requires/pyproject.toml b/test/fixtures/flit-requires/pyproject.toml new file mode 100644 index 000000000..61cc974f1 --- /dev/null +++ b/test/fixtures/flit-requires/pyproject.toml @@ -0,0 +1,10 @@ +[build-system] +requires = ["flit"] +build-backend = "flit.buildapi" + +[tool.flit.metadata] +module = "foobar" +author = "Sir Robin" +author-email = "robin@camelot.uk" +home-page = "https://github.com/sirrobin/foobar" +requires = ["attrs >=19.1.0"] diff --git a/test/fixtures/flit/foobar.py b/test/fixtures/flit/foobar.py new file mode 100644 index 000000000..4b4c67818 --- /dev/null +++ b/test/fixtures/flit/foobar.py @@ -0,0 +1,3 @@ +"""An amazing sample package!""" + +__version__ = '0.1' diff --git a/test/fixtures/flit/pyproject.toml b/test/fixtures/flit/pyproject.toml new file mode 100644 index 000000000..3dca8c957 --- /dev/null +++ b/test/fixtures/flit/pyproject.toml @@ -0,0 +1,9 @@ +[build-system] +requires = ["flit"] +build-backend = "flit.buildapi" + +[tool.flit.metadata] +module = "foobar" +author = "Sir Robin" +author-email = "robin@camelot.uk" +home-page = "https://github.com/sirrobin/foobar" diff --git a/test/fixtures/poetry-lock/foobar.py b/test/fixtures/poetry-lock/foobar.py new file mode 100644 index 000000000..e69de29bb diff --git a/test/fixtures/poetry-lock/poetry.lock b/test/fixtures/poetry-lock/poetry.lock new file mode 100644 index 000000000..3eb160325 --- /dev/null +++ b/test/fixtures/poetry-lock/poetry.lock @@ -0,0 +1,19 @@ +[[package]] +category = "main" +description = "Classes Without Boilerplate" +name = "attrs" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "19.1.0" + +[package.extras] +dev = ["coverage", "hypothesis", "pympler", "pytest", "six", "zope.interface", "sphinx", "pre-commit"] +docs = ["sphinx", "zope.interface"] +tests = ["coverage", "hypothesis", "pympler", "pytest", "six", "zope.interface"] + +[metadata] +content-hash = "9c92739040d45f898575877e69198d92bb9477423fe80c60a14376bf1f3d010e" +python-versions = "^3.7" + +[metadata.hashes] +attrs = ["69c0dbf2ed392de1cb5ec704444b08a5ef81680a61cb899dc08127123af36a79", "f0b870f674851ecbfbbbd364d6b5cbdff9dcedbc7f3f5e18a6891057f21fe399"] diff --git a/test/fixtures/poetry-lock/pyproject.toml b/test/fixtures/poetry-lock/pyproject.toml new file mode 100644 index 000000000..0a3fd420a --- /dev/null +++ b/test/fixtures/poetry-lock/pyproject.toml @@ -0,0 +1,15 @@ +[tool.poetry] +name = "foobar" +version = "0.1.0" +description = "" +authors = ["Your Name "] + +[tool.poetry.dependencies] +python = "^3.6" +attrs = "^19.1.0" + +[tool.poetry.dev-dependencies] + +[build-system] +requires = ["poetry>=0.12"] +build-backend = "poetry.masonry.api" diff --git a/test/fixtures/poetry/foobar.py b/test/fixtures/poetry/foobar.py new file mode 100644 index 000000000..e69de29bb diff --git a/test/fixtures/poetry/pyproject.toml b/test/fixtures/poetry/pyproject.toml new file mode 100644 index 000000000..4074cf7f7 --- /dev/null +++ b/test/fixtures/poetry/pyproject.toml @@ -0,0 +1,14 @@ +[tool.poetry] +name = "foobar" +version = "0.1.0" +description = "" +authors = ["Your Name "] + +[tool.poetry.dependencies] +python = "^3.6" + +[tool.poetry.dev-dependencies] + +[build-system] +requires = ["poetry>=0.12"] +build-backend = "poetry.masonry.api" diff --git a/test/fixtures/pyproject-toml/pyproject.toml b/test/fixtures/pyproject-toml/pyproject.toml new file mode 100644 index 000000000..864b334a8 --- /dev/null +++ b/test/fixtures/pyproject-toml/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["setuptools", "wheel"] +build-backend = "setuptools.build_meta:__legacy__" diff --git a/test/fixtures/pyproject-toml/setup.py b/test/fixtures/pyproject-toml/setup.py new file mode 100644 index 000000000..4d986d25d --- /dev/null +++ b/test/fixtures/pyproject-toml/setup.py @@ -0,0 +1,4 @@ +from setuptools import setup + + +setup(name="foobar", version="1.0.0") diff --git a/test/run-features b/test/run-features index 66fac4ea7..3c7b168ff 100755 --- a/test/run-features +++ b/test/run-features @@ -70,6 +70,31 @@ testPipenvFullVersion() { assertCapturedSuccess } +testPyProjectToml() { + compile "pyproject-toml" + assertCapturedSuccess +} + +testPoetry() { + compile "poetry" + assertCapturedSuccess +} + +testPoetryLock() { + compile "poetry-lock" + assertCapturedSuccess +} + +testFlit() { + compile "flit" + assertCapturedSuccess +} + +testFlitRequires() { + compile "flit-requires" + assertCapturedSuccess +} + testNoRequirements() { compile "no-requirements" assertCapturedError diff --git a/test/utils b/test/utils index ff22b7400..e2822c311 100644 --- a/test/utils +++ b/test/utils @@ -264,3 +264,22 @@ release() { assertFile() { assertEquals "$1" "$(cat ${compile_dir}/$2)" } + +# If TESTS is present in the environment, only run the specified tests. +if [ -n "$TESTS" ]; then + suite() { + for shunit_func_ in ${TESTS}; do + if declare -F ${shunit_func_} > /dev/null; then + suite_addTest ${shunit_func_} + fi + done + unset shunit_func_ + + if [ -z "$__shunit_suite" ]; then + testNoop() { + echo "Skipping tests because they do not match TESTS." + } + suite_addTest testNoop + fi + } +fi diff --git a/vendor/pip-pop/pip-diff b/vendor/pip-pop/pip-diff index 2bb1877c8..8fc7d6f98 100755 --- a/vendor/pip-pop/pip-diff +++ b/vendor/pip-pop/pip-diff @@ -12,9 +12,24 @@ Options: """ import os from docopt import docopt -from pip.req import parse_requirements -from pip.index import PackageFinder -from pip._vendor.requests import session + +try: # pip >= 10 + from pip._internal.req import parse_requirements + from pip._internal.download import PipSession as session + + def PackageFinder(find_links, index_urls, session=None): + from pip._internal.index import PackageFinder + from pip._internal.models.search_scope import SearchScope + from pip._internal.models.selection_prefs import SelectionPreferences + + search_scope = SearchScope.create(find_links, index_urls) + selection_prefs = SelectionPreferences(allow_yanked=False) + return PackageFinder.create(search_scope, selection_prefs, session=session) + +except ImportError: # pip <= 9.0.3 + from pip.req import parse_requirements + from pip.index import PackageFinder + from pip._vendor.requests import session requests = session() diff --git a/vendor/pip-pop/pip-grep b/vendor/pip-pop/pip-grep index d55000ad8..8e3cf6111 100755 --- a/vendor/pip-pop/pip-grep +++ b/vendor/pip-pop/pip-grep @@ -10,9 +10,25 @@ Options: import os import sys from docopt import docopt -from pip.req import parse_requirements -from pip.index import PackageFinder -from pip._vendor.requests import session + +try: # pip >= 10 + from pip._internal.req import parse_requirements + from pip._internal.download import PipSession as session + + def PackageFinder(find_links, index_urls, session=None): + from pip._internal.index import PackageFinder + from pip._internal.models.search_scope import SearchScope + from pip._internal.models.selection_prefs import SelectionPreferences + + search_scope = SearchScope.create(find_links, index_urls) + selection_prefs = SelectionPreferences(allow_yanked=False) + return PackageFinder.create(search_scope, selection_prefs, session=session) + +except ImportError: # pip <= 9.0.3 + from pip.req import parse_requirements + from pip.index import PackageFinder + from pip._vendor.requests import session + requests = session()