How to declare project artifacts in non-Java build? - gradle

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
}

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.

What is the recommended way to group dependencies of the same type?

I'd like to separate the dependencies in my project by type, and am considering doing so in the following way:
// Implementation dependencies
dependencies {
implementation("foo:bar:1") {
because("reason 1")
}
implementation("foo:bar:2") {
because("reason 2")
}
implementation("foo:bar:3") {
because("reason 3")
}
}
// Test implementation dependencies
dependencies {
testImplementation("foo:bar:4") {
because("reason 4")
}
testImplementation("foo:bar:5") {
because("reason 5")
}
}
Questions:
I am able to build the project after structuring the build file in this way, but I don't see any authoritative material stating that specifying multiple dependencies blocks is formally supported. Does such material exist?
Is there a more preferable way of separating dependencies by type than this? Preferably, I'd like to have a dependency-configuration (implementation, testImplementation, etc.) per module in order to document the reason for including each module, like the configuration above does.
I don't see any authoritative material stating that specifying multiple dependencies blocks is formally supported. Does such material exist?
There doesn't need to be any material because the Gradle DSL (Groovy or Kotlin) isn't anything special or magical. It's simply sugar over the Gradle API.
Specifying multiple dependencies block is perfectly legal. If you were to de-sugar the Gradle DSL, invoking multiple dependencies blocks is actually just doing:
project.getDependencies().add("implementation", "foo:bar:1")
project.getDependencies().add("testImplementation", "foo:bar:4")
It's no different than simply calling the add(...) method on a List multiple times.
Is there a more preferable way of separating dependencies by type than this?
Create a library (project or subproject) that bundles dependencies together. This is easily accomplished with the Java Library Plugin. For example, for your test library:
dependencies {
api("foo:bar:4") {
because("reason 4")
}
api("foo:bar:5") {
because("reason 5")
}
}
Then simply consume the library in your main project:
dependencies {
testImplementation(project(":my-test-library")) {
because("bundles test libs")
}
}
There is no such support and I don't think is there is need also, but to achieve your requirements we can create an extension function just to differentiate the different dependencies. Anyway many Kotlin DSL is extension functions only so add something like below. just declare this in your buildSrc Dependencies.kts file or anywhere you like but should be accessible global.
// test
fun Project.dependenciesTest(configuration: DependencyHandlerScope.() -> Unit) =
DependencyHandlerScope.of(dependencies).configuration()
//app
fun Project.dependenciesApp(configuration: DependencyHandlerScope.() -> Unit) =
DependencyHandlerScope.of(dependencies).configuration()
now call something like this in the calling site.
dependenciesApp {
implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar"))))
}
dependenciesTest {
testImplementation(AppDependencies.junit)
}

How can I call code from one subproject in a gradle tasks of another subproject?

I have a project with two subprojects.
One of these subprojects, "A", contains code that is being published to an artifact.
The other subproject, "B", has a task that needs to do exactly what one of the methods in A's code does. I can replicate the logic in groovy, but is there any way I can actually have my task in subproject B call the code that was compiled as part of subproject A?
I'd tried adding a buildscript block in B that added the artifact from A to the classpath:
buildscript {
dependencies {
classpath project(':subproject-a')
}
}
...but this gave me an error:
Cannot use project dependencies in a script classpath definition.
I don't believe I can move subproject-a to buildSrc, as I'm also publishing its artifact to a maven repository for other projects to use.
You have a chicken or egg problem where all of the Gradle project classloaders are resolved before any classes are compiled. This can be resolved using a custom configuration and a Classloader
Eg:
configurations {
custom
}
dependencies {
custom project(':subproject-a')
}
task customTask {
doLast {
def urls = configurations.custom.files.collect { it.toURI().toURL() }
ClassLoader cl = new java.net.URLClassLoader(urls as URL[])
Class myClass = cl.loadClass('com.foo.MyClass')
// assuming zero args constructor
Object myObject = myClass.newInstance()
// assuming method which accepts single String argument
java.lang.reflect.Method myMethod = myClass.getMethod('myMethodName', String.class)
myMethod.invoke(myObject, 'methodArg')
}
}

How can I reuse an IvyPublication in a custom PublishToIvyRepository task?

I have a build.gradle file with an artifact I am publishing, following the guidelines given in the Ivy publishing documentation.
publishing {
publications {
ivy(IvyPublication) {
from components.java
descriptor.withXml {
asNode().info[0].appendNode("description", description)
}
}
}
}
I have a separate PublishToIvyRepository task, which I would like to configure so that it goes to a different repository that normal, but uses the same publication as the above code. My initial attempt is this:
task publishToIvyLocal(type: PublishToIvyRepository) {
repository = mySpecialRepository
publication = project.publishing.publications[0]
}
However, this doesn't seem to work. If I put it before the publishing {} block above, I get the following error:
Cannot configure the 'publishing' extension after it has been accessed.
I'm guessing that project.publishing.publications[0] isn't the best way to reuse this publication.
How can I reuse an IvyPublication in a custom PublishToIvyRepository task?
There is no need to create a PublishToIvyRepository task on your own.
Applying the 'ivy-publish' plugin does the following:
[...]
Establishes a rule to automatically create a PublishToIvyRepository task for the combination of each IvyPublication added (see Section 35.2, “Publications”), with each IvyArtifactRepository added (see Section 35.3, “Repositories”).
So, simply add your publication with your two repositories and two tasks will be created, one to publish the publication for each repository.
The created task is named publish«PUBNAME»PublicationTo«REPONAME»Repository, which is publishIvyJavaPublicationToIvyRepository for this example.
Some example code:
publishing {
publications {
mySpecial(IvyPublication) {
// configure publication
}
}
repositories {
ivy {
name = 'first'
// configure first repository
}
ivy {
name = 'second'
// configure second repository
}
}
}
This should create the following tasks:
publishMySpecialPublicationToFirstRepository
publishMySpecialPublicationToSecondRepository
Regarding the repository name:
The name for this repository. A name must be unique amongst a repository set. A default name is provided for the repository if none is provided.
Given your code sample I agree with lu.koerfer's answer but in case if you really need a custom publication task you can use project.afterEvaluate to access the publishing container after it has been configured:
project.afterEvaluate
{
customPublicationTask.publication = project.publishing.publications["ivy"]
// a publication can be accessed by its name
}

Gradle plugin for XML Beans

I am trying to write a Gradle plugin for XML Beans. I have started with one of the 'Hello from Gradle' plugin examples, and also a plugin published by R. Artavia here. That plugin went straight to jar - I am trying to only generate source. The generated source must then be compiled with other project source and included in a single jar. Other goals include
- full plugin - all I should need is "apply plugin: 'xmlbean'"
- I can configure source/code gen location and some features if I want to
- It detects whether it needs to be rebuilt. (well, eventually!!!)
I am off to a pretty good start, but am blocked defining a new sourceSet. I am getting an error "No such property 'srcDirs'" (or 'srcDir'). It seems there is something I have to define someplace to make a new sourceSet work but I cannot find it. I have tried several different syntaxes (with/without equal sign, brackets, srcDir/srcDirs, etc. - nothing is working...
What do I need to do inside a plugin to make a new sourceSet entry be properly recognized?
Thank you!
JKE
File: xmlbean.gradle (includes greeting plugin for the moment for debugging)
apply plugin: xmlbean
apply plugin: 'java'
xmlbean {
message = 'Hi'
greeter = 'Gradle'
}
class xmlbean implements Plugin<Project> {
void apply(Project project) {
project.extensions.create("xmlbean", xmlbeanExtension)
Task xmlbeanTask = project.task('xmlbean')
xmlbeanTask << {
project.configurations {
xmlbeans
}
project.dependencies {
xmlbeans 'org.apache.xmlbeans:xmlbeans:2.5.0'
}
project.sourceSets {
main {
java {
srcDirs += '$project.buildDir/generated-source/xmlbeans'
}
}
xmlbeans {
srcDirs = ['src/main/xsd']
}
}
ant.taskdef(name: 'xmlbean',
classname: 'org.apache.xmlbeans.impl.tool.XMLBean',
classpath: project.configurations.xmlbeans.asPath)
ant.xmlbean(schema: project.sourceSets.xmlbean.srcDir,
srconly: true,
srcgendir: "$project.buildDir/generated-sources/xmlbeans",
classpath: project.configurations.xmlbeans.asPath)
println "${project.xmlbean.message} from ${project.xmlbean.greeter}"
}
project.compileJava.dependsOn(xmlbeanTask)
}
}
class xmlbeanExtension {
String message
String greeter
}
File: build.gradle
apply from: '../gradle/xmlbeans.gradle'
dependencies {
compile "xalan:xalan:$ver_xalan",
":viz-common:0.0.1",
":uform-repository:0.1.0"
}
Console: Error message:
:idk:xmlbean FAILED
FAILURE: Build failed with an exception.
* Where:
Script 'C:\jdev\cpc-maven\try.g2\comotion\gradle\xmlbeans.gradle' line: 32
* What went wrong:
Execution failed for task ':idk:xmlbean'.
> No such property: srcDirs for class: org.gradle.api.internal.tasks.DefaultSourceSet_Decorated
...
BUILD FAILED
Gradle info: version 2.5 / groovy 2.3.10 / JVM 7u55 on Windows 7 AMD64
You should try to become familiar with the Gradle DSL reference guide, because it's a huge help in situations like this. For example, if you click on the sourceSets { } link in the left navigation bar, you're taken to this section on source sets.
From there, you'll discover that the sourceSets {} block is backed by a class, SourceSetContainer. The next level of configuration nested inside is backed by a SourceSet object, and then within that you have one or more SourceDirectorySet configurations. When you follow the link to SourceDirectorySet, you'll see that there are getSrcDirs() and setSrcDirs() methods.
So how does this help? If you look closely at the exception, you'll see that Gradle is saying it can't find a srcDirs property on DefaultSourceSet_Decorated, which you can hopefully infer is an instance of SourceSet. That interface does not have an srcDirs property. That's because your xmlbeans {} block is configuring a SourceSet, not a SourceDirectorySet. You need to add another nested configuration to gain access to srcDirs.
At this point, I'm wondering whether a new source set is the appropriate solution. Unfortunately it's not clear to me exactly what the plugin should be doing, so I can't offer any alternatives at this point.

Resources