How to get Gradle TestKit test project's `sourceSets`? - gradle

I'm using Gradle's TestKit and want to ensure that a plugin is adding the correct paths to sourceSets.main.proto.
How do I access that info?

I haven't found a way to access the Project object from TestKit (and I'm suspecting it's impossible to do so).
One way to get at sourceSets.main.proto would be to create a task within the test data build.gradle file. For example:
tasks.register('assertMainProtoSrcDirsIncludesProtopPath') {
doLast {
assert sourceSets.main.proto.srcDirs.contains(file("${rootDir}/${protop.path}"))
}
}
TestKit can then be used to call that task:
#Test
#DisplayName("`sourceSets.main.proto.srcDirs` should include `protop.path`.")
public void sourceSetsMainProtoSrcDirsShouldIncludeProtopPath(final SoftAssertions softly) {
final BuildResult buildResult = gradleRunner
.withPluginClasspath()
.withProjectDir(rootDir.toFile())
.withArguments("--info", "--stacktrace", ":assertMainProtoSrcDirsIncludesProtopPath")
.build();
softly.assertThat(Objects.requireNonNull(buildResult.task(":assertMainProtoSrcDirsIncludesProtopPath")).getOutcome())
.isEqualByComparingTo(SUCCESS);
}

Related

How to add a method to every `repositories` block, or every RepositoryHandler?

A large project with many developers and gradle projects uses a private maven repository for plugins, dependencies, and publication.
I would like to define a privateMaven() method, just like the built-in jcenter(), mavenCentral(), and google() methods. Currently we write a maven block anywhere we need to use the repository - repositories, publishing.repositories, pluginManagement.repositories, ...
repositories {
maven {
url "..."
credentials { ... }
}
}
which I would rather be
repositories {
private()
}
This answer explains how to extend repositories and buildscript.repositories but it doesn't work for publishing.repositories because publishing is provided by a plugin and doesn't work for pluginManagement.repositories. Also I would also have to enumerate every repositories configuration and developers can't use privateMaven() in any block we don't extend.
Is there a way for an init script to add a method to every repositories block, or every RepositoryHandler?
Assuming you're using the maven-publish plugin, you can define an extension like so:
private.gradle
apply plugin: 'maven-publish'
publishing.repositories.ext.privateRepo = {
publishing.repositories.maven {
url "https://artifactory.website.com/private-repo"
credentials {...}
}
}
build.gradle
apply from: 'private.gradle' // or you can just include the script in here
afterEvaluate {
publishing {
publications {...}
repositories {
privateRepo()
}
}
}
You can also create a plugin if you'd like to distribute the script, the usage would remain exactly the same.
https://guides.gradle.org/implementing-gradle-plugins/
PrivatePlugin.groovy
class PrivatePlugin implements Plugin<Project> {
#Override
void apply(Project project) {
// check if the publishing extension exists first
final publishing = project.extensions.findByType(PublishingExtension.class)
if (publishing != null) {
publishing.repositories.ext.privateRepo = {
publishing.repositories.maven {
url "https://artifactory.website.com/private-repo"
credentials {...}
}
}
}
}
}
Just be sure that if you distribute it as a plugin that you don't ship it with the credentials hardcoded.
My goal is that any repositories block can use privateMaven. I don't want to explicitly extend publishing.repositories.ext, repositories.ext, buildscript.repositories.ext. In addition to being tedious, if I miss one repositories or gradle adds a new repositories then privateMaven will not be available.
One solution which is close, but not perfect, was to create an extension on with a method that takes a reference to RepositoryHandler.
Any repository block and now use the extension as so
repositories {
custom.privateMaven(it)
}
beforeSettings {
extensions.create("custom", PrivateMavenExtension::class)
}
allprojects {
extensions.create("custom", PrivateMavenExtension::class)
}
open class PrivateMavenExtension {
fun privateMaven(handler: RepositoryHandler): MavenArtifactRepository {
return handler.maven {
// implementation
}
}
}
One major hurdle I haven't solved is that classes defined in an init.d script cannot be loaded in build.gradle. If PrivateMavenExtensions is defined in an init.d script I can't reference the extension in a type-safe way from a build.gradle.kts because the<PrivateMavenExtension>() cannot be resolved.

Applying Gradle plugin from local file

I have the following gradle plugin that does the job of starting up a java process. The code for this lives under a file named startAppServerPlugin.gradle under the project's buildSrc directory.
The code of the plugin looks like this:
repositories.jcenter()
dependencies {
localGroovy()
gradleApi()
}
}
public class StartAppServer implements Plugin<Project> {
#Override
void apply(Project project) {
project.task('startServer', type: StartServerTask)
}
}
public class StartServerTask extends DefaultTask {
String command
String ready
String directory = '.'
StartServerTask(){
description = "Spawn a new server process in the background."
}
#TaskAction
void spawn(){
if(!(command && ready)) {
throw new GradleException("Ensure that mandatory fields command and ready are set.")
}
Process process = buildProcess(directory, command)
waitFor(process)
}
private waitFor(Process process) {
def line
def reader = new BufferedReader(new InputStreamReader(process.getInputStream()))
while ((line = reader.readLine()) != null) {
logger.quiet line
if (line.contains(ready)) {
logger.quiet "$command is ready."
break
}
}
}
private static Process buildProcess(String directory, String command) {
def builder = new ProcessBuilder(command.split(' '))
builder.redirectErrorStream(true)
builder.directory(new File(directory))
def process = builder.start()
process
}
}
I'm trying to figure out a way of having this imported into my main build.gradle file due everything I tried so far has been unsuccessful.
So far I have tried this:
apply from: 'startAppServerPlugin.gradle'
apply plugin: 'fts.gradle.plugins'
But it has been failing. I've tried searching online for examples of doing what I need to do but so far I've been unsuccessful. Can anyone please provide a hint as to how I'm supposed to do so?
The buildSrc folder is treated as an included build, where the code is compiled and put on the classpath of the surrounding project. The actual build.gradle file in buildSrc is only used for compiling that project, and the things you put in it will not be available elsewhere.
You are supposed to create your classes as a normal Java/Groovy/Kotlin project under buildSrc. I don't know if you can use the default package, but it is generally best practice to have a package name anyway.
For example, your StartAppServer plugin should be in buildSrc/src/main/groovy/my/package/StartAppServer.groovy. Then you can apply it in your build scripts with apply plugin: my.package.StartAppServer.
There are a lot of good examples in the user guide.
You are on the right path. The first order of business is to import the external gradle build using:
apply from: 'startAppServerPlugin.gradle'
Then you can apply the plugin with:
apply plugin: StartAppServer
See Script Plugins and Applying Binary Plugins

How to declare project artifacts in non-Java build?

I have multi-project Gradle build that contains also non-Java projects.
I want to declare the artifacts create by one such project in a way that I can use project/configuration dependencies to get them, e.g.
consumer:
dependencies {
myConf project(path: ':producer', configuration: 'myConf')
}
What I currently have is this:
producer:
configurations {
myConf
}
task produceFile {
//... somehow create the file...
outputs.file file('path/to/file')
}
artifacts.add('myConf', produceFile.outputs.files.singleFile, { builtBy produceFile })
Is there a better way to declare the artifact than my clumsy version?
I couldn't figure out a way to pass the task dependency from the artifact to the producing task in one go.
According to the documentation article on Legacy publishing and the javadoc on the ArtifactHandler, for your simple example it should be sufficient to just pass the task, as long as the task type extends AbstractArchiveTask (e.g. Zip or Jar):
artifacts.add('myConf', produceFile)
... or in the more Gradle-ish way:
artifacts {
myConf produceFile
}
The article mentioned above has another example, where a File is passed directly to the add method, which requires you to specify the task to build the file in the way you did in your example.
However, let me propose other ideas for syntax that may be experienced more 'lightweight':
artifacts {
myConf files(produceFile).singleFile { buildBy produceFile }
// or
myConf file: files(produceFile).singleFile, buildBy: [produceFile]
}
These two examples use the Project.files(...) method to resolve the output(s) of the task instead of accessing them manually. The second example makes use of the map syntax often provided by Gradle.
If you want to somehow standardize your way to publish your custom artifacts, I would propose to create a custom task type that offers any of the different arguments the ArtifactHandler can process as a method or property:
class MyTaskType extends DefaultTask {
// ... other stuff ... of course this should be part of a plugin
def getArtifact() {
return ... // either a (Configurable)PublishArtifact (if constructor is available) or a map representation
}
}
task produceFile(type: MyTaskType) {
// configure somehow
}
artifacts {
myConf produceFile.artifact
}

How to add to a plugin task the dependencies comming from build.grade script?

I Have a task from my plugin that need mysql or postgres drivers.
currently I hardcoded into FooPlugin::apply method this:
configuration.dependencies.add(project.dependencies.create('mysql:mysql-connector-java:5.1.34'))
But I would like to let users, to choose their drivers.
So for this I would like to grab all dependencies from gradle build script (build.gradle) which applying my plugin to inject these dependencies to the task dynamically.
Resolved: add a piece of code
I tried this:
class FooPlugin implements Plugin<Project>{
#Override
void apply(Project project) {
project.afterEvaluate {
def configuration = project.configurations.create('bar')
configuration.extendsFrom(project.configurations.findByName('compile'))
…
}
}
}
If you do not put into project.afterEvaluate block below error is raised:
Cannot change dependencies of configuration ':bar' after it has been resolved.
I'm not sure exactly what your trying to accomplish so I'm going to guess at a couple things.
Looks like your trying to add a dependency or react based on a dependency added. I think you can accomplish either through the resolutionStrategy
project.configurations {
compile.resolutionStrategy {
// adds a dependency to a project, always.
force 'commons-io:commons-io:2.5'
// loop through all the dependencies to modify before resolution
eachDependency { DependencyResolveDetails details ->
// here you can change details about a dependency or respond however needed
if (details.requested.group == 'mysql' && details.requested.name == 'mysql-connector-java') {
// for example we can force a specific version
details.useVersion '5.1.34'
}
// you could also add a if block for postgres if needed
}
}
}

Gradle Plugin Test UpToDateWhen method

I'm writing a gradle plugin that defines an upToDateWhen closure to skip the task when certain criteria is met. I'm having trouble figuring out how to wrap a test around this method. Currently it looks like:
class MyCoolTask extends DefaultTask {
MyCoolTask() {
outputs.upToDateWhen {
if (somecondition)
return true
else
return false
}
}
}
My test looks like this:
class MyCoolTaskTest {
#Test
void testUpToDateCheck() {
project = ProjectBuilder.builder().build()
project.apply plugin: 'myCoolPlugin'
project.myCoolTask.execute()
// But then how do you do a subsequent run and ensure that the task did not execute?
project.myCoolTask.execute() // running this a second time does not work.
project.myCoolTask.outputs.upToDateWhen() // Throws a syntax error
}
}
Any insight that could be offered would be great! Thanks!
ProjectBuilder is meant for low-level tests that configure the build but don't execute any tasks. You can either factor out the contents of outputs.upToDateWhen { ... } into a method/class and test that, and/or write an acceptance test that executes a real build using the Gradle tooling API.

Resources