Replaced pattern matched pipeline spec with annotated objects.
Old version is no longer available. git-svn-id: file:///humgen/gsa-scr1/gsa-engineering/svn_contents/trunk@3558 348d0f76-0448-11de-a6fe-93d51630548a
This commit is contained in:
parent
b99a5e06f3
commit
32fc221ffe
4
ivy.xml
4
ivy.xml
|
|
@ -42,8 +42,8 @@
|
||||||
<dependency org="commons-io" name="commons-io" rev="1.3.2" conf="default"/>
|
<dependency org="commons-io" name="commons-io" rev="1.3.2" conf="default"/>
|
||||||
|
|
||||||
<!-- Scala dependancies -->
|
<!-- Scala dependancies -->
|
||||||
<dependency org="org.scala-lang" name="scala-compiler" rev="2.8.0.RC2" conf="scala->default"/>
|
<dependency org="org.scala-lang" name="scala-compiler" rev="2.8.0.RC5" conf="scala->default"/>
|
||||||
<dependency org="org.scala-lang" name="scala-library" rev="2.8.0.RC2" conf="scala->default"/>
|
<dependency org="org.scala-lang" name="scala-library" rev="2.8.0.RC5" conf="scala->default"/>
|
||||||
|
|
||||||
<!-- Queue additional dependencies -->
|
<!-- Queue additional dependencies -->
|
||||||
<dependency org="commons-lang" name="commons-lang" rev="2.5" conf="queue->default"/>
|
<dependency org="commons-lang" name="commons-lang" rev="2.5" conf="queue->default"/>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2010, The Broad Institute
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person
|
||||||
|
* obtaining a copy of this software and associated documentation
|
||||||
|
* files (the "Software"), to deal in the Software without
|
||||||
|
* restriction, including without limitation the rights to use,
|
||||||
|
* copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the
|
||||||
|
* Software is furnished to do so, subject to the following
|
||||||
|
* conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be
|
||||||
|
* included in all copies or substantial portions of the Software.
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||||
|
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||||
|
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||||
|
* OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.broadinstitute.sting.queue.util;
|
||||||
|
|
||||||
|
import java.lang.annotation.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specifies the type of an input our output field.
|
||||||
|
* Retains it during runtime to work around type erasure.
|
||||||
|
* Written in java because scala doesn't support RetentionPolicy.RUNTIME
|
||||||
|
*/
|
||||||
|
@Documented
|
||||||
|
@Inherited
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@Target({ElementType.FIELD})
|
||||||
|
public @interface ClassType {
|
||||||
|
Class value();
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2010, The Broad Institute
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person
|
||||||
|
* obtaining a copy of this software and associated documentation
|
||||||
|
* files (the "Software"), to deal in the Software without
|
||||||
|
* restriction, including without limitation the rights to use,
|
||||||
|
* copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the
|
||||||
|
* Software is furnished to do so, subject to the following
|
||||||
|
* conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be
|
||||||
|
* included in all copies or substantial portions of the Software.
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||||
|
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||||
|
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||||
|
* OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.broadinstitute.sting.queue.util;
|
||||||
|
|
||||||
|
import java.lang.annotation.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specifies an input to a QueueFunction
|
||||||
|
* Written in java because scala doesn't support RetentionPolicy.RUNTIME
|
||||||
|
*/
|
||||||
|
@Documented
|
||||||
|
@Inherited
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@Target({ElementType.FIELD})
|
||||||
|
public @interface Input {
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2010, The Broad Institute
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person
|
||||||
|
* obtaining a copy of this software and associated documentation
|
||||||
|
* files (the "Software"), to deal in the Software without
|
||||||
|
* restriction, including without limitation the rights to use,
|
||||||
|
* copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the
|
||||||
|
* Software is furnished to do so, subject to the following
|
||||||
|
* conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be
|
||||||
|
* included in all copies or substantial portions of the Software.
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||||
|
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||||
|
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||||
|
* OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.broadinstitute.sting.queue.util;
|
||||||
|
|
||||||
|
import java.lang.annotation.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specifies an internal setting for a QueueFunction.
|
||||||
|
* Not an input or output but should be copied with a function.
|
||||||
|
* Internals should have default values that should be handled, i.e. they are always @Optional
|
||||||
|
* Written in java because scala doesn't support RetentionPolicy.RUNTIME
|
||||||
|
*/
|
||||||
|
@Documented
|
||||||
|
@Inherited
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@Target({ElementType.FIELD})
|
||||||
|
public @interface Internal {
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2010, The Broad Institute
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person
|
||||||
|
* obtaining a copy of this software and associated documentation
|
||||||
|
* files (the "Software"), to deal in the Software without
|
||||||
|
* restriction, including without limitation the rights to use,
|
||||||
|
* copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the
|
||||||
|
* Software is furnished to do so, subject to the following
|
||||||
|
* conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be
|
||||||
|
* included in all copies or substantial portions of the Software.
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||||
|
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||||
|
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||||
|
* OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.broadinstitute.sting.queue.util;
|
||||||
|
|
||||||
|
import java.lang.annotation.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specifies an input or output to a QueueFunction is optional
|
||||||
|
* Written in java because scala doesn't support RetentionPolicy.RUNTIME
|
||||||
|
*/
|
||||||
|
@Documented
|
||||||
|
@Inherited
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@Target({ElementType.FIELD})
|
||||||
|
public @interface Optional {
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2010, The Broad Institute
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person
|
||||||
|
* obtaining a copy of this software and associated documentation
|
||||||
|
* files (the "Software"), to deal in the Software without
|
||||||
|
* restriction, including without limitation the rights to use,
|
||||||
|
* copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the
|
||||||
|
* Software is furnished to do so, subject to the following
|
||||||
|
* conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be
|
||||||
|
* included in all copies or substantial portions of the Software.
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||||
|
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||||
|
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||||
|
* OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.broadinstitute.sting.queue.util;
|
||||||
|
|
||||||
|
import java.lang.annotation.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specifies an output to a QueueFunction
|
||||||
|
* Written in java because scala doesn't support RetentionPolicy.RUNTIME
|
||||||
|
*/
|
||||||
|
@Documented
|
||||||
|
@Inherited
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@Target({ElementType.FIELD})
|
||||||
|
public @interface Output {
|
||||||
|
}
|
||||||
|
|
@ -8,13 +8,13 @@ import java.io.{FileInputStream, File}
|
||||||
import java.util.Properties
|
import java.util.Properties
|
||||||
|
|
||||||
class QArguments(args: Array[String]) {
|
class QArguments(args: Array[String]) {
|
||||||
var useBsub = false
|
var bsubAllJobs = false
|
||||||
var dryRun = false
|
var dryRun = false
|
||||||
val scripts = new ListBuffer[String]
|
val scripts = new ListBuffer[String]
|
||||||
var inputPaths = List.empty[File]
|
var inputPaths = List.empty[File]
|
||||||
var argMap = Map.empty[String, String]
|
var argMap = Map.empty[String, String]
|
||||||
|
|
||||||
parseArgs(args)
|
val userArgs = parseArgs(args)
|
||||||
|
|
||||||
private def parseArgs(args: Array[String]) = {
|
private def parseArgs(args: Array[String]) = {
|
||||||
var filtered = new ListBuffer[String]
|
var filtered = new ListBuffer[String]
|
||||||
|
|
@ -26,13 +26,15 @@ class QArguments(args: Array[String]) {
|
||||||
if (isFlagged(filtered, "-dry"))
|
if (isFlagged(filtered, "-dry"))
|
||||||
dryRun = true
|
dryRun = true
|
||||||
if (isFlagged(filtered, "-bsub"))
|
if (isFlagged(filtered, "-bsub"))
|
||||||
useBsub = true
|
bsubAllJobs = true
|
||||||
for (arg <- getArgs(filtered, "-P"))
|
for (arg <- getArgs(filtered, "-P"))
|
||||||
addArg(arg)
|
addArg(arg)
|
||||||
for (arg <- getArgs(filtered, "-I"))
|
for (arg <- getArgs(filtered, "-I"))
|
||||||
addFile(arg)
|
addFile(arg)
|
||||||
for (arg <- getArgs(filtered, "-S"))
|
for (arg <- getArgs(filtered, "-S"))
|
||||||
scripts.append(arg)
|
scripts.append(arg)
|
||||||
|
|
||||||
|
List(filtered:_*)
|
||||||
}
|
}
|
||||||
|
|
||||||
private def isFlagged(filtered: ListBuffer[String], search: String) = {
|
private def isFlagged(filtered: ListBuffer[String], search: String) = {
|
||||||
|
|
@ -65,22 +67,24 @@ class QArguments(args: Array[String]) {
|
||||||
var file = new File(arg)
|
var file = new File(arg)
|
||||||
if (arg.contains("=") && !file.exists) {
|
if (arg.contains("=") && !file.exists) {
|
||||||
val tokens = arg.split("=", 2)
|
val tokens = arg.split("=", 2)
|
||||||
argMap = argMap.updated(tokens(0), tokens(1))
|
argMap += tokens(0) -> tokens(1)
|
||||||
} else if (file.exists && arg.endsWith(".properties")) {
|
} else if (arg.endsWith(".properties")) {
|
||||||
|
if (!file.exists)
|
||||||
|
throw new QException("File not found: " + file.getAbsolutePath)
|
||||||
var props = new Properties
|
var props = new Properties
|
||||||
props.load(new FileInputStream(file))
|
props.load(new FileInputStream(file))
|
||||||
for ((name, value) <- props)
|
for ((name, value) <- props)
|
||||||
argMap = argMap.updated(name, value)
|
argMap += name -> value
|
||||||
|
} else {
|
||||||
|
throw new QException("Invalid property: " + arg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def addFile(arg: String): Unit = {
|
def addFile(arg: String): Unit = {
|
||||||
var file = new File(arg)
|
var file = new File(arg)
|
||||||
if (arg.endsWith(".list")) {
|
inputPaths :+= file
|
||||||
|
if (arg.endsWith(".list"))
|
||||||
new XReadLines(file).iterator.foreach(addFile(_))
|
new XReadLines(file).iterator.foreach(addFile(_))
|
||||||
} else {
|
|
||||||
inputPaths = inputPaths ::: List(file)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ object QCommandLine extends Application with Logging {
|
||||||
new QArguments(args)
|
new QArguments(args)
|
||||||
} catch {
|
} catch {
|
||||||
case exception => {
|
case exception => {
|
||||||
|
println(exception)
|
||||||
println(usage)
|
println(usage)
|
||||||
System.exit(-1)
|
System.exit(-1)
|
||||||
}
|
}
|
||||||
|
|
@ -34,20 +35,13 @@ object QCommandLine extends Application with Logging {
|
||||||
System.exit(-1)
|
System.exit(-1)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (qArgs.inputPaths.size == 0) {
|
|
||||||
println("Error: No inputs specified")
|
|
||||||
println(usage)
|
|
||||||
System.exit(-1)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
val newArgs = new ListBuffer[String]
|
val newArgs = new ListBuffer[String]
|
||||||
newArgs.appendAll(args)
|
newArgs.appendAll(args)
|
||||||
QArguments.strip(newArgs, "-S")
|
QArguments.strip(newArgs, "-S")
|
||||||
newArgs.prepend("-nocompdaemon", "-classpath", ClasspathUtils.manifestAwareClassPath, qArgs.scripts.head)
|
newArgs.prepend("-nocompdaemon", "-classpath", ClasspathUtils.manifestAwareClassPath, qArgs.scripts.head)
|
||||||
MainGenericRunner.main(newArgs.toArray)
|
MainGenericRunner.main(newArgs.toArray)
|
||||||
|
|
||||||
// NOTE: This line is not reached because something in MainGenericRunner is exiting the VM.
|
// NOTE: This line is not reached because the MainGenericRunner exits the VM.
|
||||||
logger.debug("exiting")
|
logger.debug("exiting")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,103 @@
|
||||||
|
package org.broadinstitute.sting.queue
|
||||||
|
|
||||||
|
import org.broadinstitute.sting.queue.function.CommandLineFunction
|
||||||
|
import org.broadinstitute.sting.queue.engine.QGraph
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Syntactic sugar for filling in a pipeline using a Scala script.
|
||||||
|
*/
|
||||||
|
object QScript {
|
||||||
|
// Type aliases so users don't have to import
|
||||||
|
type File = java.io.File
|
||||||
|
type Input = org.broadinstitute.sting.queue.util.Input
|
||||||
|
type Output = org.broadinstitute.sting.queue.util.Output
|
||||||
|
type Optional = org.broadinstitute.sting.queue.util.Optional
|
||||||
|
type ClassType = org.broadinstitute.sting.queue.util.ClassType
|
||||||
|
type CommandLineFunction = org.broadinstitute.sting.queue.function.CommandLineFunction
|
||||||
|
type GatkFunction = org.broadinstitute.sting.queue.function.gatk.GatkFunction
|
||||||
|
|
||||||
|
// The arguments for executing pipelines
|
||||||
|
private var qArgs: QArguments = _
|
||||||
|
|
||||||
|
// A default pipeline. Can also use multiple 'new Pipeline()'
|
||||||
|
private val pipeline = new Pipeline
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the QArguments and returns a list of the rest of the user args.
|
||||||
|
*/
|
||||||
|
def setArgs(params: Array[String]) = {
|
||||||
|
qArgs = new QArguments(params)
|
||||||
|
qArgs.userArgs
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a list of files that were specified with "-I <file>" on the command line
|
||||||
|
* or inside a .list file.
|
||||||
|
*/
|
||||||
|
def inputs(extension: String) = qArgs.inputPaths.filter(_.getName.endsWith(extension))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exchanges the extension on a file.
|
||||||
|
*/
|
||||||
|
def swapExt(file: File, oldExtension: String, newExtension: String) =
|
||||||
|
new File(file.getName.stripSuffix(oldExtension) + newExtension)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds one or more command line functions for dispatch later during run()
|
||||||
|
*/
|
||||||
|
def add(functions: CommandLineFunction*) = pipeline.add(functions:_*)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the @Input and @Output values for all the functions
|
||||||
|
*/
|
||||||
|
def setParams(): Unit = pipeline.setParams()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the @Input and @Output values for a single function
|
||||||
|
*/
|
||||||
|
def setParams(function: CommandLineFunction): Unit = pipeline.setParams(function)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Executes functions that have been added to the pipeline.
|
||||||
|
*/
|
||||||
|
def run() = pipeline.run()
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encapsulates a set of functions to run together.
|
||||||
|
*/
|
||||||
|
protected class Pipeline {
|
||||||
|
private var functions = List.empty[CommandLineFunction]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds one or more command line functions for dispatch later during run()
|
||||||
|
*/
|
||||||
|
def add(functions: CommandLineFunction*) =
|
||||||
|
this.functions :::= List(functions:_*)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the @Input and @Output values for all the functions
|
||||||
|
*/
|
||||||
|
def setParams(): Unit =
|
||||||
|
for (function <- functions) setParams(function)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the @Input and @Output values for a single function
|
||||||
|
*/
|
||||||
|
def setParams(function: CommandLineFunction): Unit =
|
||||||
|
for ((name, value) <- qArgs.argMap) function.setValue(name, value)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Executes functions that have been added to the pipeline.
|
||||||
|
*/
|
||||||
|
def run() = {
|
||||||
|
val qGraph = new QGraph
|
||||||
|
qGraph.dryRun = qArgs.dryRun
|
||||||
|
qGraph.bsubAllJobs = qArgs.bsubAllJobs
|
||||||
|
for (function <- functions)
|
||||||
|
qGraph.add(function)
|
||||||
|
qGraph.fillIn
|
||||||
|
qGraph.run
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
package org.broadinstitute.sting.queue.engine
|
||||||
|
|
||||||
|
import org.broadinstitute.sting.queue.util.{Logging, ProcessUtils}
|
||||||
|
import org.broadinstitute.sting.queue.function.CommandLineFunction
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs jobs one at a time locally
|
||||||
|
*/
|
||||||
|
trait CommandLineRunner extends Logging {
|
||||||
|
def run(function: CommandLineFunction, qGraph: QGraph) = {
|
||||||
|
var commandLine = function.commandLine
|
||||||
|
logger.info(commandLine)
|
||||||
|
|
||||||
|
if (!qGraph.dryRun)
|
||||||
|
ProcessUtils.runCommandAndWait(function.commandLine, function.commandDirectory)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,56 @@
|
||||||
|
package org.broadinstitute.sting.queue.engine
|
||||||
|
|
||||||
|
import edu.mit.broad.core.lsf.LocalLsfJob
|
||||||
|
import collection.JavaConversions._
|
||||||
|
import management.ManagementFactory
|
||||||
|
import java.io.File
|
||||||
|
import java.util.ArrayList
|
||||||
|
import org.broadinstitute.sting.queue.function.{DispatchFunction, QFunction}
|
||||||
|
|
||||||
|
trait DispatchJobRunner {
|
||||||
|
type DispatchJobType
|
||||||
|
private var dispatchJobs = Map.empty[DispatchFunction, DispatchJobType]
|
||||||
|
|
||||||
|
protected def newJobName = DispatchJobRunner.nextJobName
|
||||||
|
|
||||||
|
def dispatch(function: DispatchFunction, qGraph: QGraph)
|
||||||
|
|
||||||
|
protected def addJob(function: DispatchFunction, dispatchJob: DispatchJobType) =
|
||||||
|
dispatchJobs += function -> dispatchJob
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Walks up the graph looking for the previous LsfJobs
|
||||||
|
*/
|
||||||
|
protected def previousJobs(function: QFunction, qGraph: QGraph) : List[DispatchJobType] = {
|
||||||
|
var previous = List.empty[DispatchJobType]
|
||||||
|
|
||||||
|
val source = qGraph.jobGraph.getEdgeSource(function)
|
||||||
|
for (incomingEdge <- qGraph.jobGraph.incomingEdgesOf(source)) {
|
||||||
|
incomingEdge match {
|
||||||
|
|
||||||
|
// Stop recursing when we find a job along the edge and return its job id
|
||||||
|
case dispatchFunction: DispatchFunction => previous :+= dispatchJobs(dispatchFunction)
|
||||||
|
|
||||||
|
// For any other type of edge find the LSF jobs preceding the edge
|
||||||
|
case qFunction: QFunction => previous :::= previousJobs(qFunction, qGraph)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
previous
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object DispatchJobRunner {
|
||||||
|
private val jobNamePrefix = "Q-" + {
|
||||||
|
var prefix = ManagementFactory.getRuntimeMXBean.getName
|
||||||
|
val index = prefix.indexOf(".")
|
||||||
|
if (index >= 0)
|
||||||
|
prefix = prefix.substring(0, index)
|
||||||
|
prefix
|
||||||
|
}
|
||||||
|
private var jobIndex = 0
|
||||||
|
|
||||||
|
private def nextJobName = {
|
||||||
|
jobIndex += 1
|
||||||
|
jobNamePrefix + "-" + jobIndex
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,57 @@
|
||||||
|
package org.broadinstitute.sting.queue.engine
|
||||||
|
|
||||||
|
import collection.JavaConversions._
|
||||||
|
import edu.mit.broad.core.lsf.LocalLsfJob
|
||||||
|
import org.broadinstitute.sting.queue.function.DispatchFunction
|
||||||
|
import java.io.File
|
||||||
|
import java.util.ArrayList
|
||||||
|
import org.broadinstitute.sting.queue.util.Logging
|
||||||
|
|
||||||
|
trait LsfJobRunner extends DispatchJobRunner with Logging {
|
||||||
|
type DispatchJobType = LocalLsfJob
|
||||||
|
|
||||||
|
def dispatch(function: DispatchFunction, qGraph: QGraph) = {
|
||||||
|
var jobName = function.jobName
|
||||||
|
if (jobName == null)
|
||||||
|
jobName = newJobName
|
||||||
|
|
||||||
|
var jobOutputFile = function.jobOutputFile
|
||||||
|
if (jobOutputFile == null)
|
||||||
|
jobOutputFile = new File(jobName + ".out")
|
||||||
|
|
||||||
|
var jobErrorFile = function.jobErrorFile
|
||||||
|
if (jobErrorFile == null)
|
||||||
|
jobErrorFile = new File(jobName + ".err")
|
||||||
|
|
||||||
|
val job = new LocalLsfJob
|
||||||
|
job.setName(jobName)
|
||||||
|
job.setOutputFile(jobOutputFile)
|
||||||
|
job.setErrFile(jobErrorFile)
|
||||||
|
job.setWorkingDir(function.commandDirectory)
|
||||||
|
job.setProject(function.jobProject)
|
||||||
|
job.setQueue(function.jobQueue)
|
||||||
|
job.setCommand(function.commandLine)
|
||||||
|
|
||||||
|
var extraArgs = List("-r")
|
||||||
|
|
||||||
|
if (function.memoryLimit.isDefined)
|
||||||
|
extraArgs :::= List("-R", "rusage[mem=" + function.memoryLimit.get + "]")
|
||||||
|
|
||||||
|
val previous = previousJobs(function, qGraph)
|
||||||
|
if (previous.size > 0)
|
||||||
|
extraArgs :::= List("-w", dependencyExpression(previous))
|
||||||
|
|
||||||
|
job.setExtraBsubArgs(new ArrayList(extraArgs))
|
||||||
|
|
||||||
|
addJob(function, job)
|
||||||
|
|
||||||
|
logger.info(job.getBsubCommand.mkString(" "))
|
||||||
|
|
||||||
|
if (!qGraph.dryRun)
|
||||||
|
job.start
|
||||||
|
}
|
||||||
|
|
||||||
|
private def dependencyExpression(jobs: List[LocalLsfJob]) = {
|
||||||
|
jobs.toSet[LocalLsfJob].map(_.getName).mkString("ended(\"", "\") && ended(\"", "\")")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
package org.broadinstitute.sting.queue.engine
|
|
||||||
|
|
||||||
import graphing.{ExplicitJobGrapher, TreeJobGrapher}
|
|
||||||
import scheduling.ResourceNode
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Syntactic sugar for filling in a pipeline using a Scala script.
|
|
||||||
*/
|
|
||||||
object Pipeline {
|
|
||||||
def addRule(rule: (Any, Any), commandString: String): Unit = TreeJobGrapher.addRule(rule, commandString)
|
|
||||||
def run(args: Array[String], sources: Any, targets: Any): Unit = TreeJobGrapher.run(args, sources, targets)
|
|
||||||
|
|
||||||
def node() = ExplicitJobGrapher.node()
|
|
||||||
def addEdge(rule: (ResourceNode, ResourceNode), commandString: String): Unit = ExplicitJobGrapher.addEdge(rule, commandString)
|
|
||||||
def run(): Unit = ExplicitJobGrapher.run()
|
|
||||||
}
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
package org.broadinstitute.sting.queue.engine
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Defines a basic command to run
|
|
||||||
* TODO: Allow overriding arguments per command such as the job queue
|
|
||||||
*/
|
|
||||||
class QCommand(val commandString: String) extends QModelEdge {
|
|
||||||
override def toString = commandString
|
|
||||||
}
|
|
||||||
|
|
@ -1,43 +0,0 @@
|
||||||
package org.broadinstitute.sting.queue.engine
|
|
||||||
|
|
||||||
import org.apache.commons.lang.builder.{EqualsBuilder, HashCodeBuilder}
|
|
||||||
import java.io.File
|
|
||||||
import org.apache.commons.lang.StringUtils
|
|
||||||
import org.broadinstitute.sting.queue.QException
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents a file extension along with several tags.
|
|
||||||
* TODO: Use the tags to map rules between wildcards, ex: *.vcf -> *.eval
|
|
||||||
*/
|
|
||||||
class QFile(val fileType: String, val parts: String*) {
|
|
||||||
val extension = (List(parts:_*) ::: List(fileType)).mkString(".")
|
|
||||||
override def toString = extension
|
|
||||||
override def equals(p1: Any) = EqualsBuilder.reflectionEquals(this, p1)
|
|
||||||
override def hashCode = HashCodeBuilder.reflectionHashCode(this)
|
|
||||||
|
|
||||||
def matchesFile(path: String): Boolean = matchesFile(new File(path))
|
|
||||||
def matchesFile(file: File): Boolean = file.getAbsolutePath.endsWith(extension)
|
|
||||||
def baseName(path: String): String = baseName(new File(path))
|
|
||||||
def baseName(file: File): String = StringUtils.removeEnd(file.getAbsolutePath, extension)
|
|
||||||
def fullName(baseName: String) = baseName + extension
|
|
||||||
}
|
|
||||||
|
|
||||||
object QFile {
|
|
||||||
def getFiles(files: Any) : List[QFile] = {
|
|
||||||
files match {
|
|
||||||
case null => List.empty[QFile]
|
|
||||||
case Nil => List.empty[QFile]
|
|
||||||
case path: String => List(new QFile(path))
|
|
||||||
case file: QFile => List(file)
|
|
||||||
// Any List or Tuple add the members to this list
|
|
||||||
case product: Product => {
|
|
||||||
var list = List.empty[QFile]
|
|
||||||
for (fileList <- product.productIterator.toList.map(getFiles(_))) {
|
|
||||||
list :::= fileList
|
|
||||||
}
|
|
||||||
list
|
|
||||||
}
|
|
||||||
case x => throw new QException("Unknown file type: " + x)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,71 @@
|
||||||
|
package org.broadinstitute.sting.queue.engine
|
||||||
|
|
||||||
|
import org.jgrapht.graph.SimpleDirectedGraph
|
||||||
|
import scala.collection.JavaConversions
|
||||||
|
import scala.collection.immutable.ListMap
|
||||||
|
import org.broadinstitute.sting.queue.util.Logging
|
||||||
|
import org.broadinstitute.sting.queue.function.{MappingFunction, CommandLineFunction, QFunction}
|
||||||
|
|
||||||
|
class QGraph extends Logging {
|
||||||
|
var dryRun = true
|
||||||
|
var bsubAllJobs = false
|
||||||
|
val jobGraph = new SimpleDirectedGraph[QNode, QFunction](classOf[QFunction])
|
||||||
|
def numJobs = JavaConversions.asSet(jobGraph.edgeSet).filter(_.isInstanceOf[CommandLineFunction]).size
|
||||||
|
|
||||||
|
def add(command: CommandLineFunction) {
|
||||||
|
add(command, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Looks through functions with multiple inputs and outputs and adds mapping functions for single inputs and outputs.
|
||||||
|
*/
|
||||||
|
def fillIn = {
|
||||||
|
// clone since edgeSet is backed by the graph
|
||||||
|
for (function <- JavaConversions.asSet(jobGraph.edgeSet).clone) {
|
||||||
|
val inputs = function.inputs
|
||||||
|
val outputs = function.outputs
|
||||||
|
|
||||||
|
if (inputs.size > 1)
|
||||||
|
for ((name, input) <- inputs)
|
||||||
|
addNullEdge(ListMap(name -> input), inputs)
|
||||||
|
|
||||||
|
if (outputs.size > 1)
|
||||||
|
for ((name, output) <- outputs)
|
||||||
|
addNullEdge(outputs, ListMap(name -> output))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def run = {
|
||||||
|
var isReady = true
|
||||||
|
for (function <- JavaConversions.asSet(jobGraph.edgeSet)) {
|
||||||
|
val missingValues = function.missingValues
|
||||||
|
if (missingValues.size > 0) {
|
||||||
|
isReady = false
|
||||||
|
logger.error(function match {
|
||||||
|
case cmd: CommandLineFunction => "Missing values for function: %s".format(cmd.commandLine)
|
||||||
|
case x => "Missing values:"
|
||||||
|
})
|
||||||
|
for (missing <- missingValues) {
|
||||||
|
logger.error(" " + missing)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isReady || this.dryRun)
|
||||||
|
(new TopologicalJobScheduler(this) with LsfJobRunner).runJobs
|
||||||
|
}
|
||||||
|
|
||||||
|
private def add(f: QFunction, replace: Boolean) {
|
||||||
|
val inputs = QNode(f.inputs.values.filter(_ != null).toSet)
|
||||||
|
val outputs = QNode(f.outputs.values.filter(_ != null).toSet)
|
||||||
|
jobGraph.addVertex(inputs)
|
||||||
|
jobGraph.addVertex(outputs)
|
||||||
|
if (replace)
|
||||||
|
jobGraph.removeAllEdges(inputs, outputs)
|
||||||
|
jobGraph.addEdge(inputs, outputs, f)
|
||||||
|
}
|
||||||
|
|
||||||
|
private def addNullEdge(input: ListMap[String, Any], output: ListMap[String, Any]) = {
|
||||||
|
add(new MappingFunction(input, output), false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
package org.broadinstitute.sting.queue.engine
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Used for modeling before an edge gets
|
|
||||||
* replicated into an actual ResourceEdge
|
|
||||||
*/
|
|
||||||
class QModelEdge
|
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
package org.broadinstitute.sting.queue.engine
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a state between QFunctions the directed acyclic QGraph
|
||||||
|
*/
|
||||||
|
case class QNode (private val items: Set[Any])
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
package org.broadinstitute.sting.queue.engine
|
|
||||||
|
|
||||||
class QRule (val inputs: List[QFile], val outputs: List[QFile], var command: QCommand)
|
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
package org.broadinstitute.sting.queue.engine
|
||||||
|
|
||||||
|
import org.jgrapht.traverse.TopologicalOrderIterator
|
||||||
|
import org.jgrapht.event.{EdgeTraversalEvent, TraversalListenerAdapter}
|
||||||
|
import collection.JavaConversions._
|
||||||
|
import org.broadinstitute.sting.queue.util.Logging
|
||||||
|
import org.broadinstitute.sting.queue.function.{MappingFunction, CommandLineFunction, DispatchFunction, QFunction}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loops over the job graph running jobs as the edges are traversed
|
||||||
|
*/
|
||||||
|
abstract class TopologicalJobScheduler(private val qGraph: QGraph)
|
||||||
|
extends CommandLineRunner with DispatchJobRunner with Logging {
|
||||||
|
|
||||||
|
protected val iterator = new TopologicalOrderIterator(this.qGraph.jobGraph)
|
||||||
|
|
||||||
|
iterator.addTraversalListener(new TraversalListenerAdapter[QNode, QFunction] {
|
||||||
|
override def edgeTraversed(event: EdgeTraversalEvent[QNode, QFunction]) = event.getEdge match {
|
||||||
|
case f: DispatchFunction if (TopologicalJobScheduler.this.qGraph.bsubAllJobs) => dispatch(f, qGraph)
|
||||||
|
case f: CommandLineFunction => run(f, qGraph)
|
||||||
|
case f: MappingFunction => /* do nothing for mapping functions */
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
def runJobs = {
|
||||||
|
logger.info("Number of jobs: %s".format(this.qGraph.numJobs))
|
||||||
|
for (target <- iterator) {
|
||||||
|
// Do nothing for now, let event handler respond
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
package org.broadinstitute.sting.queue.engine.graphing
|
|
||||||
|
|
||||||
import org.broadinstitute.sting.queue.engine.scheduling.{ResourceNode, ExecEdge}
|
|
||||||
import org.broadinstitute.sting.queue.engine.QCommand
|
|
||||||
|
|
||||||
class ExplicitJobGrapher extends JobGrapher
|
|
||||||
|
|
||||||
object ExplicitJobGrapher {
|
|
||||||
private val grapher = new ExplicitJobGrapher
|
|
||||||
|
|
||||||
def node() = new ResourceNode
|
|
||||||
|
|
||||||
def addEdge(rule: (ResourceNode, ResourceNode), commandString: String): Unit = {
|
|
||||||
addEdge(rule._1, rule._2, new QCommand(commandString))
|
|
||||||
}
|
|
||||||
|
|
||||||
private def addEdge(source: ResourceNode, target: ResourceNode, command: QCommand) = {
|
|
||||||
val resourceEdge = new ExecEdge(command.commandString)
|
|
||||||
grapher.jobGraph.addVertex(source)
|
|
||||||
grapher.jobGraph.addVertex(target)
|
|
||||||
grapher.jobGraph.addEdge(source, target, resourceEdge)
|
|
||||||
}
|
|
||||||
|
|
||||||
def run() = {
|
|
||||||
grapher.run()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,31 +0,0 @@
|
||||||
package org.broadinstitute.sting.queue.engine.graphing
|
|
||||||
|
|
||||||
import org.broadinstitute.sting.queue.QArguments
|
|
||||||
import org.broadinstitute.sting.queue.engine.scheduling._
|
|
||||||
import org.broadinstitute.sting.queue.util.Logging
|
|
||||||
import org.jgrapht.graph.SimpleDirectedGraph
|
|
||||||
|
|
||||||
abstract class JobGrapher() extends Logging {
|
|
||||||
/**
|
|
||||||
* Jobs to be run.
|
|
||||||
* Can be populated adhoc or during createJobGraph()
|
|
||||||
*/
|
|
||||||
protected val jobGraph = new SimpleDirectedGraph[ResourceNode, ResourceEdge](classOf[ResourceEdge])
|
|
||||||
|
|
||||||
var qArgs: QArguments = _
|
|
||||||
|
|
||||||
def run() = {
|
|
||||||
createJobGraph()
|
|
||||||
val scheduler = createScheduler()
|
|
||||||
scheduler.runJobs
|
|
||||||
}
|
|
||||||
|
|
||||||
protected def createJobGraph() = {}
|
|
||||||
|
|
||||||
private def createScheduler() : JobScheduler = {
|
|
||||||
qArgs.useBsub match {
|
|
||||||
case false => new SimpleJobScheduler(jobGraph, qArgs)
|
|
||||||
case true => new DispatchJobScheduler(jobGraph, qArgs)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,143 +0,0 @@
|
||||||
package org.broadinstitute.sting.queue.engine.graphing
|
|
||||||
|
|
||||||
import org.jgrapht.graph.SimpleDirectedGraph
|
|
||||||
import org.jgrapht.alg.BellmanFordShortestPath
|
|
||||||
import org.broadinstitute.sting.queue.{QArguments, QException}
|
|
||||||
import collection.mutable.ListBuffer
|
|
||||||
import collection.JavaConversions._
|
|
||||||
import org.broadinstitute.sting.queue.engine.scheduling.{ResourceEdge, ExecEdge, MapResourceNode}
|
|
||||||
import org.broadinstitute.sting.queue.engine.{QModelEdge, QCommand, QFile, QRule}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts a set of rules provided by the user and a list of files into a graph of jobs to run
|
|
||||||
*/
|
|
||||||
class TreeJobGrapher extends JobGrapher {
|
|
||||||
private val modelGraph = new SimpleDirectedGraph[List[QFile], QModelEdge](classOf[QModelEdge])
|
|
||||||
|
|
||||||
private val rules = new ListBuffer[QRule]
|
|
||||||
private var sourceFiles = List.empty[QFile]
|
|
||||||
private var targetFiles = List.empty[QFile]
|
|
||||||
|
|
||||||
// Used to tag a model edge for Element <-> List
|
|
||||||
private class QCollectionEdge extends QModelEdge
|
|
||||||
|
|
||||||
override protected def createJobGraph() = {
|
|
||||||
createModelGraph()
|
|
||||||
|
|
||||||
var missingPaths = List.empty[Tuple2[QFile,QFile]]
|
|
||||||
for (sourceFile <- sourceFiles) {
|
|
||||||
for (targetFile <- targetFiles) {
|
|
||||||
var shortestPath = BellmanFordShortestPath.findPathBetween(modelGraph, List(sourceFile), List(targetFile))
|
|
||||||
if (shortestPath == null)
|
|
||||||
missingPaths = missingPaths ::: List((sourceFile, targetFile))
|
|
||||||
else
|
|
||||||
addPaths(shortestPath, qArgs)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for ((sourceFile, targetFile) <- missingPaths) {
|
|
||||||
logger.error(String.format("No command path found between %s --> %s", sourceFile, targetFile))
|
|
||||||
}
|
|
||||||
|
|
||||||
if (missingPaths.size > 0)
|
|
||||||
throw new QException("Not all inputs and outputs found in the pipeline graph")
|
|
||||||
}
|
|
||||||
|
|
||||||
private def createModelGraph() = {
|
|
||||||
|
|
||||||
// Look for rules with more than one input or output and add
|
|
||||||
// internal dependencies between the elements and the list
|
|
||||||
for (rule <- rules) {
|
|
||||||
if (rule.inputs.size > 1) {
|
|
||||||
for (input <- rule.inputs) {
|
|
||||||
modelGraph.addVertex(List(input))
|
|
||||||
modelGraph.addVertex(rule.inputs)
|
|
||||||
modelGraph.addEdge(List(input), rule.inputs, new QCollectionEdge)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (rule.outputs.size > 1) {
|
|
||||||
for (output <- rule.outputs) {
|
|
||||||
modelGraph.addVertex(rule.outputs)
|
|
||||||
modelGraph.addVertex(List(output))
|
|
||||||
modelGraph.addEdge(rule.outputs, List(output), new QCollectionEdge)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add the explicit rules
|
|
||||||
for (rule <- rules) {
|
|
||||||
modelGraph.addVertex(rule.inputs)
|
|
||||||
modelGraph.addVertex(rule.outputs)
|
|
||||||
modelGraph.addEdge(rule.inputs, rule.outputs, rule.command)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private def addPaths(shortestPath: java.util.List[QModelEdge], qArgs: QArguments) {
|
|
||||||
for (inputFile <- qArgs.inputPaths.map(_.getAbsolutePath)) {
|
|
||||||
val source = modelGraph.getEdgeSource(shortestPath.head).head
|
|
||||||
if (source.matchesFile(inputFile)) {
|
|
||||||
val baseName = source.baseName(inputFile)
|
|
||||||
val target = modelGraph.getEdgeTarget(shortestPath.last).head
|
|
||||||
addPathsToTarget(baseName, List(target))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private def addPathsToTarget(baseName: String, targets: List[QFile]) : Unit = {
|
|
||||||
for (command <- modelGraph.incomingEdgesOf(targets)) {
|
|
||||||
val sources = modelGraph.getEdgeSource(command)
|
|
||||||
addJobGraphEdge(baseName, sources, targets, command)
|
|
||||||
addPathsToTarget(baseName, sources)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private def addJobGraphEdge(baseName: String, sources: List[QFile], targets: List[QFile], command: QModelEdge) {
|
|
||||||
val resourceSource = new MapResourceNode(mapFiles(baseName, sources))
|
|
||||||
val resourceTarget = new MapResourceNode(mapFiles(baseName, targets))
|
|
||||||
val resourceEdge = command match {
|
|
||||||
case qCommand: QCommand => new ExecEdge(qCommand.commandString)
|
|
||||||
case qTransition: QCollectionEdge => new ResourceEdge
|
|
||||||
}
|
|
||||||
jobGraph.addVertex(resourceSource)
|
|
||||||
jobGraph.addVertex(resourceTarget)
|
|
||||||
jobGraph.addEdge(resourceSource, resourceTarget, resourceEdge)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a mapping of the files based on the baseName.
|
|
||||||
* key: file extension
|
|
||||||
* value: full name
|
|
||||||
* Used by the JobScheduler to lookup values as the commands are expanded at exec time.
|
|
||||||
*/
|
|
||||||
private def mapFiles(baseName: String, files: List[QFile]) : Map[String, String] = {
|
|
||||||
Map(files.map(file => (file.extension, file.fullName(baseName))):_*)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Syntactic sugar for filling in a pipeline using a Scala script.
|
|
||||||
*/
|
|
||||||
object TreeJobGrapher {
|
|
||||||
private val grapher = new TreeJobGrapher
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sugar that allows addRule( inputs -> outputs, command )
|
|
||||||
*/
|
|
||||||
def addRule(rule: (Any, Any), commandString: String): Unit = {
|
|
||||||
val inputs = QFile.getFiles(rule._1)
|
|
||||||
val outputs = QFile.getFiles(rule._2)
|
|
||||||
val command = new QCommand(commandString)
|
|
||||||
addRule(inputs, outputs, command)
|
|
||||||
}
|
|
||||||
|
|
||||||
private def addRule(inputs: List[QFile], outputs: List[QFile], command: QCommand): Unit = {
|
|
||||||
grapher.rules += new QRule(inputs, outputs, command)
|
|
||||||
}
|
|
||||||
|
|
||||||
def run(args: Array[String], sources: Any, targets: Any) = {
|
|
||||||
grapher.qArgs = new QArguments(args)
|
|
||||||
grapher.sourceFiles = QFile.getFiles(sources)
|
|
||||||
grapher.targetFiles = QFile.getFiles(targets)
|
|
||||||
grapher.run()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,79 +0,0 @@
|
||||||
package org.broadinstitute.sting.queue.engine.scheduling
|
|
||||||
|
|
||||||
import org.jgrapht.DirectedGraph
|
|
||||||
import edu.mit.broad.core.lsf.LocalLsfJob
|
|
||||||
import collection.JavaConversions._
|
|
||||||
import management.ManagementFactory
|
|
||||||
import java.io.File
|
|
||||||
import java.util.ArrayList
|
|
||||||
import org.broadinstitute.sting.queue.QArguments
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Dispatches jobs to LSF and then returns.
|
|
||||||
*/
|
|
||||||
class DispatchJobScheduler(jobGraph: DirectedGraph[ResourceNode, ResourceEdge], qArgs: QArguments)
|
|
||||||
extends TopologicalJobScheduler(jobGraph, qArgs) {
|
|
||||||
private var lsfJobs = Map.empty[ExecEdge, LocalLsfJob]
|
|
||||||
private var lsfJobIndex = 0
|
|
||||||
private val jvmName = ManagementFactory.getRuntimeMXBean.getName
|
|
||||||
private val jobNamePrefix = "Q-" + jvmName
|
|
||||||
|
|
||||||
def processExec(exec: ExecEdge) = {
|
|
||||||
lsfJobIndex += 1
|
|
||||||
val job = new LocalLsfJob
|
|
||||||
val jobName = jobNamePrefix + "-" + lsfJobIndex
|
|
||||||
val outputFile = jobName + ".out"
|
|
||||||
val errorFile = jobName + ".err"
|
|
||||||
val workingDir = lookup(exec, "jobWorkingDir", ".")
|
|
||||||
val lsfProject = lookup(exec, "jobProject", "Queue")
|
|
||||||
val queue = lookup(exec, "jobQueue", "broad")
|
|
||||||
val memory = lookup(exec, "jobMemory", "2")
|
|
||||||
|
|
||||||
var extraArgs = List("-r", "-R", "rusage[mem=" + memory + "]")
|
|
||||||
|
|
||||||
val sourceJobs = sourceLsfJobs(exec)
|
|
||||||
if (sourceJobs.size > 0) {
|
|
||||||
extraArgs :::= List("-w", dependencyExpression(sourceJobs))
|
|
||||||
}
|
|
||||||
job.setName(jobName)
|
|
||||||
job.setExtraBsubArgs(new ArrayList(extraArgs))
|
|
||||||
job.setProject(lsfProject)
|
|
||||||
job.setWorkingDir(new File(workingDir))
|
|
||||||
job.setProject(lsfProject)
|
|
||||||
job.setCommand(exec.commandString)
|
|
||||||
job.setOutputFile(new File(workingDir, outputFile))
|
|
||||||
job.setErrFile(new File(workingDir, errorFile))
|
|
||||||
job.setQueue(queue)
|
|
||||||
|
|
||||||
lsfJobs = lsfJobs.updated(exec, job)
|
|
||||||
|
|
||||||
logger.info(job.getBsubCommand.mkString(" "))
|
|
||||||
|
|
||||||
if (!qArgs.dryRun)
|
|
||||||
job.start
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Walks up the graph looking for the previous LsfJobs for this node
|
|
||||||
*/
|
|
||||||
private def sourceLsfJobs(edge: ResourceEdge) : List[LocalLsfJob] = {
|
|
||||||
var sourceJobs = List.empty[LocalLsfJob]
|
|
||||||
|
|
||||||
val source = this.jobGraph.getEdgeSource(edge)
|
|
||||||
for (incomingEdge <- this.jobGraph.incomingEdgesOf(source)) {
|
|
||||||
incomingEdge match {
|
|
||||||
|
|
||||||
// Stop recursing when we find a job along this edge and return it's job id
|
|
||||||
case exec: ExecEdge => sourceJobs :::= List(lsfJobs(exec))
|
|
||||||
|
|
||||||
// For any other type of edge find the LSF jobs preceeding the edge
|
|
||||||
case resourceEdge: ResourceEdge => sourceJobs :::= sourceLsfJobs(resourceEdge)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sourceJobs
|
|
||||||
}
|
|
||||||
|
|
||||||
private def dependencyExpression(jobs: List[LocalLsfJob]) = {
|
|
||||||
jobs.toSet[LocalLsfJob].map(_.getName).mkString("ended(\"", "\") && ended(\"", "\")")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
package org.broadinstitute.sting.queue.engine.scheduling
|
|
||||||
|
|
||||||
import org.apache.commons.lang.text.{StrLookup, StrSubstitutor}
|
|
||||||
import java.lang.String
|
|
||||||
|
|
||||||
class ExecEdge(private val templateCommandString: String)
|
|
||||||
extends ResourceEdge {
|
|
||||||
private var convertedCommandString: String = _
|
|
||||||
def commandString = convertedCommandString
|
|
||||||
|
|
||||||
override def traverse(graph: JobScheduler) = {
|
|
||||||
// Lookup any variable using the target node, or any of it's input nodes.
|
|
||||||
val sub = new StrSubstitutor(new StrLookup { def lookup(key: String) = graph.lookup(ExecEdge.this, key, null) })
|
|
||||||
convertedCommandString = sub.replace(templateCommandString)
|
|
||||||
graph.processExec(this)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,65 +0,0 @@
|
||||||
package org.broadinstitute.sting.queue.engine.scheduling
|
|
||||||
|
|
||||||
import org.jgrapht.DirectedGraph
|
|
||||||
import org.broadinstitute.sting.queue.util.Logging
|
|
||||||
import collection.JavaConversions._
|
|
||||||
import org.broadinstitute.sting.queue.QArguments
|
|
||||||
|
|
||||||
abstract class JobScheduler(protected val jobGraph: DirectedGraph[ResourceNode, ResourceEdge],
|
|
||||||
protected val qArgs: QArguments) extends Logging {
|
|
||||||
|
|
||||||
private var missingKeys = Set.empty[String]
|
|
||||||
|
|
||||||
def runJobs
|
|
||||||
def numJobs = jobGraph.edgeSet.size
|
|
||||||
|
|
||||||
def processExec(exec: ExecEdge) : Unit
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Emulates storing of properties per node by looking up values on
|
|
||||||
* the current edge/target-node or any preceding nodes in the graph.
|
|
||||||
*/
|
|
||||||
def lookup(edge: ResourceEdge, key: String, default: String) : String = {
|
|
||||||
val value = lookupRecursive(edge, key) match {
|
|
||||||
case Some(value) => value
|
|
||||||
case None => qArgs.argMap.getOrElse(key, default)
|
|
||||||
}
|
|
||||||
if (value == null)
|
|
||||||
missingKeys = missingKeys ++ Set(key)
|
|
||||||
value
|
|
||||||
}
|
|
||||||
|
|
||||||
protected def logMissingKeys = {
|
|
||||||
if (qArgs.dryRun && !missingKeys.isEmpty) {
|
|
||||||
logger.warn("Missing keys:")
|
|
||||||
for (key <- missingKeys)
|
|
||||||
logger.warn(" ${" + key + "}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private def lookupRecursive(edge: ResourceEdge, key: String) : Option[String] = {
|
|
||||||
var value = edge.lookup(key)
|
|
||||||
if (value.isDefined)
|
|
||||||
return value
|
|
||||||
|
|
||||||
value = this.jobGraph.getEdgeTarget(edge).lookup(key)
|
|
||||||
if (value.isDefined)
|
|
||||||
return value
|
|
||||||
|
|
||||||
return lookupRecursive(this.jobGraph.getEdgeSource(edge), key)
|
|
||||||
}
|
|
||||||
|
|
||||||
private def lookupRecursive(node: ResourceNode, key: String) : Option[String] = {
|
|
||||||
var value = node.lookup(key)
|
|
||||||
if (value.isDefined)
|
|
||||||
return value
|
|
||||||
|
|
||||||
for (edge <- this.jobGraph.incomingEdgesOf(node)) {
|
|
||||||
value = lookupRecursive(edge, key)
|
|
||||||
if (value.isDefined)
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
|
|
||||||
return None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
package org.broadinstitute.sting.queue.engine.scheduling
|
|
||||||
|
|
||||||
class MapResourceNode (private val resources: Map[String,String]) extends ResourceNode {
|
|
||||||
override def lookup(key: String) : Option[String] = this.resources.get(key)
|
|
||||||
}
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
package org.broadinstitute.sting.queue.engine.scheduling
|
|
||||||
|
|
||||||
class ResourceEdge {
|
|
||||||
def traverse(graph: JobScheduler): Unit = {}
|
|
||||||
def lookup(key: String) : Option[String] = None
|
|
||||||
}
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
package org.broadinstitute.sting.queue.engine.scheduling
|
|
||||||
|
|
||||||
import org.apache.commons.lang.builder.{HashCodeBuilder, EqualsBuilder}
|
|
||||||
|
|
||||||
class ResourceNode() {
|
|
||||||
override def equals(p1: Any) = EqualsBuilder.reflectionEquals(this, p1)
|
|
||||||
override def hashCode = HashCodeBuilder.reflectionHashCode(this)
|
|
||||||
def lookup(key: String) : Option[String] = None
|
|
||||||
}
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
package org.broadinstitute.sting.queue.engine.scheduling
|
|
||||||
|
|
||||||
import org.broadinstitute.sting.queue.util.ProcessUtils
|
|
||||||
import org.broadinstitute.sting.queue.QArguments
|
|
||||||
import org.jgrapht.DirectedGraph
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Runs jobs one at a time locally
|
|
||||||
*/
|
|
||||||
class SimpleJobScheduler(jobGraph: DirectedGraph[ResourceNode, ResourceEdge], qArgs: QArguments)
|
|
||||||
extends TopologicalJobScheduler(jobGraph, qArgs) {
|
|
||||||
def processExec(exec: ExecEdge) = {
|
|
||||||
var commandString = exec.commandString
|
|
||||||
logger.info(commandString)
|
|
||||||
|
|
||||||
if (!qArgs.dryRun)
|
|
||||||
ProcessUtils.runCommandAndWait(commandString)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
package org.broadinstitute.sting.queue.engine.scheduling
|
|
||||||
|
|
||||||
import org.jgrapht.DirectedGraph
|
|
||||||
import org.jgrapht.traverse.TopologicalOrderIterator
|
|
||||||
import org.jgrapht.event.{EdgeTraversalEvent, TraversalListenerAdapter}
|
|
||||||
import collection.JavaConversions._
|
|
||||||
import org.broadinstitute.sting.queue.QArguments
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Loops over the job graph running jobs as the edges are traversed
|
|
||||||
*/
|
|
||||||
abstract class TopologicalJobScheduler(jobGraph: DirectedGraph[ResourceNode, ResourceEdge], qArgs: QArguments)
|
|
||||||
extends JobScheduler(jobGraph, qArgs) {
|
|
||||||
|
|
||||||
protected val iterator = new TopologicalOrderIterator(this.jobGraph)
|
|
||||||
|
|
||||||
iterator.addTraversalListener(new TraversalListenerAdapter[ResourceNode, ResourceEdge] {
|
|
||||||
override def edgeTraversed(event: EdgeTraversalEvent[ResourceNode, ResourceEdge]) =
|
|
||||||
event.getEdge.traverse(TopologicalJobScheduler.this)
|
|
||||||
})
|
|
||||||
|
|
||||||
override def runJobs = {
|
|
||||||
logger.info(String.format("Running %s jobs.", this.numJobs.toString))
|
|
||||||
for (target <- iterator) {
|
|
||||||
// Do nothing for now, let event handler respond
|
|
||||||
}
|
|
||||||
logMissingKeys
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,92 @@
|
||||||
|
package org.broadinstitute.sting.queue.function
|
||||||
|
|
||||||
|
import java.io.File
|
||||||
|
import org.broadinstitute.sting.queue.util._
|
||||||
|
import org.broadinstitute.sting.queue.engine.{CommandLineRunner, QGraph}
|
||||||
|
|
||||||
|
trait CommandLineFunction extends QFunction with DispatchFunction {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The command line to run locally or via grid computing.
|
||||||
|
*/
|
||||||
|
def commandLine: String
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The directory where the command should run.
|
||||||
|
*/
|
||||||
|
@Internal
|
||||||
|
var commandDirectory: File = new File(".")
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Repeats parameters with a prefix/suffix if they are set otherwise returns "".
|
||||||
|
* Skips null, Nil, None. Unwraps Some(x) to x. Everything else is called with x.toString.
|
||||||
|
*/
|
||||||
|
protected def repeat(prefix: String, params: Seq[_], suffix: String = "", separator: String = "") =
|
||||||
|
params.filter(param => hasValue(param)).map(param => prefix + toValue(param) + suffix).mkString(separator)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns parameter with a prefix/suffix if it is set otherwise returns "".
|
||||||
|
* Does not output null, Nil, None. Unwraps Some(x) to x. Everything else is called with x.toString.
|
||||||
|
*/
|
||||||
|
protected def optional(prefix: String, param: Any, suffix: String = "") =
|
||||||
|
if (hasValue(param)) prefix + toValue(param) + suffix else ""
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets a field value using the name of the field.
|
||||||
|
* Field must be annotated with @Input, @Output, or @Internal
|
||||||
|
* @returns true if the value was found and set
|
||||||
|
*/
|
||||||
|
def setValue(name: String, value: String) = {
|
||||||
|
ReflectionUtils.getField(this, name) match {
|
||||||
|
case Some(field) =>
|
||||||
|
val isInput = ReflectionUtils.hasAnnotation(field, classOf[Input])
|
||||||
|
val isOutput = ReflectionUtils.hasAnnotation(field, classOf[Output])
|
||||||
|
val isInternal = ReflectionUtils.hasAnnotation(field, classOf[Internal])
|
||||||
|
if (isInput || isOutput || isInternal) {
|
||||||
|
ReflectionUtils.setValue(this, field, value)
|
||||||
|
}
|
||||||
|
true
|
||||||
|
case None => false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private lazy val fields = ReflectionUtils.getAllFields(this.getClass)
|
||||||
|
private def internals = ReflectionUtils.getFieldsAnnotatedWith(this, fields, classOf[Internal])
|
||||||
|
def inputs = ReflectionUtils.getFieldsAnnotatedWith(this, fields, classOf[Input])
|
||||||
|
def outputs = ReflectionUtils.getFieldsAnnotatedWith(this, fields, classOf[Output])
|
||||||
|
|
||||||
|
override def missingValues = {
|
||||||
|
val missingInputs = missingFields(inputs)
|
||||||
|
val missingOutputs = missingFields(outputs)
|
||||||
|
missingInputs | missingOutputs
|
||||||
|
}
|
||||||
|
|
||||||
|
private def missingFields(fields: Map[String, Any]) = {
|
||||||
|
var missing = Set.empty[String]
|
||||||
|
for ((name, value) <- fields) {
|
||||||
|
val isOptional = ReflectionUtils.getField(this, name) match {
|
||||||
|
case Some(field) => ReflectionUtils.hasAnnotation(field, classOf[Optional])
|
||||||
|
case None => false
|
||||||
|
}
|
||||||
|
if (!isOptional)
|
||||||
|
if (!hasValue(value))
|
||||||
|
missing += name
|
||||||
|
}
|
||||||
|
missing
|
||||||
|
}
|
||||||
|
|
||||||
|
private def hasValue(param: Any) = param match {
|
||||||
|
case null => false
|
||||||
|
case Nil => false
|
||||||
|
case None => false
|
||||||
|
case _ => true
|
||||||
|
}
|
||||||
|
|
||||||
|
private def toValue(param: Any): String = param match {
|
||||||
|
case null => ""
|
||||||
|
case Nil => ""
|
||||||
|
case None => ""
|
||||||
|
case Some(x) => x.toString
|
||||||
|
case x => x.toString
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
package org.broadinstitute.sting.queue.function
|
||||||
|
|
||||||
|
import java.io.File
|
||||||
|
import org.broadinstitute.sting.queue.util.Internal
|
||||||
|
|
||||||
|
trait DispatchFunction extends QFunction with MemoryLimitedFunction {
|
||||||
|
def commandLine: String
|
||||||
|
var commandDirectory: File
|
||||||
|
|
||||||
|
var jobName: String = _
|
||||||
|
var jobOutputFile: File = _
|
||||||
|
var jobErrorFile: File = _
|
||||||
|
|
||||||
|
@Internal
|
||||||
|
var jobProject = "Queue"
|
||||||
|
|
||||||
|
@Internal
|
||||||
|
var jobQueue = "broad"
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
package org.broadinstitute.sting.queue.function
|
||||||
|
|
||||||
|
trait IntervalFunction {
|
||||||
|
type Intervals = String
|
||||||
|
var intervals: Intervals
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
package org.broadinstitute.sting.queue.function
|
||||||
|
|
||||||
|
import org.broadinstitute.sting.queue.engine.QGraph
|
||||||
|
import scala.collection.immutable.ListMap
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility class to map a set of inputs to set of outputs.
|
||||||
|
* The QGraph uses this function internally to return
|
||||||
|
*/
|
||||||
|
class MappingFunction(private val in: ListMap[String, Any], private val out: ListMap[String, Any]) extends QFunction {
|
||||||
|
def inputs = in
|
||||||
|
def outputs = out
|
||||||
|
def run(qGraph: QGraph) = null
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
package org.broadinstitute.sting.queue.function
|
||||||
|
|
||||||
|
import org.broadinstitute.sting.queue.util.{Input, Optional, ClassType}
|
||||||
|
|
||||||
|
trait MemoryLimitedFunction {
|
||||||
|
@Input
|
||||||
|
@Optional
|
||||||
|
@ClassType(classOf[Int])
|
||||||
|
var memoryLimit: Option[Int] = None
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
package org.broadinstitute.sting.queue.function
|
||||||
|
|
||||||
|
import org.broadinstitute.sting.queue.engine.QGraph
|
||||||
|
import scala.collection.immutable.ListMap
|
||||||
|
|
||||||
|
trait QFunction {
|
||||||
|
def inputs: ListMap[String, Any]
|
||||||
|
def outputs: ListMap[String, Any]
|
||||||
|
def missingValues = Set.empty[String]
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
package org.broadinstitute.sting.queue.function.gatk
|
||||||
|
|
||||||
|
import java.io.File
|
||||||
|
import org.broadinstitute.sting.queue.util.{ClassType, Input, Optional}
|
||||||
|
import org.broadinstitute.sting.queue.function.{MemoryLimitedFunction, IntervalFunction, CommandLineFunction}
|
||||||
|
|
||||||
|
trait GatkFunction extends CommandLineFunction with MemoryLimitedFunction with IntervalFunction {
|
||||||
|
@Input
|
||||||
|
@Optional
|
||||||
|
var javaTmpDir: String = _
|
||||||
|
|
||||||
|
@Input
|
||||||
|
var gatkJar: String = _
|
||||||
|
|
||||||
|
@Input
|
||||||
|
var referenceFile: String = _
|
||||||
|
|
||||||
|
@Input
|
||||||
|
@Optional
|
||||||
|
@ClassType(classOf[File])
|
||||||
|
var bamFiles: List[File] = Nil
|
||||||
|
|
||||||
|
@Input
|
||||||
|
@Optional
|
||||||
|
var dbsnp: File = _
|
||||||
|
|
||||||
|
@Input
|
||||||
|
@Optional
|
||||||
|
var intervals: Intervals = new Intervals("all")
|
||||||
|
|
||||||
|
protected def gatkCommandLine(walker: String) =
|
||||||
|
"java%s%s -jar %s -T %s -R %s%s%s "
|
||||||
|
.format(optional(" -Xmx", memoryLimit, "g"), optional(" -Djava.io.tmpdir=", javaTmpDir),
|
||||||
|
gatkJar, walker, referenceFile, repeat(" -I ", bamFiles), optional(" -D ", dbsnp), optional(" -L ", intervals))
|
||||||
|
}
|
||||||
|
|
@ -7,7 +7,7 @@ import org.apache.log4j._
|
||||||
*/
|
*/
|
||||||
trait Logging {
|
trait Logging {
|
||||||
private val className = this.getClass.getName
|
private val className = this.getClass.getName
|
||||||
lazy val logger = configuredLogger
|
protected lazy val logger = configuredLogger
|
||||||
|
|
||||||
def configuredLogger = {
|
def configuredLogger = {
|
||||||
Logging.configureLogging
|
Logging.configureLogging
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ package org.broadinstitute.sting.queue.util
|
||||||
import org.broadinstitute.sting.utils.text.XReadLines
|
import org.broadinstitute.sting.utils.text.XReadLines
|
||||||
import collection.mutable.ListBuffer
|
import collection.mutable.ListBuffer
|
||||||
import collection.JavaConversions._
|
import collection.JavaConversions._
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
object ProcessUtils extends Logging {
|
object ProcessUtils extends Logging {
|
||||||
|
|
||||||
|
|
@ -15,10 +16,10 @@ object ProcessUtils extends Logging {
|
||||||
|
|
||||||
val running = new ListBuffer[Process]()
|
val running = new ListBuffer[Process]()
|
||||||
|
|
||||||
def runCommandAndWait(command: String) = {
|
def runCommandAndWait(command: String, directory: File) = {
|
||||||
logger.debug("Running command: " + command)
|
logger.debug("Running command: " + command)
|
||||||
|
|
||||||
var builder = new ProcessBuilder("sh", "-c", command)
|
var builder = new ProcessBuilder("sh", "-c", command).directory(directory)
|
||||||
|
|
||||||
var process = builder.start
|
var process = builder.start
|
||||||
running += process
|
running += process
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,96 @@
|
||||||
|
package org.broadinstitute.sting.queue.util
|
||||||
|
|
||||||
|
import java.lang.reflect.Field
|
||||||
|
import org.broadinstitute.sting.queue.QException
|
||||||
|
import java.lang.annotation.Annotation
|
||||||
|
import scala.concurrent.JavaConversions
|
||||||
|
import scala.concurrent.JavaConversions._
|
||||||
|
import scala.collection.immutable.ListMap
|
||||||
|
|
||||||
|
object ReflectionUtils {
|
||||||
|
def getField(obj: AnyRef, name: String) = getAllFields(obj.getClass).find(_.getName == name)
|
||||||
|
|
||||||
|
def hasAnnotation(field: Field, annotation: Class[_ <: Annotation]) = field.getAnnotation(annotation) != null
|
||||||
|
|
||||||
|
def getFieldsAnnotatedWith(obj: AnyRef, fields: List[Field], annotation: Class[_ <: Annotation]) =
|
||||||
|
ListMap(fields.filter(field => hasAnnotation(field, annotation))
|
||||||
|
.map(field => (field.getName -> fieldGetter(field).invoke(obj))) :_*)
|
||||||
|
|
||||||
|
def getAllTypes(clazz: Class[_]) = {
|
||||||
|
var types = List.empty[Class[_]]
|
||||||
|
var c = clazz
|
||||||
|
while (c != null) {
|
||||||
|
types :+= c
|
||||||
|
c = c.getSuperclass
|
||||||
|
}
|
||||||
|
types
|
||||||
|
}
|
||||||
|
|
||||||
|
def getAllFields(clazz: Class[_]) = getAllTypes(clazz).map(_.getDeclaredFields).flatMap(_.toList)
|
||||||
|
|
||||||
|
def setValue(obj: AnyRef, field: Field, value: String) = {
|
||||||
|
|
||||||
|
val getter = fieldGetter(field)
|
||||||
|
val setter = fieldSetter(field)
|
||||||
|
|
||||||
|
if (getter == null)
|
||||||
|
throw new QException("Field may be private? Unable to find getter for field: " + field)
|
||||||
|
|
||||||
|
if (getter == null)
|
||||||
|
throw new QException("Field may be a val instead of var? Unable to find setter for field: " + field)
|
||||||
|
|
||||||
|
if (classOf[Seq[_]].isAssignableFrom(field.getType)) {
|
||||||
|
|
||||||
|
if (!field.isAnnotationPresent(classOf[ClassType]))
|
||||||
|
throw new QException("@ClassType must be specified due to type erasure for field: " + field)
|
||||||
|
|
||||||
|
val fieldType = field.getAnnotation(classOf[ClassType]).asInstanceOf[ClassType].value
|
||||||
|
val typeValue = coerce(fieldType, value)
|
||||||
|
|
||||||
|
var list = getter.invoke(obj).asInstanceOf[Seq[_]]
|
||||||
|
list :+= typeValue
|
||||||
|
setter.invoke(obj, list)
|
||||||
|
|
||||||
|
} else if (classOf[Option[_]].isAssignableFrom(field.getType)) {
|
||||||
|
|
||||||
|
if (!field.isAnnotationPresent(classOf[ClassType]))
|
||||||
|
throw new QException("@ClassType must be specified due to type erasure for field: " + field)
|
||||||
|
|
||||||
|
val fieldType = field.getAnnotation(classOf[ClassType]).asInstanceOf[ClassType].value
|
||||||
|
val typeValue = coerce(fieldType, value)
|
||||||
|
|
||||||
|
setter.invoke(obj, Some(typeValue))
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
val fieldType = field.getType
|
||||||
|
val typeValue = coerce(fieldType, value)
|
||||||
|
|
||||||
|
setter.invoke(obj, typeValue.asInstanceOf[AnyRef])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private[util] def fieldGetter(field: Field) = field.getDeclaringClass.getMethod(field.getName)
|
||||||
|
private[util] def fieldSetter(field: Field) = field.getDeclaringClass.getMethod(field.getName+"_$eq", field.getType)
|
||||||
|
|
||||||
|
private def coerce(clazz: Class[_], value: String) = {
|
||||||
|
if (classOf[String] == clazz) value
|
||||||
|
else if (classOf[Boolean] == clazz) value.toBoolean
|
||||||
|
else if (classOf[Byte] == clazz) value.toByte
|
||||||
|
else if (classOf[Short] == clazz) value.toShort
|
||||||
|
else if (classOf[Int] == clazz) value.toInt
|
||||||
|
else if (classOf[Long] == clazz) value.toLong
|
||||||
|
else if (classOf[Float] == clazz) value.toFloat
|
||||||
|
else if (classOf[Double] == clazz) value.toDouble
|
||||||
|
else if (hasStringConstructor(clazz))
|
||||||
|
clazz.getConstructor(classOf[String]).newInstance(value)
|
||||||
|
else throw new QException("Unable to coerce value '%s' to type '%s'.".format(value, clazz))
|
||||||
|
}
|
||||||
|
|
||||||
|
private def hasStringConstructor(clazz: Class[_]) = {
|
||||||
|
clazz.getConstructors.exists(constructor => {
|
||||||
|
val parameters = constructor.getParameterTypes
|
||||||
|
parameters.size == 1 && parameters.head == classOf[String]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue