| /******************************************************************************* |
| * Copyright (c) 2010, 2017 EclipseSource 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: |
| * EclipseSource Inc. - initial API and implementation |
| * IBM Corporation - ongoing enhancements |
| * Manumitting Technologies Inc - Bug 437726 |
| * Lars Vogel <Lars.Vogel@vogella.com> - Bug 477527 |
| *******************************************************************************/ |
| package org.eclipse.pde.internal.core.target; |
| |
| import java.io.File; |
| import java.net.URI; |
| import java.util.*; |
| import java.util.concurrent.ConcurrentHashMap; |
| import org.eclipse.core.runtime.*; |
| import org.eclipse.core.runtime.preferences.IPreferencesService; |
| import org.eclipse.equinox.internal.p2.director.PermissiveSlicer; |
| import org.eclipse.equinox.internal.p2.engine.*; |
| import org.eclipse.equinox.internal.p2.engine.phases.*; |
| import org.eclipse.equinox.internal.p2.garbagecollector.GarbageCollector; |
| import org.eclipse.equinox.p2.core.*; |
| import org.eclipse.equinox.p2.engine.*; |
| import org.eclipse.equinox.p2.engine.query.IUProfilePropertyQuery; |
| import org.eclipse.equinox.p2.engine.spi.ProvisioningAction; |
| import org.eclipse.equinox.p2.metadata.*; |
| import org.eclipse.equinox.p2.metadata.MetadataFactory.InstallableUnitDescription; |
| import org.eclipse.equinox.p2.metadata.Version; |
| import org.eclipse.equinox.p2.planner.IPlanner; |
| import org.eclipse.equinox.p2.planner.IProfileChangeRequest; |
| import org.eclipse.equinox.p2.query.*; |
| import org.eclipse.equinox.p2.repository.*; |
| import org.eclipse.equinox.p2.repository.artifact.*; |
| import org.eclipse.equinox.p2.repository.metadata.IMetadataRepository; |
| import org.eclipse.equinox.p2.repository.metadata.IMetadataRepositoryManager; |
| import org.eclipse.pde.core.target.*; |
| import org.eclipse.pde.internal.core.PDECore; |
| import org.osgi.framework.*; |
| |
| public class P2TargetUtils { |
| |
| private static final String SOURCE_IU_ID = "org.eclipse.pde.core.target.source.bundles"; //$NON-NLS-1$ |
| |
| /** |
| * URI to the local directory where the p2 agent keeps its information. |
| */ |
| public static URI AGENT_LOCATION; |
| static { |
| try { |
| AGENT_LOCATION = URIUtil.fromString("file:" + PDECore.getDefault().getStateLocation().append(".p2")); //$NON-NLS-1$//$NON-NLS-2$ |
| } catch (Exception e) { |
| // should never happen |
| } |
| } |
| |
| /** |
| * Path to the local directory where the local bundle pool is stored for p2 profile |
| * based targets. |
| */ |
| public static final IPath BUNDLE_POOL = PDECore.getDefault().getStateLocation().append(".bundle_pool"); //$NON-NLS-1$ |
| |
| /** |
| * Path to the local directory where install folders are created for p2 profile |
| * based targets. |
| */ |
| static final IPath INSTALL_FOLDERS = PDECore.getDefault().getStateLocation().append(".install_folders"); //$NON-NLS-1$ |
| |
| /** |
| * Prefix for all profiles ID's associated with target definitions |
| */ |
| static final String PROFILE_ID_PREFIX = "TARGET_DEFINITION:"; //$NON-NLS-1$ |
| |
| /** |
| * Installable unit property to mark IU's that have been installed in a profile by |
| * a bundle container (rather than as a secondary/required IU). |
| */ |
| static final String PROP_INSTALLED_IU = PDECore.PLUGIN_ID + ".installed_iu"; //$NON-NLS-1$ |
| |
| /** |
| * Profile property that keeps track of provisioning mode for the target |
| * (slice versus plan). |
| */ |
| static final String PROP_PROVISION_MODE = PDECore.PLUGIN_ID + ".provision_mode"; //$NON-NLS-1$ |
| |
| /** |
| * Profile property that keeps track of provisioning mode for the target |
| * (all environments/true versus false). |
| */ |
| static final String PROP_ALL_ENVIRONMENTS = PDECore.PLUGIN_ID + ".all_environments"; //$NON-NLS-1$ |
| |
| /** |
| * Profile property that keeps track of the target sequence number |
| */ |
| static final String PROP_SEQUENCE_NUMBER = PDECore.PLUGIN_ID + ".sequence"; //$NON-NLS-1$ |
| |
| /** |
| * Profile property that tracks whether or not source to be auto-included |
| */ |
| static final String PROP_AUTO_INCLUDE_SOURCE = PDECore.PLUGIN_ID + ".autoIncludeSource"; //$NON-NLS-1$ |
| |
| /** |
| * Profile property that tracks whether or not the configuration phase should be executed when installing |
| */ |
| static final String PROP_INCLUDE_CONFIGURE_PHASE = PDECore.PLUGIN_ID + ".includeConfigure"; //$NON-NLS-1$ |
| |
| /** |
| * Table mapping {@link ITargetDefinition} to synchronizer (P2TargetUtils) instance. |
| */ |
| private static Map<ITargetDefinition, P2TargetUtils> synchronizers = new HashMap<>(); |
| |
| /** |
| * Table mapping of ITargetDefinition and IFileArtifactRepository |
| */ |
| public static Map<ITargetDefinition, IFileArtifactRepository> fgTargetArtifactRepo = new ConcurrentHashMap<>(); |
| |
| /** |
| * Table mapping IArtifactKey to table map of IFileArtifactRepository and IFileArtifactRepository |
| */ |
| public static Map<IArtifactKey, Map<IFileArtifactRepository, File>> fgArtifactKeyRepoFile = new ConcurrentHashMap<>(); |
| |
| /** |
| * The profile to be synchronized |
| */ |
| private IProfile fProfile; |
| |
| /** |
| * Whether this container must have all required IUs of the selected IUs available and included |
| * in the target to resolve successfully. If this option is true, the planner will be used to resolve |
| * otherwise the slicer is used. The planner can describe any missing requirements as errors. |
| * <p> |
| * <code>true</code> by default |
| * </p> |
| */ |
| private boolean fIncludeAllRequired = true; |
| |
| /** |
| * Whether this container should download and include environment (platform) specific units for all |
| * available platforms (vs only the current target definition's environment settings). Only supported |
| * by the slicer so {@link IUBundleContainer#INCLUDE_REQUIRED} must be turned off for this setting to be used. |
| * <p> |
| * <code>false</code> by default |
| * </p> |
| */ |
| private boolean fIncludeMultipleEnvironments = false; |
| |
| /** |
| * Whether this container should download and include source bundles for the selected units if the associated |
| * source is available in the repository. |
| * <p> |
| * <code>false</code> by default |
| * </p> |
| */ |
| private boolean fIncludeSource = false; |
| |
| /** |
| * Whether this container should execute the configure phase when installing the IUs |
| * <p> |
| * <code>false</code> by default |
| * </p> |
| */ |
| private boolean fIncludeConfigurePhase = false; |
| |
| /** |
| * Deletes any profiles associated with target definitions that no longer exist |
| * and returns a list of profile identifiers that were deleted. |
| */ |
| public static List<String> cleanOrphanedTargetDefinitionProfiles() throws CoreException { |
| List<String> list = new ArrayList<>(); |
| IProfileRegistry registry = getProfileRegistry(); |
| ITargetPlatformService tps = (ITargetPlatformService) PDECore.getDefault().acquireService(ITargetPlatformService.class.getName()); |
| if (registry != null && tps != null) { |
| IProfile[] profiles = registry.getProfiles(); |
| for (IProfile profile : profiles) { |
| String id = profile.getProfileId(); |
| if (id.startsWith(PROFILE_ID_PREFIX)) { |
| String memento = id.substring(PROFILE_ID_PREFIX.length()); |
| ITargetHandle handle = tps.getTarget(memento); |
| if (!handle.exists()) { |
| deleteProfile(handle); |
| list.add(id); |
| } |
| } |
| } |
| } |
| return list; |
| } |
| |
| /** |
| * Recursively deletes folder and files. |
| * |
| * @param folder |
| */ |
| private static void delete(File folder) { |
| File[] files = folder.listFiles(); |
| for (File file : files) { |
| if (file.isDirectory()) { |
| delete(file); |
| } |
| file.delete(); |
| } |
| folder.delete(); |
| } |
| |
| /** |
| * Deletes the profile associated with this target handle, if any. Returns |
| * <code>true</code> if a profile existed and was deleted, otherwise <code>false</code>. |
| * |
| * @throws CoreException if unable to delete the profile |
| */ |
| public static void deleteProfile(ITargetHandle handle) throws CoreException { |
| IProfileRegistry registry = getProfileRegistry(); |
| if (registry != null) { |
| IProfile profile = registry.getProfile(getProfileId(handle)); |
| if (profile != null) { |
| String location = profile.getProperty(IProfile.PROP_INSTALL_FOLDER); |
| registry.removeProfile(getProfileId(handle)); |
| if (location != null && location.length() > 0) { |
| File folder = new File(location); |
| if (folder.exists()) { |
| delete(folder); |
| } |
| } |
| } |
| } |
| } |
| |
| @SuppressWarnings("restriction") |
| public static void forceCheckTarget(final ITargetDefinition target) { |
| final P2TargetUtils result = getSynchronizer(target); |
| if (result != null && result.fProfile != null && result.fProfile instanceof Profile) { |
| ((Profile) result.fProfile).setProperty(PROP_SEQUENCE_NUMBER, "-1"); //$NON-NLS-1$ |
| } |
| } |
| |
| /** |
| * Performs garbage collection based on remaining profiles. Should be called to avoid |
| * having PDE's bundle pool area grow unbounded. |
| */ |
| public static void garbageCollect() { |
| try { |
| IProfile[] profiles = getProfileRegistry().getProfiles(); |
| for (IProfile profile : profiles) { |
| if (profile.getProfileId().startsWith(PROFILE_ID_PREFIX)) { |
| getGarbageCollector().runGC(profile); |
| } |
| } |
| } catch (CoreException e) { |
| // XXX likely should log something here. |
| return; |
| } |
| } |
| |
| /** |
| * Generates the environment properties string for this target definition's p2 profile. |
| * |
| * @return environment properties |
| */ |
| private String generateEnvironmentProperties(ITargetDefinition target) { |
| // TODO: are there constants for these keys? |
| StringBuilder env = new StringBuilder(); |
| String ws = target.getWS(); |
| if (ws == null) { |
| ws = Platform.getWS(); |
| } |
| env.append("osgi.ws="); //$NON-NLS-1$ |
| env.append(ws); |
| env.append(","); //$NON-NLS-1$ |
| String os = target.getOS(); |
| if (os == null) { |
| os = Platform.getOS(); |
| } |
| env.append("osgi.os="); //$NON-NLS-1$ |
| env.append(os); |
| env.append(","); //$NON-NLS-1$ |
| String arch = target.getArch(); |
| if (arch == null) { |
| arch = Platform.getOSArch(); |
| } |
| env.append("osgi.arch="); //$NON-NLS-1$ |
| env.append(arch); |
| return env.toString(); |
| } |
| |
| /** |
| * Generates the NL property for this target definition's p2 profile. |
| * |
| * @return NL profile property |
| */ |
| private String generateNLProperty(ITargetDefinition target) { |
| String nl = target.getNL(); |
| if (nl == null) { |
| nl = Platform.getNL(); |
| } |
| return nl; |
| } |
| |
| public static IProvisioningAgent getAgent() throws CoreException { |
| //Is there already an agent for this location? |
| String filter = "(locationURI=" + String.valueOf(AGENT_LOCATION) + ")"; //$NON-NLS-1$//$NON-NLS-2$ |
| ServiceReference<?>[] serviceReferences = null; |
| BundleContext context = PDECore.getDefault().getBundleContext(); |
| try { |
| serviceReferences = context.getServiceReferences(IProvisioningAgent.SERVICE_NAME, filter); |
| if (serviceReferences != null) { |
| return (IProvisioningAgent) context.getService(serviceReferences[0]); |
| } |
| } catch (InvalidSyntaxException e) { |
| // ignore |
| } finally { |
| if (serviceReferences != null) |
| context.ungetService(serviceReferences[0]); |
| } |
| |
| IProvisioningAgentProvider provider = (IProvisioningAgentProvider) PDECore.getDefault().acquireService(IProvisioningAgentProvider.SERVICE_NAME); |
| try { |
| if (provider == null) { |
| throw new CoreException(new Status(IStatus.ERROR, PDECore.PLUGIN_ID, Messages.IUBundleContainer_7)); |
| } |
| IProvisioningAgent agent = provider.createAgent(AGENT_LOCATION); |
| if (agent == null) { |
| throw new CoreException(new Status(IStatus.ERROR, PDECore.PLUGIN_ID, Messages.IUBundleContainer_7)); |
| } |
| // turn off the garbage collector for the PDE agent. GC is managed on a coarser grain |
| GarbageCollector garbageCollector = (GarbageCollector) agent.getService(GarbageCollector.class.getName()); |
| if (garbageCollector != null) { |
| garbageCollector.stop(); |
| } |
| return agent; |
| } catch (ProvisionException e) { |
| throw new CoreException(new Status(IStatus.ERROR, PDECore.PLUGIN_ID, Messages.IUBundleContainer_7, e)); |
| } |
| } |
| |
| /** |
| * Returns the global p2 provisioning agent. This is useful when looking to inherit or use |
| * some settings from the global p2 world. |
| * |
| * @return the global p2 provisioning agent |
| * @throws CoreException |
| */ |
| public static IProvisioningAgent getGlobalAgent() throws CoreException { |
| IProvisioningAgent agent = (IProvisioningAgent) PDECore.getDefault().acquireService(IProvisioningAgent.SERVICE_NAME); |
| if (agent == null) |
| throw new CoreException(new Status(IStatus.ERROR, PDECore.PLUGIN_ID, Messages.IUBundleContainer_11)); |
| return agent; |
| } |
| |
| /** |
| * Returns the provisioning agent location service. |
| * |
| * @return provisioning agent location service |
| * @throws CoreException if none |
| */ |
| public static IAgentLocation getAgentLocation() throws CoreException { |
| IAgentLocation result = (IAgentLocation) getAgent().getService(IAgentLocation.SERVICE_NAME); |
| if (result == null) |
| throw new CoreException(new Status(IStatus.ERROR, PDECore.PLUGIN_ID, Messages.IUBundleContainer_10)); |
| return result; |
| } |
| |
| /** |
| * Returns the provisioning engine service. |
| * |
| * @return provisioning engine |
| * @throws CoreException if none |
| */ |
| public static IArtifactRepositoryManager getArtifactRepositoryManager() throws CoreException { |
| IArtifactRepositoryManager manager = (IArtifactRepositoryManager) getAgent().getService(IArtifactRepositoryManager.class.getName()); |
| if (manager == null) { |
| throw new CoreException(new Status(IStatus.ERROR, PDECore.PLUGIN_ID, Messages.IUBundleContainer_3)); |
| } |
| return manager; |
| } |
| |
| /** |
| * Returns the local bundle pool (repository) where bundles are stored |
| * |
| * @return local file artifact repository |
| * @throws CoreException |
| */ |
| public static IFileArtifactRepository getBundlePool() throws CoreException { |
| URI uri = BUNDLE_POOL.toFile().toURI(); |
| IArtifactRepositoryManager manager = getArtifactRepositoryManager(); |
| try { |
| if (manager.contains(uri)) |
| return (IFileArtifactRepository) manager.loadRepository(uri, null); |
| } catch (CoreException e) { |
| // could not load or there wasn't one, fall through to create |
| } |
| String repoName = "PDE Target Bundle Pool"; //$NON-NLS-1$ |
| IArtifactRepository result = manager.createRepository(uri, repoName, IArtifactRepositoryManager.TYPE_SIMPLE_REPOSITORY, null); |
| return (IFileArtifactRepository) result; |
| } |
| |
| /** |
| * Returns the provisioning engine service. |
| * |
| * @return provisioning engine |
| * @throws CoreException if none |
| */ |
| public static IEngine getEngine() throws CoreException { |
| IEngine engine = (IEngine) getAgent().getService(IEngine.class.getName()); |
| if (engine == null) { |
| throw new CoreException(new Status(IStatus.ERROR, PDECore.PLUGIN_ID, Messages.IUBundleContainer_4)); |
| } |
| return engine; |
| } |
| |
| /** |
| * Returns the p2 garbage collector |
| * |
| * @return p2 garbage collector |
| * @throws CoreException if none |
| */ |
| public static GarbageCollector getGarbageCollector() throws CoreException { |
| GarbageCollector engine = (GarbageCollector) getAgent().getService(GarbageCollector.class.getName()); |
| if (engine == null) { |
| throw new CoreException(new Status(IStatus.ERROR, PDECore.PLUGIN_ID, Messages.IUBundleContainer_9)); |
| } |
| return engine; |
| } |
| |
| /** |
| * Returns the provisioning planner. |
| * |
| * @return provisioning planner |
| * @throws CoreException if none |
| */ |
| public static IPlanner getPlanner() throws CoreException { |
| IPlanner planner = (IPlanner) getAgent().getService(IPlanner.class.getName()); |
| if (planner == null) { |
| throw new CoreException(new Status(IStatus.ERROR, PDECore.PLUGIN_ID, Messages.IUBundleContainer_5)); |
| } |
| return planner; |
| } |
| |
| /** |
| * Returns the preferences service. |
| * |
| * @return preferences service or null if none |
| */ |
| public static IPreferencesService getPreferences() { |
| return (IPreferencesService) PDECore.getDefault().acquireService(IPreferencesService.class.getName()); |
| } |
| |
| /** |
| * Returns whether the contents of the profile matches the expected contents of the target definition |
| * |
| * @return whether or not the profile and target definitions match |
| * @throws CoreException in unable to retrieve profile |
| */ |
| private boolean checkProfile(ITargetDefinition target) throws CoreException { |
| // make sure we have a profile to validate |
| if (fProfile == null) { |
| return false; |
| } |
| |
| // check that the target and profiles are in sync. If they are then life is good. |
| // If they are not equal, there is still a chance that everything is ok. |
| String profileNumber = fProfile.getProperty(PROP_SEQUENCE_NUMBER); |
| if (Integer.toString(((TargetDefinition) target).getSequenceNumber()).equals(profileNumber)) { |
| return true; |
| } |
| |
| // check if all environments setting is the same |
| boolean all = false; |
| String value = fProfile.getProperty(PROP_ALL_ENVIRONMENTS); |
| if (value != null) { |
| all = Boolean.valueOf(value).booleanValue(); |
| if (!Boolean.toString(getIncludeAllEnvironments()).equals(value)) { |
| return false; |
| } |
| } |
| |
| // ensure environment & NL settings are still the same (else we need a new profile) |
| String property = null; |
| if (!all) { |
| property = generateEnvironmentProperties(target); |
| value = fProfile.getProperty(IProfile.PROP_ENVIRONMENTS); |
| if (!property.equals(value)) { |
| return false; |
| } |
| } |
| property = generateNLProperty(target); |
| value = fProfile.getProperty(IProfile.PROP_NL); |
| if (!property.equals(value)) { |
| return false; |
| } |
| |
| // check provisioning mode: slice versus plan |
| if (!getProvisionMode(target).equals(fProfile.getProperty(PROP_PROVISION_MODE))) { |
| return false; |
| } |
| |
| // check that the include source flag matches what the profile represents |
| if (getIncludeSource() != Boolean.valueOf(fProfile.getProperty(PROP_AUTO_INCLUDE_SOURCE)).booleanValue()) { |
| return false; |
| } |
| |
| if (getIncludeConfigurePhase() != Boolean.valueOf(fProfile.getProperty(PROP_INCLUDE_CONFIGURE_PHASE)).booleanValue()) { |
| return false; |
| } |
| |
| // check top level IU's. If any have been removed from the containers that are |
| // still in the profile, we need to recreate (rather than uninstall) |
| IUProfilePropertyQuery propertyQuery = new IUProfilePropertyQuery(PROP_INSTALLED_IU, Boolean.toString(true)); |
| IQueryResult<?> queryResult = fProfile.query(propertyQuery, null); |
| Iterator<?> iterator = queryResult.iterator(); |
| Set<NameVersionDescriptor> installedIUs = new HashSet<>(); |
| while (iterator.hasNext()) { |
| IInstallableUnit unit = (IInstallableUnit) iterator.next(); |
| installedIUs.add(new NameVersionDescriptor(unit.getId(), unit.getVersion().toString())); |
| } |
| ITargetLocation[] containers = target.getTargetLocations(); |
| if (containers == null) { |
| return installedIUs.isEmpty(); |
| } |
| for (ITargetLocation container : containers) { |
| if (container instanceof IUBundleContainer) { |
| IUBundleContainer bc = (IUBundleContainer) container; |
| String[] ids = bc.getIds(); |
| Version[] versions = bc.getVersions(); |
| for (int j = 0; j < versions.length; j++) { |
| // if there is something in a container but not in the profile, recreate |
| if (!installedIUs.remove(new NameVersionDescriptor(ids[j], versions[j].toString()))) { |
| return false; |
| } |
| } |
| } |
| } |
| if (!installedIUs.isEmpty()) { |
| return false; |
| } |
| |
| // Phew! seems like the profile checks out. |
| return true; |
| } |
| |
| /** |
| * Sets whether all required units must be available to resolve this container. When <code>true</code> |
| * the resolve operation will use the planner to determine the complete set of IUs required to |
| * make the selected IUs runnable. If any dependencies are missing, the resolve operation will return an |
| * error explaining what problems exist. When <code>false</code> the resolve operation will use the slicer |
| * to determine what units to include. Any required units that are not available in the repositories will |
| * be ignored. |
| * <p> |
| * Since there is only one profile per target and the planner and slicer resolve methods are incompatible. |
| * </p> |
| * @param value whether all required units must be available to resolve this container |
| */ |
| public void setIncludeAllRequired(boolean value) { |
| fIncludeAllRequired = value; |
| } |
| |
| /** |
| * Returns whether all required units must be available to resolve this container. When <code>true</code> |
| * the resolve operation will use the planner to determine the complete set of IUs required to |
| * make the selected IUs runnable. If any dependencies are missing, the resolve operation will return an |
| * error explaining what problems exist. When <code>false</code> the resolve operation will use the slicer |
| * to determine what units to include. Any required units that are not available in the repositories will |
| * be ignored. |
| * |
| * @return whether all required units must be available to resolve this container |
| */ |
| public boolean getIncludeAllRequired() { |
| return fIncludeAllRequired; |
| } |
| |
| /** |
| * Sets whether all environment (platform) specific installable units should |
| * be included in this container when it is resolved. This feature is not supported |
| * by the planner so will only have an effect if the include all required setting |
| * is turned off ({@link #getIncludeAllRequired()}). |
| * <p> |
| * There is only one profile per target and this setting can only be set for the |
| * entire target definition. |
| * </p> |
| * @param value whether environment specific units should be included |
| */ |
| public void setIncludeAllEnvironments(boolean value) { |
| fIncludeMultipleEnvironments = value; |
| } |
| |
| /** |
| * Returns whether all environment (platform) specific installable units should |
| * be included in this container when it is resolved. This feature is not supported |
| * by the planner so will only have an effect if the include all required setting |
| * is turned off ({@link #getIncludeAllRequired()}). |
| * |
| * @return whether environment specific units should be included |
| */ |
| public boolean getIncludeAllEnvironments() { |
| return fIncludeMultipleEnvironments; |
| } |
| |
| /** |
| * Set whether or not the source bundles corresponding to any binary bundles should |
| * be automatically included in the target. |
| * |
| * @param value whether or not to include source |
| */ |
| public void setIncludeSource(boolean value) { |
| fIncludeSource = value; |
| } |
| |
| /** |
| * Returns whether or not source bundles corresponding to selected binary bundles |
| * are automatically included in the target. |
| * |
| * @return whether or not source is included automatically |
| */ |
| public boolean getIncludeSource() { |
| return fIncludeSource; |
| } |
| |
| /** |
| * Set whether or not the configuration phase should be executed when installing the IUs |
| * |
| * @param value whether or not to execute configuration phase |
| */ |
| public void setIncludeConfigurePhase(boolean value) { |
| fIncludeConfigurePhase = value; |
| } |
| |
| /** |
| * Returns whether or not the configuration phase should be executed when installing the IUs |
| * |
| * @return whether or not to execute configuration phase |
| */ |
| public boolean getIncludeConfigurePhase() { |
| return fIncludeConfigurePhase; |
| } |
| |
| /** |
| * Return whether or not the given target has a matching profile that is in sync |
| * @param target the target to check |
| * @return whether or not the target has been resolved at the p2 level |
| */ |
| public static boolean isResolved(ITargetDefinition target) { |
| P2TargetUtils synchronizer = getSynchronizer(target); |
| if (synchronizer == null) |
| return false; |
| try { |
| return synchronizer.checkProfile(target); |
| } catch (CoreException e) { |
| return false; |
| } |
| } |
| |
| /** |
| * Return whether or not the given target's matching profile is in sync |
| * @param target the target to check |
| * @return whether or not the target has been resolved at the p2 level |
| */ |
| public static boolean isProfileValid(ITargetDefinition target) { |
| P2TargetUtils synchronizer = getSynchronizer(target); |
| if (synchronizer == null) |
| return false; |
| try { |
| synchronizer.updateProfileFromRegistry(target); |
| return synchronizer.checkProfile(target); |
| } catch (CoreException e) { |
| return false; |
| } |
| } |
| |
| |
| private void updateProfileFromRegistry(ITargetDefinition target) { |
| if (fProfile == null) { |
| try { |
| fProfile = getProfileRegistry().getProfile(getProfileId(target)); |
| } catch (CoreException e) { |
| } |
| } |
| } |
| |
| /** |
| * Get the synchronizer to use for the given target. If there is already one on a |
| * container in the target, use that one. Otherwise, create a new one. Either way, |
| * ensure that all other IU containers in the target are using the same synchronizer. |
| * <p> |
| * The synchronizer is an instance of {@link P2TargetUtils} that has access to the |
| * profile and other p2 bits for the target. |
| * </p> |
| * |
| * @param target the target for which we are getting the synchronizer |
| * @return the discovered or created synchronizer |
| */ |
| static synchronized P2TargetUtils getSynchronizer(ITargetDefinition target) { |
| P2TargetUtils result = synchronizers.get(target); |
| if (result != null) |
| return result; |
| |
| result = new P2TargetUtils(); |
| synchronizers.put(target, result); |
| return result; |
| } |
| |
| /** |
| * Return the set of IUs in all IU containers associated with this synchronizer. |
| * This is a helper method so we don't have to expose the profile itself. |
| * |
| * @param target the target definition to query |
| * @param monitor the progress monitor to use |
| * @return the set of associated IUs |
| * @throws CoreException if there is a problem discovering the IUs |
| */ |
| public static IQueryResult<?> getIUs(ITargetDefinition target, IProgressMonitor monitor) throws CoreException { |
| P2TargetUtils synchronizer = getSynchronizer(target); |
| if (synchronizer == null) |
| return null; |
| synchronizer.synchronize(target, monitor); |
| return synchronizer.getProfile().query(QueryUtil.createIUAnyQuery(), null); |
| } |
| |
| /** |
| * Synchronize the profile and the target definition managed by this synchronizer. On return the profile will |
| * be resolved and correctly match the given target. The IUBundleContainers associated with |
| * the target will be notified of any changes in the underlying p2 profile and given an |
| * opportunity to update themselves accordingly. |
| * |
| * NOTE: this is a potentially *very* heavyweight operation. |
| * |
| * NOTE: this method is synchronized as it is effectively a "test and set" caching method. Two |
| * threads getting the profile at the same time should not execute concurrently or the profiles |
| * will get out of sync. |
| * |
| * @throws CoreException if there was a problem synchronizing |
| */ |
| public synchronized void synchronize(ITargetDefinition target, IProgressMonitor monitor) throws CoreException { |
| SubMonitor progress = SubMonitor.convert(monitor, 100); |
| |
| // Happiness if we have a profile and it checks out or if we can load one and it checks out. |
| if (fProfile == null) |
| fProfile = getProfileRegistry().getProfile(getProfileId(target)); |
| if (fProfile != null && checkProfile(target)) { |
| // always push the changes to the target because there can be many target objects |
| // for the same synchronizer (doh!) |
| notify(target, progress.split(25)); |
| return; |
| } |
| |
| // Either no profile was found or it was stale. Delete the current profile and recreate. |
| // This keeps the internal agent data clean and does not cost us much. |
| deleteProfile(target.getHandle()); |
| createProfile(target); |
| |
| if (progress.isCanceled()) |
| return; |
| progress.setWorkRemaining(75); |
| |
| try { |
| // Now resolve the profile and refresh the relate IU containers |
| if (getIncludeAllRequired()) |
| resolveWithPlanner(target, progress.split(60)); |
| else |
| resolveWithSlicer(target, progress.split(60)); |
| |
| // If we are updating a profile then delete the old snapshot on success. |
| notify(target, progress.split(15)); |
| } catch (CoreException e) { |
| // There was at least one problem getting the contents, delete the profile so we don't cache in a bad state, Bug 439034 |
| // TODO ALL we really want to delete is the sequence property, so that checkProfile will compare settings and contents |
| deleteProfile(target.getHandle()); |
| throw e; |
| } |
| } |
| |
| private void createProfile(ITargetDefinition target) throws CoreException, ProvisionException { |
| // create a new profile |
| IProfileRegistry registry = getProfileRegistry(); |
| if (registry == null) { |
| throw new CoreException(new Status(IStatus.ERROR, PDECore.PLUGIN_ID, Messages.AbstractTargetHandle_0)); |
| } |
| Map<String, String> properties = new HashMap<>(); |
| properties.put(IProfile.PROP_INSTALL_FOLDER, INSTALL_FOLDERS.append(Long.toString(LocalTargetHandle.nextTimeStamp())).toOSString()); |
| properties.put(IProfile.PROP_CACHE, BUNDLE_POOL.toOSString()); |
| properties.put(IProfile.PROP_INSTALL_FEATURES, Boolean.TRUE.toString()); |
| properties.put(IProfile.PROP_ENVIRONMENTS, generateEnvironmentProperties(target)); |
| properties.put(IProfile.PROP_NL, generateNLProperty(target)); |
| properties.put(PROP_SEQUENCE_NUMBER, Integer.toString(((TargetDefinition) target).getSequenceNumber())); |
| properties.put(PROP_PROVISION_MODE, getProvisionMode(target)); |
| properties.put(PROP_ALL_ENVIRONMENTS, Boolean.toString(getIncludeAllEnvironments())); |
| properties.put(PROP_AUTO_INCLUDE_SOURCE, Boolean.toString(getIncludeSource())); |
| properties.put(PROP_INCLUDE_CONFIGURE_PHASE, Boolean.toString(getIncludeConfigurePhase())); |
| fProfile = registry.addProfile(getProfileId(target), properties); |
| } |
| |
| /** |
| * Signal the relevant bundle containers that the given profile has changed. |
| */ |
| private void notify(ITargetDefinition target, IProgressMonitor monitor) throws CoreException { |
| // flush the target caches first since some of them are used in rebuilding |
| // the container caches (e.g., featureModels) |
| ((TargetDefinition) target).flushCaches(P2TargetUtils.BUNDLE_POOL.toOSString()); |
| // Now proactively recompute all the related container caches. |
| ITargetLocation[] containers = target.getTargetLocations(); |
| if (containers != null) { |
| for (ITargetLocation container : containers) { |
| if (container instanceof IUBundleContainer) { |
| ((IUBundleContainer) container).synchronizerChanged(target); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Returns the profile identifier for this target handle. There is one profile |
| * per target definition. |
| * |
| * @return profile identifier |
| * @throws CoreException in unable to generate identifier |
| */ |
| private static String getProfileId(ITargetHandle handle) throws CoreException { |
| StringBuilder buffer = new StringBuilder(); |
| buffer.append(PROFILE_ID_PREFIX); |
| // Memento strings can be very long and exceed max filename lengths, trim down to 200 + prefix + hashcode |
| String memento = handle.getMemento(); |
| if (memento.length() > 200) { |
| buffer.append(memento.substring(memento.length() - 200)); |
| buffer.append(memento.hashCode()); |
| } else { |
| buffer.append(memento); |
| } |
| return buffer.toString(); |
| } |
| |
| /** |
| * Returns the profile identifier for this target handle. There is one profile |
| * per target definition. |
| * |
| * @return definition the target to lookup |
| */ |
| public static String getProfileId(ITargetDefinition definition) { |
| try { |
| return getProfileId(definition.getHandle()); |
| } catch (CoreException e) { |
| // gotta make sure that this never happens. all we're doing here is computing a string. |
| return null; |
| } |
| } |
| |
| /** |
| * Returns the profile registry or <code>null</code> |
| * |
| * @return profile registry or <code>null</code> |
| * @throws CoreException |
| */ |
| public static IProfileRegistry getProfileRegistry() throws CoreException { |
| IProfileRegistry result = (IProfileRegistry) getAgent().getService(IProfileRegistry.SERVICE_NAME); |
| if (result == null) { |
| throw new CoreException(new Status(IStatus.ERROR, PDECore.PLUGIN_ID, Messages.IUBundleContainer_8)); |
| } |
| return result; |
| } |
| |
| /** |
| * Returns the mode used to provision this target - slice versus plan or <code>null</code> if |
| * this target has no software sites. |
| * |
| * @return provisioning mode or <code>null</code> |
| */ |
| private String getProvisionMode(ITargetDefinition target) { |
| return getIncludeAllRequired() ? TargetDefinitionPersistenceHelper.MODE_PLANNER : TargetDefinitionPersistenceHelper.MODE_SLICER; |
| } |
| |
| /** |
| * Returns the metadata repository manager. |
| * |
| * @return metadata repository manager |
| * @throws CoreException if none |
| */ |
| public static IMetadataRepositoryManager getRepoManager() throws CoreException { |
| IMetadataRepositoryManager manager = (IMetadataRepositoryManager) getAgent().getService(IMetadataRepositoryManager.SERVICE_NAME); |
| if (manager == null) { |
| throw new CoreException(new Status(IStatus.ERROR, PDECore.PLUGIN_ID, Messages.IUBundleContainer_2)); |
| } |
| return manager; |
| } |
| |
| /** |
| * Return a queryable on the metadata defined in the given repo locations |
| * |
| * @param repos the repos to lookup |
| * @param monitor the progress monitor |
| * @return the set of metadata repositories found |
| * @throws CoreException if there is a problem getting the repositories |
| */ |
| static IQueryable<IInstallableUnit> getQueryableMetadata(URI[] repos, IProgressMonitor monitor) throws CoreException { |
| IMetadataRepositoryManager manager = getRepoManager(); |
| if (repos == null) { |
| repos = manager.getKnownRepositories(IRepositoryManager.REPOSITORIES_ALL); |
| } |
| |
| int repoCount = repos.length; |
| SubMonitor subMonitor = SubMonitor.convert(monitor, repoCount); |
| |
| |
| List<IMetadataRepository> result = new ArrayList<>(repoCount); |
| MultiStatus repoStatus = new MultiStatus(PDECore.PLUGIN_ID, 0, Messages.IUBundleContainer_ProblemsLoadingRepositories, null); |
| for (int i = 0; i < repoCount; ++i) { |
| try { |
| result.add(manager.loadRepository(repos[i], subMonitor.split(1))); |
| } catch (ProvisionException e) { |
| repoStatus.add(e.getStatus()); |
| } |
| } |
| |
| if (result.size() != repos.length) { |
| throw new CoreException(repoStatus); |
| } |
| if (result.size() == 1) { |
| return result.get(0); |
| } |
| return QueryUtil.compoundQueryable(result); |
| } |
| |
| /** |
| * Used to resolve the contents of this container if the user is including all required software. The p2 planner is used |
| * to determine the complete set of IUs required to run the selected software. If all requirements are met, the bundles |
| * are downloaded from the repository into the bundle pool and added to the target definition. |
| * |
| * @param monitor for reporting progress |
| * @throws CoreException if there is a problem with the requirements or there is a problem downloading |
| */ |
| private void resolveWithPlanner(ITargetDefinition target, IProgressMonitor monitor) throws CoreException { |
| SubMonitor subMonitor = SubMonitor.convert(monitor, Messages.IUBundleContainer_0, 220); |
| |
| // Get the root IUs for every relevant container in the target definition |
| IInstallableUnit[] units = getRootIUs(target, subMonitor.split(20)); |
| |
| // create the provisioning plan |
| IPlanner planner = getPlanner(); |
| IProfileChangeRequest request = planner.createChangeRequest(fProfile); |
| // first remove everything that was explicitly installed. Then add it back. This has the net effect of |
| // removing everything that is no longer needed. |
| computeRemovals(fProfile, request, getIncludeSource()); |
| request.addAll(Arrays.asList(units)); |
| for (IInstallableUnit unit : units) { |
| request.setInstallableUnitProfileProperty(unit, PROP_INSTALLED_IU, Boolean.toString(true)); |
| } |
| |
| ProvisioningContext context = new ProvisioningContext(getAgent()); |
| context.setMetadataRepositories(getMetadataRepositories(target)); |
| context.setArtifactRepositories(getArtifactRepositories(target)); |
| |
| IProvisioningPlan plan = planner.getProvisioningPlan(request, context, subMonitor.split(20)); |
| IStatus status = plan.getStatus(); |
| if (!status.isOK()) { |
| throw new CoreException(status); |
| } |
| setPlanProperties(plan, target, TargetDefinitionPersistenceHelper.MODE_PLANNER); |
| IProvisioningPlan installerPlan = plan.getInstallerPlan(); |
| if (installerPlan != null) { |
| // this plan requires an update to the installer first, log the fact and attempt |
| // to continue, we don't want to update the running SDK while provisioning a target |
| PDECore.log(new Status(IStatus.INFO, PDECore.PLUGIN_ID, Messages.IUBundleContainer_6)); |
| } |
| subMonitor.split(10); |
| |
| // execute the provisioning plan |
| IPhaseSet phases = createPhaseSet(); |
| IEngine engine = getEngine(); |
| IStatus result = engine.perform(plan, phases, subMonitor.split(100)); |
| if (!result.isOK()) { |
| throw new CoreException(result); |
| } |
| |
| // Now that we have a plan with all the binary and explicit bundles, do a second pass and add |
| // in all the source. |
| try { |
| planInSourceBundles(fProfile, context, subMonitor.split(60)); |
| } catch (CoreException e) { |
| // XXX Review required: is adding in the source critical or optional? |
| // We failed adding in the source so remove the intermediate profile and rethrow |
| getProfileRegistry().removeProfile(fProfile.getProfileId(), fProfile.getTimestamp()); |
| throw e; |
| } |
| } |
| |
| private void setPlanProperties(IProvisioningPlan plan, ITargetDefinition definition, String mode) { |
| plan.setProfileProperty(PROP_PROVISION_MODE, mode); |
| plan.setProfileProperty(PROP_ALL_ENVIRONMENTS, Boolean.toString(getIncludeAllEnvironments())); |
| plan.setProfileProperty(PROP_AUTO_INCLUDE_SOURCE, Boolean.toString(getIncludeSource())); |
| plan.setProfileProperty(PROP_INCLUDE_CONFIGURE_PHASE, Boolean.toString(getIncludeConfigurePhase())); |
| plan.setProfileProperty(PROP_SEQUENCE_NUMBER, Integer.toString(((TargetDefinition) definition).getSequenceNumber())); |
| } |
| |
| /** |
| * @return the phase set to execute, includes the configuration phase if {@link #getIncludeConfigurePhase()} is <code>true<code> |
| */ |
| private IPhaseSet createPhaseSet() { |
| ArrayList<Phase> phases = new ArrayList<>(4); |
| phases.add(new Collect(100)); |
| phases.add(new Property(1)); |
| phases.add(new Uninstall(50, true)); |
| phases.add(new Install(50)); |
| phases.add(new CollectNativesPhase(100)); |
| if (getIncludeConfigurePhase()) { |
| phases.add(new Configure(100)); |
| } |
| |
| return new PhaseSet(phases.toArray(new Phase[phases.size()])); |
| } |
| |
| /** |
| * Update the given change request to remove anything that was explicitly installed |
| * including the internal source IU. |
| */ |
| private void computeRemovals(IProfile profile, IProfileChangeRequest request, boolean includeSource) { |
| // if include source is off then ensure that the source IU is removed. |
| if (!includeSource) { |
| IInstallableUnit sourceIU = getCurrentSourceIU(profile); |
| if (sourceIU != null) |
| request.remove(sourceIU); |
| } |
| // remove everything that is marked as roots. The plan will have the new roots added in anyway. |
| IQuery<IInstallableUnit> query = new IUProfilePropertyQuery(PROP_INSTALLED_IU, Boolean.toString(true)); |
| IQueryResult<IInstallableUnit> installedIUs = profile.query(query, null); |
| request.removeAll(installedIUs.toSet()); |
| } |
| |
| // run a second pass of the planner to add in the source bundles for everything that's |
| // in the current profile. |
| private void planInSourceBundles(IProfile fProfile, ProvisioningContext context, IProgressMonitor monitor) throws CoreException { |
| SubMonitor subMonitor = SubMonitor.convert(monitor, Messages.P2TargetUtils_ProvisioningSourceTask, 100); |
| |
| // create an IU that optionally and greedily requires the related source bundles. |
| // Completely replace any source IU that may already be in place |
| IInstallableUnit currentSourceIU = getCurrentSourceIU(fProfile); |
| |
| // determine the new version number. start at 1 |
| Version sourceVersion = Version.createOSGi(1, 0, 0); |
| if (currentSourceIU != null) { |
| Integer major = (Integer) currentSourceIU.getVersion().getSegment(0); |
| sourceVersion = Version.createOSGi(major.intValue() + 1, 0, 0); |
| } |
| IInstallableUnit sourceIU = createSourceIU(fProfile, sourceVersion); |
| |
| // call the planner again to add in the new source IU and all available source bundles |
| IPlanner planner = getPlanner(); |
| IProfileChangeRequest request = planner.createChangeRequest(fProfile); |
| if (currentSourceIU != null) |
| request.remove(currentSourceIU); |
| request.add(sourceIU); |
| IProvisioningPlan plan = planner.getProvisioningPlan(request, context, subMonitor.split(25)); |
| IStatus status = plan.getStatus(); |
| if (!status.isOK()) { |
| throw new CoreException(status); |
| } |
| if (subMonitor.isCanceled()) { |
| return; |
| } |
| |
| // execute the provisioning plan |
| long oldTimestamp = fProfile.getTimestamp(); |
| IPhaseSet phases = PhaseSetFactory.createDefaultPhaseSetExcluding(new String[] {PhaseSetFactory.PHASE_CHECK_TRUST, PhaseSetFactory.PHASE_CONFIGURE, PhaseSetFactory.PHASE_UNCONFIGURE, PhaseSetFactory.PHASE_UNINSTALL}); |
| IEngine engine = getEngine(); |
| plan.setProfileProperty(PROP_PROVISION_MODE, TargetDefinitionPersistenceHelper.MODE_PLANNER); |
| plan.setProfileProperty(PROP_ALL_ENVIRONMENTS, Boolean.toString(false)); |
| IStatus result = engine.perform(plan, phases, subMonitor.split(75)); |
| |
| if (subMonitor.isCanceled()) { |
| return; |
| } |
| if (!result.isOK()) { |
| throw new CoreException(result); |
| } |
| |
| // remove the old (intermediate) profile version now we have a new one with source. |
| getProfileRegistry().removeProfile(fProfile.getProfileId(), oldTimestamp); |
| } |
| |
| // Create and return an IU that has optional and greedy requirements on all source bundles |
| // related to bundle IUs in the given queryable. |
| private IInstallableUnit createSourceIU(IQueryable<IInstallableUnit> queryable, Version iuVersion) { |
| // compute the set of source bundles we could possibly need for the bundles in the profile |
| IRequirement bundleRequirement = MetadataFactory.createRequirement("org.eclipse.equinox.p2.eclipse.type", "bundle", null, null, false, false, false); //$NON-NLS-1$ //$NON-NLS-2$ |
| IQueryResult<IInstallableUnit> profileIUs = queryable.query(QueryUtil.createIUAnyQuery(), null); |
| ArrayList<IRequirement> requirements = new ArrayList<>(); |
| for (IInstallableUnit profileIU : profileIUs) { |
| if (profileIU.satisfies(bundleRequirement)) { |
| String id = profileIU.getId() + ".source"; //$NON-NLS-1$ |
| Version version = profileIU.getVersion(); |
| // use fully qualified name to avoid conflict with other VersionRange class |
| org.eclipse.equinox.p2.metadata.VersionRange range = new org.eclipse.equinox.p2.metadata.VersionRange(version, true, version, true); |
| IRequirement sourceRequirement = MetadataFactory.createRequirement("osgi.bundle", id, range, null, true, false, true); //$NON-NLS-1$ |
| requirements.add(sourceRequirement); |
| } |
| } |
| |
| InstallableUnitDescription sourceDescription = new MetadataFactory.InstallableUnitDescription(); |
| sourceDescription.setSingleton(true); |
| sourceDescription.setId(SOURCE_IU_ID); |
| sourceDescription.setVersion(iuVersion); |
| sourceDescription.addRequirements(requirements); |
| IProvidedCapability capability = MetadataFactory.createProvidedCapability(IInstallableUnit.NAMESPACE_IU_ID, SOURCE_IU_ID, iuVersion); |
| sourceDescription.setCapabilities(new IProvidedCapability[] {capability}); |
| return MetadataFactory.createInstallableUnit(sourceDescription); |
| } |
| |
| // Lookup and return (if any) the source IU in the given queryable. |
| private IInstallableUnit getCurrentSourceIU(IQueryable<IInstallableUnit> queryable) { |
| IQuery<IInstallableUnit> query = QueryUtil.createIUQuery(SOURCE_IU_ID); |
| IQueryResult<IInstallableUnit> list = queryable.query(query, null); |
| IInstallableUnit currentSourceIU = null; |
| if (!list.isEmpty()) |
| currentSourceIU = list.iterator().next(); |
| return currentSourceIU; |
| } |
| |
| /** |
| * Used to resolve the contents of this container when the user has chosen to manage the dependencies in the target |
| * themselves. The selected IUs and any required software that can be found will be retrieved from the repositories |
| * and added to the target. Any missing required software will be ignored. |
| * |
| * @param monitor for reporting progress |
| * @throws CoreException if there is a problem interacting with the repositories |
| */ |
| private void resolveWithSlicer(ITargetDefinition target, IProgressMonitor monitor) throws CoreException { |
| SubMonitor subMonitor = SubMonitor.convert(monitor, Messages.IUBundleContainer_0, 110); |
| |
| // resolve IUs |
| IInstallableUnit[] units = getRootIUs(target, subMonitor.split(40)); |
| |
| URI[] repositories = getMetadataRepositories(target); |
| int repoCount = repositories.length; |
| if (repoCount == 0) { |
| return; |
| } |
| IQueryable<IInstallableUnit> allMetadata = getQueryableMetadata(repositories, subMonitor.split(5)); |
| |
| // do an initial slice to add everything the user requested |
| IQueryResult<IInstallableUnit> queryResult = slice(units, allMetadata, target, subMonitor.split(5)); |
| if (queryResult == null || queryResult.isEmpty()) { |
| return; |
| } |
| |
| // If we are including source then create a source IU to bring in the relevant source |
| // bundles and run the slicer again. |
| if (getIncludeSource()) { |
| // Build an IU that represents all the source bundles and slice again to add them in if available |
| IInstallableUnit sourceIU = createSourceIU(queryResult, Version.createOSGi(1, 0, 0)); |
| IInstallableUnit[] units2 = new IInstallableUnit[units.length + 1]; |
| System.arraycopy(units, 0, units2, 0, units.length); |
| units2[units.length] = sourceIU; |
| |
| queryResult = slice(units2, allMetadata, target, subMonitor.split(5)); |
| if (queryResult == null || queryResult.isEmpty()) { |
| return; |
| } |
| } |
| |
| IEngine engine = getEngine(); |
| ProvisioningContext context = new ProvisioningContext(getAgent()); |
| context.setMetadataRepositories(repositories); |
| context.setArtifactRepositories(getArtifactRepositories(target)); |
| IProvisioningPlan plan = engine.createPlan(fProfile, context); |
| setPlanProperties(plan, target, TargetDefinitionPersistenceHelper.MODE_SLICER); |
| |
| Set<IInstallableUnit> newSet = queryResult.toSet(); |
| Iterator<IInstallableUnit> itor = newSet.iterator(); |
| while (itor.hasNext()) { |
| plan.addInstallableUnit(itor.next()); |
| } |
| for (IInstallableUnit unit : units) { |
| plan.setInstallableUnitProfileProperty(unit, PROP_INSTALLED_IU, Boolean.toString(true)); |
| } |
| |
| // remove all units that are in the current profile but not in the new slice |
| Set<?> toRemove = fProfile.query(QueryUtil.ALL_UNITS, null).toSet(); |
| toRemove.removeAll(newSet); |
| for (Object name : toRemove) { |
| plan.removeInstallableUnit((IInstallableUnit) name); |
| } |
| |
| subMonitor.split(5); |
| |
| // execute the provisioning plan |
| IPhaseSet phases = createPhaseSet(); |
| IStatus result = engine.perform(plan, phases, subMonitor.split(50)); |
| if (!result.isOK()) { |
| throw new CoreException(result); |
| } |
| } |
| |
| /** |
| * Sets up a slice operation to download the set of installable units that are both required |
| * by the provided root IUs and available in the repositories specified in the metadata. |
| * |
| * @param units The set of root IUs to search for dependencies of in the repositories |
| * @param allMetadata metadata describing the repositories where the slicer can search |
| * @param definition the target definition this operation is being executed for |
| * @param monitor progress monitor, done will not be called |
| * @return the result of the slice operation |
| * @throws CoreException if a problem occurs during the slice operation that should stop this location from resolving |
| */ |
| private IQueryResult<IInstallableUnit> slice(IInstallableUnit[] units, IQueryable<IInstallableUnit> allMetadata, ITargetDefinition definition, IProgressMonitor monitor) throws CoreException { |
| SubMonitor subMonitor = SubMonitor.convert(monitor, 100); |
| // slice IUs and all prerequisites |
| PermissiveSlicer slicer = null; |
| if (getIncludeAllEnvironments()) { |
| slicer = new PermissiveSlicer(allMetadata, new HashMap<String, String>(), true, false, true, true, false); |
| } else { |
| Map<String, String> props = new HashMap<>(); |
| props.put("osgi.os", definition.getOS() != null ? definition.getOS() : Platform.getOS()); //$NON-NLS-1$ |
| props.put("osgi.ws", definition.getWS() != null ? definition.getWS() : Platform.getWS()); //$NON-NLS-1$ |
| props.put("osgi.arch", definition.getArch() != null ? definition.getArch() : Platform.getOSArch()); //$NON-NLS-1$ |
| props.put("osgi.nl", definition.getNL() != null ? definition.getNL() : Platform.getNL()); //$NON-NLS-1$ |
| props.put(IProfile.PROP_INSTALL_FEATURES, Boolean.TRUE.toString()); |
| slicer = new PermissiveSlicer(allMetadata, props, true, false, false, true, false); |
| } |
| IQueryable<IInstallableUnit> slice = slicer.slice(units, subMonitor.split(50)); |
| IStatus sliceStatus = slicer.getStatus(); |
| // If the slicer encounters an error, stop the operation |
| if (sliceStatus.getSeverity() == IStatus.ERROR) { |
| throw new CoreException(sliceStatus); |
| } |
| |
| // Collect the IUs from the sliced |
| IQueryResult<IInstallableUnit> queryResult = null; |
| if (slice != null) |
| queryResult = slice.query(QueryUtil.createIUAnyQuery(), subMonitor.split(50)); |
| |
| // If the slicer encounters a non-error status, only report it if the slice returned no IU results |
| // It would be better to inform the user, but we do not want to stop the location from resolving (bug 350772) |
| if (!sliceStatus.isOK()) { |
| if (queryResult != null && !queryResult.iterator().hasNext()) { |
| throw new CoreException(sliceStatus); |
| } |
| } |
| |
| return queryResult; |
| } |
| |
| /** |
| * Returns the artifact repositories to consider when getting artifacts. Returns a default set of |
| * repositories if current repository settings are <code>null</code>). |
| * |
| * @return URI's of repositories to use when getting artifacts |
| * @exception CoreException |
| */ |
| private URI[] getArtifactRepositories(ITargetDefinition target) throws CoreException { |
| Set<URI> result = new HashSet<>(); |
| ITargetLocation[] containers = target.getTargetLocations(); |
| if (containers == null) { |
| containers = new ITargetLocation[0]; |
| } |
| IArtifactRepositoryManager manager = getArtifactRepositoryManager(); |
| for (ITargetLocation container : containers) { |
| if (container instanceof IUBundleContainer) { |
| URI[] repos = ((IUBundleContainer) container).getRepositories(); |
| if (repos == null) { |
| repos = manager.getKnownRepositories(IRepositoryManager.REPOSITORIES_ALL); |
| } |
| result.addAll(Arrays.asList(repos)); |
| } |
| } |
| if (useAdditionalLocalArtifacts()) { |
| // get all the artifact repos we know in the manager currently |
| result.addAll(Arrays.asList(manager.getKnownRepositories(IRepositoryManager.REPOSITORIES_ALL))); |
| |
| // Add in the IDE profile bundle pool and all known workspaces |
| findProfileRepos(result); |
| findWorkspaceRepos(result); |
| } |
| return result.toArray(new URI[result.size()]); |
| } |
| |
| /** |
| * return whether or not to use local artifact repositories when provisioning the target |
| */ |
| private boolean useAdditionalLocalArtifacts() { |
| // XXX consider using a preference here or another strategy if users are able to spec |
| // what local repos are to be considered. |
| return true; |
| } |
| |
| /** |
| * Add the artifact repos from the PDE target bundle pools from all known repos. For example, the list |
| * of "recent workspaces" maintained by the IDE is a good source. |
| * |
| * @param additionalRepos the set to which additional repos are added. |
| */ |
| private void findWorkspaceRepos(Set<URI> additionalRepos) { |
| IPreferencesService prefs = getPreferences(); |
| if (prefs == null) |
| return; |
| String recent = prefs.getString("org.eclipse.ui.ide", "RECENT_WORKSPACES", null, null); //$NON-NLS-1$ //$NON-NLS-2$ |
| if (recent == null) |
| return; |
| String[] recents = recent.split("\n"); //$NON-NLS-1$ |
| for (String recentWorkspace : recents) { |
| File bundlePool = new File(recentWorkspace + "/.metadata/.plugins/org.eclipse.pde.core/.bundle_pool"); //$NON-NLS-1$ |
| if (bundlePool.exists()) { |
| additionalRepos.add(bundlePool.toURI().normalize()); |
| } |
| } |
| } |
| |
| /** |
| * Look through the current p2 profile (_SELF_) and add the artifact repos that make up its |
| * bundle pool, dropins location, ... This helps in the cases that you are targeting stuff that |
| * makes up your current IDE. |
| * |
| * @param additionalRepos the set to which additional repos are added. |
| */ |
| private void findProfileRepos(Set<URI> additionalRepos) { |
| try { |
| // NOTE: be sure to use the global p2 agent here as we are looking for SELF. |
| IProfileRegistry profileRegistry = (IProfileRegistry) getGlobalAgent().getService(IProfileRegistry.SERVICE_NAME); |
| if (profileRegistry == null) |
| return; |
| IProfile self = profileRegistry.getProfile(IProfileRegistry.SELF); |
| if (self == null) |
| return; |
| |
| IAgentLocation location = (IAgentLocation) getGlobalAgent().getService(IAgentLocation.SERVICE_NAME); |
| URI dataArea = location.getDataArea("org.eclipse.equinox.p2.engine"); //$NON-NLS-1$ |
| dataArea = URIUtil.append(dataArea, "profileRegistry/" + self.getProfileId() + ".profile"); //$NON-NLS-1$//$NON-NLS-2$ |
| ProfileMetadataRepository profileRepo = new ProfileMetadataRepository(getGlobalAgent(), dataArea, null); |
| Collection<?> repos = profileRepo.getReferences(); |
| for (Object element : repos) { |
| if (element instanceof IRepositoryReference) { |
| IRepositoryReference reference = (IRepositoryReference) element; |
| if (reference.getType() == IRepository.TYPE_ARTIFACT && reference.getLocation() != null) |
| additionalRepos.add(reference.getLocation()); |
| } |
| } |
| } catch (CoreException e) { |
| // if there is a problem, move on. Could log something here |
| return; |
| } |
| } |
| |
| /** |
| * Returns the IU's for the given target related to the given containers |
| * |
| * @param definition the definition to filter with |
| * @param monitor |
| * @return the discovered IUs |
| * @exception CoreException if unable to retrieve IU's |
| */ |
| private IInstallableUnit[] getRootIUs(ITargetDefinition definition, IProgressMonitor monitor) throws CoreException { |
| |
| HashSet<IInstallableUnit> result = new HashSet<>(); |
| ITargetLocation[] containers = definition.getTargetLocations(); |
| if (containers == null) { |
| return new IInstallableUnit[0]; |
| } |
| SubMonitor subMonitor = SubMonitor.convert(monitor, Messages.IUBundleContainer_0, containers.length * 10); |
| MultiStatus status = new MultiStatus(PDECore.PLUGIN_ID, 0, Messages.IUBundleContainer_ProblemsLoadingRepositories, null); |
| for (ITargetLocation container : containers) { |
| if (container instanceof IUBundleContainer) { |
| try { |
| IUBundleContainer iuContainer = (IUBundleContainer) container; |
| Collections.addAll(result, iuContainer.getRootIUs(definition, subMonitor.split(10))); |
| } catch (CoreException e) { |
| status.add(e.getStatus()); |
| } |
| } |
| } |
| if (!status.isOK()) { |
| throw new CoreException(status); |
| } |
| return result.toArray(new IInstallableUnit[result.size()]); |
| } |
| |
| /** |
| * Returns the repositories to consider when resolving IU's (will return default set of |
| * repositories if current repository settings are <code>null</code>). |
| * |
| * @return URI's of repositories to use when resolving bundles |
| * @exception CoreException |
| */ |
| private URI[] getMetadataRepositories(ITargetDefinition target) throws CoreException { |
| Set<URI> result = new HashSet<>(); |
| ITargetLocation[] containers = target.getTargetLocations(); |
| if (containers == null) { |
| containers = new ITargetLocation[0]; |
| } |
| IMetadataRepositoryManager manager = getRepoManager(); |
| for (ITargetLocation container : containers) { |
| if (container instanceof IUBundleContainer) { |
| URI[] repos = ((IUBundleContainer) container).getRepositories(); |
| if (repos == null) { |
| repos = manager.getKnownRepositories(IRepositoryManager.REPOSITORIES_ALL); |
| } |
| result.addAll(Arrays.asList(repos)); |
| } |
| } |
| return result.toArray(new URI[result.size()]); |
| } |
| |
| private static final String NATIVE_ARTIFACTS = "nativeArtifacts"; //$NON-NLS-1$ |
| private static final String NATIVE_TYPE = "org.eclipse.equinox.p2.native"; //$NON-NLS-1$ |
| private static final String PARM_OPERAND = "operand"; //$NON-NLS-1$ |
| |
| protected static class CollectNativesAction extends ProvisioningAction { |
| @Override |
| public IStatus execute(Map<String, Object> parameters) { |
| InstallableUnitOperand operand = (InstallableUnitOperand) parameters.get(PARM_OPERAND); |
| IInstallableUnit installableUnit = operand.second(); |
| if (installableUnit == null) |
| return Status.OK_STATUS; |
| |
| IArtifactRepositoryManager manager; |
| try { |
| Collection<?> toDownload = installableUnit.getArtifacts(); |
| if (toDownload == null) |
| return Status.OK_STATUS; |
| |
| @SuppressWarnings("unchecked") |
| List<IArtifactRequest> artifactRequests = (List<IArtifactRequest>) parameters.get(NATIVE_ARTIFACTS); |
| IArtifactRepository destinationArtifactRepository = getBundlePool(); |
| manager = getArtifactRepositoryManager(); |
| for (Object name : toDownload) { |
| IArtifactKey keyToDownload = (IArtifactKey) name; |
| IArtifactRequest request = manager.createMirrorRequest(keyToDownload, destinationArtifactRepository, null, null); |
| artifactRequests.add(request); |
| } |
| } catch (CoreException e) { |
| return e.getStatus(); |
| } |
| return Status.OK_STATUS; |
| } |
| |
| @Override |
| public IStatus undo(Map<String, Object> parameters) { |
| // nothing to do for now |
| return Status.OK_STATUS; |
| } |
| } |
| |
| protected static class CollectNativesPhase extends InstallableUnitPhase { |
| public CollectNativesPhase(int weight) { |
| super(NATIVE_ARTIFACTS, weight); |
| } |
| |
| @Override |
| protected List<ProvisioningAction> getActions(InstallableUnitOperand operand) { |
| IInstallableUnit unit = operand.second(); |
| if (unit != null && unit.getTouchpointType().getId().equals(NATIVE_TYPE)) { |
| ArrayList<ProvisioningAction> list = new ArrayList<>(1); |
| list.add(new CollectNativesAction()); |
| return list; |
| } |
| return null; |
| } |
| |
| @Override |
| protected IStatus initializePhase(IProgressMonitor monitor, IProfile profile, Map<String, Object> parameters) { |
| parameters.put(NATIVE_ARTIFACTS, new ArrayList<>()); |
| parameters.put(PARM_PROFILE, profile); |
| return null; |
| } |
| |
| @Override |
| protected IStatus completePhase(IProgressMonitor monitor, IProfile profile, Map<String, Object> parameters) { |
| @SuppressWarnings("unchecked") |
| List<IArtifactRequest> artifactRequests = (List<IArtifactRequest>) parameters.get(NATIVE_ARTIFACTS); |
| ProvisioningContext context = (ProvisioningContext) parameters.get(PARM_CONTEXT); |
| IProvisioningAgent agent = (IProvisioningAgent) parameters.get(PARM_AGENT); |
| DownloadManager dm = new DownloadManager(context, agent); |
| for (IArtifactRequest iArtifactRequest : artifactRequests) { |
| dm.add(iArtifactRequest); |
| } |
| return dm.start(monitor); |
| } |
| } |
| |
| /** |
| * @return the profile associated with this synchronizer |
| */ |
| IProfile getProfile() { |
| return fProfile; |
| } |
| |
| } |