org.eclipse.tycho.compiler.AbstractOsgiCompilerMojo Maven / Gradle / Ivy
/*
* Copyright 2006 The Apache Software Foundation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.eclipse.tycho.compiler;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.StringJoiner;
import java.util.stream.Collectors;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.repository.ArtifactRepository;
import org.apache.maven.artifact.resolver.ArtifactResolutionRequest;
import org.apache.maven.artifact.resolver.ArtifactResolutionResult;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.model.Dependency;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
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.artifact.ProjectArtifact;
import org.apache.maven.repository.RepositorySystem;
import org.apache.maven.toolchain.ToolchainManager;
import org.apache.maven.toolchain.ToolchainManagerPrivate;
import org.apache.maven.toolchain.java.DefaultJavaToolChain;
import org.codehaus.plexus.compiler.CompilerConfiguration;
import org.codehaus.plexus.compiler.util.scan.InclusionScanException;
import org.codehaus.plexus.compiler.util.scan.SimpleSourceInclusionScanner;
import org.codehaus.plexus.compiler.util.scan.SourceInclusionScanner;
import org.codehaus.plexus.compiler.util.scan.StaleSourceScanner;
import org.codehaus.plexus.logging.Logger;
import org.codehaus.plexus.util.DirectoryScanner;
import org.codehaus.plexus.util.FileUtils;
import org.codehaus.plexus.util.xml.Xpp3Dom;
import org.eclipse.jdt.internal.compiler.util.CtSym;
import org.eclipse.jdt.internal.compiler.util.JRTUtil;
import org.eclipse.osgi.util.ManifestElement;
import org.eclipse.tycho.DefaultArtifactKey;
import org.eclipse.tycho.ReactorProject;
import org.eclipse.tycho.classpath.ClasspathEntry;
import org.eclipse.tycho.classpath.ClasspathEntry.AccessRule;
import org.eclipse.tycho.classpath.JavaCompilerConfiguration;
import org.eclipse.tycho.classpath.SourcepathEntry;
import org.eclipse.tycho.core.BundleProject;
import org.eclipse.tycho.core.TychoProject;
import org.eclipse.tycho.core.dotClasspath.JREClasspathEntry;
import org.eclipse.tycho.core.dotClasspath.M2ClasspathVariable;
import org.eclipse.tycho.core.dotClasspath.ProjectClasspathEntry;
import org.eclipse.tycho.core.ee.ExecutionEnvironmentUtils;
import org.eclipse.tycho.core.ee.StandardExecutionEnvironment;
import org.eclipse.tycho.core.ee.shared.ExecutionEnvironment;
import org.eclipse.tycho.core.maven.ToolchainProvider;
import org.eclipse.tycho.core.maven.ToolchainProvider.JDKUsage;
import org.eclipse.tycho.core.osgitools.BundleReader;
import org.eclipse.tycho.core.osgitools.DefaultClasspathEntry;
import org.eclipse.tycho.core.osgitools.DefaultClasspathEntry.DefaultAccessRule;
import org.eclipse.tycho.core.osgitools.DefaultReactorProject;
import org.eclipse.tycho.core.osgitools.OsgiBundleProject;
import org.eclipse.tycho.core.osgitools.OsgiManifest;
import org.eclipse.tycho.core.osgitools.project.EclipsePluginProject;
import org.eclipse.tycho.core.utils.TychoProjectUtils;
import org.eclipse.tycho.runtime.Adaptable;
import org.osgi.framework.Constants;
import org.osgi.framework.Filter;
import org.osgi.framework.FrameworkUtil;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.Version;
import org.osgi.framework.namespace.ExecutionEnvironmentNamespace;
import org.osgi.resource.Namespace;
import copied.org.apache.maven.plugin.AbstractCompilerMojo;
public abstract class AbstractOsgiCompilerMojo extends AbstractCompilerMojo
implements JavaCompilerConfiguration, Adaptable {
public static final String RULE_SEPARATOR = File.pathSeparator;
/**
* Exclude all but keep looking for other another match
*/
public static final String RULE_EXCLUDE_ALL = "?**/*";
private static final Set MATCH_ALL = Collections.singleton("**/*");
private static final String PREFS_FILE_PATH = ".settings" + File.separator + "org.eclipse.jdt.core.prefs";
@Parameter(property = "project", readonly = true)
protected MavenProject project;
/**
* Transitively add specified maven artifacts to compile classpath in addition to elements
* calculated according to OSGi rules. All packages from additional entries will be accessible
* at compile time.
*
* Useful when OSGi runtime classpath contains elements not defined using normal dependency
* mechanisms. For example, when Eclipse Equinox is started from application server with
* -Dosgi.parentClassloader=fwk parameter.
*/
@Parameter
private Dependency[] extraClasspathElements;
@Parameter(property = "session", readonly = true)
private MavenSession session;
@Component
private RepositorySystem repositorySystem;
/**
* Which JDK to use for compilation. Default value is SYSTEM which means the currently running
* JDK. If BREE is specified, MANIFEST header Bundle-RequiredExecutionEnvironment
* is used to define the JDK to compile against. In this case, you need to provide a
* toolchains.xml
* configuration file. The value of BREE will be matched against the id of the toolchain
* elements in toolchains.xml. Example:
*
*
* <toolchains>
* <toolchain>
* <type>jdk</type>
* <provides>
* <id>J2SE-1.5</id>
* </provides>
* <configuration>
* <jdkHome>/path/to/jdk/1.5</jdkHome>
* </configuration>
* </toolchain>
* </toolchains>
*
*
* The default value of the bootclasspath used for compilation is
* <jdkHome>/lib/*;<jdkHome>/lib/ext/*;<jdkHome>/lib/endorsed/* .
*
* For JDKs with different filesystem layouts, the bootclasspath can be specified explicitly in
* the configuration section.
*
* Example:
*
*
* <configuration>
* <jdkHome>/path/to/jdk/1.5</jdkHome>
* <bootClassPath>
* <includes>
* <include>jre/lib/amd64/default/jclSC160/*.jar</include>
* </includes>
* <excludes>
* <exclude>**/alt-*.jar</exclude>
* </excludes>
* </bootClassPath>
* </configuration>
*
*
*/
@Parameter(defaultValue = "SYSTEM")
private JDKUsage useJDK;
@Component
private ToolchainManagerPrivate toolChainManager;
/**
* A list of inclusion filters for the compiler.
*/
@Parameter
private Set includes = new HashSet<>();
/**
* A list of exclusion filters for the compiler.
*/
@Parameter
private Set excludes = new HashSet<>();
/**
* A list of exclusion filters for non-java resource files which should not be copied to the
* output directory.
*/
@Parameter
private Set excludeResources = new HashSet<>();
/**
* Whether a bundle is required to explicitly import non-java.* packages from the JDK. This is
* the design-time equivalent to the equinox runtime option
* osgi.compatibility.bootdelegation.
*
* @deprecated OSGI requires all packages to be imported and support for
* osgi.compatibility.bootdelegation will be removed in one of the next Tycho
* releases.
* @see #requireJavaPackageImports
*/
@Parameter()
@Deprecated
private Boolean requireJREPackageImports;
/**
* Since OSGi R7 it is
* allowed to
* import java.* packages as well and considered good practice. This option controls if
* Tycho enforces this rule.
*/
@Parameter(defaultValue = "false")
private boolean requireJavaPackageImports;
/**
* If set to false
(the default) issue a warning if effective compiler target level
* is incompatible with bundle minimal execution environment. If set to true
will
* fail the build if effective compiler target and minimal BREE are incompatible.
*/
@Parameter(defaultValue = "false")
private boolean strictCompilerTarget;
/**
* If set to true
, the settings file
* ${project.basedir}/.settings/org.eclipse.jdt.core.prefs will be passed to the compiler. If
* the file is not present, the build will not fail.
*/
@Parameter(defaultValue = "true")
private boolean useProjectSettings;
/**
* Whether the -release
argument for the Java compiler should be derived from the
* target level. Enabled by default.
*
* Disabling this can be useful in situations where compiling using -release
cannot
* be used, e.g. when referencing internal JDK classes exported via an OSGI framework extension.
* In that case <release>
should also be explicitly set to an empty value to
* prevent it from being inherited.
*/
@Parameter(defaultValue = "true")
private boolean deriveReleaseCompilerArgumentFromTargetLevel = true;
@Component(role = TychoProject.class)
private Map projectTypes;
@Component
private BundleReader bundleReader;
/**
* Whether all resources in the source folders should be copied to
* ${project.build.outputDirectory}.
*
* true
(default) means that all resources are copied from the source folders to
* ${project.build.outputDirectory}
.
*
* false
means that no resources are copied from the source folders to
* ${project.build.outputDirectory}
.
*
* Set this to false
in case you want to keep resources separate from java files in
* src/main/resources
and handle them using
*
* maven-resources-plugin (e.g. for resource
* filtering.
*
*/
@Parameter(defaultValue = "true")
private boolean copyResources;
/**
* The directory where the compiler log files should be placed. For each output jar a log file
* will be created and stored in this directory. Logging into files is only enabled if
* {@link #log} is specified. Default: ${project.build.directory}/compile-logs
*/
@Parameter(defaultValue = "${project.build.directory}/compile-logs")
private File logDirectory;
/**
* The format of the compiler log file. plain
will log into a plain text file
* (.log), xml
will log in xml format (.xml). If omitted, no logging into files is
* done. The log file name is derived from the jar file name:
*
*
* Example:
* build.properties:
*
* output.lib1/library.jar = lib1bin/
* output.lib2/library.jar = lib2bin/
* output.. = bin/
*
* And a configuration:
*
* <configuration>
* <logEnabled>true</logEnabled>
* <logDirectory>${project.build.directory}/logfiles</logDirectory>
* <log>xml</log>
* </configuration>
*
* Will produce the following log files
*
* ${project.build.directory}/logfiles/@dot.xml
* ${project.build.directory}/logfiles/lib1_library.jar.xml
* ${project.build.directory}/logfiles/lib2_library.jar.xml
*
*/
@Parameter
private String log;
@Component
ToolchainProvider toolchainProvider;
@Component
private ToolchainManager toolchainManager;
@Component
private Logger logger;
private StandardExecutionEnvironment[] manifestBREEs;
private File currentOutputDirectory;
private List currentSourceRoots;
private List currentExcludes;
@Override
public final void execute() throws MojoExecutionException, MojoFailureException {
getLog().debug("Manifest BREEs: " + Arrays.toString(getBREE()));
getLog().debug("Target Platform EE: " + getTargetExecutionEnvironment());
String effectiveTargetLevel = getTargetLevel();
getLog().debug("Effective source/target: " + getSourceLevel() + "/" + effectiveTargetLevel);
checkTargetLevelCompatibleWithManifestBREEs(effectiveTargetLevel, manifestBREEs);
doCompile();
doFinish();
}
private void doCompile() throws MojoExecutionException, MojoFailureException {
List sourcepath = getSourcepath();
if (sourcepath.isEmpty()) {
return;
}
Map> outputMap = sourcepath.stream().collect(
Collectors.groupingBy(SourcepathEntry::getOutputDirectory, LinkedHashMap::new, Collectors.toList()));
for (Entry> entry : outputMap.entrySet()) {
this.currentOutputDirectory = entry.getKey();
this.currentOutputDirectory.mkdirs();
this.currentSourceRoots = entry.getValue().stream().map(SourcepathEntry::getSourcesRoot)
.map(root -> new File(root.toURI().normalize()).toString()).collect(Collectors.toList());
this.currentExcludes = entry.getValue().stream().map(SourcepathEntry::getExcludes).filter(Objects::nonNull)
.flatMap(Collection::stream).distinct().collect(Collectors.toList());
super.execute();
doCopyResources();
}
this.currentOutputDirectory = null;
this.currentSourceRoots = null;
this.currentExcludes = null;
}
/**
* Subclasses might override this method to perform final tasks and as a last opportunity to
* fail the compile
*
* @throws MojoExecutionException
*/
protected void doFinish() throws MojoExecutionException {
//empty
}
/**
* Only public for tests purpose!
*/
public StandardExecutionEnvironment[] getBREE() {
if (manifestBREEs == null) {
OsgiManifest manifest = bundleReader.loadManifest(project.getBasedir());
manifestBREEs = Arrays.stream(manifest.getExecutionEnvironments())
.map(ee -> ExecutionEnvironmentUtils.getExecutionEnvironment(ee, toolchainManager, session, logger))
.toArray(StandardExecutionEnvironment[]::new);
if (manifestBREEs.length == 0) {
ManifestElement[] requireCapability = manifest.getManifestElements(Constants.REQUIRE_CAPABILITY);
if (requireCapability != null) {
List eeFilters = Arrays.stream(requireCapability)
.filter(element -> ExecutionEnvironmentNamespace.EXECUTION_ENVIRONMENT_NAMESPACE
.equals(element.getValue())) //
.map(element -> element.getDirective(Namespace.REQUIREMENT_FILTER_DIRECTIVE)) //
.map(filterDirective -> {
try {
return FrameworkUtil.createFilter(filterDirective);
} catch (InvalidSyntaxException e) {
e.printStackTrace();
return null;
}
}).filter(Objects::nonNull).collect(Collectors.toList());
manifestBREEs = ExecutionEnvironmentUtils.getProfileNames(toolchainManager, session, logger)
.stream() //
.map(name -> name.split("-")) //
.map(segments -> Map.of(ExecutionEnvironmentNamespace.EXECUTION_ENVIRONMENT_NAMESPACE,
segments[0], "version", segments[1]))
.filter(eeCapability -> eeFilters.stream().anyMatch(filter -> filter.matches(eeCapability)))
.map(ee -> ee.get(ExecutionEnvironmentNamespace.EXECUTION_ENVIRONMENT_NAMESPACE) + '-'
+ ee.get("version"))
.map(ee -> ExecutionEnvironmentUtils.getExecutionEnvironment(ee, toolchainManager, session,
logger))
.toArray(StandardExecutionEnvironment[]::new);
}
}
}
return manifestBREEs;
}
/*
* mimics the behavior of the PDE incremental builder which by default copies all (non-java)
* resource files in source directories into the target folder
*/
private void doCopyResources() throws MojoExecutionException {
if (!copyResources) {
return;
}
List compileSourceRoots = getCompileSourceRoots();
for (String sourceRoot : compileSourceRoots) {
// StaleSourceScanner.getIncludedSources throws IllegalStateException
// if directory doesnt't exist
File sourceRootFile = new File(sourceRoot);
if (!sourceRootFile.isDirectory()) {
getLog().warn("Source directory " + sourceRoot + " does not exist");
continue;
}
Set excludes = new HashSet<>();
excludes.addAll(excludeResources);
excludes.addAll(getCompileSourceExcludePaths());
excludes.addAll(getEclipsePluginProject().getBuildProperties().getBinExcludes());
excludes.add("**/*.java");
// keep ignoring the following files after
// https://github.com/codehaus-plexus/plexus-utils/pull/174
excludes.add("**/.gitignore");
excludes.add("**/.gitattributes");
StaleSourceScanner scanner = new StaleSourceScanner(0L, MATCH_ALL, excludes);
CopyMapping copyMapping = new CopyMapping();
scanner.addSourceMapping(copyMapping);
try {
scanner.getIncludedSources(sourceRootFile, getOutputDirectory());
for (CopyMapping.SourceTargetPair sourceTargetPair : copyMapping.getSourceTargetPairs()) {
FileUtils.copyFile(new File(sourceRoot, sourceTargetPair.source), sourceTargetPair.target);
}
} catch (InclusionScanException e) {
throw new MojoExecutionException("Exception while scanning for resource files in " + sourceRoot, e);
} catch (IOException e) {
throw new MojoExecutionException(
"Exception copying resource files from " + sourceRoot + " to " + getOutputDirectory(), e);
}
}
}
/** public for testing purposes */
public EclipsePluginProject getEclipsePluginProject() throws MojoExecutionException {
return ((OsgiBundleProject) getBundleProject()).getEclipsePluginProject(DefaultReactorProject.adapt(project));
}
@Override
protected File getOutputDirectory() {
return this.currentOutputDirectory;
}
@Override
public List getClasspathElements() throws MojoExecutionException {
final List classpath = new ArrayList<>();
Set seen = new HashSet<>();
for (ClasspathEntry cpe : getClasspath()) {
for (File location : cpe.getLocations()) {
String entry = location.getAbsolutePath() + toString(cpe.getAccessRules());
if (seen.add(entry)) {
classpath.add(entry);
}
}
}
if (session != null) {
ArtifactRepository repository = session.getLocalRepository();
if (repository != null) {
String basedir = repository.getBasedir();
Collection classpathEntries = getEclipsePluginProject().getClasspathEntries();
for (ProjectClasspathEntry cpe : classpathEntries) {
if (cpe instanceof M2ClasspathVariable) {
M2ClasspathVariable cpv = (M2ClasspathVariable) cpe;
String entry = new File(basedir, cpv.getRepositoryPath()).getAbsolutePath();
if (seen.add(entry)) {
classpath.add(entry);
}
}
}
}
}
return classpath;
}
protected BundleProject getBundleProject() throws MojoExecutionException {
TychoProject projectType = projectTypes.get(project.getPackaging());
if (!(projectType instanceof BundleProject)) {
throw new MojoExecutionException("Not a bundle project " + project.toString());
}
return (BundleProject) projectType;
}
private String toString(Collection rules) {
StringJoiner result = new StringJoiner(RULE_SEPARATOR, "[", "]"); // include all
if (rules != null) {
for (AccessRule rule : rules) {
result.add((rule.isDiscouraged() ? "~" : "+") + rule.getPattern());
}
result.add(RULE_EXCLUDE_ALL);
} else {
// include everything, not strictly necessary, but lets make this obvious
//result.append("[+**/*]");
return "";
}
return result.toString();
}
@Override
protected final List getCompileSourceRoots() throws MojoExecutionException {
return currentSourceRoots;
}
@Override
protected final List getCompileSourceExcludePaths() throws MojoExecutionException {
return currentExcludes;
}
@Override
protected SourceInclusionScanner getSourceInclusionScanner(int staleMillis) {
SourceInclusionScanner scanner = null;
if (includes.isEmpty() && excludes.isEmpty()) {
scanner = new StaleSourceScanner(staleMillis);
} else {
if (includes.isEmpty()) {
includes.add("**/*.java");
}
scanner = new StaleSourceScanner(staleMillis, includes, excludes);
}
return scanner;
}
@Override
protected SourceInclusionScanner getSourceInclusionScanner(String inputFileEnding) {
SourceInclusionScanner scanner = null;
if (includes.isEmpty() && excludes.isEmpty()) {
includes = Collections.singleton("**/*." + inputFileEnding);
scanner = new SimpleSourceInclusionScanner(includes, Collections. emptySet());
} else {
if (includes.isEmpty()) {
includes.add("**/*." + inputFileEnding);
}
scanner = new SimpleSourceInclusionScanner(includes, excludes);
}
return scanner;
}
@Override
protected CompilerConfiguration getCompilerConfiguration(List compileSourceRoots,
List compileSourceExcludes) throws MojoExecutionException, MojoFailureException {
CompilerConfiguration compilerConfiguration = super.getCompilerConfiguration(compileSourceRoots,
compileSourceExcludes);
if (useProjectSettings) {
String prefsFilePath = project.getBasedir() + File.separator + PREFS_FILE_PATH;
if (!new File(prefsFilePath).exists()) {
getLog().warn("Parameter 'useProjectSettings' is set to true, but preferences file '" + prefsFilePath
+ "' could not be found!");
} else {
// make sure that "-properties" is the first custom argument, otherwise it's not possible to override
// any project setting on the command line because the last argument wins.
List> copy = new ArrayList<>(
compilerConfiguration.getCustomCompilerArgumentsEntries());
compilerConfiguration.getCustomCompilerArgumentsEntries().clear();
compilerConfiguration.addCompilerCustomArgument("-properties", prefsFilePath);
compilerConfiguration.getCustomCompilerArgumentsEntries().addAll(copy);
}
}
compilerConfiguration.setTargetVersion(getTargetLevel());
compilerConfiguration.setSourceVersion(getSourceLevel());
String releaseLevel = getReleaseLevel();
if (releaseLevel != null) {
compilerConfiguration.setReleaseVersion(releaseLevel);
}
configureJavaHome(compilerConfiguration);
configureBootclasspathAccessRules(compilerConfiguration);
configureCompilerLog(compilerConfiguration);
Collection classpathEntries = getEclipsePluginProject().getClasspathEntries();
for (ProjectClasspathEntry cpe : classpathEntries) {
if (cpe instanceof JREClasspathEntry) {
JREClasspathEntry jreClasspathEntry = (JREClasspathEntry) cpe;
if (jreClasspathEntry.isModule()) {
Collection modules = jreClasspathEntry.getLimitModules();
if (!modules.isEmpty()) {
compilerConfiguration.addCompilerCustomArgument("--limit-modules", String.join(",", modules));
}
}
}
}
return compilerConfiguration;
}
private void configureCompilerLog(CompilerConfiguration compilerConfiguration) throws MojoFailureException {
if (log == null) {
return;
}
if (compilerConfiguration.getCustomCompilerArgumentsAsMap().containsKey("-log")) {
throw new MojoFailureException("Compiler logging is configured by the 'log' compiler"
+ " plugin parameter and the custom compiler argument '-log'. Only either of them is allowed.");
}
logDirectory.mkdirs();
String logFileName = null;
if (new File(project.getBuild().getOutputDirectory()).getAbsolutePath()
.equals(getOutputDirectory().getAbsolutePath())) {
logFileName = "@dot";
} else {
String suffix = "-classes";
String basePath = new File(project.getBuild().getDirectory()).getAbsolutePath();
String subPath = getOutputDirectory().getAbsolutePath().substring(basePath.length()).replace('\\', '/');
if (subPath.startsWith("/")) {
subPath = subPath.substring(1);
}
String name = subPath.replaceAll("/", "_");
if (name.endsWith(suffix)) {
logFileName = name.substring(0, name.length() - suffix.length());
} else {
logFileName = name;
}
}
String logPath = logDirectory.getAbsolutePath();
if (!logPath.endsWith(File.separator)) {
logPath = logPath + File.separator;
}
String fileExtension = log;
if ("plain".equals(log)) {
fileExtension = "log";
}
logPath = logPath + logFileName + "." + fileExtension;
compilerConfiguration.addCompilerCustomArgument("-log", logPath);
}
private void configureBootclasspathAccessRules(CompilerConfiguration compilerConfiguration)
throws MojoExecutionException {
List accessRules = new ArrayList<>();
if (requireJREPackageImports != null) {
logger.warn(
"Configuration option requireJREPackageImports is deprecated and will be removed in a future Tycho version!");
}
if (requireJREPackageImports == null || requireJREPackageImports) {
if (!requireJavaPackageImports) {
accessRules.add(new DefaultAccessRule("java/**", false));
}
accessRules.addAll(getStrictBootClasspathAccessRules());
} else {
accessRules.add(new DefaultAccessRule("java/**", false));
getTargetExecutionEnvironment().getSystemPackages().stream() //
.map(systemPackage -> systemPackage.packageName) //
.distinct() //
.map(packageName -> packageName.trim().replace('.', '/') + "/*") //
.map(accessRule -> new DefaultAccessRule(accessRule, false)) //
.forEach(accessRules::add);
// now add packages exported by framework extension bundles
accessRules
.addAll(getBundleProject().getBootClasspathExtraAccessRules(DefaultReactorProject.adapt(project)));
}
if (!accessRules.isEmpty()) {
compilerConfiguration.addCompilerCustomArgument("org.osgi.framework.system.packages",
toString(accessRules));
}
}
private List getStrictBootClasspathAccessRules() throws MojoExecutionException {
return ((OsgiBundleProject) getBundleProject()).getBundleClassPath(DefaultReactorProject.adapt(project))
.getStrictBootClasspathAccessRules();
}
private void configureJavaHome(CompilerConfiguration compilerConfiguration) throws MojoExecutionException {
if (useJDK != JDKUsage.BREE) {
return;
}
StandardExecutionEnvironment[] brees = getBREE();
String toolchainId = null;
if (brees.length > 0) {
toolchainId = brees[0].getProfileName();
} else {
getLog().warn(
"useJDK=BREE configured, but no BREE is set in bundle. Fail back to currently running execution environment ("
+ getTargetExecutionEnvironment().getProfileName() + ").");
toolchainId = getTargetExecutionEnvironment().getProfileName();
}
DefaultJavaToolChain toolChain = (DefaultJavaToolChain) ExecutionEnvironmentUtils.getToolchainFor(toolchainId,
toolchainManager, session, logger);
if (toolChain == null) {
throw new MojoExecutionException("useJDK = BREE configured, but no toolchain of type 'jdk' with id '"
+ toolchainId + "' found. See https://maven.apache.org/guides/mini/guide-using-toolchains.html");
}
compilerConfiguration.addCompilerCustomArgument("use.java.home", toolChain.getJavaHome());
configureBootClassPath(compilerConfiguration, toolChain);
}
private void configureBootClassPath(CompilerConfiguration compilerConfiguration,
DefaultJavaToolChain javaToolChain) {
Xpp3Dom config = (Xpp3Dom) javaToolChain.getModel().getConfiguration();
if (config != null) {
Xpp3Dom bootClassPath = config.getChild("bootClassPath");
if (bootClassPath != null) {
Xpp3Dom includeParent = bootClassPath.getChild("includes");
if (includeParent != null) {
Xpp3Dom[] includes = includeParent.getChildren("include");
if (includes.length > 0) {
compilerConfiguration.addCompilerCustomArgument("-bootclasspath", scanBootclasspath(
javaToolChain.getJavaHome(), includes, bootClassPath.getChild("excludes")));
}
}
}
}
}
private String scanBootclasspath(String javaHome, Xpp3Dom[] includes, Xpp3Dom excludeParent) {
DirectoryScanner scanner = new DirectoryScanner();
scanner.setBasedir(javaHome);
scanner.setIncludes(getValues(includes));
if (excludeParent != null) {
Xpp3Dom[] excludes = excludeParent.getChildren("exclude");
if (excludes.length > 0) {
scanner.setExcludes(getValues(excludes));
}
}
scanner.scan();
StringBuilder bootClassPath = new StringBuilder();
String[] includedFiles = scanner.getIncludedFiles();
for (int i = 0; i < includedFiles.length; i++) {
if (i > 0) {
bootClassPath.append(File.pathSeparator);
}
bootClassPath.append(new File(javaHome, includedFiles[i]).getAbsolutePath());
}
return bootClassPath.toString();
}
private static String[] getValues(Xpp3Dom[] doms) {
String[] values = new String[doms.length];
for (int i = 0; i < values.length; i++) {
values[i] = doms[i].getValue();
}
return values;
}
private ExecutionEnvironment getTargetExecutionEnvironment() {
// never null
return TychoProjectUtils.getExecutionEnvironmentConfiguration(DefaultReactorProject.adapt(project))
.getFullSpecification();
}
@Override
public List getClasspath() throws MojoExecutionException {
TychoProject projectType = getBundleProject();
ArrayList classpath = new ArrayList<>(
((BundleProject) projectType).getClasspath(DefaultReactorProject.adapt(project)));
if (extraClasspathElements != null) {
ArtifactRepository localRepository = session.getLocalRepository();
List remoteRepositories = project.getRemoteArtifactRepositories();
for (Dependency extraDependency : extraClasspathElements) {
Artifact artifact = repositorySystem.createDependencyArtifact(extraDependency);
ArtifactResolutionRequest request = new ArtifactResolutionRequest();
request.setArtifact(artifact);
request.setLocalRepository(localRepository);
request.setRemoteRepositories(remoteRepositories);
request.setResolveRoot(true);
request.setResolveTransitively(true);
ArtifactResolutionResult result = repositorySystem.resolve(request);
if (result.hasExceptions()) {
throw new MojoExecutionException("Could not resolve extra classpath entry",
result.getExceptions().get(0));
}
for (Artifact b : result.getArtifacts()) {
ReactorProject bProject = null;
if (b instanceof ProjectArtifact) {
bProject = DefaultReactorProject.adapt(((ProjectArtifact) b).getProject());
}
ArrayList bLocations = new ArrayList<>();
bLocations.add(b.getFile()); // TODO properly handle multiple project locations maybe
classpath.add(new DefaultClasspathEntry(bProject,
((OsgiBundleProject) getBundleProject()).readOrCreateArtifactKey(b.getFile(), () -> {
return new DefaultArtifactKey(b.getType(), b.getGroupId() + "." + b.getArtifactId(),
b.getVersion());
}), bLocations, null));
}
}
}
return classpath;
}
@Override
public String getExecutionEnvironment() throws MojoExecutionException {
return getTargetExecutionEnvironment().getProfileName();
}
@Override
public String getSourceLevel() throws MojoExecutionException {
// first, explicit POM configuration
if (source != null) {
return source;
}
// then, build.properties
String javacSource = getEclipsePluginProject().getBuildProperties().getJavacSource();
if (javacSource != null) {
return javacSource;
}
String profileName = getEclipsePluginProject().getBuildProperties().getJreCompilationProfile();
if (profileName != null) {
return ExecutionEnvironmentUtils.getExecutionEnvironment(profileName, toolchainManager, session, logger)
.getCompilerSourceLevelDefault();
}
return Arrays.stream(getBREE()) //
.map(ExecutionEnvironment::getCompilerSourceLevelDefault) //
.filter(Objects::nonNull) //
.min(Comparator.comparing(Version::parseVersion)) //
.or(() -> Optional.ofNullable(getTargetExecutionEnvironment().getCompilerSourceLevelDefault())) //
.orElse(DEFAULT_SOURCE_VERSION);
}
@Override
public String getTargetLevel() throws MojoExecutionException {
// first, explicit POM configuration
if (target != null) {
return target;
}
// then, build.properties
String javacTarget = getEclipsePluginProject().getBuildProperties().getJavacTarget();
if (javacTarget != null) {
return javacTarget;
}
String profileName = getEclipsePluginProject().getBuildProperties().getJreCompilationProfile();
if (profileName != null) {
return ExecutionEnvironmentUtils.getExecutionEnvironment(profileName, toolchainManager, session, logger)
.getCompilerTargetLevelDefault();
}
return Arrays.stream(getBREE()) //
.map(ExecutionEnvironment::getCompilerTargetLevelDefault) //
.filter(Objects::nonNull) //
.min(Comparator.comparing(Version::parseVersion)) //
.or(() -> Optional.ofNullable(getTargetExecutionEnvironment().getCompilerTargetLevelDefault())) //
.orElse(DEFAULT_TARGET_VERSION);
}
@Override
public String getReleaseLevel() throws MojoExecutionException {
// first, explicit POM configuration
if (release != null) {
return release;
}
// implicit determination disabled
if (!deriveReleaseCompilerArgumentFromTargetLevel) {
return null;
}
String targetLevel = getTargetLevel();
String[] targetLevelSplit = targetLevel.split("\\.");
String releaseLevel;
if (targetLevelSplit.length == 1 && targetLevelSplit[0].matches("\\d+")) {
releaseLevel = targetLevelSplit[0];
} else if (targetLevelSplit.length == 2 && "1".equals(targetLevelSplit[0])
&& targetLevelSplit[1].matches("\\d+")) {
releaseLevel = targetLevelSplit[1];
} else {
logger.debug("Cannot determining 'maven.compiler.release' property automatically, because target level '"
+ targetLevel + "' has an unexpected format.");
return null;
}
CtSym ctSym;
try {
ctSym = JRTUtil.getCtSym(Paths.get(System.getProperty("java.home")));
} catch (IOException e) {
logger.warn("Unable to determine 'maven.compiler.release' property automatically", e);
return null;
}
// TODO: Replace this with CtSym#getReleaseCode(String) once eclipse.jdt.core/5ba272a2d4a7478e0eb3951208ab49b7c069f37d is available with newer ECJ release
int releaseLevelInt = Integer.parseInt(releaseLevel);
String releaseCode = releaseLevelInt < 10 ? releaseLevel
: String.valueOf((char) ('A' + (releaseLevelInt - 10)));
List releaseRoots = ctSym.releaseRoots(releaseCode);
if (!releaseRoots.isEmpty()) {
return releaseLevel;
} else {
logger.debug("Not determining 'maven.compiler.release' property automatically, because level '"
+ releaseLevel + "' is not supported by compiler.");
return null;
}
}
private void checkTargetLevelCompatibleWithManifestBREEs(String effectiveTargetLevel,
StandardExecutionEnvironment[] manifestBREEs) throws MojoExecutionException {
List incompatibleBREEs = new ArrayList<>();
for (StandardExecutionEnvironment ee : manifestBREEs) {
if (!ee.isCompatibleCompilerTargetLevel(effectiveTargetLevel)) {
incompatibleBREEs.add(ee.getProfileName() + " (assumes " + ee.getCompilerTargetLevelDefault() + ")");
}
}
if (!incompatibleBREEs.isEmpty()) {
String message = "The effective compiler target level " + effectiveTargetLevel
+ " is incompatible with the following OSGi execution environments: " + incompatibleBREEs + " @ "
+ project;
if (strictCompilerTarget) {
throw new MojoExecutionException(message);
}
getLog().warn(message);
}
}
@Override
public T getAdapter(Class adapter) {
if (adapter.isAssignableFrom(JavaCompilerConfiguration.class)) {
return adapter.cast(this);
}
return null;
}
}