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
}
...
}
Related
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.
I have 5 different apps in my monorepo, the build.gradle for each of these apps contains a nearly indentical task, which along with an upload script is in charge of uploading files matching to our Jenkins file server.
we can call this task releaseBuilds()
This code is in a file called artifactUploads.gradle, this file also contains a custom plugin called UploadPlugin. Now my concern is with the System.out.println() line in the task.
apply plugin: UploadPlugin
task copyReleaseBuilds() {
doLast {
def dir = project.file('build/outputs/apk')
dir.eachFileRecurse(FILES) { file ->
if (file.getName().contains(".apk") && file.getName().contains("release")) {
System.out.println("uploading ${file.getName()}")
def rootDir = project.getRootDir().path
def filePath = file.path
def fileName = file.name.replace('app-', '')
exec {
workingDir rootDir
commandLine "./uploadBuild.sh", filePath, "$dirPath" + fileName
}.assertNormalExitValue()
}
}
}
}
class UploadPlugin implements Plugin<Project> {
void apply(Project project) {
//Create container instance for config object
//plugin stuff
NamedDomainObjectContainer<Config> configContainer =
project.container(Config)
project.extensions.add('directory', configContainer)
project.task('uploadConfig') << {
def uploadConfig = project.extensions.getByName('directory')
}
}
class Config{
String name
String dirPath
Config(final String name){
this.name = name
}
}
In the build.gradle file for each of my apps/projects. I have the line
apply from: "${rootDir}/artifactUploads.gradle
along with
directory{
name{
dirPath = "hca/"
println "stuff"
}
}
directory is some object that is created in my CustomPlugin.
Now when I build the line println "Stuff" is output to the gradle log, however I can not find any console outputs that are put in artifactUploads.gradle which contains my CustomPlugin and the task that all my apps share.
From what I understand, since each of my apps build.gradle has
apply from: artifactUploads.gradle, each app should now run the copyReleaseBuilds() task, with the custom parameter dirPath which is set in the DSL part of each app build.gradle.
Is there another reason the build would not be hitting that System.out.println()? I'm very new to gradle in general so thanks for your help
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
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]
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()
}
}
}
}
}