blob: ca5d8981e8dad27ef532d27fbda974219b602c5e [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2008 IBM Corporation 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:
* IBM Corporation - initial API and implementation
* Sonatype Inc. - adopted to work outside of eclipse workspace
*******************************************************************************/
package org.eclipse.tycho.core.osgitools;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import org.codehaus.plexus.component.annotations.Component;
import org.codehaus.plexus.component.annotations.Requirement;
import org.eclipse.osgi.internal.resolver.ExportPackageDescriptionImpl;
import org.eclipse.osgi.service.resolver.BaseDescription;
import org.eclipse.osgi.service.resolver.BundleDescription;
import org.eclipse.osgi.service.resolver.BundleSpecification;
import org.eclipse.osgi.service.resolver.ExportPackageDescription;
import org.eclipse.osgi.service.resolver.HostSpecification;
import org.eclipse.osgi.service.resolver.ImportPackageSpecification;
import org.eclipse.osgi.service.resolver.StateHelper;
import org.eclipse.osgi.util.ManifestElement;
import org.eclipse.tycho.classpath.ClasspathEntry.AccessRule;
import org.eclipse.tycho.core.osgitools.DefaultClasspathEntry.DefaultAccessRule;
import org.osgi.framework.Constants;
/**
* Helper class that computes compile dependencies of a bundle project.
*
* Code below is copy&paste of org.eclipse.pde.internal.core.RequiredPluginsClasspathContainer
* adopted to work outside of Eclipse runtime.
*
* Note that some functionality, namely SecondaryDependencies, ExtraClasspathEntries and
* isPatchFragment, has been removed due to time constraints.
*/
@Component(role = DependencyComputer.class)
public class DependencyComputer {
@Requirement
private BundleReader manifestReader;
public static class DependencyEntry {
public final BundleDescription desc;
public final List<AccessRule> rules;
public DependencyEntry(BundleDescription desc, List<AccessRule> rules) {
this.desc = desc;
this.rules = rules;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((desc == null) ? 0 : desc.hashCode());
result = prime * result + ((rules == null) ? 0 : rules.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
DependencyEntry other = (DependencyEntry) obj;
if (desc == null) {
if (other.desc != null)
return false;
} else if (!desc.equals(other.desc))
return false;
if (rules == null) {
if (other.rules != null)
return false;
} else if (!rules.equals(other.rules))
return false;
return true;
}
}
public List<DependencyEntry> computeDependencies(StateHelper helper, BundleDescription desc) {
ArrayList<DependencyEntry> entries = new ArrayList<>();
if (desc == null)
return entries;
Map<BundleDescription, ArrayList<AccessRule>> map = retrieveVisiblePackagesFromState(helper, desc);
HashSet<BundleDescription> added = new HashSet<>();
// to avoid cycles, e.g. when a bundle imports a package it exports
added.add(desc);
HostSpecification host = desc.getHost();
if (host != null) {
addHostPlugin(host, added, map, entries);
}
// add dependencies
BundleSpecification[] required = desc.getRequiredBundles();
for (BundleSpecification required1 : required) {
addDependency((BundleDescription) required1.getSupplier(), added, map, entries);
}
// addSecondaryDependencies(desc, added, entries);
// add Import-Package
// sort by symbolicName_version to get a consistent order
Map<String, BundleDescription> sortedMap = new TreeMap<>();
for (BundleDescription bundle : map.keySet()) {
sortedMap.put(bundle.toString(), bundle);
}
for (BundleDescription bundle : sortedMap.values()) {
addDependencyViaImportPackage(bundle, added, map, entries);
}
// addExtraClasspathEntries(added, entries);
// for (int i = 0; i < entries.size(); i++) {
// System.err.println(i + "\t" + entries.get(i).desc);
// }
return entries;
}
private Map<BundleDescription, ArrayList<AccessRule>> retrieveVisiblePackagesFromState(StateHelper helper,
BundleDescription desc) {
Map<BundleDescription, ArrayList<AccessRule>> visiblePackages = new HashMap<>();
addVisiblePackagesFromState(helper, desc, visiblePackages);
if (desc.getHost() != null)
addVisiblePackagesFromState(helper, (BundleDescription) desc.getHost().getSupplier(), visiblePackages);
return visiblePackages;
}
private void addVisiblePackagesFromState(StateHelper helper, BundleDescription desc,
Map<BundleDescription, ArrayList<AccessRule>> visiblePackages) {
if (desc == null)
return;
ExportPackageDescription[] exports = helper.getVisiblePackages(desc, StateHelper.VISIBLE_INCLUDE_EE_PACKAGES);
for (ExportPackageDescription export : exports) {
BundleDescription exporter = export.getExporter();
if (exporter == null)
continue;
ArrayList<AccessRule> list = visiblePackages.get(exporter);
if (list == null)
list = new ArrayList<>();
AccessRule rule = getRule(helper, desc, export);
if (!list.contains(rule))
list.add(rule);
visiblePackages.put(exporter, list);
}
}
private AccessRule getRule(StateHelper helper, BundleDescription desc, ExportPackageDescription export) {
boolean discouraged = helper.getAccessCode(desc, export) == StateHelper.ACCESS_DISCOURAGED;
String name = export.getName();
String path = (name.equals(".")) ? "*" : name.replaceAll("\\.", "/") + "/*";
return new DefaultAccessRule(path, discouraged);
}
protected void addDependencyViaImportPackage(BundleDescription desc, HashSet<BundleDescription> added,
Map<BundleDescription, ArrayList<AccessRule>> map, ArrayList<DependencyEntry> entries) {
if (desc == null || !added.add(desc))
return;
addPlugin(desc, true, map, entries);
if (hasExtensibleAPI(desc) && desc.getContainingState() != null) {
BundleDescription[] fragments = desc.getFragments();
for (BundleDescription fragment : fragments) {
if (fragment.isResolved()) {
addDependencyViaImportPackage(fragment, added, map, entries);
}
}
}
}
private void addDependency(BundleDescription desc, HashSet<BundleDescription> added,
Map<BundleDescription, ArrayList<AccessRule>> map, ArrayList<DependencyEntry> entries) {
addDependency(desc, added, map, entries, true);
}
private void addDependency(BundleDescription desc, HashSet<BundleDescription> added,
Map<BundleDescription, ArrayList<AccessRule>> map, ArrayList<DependencyEntry> entries, boolean useInclusion) {
if (desc == null || !added.add(desc))
return;
BundleDescription[] fragments = hasExtensibleAPI(desc) ? desc.getFragments() : new BundleDescription[0];
// add fragment patches before host
for (BundleDescription fragment : fragments) {
if (fragment.isResolved() && isPatchFragment(fragment)) {
addDependency(fragment, added, map, entries, useInclusion);
}
}
addPlugin(desc, useInclusion, map, entries);
// add fragments that are not patches after the host
for (BundleDescription fragment : fragments) {
if (fragment.isResolved() && !isPatchFragment(fragment)) {
addDependency(fragment, added, map, entries, useInclusion);
}
}
BundleSpecification[] required = desc.getRequiredBundles();
for (BundleSpecification required1 : required) {
addDependency((BundleDescription) required1.getSupplier(), added, map, entries, useInclusion);
}
}
private boolean isPatchFragment(BundleDescription bundleDescription) {
return false; // TODO
}
private boolean addPlugin(BundleDescription desc, boolean useInclusions,
Map<BundleDescription, ArrayList<AccessRule>> map, ArrayList<DependencyEntry> entries) {
List<AccessRule> rules = useInclusions ? getInclusions(map, desc) : null;
DependencyEntry entry = new DependencyEntry(desc, rules);
if (!entries.contains(entry))
entries.add(entry);
return true;
}
private List<AccessRule> getInclusions(Map<BundleDescription, ArrayList<AccessRule>> map, BundleDescription desc) {
ArrayList<AccessRule> rules;
if (desc.getHost() != null)
rules = map.get((BundleDescription) desc.getHost().getSupplier());
else
rules = map.get(desc);
return rules != null ? rules : new ArrayList<AccessRule>();
}
private void addHostPlugin(HostSpecification hostSpec, HashSet<BundleDescription> added,
Map<BundleDescription, ArrayList<AccessRule>> map, ArrayList<DependencyEntry> entries) {
BaseDescription desc = hostSpec.getSupplier();
if (desc instanceof BundleDescription) {
BundleDescription host = (BundleDescription) desc;
// add host plug-in
if (added.add(host) && addPlugin(host, false, map, entries)) {
BundleSpecification[] required = host.getRequiredBundles();
for (BundleSpecification required1 : required) {
addDependency((BundleDescription) required1.getSupplier(), added, map, entries);
}
// add Import-Package
ImportPackageSpecification[] imports = host.getImportPackages();
for (ImportPackageSpecification import1 : imports) {
BaseDescription supplier = import1.getSupplier();
if (supplier instanceof ExportPackageDescription) {
addDependencyViaImportPackage(((ExportPackageDescription) supplier).getExporter(), added, map,
entries);
}
}
}
}
}
private boolean hasExtensibleAPI(BundleDescription desc) {
// TODO re-enable Eclipse-ExtensibleAPI
return true; //"true".equals(state.getManifestAttribute(desc, "Eclipse-ExtensibleAPI"));
}
/**
* Although totally not obvious from the specification text, section 3.15 "Extension Bundles" of
* OSGi Core Spec apparently says that framework extension bundles can export additional
* packaged of the underlying JRE. More specific explanation is provided in [1] and I verified
* that at least Equinox 3.7.1 does indeed behave like described.
* <p/>
* There does not seem to be a way to tell which packages exported by a framework extension
* bundle are supposed to come from JRE and which from the bundle itself, so returned classpath
* access rules include all packages exported by the framework extension bundles.
*
* [1] http://blog.meschberger.ch/2008/10/osgi-bundles-require-classes-from.html
*/
public List<AccessRule> computeBootClasspathExtraAccessRules(StateHelper helper, BundleDescription desc) {
List<AccessRule> result = new ArrayList<>();
ExportPackageDescription[] exports = helper.getVisiblePackages(desc);
for (ExportPackageDescription export : exports) {
BundleDescription host = export.getExporter();
BaseDescription fragment = ((ExportPackageDescriptionImpl) export).getFragmentDeclaration();
if (host.getBundleId() == 0 && fragment != null && isFrameworkExtension(fragment.getSupplier())) {
result.add(getRule(helper, host, export));
}
}
return result;
}
private boolean isFrameworkExtension(BundleDescription bundle) {
OsgiManifest mf = manifestReader.loadManifest(new File(bundle.getLocation()));
ManifestElement[] elements = mf.getManifestElements(Constants.FRAGMENT_HOST);
if (elements.length == 1) {
if (Constants.EXTENSION_FRAMEWORK.equals(elements[0].getDirective(Constants.EXTENSION_DIRECTIVE))) {
return true;
}
}
return false;
}
}