blob: b34ef7dd925547c78c6f29609c190c2f9dea1ba6 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2008, 2021 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM Corporation - initial API and implementation
* EclipseSource Corporation - ongoing enhancements
* Hannes Wellmann - Bug 577541 - Clean up ClasspathHelper and TargetWeaver
* Hannes Wellmann - Bug 577543 - Only weave dev.properties for secondary launches if plug-in is from Running-Platform
* Hannes Wellmann - Bug 577118 - Handle multiple Plug-in versions in launching facility
*******************************************************************************/
package org.eclipse.pde.internal.core;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Map;
import java.util.Optional;
import java.util.Properties;
import org.eclipse.core.runtime.FileLocator;
import org.eclipse.core.runtime.Platform;
import org.eclipse.pde.core.plugin.IPluginBase;
import org.eclipse.pde.core.plugin.IPluginModelBase;
import org.osgi.framework.Bundle;
import org.osgi.framework.Constants;
import org.osgi.framework.Version;
/**
* Supports target weaving (combining the target platform with workspace
* projects to generate a woven target platform).
*
* @since 3.4
*/
public class TargetWeaver {
private TargetWeaver() { // static use only
}
/**
* Location of dev.properties, {@code null} if the Platform is not in
* development mode.
*/
private static String fgDevPropertiesURL = null;
/**
* Property file corresponding to dev.properties
*/
private static Properties fgDevProperties = null;
/**
* Initializes system properties
*/
static {
if (Platform.inDevelopmentMode()) {
fgDevPropertiesURL = System.getProperty("osgi.dev"); //$NON-NLS-1$
}
}
/**
* Returns the dev.properties as a property store.
*
* @return properties
*/
private static synchronized Properties getDevProperties() {
if (fgDevPropertiesURL != null) {
if (fgDevProperties == null) {
fgDevProperties = new Properties();
try {
URL url = new URL(fgDevPropertiesURL);
String path = url.getFile();
if (path != null && path.length() > 0) {
File file = new File(path);
if (file.exists()) {
try (InputStream stream = new FileInputStream(file)) {
fgDevProperties.load(stream);
}
}
}
} catch (IOException e) {
PDECore.log(e);
}
}
return fgDevProperties;
}
return null;
}
/**
* Updates the bundle class path if this manifest refers to a project in development
* mode from the launching workspace.
*
* @param manifest manifest to update
* @param bundleLocation the location of the Manifest's bundle
*/
public static void weaveManifest(Map<String, String> manifest, File bundleLocation) {
if (manifest != null && fgDevPropertiesURL != null) {
Properties properties = getDevProperties();
String id = manifest.get(Constants.BUNDLE_SYMBOLICNAME);
String version = manifest.get(Constants.BUNDLE_VERSION);
if (id != null && version != null) {
int index = id.indexOf(';');
if (index != -1) {
id = id.substring(0, index);
}
String property = getDevProperty(bundleLocation.toPath(), id, version, properties);
if (property != null) {
manifest.put(Constants.BUNDLE_CLASSPATH, property);
}
}
}
}
/**
* When launching a secondary runtime workbench, all projects already in dev
* mode that participate in that runtime must continue in dev mode such that
* their class files are found.
*
* @param launchDevProperties dev.properties
* @param launchedPlugins the bundles that participate in secondary runtime
*/
static void weaveRunningPlatformDevProperties(Map<IPluginModelBase, String> launchDevProperties,
Iterable<IPluginModelBase> launchedPlugins) {
if (fgDevPropertiesURL != null) {
Properties platformDevProperties = getDevProperties();
for (IPluginModelBase launchedPlugin : launchedPlugins) {
String devCP = getDevProperty(launchedPlugin, platformDevProperties);
if (devCP != null) {
launchDevProperties.put(launchedPlugin, devCP);
}
}
}
}
/**
* If a source annotation is pointing to a host project that is being wove, returns
* an empty string so that the source annotation is the root of the project.
* Otherwise returns the given library name.
*
* @param model plug-in we are attaching source for
* @param libraryName the standard library name
* @return empty string or the standard library name
*/
static String getWeavedSourceLibraryName(IPluginModelBase model, String libraryName) {
// Note that if the host project has binary-linked libraries, these libraries appear in the dev.properties file with full path names,
// and the library name must be returned as-is.
if (fgDevPropertiesURL != null && !new File(libraryName).isAbsolute()) {
Properties properties = getDevProperties();
String id = null;
if (model.getBundleDescription() != null) {
id = model.getBundleDescription().getSymbolicName();
}
/*
* Workaround for bug 332112: Do not hack the source path for
* bundles that are not coming from the host workspace.
*
* The architectural bug is that this weaving takes place at the
* wrong level. It should already be done while the target platform
* resolves bundles from the host workspace.
*/
if (id != null) {
String property = getDevProperty(model, properties);
if (property != null) {
return ""; //$NON-NLS-1$
}
}
}
return libraryName;
}
private static String getDevProperty(IPluginModelBase plugin, Properties devProperties) {
// If it has an underlying resource, then it's from the local workspace.
if (plugin.getUnderlyingResource() == null) {
Path pluginLocation = Path.of(plugin.getInstallLocation());
IPluginBase pluginBase = plugin.getPluginBase();
return getDevProperty(pluginLocation, pluginBase.getId(), pluginBase.getVersion(), devProperties);
}
return null;
}
private static String getDevProperty(Path bundleLocation, String id, String version, Properties devProperties) {
String devCP = ClasspathHelper.getDevClasspath(devProperties, id, version);
return devCP != null && isBundleOfRunningPlatform(bundleLocation, id, version) ? devCP : null;
}
private static boolean isBundleOfRunningPlatform(Path pluginLocation, String id, String version) {
Bundle platformBundle = findRunningPlatformBundle(id, version);
if (platformBundle != null) {
try {
Optional<File> bundleFile = FileLocator.getBundleFileLocation(platformBundle);
return bundleFile.isPresent() && Files.isSameFile(pluginLocation, bundleFile.get().toPath());
} catch (IOException e) {
PDECore.logException(e);
}
}
return false;
}
private static Bundle findRunningPlatformBundle(String symbolicName, String versionStr) {
// Obtain all bundles of the running platform with given symbolicName
// and filter for version here. This is likely faster than specifying a
// range like: "[" + version + "," + version + "]"
Version version = Version.parseVersion(versionStr);
Bundle[] platformBundles = Platform.getBundles(symbolicName, null);
if (platformBundles == null) {
return null;
}
return Arrays.stream(platformBundles).filter(b -> b.getVersion().equals(version)).findAny().orElse(null);
}
}