Skip to content

Commit

Permalink
Add GHA action (#7)
Browse files Browse the repository at this point in the history
* add gha action an

* publish index files

* add checkout

* use github.token

* remove this checkout?

* output to temp

* try explicit working directories

* try github.action_path

* use default env

* change yaml str type

* try with github.action_path again

* update deps

* try --manifest-path

* remove quotes

* a bit more verbosity in deployment decision

* typo

* always add noarch

* Disable GH Pages on tests
  • Loading branch information
jaimergp authored May 16, 2024
1 parent 65da315 commit ffd9c8c
Show file tree
Hide file tree
Showing 7 changed files with 240 additions and 397 deletions.
15 changes: 15 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,18 @@ jobs:
- name: Build recipe (${{ env.PIXI_ENV_NAME }})
if: matrix.python-version == '310'
run: pixi run build

action:
runs-on: ubuntu-latest
permissions:
contents: write # to deploy to GH Pages automatically
steps:
- name: Checkout
uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5
with:
fetch-depth: 0
- uses: ./
with:
channel: conda-forge
keep-trees: python=3.9
gh-pages-branch: '' # disable on main
33 changes: 33 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,36 @@
# conda-subchannel

Create subsets of conda channels thanks to CEP-15 metadata

## conda plugin

```bash
$ conda install -n base conda-subchannel
$ conda subchannel --channel=conda-forge python=3.9
$ python -m http.serve --directory subchannel/
```

## Github Actions action

```yaml
name: Create conda subchannel

on:
push:
branches:
- main
pull_request:

jobs:
build-and-deploy:
runs-on: ubuntu-latest
permissions:
contents: write # to deploy to GH Pages automatically
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
steps:
- uses: jaimergp/conda-subchannel@main
with:
channel: conda-forge
keep-trees: python=3.9
```
124 changes: 124 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
name: 'conda subchannel'
description: 'Republish a subset of an existing conda channel'
inputs:
channel:
description: "Source conda channel"
required: true
repodata-fn:
description: "Source repodata file to process from channel"
required: false
default: "repodata.json"
subdirs:
description: "List of platforms to support, space separated. Defaults to linux-64. Noarch is always included"
required: false
default: "linux-64"
after:
description: "Timestamp as ts:<float> or date as YYYY-[MM[-DD[-HH[-MM[-SS]]]]]"
required: false
default: ""
before:
description: "Timestamp as ts:<float> or date as YYYY-[MM[-DD[-HH[-MM[-SS]]]]]"
required: false
default: ""
keep-trees:
description: "Keep packages matching these specs and their dependencies. Space separated"
required: false
default: ""
keep-specs:
description: "Keep packages matching these specs only. Space separated"
required: false
default: ""
remove-specs:
description: "Remove packages matching these specs. Space separated"
required: false
default: ""
gh-pages-branch:
description: "Name of the branch for the GH Pages deployment. Set to `''` to disable."
required: false
default: gh-pages
outputs:
output-directory:
description: "Path to the directory containing the subchannel data"
value: ${{ steps.validate.outputs.output-path }}
runs:
using: "composite"
steps:
- name: Check Runner OS
if: ${{ runner.os != 'Linux' }}
shell: bash
run: |
echo "::error title=⛔ error hint::Only Linux is supported"
exit 1
- name: Validate arguments
shell: bash
id: validate
run: |
if [[
"${{ inputs.after }}" == ""
&& "${{ inputs.before }}" == ""
&& "${{ inputs.keep-trees }}" == ""
&& "${{ inputs.keep-specs }}" == ""
&& "${{ inputs.remove-specs }}" == ""
]]; then
echo "::error title=⛔ error hint::At least one of `after`, `before`, `keep-trees`, `keep-specs` or `remove-specs` must be set"
exit 1
fi
mkdir -p "${{ runner.temp }}/subchannel"
echo "output-directory=${{ runner.temp }}/subchannel" >> $GITHUB_OUTPUT
- uses: prefix-dev/setup-pixi@632d17935141ec801697e2c359784b878adecbbe # v0.6.0
with:
environments: default
manifest-path: ${{ github.action_path }}/pyproject.toml
- name: Setup project
shell: bash
run: cd "${{ github.action_path }}" && pixi run --environment default dev
- name: Run subchannel
shell: pixi run --manifest-path "${{ github.action_path }}/pyproject.toml" --environment default bash -e {0}
run: |
args="--channel ${{ inputs.channel }}"
args+=" --output ${{ steps.validate.outputs.output-directory }}"
args+=" --repodata-fn ${{ inputs.repodata-fn }}"
for subdir in ${{ inputs.subdirs }}; do
args+=" --subdir $subdir"
done
if [[ "${{ inputs.after }}" != "" ]]; then
args+=" --after ${{ inputs.after }}"
fi
if [[ "${{ inputs.before }}" != "" ]]; then
args+=" --before ${{ inputs.before }}"
fi
if [[ "${{ inputs.keep-trees }}" != "" ]]; then
for spec in ${{ inputs.keep-trees }}; do
args+=" --keep-tree $spec"
done
fi
if [[ "${{ inputs.keep-specs }}" != "" ]]; then
for spec in ${{ inputs.keep-specs }}; do
args+=" --keep $spec"
done
fi
if [[ "${{ inputs.remove-specs }}" != "" ]]; then
for spec in ${{ inputs.remove-specs }}; do
args+=" --remove $spec"
done
fi
echo "Running: conda subchannel $args"
conda subchannel $args
- name: Decide deployment
id: decide
shell: bash
run: |
if [[ "${{ inputs.gh-pages-branch }}" != "" && "${{ github.ref }}" == "refs/heads/main" ]]; then
echo "deploy=true" >> $GITHUB_OUTPUT
else
echo "Will skip deployment to GH Pages."
echo "deploy=false" >> $GITHUB_OUTPUT
fi
- uses: peaceiris/actions-gh-pages@4f9cc6602d3f66b9c108549d475ec49e8ef4d45e # v4.0.0
if: steps.decide.outputs.deploy == 'true'
with:
github_token: ${{ github.token }}
publish_branch: ${{ inputs.gh-pages-branch }}
publish_dir: ${{ steps.validate.outputs.output-directory }}
user_name: 'github-actions[bot]'
user_email: 'github-actions[bot]@users.noreply.github.com'
17 changes: 12 additions & 5 deletions conda_subchannel/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,13 @@ def date_argument(date: str) -> float:


def configure_parser(parser: argparse.ArgumentParser):
parser.add_argument("-c", "--channel", required=True, dest="channel")
parser.add_argument(
"-c",
"--channel",
required=True,
dest="channel",
help="Source conda channel.",
)
parser.add_argument(
"--repodata-fn",
default=REPODATA_FN,
Expand Down Expand Up @@ -93,9 +99,10 @@ def execute(args: argparse.Namespace) -> int:
raise ArgumentError("Please provide at least one filter.")

with Spinner("Syncing source channel"):
subdir_datas = _fetch_channel(
args.channel, args.subdirs or context.subdirs, args.repodata_fn
)
subdirs = args.subdirs or context.subdirs
if "noarch" not in subdirs:
subdirs = *subdirs, "noarch"
subdir_datas = _fetch_channel(args.channel, subdirs, args.repodata_fn)
for sd in sorted(subdir_datas, key=lambda sd: sd.channel.name):
print(" -", sd.channel.name, sd.channel.subdir)

Expand All @@ -117,6 +124,6 @@ def execute(args: argparse.Namespace) -> int:

with Spinner(f"Writing output to {args.output}"):
repodatas = _dump_records(records, args.channel)
_write_to_disk(repodatas, args.output)
_write_to_disk(args.channel, repodatas, args.output)

return 0
55 changes: 55 additions & 0 deletions conda_subchannel/core.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
from __future__ import annotations

import bz2
import hashlib
import json
import logging
from datetime import datetime, timezone
from pathlib import Path
from typing import TYPE_CHECKING

Expand Down Expand Up @@ -162,7 +164,55 @@ def _dump_records(
return repodatas


def _checksum(path, algorithm, buffersize=65536):
hash_impl = getattr(hashlib, algorithm)()
with open(path, "rb") as f:
for block in iter(lambda: f.read(buffersize), b""):
hash_impl.update(block)
return hash_impl.hexdigest()


def _write_channel_index_md(source_channel: Channel, channel_path: Path):
channel_path = Path(channel_path)
lines = [
f"# {channel_path.name}",
"",
f"Derived from [{source_channel.name}]({source_channel.base_url})",
"",
""
]
for subdir in channel_path.glob("*"):
if subdir.is_file():
continue
lines[-1] += f"[{subdir.name}]({subdir.name}) "
(channel_path / "index.md").write_text("\n".join(lines))


def _write_subdir_index_md(subdir_path: Path):
subdir_path = Path(subdir_path)
lines = [
f"# {'/'.join(subdir_path.parts[-2:])}",
"| Filename | Size (B) | Last modified | SHA256 | MD5 |",
"|----------|----------|---------------|--------|-----|",
]
for path in sorted(subdir_path.glob("*")):
if path.name == "index.md":
continue
stat = path.stat()
size = stat.st_size
lastmod = datetime.fromtimestamp(stat.st_mtime)
sha256 = _checksum(path, "sha256")
md5 = _checksum(path, "md5")
lines.append(
f"| [{path.name}]({path.name}) | {size} | {lastmod} | `{sha256}` | `{md5}` |"
)
lines.append("")
lines.append(f"> Last modified on {datetime.now(tz=timezone.utc)}")
(subdir_path / "index.md").write_text("\n".join(lines))


def _write_to_disk(
source_channel: Channel | str,
repodatas: dict[str, dict[str, Any]],
path: os.PathLike | str,
outputs: Iterable[str] = ("bz2", "zstd"),
Expand All @@ -185,8 +235,13 @@ def _write_to_disk(
level=ZSTD_COMPRESS_LEVEL, threads=ZSTD_COMPRESS_THREADS
).compress(json_contents.encode("utf-8"))
fo.write(repodata_zst_content)
_write_subdir_index_md(path / subdir)

# noarch must always be present
noarch_repodata = path / "noarch" / "repodata.json"
if not noarch_repodata.is_file():
noarch_repodata.parent.mkdir(parents=True, exist_ok=True)
noarch_repodata.write_text("{}")
_write_subdir_index_md(path / "noarch")

_write_channel_index_md(Channel(source_channel), path)
Loading

0 comments on commit ffd9c8c

Please sign in to comment.