Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/develop' into switch_to_arrays
Browse files Browse the repository at this point in the history
  • Loading branch information
rcannood committed Dec 16, 2024
2 parents 18f594b + 33dcde5 commit 9b0d19c
Show file tree
Hide file tree
Showing 147 changed files with 1,991 additions and 1,232 deletions.
5 changes: 4 additions & 1 deletion .github/workflows/ns_test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,15 @@ jobs:
steps:
- uses: actions/checkout@v4

- name: Set up sbt
- name: Set up java
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: '11'

- name: Set up sbt
uses: sbt/setup-sbt@v1

- name: Build viash
run: |
echo "${HOME}/.local/bin" >> $GITHUB_PATH
Expand Down
22 changes: 9 additions & 13 deletions .github/workflows/sbt_test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,6 @@ jobs:
- uses: viash-io/viash-actions/update-docker-engine@v5
if: runner.os == 'Linux'

- name: Set up Nextflow
if: ${{ runner.os == 'Linux' && matrix.java.run_nextflow }}
uses: nf-core/setup-nextflow@v1
with:
version: ${{ matrix.java.nxf_ver }}

- name: Set up R
uses: r-lib/actions/setup-r@v2
with:
Expand All @@ -42,18 +36,14 @@ jobs:
processx
testthat
- name: Set up java & sbt
- name: Set up java
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: ${{ matrix.java.ver }}

- name: Set up sbt specifically on macOS on arm64 if needed
if: ${{ runner.os == 'macOS' && runner.arch == 'ARM64'}}
run: |
if ! command -v sbt &> /dev/null; then
brew install sbt
fi
- name: Set up sbt
uses: sbt/setup-sbt@v1

- name: Set up Scala
run: |
Expand All @@ -69,6 +59,12 @@ jobs:
with:
python-version: '3.x'

- name: Set up Nextflow
if: ${{ runner.os == 'Linux' && matrix.java.run_nextflow }}
uses: nf-core/setup-nextflow@v1
with:
version: ${{ matrix.java.nxf_ver }}

- name: Run tests
run: |
if [[ "${{ matrix.config.name }}" =~ ^ubuntu.*$ ]] && [[ "${{ matrix.java.run_coverage }}" == "true" ]]; then
Expand Down
217 changes: 98 additions & 119 deletions CHANGELOG.md

Large diffs are not rendered by default.

25 changes: 15 additions & 10 deletions build.sbt
Original file line number Diff line number Diff line change
@@ -1,32 +1,37 @@
name := "viash"

version := "0.9.0-dev"
version := "0.9.1-dev"

scalaVersion := "2.13.12"
scalaVersion := "3.3.4"

libraryDependencies ++= Seq(
"org.scalactic" %% "scalactic" % "3.2.15" % "test",
"org.scalatest" %% "scalatest" % "3.2.15" % "test",
"org.rogach" %% "scallop" % "5.0.0",
"org.scala-lang" % "scala-reflect" % scalaVersion.value,
"org.scala-lang.modules" %% "scala-parser-combinators" % "2.1.1",
"org.scala-lang.modules" %% "scala-parallel-collections" % "1.0.4",
"com.github.julien-truffaut" %% "monocle-core" % "2.1.0",
"com.github.julien-truffaut" %% "monocle-macro" % "2.1.0"
"dev.optics" %% "monocle-core" % "3.1.0",
"dev.optics" %% "monocle-macro" % "3.1.0"
)

val circeVersion = "0.14.1"
val circeVersion = "0.14.7"

libraryDependencies ++= Seq(
"io.circe" %% "circe-core",
"io.circe" %% "circe-generic",
"io.circe" %% "circe-parser",
"io.circe" %% "circe-generic-extras",
"io.circe" %% "circe-optics",
"io.circe" %% "circe-yaml"
// "io.circe" %% "circe-generic-extras",
// "io.circe" %% "circe-optics",
// "io.circe" %% "circe-yaml"
).map(_ % circeVersion)

scalacOptions ++= Seq("-unchecked", "-deprecation")
libraryDependencies ++= Seq(
"io.circe" %% "circe-optics" % "0.15.0",
"io.circe" %% "circe-yaml" % "0.15.2",
)

scalacOptions ++= Seq("-unchecked", "-deprecation", "-explain")
scalacOptions ++= Seq("-Xmax-inlines", "50")

organization := "Data Intuitive"
startYear := Some(2020)
Expand Down
2 changes: 1 addition & 1 deletion project/plugins.sbt
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "2.1.1")
addSbtPlugin("org.scoverage" % "sbt-scoverage" % "2.0.9")
addSbtPlugin("org.scoverage" % "sbt-scoverage" % "2.1.0")
addSbtPlugin("de.heikoseeberger" % "sbt-header" % "5.9.0")
17 changes: 11 additions & 6 deletions src/main/resources/io/viash/runners/nextflow/VDSL3Helper.nf
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,11 @@ def vdsl3WorkflowFactory(Map args, Map meta, String rawScript) {
val = val.join(par.multiple_sep)
}
if (par.direction == "output" && par.type == "file") {
val = val.replaceAll('\\$id', id).replaceAll('\\$key', key)
val = val
.replaceAll('\\$id', id)
.replaceAll('\\$\\{id\\}', id)
.replaceAll('\\$key', key)
.replaceAll('\\$\\{key\\}', key)
}
[parName, val]
}
Expand Down Expand Up @@ -202,16 +206,17 @@ def _vdsl3ProcessFactory(Map workflowArgs, Map meta, String rawScript) {
def createParentStr = meta.config.allArguments
.findAll { it.type == "file" && it.direction == "output" && it.create_parent }
.collect { par ->
"\${ args.containsKey(\"${par.plainName}\") ? \"mkdir_parent \\\"\" + (args[\"${par.plainName}\"] instanceof String ? args[\"${par.plainName}\"] : args[\"${par.plainName}\"].join('\" \"')) + \"\\\"\" : \"\" }"
def contents = "args[\"${par.plainName}\"] instanceof List ? args[\"${par.plainName}\"].join('\" \"') : args[\"${par.plainName}\"]"
"\${ args.containsKey(\"${par.plainName}\") ? \"mkdir_parent '\" + escapeText(${contents}) + \"'\" : \"\" }"
}
.join("\n")

// construct inputFileExports
def inputFileExports = meta.config.allArguments
.findAll { it.type == "file" && it.direction.toLowerCase() == "input" }
.collect { par ->
def viash_par_contents = "(viash_par_${par.plainName} instanceof List ? viash_par_${par.plainName}.join(\"${par.multiple_sep}\") : viash_par_${par.plainName})"
"\n\${viash_par_${par.plainName}.empty ? \"\" : \"export VIASH_PAR_${par.plainName.toUpperCase()}=\\\"\" + ${viash_par_contents} + \"\\\"\"}"
def contents = "viash_par_${par.plainName} instanceof List ? viash_par_${par.plainName}.join(\"${par.multiple_sep}\") : viash_par_${par.plainName}"
"\n\${viash_par_${par.plainName}.empty ? \"\" : \"export VIASH_PAR_${par.plainName.toUpperCase()}='\" + escapeText(${contents}) + \"'\"}"
}

// NOTE: if using docker, use /tmp instead of tmpDir!
Expand Down Expand Up @@ -248,6 +253,7 @@ def _vdsl3ProcessFactory(Map workflowArgs, Map meta, String rawScript) {
def procStr =
"""nextflow.enable.dsl=2
|
|def escapeText = { s -> s.toString().replaceAll("'", "'\\\"'\\\"'") }
|process $procKey {$drctvStrs
|input:
| tuple val(id)$inputPaths, val(args), path(resourcesDir, stageAs: ".viash_meta_resources")
Expand All @@ -259,10 +265,9 @@ def _vdsl3ProcessFactory(Map workflowArgs, Map meta, String rawScript) {
|$stub
|\"\"\"
|script:$assertStr
|def escapeText = { s -> s.toString().replaceAll('([`"])', '\\\\\\\\\$1') }
|def parInject = args
| .findAll{key, value -> value != null}
| .collect{key, value -> "export VIASH_PAR_\${key.toUpperCase()}=\\\"\${escapeText(value)}\\\""}
| .collect{key, value -> "export VIASH_PAR_\${key.toUpperCase()}='\${escapeText(value)}'"}
| .join("\\n")
|\"\"\"
|# meta exports
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
Map _processInputValues(Map inputs, Map config, String id, String key) {
if (!workflow.stubRun) {
config.allArguments.each { arg ->
if (arg.required) {
if (arg.required && arg.direction == "input") {
assert inputs.containsKey(arg.plainName) && inputs.get(arg.plainName) != null :
"Error in module '${key}' id '${id}': required input argument '${arg.plainName}' is missing"
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,5 @@
Map _processOutputValues(Map outputs, Map config, String id, String key) {
Map _checkValidOutputArgument(Map outputs, Map config, String id, String key) {
if (!workflow.stubRun) {
config.allArguments.each { arg ->
if (arg.direction == "output" && arg.required) {
assert outputs.containsKey(arg.plainName) && outputs.get(arg.plainName) != null :
"Error in module '${key}' id '${id}': required output argument '${arg.plainName}' is missing"
}
}

outputs = outputs.collectEntries { name, value ->
def par = config.allArguments.find { it.plainName == name && it.direction == "output" }
assert par != null : "Error in module '${key}' id '${id}': '${name}' is not a valid output argument"
Expand All @@ -18,3 +11,14 @@ Map _processOutputValues(Map outputs, Map config, String id, String key) {
}
return outputs
}

void _checkAllRequiredOuputsPresent(Map outputs, Map config, String id, String key) {
if (!workflow.stubRun) {
config.allArguments.each { arg ->
if (arg.direction == "output" && arg.required) {
assert outputs.containsKey(arg.plainName) && outputs.get(arg.plainName) != null :
"Error in module '${key}' id '${id}': required output argument '${arg.plainName}' is missing"
}
}
}
}
154 changes: 154 additions & 0 deletions src/main/resources/io/viash/runners/nextflow/states/publishFiles.nf
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
def publishFiles(Map args) {
def key_ = args.get("key")

assert key_ != null : "publishFiles: key must be specified"

workflow publishFilesWf {
take: input_ch
main:
input_ch
| map { tup ->
def id_ = tup[0]
def state_ = tup[1]

// the input files and the target output filenames
def inputoutputFilenames_ = collectInputOutputPaths(state_, id_ + "." + key_).transpose()
def inputFiles_ = inputoutputFilenames_[0]
def outputFilenames_ = inputoutputFilenames_[1]

[id_, inputFiles_, outputFilenames_]
}
| publishFilesProc
emit: input_ch
}
return publishFilesWf
}

process publishFilesProc {
// todo: check publishpath?
publishDir path: "${getPublishDir()}/", mode: "copy"
tag "$id"
input:
tuple val(id), path(inputFiles, stageAs: "_inputfile?/*"), val(outputFiles)
output:
tuple val(id), path{outputFiles}
script:
def copyCommands = [
inputFiles instanceof List ? inputFiles : [inputFiles],
outputFiles instanceof List ? outputFiles : [outputFiles]
]
.transpose()
.collectMany{infile, outfile ->
if (infile.toString() != outfile.toString()) {
[
"[ -d \"\$(dirname '${outfile.toString()}')\" ] || mkdir -p \"\$(dirname '${outfile.toString()}')\"",
"cp -r '${infile.toString()}' '${outfile.toString()}'"
]
} else {
// no need to copy if infile is the same as outfile
[]
}
}
"""
echo "Copying output files to destination folder"
${copyCommands.join("\n ")}
"""
}


// this assumes that the state contains no other values other than those specified in the config
def publishFilesByConfig(Map args) {
def config = args.get("config")
assert config != null : "publishFilesByConfig: config must be specified"

def key_ = args.get("key", config.name)
assert key_ != null : "publishFilesByConfig: key must be specified"

workflow publishFilesSimpleWf {
take: input_ch
main:
input_ch
| map { tup ->
def id_ = tup[0]
def state_ = tup[1] // e.g. [output: new File("myoutput.h5ad"), k: 10]
def origState_ = tup[2] // e.g. [output: '$id.$key.foo.h5ad']


// the processed state is a list of [key, value, inputPath, outputFilename] tuples, where
// - key is a String
// - value is any object that can be serialized to a Yaml (so a String/Integer/Long/Double/Boolean, a List, a Map, or a Path)
// - inputPath is a List[Path]
// - outputFilename is a List[String]
// - (inputPath, outputFilename) are the files that will be copied from src to dest (relative to the state.yaml)
def processedState =
config.allArguments
.findAll { it.direction == "output" }
.collectMany { par ->
def plainName_ = par.plainName
// if the state does not contain the key, it's an
// optional argument for which the component did
// not generate any output OR multiple channels were emitted
// and the output was just not added to using the channel
// that is now being parsed
if (!state_.containsKey(plainName_)) {
return []
}
def value = state_[plainName_]
// if the parameter is not a file, it should be stored
// in the state as-is, but is not something that needs
// to be copied from the source path to the dest path
if (par.type != "file") {
return [[inputPath: [], outputFilename: []]]
}
// if the orig state does not contain this filename,
// it's an optional argument for which the user specified
// that it should not be returned as a state
if (!origState_.containsKey(plainName_)) {
return []
}
def filenameTemplate = origState_[plainName_]
// if the pararameter is multiple: true, fetch the template
if (par.multiple && filenameTemplate instanceof List) {
filenameTemplate = filenameTemplate[0]
}
// instantiate the template
def filename = filenameTemplate
.replaceAll('\\$id', id_)
.replaceAll('\\$\\{id\\}', id_)
.replaceAll('\\$key', key_)
.replaceAll('\\$\\{key\\}', key_)
if (par.multiple) {
// if the parameter is multiple: true, the filename
// should contain a wildcard '*' that is replaced with
// the index of the file
assert filename.contains("*") : "Module '${key_}' id '${id_}': Multiple output files specified, but no wildcard '*' in the filename: ${filename}"
def outputPerFile = value.withIndex().collect{ val, ix ->
def filename_ix = filename.replace("*", ix.toString())
def inputPath = val instanceof File ? val.toPath() : val
[inputPath: inputPath, outputFilename: filename_ix]
}
def transposedOutputs = ["inputPath", "outputFilename"].collectEntries{ key ->
[key, outputPerFile.collect{dic -> dic[key]}]
}
return [[key: plainName_] + transposedOutputs]
} else {
def value_ = java.nio.file.Paths.get(filename)
def inputPath = value instanceof File ? value.toPath() : value
return [[inputPath: [inputPath], outputFilename: [filename]]]
}
}

def inputPaths = processedState.collectMany{it.inputPath}
def outputFilenames = processedState.collectMany{it.outputFilename}


[id_, inputPaths, outputFilenames]
}
| publishFilesProc
emit: input_ch
}
return publishFilesSimpleWf
}



Loading

0 comments on commit 9b0d19c

Please sign in to comment.