blob: 13ed51d1fd2e373563a59e139283739fb752c22c [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2008, 2014 Sonatype Inc. and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Sonatype Inc. - initial API and implementation
*******************************************************************************/
package org.eclipse.tycho.core.osgitools;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.project.MavenProject;
import org.codehaus.plexus.component.annotations.Component;
import org.codehaus.plexus.component.annotations.Requirement;
import org.eclipse.osgi.container.namespaces.EclipsePlatformNamespace;
import org.eclipse.osgi.internal.framework.FilterImpl;
import org.eclipse.osgi.service.resolver.BundleDescription;
import org.eclipse.osgi.service.resolver.State;
import org.eclipse.tycho.ArtifactDescriptor;
import org.eclipse.tycho.ArtifactKey;
import org.eclipse.tycho.ArtifactType;
import org.eclipse.tycho.PackagingType;
import org.eclipse.tycho.ReactorProject;
import org.eclipse.tycho.artifacts.DependencyArtifacts;
import org.eclipse.tycho.classpath.ClasspathEntry;
import org.eclipse.tycho.classpath.ClasspathEntry.AccessRule;
import org.eclipse.tycho.core.ArtifactDependencyVisitor;
import org.eclipse.tycho.core.ArtifactDependencyWalker;
import org.eclipse.tycho.core.BundleProject;
import org.eclipse.tycho.core.PluginDescription;
import org.eclipse.tycho.core.TargetPlatformConfiguration;
import org.eclipse.tycho.core.TychoConstants;
import org.eclipse.tycho.core.TychoProject;
import org.eclipse.tycho.core.ee.StandardExecutionEnvironment;
import org.eclipse.tycho.core.ee.shared.ExecutionEnvironment;
import org.eclipse.tycho.core.ee.shared.ExecutionEnvironmentConfiguration;
import org.eclipse.tycho.core.osgitools.DefaultClasspathEntry.DefaultAccessRule;
import org.eclipse.tycho.core.osgitools.DependencyComputer.DependencyEntry;
import org.eclipse.tycho.core.osgitools.project.BuildOutputJar;
import org.eclipse.tycho.core.osgitools.project.EclipsePluginProject;
import org.eclipse.tycho.core.osgitools.project.EclipsePluginProjectImpl;
import org.eclipse.tycho.core.resolver.shared.PlatformPropertiesUtils;
import org.eclipse.tycho.core.shared.BuildPropertiesParser;
import org.eclipse.tycho.core.shared.TargetEnvironment;
import org.eclipse.tycho.core.utils.TychoProjectUtils;
import org.eclipse.tycho.model.Feature;
import org.eclipse.tycho.model.ProductConfiguration;
import org.eclipse.tycho.model.UpdateSite;
import org.osgi.framework.BundleException;
import org.osgi.framework.InvalidSyntaxException;
@Component(role = TychoProject.class, hint = PackagingType.TYPE_ECLIPSE_PLUGIN)
public class OsgiBundleProject extends AbstractTychoProject implements BundleProject {
private static final String CTX_ARTIFACT_KEY = TychoConstants.CTX_BASENAME + "/osgiBundle/artifactKey";
@Requirement
private BundleReader bundleReader;
@Requirement
private BuildPropertiesParser buildPropertiesParser;
@Requirement
private EquinoxResolver resolver;
@Requirement
private DependencyComputer dependencyComputer;
@Override
public ArtifactDependencyWalker getDependencyWalker(MavenProject project, TargetEnvironment environment) {
return getDependencyWalker(project);
}
@Override
public ArtifactDependencyWalker getDependencyWalker(MavenProject project) {
final DependencyArtifacts artifacts = getDependencyArtifacts(project);
final List<ClasspathEntry> cp = getClasspath(project);
return new ArtifactDependencyWalker() {
@Override
public void walk(ArtifactDependencyVisitor visitor) {
for (ClasspathEntry entry : cp) {
ArtifactDescriptor artifact = artifacts.getArtifact(entry.getArtifactKey());
ArtifactKey key = artifact.getKey();
File location = artifact.getLocation();
ReactorProject project = artifact.getMavenProject();
String classifier = artifact.getClassifier();
Set<Object> installableUnits = artifact.getInstallableUnits();
PluginDescription plugin = new DefaultPluginDescription(key, location, project, classifier, null,
installableUnits);
visitor.visitPlugin(plugin);
}
}
@Override
public void traverseFeature(File location, Feature feature, ArtifactDependencyVisitor visitor) {
}
@Override
public void traverseUpdateSite(UpdateSite site, ArtifactDependencyVisitor artifactDependencyVisitor) {
}
@Override
public void traverseProduct(ProductConfiguration productConfiguration, ArtifactDependencyVisitor visitor) {
}
};
}
@Override
public ArtifactKey getArtifactKey(ReactorProject project) {
ArtifactKey key = (ArtifactKey) project.getContextValue(CTX_ARTIFACT_KEY);
if (key == null) {
throw new IllegalStateException("Project has not been setup yet " + project.toString());
}
return key;
}
@Override
public void setupProject(MavenSession session, MavenProject project) {
ArtifactKey key = readArtifactKey(project.getBasedir());
project.setContextValue(CTX_ARTIFACT_KEY, key);
}
public ArtifactKey readArtifactKey(File location) {
OsgiManifest mf = bundleReader.loadManifest(location);
return mf.toArtifactKey();
}
@Override
public String getManifestValue(String key, MavenProject project) {
return getManifest(project).getValue(key);
}
private OsgiManifest getManifest(MavenProject project) {
return bundleReader.loadManifest(project.getBasedir());
}
@Override
public void resolveClassPath(MavenSession session, MavenProject project) {
DependencyArtifacts artifacts = getDependencyArtifacts(project);
State state = getResolverState(project, artifacts);
if (getLogger().isDebugEnabled() && DebugUtils.isDebugEnabled(session, project)) {
getLogger().debug(resolver.toDebugString(state));
}
BundleDescription bundleDescription = state.getBundleByLocation(project.getBasedir().getAbsolutePath());
List<ClasspathEntry> classpath = new ArrayList<>();
// dependencies
List<AccessRule> strictBootClasspathAccessRules = new ArrayList<>();
strictBootClasspathAccessRules.add(new DefaultAccessRule("java/**", false));
for (DependencyEntry entry : dependencyComputer.computeDependencies(state.getStateHelper(), bundleDescription)) {
if (EquinoxResolver.SYSTEM_BUNDLE_ID == entry.desc.getBundleId()) {
if (entry.rules != null) {
strictBootClasspathAccessRules.addAll(entry.rules);
}
if (EquinoxResolver.SYSTEM_BUNDLE_SYMBOLIC_NAME.equals(entry.desc.getSymbolicName())) {
// synthetic system.bundle has no filesystem location
continue;
}
}
File location = new File(entry.desc.getLocation());
ArtifactDescriptor otherArtifact = getArtifact(artifacts, location, entry.desc.getSymbolicName());
ReactorProject otherProject = otherArtifact.getMavenProject();
List<File> locations;
if (otherProject != null) {
locations = getOtherProjectClasspath(otherArtifact, otherProject, null);
} else {
locations = getBundleClasspath(otherArtifact);
}
if (locations.isEmpty() && !entry.rules.isEmpty()) {
getLogger().warn("Empty classpath of required bundle " + otherArtifact);
}
classpath.add(new DefaultClasspathEntry(otherProject, otherArtifact.getKey(), locations, entry.rules));
}
// build.properties/jars.extra.classpath
ReactorProject projectProxy = DefaultReactorProject.adapt(project);
addExtraClasspathEntries(classpath, projectProxy, artifacts);
// project itself
ArtifactDescriptor artifact = getArtifact(artifacts, project.getBasedir(), bundleDescription.getSymbolicName());
List<File> projectClasspath = getThisProjectClasspath(artifact, projectProxy);
classpath.add(new DefaultClasspathEntry(projectProxy, artifact.getKey(), projectClasspath, null));
project.setContextValue(TychoConstants.CTX_ECLIPSE_PLUGIN_CLASSPATH, classpath);
project.setContextValue(TychoConstants.CTX_ECLIPSE_PLUGIN_STRICT_BOOTCLASSPATH_ACCESSRULES,
strictBootClasspathAccessRules);
project.setContextValue(TychoConstants.CTX_ECLIPSE_PLUGIN_BOOTCLASSPATH_EXTRA_ACCESSRULES,
dependencyComputer.computeBootClasspathExtraAccessRules(state.getStateHelper(), bundleDescription));
addPDESourceRoots(project);
}
protected ArtifactDescriptor getArtifact(DependencyArtifacts artifacts, File location, String id) {
Map<String, ArtifactDescriptor> classified = artifacts.getArtifact(location);
if (classified != null) {
for (ArtifactDescriptor artifact : classified.values()) {
if (id.equals(artifact.getKey().getId())) {
return artifact;
}
}
}
return null;
}
private void addPDESourceRoots(MavenProject project) {
EclipsePluginProjectImpl eclipsePluginProject = getEclipsePluginProject(DefaultReactorProject.adapt(project));
for (BuildOutputJar outputJar : eclipsePluginProject.getOutputJars()) {
for (File sourceFolder : outputJar.getSourceFolders()) {
removeDuplicateTestCompileRoot(sourceFolder, project.getTestCompileSourceRoots());
project.addCompileSourceRoot(sourceFolder.getAbsolutePath());
}
}
}
private void removeDuplicateTestCompileRoot(File sourceFolder, List<String> testCompileSourceRoots) {
for (Iterator<String> iterator = testCompileSourceRoots.iterator(); iterator.hasNext();) {
String testCompileRoot = iterator.next();
if (sourceFolder.equals(new File(testCompileRoot))) {
// avoid duplicate source folders (bug 368445)
iterator.remove();
getLogger()
.debug("Removed duplicate test compile root " + testCompileRoot + " from maven project model");
return;
}
}
}
private State getResolverState(MavenProject project, DependencyArtifacts artifacts) {
try {
ExecutionEnvironmentConfiguration eeConfiguration = TychoProjectUtils
.getExecutionEnvironmentConfiguration(project);
ExecutionEnvironment executionEnvironment = eeConfiguration.getFullSpecification();
return resolver.newResolvedState(project, executionEnvironment, eeConfiguration.isIgnoredByResolver(),
artifacts);
} catch (BundleException e) {
throw new RuntimeException(e);
}
}
public EclipsePluginProjectImpl getEclipsePluginProject(ReactorProject otherProject) {
EclipsePluginProjectImpl pdeProject = (EclipsePluginProjectImpl) otherProject
.getContextValue(TychoConstants.CTX_ECLIPSE_PLUGIN_PROJECT);
if (pdeProject == null) {
try {
pdeProject = new EclipsePluginProjectImpl(otherProject, buildPropertiesParser);
otherProject.setContextValue(TychoConstants.CTX_ECLIPSE_PLUGIN_PROJECT, pdeProject);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
return pdeProject;
}
@Override
public List<ClasspathEntry> getClasspath(MavenProject project) {
@SuppressWarnings("unchecked")
List<ClasspathEntry> classpath = (List<ClasspathEntry>) project
.getContextValue(TychoConstants.CTX_ECLIPSE_PLUGIN_CLASSPATH);
if (classpath == null) {
throw new IllegalStateException();
}
return classpath;
}
@Override
public List<ClasspathEntry.AccessRule> getBootClasspathExtraAccessRules(MavenProject project) {
@SuppressWarnings("unchecked")
List<ClasspathEntry.AccessRule> rules = (List<AccessRule>) project
.getContextValue(TychoConstants.CTX_ECLIPSE_PLUGIN_BOOTCLASSPATH_EXTRA_ACCESSRULES);
if (rules == null) {
throw new IllegalStateException();
}
return rules;
}
/**
* Returns project compile classpath entries.
*/
private List<File> getThisProjectClasspath(ArtifactDescriptor bundle, ReactorProject project) {
LinkedHashSet<File> classpath = new LinkedHashSet<>();
EclipsePluginProject pdeProject = getEclipsePluginProject(project);
Map<String, BuildOutputJar> outputJars = pdeProject.getOutputJarMap();
// unconditionally add all output jars (even if does not exist or not on Bundle-ClassPath)
for (BuildOutputJar jar : outputJars.values()) {
classpath.add(jar.getOutputDirectory());
}
// Bundle-ClassPath entries that do not have associated output folders
// => assume it's checked into SCM or will be copied here later during build
for (String cp : parseBundleClasspath(bundle)) {
if (!outputJars.containsKey(cp)) {
classpath.add(new File(project.getBasedir(), cp));
}
}
return new ArrayList<>(classpath);
}
/**
* Returns bundle classpath entries. If <code>nestedPath</code> is not <code>null</code>, returns
* single class folder that corresponds specified nestedPath. If <code>nestedPath</code> is
* <code>null</code>, returns entries specified in Bundle-ClassPath.
*/
private List<File> getOtherProjectClasspath(ArtifactDescriptor bundle, ReactorProject otherProject,
String nestedPath) {
LinkedHashSet<File> classpath = new LinkedHashSet<>();
EclipsePluginProject pdeProject = getEclipsePluginProject(otherProject);
Map<String, BuildOutputJar> outputJars = pdeProject.getOutputJarMap();
String[] bundleClassPath;
if (nestedPath == null) {
bundleClassPath = parseBundleClasspath(bundle);
} else {
bundleClassPath = new String[] { nestedPath };
}
for (String cp : bundleClassPath) {
if (outputJars.containsKey(cp)) {
// add output folder even if it does not exist (yet)
classpath.add(outputJars.get(cp).getOutputDirectory());
} else {
// no associated output folder
// => assume it's checked into SCM or will be copied here later during build
classpath.add(new File(otherProject.getBasedir(), cp));
}
}
return new ArrayList<>(classpath);
}
private void addExtraClasspathEntries(List<ClasspathEntry> classpath, ReactorProject project,
DependencyArtifacts artifacts) {
EclipsePluginProject pdeProject = getEclipsePluginProject(project);
Collection<BuildOutputJar> outputJars = pdeProject.getOutputJarMap().values();
for (BuildOutputJar buildOutputJar : outputJars) {
List<String> entries = buildOutputJar.getExtraClasspathEntries();
for (String entry : entries) {
Pattern platformURL = Pattern.compile("platform:/(plugin|fragment)/([^/]*)/*(.*)");
Matcher m = platformURL.matcher(entry.trim());
String bundleId = null;
String path = null;
if (m.matches()) {
bundleId = m.group(2).trim();
path = m.group(3).trim();
if (path != null && path.length() <= 0) {
path = null;
}
ArtifactDescriptor matchingBundle = artifacts.getArtifact(ArtifactType.TYPE_ECLIPSE_PLUGIN,
bundleId, null);
if (matchingBundle != null) {
List<File> locations;
if (matchingBundle.getMavenProject() != null) {
locations = getOtherProjectClasspath(matchingBundle, matchingBundle.getMavenProject(),
path);
} else if (path != null) {
locations = getBundleEntry(matchingBundle, path);
} else {
locations = getBundleClasspath(matchingBundle);
}
classpath.add(new DefaultClasspathEntry(matchingBundle.getMavenProject(),
matchingBundle.getKey(), locations, null));
} else {
getLogger().warn("Missing extra classpath entry " + entry.trim());
}
} else {
entry = entry.trim();
File file = new File(project.getBasedir(), entry).getAbsoluteFile();
if (file.exists()) {
List<File> locations = Collections.singletonList(file);
ArtifactKey projectKey = getArtifactKey(project);
classpath.add(new DefaultClasspathEntry(project, projectKey, locations, null));
} else {
getLogger().warn("Missing extra classpath entry " + entry);
}
}
}
}
}
private List<File> getBundleClasspath(ArtifactDescriptor bundle) {
LinkedHashSet<File> classpath = new LinkedHashSet<>();
for (String cp : parseBundleClasspath(bundle)) {
File entry;
if (".".equals(cp)) {
entry = bundle.getLocation();
} else {
entry = getNestedJarOrDir(bundle, cp);
}
if (entry != null) {
classpath.add(entry);
}
}
return new ArrayList<>(classpath);
}
private List<File> getBundleEntry(ArtifactDescriptor bundle, String nestedPath) {
LinkedHashSet<File> classpath = new LinkedHashSet<>();
File entry;
if (".".equals(nestedPath)) {
entry = bundle.getLocation();
} else {
entry = getNestedJarOrDir(bundle, nestedPath);
}
if (entry != null) {
classpath.add(entry);
}
return new ArrayList<>(classpath);
}
private String[] parseBundleClasspath(ArtifactDescriptor bundle) {
OsgiManifest mf = bundleReader.loadManifest(bundle.getLocation());
return mf.getBundleClasspath();
}
private File getNestedJarOrDir(ArtifactDescriptor bundle, String cp) {
return bundleReader.getEntry(bundle.getLocation(), cp);
}
@Override
public TargetEnvironment getImplicitTargetEnvironment(MavenProject project) {
String filterStr = getManifestValue(EclipsePlatformNamespace.ECLIPSE_PLATFORM_FILTER_HEADER, project);
if (filterStr != null) {
try {
FilterImpl filter = FilterImpl.newInstance(filterStr);
String ws = sn(filter.getPrimaryKeyValue(PlatformPropertiesUtils.OSGI_WS));
String os = sn(filter.getPrimaryKeyValue(PlatformPropertiesUtils.OSGI_OS));
String arch = sn(filter.getPrimaryKeyValue(PlatformPropertiesUtils.OSGI_ARCH));
// validate if os/ws/arch are not null and actually match the filter
if (ws != null && os != null && arch != null) {
Map<String, String> properties = new HashMap<>();
properties.put(PlatformPropertiesUtils.OSGI_WS, ws);
properties.put(PlatformPropertiesUtils.OSGI_OS, os);
properties.put(PlatformPropertiesUtils.OSGI_ARCH, arch);
if (filter.matches(properties)) {
return new TargetEnvironment(os, ws, arch);
}
}
} catch (InvalidSyntaxException e) {
// at least we tried...
}
}
return null;
}
private static String sn(String str) {
if (str != null && !"".equals(str.trim())) {
return str;
}
return null;
}
@Override
public void readExecutionEnvironmentConfiguration(MavenProject project, ExecutionEnvironmentConfiguration sink) {
// read packaging-type independent configuration
super.readExecutionEnvironmentConfiguration(project, sink);
// only in plugin projects, the profile may also be ...
// ... specified in build.properties (for PDE compatibility)
String pdeProfile = getEclipsePluginProject(DefaultReactorProject.adapt(project)).getBuildProperties()
.getJreCompilationProfile();
if (pdeProfile != null) {
sink.setProfileConfiguration(pdeProfile.trim(), "build.properties");
} else {
// ... derived from BREE in bundle manifest
StandardExecutionEnvironment[] manifestBREEs = getManifest(project).getExecutionEnvironments();
if (manifestBREEs.length > 0) {
TargetPlatformConfiguration tpConfiguration = TychoProjectUtils.getTargetPlatformConfiguration(project);
switch (tpConfiguration.getBREEHeaderSelectionPolicy()) {
case first:
sink.setProfileConfiguration(manifestBREEs[0].getProfileName(),
"Bundle-RequiredExecutionEnvironment (first entry)");
break;
case minimal:
ExecutionEnvironment manifestMinimalEE = Collections.min(Arrays.asList(manifestBREEs));
sink.setProfileConfiguration(manifestMinimalEE.getProfileName(),
"Bundle-RequiredExecutionEnvironment (minimal entry)");
}
}
}
}
}