Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
kurtmckee committed Oct 24, 2023
0 parents commit 2cbcd04
Show file tree
Hide file tree
Showing 13 changed files with 474 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: "detector"
uses: "kurtmckee/detect-pythons@main"

- name: "Print it!"
shell: "bash"
run: |
echo '${{ steps.detector.outputs.python-identifiers }}'
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
__pycache__
38 changes: 38 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
ci:
autoupdate_schedule: "monthly"

repos:
- repo: "https://github.com/pre-commit/pre-commit-hooks"
rev: "v4.5.0"
hooks:
- id: "trailing-whitespace"
- id: "end-of-file-fixer"
- id: "check-yaml"
- id: "check-added-large-files"

- repo: "https://github.com/python-jsonschema/check-jsonschema"
rev: "0.27.0"
hooks:
- id: "check-github-workflows"
- id: "check-dependabot"

- repo: "https://github.com/shellcheck-py/shellcheck-py"
rev: "v0.9.0.6"
hooks:
- id: "shellcheck"
args:
- "--shell=bash"

- repo: "local"
hooks:
- id: "sync-identify-code"
name: "Synchronize identify.py source code into 'detector.sh'"
language: "python"
entry: "python src/detect_pythons/sync_identify_code.py"
files: "^src/detect_pythons/identify.py$"

- id: "sync-detector-code"
name: "Synchronize detector source code into 'action.yml'"
language: "python"
entry: "python src/detect_pythons/sync_detector_code.py"
files: "^src/detect_pythons/detector.*$"
6 changes: 6 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Identify Python interpreters
############################

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

----
5 changes: 5 additions & 0 deletions TODO.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
* Expand README
* Create ``releases`` branch for tagging and such
* Add pre-commit hook to check README against latest tagged SHA
* Add test suite that can run locally
* Add test suite that verifies expected outputs in CI
143 changes: 143 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
name: "Detect installed Python interpreters"
description: |
Detect 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 implementations and 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: "Detect Pythons on Linux / macOS"
id: "linux"
if: "runner.os != 'windows'"
shell: "bash"
# Do not edit the 'run' code below.
# It is copied from 'detector.sh' by a pre-commit hook.
# START: detector.sh
run: |
python_code=$(
cat <<'identify.py_SOURCE_CODE'
from __future__ import print_function
import sysconfig
def main():
ext_suffix = sysconfig.get_config_var("EXT_SUFFIX")
if ext_suffix is not None:
print(ext_suffix)
else:
# Python 2.7 on GitHub macOS runners
import platform
print(
"." + platform.python_implementation().lower(),
sysconfig.get_config_var("py_version_nodot"),
sysconfig.get_config_var("MACHDEP"),
sep="-"
)
if __name__ == "__main__":
main()
identify.py_SOURCE_CODE
)
# Search paths in $PATH for Python interpreters.
IFS=: read -r -a all_paths <<< "$PATH"
# Detect 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 "${python_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"
# END: detector.sh

- name: "Detect Pythons on Windows"
id: "windows"
if: "runner.os == 'windows'"
shell: "powershell"
# Do not edit the 'run' code below.
# It is copied from 'detector.ps1' by a pre-commit hook.
# START: detector.ps1
run: |
$true | Out-Null # No-op to prevent YAML syntax errors.
# Search paths in $PATH for Python interpreters.
$all_paths = $env:PATH -split ";"
# Detect 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"
# END: detector.ps1

- name: "Output"
id: "final-step"
shell: "bash"
run: |
echo 'python-identifiers=${{ steps.linux.outputs.python-identifiers }}${{ steps.windows.outputs.python-identifiers }}' > "$GITHUB_OUTPUT"
20 changes: 20 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
[tool.poetry]
name = "detect_pythons"
version = "1.0.0"
description = "A GitHub action to detect installed Pythons. Suitable for cache-busting."
authors = ["Kurt McKee <[email protected]>"]
license = "MIT"
readme = "README.rst"

[tool.poetry.scripts]
identify = "detect_pythons.identify:main"
sync-detector-code = "detect_pythons.sync_detector_code:main"
sync-identify-code = "detect_pythons.sync_identify_code:main"

[tool.poetry.dependencies]
python = ">=3.8"


[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
Empty file added src/detect_pythons/__init__.py
Empty file.
23 changes: 23 additions & 0 deletions src/detect_pythons/detector.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Search paths in $PATH for Python interpreters.
$all_paths = $env:PATH -split ";"

# Detect 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"
71 changes: 71 additions & 0 deletions src/detect_pythons/detector.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
python_code=$(
cat <<'identify.py_SOURCE_CODE'
from __future__ import print_function
import sysconfig
def main():
ext_suffix = sysconfig.get_config_var("EXT_SUFFIX")
if ext_suffix is not None:
print(ext_suffix)
else:
# Python 2.7 on GitHub macOS runners
import platform
print(
"." + platform.python_implementation().lower(),
sysconfig.get_config_var("py_version_nodot"),
sysconfig.get_config_var("MACHDEP"),
sep="-"
)
if __name__ == "__main__":
main()
identify.py_SOURCE_CODE
)

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

# Detect 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 "${python_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}"
22 changes: 22 additions & 0 deletions src/detect_pythons/identify.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
from __future__ import print_function

import sysconfig


def main():
ext_suffix = sysconfig.get_config_var("EXT_SUFFIX")
if ext_suffix is not None:
print(ext_suffix)
else:
# Python 2.7 on GitHub macOS runners
import platform
print(
"." + platform.python_implementation().lower(),
sysconfig.get_config_var("py_version_nodot"),
sysconfig.get_config_var("MACHDEP"),
sep="-"
)


if __name__ == "__main__":
main()
Loading

0 comments on commit 2cbcd04

Please sign in to comment.