Re-publish gradle artifacts - groovy

We have an ivy repository, and we are using gradle for our dependency management and build framework. When an artifact is determined to be production-ready, we don't want to have to build it again, so we want to just "promote" an existing artifact via a web application that is leveraging Gradle and the tooling API to do most of the heaving lifting for us.
Currently, I'm copying the artifacts to a local folder and running another build.gradle that just re-publishes it. We are publishing it to a new folder in our existing repository, and a folder in the release repository.
In doing so, it is only publishing the ivy.xml to both locations.
I'm guessing this is due to where the artifacts are located.
PromotionService.groovy
void promote(Project project, Build build, String newVersion) {
def artifactLocation = "/path/to/repository"
// we are generating this build.gradle and copying it
def buildFileText = new File('promote.gradle').getText('UTF-8')
def artifacts = buildDao.findArtifactsByBuild(build)
def localBuildFolderPath = "/path/to/local/gradle/build"
def localBuildFolder = new File(localBuildFolderPath)
localBuildFolder.mkdirs()
// remove everything currently in the directory
def buildFiles = localBuildFolder.listFiles()
buildFiles.each {
it.delete()
}
def newFile = new File("/path/to/local/gradle/build.gradle")
newFile.mkdirs()
if (newFile.exists())
newFile.delete()
newFile << buildFileText
artifacts.each { VersionedArtifact it ->
def folder = new File("${artifactLocation}/${it.module}/${build.branch}/${it.version}")
def files = folder.listFiles()
files.each { File from ->
// remove version number from file name
String fromName = from.name
def matcher = fromName =~ /(.*?)-(\d)+\.(\d)+\.(\d)+(\.\d+)?\.(.*)/
fromName = "${matcher[0][1]}.${matcher[0][6]}"
File to = new File("${localBuildFolderPath}/${it.module}/${fromName}")
to.mkdirs()
if (to.exists()) to.delete()
// wrapper for Guava's Files.copy()
FileUtil.copy(from, to)
}
ProjectConnection connection = GradleConnector.newConnector().forProjectDirectory(new File("${workingDir}/gradle")).connect()
connection.newBuild()
.forTasks("publishReleaseBranchPublicationToIvyRepository", "publishReleaseRepoPublicationToReleaseRepository")
.withArguments("-PMODULE=${it.module}", "-PVERSION=${it.version}", "-PNEWVERSION=${newVersion}")
.run()
}
}
build.gradle
apply plugin: 'groovy'
apply plugin: 'ivy-publish'
publishing {
publications {
releaseBranch(IvyPublication) {
organisation 'our-organization'
module MODULE
revision VERSION
descriptor.status = 'release'
configurations { archive {
} }
}
releaseRepo(IvyPublication) {
organisation 'our-organization'
module MODULE
revision NEWVERSION
descriptor.status = 'release'
configurations { archive {
}}
}
}
repositories {
ivy {
name 'ivy'
url "/path/to/ivy/repo"
layout "pattern", {
ivy "[organisation]/[module]/release/[revision]/[module]-[revision].xml"
artifact "[organisation]/[module]/release/[revision]/[artifact](-[classifier])-[revision].[ext]"
}
}
ivy {
name 'release'
url "/path/to/release/repo"
layout "pattern", {
ivy "[organisation]/[module]/[revision]/[module]-[revision].xml"
artifact "[organization]/[module]/[revision]/[artifact](-[classifier])-[revision].[ext]"
}
}
}
}
Edit: Made it clearer we're writing a web application to promote artifacts.

It's not clear to me why the promotion is implemented using the tooling API, rather than as a regular Gradle task or plugin. Anyway, the IvyPublications are neither configured using IvyPublication#from, nor using IvyPublication#artifact. Hence they won't have any artifacts.

Related

Gradle Download And Unzip Multiple Dependencies

I have a generic Artifatory repo that contains some zip files that I need to download and extract in an Android Studio project. I am new to Gradle but found that using an Ivy repository object maybe the best way to do this instead of just downloading the files using a Gradle task which calls curl.
I have managed to get a single zip file downloaded and extracted but soon as I try to add another dependency it overwrites my configuration. See the dependencies block in the code below.
Why is it not adding another dependency and is there a better way to do this?
app:build.gradle:
repositories {
ivy {
url property("artifactoryUrl")
credentials {
username property("artifactoryUser")
password property("artifactoryPassword")
}
authentication {
basic(BasicAuthentication)
}
patternLayout {
artifact '/[organisation]/[module]/third-party/[revision](.[ext])'
}
metadataSources {
artifact()
}
}
}
configurations {
thirdPartyDependencies
}
dependencies {
thirdPartyDependencies "artifactory:test-generic:FreeImage#zip"
thirdPartyDependencies "artifactory:test-generic:GLEW#zip" // thirdPartyDependencies has been overwritten
}
task CopyThirdPartyDependencies(type: Copy) {
def thirdPartyDirectory = getProject().getRootDir().toString() + "/third-party/"
println(configurations.thirdPartyDependencies.resolvedConfiguration.resolvedArtifacts)
configurations.thirdPartyDependencies.resolvedConfiguration.resolvedArtifacts.each { artifact ->
copy {
from zipTree(artifact.getFile())
into thirdPartyDirectory
}
}
}
preBuild.dependsOn(CopyThirdPartyDependencies)

Android Studio Gradle: how to get variant/flavour in custom assets processor task

I have simple assets processing gradle task prepare_assets in Android Studio 3.1.3. It is linked to preBuild task:
preBuild.dependsOn(prepare_assets)
Now I have several flavors for different resolution versions and I want to let prepare_assets know what assets to process. Maybe I don't get the idea of Gradle, but I can't understand how to achieve this: I tried to set variable in config phase
applicationVariants.all { variant ->
buildType = variant.buildType.name // sets the current build type
}
but when I read variable in my task it always the same.
In flavours section declaration:
all { flavor ->
task("${flavor.name.capitalize()}_prepare_assets") {
println "*** conf TEST for ${flavor.name.capitalize()}***"
doLast {
println "*** action TEST ${flavor.name.capitalize()}***"
if (flavor.name.equals("fullhd"))
{
//WARNING: to call copy, javaexec closures here use project.copy and project.javaexec!
}
else
{
...
}
}
}
}
In the bottom of file adding dependenses for runtime created android tasks for all flavors:
tasks.whenTaskAdded { theTask ->
if (theTask.class.name.contains("AppPreBuildTask_Decorated"))
{
for (Iterator iterator = android.productFlavors.iterator(); iterator.hasNext();) {
String flv_name = iterator.next().name.capitalize();
if (theTask.name.contains(flv_name+"Debug") || theTask.name.contains(flv_name+"Release"))
theTask.dependsOn "${flv_name}_prepare_assets";
}
}
}

How to properly add `mock` package to Android Studio project?

I want Android Studio to recognize my mock folder by putting mock at the end of the package name while in the Android pane.
How can I achieve this?
I've tried different combinations of:
right-clicking -> new package
right-clicking -> new Java Folder
declaring sourceSets in Gradle like so:
sourceSets {
mock {
java.srcDir 'src/mock'
}
}
add Mock as a flavor
productFlavors {
mock {
applicationIdSuffix = ".mock"
dimension "default"
}
prod {
dimension "default"
}
}
// Remove mockRelease as it's not needed.
android.variantFilter { variant ->
if (variant.buildType.name == 'release'
&& variant.flavors[0].name == 'mock') {
variant.ignore = true
}
}

Why does Android Studio not run my generateConfig but command line does

I'm trying to generate identical assets for each of the subprojects in a project. Since exactly the same thing has to happen for each subproject, I'm trying to avoid having to add the generation code to each subproject.
What I've got so far is:
// in my outer build.gradle
allprojects { proj ->
gradle.projectsEvaluated {
def configTemplate = project.file('src/main/templates/configuration.json')
def android = proj.getProperties()['android']
if (configTemplate.exists() && android != null) {
task generateConfigFiles(type: ConfigureTask) {
template configTemplate
environments proj.rootProject.file('scripts/environments.json')
environmentName host
destination new File(project.buildDir, 'templates/configuration.json')
}
android.applicationVariants.all { variant ->
variant.mergeAssets.dependsOn generateConfigFiles
}
}
}
}
// each application build.gradle
android {
sourceSets {
main {
assets.srcDir 'build/templates'
}
}
}
This seems to work exactly as expected when I run gradle from the command line ./gradlew mergeQaDebugAssets runs the generateConfigFiles task before running mergeQaDebugAssets
If I attempt to run this from Android Studio using the gradle window, it never runs generateConfigFiles
I've also gotten it to work by just including the projectsEvaluated step in each of the project build.gradle files, but, as noted, I'm trying to avoid the code duplication and maintenance headaches that brings.

publish artifact overwrite other artifact in Gradle

I am experimenting with Gradle to build a few jars, rather than maintain a list of classes that hold EJBs so that I can deploy them separately I thought it might be neat to scan the classes when making the jar.
Rather than load the classes and use reflection to get the annotations I thought it may be simpler to scan the classes with asm, hence the chuncky ClassReader in one of the tasks.
I don't think this is the issue so can be ignored, basically I have 2 tasks that I use to define the contents of the jars, both report that different content is going into them via the eachFile print out, however when I look in the publish repository location both files and associated sha1 are identical.
Either Gradle is broken or, more likely, I've done something crazy but can't see what it is, can anyone help?
By the way if I disable the publish of either of the jar files the one that does get created is correct so I think it's something wrong with the publish rather than the jarring up, but could be wrong.
// ASM is used to interpret the class files, this avoids having to load all classes in the vm and use reflection
import org.objectweb.asm.*
task ejbJar(type: Jar) {
//outputs.upToDateWhen { false }
from "${project.buildDir}/classes/main"
eachFile { println "EJB server: ${name}" }
include getEjbClassFiles(project.buildDir)
}
task clientEjbJar(type: Jar) {
//outputs.upToDateWhen { false }
from "${project.buildDir}/classes/main/com/company/core/versioner"
eachFile { println "Client EJB ${name}" }
include '**/*'
}
artifacts {
archives clientEjbJar
archives ejbJar
}
String[] getEjbClassFiles(base) {
def includedFiles = []
def baseDir = project.file("${base}/classes/main")
def parentPath = baseDir.toPath()
if (baseDir.isDirectory()) {
baseDir.eachFileRecurse(groovy.io.FileType.FILES) { file ->
if(file.name.endsWith('.class')) {
//get hold of annotations in there --- org.objectweb.asm.Opcodes.ASM4
def reader = new ClassReader(file.bytes).accept(
new ClassVisitor(Opcodes.ASM4) {
public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
if(desc.equals("Ljavax/ejb/Stateless;") ||
desc.equals("Ljavax/ejb/Stateful;")) {
includedFiles += parentPath.relativize(file.toPath())
}
return null //no interest in actually visiting the annotation values
}
},
ClassReader.SKIP_DEBUG | ClassReader.EXPAND_FRAMES | ClassReader.SKIP_FRAMES | ClassReader.SKIP_CODE
)
}
}
}
return includedFiles
}
publishing {
publications {
mypub(IvyPublication) {
artifact(ejbJar) {
name 'ejb'
}
artifact(clientEjbJar) {
name 'client-ejb'
}
}
}
repositories {
ivy {
name 'personal'
url "${ant['developer.repository']}/"
layout 'pattern', {
artifact "[organisation]/[module]/[artifact]/[revision]/[type]/[artifact]-[revision].[ext]"
ivy "[organisation]/[module]/[type]/[revision]/[type]/[type]-[revision].[ext]"
}
}
}
}
I did break the thing down into a simpler form as I thought it may be a Gradle bug.
The simplified form was:
apply plugin: 'java'
apply plugin: 'ivy-publish'
task bigJar(type: Jar) {
from "${rootDir}/src/main/resources"
include '**/*'
}
task smallJar(type: Jar) {
from "${rootDir}/src/main/resources/A/B"
include '**/*'
}
group 'ICantBeEmpty'
artifacts {
archives bigJar
archives smallJar
}
publishing {
publications {
mypub(IvyPublication) {
artifact(bigJar) { name 'biggie' }
artifact(smallJar) { name 'smallie' }
}
repositories {
ivy {
name 'personal'
url "c:/temp/gradletest"
layout 'pattern', {
artifact "[organisation]/[module]/[artifact]/[revision]/[type]/[artifact]-[revision].[ext]"
ivy "[organisation]/[module]/[type]/[revision]/[type]/[type]-[revision].[ext]"
}
}
}
}
}
This results in 2 files in c:/temp/gradletest/ICantBeEmpty/report-bug/biggie/unspecified/biggie-unspecified.jar and c:/temp/gradletest/ICantBeEmpty/report-bug/smallie/unspecified/smallie-unspecified.jar
Both of these files are identical, however I think I know why see my later answer.
Whilst looking at some configurations I noticed some odd behaviour that led me to a resolution of this issue, and it is a Gradle bug.
In my build I had a scratch task doing
configurations.archives.artifacts.each { println it }
This gave me 5 different lines output, however changing it to this
configurations.archives.artifacts.each { println it.file }
produced the same filename 5 times.
It turns out this is related to my issue, although the artifacts are there as separate entities the name used to uniquely identify them was the same so the same file was always chosen during a publish. The name of the artifacts is given by ${baseName}-${appendix}-${version}-${classifier}.${extension} by default in the java plugin. This means that if neither appendix or classifier is specified then the artifact will have the same name.
I tested this using the above sample code by adding an appendix name
task bigJar(type: Jar) {
appendix = 'big'
from "${rootDir}/src/main/resources"
include '**/*'
}
task smallJar(type: Jar) {
appendix = 'small'
from "${rootDir}/src/main/resources/A/B"
include '**/*'
}
Using this rather than the code from the question produces 2 different jars.
It's not a complete answer but is a good enough work around, if I add a new publication definition I can publish the artifacts that I want to to the location that I want, the only downside is that it will create another gradle task which isn't ideal.
publications {
mypub(IvyPublication) {
artifact(ejbJar) {
name 'ejb'
}
}
newpub(IvyPublication) {
artifact(clientEjbJar) {
name 'client-ejb'
}
}
}
The above answer works in the short term, however does reveal yet another short coming in the Gradle world enter link description here
Not sure Gradle is all it could be at the moment, and so far no one has answered my questions so maybe it's not that actively developed!!
I'm no expert in this part of Gradle, but the functionality you are using is marked as "incubating"; you are using the new publishing feature which might or might not be complete. Perhaps you should use the old way of doing things. You also seem to be mixing both ways by using the artifacts closure.

Resources