From 05b679ae61f6215bd1c49a14d8869ac3188c6b76 Mon Sep 17 00:00:00 2001 From: Mario Frank Date: Thu, 15 Jan 2026 15:37:18 +0100 Subject: [PATCH 1/3] [FIX] Implement a configuration option `create_partial_scripts` (`false` by default) and only create partial scripts with missing tool information as comment if set to `true`. Otherwise, log output file and missing tools as error. --- src/main/java/nl/uu/cs/ape/APE.java | 27 ++++++++++++---- .../uu/cs/ape/configuration/APERunConfig.java | 32 +++++++++++++++++++ .../tags/APEConfigTagFactory.java | 27 ++++++++++++++++ .../graphviz/SolutionGraphFactory.java | 11 ++++++- src/test/resources/cli/gmt/base_config.json | 1 + 5 files changed, 91 insertions(+), 7 deletions(-) diff --git a/src/main/java/nl/uu/cs/ape/APE.java b/src/main/java/nl/uu/cs/ape/APE.java index 6f750a18..1a8067e5 100644 --- a/src/main/java/nl/uu/cs/ape/APE.java +++ b/src/main/java/nl/uu/cs/ape/APE.java @@ -4,9 +4,8 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; -import java.util.Arrays; -import java.util.Collection; -import java.util.SortedSet; +import java.util.*; + import org.json.JSONException; import org.json.JSONObject; @@ -28,6 +27,7 @@ import nl.uu.cs.ape.models.logic.constructs.TaxonomyPredicate; import nl.uu.cs.ape.solver.SynthesisEngine; import nl.uu.cs.ape.solver.minisat.SATSynthesisEngine; +import nl.uu.cs.ape.solver.solutionStructure.ModuleNode; import nl.uu.cs.ape.solver.solutionStructure.SolutionWorkflow; import nl.uu.cs.ape.solver.solutionStructure.SolutionsList; import nl.uu.cs.ape.solver.solutionStructure.cwl.DefaultCWLCreator; @@ -413,9 +413,24 @@ public static boolean writeExecutableWorkflows(SolutionsList allSolutions) { /* Creating the requested scripts in parallel. */ allSolutions.getParallelStream().filter(solution -> solution.getIndex() < noExecutions).forEach(solution -> { try { - File script = executionsFolder.resolve(solution.getFileName() + ".sh").toFile(); - APEFiles.write2file(solution.getScriptExecution(), script, false); - + File script = executionsFolder.resolve(solution.getFileName() + ".sh").toFile(); + if (allSolutions.getRunConfiguration().getCreatePartialScripts()) { + APEFiles.write2file(solution.getScriptExecution(), script, false); + } else { + List emptyOperations = new ArrayList<>(); + for (ModuleNode operation : solution.getModuleNodes()){ + String code = operation.getUsedModule().getExecutionCommand(); + if (code == null || code.equals("")) { + emptyOperations.add(String.format("'%s'", operation.getNodeLabel())); + } + } + + if (emptyOperations.isEmpty()) { + APEFiles.write2file(solution.getScriptExecution(), script, false); + } else { + log.error("Cannot create {} due to missing code for: {}", script.getAbsolutePath(), String.join(", ", emptyOperations)); + } + } } catch (IOException e) { log.error("Error occurred while writing an executable (workflow) script to the file system."); e.printStackTrace(); diff --git a/src/main/java/nl/uu/cs/ape/configuration/APERunConfig.java b/src/main/java/nl/uu/cs/ape/configuration/APERunConfig.java index 019f0dec..f3d6c438 100644 --- a/src/main/java/nl/uu/cs/ape/configuration/APERunConfig.java +++ b/src/main/java/nl/uu/cs/ape/configuration/APERunConfig.java @@ -42,6 +42,10 @@ public class APERunConfig { * Path to the directory that will contain all the solutions to the problem. */ private final APEConfigTag SOLUTION_DIR_PATH = new APEConfigTagFactory.TAGS.SOLUTION_DIR_PATH(); + /** + * Mode is true if partial shell scripts are generated. + */ + private final APEConfigTag CREATE_PARTIAL_SCRIPTS = new APEConfigTagFactory.TAGS.CREATE_PARTIAL_SCRIPTS(); /** * Min and Max possible length of the solutions (length of the automaton). For * no upper limit, max length should be set to 0. @@ -119,6 +123,7 @@ public class APERunConfig { this.CONSTRAINTS_FILE, this.CONSTRAINTS_CONTENT, this.SOLUTION_DIR_PATH, + this.CREATE_PARTIAL_SCRIPTS, this.SOLUTION_LENGTH_RANGE, this.NO_SOLUTIONS, this.NO_EXECUTIONS, @@ -142,6 +147,7 @@ public class APERunConfig { new CONSTRAINTS_FILE(), new CONSTRAINTS_CONTENT(), new SOLUTION_DIR_PATH(), + new CREATE_PARTIAL_SCRIPTS(), new SOLUTION_LENGTH_RANGE(), new NO_SOLUTIONS(), new NO_EXECUTIONS(), @@ -183,6 +189,7 @@ private APERunConfig(Builder builder) { setMaxNoSolutions(builder.maxNoSolutions); setToolSeqRepeat(builder.toolSeqRepeat); setSolutionPath(builder.solutionDirPath); + setCreatePartialScrips(builder.createPartialScripts); setNoExecutions(builder.noExecutions); setNoGraphs(builder.noGraphs); setNoCWL(builder.noCWL); @@ -408,6 +415,22 @@ public void setSolutionPath(String solutionPath) { SOLUTION_DIR_PATH.setValue(Paths.get(solutionPath)); } + /** + * Gets partial script output mode. + * + * @return the value of {@link #CREATE_PARTIAL_SCRIPTS} + */ + public boolean getCreatePartialScripts() { + return CREATE_PARTIAL_SCRIPTS.getValue(); + } + + /** + * @param createPartialScripts the partial script output mode to set + */ + public void setCreatePartialScrips(boolean createPartialScripts) { + CREATE_PARTIAL_SCRIPTS.setValue(createPartialScripts); + } + /** * Gets solution min and max length. * @@ -679,6 +702,8 @@ public interface IBuildStage { IBuildStage withSolutionDirPath(String solutionPath); + IBuildStage withCreatePartialScripts(boolean createPartialScripts); + IBuildStage withNoExecutions(int noExecutions); IBuildStage withNoGraphs(int noGraphs); @@ -712,6 +737,7 @@ public static final class Builder implements ISolutionMinLengthStage, ISolutionM private JSONArray constraintsJSON; private boolean toolSeqRepeat; private String solutionDirPath; + private boolean createPartialScripts; private int noExecutions; private int noGraphs; private int noCWL; @@ -767,6 +793,12 @@ public IBuildStage withSolutionDirPath(String solutionDirPath) { return this; } + @Override + public IBuildStage withCreatePartialScripts(boolean createPartialScripts) { + this.createPartialScripts = createPartialScripts; + return this; + } + @Override public IBuildStage withNoExecutions(int noExecutions) { this.noExecutions = noExecutions; diff --git a/src/main/java/nl/uu/cs/ape/configuration/tags/APEConfigTagFactory.java b/src/main/java/nl/uu/cs/ape/configuration/tags/APEConfigTagFactory.java index 395e61cc..fafa5575 100644 --- a/src/main/java/nl/uu/cs/ape/configuration/tags/APEConfigTagFactory.java +++ b/src/main/java/nl/uu/cs/ape/configuration/tags/APEConfigTagFactory.java @@ -825,6 +825,33 @@ public String getDescription() { } } + /** + * Configuration field. + */ + public static class CREATE_PARTIAL_SCRIPTS extends TYPES.Bool { + + @Override + public String getTagName() { + return "create_partial_scripts"; + } + + @Override + public String getLabel() { + return "Create partial script files"; + } + + @Override + public String getDescription() { + return "Tag to indicate whether partial shell scripts shall be created."; + } + + @Override + public APEConfigDefaultValue getDefault() { + return APEConfigDefaultValue.withDefault(false); + } + } + + /** * Configuration field. */ diff --git a/src/main/java/nl/uu/cs/ape/solver/solutionStructure/graphviz/SolutionGraphFactory.java b/src/main/java/nl/uu/cs/ape/solver/solutionStructure/graphviz/SolutionGraphFactory.java index 063e09d1..5da247f8 100644 --- a/src/main/java/nl/uu/cs/ape/solver/solutionStructure/graphviz/SolutionGraphFactory.java +++ b/src/main/java/nl/uu/cs/ape/solver/solutionStructure/graphviz/SolutionGraphFactory.java @@ -4,6 +4,7 @@ import static guru.nidi.graphviz.model.Factory.node; import static guru.nidi.graphviz.model.Factory.to; +import java.util.Collections; import java.util.List; import guru.nidi.graphviz.attribute.Attributes; @@ -272,6 +273,12 @@ public static String generateScriptExecution(SolutionWorkflow workflow) { List workflowInputs = workflow.getWorkflowInputTypeStates(); List workflowOutputs = workflow.getWorkflowOutputTypeStates(); List moduleNodes = workflow.getModuleNodes(); + + // Add the generation header + script.append(String.format("# WorkflowNo_%02d\n", workflow.getIndex()+1)); + script.append("# This workflow is generated by APE (https://github.com/workflomics/ape).\n"); + + // Add input parameter check and assignment. script.append("if [ $# -ne " + workflowInputs.size() + " ]\n\tthen\n"); script .append("\t\techo \"" + workflowInputs.size() @@ -281,10 +288,12 @@ public static String generateScriptExecution(SolutionWorkflow workflow) { script.append(input.getShortNodeID() + "=$" + (in++) + "\n"); } script.append("\n"); + + // Process nodes of the workflow. for (ModuleNode operation : moduleNodes) { String code = operation.getUsedModule().getExecutionCommand(); if (code == null || code.equals("")) { - script.append("\"Error. Tool '" + operation.getNodeLabel() + "' is missing the execution code.\"") + script.append("# Error: Tool '" + operation.getNodeLabel() + "' is missing the execution code. Skipping.") .append("\n"); } else { for (int i = 0; i < operation.getInputTypes().size(); i++) { diff --git a/src/test/resources/cli/gmt/base_config.json b/src/test/resources/cli/gmt/base_config.json index aed5bb10..b8787302 100644 --- a/src/test/resources/cli/gmt/base_config.json +++ b/src/test/resources/cli/gmt/base_config.json @@ -2,6 +2,7 @@ "ontologyPrefixIRI": "http://www.co-ode.org/ontologies/ont.owl#", "toolsTaxonomyRoot": "ToolsTaxonomy", "dataDimensionsTaxonomyRoots": ["TypesTaxonomy"], + "create_partial_scripts": "true", "solution_length": { "min": 1, "max": 8 }, "solutions": "100", "number_of_execution_scripts": "3", From d6e2fdc81f923f97c9dcfe32ee4301cd3881c8fa Mon Sep 17 00:00:00 2001 From: Mario Frank Date: Mon, 19 Jan 2026 15:07:30 +0100 Subject: [PATCH 2/3] [Update] Set partial script generation to true as default, extend support for restricting to full implementations for CWL and snakemake by leveraging "CWL file reference" and "code". --- src/main/java/nl/uu/cs/ape/APE.java | 66 ++++++++++++++++--- src/main/java/nl/uu/cs/ape/Main.java | 7 +- .../tags/APEConfigTagFactory.java | 2 +- .../cwl/DefaultCWLCreator.java | 7 +- .../snakemake/SnakemakeCreator.java | 9 ++- .../uu/cs/ape/test/sat/ape/UseCaseTest.java | 2 +- 6 files changed, 74 insertions(+), 19 deletions(-) diff --git a/src/main/java/nl/uu/cs/ape/APE.java b/src/main/java/nl/uu/cs/ape/APE.java index 1a8067e5..2ea2198d 100644 --- a/src/main/java/nl/uu/cs/ape/APE.java +++ b/src/main/java/nl/uu/cs/ape/APE.java @@ -390,9 +390,10 @@ public static boolean writeSolutionToFile(SolutionsList allSolutions) throws IOE * solutions and executing them. * * @param allSolutions Set of {@link SolutionWorkflow}. + * @param createPartialImplementations Sets whether implementations with missing code shall be created. * @return true if the execution was successfully performed, false otherwise. */ - public static boolean writeExecutableWorkflows(SolutionsList allSolutions) { + public static boolean writeExecutableWorkflows(SolutionsList allSolutions, boolean createPartialImplementations) { Path executionsFolder = allSolutions.getRunConfiguration().getSolutionDirPath2Executables(); Integer noExecutions = allSolutions.getRunConfiguration().getNoExecutions(); if (executionsFolder == null || noExecutions == null || noExecutions == 0 || allSolutions.isEmpty()) { @@ -414,7 +415,7 @@ public static boolean writeExecutableWorkflows(SolutionsList allSolutions) { allSolutions.getParallelStream().filter(solution -> solution.getIndex() < noExecutions).forEach(solution -> { try { File script = executionsFolder.resolve(solution.getFileName() + ".sh").toFile(); - if (allSolutions.getRunConfiguration().getCreatePartialScripts()) { + if (createPartialImplementations) { APEFiles.write2file(solution.getScriptExecution(), script, false); } else { List emptyOperations = new ArrayList<>(); @@ -625,9 +626,10 @@ public static boolean writeControlFlowGraphs(SolutionsList allSolutions, RankDir * * @param allSolutions Set of {@link SolutionWorkflow} which should be * represented in CWL. + * @param createPartialImplementations Sets whether implementations with missing code shall be created. * @return true if the execution was successfully performed, false otherwise. */ - public static boolean writeCWLWorkflows(SolutionsList allSolutions) { + public static boolean writeCWLWorkflows(SolutionsList allSolutions, boolean createPartialImplementations) { if (allSolutions.isEmpty()) { return false; @@ -658,8 +660,26 @@ public static boolean writeCWLWorkflows(SolutionsList allSolutions) { // Write the cwl file to the file system String titleCWL = solution.getFileName() + ".cwl"; File script = cwlFolder.resolve(titleCWL).toFile(); - DefaultCWLCreator cwlCreator = new DefaultCWLCreator(solution); - APEFiles.write2file(cwlCreator.generate(), script, false); + + if (createPartialImplementations) { + DefaultCWLCreator cwlCreator = new DefaultCWLCreator(solution); + APEFiles.write2file(cwlCreator.generate(), script, false); + } else { + List emptyOperations = new ArrayList<>(); + for (ModuleNode operation : solution.getModuleNodes()){ + String instruction = operation.getUsedModule().getCwlFileReference(); + if (instruction == null) { + emptyOperations.add(String.format("'%s'", operation.getNodeLabel())); + } + } + + if (emptyOperations.isEmpty()) { + DefaultCWLCreator cwlCreator = new DefaultCWLCreator(solution); + APEFiles.write2file(cwlCreator.generate(), script, false); + } else { + log.error("Cannot create CWL file {} due to missing CWL reference for: {}", script.getAbsolutePath(), String.join(", ", emptyOperations)); + } + } } catch (IOException e) { log.error("Error occurred while writing a CWL file to the file system."); @@ -682,7 +702,16 @@ public static boolean writeCWLWorkflows(SolutionsList allSolutions) { return true; } - public static boolean writeSnakemakeWorkflows(SolutionsList allSolutions) { + /** + * Generate Snakemake scripts that represent executable versions of the workflows + * solutions. + * + * @param allSolutions Set of {@link SolutionWorkflow} which should be + * represented in Snakemake. + * @param createPartialImplementations Sets whether implementations with missing code shall be created. + * @return true if the execution was successfully performed, false otherwise. + */ + public static boolean writeSnakemakeWorkflows(SolutionsList allSolutions, boolean createPartialImplementations) { if (allSolutions.isEmpty()) { return false; } @@ -708,11 +737,28 @@ public static boolean writeSnakemakeWorkflows(SolutionsList allSolutions) { allSolutions.getParallelStream().filter(solution -> solution.getIndex() < noSnakemakeFiles).forEach(solution -> { try { - String titleSnakefile= solution.getFileName() + "_snakefile"; - File script = snakemakeFolder.resolve(titleSnakefile).toFile(); - SnakemakeCreator snakemakeCreator = new SnakemakeCreator(solution); - APEFiles.write2file(snakemakeCreator.generateSnakemakeRepresentation(), script, false); + String titleSnakefile = solution.getFileName() + "_snakefile"; + File script = snakemakeFolder.resolve(titleSnakefile).toFile(); + if (createPartialImplementations) { + SnakemakeCreator snakemakeCreator = new SnakemakeCreator(solution); + APEFiles.write2file(snakemakeCreator.generateSnakemakeRepresentation(), script, false); + } else { + List emptyOperations = new ArrayList<>(); + for (ModuleNode operation : solution.getModuleNodes()){ + String code = operation.getUsedModule().getExecutionCommand(); + if (code == null || code.equals("")) { + emptyOperations.add(String.format("'%s'", operation.getNodeLabel())); + } + } + + if (emptyOperations.isEmpty()) { + SnakemakeCreator snakemakeCreator = new SnakemakeCreator(solution); + APEFiles.write2file(snakemakeCreator.generateSnakemakeRepresentation(), script, false); + } else { + log.error("Cannot create Snakemake file {} due to missing code for: {}", script.getAbsolutePath(), String.join(", ", emptyOperations)); + } + } } catch (IOException e) { log.error("Error occurred while writing a Snakemake file to the file system."); e.printStackTrace(); diff --git a/src/main/java/nl/uu/cs/ape/Main.java b/src/main/java/nl/uu/cs/ape/Main.java index 98a8df5f..584e1714 100644 --- a/src/main/java/nl/uu/cs/ape/Main.java +++ b/src/main/java/nl/uu/cs/ape/Main.java @@ -232,13 +232,14 @@ public static void executeSynthesis(String[] args) { log.info("The problem is UNSAT."); } else { try { + boolean createPartialImplementations = solutions.getRunConfiguration().getCreatePartialScripts(); APE.writeSolutionToFile(solutions); // The following method can be changed to write the solutions in different // formats (e.g., control flow graph, data flow graph) APE.writeTavernaDesignGraphs(solutions); - APE.writeExecutableWorkflows(solutions); - APE.writeCWLWorkflows(solutions); - APE.writeSnakemakeWorkflows(solutions); + APE.writeExecutableWorkflows(solutions, createPartialImplementations); + APE.writeCWLWorkflows(solutions, createPartialImplementations); + APE.writeSnakemakeWorkflows(solutions, createPartialImplementations); } catch (IOException e) { log.error("Error in writing the solutions. to the file system."); e.printStackTrace(); diff --git a/src/main/java/nl/uu/cs/ape/configuration/tags/APEConfigTagFactory.java b/src/main/java/nl/uu/cs/ape/configuration/tags/APEConfigTagFactory.java index fafa5575..7e2b410b 100644 --- a/src/main/java/nl/uu/cs/ape/configuration/tags/APEConfigTagFactory.java +++ b/src/main/java/nl/uu/cs/ape/configuration/tags/APEConfigTagFactory.java @@ -847,7 +847,7 @@ public String getDescription() { @Override public APEConfigDefaultValue getDefault() { - return APEConfigDefaultValue.withDefault(false); + return APEConfigDefaultValue.withDefault(true); } } diff --git a/src/main/java/nl/uu/cs/ape/solver/solutionStructure/cwl/DefaultCWLCreator.java b/src/main/java/nl/uu/cs/ape/solver/solutionStructure/cwl/DefaultCWLCreator.java index 6ef6ff1d..bf899307 100644 --- a/src/main/java/nl/uu/cs/ape/solver/solutionStructure/cwl/DefaultCWLCreator.java +++ b/src/main/java/nl/uu/cs/ape/solver/solutionStructure/cwl/DefaultCWLCreator.java @@ -255,11 +255,14 @@ private void generateStepOut(ModuleNode moduleNode) { */ private void generateDefaultStepRun(ModuleNode moduleNode) { final int baseInd = 2; - String moduleReference = "add-path-to-the-implementation/" + moduleNode.getUsedModule().getPredicateID() - + ".cwl "; + String moduleName = moduleNode.getUsedModule().getPredicateID(); + String moduleReference = "add-path-to-the-implementation/" + moduleName + ".cwl "; if (moduleNode.getUsedModule().getCwlFileReference() != null) { moduleReference = moduleNode.getUsedModule().getCwlFileReference(); + } else { + log.info("No CWL reference for {} specified, using fallback reference \"{}\"", moduleName, moduleReference); } + cwlRepresentation // Main key .append(ind(baseInd)) diff --git a/src/main/java/nl/uu/cs/ape/solver/solutionStructure/snakemake/SnakemakeCreator.java b/src/main/java/nl/uu/cs/ape/solver/solutionStructure/snakemake/SnakemakeCreator.java index f46e6d01..7f09d141 100644 --- a/src/main/java/nl/uu/cs/ape/solver/solutionStructure/snakemake/SnakemakeCreator.java +++ b/src/main/java/nl/uu/cs/ape/solver/solutionStructure/snakemake/SnakemakeCreator.java @@ -148,10 +148,15 @@ private void generateRuleOutput(ModuleNode moduleNode) { * @param moduleNode The {@link ModuleNode} corresponding to the rule. */ private void generateRuleShell(ModuleNode moduleNode) { - String name = moduleNode.getUsedModule().getPredicateLabel(); + String moduleName = moduleNode.getUsedModule().getPredicateLabel(); + String command = moduleNode.getUsedModule().getExecutionCommand(); + if (command == null || command.equals("")) { + log.info("No command for {} specified, using this name as fallback command \"{}\"", moduleName); + command = moduleName; + } snakemakeRepresentation .append(ind(1)) - .append(String.format("shell: 'add-path-to-implementation/%s {input} {output}'", name)) + .append(String.format("shell: 'add-path-to-implementation/%s {input} {output}'", command)) .append("\n"); } diff --git a/src/test/java/nl/uu/cs/ape/test/sat/ape/UseCaseTest.java b/src/test/java/nl/uu/cs/ape/test/sat/ape/UseCaseTest.java index 7c8a4fe5..bf33ece0 100644 --- a/src/test/java/nl/uu/cs/ape/test/sat/ape/UseCaseTest.java +++ b/src/test/java/nl/uu/cs/ape/test/sat/ape/UseCaseTest.java @@ -141,7 +141,7 @@ void testUseCase(String name, String evaluationPath) // Test the generation of CWL files if they should be generated if (mutation.number_of_cwl_files != 0) { - boolean writingSuccess = APE.writeCWLWorkflows(solutions); + boolean writingSuccess = APE.writeCWLWorkflows(solutions, solutions.getRunConfiguration().getCreatePartialScripts()); assertTrue(writingSuccess); String cwl_path = config.get("solutions_dir_path").toString(); From 2fea571c550ca2403f6ada35b31ca657110a3bab Mon Sep 17 00:00:00 2001 From: Mario Frank Date: Tue, 27 Jan 2026 11:15:07 +0100 Subject: [PATCH 3/3] [Update] Use loglevel `info` since missing commands are not an error. Instead, we inform the user that the file was not generated. --- src/main/java/nl/uu/cs/ape/APE.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/nl/uu/cs/ape/APE.java b/src/main/java/nl/uu/cs/ape/APE.java index 2ea2198d..2af3034e 100644 --- a/src/main/java/nl/uu/cs/ape/APE.java +++ b/src/main/java/nl/uu/cs/ape/APE.java @@ -429,7 +429,7 @@ public static boolean writeExecutableWorkflows(SolutionsList allSolutions, boole if (emptyOperations.isEmpty()) { APEFiles.write2file(solution.getScriptExecution(), script, false); } else { - log.error("Cannot create {} due to missing code for: {}", script.getAbsolutePath(), String.join(", ", emptyOperations)); + log.info("Cannot create {} due to missing code for: {}", script.getAbsolutePath(), String.join(", ", emptyOperations)); } } } catch (IOException e) { @@ -677,7 +677,7 @@ public static boolean writeCWLWorkflows(SolutionsList allSolutions, boolean crea DefaultCWLCreator cwlCreator = new DefaultCWLCreator(solution); APEFiles.write2file(cwlCreator.generate(), script, false); } else { - log.error("Cannot create CWL file {} due to missing CWL reference for: {}", script.getAbsolutePath(), String.join(", ", emptyOperations)); + log.info("Cannot create CWL file {} due to missing CWL reference for: {}", script.getAbsolutePath(), String.join(", ", emptyOperations)); } } @@ -756,7 +756,7 @@ public static boolean writeSnakemakeWorkflows(SolutionsList allSolutions, boolea SnakemakeCreator snakemakeCreator = new SnakemakeCreator(solution); APEFiles.write2file(snakemakeCreator.generateSnakemakeRepresentation(), script, false); } else { - log.error("Cannot create Snakemake file {} due to missing code for: {}", script.getAbsolutePath(), String.join(", ", emptyOperations)); + log.info("Cannot create Snakemake file {} due to missing code for: {}", script.getAbsolutePath(), String.join(", ", emptyOperations)); } } } catch (IOException e) {