I want to create a custom Gradle plugin that will encapsulate Checkstyle and PMD configurations. So, other projects can just apply one custom plugin without bothering about any additional configurations.
I applied checkstyle plugin.
plugins {
id 'java-gradle-plugin'
id 'checkstyle'
}
And then I applied it inside my custom plugin.
public class CustomPlugin implements Plugin<Project> {
public void apply(Project project) {
project.getPluginManager().apply(CheckstylePlugin.class);
}
}
When I try to build the project I get an error.
Unable to find: config/checkstyle/checkstyle.xml
How can I override other plugin's properties? For example, I want to change the default checkstyle.xml path. I can do it manually inside build.gradle of the plugin project itself. But in this case, other projects that apply the plugin won't have this configurations defined by default (I tested it).
EDIT 1:
I managed to configure checkstyle plugin with ChecktyleExtension.
public class MetricCodingRulesGradlePluginPlugin implements Plugin<Project> {
public void apply(Project project) {
project.getPluginManager().apply("checkstyle");
project.getExtensions().configure(CheckstyleExtension.class, checkstyleExtension -> {
checkstyleExtension.setConfigFile(new File("style/checkstyle.xml"));
});
}
}
checkstyle.xml is placed in the plugin project. When I try to apply it within any other project, checkstyle searches it inside the current project directory but not the plugin's one. Is it possible to overcome this issue? I don't want users of that plugin to put any additional files inside their project.
EDIT 2:
I put the config files to resources folder and tried to read the content.
public class MetricCodingRulesGradlePluginPlugin implements Plugin<Project> {
public void apply(Project project) {
project.getPluginManager().apply("checkstyle");
project.getExtensions().configure(CheckstyleExtension.class, checkstyleExtension -> {
URL url = getClass().getClassLoader().getResource("style/checkstyle.xml");
System.out.println("URL: " + url);
try {
checkstyleExtension.setConfigFile(
Paths.get(url.toURI())
.toFile()
);
} catch (URISyntaxException e) {
throw new RuntimeException(e);
}
});
}
}
When I apply the plugin to another project, I get the following error:
URL: jar:file:/Users/user/.gradle/caches/jars-9/8f4176a8ae146bf601f1214b287eb805/my-plugin-0.0.1-SNAPSHOT.jar!/style/checkstyle.xml
Caused by: java.nio.file.FileSystemNotFoundException
at com.sun.nio.zipfs.ZipFileSystemProvider.getFileSystem(ZipFileSystemProvider.java:171)
at com.sun.nio.zipfs.ZipFileSystemProvider.getPath(ZipFileSystemProvider.java:157)
Java cannot read the file from the jar archive for some reason. Any approaches to overcome this error?
You'd need to bundle the checkstyle.xml within your plugin's resources folder, so when you ship it, you can always access it from within the plugin code.
Basically, you need to put the config under src/main/resources/checkstyle.xml of the plugin and then access it like this:
URL resourceURL = getClass().getClassLoader().getResource("checkstyle.xml");
if (resourceURL != null) {
File resourceFile = File(resourceURL.getFile());
checkstyleExtension.setConfigFile(resourceFile);
}
Also remember, if you ship your plugin as a .jar, you'd need to unpack the checkstyle.xml into a temp file beforehand. Roughly:
File temp = File.createTempFile(".checkstyle", ".xml")
try (FileOutputStream out = new FileOutputStream(temp)) {
try (InputStream resourceStream = getClass().getClassLoader().getResourceAsStream("checkstyle.xml")) {
byte[] buffer = new byte[1024];
int bytes = resourceStream.read(buffer);
while (bytes >= 0) {
out.write(buffer, 0, bytes);
bytes = resourceStream.read(buffer);
}
}
}
my goal is to develop an internal tool for artifact deploying. The artifacts are located in a local repository management system (sonatype nexus). After researching, I tried to implement the task, with Aether-Api. But I failed at reprogramming one of their examples at my own. I can't evaluate the error.
public class SourceMaven
{
private static RepositorySystem newRepositorySystem()
{
DefaultServiceLocator locator = MavenRepositorySystemUtils.newServiceLocator();
locator.addService(RepositoryConnectorFactory.class, BasicRepositoryConnectorFactory.class);
locator.addService(TransporterFactory.class, FileTransporterFactory.class);
locator.addService(TransporterFactory.class, HttpTransporterFactory.class);
return locator.getService(RepositorySystem.class);
}
private static RepositorySystemSession newSession(RepositorySystem system)
{
DefaultRepositorySystemSession session = MavenRepositorySystemUtils.newSession();
LocalRepository localRepo = new LocalRepository("/usr/local/home/myusername/tmp/aether");
session.setLocalRepositoryManager(system.newLocalRepositoryManager(session, localRepo));
return session;
}
private static List<RemoteRepository> newRepositories( RepositorySystem system, RepositorySystemSession session)
{
return new ArrayList<RemoteRepository>(Arrays.asList(newCentralRepository()));
}
private static RemoteRepository newCentralRepository()
{
return new RemoteRepository.Builder("sonanexus", "default", "http://ournexusservername:8081/nexus/#nexus").build();
}
public List<String> getReleaseList(String url)
{
RepositorySystem system = newRepositorySystem();
RepositorySystemSession session = newSession(system);
Artifact artifact = new DefaultArtifact("org.eclipse.aether:aether-util:[0,)");
VersionRangeRequest rangeRequest = new VersionRangeRequest();
rangeRequest.setArtifact(artifact);
rangeRequest.setRepositories(newRepositories(system, session));
try
{
VersionRangeResult rangeResult = system.resolveVersionRange(session, rangeRequest);
List<Version> versions = rangeResult.getVersions();
System.out.println("available versions " + versions);
}
catch (VersionRangeResolutionException ex)
{
System.out.println("failed ...");
}
return null;
}
}
As output I only get an empty List without an error.
available versions []
The requestested artifact coordinates are linked in our nexus and can be found in the webinterface.
Code above is working.
It is important to clarify the url more specific so instead of
http://hostname:8081/nexus/#nexus
i needed to change it to:
http://hostname:8081/nexus/content/groups/public
Play can be launched in dev mode (via run), in production mode (via start) or in test mode. Is there a way to provide a different config file (conf/application.conf) depending on which mode it is launched in?
I usually have a base configuration (application.conf) and three extra configs per environment. In Play Framework 2.4 it can be done by extending GuiceApplicationLoader and merging base conf with your environment specific conf. You can go one step forward and provide different guice modules per environment.
Scala version:
class CustomApplicationLoader extends GuiceApplicationLoader {
override protected def builder(context: Context): GuiceApplicationBuilder = {
val builder = initialBuilder.in(context.environment).overrides(overrides(context): _*)
context.environment.mode match {
case Prod =>
// start mode
val prodConf = Configuration(ConfigFactory.load("prod.conf"))
builder.loadConfig(prodConf ++ context.initialConfiguration).bindings(new ProdModule());
case Dev =>
// run mode
val devConf = Configuration(ConfigFactory.load("dev.conf"))
builder.loadConfig(devConf ++ context.initialConfiguration).bindings(new DevModule());
case Test =>
// test mode
val testConf = Configuration(ConfigFactory.load("test.conf"))
builder.loadConfig(testConf ++ context.initialConfiguration).bindings(new TestModule());
}
}
}
Java version:
public class CustomApplicationLoader extends GuiceApplicationLoader {
#Override
public GuiceApplicationBuilder builder(ApplicationLoader.Context context) {
final Environment environment = context.environment();
GuiceApplicationBuilder builder = initialBuilder.in(environment);
Configuration config = context.initialConfiguration();
if (environment.isTest()) {
config = merge("test.conf", config);
builder = builder.bindings(new TestModule());
} else if (environment.isDev()) {
config = merge("dev.conf", config);
builder = builder.bindings(new DevModule());
} else if (environment.isProd()) {
config = merge("prod.conf", config);
builder = builder.bindings(new DevModule());
} else {
throw new IllegalStateException("No such mode.");
}
return builder.in(environment).loadConfig(config);
}
private Configuration merge(String configName, Configuration currentConfig) {
return new Configuration(currentConfig.getWrappedConfiguration().$plus$plus(new play.api.Configuration(ConfigFactory.load(configName))));
}
}
Do not forget to include play.application.loader = "modules.CustomApplicationLoader" to your application.conf.
In lower versions of Play something similar can be achieved by using GlobalSettings class and overriding onLoadConfig. Mind GlobalSettings in Play 2.4 is depracted.
If you don't like including test.conf and test mocks from TestModule to your production build, you can filter the files with sbt.
You can set a different config file using one of the 3 ways play gives to you:
1 - Using -Dconfig.resource
It will search for an alternative configuration file in the
application classpath (you usually provide these alternative
configuration files into your application conf/ directory before
packaging). Play will look into conf/ so you don’t have to add conf/.
$ /path/to/bin/ -Dconfig.resource=prod.conf
2 - Using -Dconfig.file
You can also specify another local configuration file not packaged
into the application artifacts:
$ /path/to/bin/ -Dconfig.file=/opt/conf/prod.conf
3 - Using -Dconfig.url
You can also specify a configuration file to be loaded from any URL:
$ /path/to/bin/
-Dconfig.url=http://conf.mycompany.com/conf/prod.conf
Checkout more on:
https://www.playframework.com/documentation/2.3.x/ProductionConfiguration
This thing can be done by having loading config files based on the environment which can be supplied via -Dmode=staging/dev/prod, and for loading the files I override onLoadConfig of GlobalSettings in Global.java.
Java snippet-
#Override
public Configuration onLoadConfig(Configuration config, File file,ClassLoader classLoader) {
Configuration updatedConfig = config;
String mode = config.getString("mode");
if (StringUtils.isNotEmpty(mode)) {
try {
File modeFolder = FileUtils.getFile(file, "conf/" + mode);
if (modeFolder.exists()) {
play.api.Configuration modeConfig = config.getWrappedConfiguration();
IOFileFilter fileFilter = new WildcardFileFilter("*.conf");
Collection<File> fileList = FileUtils.listFiles(modeFolder, fileFilter, null);
for (File confFile : fileList) {
modeConfig = modeConfig
.$plus$plus(new play.api.Configuration(ConfigFactory.parseFile(confFile)));
}
updatedConfig = new Configuration(modeConfig);
}
} catch (Exception e) {
Logger.error("Exception while loading configuration for mode : " + mode, e);
}
} else {
Logger.error("Please provide mode in which play application has to start (Ex. play -Dmode=<mode>) ");
}
For each mode, create a folder(name same as environment) and keep environment specific config in that folder.
I am creating a Gradle plugin (Y) in Java that is equivalent to a Maven one I previously implemented. Given a dependency (X) of the plugin (Y), I need to find its URL and the URL of all the dependencies X depends on. This is needed to create a full classpath for X (which is a Java application) to execute it on a new JVM/process.
In a Maven plugin, this is achieved with:
//injected objects
//#Parameter(defaultValue = "${plugin.artifacts}", required = true, readonly = true)
//private List<Artifact> artifacts;
//#Component
//private ProjectBuilder projectBuilder;
//#Parameter(defaultValue="${repositorySystemSession}", required = true, readonly = true)
//private RepositorySystemSession repoSession;
// first find the artifact, ie the jar of the dependency
Artifact x = null;
for(Artifact art : artifacts){
if(art.getArtifactId().equals("name-of-the-X-artifact")){
x = art;
break;
}
}
/*
* build a project descriptor for the artifact, which is needed to
* query all of its dependencies
*/
DefaultProjectBuildingRequest req = new DefaultProjectBuildingRequest();
req.setRepositorySession(repoSession);
req.setValidationLevel(ModelBuildingRequest.VALIDATION_LEVEL_MINIMAL);
req.setSystemProperties(System.getProperties());
req.setResolveDependencies(true);
ProjectBuildingResult res = projectBuilder.build(x, req);
// finally, determine the full classpath
String cp = x.getFile().getAbsolutePath();
for(Artifact dep : res.getProject().getArtifacts()){
cp += File.pathSeparator + dep.getFile().getAbsolutePath();
}
How to achieve the same in Gradle? Given:
public class MyPluginY implements Plugin<Project> {
#Override
public void apply(Project p) {
}
}
I can iterate through its dependencies with for(Configuration c : p.getBuildscript().getConfigurations()) and then for(Dependency d: c.getAllDependencies()). However:
How to determine the URL of d?
How to recursively find all the dependencies of d?
Maybe this is going to be a larger task than I had originally thought, but regardless, I'm trying to load a MavenProject from a file and then resolve its dependencies. I've got the code for both bits but I'm missing some object references that I need; specifically I need to get instances of RepositorySystemSession and RepositorySystem. Any tips?
Note: I have tagged this question with maven-plugin, but this is not a Maven plugin. I am happy to mandate Maven 3 (think I already have anyway..)
Here's the code I have so far:
Constructing the MavenProject:
public static MavenProject loadProject(File pomFile) throws Exception
{
MavenProject ret = null;
MavenXpp3Reader mavenReader = new MavenXpp3Reader();
if (pomFile != null && pomFile.exists())
{
FileReader reader = null;
try
{
reader = new FileReader(pomFile);
Model model = mavenReader.read(reader);
model.setPomFile(pomFile);
ret = new MavenProject(model);
}
finally
{
// Close reader
}
}
return ret;
}
Resolving dependencies:
public static List<Dependency> getArtifactsDependencies(MavenProject project, String dependencyType, String scope) throws Exception
{
DefaultArtifact pomArtifact = new DefaultArtifact(project.getId());
RepositorySystemSession repoSession = null; // TODO
RepositorySystem repoSystem = null; // TODO
List<RemoteRepository> remoteRepos = project.getRemoteProjectRepositories();
List<Dependency> ret = new ArrayList<Dependency>();
Dependency dependency = new Dependency(pomArtifact, scope);
CollectRequest collectRequest = new CollectRequest();
collectRequest.setRoot(dependency);
collectRequest.setRepositories(remoteRepos);
DependencyNode node = repoSystem.collectDependencies(repoSession, collectRequest).getRoot();
DependencyRequest projectDependencyRequest = new DependencyRequest(node, null);
repoSystem.resolveDependencies(repoSession, projectDependencyRequest);
PreorderNodeListGenerator nlg = new PreorderNodeListGenerator();
node.accept(nlg);
ret.addAll(nlg.getDependencies(true));
return ret;
}
I realise this might be an unusual request, maybe I should just scrap what I was trying to do and wrap it as a plugin...but I kind of just want to finish what I started now! Thanks in advance.
Try jcabi-aether, which is a wrapper around Apache Aether from Sonatype:
final File repo = this.session.getLocalRepository().getBasedir();
final Collection<Artifact> deps = new Aether(this.getProject(), repo).resolve(
new DefaultArtifact("junit", "junit-dep", "", "jar", "4.10"),
JavaScopes.RUNTIME
);
If you are outside of Maven plugin:
final File repo = new File(System.getProperty("java.io.tmpdir"), "my-repo");
final MavenProject project = new MavenProject();
project.setRemoteArtifactRepositories(
Arrays.asList(
new RemoteRepository(
"maven-central",
"default",
"http://repo1.maven.org/maven2/"
)
)
);
final Collection<Artifact> deps = new Aether(project, repo).resolve(
new DefaultArtifact("junit", "junit-dep", "", "jar", "4.10"),
JavaScopes.RUNTIME
);
I would recommend to read the information about Aether lib which is exactly is for such kind of purposes.
Note: Aether was previousely developed at Sonatype, but has since moved to Eclipse.
I just whipped up a solution to both your and my problem:
/*******************************************************************************
* Copyright (c) 2013 TerraFrame, Inc. All rights reserved.
*
* This file is part of Runway SDK(tm).
*
* Runway SDK(tm) is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* Runway SDK(tm) is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with Runway SDK(tm). If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/
package com.test.mavenaether;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import org.apache.maven.artifact.repository.ArtifactRepository;
import org.apache.maven.artifact.repository.ArtifactRepositoryPolicy;
import org.apache.maven.artifact.repository.MavenArtifactRepository;
import org.apache.maven.artifact.repository.layout.DefaultRepositoryLayout;
import org.apache.maven.model.Model;
import org.apache.maven.model.io.xpp3.MavenXpp3Reader;
import org.apache.maven.project.MavenProject;
import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
import org.sonatype.aether.artifact.Artifact;
import org.sonatype.aether.resolution.DependencyResolutionException;
import org.sonatype.aether.util.artifact.DefaultArtifact;
import org.sonatype.aether.util.artifact.JavaScopes;
import com.jcabi.aether.Aether;
public class MavenAether
{
public static void main(String[] args) throws Exception
{
String classpath = getClasspathFromMavenProject(new File("/users/terraframe/documents/workspace/MavenSandbox/pom.xml"), new File("/users/terraframe/.m2/repository"));
System.out.println("classpath = " + classpath);
}
public static String getClasspathFromMavenProject(File projectPom, File localRepoFolder) throws DependencyResolutionException, IOException, XmlPullParserException
{
MavenProject proj = loadProject(projectPom);
proj.setRemoteArtifactRepositories(
Arrays.asList(
(ArtifactRepository) new MavenArtifactRepository(
"maven-central", "http://repo1.maven.org/maven2/", new DefaultRepositoryLayout(),
new ArtifactRepositoryPolicy(), new ArtifactRepositoryPolicy()
)
)
);
String classpath = "";
Aether aether = new Aether(proj, localRepoFolder);
List<org.apache.maven.model.Dependency> dependencies = proj.getDependencies();
Iterator<org.apache.maven.model.Dependency> it = dependencies.iterator();
while (it.hasNext()) {
org.apache.maven.model.Dependency depend = it.next();
final Collection<Artifact> deps = aether.resolve(
new DefaultArtifact(depend.getGroupId(), depend.getArtifactId(), depend.getClassifier(), depend.getType(), depend.getVersion()),
JavaScopes.RUNTIME
);
Iterator<Artifact> artIt = deps.iterator();
while (artIt.hasNext()) {
Artifact art = artIt.next();
classpath = classpath + " " + art.getFile().getAbsolutePath();
}
}
return classpath;
}
public static MavenProject loadProject(File pomFile) throws IOException, XmlPullParserException
{
MavenProject ret = null;
MavenXpp3Reader mavenReader = new MavenXpp3Reader();
if (pomFile != null && pomFile.exists())
{
FileReader reader = null;
try
{
reader = new FileReader(pomFile);
Model model = mavenReader.read(reader);
model.setPomFile(pomFile);
ret = new MavenProject(model);
}
finally
{
reader.close();
}
}
return ret;
}
}
pom.xml:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.test</groupId>
<artifactId>MavenSandbox</artifactId>
<version>0.0.1-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>com.jcabi</groupId>
<artifactId>jcabi-aether</artifactId>
<version>0.7.19</version>
</dependency>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-core</artifactId>
<version>3.0.3</version>
</dependency>
</dependencies>
</project>
The code first loads the Maven project (using your function provided in the original question), then uses jcabi-aether to find the artifact in your local repository. You will need to change the two parameters in the main function: the location for the pom.xml of your project, and the location of your local repository.
Enjoy! :)
Try this (as can be seen from the ather-demo):
...
LocalRepository localRepository = new LocalRepository("/path/to/local-repo");
RepositorySystem system = getRepositorySystemInstance();
RepositorySystemSession session = getRepositorySystemSessionInstance(system, localRepository);
....
public static RepositorySystem getRepositorySystemInstance()
{
/**
* Aether's components implement org.sonatype.aether.spi.locator.Service to ease manual wiring and using the
* prepopulated DefaultServiceLocator, we only need to register the repository connector factories.
*/
MavenServiceLocator locator = new MavenServiceLocator();
locator.addService(RepositoryConnectorFactory.class, FileRepositoryConnectorFactory.class);
locator.addService(RepositoryConnectorFactory.class, WagonRepositoryConnectorFactory.class);
locator.setServices(WagonProvider.class, new ManualWagonProvider());
return locator.getService(RepositorySystem.class);
}
private static RepositorySystemSession getRepositorySystemSessionInstance(RepositorySystem system,
LocalRepository localRepo)
{
MavenRepositorySystemSession session = new MavenRepositorySystemSession();
session.setLocalRepositoryManager(system.newLocalRepositoryManager(localRepo));
session.setTransferListener(new ConsoleTransferListener());
session.setRepositoryListener(new ConsoleRepositoryListener());
// Set this in order to generate dirty trees
session.setDependencyGraphTransformer(null);
return session;
}
There is a nice set of standalone examples for Eclipses Aether API which is used in the latest Maven (3.1.1) and it can be found here.
Note: Maven 3.1.X still uses Aether 0.9.0.M2 (and the latest version which us used in the examples is 0.9.0.M3). So to run these examples inside a Maven plugin, version M2 is required, and a standalone application can use the latest M3 version.
This is covered in "Aether/Setting Aether Up" for the RepositorySystem and in "Aether/Creating a Repository System Session" in the eclipse wiki.
There is not a ton of documentation, but you can create a RepositorySystem as follows:
// verbatim copy from the Setting Aether Up link
private static RepositorySystem newRepositorySystem()
{
DefaultServiceLocator locator = MavenRepositorySystemUtils.newServiceLocator();
locator.addService( RepositoryConnectorFactory.class, BasicRepositoryConnectorFactory.class );
locator.addService( TransporterFactory.class, FileTransporterFactory.class );
locator.addService( TransporterFactory.class, HttpTransporterFactory.class );
return locator.getService( RepositorySystem.class );
}
Do note that this requires the dependencies detailed in "Getting Aether", most notably maven-aether-provider.
When you have your repository system the tutorial goes on to create a RepositorySystemSession with the following factory method:
// copied verbatim from "Creating a Repository System Session"
private static RepositorySystemSession newSession( RepositorySystem system )
{
DefaultRepositorySystemSession session = MavenRepositorySystemUtils.newSession();
LocalRepository localRepo = new LocalRepository( "target/local-repo" );
session.setLocalRepositoryManager( system.newLocalRepositoryManager( session, localRepo ) );
return session;
}