I want to create a simple task that does a few things in sequence:
- Run a command.
- Create a .tar.gz file from the output of (1)
- Upload the tarball to S3
I can make all of this work without too much trouble by creating a plugin and using tasks like Exec and Tar and a series of DependsOn relationships. Note that the command in (1) is never considered “up to date” so that every time the upload to S3 task is called the other tasks upon which it depends have to run. Also, there is never any use case for running any tasks but the final one that uploads to S3. Unfortunately, the plugin approach ends up creating 5 tasks and the user should never call any of them but the last one.
What I think I really want is just a single task. I can almost do that by creating a task class that extends DefaultTask and it ends up being much less code and less confusing. There is, unfortunately, one big blockerr: since you can’t directly instantiate and call Task classes from other tasks what was easy with the Tar task becomes a lot of code as you have to create InputStreams, OutputStream, add a tar and compression library dependency, etc.
2 questions:
- It seems like a design flaw that you can’t re-use Task classes in other tasks. Why does this limitation exist and is there any workaround?
- What’s the best way to do this?
For reference, here’s what my plugin looks like:
class MakeDependencySnapshot implements Plugin<Project> {
private static final File snapshotVersionFile = new File('bower-snapshot-version')
private static final String bowerSnapshotGroup = 'Bower Dependency Snapshots'
private String getVersionFromFile() {
String fileContents = snapshotVersionFile.text
String result = fileContents.trim()
assert !result.contains("\n")
return result
}
@Override
void apply(Project project) {
project.extensions.create('jsSnapshot', MakeDependencySnapshotOpts)
def version = null;
if (project.properties.containsKey('snapshotVersion')) {
project.logger.info("User specified version as: {}", project.snapshotVersion);
version = project.snapshotVersion
} else {
version = getVersionFromFile()
project.logger.info('Obtained version information from {}. Current version is {}',
snapshotVersionFile, version)
version = (version.toInteger() + 1).toString()
}
project.logger.info('Will set the snapshot version to {}', version)
def tempDir = File.createTempDir()
def snapShotName = "snapshot-${version}.tar.gz"
def tarball = new File("$tempDir/$snapShotName")
project.logger.info("Will save tar of bower components to {}", tempDir)
project.task('runBower', type: Exec) {
group bowerSnapshotGroup
description 'Reruns Bower to update all dependencies'
// Each bower run can download new patch fixes, etc. so this is never considered up to date
outputs.upToDateWhen { false; }
commandLine 'bower', 'install'
}
project.task('tarBower', type: Tar, dependsOn: project.tasks.runBower) {
group bowerSnapshotGroup
description "Creates a tar.gz file of all the Bower dependencies"
from '.bower_components'
compression Compression.GZIP
archiveName tarball.toString()
}
project.afterEvaluate {
project.task('uploadBowerDepsToS3', dependsOn: project.tasks.tarBower) << {
def s3Client = new AmazonS3Client()
s3Client.putObject(project.jsSnapshot.s3Bucket, snapShotName, tarball)
}
project.uploadBowerDepsToS3 {
group bowerSnapshotGroup
description 'Uploads the .tar.gz file containing all the dependencies to S3'
}
project.task('updateSnapshotVersionNumber', dependsOn: project.tasks.uploadBowerDepsToS3) << {
snapshotVersionFile.write "$version\n"
}
project.updateSnapshotVersionNumber {
group bowerSnapshotGroup
description "Writes the updated version number to $snapshotVersionFile"
}
project.task('updateSnapshot', dependsOn: project.tasks.updateSnapshotVersionNumber) {
project.logger.info('Running snapshot update tasks.')
}
project.updateSnapshot {
group bowerSnapshotGroup
description 'Causes all bower snapshot update tasks (re-running bower, creating the tarball, etc.) '
'to run'
}
}
}
}
class MakeDependencySnapshotOpts {
String s3Bucket
}
And here is what my task COULD look like if I could instantiate the Tar task and call it:
class UpdateSnapshot extends DefaultTask {
@TaskAction
def update() {
Project project = getProject();
project.exec {
commandLine 'bower', 'install'
}
def tarFile = File.createTempFile('dependencies', '.tar.gz')
project.logger.info('Will create tarball at {}', tarFile);
// This fails with "Task ... has been instantiated directly which is not supported...."
Tar tar = new Tar()
tar.from ='.bower_components'
tar.compression = Compression.GZIP
tar.execute()
// etc.
}
}