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

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
}

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.

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
}

Are pmd, repositories etc tasks in Gradle

I am creating basic custom tasks in Gradle and learning how to extend them to do more complicated actions (Learning from here: https://docs.gradle.org/current/userguide/tutorial_using_tasks.html).
One of my reference projects, which I am extending to learn Gradle looks something like this
// pmd config
pmd {
ignoreFailures = false
reportsDir = file("$globalOutputDir/pmd")
toolVersion = toolVersions.pmdVersion
}
repositories {
mavenCentral()
}
task listSubProjects{
doLast{
println 'Searching in root dir `'
}
}
My question is around the pmd and repositories sections and why they don't have a clear qualifier like "task" on them but my listSubProjects requires a task qualifier? Are these inherited tasks from plugins and don't need a task qualifier?
The blocks that you see are task extensions, also discussed here.
A plugin creator can define extensions to allow users to configure a plugin:
// plugin code
class GreetingPluginExtension {
// default value
String message = 'Hello from GreetingPlugin'
}
// plugin code
class GreetingPlugin implements Plugin<Project> {
void apply(Project project) {
// Add the 'greeting' extension object
def extension = project.extensions.create('greeting', GreetingPluginExtension)
// Add a task that uses configuration from the extension object
...
}
}
In project.extensions.create('greeting',... the greeting block to be used later in build.gradle files is defined.
Then in user build.gradle files
apply plugin: GreetingPlugin
// Configure the extension
greeting.message = 'Hi from Gradle'
// Same effect as previous lines but with different syntax
greeting {
message = 'Hi from Gradle'
}
Often the name of the extension is chosen to be the same as the plugin and/or the task, which can make things confusing.

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 publish multiple independent artifacts

I've got a project that builds using Gradle and the ivy-publish plugin. In addition to building a JAR, build.gradle also executes a run task that executes XmlFileGenerator.main(), which generates 5 XML files (call them A, B, C, D, and E). I'm looking to publish each of these XML files to our Ivy repository; each should have the same group and version but a different module and a different filename, and each should have its own ivy.xml that lists only itself.
I'm able to set the filename of the file that's published, but the module name remains the same as my project's name, and as a result all of my XML files are published under the same module name instead of under independent ones.
So for example, I want A.xml to be published at {myLocalIvyRootDir}\my-group\A\{version}\xmls\A-{version}.xml and I want B.xml to be published at {myLocalIvyRootDir}\my-group\B\{version}\xmls\B-{version}.xml. But instead A is published at {myLocalIvyRootDir}\my-group\my-project\{version}\xmls\A-{version}.xml and B is published alongside it at {myLocalIvyRootDir}\my-group\my-project\{version}\xmls\B-{version}.xml.
Here's the relevant subset of build.gradle (showing only A but not B-E):
apply plugin: 'ivy-publish'
group = 'my-group'
publishing {
publications {
ivy(IvyPublication) {
artifact jar
}
aXml(IvyPublication) {
artifact('target/A.xml') {
name = 'A'
extension = 'xml'
type = 'xml'
}
}
}
}
mainClassName = 'my-group.my-project.XmlFileGenerator'
I've tried defining the module property on the publication with this code:
aXml(IvyPublication) {
module 'A'
artifact('target/A.xml') {
name = 'A'
extension = 'xml'
type = 'xml'
}
}
But I get the following error message:
> org.gradle.api.internal.MissingMethodException: Could not find method module() for arguments [A] on org.gradle.api.publish.ivy.internal.publication.DefaultIvyPublication_Decorated#32384c50.
And I've tried changing the rootProject.name dynamically with code like:
publishing {
publications {
ivy(IvyPublication) {
artifact jar
}
project.metaClass.getName {"A"}
aXml(IvyPublication) {
artifact('target/A.xml') {
name = 'A'
extension = 'xml'
type = 'xml'
}
}
}
}
That produced no errors, but also no change in behavior.
I feel like I'm probably just missing something small, but don't know what it is. Can anyone point me in the right direction?
It turned out that this particular project was still pointing to Gradle 1.6, before these properties were made available (they were added in 1.7). So all that was needed was to point to 1.7, and everything worked as intended.

Resources