From 627c73caf100c3541da2a4d00582b229f1610f7e Mon Sep 17 00:00:00 2001 From: John Brestelli Date: Tue, 13 Oct 2026 16:24:03 -0400 Subject: [PATCH 1/2] Add nextflow lint CI Co-Authored-By: Claude Opus 5.5 (1M context) --- .github/workflows/nextflow-lint.yml | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 .github/workflows/nextflow-lint.yml diff --git a/.github/workflows/nextflow-lint.yml b/.github/workflows/nextflow-lint.yml new file mode 100644 index 0000000..2798937 --- /dev/null +++ b/.github/workflows/nextflow-lint.yml @@ -0,0 +1,9 @@ +name: Nextflow lint + +on: + push: + pull_request: + +jobs: + lint: + uses: VEuPathDB/.github/.github/workflows/nextflow-lint.yml@main From 7f8624c67e529960a13db129517835be3b092e64 Mon Sep 17 00:00:00 2001 From: John Brestelli Date: Tue, 13 Oct 2026 16:38:46 -0400 Subject: [PATCH 2/2] Fix Nextflow 26 strict syntax errors - depth_policy: replace @Field constants with zero-arg functions; C-style (long) casts -> 'as long' - concatenate_fastq: call local closure via asList.call(...) - sratools/fasterqdump: move errorStrategy/maxRetries directives above input/output sections depth_policy nf-tests (38) pass on both 25.04.0 and 26.10.0. Co-Authored-By: Claude Opus 5.5 (1M context) --- modules/local/concatenate_fastq.nf | 4 +-- modules/local/depth_policy.nf | 38 ++++++++++---------- modules/nf-core/sratools/fasterqdump/main.nf | 8 ++--- 3 files changed, 24 insertions(+), 26 deletions(-) diff --git a/modules/local/concatenate_fastq.nf b/modules/local/concatenate_fastq.nf index 5a403c0..7a07239 100644 --- a/modules/local/concatenate_fastq.nf +++ b/modules/local/concatenate_fastq.nf @@ -19,8 +19,8 @@ process CONCATENATE_FASTQ { script: def asList = { it instanceof Collection ? it.toList() : (it ? [it] : []) } - def r1 = asList(r1_files) - def r2 = asList(r2_files) + def r1 = asList.call(r1_files) + def r2 = asList.call(r2_files) def layoutError = null if (r1.isEmpty()) { diff --git a/modules/local/depth_policy.nf b/modules/local/depth_policy.nf index 939b168..809571c 100644 --- a/modules/local/depth_policy.nf +++ b/modules/local/depth_policy.nf @@ -1,5 +1,3 @@ -import groovy.transform.Field - /* * Pure depth-policy functions. No params access, no file IO — everything is passed in * so this can be unit tested without a pipeline run. @@ -12,15 +10,15 @@ import groovy.transform.Field // Cost/runtime sanity bounds: a floor so tiny genomes still get usable depth, // a ceiling so huge genomes don't blow up downstream alignment cost. -@Field final long MIN_TARGET_FRAGMENTS = 1000000L -@Field final long MAX_TARGET_FRAGMENTS = 100000000L +def MIN_TARGET_FRAGMENTS() { 1000000L } +def MAX_TARGET_FRAGMENTS() { 100000000L } // Each assay sets its target on a different axis, so none of these need pairing // adjustment beyond basesPerFragment below. // RNA-seq depth tracks transcriptome complexity - gene count and expression dynamic // range - which moves far less than genome size across these organisms, so it is flat. -@Field final long RNASEQ_TARGET_FRAGMENTS = 20000000L +def RNASEQ_TARGET_FRAGMENTS() { 20000000L } // ChIP-seq depth does scale with genome size, but sublinearly: reads-per-peak is // genome-independent, peak count grows slowly, and only background depth is linear. @@ -29,37 +27,37 @@ import groovy.transform.Field // mapped reads while this target is measured before alignment and duplicate removal. // The floor only guards the smallest genomes: it binds below ~24Mb, where it and the curve // agree closely, so depth is set by the curve for essentially every organism here. -@Field final long CHIPSEQ_FLOOR_FRAGMENTS = 3000000L -@Field final long CHIPSEQ_ANCHOR_FRAGMENTS = 7500000L -@Field final long CHIPSEQ_ANCHOR_GENOME = 150000000L -@Field final double CHIPSEQ_GENOME_EXPONENT = 0.5d +def CHIPSEQ_FLOOR_FRAGMENTS() { 3000000L } +def CHIPSEQ_ANCHOR_FRAGMENTS() { 7500000L } +def CHIPSEQ_ANCHOR_GENOME() { 150000000L } +def CHIPSEQ_GENOME_EXPONENT() { 0.5d } -@Field final List VALID_ASSAY_TYPES = ["DNASeq", "RNASeq", "ChipSeq"] +def VALID_ASSAY_TYPES() { ["DNASeq", "RNASeq", "ChipSeq"] } // Every target clamps up to this floor, so a sample at or below it is never subsampled // and measuring its on-target fraction cannot change the outcome. def minTargetFragments() { - return MIN_TARGET_FRAGMENTS + return MIN_TARGET_FRAGMENTS() } def targetOnTargetFragments(Map policy) { - if (!VALID_ASSAY_TYPES.contains(policy.assayType)) { + if (!VALID_ASSAY_TYPES().contains(policy.assayType)) { throw new IllegalArgumentException( - "Unrecognized assayType '${policy.assayType}'; valid values are ${VALID_ASSAY_TYPES}" + "Unrecognized assayType '${policy.assayType}'; valid values are ${VALID_ASSAY_TYPES()}" ) } if (policy.assayType == "RNASeq") { - return RNASEQ_TARGET_FRAGMENTS + return RNASEQ_TARGET_FRAGMENTS() } if (policy.assayType == "ChipSeq") { - double scaled = CHIPSEQ_ANCHOR_FRAGMENTS * Math.pow( - (policy.genomeSize as double) / CHIPSEQ_ANCHOR_GENOME, CHIPSEQ_GENOME_EXPONENT) - return Math.min(MAX_TARGET_FRAGMENTS, Math.max(CHIPSEQ_FLOOR_FRAGMENTS, (long) scaled)) + double scaled = CHIPSEQ_ANCHOR_FRAGMENTS() * Math.pow( + (policy.genomeSize as double) / CHIPSEQ_ANCHOR_GENOME(), CHIPSEQ_GENOME_EXPONENT()) + return Math.min(MAX_TARGET_FRAGMENTS(), Math.max(CHIPSEQ_FLOOR_FRAGMENTS(), scaled as long)) } // DNASeq, the only coverage-denominated assay. Truncate rather than round: a // deliberately conservative (never over-) estimate. - long raw = (long) ((policy.genomeSize * policy.targetCoverage) / policy.basesPerFragment) - return Math.max(MIN_TARGET_FRAGMENTS, Math.min(MAX_TARGET_FRAGMENTS, raw)) + long raw = ((policy.genomeSize * policy.targetCoverage) / policy.basesPerFragment) as long + return Math.max(MIN_TARGET_FRAGMENTS(), Math.min(MAX_TARGET_FRAGMENTS(), raw)) } def depthPlan(Map metrics, Map policy) { @@ -112,7 +110,7 @@ def depthPlan(Map metrics, Map policy) { double effective = Math.max(observed, policy.minOnTargetFraction as double) long rawFragments = Math.min( - (long) Math.ceil(targetOnTarget / effective), + Math.ceil(targetOnTarget / effective) as long, metrics.totalReads as long ) diff --git a/modules/nf-core/sratools/fasterqdump/main.nf b/modules/nf-core/sratools/fasterqdump/main.nf index 0ec29cf..9508e67 100644 --- a/modules/nf-core/sratools/fasterqdump/main.nf +++ b/modules/nf-core/sratools/fasterqdump/main.nf @@ -7,6 +7,10 @@ process SRATOOLS_FASTERQDUMP { 'https://depot.galaxyproject.org/singularity/mulled-v2-5f89fe0cd045cb1d615630b9261a1d17943a9b6a:2f4a4c900edd6801ff0068c2b3048b4459d119eb-0' : 'biocontainers/mulled-v2-5f89fe0cd045cb1d615630b9261a1d17943a9b6a:2f4a4c900edd6801ff0068c2b3048b4459d119eb-0' }" + errorStrategy 'retry' + + maxRetries 1 + input: tuple val(meta), path(sra) path ncbi_settings @@ -16,10 +20,6 @@ process SRATOOLS_FASTERQDUMP { tuple val(meta), path('*.fastq.gz'), emit: reads path "versions.yml" , emit: versions - errorStrategy 'retry' - - maxRetries 1 - when: task.ext.when == null || task.ext.when