/******************************************************************************* * Copyright (c) 2008-2010, Cloudsmith Inc. * The code, documentation and other materials contained herein have been * licensed under the Eclipse Public License - v 1.0 by the copyright holder * listed above, as the Initial Contributor under such license. The text of * such license is available at www.eclipse.org. * * Contributors: * Lorenzo Bettini - https://bugs.eclipse.org/bugs/show_bug.cgi?id=428301 ******************************************************************************/ package org.eclipse.buckminster.pde.tasks; import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Properties; import org.apache.tools.ant.Project; import org.apache.tools.ant.types.FileSet; import org.apache.tools.ant.types.PatternSet.NameEntry; import org.apache.tools.ant.types.selectors.FilenameSelector; import org.apache.tools.ant.types.selectors.OrSelector; import org.eclipse.buckminster.core.actor.AbstractActor; import org.eclipse.buckminster.core.cspec.model.CSpec; import org.eclipse.buckminster.core.helpers.BMProperties; import org.eclipse.buckminster.core.helpers.MapUnion; import org.eclipse.buckminster.core.version.VersionHelper; import org.eclipse.buckminster.pde.IPDEConstants; import org.eclipse.buckminster.pde.MatchRule; import org.eclipse.buckminster.pde.Messages; import org.eclipse.buckminster.pde.PDEPlugin; import org.eclipse.buckminster.pde.cspecgen.CSpecGenerator; import org.eclipse.buckminster.pde.tasks.FeatureRootAdvice.ConfigAdvice; import org.eclipse.buckminster.runtime.BuckminsterException; import org.eclipse.buckminster.runtime.IOUtils; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Path; import org.eclipse.equinox.internal.p2.core.helpers.StringHelper; import org.eclipse.equinox.internal.p2.metadata.InstallableUnit; import org.eclipse.equinox.internal.p2.publisher.FileSetDescriptor; import org.eclipse.equinox.p2.metadata.IArtifactKey; import org.eclipse.equinox.p2.metadata.IInstallableUnit; import org.eclipse.equinox.p2.metadata.IVersionedId; import org.eclipse.equinox.p2.metadata.MetadataFactory; import org.eclipse.equinox.p2.metadata.Version; import org.eclipse.equinox.p2.metadata.VersionRange; import org.eclipse.equinox.p2.metadata.VersionedId; import org.eclipse.equinox.p2.metadata.expression.IMatchExpression; import org.eclipse.equinox.p2.publisher.AdviceFileAdvice; import org.eclipse.equinox.p2.publisher.IPublisherAdvice; import org.eclipse.equinox.p2.publisher.IPublisherInfo; import org.eclipse.equinox.p2.publisher.IPublisherResult; import org.eclipse.equinox.p2.publisher.eclipse.Feature; import org.eclipse.equinox.p2.publisher.eclipse.FeatureEntry; import org.eclipse.equinox.p2.repository.artifact.spi.ArtifactDescriptor; import org.eclipse.osgi.util.NLS; import org.eclipse.pde.internal.build.IPDEBuildConstants; import org.eclipse.pde.internal.build.Utils; @SuppressWarnings("restriction") public class FeaturesAction extends org.eclipse.equinox.p2.publisher.eclipse.FeaturesAction { private static final Project PROPERTY_REPLACER = new Project(); private static FeatureRootAdvice createRootAdvice(String featureId, Properties buildProperties, IPath baseDirectory, String[] configs) { Map<String, Map<String, String>> configMap = Utils.processRootProperties(buildProperties, true); if (configMap.size() == 1) { Map<String, String> entry = configMap.get(Utils.ROOT_COMMON); if (entry != null && entry.isEmpty()) return null; } FeatureRootAdvice advice = new FeatureRootAdvice(featureId); for (Map.Entry<String, Map<String, String>> entry : configMap.entrySet()) { String config = entry.getKey(); Map<String, String> rootMap = entry.getValue(); populateConfigAdvice(advice, config, rootMap, baseDirectory, configs); } return advice; } private static void populateConfigAdvice(FeatureRootAdvice advice, String config, Map<String, String> rootMap, IPath baseDirectory, String[] configs) { if (config.equals(Utils.ROOT_COMMON)) config = ""; //$NON-NLS-1$ else { config = reorderConfig(config); int idx = configs.length; while (--idx >= 0) if (config.equals(configs[idx])) break; if (idx < 0) // Config was not on the list return; } ConfigAdvice configAdvice = advice.getConfigAdvice(config); FileSetDescriptor descriptor = configAdvice.getDescriptor(); List<String> permissionsKeys = new ArrayList<String>(); for (Map.Entry<String, String> rootEntry : rootMap.entrySet()) { String key = rootEntry.getKey(); if (key.equals(Utils.ROOT_LINK)) { descriptor.setLinks(rootEntry.getValue()); continue; } if (key.startsWith(Utils.ROOT_PERMISSIONS)) { permissionsKeys.add(key); continue; } for (String rootValue : StringHelper.getArrayFromString(rootEntry.getValue(), ',')) { String rootName = rootValue; boolean isAbsolute = rootName.startsWith("absolute:"); //$NON-NLS-1$ if (isAbsolute) rootName = rootName.substring(9); boolean isFile = rootName.startsWith("file:"); //$NON-NLS-1$ if (isFile) rootName = rootName.substring(5); if (rootName.length() == 0) continue; IPath basePath; String pattern; // Base path cannot contain wild card characters // IPath rootPath = Path.fromPortableString(rootName); int firstStar = -1; int numSegs = rootPath.segmentCount(); for (int idx = 0; idx < numSegs; ++idx) if (rootPath.segment(idx).indexOf('*') >= 0) { firstStar = idx; break; } if (firstStar == -1) { if (isFile) { pattern = rootPath.lastSegment(); basePath = rootPath.removeLastSegments(1); } else { pattern = "**"; //$NON-NLS-1$ basePath = rootPath; } } else { basePath = rootPath.removeLastSegments(rootPath.segmentCount() - (firstStar + 1)); pattern = rootPath.removeFirstSegments(firstStar).toPortableString(); } if (!isAbsolute) basePath = baseDirectory.append(basePath.makeRelative()); FileSet fileset = new FileSet(); fileset.setProject(PROPERTY_REPLACER); fileset.setErrorOnMissingDir(false); File base = basePath.toFile(); fileset.setDir(base); NameEntry include = fileset.createInclude(); include.setName(pattern); String[] files = fileset.getDirectoryScanner().getIncludedFiles(); if (files.length == 0) { PDEPlugin.getLogger().warning( NLS.bind(Messages.rootAdviceForConfig_0_in_1_at_2_does_not_appoint_existing_artifacts, new Object[] { config, IPDEBuildConstants.PROPERTIES_FILE, baseDirectory.toOSString() })); continue; } IPath destBaseDir = Path.fromPortableString(key); for (String found : files) { IPath foundFile = Path.fromOSString(found); String destDir = destBaseDir.append(foundFile.removeLastSegments(1)).toPortableString(); configAdvice.addRootfile(new File(base, found), destDir); } } } for (String permissionKey : permissionsKeys) { String permissionString = rootMap.get(permissionKey); String[] names = StringHelper.getArrayFromString(permissionString, ','); OrSelector orSelector = new OrSelector(); orSelector.setProject(PROPERTY_REPLACER); for (String name : names) { // Workaround for bogus entries in the equinox executable // feature if ("${launcherName}.app/Contents/MacOS/${launcherName}".equals(name)) //$NON-NLS-1$ name = "Eclipse.app/Contents/MacOS/launcher"; //$NON-NLS-1$ FilenameSelector nameSelector = new FilenameSelector(); nameSelector.setProject(PROPERTY_REPLACER); nameSelector.setName(name); orSelector.addFilename(nameSelector); } permissionKey = permissionKey.substring(Utils.ROOT_PERMISSIONS.length()); for (File file : configAdvice.getFiles()) { IPath finalFilePath = configAdvice.computePath(file); if (orSelector.isSelected(null, finalFilePath.toOSString(), null)) descriptor.addPermissions(new String[] { permissionKey, finalFilePath.toPortableString() }); } } } private static String reorderConfig(String config) { String[] parsed = StringHelper.getArrayFromString(config, '.'); return parsed[1] + '.' + parsed[0] + '.' + parsed[2]; } private final Map<IVersionedId, CSpec> cspecs; private final Map<IVersionedId, Map<String, String>> properties = new HashMap<IVersionedId, Map<String, String>>(); private final Map<IVersionedId, File> adviceFiles = new HashMap<IVersionedId, File>(); public FeaturesAction(File[] featureBinaries, Map<IVersionedId, CSpec> cspecs) { super(featureBinaries); this.cspecs = cspecs; } @Override public IStatus perform(IPublisherInfo publisherInfo, IPublisherResult results, IProgressMonitor monitor) { for (Entry<IVersionedId, CSpec> entry : cspecs.entrySet()) { CSpec cspec = entry.getValue(); try { IPath location = cspec.getComponentLocation(); if (location.hasTrailingSeparator()) { IVersionedId vn = entry.getKey(); vn = new VersionedId(vn.getId(), VersionHelper.replaceQualifier(vn.getVersion(), null)); File buildProps = location.append(IPDEBuildConstants.PROPERTIES_FILE).toFile(); InputStream input = null; Properties props = new Properties(); try { input = new BufferedInputStream(new FileInputStream(buildProps)); props.load(input); properties.put(vn, new BMProperties(props)); IPublisherAdvice rootAdvice = createRootAdvice(cspec.getName(), props, location, publisherInfo.getConfigurations()); if (rootAdvice != null) publisherInfo.addAdvice(rootAdvice); } catch (FileNotFoundException e) { // OK, we don't have any roots } catch (IOException e) { throw BuckminsterException.wrap(e); } finally { IOUtils.close(input); } File adviceFile = location.append("p2.inf").toFile(); //$NON-NLS-1$ if (adviceFile.canRead()) adviceFiles.put(vn, adviceFile); } } catch (CoreException e) { return e.getStatus(); } } return super.perform(publisherInfo, results, monitor); } @Override protected void generateFeatureIUs(Feature[] featureList, IPublisherResult result) { for (Feature feature : featureList) addCapabilityAdvice(feature); super.generateFeatureIUs(featureList, result); } @Override protected ArrayList<IInstallableUnit> generateRootFileIUs(Feature feature, IPublisherResult result, IPublisherInfo publisherInfo) { ArrayList<IInstallableUnit> ius = new ArrayList<IInstallableUnit>(); Collection<FeatureRootAdvice> collection = publisherInfo.getAdvice(null, false, feature.getId(), Version.parseVersion(feature.getVersion()), FeatureRootAdvice.class); if (collection.isEmpty()) return ius; for (FeatureRootAdvice advice : collection) { String[] configs = advice.getConfigs(); for (int i = 0; i < configs.length; i++) { String config = configs[i]; ConfigAdvice configAdvice = advice.getConfigAdvice(config); if (configAdvice == null) continue; File[] files = configAdvice.getFiles(); if (files.length == 0) continue; IInstallableUnit iu = createFeatureRootFileIU(feature.getId(), feature.getVersion(), null, configAdvice.getDescriptor()); Collection<IArtifactKey> keys = iu.getArtifacts(); if (keys.isEmpty()) continue; IArtifactKey artifactKey = keys.iterator().next(); ArtifactDescriptor artifactDescriptor = new ArtifactDescriptor(artifactKey); publishArtifact(artifactDescriptor, files, null, publisherInfo, configAdvice); result.addIU(iu, IPublisherResult.NON_ROOT); ius.add(iu); } } return ius; } @Override protected void generateSiteReferences(Feature feature, IPublisherResult result, IPublisherInfo publisherInfo) { // We rely on the SiteReferencesAction for this and simply skip the // update sites and discovery sites // that are provided in other features. return; } private void addCapabilityAdvice(Feature feature) { Version v = Version.parseVersion(feature.getVersion()); IVersionedId vn = new VersionedId(feature.getId(), VersionHelper.replaceQualifier(v, null)); Map<String, String> localProps = properties.get(vn); File adviceFile = adviceFiles.get(vn); if (adviceFile != null) info.addAdvice(new AdviceFileAdvice(feature.getId() + IPDEConstants.FEATURE_GROUP, v, Path.fromOSString(adviceFile.getParent()), Path .fromOSString(adviceFile.getName()))); Map<String, ? extends Object> props = AbstractActor.getActiveContext().getProperties(); if (localProps != null) props = new MapUnion<String, Object>(localProps, props); if (props == null || !VersionConsolidator.getBooleanProperty(props, IPDEConstants.PROP_PDE_FEATURE_RANGE_GENERATION, true)) // Generation is turned off return; MatchRule matchRuleLower = MatchRule.PERFECT; MatchRule matchRule = MatchRule.PERFECT; String tmp = (String) props.get(IPDEConstants.PROP_PDE_MATCH_RULE_FEATURE_LOWER); if (tmp != null) matchRuleLower = MatchRule.getMatchRule(tmp); tmp = (String) props.get(IPDEConstants.PROP_PDE_MATCH_RULE_FEATURE); if (tmp != null) matchRule = MatchRule.getMatchRule(tmp); boolean requirementGreedy = VersionConsolidator.getBooleanProperty(props, IPDEConstants.PROP_PDE_FEATURE_REQUIREMENTS_GREEDY, true); if (requirementGreedy && (matchRule == MatchRule.NONE || matchRule == MatchRule.PERFECT)) // All defaults return; CapabilityAdvice advice = new CapabilityAdvice(feature.getId() + IPDEConstants.FEATURE_GROUP, v); FeatureEntry[] entries = feature.getEntries(); int idx = entries.length; while (--idx >= 0) { FeatureEntry entry = entries[idx]; if (entry.isPatch()) continue; String id = entry.getId(); if (!entry.isPlugin()) id = id + IPDEConstants.FEATURE_GROUP; Version version = Version.create(entry.getVersion()); if (version == null || version.equals(Version.emptyVersion)) version = null; int min = entry.isOptional() ? 0 : 1; if (entry.isRequires()) { if (!requirementGreedy) { // Advice to replace with a non greedy requirement but do // not alter this requirement in any other way. VersionRange range = CSpecGenerator.createRuleBasedRange(MatchRule.getMatchRule(entry.getMatch()), MatchRule.PERFECT, version); advice.addRequirement(MetadataFactory.createRequirement(IInstallableUnit.NAMESPACE_IU_ID, id, range, getFilter(entry), min, 1, false)); } continue; } if (version == null || matchRule == MatchRule.NONE || matchRule == MatchRule.PERFECT) continue; VersionRange range = CSpecGenerator.createRuleBasedRange(matchRule, matchRuleLower, version); advice.addRequirement(MetadataFactory.createRequirement(IInstallableUnit.NAMESPACE_IU_ID, id, range, getFilter(entry), min, 1, true)); } if (!advice.isEmpty()) info.addAdvice(advice); } private void expandFilter(String filter, String osgiFilterValue, StringBuilder result) { String[] filters = StringHelper.getArrayFromString(filter, ','); if (filters.length > 1) result.append("(|"); //$NON-NLS-1$ for (int idx = 0; idx < filters.length; ++idx) { result.append('('); result.append(osgiFilterValue); result.append('='); result.append(filters[0]); result.append(')'); } if (filters.length > 1) result.append(')'); } private IMatchExpression<IInstallableUnit> getFilter(FeatureEntry entry) { StringBuilder result = new StringBuilder(); result.append("(&"); //$NON-NLS-1$ if (entry.getFilter() != null) result.append(entry.getFilter()); expandFilter(entry.getOS(), "osgi.os", result); //$NON-NLS-1$ expandFilter(entry.getWS(), "osgi.ws", result); //$NON-NLS-1$ expandFilter(entry.getArch(), "osgi.arch", result);//$NON-NLS-1$ expandFilter(entry.getNL(), "osgi.nl", result); //$NON-NLS-1$ if (result.length() == 2) return null; result.append(')'); return InstallableUnit.parseFilter(result.toString()); } }