All Downloads are FREE. Search and download functionalities are using the official Maven repository.

it.bancaditalia.oss.AbstractRMojo Maven / Gradle / Ivy

Go to download

This plugin brings into Maven the lifecycle of an R package. It builds onto the existing R installation to perform build, check, etc. It provides means to complete a DESCRIPTION file with version, etc. In the future, It will allow to deploy the project to CRAN as well as a Maven artifact repository.

The newest version!
package it.bancaditalia.oss;

import java.io.File;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.maven.execution.MavenSession;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugins.annotations.Component;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.project.MavenProject;
import org.apache.maven.project.MavenProjectHelper;
import org.rosuda.JRI.REXP;
import org.rosuda.JRI.Rengine;

import com.sun.jna.Library;
import com.sun.jna.Native;
import com.sun.jna.Platform;

public abstract class AbstractRMojo extends AbstractMojo
{
	private static final String														ERROR_ATTR		= "JRIERROR";
	protected static final Pattern													RVERSIONPATTERN	= Pattern.compile("(\\d+)[-.](\\d+)(?=[-.](\\d+))?.*");
	private static Rengine															engine;

	@Component protected MavenProjectHelper											mavenProjectHelper;
	@Parameter(defaultValue = "${project}", readonly = true) protected MavenProject	project;
	@Parameter(defaultValue = "${session}", readonly = true) protected MavenSession	session;

	/**
	 * Location of R installation directory. Default value is taken from R_HOME environment variable.
	 */
	@Parameter(defaultValue = "${env.R_HOME}", property = "rHome") File				rHome;

	/**
	 * Additional paths where to locate shared libraries needed by R and R packages.
	 */
	@Parameter(property = "sharedLibs") File[]										sharedLibs;

	/**
	 * True if the artifact produced by the build should be attached to the project.
	 */
	@Parameter(defaultValue = "true", property = "attachArtifact") boolean			attachArtifact;

	/**
	 * Classifier of produced R artifact.
	 */
	@Parameter(property = "classifier") String										classifier;

	private interface libc extends Library
	{
		libc INSTANCE = (libc) Native.loadLibrary(Platform.isWindows() ? "msvcrt" : "c", libc.class);

		int setenv(String name, String value, int overwrite);

		int open(String pathname, int flags);
	}

	public synchronized Rengine getEngine() throws MojoExecutionException
	{
		if (engine == null)
			try
			{
				if (rHome == null || !rHome.exists() || !rHome.isDirectory())
					throw new MojoExecutionException(
							"Environment variable R_HOME is not set or invalid. Either set it or use  property in configuration.");

				File libjri = new File(rHome, "library/rJava/jri/libjri.so");
				if (!libjri.exists() && !libjri.isFile())
					throw new MojoExecutionException("Library libjri.so cannot be found. Ensure that rJava package is installed in R.");

				System.setProperty("java.library.path",
						System.getProperty("java.library.path") + File.pathSeparator + libjri.getParentFile().getAbsolutePath());

				// set LD_LIBRARY_PATH (for *NIX)
				StringBuilder builder = new StringBuilder();
				builder.append(libjri.getParentFile().getAbsolutePath()).append(File.pathSeparator);
				if (sharedLibs != null)
					for (File sharedLib : sharedLibs)
						builder.append(sharedLib.getAbsolutePath()).append(File.pathSeparator);
				builder.append(System.getenv("LD_LIBRARY_PATH"));
				libc.INSTANCE.setenv("LD_LIBRARY_PATH", builder.toString(), 1);

				// set PATH (for Win)
				builder.setLength(0);
				builder.append(libjri.getParentFile().getAbsolutePath()).append(File.pathSeparator);
				if (sharedLibs != null)
					for (File sharedLib : sharedLibs)
						builder.append(sharedLib.getAbsolutePath()).append(File.pathSeparator);
				builder.append(System.getenv("PATH"));
				libc.INSTANCE.setenv("PATH", builder.toString(), 1);

				// set java.library.path
				builder.setLength(0);
				builder.append(libjri.getParentFile().getAbsolutePath()).append(File.pathSeparator);
				if (sharedLibs != null)
					for (File sharedLib : sharedLibs)
						builder.append(sharedLib.getAbsolutePath()).append(File.pathSeparator);
				builder.append(System.getProperty("java.library.path"));
				System.setProperty("java.library.path", builder.toString());

				final Field sysPathsField = ClassLoader.class.getDeclaredField("sys_paths");
				sysPathsField.setAccessible(true);
				sysPathsField.set(null, null);

				try
				{
					System.loadLibrary("jri");
				}
				catch (UnsatisfiedLinkError e)
				{
					if (!e.getMessage().endsWith("already loaded in another classloader"))
						throw e;
				}
				libc.INSTANCE.setenv("R_HOME", rHome.getAbsolutePath(), 1);

				getLog().info("");
				getLog().info("Starting R engine...");

				engine = new Rengine(new String[] { "--vanilla" }, false, null);
				getLog().info("Querying available R packages...");
				REXP packages = engine.eval("installed.packages()[,'Package']");
				if (packages == null || packages.asStringArray() == null)
					throw new MojoExecutionException("Cannot query R for installed packages.");
				getLog().debug("Available R packages: " + Arrays.toString(packages.asStringArray()));
				boolean found = false;
				for (String packName : packages.asStringArray())
					if ("devtools".equals(packName))
					{
						getLog().debug("Package 'devtools' found.");
						found = true;
						break;
					}

				if (!found)
					throw new MojoExecutionException("Package devtools is not installed in R.");
				else
				{
					getLog().info("Loading required packages...");
					REXP res = tryCatch("library(devtools)");
					if (res == null)
						throw new MojoExecutionException("Unexpected error while loading R. Please check R runtime requirements and try again");
				}
			}
			catch (MojoExecutionException | RuntimeException | Error e)
			{
				throw e;
			}
			catch (Exception e)
			{
				throw new MojoExecutionException("Unexpected error", e);
			}

		return engine;
	}

	public REXP tryCatch(String expression) throws MojoExecutionException
	{
		synchronized (getEngine())
		{
			expression = "tryCatch({ " + expression + "}, error = function(e) { e <- e[1]; attr(e, '" + ERROR_ATTR + "') <- T; e })";
			getLog().debug(expression);
			REXP res = engine.eval(expression);
			getLog().debug(res.toString());
			if (res != null && res.getAttribute(ERROR_ATTR) != null && res.getAttribute(ERROR_ATTR).asBool() != null
					&& res.getAttribute(ERROR_ATTR).asBool().isTRUE())
				throw new MojoExecutionException("R engine threw an error: " + res.asVector().at(0).asString());
			else
				return res;
		}
	}

	protected String checkRPackageVersion() throws MojoExecutionException
	{
		Matcher versionMatcher = RVERSIONPATTERN.matcher(project.getVersion());
		if (versionMatcher.find())
			return versionMatcher.group(1) + "." + versionMatcher.group(2) + (versionMatcher.groupCount() > 2 ? "-" + versionMatcher.group(3) : "");
		else
			throw new MojoExecutionException("Project version \"" + project.getVersion() 
					+ "\" does not match regular expression \"(\\d+[-.]\\d+([-.]\\d+)?).*\"");
	}

	protected void setupDirectories()
	{
		new File(project.getBuild().getDirectory()).mkdirs();
		new File(project.getBuild().getOutputDirectory()).mkdirs();
	}

	protected String sanitize(String path)
	{
		return path != null ? path.replaceAll("\\\\", "\\\\").replaceAll("'", "\\'") : null;
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy