From 6b604b60e350946e7c87072cd3f879208c6fb4c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoph=20L=C3=A4ubrich?= Date: Thu, 19 Oct 2023 13:47:45 +0200 Subject: [PATCH] Add actions that publish IUs used in the build This adds actions for code that want to transform an PDE project into a set of requirements/capabilities for example to compute the required units of a target platform for such project. --- .../META-INF/MANIFEST.MF | 5 +- .../eclipse/BndInstructionsAction.java | 65 ++++++++++++ .../eclipse/BuildPropertiesAction.java | 69 ++++++++++++ .../p2/publisher/eclipse/PdeAction.java | 100 ++++++++++++++++++ 4 files changed, 238 insertions(+), 1 deletion(-) create mode 100644 bundles/org.eclipse.equinox.p2.publisher.eclipse/src/org/eclipse/equinox/p2/publisher/eclipse/BndInstructionsAction.java create mode 100644 bundles/org.eclipse.equinox.p2.publisher.eclipse/src/org/eclipse/equinox/p2/publisher/eclipse/BuildPropertiesAction.java create mode 100644 bundles/org.eclipse.equinox.p2.publisher.eclipse/src/org/eclipse/equinox/p2/publisher/eclipse/PdeAction.java diff --git a/bundles/org.eclipse.equinox.p2.publisher.eclipse/META-INF/MANIFEST.MF b/bundles/org.eclipse.equinox.p2.publisher.eclipse/META-INF/MANIFEST.MF index de94c0b13f..8fcbe9faf9 100644 --- a/bundles/org.eclipse.equinox.p2.publisher.eclipse/META-INF/MANIFEST.MF +++ b/bundles/org.eclipse.equinox.p2.publisher.eclipse/META-INF/MANIFEST.MF @@ -8,7 +8,10 @@ Bundle-ActivationPolicy: lazy Bundle-Vendor: %providerName Bundle-Localization: plugin Bundle-RequiredExecutionEnvironment: JavaSE-17 -Import-Package: org.eclipse.equinox.app;version="[1.0.0,2.0.0)", +Import-Package: aQute.bnd.header;version="2.6.0", + aQute.bnd.osgi;version="7.0.0", + aQute.bnd.stream;version="1.4.0", + org.eclipse.equinox.app;version="[1.0.0,2.0.0)", org.eclipse.equinox.frameworkadmin;version="[2.0.0,3.0.0)", org.eclipse.equinox.internal.frameworkadmin.equinox, org.eclipse.equinox.internal.frameworkadmin.utils, diff --git a/bundles/org.eclipse.equinox.p2.publisher.eclipse/src/org/eclipse/equinox/p2/publisher/eclipse/BndInstructionsAction.java b/bundles/org.eclipse.equinox.p2.publisher.eclipse/src/org/eclipse/equinox/p2/publisher/eclipse/BndInstructionsAction.java new file mode 100644 index 0000000000..2cee14cb41 --- /dev/null +++ b/bundles/org.eclipse.equinox.p2.publisher.eclipse/src/org/eclipse/equinox/p2/publisher/eclipse/BndInstructionsAction.java @@ -0,0 +1,65 @@ +/******************************************************************************* + * Copyright (c) 2023 Christoph Läubrich 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: + * Christoph Läubrich - initial API and implementation + *******************************************************************************/ +package org.eclipse.equinox.p2.publisher.eclipse; + +import aQute.bnd.osgi.Constants; +import aQute.bnd.osgi.Processor; +import java.io.File; +import java.io.IOException; +import java.util.List; +import java.util.UUID; +import org.eclipse.core.runtime.*; +import org.eclipse.equinox.p2.metadata.*; +import org.eclipse.equinox.p2.metadata.MetadataFactory.InstallableUnitDescription; +import org.eclipse.equinox.p2.publisher.*; + +/** + * This publisher action can handle bnd instruction files and transform these + * into InstallableUnits that can be used to provision a system that is used for + * building, so this action will usually used in the context of Tycho, + * Oomph or similar. + */ +public class BndInstructionsAction extends AbstractPublisherAction { + + private static final List DEPENDENCY_INSTRUCTIONS = List.of(Constants.BUILDPATH, Constants.TESTPATH); + + private final File bndFile; + + public BndInstructionsAction(File bndFile) { + this.bndFile = bndFile; + } + + @Override + public IStatus perform(IPublisherInfo publisherInfo, IPublisherResult results, IProgressMonitor monitor) { + try (Processor processor = new Processor()) { + processor.setProperties(bndFile); + List requirements = DEPENDENCY_INSTRUCTIONS.stream() + .flatMap(instr -> processor.getMergedParameters(instr).stream().mapToObj((key, attr) -> { + return MetadataFactory.createRequirement(BundlesAction.CAPABILITY_NS_OSGI_BUNDLE, key, + VersionRange.emptyRange, null, false, true); + })).toList(); + if (!requirements.isEmpty()) { + InstallableUnitDescription result = new MetadataFactory.InstallableUnitDescription(); + result.setId("bnd-bundle-requirements-" + UUID.randomUUID()); //$NON-NLS-1$ + result.setVersion(Version.createOSGi(0, 0, 0, String.valueOf(System.currentTimeMillis()))); + result.addRequirements(requirements); + results.addIU(MetadataFactory.createInstallableUnit(result), IPublisherResult.NON_ROOT); + } + } catch (IOException e) { + return Status.error(bndFile.getAbsolutePath() + " can not be processed", e); //$NON-NLS-1$ + } + return Status.OK_STATUS; + } + +} diff --git a/bundles/org.eclipse.equinox.p2.publisher.eclipse/src/org/eclipse/equinox/p2/publisher/eclipse/BuildPropertiesAction.java b/bundles/org.eclipse.equinox.p2.publisher.eclipse/src/org/eclipse/equinox/p2/publisher/eclipse/BuildPropertiesAction.java new file mode 100644 index 0000000000..723eed65a9 --- /dev/null +++ b/bundles/org.eclipse.equinox.p2.publisher.eclipse/src/org/eclipse/equinox/p2/publisher/eclipse/BuildPropertiesAction.java @@ -0,0 +1,69 @@ +/******************************************************************************* + * Copyright (c) 2023 Christoph Läubrich 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: + * Christoph Läubrich - initial API and implementation + *******************************************************************************/ +package org.eclipse.equinox.p2.publisher.eclipse; + +import java.io.*; +import java.util.*; +import java.util.function.Predicate; +import org.eclipse.core.runtime.*; +import org.eclipse.equinox.p2.metadata.*; +import org.eclipse.equinox.p2.metadata.MetadataFactory.InstallableUnitDescription; +import org.eclipse.equinox.p2.publisher.*; + +/** + * This publisher action can handle build.properties files and transform these + * into InstallableUnits that can be used to provision a system that is used for + * building, so this action will usually used in the context of Tycho, + * Oomph or similar. + */ +public class BuildPropertiesAction extends AbstractPublisherAction { + + private static final String KEY_ADDITIONAL_BUNDLES = "additional.bundles"; //$NON-NLS-1$ + private static final String SEPERATOR = ","; //$NON-NLS-1$ + + private final File buildPropertiesFile; + + public BuildPropertiesAction(File buildPropertiesFile) { + this.buildPropertiesFile = buildPropertiesFile; + } + + @Override + public IStatus perform(IPublisherInfo publisherInfo, IPublisherResult results, IProgressMonitor monitor) { + if (buildPropertiesFile.exists()) { + Properties properties = new Properties(); + try (FileInputStream stream = new FileInputStream(buildPropertiesFile)) { + properties.load(stream); + } catch (IOException e) { + return Status.error("Can't read build.properties file: " + buildPropertiesFile.getAbsolutePath(), e); //$NON-NLS-1$ + } + String property = properties.getProperty(KEY_ADDITIONAL_BUNDLES); + if (property != null) { + List requirements = Arrays.stream(property.split(SEPERATOR)).map(String::strip) + .filter(Predicate.not(String::isBlank)) + .map(bsn -> MetadataFactory.createRequirement(BundlesAction.CAPABILITY_NS_OSGI_BUNDLE, bsn, + VersionRange.emptyRange, null, true, true)) + .toList(); + if (!requirements.isEmpty()) { + InstallableUnitDescription result = new MetadataFactory.InstallableUnitDescription(); + result.setId("additional-bundle-requirements-" + UUID.randomUUID()); //$NON-NLS-1$ + result.setVersion(Version.createOSGi(0, 0, 0, String.valueOf(System.currentTimeMillis()))); + result.addRequirements(requirements); + results.addIU(MetadataFactory.createInstallableUnit(result), IPublisherResult.NON_ROOT); + } + } + } + return Status.OK_STATUS; + } + +} diff --git a/bundles/org.eclipse.equinox.p2.publisher.eclipse/src/org/eclipse/equinox/p2/publisher/eclipse/PdeAction.java b/bundles/org.eclipse.equinox.p2.publisher.eclipse/src/org/eclipse/equinox/p2/publisher/eclipse/PdeAction.java new file mode 100644 index 0000000000..b350f8ec70 --- /dev/null +++ b/bundles/org.eclipse.equinox.p2.publisher.eclipse/src/org/eclipse/equinox/p2/publisher/eclipse/PdeAction.java @@ -0,0 +1,100 @@ +/******************************************************************************* + * Copyright (c) 2023 Christoph Läubrich 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: + * Christoph Läubrich - initial API and implementation + *******************************************************************************/ +package org.eclipse.equinox.p2.publisher.eclipse; + +import java.io.*; +import java.util.Map.Entry; +import java.util.Properties; +import java.util.Set; +import java.util.jar.*; +import org.eclipse.core.runtime.*; +import org.eclipse.equinox.p2.publisher.*; +import org.eclipse.osgi.framework.util.CaseInsensitiveDictionaryMap; +import org.eclipse.osgi.service.resolver.BundleDescription; + +/** + * This publisher action can handle a PDE project root and transform this into + * InstallableUnits that can be used to provision a system that is used for + * building, so this action will usually used in the context of Tycho, + * Oomph or similar. + */ +@SuppressWarnings("restriction") +public class PdeAction extends AbstractPublisherAction { + + private final File basedir; + + public PdeAction(File basedir) { + this.basedir = basedir; + } + + @Override + public IStatus perform(IPublisherInfo publisherInfo, IPublisherResult results, IProgressMonitor monitor) { + SubMonitor subMonitor = SubMonitor.convert(monitor, 3); + File buildProperties = new File(basedir, "build.properties"); //$NON-NLS-1$ + if (buildProperties.isFile()) { + new BuildPropertiesAction(buildProperties).perform(publisherInfo, results, subMonitor.split(1)); + } + subMonitor.setWorkRemaining(2); + File pdeBnd = new File(basedir, "pde.bnd"); //$NON-NLS-1$ + if (pdeBnd.isFile()) { + new BndInstructionsAction(pdeBnd).perform(publisherInfo, results, subMonitor.split(1)); + } + subMonitor.setWorkRemaining(1); + File manifest = getManifestFile(); + if (manifest.isFile()) { + Attributes mainAttributes; + try (FileInputStream stream = new FileInputStream(manifest)) { + mainAttributes = new Manifest(stream).getMainAttributes(); + } catch (IOException e) { + return Status.error("Can't parse manifest", e); //$NON-NLS-1$ + } + CaseInsensitiveDictionaryMap headers = new CaseInsensitiveDictionaryMap<>( + mainAttributes.size()); + Set> entrySet = mainAttributes.entrySet(); + for (Entry entry : entrySet) { + headers.put(entry.getKey().toString(), entry.getValue().toString()); + } + BundleDescription bundleDescription = BundlesAction.createBundleDescription(headers, basedir); + new BundlesAction(new BundleDescription[] { bundleDescription }).perform(publisherInfo, results, + subMonitor.split(1)); + } + return Status.OK_STATUS; + } + + private File getManifestFile() { + File pdePreferences = new File(basedir, ".settings/org.eclipse.pde.core.prefs"); //$NON-NLS-1$ + if (pdePreferences.isFile()) { + Properties properties = new Properties(); + try { + properties.load(new FileInputStream(pdePreferences)); + } catch (IOException e) { + // if loading fails, handle this as if we have no special settings... + } + String property = properties.getProperty("BUNDLE_ROOT_PATH"); //$NON-NLS-1$ + if (property != null) { + File file = new File(new File(basedir, property), JarFile.MANIFEST_NAME); + if (file.isFile()) { + // even though configured, the manifest might not be there (e.g. not generated + // yet) in such case, still fall back to the default location, it can only + // happen that noting is there as well so things are not getting worser this + // way... + return file; + } + } + } + // return (possibly non existent) default location... + return new File(basedir, JarFile.MANIFEST_NAME); + } + +}