mirror of
https://github.com/bec-project/ophyd_devices.git
synced 2026-02-20 17:28:42 +01:00
ci: add device list update workflow and script
This commit is contained in:
97
.github/scripts/retrieve_device_classes.py
vendored
Normal file
97
.github/scripts/retrieve_device_classes.py
vendored
Normal file
@@ -0,0 +1,97 @@
|
||||
"""
|
||||
This module contains helper methods to retrieve a list of all devices in a directory.
|
||||
It does so by recursively traversing the directory and checking if the file contains a
|
||||
class that inherits from the Device class.
|
||||
"""
|
||||
|
||||
import importlib.util
|
||||
import os
|
||||
|
||||
from ophyd import Device
|
||||
|
||||
|
||||
def get_devices(repo_name: str, ignore: str = "") -> dict:
|
||||
"""
|
||||
Get all devices in a directory.
|
||||
|
||||
Args:
|
||||
directory (str): The directory to search for devices.
|
||||
ignore (str): A comma-separated list of module names to ignore.
|
||||
|
||||
Returns:
|
||||
list: A list of all devices in the directory.
|
||||
"""
|
||||
devices = {}
|
||||
|
||||
anchor_module = importlib.import_module(f"{repo_name}.devices")
|
||||
directory = os.path.dirname(anchor_module.__file__)
|
||||
|
||||
for root, _, files in os.walk(directory):
|
||||
for file in files:
|
||||
if not file.endswith(".py") or file.startswith("__"):
|
||||
continue
|
||||
|
||||
path = os.path.join(root, file)
|
||||
subs = os.path.dirname(os.path.relpath(path, directory)).split("/")
|
||||
if len(subs) == 1 and not subs[0]:
|
||||
module_name = file.split(".")[0]
|
||||
else:
|
||||
module_name = ".".join(subs + [file.split(".")[0]])
|
||||
|
||||
if module_name in ignore.split(","):
|
||||
continue
|
||||
module = importlib.import_module(f"{repo_name}.devices.{module_name}")
|
||||
|
||||
for name in dir(module):
|
||||
obj = getattr(module, name)
|
||||
if not hasattr(obj, "__module__") or obj.__module__ != module.__name__:
|
||||
continue
|
||||
if isinstance(obj, type) and issubclass(obj, Device) and obj is not Device:
|
||||
devices[obj.__name__] = obj
|
||||
|
||||
return dict(sorted(devices.items(), key=lambda x: x[0].lower()))
|
||||
|
||||
|
||||
def write_device_list(devices: dict, file_name: str, repo_name: str, append=False):
|
||||
"""
|
||||
Write the list of devices to a file.
|
||||
|
||||
Args:
|
||||
devices (list): A list of devices.
|
||||
file_out (str): The file to write the devices to.
|
||||
repo_name (str): The repository name for linking to the source code.
|
||||
append (bool): Whether to append to the file or overwrite it.
|
||||
"""
|
||||
if not append or not os.path.exists(file_name):
|
||||
with open(file_name, "w", encoding="utf-8") as output_file:
|
||||
output_file.write("// This file was autogenerated. Do not edit it manually.\n")
|
||||
output_file.write("## Device List\n")
|
||||
|
||||
with open(file_name, "a", encoding="utf-8") as output_file:
|
||||
output_file.write(f"### {repo_name} \n")
|
||||
output_file.write("| Device | Documentation | Module |\n")
|
||||
output_file.write("| :----- | :------------- | :------ |\n")
|
||||
for dev, cls in devices.items():
|
||||
doc = cls.__doc__
|
||||
doc = "" if doc is None else doc.replace("\n", "<br>")
|
||||
output_file.write(
|
||||
f"| {dev} | {doc} | [{cls.__module__}](https://github.com/bec-project/{repo_name}/tree/main/{cls.__module__.replace('.', '/')}.py) |\n"
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser(description="Retrieve a list of devices in a directory.")
|
||||
parser.add_argument("repo", type=str, help="The repository to link to.")
|
||||
parser.add_argument("output", type=str, help="The file to write the devices to.")
|
||||
parser.add_argument(
|
||||
"--append", action="store_true", help="Append to the file instead of overwriting it."
|
||||
)
|
||||
parser.add_argument(
|
||||
"--ignore", type=str, help="Ignore the specified modules (comma-separated).", default=""
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
devs = get_devices(args.repo, ignore=args.ignore)
|
||||
write_device_list(devs, args.output, args.repo, append=args.append)
|
||||
98
.github/workflows/device-list-update.yml
vendored
Normal file
98
.github/workflows/device-list-update.yml
vendored
Normal file
@@ -0,0 +1,98 @@
|
||||
name: Update device list
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
device_list_update:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
concurrency:
|
||||
group: device-list-update-${{ github.ref_name }}
|
||||
cancel-in-progress: true
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0 # required for git diff / reset
|
||||
ssh-key: ${{ secrets.CI_DEPLOY_SSH_KEY }}
|
||||
ssh-known-hosts: ${{ secrets.CI_DEPLOY_SSH_KNOWN_HOSTS }}
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.11"
|
||||
|
||||
- name: Setup | Force release branch to be at workflow sha
|
||||
run: |
|
||||
git reset --hard ${{ github.sha }}
|
||||
|
||||
- name: Evaluate | Verify upstream has NOT changed
|
||||
shell: bash
|
||||
run: |
|
||||
set +o pipefail
|
||||
|
||||
UPSTREAM_BRANCH_NAME="$(git status -sb | head -n 1 | cut -d' ' -f2 | grep -E '\.{3}' | cut -d'.' -f4)"
|
||||
printf '%s\n' "Upstream branch name: $UPSTREAM_BRANCH_NAME"
|
||||
|
||||
set -o pipefail
|
||||
|
||||
if [ -z "$UPSTREAM_BRANCH_NAME" ]; then
|
||||
printf >&2 '%s\n' "::error::Unable to determine upstream branch name!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
git fetch "${UPSTREAM_BRANCH_NAME%%/*}"
|
||||
|
||||
if ! UPSTREAM_SHA="$(git rev-parse "$UPSTREAM_BRANCH_NAME")"; then
|
||||
printf >&2 '%s\n' "::error::Unable to determine upstream branch sha!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
HEAD_SHA="$(git rev-parse HEAD)"
|
||||
|
||||
if [ "$HEAD_SHA" != "$UPSTREAM_SHA" ]; then
|
||||
printf >&2 '%s\n' "[HEAD SHA] $HEAD_SHA != $UPSTREAM_SHA [UPSTREAM SHA]"
|
||||
printf >&2 '%s\n' "::error::Upstream has changed, aborting release..."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
printf '%s\n' "Verified upstream branch has not changed, continuing..."
|
||||
|
||||
- name: Install Python dependencies
|
||||
run: |
|
||||
pip install .
|
||||
|
||||
- name: Generate device list
|
||||
run: |
|
||||
python .github/scripts/retrieve_device_classes.py \
|
||||
"ophyd_devices" \
|
||||
"./ophyd_devices/devices/device_list.md" \
|
||||
--ignore areadetector.plugins
|
||||
|
||||
- name: Commit and push if device list changed
|
||||
run: |
|
||||
FILE="./ophyd_devices/devices/device_list.md"
|
||||
|
||||
if [ -f "$FILE" ]; then
|
||||
git add "$FILE"
|
||||
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||
|
||||
if ! git diff-index --quiet HEAD --; then
|
||||
git commit -m "docs: Update device list"
|
||||
|
||||
git push origin "${{ github.ref_name }}"
|
||||
|
||||
echo "Device list updated"
|
||||
else
|
||||
echo "No changes detected"
|
||||
fi
|
||||
else
|
||||
echo "Device list file not found"
|
||||
fi
|
||||
7
.github/workflows/semantic_release.yml
vendored
7
.github/workflows/semantic_release.yml
vendored
@@ -9,8 +9,6 @@ on:
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
|
||||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -39,7 +37,7 @@ jobs:
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.11'
|
||||
python-version: "3.11"
|
||||
|
||||
- name: Setup | Force release branch to be at workflow sha
|
||||
run: |
|
||||
@@ -49,9 +47,6 @@ jobs:
|
||||
# the upstream branch while this workflow was running. This is important
|
||||
# because we are committing a version change (--commit). You may omit this step
|
||||
# if you have 'commit: false' in your configuration.
|
||||
#
|
||||
# You may consider moving this to a repo script and call it from this step instead
|
||||
# of writing it in-line.
|
||||
shell: bash
|
||||
run: |
|
||||
set +o pipefail
|
||||
|
||||
Reference in New Issue
Block a user