Skip to content

Commit df521c9

Browse files
committed
ci(release): move the conda legs' and pypi KTLO steps' shell into util/ scripts (#1967)
1 parent bb78d84 commit df521c9

16 files changed

Lines changed: 1088 additions & 344 deletions

‎.github/workflows/create-release.yml‎

Lines changed: 19 additions & 149 deletions
Original file line numberDiff line numberDiff line change
@@ -736,21 +736,16 @@ jobs:
736736
# #1633: this KTLO leg's outcome despite ``continue-on-error``, for
737737
# ``release_status``. ``steps.*.outcome`` covers every step with an ``id``:
738738
# all that can fail here but the TestPyPI upload, never a failure, and
739-
# #1634's tag check, which fails every leg alike.
739+
# #1634's tag check, which fails every leg alike. #1967: on the image's
740+
# ``python3``, as the summary below, since the leg's may be what failed.
740741
- name: Record this KTLO leg's outcome
741742
id: ktlo
742743
if: ${{ !cancelled() && matrix.ktlo == true }}
743744
continue-on-error: ${{ matrix.ktlo == true }}
744745
env:
745746
LEG: ${{ matrix.python-version }}
746747
OUTCOMES: ${{ join(steps.*.outcome, ' ') }}
747-
run: |
748-
case " $OUTCOMES " in
749-
*" failure "*) result=failure ;;
750-
*) result=success ;;
751-
esac
752-
echo "ktlo_$(printf '%s' "$LEG" | tr -d '.')=$result" >> "$GITHUB_OUTPUT"
753-
echo "Python $LEG (KTLO, non-blocking): $result (step outcomes: $OUTCOMES)"
748+
run: /usr/bin/python3 util/release_ktlo.py
754749

755750
# #1672: the warning and job-summary line for each failed step, the same
756751
# step as ends every KTLO leg in unit-tests.yml; see its ``test`` job.
@@ -761,16 +756,7 @@ jobs:
761756
env:
762757
LEG: Python ${{ matrix.python-version }}${{ matrix.ktlo && ' (KTLO, non-blocking)' || '' }}
763758
STEPS: ${{ toJSON(steps) }}
764-
run: |
765-
failed=$(jq -r 'to_entries[] | select(.value.outcome == "failure") | .key' <<< "$STEPS")
766-
if [ -z "$failed" ]; then
767-
echo "$LEG: no step failed."
768-
exit 0
769-
fi
770-
while IFS= read -r id; do
771-
echo "::warning title=${id} failed on ${LEG//,/%2C}::KTLO legs never block (#1633), so this job still succeeds (#1672); see the ${id} step's log."
772-
echo "- ${LEG}: step \`${id}\` failed; non-blocking, so this job still succeeds." >> "$GITHUB_STEP_SUMMARY"
773-
done <<< "$failed"
759+
run: /usr/bin/python3 util/leg_summary.py ktlo
774760

775761
conda:
776762
name: Conda deployment (release) on ${{ matrix.os }} with Python ${{ matrix.python-version }}
@@ -825,60 +811,17 @@ jobs:
825811
run: python3 util/release_tags.py check
826812

827813
# #888: the per-leg correctness fix. Uncached, per ``version_check``'s
828-
# Anaconda check, so this reads the live state.
814+
# Anaconda check, so this reads the live state. #1967: util/release_conda.py
815+
# holds this step's shell, and the build's and the Intel upload's, which
816+
# cron-conda.yml's ``conda-dist`` runs too; before setup-python, so here
817+
# on the image's ``python3``.
829818
- name: Check whether this leg is already on Anaconda
830819
id: check_leg
831820
env:
832821
PCAPKIT_VERSION: ${{ needs.version_check.outputs.PCAPKIT_VERSION }}
833-
run: |
834-
set -euo pipefail
835-
836-
case "${{ matrix.os }}" in
837-
macos-latest)
838-
target_platform="osx-arm64"
839-
;;
840-
macos-26-intel)
841-
target_platform="osx-64"
842-
;;
843-
ubuntu-latest)
844-
target_platform="linux-64"
845-
;;
846-
*)
847-
target_platform=""
848-
;;
849-
esac
850-
py_tag="py$(echo "${{ matrix.python-version }}" | tr -d '.')"
851-
852-
# Same coupling risk, and the same fix, as version_check's checks.
853-
status="$(curl -s --retry 3 --retry-connrefused \
854-
-o /tmp/anaconda_release.json -w '%{http_code}' \
855-
"https://api.anaconda.org/release/jarryshaw/pypcapkit/${PCAPKIT_VERSION}" || echo 000)"
856-
857-
exists=false
858-
if [ "$status" = "200" ]; then
859-
# The `printf` form of version_check's checks, and needed here: this
860-
# runs before setup-python, so `python3` is the image's (3.12 on
861-
# ubuntu-latest, 3.13 on macos-latest), and indented `python3 -c`
862-
# source would fail under `set -e` -- only on the retry this is for.
863-
exists="$(printf '%s\n' \
864-
"import json" \
865-
"with open('/tmp/anaconda_release.json') as f:" \
866-
" data = json.load(f)" \
867-
"target_platform = '$target_platform'" \
868-
"py_tag = '$py_tag'" \
869-
"found = any(" \
870-
" d.get('attrs', {}).get('subdir') == target_platform" \
871-
" and d.get('attrs', {}).get('build', '').startswith(py_tag + '_')" \
872-
" for d in data.get('distributions', [])" \
873-
")" \
874-
"print('true' if found else 'false')" \
875-
| python3 -)"
876-
fi
877-
878-
echo "PCAPKIT_LEG_EXISTS=$exists" >> "$GITHUB_OUTPUT"
879-
if [ "$exists" = "true" ]; then
880-
echo "::notice title=conda leg already published::pypcapkit ${PCAPKIT_VERSION} for ${target_platform} / ${py_tag} is already on anaconda.org/jarryshaw (http ${status}); skipping this leg's upload."
881-
fi
822+
PCAPKIT_CONDA_OS: ${{ matrix.os }}
823+
PCAPKIT_PYTHON_VERSION: ${{ matrix.python-version }}
824+
run: python3 util/release_conda.py leg /tmp/anaconda_release.json
882825

883826
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
884827
with:
@@ -935,6 +878,8 @@ jobs:
935878
conda install -n base --override-channels -c conda-forge -y conda-build
936879
conda build --version
937880
881+
# On conda's base ``python``, which the login shell activates, from this
882+
# directory, as conda-dist's step of the same name.
938883
- name: Build conda packages
939884
id: conda_build
940885
shell: bash -el {0}
@@ -944,58 +889,10 @@ jobs:
944889
# because ``conda/build`` says so: at a hand-tagged commit it may still
945890
# hold the previous version's counter until ``tag``'s reset (#1634).
946891
PCAPKIT_BUILD: "0"
892+
PCAPKIT_CONDA_OS: ${{ matrix.os }}
893+
PCAPKIT_PYTHON_VERSION: ${{ matrix.python-version }}
947894
working-directory: conda/pypcapkit
948-
run: |
949-
set -euo pipefail
950-
951-
out_dir="$(mktemp -d -t compilation-XXXXXXXXXX)"
952-
echo "out_dir=$out_dir" >> "$GITHUB_OUTPUT"
953-
954-
conda build . --package-format .tar.bz2 --no-anaconda-upload --output-folder "$out_dir" --python "${{ matrix.python-version }}"
955-
host_package="$(find "$out_dir" -type f \( -name '*.tar.bz2' -o -name '*.conda' \) -print | sort | sed -n '1p')"
956-
if [ -z "$host_package" ]; then
957-
echo "::error::No conda package was built"
958-
exit 1
959-
fi
960-
961-
case "${{ matrix.os }}" in
962-
macos-latest)
963-
target_platform="osx-arm64"
964-
;;
965-
macos-26-intel)
966-
target_platform="osx-64"
967-
;;
968-
ubuntu-latest)
969-
target_platform="linux-64"
970-
;;
971-
*)
972-
target_platform=""
973-
;;
974-
esac
975-
976-
echo "host_package=$host_package" >> "$GITHUB_OUTPUT"
977-
978-
# #1822: never ``conda convert`` across architectures; see the matrix.
979-
host_platform="$(basename "$(dirname "$host_package")")"
980-
if [ "$host_platform" != "$target_platform" ]; then
981-
echo "::error title=${host_platform} package on a ${target_platform:-unmapped} leg (#1822)::${{ matrix.os }} publishes ${target_platform:-nothing}, and conda built ${host_platform} here; a package is published only for the platform it was built on."
982-
exit 1
983-
fi
984-
985-
package_paths="$(find "$out_dir" -type f \( -name '*.tar.bz2' -o -name '*.conda' \) -print | sort)"
986-
if [ -z "$package_paths" ]; then
987-
echo "::error::No conda packages were found"
988-
exit 1
989-
fi
990-
991-
printf '%s\n' "$package_paths"
992-
package_paths_csv="$(printf '%s\n' "$package_paths" | paste -sd ',' -)"
993-
echo "packages_csv=$package_paths_csv" >> "$GITHUB_OUTPUT"
994-
{
995-
echo 'packages<<EOF'
996-
printf '%s\n' "$package_paths"
997-
echo EOF
998-
} >> "$GITHUB_OUTPUT"
895+
run: python ../../util/release_conda.py build
999896

1000897
# #1855: cron-conda.yml's ``conda-dist`` step of the same name, which says
1001898
# what it checks and why it runs setup-python's interpreter, so a release
@@ -1025,7 +922,8 @@ jobs:
1025922
# parser -- from conda-forge's anaconda-client in base. One difference:
1026923
# `--skip-existing`, so a file anaconda.org already has is skipped where
1027924
# the action, with no `force:`, fails on it. "Check whether this leg is
1028-
# already on Anaconda" still gates both.
925+
# already on Anaconda" still gates both. ``release_conda.py upload`` runs
926+
# on setup-python's ``python3``.
1029927
- name: Upload conda packages to Anaconda (Intel)
1030928
if: ${{ matrix.os == 'macos-26-intel' && steps.check_leg.outputs.PCAPKIT_LEG_EXISTS != 'true' }}
1031929
shell: bash
@@ -1035,35 +933,7 @@ jobs:
1035933
PCAPKIT_CONDA_CHANNEL: jarryshaw
1036934
PCAPKIT_CONDA_PACKAGES: ${{ steps.conda_build.outputs.packages }}
1037935
PCAPKIT_CONDA_LABELS: ${{ needs.version_check.outputs.PCAPKIT_CONDA_LABEL }}
1038-
run: |
1039-
set -euo pipefail
1040-
1041-
conda install -n base --override-channels -c conda-forge -y anaconda-client
1042-
1043-
# The action's handling of its `labels:` and `packages:` inputs: a
1044-
# comma-separated list, each trimmed, and the build step's
1045-
# newline-separated paths.
1046-
labels=()
1047-
IFS=',' read -ra names <<< "$PCAPKIT_CONDA_LABELS"
1048-
for name in ${names[@]+"${names[@]}"}; do
1049-
name="$(echo "$name" | xargs)"
1050-
if [ -n "$name" ]; then
1051-
labels+=(--label "$name")
1052-
fi
1053-
done
1054-
packages=()
1055-
while IFS= read -r path; do
1056-
if [ -n "$path" ]; then
1057-
packages+=("$path")
1058-
fi
1059-
done <<< "$PCAPKIT_CONDA_PACKAGES"
1060-
if [ "${#packages[@]}" -eq 0 ]; then
1061-
echo "::error::No conda packages to upload"
1062-
exit 1
1063-
fi
1064-
1065-
"${CONDA}/bin/anaconda" --token "$ANACONDA_API_TOKEN" upload \
1066-
--user "$PCAPKIT_CONDA_CHANNEL" --skip-existing ${labels[@]+"${labels[@]}"} "${packages[@]}"
936+
run: python3 util/release_conda.py upload
1067937

1068938
# ``pypi``'s upload script, for the same reasons (#1634, #1640). Not
1069939
# gated on ``check_leg`` as the Anaconda upload is: a leg whose Anaconda

‎.github/workflows/cron-conda.yml‎

Lines changed: 7 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -501,70 +501,20 @@ jobs:
501501
conda install -n base --override-channels -c conda-forge -y conda-build
502502
conda build --version
503503
504+
# #1967: util/release_conda.py, as create-release.yml's step of the same
505+
# name, on conda's base `python`, which the login shell activates. #1822:
506+
# it fails a package built for another platform than the leg's.
504507
- name: Build conda packages
505508
id: conda_build
506509
continue-on-error: ${{ matrix.os == 'windows-latest' }}
507510
shell: bash -el {0}
508511
env:
509512
PCAPKIT_VERSION: ${{ needs.conda-update.outputs.PCAPKIT_VERSION }}
510513
PCAPKIT_BUILD: ${{ needs.conda-update.outputs.PCAPKIT_BUILD }}
514+
PCAPKIT_CONDA_OS: ${{ matrix.os }}
515+
PCAPKIT_PYTHON_VERSION: ${{ matrix.python-version }}
511516
working-directory: conda/pypcapkit
512-
run: |
513-
set -euo pipefail
514-
515-
out_dir="$(mktemp -d -t compilation-XXXXXXXXXX)"
516-
echo "out_dir=$out_dir" >> "$GITHUB_OUTPUT"
517-
518-
conda build . --package-format .tar.bz2 --no-anaconda-upload --output-folder "$out_dir" --python "${{ matrix.python-version }}"
519-
host_package="$(find "$out_dir" -type f \( -name '*.tar.bz2' -o -name '*.conda' \) -print | sort | sed -n '1p')"
520-
if [ -z "$host_package" ]; then
521-
echo "::error::No conda package was built"
522-
exit 1
523-
fi
524-
525-
case "${{ matrix.os }}" in
526-
macos-latest)
527-
target_platform="osx-arm64"
528-
;;
529-
macos-26-intel)
530-
target_platform="osx-64"
531-
;;
532-
ubuntu-latest)
533-
target_platform="linux-64"
534-
;;
535-
windows-latest)
536-
target_platform="win-64"
537-
;;
538-
*)
539-
target_platform=""
540-
;;
541-
esac
542-
543-
echo "host_package=$host_package" >> "$GITHUB_OUTPUT"
544-
545-
# #1822: never `conda convert` across architectures. It relabels the
546-
# package and copies its binaries unchanged, so the osx-64 package it
547-
# made from the arm64 build carried arm64 extensions.
548-
host_platform="$(basename "$(dirname "$host_package")")"
549-
if [ "$host_platform" != "$target_platform" ]; then
550-
echo "::error title=${host_platform} package on a ${target_platform:-unmapped} leg (#1822)::${{ matrix.os }} publishes ${target_platform:-nothing}, and conda built ${host_platform} here; a package is published only for the platform it was built on."
551-
exit 1
552-
fi
553-
554-
package_paths="$(find "$out_dir" -type f \( -name '*.tar.bz2' -o -name '*.conda' \) -print | sort)"
555-
if [ -z "$package_paths" ]; then
556-
echo "::error::No conda packages were found"
557-
exit 1
558-
fi
559-
560-
printf '%s\n' "$package_paths"
561-
package_paths_csv="$(printf '%s\n' "$package_paths" | paste -sd ',' -)"
562-
echo "packages_csv=$package_paths_csv" >> "$GITHUB_OUTPUT"
563-
{
564-
echo 'packages<<EOF'
565-
printf '%s\n' "$package_paths"
566-
echo EOF
567-
} >> "$GITHUB_OUTPUT"
517+
run: python ../../util/release_conda.py build
568518

569519
# #1814: the package's only run requirement is Python, so it has only what
570520
# it vendors in `pcapkit/_extern`, and package discovery alone ships those
@@ -614,35 +564,7 @@ jobs:
614564
PCAPKIT_CONDA_CHANNEL: jarryshaw
615565
PCAPKIT_CONDA_PACKAGES: ${{ steps.conda_build.outputs.packages }}
616566
PCAPKIT_CONDA_LABELS: ${{ needs.conda-update.outputs.PCAPKIT_CONDA_LABEL }}
617-
run: |
618-
set -euo pipefail
619-
620-
conda install -n base --override-channels -c conda-forge -y anaconda-client
621-
622-
# The action's handling of its `labels:` and `packages:` inputs: a
623-
# comma-separated list, each trimmed, and the build step's
624-
# newline-separated paths.
625-
labels=()
626-
IFS=',' read -ra names <<< "$PCAPKIT_CONDA_LABELS"
627-
for name in ${names[@]+"${names[@]}"}; do
628-
name="$(echo "$name" | xargs)"
629-
if [ -n "$name" ]; then
630-
labels+=(--label "$name")
631-
fi
632-
done
633-
packages=()
634-
while IFS= read -r path; do
635-
if [ -n "$path" ]; then
636-
packages+=("$path")
637-
fi
638-
done <<< "$PCAPKIT_CONDA_PACKAGES"
639-
if [ "${#packages[@]}" -eq 0 ]; then
640-
echo "::error::No conda packages to upload"
641-
exit 1
642-
fi
643-
644-
"${CONDA}/bin/anaconda" --token "$ANACONDA_API_TOKEN" upload \
645-
--user "$PCAPKIT_CONDA_CHANNEL" --skip-existing ${labels[@]+"${labels[@]}"} "${packages[@]}"
567+
run: python3 util/release_conda.py upload
646568

647569
# #1634: the upload below never makes the release. ncipollo/release-action
648570
# v1.21.0 creates a missing one, and its tag, at ``commit``

‎docs/source/contributing/workflows.rst‎

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1222,9 +1222,10 @@ does; each has tests under :file:`tests/project/` that run it.
12221222
- By hand
12231223
* - :file:`util/leg_summary.py`
12241224
- ``KTLO summary`` in ``unit-tests.yml``'s ``test``, ``integration`` and
1225-
``pypcap-parity`` (and so their KTLO twins) and ``python-compatibility.yml``'s
1226-
``compatibility``, as ``leg_summary.py ktlo``; ``Experimental summary`` in
1227-
``experimental.yml``'s ``experimental``, as ``leg_summary.py experimental``.
1225+
``pypcap-parity`` (and so their KTLO twins), ``python-compatibility.yml``'s
1226+
``compatibility`` and ``create-release.yml``'s ``pypi``, as ``leg_summary.py
1227+
ktlo``; ``Experimental summary`` in ``experimental.yml``'s ``experimental``,
1228+
as ``leg_summary.py experimental``.
12281229
- No.
12291230
* - :file:`util/classify_changes.py`
12301231
- ``Classify the diff`` in the ``changes`` job of ``unit-tests.yml`` and of
@@ -1313,6 +1314,19 @@ does; each has tests under :file:`tests/project/` that run it.
13131314
``release_published.py pypi`` and ``anaconda``, on setup-python's
13141315
``python3`` (`#888 <https://github.com/JarryShaw/PyPCAPKit/issues/888>`__).
13151316
- No.
1317+
* - :file:`util/release_conda.py`
1318+
- ``Check whether this leg is already on Anaconda`` in ``create-release.yml``'s
1319+
``conda``, as ``release_conda.py leg``; ``Build conda packages`` and ``Upload
1320+
conda packages to Anaconda (Intel)`` in that job and in ``cron-conda.yml``'s
1321+
``conda-dist``, as ``release_conda.py build`` and ``upload`` (`#1822
1322+
<https://github.com/JarryShaw/PyPCAPKit/issues/1822>`__, `#2044
1323+
<https://github.com/JarryShaw/PyPCAPKit/issues/2044>`__).
1324+
- No.
1325+
* - :file:`util/release_ktlo.py`
1326+
- ``Record this KTLO leg's outcome`` in ``create-release.yml``'s ``pypi``, on
1327+
the image's ``/usr/bin/python3`` (`#1633
1328+
<https://github.com/JarryShaw/PyPCAPKit/issues/1633>`__).
1329+
- No.
13161330

13171331
The summaries run on the image's ``/usr/bin/python3`` rather than the leg's
13181332
interpreter, which may have failed to set up or be too broken to run anything, so
@@ -1333,13 +1347,13 @@ uutils' ``timeout`` behaves as GNU's (`#2020
13331347
<https://github.com/JarryShaw/PyPCAPKit/issues/2020>`__), and the bound reaches
13341348
apt's own children, which a timeout in the script, running as the runner's user,
13351349
could not signal. Both ``changes`` jobs check out on every event, since the script is in the
1336-
tree. ``create-release.yml``'s ``pypi`` job keeps its own copy of
1337-
the KTLO summary's shell until its shell moves to scripts too; a project pin runs
1338-
that copy and the script on the same steps and requires the same output.
1339-
The release scripts run as ``python3``, from a checkout of the whole tree, and are
1350+
tree. The release scripts run as ``python3``, from a checkout of the whole tree, and are
13401351
standard library only: :file:`util/release_tags.py` on the image's
13411352
own, since ``github`` has no setup-python and ``pypi`` and ``conda`` run the check
1342-
before theirs, and :file:`util/release_upload.py` on the leg's, after it. A project
1353+
before theirs, and :file:`util/release_upload.py` on the leg's, after it.
1354+
:file:`util/release_conda.py` runs ``leg`` on the image's ``python3``, before
1355+
setup-python, ``build`` on conda's base ``python`` under the login shell, from
1356+
:file:`conda/pypcapkit`, and ``upload`` on setup-python's. A project
13431357
pin runs each on the cases the shell it replaced handled and requires the same
13441358
status, output and calls.
13451359
:file:`util/_actions.py` is standard library only, and keeps to 3.7's syntax,

0 commit comments

Comments
 (0)