[
  {
    "path": ".gitattributes",
    "content": "*.venv linguist-language=Makefile\n"
  },
  {
    "path": ".github/workflows/test.yml",
    "content": "name: Run automated tests\non:\n  push:\n    paths-ignore:\n      - '**.md'\n      - '.git*'\n  pull_request:\n  schedule:\n    - cron: '17 7 9,19,29 * *'\n\n\njobs:\n  linux:\n    name: linux-py${{ matrix.py }}\n    runs-on: ubuntu-latest\n    container: python:${{ matrix.py }}\n    strategy:\n      matrix:\n        py:\n          - '3.9'\n          - '3.10'\n          - '3.11'\n          - '3.12'\n    steps:\n      - uses: actions/checkout@v3\n        with:\n          fetch-depth: 0\n\n      - name: CI setup\n        run: |-\n          git config --global --add safe.directory $(pwd)\n\n      - name: Run functional tests\n        run:  make -C tests test-verbose\n        env:\n          TEST_SUBPROCESS_TIMEOUT: 300\n\n      - name: Run packaging tests\n        run:  make -C pypi test\n\n  other:\n    name: ${{ matrix.os }}\n    runs-on: ${{ matrix.os }}\n    strategy:\n      matrix:\n        os:\n          - windows-latest\n          - macos-latest\n    steps:\n      - uses: actions/checkout@v3\n        with:\n          fetch-depth: 0\n\n      - name: Remove py.exe on Windows\n        run: del /f %WINDIR%\\py.exe\n        shell: cmd\n        if: matrix.os == 'windows-latest'\n\n      - name: Configure Windows environment variables\n        run: |\n          echo \"PY=python\" >> $GITHUB_ENV\n          echo \"TEMP=C:\\tmp\" >> $GITHUB_ENV  # remove when pip>22.0.3 is released\n          mkdir -p \"C:\\tmp\"                  # see https://github.com/sio/Makefile.venv/issues/17\n        shell: bash\n        if: matrix.os == 'windows-latest'\n\n      - name: Run functional tests\n        run:  make -C tests test-verbose\n        env:\n          TEST_SUBPROCESS_TIMEOUT: 300\n\n      - name: Run packaging tests\n        run:  make -C pypi test\n\n  cygwin:\n    name: windows-cygwin\n    runs-on: windows-latest\n    env:\n      CYGWIN_ROOT: D:\\cygwin\n      CYGWIN_PACKAGES: \"\\\n        make,\\\n        python3,\\\n        git,\\\n        bash,\\\n        python-pip-wheel,\\\n        python-setuptools-wheel,\\\n        python-wheel-wheel\"\n      CYGWIN_MIRROR: http://mirrors.kernel.org/sourceware/cygwin/\n      LC_ALL: C.UTF-8\n      LANG: C.UTF-8\n      SHELLOPTS: igncr\n    steps:\n      - uses: actions/checkout@v3\n        with:\n          fetch-depth: 0\n      - name: Install Cygwin\n        run: >\n          md %CYGWIN_ROOT% &&\n          choco install cygwin --params='\"/InstallDir:%CYGWIN_ROOT%\"' &&\n          %CYGWIN_ROOT%\\cygwinsetup.exe\n          --quiet-mode\n          --no-desktop\n          --download\n          --local-install\n          --no-verify\n          --site \"%CYGWIN_MIRROR%\"\n          --local-package-dir \"%CYGWIN_ROOT%\"\n          --root \"%CYGWIN_ROOT%\"\n          --packages \"%CYGWIN_PACKAGES%\"\n        shell: cmd\n      - name: Workaround for repo permissions mixup (https://stackoverflow.com/questions/7184941)\n        shell: D:\\cygwin\\bin\\bash.exe \"{0}\"\n        run: |\n          set -v\n          export PATH=/bin\n          set -euo pipefail\n          chown $(id -u):$(id -g) .\n          mkdir -p \"$HOME\"\n          touch \"$HOME/.gitconfig\"\n          git config --global --add safe.directory \"$PWD\"\n      - name: Run automated tests\n        shell: D:\\cygwin\\bin\\bash.exe \"{0}\"\n        run: |\n          export PATH=/bin\n          make -C tests test-verbose\n        env:\n          TEST_SUBPROCESS_TIMEOUT: 300\n      - name: Failure diagnostics\n        run: |\n         find \"$CYGWIN_ROOT\" -type f\n         env\n        shell: bash\n        if: failure()\n"
  },
  {
    "path": ".gitignore",
    "content": "# Used for testing \"include Makefile.venv\"\n/Makefile\n\n# Python tests\n*.pyc\n\n# Packaging\n*.egg-info\n.venv/\n.tox/\n/pypi/src/Makefile_venv/LICENSE\n/pypi/src/Makefile_venv/Makefile.venv\n/pypi/src/Makefile_venv/README.md\n/pypi/dist/\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Changelog for Makefile.venv\n\n<!--Template for new entries\n\n\n## CURRENT\n\n*\n*\n\n[Source code tree](https://github.com/sio/Makefile.venv/tree/CURRENT)\n| [Tarball](https://github.com/sio/Makefile.venv/tarball/CURRENT)\n| [Commit history](https://github.com/sio/Makefile.venv/compare/PREVIOUS...CURRENT)\n-->\n\n\n## v2023.04.17\n\n* Added support for [pyproject.toml]\n  (thanks to [@sla-te], issue [#22])\n\n[pyproject.toml]: https://pip.pypa.io/en/stable/reference/build-system/pyproject-toml/\n[@sla-te]: https://github.com/sla-te\n[#22]: https://github.com/sio/Makefile.venv/issues/22\n\n[Source code tree](https://github.com/sio/Makefile.venv/tree/v2023.04.17)\n| [Tarball](https://github.com/sio/Makefile.venv/tarball/v2023.04.17)\n| [Commit history](https://github.com/sio/Makefile.venv/compare/v2022.07.20...v2023.04.17)\n\n\n## v2022.07.20\n\n* Support another edge case: \"make -R\" (running without builtin variables)\n* Minor documentation improvements\n\n[Source code tree](https://github.com/sio/Makefile.venv/tree/v2022.07.20)\n| [Tarball](https://github.com/sio/Makefile.venv/tarball/v2022.07.20)\n| [Commit history](https://github.com/sio/Makefile.venv/compare/v2022.04.13...v2022.07.20)\n\n\n## v2022.04.13\n\n* Makefile.venv is now installable from [PyPI](https://pypi.org/project/Makefile.venv/)\n* No changes to Makefile.venv itself\n\n[Source code tree](https://github.com/sio/Makefile.venv/tree/v2022.04.13)\n| [Tarball](https://github.com/sio/Makefile.venv/tarball/v2022.04.13)\n| [Commit history](https://github.com/sio/Makefile.venv/compare/v2021.12.16...v2022.04.13)\n\n\n## v2021.12.16\n\n* Added support for `py` entrypoint on Windows (thanks to [@gschwaer], issue [#15])\n* Improved cross-platform support: now Windows without a POSIX shell should\n  work too (wrappers for `cmd.exe` missing capabilities were added)\n* Minor improvements to debug messages and test suite common tools\n\n[@gschwaer]: https://github.com/gschwaer\n[#15]: https://github.com/sio/Makefile.venv/issues/15\n\n[Source code tree](https://github.com/sio/Makefile.venv/tree/v2021.12.16)\n| [Tarball](https://github.com/sio/Makefile.venv/tarball/v2021.12.16)\n| [Commit history](https://github.com/sio/Makefile.venv/compare/v2021.12.01...v2021.12.16)\n\n\n## v2021.12.01\n\n* Move setup․py path to variable (SETUP_PY): now multiple paths are supported\n  and setup․py processing may be skipped by providing empty value (issue [#14])\n* Improve documentation and extend test suite\n\n[#14]: https://github.com/sio/Makefile.venv/issues/14\n\n[Source code tree](https://github.com/sio/Makefile.venv/tree/v2021.12.01)\n| [Tarball](https://github.com/sio/Makefile.venv/tarball/v2021.12.01)\n| [Commit history](https://github.com/sio/Makefile.venv/compare/v2020.08.14...v2021.12.01)\n\n\n## v2020.08.14\n\n* Install 'wheel' package automatically when creating virtual environment\n\n[Source code tree](https://github.com/sio/Makefile.venv/tree/v2020.08.14)\n| [Tarball](https://github.com/sio/Makefile.venv/tarball/v2020.08.14)\n| [Commit history](https://github.com/sio/Makefile.venv/compare/v2020.08.04...v2020.08.14)\n\n\n## v2020.08.04\n\n* Allow REQUIREMENTS_TXT to be generated with a Makefile recipe\n* Improve documentation\n\n[Source code tree](https://github.com/sio/Makefile.venv/tree/v2020.08.04)\n| [Tarball](https://github.com/sio/Makefile.venv/tarball/v2020.08.04)\n| [Commit history](https://github.com/sio/Makefile.venv/compare/v2020.05.07...v2020.08.04)\n\n\n## v2020.05.07\n\n* Add `debug-venv` target for troubleshooting\n\n[Source code tree](https://github.com/sio/Makefile.venv/tree/v2020.05.07)\n| [Tarball](https://github.com/sio/Makefile.venv/tarball/v2020.05.07)\n| [Commit history](https://github.com/sio/Makefile.venv/compare/v2020.05.05...v2020.05.07)\n\n\n## v2020.05.05\n\n* Mark paths with spaces as unsupported\n* Do not convert WORKDIR into absolute path\n* Sanitize path when removing VENVDIR - avoid destructive consequences of\n  paths with spaces\n\n[Source code tree](https://github.com/sio/Makefile.venv/tree/v2020.05.05)\n| [Tarball](https://github.com/sio/Makefile.venv/tarball/v2020.05.05)\n| [Commit history](https://github.com/sio/Makefile.venv/compare/v2020.02.26...v2020.05.05)\n\n\n## v2020.02.26\n\n* Update setuptools when creating venv\n* Trigger venv update on setup.cfg changes\n\n[Source code tree](https://github.com/sio/Makefile.venv/tree/v2020.02.26)\n| [Tarball](https://github.com/sio/Makefile.venv/tarball/v2020.02.26)\n| [Commit history](https://github.com/sio/Makefile.venv/compare/v2019.12.05...v2020.02.26)\n\n\n## v2019.12.05\n\n* Use Python to detect if Windows paths are required. This helps to avoid\n  mistakes when using different combinations of Cygwin/native Windows\n  environments. Thanks to [@jpc4242](https://github.com/jpc4242)\n\n[Source code tree](https://github.com/sio/Makefile.venv/tree/v2019.12.05)\n| [Tarball](https://github.com/sio/Makefile.venv/tarball/v2019.12.05)\n| [Commit history](https://github.com/sio/Makefile.venv/compare/v2019.12.04...v2019.12.05)\n\n\n## v2019.12.04\n\n* New configuration variable: FORCE_UNIX_PATHS. If this variable is set,\n  unix-like file paths are assumed and no Windows detection takes place.\n  Thanks to [@jpc4242](https://github.com/jpc4242) for reporting\n  [the issue](https://github.com/sio/Makefile.venv/issues/2) with Cygwin.\n\n[Source code tree](https://github.com/sio/Makefile.venv/tree/v2019.12.04)\n| [Tarball](https://github.com/sio/Makefile.venv/tarball/v2019.12.04)\n| [Commit history](https://github.com/sio/Makefile.venv/compare/v2019.11.22...v2019.12.04)\n\n\n## v2019.11.22\n\n* Upgrade pip only at initial environment creation. This helps to avoid build\n  failures with old Python versions where pip can not be upgraded to newer\n  releases. [Example](https://circleci.com/gh/sio/bash-complete-partial-path/53)\n\n[Source code tree](https://github.com/sio/Makefile.venv/tree/v2019.11.22)\n| [Tarball](https://github.com/sio/Makefile.venv/tarball/v2019.11.22)\n| [Commit history](https://github.com/sio/Makefile.venv/compare/v2019.11.08...v2019.11.22)\n\n\n## v2019.11.08\n\n* Support multiple requirements.txt files via REQUIREMENTS_TXT environment\n  variable\n\n[Source code tree](https://github.com/sio/Makefile.venv/tree/v2019.11.08)\n| [Tarball](https://github.com/sio/Makefile.venv/tarball/v2019.11.08)\n| [Commit history](https://github.com/sio/Makefile.venv/compare/v2019.11.07...v2019.11.08)\n\n\n## v2019.11.07\n\n* Virtual environment creation happens only once. Dependencies change does not\n  trigger a redundant call to `-m venv` if environment already exists.\n\n[Source code tree](https://github.com/sio/Makefile.venv/tree/v2019.11.07)\n| [Tarball](https://github.com/sio/Makefile.venv/tarball/v2019.11.07)\n| [Commit history](https://github.com/sio/Makefile.venv/compare/v2019.11.06...v2019.11.07)\n\n\n## v2019.11.06\n\n* New pattern rule for rarely used dependencies (CLI tools in virtual\n  environment)\n* Improved code readability and documentation\n\n[Source code tree](https://github.com/sio/Makefile.venv/tree/v2019.11.06)\n| [Tarball](https://github.com/sio/Makefile.venv/tarball/v2019.11.06)\n| [Commit history](https://github.com/sio/Makefile.venv/compare/v2019.10.04...v2019.11.06)\n\n\n## v2019.10.04\n\n* Automated testing for new releases with GitHub CI\n* Deduplicated code for interactive shell targets\n\n[Source code tree](https://github.com/sio/Makefile.venv/tree/v2019.10.04)\n| [Tarball](https://github.com/sio/Makefile.venv/tarball/v2019.10.04)\n| [Commit history](https://github.com/sio/Makefile.venv/compare/v2019.10.03...v2019.10.04)\n\n\n## v2019.10.03\n\n* Cleaner process tree thanks to launching interactive shells via `exec`\n\n[Source code tree](https://github.com/sio/Makefile.venv/tree/v2019.10.03)\n| [Tarball](https://github.com/sio/Makefile.venv/tarball/v2019.10.03)\n| [Commit history](https://github.com/sio/Makefile.venv/compare/v2019.10.01...v2019.10.03)\n\n\n## v2019.10.01\n\n* New targets for interactive shells in virtual environment:\n  `make bash`, `make zsh`\n* Promotional post in author's blog: [https://potyarkin.com/...][blog]\n\n[blog]: https://potyarkin.com/posts/2019/manage-python-virtual-environment-from-your-makefile/\n\n[Source code tree](https://github.com/sio/Makefile.venv/tree/v2019.10.01)\n| [Tarball](https://github.com/sio/Makefile.venv/tarball/v2019.10.01)\n| [Commit history](https://github.com/sio/Makefile.venv/compare/v2019.09.30...v2019.10.01)\n\n\n## v2019.09.30\n\n* First reusable version of Makefile.venv. All essential features are available.\n\n[Source code tree](https://github.com/sio/Makefile.venv/tree/v2019.09.30)\n| [Tarball](https://github.com/sio/Makefile.venv/tarball/v2019.09.30)\n| [Commit history](https://github.com/sio/Makefile.venv/compare/9c9b6d5aae8955d207d5c9d45b754c01c20be650...v2019.09.30)\n"
  },
  {
    "path": "LICENSE",
    "content": "                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"{}\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright {yyyy} {name of copyright owner}\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "Makefile.venv",
    "content": "#\n# SEAMLESSLY MANAGE PYTHON VIRTUAL ENVIRONMENT WITH A MAKEFILE\n#\n# https://github.com/sio/Makefile.venv             v2023.04.17\n#\n#\n# Insert `include Makefile.venv` at the bottom of your Makefile to enable these\n# rules.\n#\n# When writing your Makefile use '$(VENV)/python' to refer to the Python\n# interpreter within virtual environment and '$(VENV)/executablename' for any\n# other executable in venv.\n#\n# This Makefile provides the following targets:\n#   venv\n#       Use this as a dependency for any target that requires virtual\n#       environment to be created and configured\n#   python, ipython\n#       Use these to launch interactive Python shell within virtual environment\n#   shell, bash, zsh\n#       Launch interactive command line shell. \"shell\" target launches the\n#       default shell Makefile executes its rules in (usually /bin/sh).\n#       \"bash\" and \"zsh\" can be used to refer to the specific desired shell.\n#   show-venv\n#       Show versions of Python and pip, and the path to the virtual environment\n#   clean-venv\n#       Remove virtual environment\n#   $(VENV)/executable_name\n#       Install `executable_name` with pip. Only packages with names matching\n#       the name of the corresponding executable are supported.\n#       Use this as a lightweight mechanism for development dependencies\n#       tracking. E.g. for one-off tools that are not required in every\n#       developer's environment, therefore are not included into\n#       requirements.txt or setup.py.\n#       Note:\n#           Rules using such target or dependency MUST be defined below\n#           `include` directive to make use of correct $(VENV) value.\n#       Example:\n#           codestyle: $(VENV)/pyflakes\n#               $(VENV)/pyflakes .\n#       See `ipython` target below for another example.\n#\n# This Makefile can be configured via following variables:\n#   PY\n#       Command name for system Python interpreter. It is used only initially to\n#       create the virtual environment\n#       Default: python3\n#   REQUIREMENTS_TXT\n#       Space separated list of paths to requirements.txt files.\n#       Paths are resolved relative to current working directory.\n#       Default: requirements.txt\n#\n#       Non-existent files are treated as hard dependencies,\n#       recipes for creating such files must be provided by the main Makefile.\n#       Providing empty value (REQUIREMENTS_TXT=) turns off processing of\n#       requirements.txt even when the file exists.\n#   SETUP_PY, SETUP_CFG, PYPROJECT_TOML, VENV_LOCAL_PACKAGE\n#       Space separated list of paths to files that contain build instructions\n#       for local Python packages. Corresponding packages will be installed\n#       into venv in editable mode along with all their dependencies.\n#       Default: setup.py setup.cfg pyproject.toml (whichever present)\n#\n#       Non-existent and empty values are treated in the same way as for REQUIREMENTS_TXT.\n#   WORKDIR\n#       Parent directory for the virtual environment.\n#       Default: current working directory.\n#   VENVDIR\n#       Python virtual environment directory.\n#       Default: $(WORKDIR)/.venv\n#\n# This Makefile was written for GNU Make and may not work with other make\n# implementations.\n#\n#\n# Copyright (c) 2019-2023 Vitaly Potyarkin\n#\n# Licensed under the Apache License, Version 2.0\n#    <http://www.apache.org/licenses/LICENSE-2.0>\n#\n\n\n#\n# Configuration variables\n#\n\nWORKDIR?=.\nVENVDIR?=$(WORKDIR)/.venv\nREQUIREMENTS_TXT?=$(wildcard requirements.txt)  # Multiple paths are supported (space separated)\nSETUP_PY?=$(wildcard setup.py)                  # Multiple paths are supported (space separated)\nSETUP_CFG?=$(foreach s,$(SETUP_PY),$(wildcard $(patsubst %setup.py,%setup.cfg,$(s))))\nPYPROJECT_TOML?=$(wildcard pyproject.toml)\nVENV_LOCAL_PACKAGE?=$(SETUP_PY) $(SETUP_CFG) $(PYPROJECT_TOML)\nMARKER=.initialized-with-Makefile.venv\n\n\n#\n# Python interpreter detection\n#\n\n_PY_AUTODETECT_MSG=Detected Python interpreter: $(PY). Use PY environment variable to override\n\nifeq (ok,$(shell test -e /dev/null 2>&1 && echo ok))\nNULL_STDERR=2>/dev/null\nelse\nNULL_STDERR=2>NUL\nendif\n\nifndef PY\n_PY_OPTION:=python3\nifeq (ok,$(shell $(_PY_OPTION) -c \"print('ok')\" $(NULL_STDERR)))\nPY=$(_PY_OPTION)\nendif\nendif\n\nifndef PY\n_PY_OPTION:=$(VENVDIR)/bin/python\nifeq (ok,$(shell $(_PY_OPTION) -c \"print('ok')\" $(NULL_STDERR)))\nPY=$(_PY_OPTION)\n$(info $(_PY_AUTODETECT_MSG))\nendif\nendif\n\nifndef PY\n_PY_OPTION:=$(subst /,\\,$(VENVDIR)/Scripts/python)\nifeq (ok,$(shell $(_PY_OPTION) -c \"print('ok')\" $(NULL_STDERR)))\nPY=$(_PY_OPTION)\n$(info $(_PY_AUTODETECT_MSG))\nendif\nendif\n\nifndef PY\n_PY_OPTION:=py -3\nifeq (ok,$(shell $(_PY_OPTION) -c \"print('ok')\" $(NULL_STDERR)))\nPY=$(_PY_OPTION)\n$(info $(_PY_AUTODETECT_MSG))\nendif\nendif\n\nifndef PY\n_PY_OPTION:=python\nifeq (ok,$(shell $(_PY_OPTION) -c \"print('ok')\" $(NULL_STDERR)))\nPY=$(_PY_OPTION)\n$(info $(_PY_AUTODETECT_MSG))\nendif\nendif\n\nifndef PY\ndefine _PY_AUTODETECT_ERR\nCould not detect Python interpreter automatically.\nPlease specify path to interpreter via PY environment variable.\nendef\n$(error $(_PY_AUTODETECT_ERR))\nendif\n\n\n#\n# Internal variable resolution\n#\n\nVENV=$(VENVDIR)/bin\nEXE=\n# Detect windows\nifeq (win32,$(shell $(PY) -c \"import __future__, sys; print(sys.platform)\"))\nVENV=$(VENVDIR)/Scripts\nEXE=.exe\nendif\n\ntouch=touch $(1)\nifeq (,$(shell command -v touch $(NULL_STDERR)))\n# https://ss64.com/nt/touch.html\ntouch=type nul >> $(subst /,\\,$(1)) && copy /y /b $(subst /,\\,$(1))+,, $(subst /,\\,$(1))\nendif\n\nRM?=rm -f\nifeq (,$(shell command -v $(firstword $(RM)) $(NULL_STDERR)))\nRMDIR:=rd /s /q\nelse\nRMDIR:=$(RM) -r\nendif\n\n\n#\n# Virtual environment\n#\n\n.PHONY: venv\nvenv: $(VENV)/$(MARKER)\n\n.PHONY: clean-venv\nclean-venv:\n\t-$(RMDIR) \"$(VENVDIR)\"\n\n.PHONY: show-venv\nshow-venv: venv\n\t@$(VENV)/python -c \"import sys; print('Python ' + sys.version.replace('\\n',''))\"\n\t@$(VENV)/pip --version\n\t@echo venv: $(VENVDIR)\n\n.PHONY: debug-venv\ndebug-venv:\n\t@echo \"PATH (Shell)=$$PATH\"\n\t@$(MAKE) --version\n\t$(info PATH (GNU Make)=\"$(PATH)\")\n\t$(info SHELL=\"$(SHELL)\")\n\t$(info PY=\"$(PY)\")\n\t$(info REQUIREMENTS_TXT=\"$(REQUIREMENTS_TXT)\")\n\t$(info VENV_LOCAL_PACKAGE=\"$(VENV_LOCAL_PACKAGE)\")\n\t$(info VENVDIR=\"$(VENVDIR)\")\n\t$(info VENVDEPENDS=\"$(VENVDEPENDS)\")\n\t$(info WORKDIR=\"$(WORKDIR)\")\n\n\n#\n# Dependencies\n#\n\nifneq ($(strip $(REQUIREMENTS_TXT)),)\nVENVDEPENDS+=$(REQUIREMENTS_TXT)\nendif\n\nifneq ($(strip $(VENV_LOCAL_PACKAGE)),)\nVENVDEPENDS+=$(VENV_LOCAL_PACKAGE)\nendif\n\n$(VENV):\n\t$(PY) -m venv $(VENVDIR)\n\t$(VENV)/python -m pip install --upgrade pip setuptools wheel\n\n$(VENV)/$(MARKER): $(VENVDEPENDS) | $(VENV)\nifneq ($(strip $(REQUIREMENTS_TXT)),)\n\t$(VENV)/pip install $(foreach path,$(REQUIREMENTS_TXT),-r $(path))\nendif\nifneq ($(strip $(VENV_LOCAL_PACKAGE)),)\n\t$(VENV)/pip install $(foreach path,$(sort $(VENV_LOCAL_PACKAGE)),-e $(dir $(path)))\nendif\n\t$(call touch,$(VENV)/$(MARKER))\n\n\n#\n# Interactive shells\n#\n\n.PHONY: python\npython: venv\n\texec $(VENV)/python\n\n.PHONY: ipython\nipython: $(VENV)/ipython\n\texec $(VENV)/ipython\n\n.PHONY: shell\nshell: venv\n\t. $(VENV)/activate && exec $(notdir $(SHELL))\n\n.PHONY: bash zsh\nbash zsh: venv\n\t. $(VENV)/activate && exec $@\n\n\n#\n# Commandline tools (wildcard rule, executable name must match package name)\n#\n\nifneq ($(EXE),)\n$(VENV)/%: $(VENV)/%$(EXE) ;\n.PHONY:    $(VENV)/%\n.PRECIOUS: $(VENV)/%$(EXE)\nendif\n\n$(VENV)/%$(EXE): $(VENV)/$(MARKER)\n\t$(VENV)/pip install --upgrade $*\n\t$(call touch,$@)\n"
  },
  {
    "path": "README.md",
    "content": "# Seamlessly manage Python virtual environment with a Makefile\n\n*Makefile.venv* takes care of creating, updating and invoking Python virtual\nenvironment that you can use in your Makefiles. It will allow you to reduce\nvenv related routines to almost zero!\n\n[![test status][badge]][tests]\n\n[badge]: https://github.com/sio/Makefile.venv/workflows/Run%20automated%20tests/badge.svg\n[tests]: https://github.com/sio/Makefile.venv/actions?query=branch%3Amaster+\n\n*Makefile.venv* aims to be an one-stop solution for Python virtual environment\nmanagement, regardless of the format used to define the venv: requirements.txt\nand setup․py are supported out of the box because they have become de-facto\nstandards, but if anything else will take their place - Makefile.venv will\nsupport that too. [Pip-compile], pipenv and poetry are compatible but require\nsome configuration.\n\n\n## Installation\n\n### Recommended method\n\nCopy [*Makefile.venv*](Makefile.venv) to your project directory and add\ninclude statement to the bottom of your `Makefile`:\n\n```make\ninclude Makefile.venv\n```\n\n### Alternative method\n\nAlternatively, you can add installation actions as the Makefile recipe:\n\n> **Note the checksum step!** Do not skip it, it would be as bad as [piping curl\n> to shell](https://0x46.net/thoughts/2019/04/27/piping-curl-to-shell/)!\n\n```make\ninclude Makefile.venv\nMakefile.venv:\n\tcurl \\\n\t\t-o Makefile.fetched \\\n\t\t-L \"https://github.com/sio/Makefile.venv/raw/v2023.04.17/Makefile.venv\"\n\techo \"fb48375ed1fd19e41e0cdcf51a4a0c6d1010dfe03b672ffc4c26a91878544f82 *Makefile.fetched\" \\\n\t\t| sha256sum --check - \\\n\t\t&& mv Makefile.fetched Makefile.venv\n```\n\n> Notes:\n>\n> * *curl* and/or *sha256sum* may not be available by default depending on what\n>   OS and configuration is used\n> * To install *sha256sum* on macOS use `brew install coreutils`\n> * You can use Perl's *shasum -a 256* instead of *sha256sum*, as described\n>   [here](https://github.com/sio/Makefile.venv/issues/11).\n\n### Another alternative method\n\nIf you want to use *Makefile.venv* in multiple projects and to be able to\nconveniently manage *Makefile.venv* version from one place you might find this\n[pip package] useful:\n\n- Install globally: `pip install Makefile.venv` or\n- Install for current user: `pip install --user Makefile.venv`\n\nThis package will install *Makefile.venv* into your `site-packages/` and will\nadd a command-line entrypoint which prints the full path to *Makefile.venv*.\nInclude it it in your makefiles like this:\n\n```make\ninclude $(shell Makefile.venv)\n```\n\n[pip package]: https://pypi.org/project/Makefile.venv/\n\n\n## Usage\n\nWhen writing your Makefile use `$(VENV)/python` to refer to the Python\ninterpreter within virtual environment and `$(VENV)/executablename` for any\nother executable in venv.\n\n*Makefile.venv* is not meant to be used as a standalone tool, think of it more\nlike a library that enables extra functionality.\n\n\n## Demo screencast\n\n<a href=\"https://asciinema.org/a/279646\" target=\"_blank\">\n<img src=\"https://asciinema.org/a/279646.svg\" title=\"Demo screencast\"/>\n</a>\n\n\n## Targets\n\n*Makefile.venv* provides the following targets. Some are meant to be executed\ndirectly via `make $target`, some are meant to be dependencies for other\ntargets written by you.\n\n##### venv\n\nUse this as a dependency for any target that requires virtual environment to\nbe created and configured.\n\n*venv* is a .PHONY target and rules that depend on it will be executed every\ntime make is run. This behavior is sensible as default because most Python\nprojects use Makefiles for running development chores, not for artifact\nbuilding. In cases where that is not desirable use [order-only prerequisite]\nsyntax:\n\n```make\nartifacts.tar.gz: | venv\n\t...\n```\n\n[order-only prerequisite]: https://www.gnu.org/software/make/manual/html_node/Prerequisite-Types.html\n\n##### python, ipython\n\nExecute these targets to launch interactive Python shell within virtual\nenvironment. Ipython is not installed by default when creating the virtual\nenvironment but will be installed automatically when called for the first\ntime.\n\n##### shell, bash, zsh\n\nExecute these targets to launch interactive command line shell. `shell` target\nlaunches the default shell Makefile executes its rules in (usually /bin/sh).\n`bash` and `zsh` can be used to refer to the specific desired shell (if it's\ninstalled).\n\n##### show-venv\n\nExecute this target to show versions of Python and pip, and the path to the\nvirtual environment. Use this for debugging purposes.\n\n##### clean-venv\n\nExecute this target to remove virtual environment. You can add this as a\ndependency to the `clean` target in your main Makefile.\n\n##### $(VENV)/executablename\n\nUse this target as a dependency for tasks that need `executablename` to be\ninstalled if `executablename` is not listed as project's dependency neither in\n`requirements.txt` nor in `setup.py`. Only packages with names matching the\nname of the corresponding executable are supported.\n\nThis can be a lightweight mechanism for development dependencies tracking.\nE.g. for one-off tools that are not required in every developer's\nenvironment, therefore are not included in formal dependency lists.\n\n**Note:** Rules using such dependency MUST be defined below\n`include` directive to make use of correct $(VENV) value.\n\nExample (see `ipython` target for another example):\n\n```Makefile\ncodestyle: $(VENV)/pyflakes  # `venv` dependency is assumed and may be omitted\n\t$(VENV)/pyflakes .\n```\n\n## Variables\n\n*Makefile.venv* can be configured via following variables:\n\n##### PY\n\nCommand name for system Python interpreter. It is used only initially to\ncreate the virtual environment. *Default: python3*\n\n##### REQUIREMENTS_TXT\n\nSpace separated list of paths to requirements.txt files.\nPaths are resolved relative to current working directory.\n*Default: requirements.txt*\n\nNon-existent files are treated as hard dependencies, recipes for creating such\nfiles must be provided by the main Makefile (sample usage: [pip-compile]).\nProviding empty value (`REQUIREMENTS_TXT=`) turns off processing of\nrequirements.txt even when the file exists.\n\n[pip-compile]: docs/howto-pip-compile.md\n\n##### SETUP_PY, SETUP_CFG, PYPROJECT_TOML, VENV_LOCAL_PACKAGE\n\nSpace separated list of paths to files that contain build instructions\nfor local Python packages. Corresponding packages will be installed\ninto venv in [editable mode] along with all their dependencies.\n*Default: setup.py setup.cfg pyproject.toml (whichever present)*\n\nNon-existent and empty values are treated in the same way as for REQUIREMENTS_TXT.\n\n[editable mode]: https://pip.pypa.io/en/stable/cli/pip_install/#editable-installs\n\n##### WORKDIR\n\nParent directory for the virtual environment. *Default: current working\ndirectory*\n\n##### VENVDIR\n\nPython virtual environment directory. *Default: $(WORKDIR)/.venv*\n\n##### PIP_*\n\nVariables named starting with `PIP_` are not processed by *Makefile.venv* in\nany way and are passed to underlying pip calls as is. See [pip\ndocumentation](https://pip.pypa.io/en/stable/user_guide/#environment-variables)\nfor more information.\n\nUse these variables to customize pip invocation, for example to provide custom\npackage index url:\n\n```\nPIP_EXTRA_INDEX_URL=\"https://your.index/url\"\n```\n\n\n## Samples\n\nMakefile:\n\n```make\n.PHONY: test\ntest: venv\n\t$(VENV)/python -m unittest\n\ninclude Makefile.venv\n```\n\nLarger sample from a real project can be seen\n[here](https://github.com/sio/issyours/blob/master/Makefile).\nAlso see [an introductory blog\npost](https://potyarkin.com/posts/2019/manage-python-virtual-environment-from-your-makefile/)\nfrom project author.\n\nCommand line:\n\n```\n$ make test\n\n...Skipped: creating and updating virtual environment...\n\n...\n----------------------------------------------------------------------\nRan 3 tests in 0.000s\n\nOK\n```\n```\n$ make show-venv\nPython 3.5.4 (v3.5.4:3f56838, Aug  8 2017, 02:07:06) [MSC v.1900 32 bit (Intel)]\npip 19.2.3 from c:\\users\\99e7~1\\appdata\\local\\temp\\.venv\\lib\\site-packages\\pip (python 3.5)\nvenv: C:\\Users\\99E7~1\\AppData\\Local\\Temp\\.venv\n```\n```\n$ make python\nC:/Users/99E7~1/AppData/Local/Temp/.venv/Scripts/python\nPython 3.5.4 (v3.5.4:3f56838, Aug  8 2017, 02:07:06) [MSC v.1900 32 bit (Intel)] on win32\nType \"help\", \"copyright\", \"credits\" or \"license\" for more information.\n>>> _\n```\n\n\n## Compatibility\n\n*Makefile.venv* was written for GNU Make and may not work with other make\nimplementations. Please be aware that GNU Make [can not correctly handle][spaces]\nwhitespace characters in file paths. Such filepaths therefore are\nconsidered unsupported by *Makefile.venv*\n\n[spaces]: https://stackoverflow.com/questions/9838384/can-gnu-make-handle-filenames-with-spaces\n\n*Makefile.venv* is being [continuously tested][tests] on Linux, Windows and macOS. Any\ninconsistency encountered when running on Windows should be considered a bug\nand should be reported via [issues].\n\n\n## Support and contributing\n\nIf you need help with using this Makefile or including it into your project,\nplease create **[an issue][issues]**.\nIssues are also the primary venue for reporting bugs and posting feature\nrequests. General discussion related to this project is also acceptable and\nvery welcome!\n\nIn case you wish to contribute code or documentation, feel free to open\n**[a pull request](https://github.com/sio/Makefile.venv/pulls)**. That would\ncertainly make my day!\n\nI'm open to dialog and I promise to behave responsibly and treat all\ncontributors with respect. Please try to do the same, and treat others the way\nyou want to be treated.\n\nIf for some reason you'd rather not use the issue tracker, contacting me via\nemail is OK too. Please use a descriptive subject line to enhance visibility\nof your message. Also please keep in mind that public discussion channels are\npreferable because that way many other people may benefit from reading past\nconversations. My email is visible under the GitHub profile and in the commit\nlog.\n\n[issues]: https://github.com/sio/Makefile.venv/issues\n\n\n## License and copyright\n\nCopyright 2019-2023 Vitaly Potyarkin\n\n    Licensed under the Apache License, Version 2.0 (the \"License\");\n    you may not use this file except in compliance with the License.\n    You may obtain a copy of the License at\n\n        http://www.apache.org/licenses/LICENSE-2.0\n\n    Unless required by applicable law or agreed to in writing, software\n    distributed under the License is distributed on an \"AS IS\" BASIS,\n    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n    See the License for the specific language governing permissions and\n    limitations under the License.\n"
  },
  {
    "path": "docs/README.md",
    "content": "# Documentation for Makefile.venv\n\n## Usage\n\nGeneral usage is described in [README.md](../README.md). If you feel that some\ntopics require a dedicated documentation entry please create an [issue] or\nbetter yet, a [pull request].\n\n[issue]: https://github.com/sio/Makefile.venv/issues\n[pull request]: https://github.com/sio/Makefile.venv/pulls\n\n\n## How-tos\n\n- [Integrating pip-compile into Makefile.venv workflow](howto-pip-compile.md)\n- [Using custom Makefile recipe to create virtual environment](howto-custom-venv-recipe.md)\n"
  },
  {
    "path": "docs/howto-custom-venv-recipe.md",
    "content": "# Using custom Makefile recipe to create virtual environment\n\nSometimes assumptions made by *Makefile.venv* about how virtual environment\nshould be created seem too rigid. Using venv module from standard library and\nalways installing the latest version of pip, setuptools and wheel is a sane\ndefault, but users may want to:\n\n- Use another tool to create the virtual environment ([virtualenv] anyone?)\n  *or*\n- Pass some [extra command line arguments] to venv module\n  *or*\n- Specify which [versions of pip/setuptools/wheel] to use instead of latest.\n\nThere is a way to do all of that.\n\nYou can provide an alternative recipe for venv creation after you include\n*Makefile.venv*:\n\n```makefile\nCUSTOM_INITIAL_PACKAGES=path/to/your/requirements-for-pip-wheel-setuptools.txt\n$(VENV):\n\t$(PY) -m venv $(VENVDIR)\n\t$(VENV)/python -m pip install -r $(CUSTOM_INITIAL_PACKAGES)\n```\n\nHere is [another example] where the last supported pip version is used when\noutdated Python interpreter is detected:\n\n```makefile\n# Override default venv packages for older Python versions\nifeq (True,$(shell $(PY) -c \"import sys; print(sys.version_info < (3,5))\"))\n$(VENV):\n\t$(PY) -m venv $(VENVDIR)\n\t$(VENV)/python -m pip install --upgrade \"pip<19.2\" \"setuptools<44.0\" \"wheel<0.34\"\nendif\n```\n\nIt works because GNU Make allows to [redefine the recipe] for any target later\nin the Makefile. Downside is that this usage is not officially endorsed and\nGNU Make will spit out some warnings:\n\n```\nMakefile:46: warning: overriding recipe for target '.venv/bin'\nMakefile.venv:137: warning: ignoring old recipe for target '.venv/bin'\n```\n\nDespite the small drawback this solution allows for endless customization at\npractically zero cost. There is no way we could predict all oddly specific\nrequests that users may come up with, let alone add variables and feature\nflags to support those. But with custom recipes users can define their venv\nwhichever way they need.\n\n[Alternative suggestion] with `*-default` targets was considered and rejected\nbecause of being too invasive and producing unexpected outcomes in some\nscenarios.\nSee more [here](https://github.com/sio/Makefile.venv/issues/13#issuecomment-928932526)\n\n[virtualenv]: https://pypi.org/project/virtualenv/\n[extra command line arguments]: https://github.com/sio/Makefile.venv/pull/10\n[versions of pip/setuptools/wheel]: https://github.com/sio/Makefile.venv/issues/13\n[another example]: https://github.com/sio/bash-complete-partial-path/blob/2be6ef1f1885d3cb1ec2547ae41d78aa66f4ab78/Makefile#L42-L48\n[redefine the recipe]: https://www.gnu.org/software/make/manual/html_node/Multiple-Rules.html\n[Alternative suggestion]: https://stackoverflow.com/questions/11958626/m/49804748#49804748\n\n---\n\n> Thanks to [@belm0] and [@simaoafonso-pwt] for bringing this to author's attention!\n\n[@belm0]: https://github.com/belm0\n[@simaoafonso-pwt]: https://github.com/simaoafonso-pwt\n\n---\n\n> Automated tests ensure that this workflow will not break in the future:\n> [tests/test_recipe_override.py]\n\n[tests/test_recipe_override.py]: ../tests/test_recipe_override.py\n"
  },
  {
    "path": "docs/howto-pip-compile.md",
    "content": "# Using pip-compile with Makefile.venv\n\n---\n\n> **Note:** *Described workflow is possible only with v2020.08.04 and newer*\n\n---\n\n*Makefile.venv* supports many non-trivial use cases. One such case is using\n`pip-compile` installed into a virtual environment to generate requirements\nfile that defines that very environment.\n\nAs always with *Makefile.venv* all venv related routines are handled\nautomatically and are totally transparent to user.\n\nThe task at hand is twofold:\n\n- We need to automatically generate `requirements.txt` with a Makefile recipe\n- We want to use a tool from virtual environment before that virtual\n  environment is finalized by *Makefile.venv*\n\nHere is how it can be done:\n\n```Makefile\n# We need to explicitly specify this value because by default it's ok for\n# requirements.txt to be missing. Providing any non-default value tells\n# Makefile.venv that the files listed must exist or be made from recipe.\n# This statement MUST come before `include Makefile.venv`\nREQUIREMENTS_TXT=requirements.txt\n\n\ninclude Makefile.venv\n\n\n# Save pip-compile path to variable for brevity\n# We may omit $(EXE) suffix if we're ok with Windows builds being broken\nPIP_COMPILE=$(VENV)/pip-compile$(EXE)\n\n\n# This and the next recipe MUST be defined after include statement.\n# We need to inject pip-compile into virtual environment\n# before Makefile.venv finishes working on it, but after venv is created.\n# There already exists a target we can add as dependency for this case:\n$(PIP_COMPILE): | $(VENV)\n\t$(VENV)/pip install pip-tools  # or any other installation method\n\ttouch $@\n\n\n# Our requirements file directly depends upon *.in file and also requires\n# pip-compile to be available\nrequirements.txt: requirements.in | $(PIP_COMPILE)\n\t$(PIP_COMPILE) --output-file $@ $<\n```\n\nAnd that's all. The rest of the Makefile should be written as usual when\nworking with *Makefile.venv*: depend on `venv`, call `$(VENV)/entrypoints`,\netc. The trick is to use `$(VENV)` as dependency for `$(PIP_COMPILE)` - that\nway we can avoid circular dependency and pip-compile will be installed after\nbare venv is created but before *Makefile.venv* attempts to fill it with\npackages from 'requirements.txt'.\n\nFor example, after adding this rule to the Makefile above:\n\n```Makefile\nall: venv\n    $(VENV)/pip freeze\n```\n\nwe can execute `make all` in a folder with only the makefiles and\n`requirements.in` - and both `requirements.txt` and full virtual environment\ndescribed by it will be created automatically upon first invocation.\n\n---\n\n> Thanks to [@belm0] for [bringing this](https://github.com/sio/Makefile.venv/issues/8)\n> to author's attention!\n\n[@belm0]: https://github.com/belm0\n\n---\n\n> Automated tests ensure that this workflow will not break in the future:\n> [tests/test_pip_compile.py]\n\n[tests/test_pip_compile.py]: ../tests/test_pip_compile.py\n\n"
  },
  {
    "path": "docs/howto-pipenv.md",
    "content": "# Integrating pipenv into Makefile.venv workflow\n\nIt's possible, but this article has not been written yet.\n\nSee [pip-compile how-to](howto-pip-compile.md) to get a rough idea on how to\nproceed.\n\n> TODO: write pipenv how-to\n"
  },
  {
    "path": "docs/howto-poetry.md",
    "content": "# Integrating poetry into Makefile.venv workflow\n\nIt's possible, but this article has not been written yet.\n\nSee [pip-compile how-to](howto-pip-compile.md) to get a rough idea on how to\nproceed.\n\n> TODO: write poetry how-to\n"
  },
  {
    "path": "pypi/Makefile",
    "content": "PACKAGE_ROOT=src/Makefile_venv\nPACKAGE_DATA=\\\n\t$(PACKAGE_ROOT)/LICENSE \\\n\t$(PACKAGE_ROOT)/Makefile.venv \\\n\t$(PACKAGE_ROOT)/README.md\n\n\ninclude ../Makefile.venv\n\n\n.PHONY: package build\npackage build: dist\n\n\ndist: setup.cfg pyproject.toml Makefile\ndist: $(PACKAGE_DATA)\ndist: | $(VENV)/build\n\t-$(RM) -rv dist\n\t$(VENV)/python -m build\n\n\n$(PACKAGE_ROOT)/%: ../%\n\tcp $< $@\n\n\n.PHONY: upload\nupload: dist | $(VENV)/twine\n\t$(VENV)/twine upload --repository testpypi $(TWINE_ARGS) dist/*\n\t$(VENV)/twine upload $(TWINE_ARGS) dist/*\n\n\n.PHONY: test\ntest: $(PACKAGE_DATA)\ntest: | $(VENV)/tox\n\t$(VENV)/tox $(TOX_ARGS)\n\n\n.PHONY: clean\nclean:\n\tgit clean -idx\n"
  },
  {
    "path": "pypi/README.md",
    "content": "# Packaging Makefile.venv for PyPI\n\nThis directory contains helper code and configuration files used to build a\nPython package for *Makefile.venv* and to upload that package to PyPI.\n\nIt is assumed that *Makefile.venv* itself is in a working state when working\non packaging, so we depend on it in multiple places here.\n\nThis is also the reason why packaging tests are stored separately from the\nrest of test suite: we can not depend on *Makefile.venv* in the main test\nsuite, but it's too useful to forego when doing auxiliary work.\n"
  },
  {
    "path": "pypi/pyproject.toml",
    "content": "[build-system]\nrequires = [\n    \"setuptools>=45\",\n    \"setuptools_scm>=6.2\",\n    \"wheel\",\n]\nbuild-backend = \"setuptools.build_meta\"\n\n\n[tool.setuptools_scm]\nroot = \"..\"\n"
  },
  {
    "path": "pypi/setup.cfg",
    "content": "[metadata]\nname = Makefile.venv\nurl = https://github.com/sio/Makefile.venv\ndescription = Seamlessly manage Python virtual environment with a Makefile\nlong_description = file: src/Makefile_venv/README.md\nlong_description_content_type = text/markdown\nkeywords = makefile, virtualenv, env\nlicense = Apache-2.0\nlicense_file = src/Makefile_venv/LICENSE\nclassifiers =\n    Environment :: Console\n    Intended Audience :: Developers\n    Intended Audience :: System Administrators\n    License :: OSI Approved :: Apache Software License\n\n\n[options]\nzip_safe = False\ninclude_package_data = True\npackages = Makefile_venv\npackage_dir =\n    =src\n\n\n[options.package_data]\n* = Makefile.venv, *.md\n\n\n[options.entry_points]\nconsole_scripts =\n    Makefile.venv = Makefile_venv:cli\n"
  },
  {
    "path": "pypi/src/Makefile_venv/__init__.py",
    "content": "'''\nPython entrypoint that returns absolute path to Makefile.venv\n'''\n\ntry:\n    from importlib.resources import as_file, files\n    def path():\n        with as_file(files(__name__).joinpath('Makefile.venv')) as f:\n            return str(f.resolve())\nexcept (ImportError, ModuleNotFoundError) as e:\n    import pkg_resources\n    def path():\n        return pkg_resources.resource_filename(__name__, 'Makefile.venv')\n\n\ndef cli():\n    '''Print Makefile.venv path to stdout'''\n    print(path())\n"
  },
  {
    "path": "pypi/tests/test_installable.py",
    "content": "import pytest\n\nfrom subprocess import run\nfrom pathlib import Path\n\nimport Makefile_venv\n\n\ndef test_makefile_is_installed():\n    makefile = Path(Makefile_venv.path())\n    assert makefile.name == 'Makefile.venv'\n    assert makefile.exists()\n    with open(makefile, 'r') as f:\n        content = f.read()\n        assert 'SEAMLESSLY MANAGE PYTHON VIRTUAL ENVIRONMENT WITH A MAKEFILE' in content\n\n\ndef test_shell_entrypoint():\n    shell = run(['Makefile.venv'], capture_output=True, check=True, text=True, shell=True)\n    assert Makefile_venv.path() == shell.stdout.strip()\n"
  },
  {
    "path": "pypi/tox.ini",
    "content": "[tox]\nenvlist =\n    pypi-latest\nisolated_build = True\n\n\n[testenv]\ndeps =\n    pytest\ncommands =\n    pytest -rA --color=yes -vv\n"
  },
  {
    "path": "tests/Makefile",
    "content": "PY?=python3\nREPODIR:=$(dir $(firstword $(MAKEFILE_LIST)))..\nARGS?=\n\n.PHONY: test\ntest: deps\n\tcd $(REPODIR) && $(PY) -m unittest $(ARGS)\n\n.PHONY: deps\ndeps:\n\t@-pwd\n\t@$(PY) -c \"import sys; print('Python ' + sys.version.replace('\\n',''))\"\n\t@$(PY) -c \"import platform; print(platform.platform())\"\n\t@$(MAKE) --version\n\t@-git --version\n\t@echo SHELL=$(SHELL)\n\ntest-fast: export SKIP_SLOW_TESTS=1\ntest-fast: test\n\ntest-verbose: ARGS+=-v\ntest-verbose: test\n"
  },
  {
    "path": "tests/README.md",
    "content": "# Some sanity checks for Makefile.venv\n\nExecute `python -m unittest` from the repo's top directory\nor run `make` from the `tests/` directory.\n\nSlow tests can be skipped by setting `SKIP_SLOW_TESTS` environment variable,\ne.g. `make SKIP_SLOW_TESTS=1` (shortcut: `make test-fast`)\n"
  },
  {
    "path": "tests/__init__.py",
    "content": "'''\nSome sanity checks for Makefile.venv\n'''\n"
  },
  {
    "path": "tests/common.py",
    "content": "'''\nCommon utilities for testing Makefile.venv\n'''\n\n\nimport os\nimport sys\nfrom shutil import copyfile\nfrom subprocess import run, PIPE\nfrom tempfile import TemporaryDirectory\nfrom unittest import TestCase, skipIf\n\n\nslow_test = skipIf(\n    os.environ.get('SKIP_SLOW_TESTS', False),\n    'slow test'\n)\n\n\nclass MakefileTestCase(TestCase):\n    '''Base class for Makefile.venv tests'''\n\n    MAKEFILE = 'Makefile.venv'\n    MAKE = 'make'\n    TIMEOUT = int(os.getenv('TEST_SUBPROCESS_TIMEOUT', 60)) # seconds\n    TMPPREFIX = 'Makefile.venv_test_'\n\n    def __init__(self, *a, **ka):\n        self.env = os.environ.copy()\n        self.env.update(dict(\n            LANG='C',\n        ))\n        for variable in (\n                # PY is intentionally passed through because test runner may\n                # be using non-standard interpreter command/path\n                'REQUIREMENTS_TXT',\n                'SETUP_CFG',\n                'SETUP_PY',\n                'VENV',\n                'VENVDEPENDS',\n                'VENVDIR',\n                'WORKDIR',\n        ):\n            if variable in self.env:  # Clear environment variables for tests\n                self.env.pop(variable)\n        return super().__init__(*a, **ka)\n\n\n    def make(self, *args, makefile=None, debug=False, dry_run=False, returncode=0):\n        '''Execute Makefile.venv with GNU Make in temporary directory'''\n        if makefile is None:\n            makefile = self.MAKEFILE\n        command = [self.MAKE, '-C', self.tmpdir.name, '-f', os.path.abspath(makefile)]\n        if debug:\n            command.append('-drR')\n        if dry_run:\n            command.append('-n')\n        command.extend(args)\n\n        process = run(command, stdout=PIPE, stderr=PIPE, timeout=self.TIMEOUT, text=True)\n        if returncode is not None:\n            self.check_returncode(process, returncode)\n        return process\n\n    def check_returncode(self, process, returncode=0):\n        '''Check subprocess return code in automated tests'''\n        self.assertEqual(\n            process.returncode,\n            returncode,\n            msg='\\n'.join(\n                part for part in (\n                    '{} exited with code {} (expected {})'.format(self.MAKE, process.returncode, returncode),\n                    '\\nstdout:',\n                    process.stdout,\n                    'stderr:',\n                    process.stderr,\n                )\n                if part.strip()\n            )\n        )\n\n    def copy(self, filename=None, content=None, makefile=False, data_dir='tests/data', dest_dir=None):\n        '''Copy test data to temporary directory. Return full path to resulting file'''\n        if not any([filename, content, makefile]):\n            raise ValueError('At least one of parameters must be provided: filename, content, makefile')\n        if content is None:\n            src = os.path.join(data_dir, filename)\n            with open(src) as source:\n                content = source.read()\n        if makefile and not filename:\n            filename = 'Makefile'\n        if dest_dir:\n            dest_dir = os.path.join(self.tmpdir.name, dest_dir)\n            if not os.path.exists(dest_dir):\n                os.mkdir(dest_dir)\n            dest = os.path.join(dest_dir, filename)\n        else:\n            dest = os.path.join(self.tmpdir.name, filename)\n        if makefile:\n            content = content.replace(\n                    '{{ Makefile.venv }}',\n                    os.path.abspath(self.MAKEFILE)\n                )\n        with open(dest, 'w') as output:\n            output.write(content)\n        return dest\n\n    def setUp(self):\n        self.tmpdir = TemporaryDirectory(prefix=self.TMPPREFIX)\n        os.environ.clear()\n        os.environ.update(self.env)\n\n    def tearDown(self):\n        self.tmpdir.cleanup()\n        del self.tmpdir\n        os.environ.clear()\n        os.environ.update(self.env)\n"
  },
  {
    "path": "tests/data/dependencies.mk",
    "content": ".DEFAULT: hello\n\ninclude {{ Makefile.venv }}\n\nhello: venv\n\t$(VENV)/python hello.py\n\nfreeze: venv\n\t$(VENV)/pip freeze\n\noneoff: $(VENV)/pyflakes\n\t$(VENV)/pyflakes --help\n"
  },
  {
    "path": "tests/data/hello.py",
    "content": "import pyfiglet\nprint('hello')\n"
  },
  {
    "path": "tests/data/pip-compile.mk",
    "content": "all: venv\n\t$(VENV)/pyflakes --help\n\n\nclean: clean-venv\n\t-$(RM) Makefile.venv requirements.txt\n\n\n# You need to explicitly specify this value because by default it's ok for\n# requirements.txt to be missing. This statement MUST come before `include\n# Makefile.venv`\nREQUIREMENTS_TXT=requirements.txt  requirements.txt   # add trailing whitespace\n\n\n# Save pip-compile path to variable for brevity\nPIP_COMPILE=$(VENV)/pip-compile$(EXE)\n\n\ninclude {{ Makefile.venv }}\n\n\n# You need to inject pip-compile into virtual environment\n# before Makefile.venv finishes working on it, but after venv is created.\n# There already exists a target you can add as dependency for this case:\n$(PIP_COMPILE): | $(VENV)\n\t$(VENV)/pip install pip-tools\n\t$(call touch,$@)\n\n\n# Your requirements file directly depends upon *.in file and also requires\n# pip-compile to be available\nrequirements.txt: requirements.in | $(PIP_COMPILE)\n\t$(PIP_COMPILE) --output-file $@ $<\n"
  },
  {
    "path": "tests/data/pyproject.toml",
    "content": "[project]\nname = \"hello\"\nversion = \"0.0.1\"\ndependencies = [\n    \"pyfiglet\",\n]\n"
  },
  {
    "path": "tests/data/recipe-override.mk",
    "content": "# Makefile for tests/test_recipe_override.py\n\ninclude {{ Makefile.venv }}\n\nCUSTOM_PACKAGE=noop==1.0\n\n$(VENV):\n\t$(PY) -m venv $(VENVDIR)\n\t$(VENV)/python -m pip install --upgrade pip setuptools wheel $(CUSTOM_PACKAGE)\n\nfreeze: venv\n\t$(VENV)/pip freeze\n"
  },
  {
    "path": "tests/data/requirements-extra.txt",
    "content": "console\n"
  },
  {
    "path": "tests/data/requirements.in",
    "content": "pyflakes\n"
  },
  {
    "path": "tests/data/requirements.txt",
    "content": "pyfiglet\n"
  },
  {
    "path": "tests/data/setup.py",
    "content": "from setuptools import setup\n\nsetup(\n    name='hello',\n    py_modules=['hello'],\n    entry_points={\n        'console_scripts': [\n            'hello = hello',\n        ],\n    },\n    install_requires=[\n        'pyfiglet',\n    ],\n)\n"
  },
  {
    "path": "tests/test_dependencies.py",
    "content": "import os\nfrom pathlib import Path\nfrom time import sleep\n\nfrom tests.common import MakefileTestCase, slow_test\n\n\ndef touch(filepath):\n    Path(filepath).touch()\n\n\nclass TestDependencies(MakefileTestCase):\n\n    @slow_test\n    def test_requirements_txt(self):\n        '''Check that requirements.txt is being processed correctly'''\n        self.common_dependency_checks('requirements.txt')\n\n    @slow_test\n    def test_setup_py(self):\n        '''Check that setup.py is being processed correctly'''\n        self.common_dependency_checks('setup.py')\n\n    @slow_test\n    def test_pyproject_toml(self):\n        '''Check that pyproject.toml is being processed correctly'''\n        self.common_dependency_checks('pyproject.toml')\n\n    @slow_test\n    def test_setup_cfg(self):\n        '''Check that setup.cfg is being processed correctly'''\n        # Repeat setup.py test\n        makefile = self.common_dependency_checks('setup.py')\n\n        # Creating setup.cfg must trigger venv rebuild\n        setup_cfg = Path(makefile).parent / 'setup.cfg'\n        touch(setup_cfg)\n        make = self.make('hello', makefile=makefile)\n        self.assertIn('/pip install -e', make.stdout)\n\n        # But only one rebuild\n        make = self.make('hello', makefile=makefile, dry_run=True)\n        self.assertNotIn('/pip install -e', make.stdout)\n\n    def common_dependency_checks(self, dependency_list):\n        '''Generic unit test for setup.py and requirements.txt'''\n        dependencies = self.copy(dependency_list)\n        hello = self.copy('hello.py')\n        makefile = self.copy('dependencies.mk', makefile=True)\n\n        # Create virtual environment with specified dependencies\n        make = self.make('hello', makefile=makefile)\n        for line in make.stdout.split('\\n'):\n            if line.strip() == 'hello':\n                break\n        else:\n            raise AssertionError(\"'hello' not found in make stdout:\\n{}\".format(make.stdout))\n        self.assertIn('Collecting pyfiglet', make.stdout)\n\n        # Second invocation must not trigger dependencies installation\n        second = self.make('hello', makefile=makefile)\n        self.assertNotIn('Collecting pyfiglet', second.stdout)\n\n        # When dependencies list was modified, venv has to be updated\n        sleep(1)  # Ensure that timestamps differ significantly\n        touch(dependencies)\n        third = self.make('hello', makefile=makefile, dry_run=True)\n        self.assertTrue(any('pip install %s' % x in third.stdout for x in {'-r', '-e'}))\n        return makefile\n\n    @slow_test\n    def test_requirements_txt_multiple(self):\n        '''Check that multiple requirements.txt files are supported'''\n        data = ['requirements.txt', 'requirements-extra.txt', 'hello.py']\n        for name in data:\n            self.copy(name)\n        makefile = self.copy('dependencies.mk', makefile=True)\n\n        os.environ['REQUIREMENTS_TXT'] = 'requirements.txt requirements-extra.txt'\n        self.make('hello', makefile=makefile)\n        freeze = self.make('freeze', makefile=makefile)\n        for package in ['pyfiglet', 'console']:\n            with self.subTest(package=package):\n                self.assertIn('%s==' % package, freeze.stdout)\n        for word in ['Collecting', 'installing', 'installed', 'cache']:\n            with self.subTest(word=word):\n                self.assertNotIn(word.lower(), freeze.stdout.lower())\n\n    @slow_test\n    def test_one_off(self):\n        '''Check that one-off requirements are supported'''\n        makefile = self.copy('dependencies.mk', makefile=True)\n        make = self.make('oneoff', makefile=makefile)\n\n        pyflakes_words = ['pyflakes', '--version', '--help']\n        venv_words = ['Collecting pyflakes', 'Installing', 'Successfully']\n        for word in pyflakes_words + venv_words:\n            with self.subTest(word=word):\n                self.assertIn(word, make.stdout)\n\n        # Second invocation must not rebuild venv\n        repeat = self.make('oneoff', makefile=makefile)\n        for word in pyflakes_words:\n            with self.subTest(word=word):\n                self.assertIn(word, repeat.stdout)\n        for word in venv_words:\n            with self.subTest(word=word):\n                self.assertNotIn(word, repeat.stdout)\n"
  },
  {
    "path": "tests/test_makefile.py",
    "content": "'''\nTest Makefile.venv invocation\n'''\n\n\nimport os.path\nfrom tests.common import MakefileTestCase, slow_test\n\n\nclass TestInvocation(MakefileTestCase):\n\n    def test_gnu_make(self):\n        '''Only GNU Make is supported'''\n        make = self.make('--version')\n        self.assertTrue('gnu make' in make.stdout.lower())\n\n    @slow_test\n    def test_creating(self, *cli_args):\n        '''Create empty virtual environment'''\n        make = self.make('debug-venv', 'venv', *cli_args)\n        self.assertTrue(\n            os.path.isdir(os.path.join(self.tmpdir.name, '.venv')),\n            msg='Failed to create virtual environment',\n        )\n\n        version = self.make('show-venv', *cli_args)\n        for line in ('python ', 'pip ', 'venv: '):\n            with self.subTest(line=line):\n                self.assertIn(line.lower(), version.stdout.lower())\n\n        self.make('clean-venv', *cli_args)\n        self.assertFalse(\n            os.path.isdir(os.path.join(self.tmpdir.name, '.venv')),\n            msg='Failed to remove virtual environment',\n        )\n\n    @slow_test\n    def test_no_builtin_variables(self):\n        '''\n        Check that \"make -R\" does not break anything\n\n        Thanks to @martinthomson:\n            https://github.com/sio/Makefile.venv/issues/19\n            https://github.com/sio/Makefile.venv/pull/20\n        '''\n        return self.test_creating('-R')\n"
  },
  {
    "path": "tests/test_pip_compile.py",
    "content": "'''\nTests for issue #8\nhttps://github.com/sio/Makefile.venv/issues/8\n\nSupport for generated requirements.txt files\n'''\n\n\nfrom tests.common import MakefileTestCase, slow_test\n\n\nclass TestPipCompile(MakefileTestCase):\n\n    @slow_test\n    def test_issue8(self):\n        '''Check that REQUIREMENTS_TXT can be generated with a Makefile recipe'''\n        self.copy('requirements.in')\n        makefile = self.copy('pip-compile.mk', makefile=True)\n\n        make = self.make(makefile=makefile)\n        for line in [\n                'Collecting pyflakes',\n                'show this help message and exit',\n                'usage: pyflakes',\n        ]:\n            self.assertIn(line, make.stdout)\n\n        # Second invokation must not need to rebuild venv\n        repeat = self.make(makefile=makefile)\n        self.assertIn('pyflakes --help', '\\n'.join(repeat.stdout.splitlines()[:2]))\n\n"
  },
  {
    "path": "tests/test_py_autodetect.py",
    "content": "'''\nTests for Python interpreter auto detection\n'''\n\nimport os\nimport shutil\nimport sys\nfrom pathlib import Path\nfrom tests.common import MakefileTestCase, slow_test\nfrom textwrap import dedent\nfrom unittest import skipIf\n\n\nskip_py_launcher = skipIf(\n    (os.getenv('WINDIR', '') / Path('py.exe')).exists(),\n    r'C:\\Windows\\py.exe exists and will interfere with this test'\n    # See GNU Make source code for more information (src/w32/subproc/sub_proc.c):\n    # https://github.com/mirror/make/blob/e62f4cf9a2eaf71c0d0102c28280e52e3c169b99/src/w32/subproc/sub_proc.c#L499-L510\n)\n\n\nclass TestPyAutoDetect(MakefileTestCase):\n\n    MAKE = shutil.which('make')  # use abspath because we mangle the PATH later\n\n    UNIX_WRAPPER = '''\\\n        #!/bin/sh\n        [ \"$1\" = \"-3\" ] && shift\n        \"{executable}\" \"$@\"\n        '''\n    WINDOWS_WRAPPER = '''\\\n        @echo off\n        if \"%1\"==\"-3\" shift\n        REM Unfortunately shift does not affect the value of %*\n        REM so we are stuck with unclean %1..%9\n        REM which is more than enough for our usecase\n        {executable} %1 %2 %3 %4 %5 %6 %7 %8 %9\n        '''\n\n    def __init__(self, *a, **ka):\n        super().__init__(*a, **ka)\n        if sys.platform == 'win32':\n            script = self.WINDOWS_WRAPPER\n        else:\n            script = self.UNIX_WRAPPER\n        self.script = dedent(script)\n\n    def setUp(self):\n        '''Tests in this module require PY to be unset'''\n        super().setUp()\n        self.PY = os.environ.pop('PY', None)\n\n    def save_script(self, relative_path: str, executable=None):\n        '''Save Python interpreter entry point to provided relative path'''\n        if executable is None:\n            executable = sys.executable\n        if sys.platform == 'win32':\n            relative_path += '.cmd'\n        dest = Path(self.tmpdir.name) / relative_path\n        dest.parent.mkdir(parents=True, exist_ok=True)\n        with dest.open('w') as out:\n            out.write(self.script.format(executable=executable))\n        dest.chmod(0o777)\n\n    def test_autodetect_happy_path(self):\n        '''Check that 'python3' is used by default'''\n        self.save_script('bin/python3')\n        self.save_script('bin/py')\n        self.save_script('bin/python')\n        os.environ['PATH'] = 'bin'\n        make = self.make('debug-venv')\n        self.assertIn('PY=\"python3\"', make.stdout)\n\n    @slow_test\n    def test_autodetect_venv(self):\n        '''Check that VENV interpreter is used if exists'''\n        if self.PY:  # restore PY to be able to create venv\n            os.environ['PY'] = self.PY\n        create = self.make('venv')\n        os.environ.pop('PY', None)\n\n        os.environ['PATH'] = 'nonexistent'\n        make = self.make('debug-venv')\n        for line in make.stdout.splitlines():\n            if line.startswith('PY='):\n                self.assertIn('.venv', line)\n                break\n        else:\n            self.assertTrue(False, 'Failed to parse stdout:\\n%s' % make.stdout)\n\n    def test_autodetect_py_3(self):\n        '''Check that 'py -3' is used when appropriate'''\n        self.save_script('bin/py')\n        self.save_script('bin/python')\n        os.environ['PATH'] = 'bin'\n        make = self.make('debug-venv')\n        self.assertIn('PY=\"py -3\"', make.stdout)\n\n    @skip_py_launcher\n    def test_autodetect_worst_path(self):\n        '''Check that 'python' is used if nothing else is there'''\n        self.save_script('bin/python')\n        os.environ['PATH'] = 'bin'\n        make = self.make('debug-venv')\n        self.assertIn('PY=\"python\"', make.stdout)\n\n    @skip_py_launcher\n    def test_autodetect_failure(self):\n        '''Check that autodetect failure is raised to the top'''\n        os.environ['PATH'] = 'bin'\n        make = self.make('debug-venv', returncode=2)\n        self.assertIn('Could not detect Python interpreter', make.stderr)\n"
  },
  {
    "path": "tests/test_pyproject_toml.py",
    "content": "from time import sleep\nfrom tests.common import MakefileTestCase, slow_test\nfrom tests.test_dependencies import touch\n\nclass TestPyprojectToml(MakefileTestCase):\n\n    def test_empty(self):\n        '''\n        Empty value for PYPROJECT_TOML should result in ignoring the file\n        even when it exists\n        '''\n        sample_makefile = '\\n'.join((\n            'PYPROJECT_TOML=',\n            'include {{ Makefile.venv }}',\n        ))\n        makefile = self.copy(content=sample_makefile, makefile=True)\n        self.copy('pyproject.toml')\n        make = self.make(makefile=makefile, dry_run=True)\n        self.assertNotIn('/pip install', make.stdout)\n\n    def test_nonexistent(self):\n        '''\n        Nonexistent path for PYPROJECT_TOML should result in error\n        because non-default values are treated as hard dependencies\n        and are expected to be made via Makefile recipe\n        '''\n        sample_makefile = '\\n'.join((\n            'PYPROJECT_TOML=nonexistent.toml',\n            'include {{ Makefile.venv }}',\n        ))\n        makefile = self.copy(content=sample_makefile, makefile=True)\n        self.copy('pyproject.toml')\n        make = self.make(makefile=makefile, dry_run=True, returncode=None)\n        self.assertEqual(make.returncode, 2)\n        self.assertIn('no rule to make target', make.stderr.lower())\n        self.assertNotIn('/pip install', make.stdout)\n\n    @slow_test\n    def test_multiple(self):\n        '''\n        Check that multiple pyproject.toml files are supported\n        '''\n        files = [\n            'pyproject.toml',\n            'hello.py',\n        ]\n        for dest_dir in ['one', 'two']:\n            for filename in files:\n                self.copy(filename, dest_dir=dest_dir)\n        translate = {\n            'name = \"hello\"': 'name = \"hello2\"',\n            'pyfiglet': 'console'\n        }\n        with open('tests/data/pyproject.toml') as f:\n            setup_content = f.read()\n        for old, new in translate.items():\n            setup_content = setup_content.replace(old, new)\n        second_setup_py = self.copy(  # avoid package name collision\n            'pyproject.toml',\n            content=setup_content,\n            dest_dir='two',\n        )\n        makefile_content = '\\n'.join((\n            'PYPROJECT_TOML=one/pyproject.toml two/pyproject.toml',\n            'include {{ Makefile.venv }}',\n        ))\n        makefile = self.copy(content=makefile_content, makefile=True)\n\n        # First invocation creates venv\n        make = self.make('venv', makefile=makefile)\n        self.assertIn('pip install -e', make.stdout)\n        for package in ['pyfiglet', 'console']:\n            self.assertIn('Collecting %s' % package, make.stdout)\n\n        # Second should be a noop\n        make = self.make('venv', makefile=makefile)\n        self.assertNotIn('pip install', make.stdout)\n\n        # Touching one of setup.cfg files should trigger venv update\n        sleep(1)  # macOS mtime seems to happen in whole seconds\n        touch(second_setup_py)\n        make = self.make('venv', makefile=makefile, dry_run=True)\n        self.assertIn('pip install -e', make.stdout)\n"
  },
  {
    "path": "tests/test_recipe_override.py",
    "content": "'''\nCheck that default recipe for $(VENV) can be safely overwritten by user\n\nSome of our users rely upon this unofficial GNU Make feature\n\nDocumentation:\n    https://www.gnu.org/software/make/manual/html_node/Multiple-Rules.html\n\nFeature in use:\n    https://github.com/sio/Makefile.venv/issues/13\n    https://github.com/sio/Makefile.venv/pull/10\n    https://github.com/sio/bash-complete-partial-path/blob/2be6ef1f1885d3cb1ec2547ae41d78aa66f4ab78/Makefile#L42-L48\n'''\n\nfrom tests.common import MakefileTestCase, slow_test\n\nCUSTOM_PACKAGE = 'noop==1.0'\n\nclass TestMakefileRecipeOverride(MakefileTestCase):\n\n    @slow_test\n    def test_recipe_override(self):\n        '''Check that default recipe for $(VENV) may be overwritten by user'''\n        makefile = self.copy('recipe-override.mk', makefile=True)\n\n        first = self.make('freeze', makefile=makefile)\n        self.assertIn(CUSTOM_PACKAGE, first.stdout.splitlines())\n\n        second = self.make('freeze', makefile=makefile)\n        self.assertNotIn('pip install', second.stdout)\n        self.assertIn(CUSTOM_PACKAGE, second.stdout.splitlines())\n"
  },
  {
    "path": "tests/test_release_checksum.py",
    "content": "import re\nfrom hashlib import sha256\nfrom unittest import TestCase\n\nfrom .test_release_version import skip_dev, DEV_VERSION_EXPLAINED\n\n\ndef calculate_checksum(filename):\n    with open(filename) as f:\n        checksum = sha256()\n        for line in f:\n            checksum.update(line.encode())\n    return checksum.hexdigest()\n\n\nclass TestChecksum(TestCase):\n    makefile = 'Makefile.venv'\n    readme = 'README.md'\n    pattern = re.compile(r'\\b[0-9a-f]{64}\\b', re.IGNORECASE)\n\n    def read_checksum(self, filename):\n        with open(filename) as f:\n            match = self.pattern.search(f.read())\n        if not match:\n            raise ValueError('checksum not found in {}'.format(filename))\n        return match.group().lower()\n\n    @skip_dev\n    def test_checksum(self):\n        '''\n        Check that installation instructions contain valid checksum\n        (versions ending with -dev suffix are skipped)\n        '''\n        recorded = self.read_checksum(self.readme)\n        calculated = calculate_checksum(self.makefile)\n        self.assertEqual(\n            recorded,\n            calculated,\n            '{} but versions in README and in Makefile.venv do not match'.format(DEV_VERSION_EXPLAINED)\n        )\n\n"
  },
  {
    "path": "tests/test_release_version.py",
    "content": "import re\nfrom unittest import TestCase, skipIf\nfrom subprocess import run, PIPE\n\n\nVERSION_PATTERN = re.compile(r'\\b(v\\d{4}\\.\\d{2}\\.\\d{2}(?:-\\w+|))\\b', re.IGNORECASE)\nDEV_VERSION_EXPLAINED = 'Proper release detected (version does not end with -dev suffix)'\n\n\ndef get_version(filename, pattern=VERSION_PATTERN):\n    with open(filename) as f:\n        match = pattern.search(f.read())\n    if not match:\n        raise ValueError('version pattern not found in {}'.format(filename))\n    return match.group(1)\n\n\nskip_dev = skipIf(\n    get_version('Makefile.venv').endswith('-dev'),\n    'not a final release'\n)\n\n\nclass TestVersion(TestCase):\n\n    pattern = VERSION_PATTERN\n    readme = 'README.md'\n    makefile = 'Makefile.venv'\n    changelog = 'CHANGELOG.md'\n\n    @skip_dev\n    def test_version(self):\n        '''Check that release versions in README and in Makefile.venv match'''\n        self.assertEqual(\n                get_version(self.readme),\n                get_version(self.makefile),\n        )\n\n    @skip_dev\n    def test_git_tag(self):\n        '''\n        Check that git tag contains valid release version\n        (versions ending with -dev suffix are skipped)\n        '''\n        cmd = 'git log -1 --format=%D --'.split() + [self.makefile]\n        process = run(cmd, stdout=PIPE, stderr=PIPE)\n        git_log = process.stdout.decode()\n        match = self.pattern.search(git_log)\n        if not match:\n            debug = 'exit code: {rc}\\nstdout:\\n{stdout}\\nstderr:\\n{stderr}'.format(\n                rc = process.returncode,\n                stdout = git_log,\n                stderr = process.stderr.decode(),\n            )\n            raise AssertionError('{} but version pattern not found in {!r}:\\n{}'.format(\n                DEV_VERSION_EXPLAINED,\n                ' '.join(cmd),\n                debug)\n            )\n        git_version = match.group(1)\n        self.assertEqual(\n            git_version,\n            get_version(self.makefile),\n            '{} but git tag does not match the version in Makefile.venv'.format(DEV_VERSION_EXPLAINED)\n        )\n\n    @skip_dev\n    def test_changelog(self):\n        '''Check that changelog contains an entry for current version'''\n        version = get_version(self.makefile)\n        header = '## %s' % version\n        with open(self.changelog) as changelog:\n            for line in changelog:\n                if line.startswith(header):\n                    break\n            else:\n                raise AssertionError('header not found in {changelog}: {header!r}'.format(\n                    header=header,\n                    changelog=self.changelog,\n                ))\n"
  },
  {
    "path": "tests/test_requirements_txt.py",
    "content": "'''\nCheck edge cases for REQUIREMENTS_TXT value\n\nMentioned in:\n    https://github.com/sio/Makefile.venv/issues/14#issuecomment-982578931\n'''\n\nfrom tests.common import MakefileTestCase\n\n\nclass TestRequirementsTxt(MakefileTestCase):\n\n    def test_requirements_txt_empty(self):\n        '''\n        Empty value for REQUIREMENTS_TXT should result in ignoring\n        requirements.txt even when it exists\n        '''\n        sample_makefile = '\\n'.join((\n            'REQUIREMENTS_TXT=',\n            'include {{ Makefile.venv }}',\n        ))\n        makefile = self.copy(content=sample_makefile, makefile=True)\n        self.copy('requirements.txt')\n        make = self.make(makefile=makefile, dry_run=True)\n        self.assertNotIn('/pip install', make.stdout)\n\n    def test_requirements_txt_nonexistent(self):\n        '''\n        Nonexistent path for REQUIREMENTS_TXT should result in error because\n        non-default values are treated as hard dependencies and are expected to\n        be made via Makefile recipe\n        '''\n        sample_makefile = '\\n'.join((\n            'REQUIREMENTS_TXT=nonexistent.txt',\n            'include {{ Makefile.venv }}',\n        ))\n        makefile = self.copy(content=sample_makefile, makefile=True)\n        self.copy('requirements.txt')\n        make = self.make(makefile=makefile, dry_run=True, returncode=None)\n        self.assertEqual(make.returncode, 2)\n        self.assertIn('no rule to make target', make.stderr.lower())\n        self.assertNotIn('/pip install', make.stdout)\n"
  },
  {
    "path": "tests/test_setup_py.py",
    "content": "from time import sleep\nfrom tests.common import MakefileTestCase, slow_test\nfrom tests.test_dependencies import touch\n\nclass TestSetupPy(MakefileTestCase):\n\n    def test_setup_py_empty(self):\n        '''\n        Empty value for SETUP_PY should result in ignoring the file\n        even when it exists\n        '''\n        sample_makefile = '\\n'.join((\n            'SETUP_PY=',\n            'include {{ Makefile.venv }}',\n        ))\n        makefile = self.copy(content=sample_makefile, makefile=True)\n        self.copy('setup.py')\n        make = self.make(makefile=makefile, dry_run=True)\n        self.assertNotIn('/pip install', make.stdout)\n\n    def test_setup_py_nonexistent(self):\n        '''\n        Nonexistent path for SETUP_PY should result in error\n        because non-default values are treated as hard dependencies\n        and are expected to be made via Makefile recipe\n        '''\n        sample_makefile = '\\n'.join((\n            'SETUP_PY=nonexistent.py',\n            'include {{ Makefile.venv }}',\n        ))\n        makefile = self.copy(content=sample_makefile, makefile=True)\n        self.copy('setup.py')\n        make = self.make(makefile=makefile, dry_run=True, returncode=None)\n        self.assertEqual(make.returncode, 2)\n        self.assertIn('no rule to make target', make.stderr.lower())\n        self.assertNotIn('/pip install', make.stdout)\n\n    @slow_test\n    def test_setup_py_multiple(self):\n        '''\n        Check that multiple setup.py files are supported\n        '''\n        files = [\n            'setup.py',\n            'hello.py',\n        ]\n        for dest_dir in ['one', 'two']:\n            for filename in files:\n                self.copy(filename, dest_dir=dest_dir)\n        setup_content = '\\n'.join((\n            'from setuptools import setup',\n            'setup(name=\"hello2\", install_requires=[\"console\",])'\n        ))\n        second_setup_py = self.copy(  # avoid package name collision\n            'setup.py',\n            content=setup_content,\n            dest_dir='two',\n        )\n        makefile_content = '\\n'.join((\n            'SETUP_PY=one/setup.py two/setup.py',\n            'include {{ Makefile.venv }}',\n        ))\n        makefile = self.copy(content=makefile_content, makefile=True)\n\n        # First invocation creates venv\n        make = self.make('venv', makefile=makefile)\n        self.assertIn('pip install -e', make.stdout)\n        for package in ['pyfiglet', 'console']:\n            self.assertIn('Collecting %s' % package, make.stdout)\n\n        # Second should be a noop\n        make = self.make('venv', makefile=makefile)\n        self.assertNotIn('pip install', make.stdout)\n\n        # Touching one of setup.cfg files should trigger venv update\n        sleep(1)  # macOS mtime seems to happen in whole seconds\n        touch(second_setup_py)\n        make = self.make('venv', makefile=makefile, dry_run=True)\n        self.assertIn('pip install -e', make.stdout)\n\n    def test_setup_cfg_multiple(self):\n        '''Check that multiple setup.cfg files are picked up correctly'''\n        for dirname in {'one', 'two'}:\n            for filename in {'setup.py', 'setup.cfg'}:\n                self.copy(filename, content='', dest_dir=dirname)\n        makefile = self.copy(makefile=True, content='\\n'.join((\n            'SETUP_PY=one/setup.py two/setup.py',\n            'include {{ Makefile.venv }}',\n        )))\n        make = self.make('debug-venv', makefile=makefile)\n        count = 0\n        for line in make.stdout.splitlines():\n            if 'one/setup.cfg two/setup.cfg' not in line:\n                continue\n            if 'VENVDEPENDS=' in line:\n                count += 1\n            if 'VENV_LOCAL_PACKAGE=' in line:\n                count += 1\n        self.assertEqual(count, 2, \"Unexpected number of setup.cfg lines in stdout:\\n{}\".format(make.stdout))\n"
  },
  {
    "path": "tests/test_spaces.py",
    "content": "from tests.common import MakefileTestCase, slow_test\n\n\nclass TestSpacesInPath(MakefileTestCase):\n\n    TMPPREFIX = 'Makefile.venv test with spaces '\n\n    @slow_test\n    def test_spaces(self):\n        '''Check that spaces in project path do not break anything'''\n        for filename in {'hello.py', 'requirements.txt'}:\n            self.copy(filename)\n        makefile = self.copy('dependencies.mk', makefile=True)\n\n        make = self.make('hello', makefile=makefile)\n        self.assertIn('Collecting pyfiglet', make.stdout)\n"
  }
]