Get a project source dir in a Gradle plugin - java

First of all sorry if I've missed this in the documentation somewhere.
I'm trying to write a Gradle plugin in Java.
More specifically I need to do this:
public class MyPlugin implements Plugin<Project>{
#Override
public void apply(Project project) {
project.getSourceDir();
}
}
In other words, how can I, if possible, get the directory where the sources of the project my plugin is run over reside?
Am I correct in my assumption that when the apply method gets called Gradle will already know where the sources are and this location can not change further on?
Thanks.

Yes because the plugin is applied to a Project per your parameter. If you "apply" it to two projects then it creates two instances of the plugin and pass your project objects to the apply method.

Look through the Gradle user guide.
A project has a list of "sourceSet" objects, which you get to with "project.sourceSets". Each sourceSet has a set of properties.
It's good to browse through The DSL reference to see what properties are available. Look for "SourceSet".

Related

Question about groovy scripts recognition by IntelliJ IDEA

I guess by asking this I might sound a bit illegible, but I'm still unsure as to how to approach the problem.
In my spring project (not really my, work stuff) I've got some groovy scripts which are initially treated as resources, yet in reality they are rather the "source code" which is compiled not during the gradle assembly of the project but during the runtime by the application itself. And everything's fine with that.
The problem is that the IDE doesn't treat the groovy file properly. Dumb example to somehow describe what I mean:
import myproject.example.blabla
import groovy.transform.CompileStatic
#CompileStatic
class SomeClass1 implements SomeClass2 {
private final SomeClass2 someName1
SomeClass1() {
someName1 = new something
}
#Override
String getSmth() {
return someName1.getSmth()
}
}
The problems:
when I make "command + left_click" on SomeClass2, it says Cannot find declaration to go to, but when I press "command + O" it finds the file because it actually exists
.getSmth() is red, because Cannot resolve symbol
So it seems that I need to somehow show the dependencies via gradle to IDE only. Like, somehow specify the dependencies explicitly for IntelliJ IDEA so that it would understand that it is a source code as well and stop underlining everything with red.
Such files must be located in the module's Source Root directory for the IDE to recognize them as sources and so that navigation would also work.
In a Gradle-based project IDE configures Source Roots automatically based on the Gradle's Source Sets configuration. For each Gradle source set IDE creates a module with one Source Root directory.
So you must configure Gradle to create source set for the directories where these files are located: add them into default sources sets or create a custom source set for them.

How can I have separate debug and release versions of gradle project that pull in different versions of the same class?

I have two versions of the same Java class (same name / methods). Since it's Java, both .java files have the same name. I want to configure gradle in such a way that I can build a "debug" version of my application that pulls in one of these files, and a "production" version of my application that pulls in the other one. How would I go about doing this?
This class has only static methods. I don't ever want to make an instance of this class. I additionally don't want to add the overhead of an if statement in each of the methods on this class to check which version I'm in.
Following #JFabianMeier's answer you could use 4 projects:
with the production version class
with the debug version class
with code that uses either of the two, parameterized according to Migrating Maven profiles ... → Example 6. Mimicking the behavior of Maven profiles in Gradle. (I'm also a Maven guy and therefore can't tell you exactly how to do it in Gradle.)
a multi-project with 1./2./3. as sub[-]projects for building all of them in one go, maybe parameterized to build just 1.+ 3. or 2.+ 3.
Have you tried creating production and debug source directories/sets? According to the docs you can use multiple directories when specifying source directories in your source set. Try dropping the different versions of your class in their respective production/debug source directories.
I haven't tested myself (not sure about the syntax), but this is based on the Gradle's Java compilation documentation.
sourceSets {
// Could also name this production
main {
java {
srcDirs ['src/main/java', 'src/prod/java']
}
}
debug {
java {
srcDirs ['src/main/java', 'src/debug/java']
}
}
}
You could do the following:
Put the class into a separate project (so generate a separate jar from it)
Then you can have two different jars, for production and debugging
Then you can pull either one or the other jar in gradle depending on a parameter.
Alternatively, you could look into template engines like Velocity which allow you to generate source code during the build depending on variables and then compile it.
Android has a neat feature called Product Flavors. It lets you swap classes at compile time effortlessly and keep your project clean.
This post is very good to get a taste of it: https://android-developers.googleblog.com/2015/12/leveraging-product-flavors-in-android.html
And here is the full documentation: https://developer.android.com/studio/build/build-variants#product-flavors

Cyclic reference when a java util library is tested with external mock util library also using the java util lib

Problem
In java, I have a a "Util project" using another "Mock project" when doing unit test.
My problem is that the "Mock Project" is as well using the "Util project" to build some of the Mock object.
When i use maven to build my projects, i can't build it cause one project miss the jar from the second and reverse case for the other project.
Example
As you can see in the example below, it make sense that both project needs each other and each piece of code is located in the right project, what is "Mock" is in "Mock" project, what is "Util" is in "Util" project.
public class TestProjectUtil
{
#Test myMethod()
{
//some code
GeneratedEntity obj = ProjectMockUtil.generateEntity();
}
}
public class ProjectMockUtil
{
public static EntityObj generateEntity()
{
//Some code
EntityObj obj = new EntityObj();
MethodList names = ProjectUtil.Reflection.getMethodList(obj);
//Some code
}
}
Question
How should you deal with this type of situation. I tried to force maven to still build my project and ignore failure but as soon as one class fail to compile then the generated jar does not include any class at all, so the jar is empty.
As well i do not believe that a refactoring is ultimately needed in my case, the different classes are in the right projects and i do not want to duplicate my code for the sake of having the same class in both project to satisfy maven and make it work.
What might be the best approach ?
option 1
Another way to do it is to build the first project as a JAR WITHOUT MAVEN, in this case your jar is usable by the second project when you run maven for the first time. (the JAR will need to be added as dependency in the POM).
After that you can build the first project normally with maven, then rebuild the second project again but this time change the JAR reference in the POM to use dependency from local repository (the one you just build with first project).
And for future build always use MAVEN as before, it will work properly.
Option 2
Another way to do so is to merge the 2 projects together, its not always logic to do so but in my case, it could be logic to merge 2 class util together and create a separation via package name, for instance first project under dev.helper.helperjse, then the second project dev.helper.helpermock
In this case we don't have the issue with circular reference since within a project circular reference are accepted and normal.
Option 3
Another way is to change the argument of the maven compilation plugin and pass argument to force compilation error .class to be added to the jar file and to not fail on error. (this one i did not find what are the arguments yet, Happy if someone knows).

Gradle custom plugin for wrapping a Maven artifact repository

Introduction
I am writing a custom plugin for Gradle to allow users to publish artifacts generated by their builds to a corporate Maven repository. I want users to be able to use the standard maven-publish plugin to upload their artifacts, but without them knowing the repository's details, i.e. authentication.
Users may run local builds but they cannot publish anything to the repository. The same user-provided build scripts, when running on a build server, will be able to publish. Users may download the plugin and use it locally, but it won't work. It will either fail or do a mock no-op upload to local/dummy repo/whatever, this is not important right now. When running on the build server, the plugin will just "know it" and work properly.
As I said, I want users to use the standard maven-publish plugin. I would like something like this for the users' build.gradle:
repositories {
mavenCentral()
Organization.mavenCorporate() /* this would be it */
}
(The Organization part is not required, it's just how I have seen it works with Gradle's project extensions, which seem to be currently favored over conventions).
Current status
So far I have been able to get the above piece of the script working to some extent. When I run a build which uses my plugin, it does:
not fail at syntax check/compile time, so I know Gradle understands this piece of code.
print some info when the plugin is applied, so I know Gradle is really using it.
print my (currently hard-coded) repository's name when I do println Organization.mavenCorporate().name inside the build script, so I know the extension is working.
print my repository's name when I do println project.repositories[0].name inside the build script, so I know the project knows the repository. The name is the same as the previous print and contains a System.nanoTime() to be sure it is not being generated twice. Also, if I print the default object's toString() instead of its name, both identifiers are equal.
However, when the build tries to resolve actual dependencies, it fails with the following message:
Execution failed for task ':myproject:compileJava'.
> Could not resolve all dependencies for configuration ':myproject:compileClasspath'.
> Cannot resolve external dependency org.apache.logging.log4j:log4j-api:2.3 because no repositories are defined.
Required by:
project :myproject
> Cannot resolve external dependency org.apache.logging.log4j:log4j-web:2.3 because no repositories are defined.
Required by:
project :myproject
[...]
What bothers me is the 'because no repositories are defined' part. I would expect it to fail for a lot of different reasons, but not this one. When I add another repository which does not contain the needed dependencies, it fails with another kind of error.
The code
My plugin is written in Java. I have seen a plugin from the Gradle Plugin Portal which has a very similar purpose, but it's written in Groovy. It simply adds some methods to project.repositories.metaClass. I don't know Groovy, but that looks like a quick-and-dirty solution to avoid dealing with Gradle's API in a proper way.
As of now, I have the following:
The main plugin class:
public class MyPlugin implements Plugin<Project> {
#Override
public void apply(Project project) {
_printVersionInfo(project);
// this allows the use of Organization methods as Gradle DSL
project.getExtensions().create("Organization", Organization.class, project);
// this should add the repo to project's RepositoryManager
project.getRepositories().add(CustomRepository.getInstance(project));
}
[unrelated methods omitted]
}
The custom repository class:
public class CustomRepository implements MavenArtifactRepository {
private static MavenArtifactRepository repo;
public static MavenArtifactRepository getInstance(Project project) {
if (repo == null) {
DefaultRepositoryHandler drh = (DefaultRepositoryHandler) project.getRepositories();
repo = drh.maven(new CustomRepoAction());
}
return repo;
}
[implemented methods from MavenArtifactRepository omitted for brevity; for debugging purposes, every method except getName() and getUrl() currently throws an UnsupportedOperationException]
}
The extension Organization class. Contains only the mavenCorporate() DSL method, which returns a MavenArtifactRepository obtained with the same call to CustomRepository.getInstance(project) as in the main plugin class.
The CustomRepoActionclass which implements Action<MavenArtifactRepository>. Contains only the implementation of execute method, which sets the name and URL of the MavenArtifactRepository passed as the parameter.
I am using Java 8 and Gradle 3.4. I am relatively new to Gradle as the user, and not really familiar with the new features from Java 7 and 8.
The question
Am I missing something? Why doesn't Gradle recognize the MavenRepositoryArtifact returned by Organization.mavenCorporate() as a repository? Do I need to bind the repository with the project, the repository handler, or any kind of manager object that I am not aware of?
EDIT: Thanks to #lukegv's comment, now I realize I cannot force my custom DSL into DefaultRepositoryHandler. I tried something like this:
repositories {
mavenCentral()
maven Organization.mavenCorporateAction() /* instance of CustomRepoAction */
}
and it works, adds a MavenArtifactRepository to project's repositories. But that doesn't keep users from accessing to the repository's internals, i.e. password. However, RepositoryHandler.maven(Action<? super MavenArtifactRepository> action) doesn't allow me to add a subclass of MavenArtifactRepository in which I could control this behaviour.
Do I need to implement a RepositoryHandler and make it the default repository handler for projects? Is that even possible without implementing my own custom Project class and messing with Gradle's internals? Is there a clean way of achieving this:
I want users to be able to use the standard maven-publish plugin to
upload their artifacts, but without them knowing the repository's
details, i.e. authentication.
with decoration, or anything implying minimal or no changes to the default behavior of everything else?

Unable to find main class :bootRepackage

I got a problem with my gradle build. I use the standard proposed by the Spring Website (https://spring.io/guides/gs/rest-service/), but when I try to use gradle build, I got this error :
It doesnt work for this gradle, but when I use another one (that I took when I was at school) it work perfectly.
There are two possibilities
Your source directory is not in the right location (use the sourceSets directive to fix this. your source directory should resemble something like src/main/java/your/package)
Add this to indicate where your main class is
springBoot {
mainClass = "hello.FileUploader"
}
I am pretty sure it is 1.
I also have this problem, Here I solved the problem:
use org.springframework.boot:spring-boot-starter instead of org.springframework.boot:spring-boot-starter-web, if your project is only an module that will be used in other project.
Or Set the main class in gradle:
mainClassName = 'your.package.MainClass'
Or Just disable the bootRepackage
bootRepackage {
enabled = false
}
There is no main method in your project (otherwise the plugin would find one). A main method has a very specific signature, so check that you have public static void main(String[] args).
If the main class is not defined in the current project which the build.gradle file belongs to, but you want to launch it for some purpose, like sprint integration test. Do it like this:
Adding
bootRepackage {
mainClass = 'your.app.package.Application'
}
in build.gradle (after the line apply plugin: 'spring-boot', because the plugin needs to be loaded) fix the problem.
I know this is a very old post. But I came across this issue while trying to build my first spring- boot application (https://spring.io/guides/gs/spring-boot/#scratch).
So the location of the pom.xml, mentioned in the tutorial is incorrect. You need to place it outside your src folder.
So here is the final directory structure -
/workspace/src/main/java/hello/HelloController.java
/workspace/src/main/java/hello/Application.java
/workspace/pom.xml
I solved it by specifying the encoding. Probably it's because I wrote the code in an IDE.
java -Dfile.encoding=UTF-8 -jar build <filename>.jar
This happened to me as well.
I was confused by the location of build.gradle file: I thought it should be located in src/main/java/hello, because it is mentioned right after the instruction to create this sub-directory structure.
It should be placed in the root folder containing src folder. Once I did that and called "gradle build" from the root folder and not "./gradlew build" as the guide instructs, build was successful.
I did not perform the standard installation for gradle, just downloaded the binaries, maybe this is the reason that "./gradlew build" failed for me.

Categories