| /******************************************************************************* |
| * Copyright (c) 2008, 2017 Code 9 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: |
| * Code 9 - initial API and implementation |
| * IBM - ongoing development |
| * Rapicorp - ongoing development |
| ******************************************************************************/ |
| package org.eclipse.equinox.p2.publisher.eclipse; |
| |
| import java.io.File; |
| import java.util.*; |
| import java.util.Map.Entry; |
| import org.eclipse.core.runtime.*; |
| import org.eclipse.equinox.frameworkadmin.BundleInfo; |
| import org.eclipse.equinox.internal.p2.metadata.TouchpointInstruction; |
| import org.eclipse.equinox.internal.p2.publisher.eclipse.GeneratorBundleInfo; |
| import org.eclipse.equinox.p2.metadata.*; |
| import org.eclipse.equinox.p2.metadata.MetadataFactory.InstallableUnitDescription; |
| import org.eclipse.equinox.p2.metadata.expression.IMatchExpression; |
| import org.eclipse.equinox.p2.publisher.*; |
| import org.eclipse.equinox.p2.repository.IRepository; |
| import org.eclipse.equinox.p2.repository.IRepositoryReference; |
| import org.eclipse.equinox.spi.p2.publisher.PublisherHelper; |
| import org.eclipse.osgi.util.ManifestElement; |
| import org.osgi.framework.BundleException; |
| import org.osgi.framework.Constants; |
| |
| /** |
| * Publish CUs for all the configuration data in the current result. |
| * This adds config-specific CUs to capture start levels etc found in the config.ini |
| * etc for is os, ws, arch combination seen so far. |
| */ |
| public class ConfigCUsAction extends AbstractPublisherAction { |
| |
| protected static final String ORG_ECLIPSE_UPDATE_CONFIGURATOR = "org.eclipse.update.configurator"; //$NON-NLS-1$ |
| protected static final String DEFAULT_START_LEVEL = "osgi.bundles.defaultStartLevel"; //$NON-NLS-1$ |
| private static Collection<String> PROPERTIES_TO_SKIP; |
| private static HashSet<String> PROGRAM_ARGS_TO_SKIP; |
| protected Version version; |
| protected String id; |
| protected String flavor; |
| IPublisherResult outerResults = null; |
| |
| // TODO consider moving this filtering to the LaunchingAdvice and ConfigAdvice so |
| // it is not hardcoded in the action. |
| static { |
| PROPERTIES_TO_SKIP = new HashSet<>(); |
| PROPERTIES_TO_SKIP.add("osgi.frameworkClassPath"); //$NON-NLS-1$ |
| PROPERTIES_TO_SKIP.add("osgi.framework"); //$NON-NLS-1$ |
| PROPERTIES_TO_SKIP.add("osgi.bundles"); //$NON-NLS-1$ |
| PROPERTIES_TO_SKIP.add("eof"); //$NON-NLS-1$ |
| PROPERTIES_TO_SKIP.add("eclipse.p2.profile"); //$NON-NLS-1$ |
| PROPERTIES_TO_SKIP.add("eclipse.p2.data.area"); //$NON-NLS-1$ |
| PROPERTIES_TO_SKIP.add("org.eclipse.update.reconcile"); //$NON-NLS-1$ |
| PROPERTIES_TO_SKIP.add("org.eclipse.equinox.simpleconfigurator.configUrl"); //$NON-NLS-1$ |
| |
| PROGRAM_ARGS_TO_SKIP = new HashSet<>(); |
| PROGRAM_ARGS_TO_SKIP.add("--launcher.library"); //$NON-NLS-1$ |
| PROGRAM_ARGS_TO_SKIP.add("-startup"); //$NON-NLS-1$ |
| PROGRAM_ARGS_TO_SKIP.add("-configuration"); //$NON-NLS-1$ |
| } |
| |
| public static String getAbstractCUCapabilityNamespace(String id, String type, String flavor, String configSpec) { |
| return flavor + id; |
| } |
| |
| public static String getAbstractCUCapabilityId(String id, String type, String flavor, String configSpec) { |
| return id + "." + type; //$NON-NLS-1$ |
| } |
| |
| /** |
| * Returns the id of the top level IU published by this action for the given id and flavor. |
| * @param id the id of the application being published |
| * @param flavor the flavor being published |
| * @return the if for ius published by this action |
| */ |
| public static String computeIUId(String id, String flavor) { |
| return flavor + id + ".configuration"; //$NON-NLS-1$ |
| } |
| |
| public ConfigCUsAction(IPublisherInfo info, String flavor, String id, Version version) { |
| this.flavor = flavor; |
| this.id = id; |
| this.version = version; |
| } |
| |
| @Override |
| public IStatus perform(IPublisherInfo publisherInfo, IPublisherResult results, IProgressMonitor monitor) { |
| IPublisherResult innerResult = new PublisherResult(); |
| this.outerResults = results; |
| this.info = publisherInfo; |
| // we have N platforms, generate a CU for each |
| // TODO try and find common properties across platforms |
| String[] configSpecs = publisherInfo.getConfigurations(); |
| for (int i = 0; i < configSpecs.length; i++) { |
| if (monitor.isCanceled()) |
| return Status.CANCEL_STATUS; |
| String configSpec = configSpecs[i]; |
| Collection<IConfigAdvice> configAdvice = publisherInfo.getAdvice(configSpec, false, id, version, IConfigAdvice.class); |
| BundleInfo[] bundles = fillInBundles(configAdvice, results); |
| publishBundleCUs(publisherInfo, bundles, configSpec, innerResult); |
| publishConfigIUs(configAdvice, innerResult, configSpec); |
| Collection<IExecutableAdvice> launchingAdvice = publisherInfo.getAdvice(configSpec, false, id, version, IExecutableAdvice.class); |
| publishIniIUs(launchingAdvice, innerResult, configSpec); |
| } |
| // merge the IUs into the final result as non-roots and create a parent IU that captures them all |
| results.merge(innerResult, IPublisherResult.MERGE_ALL_NON_ROOT); |
| publishTopLevelConfigurationIU(innerResult.getIUs(null, IPublisherResult.ROOT), results); |
| return Status.OK_STATUS; |
| } |
| |
| private void publishTopLevelConfigurationIU(Collection<? extends IVersionedId> children, IPublisherResult result) { |
| InstallableUnitDescription descriptor = createParentIU(children, computeIUId(id, flavor), version); |
| descriptor.setSingleton(true); |
| IInstallableUnit rootIU = MetadataFactory.createInstallableUnit(descriptor); |
| if (rootIU == null) |
| return; |
| result.addIU(rootIU, IPublisherResult.ROOT); |
| } |
| |
| // there seem to be cases where the bundle infos are not filled in with symbolic name and version. |
| // fill in the missing data. |
| private BundleInfo[] fillInBundles(Collection<IConfigAdvice> configAdvice, IPublisherResult results) { |
| ArrayList<BundleInfo> result = new ArrayList<>(); |
| for (IConfigAdvice advice : configAdvice) { |
| |
| int defaultStart = BundleInfo.NO_LEVEL; |
| Map<String, String> adviceProperties = advice.getProperties(); |
| if (adviceProperties.containsKey(DEFAULT_START_LEVEL)) { |
| try { |
| defaultStart = Integer.parseInt(adviceProperties.get(DEFAULT_START_LEVEL)); |
| } catch (NumberFormatException e) { |
| //don't know default |
| } |
| } |
| |
| BundleInfo[] bundles = advice.getBundles(); |
| for (int i = 0; i < bundles.length; i++) { |
| BundleInfo bundleInfo = bundles[i]; |
| |
| if (bundleInfo.getStartLevel() != BundleInfo.NO_LEVEL && bundleInfo.getStartLevel() == defaultStart) { |
| bundleInfo.setStartLevel(BundleInfo.NO_LEVEL); |
| } |
| |
| // prime the result with the current info. This will be replaced if there is more info... |
| if ((bundleInfo.getSymbolicName() != null && bundleInfo.getVersion() != null) || bundleInfo.getLocation() == null) |
| result.add(bundles[i]); |
| else { |
| try { |
| File location = new File(bundleInfo.getLocation()); |
| Dictionary<String, String> manifest = BundlesAction.loadManifestIgnoringExceptions(location); |
| if (manifest == null) |
| continue; |
| GeneratorBundleInfo newInfo = new GeneratorBundleInfo(bundleInfo); |
| ManifestElement[] element = ManifestElement.parseHeader("dummy-bsn", manifest.get(Constants.BUNDLE_SYMBOLICNAME)); //$NON-NLS-1$ |
| newInfo.setSymbolicName(element[0].getValue()); |
| newInfo.setVersion(manifest.get(Constants.BUNDLE_VERSION)); |
| result.add(newInfo); |
| } catch (BundleException e) { |
| // TODO Auto-generated catch block |
| e.printStackTrace(); |
| } |
| } |
| } |
| } |
| return result.toArray(new BundleInfo[result.size()]); |
| } |
| |
| /** |
| * Publish the IUs that capture the eclipse.ini information such as vmargs and program args, etc |
| */ |
| private void publishIniIUs(Collection<IExecutableAdvice> launchingAdvice, IPublisherResult results, String configSpec) { |
| if (launchingAdvice.isEmpty()) |
| return; |
| |
| String configureData = ""; //$NON-NLS-1$ |
| String unconfigureData = ""; //$NON-NLS-1$ |
| if (!launchingAdvice.isEmpty()) { |
| String[] dataStrings = getLauncherConfigStrings(launchingAdvice); |
| configureData += dataStrings[0]; |
| unconfigureData += dataStrings[1]; |
| } |
| // if there is nothing to configure or unconfigure, then don't even bother generating this IU |
| if (configureData.length() == 0 && unconfigureData.length() == 0) |
| return; |
| |
| Map<String, String> touchpointData = new HashMap<>(); |
| touchpointData.put("configure", configureData); //$NON-NLS-1$ |
| touchpointData.put("unconfigure", unconfigureData); //$NON-NLS-1$ |
| IInstallableUnit cu = createCU(id, version, "ini", flavor, configSpec, touchpointData); //$NON-NLS-1$ |
| results.addIU(cu, IPublisherResult.ROOT); |
| } |
| |
| /** |
| * Publish the IUs that capture the config.ini information such as properties etc |
| */ |
| private void publishConfigIUs(Collection<IConfigAdvice> configAdvice, IPublisherResult results, String configSpec) { |
| if (configAdvice.isEmpty()) |
| return; |
| |
| String configureData = ""; //$NON-NLS-1$ |
| String unconfigureData = ""; //$NON-NLS-1$ |
| if (!configAdvice.isEmpty()) { |
| String[] dataStrings = getConfigurationStrings(configAdvice); |
| configureData += dataStrings[0]; |
| unconfigureData += dataStrings[1]; |
| } |
| // if there is nothing to configure or unconfigure, then don't even bother generating this IU |
| if (configureData.length() == 0 && unconfigureData.length() == 0) |
| return; |
| |
| Map<String, String> touchpointData = new HashMap<>(); |
| touchpointData.put("configure", configureData); //$NON-NLS-1$ |
| touchpointData.put("unconfigure", unconfigureData); //$NON-NLS-1$ |
| IInstallableUnit cu = createCU(id, version, "config", flavor, configSpec, touchpointData); //$NON-NLS-1$ |
| results.addIU(cu, IPublisherResult.ROOT); |
| } |
| |
| /** |
| * Create a CU whose id is flavor+id.type.configspec with the given version. |
| * The resultant IU has the self capability and an abstract capabilty in the flavor+id namespace |
| * with the name id.type and the given version. This allows others to create an abstract |
| * dependency on having one of these things around but not having to list out the configs. |
| */ |
| private IInstallableUnit createCU(String cuId, Version cuVersion, String cuType, String cuFlavor, String configSpec, Map<String, String> touchpointData) { |
| InstallableUnitDescription cu = new InstallableUnitDescription(); |
| String resultId = createCUIdString(cuId, cuType, cuFlavor, configSpec); |
| cu.setId(resultId); |
| cu.setVersion(cuVersion); |
| cu.setFilter(createFilterSpec(configSpec)); |
| IProvidedCapability selfCapability = PublisherHelper.createSelfCapability(resultId, cuVersion); |
| String namespace = getAbstractCUCapabilityNamespace(cuId, cuType, cuFlavor, configSpec); |
| String abstractId = getAbstractCUCapabilityId(cuId, cuType, cuFlavor, configSpec); |
| IProvidedCapability abstractCapability = MetadataFactory.createProvidedCapability(namespace, abstractId, cuVersion); |
| cu.setCapabilities(new IProvidedCapability[] {selfCapability, abstractCapability}); |
| cu.addTouchpointData(MetadataFactory.createTouchpointData(touchpointData)); |
| cu.setTouchpointType(PublisherHelper.TOUCHPOINT_OSGI); |
| return MetadataFactory.createInstallableUnit(cu); |
| } |
| |
| protected String[] getConfigurationStrings(Collection<IConfigAdvice> configAdvice) { |
| String configurationData = ""; //$NON-NLS-1$ |
| String unconfigurationData = ""; //$NON-NLS-1$ |
| Set<String> properties = new HashSet<>(); |
| for (IConfigAdvice advice : configAdvice) { |
| for (Entry<String, String> aProperty : advice.getProperties().entrySet()) { |
| String key = aProperty.getKey(); |
| if (shouldPublishProperty(key) && !properties.contains(key)) { |
| properties.add(key); |
| Map<String, String> parameters = new LinkedHashMap<>(); |
| parameters.put("propName", key); //$NON-NLS-1$ |
| parameters.put("propValue", aProperty.getValue()); //$NON-NLS-1$ |
| configurationData += TouchpointInstruction.encodeAction("setProgramProperty", parameters); //$NON-NLS-1$ |
| parameters.put("propValue", ""); //$NON-NLS-1$//$NON-NLS-2$ |
| unconfigurationData += TouchpointInstruction.encodeAction("setProgramProperty", parameters); //$NON-NLS-1$ |
| } |
| } |
| if (advice instanceof ProductFileAdvice) { |
| for (IRepositoryReference repo : ((ProductFileAdvice) advice).getUpdateRepositories()) { |
| Map<String, String> parameters = new LinkedHashMap<>(); |
| parameters.put("type", Integer.toString(repo.getType())); //$NON-NLS-1$ |
| parameters.put("location", repo.getLocation().toString()); //$NON-NLS-1$ |
| parameters.put("enabled", Boolean.toString((repo.getOptions() & IRepository.ENABLED) == IRepository.ENABLED)); //$NON-NLS-1$ |
| configurationData += TouchpointInstruction.encodeAction("addRepository", parameters); //$NON-NLS-1$ |
| parameters.remove("enabled"); //$NON-NLS-1$ |
| unconfigurationData += TouchpointInstruction.encodeAction("removeRepository", parameters);//$NON-NLS-1$ |
| } |
| } |
| } |
| return new String[] {configurationData, unconfigurationData}; |
| } |
| |
| private boolean shouldPublishProperty(String key) { |
| return !PROPERTIES_TO_SKIP.contains(key); |
| } |
| |
| private boolean shouldPublishJvmArg(String key) { |
| return true; |
| } |
| |
| private boolean shouldPublishProgramArg(String key) { |
| return !PROGRAM_ARGS_TO_SKIP.contains(key); |
| } |
| |
| protected String[] getLauncherConfigStrings(Collection<IExecutableAdvice> launchingAdvice) { |
| String configurationData = ""; //$NON-NLS-1$ |
| String unconfigurationData = ""; //$NON-NLS-1$ |
| |
| Map<String, String> touchpointParameters = new LinkedHashMap<>(); |
| Set<String> jvmSet = new HashSet<>(); |
| Set<String> programSet = new HashSet<>(); |
| for (IExecutableAdvice advice : launchingAdvice) { |
| String[] jvmArgs = advice.getVMArguments(); |
| for (int i = 0; i < jvmArgs.length; i++) |
| if (shouldPublishJvmArg(jvmArgs[i]) && !jvmSet.contains(jvmArgs[i])) { |
| jvmSet.add(jvmArgs[i]); |
| touchpointParameters.clear(); |
| touchpointParameters.put("jvmArg", jvmArgs[i]); //$NON-NLS-1$ |
| configurationData += TouchpointInstruction.encodeAction("addJvmArg", touchpointParameters); //$NON-NLS-1$ |
| unconfigurationData += TouchpointInstruction.encodeAction("removeJvmArg", touchpointParameters); //$NON-NLS-1$ |
| } |
| String[] programArgs = advice.getProgramArguments(); |
| for (int i = 0; i < programArgs.length; i++) |
| if (shouldPublishProgramArg(programArgs[i]) && !programSet.contains(programArgs[i])) { |
| if (programArgs[i].startsWith("-")) //$NON-NLS-1$ |
| programSet.add(programArgs[i]); |
| touchpointParameters.clear(); |
| touchpointParameters.put("programArg", programArgs[i]); //$NON-NLS-1$ |
| configurationData += TouchpointInstruction.encodeAction("addProgramArg", touchpointParameters); //$NON-NLS-1$ |
| unconfigurationData += TouchpointInstruction.encodeAction("removeProgramArg", touchpointParameters); //$NON-NLS-1$ |
| } else if (i + 1 < programArgs.length && !programArgs[i + 1].startsWith("-")) { //$NON-NLS-1$ |
| // if we are not publishing then skip over the following arg as it is assumed to be a parameter |
| // to this command line arg. |
| i++; |
| } |
| } |
| return new String[] {configurationData, unconfigurationData}; |
| } |
| |
| /** |
| * Publish the CUs related to the given set of bundles. This generally covers the start-level and |
| * and whether or not the bundle is to be started. |
| */ |
| protected void publishBundleCUs(IPublisherInfo publisherInfo, BundleInfo[] bundles, String configSpec, IPublisherResult result) { |
| if (bundles == null) |
| return; |
| |
| String cuIdPrefix = ""; //$NON-NLS-1$ |
| IMatchExpression<IInstallableUnit> filter = null; |
| if (configSpec != null) { |
| cuIdPrefix = createIdString(configSpec); |
| filter = createFilterSpec(configSpec); |
| } |
| |
| for (int i = 0; i < bundles.length; i++) { |
| GeneratorBundleInfo bundle = createGeneratorBundleInfo(bundles[i], result); |
| if (bundle == null) |
| continue; |
| |
| IInstallableUnit iu = bundle.getIU(); |
| |
| // If there is no host, or the filters don't match, skip this one. |
| if (iu == null || !filterMatches(iu.getFilter(), configSpec)) |
| continue; |
| |
| // TODO need to factor this out into its own action |
| if (bundle.getSymbolicName().equals(ORG_ECLIPSE_UPDATE_CONFIGURATOR)) { |
| bundle.setStartLevel(BundleInfo.NO_LEVEL); |
| bundle.setMarkedAsStarted(false); |
| bundle.setSpecialConfigCommands("setProgramProperty(propName:org.eclipse.update.reconcile, propValue:false);"); //$NON-NLS-1$ |
| bundle.setSpecialUnconfigCommands("setProgramProperty(propName:org.eclipse.update.reconcile, propValue:);"); //$NON-NLS-1$ |
| } else if (bundle.getStartLevel() == BundleInfo.NO_LEVEL && !bundle.isMarkedAsStarted()) { |
| // this bundle does not require any particular configuration, the plug-in default IU will handle installing it |
| continue; |
| } |
| |
| IInstallableUnit cu = null; |
| if (this.version != null && !this.version.equals(Version.emptyVersion)) |
| cu = BundlesAction.createBundleConfigurationUnit(bundle.getSymbolicName(), this.version, false, bundle, flavor + cuIdPrefix, filter); |
| else |
| cu = BundlesAction.createBundleConfigurationUnit(bundle.getSymbolicName(), Version.parseVersion(bundle.getVersion()), false, bundle, flavor + cuIdPrefix, filter); |
| |
| if (cu != null) { |
| // Product Query will run against the repo, make sure these CUs are in before then |
| // TODO review the aggressive addition to the metadata repo. perhaps the query can query the result as well. |
| // IMetadataRepository metadataRepository = info.getMetadataRepository(); |
| // if (metadataRepository != null) { |
| // metadataRepository.addInstallableUnits(new IInstallableUnit[] {cu}); |
| // } |
| result.addIU(cu, IPublisherResult.ROOT); |
| } |
| } |
| } |
| |
| protected GeneratorBundleInfo createGeneratorBundleInfo(BundleInfo bundleInfo, IPublisherResult result) { |
| String name = bundleInfo.getSymbolicName(); |
| |
| //query for a matching IU |
| IInstallableUnit iu = queryForIU(outerResults, name, Version.create(bundleInfo.getVersion())); |
| if (iu != null) { |
| if (iu.getVersion() == null) |
| bundleInfo.setVersion("0.0.0"); //$NON-NLS-1$ |
| else |
| bundleInfo.setVersion(iu.getVersion().toString()); |
| GeneratorBundleInfo newInfo = new GeneratorBundleInfo(bundleInfo); |
| newInfo.setIU(iu); |
| return newInfo; |
| } |
| |
| if (bundleInfo.getLocation() != null || bundleInfo.getVersion() != null) |
| return new GeneratorBundleInfo(bundleInfo); |
| //harder: try id_version |
| int i = name.indexOf('_'); |
| while (i > -1) { |
| try { |
| Version bundleVersion = Version.parseVersion(name.substring(i)); |
| bundleInfo.setSymbolicName(name.substring(0, i)); |
| bundleInfo.setVersion(bundleVersion.toString()); |
| return new GeneratorBundleInfo(bundleInfo); |
| } catch (IllegalArgumentException e) { |
| // the '_' found was probably part of the symbolic id |
| i = name.indexOf('_', i); |
| } |
| } |
| |
| return null; |
| } |
| |
| } |