diff --git a/.github/workflows/test-python.yml b/.github/workflows/test-python.yml index 7f2d9637..cdea4eb1 100644 --- a/.github/workflows/test-python.yml +++ b/.github/workflows/test-python.yml @@ -290,6 +290,108 @@ jobs: with: python-version-file: .tool-versions + setup-versions-from-pipfile-with-python_version: + name: Setup ${{ matrix.python }} ${{ matrix.os }} Pipfile with python_version + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: + [ + macos-latest, + windows-latest, + ubuntu-20.04, + ubuntu-22.04, + ubuntu-22.04-arm, + macos-13, + ubuntu-latest, + ubuntu-24.04-arm + ] + python: [3.9.13, 3.10.11, 3.11.9, 3.13.2] + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: build-version-file ${{ matrix.python }} + run: | + echo '[requires] + python_version = "${{ matrix.python }}" + ' > Pipenv + + - name: setup-python ${{ matrix.python }} + id: setup-python + uses: ./ + with: + python-version-file: Pipenv + + - name: Check python-path + run: ./__tests__/check-python-path.sh '${{ steps.setup-python.outputs.python-path }}' + shell: bash + + - name: Validate version + run: | + $pythonVersion = (python --version) + if ("Python ${{ matrix.python }}".replace("==", "") -ne "$pythonVersion"){ + Write-Host "The current version is $pythonVersion; expected version is ${{ matrix.python }}" + exit 1 + } + $pythonVersion + shell: pwsh + + - name: Run simple code + run: python -c 'import math; print(math.factorial(5))' + + setup-versions-from-pipfile-with-python_full_version: + name: Setup ${{ matrix.python }} ${{ matrix.os }} Pipfile with python_full_version + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: + [ + macos-latest, + windows-latest, + ubuntu-20.04, + ubuntu-22.04, + ubuntu-22.04-arm, + macos-13, + ubuntu-latest, + ubuntu-24.04-arm + ] + python: [3.9.13, 3.10.11, 3.11.9, 3.13.2] + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: build-version-file ${{ matrix.python }} + run: | + echo '[requires] + python_full_version = "${{ matrix.python }}" + ' > Pipenv + + - name: setup-python ${{ matrix.python }} + id: setup-python + uses: ./ + with: + python-version-file: Pipenv + + - name: Check python-path + run: ./__tests__/check-python-path.sh '${{ steps.setup-python.outputs.python-path }}' + shell: bash + + - name: Validate version + run: | + $pythonVersion = (python --version) + if ("Python ${{ matrix.python }}".replace("==", "") -ne "$pythonVersion"){ + Write-Host "The current version is $pythonVersion; expected version is ${{ matrix.python }}" + exit 1 + } + $pythonVersion + shell: pwsh + + - name: Run simple code + run: python -c 'import math; print(math.factorial(5))' + setup-pre-release-version-from-manifest: name: Setup 3.14.0-alpha.6 ${{ matrix.os }} runs-on: ${{ matrix.os }} diff --git a/__tests__/utils.test.ts b/__tests__/utils.test.ts index 6c0f0e13..db9ff7b3 100644 --- a/__tests__/utils.test.ts +++ b/__tests__/utils.test.ts @@ -12,6 +12,7 @@ import { getVersionInputFromFile, getVersionInputFromPlainFile, getVersionInputFromTomlFile, + getVersionInputFromPipfileFile, getNextPageUrl, isGhes, IS_WINDOWS, @@ -216,6 +217,44 @@ describe('Version from file test', () => { expect(_fn(toolVersionFilePath)).toEqual(['3.14t-dev']); } ); + + it.each([getVersionInputFromPipfileFile, getVersionInputFromFile])( + 'Version from python_version in Pipfile', + async _fn => { + await io.mkdirP(tempDir); + const pythonVersionFileName = 'Pipfile'; + const pythonVersionFilePath = path.join(tempDir, pythonVersionFileName); + const pythonVersion = '3.7'; + const pythonVersionFileContent = `[requires]\npython_version = "${pythonVersion}"`; + fs.writeFileSync(pythonVersionFilePath, pythonVersionFileContent); + expect(_fn(pythonVersionFilePath)).toEqual([pythonVersion]); + } + ); + + it.each([getVersionInputFromPipfileFile, getVersionInputFromFile])( + 'Version from python_full_version in Pipfile', + async _fn => { + await io.mkdirP(tempDir); + const pythonVersionFileName = 'Pipfile'; + const pythonVersionFilePath = path.join(tempDir, pythonVersionFileName); + const pythonVersion = '3.7.0'; + const pythonVersionFileContent = `[requires]\npython_full_version = "${pythonVersion}"`; + fs.writeFileSync(pythonVersionFilePath, pythonVersionFileContent); + expect(_fn(pythonVersionFilePath)).toEqual([pythonVersion]); + } + ); + + it.each([getVersionInputFromPipfileFile, getVersionInputFromFile])( + 'Pipfile undefined version', + async _fn => { + await io.mkdirP(tempDir); + const pythonVersionFileName = 'Pipfile'; + const pythonVersionFilePath = path.join(tempDir, pythonVersionFileName); + const pythonVersionFileContent = ``; + fs.writeFileSync(pythonVersionFilePath, pythonVersionFileContent); + expect(_fn(pythonVersionFilePath)).toEqual([]); + } + ); }); describe('getNextPageUrl', () => { diff --git a/dist/setup/index.js b/dist/setup/index.js index 2cdcfaad..85b8b0c2 100644 --- a/dist/setup/index.js +++ b/dist/setup/index.js @@ -97759,7 +97759,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.getDownloadFileName = exports.getNextPageUrl = exports.getBinaryDirectory = exports.getVersionInputFromFile = exports.getVersionInputFromToolVersions = exports.getVersionInputFromPlainFile = exports.getVersionInputFromTomlFile = exports.getOSInfo = exports.getLinuxInfo = exports.logWarning = exports.isCacheFeatureAvailable = exports.isGhes = exports.validatePythonVersionFormatForPyPy = exports.writeExactPyPyVersionFile = exports.readExactPyPyVersionFile = exports.getPyPyVersionFromPath = exports.isNightlyKeyword = exports.validateVersion = exports.createSymlinkInFolder = exports.WINDOWS_PLATFORMS = exports.WINDOWS_ARCHS = exports.IS_MAC = exports.IS_LINUX = exports.IS_WINDOWS = void 0; +exports.getDownloadFileName = exports.getNextPageUrl = exports.getBinaryDirectory = exports.getVersionInputFromFile = exports.getVersionInputFromPipfileFile = exports.getVersionInputFromToolVersions = exports.getVersionInputFromPlainFile = exports.getVersionInputFromTomlFile = exports.getOSInfo = exports.getLinuxInfo = exports.logWarning = exports.isCacheFeatureAvailable = exports.isGhes = exports.validatePythonVersionFormatForPyPy = exports.writeExactPyPyVersionFile = exports.readExactPyPyVersionFile = exports.getPyPyVersionFromPath = exports.isNightlyKeyword = exports.validateVersion = exports.createSymlinkInFolder = exports.WINDOWS_PLATFORMS = exports.WINDOWS_ARCHS = exports.IS_MAC = exports.IS_LINUX = exports.IS_WINDOWS = void 0; /* eslint no-unsafe-finally: "off" */ const cache = __importStar(__nccwpck_require__(5116)); const core = __importStar(__nccwpck_require__(7484)); @@ -98014,7 +98014,37 @@ function getVersionInputFromToolVersions(versionFile) { } exports.getVersionInputFromToolVersions = getVersionInputFromToolVersions; /** - * Python version extracted from a plain, .tool-versions or TOML file. + * Python version extracted from the Pipfile file. + */ +function getVersionInputFromPipfileFile(versionFile) { + core.debug(`Trying to resolve version form ${versionFile}`); + let pipfileFile = fs_1.default.readFileSync(versionFile, 'utf8'); + // Normalize the line endings in the pipfileFile + pipfileFile = pipfileFile.replace(/\r\n/g, '\n'); + const pipfileConfig = toml.parse(pipfileFile); + const keys = ['requires']; + if (!('requires' in pipfileConfig)) { + core.warning(`No Python version found in ${versionFile}`); + return []; + } + if ('python_full_version' in pipfileConfig['requires']) { + // specifies a full python version + keys.push('python_full_version'); + } + else { + keys.push('python_version'); + } + const versions = []; + const version = extractValue(pipfileConfig, keys); + if (version !== undefined) { + versions.push(version); + } + core.info(`Extracted ${versions} from ${versionFile}`); + return [extractValue(pipfileConfig, keys)]; +} +exports.getVersionInputFromPipfileFile = getVersionInputFromPipfileFile; +/** + * Python version extracted from a plain, .tool-versions, Pipfile or TOML file. */ function getVersionInputFromFile(versionFile) { if (versionFile.endsWith('.toml')) { @@ -98023,6 +98053,9 @@ function getVersionInputFromFile(versionFile) { else if (versionFile.match('.tool-versions')) { return getVersionInputFromToolVersions(versionFile); } + else if (versionFile.match('Pipfile')) { + return getVersionInputFromPipfileFile(versionFile); + } else { return getVersionInputFromPlainFile(versionFile); } diff --git a/docs/advanced-usage.md b/docs/advanced-usage.md index 72b35016..69c8fea5 100644 --- a/docs/advanced-usage.md +++ b/docs/advanced-usage.md @@ -309,6 +309,15 @@ steps: - run: python my_script.py ``` +```yaml +steps: +- uses: actions/checkout@v4 +- uses: actions/setup-python@v5 + with: + python-version-file: 'Pipfile' # Read python version from a file Pipfile +- run: python my_script.py +``` + ## Check latest version The `check-latest` flag defaults to `false`. Use the default or set `check-latest` to `false` if you prefer stability and if you want to ensure a specific `Python or PyPy` version is always used. diff --git a/src/utils.ts b/src/utils.ts index 6274895e..cb363547 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -310,14 +310,50 @@ export function getVersionInputFromToolVersions(versionFile: string): string[] { return []; } } + /** - * Python version extracted from a plain, .tool-versions or TOML file. + * Python version extracted from the Pipfile file. + */ +export function getVersionInputFromPipfileFile(versionFile: string): string[] { + core.debug(`Trying to resolve version form ${versionFile}`); + + let pipfileFile = fs.readFileSync(versionFile, 'utf8'); + // Normalize the line endings in the pipfileFile + pipfileFile = pipfileFile.replace(/\r\n/g, '\n'); + + const pipfileConfig = toml.parse(pipfileFile); + const keys = ['requires']; + + if (!('requires' in pipfileConfig)) { + core.warning(`No Python version found in ${versionFile}`); + return []; + } + if ('python_full_version' in (pipfileConfig['requires'] as toml.JsonMap)) { + // specifies a full python version + keys.push('python_full_version'); + } else { + keys.push('python_version'); + } + const versions = []; + const version = extractValue(pipfileConfig, keys); + if (version !== undefined) { + versions.push(version); + } + + core.info(`Extracted ${versions} from ${versionFile}`); + return [extractValue(pipfileConfig, keys)] as string[]; +} + +/** + * Python version extracted from a plain, .tool-versions, Pipfile or TOML file. */ export function getVersionInputFromFile(versionFile: string): string[] { if (versionFile.endsWith('.toml')) { return getVersionInputFromTomlFile(versionFile); } else if (versionFile.match('.tool-versions')) { return getVersionInputFromToolVersions(versionFile); + } else if (versionFile.match('Pipfile')) { + return getVersionInputFromPipfileFile(versionFile); } else { return getVersionInputFromPlainFile(versionFile); }