Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@
package org.eclipse.pde.internal.core.bnd;

import java.io.File;
import java.io.InputStream;
import java.util.Collection;
import java.util.Set;
import java.util.jar.JarFile;
import java.util.jar.Manifest;

import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.IPath;
Expand All @@ -23,14 +28,23 @@
import org.eclipse.pde.internal.core.natures.PluginProject;

import aQute.bnd.osgi.Analyzer;
import aQute.bnd.osgi.Jar;
import aQute.bnd.osgi.Resource;

/**
* An analyzer that is initialized by a {@link PdeProjectJar} and with the
* resolved classpath of the project.
*/
public class PdeProjectAnalyzer extends Analyzer {

public static final Set<String> DEFAULT_COPY_HEADER = Set.of(BUNDLE_CLASSPATH, BUNDLE_SYMBOLICNAME, BUNDLE_VERSION,
BUNDLE_NAME, BUNDLE_LOCALIZATION, BUNDLE_VENDOR, BUNDLE_ACTIVATOR);

public PdeProjectAnalyzer(IProject project, boolean includeTest) throws Exception {
this(project, includeTest, DEFAULT_COPY_HEADER);
}

public PdeProjectAnalyzer(IProject project, boolean includeTest, Collection<String> copyHeader) throws Exception {
super(includeTest ? new PdeTestProjectJar(project) : new PdeProjectJar(project));
set(NOEXTRAHEADERS, "true"); //$NON-NLS-1$
if (PluginProject.isJavaProject(project)) {
Expand All @@ -47,6 +61,26 @@ public PdeProjectAnalyzer(IProject project, boolean includeTest) throws Exceptio
}
}
}
Jar jar = getJar();
Resource manifestResource = jar.getResource(JarFile.MANIFEST_NAME);
if (manifestResource != null) {
if (copyHeader.isEmpty()) {
jar.remove(JarFile.MANIFEST_NAME);
} else {
Manifest mf;
try (InputStream in = manifestResource.openInputStream()) {
mf = new Manifest(in);
}
jar.remove(JarFile.MANIFEST_NAME);
for (String header : copyHeader) {
String value = mf.getMainAttributes().getValue(header);
if (value != null) {
set(header, value);
}
}
}

}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,26 @@
*******************************************************************************/
package org.eclipse.pde.internal.core.bnd;

import java.util.HashMap;
import java.util.Map;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.pde.core.build.IBuild;
import org.eclipse.pde.core.build.IBuildEntry;
import org.eclipse.pde.internal.core.ICoreConstants;
import org.eclipse.pde.internal.core.build.WorkspaceBuildModel;
import org.eclipse.pde.internal.core.natures.PluginProject;

import aQute.bnd.osgi.Jar;
import aQute.bnd.osgi.JarResource;

/**
* A {@link PdeProjectJar} packages a project into a jar like it would be
Expand All @@ -32,42 +41,137 @@
* result of such operation has to be used in the project directly, use
* {@link ProjectJar} instead that explodes all additional data into the output
* folder.
* <p>
* The jar content is driven by the {@code bin.includes} property in
* {@code build.properties}:
* <ul>
* <li>{@code .} includes the compiled output (from {@code output.} entries) at
* the jar root</li>
* <li>Library names (e.g. {@code lib.jar}) with matching {@code output.}
* entries become embedded inner jars</li>
* <li>All other entries are included as files/folders from the project
* root</li>
* </ul>
*/
public class PdeProjectJar extends Jar {

private static final String DOT = "."; //$NON-NLS-1$

public PdeProjectJar(IProject project) throws CoreException {
super(project.getName());
IFile buildFile = project.getFile(ICoreConstants.BUILD_FILENAME_DESCRIPTOR);
if (buildFile.exists()) {
IBuild build = new WorkspaceBuildModel(buildFile).getBuild();
IBuildEntry[] buildEntries = build.getBuildEntries();
for (IBuildEntry entry : buildEntries) {
String name = entry.getName();
if (name.startsWith(IBuildEntry.OUTPUT_PREFIX)) {
String folder = entry.getFirstToken();
if (folder != null) {
IFolder outputFolder = project.getFolder(folder);
if (outputFolder.exists()) {
// TODO if the library is not '.' then it should
// actually become an embedded jar!
include(outputFolder, ""); //$NON-NLS-1$
}
}
if (!buildFile.exists()) {
return;
}
IBuild build = new WorkspaceBuildModel(buildFile).getBuild();
// Build a map from library name to its output build entry
Map<String, IBuildEntry> outputEntries = new HashMap<>();
for (IBuildEntry entry : build.getBuildEntries()) {
String name = entry.getName();
if (name.startsWith(IBuildEntry.OUTPUT_PREFIX)) {
String library = name.substring(IBuildEntry.OUTPUT_PREFIX.length());
outputEntries.put(library, entry);
}
}
// bin.includes defines what goes into the jar
IBuildEntry binIncludes = build.getEntry(IBuildEntry.BIN_INCLUDES);
if (binIncludes == null) {
return;
}
for (String token : binIncludes.getTokens()) {
if (DOT.equals(token)) {
includeDotEntry(project, outputEntries.get(DOT));
} else {
IBuildEntry outputEntry = outputEntries.get(token);
if (outputEntry != null) {
includeLibraryEntry(project, token, outputEntry);
} else {
includeFileEntry(project, token);
}
}
IBuildEntry entry = build.getEntry(IBuildEntry.BIN_INCLUDES);
if (entry != null) {
// TODO adding bin included here!
}
}

/**
* Handles the {@code .} (dot) entry in {@code bin.includes}. The dot entry
* means the compiled classes should be included at the jar root.
* <p>
* If an {@code output.} entry exists, its folders are included. Otherwise,
* for Java projects the default JDT output folder is used.
*/
private void includeDotEntry(IProject project, IBuildEntry outputEntry) throws CoreException {
if (outputEntry != null) {
includeOutputFolders(project, outputEntry);
} else if (PluginProject.isJavaProject(project)) {
// '.' is in bin.includes but no output. entry exists;
// fall back to the default Java output folder
IJavaProject javaProject = JavaCore.create(project);
IWorkspaceRoot workspaceRoot = project.getWorkspace().getRoot();
IPath defaultOutput = javaProject.getOutputLocation();
IFolder defaultOutputFolder = workspaceRoot.getFolder(defaultOutput);
FileResource.addResources(this, defaultOutputFolder, null);
}
}

/**
* Handles a library entry (e.g. {@code lib.jar}) in {@code bin.includes}
* that has a matching {@code output.lib.jar} entry. The compiled output is
* packaged as an embedded inner jar.
*/
private void includeLibraryEntry(IProject project, String libraryName, IBuildEntry outputEntry)
throws CoreException {
Jar innerJar = new Jar(libraryName);
includeOutputFolders(project, outputEntry, innerJar);
putResource(libraryName, new JarResource(innerJar));
}

/**
* Includes all output folders from the given entry into this jar.
*/
private void includeOutputFolders(IProject project, IBuildEntry outputEntry) throws CoreException {
includeOutputFolders(project, outputEntry, this);
}

/**
* Includes all output folders from the given entry into the specified target
* jar.
*/
private static void includeOutputFolders(IProject project, IBuildEntry outputEntry, Jar targetJar)
throws CoreException {
for (String folder : outputEntry.getTokens()) {
String folderPath = folder.endsWith("/") ? folder.substring(0, folder.length() - 1) : folder; //$NON-NLS-1$
IFolder outputFolder = project.getFolder(folderPath);
if (outputFolder.exists()) {
FileResource.addResources(targetJar, outputFolder, null);
}
}
}

private void include(IFolder folder, String prefix) throws CoreException {
/**
* Handles a regular file or folder entry from {@code bin.includes} that
* does not have a matching {@code output.} entry. The file or folder is
* included directly from the project root.
*/
private void includeFileEntry(IProject project, String token) throws CoreException {
String path = token.endsWith("/") ? token.substring(0, token.length() - 1) : token; //$NON-NLS-1$
IResource resource = project.findMember(path);
if (resource instanceof IFile file) {
putResource(token, new FileResource(file));
} else if (resource instanceof IFolder folder) {
String prefix = token.endsWith("/") ? token : token + "/"; //$NON-NLS-1$ //$NON-NLS-2$
includeFolder(folder, prefix);
}
}

private void includeFolder(IFolder folder, String prefix) throws CoreException {
if (!folder.exists()) {
return;
}
for (IResource resource : folder.members()) {
if (resource instanceof IFile file) {
putResource(prefix + file.getName(), new FileResource(file));
} else if (resource instanceof IFolder subfolder) {
include(subfolder, prefix + subfolder.getName() + "/"); //$NON-NLS-1$
includeFolder(subfolder, prefix + subfolder.getName() + "/"); //$NON-NLS-1$
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion ui/org.eclipse.pde.launching/META-INF/MANIFEST.MF
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: %name
Bundle-SymbolicName: org.eclipse.pde.launching;singleton:=true
Bundle-Version: 3.13.600.qualifier
Bundle-Version: 3.13.700.qualifier
Bundle-RequiredExecutionEnvironment: JavaSE-17
Bundle-Vendor: %provider-name
Require-Bundle: org.eclipse.jdt.junit.core;bundle-version="[3.6.0,4.0.0)",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.jar.JarOutputStream;
import java.util.jar.Manifest;
import java.util.stream.Collectors;
Expand Down Expand Up @@ -232,7 +233,7 @@ protected void collectExecutionArguments(ILaunchConfiguration configuration, Lis
if (isJUnitContainerProject(javaProject)) {
//if this is a junit container project it means a user can use additional classes (from the junit container and possible other) that
//are not required to be imported, we compute a fragment manifest here to add additional imports ...
try (PdeProjectAnalyzer analyzer = new PdeProjectAnalyzer(javaProject.getProject(), true)) {
try (PdeProjectAnalyzer analyzer = new PdeProjectAnalyzer(javaProject.getProject(), true, Set.of())) {
analyzer.setImportPackage("*"); //$NON-NLS-1$
String bsn = testPlugin.getId() + "-additional-test-probe-imports"; //$NON-NLS-1$
analyzer.setBundleSymbolicName(bsn);
Expand Down
Loading