Skip to content

Commit 29f95f5

Browse files
authored
Merge pull request #89 from DessimozLab/copilot/merge-all-requests
Merge pending dependabot GitHub Actions bumps into dev
2 parents 553f860 + 45c0ab7 commit 29f95f5

18 files changed

Lines changed: 734 additions & 231 deletions

.github/workflows/docker-image.yml

Lines changed: 105 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,36 @@ on:
66
release:
77
type: [published]
88

9+
permissions:
10+
contents: read
11+
id-token: write
12+
913
env:
10-
TEST_TAG: dessimozlab/fastoma:test
14+
REGISTRY_IMAGE: dessimozlab/fastoma
1115

1216
jobs:
1317

1418
build:
1519

16-
runs-on: ubuntu-latest
20+
strategy:
21+
fail-fast: false
22+
matrix:
23+
include:
24+
- platform: linux/amd64
25+
runner: ubuntu-latest
26+
- platform: linux/arm64
27+
runner: ubuntu-24.04-arm
28+
29+
runs-on: ${{ matrix.runner }}
1730

1831
steps:
32+
- name: Prepare
33+
run: |
34+
platform=${{ matrix.platform }}
35+
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
36+
1937
- name: Checkout
20-
uses: actions/checkout@v6.0.1
38+
uses: actions/checkout@v6
2139
with:
2240
submodules: recursive
2341

@@ -26,8 +44,7 @@ jobs:
2644
uses: docker/metadata-action@v5
2745
with:
2846
# list of Docker images to use as base name for tags
29-
images: |
30-
dessimozlab/fastoma
47+
images: ${{ env.REGISTRY_IMAGE }}
3148
# generate Docker tags based on the following events/attributes
3249
tags: |
3350
type=schedule
@@ -47,40 +64,99 @@ jobs:
4764
- name: Set up Docker Buildx
4865
uses: docker/setup-buildx-action@v3
4966

50-
- name: Build and export to docker for testing
67+
- name: Login to DockerHub
68+
uses: docker/login-action@v3
69+
with:
70+
username: ${{ secrets.DOCKER_HUB_USERNAME }}
71+
password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
72+
73+
- name: Build and push by digest only
74+
id: build
5175
uses: docker/build-push-action@v6
5276
with:
5377
context: .
54-
load: true
55-
tags: ${{ env.TEST_TAG }}
78+
platforms: ${{ matrix.platform }}
79+
tags: ${{ env.REGISTRY_IMAGE }}
80+
labels: ${{ steps.meta.outputs.labels }}
81+
outputs: type=image,push-by-digest=true,name-canonical=true,push=true
82+
provenance: mode=max
83+
sbom: true
84+
85+
- name: Export digest
86+
run: |
87+
mkdir -p ${{ runner.temp }}/digests
88+
digest="${{ steps.build.outputs.digest }}"
89+
touch "${{ runner.temp }}/digests/${digest#sha256:}"
5690
57-
#- name: Test
58-
# run: |
59-
# docker run --rm -i -v $PWD/tests:/input -v $PWD/tests/:/reads -v $PWD/output:/out -v $PWD/run:/run ${{ env.TEST_TAG }} --tree --standalone_path /input/marker_genes --dna_reference /input/cds-marker_genes.fasta.gz --reads /reads/sample_1.fastq --output_path /out
60-
# if [ ! -f output/tree_sample_1.nwk ] ; then exit 1; fi
91+
- name: Upload digest
92+
uses: actions/upload-artifact@v7
93+
with:
94+
name: digests-${{ env.PLATFORM_PAIR }}
95+
path: ${{ runner.temp }}/digests/*
96+
if-no-files-found: error
97+
retention-days: 1
98+
99+
merge:
100+
runs-on: ubuntu-latest
101+
needs:
102+
- build
103+
steps:
104+
- name: Download digests
105+
uses: actions/download-artifact@v8
106+
with:
107+
path: ${{ runner.temp }}/digests
108+
pattern: digests-*
109+
merge-multiple: true
61110

62111
- name: Login to DockerHub
63112
uses: docker/login-action@v3
64113
with:
65114
username: ${{ secrets.DOCKER_HUB_USERNAME }}
66115
password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
67116

68-
- name: Set platforms
69-
id: set_platforms
117+
- name: Set up Docker Buildx
118+
uses: docker/setup-buildx-action@v3
119+
120+
- name: Docker meta
121+
id: meta
122+
uses: docker/metadata-action@v5
123+
with:
124+
images: ${{ env.REGISTRY_IMAGE }}
125+
tags: |
126+
type=schedule
127+
type=ref,event=branch
128+
type=ref,event=pr
129+
type=semver,pattern={{version}}
130+
type=semver,pattern={{major}}.{{minor}}
131+
type=semver,pattern={{major}}
132+
type=sha
133+
labels: |
134+
org.opencontainers.image.source=${{ github.repository }}
135+
org.opencontainers.image.revision=${{ github.sha }}
136+
137+
- name: Create manifest list and push
138+
working-directory: ${{ runner.temp }}/digests
70139
run: |
71-
echo "github ref: ${GITHUB_REF}"
72-
if [[ "${GITHUB_REF##*/}" == "main" || "${GITHUB_REF##*/}" == "dev" || "${GITHUB_REF}" == "refs/tags/"* ]]; then
73-
echo "platforms=linux/amd64,linux/arm64" >> $GITHUB_OUTPUT
74-
else
75-
echo "platforms=linux/amd64" >> $GITHUB_OUTPUT
76-
fi
77-
78-
- name: Build and push
79-
uses: docker/build-push-action@v6
140+
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
141+
$(printf '${{ env.REGISTRY_IMAGE }}@sha256:%s ' *)
142+
143+
- name: Inspect image
144+
run: |
145+
docker buildx imagetools inspect ${{ env.REGISTRY_IMAGE }}:${{ steps.meta.outputs.version }}
146+
147+
publish-doc:
148+
runs-on: ubuntu-latest
149+
needs:
150+
- merge
151+
152+
steps:
153+
- name: Checkout
154+
uses: actions/checkout@v6
155+
156+
- name: Update repo description
157+
uses: peter-evans/dockerhub-description@v5
80158
with:
81-
context: .
82-
platforms: ${{ steps.set_platforms.outputs.platforms }}
83-
push: true
84-
#${{ github.event_name != 'push' && github.event_name != 'pull_request' }}
85-
tags: ${{ steps.meta.outputs.tags }}
86-
labels: ${{ steps.meta.outputs.labels }}
159+
username: ${{ secrets.DOCKER_HUB_USERNAME }}
160+
password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
161+
repository: ${{ env.REGISTRY_IMAGE }}
162+
readme-filepath: ./README.md

.github/workflows/nf-test.yml

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,13 @@ on:
1212
env:
1313
NFT_DIFF: "pdiff"
1414
NFT_DIFF_ARGS: "--line-numbers --width 120 --expand-tabs=2"
15-
NFT_VER: "0.9.2"
16-
NFT_WORKDIR: "${{ github.workspace }}/nf-test-work"
15+
NFT_VER: "0.9.4"
16+
NFT_WORKDIR: "/tmp/nft-test"
17+
NXF_WORK: "/tmp/nxf-work"
1718
NXF_ANSI_LOG: false
1819
NXF_SINGULARITY_CACHEDIR: ${{ github.workspace }}/.singularity
1920
NXF_SINGULARITY_LIBRARYDIR: ${{ github.workspace }}/.singularity
21+
NXF_CONDA_CACHEDIR: "/tmp/conda"
2022

2123
concurrency:
2224
group: "${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}"
@@ -40,15 +42,15 @@ jobs:
4042

4143
steps:
4244
- name: Check out pipeline code
43-
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v4
45+
uses: actions/checkout@0c366fd6a839edf440554fa01a7085ccba70ac98 # v4
4446
with:
4547
fetch-depth: 0
4648

47-
- uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 # v5
49+
- uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v5
4850
with:
4951
python-version: "3.11"
5052

51-
- uses: actions/setup-java@f2beeb24e141e01a676f977032f5a29d81c9e27e # v4
53+
- uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v4
5254
with:
5355
distribution: "temurin"
5456
java-version: "17"
@@ -78,7 +80,7 @@ jobs:
7880

7981
- name: Set up miniconda
8082
if: matrix.profile == 'conda'
81-
uses: conda-incubator/setup-miniconda@835234971496cad1653abb28a638a281cf32541f # v3
83+
uses: conda-incubator/setup-miniconda@fc2d68f6413eb2d87b895e92f8584b5b94a10167 # v3
8284
with:
8385
miniconda-version: "latest"
8486
auto-update-conda: true
@@ -143,7 +145,7 @@ jobs:
143145

144146
- name: Upload test results
145147
if: always() # run even if tests fail
146-
uses: actions/upload-artifact@v6
148+
uses: actions/upload-artifact@v7
147149
with:
148150
name: nf-test-results-${{ matrix.filter }}-${{ matrix.profile }}-${{ matrix.NXF_VER }}-${{ matrix.shard }}
149151
path: |

.github/workflows/publish-pypi-release.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ jobs:
1414
id-token: write
1515

1616
steps:
17-
- uses: actions/checkout@v6.0.1
17+
- uses: actions/checkout@v6
1818
- name: Set up Python
1919
uses: actions/setup-python@v6
2020
with:

Dockerfile

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,21 @@ RUN apt-get update \
1515
&& rm -rf /var/lib/apt/lists/*
1616

1717
WORKDIR /src
18-
RUN pip install --upgrade hatch pip
18+
RUN pip install --upgrade pip \
19+
&& pip install "hatch<1.17" "virtualenv<20.26"
1920
COPY pyproject.toml .
20-
RUN python -m venv /app \
21-
&& hatch dep show requirements --all > requirements.txt \
22-
&& /app/bin/pip install wheel setuptools \
23-
&& /app/bin/pip install -r requirements.txt
21+
22+
RUN hatch dep show requirements --all > requirements.txt \
23+
&& pip install wheel setuptools -r requirements.txt
2424

2525
COPY . .
26-
RUN ls -la \
27-
&& hatch build \
28-
&& ls -la dist/ \
26+
RUN hatch build \
27+
&& ls -la dist/
28+
29+
# Create a clean venv for runtime and install the wheel
30+
RUN python -m venv /app \
31+
&& /app/bin/pip install --upgrade pip wheel setuptools \
32+
&& /app/bin/pip install -r requirements.txt \
2933
&& /app/bin/pip install dist/*.whl
3034

3135

@@ -44,3 +48,5 @@ RUN apt-get update \
4448

4549
COPY --from=builder /app /app
4650
ENV PATH="/app/bin:$PATH"
51+
52+
RUN python -c "import FastOMA; print(FastOMA.__version__)"

FastOMA/_infer_subhog.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
from ._utils_subhog import MSAFilter, MSAFilterElbow, MSAFilterTrimAL
3131

3232
from .zoo.utils import unique
33+
from .zoo.wrappers import WrapperError
3334

3435
low_so_detection = True # detection of proteins with low species overlap score in gene tree
3536
fragment_detection = True # this also need to be consistent in _hog_class.py
@@ -76,10 +77,15 @@ def read_infer_xml_rhog(rhogid, inferhog_concurrent_on, pickles_rhog_folder, pi
7677
species_names_rhog = list(set(species_names_rhog))
7778
logger.info("Number of unique species in rHOG " + rhogid + " is " + str(len(species_names_rhog)) + ".")
7879

79-
if inferhog_concurrent_on: # for big HOG we use parallelization at the level taxonomic level using concurrent
80-
infer_hogs_concurrent(species_tree, rhogid, pickles_subhog_folder_all, rhogs_fa_folder, conf_infer_subhhogs)
81-
else:
82-
infer_hogs_for_rhog_levels_recursively(species_tree, rhogid, pickles_subhog_folder_all, rhogs_fa_folder, conf_infer_subhhogs)
80+
try:
81+
if inferhog_concurrent_on: # for big HOG we use parallelization at the level taxonomic level using concurrent
82+
infer_hogs_concurrent(species_tree, rhogid, pickles_subhog_folder_all, rhogs_fa_folder, conf_infer_subhhogs)
83+
else:
84+
infer_hogs_for_rhog_levels_recursively(species_tree, rhogid, pickles_subhog_folder_all, rhogs_fa_folder, conf_infer_subhhogs)
85+
except WrapperError as e:
86+
logger.exception("Error of external tool during subhog inference: %s", str(e))
87+
sys.exit(getattr(e, "exit_code", 1))
88+
8389

8490
##### Now read the final pickle file for this rootHOG
8591
root_node_name = species_tree.name

FastOMA/zoo/utils.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,4 +109,18 @@ def unique(seq):
109109
return [x for x in seq if x not in seen and not seen.add(x)]
110110

111111

112-
112+
def summarize_long_message(message: str, head_chars: int = 1000, tail_lines: int = 50) -> str:
113+
"""
114+
Summarize a potentially long messge string.
115+
Shows first head_chars characters and last tail_lines lines with ellipsis.
116+
"""
117+
output = message or ""
118+
# Get last tail_lines
119+
lines = output.splitlines()
120+
tail = "\n".join(lines[-tail_lines:]) if len(lines) > tail_lines else "\n".join(lines)
121+
# Get first head_chars
122+
head = output[:head_chars] + ("…" if len(output) > head_chars else "")
123+
if len(lines) > tail_lines or len(output) > head_chars:
124+
return f"{head}\n...\n{tail}"
125+
else:
126+
return output

FastOMA/zoo/wrappers/__init__.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
class WrapperError(Exception):
2-
pass
2+
def __init__(self, message, exit_code=1):
3+
super().__init__(message)
4+
self.exit_code = exit_code
35

46

FastOMA/zoo/wrappers/aligners/mafft.py

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from .base_aligner import Aligner, AlignmentInput, DataType
88
from ...seq_utils.utils import iter_seqrecs_from_any
99
from ...wrappers import WrapperError
10+
from ...utils import summarize_long_message
1011
from ..options import StringOption, FlagOption, IntegerOption, FloatOption, MultiOption, OptionSet
1112
import tempfile
1213
import logging
@@ -120,7 +121,8 @@ def __call__(self, *args, **kwargs):
120121
logger.debug('Output of Mafft: stdout={}; stderr={}'.format(output, error))
121122
if len(output) == 0 and len(error) > 0:
122123
logger.warning('is MAFFT_BINARIES set correctly: {}'.format(os.getenv('MAFFT_BINARIES', '')))
123-
raise WrapperError('Mafft did not compute any alignments. StdErr: {}'.format(error))
124+
logger.warning("Mafft did not compute any alignments. StdErr:\n%s", summarize_long_message(error))
125+
raise WrapperError('Mafft did not compute any alignments')
124126
self.result = self._read_result(output) # store result
125127
self.stdout = output
126128
self.stderr = error
@@ -140,6 +142,20 @@ def _call(self, filename, *args, **kwargs):
140142
"""
141143
self.cli('{} {}'.format(self.command(), filename),
142144
wait=True)
145+
146+
ret = self.cli.process.returncode
147+
if ret != 0:
148+
logger.error('Mafft returned non-zero exit status: {}'.format(ret))
149+
logger.error('Output of Mafft:\n\n%s\nstdout=\n%s\n%s\n\n%s\nstderr=\n%s\n%s\n\n',
150+
"=" * 30, "=" * 30, summarize_long_message(self.cli.get_stdout()),
151+
"=" * 30, "=" * 30, summarize_long_message(self.cli.get_stderr()))
152+
if ret < 0:
153+
sig = -ret
154+
raise WrapperError(f'Mafft was terminated by signal {sig}', exit_code=128 + sig)
155+
else:
156+
if ret == 1 and (was_oom_killed() or "Killed" in self.cli.get_stderr()):
157+
raise WrapperError(f'Mafft was killed by the kernel due to running out of memory', exit_code=137)
158+
raise WrapperError(f'Mafft exited with code {ret}', exit_code=ret)
143159
return self.cli.get_stdout(), self.cli.get_stderr()
144160

145161
def command(self):
@@ -334,3 +350,17 @@ def get_default_options():
334350
StringOption('--merge', '', active=False),
335351
IntegerOption('--thread', -1, active=False),
336352
])
353+
354+
355+
def was_oom_killed():
356+
"""
357+
Check if the process was killed by the kernel due to running out of memory.
358+
"""
359+
try:
360+
with open("/sys/fs/cgroup/memory.events") as f:
361+
for line in f:
362+
if line.startswith("oom_kill"):
363+
return int(line.split()[1]) > 0
364+
except FileNotFoundError:
365+
return False
366+
return False

0 commit comments

Comments
 (0)