Replacing a string within a .class file on build with Gradle Kotlin - java

I've got a Java application with the following variable:
static final String MAGIC_VERSION = "SET_BY_MAGIC";
Using gradle, I want to replace this on runtime with my gradle project version, however I am having issues replacing the string.
This is my gradle file:
tasks {
processResources {
duplicatesStrategy = DuplicatesStrategy.INCLUDE
from(sourceSets["main"].java) {
filter {
it.replace("SET_BY_MAGIC", version as String)
}
}
}
}
What am I doing wrong?

Related

run gradle test task twice for 2 java versions

I want to run gradle tests as part of my build but twice, once using java 8 and then using java 11.
i have tried following,but get issues when I run test using gradle command
test {
dependsOn ['testsOn8','testsOn11']
}
task('testsOn8', type: Test) {
javaLauncher = javaToolchains.launcherFor {
languageVersion = JavaLanguageVersion.of(8)
}
}
task('testsOn11', type: Test) {
javaLauncher = javaToolchains.launcherFor {
languageVersion = JavaLanguageVersion.of(11)
}
}

I can't open my class just find a BuildConfig file

For unknown reason a java file in an android studio project
has a sub arrow to BuildConfig class from an other package of an other project
/**
* Automatically generated file. DO NOT MODIFY
*/
package alsayed.aly.maintenanceplaner;
public final class BuildConfig {
public static final boolean DEBUG = Boolean.parseBoolean("true");
public static final String APPLICATION_ID = "alsayed.aly.maintenanceplaner";
public static final String BUILD_TYPE = "debug";
public static final String FLAVOR = "";
public static final int VERSION_CODE = 1;
public static final String VERSION_NAME = "1.0";
}
I tried to revert it but it is not working as the file just created now.
At build time, Gradle generates the BuildConfig class so your app code can inspect information about the current build.
You can also add custom fields to the BuildConfig class from your Gradle build configuration file using the buildConfigField() method and access those values in your app's runtime code. Likewise, you can add app resource values with resValue().
android {
...
buildTypes {
release {
// These values are defined only for the release build, which
// is typically used for full builds and continuous builds.
buildConfigField("String", "BUILD_TIME", "\"${minutesSinceEpoch}\"")
resValue("string", "build_time", "${minutesSinceEpoch}")
...
}
debug {
// Use static values for incremental builds to ensure that
// resource files and BuildConfig aren't rebuilt with each run.
// If they were dynamic, they would prevent certain benefits of
// Instant Run as well as Gradle UP-TO-DATE checks.
buildConfigField("String", "BUILD_TIME", "\"0\"")
resValue("string", "build_time", "0")
}
}
}
In your app code, you can access the properties as follows:
Log.i(TAG, BuildConfig.BUILD_TIME);
Log.i(TAG, getString(R.string.build_time));

Is it possible to swap out the JavaExecAction Gradle is using to run Java?

Java fails to launch when the classpath is too long. The length limit is particularly short on Windows.
Gradle seem uninterested in fixing the issue on their side (even though it's sort of their responsibility since they're the ones launching Java), so we ended up substituting the JavaExec task out with our own alternative.
The alternative works like this:
public class WorkingJavaExec extends JavaExec {
private static final String MATCH_CHUNKS_OF_70_CHARACTERS =
"(?<=\\G.{70})";
private final Logger logger = LoggerFactory.getLogger(getClass());
#Override
public void exec() {
FileCollection oldClasspath = getClasspath();
File jarFile = null;
try {
if (!oldClasspath.isEmpty()) {
try {
jarFile =
toJarWithClasspath(oldClasspath.getFiles());
setClasspath(getProject().files(jarFile));
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
super.exec();
} finally {
setClasspath(oldClasspath);
if (jarFile != null) {
try {
Files.delete(jarFile.toPath());
} catch (Exception e) {
logger.warn("Couldn't delete: " + jarFile, e);
}
}
}
}
public static File toJarWithClasspath(Set<File> files)
throws IOException {
File jarFile = File.createTempFile("long-classpath", ".jar");
try (ZipOutputStream zip =
new ZipOutputStream(new FileOutputStream(jarFile))) {
zip.putNextEntry(new ZipEntry("META-INF/MANIFEST.MF"));
try (PrintWriter writer =
new PrintWriter(
new OutputStreamWriter(
zip, StandardCharsets.UTF_8))) {
writer.println("Manifest-Version: 1.0");
String classPath = files.stream().map(
file -> file.toURI().toString())
.collect(Collectors.joining(" "));
String classPathEntry = "Class-Path: " + classPath;
writer.println(Arrays.stream(
classPathEntry.split(MATCH_CHUNKS_OF_70_CHARACTERS))
.collect(Collectors.joining("\n ")));
}
}
return jarFile;
}
}
Using this is cumbersome, though, because everywhere someone might run JavaExec, I have to replace it with WorkingJavaExec. New developers also don't know that there is this pitfall in Gradle in the first place, so they don't even know it's something they have to work around.
In reading the internals of Gradle, I saw that JavaExec internally uses a JavaExecAction to do the actual exec.
I thought that maybe by replacing this, we could fix the problem as if Gradle had fixed it themselves, and maybe it would then also apply to other tasks, such as Test. But I haven't been able to find any examples anywhere. (Even in other large projects, which you would expect to have hit the same issue!)
Is it possible to substitute JavaExecAction, and if so, how?
I'm not sure you can "substitute" JavaExecAction because it is set during JavaExec task instanciation, but I think you can solve this problem in a nicer way, using a custom Plugin as follow:
class FixClasspathLimitPlugin implements Plugin<Project> {
#Override
void apply(Project project) {
// after project has been evaluated, hack into all tasks of type JavaExec declared.
project.afterEvaluate {
project.tasks.stream().filter { task -> task instanceof JavaExec }.forEach {
println "Reconfiguring classpath for : $it"
JavaExec javaExec = (JavaExec) it;
FileCollection oldClasspath = javaExec.getClasspath()
// insert an Action at first position, that will change classpath
javaExec.doFirst { task ->
((JavaExec) task).setClasspath(getProject().files(toJarWithClasspath(oldClasspath.getFiles())));
}
// optional - reset old classpath
javaExec.doLast { task ->
((JavaExec) task).setClasspath(oldClasspath)
}
}
}
}
public static File toJarWithClasspath(Set<File> files)
throws Exception {
// same method implementation as given in your question
}
This way, you won't have to replace JavaExec in all build scripts written by your team, you will only have to ensure that these scripts apply your plugin.
And if you use a custom distribution of Gradle and use wrapper in you enterprise, you can even include this plugin in this distribution as an Init Script, as explained here: https://docs.gradle.org/current/userguide/init_scripts.html#sec:using_an_init_script
Put a file that ends with .gradle in the GRADLE_HOME/init.d/ directory, in the Gradle distribution. This allows you to package up a custom Gradle distribution containing some custom build logic and plugins. You can combine this with the Gradle wrapper as a way to make custom logic available to all builds in your enterprise.
This way, the plugin will be applied in a "transparent" way.
Concerning the Test task: it does not use JavaExecAction, I think, but a similar solution could be applied, using a similar plugin.
You can use the jar task to add the class path to the manifest for you:
jar {
baseName = "my-app"
version = "1.0.0"
manifest {
attributes("Class-Path": configurations.compile.collect { it.getName() }.join(' '))
}
}
And then you can reference that jar when launching:
task run(type:JavaExec) {
classpath = jar.outputs.files
main = "myapp.MainClass"
}
That works around the command line path limit. You might also want to copy the dependency JARs to the output folder, so they will be available at runtime.
task copyDependencies(type: Copy, dependsOn: [ "build" ]) {
from configurations.runtime
into "./build/libs"
}
build.finalizedBy(copyDependencies)
Helpful?

gradle :compilePlayBinaryPlayRoutes is not generating all imports

I am switching a play app from SBT to gradle and the routes and reverse routes scala files are not being generated with
import _root_.play.libs.F
which is causing
build/src/play/binary/routesScalaSources/controllers/ReverseRoutes.scala:260: not found: value F
def validate(accountId:F.Option[java.lang.Long]): Call = {
I am using gradle 3.5, play: '2.4.8', scala: '2.11' and java: '1.8'. Does anyone know if there is a compatibility issue with or some other known issue that is stopping the import from being added to the generated scala file?
** EDIT **
I found this class RoutesCompile with a method additionalImports but I can't find how to use it in the build.gradle file. (I am super new to gradle, more of a maven guy)
** EDIT 2 **
Based on the Javadoc in the RoutesCompile class it seems like I should be adding it to the model like this:
model {
components {
play {
platform play: '2.4.8', scala: '2.11', java: '1.8'
injectedRoutesGenerator = true
additionalImports = ['play.libs.F']
sources {
twirlTemplates {
defaultImports = TwirlImports.JAVA
source.srcDir "assets/views"
source.exclude "assets/stylesheets"
}
}
}
}
}
But I get the following error:
> Exception thrown while executing model rule: play { ... } # build.gradle line 147, column 9
> No such property: additionalImports for class: org.gradle.play.PlayApplicationSpec
I have finally found the answer to my issue. I needed to get the task and add the additional import that way.
model {
components {
play {
platform play: '2.4.8', scala: '2.11', java: '1.8'
injectedRoutesGenerator = true
tasks.withType(RoutesCompile) {
additionalImports = ['play.libs.F']
}
sources {
twirlTemplates {
defaultImports = TwirlImports.JAVA
source.srcDir "assets/views"
source.exclude "assets/stylesheets"
}
}
}
}
}

Gradle: How to create multiple jar?

I'm new to Gradle and Groovy, and I'd hope there would be something to solve my problem.
I have several packages, each of which needs to be compiled into one jar.
One solution I've found is to create multiple tasks that are of Jar type, but I'd like to avoid copy/paste and end up with a giant gradle file whenever I add a new package to my project.
My current implementation is to have multiple jar tasks, like this one :
task jarFoo(type: Jar) {
baseName = "foo"
version = "1.0.0"
String className = baseName.capitalize()
from(sourceSets.main.output) {
include "$baseName/**"
}
from {
configurations.compile.collect {
it.isDirectory() ? it : zipTree(it)
}
}
manifest {
attributes "Implementation-Title": "$className",
"Implementation-Version": "$version",
"Main-Class": "$baseName.$className"
}
}
It works like a charm, however I add packages very often and I will end up with a lot of packages, therefore a lot of tasks and a lot of copied/pasted code.
After fiddling with build.gradle file, I've found that I needed to extend from Jar in order to get a jar created.
So here's the code for the class so far :
class JarTask extends Jar {
String jarName = "default"
String jarVersion = "1.0.0"
#TaskAction
def jar() {
baseName = jarName
version = jarVersion
String className = baseName.capitalize()
// Thanks Opal for reminding that sourceSets
// is defined in project.
from(project.sourceSets.main.output) {
include "$baseName/**"
}
from {
configurations.compile.collect {
it.isDirectory() ? it : zipTree(it)
}
}
manifest {
attributes "Implementation-Title": "$className",
"Implementation-Version": "$version",
"Main-Class": "$baseName.$className"
}
}
}
task jarFoo(type: JarTask) {
jarName = "foo"
}
task jarBar(type: JarTask) {
jarName = "bar"
jarVersion = "1.2.42"
}
The problem is that the jar that is created ignores basically everything in the method: it contains only a MANIFEST.MF containing one line with the manifest version and is given the name of the project, not the name given in task. Nothing else.
If needed, you can find the code online in my GitHub repo (mainly in French).
Any idea would be truly appreciated!
Here is another easier option that allows to pass parameters. I found the inspiration on this topic : https://discuss.gradle.org/t/passing-arguments-to-a-task/8427/20, which sounds exactly like what I was trying to do.
Here we basically define a method that returns a task, given some parameters. The rest is just testing if a version is given, or the code already given in question and adapted with #Opal great help.
It is sufficient to include the new builds in the artifacts block to make tasks available. Then, just run gradle jarqcm to build a single package or gradle assemble to compile everything.
apply plugin: "idea"
apply plugin: "java"
repositories {
mavenCentral()
}
dependencies {
compile "com.intellij:forms_rt:7.0.3"
runtime "com.intellij:forms_rt:7.0.3"
}
def jarPackage(artifactName, artifactVersion) {
if (artifactVersion == "" || artifactVersion == null) {
artifactVersion = "1.0.0"
}
return tasks.create("jar${artifactName}", Jar) {
baseName = artifactName
version = artifactVersion
String className = baseName.capitalize()
from(sourceSets.main.output) {
include "$baseName/**"
}
from {
configurations.compile.collect {
it.isDirectory() ? it : zipTree(it)
}
}
manifest {
attributes "Implementation-Title": "$className",
"Implementation-Version": "$version",
"Main-Class": "$baseName.$className"
}
}
}
artifacts {
archives jarPackage("aleatoire", ""), jarPackage("calculatrice", "1.2.3"), jarPackage("copier", ""),
jarPackage("qcm", "1.0.0")
}
After you edited the question is easy. There's no property sourceSets for the given task (Jar in this case). sourceSets are defined on Project and every task that extends DefaultTask inherits project field from it's parent. So you just need:
from(project.sourceSets.main.output) {
include "$baseName/**"
}
ANSWER
I hope you understand the difference between task configuration and execution phase. This is the problem that occurs here. Basically you extended Jar task which as all tasks of type Copy is not designed to be extended - see here. In task action you configure the artifacts to be copied but.. there's too late for configuration - it's execution phase. To solve the problem task rules may be used. I've modified the script and it's:
apply plugin: "idea"
apply plugin: "java"
repositories {
mavenCentral()
}
dependencies {
compile "com.intellij:forms_rt:7.0.3"
runtime "com.intellij:forms_rt:7.0.3"
}
tasks.addRule('Pattern: build<ID>') { String taskName ->
if (taskName.startsWith('build')) {
task(taskName, type: Jar) {
baseName = taskName - 'build'
version = '1.0.0'
String className = baseName.capitalize()
from(sourceSets.main.output) {
include "$baseName/**"
}
from {
configurations.compile.collect {
it.isDirectory() ? it : zipTree(it)
}
}
manifest {
attributes "Implementation-Title": "$className",
"Implementation-Version": "$version",
"Main-Class": "$baseName.$className"
}
}
}
}
artifacts {
archives project.tasks['buildqcm'], project.tasks['buildlistage'] //etc
}
and should invoked simply with gradle buildlistage buildqcm. You can make additional validation to check if <ID> passed is on the list of packages e.g. Hope that helps and sorry for having to wait so long :/

Categories