Avoid Multiple OS Inspections

Our project being compiled and deployed on both Windows and Linux machine (and hopefully Mac in the future). Many of our tasks are commandLine oriented. For example:

task updateFiles(type: Exec) {
switch (project.operationSystem) {
    case 'windows':
        commandLine('cmd', '/c', 'command_line')
        break

    case 'linux':
        commandLine('command_line')
        break

    default:
        throw new GradleException('Operation system not supported.')
        break
    }
}

This switch case is basically being written over and over over couple of tasks. Any elegant way to avoid it?
Thanks

You could try to pull it out into its own Gradle file and then apply it to the select projects that need it. For example, if you’re running one of the latest versions of Gradle, you can use the model-based approach. Note, this doesn’t avoid multiple OperatingSystem inspections, but it does make integrating this task more modular (e.g., apply it when you need it).

// $rootDir/gradle/updateFiles.gradle
model {
     tasks {
          updateFiles(Exec) {
               // switch-statement here
          }
     }
}
// $rootDir/build.gradle
def projectsThatNeedUpdateFiles = [ project(':project1')
                                  , project(':project2')
                                  , project(':project3')
                                  ]
configure(projectsThatNeedUpdateFiles) {
    apply from: "$rootDir/gradle/updateFiles.gradle"
}

There are two other approaches I would consider:

  1. Write a specific task type for each different tool. The task can then be responsible for translating the tool specific arguments into a command line.

The advantage of this is that you get away with the generic Exec task type and can describe what you’re trying to do vs just specifying command-line arguments. e.g., let’s say that the Linux, Mac and Windows tools all had a “verbose” option. On Windows, it’s ‘/V’. On Linux, it’s ‘-v’. On Mac OS X, it’s ‘–verbose’. Instead of having to remember that, you’d have a verbose property on your new task and the task implementation would work out the details.

The other advantage of this is that you may be able to add incremental build support. By default, an Exec task is going to always run (it has no inputs or outputs). But if your tools actually have a known set of inputs and outputs, you could mark them as inputs and outputs and your tasks would only run if those change.

You can also write unit/integration tests for these custom task types to verify they produce the correct command-line.

Or…

Write a new task type that allows you to specify a complete ExecSpec for each OS and that task is responsible for choosing the correct command-line given the OS.

This will probably look more elegant than a switch and be less surprising, but like Exec it’s a low-level task and doesn’t get you any of the advantages of the first approach.

You could have a property file for each OS containing values and flags

Eg

def props = new Properties() 
file("$rootDir/${operationSystem}.properties").withInputStream {
   props.load(it)
}
ext.osProps = props

task updateFiles(type: Exec) {
   commandLine(osProps["updateFiles.commandLine"].split(','))
}

Thanks Kevin, we actually doing something similar. This however does not solves the the multiple if/else operation system issues we are having, because each task suppose to be able to run on both Unix and Windows.

sterling,
Thanks for the detailed answer. I’m quite new to Gradle, and I’m not sure I completely understand. Will you please be kind enough to supply code examples for the two solution so I’ll be sure I indeed understand your point correctly?

Lance_Java,
Interesting idea. Then I should basically have one command_line property file for each OS and I can just extract them dynamically on run time. Thanks for the idea!