From 7d87cd134769ad0bc071daa2f197513108e1ba43 Mon Sep 17 00:00:00 2001 From: Sebastian Ratz Date: Mon, 8 Jul 2024 14:27:23 +0100 Subject: [PATCH] Add support for bundle URL types in macOS launcher in product files Adds CFBundleURLTypes entries to the Info.plist dictionary. Contributes to https://github.com/eclipse-platform/eclipse.platform.ui/issues/1901. --- .../p2/publisher/eclipse/BrandingIron.java | 14 +++- .../publisher/eclipse/IProductDescriptor.java | 14 +++- .../p2/publisher/eclipse/InfoPListEditor.java | 69 ++++++++++++++---- .../publisher/eclipse/MacOsBundleUrlType.java | 29 ++++++++ .../p2/publisher/eclipse/ProductFile.java | 50 ++++++++++++- .../eclipse/EquinoxExecutableAction.java | 4 +- .../p2/publisher/eclipse/IBrandingAdvice.java | 16 ++++- .../eclipse/IMacOsBundleUrlType.java | 22 ++++++ .../publisher/eclipse/ProductFileAdvice.java | 8 ++- .../actions/EquinoxExecutableActionTest.java | 72 ++++++++++++------- .../actions/ProductFileAdviceTest.java | 18 ++++- .../publisher/actions/ProductFileTest.java | 17 ++++- .../productFileActionTest.product | 6 ++ .../productWithMacOsBundleUrlTypes.product | 37 ++++++++++ 14 files changed, 327 insertions(+), 49 deletions(-) create mode 100644 bundles/org.eclipse.equinox.p2.publisher.eclipse/src/org/eclipse/equinox/internal/p2/publisher/eclipse/MacOsBundleUrlType.java create mode 100644 bundles/org.eclipse.equinox.p2.publisher.eclipse/src/org/eclipse/equinox/p2/publisher/eclipse/IMacOsBundleUrlType.java create mode 100644 bundles/org.eclipse.equinox.p2.tests/testData/ProductActionTest/productWithMacOsBundleUrlTypes.product diff --git a/bundles/org.eclipse.equinox.p2.publisher.eclipse/src/org/eclipse/equinox/internal/p2/publisher/eclipse/BrandingIron.java b/bundles/org.eclipse.equinox.p2.publisher.eclipse/src/org/eclipse/equinox/internal/p2/publisher/eclipse/BrandingIron.java index 33a61ad68b..e924311a12 100644 --- a/bundles/org.eclipse.equinox.p2.publisher.eclipse/src/org/eclipse/equinox/internal/p2/publisher/eclipse/BrandingIron.java +++ b/bundles/org.eclipse.equinox.p2.publisher.eclipse/src/org/eclipse/equinox/internal/p2/publisher/eclipse/BrandingIron.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2005, 2012 IBM Corporation and others. + * Copyright (c) 2005, 2024 IBM Corporation and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 @@ -11,16 +11,19 @@ * Contributors: * IBM Corporation - initial API and implementation * Code 9 - Additional function and fixes + * SAP SE - support macOS bundle URL types *******************************************************************************/ package org.eclipse.equinox.internal.p2.publisher.eclipse; import java.io.*; import java.nio.file.Files; +import java.util.List; import javax.xml.transform.TransformerException; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.equinox.internal.p2.core.helpers.LogHelper; import org.eclipse.equinox.p2.metadata.Version; +import org.eclipse.equinox.p2.publisher.eclipse.IMacOsBundleUrlType; import org.eclipse.pde.internal.publishing.Activator; import org.eclipse.pde.internal.publishing.Utils; import org.eclipse.pde.internal.swt.tools.IconExe; @@ -38,6 +41,7 @@ public class BrandingIron { private boolean brandIcons = true; private String id; private Version version; + private List macOsBundleUrlTypes = List.of(); public BrandingIron() { } @@ -60,6 +64,10 @@ public void setIcons(String[] value) { icons = (value == null || value.length == 0) ? null : value; } + public void setMacOsBundleUrlTypes(List value) { + macOsBundleUrlTypes = value; + } + public void setIcons(String value) { icons = org.eclipse.equinox.internal.frameworkadmin.utils.Utils.getTokens(value, ",");//$NON-NLS-1$ if (icons[0].startsWith("${")) { //$NON-NLS-1$ @@ -562,6 +570,10 @@ private void modifyInfoPListFile(ExecutablesDescriptor descriptor, File initialR infoPListEditor.setKey(InfoPListEditor.ICON_KEY, iconName); } + for (IMacOsBundleUrlType macOsBundleUrlType : macOsBundleUrlTypes) { + infoPListEditor.addCfBundleUrlType(macOsBundleUrlType.getScheme(), macOsBundleUrlType.getName()); + } + File target = new File(targetRoot, "Info.plist"); //$NON-NLS-1$ ; try { target.getParentFile().mkdirs(); diff --git a/bundles/org.eclipse.equinox.p2.publisher.eclipse/src/org/eclipse/equinox/internal/p2/publisher/eclipse/IProductDescriptor.java b/bundles/org.eclipse.equinox.p2.publisher.eclipse/src/org/eclipse/equinox/internal/p2/publisher/eclipse/IProductDescriptor.java index 895eff7bf1..c4e08ecd8b 100644 --- a/bundles/org.eclipse.equinox.p2.publisher.eclipse/src/org/eclipse/equinox/internal/p2/publisher/eclipse/IProductDescriptor.java +++ b/bundles/org.eclipse.equinox.p2.publisher.eclipse/src/org/eclipse/equinox/internal/p2/publisher/eclipse/IProductDescriptor.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2008, 2014 Code 9 and others. + * Copyright (c) 2008, 2024 Code 9 and others. * * This * program and the accompanying materials are made available under the terms of @@ -14,6 +14,7 @@ * EclipseSource - ongoing development * SAP AG - ongoing development * Rapicorp - additional features + * SAP SE - support macOS bundle URL types ******************************************************************************/ package org.eclipse.equinox.internal.p2.publisher.eclipse; @@ -22,6 +23,7 @@ import java.util.Map; import org.eclipse.equinox.frameworkadmin.BundleInfo; import org.eclipse.equinox.p2.metadata.IVersionedId; +import org.eclipse.equinox.p2.publisher.eclipse.IMacOsBundleUrlType; import org.eclipse.equinox.p2.repository.IRepositoryReference; /** @@ -207,4 +209,14 @@ public interface IProductDescriptor { */ public String getVM(String os); + /** + * Returns a list of URI schemes handled by the product. + *

+ * Currently, these are only evaluated on macOS, since they need to be placed in + * the Information Property List file (Info.plist). + */ + public default List getMacOsBundleUrlTypes() { + return List.of(); + } + } diff --git a/bundles/org.eclipse.equinox.p2.publisher.eclipse/src/org/eclipse/equinox/internal/p2/publisher/eclipse/InfoPListEditor.java b/bundles/org.eclipse.equinox.p2.publisher.eclipse/src/org/eclipse/equinox/internal/p2/publisher/eclipse/InfoPListEditor.java index 0360fb0881..6aec61376a 100644 --- a/bundles/org.eclipse.equinox.p2.publisher.eclipse/src/org/eclipse/equinox/internal/p2/publisher/eclipse/InfoPListEditor.java +++ b/bundles/org.eclipse.equinox.p2.publisher.eclipse/src/org/eclipse/equinox/internal/p2/publisher/eclipse/InfoPListEditor.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2015, 2023 Rapicorp, Inc and others. + * Copyright (c) 2015, 2024 Rapicorp, Inc and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 @@ -10,6 +10,7 @@ * * Contributors: * Rapicorp, Inc. - initial API and implementation + * SAP SE - support macOS bundle URL types *******************************************************************************/ package org.eclipse.equinox.internal.p2.publisher.eclipse; @@ -34,6 +35,7 @@ public class InfoPListEditor { public static final String BUNDLE_INFO_KEY = "CFBundleGetInfoString"; //$NON-NLS-1$ public static final String BUNDLE_VERSION_KEY = "CFBundleVersion"; //$NON-NLS-1$ public static final String BUNDLE_SHORT_VERSION_KEY = "CFBundleShortVersionString"; //$NON-NLS-1$ + public static final String BUNDLE_URL_TYPES = "CFBundleURLTypes"; //$NON-NLS-1$ public static final String ICON_KEY = "CFBundleIconFile"; //$NON-NLS-1$ private final Element infoPList; @@ -108,15 +110,57 @@ public void setKey(String key, String value) { } private void addKey(String key, String value) throws DOMException, XPathExpressionException { - Element keyNode = document.createElement("key"); //$NON-NLS-1$ - Text keyName = document.createTextNode(key); - keyNode.appendChild(keyName); + Node dict = getNode(infoPList, "/plist/dict"); //$NON-NLS-1$ + appendTextContentChild(dict, "key", key); //$NON-NLS-1$ + appendTextContentChild(dict, "string", value); //$NON-NLS-1$ + } + + public void addCfBundleUrlType(String scheme, String displayName) { + if (scheme == null) { + throw new IllegalArgumentException("Scheme can't be null"); //$NON-NLS-1$ + } + if (displayName == null) { + throw new IllegalArgumentException("Display Name can't be null"); //$NON-NLS-1$ + } + Node bundleUrlTypesNode = getOrCreateCfBundleUrlTypesArray(); + Element dict = appendNewChild(bundleUrlTypesNode, "dict"); //$NON-NLS-1$ + { + appendTextContentChild(dict, "key", "CFBundleURLName"); //$NON-NLS-1$ //$NON-NLS-2$ + appendTextContentChild(dict, "string", displayName); //$NON-NLS-1$ + } + { + appendTextContentChild(dict, "key", "CFBundleURLSchemes"); //$NON-NLS-1$ //$NON-NLS-2$ + Element array = appendNewChild(dict, "array"); //$NON-NLS-1$ + appendTextContentChild(array, "string", scheme); //$NON-NLS-1$ + } + } + + private Node getOrCreateCfBundleUrlTypesArray() { + try { + String expression = String.format("/plist/dict/key[text() = '%s']/following-sibling::array[1]", //$NON-NLS-1$ + BUNDLE_URL_TYPES); + Node arrayNode = getNode(infoPList, expression); + if (arrayNode == null) { + Node dict = getNode(infoPList, "/plist/dict"); //$NON-NLS-1$ + appendTextContentChild(dict, "key", "CFBundleURLTypes"); //$NON-NLS-1$ //$NON-NLS-2$ + arrayNode = appendNewChild(dict, "array"); //$NON-NLS-1$ + } + return arrayNode; + } catch (XPathExpressionException e) { + // Can't happen since we craft the expression carefully + throw new IllegalStateException(e); + } + } + + Element appendNewChild(Node parent, String name) { + Element node = document.createElement(name); + parent.appendChild(node); + return node; + } - Element stringNode = document.createElement("string"); //$NON-NLS-1$ - Text stringValue = document.createTextNode(value); - stringNode.appendChild(stringValue); - getNode(infoPList, "/plist/dict").appendChild(keyNode); //$NON-NLS-1$ - getNode(infoPList, "/plist/dict").appendChild(stringNode); //$NON-NLS-1$ + private void appendTextContentChild(Node parent, String elementName, String text) { + Element keyNode = appendNewChild(parent, elementName); + keyNode.appendChild(document.createTextNode(text)); } private XPath getXPathTool() { @@ -176,12 +220,9 @@ public List getEclipseArguments() { public void setEclipseArguments(List arguments) { try { removeNodes(infoPList, "/plist/dict/key[text() = 'Eclipse']/following-sibling::array[1]/string"); //$NON-NLS-1$ + Node parent = getNode(infoPList, "/plist/dict/key[text() = 'Eclipse']/following-sibling::array[1]"); //$NON-NLS-1$ for (String arg : arguments) { - Element stringNode = document.createElement("string"); //$NON-NLS-1$ - Text stringName = document.createTextNode(arg); - stringNode.appendChild(stringName); - Node toAppendTo = getNode(infoPList, "/plist/dict/key[text() = 'Eclipse']/following-sibling::array[1]"); //$NON-NLS-1$ - toAppendTo.appendChild(stringNode); + appendTextContentChild(parent, "string", arg); //$NON-NLS-1$ } } catch (XPathExpressionException e) { //can't happen diff --git a/bundles/org.eclipse.equinox.p2.publisher.eclipse/src/org/eclipse/equinox/internal/p2/publisher/eclipse/MacOsBundleUrlType.java b/bundles/org.eclipse.equinox.p2.publisher.eclipse/src/org/eclipse/equinox/internal/p2/publisher/eclipse/MacOsBundleUrlType.java new file mode 100644 index 0000000000..8d564a4692 --- /dev/null +++ b/bundles/org.eclipse.equinox.p2.publisher.eclipse/src/org/eclipse/equinox/internal/p2/publisher/eclipse/MacOsBundleUrlType.java @@ -0,0 +1,29 @@ +/******************************************************************************* + * Copyright (c) 2024 SAP SE 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: + * SAP SE - initial API and implementation + *******************************************************************************/ +package org.eclipse.equinox.internal.p2.publisher.eclipse; + +import org.eclipse.equinox.p2.publisher.eclipse.IMacOsBundleUrlType; + +record MacOsBundleUrlType(String scheme, String name) implements IMacOsBundleUrlType { + + @Override + public String getScheme() { + return scheme(); + } + + @Override + public String getName() { + return name(); + } +} diff --git a/bundles/org.eclipse.equinox.p2.publisher.eclipse/src/org/eclipse/equinox/internal/p2/publisher/eclipse/ProductFile.java b/bundles/org.eclipse.equinox.p2.publisher.eclipse/src/org/eclipse/equinox/internal/p2/publisher/eclipse/ProductFile.java index a7fd0762e1..f5d2761a97 100644 --- a/bundles/org.eclipse.equinox.p2.publisher.eclipse/src/org/eclipse/equinox/internal/p2/publisher/eclipse/ProductFile.java +++ b/bundles/org.eclipse.equinox.p2.publisher.eclipse/src/org/eclipse/equinox/internal/p2/publisher/eclipse/ProductFile.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2005, 2023 IBM Corporation and others. + * Copyright (c) 2005, 2024 IBM Corporation and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 @@ -16,6 +16,7 @@ * SAP AG - ongoing development * Rapicorp - additional features * Red Hat Inc. - Bug 460967 + * SAP SE - support macOS bundle URL types *******************************************************************************/ package org.eclipse.equinox.internal.p2.publisher.eclipse; @@ -32,6 +33,7 @@ import org.eclipse.equinox.p2.metadata.IVersionedId; import org.eclipse.equinox.p2.metadata.VersionedId; import org.eclipse.equinox.p2.publisher.eclipse.FeatureEntry; +import org.eclipse.equinox.p2.publisher.eclipse.IMacOsBundleUrlType; import org.eclipse.equinox.p2.repository.IRepository; import org.eclipse.equinox.p2.repository.IRepositoryReference; import org.eclipse.equinox.p2.repository.spi.RepositoryReference; @@ -65,6 +67,8 @@ public class ProductFile extends DefaultHandler implements IProductDescriptor { private static final String ATTRIBUTE_ARCH = "arch"; //$NON-NLS-1$ private static final String ATTRIBUTE_ENABLED = "enabled"; //$NON-NLS-1$ private static final String ATTRIBUTE_FEATURE_INSTALL_MODE = "installMode"; //$NON-NLS-1$ + private static final String ATTRIBUTE_MACOS_BUNDLE_URL_TYPE_SCHEME = "scheme"; //$NON-NLS-1$ + private static final String ATTRIBUTE_MACOS_BUNDLE_URL_TYPE_NAME = "name"; //$NON-NLS-1$ private static final String PROPERTY_ECLIPSE_APPLICATION = "eclipse.application"; //$NON-NLS-1$ private static final String PROPERTY_ECLIPSE_PRODUCT = "eclipse.product"; //$NON-NLS-1$ @@ -134,6 +138,8 @@ public class ProductFile extends DefaultHandler implements IProductDescriptor { private static final String EL_ARCH_SPARC = "argsSPARC"; //$NON-NLS-1$ private static final String EL_REPOSITORIES = "repositories"; //$NON-NLS-1$ private static final String EL_REPOSITORY = "repository"; //$NON-NLS-1$ + private static final String EL_BUNDLE_URL_TYPES = "bundleUrlTypes"; //$NON-NLS-1$ + private static final String EL_BUNDLE_URL_TYPE = "bundleUrlType"; //$NON-NLS-1$ //These constants form a small state machine to parse the .product file private static final int STATE_START = 0; @@ -169,6 +175,8 @@ public class ProductFile extends DefaultHandler implements IProductDescriptor { private static final int STATE_VM_LINUX = 31; private static final int STATE_VM_MACOS = 32; private static final int STATE_VM_WINDOWS = 33; + private static final int STATE_LAUNCHER_MAC = 34; + private static final int STATE_LAUNCHER_MAC_BUNDLE_URL_TYPES = 35; private static final String PI_PDEBUILD = "org.eclipse.pde.build"; //$NON-NLS-1$ private final static int EXCEPTION_PRODUCT_FORMAT = 23; @@ -207,6 +215,7 @@ public class ProductFile extends DefaultHandler implements IProductDescriptor { private final String currentOS; private final List repositories = new ArrayList<>(); private final Map vms = new HashMap<>(); + private final List macOsBundleUrlTypes = new ArrayList<>(); private static String normalize(String text) { if (text == null || text.trim().length() == 0) @@ -802,6 +811,7 @@ public void startElement(String uri, String localName, String qName, Attributes break; case OS_MACOSX: processMac(attributes); + state = STATE_LAUNCHER_MAC; break; default: break; @@ -813,8 +823,24 @@ public void startElement(String uri, String localName, String qName, Attributes } break; + case STATE_LAUNCHER_MAC: + if (null != localName) { + if (EL_BUNDLE_URL_TYPES.equals(localName)) { + state = STATE_LAUNCHER_MAC_BUNDLE_URL_TYPES; + } + } + break; - case STATE_LAUNCHER_ARGS : + case STATE_LAUNCHER_MAC_BUNDLE_URL_TYPES: + if (null != localName) { + if (EL_BUNDLE_URL_TYPE.equals(localName)) { + processMacOsBundleUrlTypes(attributes); + state = STATE_LAUNCHER_MAC_BUNDLE_URL_TYPES; + } + } + break; + + case STATE_LAUNCHER_ARGS: if (null != localName) switch (localName) { case PROGRAM_ARGS: state = STATE_PROGRAM_ARGS; @@ -1084,6 +1110,16 @@ public void endElement(String uri, String localName, String qName) { if (EL_LICENSE.equals(localName)) state = STATE_PRODUCT; break; + case STATE_LAUNCHER_MAC_BUNDLE_URL_TYPES: + if (EL_BUNDLE_URL_TYPES.equals(localName)) { + state = STATE_LAUNCHER_MAC; + } + break; + case STATE_LAUNCHER_MAC: + if (OS_MACOSX.equals(localName)) { + state = STATE_LAUNCHER; + } + break; case STATE_VM : state = STATE_PRODUCT; break; @@ -1376,8 +1412,18 @@ private void processMac(Attributes attributes) { addIcon(OS_MACOSX, attributes.getValue(ATTRIBUTE_ICON)); } + private void processMacOsBundleUrlTypes(Attributes attributes) { + macOsBundleUrlTypes.add(new MacOsBundleUrlType(attributes.getValue(ATTRIBUTE_MACOS_BUNDLE_URL_TYPE_SCHEME), + attributes.getValue(ATTRIBUTE_MACOS_BUNDLE_URL_TYPE_NAME))); + } + @Override public ProductContentType getProductContentType() { return productContentType; } + + @Override + public List getMacOsBundleUrlTypes() { + return macOsBundleUrlTypes; + } } diff --git a/bundles/org.eclipse.equinox.p2.publisher.eclipse/src/org/eclipse/equinox/p2/publisher/eclipse/EquinoxExecutableAction.java b/bundles/org.eclipse.equinox.p2.publisher.eclipse/src/org/eclipse/equinox/p2/publisher/eclipse/EquinoxExecutableAction.java index 79891de067..47199d87e8 100644 --- a/bundles/org.eclipse.equinox.p2.publisher.eclipse/src/org/eclipse/equinox/p2/publisher/eclipse/EquinoxExecutableAction.java +++ b/bundles/org.eclipse.equinox.p2.publisher.eclipse/src/org/eclipse/equinox/p2/publisher/eclipse/EquinoxExecutableAction.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2008, 2017 Code 9 and others. + * Copyright (c) 2008, 2024 Code 9 and others. * * This * program and the accompanying materials are made available under the terms of @@ -13,6 +13,7 @@ * Code 9 - initial API and implementation * IBM - ongoing development * Pascal Rapicault - Support for bundled macosx http://bugs.eclipse.org/57349 + * SAP SE - support macOS bundle URL types ******************************************************************************/ package org.eclipse.equinox.p2.publisher.eclipse; @@ -254,6 +255,7 @@ protected void fullBrandExecutables(ExecutablesDescriptor descriptor, IBrandingA iron.setId(idBase); iron.setVersion(version); iron.setIcons(advice.getIcons()); + iron.setMacOsBundleUrlTypes(advice.getMacOsBundleUrlTypes()); String name = advice.getExecutableName(); if (name == null) name = "eclipse"; //$NON-NLS-1$ diff --git a/bundles/org.eclipse.equinox.p2.publisher.eclipse/src/org/eclipse/equinox/p2/publisher/eclipse/IBrandingAdvice.java b/bundles/org.eclipse.equinox.p2.publisher.eclipse/src/org/eclipse/equinox/p2/publisher/eclipse/IBrandingAdvice.java index 4fcfac69f4..ae587c6f08 100644 --- a/bundles/org.eclipse.equinox.p2.publisher.eclipse/src/org/eclipse/equinox/p2/publisher/eclipse/IBrandingAdvice.java +++ b/bundles/org.eclipse.equinox.p2.publisher.eclipse/src/org/eclipse/equinox/p2/publisher/eclipse/IBrandingAdvice.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2011 EclipseSource and others. + * Copyright (c) 2009, 2024 EclipseSource and others. * * This * program and the accompanying materials are made available under the terms of @@ -11,9 +11,11 @@ * * Contributors: * EclipseSource - initial API and implementation + * SAP SE - support macOS bundle URL types ******************************************************************************/ package org.eclipse.equinox.p2.publisher.eclipse; +import java.util.List; import org.eclipse.equinox.p2.publisher.IPublisherAdvice; /** @@ -42,4 +44,16 @@ public interface IBrandingAdvice extends IPublisherAdvice { * @return the name of the branded launcher or null if none. */ public String getExecutableName(); + + /** + * Returns the list of URL schemes / names to be handled by the macOS app + * bundle. + *

+ * They will be stored in the Information Property List file + * (Info.plist) of the app bundle. + * + * @return the the list of URL schemes / names to be handled by the macOS app + * bundle + */ + public List getMacOsBundleUrlTypes(); } diff --git a/bundles/org.eclipse.equinox.p2.publisher.eclipse/src/org/eclipse/equinox/p2/publisher/eclipse/IMacOsBundleUrlType.java b/bundles/org.eclipse.equinox.p2.publisher.eclipse/src/org/eclipse/equinox/p2/publisher/eclipse/IMacOsBundleUrlType.java new file mode 100644 index 0000000000..f9b2114d87 --- /dev/null +++ b/bundles/org.eclipse.equinox.p2.publisher.eclipse/src/org/eclipse/equinox/p2/publisher/eclipse/IMacOsBundleUrlType.java @@ -0,0 +1,22 @@ +/******************************************************************************* + * Copyright (c) 2024 SAP SE 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: + * SAP SE - initial API and implementation + *******************************************************************************/ +package org.eclipse.equinox.p2.publisher.eclipse; + +public interface IMacOsBundleUrlType { + + String getScheme(); + + String getName(); + +} diff --git a/bundles/org.eclipse.equinox.p2.publisher.eclipse/src/org/eclipse/equinox/p2/publisher/eclipse/ProductFileAdvice.java b/bundles/org.eclipse.equinox.p2.publisher.eclipse/src/org/eclipse/equinox/p2/publisher/eclipse/ProductFileAdvice.java index 9f79cd5188..9a976367b8 100644 --- a/bundles/org.eclipse.equinox.p2.publisher.eclipse/src/org/eclipse/equinox/p2/publisher/eclipse/ProductFileAdvice.java +++ b/bundles/org.eclipse.equinox.p2.publisher.eclipse/src/org/eclipse/equinox/p2/publisher/eclipse/ProductFileAdvice.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2008, 2021 Code 9 and others. + * Copyright (c) 2008, 2024 Code 9 and others. * * This * program and the accompanying materials are made available under the terms of @@ -15,6 +15,7 @@ * IBM Corporation - ongoing development * Rapicorp - additional features * Christoph Läubrich - Bug 574952 p2 should distinguish between "product plugins" and "configuration plugins" (gently sponsored by Compart AG) + * SAP SE - support macOS bundle URL types ******************************************************************************/ package org.eclipse.equinox.p2.publisher.eclipse; @@ -175,6 +176,11 @@ public String getLicenseText() { return product.getLicenseText(); } + @Override + public List getMacOsBundleUrlTypes() { + return product.getMacOsBundleUrlTypes(); + } + /** * Returns the update repositories for this product */ diff --git a/bundles/org.eclipse.equinox.p2.tests/src/org/eclipse/equinox/p2/tests/publisher/actions/EquinoxExecutableActionTest.java b/bundles/org.eclipse.equinox.p2.tests/src/org/eclipse/equinox/p2/tests/publisher/actions/EquinoxExecutableActionTest.java index f9398d9383..a5cb304fee 100644 --- a/bundles/org.eclipse.equinox.p2.tests/src/org/eclipse/equinox/p2/tests/publisher/actions/EquinoxExecutableActionTest.java +++ b/bundles/org.eclipse.equinox.p2.tests/src/org/eclipse/equinox/p2/tests/publisher/actions/EquinoxExecutableActionTest.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2008, 2021 Code 9 and others. + * Copyright (c) 2008, 2024 Code 9 and others. * * This * program and the accompanying materials are made available under the terms of @@ -12,6 +12,7 @@ * Contributors: * Code 9 - initial API and implementation * IBM - ongoing development + * SAP SE - support macOS bundle URL types ******************************************************************************/ package org.eclipse.equinox.p2.tests.publisher.actions; @@ -19,6 +20,7 @@ import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.nullable; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import java.io.File; @@ -26,16 +28,13 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.Reader; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collection; -import java.util.Enumeration; import java.util.LinkedList; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; -import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.NullProgressMonitor; @@ -54,6 +53,7 @@ import org.eclipse.equinox.p2.publisher.IPublisherResult; import org.eclipse.equinox.p2.publisher.eclipse.EquinoxExecutableAction; import org.eclipse.equinox.p2.publisher.eclipse.IBrandingAdvice; +import org.eclipse.equinox.p2.publisher.eclipse.IMacOsBundleUrlType; import org.eclipse.equinox.p2.repository.artifact.IArtifactDescriptor; import org.eclipse.equinox.p2.repository.artifact.IArtifactRepository; import org.eclipse.equinox.p2.tests.TestActivator; @@ -236,23 +236,33 @@ private void checkExecutableContents(IArtifactKey key) throws IOException { * properly rewritten. * @param zip file to check for the Info.plist */ - private void checkInfoPlist(ZipFile zip) { - ZipEntry candidate = null; - boolean found = false; - for (Enumeration iter = zip.entries(); !found && iter.hasMoreElements();) { - candidate = iter.nextElement(); - found = candidate.getName().endsWith("Info.plist"); - } - assertTrue(found); - try { - String contents = readContentsAndClose(zip.getInputStream(candidate)); + private void checkInfoPlist(ZipFile zip) throws IOException { + var candidate = zip.stream().filter(e -> e.getName().endsWith("Info.plist")).findFirst(); + assertTrue(candidate.isPresent()); + try (InputStream is = zip.getInputStream(candidate.get())) { + String contents = new String(is.readAllBytes(), StandardCharsets.UTF_8); assertEquals(id, getPlistStringValue(contents, "CFBundleIdentifier")); assertEquals(EXECUTABLE_NAME, getPlistStringValue(contents, "CFBundleExecutable")); assertEquals(EXECUTABLE_NAME, getPlistStringValue(contents, "CFBundleName")); assertEquals(EXECUTABLE_NAME, getPlistStringValue(contents, "CFBundleDisplayName")); assertEquals(version.toString(), getPlistStringValue(contents, "CFBundleVersion")); - } catch (IOException e) { - fail(); + assertEquals(""" + + CFBundleURLName + Eclipse Command + CFBundleURLSchemes + + eclipse+command + + + + CFBundleURLName + Vendor Application + CFBundleURLSchemes + + vendor + + """.replaceAll(">\\s+<", "><"), getPlistBundleUrlTypesXmlWithoutWhitespace(contents)); } } @@ -265,16 +275,15 @@ private String getPlistStringValue(String contents, String key) { return null; } - private String readContentsAndClose(InputStream inputStream) throws IOException { - StringBuilder sb = new StringBuilder(); - try (Reader is = new InputStreamReader(inputStream)) { - char[] buf = new char[1024]; - int rc; - while ((rc = is.read(buf)) >= 0) { - sb.append(buf, 0, rc - 1); - } - return sb.toString(); + private static final Pattern PLIST_BUNDLE_URL_TYPES_PATTERN = Pattern + .compile("CFBundleURLTypes\\s*\\s*(.*.*?)\\s*", Pattern.DOTALL); + + private String getPlistBundleUrlTypesXmlWithoutWhitespace(String contents) { + Matcher m = PLIST_BUNDLE_URL_TYPES_PATTERN.matcher(contents); + if (m.find()) { + return m.group(1).replace("\\n", "").replaceAll(">\\s+<", "><"); } + return null; } private List setupBrandingAdvice(final String osArg, final File icon) { @@ -300,6 +309,17 @@ public String[] getIcons() { public String getExecutableName() { return EXECUTABLE_NAME; } + + @Override + public List getMacOsBundleUrlTypes() { + IMacOsBundleUrlType scheme1 = mock(IMacOsBundleUrlType.class); + when(scheme1.getName()).thenReturn("Eclipse Command"); + when(scheme1.getScheme()).thenReturn("eclipse+command"); + IMacOsBundleUrlType scheme2 = mock(IMacOsBundleUrlType.class); + when(scheme2.getName()).thenReturn("Vendor Application"); + when(scheme2.getScheme()).thenReturn("vendor"); + return List.of(scheme1, scheme2); + } }); return brandingAdvice; } diff --git a/bundles/org.eclipse.equinox.p2.tests/src/org/eclipse/equinox/p2/tests/publisher/actions/ProductFileAdviceTest.java b/bundles/org.eclipse.equinox.p2.tests/src/org/eclipse/equinox/p2/tests/publisher/actions/ProductFileAdviceTest.java index 91bc64d39e..658056d8ca 100644 --- a/bundles/org.eclipse.equinox.p2.tests/src/org/eclipse/equinox/p2/tests/publisher/actions/ProductFileAdviceTest.java +++ b/bundles/org.eclipse.equinox.p2.tests/src/org/eclipse/equinox/p2/tests/publisher/actions/ProductFileAdviceTest.java @@ -1,5 +1,5 @@ /******************************************************************************* -* Copyright (c) 2009, 2017 EclipseSource and others. +* Copyright (c) 2009, 2024 EclipseSource and others. * * This * program and the accompanying materials are made available under the terms of @@ -12,14 +12,17 @@ * Contributors: * EclipseSource - initial API and implementation * IBM Corporation - on-going maintenance +* SAP SE - support macOS bundle URL types ******************************************************************************/ package org.eclipse.equinox.p2.tests.publisher.actions; import java.io.File; +import java.util.List; import java.util.Map; import java.util.Properties; import org.eclipse.equinox.frameworkadmin.BundleInfo; import org.eclipse.equinox.internal.p2.publisher.eclipse.ProductFile; +import org.eclipse.equinox.p2.publisher.eclipse.IMacOsBundleUrlType; import org.eclipse.equinox.p2.publisher.eclipse.ProductFileAdvice; import org.eclipse.equinox.p2.tests.AbstractProvisioningTest; import org.eclipse.equinox.p2.tests.TestData; @@ -198,6 +201,19 @@ public void testGetIcons() { assertEquals("2.1", absolutePath, icons[0]); } + /** + * Test method for + * {@link org.eclipse.equinox.p2.publisher.eclipse.ProductFileAdvice#getMacOsBundleUrlTypes()}. + */ + public void testgetMacOsBundleUrlTypes() { + List macOsBundleUrlTypes = productFileAdvice2.getMacOsBundleUrlTypes(); + assertEquals(2, macOsBundleUrlTypes.size()); + assertEquals("eclipse+command", macOsBundleUrlTypes.get(0).getScheme()); + assertEquals("Eclipse Command", macOsBundleUrlTypes.get(0).getName()); + assertEquals("vendor", macOsBundleUrlTypes.get(1).getScheme()); + assertEquals("Vendor Application", macOsBundleUrlTypes.get(1).getName()); + } + public void testSimpleConfiguratorConfigURL() throws Exception { File rootFolder = getTestFolder("simpleConfiguratorConfigURL"); File sampleProduct = new File(rootFolder, "sample.product"); diff --git a/bundles/org.eclipse.equinox.p2.tests/src/org/eclipse/equinox/p2/tests/publisher/actions/ProductFileTest.java b/bundles/org.eclipse.equinox.p2.tests/src/org/eclipse/equinox/p2/tests/publisher/actions/ProductFileTest.java index 49e5fbde29..0f95b4e89b 100644 --- a/bundles/org.eclipse.equinox.p2.tests/src/org/eclipse/equinox/p2/tests/publisher/actions/ProductFileTest.java +++ b/bundles/org.eclipse.equinox.p2.tests/src/org/eclipse/equinox/p2/tests/publisher/actions/ProductFileTest.java @@ -1,5 +1,5 @@ /******************************************************************************* -* Copyright (c) 2009, 2017 EclipseSource and others. +* Copyright (c) 2009, 2024 EclipseSource and others. * * This * program and the accompanying materials are made available under the terms of @@ -11,6 +11,7 @@ * * Contributors: * EclipseSource - initial API and implementation +* SAP SE - support macOS bundle URL types ******************************************************************************/ package org.eclipse.equinox.p2.tests.publisher.actions; @@ -31,6 +32,7 @@ import org.eclipse.equinox.p2.metadata.IVersionedId; import org.eclipse.equinox.p2.metadata.Version; import org.eclipse.equinox.p2.metadata.VersionedId; +import org.eclipse.equinox.p2.publisher.eclipse.IMacOsBundleUrlType; import org.eclipse.equinox.p2.tests.TestData; import org.junit.Before; import org.junit.Test; @@ -303,4 +305,17 @@ public void testGetVM() throws Exception { assertEquals("org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-9", product.getVM(Platform.OS_LINUX)); assertNull(product.getVM(Platform.OS_MACOSX)); } + + @Test + public void testGetMacOsBundleUrlTypes() throws Exception { + String productWithMacOsBundleUrlTypes = TestData + .getFile("ProductActionTest", "productWithMacOsBundleUrlTypes.product").toString(); + ProductFile product = new ProductFile(productWithMacOsBundleUrlTypes); + List macOsBundleUrlTypes = product.getMacOsBundleUrlTypes(); + assertEquals(2, macOsBundleUrlTypes.size()); + assertEquals("eclipse+command", macOsBundleUrlTypes.get(0).getScheme()); + assertEquals("Eclipse Command", macOsBundleUrlTypes.get(0).getName()); + assertEquals("vendor", macOsBundleUrlTypes.get(1).getScheme()); + assertEquals("Vendor Application", macOsBundleUrlTypes.get(1).getName()); + } } diff --git a/bundles/org.eclipse.equinox.p2.tests/testData/ProductActionTest/productFileActionTest.product b/bundles/org.eclipse.equinox.p2.tests/testData/ProductActionTest/productFileActionTest.product index e7ffcb301f..1abd34be77 100644 --- a/bundles/org.eclipse.equinox.p2.tests/testData/ProductActionTest/productFileActionTest.product +++ b/bundles/org.eclipse.equinox.p2.tests/testData/ProductActionTest/productFileActionTest.product @@ -20,6 +20,12 @@ + + + + + + diff --git a/bundles/org.eclipse.equinox.p2.tests/testData/ProductActionTest/productWithMacOsBundleUrlTypes.product b/bundles/org.eclipse.equinox.p2.tests/testData/ProductActionTest/productWithMacOsBundleUrlTypes.product new file mode 100644 index 0000000000..dac61713d8 --- /dev/null +++ b/bundles/org.eclipse.equinox.p2.tests/testData/ProductActionTest/productWithMacOsBundleUrlTypes.product @@ -0,0 +1,37 @@ + + + + + + + + + + -XstartOnFirstThread -Dorg.eclipse.swt.internal.carbon.smallFonts + + + + + + + + + + + + + + + + + http://www.example.com + + This is the liCenSE. + + + + + + + +