How can I apply a plugin configuration to a Gradle project externally of the build so that it does not get included in Git source control?
Details
I have a Gradle project which uses the gradle-git-properties plugin to generate a git.properties file based on the project's Git details.
plugins {
id 'java'
id 'com.gorylenko.gradle-git-properties'
}
// Rest of build.gradle goes here
Additionally, I'm checking out my project to multiple locations locally using git working trees. However, projects using gradle-git-properties fail when run from a linked working tree, per gradle-git-properties#14.
$ ./gradlew generateGitProperties
> Task :subproject:generateGitProperties FAILED
FAILURE: Build failed with an exception.
* What went wrong:
Execution failed for task ':subproject:generateGitProperties'.
> Error while evaluating property 'generatedProperties' of task ':subproject:generateGitProperties'
> gradlegitproperties.org.eclipse.jgit.errors.RepositoryNotFoundException: repository not found: /Users/Me/my-project/.git/worktrees/my-project
I have been using a plugin configuration to work around the issue, allowing the project to build successfully when run from the linked working tree:
gitProperties {
Path dotGitPath = rootProject.layout.projectDirectory.asFile.toPath().resolve(".git")
if (Files.isRegularFile(dotGitPath)) {
Files.lines(dotGitPath).withCloseable { ditGitFileLines ->
dotGitDirectory = ditGitFileLines
.filter { it.startsWith("gitdir: ") }
.map { it.substring('gitdir: '.length(), it.lastIndexOf('/.git/')) }
.map { project.objects.directoryProperty().convention(project.layout.projectDirectory.dir(it)) }
.findFirst()
.orElse(project.objects.directoryProperty().convention(project.layout.projectDirectory.dir(".git")))
}
}
}
The problem with this solution is that it requires modifying the build file. This is a shared repository, and I'm the only person accessing it who uses multiple git working trees. Therefore, I don't want to include this workaround in the build itself.
Is there a way I can automatically apply this plugin configuration externally to the build file or any other file that would be committed with the project?
A Gradle initialization script can be used, putting the plugin configuration into an external file.
There are several ways to use an init script:
Specify a file on the command line. The command line option is -I or --init-script followed by the path to the script. […]
Put a file called init.gradle (or init.gradle.kts for Kotlin) in the USER_HOME/.gradle/ directory.
Put a file that ends with .gradle (or .init.gradle.kts for Kotlin) in the USER_HOME/.gradle/init.d/ directory.
Put a file that ends with .gradle (or .init.gradle.kts for Kotlin) in the GRADLE_HOME/init.d/ directory, in the Gradle distribution. […]
~/.gradle/init.d/git-worktree-fix.gradle
import java.nio.file.Files
import java.nio.file.Path
allprojects {
plugins.withId('com.gorylenko.gradle-git-properties') {
gitProperties {
Path dotGitPath = rootProject.layout.projectDirectory.asFile.toPath().resolve(".git")
if (Files.isRegularFile(dotGitPath)) {
Files.lines(dotGitPath).withCloseable { ditGitFileLines ->
dotGitDirectory = ditGitFileLines
.filter { it.startsWith("gitdir: ") }
.map { it.substring('gitdir: '.length(), it.lastIndexOf('/.git/')) }
.map { project.objects.directoryProperty().convention(project.layout.projectDirectory.dir(it)) }
.findFirst()
.orElse(project.objects.directoryProperty().convention(project.layout.projectDirectory.dir(".git")))
}
}
}
}
}
The gitProperties configuration is wrapped in a plugins.withId('com.gorylenko.gradle-git-properties') check, since otherwise projects not using the gradle-git-properties plugin would fail when using this script.
Related
We are currently in the process of setting up a Jenkins shared library to be used for several Java projects. Our file vars/utils.groovy contains the following (simplified) snippet:
def mvn(String mvnCommand) {
// withCredentials(userVarEtc=USER, pwVarEtc=PASS, etc, etc) {
sh "mvn ${mvnCommand} -Duser${USER} -Dpass${PASS} --settings maven_settings.xml"
// }
}
Although above snippet works fine, it requires all Java projects to keep the exact same maven_settings.xml template file in the root of the project. To prevent duplication we would like to add the maven_settings.xml file in the shared library and refer to its shared library path in the groovy method above. Would anyone know how to do this?
I can think of two possible solutions.
Solution 1
Keep the file maven_settings.xml in the resources directory.
shared-library-repository
+--resources/maven_settings.xml
+--vars/utils.groovy
In your shared library method, read this file and write to workspace before calling the maven commands.
def mvn(String mvnCommand) {
def myMavenSettings = libraryResource 'maven_settings.xml'
writeFile file: 'maven_settings.xml', text: myMavenSettings
withCredentials(userVarEtc=USER, pwVarEtc=PASS, etc, etc) {
sh "mvn ${mvnCommand} -Duser${USER} -Dpass${PASS} --settings $WORKSPACE/maven_settings.xml"
}
}
Solution 2
Install the Config File Provider plugin.
Go to Manage Jenkins > Managed files > Add a new Config.
Click Maven settings.xml and then Submit.
Type the name and paste the content in the designated fields.
Rename or copy the file ID and then click Save.
In you shared library method, invoke the plugin step to run maven commands.
def mvn(String mvnCommand) {
configFileProvider([configFile(fileId: '<file_ID>', variable: 'MAVEN_SETTINGS')]) {
withCredentials(userVarEtc=USER, pwVarEtc=PASS, etc, etc) {
sh "mvn ${mvnCommand} -Duser${USER} -Dpass${PASS} --settings $MAVEN_SETTINGS"
}
}
}
I want to apply a shared gradle file to my projects settings.gradle. The shared file is located in a jar which must be downloaded and extracted during the configuration phase. This is because is applies a plugin which must be applied in the configuration phase. I found this related question: How to share a common build.gradle via a repository? My preferred way is described in this answer: https://stackoverflow.com/a/39228611/987860
However, this appears to be working in build.gradle only. I tried to move the buildscript block to my settings.gradle.
settings.gradle
buildscript {
ext {
dependencyVersion = '0.1.2'
}
repositories {
maven {
credentials {
username 'user'
password 'password'
}
url 'https://my-private-maven-repo.com'
}
}
dependencies {
classpath "my.group:myartifact:$dependencyVersion"
}
dependencies {
def gradleScripts = new File(rootDir, '/build/gradle')
delete gradleScripts
def jars = configurations.classpath.files as List<File>
ant.unjar(src: jars.find { it.name.matches '.*myartifact.*' }, dest: gradleScripts) {
patternset {
include(name:'*.gradle')
}
}
}
}
apply from: new File(rootDir, '/build/gradle/myscript.gradle')
But this results in the following exception:
FAILURE: Build failed with an exception.
* Where:
Settings file 'settings.gradle' line: 24
* What went wrong:
A problem occurred evaluating settings 'journal'.
> Could not get unknown property 'ant' for object of type org.gradle.api.internal.artifacts.dsl.dependencies.DefaultDependencyHandler.
* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output.
BUILD FAILED
Total time: 0.019 secs
Could not get unknown property 'ant' for object of type org.gradle.api.internal.artifacts.dsl.dependencies.DefaultDependencyHandler.
Is there any way to utilize ant int the confiuration phase before my settings.gradle is evaluated? I need to have the dependency downloaded and extractet before the to-be-downloaded file gets applied.
This is a really unusual way to do things. I'd really recommend not doing what you're trying to do because it'll make your build much slower than it should be. You're deleting build/gradle and extracting the contents of the plugin's jar on every build.
Everything inside a build.gradle (or settings.gradle) can be put into a plugin and distributed that way. You already have a jar that needs to be downloaded, so converting myscript.gradle into a plugin is very easy to roughly convert.
Put this in src/main/groovy/some/package/MyPlugin.groovy in the project that's producing the plugin jar already:
package some.package
import org.gradle.api.*
class MyPlugin implements Plugin<Project> {
void apply(Project project) {
project.with {
// contents of script
}
}
}
For plugins applied to settings.gradle:
package some.package
import org.gradle.api.*
class MyPlugin implements Plugin<Settings> {
void apply(Settings settings) {
settings.with {
// contents of script
}
}
}
Then you can just add the dependency to the plugin and use apply plugin: some.package.MyPlugin.
There are a lot of other advantages of developing/distributing plugins in this way. You can find more information on plugin development in the Gradle Guides.
Alternatively, if you absolutely must keep the separate .gradle script. If you can serve it separately (outside of the jar), you can do:
apply from: "http://example.com/some/url/myscript.gradle"
The downside with this is that it'll download the file on every build (this is fixed in Gradle 4.2).
My build.gradle file contains a section like this to upload archives to SonaType:
uploadArchives {
repositories {
mavenDeployer {
beforeDeployment {
MavenDeployment deployment -> signing.signPom(deployment);
}
// HERE
repository(url: sonatypeRepoURI) {
authentication(userName: sonatypeUsername,
password: sonatypePassword);
}
pom.project {
// etc etc
}
}
}
}
At the point marked HERE, other users wishing to use my build file will fail, because at least the first variable is not defined:
FAILURE: Build failed with an exception.
* Where:
Build file '/path/to/build.gradle' line: 144
* What went wrong:
A problem occurred evaluating root project 'whateverTheProject'.
> No such property: sonatypeRepoURI for class:
org.gradle.api.publication.maven.internal.ant.DefaultGroovyMavenDeployer
How do I modify the section above so that users are not affected by these variables not being defined for them?
You could try to add all the needed properties to your gradle.properties file, which you add to version control, but leave the values empty.
Eg:
version=1.0
signing.keyId=
signing.password=
signing.secretKeyRingFile=
sonatypeUsername=
sonatypePassword=
Then you override these in your own ${USER}/.gradle/gradle.properties.
As an example take a look at a working project https://github.com/judoole/monitorino. Should be able to run all tasks at any machine except snapshot, stage and build.
Edit: I would not do it like this today. Follow the Gradle guide, using required. Just as the example from #jb-nizet Gradle ref 53.3.3 Conditional Signing: http://www.gradle.org/docs/current/userguide/signing_plugin.html
Very simple just create the "gradle.properties" file in "~/.gradle" with the following contents:
sonatypeUsername=
sonatypePassword=
After run your project. Its running properly.
I'm using Gradle to build a jar containing an xml file in META-INF. This file has a row like
<property name="databasePlatform" value="${sqlDialect}" />
to allow for different SQL databases for different environments. I want to tell gradle to expand ${sqlDialect} from the project properties.
I tried this:
jar {
expand project.properties
}
but it fails with a GroovyRuntimeException that seems to me like the Jar task attempts to expand properties in .class files as well. So then I tried
jar {
from(sourceSets.main.resources) {
expand project.properties
}
}
which does not throw the above exception, but instead results in all resources being copied twice - once with property expansion and once without. I managed to work around this with
jar {
eachFile {
if(it.relativePath.segments[0] in ['META-INF']) {
expand project.properties
}
}
}
which does what I want, since in my use case I only need to expand properties of files in the META-INF directory. But this feels like a pretty ugly hack, is there a better way to do this?
I stumbled across this post in a thread about a different but closely related issue. Turns out you want to configure the processResources task, not the jar task:
processResources {
expand project.properties
}
For some reason, though, I did have to clean once before Gradle noticed the change.
In addition to #emil-lundberg 's excellent solution, I'd limit the resource processing to just the desired target file:
build.gradle
processResources {
filesMatching("**/applicationContext.xml") {
expand(project: project)
}
}
An additional note: if the ${...} parentheses are causing "Could not resolve placeholder" errors, you can alternatively use <%=...%>. N.B. tested with a *.properties file, not sure how this would work for an XML file.
I've had similar problems migrating from maven to gradle build. And so far the simplest/easiest solution was to simply do the filtering yourself such as:
processResources {
def buildProps = new Properties()
buildProps.load(file('build.properties').newReader())
filter { String line ->
line.findAll(/\$\{([a-z,A-Z,0-9,\.]+)\}/).each {
def key = it.replace("\${", "").replace("}", "")
if (buildProps[key] != null)
{
line = line.replace(it, buildProps[key])
}
}
line
}
}
This will load all the properties from the specified properties file and filter all the "${some.property.here}" type placeholders. Fully supports dot-separated properties in the *.properties file.
As an added bonus, it doesn't clash with $someVar type placeholders like expand() does. Also, if the placeholder could not be matched with a property, it's left untouched, thus reducing the possibility of property clashes from different sources.
here is what worked for me (Gradle 4.0.1) in a multi-module project:
in /webshared/build.gradle:
import org.apache.tools.ant.filters.*
afterEvaluate {
configure(allProcessResourcesTasks()) {
filter(ReplaceTokens,
tokens: [myAppVersion: MY_APP_VERSION])
}
}
def allProcessResourcesTasks() {
sourceSets*.processResourcesTaskName.collect {
tasks[it]
}
}
and my MY_APP_VERSION variable is defined in top-level build.gradle file:
ext {
// application release version.
// it is used in the ZIP file name and is shown in "About" dialog.
MY_APP_VERSION = "1.0.0-SNAPSHOT"
}
and my resource file is in /webshared/src/main/resources/version.properties :
# Do NOT set application version here, set it in "build.gradle" file
# This file is transformed/populated during the Gradle build.
version=#myAppVersion#
I took your first attempt and created a test project. I put a pom file from a jenkins plugin in ./src/main/resources/META-INF/. I assume it is a good enough xml example. I replaced the artifactId line to look like the following:
<artifactId>${artifactId}</artifactId>
My build.gradle:
apply plugin: 'java'
jar {
expand project.properties
}
When I ran gradle jar for the first time it exploded because I forgot to define a value for the property. My second attempt succeeded with the following commandline:
gradle jar -PartifactId=WhoCares
For testing purposes I just defined the property using -P. I'm not sure how you are trying to define your property, but perhaps that is the missing piece. Without seeing the stacktrace of your exception it's hard to know for sure, but the above example worked perfectly for me and seems to solve your problem.
Currently, our gradle setup is geared to publish to certain maven repositories when a build is done. For a particular customer, I need to zip up the jars, license files, pom.xml, and ivy.xml files, and send it all in a zip. To do this, I just need to define an alternate location to publish it to. All the documentation on the gradle site seems aimed at writing one set of publishing rules, not an alternative set.
I was hoping to simply write a different task which would focus on building this customer-specific zip file. So far, I have it collecting all the jars (which includes source and runnable code) along with the license and notice file. But I haven't cracked the nut on defining a local ivy repository and a local maven repository that is only part of this alt task.
task alt {
dependsOn subprojects*.tasks*.matching { task -> task.name == 'assemble' }
subprojects.each{project ->
if (project.hasProperty('sourceJar')) {
evaluationDependsOn(project.name)
}
}
File altDir = mkdir("$buildDir/alt")
subprojects.each { project ->
if (project.hasProperty('sourceJar')) {
// Extra the module name from the path of the sub-project
String submodule = project.projectDir.absolutePath.split(File.separator).last()
File subfolder = mkdir(altDir.absolutePath + "/${project.group}/${group}.${submodule}/$version")
project.tasks.withType(Jar).each {archiveTask ->
copy {
from archiveTask.archivePath
from("$rootDir") {
include 'license.txt'
include 'notice.txt'
}
into subfolder
}
}
}
}
}
Here's the gradle docs that tells you how to generate the pom. Also if you are looking to install that file to your local repository you could use the mechanism described in this blog entry . Essentially all you have to do
configure(install.repositories.mavenInstaller) {
pom.project {
version '1.0'
artifactId 'your.artifact.id'
groupId 'your.group.id'
}
}
It doesn't look like it possible at the moment to generate the ivy.xml through gradle, but once you have your pom file you could use ivy itself to generate the ivy file described here.
Section 64.5 of http://www.gradle.org/docs/current/userguide/publishing_ivy.html covers "Generating the Ivy module descriptor file without publishing".
The documentation is a little broken (eg inconsistent naming conventions). The following works for me:
apply plugin: 'ivy-publish'
publishing {
publications {
aoeu(IvyPublication)
}
}
This will generate a target generateDescriptorFileForAoeuPublication.