Gradle: adding assets folder dynamically - android

I have a project with many flavors, for each flavor I have a configuration file which specifies which assets to include.
here is what I have so far:
applicationVariants.all { variant ->
variant.outputs.each { output ->
def config = getFlavourConfig(variant.getFlavorName());
if(config!=null) {
if(config.get("font-assets") != null ) {
config.get("font-assets").each {
println it
/*this is not working ->*/ variant.assets.srcDirs += ['src/extensions/assets/'+it]
}
}
}
}
}
getFlavourConfig parses a gson configuration file. The json has ["font-assets":["fontfolder1","fontfolder2"]
In this line
variant.assets.srcDirs += ['src/extensions/assets/'+it]
I want to add the assets dir to the flavor. Any ideas?

You might be overthinking the work required. Just create the productFlavor then define the flavor's assets.srcDirs. Now we can sit back and let Android's gradle plugin do all the work.
android {
//...
productFlavors {
blah {}
more {}
}
sourceSets {
blah {
assets.srcDirs = files(getFlavorConfig(it.name))
}
more {
assets.srcDirs = files(getFlavorConfig(it.name))
}
}
}
// simplified method for example
def getFlavorConfig(String str) {
return ["$projectDir.absolutePath/src/extensions/assets/first", "$projectDir.absolutePath/src/extensions/assets/second"];
}
After the build our files will be where we expect:
$ ls app/build/intermediates/assets/blah/debug/
hello.txt world.txt
$ ls app/build/intermediates/assets/more/debug/
hello.txt world.txt
$ ls app/build/intermediates/assets/blah/release/
hello.txt world.txt
$ ls app/build/intermediates/assets/more/release/
hello.txt world.txt

I ended up using one flavour at a build time and used the following line:
android.sourceSets.main.assets.srcDirs += ['src/extensions/assets/'+it]

Related

Could not get unknown property 'apkNames' in android gradle plugin 4.1.0

I am using this code in build.gradle:
android {
applicationVariants.all { variant ->
variant.packageApplicationProvider.configure { packageApplicationTask ->
doLast {
packageApplicationTask.apkNames.each { apkName ->
def apkDir = "./build/outputs/apk/${flavorName}/${buildType.name}"
def apkDestName = apkName.replace("app-", "stickerly-android-" + variant.versionName + "-").replace(".apk", "-" + getDate() + ".apk")
println "#####Rename ${variant.name} Apk File"
copy {
from("$apkDir/$apkName").into(apkDir).rename { String fileName -> apkDestName }
}
println "#####Copy mapping File"
def mappingDir = "./build/outputs/mapping/${flavorName}${buildType.name.capitalize()}"
copy {
from("$mappingDir/mapping.txt").into(mappingDir).rename {
"mapping-stickerly-${variant.versionName}.txt"
}
}
}
}
}
}
}
With this code I rename apk file and copy mapping file. I worked in android gradle plugin 4.0, but it does not work in 4.1 with this message
Where:
Script '/Users/snow/workspace/stickerly-android/app/build-android-extra.gradle' line: 5
What went wrong:
Execution failed for task ':app:packageExternalArm8Debug'.
Could not get unknown property 'apkNames' for task ':app:packageExternalArm8Debug' of type com.android.build.gradle.tasks.PackageApplication.
I think API has changed but I can not find any documents. Someone can help me?
Thanks.
property apkNames is removed in AGP 4.1
you can try this
gradle.taskGraph.afterTask { Task task, TaskState state ->
if (!state.failure
&& task.project.name == project.name
&& task.name.startsWith("package")
&& !task.name.endsWith("UnitTest")) {
def outputDir = task.outputDirectory.getAsFile().get()
task.variantOutputs.getOrNull()?.each { variant ->
println "" + outputDir + "/" + variant.outputFileName.get()
}
}
}
add this at the end of your build.gradle file.
change println to whatever logic you want.
by the way, if you want to check the properties you may use, just add gradle plugin as a dependency of your project, click Sync in Android Stuido, then you can find it in External Librarys (key map: Command + 1, and switch to project view).
like this
dependencies {
implementation 'com.android.tools.build:gradle:4.1.0'
}
and these properties and tasks are intentionally invisible in lib com.android.tools.build:gradle-api, modifications in future releases are expected.

Copy file based on flavor at the start of Android gradle build

I have two flavors in the gradle file for an Android app:
productFlavors {
production { }
devel { }
}
I have a configuration file that I need to copy to the app/ directory before any other tasks run when building the project. The is a configuration file per flavor, i.e.:
etc/configuration-production.json
etc/configuration-devel.json
When building devel I need to do essentially this:
cp etc/configuration-devel.json app/configuration.json
When building production:
cp etc/configuration-production.json app/configuration.json
How do I automate this in gradle? This copy needs to happen first and foremost when executing a build since some of the tasks need that app/configuration.json file to be there.
I tried:
task copyConfig(type: Copy) {
from "etc/configuration-${Flavor.name}.json"
into "app/configuration.json"
}
build.dependsOn copyConfig
But didn't work. The copyConfig task didn't run.
You can add the following to your app build.gradle for copying your file from etc/configuration-XXX.json to app/configuration.json in the first statement of the respective tasks assembleDevel.* & assembleProduction.* :
def cp(copyType) {
println "copying " + "../etc/configuration-" + copyType + ".json"
copy {
from "../etc/configuration-" + copyType + ".json"
into '.'
rename { String fileName ->
fileName.replace("configuration-" + copyType + ".json", "configuration.json")
}
}
}
tasks.whenTaskAdded { task ->
if (task.name ==~ /assembleDevel.*/) {
task.doFirst() {
cp("devel")
}
} else if (task.name ==~ /assembleProduction.*/) {
task.doFirst() {
cp("production")
}
}
}
This is the required configuration :
app/
├── build.gradle
etc/
├── configuration-production.json
└── configuration-devel.json
If assembleDevel.*/assembleProduction.* are not the tasks you are looking for, you can replace them with for instance : prepareDevel.*Dependencies/prepareProduction.*Dependencies

Specifying deployed filename for AAR artifact in Android library project

I'm trying to configure an Android library project to deploy multiple artifacts to a locally hosted Maven repository. I've gotten far enough such that both artifacts have their own POM generated, and it gets deployed properly to the repo, with the following script:
android {
// Publish both debug and release
publishNonDefault true
}
uploadArchives {
repositories.mavenDeployer {
def majorVersion = 1
def minorVersion = 1
def buildVersion = project.properties.get('RELEASE', '0').toInteger()
addFilter('release') { artifact, file ->
file.name.contains('release')
}
addFilter('debug') { artifact, file ->
file.name.contains('debug')
}
activePomFilters.each { filter ->
pom(filter.name) {
groupId = 'com.redacted'
artifactId = 'redacted'
packaging = 'aar'
version = "${majorVersion}.${minorVersion}.${buildVersion}"
if (!project.hasProperty('RELEASE')) {
version += "-SNAPSHOT"
}
if (filter.name == 'debug') {
artifactId += '-debug'
}
}
}
}
}
The expected delivery is:
com/
redacted/
redacted/
1.1.0-SNAPSHOT/
redacted-debug/
1.1.0-SNAPSHOT/
Which happens as expected, but it seems to publish the artifacts with an additional suffix (which breaks the dependency discovery), and I cannot figure out where it is coming from, or how to change it. What I see is:
com/redacted/redacted/1.1.0-SNAPSHOT/
redacted-1.1.0-20150717.213849-1-release.aar
redacted-1.1.0-20150717.213849-1-release.aar.md5
redacted-1.1.0-20150717.213849-1-release.aar.sha1
redacted-1.1.0-20150717.213849-1.pom
redacted-1.1.0-20150717.213849-1.pom.md5
redacted-1.1.0-20150717.213849-1.pom.sha1
For some reason, it's appending the date, as well as a -release suffix to only the AAR-related files, but not the POM files. If I manually rename these files, everything works as expected. For example, this is what I expect to be output:
com/redacted/redacted/1.1.0-SNAPSHOT/
redacted-1.1.0-20150717.213849-1.aar
redacted-1.1.0-20150717.213849-1.aar.md5
redacted-1.1.0-20150717.213849-1.aar.sha1
redacted-1.1.0-20150717.213849-1.pom
redacted-1.1.0-20150717.213849-1.pom.md5
redacted-1.1.0-20150717.213849-1.pom.sha1
How can I change how these files are delivered?
What you are running in to is this (emphasis mine):
Important: When enabling publishing of non default, the Maven publishing plugin will publish these additional variants as extra packages (with classifier). This means that this is not really compatible with publishing to a maven repository. You should either publish a single variant to a repository OR enable all config publishing for inter-project dependencies.
See the documentation: http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Library-Publication
The suffixes release and debug that you see are the classifiers introduced by enabling publishing of non-default artifacts. The <artifact> elements in build/ivy.xml, which is used as the basis for the Maven configuration, contain these classifiers.
Iterating over the artifacts in the configurations and removing the classifier does not work. Although setting the classifier is allowed, its original value is kept.
But what does work is wrapping the original artifacts. The wrapper will always return null for a classifier. This does result in the release and debug artifact having the same fully-qualified ID (= name + classifier), which results in only one artifact being published. This can be fixed by using a different name for debug artifacts:
class UnclassifiedPublishArtifact implements PublishArtifact {
private PublishArtifact delegatee;
private boolean isDebugArtifact;
UnclassifiedPublishArtifact(PublishArtifact delegatee, isDebugArtifact) {
this.delegatee = delegatee
this.isDebugArtifact = isDebugArtifact
}
#Override
String getName() {
return delegatee.name + (isDebugArtifact ? '-debug' : '')
}
#Override
String getExtension() {
return delegatee.extension
}
#Override
String getType() {
return delegatee.type
}
#Override
String getClassifier() {
return null
}
#Override
File getFile() {
return delegatee.file
}
#Override
Date getDate() {
return delegatee.date
}
#Override
TaskDependency getBuildDependencies() {
return delegatee.buildDependencies
}
}
project.afterEvaluate {
configurations.each { configuration ->
def artifacts = configuration.artifacts
if (!artifacts.isEmpty()) {
def unclassifiedArtifacts = []
unclassifiedArtifacts.addAll(artifacts.collect { classifiedArtifact ->
new UnclassifiedPublishArtifact(classifiedArtifact, classifiedArtifact.classifier == 'debug')
})
artifacts.clear()
artifacts.addAll(unclassifiedArtifacts)
}
}
}
I can't quite understand from the documentation what the consequences are for project dependencies, so you should check if these still work.

Package name does not correspond to the file path - Gradle configuration

I'm trying migrating a normal Android Studio (IntelliJ) project to Gradle project recently. And currently I'm encounter a problem: IntelliJ gives me a warning on the beginning of every file says that my 'package name does not correspond to the file path'. e.g.
The first line of my some/prefixes/a/b/c/d/E.java is:
package a.b.c.d;
....
IntelliJ thinks the package name should be 'c.d' instead of 'a.b.c.d'. Because I set
SourceSets {
main.java.srcDirs = ["some/prefixes/a/b"]
}
in the module's build.gradle.
I know I could do the change below to make IntelliJ happy:
SourceSets {
main.java.srcDirs = ['some/prefixes']
}
But I can't do that because there're huge numbers of projects under 'some/prefixes' and I definitely don't want to introduce all of them into this module.
I used to add a packagePrefix="a.b" in my 'module.iml' in my original Android studio project and it works well:
https://www.jetbrains.com/idea/help/configuring-content-roots.html#d2814695e312
But I don't know how to accomplish similar fix after migrating to Gradle project.
I end up to write a task for gradle.
The task add the famous packagePrefix to the *.iml file.
This solution only work for intelliJ, I hope someone have a better solution.
task addPackagePrefix << {
println 'addPackagePrefix'
def imlFile = file(MODULE_NAME+".iml")
if (!imlFile.exists()) {
println 'no module find '
return
}
def parsedXml = (new XmlParser()).parse(imlFile)
if(parsedXml.component[1] && parsedXml.component[1].content){
parsedXml.component[1].content.findAll { Node node ->
node.sourceFolder.findAll { Node s ->
def url = s.attribute("url").toString()
if (url.endsWith(SRC_DIR)) {
println 'Node founded '
def attr = s.attribute('packagePrefix')
if (attr == null) {
// add prefix
println 'Adding package prefix'
s.attributes().put('packagePrefix', PACKAGE_NAME)
println s.toString()
// writing
def writer = new StringWriter()
new XmlNodePrinter(new PrintWriter(writer)).print(parsedXml)
imlFile.text = writer.toString()
}
}
}
}
}

Android+Gradle: list directories into a file

I'm trying to convert a task I have in an ANT build to Gradle:
<target name="index-assets" depends="copy-assets">
<path id="assets.path">
<!-- contexts (and surplus) -->
<dirset id="assets.dirset" dir="assets/" defaultexcludes="yes"/>
<!-- assets -->
<fileset id="assets.fileset" dir="assets/" includes="**" excludes="asset.index" defaultexcludes="yes"/>
</path>
<pathconvert pathsep="${line.separator}" property="assets" refid="assets.path" dirsep="/">
<mapper>
<globmapper from="${basedir}/assets/*" to="*" handledirsep="yes"/>
</mapper>
</pathconvert>
<echo file="assets/asset.index">${assets}</echo>
</target>
<target name="-pre-build" depends="index-assets"/>
I guess I'm still not completely grasping basic Gradle concepts, but here's what I tried:
task indexAssets << {
def assets = file("assets")
def contexts = files(assets)
inputs.file(assets)
outputs.file("assets/assets-gradle.index")
def tree = fileTree(dir: 'assets', include: ['**/*'], exclude: ['**/.svn/**', 'asset.index'])
contexts.collect { relativePath(it) }.sort().each { println it }
tree.collect { relativePath(it) }.sort().each { println it }
}
The tree is fine, but contains only file (leaf) paths
I just can't seem to get the simple clean list of directories (contexts) though. I tried several other variants (tree, include/exclude), but I either get a single file in that directory, the directory name itself or nothing. I just want a simple list of directories found in the 'assets' dir.
For now I'm just trying to print the paths, but I'd also like to know the proper way to later write these into a file (like ANT's echo file).
Update:
This groovy snippet seems to do that part (+ svn filter), but I'd rather find a more "Gradley" way of doing this task. It runs in a context of a build variant later as a pre-build dependency. (NOTE: I had to specify the 'Project' as part of the path in this hack since I guess I'm not in that project context for the task?)
def list = []
def dir = new File("Project/assets")
dir.eachDirMatch (~/^(?!\.svn).*/) { file ->
list << file
}
list.each {
println it.name
}
Ok, This is the cleanest way I found so far.
I'd still be happier if FileTree collect patterns were able to do this, but this is almost as concise, and maybe even slightly more explicit and self-explanatory.
The key is using fileTree.visit with relativePath (see below)
As an extra, I've added the task context and adding dependency on the relevant build step, as well as writing the actual assets index file per variant build.
Why is this required, you ask? Since AssetManager is very slow, see here and the answer thread that follows (which triggered the original ANT target).
android {
task indexAssets {
description 'Index Build Variant assets for faster lookup by AssetManager later'
ext.assetsSrcDir = file( "${projectDir}/src/main/assets" )
ext.assetsBuildDir = file( "${buildDir}/assets" )
inputs.dir assetsSrcDir
//outputs.dir assetsBuildDir
doLast {
android.applicationVariants.each { target ->
ext.variantPath = "${buildDir.name}/assets/${target.dirName}"
println "copyAssetRec:${target.dirName} -> ${ext.variantPath}"
def relativeVariantAssetPath = projectDir.name.toString() + "/" + ext.variantPath.toString()
def assetIndexFile = new File(relativeVariantAssetPath +"/assets.index")
def contents = ""
def tree = fileTree(dir: "${ext.variantPath}", exclude: ['**/.svn/**', '*.index'])
tree.visit { fileDetails ->
contents += "${fileDetails.relativePath}" + "\n"
}
assetIndexFile.write contents
}
}
}
indexAssets.dependsOn {
tasks.matching { task -> task.name.startsWith( 'merge' ) && task.name.endsWith( 'Assets' ) }
}
tasks.withType( Compile ) {
compileTask -> compileTask.dependsOn indexAssets
}
...
}

Categories

Resources