├── vars └── main.yml ├── current-version.txt ├── .gitignore ├── handlers └── main.yml ├── .ansible-lint ├── .github ├── workflows │ ├── constraints.txt │ ├── release-drafter.yml │ ├── labeler.yml │ ├── release.yml │ └── tests.yml ├── dependabot.yml ├── release-drafter.yml ├── labels.yml └── stale.yml ├── tasks ├── Darwin.yml ├── Debian.yml ├── RedHat.yml ├── main.yml ├── custom_facts.yml └── install.yml ├── templates ├── check-configure-options.py.j2 ├── pyenv_python_installations.fact.j2 └── .pyenvrc.j2 ├── molecule ├── default │ ├── converge.yml │ └── molecule.yml └── multi-version │ ├── molecule.yml │ └── converge.yml ├── .yamllint ├── meta └── main.yml ├── .pre-commit-config.yaml ├── LICENSE ├── defaults └── main.yml └── README.md /vars/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | -------------------------------------------------------------------------------- /current-version.txt: -------------------------------------------------------------------------------- 1 | 3.0.0 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .vscode/ 3 | -------------------------------------------------------------------------------- /handlers/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # handlers file for pyenv 3 | -------------------------------------------------------------------------------- /.ansible-lint: -------------------------------------------------------------------------------- 1 | --- 2 | exclude_paths: [~/.ansible] 3 | 4 | skip_list: 5 | - '306' 6 | - '106' 7 | -------------------------------------------------------------------------------- /.github/workflows/constraints.txt: -------------------------------------------------------------------------------- 1 | pip==25.1.1 2 | ansible 3 | yamllint==1.37.1 4 | ansible-lint==24.2.0 5 | -------------------------------------------------------------------------------- /tasks/Darwin.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Install development packages necessary for building Python 3 | community.general.homebrew: 4 | name: "{{ pyenv_osx_packages }}" 5 | state: present 6 | become: false 7 | -------------------------------------------------------------------------------- /templates/check-configure-options.py.j2: -------------------------------------------------------------------------------- 1 | import sysconfig 2 | 3 | config_args = sysconfig.get_config_var("CONFIG_ARGS") 4 | config_args = config_args.replace("'", "").split() 5 | required_args = {{ pyenv_python_configure_opts.split() }} 6 | print(all(s in config_args for s in required_args)) 7 | -------------------------------------------------------------------------------- /.github/workflows/release-drafter.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Release Drafter 3 | 4 | "on": 5 | push: 6 | branches: 7 | - main 8 | jobs: 9 | draft_release: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: release-drafter/release-drafter@v6.1.0 13 | env: 14 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 15 | -------------------------------------------------------------------------------- /tasks/Debian.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Ensure apt cache is up to date 3 | ansible.builtin.apt: 4 | update_cache: true 5 | cache_valid_time: 3600 6 | become: true 7 | 8 | - name: Install development packages necessary for building Python 9 | ansible.builtin.apt: 10 | pkg: "{{ pyenv_debian_packages }}" 11 | state: present 12 | become: true 13 | -------------------------------------------------------------------------------- /molecule/default/converge.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Converge 3 | hosts: all 4 | become: true 5 | 6 | tasks: 7 | - name: "Include staticdev.pyenv" 8 | ansible.builtin.include_role: 9 | name: "staticdev.pyenv" 10 | vars: 11 | pyenv_version: "v2.3.35" 12 | pyenv_virtualenv_version: "v1.2.1" 13 | pyenv_update_version: "172a0ed" 14 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | --- 2 | version: 2 3 | updates: 4 | - package-ecosystem: github-actions 5 | directory: "/" 6 | schedule: 7 | interval: daily 8 | - package-ecosystem: pip 9 | directory: "/.github/workflows" 10 | schedule: 11 | interval: daily 12 | - package-ecosystem: pip 13 | directory: "/docs" 14 | schedule: 15 | interval: daily 16 | -------------------------------------------------------------------------------- /.github/workflows/labeler.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Labeler 3 | 4 | "on": 5 | push: 6 | branches: 7 | - main 8 | 9 | jobs: 10 | labeler: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Check out the repository 14 | uses: actions/checkout@v4 15 | 16 | - name: Run Labeler 17 | uses: crazy-max/ghaction-github-labeler@v5.3.0 18 | with: 19 | skip-delete: true 20 | -------------------------------------------------------------------------------- /molecule/multi-version/molecule.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dependency: 3 | name: galaxy 4 | driver: 5 | name: podman 6 | platforms: 7 | - name: instance 8 | image: "docker.io/geerlingguy/docker-${MOLECULE_DISTRO:-debian12}-ansible:latest" 9 | command: ${MOLECULE_COMMAND:-""} 10 | volumes: 11 | - /sys/fs/cgroup:/sys/fs/cgroup:ro 12 | privileged: true 13 | pre_build_image: true 14 | provisioner: 15 | name: ansible 16 | -------------------------------------------------------------------------------- /molecule/default/molecule.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dependency: 3 | name: galaxy 4 | driver: 5 | name: podman 6 | platforms: 7 | - name: instance 8 | image: "docker.io/geerlingguy/docker-${MOLECULE_DISTRO:-debian12}-ansible:latest" 9 | command: ${MOLECULE_COMMAND:-""} 10 | volumes: 11 | - /sys/fs/cgroup:/sys/fs/cgroup:rw 12 | cgroupns_mode: host 13 | privileged: true 14 | pre_build_image: true 15 | provisioner: 16 | name: ansible 17 | -------------------------------------------------------------------------------- /tasks/RedHat.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Use action module which can interchangably use yum or dnf depending on what is available ( Eg. dnf on Fedora vs yum on Centos ) 3 | # Note: special variable ansible_pkg_mgr requires fact_gathering to be enabled 4 | # More: https://ansible-tips-and-tricks.readthedocs.io/en/latest/os-dependent-tasks/installing_packages/ 5 | - name: Install development packages necessary for building Python 6 | action: > 7 | {{ ansible_pkg_mgr }} name="{{ pyenv_redhat_packages }}" state=present 8 | become: true 9 | -------------------------------------------------------------------------------- /.yamllint: -------------------------------------------------------------------------------- 1 | --- 2 | extends: default 3 | 4 | rules: 5 | line-length: 6 | max: 180 7 | level: warning 8 | braces: 9 | level: warning 10 | max-spaces-inside: 1 11 | document-start: 12 | level: error 13 | comments-indentation: false 14 | comments: 15 | level: error 16 | min-spaces-from-content: 1 17 | octal-values: 18 | forbid-implicit-octal: true 19 | forbid-explicit-octal: true 20 | truthy: 21 | level: error 22 | 23 | ignore: | 24 | .github/stale.yml 25 | .cache 26 | roles 27 | -------------------------------------------------------------------------------- /meta/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | galaxy_info: 3 | role_name: pyenv 4 | author: staticdev 5 | namespace: staticdev 6 | description: Ansible Galaxy role for pyenv. 7 | company: StaticDev 8 | license: MIT 9 | min_ansible_version: "2.11" 10 | 11 | platforms: 12 | - name: macOS 13 | versions: 14 | - Sierra 15 | 16 | - name: Debian 17 | versions: 18 | - bookworm 19 | - bullseye 20 | 21 | - name: Ubuntu 22 | versions: 23 | - jammy 24 | - focal 25 | 26 | galaxy_tags: 27 | - development 28 | - pyenv 29 | - python 30 | - system 31 | - workstation 32 | 33 | dependencies: [] 34 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | repos: 3 | - repo: https://github.com/pre-commit/pre-commit-hooks 4 | rev: v4.6.0 5 | hooks: 6 | - id: check-added-large-files 7 | - id: check-toml 8 | - id: check-yaml 9 | - id: end-of-file-fixer 10 | - id: trailing-whitespace 11 | - id: check-added-large-files 12 | - repo: https://github.com/adrienverge/yamllint.git 13 | rev: v1.35.1 14 | hooks: 15 | - id: yamllint 16 | args: ["-c=.yamllint", "."] 17 | - repo: https://github.com/ansible-community/ansible-lint.git 18 | rev: v24.6.1 19 | hooks: 20 | - id: ansible-lint 21 | files: \.(yaml|yml)$ 22 | -------------------------------------------------------------------------------- /molecule/multi-version/converge.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Converge 3 | hosts: all 4 | become: true 5 | 6 | tasks: 7 | - name: "Include staticdev.pyenv" 8 | ansible.builtin.include_role: 9 | name: "staticdev.pyenv" 10 | vars: 11 | pyenv_version: "v2.3.35" 12 | pyenv_virtualenv_version: "v1.2.1" 13 | pyenv_update_version: "172a0ed" 14 | pyenv_global: 15 | - 3.12.1 16 | - 3.11.7 17 | pyenv_python_versions: 18 | - 3.12.1 19 | - 3.11.7 20 | pyenv_virtualenvs: 21 | - venv_name: latest_v312 22 | py_version: 3.12.1 23 | - venv_name: latest_v311 24 | py_version: 3.11.7 25 | -------------------------------------------------------------------------------- /tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Load custom facts. 3 | ansible.builtin.import_tasks: custom_facts.yml 4 | become: true 5 | 6 | - name: Run Darwin specific tasks. 7 | ansible.builtin.import_tasks: Darwin.yml 8 | when: ansible_os_family == "Darwin" 9 | 10 | - name: Run Debian specific tasks. 11 | ansible.builtin.import_tasks: Debian.yml 12 | when: ansible_os_family == "Debian" 13 | 14 | - name: Run RedHat specific tasks. 15 | ansible.builtin.import_tasks: RedHat.yml 16 | when: ansible_os_family == "RedHat" 17 | 18 | - name: Install pyenv on user level. 19 | ansible.builtin.import_tasks: install.yml 20 | become: true 21 | become_user: "{{ pyenv_owner }}" 22 | when: pyenv_env == "user" 23 | 24 | - name: Install pyenv on system level. 25 | ansible.builtin.import_tasks: install.yml 26 | become: true 27 | when: pyenv_env == "system" 28 | -------------------------------------------------------------------------------- /tasks/custom_facts.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Create folder for custom facts 3 | ansible.builtin.file: 4 | path: /etc/ansible/facts.d 5 | state: directory 6 | mode: "0755" 7 | 8 | - name: Copy over check-configure-options.py 9 | ansible.builtin.template: 10 | src: templates/check-configure-options.py.j2 11 | dest: /etc/ansible/facts.d/check-configure-options.py 12 | owner: "{{ pyenv_owner }}" 13 | group: "{{ pyenv_owner_group }}" 14 | mode: "0755" 15 | 16 | - name: Copy over python_check fact file 17 | ansible.builtin.template: 18 | src: templates/pyenv_python_installations.fact.j2 19 | dest: /etc/ansible/facts.d/pyenv_python_installations.fact 20 | owner: "{{ pyenv_owner }}" 21 | group: "{{ pyenv_owner_group }}" 22 | mode: "0755" 23 | 24 | - name: Reload setup to gather custom facts 25 | ansible.builtin.setup: 26 | filter: ansible_local 27 | -------------------------------------------------------------------------------- /.github/release-drafter.yml: -------------------------------------------------------------------------------- 1 | --- 2 | template: | 3 | ## Changes 4 | $CHANGES 5 | categories: 6 | - title: ":boom: Breaking Changes" 7 | label: "breaking" 8 | - title: ":rocket: Features" 9 | label: "enhancement" 10 | - title: ":fire: Removals and Deprecations" 11 | label: "removal" 12 | - title: ":beetle: Fixes" 13 | label: "bug" 14 | - title: ":raising_hand: Help wanted" 15 | label: "help wanted" 16 | - title: ":racehorse: Performance" 17 | label: "performance" 18 | - title: ":rotating_light: Testing" 19 | label: "testing" 20 | - title: ":construction_worker: Continuous Integration" 21 | label: "ci" 22 | - title: ":books: Documentation" 23 | label: "documentation" 24 | - title: ":hammer: Refactoring" 25 | label: "refactoring" 26 | - title: ":lipstick: Style" 27 | label: "style" 28 | - title: ":package: Dependencies" 29 | labels: 30 | - "dependencies" 31 | - "build" 32 | 33 | exclude-labels: 34 | - "skip-changelog" 35 | -------------------------------------------------------------------------------- /templates/pyenv_python_installations.fact.j2: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | configure_opts_checker="/etc/ansible/facts.d/check-configure-options.py" 4 | 5 | declare -a installs_to_check=({{ " ".join(pyenv_python_versions) }}) 6 | declare -a to_reinstall=() 7 | declare -a to_install=() 8 | 9 | # Join array to string 10 | function join { local IFS="$1"; shift; echo "[$*]"; } 11 | 12 | for version in "${installs_to_check[@]}" 13 | do 14 | py_installation="{{ pyenv_path }}/versions/$version/bin/python" 15 | 16 | if [[ -x "$py_installation" ]]; then 17 | is_valid_install=$("$py_installation" "$configure_opts_checker") 18 | 19 | if [[ $is_valid_install =~ "False" ]]; then 20 | to_reinstall+=("\"$version\"") 21 | fi 22 | else 23 | to_install+=("\"$version\"") 24 | fi 25 | done 26 | 27 | to_reinstall_str=$(join , ${to_reinstall[@]}) 28 | to_install_str=$(join , ${to_install[@]}) 29 | printf "{ \"to_reinstall\": "$to_reinstall_str", \"to_install\": "$to_install_str" }\n" 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2023 by staticdev 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Release 3 | 4 | "on": 5 | push: 6 | branches: 7 | - main 8 | 9 | jobs: 10 | release: 11 | name: Release 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Check out the repository 15 | uses: actions/checkout@v4 16 | with: 17 | fetch-depth: 2 18 | 19 | - name: Set up Python 20 | uses: actions/setup-python@v5 21 | with: 22 | python-version: "3.13" 23 | 24 | - name: Upgrade pip 25 | run: | 26 | pip install --constraint=.github/workflows/constraints.txt pip 27 | pip --version 28 | 29 | - name: Check if there is a parent commit 30 | id: check-parent-commit 31 | run: | 32 | echo "::set-output name=sha::$(git rev-parse --verify --quiet HEAD^)" 33 | 34 | - name: Detect and tag new version 35 | id: check-version 36 | if: steps.check-parent-commit.outputs.sha 37 | uses: salsify/action-detect-and-tag-new-version@v2 38 | with: 39 | tag-template: "{VERSION}" 40 | version-command: | 41 | cat current-version.txt 42 | 43 | - name: Install Ansible 44 | run: | 45 | pip install --constraint=.github/workflows/constraints.txt ansible 46 | 47 | - name: Trigger a new import on Galaxy. 48 | run: >- 49 | ansible-galaxy role import --api-key ${{ secrets.GALAXY_API_KEY }} 50 | $(echo ${{ github.repository }} | cut -d/ -f1) $(echo ${{ github.repository }} | cut -d/ -f2) 51 | 52 | - name: Publish the release notes 53 | uses: release-drafter/release-drafter@v6.1.0 54 | with: 55 | publish: ${{ steps.check-version.outputs.tag != '' }} 56 | tag: ${{ steps.check-version.outputs.tag }} 57 | env: 58 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 59 | -------------------------------------------------------------------------------- /defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # defaults file for pyenv 3 | pyenv_version: "HEAD" 4 | pyenv_virtualenv_version: "HEAD" 5 | pyenv_update_version: "HEAD" 6 | pyenv_env: "user" 7 | pyenv_path: "{% if pyenv_env == 'user' %}{{ ansible_env.HOME }}/pyenv{% else %}/usr/local/pyenv{% endif %}" 8 | pyenvrc_path: "{{ pyenv_path }}" 9 | pyenv_owner: "{{ ansible_facts.user_id }}" 10 | pyenv_owner_group: "{{ pyenv_owner }}" 11 | pyenv_shellrc_file: "{% if pyenv_env == 'user' %}~/.bashrc{% else %}/etc/profile.d/pyenv.sh{% endif %}" 12 | pyenv_update_git_install: true 13 | pyenv_enable_autocompletion: false 14 | pyenv_enable_virtualenvs: true 15 | pyenv_python_versions: 16 | - 3.12.1 17 | pyenv_global: 18 | - 3.12.1 19 | pyenv_virtualenvs: 20 | [] 21 | # - { venv_name: "latest", py_version: "3.12.1" } 22 | # For a system install, the shims dir will not be writable by users, disable rehashing 23 | pyenv_init_options: "{% if pyenv_env != 'user' %}--no-rehash{% endif %}" 24 | 25 | pyenv_update: false 26 | 27 | # additional options for the build process, e.g "--enable-shared" 28 | pyenv_install_extra_opts: "" 29 | pyenv_python_configure_opts: "" 30 | pyenv_uninstall_python_w_wrong_configure_opts: false 31 | 32 | pyenv_debian_packages: 33 | - build-essential 34 | - curl 35 | - git 36 | - llvm 37 | - libbz2-dev 38 | - libffi-dev 39 | - liblzma-dev 40 | - libncurses5-dev 41 | - libncursesw5-dev 42 | - libreadline-dev 43 | - libsqlite3-dev 44 | - libssl-dev 45 | - python3-openssl 46 | - tk-dev 47 | - wget 48 | - xz-utils 49 | - zlib1g-dev 50 | pyenv_redhat_packages: 51 | - make 52 | - git 53 | - gcc 54 | - "{{ ((ansible_facts.distribution_major_version | default(0) | int) < 8) | ternary('libselinux-python', 'python3-libselinux') }}" 55 | - zlib-devel 56 | - openssl-devel 57 | - bzip2-devel 58 | - readline-devel 59 | - libffi-devel 60 | - sqlite-devel 61 | - gdbm-devel 62 | pyenv_osx_packages: 63 | - readline 64 | - xz 65 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Tests 3 | 4 | "on": 5 | pull_request: 6 | push: 7 | branches: 8 | - main 9 | schedule: 10 | - cron: "0 7 * * 0" 11 | 12 | defaults: 13 | run: 14 | working-directory: "staticdev.pyenv" 15 | 16 | jobs: 17 | lint: 18 | name: Lint 19 | runs-on: ubuntu-latest 20 | steps: 21 | - name: Check out the repository 22 | uses: actions/checkout@v4 23 | with: 24 | path: "staticdev.pyenv" 25 | 26 | - name: Set up Python 27 | uses: actions/setup-python@v5 28 | with: 29 | python-version: "3.13" 30 | 31 | - name: Install test dependencies 32 | run: pip install --constraint=.github/workflows/constraints.txt ansible yamllint ansible-lint 33 | 34 | - name: Lint code 35 | run: | 36 | yamllint . 37 | ansible-lint 38 | 39 | molecule: 40 | name: Molecule 41 | runs-on: ubuntu-latest 42 | 43 | strategy: 44 | matrix: 45 | distro: 46 | - debian12 47 | - debian11 48 | - ubuntu2404 49 | - ubuntu2204 50 | 51 | steps: 52 | - name: Check out the repository 53 | uses: actions/checkout@v4 54 | with: 55 | path: "staticdev.pyenv" 56 | 57 | - name: Set up Python 58 | uses: actions/setup-python@v5 59 | with: 60 | python-version: "3.13" 61 | 62 | - name: Upgrade pip 63 | run: | 64 | python3 -m pip install --constraint=.github/workflows/constraints.txt pip 65 | python3 -m pip --version 66 | 67 | - name: Install test dependencies 68 | run: | 69 | python3 -m pip install --constraint=.github/workflows/constraints.txt ansible 'molecule-plugins[podman]' 70 | 71 | - name: Run Molecule tests 72 | run: molecule test 73 | env: 74 | PY_COLORS: "1" 75 | ANSIBLE_FORCE_COLOR: "1" 76 | MOLECULE_DISTRO: ${{ matrix.distro }} 77 | -------------------------------------------------------------------------------- /.github/labels.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Labels names are important as they are used by Release Drafter to decide 3 | # regarding where to record them in changelog or if to skip them. 4 | # 5 | # The repository labels will be automatically configured using this file and 6 | # the GitHub Action https://github.com/marketplace/actions/github-labeler. 7 | - name: breaking 8 | description: Breaking Changes 9 | color: "bfd4f2" 10 | - name: bug 11 | description: Something isn't working 12 | color: "d73a4a" 13 | - name: build 14 | description: Build System and Dependencies 15 | color: "bfdadc" 16 | - name: ci 17 | description: Continuous Integration 18 | color: "4a97d6" 19 | - name: dependencies 20 | description: Pull requests that update a dependency file 21 | color: "0366d6" 22 | - name: documentation 23 | description: Improvements or additions to documentation 24 | color: "0075ca" 25 | - name: duplicate 26 | description: This issue or pull request already exists 27 | color: "cfd3d7" 28 | - name: enhancement 29 | description: New feature or request 30 | color: "a2eeef" 31 | - name: github_actions 32 | description: Pull requests that update Github_actions code 33 | color: "000000" 34 | - name: good first issue 35 | description: Good for newcomers 36 | color: "7057ff" 37 | - name: help wanted 38 | description: Extra attention is needed 39 | color: "008672" 40 | - name: invalid 41 | description: This doesn't seem right 42 | color: "e4e669" 43 | - name: performance 44 | description: Performance 45 | color: "016175" 46 | - name: python 47 | description: Pull requests that update Python code 48 | color: "2b67c6" 49 | - name: question 50 | description: Further information is requested 51 | color: "d876e3" 52 | - name: refactoring 53 | description: Refactoring 54 | color: "ef67c4" 55 | - name: removal 56 | description: Removals and deprecations 57 | color: "9ae7ea" 58 | - name: style 59 | description: Style 60 | color: "c120e5" 61 | - name: testing 62 | description: Testing 63 | color: "b1fc6f" 64 | - name: wontfix 65 | description: This will not be worked on 66 | color: "ffffff" 67 | - name: skip-changelog 68 | description: This will not be added to release notes 69 | color: "dddddd" 70 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Configuration for probot-stale - https://github.com/probot/stale 2 | --- 3 | # Number of days of inactivity before an Issue or Pull Request becomes stale 4 | daysUntilStale: 90 5 | 6 | # Number of days of inactivity before an Issue or Pull Request with the stale label is closed. 7 | # Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale. 8 | daysUntilClose: 30 9 | 10 | # Only issues or pull requests with all of these labels are check if stale. Defaults to `[]` (disabled) 11 | onlyLabels: [] 12 | 13 | # Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable 14 | exemptLabels: 15 | - pinned 16 | - security 17 | - planned 18 | 19 | # Set to true to ignore issues in a project (defaults to false) 20 | exemptProjects: false 21 | 22 | # Set to true to ignore issues in a milestone (defaults to false) 23 | exemptMilestones: false 24 | 25 | # Set to true to ignore issues with an assignee (defaults to false) 26 | exemptAssignees: false 27 | 28 | # Label to use when marking as stale 29 | staleLabel: stale 30 | 31 | # Limit the number of actions per hour, from 1-30. Default is 30 32 | limitPerRun: 30 33 | 34 | pulls: 35 | markComment: |- 36 | This pull request has been marked 'stale' due to lack of recent activity. If there is no further activity, the PR will be closed in another 30 days. Thank you for your contribution! 37 | 38 | Please read [this blog post](https://www.jeffgeerling.com/blog/2020/enabling-stale-issue-bot-on-my-github-repositories) to see the reasons why I mark pull requests as stale. 39 | 40 | unmarkComment: >- 41 | This pull request is no longer marked for closure. 42 | 43 | closeComment: >- 44 | This pull request has been closed due to inactivity. If you feel this is in error, please reopen the pull request or file a new PR with the relevant details. 45 | 46 | issues: 47 | markComment: |- 48 | This issue has been marked 'stale' due to lack of recent activity. If there is no further activity, the issue will be closed in another 30 days. Thank you for your contribution! 49 | 50 | Please read [this blog post](https://www.jeffgeerling.com/blog/2020/enabling-stale-issue-bot-on-my-github-repositories) to see the reasons why I mark issues as stale. 51 | 52 | unmarkComment: >- 53 | This issue is no longer marked for closure. 54 | 55 | closeComment: >- 56 | This issue has been closed due to inactivity. If you feel this is in error, please reopen the issue or file a new issue with the relevant details. 57 | -------------------------------------------------------------------------------- /templates/.pyenvrc.j2: -------------------------------------------------------------------------------- 1 | # Include this file into your ~/.bashrc 2 | # ------------------------------------- 3 | export PYENV_ROOT="{{ pyenv_path }}" 4 | export PATH="$PYENV_ROOT/bin:$PATH" 5 | {% if pyenv_tmpdir is defined %} 6 | export TMPDIR="{{ pyenv_tmpdir }}" 7 | {% endif %} 8 | {% if pyenv_python_build_build_path is defined %} 9 | export PYTHON_BUILD_BUILD_PATH="{{ pyenv_python_build_build_path }}" 10 | {% endif %} 11 | {% if pyenv_python_build_cache_path is defined %} 12 | export PYTHON_BUILD_CACHE_PATH="{{ pyenv_python_build_cache_path }}" 13 | {% endif %} 14 | {% if pyenv_python_build_mirror_url is defined %} 15 | export PYTHON_BUILD_MIRROR_URL="{{ pyenv_python_build_mirror_url }}" 16 | {% endif %} 17 | {% if pyenv_python_build_mirror_url_skip_checksum is defined %} 18 | export PYTHON_BUILD_MIRROR_URL_SKIP_CHECKSUM="{{ pyenv_python_build_mirror_url_skip_checksum }}" 19 | {% endif %} 20 | {% if pyenv_python_build_skip_mirror is defined %} 21 | export PYTHON_BUILD_SKIP_MIRROR="{{ pyenv_python_build_skip_mirror }}" 22 | {% endif %} 23 | {% if pyenv_python_build_skip_homebrew is defined %} 24 | export PYTHON_BUILD_SKIP_HOMEBREW="{{ pyenv_python_build_skip_homebrew }}" 25 | {% endif %} 26 | {% if pyenv_python_build_root is defined %} 27 | export PYTHON_BUILD_ROOT="{{ pyenv_python_build_root }}" 28 | {% endif %} 29 | {% if pyenv_python_build_definitions is defined %} 30 | export PYTHON_BUILD_DEFINITIONS="{{ pyenv_python_build_definitions }}" 31 | {% endif %} 32 | {% if pyenv_python_configure_opts is defined %} 33 | export PYTHON_CONFIGURE_OPTS="{{ pyenv_python_configure_opts }}" 34 | {% endif %} 35 | {% if pyenv_python_cflags is defined %} 36 | export PYTHON_CFLAGS="{{ pyenv_python_cflags }}" 37 | {% endif %} 38 | {% if pyenv_python_make_opts is defined %} 39 | export PYTHON_MAKE_OPTS="{{ pyenv_python_make_opts }}" 40 | {% endif %} 41 | {% if pyenv_python_make_install_opts is defined %} 42 | export PYTHON_MAKE_INSTALL_OPTS="{{ pyenv_python_make_install_opts }}" 43 | {% endif %} 44 | {% if pyenv_configure_opts is defined %} 45 | export CONFIGURE_OPTS="{{ pyenv_configure_opts }}" 46 | {% endif %} 47 | {% if pyenv_cc is defined %} 48 | export CC="{{ pyenv_cc }}" 49 | {% endif %} 50 | {% if pyenv_make is defined %} 51 | export MAKE="{{ pyenv_make }}" 52 | {% endif %} 53 | {% if pyenv_make_opts is defined %} 54 | export MAKE_OPTS="{{ pyenv_make_opts }}" 55 | {% endif %} 56 | {% if pyenv_make_install_opts is defined %} 57 | export MAKE_INSTALL_OPTS="{{ pyenv_make_install_opts }}" 58 | {% endif %} 59 | {% if pyenv_profile_task is defined %} 60 | export PROFILE_TASK="{{ pyenv_profile_task }}" 61 | {% endif %} 62 | {% if pyenv_custom_pyenvrc_file is defined %} 63 | [ -f "{{ pyenvrc_path }}/.pyenvrc.custom" ] && source "{{ pyenvrc_path }}/.pyenvrc.custom" 64 | {% endif %} 65 | eval "$(pyenv init --path)" 66 | eval "$(pyenv init - {{ pyenv_init_options }})" 67 | -------------------------------------------------------------------------------- /tasks/install.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Install PyEnv 3 | ansible.builtin.git: 4 | repo: https://github.com/pyenv/pyenv.git 5 | dest: "{{ pyenv_path }}" 6 | version: "{{ pyenv_version }}" 7 | update: "{{ pyenv_update_git_install }}" 8 | force: true 9 | 10 | - name: Install PyEnv-virtualenv plugin 11 | ansible.builtin.git: 12 | repo: https://github.com/yyuu/pyenv-virtualenv.git 13 | dest: "{{ pyenv_path }}/plugins/pyenv-virtualenv" 14 | version: "{{ pyenv_virtualenv_version }}" 15 | update: "{{ pyenv_update_git_install }}" 16 | force: true 17 | when: pyenv_enable_virtualenvs 18 | 19 | - name: Install PyEnv-update plugin 20 | ansible.builtin.git: 21 | repo: https://github.com/pyenv/pyenv-update.git 22 | dest: "{{ pyenv_path }}/plugins/pyenv-update" 23 | version: "{{ pyenv_update_version }}" 24 | update: "{{ pyenv_update_git_install }}" 25 | force: true 26 | when: pyenv_update 27 | 28 | - name: Install .pyenvrc 29 | ansible.builtin.template: 30 | src: ".pyenvrc.j2" 31 | dest: "{{ pyenvrc_path }}/.pyenvrc" 32 | owner: "{{ pyenv_owner }}" 33 | mode: "0644" 34 | 35 | - name: Install custom .pyenvrc 36 | ansible.builtin.copy: 37 | src: "{{ pyenv_custom_pyenvrc_file }}" 38 | dest: "{{ pyenvrc_path }}/.pyenvrc.custom" 39 | owner: "{{ pyenv_owner }}" 40 | mode: "0644" 41 | when: pyenv_custom_pyenvrc_file is defined 42 | 43 | - name: Use legacy variable pyenv_setting_path if available 44 | ansible.builtin.set_fact: 45 | pyenv_shellrc_file: "{{ pyenv_setting_path }}" 46 | when: pyenv_setting_path is defined 47 | 48 | - name: Print deprecation warning if pyenv_setting_path defined 49 | ansible.builtin.debug: 50 | msg: pyenv_setting_path is deprecated, use pyenv_shellrc_file instead 51 | when: pyenv_setting_path is defined 52 | 53 | - name: "Load pyenv env variables in {{ pyenv_shellrc_file }}" 54 | ansible.builtin.lineinfile: 55 | dest: "{{ pyenv_shellrc_file }}" 56 | regexp: "\\.pyenvrc$" 57 | line: "source {{ pyenvrc_path }}/.pyenvrc" 58 | state: present 59 | mode: "0644" 60 | create: true 61 | 62 | - name: "Add pyenv autocomplete in {{ pyenv_shellrc_file }}" 63 | ansible.builtin.lineinfile: 64 | dest: "{{ pyenv_shellrc_file }}" 65 | regexp: "pyenv\\.bash$" 66 | line: "source {{ pyenv_path }}/completions/pyenv.bash" 67 | mode: "0644" 68 | state: present 69 | when: pyenv_enable_autocompletion 70 | 71 | - name: Update Pyenv interpreter list 72 | ansible.builtin.shell: 73 | cmd: . {{ pyenvrc_path }}/.pyenvrc && pyenv update 74 | register: output 75 | changed_when: "output.rc == 0" 76 | when: pyenv_update 77 | 78 | - name: Uninstall existing Python interpreters w/ wrong compilation flags 79 | ansible.builtin.shell: 80 | cmd: ". {{ pyenvrc_path }}/.pyenvrc && pyenv uninstall -f {{ item }}" 81 | args: 82 | removes: "{{ pyenv_path }}/versions/{{ item }}/bin/python" 83 | loop: "{{ ansible_local['pyenv_python_installations']['to_reinstall'] | default([]) }}" 84 | when: pyenv_uninstall_python_w_wrong_configure_opts 85 | 86 | - name: "Install Python interpreters {{ pyenv_python_versions }}" 87 | ansible.builtin.shell: 88 | cmd: ". {{ pyenvrc_path }}/.pyenvrc && pyenv install {{ pyenv_install_extra_opts }} {{ item }}" 89 | args: 90 | creates: "{{ pyenv_path }}/versions/{{ item }}/bin/python" 91 | with_items: "{{ pyenv_python_versions }}" 92 | 93 | - name: Create virtual environments 94 | ansible.builtin.shell: 95 | cmd: . {{ pyenvrc_path }}/.pyenvrc && pyenv virtualenv {{ item.py_version }} {{ item.venv_name }} 96 | args: 97 | creates: "{{ pyenv_path }}/versions/{{ item.py_version }}/envs/{{ item.venv_name }}/bin/python" 98 | with_items: "{{ pyenv_virtualenvs }}" 99 | when: pyenv_enable_virtualenvs 100 | 101 | - name: Update the pyenv globals 102 | when: pyenv_global is defined 103 | vars: 104 | pyenv_global_versions_path: "{{ pyenv_path }}/version" 105 | block: 106 | - name: "Does pyenv globals file exist?" 107 | ansible.builtin.stat: 108 | path: "{{ pyenv_global_versions_path }}" 109 | register: _pyenv_globals_file 110 | - name: "Read the pyenv global versions" 111 | ansible.builtin.slurp: 112 | src: "{{ pyenv_global_versions_path }}" 113 | register: _pyenv_globals_file_content 114 | when: _pyenv_globals_file.stat.exists 115 | - name: "Set pyenv global {{ pyenv_global }}" 116 | ansible.builtin.shell: . {{ pyenvrc_path }}/.pyenvrc && pyenv global {{ pyenv_global | join(' ') }} && pyenv rehash 117 | changed_when: true 118 | when: > 119 | ( 120 | _pyenv_globals_file.stat.exists 121 | and (_pyenv_globals_file_content.content | b64decode | split('\n') | select() | list) != pyenv_global 122 | ) 123 | or not _pyenv_globals_file.stat.exists 124 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ansible role: Pyenv 2 | 3 | [![Tests](https://github.com/staticdev/ansible-galaxy-pyenv/workflows/Tests/badge.svg)][tests] 4 | 5 | [tests]: https://github.com/staticdev/ansible-galaxy-pyenv/actions?workflow=Tests 6 | 7 | **DEPRECATED: this project is not maintained anymore in favor direct installation with [nix package manager](https://nixos.wiki/wiki/Nix_package_manager) and [Nixpkgs](https://github.com/NixOS/nixpkgs).** 8 | 9 | Ansible Galaxy role for [pyenv] on Debian / Ubuntu / RedHat / OSX. 10 | 11 | Install it with the following command: 12 | 13 | ```console 14 | $ ansible-galaxy install staticdev.pyenv 15 | ``` 16 | 17 | ## Requirements 18 | 19 | None. 20 | 21 | ## Role Variables 22 | 23 | Here is the list of all variables and their default values: 24 | 25 | - `pyenv_version: "HEAD"` - check https://github.com/pyenv/pyenv/releases 26 | - `pyenv_virtualenv_version: "HEAD"` - check https://github.com/pyenv/pyenv-virtualenv/releases 27 | - `pyenv_update_version: "HEAD"` - usually do not have releases but one can specify a commit hash 28 | - `pyenv_env: "user"` (should be either `"user"` or `"system"`) 29 | - `pyenv_path: "{% if pyenv_env == 'user' %}{{ ansible_env.HOME }}/pyenv{% else %}/usr/local/pyenv{% endif %}"` 30 | - `pyenvrc_path: "{{ pyenv_path }}"` 31 | - `pyenv_owner: "{{ ansible_facts.user_id }}"` 32 | - `pyenv_owner_group: "{{ pyenv_owner }}"` 33 | - `pyenv_python_versions: [3.12.1]` 34 | - `pyenv_virtualenvs: [{ venv_name: latest, py_version: 3.12.1 }]` 35 | - `pyenv_global: [3.12.1]` 36 | - `pyenv_update_git_install: true` (get latest pyenv from git) 37 | - `pyenv_enable_autocompletion: false` 38 | - `pyenv_enable_virtualenvs: true` 39 | - `pyenv_shellrc_file: "{% if pyenv_env == 'user' %}~/.bashrc{% else %}/etc/profile.d/pyenv.sh{% endif %}"` 40 | - `pyenv_tmpdir: (must be explicitly defined)` - env variable `TMPDIR` used by [python-build][python-build] as described in [Special Environment Variables][special-env-vars]. 41 | - `pyenv_python_build_build_path: (must be explicitly defined)` - env variable `PYTHON_BUILD_BUILD_PATH` used by [python-build][python-build] as described in [Special Environment Variables][special-env-vars]. 42 | - `pyenv_python_build_cache_path: (must be explicitly defined)` - env variable `PYTHON_BUILD_CACHE_PATH` used by [python-build][python-build] as described in [Special Environment Variables][special-env-vars]. 43 | - `pyenv_python_build_mirror_url: (must be explicitly defined)` - env variable `PYTHON_BUILD_MIRROR_URL` used by [python-build][python-build] as described in [Special Environment Variables][special-env-vars]. 44 | - `pyenv_python_build_mirror_url_skip_checksum: (must be explicitly defined)` - env variable `PYTHON_BUILD_MIRROR_URL_SKIP_CHECKSUM` used by [python-build][python-build] as described in [Special Environment Variables][special-env-vars]. 45 | - `pyenv_python_build_skip_mirror: (must be explicitly defined)` - env variable `PYTHON_BUILD_SKIP_MIRROR` used by [python-build][python-build] as described in [Special Environment Variables][special-env-vars]. 46 | - `pyenv_python_build_skip_homebrew: (must be explicitly defined)` - env variable `PYTHON_BUILD_SKIP_HOMEBREW` used by [python-build][python-build] as described in [Special Environment Variables][special-env-vars]. 47 | - `pyenv_python_build_root: (must be explicitly defined)` - env variable `PYTHON_BUILD_ROOT` used by [python-build][python-build] as described in [Special Environment Variables][special-env-vars]. 48 | - `pyenv_python_build_definitions: (must be explicitly defined)` - env variable `PYTHON_BUILD_DEFINITIONS` used by [python-build][python-build] as described in [Special Environment Variables][special-env-vars]. 49 | - `pyenv_python_configure_opts: (must be explicitly defined)` - env variable `PYTHON_CONFIGURE_OPTS` used by [python-build][python-build] as described in [Special Environment Variables][special-env-vars]. 50 | - `pyenv_python_cflags: (must be explicitly defined)` - env variable `PYTHON_CFLAGS` used by [python-build][python-build] as described in [Special Environment Variables][special-env-vars]. 51 | - `pyenv_python_make_opts: (must be explicitly defined)` - env variable `PYTHON_MAKE_OPTS` used by [python-build][python-build] as described in [Special Environment Variables][special-env-vars]. 52 | - `pyenv_python_make_install_opts: (must be explicitly defined)` - env variable `PYTHON_MAKE_INSTALL_OPTS` used by [python-build][python-build] as described in [Special Environment Variables][special-env-vars]. 53 | - `pyenv_configure_opts: (must be explicitly defined)` - env variable `CONFIGURE_OPTS` used by [python-build][python-build] as described in [Special Environment Variables][special-env-vars]. 54 | - `pyenv_cc: (must be explicitly defined)` - env variable `CC` used by [python-build][python-build] as described in [Special Environment Variables][special-env-vars]. 55 | - `pyenv_make: (must be explicitly defined)` - env variable `MAKE` used by [python-build][python-build] as described in [Special Environment Variables][special-env-vars]. 56 | - `pyenv_make_opts: (must be explicitly defined)` - env variable `MAKE_OPTS` used by [python-build][python-build] as described in [Special Environment Variables][special-env-vars]. 57 | - `pyenv_make_install_opts: (must be explicitly defined)` - env variable `MAKE_INSTALL_OPTS` used by [python-build][python-build] as described in [Special Environment Variables][special-env-vars]. 58 | - `pyenv_profile_task: (must be explicitly defined)` - env variable `PROFILE_TASK` to customize the task used for profile guided optimization as described in [building Python for maximum performance][max-performance]. See also [here](https://docs.python.org/3/using/configure.html#cmdoption-enable-optimizations). 59 | - `pyenv_custom_pyenvrc_file: (must be explicitly defined)` - path to a custom `.pyenvrc` shell file that will be sourced from `{{ pyenvrc_path }}/.pyenvrc`. It allows you to freely customize the environment to be used during `pyenv` execution. If defined, this file will be copied as `{{ pyenvrc_path }}/.pyenvrc.custom`. 60 | - `pyenv_install_extra_opts: ("" -no extra options added-)` - check output of `pyenv install --help` for available additional options. 61 | 62 | ## Dependencies 63 | 64 | None. 65 | 66 | ## Example Playbook 67 | 68 | ```yaml 69 | - hosts: servers 70 | roles: 71 | - role: staticdev.pyenv 72 | vars: 73 | # from https://github.com/pyenv/pyenv/releases 74 | pyenv_version: "v2.3.35" 75 | # from https://github.com/pyenv/pyenv-virtualenv/releases 76 | pyenv_virtualenv_version: "v1.2.1" 77 | # from https://github.com/pyenv/pyenv-update/commits/master/ 78 | pyenv_update_version: "172a0ed" 79 | pyenv_shellrc_file: "{{ ansible_env.HOME }}/.shrc" 80 | pyenv_path: "{{ ansible_env.HOME }}/.pyenv" 81 | pyenvrc_path: "{{ ansible_env.HOME }}" 82 | pyenv_owner: "{{ instance_owner }}" 83 | pyenv_global: 84 | - 3.12.1 85 | - 3.11.7 86 | pyenv_enable_autocompletion: false 87 | pyenv_python_versions: 88 | - 3.12.1 89 | - 3.11.7 90 | pyenv_virtualenvs: 91 | - venv_name: latest_v312 92 | py_version: 3.12.1 93 | - venv_name: latest_v311 94 | py_version: 3.11.7 95 | pyenv_make_opts: "-j4" 96 | pyenv_python_configure_opts: "--enable-optimizations --with-lto --with-ensurepip=upgrade" 97 | pyenv_python_cflags: "-march=native -mtune=native" 98 | pyenv_profile_task: "-m test.regrtest --pgo -j0" 99 | ``` 100 | 101 | ## License 102 | 103 | Distributed under the terms of the [MIT] license, 104 | _Ansible role Pyenv_ is free and open source software. 105 | 106 | ## Author Information 107 | 108 | [staticdev]. Heavily based on Maxim Avanov's [avanov.pyenv] 109 | 110 | [avanov.pyenv]: https://galaxy.ansible.com/avanov/pyenv 111 | [mit]: https://opensource.org/licenses/MIT 112 | [pyenv]: https://github.com/yyuu/pyenv 113 | [staticdev]: https://github.com/staticdev 114 | [python-build]: https://github.com/pyenv/pyenv/tree/master/plugins/python-build "python-build plugin" 115 | [special-env-vars]: https://github.com/pyenv/pyenv/blob/master/plugins/python-build/README.md#special-environment-variables "Special environment variables" 116 | [max-performance]: https://github.com/pyenv/pyenv/blob/master/plugins/python-build/README.md#building-for-maximum-performance "Building for maximum performance" 117 | --------------------------------------------------------------------------------