Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
kurtmckee committed Oct 11, 2023
0 parents commit 27a8bfa
Show file tree
Hide file tree
Showing 6 changed files with 301 additions and 0 deletions.
34 changes: 34 additions & 0 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
name: "Test"

on:
push:
branches:
- "main"

jobs:
test-linux:
name: "Test on ${{ matrix.config.os-name }}"
runs-on: "${{ matrix.config.runner }}"
strategy:
matrix:
config:
- os-name: "Linux"
runner: "ubuntu-latest"
test-label: "ci-test-linux"
- os-name: "macOS"
runner: "macos-latest"
test-label: "ci-test-macos"
- os-name: "Windows"
runner: "windows-latest"
test-label: "ci-test-windows"
fail-fast: false

steps:
- name: "Use it!"
id: "finder"
uses: "kurtmckee/setup-python-version-detector@main"

- name: "Print it!"
shell: "bash"
run: |
echo '${{ steps.finder.outputs.python-identifiers }}'
7 changes: 7 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Identify Python interpreters
############################

*Robust cache-busting based on Python implementations, architectures, and versions.*

----

133 changes: 133 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
name: "Identify installed Python interpreters"
description: |
Find installed Python interpreters and output them as "python-identifiers".
This can be useful for cache busting of tox, virtual environments,
and other items that are sensitive to changes in Python minor versions.
Only Python interpreters named "python" in the $PATH will be found.
outputs:
python-identifiers:
description: |
A string of sorted Python identifiers.
In most cases the identifiers will be paths,
but for system Pythons, 'sysconfig.get_config_var("EXT_SUFFIX")' will be included.
The string will be separated by OS-specific PATH separators;
":" is used on Linux and macOS, and ";" is used on Windows.
value: "${{ steps.final-step.outputs.python-identifiers }}"

runs:
using: "composite"
steps:
- name: "Find Pythons on Linux / macOS"
id: "linux"
if: "runner.os != 'windows'"
shell: "bash"
run: |
true > /dev/null # Prevent YAML syntax errors when embedded in 'action.yml'.
code=$(
cat <<'END_CODE'
from __future__ import print_function
import sysconfig
ext_suffix = sysconfig.get_config_var("EXT_SUFFIX")
if ext_suffix is not None:
print(ext_suffix)
else:
# Python 2.7 on macOS
import platform
print(
"." + platform.python_implementation().lower(),
sysconfig.get_config_var("py_version_nodot"),
sysconfig.get_config_var("build"),
sep="-"
)
END_CODE
)
# Search paths in $PATH for Python interpreters.
IFS=: read -r -a all_paths <<< "$PATH"
# Find Python interpreters.
paths=()
for path in "${all_paths[@]}"; do
# Interpreters in RUNNER_TOOL_CACHE have directory names that include:
#
# * The implementation (like "Python" or "PyPy")
# * A version (like "3.10.12")
# * The architecture (like "x64")
#
# In such cases, the path can be used as the identifier.
if [[ "${path/#${RUNNER_TOOL_CACHE}/}" != "${path}" ]]; then
# Check for bin/python first;
# this results in duplicate paths which are later removed.
if [[ -x "${path}/bin/python" ]]; then
paths+=("${path}/bin")
elif [[ -x "${path}/python" ]]; then
paths+=("${path}")
fi
else
# System Pythons (e.g. /usr/bin/python) have nothing unique in the path.
# In such cases, it's necessary to run the executable to get something unique.
if [[ -x "${path}/python" ]]; then
"${path}/python" -m sysconfig 1>&2
paths+=("$(echo "${code}" | "${path}/python" -)")
fi
fi
done
# Sort the paths, ensure each path is unique, and create the output result.
result="$(
echo "${paths[*]}" \
| tr ' ' '\n' \
| sort \
| uniq \
| tr '\n' ':'
)"
# Trim trailing colons.
result="${result%:}"
# Output path information.
echo "python-identifiers=${result}" > "$GITHUB_OUTPUT"
- name: "Find Pythons on Windows"
id: "windows"
if: "runner.os == 'windows'"
shell: "powershell"
run: |
$true | Out-Null # Prevent YAML syntax errors when embedded in 'action.yml'.
# Search paths in $PATH for Python interpreters.
$all_paths = $env:PATH -split ";"
# Find Python interpreters.
$paths = @()
foreach ($path in $all_paths) {
# Only consider paths in RUNNER_TOOL_CACHE.
if ($path.StartsWith($env:RUNNER_TOOL_CACHE)) {
if (Test-Path "$path\python.exe") {
$paths += $path
}
}
}
# Sort the paths, ensure each path is unique, and create the output result.
$result = (
$paths `
| Sort-Object `
| Get-Unique
) -join ";"
# Output path information.
Write-Output "python-identifiers=$result" > "$env:GITHUB_OUTPUT"
- name: "Output"
id: "final-step"
shell: "bash"
run: |
echo 'python-identifiers=${{ steps.linux.outputs.python-identifiers }}${{ steps.windows.outputs.python-identifiers }}' > "$GITHUB_OUTPUT"
25 changes: 25 additions & 0 deletions finder.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
$true | Out-Null # Prevent YAML syntax errors when embedded in 'action.yml'.

# Search paths in $PATH for Python interpreters.
$all_paths = $env:PATH -split ";"

# Find Python interpreters.
$paths = @()
foreach ($path in $all_paths) {
# Only consider paths in RUNNER_TOOL_CACHE.
if ($path.StartsWith($env:RUNNER_TOOL_CACHE)) {
if (Test-Path "$path\python.exe") {
$paths += $path
}
}
}

# Sort the paths, ensure each path is unique, and create the output result.
$result = (
$paths `
| Sort-Object `
| Get-Unique
) -join ";"

# Output path information.
Write-Output "python-identifiers=$result"
67 changes: 67 additions & 0 deletions finder.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
true > /dev/null # Prevent YAML syntax errors when embedded in 'action.yml'.

code=$(
cat <<'END_CODE'
from __future__ import print_function
import sysconfig
ext_suffix = sysconfig.get_config_var("EXT_SUFFIX")
if ext_suffix is not None:
print(ext_suffix)
else:
# Python 2.7 on macOS
import platform
print(
"." + platform.python_implementation().lower(),
sysconfig.get_config_var("py_version_nodot"),
sysconfig.get_config_var("build"),
sep="-"
)
END_CODE
)

# Search paths in $PATH for Python interpreters.
IFS=: read -r -a all_paths <<< "$PATH"

# Find Python interpreters.
paths=()
for path in "${all_paths[@]}"; do
# Interpreters in RUNNER_TOOL_CACHE have directory names that include:
#
# * The implementation (like "Python" or "PyPy")
# * A version (like "3.10.12")
# * The architecture (like "x64")
#
# In such cases, the path can be used as the identifier.
if [[ "${path/#${RUNNER_TOOL_CACHE}/}" != "${path}" ]]; then
# Check for bin/python first;
# this results in duplicate paths which are later removed.
if [[ -x "${path}/bin/python" ]]; then
paths+=("${path}/bin")
elif [[ -x "${path}/python" ]]; then
paths+=("${path}")
fi
else
# System Pythons (e.g. /usr/bin/python) have nothing unique in the path.
# In such cases, it's necessary to run the executable to get something unique.
if [[ -x "${path}/python" ]]; then
paths+=("$(echo "${code}" | "${path}/python" -)")
fi
fi
done

# Sort the paths, ensure each path is unique, and create the output result.
result="$(
echo "${paths[*]}" \
| tr ' ' '\n' \
| sort \
| uniq \
| tr '\n' ':'
)"

# Trim trailing colons.
result="${result%:}"

# Output path information.
echo "python-identifiers=${result}"
35 changes: 35 additions & 0 deletions identify.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
from __future__ import print_function

import platform
import sys


def main():
python_version = platform.python_version()
implementation = platform.python_implementation()
architecture = "/".join(platform.architecture())

# PyPy
if hasattr(sys, "pypy_version_info"):
implementation_version = ".".join(str(v) for v in sys.pypy_version_info[:3])

# CPython
else:
implementation_version, _, _ = sys.version.partition(" ")

# GraalPy
try:
implementation_version = __graalpython__.get_graalvm_version()
except NameError:
pass

output = "{0} [{1} {2}; {3}]".format(
python_version, implementation, implementation_version, architecture
)
print(output)

return 0


if __name__ == "__main__":
sys.exit(main())

0 comments on commit 27a8bfa

Please sign in to comment.