| /******************************************************************************* |
| * Copyright (c) 2008, 2018 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.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Dictionary; |
| import java.util.Hashtable; |
| import java.util.LinkedHashMap; |
| import java.util.LinkedHashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Properties; |
| import java.util.Set; |
| |
| import org.apache.maven.project.MavenProject; |
| import org.codehaus.plexus.component.annotations.Component; |
| import org.codehaus.plexus.component.annotations.Requirement; |
| import org.codehaus.plexus.logging.Logger; |
| import org.eclipse.osgi.internal.resolver.StateImpl; |
| import org.eclipse.osgi.service.resolver.BundleDescription; |
| import org.eclipse.osgi.service.resolver.BundleSpecification; |
| import org.eclipse.osgi.service.resolver.HostSpecification; |
| import org.eclipse.osgi.service.resolver.ResolverError; |
| import org.eclipse.osgi.service.resolver.State; |
| import org.eclipse.osgi.service.resolver.StateObjectFactory; |
| import org.eclipse.osgi.service.resolver.VersionConstraint; |
| import org.eclipse.osgi.util.ManifestElement; |
| import org.eclipse.tycho.ArtifactDescriptor; |
| import org.eclipse.tycho.ArtifactType; |
| import org.eclipse.tycho.artifacts.DependencyArtifacts; |
| import org.eclipse.tycho.core.TargetPlatformConfiguration; |
| import org.eclipse.tycho.core.TychoConstants; |
| import org.eclipse.tycho.core.ee.ExecutionEnvironmentUtils; |
| import org.eclipse.tycho.core.ee.shared.ExecutionEnvironment; |
| import org.eclipse.tycho.core.resolver.shared.PlatformPropertiesUtils; |
| import org.eclipse.tycho.core.shared.TargetEnvironment; |
| import org.eclipse.tycho.core.utils.TychoProjectUtils; |
| import org.osgi.framework.BundleException; |
| import org.osgi.framework.Constants; |
| |
| @Component(role = EquinoxResolver.class) |
| public class EquinoxResolver { |
| // see http://wiki.osgi.org/wiki/System_Bundle |
| public static final String SYSTEM_BUNDLE_SYMBOLIC_NAME = "system.bundle"; |
| public static final long SYSTEM_BUNDLE_ID = 0L; |
| |
| private static StateObjectFactory factory = StateObjectFactory.defaultFactory; |
| |
| @Requirement |
| private BundleReader manifestReader; |
| |
| @Requirement |
| private Logger logger; |
| |
| public State newResolvedState(MavenProject project, ExecutionEnvironment ee, boolean ignoreEE, |
| DependencyArtifacts artifacts) throws BundleException { |
| Properties properties = getPlatformProperties(project, ee); |
| |
| State state = newState(artifacts, properties, ignoreEE); |
| |
| resolveState(state); |
| |
| BundleDescription bundleDescription = state.getBundleByLocation(getNormalizedPath(project.getBasedir())); |
| |
| assertResolved(state, bundleDescription); |
| |
| return state; |
| } |
| |
| public State newResolvedState(File basedir, ExecutionEnvironment ee, DependencyArtifacts artifacts) |
| throws BundleException { |
| Properties properties = getPlatformProperties(new Properties(), null, ee); |
| |
| State state = newState(artifacts, properties, false); |
| |
| resolveState(state); |
| |
| BundleDescription bundleDescription = state.getBundleByLocation(getNormalizedPath(basedir)); |
| |
| assertResolved(state, bundleDescription); |
| |
| return state; |
| } |
| |
| protected void resolveState(State state) { |
| state.resolve(false); |
| |
| // warn about missing/ambiguous/inconsistent system.bundle |
| BundleDescription[] bundles = state.getBundles("system.bundle"); |
| if (bundles == null || bundles.length == 0) { |
| logger.warn("No system.bundle"); |
| } else if (bundles.length > 1) { |
| logger.warn("Multiple system.bundles " + Arrays.toString(bundles)); |
| } else if (bundles[0].getBundleId() != SYSTEM_BUNDLE_ID) { |
| logger.warn("system.bundle bundleId == " + bundles[0].getBundleId()); |
| } |
| } |
| |
| public String toDebugString(State state) { |
| StringBuilder sb = new StringBuilder("Resolved OSGi state\n"); |
| for (BundleDescription otherBundle : state.getBundles()) { |
| if (!otherBundle.isResolved()) { |
| sb.append("NOT "); |
| } |
| sb.append("RESOLVED "); |
| sb.append(otherBundle.toString()).append(" : ").append(otherBundle.getLocation()); |
| sb.append('\n'); |
| for (ResolverError error : state.getResolverErrors(otherBundle)) { |
| sb.append('\t').append(error.toString()).append('\n'); |
| } |
| } |
| return sb.toString(); |
| } |
| |
| protected Properties getPlatformProperties(MavenProject project, ExecutionEnvironment ee) { |
| TargetPlatformConfiguration configuration = TychoProjectUtils.getTargetPlatformConfiguration(project); |
| TargetEnvironment environment = configuration.getEnvironments().get(0); |
| |
| Properties properties = new Properties(); |
| properties.putAll((Properties) project.getContextValue(TychoConstants.CTX_MERGED_PROPERTIES)); |
| |
| return getPlatformProperties(properties, environment, ee); |
| } |
| |
| protected Properties getPlatformProperties(Properties properties, TargetEnvironment environment, |
| ExecutionEnvironment ee) { |
| if (environment != null) { |
| properties.put(PlatformPropertiesUtils.OSGI_OS, environment.getOs()); |
| properties.put(PlatformPropertiesUtils.OSGI_WS, environment.getWs()); |
| properties.put(PlatformPropertiesUtils.OSGI_ARCH, environment.getArch()); |
| } |
| |
| ExecutionEnvironmentUtils.applyProfileProperties(properties, ee.getProfileProperties()); |
| |
| // Put Equinox OSGi resolver into development mode. |
| // See http://www.nabble.com/Re:-resolving-partially-p18449054.html |
| properties.put(StateImpl.OSGI_RESOLVER_MODE, StateImpl.DEVELOPMENT_MODE); |
| return properties; |
| } |
| |
| protected State newState(DependencyArtifacts artifacts, Properties properties, boolean ignoreEE) |
| throws BundleException { |
| State state = factory.createState(true); |
| |
| Map<File, Dictionary<String, String>> systemBundles = new LinkedHashMap<>(); |
| Map<File, Dictionary<String, String>> externalBundles = new LinkedHashMap<>(); |
| Map<File, Dictionary<String, String>> projects = new LinkedHashMap<>(); |
| |
| for (ArtifactDescriptor artifact : artifacts.getArtifacts(ArtifactType.TYPE_ECLIPSE_PLUGIN)) { |
| File location = artifact.getLocation(); |
| Dictionary<String, String> mf = loadManifest(location); |
| if (isFrameworkImplementation(location, mf)) { |
| systemBundles.put(location, mf); |
| } else if (artifact.getMavenProject() != null) { |
| projects.put(location, mf); |
| } else { |
| externalBundles.put(location, mf); |
| } |
| } |
| |
| long id = SYSTEM_BUNDLE_ID; |
| if (systemBundles.isEmpty()) { |
| // there were no OSGi framework implementations among bundles being resolve |
| // fabricate system.bundle to export visible JRE packages |
| state.addBundle(factory.createBundleDescription(state, getSystemBundleManifest(properties), "", id++)); |
| } else { |
| // use first framework implementation found as system bundle, i.e. bundleId==0 |
| // TODO test what happens when multiple framework implementations are present |
| for (Map.Entry<File, Dictionary<String, String>> entry : systemBundles.entrySet()) { |
| addBundle(state, id++, entry.getKey(), entry.getValue(), false); |
| } |
| } |
| for (Map.Entry<File, Dictionary<String, String>> entry : externalBundles.entrySet()) { |
| addBundle(state, id++, entry.getKey(), entry.getValue(), false); |
| } |
| for (Map.Entry<File, Dictionary<String, String>> entry : projects.entrySet()) { |
| // make sure reactor projects override anything from the target platform |
| // that has the same bundle symbolic name |
| addBundle(state, id++, entry.getKey(), entry.getValue(), true/* override */); |
| } |
| |
| List<Dictionary<Object, Object>> allProps = new ArrayList<>(); |
| |
| // force our system.bundle |
| Hashtable<Object, Object> platformProperties = new Hashtable<>(properties); |
| platformProperties.put(StateImpl.STATE_SYSTEM_BUNDLE, state.getBundle(SYSTEM_BUNDLE_ID).getSymbolicName()); |
| allProps.add(platformProperties); |
| if (ignoreEE) { |
| // ignoring EE by adding all known EEs |
| for (String profile : ExecutionEnvironmentUtils.getProfileNames()) { |
| Properties envProps = ExecutionEnvironmentUtils.getExecutionEnvironment(profile).getProfileProperties(); |
| String systemPackages = envProps.getProperty("org.osgi.framework.system.packages"); |
| String execEnv = envProps.getProperty("org.osgi.framework.executionenvironment"); |
| Dictionary<Object, Object> prop = new Hashtable<>(); |
| prop.put("org.osgi.framework.system.packages", systemPackages); |
| prop.put("org.osgi.framework.executionenvironment", execEnv); |
| allProps.add(prop); |
| } |
| } |
| |
| Dictionary<Object, Object>[] stateProperties = allProps.toArray(new Dictionary[allProps.size()]); |
| |
| state.setPlatformProperties(stateProperties); |
| |
| return state; |
| } |
| |
| private boolean isFrameworkImplementation(File location, Dictionary<String, String> mf) { |
| // starting with OSGi R4.2, /META-INF/services/org.osgi.framework.launch.FrameworkFactory |
| // can be used to detect framework implementation |
| // See http://www.osgi.org/javadoc/r4v42/org/osgi/framework/launch/FrameworkFactory.html |
| |
| // Assume only framework implementation export org.osgi.framework package |
| String value = mf.get(Constants.EXPORT_PACKAGE); |
| if (value != null) { |
| try { |
| ManifestElement[] exports = ManifestElement.parseHeader(Constants.EXPORT_PACKAGE, value); |
| for (ManifestElement export : exports) { |
| if ("org.osgi.framework".equals(export.getValue())) { |
| return true; |
| } |
| } |
| } catch (BundleException e) { |
| // fall through |
| } |
| } |
| return false; |
| } |
| |
| public void addBundle(State state, long id, File bundleLocation, Dictionary<String, String> mf, boolean override) |
| throws BundleException { |
| BundleDescription descriptor = factory.createBundleDescription(state, mf, getNormalizedPath(bundleLocation), |
| id); |
| |
| if (override) { |
| BundleDescription[] conflicts = state.getBundles(descriptor.getSymbolicName()); |
| if (conflicts != null) { |
| for (BundleDescription conflict : conflicts) { |
| state.removeBundle(conflict); |
| logger.warn( |
| conflict.toString() + " has been replaced by another bundle with the same symbolic name " |
| + descriptor.toString()); |
| } |
| } |
| } |
| |
| state.addBundle(descriptor); |
| } |
| |
| private static String getNormalizedPath(File file) throws BundleException { |
| return file.getAbsolutePath(); |
| } |
| |
| private Dictionary<String, String> loadManifest(File bundleLocation) { |
| if (bundleLocation == null || !bundleLocation.exists()) { |
| throw new IllegalArgumentException("bundleLocation not found: " + bundleLocation); |
| } |
| |
| return manifestReader.loadManifest(bundleLocation).getHeaders(); |
| } |
| |
| private Dictionary<String, String> getSystemBundleManifest(Properties properties) { |
| String systemPackages = properties.getProperty(Constants.FRAMEWORK_SYSTEMPACKAGES); |
| |
| Dictionary<String, String> systemBundleManifest = new Hashtable<>(); |
| systemBundleManifest.put(Constants.BUNDLE_SYMBOLICNAME, SYSTEM_BUNDLE_SYMBOLIC_NAME); |
| systemBundleManifest.put(Constants.BUNDLE_VERSION, "0.0.0"); |
| systemBundleManifest.put(Constants.BUNDLE_MANIFESTVERSION, "2"); |
| systemBundleManifest.put(StateImpl.Eclipse_JREBUNDLE, "true"); |
| if (systemPackages != null && systemPackages.trim().length() > 0) { |
| systemBundleManifest.put(Constants.EXPORT_PACKAGE, systemPackages); |
| } else { |
| logger.warn( |
| "Undefined or empty org.osgi.framework.system.packages system property, system.bundle does not export any packages."); |
| } |
| |
| return systemBundleManifest; |
| } |
| |
| public void assertResolved(State state, BundleDescription desc) throws BundleException { |
| if (!desc.isResolved()) { |
| |
| if (logger.isDebugEnabled()) { |
| logger.debug("Equinox resolver state:\n" + toDebugString(state)); |
| } |
| |
| StringBuilder msg = new StringBuilder(); |
| msg.append("Bundle ").append(desc.getSymbolicName()).append(" cannot be resolved\n"); |
| msg.append("Resolution errors:\n"); |
| ResolverError[] errors = getResolverErrors(state, desc); |
| for (ResolverError error : errors) { |
| msg.append(" Bundle ").append(error.getBundle().getSymbolicName()).append(" - ") |
| .append(error.toString()).append("\n"); |
| } |
| |
| throw new BundleException(msg.toString()); |
| } |
| } |
| |
| public ResolverError[] getResolverErrors(State state, BundleDescription bundle) { |
| Set<ResolverError> errors = new LinkedHashSet<>(); |
| getRelevantErrors(state, errors, bundle); |
| return errors.toArray(new ResolverError[errors.size()]); |
| } |
| |
| private void getRelevantErrors(State state, Set<ResolverError> errors, BundleDescription bundle) { |
| ResolverError[] bundleErrors = state.getResolverErrors(bundle); |
| for (ResolverError error : bundleErrors) { |
| errors.add(error); |
| VersionConstraint constraint = error.getUnsatisfiedConstraint(); |
| if (constraint instanceof BundleSpecification || constraint instanceof HostSpecification) { |
| BundleDescription[] requiredBundles = state.getBundles(constraint.getName()); |
| for (BundleDescription requiredBundle : requiredBundles) { |
| // if one of the constraints is the bundle itself (recursive dependency) |
| // do not handle that bundle (again). See bug 442594. |
| if (bundle.equals(requiredBundle)) { |
| continue; |
| } |
| getRelevantErrors(state, errors, requiredBundle); |
| } |
| } |
| } |
| } |
| |
| } |