commit 33ed3db2ba868be2238825bd620a66b3519b838e Author: Christian Tosta <7252968+christiantosta@users.noreply.github.com> Date: Tue Aug 11 14:46:19 2026 -0300 Initial Commit diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..264516b --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,62 @@ +# SPDX-FileCopyrightText: 2026 Christian Tosta +# SPDX-License-Identifier: GPL-2.0-or-later +# @file .github/workflows/build.yml +# @brief Simplified master workflow using CMake workflows + +name: Pyenv Build & Release + +on: + push: + tags: + - "v*" + +jobs: + release-ci: + runs-on: ubuntu-24.04 + outputs: + VERSION: ${{ steps.step_getlatest_tag.outputs.VERSION }} + steps: + - name: Checkout local + uses: actions/checkout@v4 + with: + repository: ${{ github.repository }} + sparse-checkout: | + .github/workflows + + - name: Install build dependencies + run: sudo apt-get install git -y -qq + + - name: Get latest pyenv release tag + id: step_getlatest_tag + run: | + latest_tag=$(git -c 'versionsort.suffix=-' ls-remote --exit-code --refs --sort='version:refname' --tags https://github.com/pyenv/pyenv '*.*' \ + | tail --lines=1 | cut --delimiter='/' --fields=3) + + test ! -z "$latest_tag" && echo "LATEST_TAG=$latest_tag" >> "$GITHUB_ENV" || exit 1 + echo "VERSION=$(echo $latest_tag | sed 's/v//')" >> "$GITHUB_OUTPUT" + + - name: Save version + run: echo ${{ steps.step_getlatest_tag.outputs.VERSION }} > VERSION + + - name: Upload version + uses: actions/upload-artifact@v4 + with: + name: VERSION + path: VERSION + retention-days: 1 + + debian: + needs: release-ci + uses: ./.github/workflows/targets/debian.yml + with: + containers: "['debian:trixie','debian:bookworm']" + + fedora: + needs: release-ci + uses: ./.github/workflows/targets/fedora.yml + with: + containers: "['fedora:44','fedora:45','fedora:latest']" + + publish: + needs: [debian, fedora] + uses: ./.github/workflows/release.yml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..7c6977b --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,34 @@ +# SPDX-FileCopyrightText: 2026 Christian Tosta +# SPDX-License-Identifier: GPL-2.0-or-later + +name: Publish release + +on: workflow_call + +jobs: + publish: + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v4 + + - name: Get version + uses: actions/download-artifact@v4 + with: + name: VERSION + + - name: Set version + run: echo "VERSION=$(cat VERSION | xargs)" >> "$GITHUB_ENV" + + - name: Download artifacts + uses: actions/download-artifact@v4 + with: + pattern: pyenv-* + merge-multiple: true + + - name: Release + uses: softprops/action-gh-release@v2 + if: startsWith(github.ref, 'refs/tags/') + with: + files: | + *.deb + *.rpm diff --git a/.github/workflows/targets/debian.yml b/.github/workflows/targets/debian.yml new file mode 100644 index 0000000..5ba5cc1 --- /dev/null +++ b/.github/workflows/targets/debian.yml @@ -0,0 +1,38 @@ +# SPDX-FileCopyrightText: 2026 Christian Tosta +# SPDX-License-Identifier: GPL-2.0-or-later +# @file .github/workflows/targets/debian.yml +# @brief Simplified Debian build target using workflow preset + +name: Debian Build Target + +on: + workflow_call: + inputs: + containers: + required: true + type: string + +jobs: + build-deb: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + container: ${{ fromJson(inputs.containers) }} + container: + image: ${{ matrix.container }} + steps: + - name: Checkout Repository + uses: actions/checkout@v4 + + - name: Install Dependencies + run: apt-get update && apt-get install -y cmake build-essential git dpkg-dev fakeroot + + - name: Run CMake Workflow + run: cmake --workflow --preset deb-workflow + + - name: Upload Artifacts + uses: actions/upload-artifact@v4 + with: + name: pyenv-deb-${{ matrix.container }} + path: build/deb/*.deb diff --git a/.github/workflows/targets/fedora.yml b/.github/workflows/targets/fedora.yml new file mode 100644 index 0000000..9f29b3b --- /dev/null +++ b/.github/workflows/targets/fedora.yml @@ -0,0 +1,34 @@ +# SPDX-FileCopyrightText: 2026 Christian Tosta +# SPDX-License-Identifier: GPL-2.0-or-later +# @file .github/workflows/targets/fedora.yml +# @brief Fedora build target with internal container matrix + +name: Fedora Build Target + +on: + workflow_call: + +jobs: + build-rpm: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + container: [fedora:42, fedora:43, fedora:latest] + container: + image: ${{ matrix.container }} + steps: + - name: Checkout Repository + uses: actions/checkout@v4 + + - name: Install Dependencies + run: dnf install -y cmake gcc-c++ make git rpm-build + + - name: Run CMake Workflow + run: cmake --workflow --preset rpm-workflow + + - name: Upload Artifacts + uses: actions/upload-artifact@v4 + with: + name: pyenv-rpm-${{ matrix.container }} + path: build/rpm/*.rpm diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f4cd774 --- /dev/null +++ b/.gitignore @@ -0,0 +1,37 @@ +.agignore +/bats +/bats-*/ +bin/ +build/ +/cache +CHANGELOG.md +COMMANDS.md +completions/ +CONDUCT.md +CONTRIBUTING.md +/default-packages +.dockerignore +.editorconfig +.idea +install_local_python.gif +libexec/ +/libexec/*.dylib +LICENSE.pyenv.md +MAINTENANCE.md +Makefile +man/ +plugins/ +pyenv/ +pyenv.d/ +README.pyenv.md +/shims +/sources +src/ +/src/Makefile +/src/*.o +terminal_output.png +test/ +*.un~ +/version +/versions +.vimrc diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..8cc3bb6 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,47 @@ +# SPDX-FileCopyrightText: 2026 Christian Tosta +# SPDX-License-Identifier: GPL-2.0-or-later +# +# @file CMakeLists.txt +# @brief Main CMake configuration for multi-distribution pyenv packaging. + +cmake_minimum_required(VERSION 3.24) +project(pyenv-packaging NONE) + +file(READ "${CMAKE_CURRENT_SOURCE_DIR}/metadata.json" METADATA_JSON) +string(JSON PYENV_VERSION GET ${METADATA_JSON} "version") +string(JSON PYENV_URL GET ${METADATA_JSON} "source_url") +string(REPLACE "\${self.version}" "${PYENV_VERSION}" PYENV_URL "${PYENV_URL}") + +include(ExternalProject) + +ExternalProject_Add(pyenv_upstream + URL ${PYENV_URL} + DOWNLOAD_EXTRACT_TIMESTAMP TRUE + CONFIGURE_COMMAND "" + BUILD_COMMAND "" + INSTALL_COMMAND "" + PATCH_COMMAND sh -c "find \\( -name .git\\* \\) -exec rm -rf {} +" +) + +set(PYENV_SRC_UPSTREAM "${CMAKE_CURRENT_BINARY_DIR}/pyenv_upstream-prefix/src/pyenv_upstream") +set(PYENV_SRC "${CMAKE_CURRENT_SOURCE_DIR}/pyenv") + +add_custom_target(merge_upstream ALL + COMMAND ${CMAKE_COMMAND} -E copy_directory "${PYENV_SRC_UPSTREAM}" "${PYENV_SRC}" + DEPENDS pyenv_upstream +) + +include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/CPackRules.cmake) + +install(DIRECTORY ${PYENV_SRC}/bin ${PYENV_SRC}/libexec ${PYENV_SRC}/plugins ${PYENV_SRC}/pyenv.d + DESTINATION ${PACKAGE_PREFIX}/share/pyenv) + +install(DIRECTORY ${PYENV_SRC}/man DESTINATION ${PACKAGE_PREFIX}/share) + +install(CODE "file(MAKE_DIRECTORY \"\$ENV{DESTDIR}${PACKAGE_PREFIX}/share/pyenv/versions\")") +install(CODE "file(MAKE_DIRECTORY \"\$ENV{DESTDIR}${PACKAGE_PREFIX}/share/pyenv/shims\")") +install(CODE "file(MAKE_DIRECTORY \"\$ENV{DESTDIR}${PACKAGE_PREFIX}/share/pyenv/hooks\")") + +install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/pyenv.sh" + DESTINATION /etc/profile.d + PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ) diff --git a/CMakePresets.json b/CMakePresets.json new file mode 100644 index 0000000..a3616b1 --- /dev/null +++ b/CMakePresets.json @@ -0,0 +1,88 @@ +{ + "version": 6, + "cmakeMinimumRequired": { + "major": 3, + "minor": 24 + }, + "configurePresets": [ + { + "name": "deb-config", + "displayName": "Debian / Ubuntu Build", + "binaryDir": "${sourceDir}/build/deb", + "cacheVariables": { + "BUILD_DEB": "ON", + "BUILD_RPM": "OFF", + "CMAKE_INSTALL_PREFIX": "/usr" + } + }, + { + "name": "rpm-config", + "displayName": "Fedora / RPM Build", + "binaryDir": "${sourceDir}/build/rpm", + "cacheVariables": { + "BUILD_DEB": "OFF", + "BUILD_RPM": "ON", + "CMAKE_INSTALL_PREFIX": "/usr" + } + }, + { + "name": "act-config", + "displayName": "Local Containerized Build (act)", + "binaryDir": "${sourceDir}/build/act", + "cacheVariables": { + "USE_ACT_CONTAINER": "ON", + "BUILD_DEB": "ON" + } + } + ], + "buildPresets": [ + { + "name": "deb-build", + "configurePreset": "deb-config" + }, + { + "name": "rpm-build", + "configurePreset": "rpm-config" + }, + { + "name": "rpm-rebuild", + "configurePreset": "rpm-config", + "targets": [ "rpm-rebuild" ] + }, + { + "name": "act-build", + "configurePreset": "act-config", + "targets": ["act-build"] + } + ], + "packagePresets": [ + { + "name": "deb-package", + "configurePreset": "deb-config" + }, + { + "name": "srpm-package", + "configurePreset": "rpm-config", + "configFile": "CPackSourceConfig.cmake", + "generators": ["RPM"] + } + ], + "workflowPresets": [ + { + "name": "deb-workflow", + "steps": [ + { "type": "configure", "name": "deb-config" }, + { "type": "build", "name": "deb-build" }, + { "type": "package", "name": "deb-package" } + ] + }, + { + "name": "rpm-workflow", + "steps": [ + { "type": "configure", "name": "rpm-config" }, + { "type": "build", "name": "rpm-build" }, + { "type": "package", "name": "srpm-package" } + ] + } + ] +} diff --git a/cmake/ActBuilder.cmake b/cmake/ActBuilder.cmake new file mode 100644 index 0000000..8dc1beb --- /dev/null +++ b/cmake/ActBuilder.cmake @@ -0,0 +1,20 @@ +# SPDX-FileCopyrightText: 2026 Christian Tosta +# SPDX-License-Identifier: GPL-2.0-or-later +# @file cmake/ActBuilder.cmake +# @brief Helper module to orchestrate local builds via act containerization + +option(USE_ACT_CONTAINER "Enable local containerized build via act" OFF) + +find_program(ACT_EXECUTABLE act) + +if(ACT_EXECUTABLE) + message(STATUS "Found act utility: ${ACT_EXECUTABLE}") + + add_custom_target(act-build + COMMAND ${ACT_EXECUTABLE} push --container-architecture linux/amd64 + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + COMMENT "Running local CI pipeline using act..." + ) +else() + message(WARNING "The 'act' utility was not found. Local containerized builds via act-build target will be unavailable.") +endif() diff --git a/cmake/CPackConfig.cmake.in b/cmake/CPackConfig.cmake.in new file mode 100644 index 0000000..0406392 --- /dev/null +++ b/cmake/CPackConfig.cmake.in @@ -0,0 +1,29 @@ +# This file will be configured to contain variables for CPack. These variables +# should be set in the CMake list file of the project before CPack module is +# included. Example variables are: +# CPACK_GENERATOR - Generator used to create package +# CPACK_INSTALL_CMAKE_PROJECTS - For each project (path, name, component) +# CPACK_CMAKE_GENERATOR - CMake Generator used for the projects +# CPACK_INSTALL_COMMANDS - Extra commands to install components +# CPACK_INSTALL_DIRECTORIES - Extra directories to install +# CPACK_PACKAGE_DESCRIPTION_FILE - Description file for the package +# CPACK_PACKAGE_DESCRIPTION_SUMMARY - Summary of the package +# CPACK_PACKAGE_EXECUTABLES - List of pairs of executables and labels +# CPACK_PACKAGE_FILE_NAME - Name of the package generated +# CPACK_PACKAGE_ICON - Icon used for the package +# CPACK_PACKAGE_INSTALL_DIRECTORY - Name of directory for the installer +# CPACK_PACKAGE_NAME - Package project name +# CPACK_PACKAGE_VENDOR - Package project vendor +# CPACK_PACKAGE_VERSION - Package project version +# CPACK_PACKAGE_VERSION_MAJOR - Package project version (major) +# CPACK_PACKAGE_VERSION_MINOR - Package project version (minor) +# CPACK_PACKAGE_VERSION_PATCH - Package project version (patch) + +# There are certain generator specific ones + +# NSIS Generator: +# CPACK_PACKAGE_INSTALL_REGISTRY_KEY - Name of the registry key for the installer +# CPACK_NSIS_EXTRA_UNINSTALL_COMMANDS - Extra commands used during uninstall +# CPACK_NSIS_EXTRA_INSTALL_COMMANDS - Extra commands used during install + +@_CPACK_OTHER_VARIABLES_@ diff --git a/cmake/CPackRules.cmake b/cmake/CPackRules.cmake new file mode 100644 index 0000000..5611129 --- /dev/null +++ b/cmake/CPackRules.cmake @@ -0,0 +1,79 @@ +# SPDX-FileCopyrightText: 2026 Christian Tosta +# SPDX-License-Identifier: GPL-2.0-or-later +# @file targets/debian/cmake/CPackRules.cmake +# @brief Centralized CPack packaging rules for DEB and RPM + +option(BUILD_DEB "Build DEB package" OFF) +option(BUILD_RPM "Build RPM package" OFF) + +set(CPACK_SOURCE_IGNORE_FILES + "/.gitea$" + "/.gitignore$" + "/.git/" + "/[.]agignore$" + "/[.]dockerignore$" + "/[.]editorconfig$" + "/[.]gitea/" + "/[.]github/" + "/[.]vimrc$" + "/build/" + "/Makefile$" + "pkg\\.spec\\.in$" + "~$|\\.swp$" +) +set(CPACK_IGNORE_FILES "${CPACK_SOURCE_IGNORE_FILES}") + +if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/cmake/ActBuilder.cmake") + include(cmake/ActBuilder.cmake) +endif() + +file(READ "${CMAKE_CURRENT_SOURCE_DIR}/metadata.json" METADATA_JSON) + +string(JSON CPACK_PACKAGE_NAME GET ${METADATA_JSON} "name") +string(JSON CPACK_PACKAGE_VERSION GET ${METADATA_JSON} "version") +string(JSON CPACK_PACKAGE_RELEASE GET ${METADATA_JSON} "release") +string(JSON CPACK_PACKAGE_VENDOR GET ${METADATA_JSON} "maintainer") +set(CPACK_VERBATIM_VARIABLES YES) + +if(CPACK_PACKAGING_INSTALL_PREFIX AND NOT CPACK_PACKAGING_INSTALL_PREFIX STREQUAL "/") + set(PACKAGE_PREFIX "${CPACK_PACKAGING_INSTALL_PREFIX}") +elseif(CMAKE_INSTALL_PREFIX AND NOT CMAKE_INSTALL_PREFIX STREQUAL "/") + set(PACKAGE_PREFIX "${CMAKE_INSTALL_PREFIX}") +else() + set(PACKAGE_PREFIX "/usr/local") +endif() + +set(CPACK_PACKAGING_INSTALL_PREFIX "${PACKAGE_PREFIX}") +set(CPACK_SET_DESTDIR ON) +set(CPACK_PACKAGE_RELOCATABLE "OFF") + +if(BUILD_RPM) + set(CPACK_RPM_FILE_NAME "RPM-DEFAULT") + set(CPACK_SOURCE_PACKAGE_FILE_NAME "${CPACK_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION}") + set(CPACK_RPM_USER_BINARY_SPECFILE "${CMAKE_BINARY_DIR}/${CPACK_PACKAGE_NAME}.spec") + configure_file("${CMAKE_CURRENT_SOURCE_DIR}/pkg.spec.in" + "${CPACK_RPM_USER_BINARY_SPECFILE}" + @ONLY + IMMEDIATE + ) +elseif(BUILD_DEB) + set(CPACK_GENERATOR "DEB") + + string(JSON CPACK_DEBIAN_PACKAGE_MAINTAINER GET ${METADATA_JSON} "maintainer") + string(JSON CPACK_DEBIAN_PACKAGE_ARCHITECTURE GET ${METADATA_JSON} "architecture") + + # Build and runtime dependencies + string(JSON DEPENDS_LEN LENGTH ${METADATA_JSON} "deb-depends") + set(CPACK_DEBIAN_PACKAGE_DEPENDS "") + math(EXPR DEPENDS_MAX "${DEPENDS_LEN} - 1") + foreach(i RANGE 0 ${DEPENDS_MAX}) + string(JSON DEP GET ${METADATA_JSON} "deb-depends" ${i}) + list(APPEND CPACK_DEBIAN_PACKAGE_DEPENDS ${DEP}) + endforeach() + list(JOIN CPACK_DEBIAN_PACKAGE_DEPENDS ", " CPACK_DEBIAN_PACKAGE_DEPENDS) + set(CPACK_PACKAGE_FILE_NAME "${CPACK_PACKAGE_NAME}_${CPACK_PACKAGE_VERSION}_${CPACK_DEBIAN_PACKAGE_ARCHITECTURE}") + set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON) + set(CPACK_COMPONENTS_ALL_GROUPS_IN_ONE_PACKAGE ON) +endif() + +include(CPack) diff --git a/metadata.json b/metadata.json new file mode 100644 index 0000000..a99eb62 --- /dev/null +++ b/metadata.json @@ -0,0 +1,20 @@ +{ + "name": "pyenv", + "version": "2.8.3", + "release": "1", + "maintainer": "Christian Tosta ", + "architecture": "all", + "source_url": "https://github.com/pyenv/pyenv/archive/refs/tags/v${self.version}.tar.gz", + "deb-depends": [ + "bash", + "git", + "build-essential", + "libssl-dev", + "zlib1g-dev", + "libbz2-dev", + "libreadline-dev", + "libsqlite3-dev", + "libffi-dev", + "liblzma-dev" + ] +} diff --git a/pkg.spec.in b/pkg.spec.in new file mode 100644 index 0000000..dd4a354 --- /dev/null +++ b/pkg.spec.in @@ -0,0 +1,77 @@ +# SPDX-FileCopyrightText: 2026 Christian Tosta +# SPDX-License-Identifier: GPL-2.0-or-later + +%global plugins ccache virtualenv pip-migrate +%global _prefix @PACKAGE_PREFIX@ + +Name: @CPACK_PACKAGE_NAME@ +Version: @CPACK_PACKAGE_VERSION@ +Release: @CPACK_PACKAGE_RELEASE@%{?dist} +Summary: Simple Python version management + +License: MIT +URL: https://github.com/pyenv/pyenv +Source0: %{url}/archive/refs/tags/v%{version}.tar.gz#/%{name}-%{version}.tar.gz + +BuildArch: noarch +BuildRequires: git + +Requires: bash +Requires: git +Requires: gcc +Requires: make +Requires: openssl-devel +Requires: bzip2-devel +Requires: readline-devel +Requires: sqlite-devel +Requires: libffi-devel +Requires: xz-devel +Requires: tk-devel +Requires: gdbm-devel +Requires: ncurses-devel +Requires: zlib-devel +Requires: libuuid-devel + +%description +pyenv lets you easily switch between multiple versions of Python. It's simple, +unobtrusive, and follows the UNIX tradition of single-purpose tools that do one +thing well. + +%prep +%setup -q -n %{name}-%{version} + +%build +set +x +for plugin in %{plugins}; do + echo -n "Cloning plugin [${plugin}]... " + git clone -q https://github.com/pyenv/pyenv-${plugin} plugins/${plugin} + echo "done." +done +set -x + +%install +rm -rf %{buildroot} + +mkdir -p %{buildroot}%{_datadir}/%{name}/{versions,shims,hooks} +mkdir -p %{buildroot}%{_sysconfdir}/profile.d + +pushd pyenv + cp -r bin libexec plugins pyenv.d %{buildroot}%{_datadir}/%{name}/ + cp -r man %{buildroot}%{_datadir} +popd + +install -p -m 0644 pyenv.sh %{buildroot}%{_sysconfdir}/profile.d/pyenv.sh + +%files +%defattr(-,root,root,-) +%license pyenv/LICENSE +%doc pyenv/README.md pyenv/CHANGELOG.md + +%{_sysconfdir}/profile.d/pyenv.sh +%{_mandir}/man1/*.1* +%{_datadir}/%{name}/ + +%changelog +* Thu Jun 18 2026 Ekaaty Build Service - @CPACK_PACKAGE_VERSION@-@CPACK_PACKAGE_RELEASE@ +- Initial RPM package release for enterprise multi-user environments. +- Added pyenv.sh into /etc/profile.d/ for automated initialization. diff --git a/pyenv.sh b/pyenv.sh new file mode 100644 index 0000000..79a747a --- /dev/null +++ b/pyenv.sh @@ -0,0 +1,40 @@ +# ============================================================================== +# /etc/profile.d/pyenv.sh +# Global Pyenv Configuration for Multi-User Enterprise Environment +# ============================================================================== + +# Add the globally compiled Pyenv core binaries to the system PATH +export PATH="/opt/pyenv/bin:$PATH" + +# Isolate the Pyenv execution root to the current user's HOME directory +# This ensures that 'pyenv rehash' and shim generation have native write permissions +export PYENV_ROOT="$HOME/.pyenv" + +# Bootstrap required directory structure inside the user's HOME if missing +if [ ! -d "$PYENV_ROOT/versions" ]; then + mkdir -p "$PYENV_ROOT/versions" "$PYENV_ROOT/shims" +fi + +# Define the hook path required by Pyenv architecture plugins +export PYENV_HOOK_PATH="/opt/pyenv/hooks" + +# SELF-HEALING: Detect and forcefully purge broken/orphaned symbolic links +# This prevents shell errors if an administrator uninstalls a global Python version +find "$PYENV_ROOT/versions/" -xtype l -delete 2>/dev/null + +# SYNCHRONIZATION: Map globally available Python installations into the user's scope +# Individual symlinks are used so the user's Pyenv can correctly resolve each version +find /opt/pyenv/versions/ -type d -name '[23]*' | \ + while read path; do + version=$(basename $path); + mkdir -p "$PYENV_ROOT/versions/$version/envs" 2>/dev/null + if [ -d "$PYENV_ROOT/versions/$version" ]; then + ln -snf /opt/pyenv/versions/$version/* \ + "$PYENV_ROOT/versions/$version/" 2>/dev/null; + fi + done + +# INITIALIZATION: Spawn the Pyenv environment and virtualenv managers +# into the shell session +eval "$(pyenv init -)" +eval "$(pyenv virtualenv-init -)"