diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index f2a674df7e..c7ab900f49 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -6,6 +6,25 @@ If you are reading this in the browser, then you can quickly jump to specific ve ## 5.0.0 (under development) +### Support for parallel execution of product assembly / archiving + +The mojos `materialize-products` and `archive-products` now support a new `` parameter +that enables the parallel assembly / packaging of different product variants. + +You can enable this for example like this: + +```xml + + org.eclipse.tycho + tycho-p2-director-plugin + ${tycho-version} + + true + + +``` + + ### new `repo-to-runnable` mojo This is a replacement for the [Repo2Runnable ant task](https://wiki.eclipse.org/Equinox/p2/Ant_Tasks#Repo2Runnable), example: diff --git a/tycho-core/src/main/java/org/eclipse/tycho/p2/tools/director/shared/AbstractDirectorApplicationCommand.java b/tycho-core/src/main/java/org/eclipse/tycho/p2/tools/director/shared/AbstractDirectorApplicationCommand.java index dff225b57e..965d526ddd 100644 --- a/tycho-core/src/main/java/org/eclipse/tycho/p2/tools/director/shared/AbstractDirectorApplicationCommand.java +++ b/tycho-core/src/main/java/org/eclipse/tycho/p2/tools/director/shared/AbstractDirectorApplicationCommand.java @@ -20,10 +20,12 @@ import java.util.StringJoiner; import java.util.stream.Collectors; +import org.eclipse.equinox.p2.engine.IPhaseSet; import org.eclipse.tycho.ArtifactType; import org.eclipse.tycho.DependencySeed; import org.eclipse.tycho.TargetEnvironment; import org.eclipse.tycho.p2.CommandLineArguments; +import org.eclipse.tycho.p2tools.copiedfromp2.PhaseSetFactory; /** * Base class for calling a p2 director via command line arguments. @@ -42,6 +44,7 @@ public abstract class AbstractDirectorApplicationCommand implements DirectorRunt private File destination; private File bundlePool; + private IPhaseSet phaseSet; @Override public final void addMetadataSources(Iterable metadataRepositories) { @@ -110,6 +113,18 @@ public void setProfileProperties(Map profileProperties) { this.profileProperties = profileProperties == null ? Map.of() : profileProperties; } + @Override + public void setPhaseSet(IPhaseSet phaseSet) { + this.phaseSet = phaseSet; + } + + public IPhaseSet getPhaseSet() { + if (phaseSet == null) { + return PhaseSetFactory.createDefaultPhaseSet(); + } + return phaseSet; + } + /** * Returns the command line arguments for the p2 director application (not including the * -application argument). diff --git a/tycho-core/src/main/java/org/eclipse/tycho/p2/tools/director/shared/DirectorCommandException.java b/tycho-core/src/main/java/org/eclipse/tycho/p2/tools/director/shared/DirectorCommandException.java index e4ce521f71..e8173a6c6e 100644 --- a/tycho-core/src/main/java/org/eclipse/tycho/p2/tools/director/shared/DirectorCommandException.java +++ b/tycho-core/src/main/java/org/eclipse/tycho/p2/tools/director/shared/DirectorCommandException.java @@ -19,4 +19,8 @@ public DirectorCommandException(String message) { super(message); } + public DirectorCommandException(String message, Throwable cause) { + super(message, cause); + } + } diff --git a/tycho-core/src/main/java/org/eclipse/tycho/p2/tools/director/shared/DirectorRuntime.java b/tycho-core/src/main/java/org/eclipse/tycho/p2/tools/director/shared/DirectorRuntime.java index a92fcf57f8..0b261abf82 100644 --- a/tycho-core/src/main/java/org/eclipse/tycho/p2/tools/director/shared/DirectorRuntime.java +++ b/tycho-core/src/main/java/org/eclipse/tycho/p2/tools/director/shared/DirectorRuntime.java @@ -16,6 +16,7 @@ import java.net.URI; import java.util.Map; +import org.eclipse.equinox.p2.engine.IPhaseSet; import org.eclipse.tycho.DependencySeed; import org.eclipse.tycho.PlatformPropertiesUtils; import org.eclipse.tycho.TargetEnvironment; @@ -53,13 +54,15 @@ public interface Command { void execute() throws DirectorCommandException; void setProfileProperties(Map profileProperties); + + void setPhaseSet(IPhaseSet phaseSet); } /** * Returns a new {@link Command} instance that can be used to execute a command with this * director runtime. */ - public Command newInstallCommand(); + public Command newInstallCommand(String name); /** * Computes the destination of a director install based on a target environment diff --git a/tycho-core/src/main/java/org/eclipse/tycho/p2tools/DirectorApplicationWrapper.java b/tycho-core/src/main/java/org/eclipse/tycho/p2tools/DirectorApplicationWrapper.java index 69b1989fca..46bb966dc0 100644 --- a/tycho-core/src/main/java/org/eclipse/tycho/p2tools/DirectorApplicationWrapper.java +++ b/tycho-core/src/main/java/org/eclipse/tycho/p2tools/DirectorApplicationWrapper.java @@ -17,10 +17,16 @@ import org.codehaus.plexus.component.annotations.Component; import org.codehaus.plexus.component.annotations.Requirement; import org.codehaus.plexus.logging.Logger; -import org.eclipse.equinox.internal.p2.director.app.DirectorApplication; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.equinox.p2.core.IProvisioningAgent; +import org.eclipse.equinox.p2.core.IProvisioningAgentProvider; +import org.eclipse.tycho.core.shared.StatusTool; import org.eclipse.tycho.p2.tools.director.shared.AbstractDirectorApplicationCommand; import org.eclipse.tycho.p2.tools.director.shared.DirectorCommandException; import org.eclipse.tycho.p2.tools.director.shared.DirectorRuntime; +import org.eclipse.tycho.p2tools.copiedfromp2.DirectorApplication; +import org.eclipse.tycho.p2tools.copiedfromp2.ILog; @Component(role = DirectorRuntime.class) public final class DirectorApplicationWrapper implements DirectorRuntime { @@ -32,26 +38,81 @@ public final class DirectorApplicationWrapper implements DirectorRuntime { @Requirement Logger logger; + @Requirement + IProvisioningAgentProvider agentProvider; + + @Requirement + IProvisioningAgent agent; + @Override - public Command newInstallCommand() { - return new DirectorApplicationWrapperCommand(); + public Command newInstallCommand(String name) { + return new DirectorApplicationWrapperCommand(name, agentProvider, agent, logger); } - private class DirectorApplicationWrapperCommand extends AbstractDirectorApplicationCommand { + private static class DirectorApplicationWrapperCommand extends AbstractDirectorApplicationCommand implements ILog { + + private Logger logger; + private String name; + private IProvisioningAgentProvider agentProvider; + private IProvisioningAgent agent; + + public DirectorApplicationWrapperCommand(String name, IProvisioningAgentProvider agentProvider, + IProvisioningAgent agent, Logger logger) { + this.name = name; + this.agentProvider = agentProvider; + this.agent = agent; + this.logger = logger; + } @Override public void execute() { List arguments = getDirectorApplicationArguments(); if (logger.isDebugEnabled()) { - logger.debug("Calling director with arguments: " + arguments); + logger.info("Calling director with arguments: " + arguments); } - Object exitCode = new DirectorApplication().run(arguments.toArray(new String[arguments.size()])); + try { + Object exitCode = new DirectorApplication(this, getPhaseSet(), agent, agentProvider) + .run(arguments.toArray(new String[arguments.size()])); + if (!(EXIT_OK.equals(exitCode))) { + throw new DirectorCommandException("Call to p2 director application failed with exit code " + + exitCode + ". Program arguments were: " + arguments + "."); + } + } catch (CoreException e) { + throw new DirectorCommandException("Call to p2 director application failed:" + + StatusTool.collectProblems(e.getStatus()) + ". Program arguments were: " + arguments + ".", + e); + } + + } - if (!(EXIT_OK.equals(exitCode))) { - throw new DirectorCommandException("Call to p2 director application failed with exit code " + exitCode - + ". Program arguments were: " + arguments + "."); + @Override + public void log(IStatus status) { + String message = getMsgLine(StatusTool.toLogMessage(status)); + if (status.getSeverity() == IStatus.ERROR) { + logger.error(message, status.getException()); + } else if (status.getSeverity() == IStatus.WARNING) { + logger.warn(message); + } else if (status.getSeverity() == IStatus.INFO) { + logger.info(message); + } else { + logger.debug(message); } + + } + + @Override + public void printOut(String line) { + logger.info(getMsgLine(line)); + } + + private String getMsgLine(String line) { + return "[" + name + "] " + line; + } + + @Override + public void printErr(String line) { + logger.error(getMsgLine(line)); } } diff --git a/tycho-core/src/main/java/org/eclipse/tycho/p2tools/MavenDirectorLog.java b/tycho-core/src/main/java/org/eclipse/tycho/p2tools/MavenDirectorLog.java new file mode 100644 index 0000000000..86e6d5f287 --- /dev/null +++ b/tycho-core/src/main/java/org/eclipse/tycho/p2tools/MavenDirectorLog.java @@ -0,0 +1,59 @@ +/******************************************************************************* + * 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.tycho.p2tools; + +import org.apache.maven.plugin.logging.Log; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.tycho.core.shared.StatusTool; +import org.eclipse.tycho.p2tools.copiedfromp2.ILog; + +public class MavenDirectorLog implements ILog { + + private String name; + private Log logger; + + public MavenDirectorLog(String name, Log logger) { + this.name = name; + this.logger = logger; + } + + @Override + public void log(IStatus status) { + String message = getMsgLine(StatusTool.toLogMessage(status)); + if (status.getSeverity() == IStatus.ERROR) { + logger.error(message, status.getException()); + } else if (status.getSeverity() == IStatus.WARNING) { + logger.warn(message); + } else if (status.getSeverity() == IStatus.INFO) { + logger.info(message); + } else { + logger.debug(message); + } + + } + + @Override + public void printOut(String line) { + logger.info(getMsgLine(line)); + } + + private String getMsgLine(String line) { + return "[" + name + "] " + line; + } + + @Override + public void printErr(String line) { + logger.error(getMsgLine(line)); + } + +} diff --git a/tycho-core/src/main/java/org/eclipse/tycho/p2tools/TychoDirectorApplication.java b/tycho-core/src/main/java/org/eclipse/tycho/p2tools/TychoDirectorApplication.java deleted file mode 100644 index 459b414759..0000000000 --- a/tycho-core/src/main/java/org/eclipse/tycho/p2tools/TychoDirectorApplication.java +++ /dev/null @@ -1,32 +0,0 @@ -/******************************************************************************* - * 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.tycho.p2tools; - -import org.eclipse.equinox.internal.p2.director.app.DirectorApplication; -import org.eclipse.equinox.p2.core.IProvisioningAgent; -import org.eclipse.equinox.p2.core.IProvisioningAgentProvider; -import org.eclipse.equinox.p2.repository.artifact.IArtifactRepositoryManager; - -/** - * Enhances the P2 {@link DirectorApplication} by injecting the agent provider from plexus context, - * additionally to that ensures that the extension classloader is used to load this class as it is - * part of tycho-core module. - */ -public class TychoDirectorApplication extends DirectorApplication { - - public TychoDirectorApplication(IProvisioningAgentProvider agentProvider, IProvisioningAgent agent) { - //TODO should be able to control agent creation see https://github.com/eclipse-equinox/p2/pull/398 - //Until now we need to fetch a service to trigger loading of the internal osgi framework... - agent.getService(IArtifactRepositoryManager.class); - } -} diff --git a/tycho-core/src/main/java/org/eclipse/tycho/p2tools/copiedfromp2/DirectorApplication.java b/tycho-core/src/main/java/org/eclipse/tycho/p2tools/copiedfromp2/DirectorApplication.java new file mode 100644 index 0000000000..7e0953a8f0 --- /dev/null +++ b/tycho-core/src/main/java/org/eclipse/tycho/p2tools/copiedfromp2/DirectorApplication.java @@ -0,0 +1,1798 @@ +/******************************************************************************* + * Copyright (c) 2007, 2020 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 + * Cloudsmith - https://bugs.eclipse.org/bugs/show_bug.cgi?id=226401 + * EclipseSource - ongoing development + * Sonatype, Inc. - ongoing development + * Pascal Rapicault - Support for bundled macosx 431116 + * Red Hat, Inc. - support repositories passed via fragments (see bug 378329).Bug 460967 + * SAP AG - list formatting (bug 423538) + * Todor Boev - Software AG + *******************************************************************************/ +package org.eclipse.tycho.p2tools.copiedfromp2; + +import static org.eclipse.core.runtime.IStatus.ERROR; +import static org.eclipse.core.runtime.IStatus.INFO; +import static org.eclipse.core.runtime.IStatus.OK; +import static org.eclipse.core.runtime.IStatus.WARNING; +import static org.eclipse.equinox.internal.p2.director.app.Activator.ID; + +import java.io.BufferedInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.PrintStream; +import java.net.URI; +import java.net.URISyntaxException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.cert.Certificate; +import java.security.cert.CertificateEncodingException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.EventObject; +import java.util.HashMap; +import java.util.HashSet; +import java.util.HexFormat; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Properties; +import java.util.Set; +import java.util.TreeSet; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +import org.bouncycastle.openpgp.PGPPublicKey; +import org.eclipse.core.runtime.Assert; +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.MultiStatus; +import org.eclipse.core.runtime.NullProgressMonitor; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.URIUtil; +import org.eclipse.equinox.app.IApplication; +import org.eclipse.equinox.app.IApplicationContext; +import org.eclipse.equinox.internal.p2.core.helpers.ServiceHelper; +import org.eclipse.equinox.internal.p2.core.helpers.StringHelper; +import org.eclipse.equinox.internal.p2.director.ProfileChangeRequest; +import org.eclipse.equinox.internal.p2.director.app.Activator; +import org.eclipse.equinox.internal.p2.director.app.IUListFormatter; +import org.eclipse.equinox.internal.p2.director.app.Messages; +import org.eclipse.equinox.internal.p2.director.app.PrettyQuery; +import org.eclipse.equinox.internal.p2.engine.EngineActivator; +import org.eclipse.equinox.internal.p2.engine.phases.AuthorityChecker; +import org.eclipse.equinox.internal.provisional.p2.core.eventbus.IProvisioningEventBus; +import org.eclipse.equinox.internal.provisional.p2.core.eventbus.ProvisioningListener; +import org.eclipse.equinox.internal.provisional.p2.director.IDirector; +import org.eclipse.equinox.internal.provisional.p2.director.PlanExecutionHelper; +import org.eclipse.equinox.internal.provisional.p2.repository.RepositoryEvent; +import org.eclipse.equinox.p2.core.IProvisioningAgent; +import org.eclipse.equinox.p2.core.IProvisioningAgentProvider; +import org.eclipse.equinox.p2.core.ProvisionException; +import org.eclipse.equinox.p2.core.UIServices; +import org.eclipse.equinox.p2.engine.IEngine; +import org.eclipse.equinox.p2.engine.IPhaseSet; +import org.eclipse.equinox.p2.engine.IProfile; +import org.eclipse.equinox.p2.engine.IProfileRegistry; +import org.eclipse.equinox.p2.engine.IProvisioningPlan; +import org.eclipse.equinox.p2.engine.ProvisioningContext; +import org.eclipse.equinox.p2.engine.query.UserVisibleRootQuery; +import org.eclipse.equinox.p2.metadata.IArtifactKey; +import org.eclipse.equinox.p2.metadata.IInstallableUnit; +import org.eclipse.equinox.p2.metadata.ITouchpointData; +import org.eclipse.equinox.p2.metadata.ITouchpointInstruction; +import org.eclipse.equinox.p2.metadata.IVersionedId; +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.planner.IPlanner; +import org.eclipse.equinox.p2.planner.IProfileChangeRequest; +import org.eclipse.equinox.p2.query.Collector; +import org.eclipse.equinox.p2.query.IQuery; +import org.eclipse.equinox.p2.query.IQueryResult; +import org.eclipse.equinox.p2.query.IQueryable; +import org.eclipse.equinox.p2.query.QueryUtil; +import org.eclipse.equinox.p2.repository.IRepository; +import org.eclipse.equinox.p2.repository.artifact.IArtifactRepositoryManager; +import org.eclipse.equinox.p2.repository.artifact.spi.IArtifactUIServices; +import org.eclipse.equinox.p2.repository.metadata.IMetadataRepositoryManager; +import org.eclipse.equinox.p2.repository.metadata.spi.IInstallableUnitUIServices; +import org.eclipse.equinox.p2.repository.spi.PGPPublicKeyService; +import org.eclipse.osgi.service.environment.EnvironmentInfo; +import org.eclipse.osgi.util.NLS; + +public class DirectorApplication implements IApplication, ProvisioningListener { + public static class AvoidTrustPromptService extends UIServices + implements IArtifactUIServices, IInstallableUnitUIServices { + + // These are instructions that are common, expected, and both uninteresting and + // non-threatening. + private static final Pattern IGNORED_TOUCHPOINT_DATA = Pattern.compile( + "manifest=Instruction\\[Bundle-SymbolicName: [^ ]+(; singleton:=true)? Bundle-Version: [^,]+( Fragment-Host: [^;]+;bundle-version=\"[^\"]+\")?,null]|" //$NON-NLS-1$ + + "zipped=Instruction\\[true,null]"); //$NON-NLS-1$ + + private final PrintStream out; + private final ByteArrayOutputStream details; + private final boolean trustSignedContentOnly; + private final Set trustedAuthorityURIs; + private final Set trustedPGPKeyFingerprints; + private final Set trustedCertificateFingerprints; + + public AvoidTrustPromptService() { + this(false, false, null, null, null); + } + + public AvoidTrustPromptService(boolean verbose, boolean trustSignedContentOnly, Set trustedAuthorityURIs, + Set trustedPGPKeys, Set trustedCertificates) { + if (verbose) { + this.out = System.out; + this.details = null; + } else { + details = new ByteArrayOutputStream(); + this.out = new PrintStream(details, false, StandardCharsets.UTF_8); + } + this.trustSignedContentOnly = trustSignedContentOnly; + this.trustedAuthorityURIs = trustedAuthorityURIs; + this.trustedPGPKeyFingerprints = trustedPGPKeys; + this.trustedCertificateFingerprints = trustedCertificates; + } + + public void dump() { + if (details != null) { + out.close(); + System.out.println(new String(details.toByteArray(), StandardCharsets.UTF_8)); + } + } + + @Override + public AuthenticationInfo getUsernamePassword(String location) { + return null; + } + + @Override + public AuthenticationInfo getUsernamePassword(String location, AuthenticationInfo previousInfo) { + return null; + } + + @Deprecated + @Override + public TrustInfo getTrustInfo(Certificate[][] untrustedChains, String[] unsignedDetail) { + throw new UnsupportedOperationException( + "Use AvoidTrustPromptService.getTrustAuthorityInfo(Map>, Map>)"); //$NON-NLS-1$ + } + + @Deprecated + @Override + public TrustInfo getTrustInfo(Certificate[][] untrustedChains, Collection untrustedPGPKeys, + String[] unsignedDetail) { + + throw new UnsupportedOperationException( + "Use AvoidTrustPromptService.getTrustAuthorityInfo(Map>, Map>)"); //$NON-NLS-1$ + } + + @Override + public TrustAuthorityInfo getTrustAuthorityInfo(Map> siteIUs, + Map> siteCertificates) { + + Set trustedAuthorities = new LinkedHashSet<>(); + for (Map.Entry> entry : siteIUs.entrySet()) { + URI authority = entry.getKey(); + out.println(NLS.bind(Messages.DirectorApplication_FetchingIUsHeading, authority)); + for (IInstallableUnit iu : entry.getValue()) { + out.println(" " + iu); //$NON-NLS-1$ + for (ITouchpointData touchpointData : iu.getTouchpointData()) { + for (Map.Entry data : touchpointData.getInstructions() + .entrySet()) { + String text = data.toString().replaceAll("\\s+", " ").trim(); //$NON-NLS-1$ //$NON-NLS-2$ + if (!IGNORED_TOUCHPOINT_DATA.matcher(text).matches()) { + out.println(" " + text); //$NON-NLS-1$ + } + } + } + } + + if (trustedAuthorityURIs != null) { + List authorityChain = AuthorityChecker.getAuthorityChain(authority); + for (URI uri : authorityChain) { + if (trustedAuthorityURIs.contains(uri)) { + trustedAuthorities.add(authority); + break; + } + } + } + } + + return new TrustAuthorityInfo(trustedAuthorityURIs != null ? trustedAuthorities : siteIUs.keySet(), false, + false); + } + + @Override + public TrustInfo getTrustInfo(Map, Set> untrustedCertificateChains, + Map> untrustedPGPKeys, Set unsignedArtifacts, + Map artifactFiles) { + + Set trustedCertificates = new LinkedHashSet<>(); + if (untrustedCertificateChains != null) { + for (Map.Entry, Set> entry : untrustedCertificateChains.entrySet()) { + out.println(Messages.DirectorApplication_CertficateTrustChainHeading); + List chain = entry.getKey(); + boolean trusted = false; + for (Certificate certificate : chain) { + String fingerprint = getFingerprint(certificate); + if (trustedCertificateFingerprints == null + || trustedCertificateFingerprints.contains(fingerprint)) { + trusted = true; + } + + // Indent all the lines two more spaces then the lines for the artifacts. + String text = certificate.toString().replaceAll("(\r?\n)", "$1 "); //$NON-NLS-1$ //$NON-NLS-2$ + out.println(" " + fingerprint + " -> " + text); //$NON-NLS-1$ //$NON-NLS-2$ + } + + for (IArtifactKey artifactKey : entry.getValue()) { + File artifactFile = artifactFiles.get(artifactKey); + out.println(" " + artifactKey + " -> " + artifactFile); //$NON-NLS-1$ //$NON-NLS-2$ + } + + if (trusted) { + trustedCertificates.add(chain.get(0)); + } + } + } + + Set pgpKeys = new LinkedHashSet<>(); + if (untrustedPGPKeys != null) { + for (Map.Entry> entry : untrustedPGPKeys.entrySet()) { + PGPPublicKey key = entry.getKey(); + String fingerprint = PGPPublicKeyService.toHexFingerprint(key); + if (trustedPGPKeyFingerprints == null || trustedPGPKeyFingerprints.contains(fingerprint)) { + pgpKeys.add(key); + } + + out.println(NLS.bind(Messages.DirectorApplication_PGPKeysHeading, fingerprint)); + for (IArtifactKey artifactKey : entry.getValue()) { + File artifactFile = artifactFiles.get(artifactKey); + out.println(" " + artifactKey + " -> " + artifactFile); //$NON-NLS-1$ //$NON-NLS-2$ + } + } + } + + if (unsignedArtifacts != null && !unsignedArtifacts.isEmpty()) { + out.println(Messages.DirectorApplication_UnsignedHeading); + for (IArtifactKey artifactKey : unsignedArtifacts) { + File artifactFile = artifactFiles.get(artifactKey); + out.println(" " + artifactKey + " -> " + artifactFile); //$NON-NLS-1$ //$NON-NLS-2$ + } + } + + return new TrustInfo(trustedCertificates, pgpKeys, false, !trustSignedContentOnly); + } + + private String getFingerprint(Certificate certificate) { + try { + MessageDigest digester = MessageDigest.getInstance("SHA-256"); //$NON-NLS-1$ + return HexFormat.of().formatHex(digester.digest(certificate.getEncoded())); + } catch (CertificateEncodingException | NoSuchAlgorithmException e) { + return "deadbeef"; //$NON-NLS-1$ + } + } + } + + class LocationQueryable implements IQueryable { + private URI location; + + public LocationQueryable(URI location) { + this.location = location; + Assert.isNotNull(location); + } + + @Override + public IQueryResult query(IQuery query, IProgressMonitor monitor) { + return getInstallableUnits(location, query, monitor); + } + } + + private static class CommandLineOption { + final String[] identifiers; + private final String optionSyntaxString; + private final String helpString; + + CommandLineOption(String[] identifiers, String optionSyntaxString, String helpString) { + this.identifiers = identifiers; + this.optionSyntaxString = optionSyntaxString; + this.helpString = helpString; + } + + boolean isOption(String opt) { + int idx = identifiers.length; + while (--idx >= 0) + if (identifiers[idx].equalsIgnoreCase(opt)) + return true; + return false; + } + + void appendHelp(PrintStream out) { + out.print(identifiers[0]); + for (int idx = 1; idx < identifiers.length; ++idx) { + out.print(" | "); //$NON-NLS-1$ + out.print(identifiers[idx]); + } + if (optionSyntaxString != null) { + out.print(' '); + out.print(optionSyntaxString); + } + out.println(); + out.print(" "); //$NON-NLS-1$ + out.println(helpString); + } + + @SuppressWarnings("nls") + void appendHelpDocumentation(PrintStream out) { + out.print("
"); + out.print(identifiers[0]); + for (int idx = 1; idx < identifiers.length; ++idx) { + out.print(" | "); //$NON-NLS-1$ + out.print(identifiers[idx]); + } + if (optionSyntaxString != null) { + out.print(' '); + out.print(escape(optionSyntaxString)); + } + out.println("
"); + out.println("
"); + out.println(escape(helpString)); + out.println("
"); + } + + @SuppressWarnings("nls") + private String escape(String string) { + return string.replace("&", "&").replace("<", "<").replace(">", ">"); + } + } + + private static final CommandLineOption OPTION_HELP = new CommandLineOption(new String[] { // + "-help", "-h", "-?" }, //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + null, Messages.Help_Prints_this_command_line_help); + private static final CommandLineOption OPTION_LIST = new CommandLineOption(new String[] { // + "-list", "-l" }, //$NON-NLS-1$ //$NON-NLS-2$ + Messages.Help_lb_lt_comma_separated_list_gt_rb, Messages.Help_List_all_IUs_found_in_repos); + private static final CommandLineOption OPTION_LIST_FORMAT = new CommandLineOption(new String[] { // + "-listFormat", "-lf" }, //$NON-NLS-1$ //$NON-NLS-2$ + Messages.Help_lt_list_format_gt, Messages.Help_formats_the_IU_list); + private static final CommandLineOption OPTION_LIST_INSTALLED = new CommandLineOption(new String[] { // + "-listInstalledRoots", "-lir" }, //$NON-NLS-1$ //$NON-NLS-2$ + null, Messages.Help_List_installed_roots); + private static final CommandLineOption OPTION_INSTALL_IU = new CommandLineOption(new String[] { // + "-installIU", "-installIUs", "-i" }, //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + Messages.Help_lt_comma_separated_list_gt, Messages.Help_Installs_the_listed_IUs); + private static final CommandLineOption OPTION_UNINSTALL_IU = new CommandLineOption(new String[] { // + "-uninstallIU", "-uninstallIUs", "-u" }, //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + Messages.Help_lt_comma_separated_list_gt, Messages.Help_Uninstalls_the_listed_IUs); + private static final CommandLineOption OPTION_REVERT = new CommandLineOption(new String[] { // + "-revert" }, //$NON-NLS-1$ + Messages.Help_lt_comma_separated_list_gt, Messages.Help_Revert_to_previous_state); + private static final CommandLineOption OPTION_DESTINATION = new CommandLineOption(new String[] { // + "-destination", "-d" }, //$NON-NLS-1$ //$NON-NLS-2$ + Messages.Help_lt_path_gt, Messages.Help_The_folder_in_which_the_targetd_product_is_located); + private static final CommandLineOption OPTION_METADATAREPOS = new CommandLineOption(new String[] { // + "-metadatarepository", "metadatarepositories", "-m" }, //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + Messages.Help_lt_comma_separated_list_gt, Messages.Help_A_list_of_URLs_denoting_metadata_repositories); + private static final CommandLineOption OPTION_ARTIFACTREPOS = new CommandLineOption(new String[] { // + "-artifactrepository", "artifactrepositories", "-a" }, //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + Messages.Help_lt_comma_separated_list_gt, Messages.Help_A_list_of_URLs_denoting_artifact_repositories); + private static final CommandLineOption OPTION_REPOSITORIES = new CommandLineOption(new String[] { // + "-repository", "repositories", "-r" }, //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + Messages.Help_lt_comma_separated_list_gt, Messages.Help_A_list_of_URLs_denoting_colocated_repositories); + private static final CommandLineOption OPTION_VERIFY_ONLY = new CommandLineOption(new String[] { // + "-verifyOnly" }, //$NON-NLS-1$ + null, Messages.Help_Only_verify_dont_install); + private static final CommandLineOption OPTION_PROFILE = new CommandLineOption(new String[] { // + "-profile", "-p" }, //$NON-NLS-1$ //$NON-NLS-2$ + Messages.Help_lt_name_gt, Messages.Help_Defines_what_profile_to_use_for_the_actions); + private static final CommandLineOption OPTION_FLAVOR = new CommandLineOption(new String[] { // + "-flavor", "-f" }, //$NON-NLS-1$ //$NON-NLS-2$ + Messages.Help_lt_name_gt, Messages.Help_Defines_flavor_to_use_for_created_profile); + private static final CommandLineOption OPTION_SHARED = new CommandLineOption(new String[] { // + "-shared", "-s" }, //$NON-NLS-1$ //$NON-NLS-2$ + Messages.Help_lb_lt_path_gt_rb, Messages.Help_Use_a_shared_location_for_the_install); + private static final CommandLineOption OPTION_BUNDLEPOOL = new CommandLineOption(new String[] { // + "-bundlepool", "-b" }, //$NON-NLS-1$ //$NON-NLS-2$ + Messages.Help_lt_path_gt, Messages.Help_The_location_where_the_plugins_and_features_will_be_stored); + private static final CommandLineOption OPTION_IU_PROFILE_PROPS = new CommandLineOption(new String[] { // + "-iuProfileproperties" }, //$NON-NLS-1$ + Messages.Help_lt_path_gt, Messages.Help_path_to_IU_profile_properties_file); + private static final CommandLineOption OPTION_PROFILE_PROPS = new CommandLineOption(new String[] { // + "-profileproperties" }, //$NON-NLS-1$ + Messages.Help_lt_comma_separated_list_gt, Messages.Help_A_list_of_properties_in_the_form_key_value_pairs); + private static final CommandLineOption OPTION_ROAMING = new CommandLineOption(new String[] { // + "-roaming" }, //$NON-NLS-1$ + null, Messages.Help_Indicates_that_the_product_can_be_moved); + private static final CommandLineOption OPTION_P2_OS = new CommandLineOption(new String[] { // + "-p2.os" }, //$NON-NLS-1$ + null, Messages.Help_The_OS_when_profile_is_created); + private static final CommandLineOption OPTION_P2_WS = new CommandLineOption(new String[] { // + "-p2.ws" }, //$NON-NLS-1$ + null, Messages.Help_The_WS_when_profile_is_created); + private static final CommandLineOption OPTION_P2_ARCH = new CommandLineOption(new String[] { // + "-p2.arch" }, //$NON-NLS-1$ + null, Messages.Help_The_ARCH_when_profile_is_created); + private static final CommandLineOption OPTION_P2_NL = new CommandLineOption(new String[] { // + "-p2.nl" }, //$NON-NLS-1$ + null, Messages.Help_The_NL_when_profile_is_created); + private static final CommandLineOption OPTION_PURGEHISTORY = new CommandLineOption(new String[] { // + "-purgeHistory" }, //$NON-NLS-1$ + null, Messages.Help_Purge_the_install_registry); + private static final CommandLineOption OPTION_FOLLOW_REFERENCES = new CommandLineOption(new String[] { // + "-followReferences" }, //$NON-NLS-1$ + null, Messages.Help_Follow_references); + private static final CommandLineOption OPTION_TAG = new CommandLineOption(new String[] { // + "-tag" }, //$NON-NLS-1$ + Messages.Help_lt_name_gt, Messages.Help_Defines_a_tag_for_provisioning_session); + private static final CommandLineOption OPTION_LIST_TAGS = new CommandLineOption(new String[] { // + "-listTags" }, //$NON-NLS-1$ + null, Messages.Help_List_Tags); + private static final CommandLineOption OPTION_DOWNLOAD_ONLY = new CommandLineOption(new String[] { // + "-downloadOnly" }, //$NON-NLS-1$ + null, Messages.Help_Download_Only); + private static final CommandLineOption OPTION_VERBOSE_TRUST = new CommandLineOption(new String[] { // + "-verboseTrust", "-vt" }, //$NON-NLS-1$ //$NON-NLS-2$ + null, "Whether to print detailed information about the content trust."); //$NON-NLS-1$ + private static final CommandLineOption OPTION_TRUST_SIGNED_CONTENT_ONLY = new CommandLineOption(new String[] { // + "-trustSignedContentOnly", "-tsco" }, //$NON-NLS-1$ //$NON-NLS-2$ + null, Messages.DirectorApplication_Help_TrustSignedContentOnly); + private static final CommandLineOption OPTION_TRUSTED_AUTHORITIES = new CommandLineOption(new String[] { // + "-trustedAuthorities", "-ta" }, //$NON-NLS-1$ //$NON-NLS-2$ + Messages.Help_lt_comma_separated_list_gt, Messages.DirectorApplication_Help_TrustedAuthorities); + private static final CommandLineOption OPTION_TRUSTED_PGP_KEYS = new CommandLineOption(new String[] { // + "-trustedPGPKeys", "-tk" }, //$NON-NLS-1$ //$NON-NLS-2$ + Messages.Help_lt_comma_separated_list_gt, Messages.DirectorApplication_Help_TrustedKeys); + private static final CommandLineOption OPTION_TRUSTED_CERTIFCATES = new CommandLineOption(new String[] { // + "-trustedCertificates", "-tc" }, //$NON-NLS-1$ //$NON-NLS-2$ + Messages.Help_lt_comma_separated_list_gt, Messages.DirectorApplication_Help_TrustedCertificates); + private static final CommandLineOption OPTION_IGNORED = new CommandLineOption(new String[] { // + "-showLocation", "-eclipse.password", "-eclipse.keyring" }, //$NON-NLS-1$ //$NON-NLS-2$//$NON-NLS-3$ + null, ""); //$NON-NLS-1$ + + private static final Integer EXIT_ERROR = 13; + static private final String FLAVOR_DEFAULT = "tooling"; //$NON-NLS-1$ + static private final String PROP_P2_PROFILE = "eclipse.p2.profile"; //$NON-NLS-1$ + static private final String NO_ARTIFACT_REPOSITORIES_AVAILABLE = "noArtifactRepositoriesAvailable"; //$NON-NLS-1$ + + private static final String FOLLOW_ARTIFACT_REPOSITORY_REFERENCES = "org.eclipse.equinox.p2.director.followArtifactRepositoryReferences"; //$NON-NLS-1$ + private static final String LIST_GROUPS_SHORTCUT = "Q:GROUP"; //$NON-NLS-1$ + private static final String QUERY_SEPARATOR = "Q:"; //$NON-NLS-1$ + private static final String QUERY_SEPARATOR_SMALL = "q:"; //$NON-NLS-1$ + + private static Set getAuthorityURIs(String spec) throws CoreException { + Set result = new LinkedHashSet<>(); + List rawURIs = new ArrayList<>(); + getURIs(rawURIs, spec); + for (URI uri : rawURIs) { + // Avoid URIs like https://host/ in favor of https://host which actual works. + List authorityChain = AuthorityChecker.getAuthorityChain(uri); + URI mainAuthority = authorityChain.get(0); + if (authorityChain.size() == 2) { + if ((mainAuthority + "/").equals(authorityChain.get(1).toString())) { //$NON-NLS-1$ + result.add(mainAuthority); + continue; + } + } + + // For longer authorities, ensure that it ends with a "/" + if (uri.toString().endsWith("/")) { //$NON-NLS-1$ + result.add(uri); + } + + result.add(URI.create(uri + "/")); //$NON-NLS-1$ + } + return result; + } + + private static void getURIs(List uris, String spec) throws CoreException { + if (spec == null) + return; + String[] urlSpecs = StringHelper.getArrayFromString(spec, ','); + for (String urlSpec : urlSpecs) { + try { + uris.add(new URI(urlSpec)); + } catch (URISyntaxException e1) { + try { + uris.add(URIUtil.fromString(urlSpec)); + } catch (URISyntaxException e) { + throw new ProvisionException(NLS.bind(Messages.unable_to_parse_0_to_uri_1, urlSpec, e.getMessage()), + e); + } + } + } + } + + private static String getRequiredArgument(String[] args, int argIdx) throws CoreException { + if (argIdx < args.length) { + String arg = args[argIdx]; + if (!arg.startsWith("-")) //$NON-NLS-1$ + return arg; + } + throw new ProvisionException(NLS.bind(Messages.option_0_requires_an_argument, args[argIdx - 1])); + } + + private static String getOptionalArgument(String[] args, int argIdx) { + // Look ahead to the next argument + ++argIdx; + if (argIdx < args.length) { + String arg = args[argIdx]; + if (!arg.startsWith("-")) //$NON-NLS-1$ + return arg; + } + return null; + } + + private static void parseIUsArgument(List> vnames, String arg) { + String[] roots = StringHelper.getArrayFromString(arg, ','); + for (String root : roots) { + if (root.equalsIgnoreCase(LIST_GROUPS_SHORTCUT)) { + vnames.add(new PrettyQuery<>(QueryUtil.createIUGroupQuery(), "All groups")); //$NON-NLS-1$ + continue; + } + if (root.startsWith(QUERY_SEPARATOR) || root.startsWith(QUERY_SEPARATOR_SMALL)) { + String queryString = root.substring(2); + vnames.add(new PrettyQuery<>(QueryUtil.createQuery(queryString, new Object[0]), queryString)); + continue; + } + IVersionedId vId = VersionedId.parse(root); + Version v = vId.getVersion(); + IQuery query = new PrettyQuery<>(QueryUtil.createIUQuery(vId.getId(), + Version.emptyVersion.equals(v) ? VersionRange.emptyRange : new VersionRange(v, true, v, true)), + root); + vnames.add(query); + } + } + + private static File processFileArgument(String arg) { + if (arg.startsWith("file:")) //$NON-NLS-1$ + arg = arg.substring(5); + + // we create a path object here to handle ../ entries in the middle of paths + return IPath.fromOSString(arg).toFile(); + } + + private IArtifactRepositoryManager artifactManager; + IMetadataRepositoryManager metadataManager; + + private URI[] artifactReposForRemoval; + private URI[] metadataReposForRemoval; + + private final List artifactRepositoryLocations = new ArrayList<>(); + private final List metadataRepositoryLocations = new ArrayList<>(); + private final List> rootsToInstall = new ArrayList<>(); + private final List> rootsToUninstall = new ArrayList<>(); + private final List> rootsToList = new ArrayList<>(); + + private File bundlePool = null; + private File destination; + private File sharedLocation; + private String flavor; + private boolean printHelpInfo = false; + private boolean printIUList = false; + private boolean printRootIUList = false; + private boolean printTags = false; + private IUListFormatter listFormat; + + private String revertToPreviousState = NOTHING_TO_REVERT_TO; + private static String NOTHING_TO_REVERT_TO = "-1"; //$NON-NLS-1$ + private static String REVERT_TO_PREVIOUS = "0"; //$NON-NLS-1$ + private boolean verifyOnly; + private boolean roamingProfile; + private boolean purgeRegistry; + private boolean followReferences; + private boolean downloadOnly; + private String profileId; + private String profileProperties; // a comma-separated list of property pairs "tag=value" + private String iuProfileProperties; // path to Properties file with IU profile properties + private String ws; + private String os; + private String arch; + private String nl; + private String tag; + private boolean verboseTrust; + private boolean trustSignedContentOnly; + private Set trustedAuthorityURIs; + private Set trustedPGPKeys; + private Set trustedCertificates; + + private IEngine engine; + private boolean noProfileId; + private IPlanner planner; + private final ILog log; + + private IProvisioningAgent targetAgent; + private boolean targetAgentIsSelfAndUp; + private boolean noArtifactRepositorySpecified; + private AvoidTrustPromptService trustService; + private IPhaseSet phaseSet; + private IProvisioningAgent defaultAgent; + private IProvisioningAgentProvider provisioningAgentProvider; + + public DirectorApplication(ILog log, IPhaseSet phaseSet, IProvisioningAgent defaultAgent, + IProvisioningAgentProvider provisioningAgentProvider) { + this.log = log; + this.phaseSet = phaseSet; + this.defaultAgent = defaultAgent; + this.provisioningAgentProvider = provisioningAgentProvider; + } + + protected ProfileChangeRequest buildProvisioningRequest(IProfile profile, Collection installs, + Collection uninstalls) { + ProfileChangeRequest request = new ProfileChangeRequest(profile); + markRoots(request, installs); + markRoots(request, uninstalls); + request.addAll(installs); + request.removeAll(uninstalls); + buildIUProfileProperties(request); + return request; + } + + // read the given file into a Properties object + private Properties loadProperties(File file) { + if (!file.exists()) { + // log a warning and return + log.log(new Status(WARNING, ID, NLS.bind(Messages.File_does_not_exist, file.getAbsolutePath()))); + return null; + } + Properties properties = new Properties(); + try (InputStream input = new BufferedInputStream(new FileInputStream(file))) { + properties.load(input); + } catch (IOException e) { + log.log(new Status(ERROR, ID, NLS.bind(Messages.Problem_loading_file, file.getAbsolutePath()), e)); + return null; + } + return properties; + } + + private void buildIUProfileProperties(IProfileChangeRequest request) { + final String KEYWORD_KEY = "key"; //$NON-NLS-1$ + final String KEYWORD_VALUE = "value"; //$NON-NLS-1$ + final String KEYWORD_VERSION = "version"; //$NON-NLS-1$ + + if (iuProfileProperties == null) + return; + + // read the file into a Properties object for easier processing + Properties properties = loadProperties(new File(iuProfileProperties)); + if (properties == null) + return; + + // format for a line in the properties input file is + // ..=value + // id is the IU id + // keyword is either "key" or "value" + // uniqueNumber is used to group keys and values together + Set alreadyProcessed = new HashSet<>(); + for (Object object : properties.keySet()) { + String line = (String) object; + int index = line.lastIndexOf('.'); + if (index == -1) + continue; + int num = -1; + String id = null; + try { + num = Integer.parseInt(line.substring(index + 1)); + line = line.substring(0, index); + index = line.lastIndexOf('.'); + if (index == -1) + continue; + // skip over the keyword + id = line.substring(0, index); + } catch (NumberFormatException e) { + log.log(new Status(WARNING, ID, NLS.bind(Messages.Bad_format, line, iuProfileProperties), e)); + continue; + } catch (IndexOutOfBoundsException e) { + log.log(new Status(WARNING, ID, NLS.bind(Messages.Bad_format, line, iuProfileProperties), e)); + continue; + } + + String versionLine = id + '.' + KEYWORD_VERSION + '.' + num; + String keyLine = id + '.' + KEYWORD_KEY + '.' + num; + String valueLine = id + '.' + KEYWORD_VALUE + '.' + num; + + if (alreadyProcessed.contains(versionLine) || alreadyProcessed.contains(keyLine) + || alreadyProcessed.contains(valueLine)) + continue; + + // skip over this key/value pair next time we see it + alreadyProcessed.add(versionLine); + alreadyProcessed.add(keyLine); + alreadyProcessed.add(valueLine); + + Version version = Version.create((String) properties.get(versionLine)); // it is ok to have a null version + String key = (String) properties.get(keyLine); + String value = (String) properties.get(valueLine); + + if (key == null || value == null) { + String message = NLS.bind(Messages.Unmatched_iu_profile_property_key_value, key + '/' + value); + log.log(new Status(WARNING, ID, message)); + continue; + } + + // lookup the IU - a null version matches all versions + IQuery query = QueryUtil.createIUQuery(id, version); + // if we don't have a version the choose the latest. + if (version == null) + query = QueryUtil.createLatestQuery(query); + IQueryResult qr = getInstallableUnits(null, query, null); + if (qr.isEmpty()) { + String msg = NLS.bind(Messages.Cannot_set_iu_profile_property_iu_does_not_exist, id + '/' + version); + log.log(new Status(WARNING, ID, msg)); + continue; + } + IInstallableUnit iu = qr.iterator().next(); + request.setInstallableUnitProfileProperty(iu, key, value); + } + + } + + private void cleanupRepositories() { + if (artifactReposForRemoval != null && artifactManager != null) { + for (int i = 0; i < artifactReposForRemoval.length && artifactReposForRemoval[i] != null; i++) { + artifactManager.removeRepository(artifactReposForRemoval[i]); + } + } + if (metadataReposForRemoval != null && metadataManager != null) { + for (int i = 0; i < metadataReposForRemoval.length && metadataReposForRemoval[i] != null; i++) { + metadataManager.removeRepository(metadataReposForRemoval[i]); + } + } + } + + private IQueryResult collectRootIUs(IQuery query) { + IProgressMonitor nullMonitor = new NullProgressMonitor(); + + int top = metadataRepositoryLocations.size(); + if (top == 0) + return getInstallableUnits(null, query, nullMonitor); + + List> locationQueryables = new ArrayList<>(top); + for (int i = 0; i < top; i++) + locationQueryables.add(new LocationQueryable(metadataRepositoryLocations.get(i))); + return QueryUtil.compoundQueryable(locationQueryables).query(query, nullMonitor); + } + + private Collection collectRoots(IProfile profile, List> rootNames, + boolean forInstall) throws CoreException { + ArrayList allRoots = new ArrayList<>(); + for (IQuery rootQuery : rootNames) { + IQueryResult roots = null; + if (forInstall) + roots = collectRootIUs(QueryUtil.createLatestQuery(rootQuery)); + + if (roots == null || roots.isEmpty()) + roots = profile.query(rootQuery, new NullProgressMonitor()); + + Iterator itor = roots.iterator(); + if (!itor.hasNext()) + throw new CoreException(new Status(ERROR, ID, NLS.bind(Messages.Missing_IU, rootQuery))); + do { + allRoots.add(itor.next()); + } while (itor.hasNext()); + } + return allRoots; + } + + private String getEnvironmentProperty() { + HashMap values = new HashMap<>(); + if (os != null) + values.put("osgi.os", os); //$NON-NLS-1$ + if (nl != null) + values.put("osgi.nl", nl); //$NON-NLS-1$ + if (ws != null) + values.put("osgi.ws", ws); //$NON-NLS-1$ + if (arch != null) + values.put("osgi.arch", arch); //$NON-NLS-1$ + return values.isEmpty() ? null : toString(values); + } + + private IProfile getProfile() { + IProfileRegistry profileRegistry = targetAgent.getService(IProfileRegistry.class); + if (profileId == null) { + profileId = IProfileRegistry.SELF; + noProfileId = true; + } + return profileRegistry.getProfile(profileId); + } + + private IProfile initializeProfile() throws CoreException { + IProfile profile = getProfile(); + if (profile == null) { + if (destination == null) + missingArgument("destination"); //$NON-NLS-1$ + if (flavor == null) + flavor = System.getProperty("eclipse.p2.configurationFlavor", FLAVOR_DEFAULT); //$NON-NLS-1$ + + Map props = new HashMap<>(); + props.put(IProfile.PROP_INSTALL_FOLDER, destination.toString()); + if (bundlePool == null) + props.put(IProfile.PROP_CACHE, + sharedLocation == null ? destination.getAbsolutePath() : sharedLocation.getAbsolutePath()); + else + props.put(IProfile.PROP_CACHE, bundlePool.getAbsolutePath()); + if (roamingProfile) + props.put(IProfile.PROP_ROAMING, Boolean.TRUE.toString()); + + String env = getEnvironmentProperty(); + if (env != null) + props.put(IProfile.PROP_ENVIRONMENTS, env); + if (profileProperties != null) + putProperties(profileProperties, props); + profile = targetAgent.getService(IProfileRegistry.class).addProfile(profileId, props); + } + return profile; + } + + private void initializeRepositories() throws CoreException { + if (rootsToInstall.isEmpty() && revertToPreviousState == NOTHING_TO_REVERT_TO && !printIUList) + // Not much point initializing repositories if we have nothing to install + return; + if (artifactRepositoryLocations == null) + missingArgument("-artifactRepository"); //$NON-NLS-1$ + + artifactManager = targetAgent.getService(IArtifactRepositoryManager.class); + if (artifactManager == null) + throw new ProvisionException(Messages.Application_NoManager); + + int removalIdx = 0; + boolean anyValid = false; // do we have any valid repos or did they all fail to load? + artifactReposForRemoval = new URI[artifactRepositoryLocations.size()]; + for (URI location : artifactRepositoryLocations) { + try { + if (!artifactManager.contains(location)) { + artifactManager.loadRepository(location, null); + artifactReposForRemoval[removalIdx++] = location; + } + anyValid = true; + } catch (ProvisionException e) { + // one of the repositories did not load + log.log(e.getStatus()); + } + } + if (!anyValid) + noArtifactRepositorySpecified = true; + + if (metadataRepositoryLocations == null) + missingArgument("metadataRepository"); //$NON-NLS-1$ + + metadataManager = targetAgent.getService(IMetadataRepositoryManager.class); + if (metadataManager == null) + throw new ProvisionException(Messages.Application_NoManager); + + removalIdx = 0; + anyValid = false; // do we have any valid repos or did they all fail to load? + int top = metadataRepositoryLocations.size(); + metadataReposForRemoval = new URI[top]; + for (int i = 0; i < top; i++) { + URI location = metadataRepositoryLocations.get(i); + try { + if (!metadataManager.contains(location)) { + metadataManager.loadRepository(location, null); + metadataReposForRemoval[removalIdx++] = location; + } + anyValid = true; + } catch (ProvisionException e) { + // one of the repositories did not load + log.log(e.getStatus()); + } + } + if (!anyValid) + // all repositories failed to load + throw new ProvisionException(Messages.Application_NoRepositories); + + if (!EngineActivator.EXTENDED) + return; + + File[] extensions = EngineActivator.getExtensionsDirectories(); + + for (File f : extensions) { + metadataManager.addRepository(f.toURI()); + metadataManager.setRepositoryProperty(f.toURI(), EngineActivator.P2_FRAGMENT_PROPERTY, + Boolean.TRUE.toString()); + metadataRepositoryLocations.add(f.toURI()); + artifactManager.addRepository(f.toURI()); + artifactManager.setRepositoryProperty(f.toURI(), EngineActivator.P2_FRAGMENT_PROPERTY, + Boolean.TRUE.toString()); + artifactRepositoryLocations.add(f.toURI()); + } + } + + private void adjustDestination() { + // Detect the desire to have a bundled mac application and tweak the environment + if (destination == null) + return; + if (org.eclipse.osgi.service.environment.Constants.OS_MACOSX.equals(os) + && destination.getName().endsWith(".app")) //$NON-NLS-1$ + destination = new File(destination, "Contents/Eclipse"); //$NON-NLS-1$ + } + + // Implement something here to position "p2 folder" correctly + private void initializeServices() throws CoreException { + if (targetAgent == null) { + if (destination != null || sharedLocation != null) { + File dataAreaFile = sharedLocation == null ? new File(destination, "p2") : sharedLocation;//$NON-NLS-1$ + targetAgent = createAgent(dataAreaFile.toURI()); + targetAgentIsSelfAndUp = false; + } else { + targetAgent = getDefaultAgent(); + targetAgentIsSelfAndUp = true; + } + } + if (profileId == null) { + if (destination != null) { + File configIni = new File(destination, "configuration/config.ini"); //$NON-NLS-1$ + Properties ciProps = new Properties(); + try (InputStream in = new BufferedInputStream(new FileInputStream(configIni));) { + ciProps.load(in); + profileId = ciProps.getProperty(PROP_P2_PROFILE); + } catch (IOException e) { + // Ignore + } + if (profileId == null) + profileId = destination.toString(); + } + } + if (profileId != null) + targetAgent.registerService(PROP_P2_PROFILE, profileId); + else + targetAgent.unregisterService(PROP_P2_PROFILE, null); + + IDirector director = targetAgent.getService(IDirector.class); + if (director == null) + throw new ProvisionException(Messages.Missing_director); + + planner = targetAgent.getService(IPlanner.class); + if (planner == null) + throw new ProvisionException(Messages.Missing_planner); + + engine = targetAgent.getService(IEngine.class); + if (engine == null) + throw new ProvisionException(Messages.Missing_Engine); + + trustService = new AvoidTrustPromptService(verboseTrust, trustSignedContentOnly, trustedAuthorityURIs, + trustedPGPKeys, trustedCertificates); + targetAgent.registerService(UIServices.SERVICE_NAME, trustService); + + IProvisioningEventBus eventBus = targetAgent.getService(IProvisioningEventBus.class); + if (eventBus == null) + return; + eventBus.addListener(this); + + } + + /** + * Called when the director application want to use the default provisioning agent + * + * @return the current default agent, never null + * @throws CoreException + * when fetching the agent failed + */ + protected IProvisioningAgent getDefaultAgent() throws CoreException { + return defaultAgent; + } + + /** + * Creates a new agent for the given data area + * + * @param p2DataArea + * the data area to create a new agent + * @return the new agent, never null + * @throws CoreException + * if creation of the agent for the given location failed + */ + protected IProvisioningAgent createAgent(URI p2DataArea) throws CoreException { + IProvisioningAgent agent = provisioningAgentProvider.createAgent(p2DataArea); + agent.registerService(IProvisioningAgent.INSTALLER_AGENT, provisioningAgentProvider.createAgent(null)); + return agent; + } + + /* + * See bug: https://bugs.eclipse.org/340971 Using the event bus to detect whether or not a + * repository was added in a touchpoint action. If it was, then (if it exists) remove it from + * our list of repos to remove after we complete our install. + */ + @Override + public void notify(EventObject o) { + if (!(o instanceof RepositoryEvent)) + return; + RepositoryEvent event = (RepositoryEvent) o; + if (RepositoryEvent.ADDED != event.getKind()) + return; + + // TODO BE CAREFUL SINCE WE ARE MODIFYING THE SELF PROFILE + int type = event.getRepositoryType(); + URI location = event.getRepositoryLocation(); + if (IRepository.TYPE_ARTIFACT == type && artifactReposForRemoval != null) { + for (int i = 0; i < artifactReposForRemoval.length; i++) { + if (artifactReposForRemoval[i] != null && URIUtil.sameURI(artifactReposForRemoval[i], (location))) { + artifactReposForRemoval[i] = null; + break; + } + } + // either found or not found. either way, we're done here + return; + } + if (IRepository.TYPE_METADATA == type && metadataReposForRemoval != null) { + for (int i = 0; i < metadataReposForRemoval.length; i++) { + if (metadataReposForRemoval[i] != null && URIUtil.sameURI(metadataReposForRemoval[i], (location))) { + metadataReposForRemoval[i] = null; + break; + } + } + // either found or not found. either way, we're done here + return; + } + } + + private void markRoots(IProfileChangeRequest request, Collection roots) { + for (IInstallableUnit root : roots) { + request.setInstallableUnitProfileProperty(root, IProfile.PROP_PROFILE_ROOT_IU, Boolean.TRUE.toString()); + } + } + + private void missingArgument(String argumentName) throws CoreException { + throw new ProvisionException(NLS.bind(Messages.Missing_Required_Argument, argumentName)); + } + + private void performList() throws CoreException { + if (metadataRepositoryLocations.isEmpty()) + missingArgument("metadataRepository"); //$NON-NLS-1$ + + ArrayList allRoots = new ArrayList<>(); + if (rootsToList.size() == 0) { + for (IInstallableUnit element : collectRootIUs(QueryUtil.createIUAnyQuery())) + allRoots.add(element); + } else { + for (IQuery root : rootsToList) { + for (IInstallableUnit element : collectRootIUs(root)) + allRoots.add(element); + } + } + + allRoots.sort(null); + + String formattedString = listFormat.format(allRoots); + log.printOut(formattedString); + } + + private void performProvisioningActions() throws CoreException { + IProfile profile = initializeProfile(); + Collection installs = collectRoots(profile, rootsToInstall, true); + Collection uninstalls = collectRoots(profile, rootsToUninstall, false); + + // keep this result status in case there is a problem so we can report it to the + // user + boolean wasRoaming = Boolean.parseBoolean(profile.getProperty(IProfile.PROP_ROAMING)); + try { + updateRoamingProperties(profile); + + ProvisioningContext context = new ProvisioningContext(targetAgent); + context.setMetadataRepositories(metadataRepositoryLocations.stream().toArray(URI[]::new)); + context.setArtifactRepositories(artifactRepositoryLocations.stream().toArray(URI[]::new)); + context.setProperty(ProvisioningContext.FOLLOW_REPOSITORY_REFERENCES, String.valueOf(followReferences)); + context.setProperty(FOLLOW_ARTIFACT_REPOSITORY_REFERENCES, String.valueOf(followReferences)); + ProfileChangeRequest request = buildProvisioningRequest(profile, installs, uninstalls); + printRequest(request); + planAndExecute(profile, context, request); + } finally { + // if we were originally were set to be roaming and we changed it, change it + // back before we return + if (wasRoaming && !Boolean.parseBoolean(profile.getProperty(IProfile.PROP_ROAMING))) { + setRoaming(profile); + } + } + } + + private void planAndExecute(IProfile profile, ProvisioningContext context, ProfileChangeRequest request) + throws CoreException { + IProvisioningPlan result = planner.getProvisioningPlan(request, context, new NullProgressMonitor()); + + IStatus operationStatus = result.getStatus(); + if (!operationStatus.isOK()) { + throw new CoreException(operationStatus); + } + + log.log(operationStatus); + executePlan(context, result); + } + + private void executePlan(ProvisioningContext context, IProvisioningPlan result) throws CoreException { + if (verifyOnly) { + return; + } + IStatus operationStatus; + operationStatus = PlanExecutionHelper.executePlan(result, engine, phaseSet, context, new NullProgressMonitor()); + switch (operationStatus.getSeverity()) { + case OK: + break; + case INFO: + case WARNING: + log.log(operationStatus); + break; + // All other status codes correspond to IStatus.isOk() == false + default: + if (noArtifactRepositorySpecified && hasNoRepositoryFound(operationStatus)) { + throw new ProvisionException(Messages.Application_NoRepositories); + } + throw new CoreException(operationStatus); + } + + if (tag == null) { + return; + } + long newState = result.getProfile().getTimestamp(); + IProfileRegistry registry = targetAgent.getService(IProfileRegistry.class); + registry.setProfileStateProperty(result.getProfile().getProfileId(), newState, IProfile.STATE_PROP_TAG, tag); + } + + private boolean hasNoRepositoryFound(IStatus status) { + if (status.getException() != null + && NO_ARTIFACT_REPOSITORIES_AVAILABLE.equals(status.getException().getMessage())) + return true; + if (status.isMultiStatus()) { + for (IStatus child : status.getChildren()) { + if (hasNoRepositoryFound(child)) + return true; + } + } + return false; + } + + public void processArguments(String[] args) throws CoreException { + if (args == null) { + printHelpInfo = true; + return; + } + + // Set platform environment defaults + EnvironmentInfo info = ServiceHelper.getService(Activator.getContext(), EnvironmentInfo.class); + os = info.getOS(); + ws = info.getWS(); + nl = info.getNL(); + arch = info.getOSArch(); + + for (int i = 0; i < args.length; i++) { + // check for args without parameters (i.e., a flag arg) + String opt = args[i]; + if (OPTION_LIST.isOption(opt)) { + printIUList = true; + String optionalArgument = getOptionalArgument(args, i); + if (optionalArgument != null) { + parseIUsArgument(rootsToList, optionalArgument); + i++; + } + continue; + } + + if (OPTION_LIST_FORMAT.isOption(opt)) { + String formatString = getRequiredArgument(args, ++i); + listFormat = new IUListFormatter(formatString); + continue; + } + + if (OPTION_LIST_INSTALLED.isOption(opt)) { + printRootIUList = true; + continue; + } + + if (OPTION_LIST_TAGS.isOption(opt)) { + printTags = true; + continue; + } + + if (OPTION_DOWNLOAD_ONLY.isOption(opt)) { + downloadOnly = true; + continue; + } + + if (OPTION_HELP.isOption(opt)) { + printHelpInfo = true; + continue; + } + + if (OPTION_INSTALL_IU.isOption(opt)) { + parseIUsArgument(rootsToInstall, getRequiredArgument(args, ++i)); + continue; + } + + if (OPTION_UNINSTALL_IU.isOption(opt)) { + parseIUsArgument(rootsToUninstall, getRequiredArgument(args, ++i)); + continue; + } + + if (OPTION_REVERT.isOption(opt)) { + String targettedState = getOptionalArgument(args, i); + if (targettedState == null) { + revertToPreviousState = REVERT_TO_PREVIOUS; + } else { + i++; + revertToPreviousState = targettedState; + } + continue; + + } + if (OPTION_PROFILE.isOption(opt)) { + profileId = getRequiredArgument(args, ++i); + continue; + } + + if (OPTION_FLAVOR.isOption(opt)) { + flavor = getRequiredArgument(args, ++i); + continue; + } + + if (OPTION_SHARED.isOption(opt)) { + if (++i < args.length) { + String nxt = args[i]; + if (nxt.startsWith("-")) //$NON-NLS-1$ + --i; // Oops, that's the next option, not an argument + else + sharedLocation = processFileArgument(nxt); + } + if (sharedLocation == null) + // -shared without an argument means "Use default shared area" + sharedLocation = IPath.fromOSString(System.getProperty("user.home")).append(".p2/").toFile(); //$NON-NLS-1$ //$NON-NLS-2$ + continue; + } + + if (OPTION_DESTINATION.isOption(opt)) { + destination = processFileArgument(getRequiredArgument(args, ++i)); + continue; + } + + if (OPTION_BUNDLEPOOL.isOption(opt)) { + bundlePool = processFileArgument(getRequiredArgument(args, ++i)); + continue; + } + + if (OPTION_METADATAREPOS.isOption(opt)) { + getURIs(metadataRepositoryLocations, getRequiredArgument(args, ++i)); + continue; + } + + if (OPTION_ARTIFACTREPOS.isOption(opt)) { + getURIs(artifactRepositoryLocations, getRequiredArgument(args, ++i)); + continue; + } + + if (OPTION_REPOSITORIES.isOption(opt)) { + String arg = getRequiredArgument(args, ++i); + getURIs(metadataRepositoryLocations, arg); + getURIs(artifactRepositoryLocations, arg); + continue; + } + + if (OPTION_PROFILE_PROPS.isOption(opt)) { + profileProperties = getRequiredArgument(args, ++i); + continue; + } + + if (OPTION_IU_PROFILE_PROPS.isOption(opt)) { + iuProfileProperties = getRequiredArgument(args, ++i); + continue; + } + + if (OPTION_ROAMING.isOption(opt)) { + roamingProfile = true; + continue; + } + + if (OPTION_VERIFY_ONLY.isOption(opt)) { + verifyOnly = true; + continue; + } + + if (OPTION_PURGEHISTORY.isOption(opt)) { + purgeRegistry = true; + continue; + } + + if (OPTION_FOLLOW_REFERENCES.isOption(opt)) { + followReferences = true; + continue; + } + + if (OPTION_P2_OS.isOption(opt)) { + os = getRequiredArgument(args, ++i); + continue; + } + + if (OPTION_P2_WS.isOption(opt)) { + ws = getRequiredArgument(args, ++i); + continue; + } + + if (OPTION_P2_NL.isOption(opt)) { + nl = getRequiredArgument(args, ++i); + continue; + } + + if (OPTION_P2_ARCH.isOption(opt)) { + arch = getRequiredArgument(args, ++i); + continue; + } + + if (OPTION_TAG.isOption(opt)) { + tag = getRequiredArgument(args, ++i); + continue; + } + + if (OPTION_VERBOSE_TRUST.isOption(opt)) { + verboseTrust = true; + continue; + } + + if (OPTION_TRUST_SIGNED_CONTENT_ONLY.isOption(opt)) { + trustSignedContentOnly = true; + continue; + } + + if (OPTION_TRUSTED_AUTHORITIES.isOption(opt)) { + String optionalArgument = getOptionalArgument(args, i); + if (optionalArgument != null) { + i++; + } + trustedAuthorityURIs = getAuthorityURIs(optionalArgument); + continue; + } + + if (OPTION_TRUSTED_PGP_KEYS.isOption(opt)) { + String optionalArgument = getOptionalArgument(args, i); + if (optionalArgument != null) { + i++; + } + trustedPGPKeys = new HashSet<>(Arrays.asList(StringHelper.getArrayFromString(optionalArgument, ','))); + continue; + } + + if (OPTION_TRUSTED_CERTIFCATES.isOption(opt)) { + String optionalArgument = getOptionalArgument(args, i); + if (optionalArgument != null) { + i++; + } + trustedCertificates = new HashSet<>( + Arrays.asList(StringHelper.getArrayFromString(optionalArgument, ','))); + continue; + } + + if (OPTION_IGNORED.isOption(opt)) { + String optionalArgument = getOptionalArgument(args, i); + if (optionalArgument != null) { + i++; + } + continue; + } + + if (opt != null && opt.length() > 0) + throw new ProvisionException(NLS.bind(Messages.unknown_option_0, opt)); + } + + if (listFormat != null && !printIUList && !printRootIUList) { + throw new ProvisionException(NLS.bind(Messages.ArgRequiresOtherArgs, // + new String[] { OPTION_LIST_FORMAT.identifiers[0], OPTION_LIST.identifiers[0], + OPTION_LIST_INSTALLED.identifiers[0] })); + } + + else if (!printHelpInfo && !printIUList && !printRootIUList && !printTags && !purgeRegistry + && rootsToInstall.isEmpty() && rootsToUninstall.isEmpty() + && revertToPreviousState == NOTHING_TO_REVERT_TO) { + log.printOut(Messages.Help_Missing_argument); + printHelpInfo = true; + } + + if (listFormat == null) { + listFormat = new IUListFormatter("${id}=${version}"); //$NON-NLS-1$ + } + } + + /** + * @param pairs + * a comma separated list of tag=value pairs + * @param properties + * the collection into which the pairs are put + */ + private void putProperties(String pairs, Map properties) { + String[] propPairs = StringHelper.getArrayFromString(pairs, ','); + for (String propPair : propPairs) { + int eqIdx = propPair.indexOf('='); + if (eqIdx < 0) + continue; + String tagKey = propPair.substring(0, eqIdx).trim(); + if (tagKey.length() == 0) + continue; + String tagValue = propPair.substring(eqIdx + 1).trim(); + if (tagValue.length() > 0) + properties.put(tagKey, tagValue); + } + } + + private void cleanupServices() { + // dispose agent, only if it is not already up and running + if (targetAgent != null && !targetAgentIsSelfAndUp) { + targetAgent.stop(); + targetAgent = null; + } + } + + public Object run(String[] args) throws CoreException { + long time = System.currentTimeMillis(); + + try { + processArguments(args); + if (printHelpInfo) + performHelpInfo(false); + else { + adjustDestination(); + initializeServices(); + if (!(printIUList || printRootIUList || printTags)) { + if (!canInstallInDestination()) { + log.printOut(NLS.bind(Messages.Cant_write_in_destination, destination.getAbsolutePath())); + return EXIT_ERROR; + } + } + initializeRepositories(); + + if (revertToPreviousState != NOTHING_TO_REVERT_TO) { + revertToPreviousState(); + } else if (!(rootsToInstall.isEmpty() && rootsToUninstall.isEmpty())) { + performProvisioningActions(); + } + if (printIUList) { + performList(); + } + if (printRootIUList) { + performListInstalledRoots(); + } + if (printTags) { + performPrintTags(); + } + if (purgeRegistry) { + purgeRegistry(); + } + log.printOut(NLS.bind(Messages.Operation_complete, Long.valueOf(System.currentTimeMillis() - time))); + } + return IApplication.EXIT_OK; + } finally { + cleanupRepositories(); + cleanupServices(); + } + } + + private void purgeRegistry() throws ProvisionException { + if (getProfile() == null) + return; + IProfileRegistry registry = targetAgent.getService(IProfileRegistry.class); + long[] allProfiles = registry.listProfileTimestamps(profileId); + for (int i = 0; i < allProfiles.length - 1; i++) { + registry.removeProfile(profileId, allProfiles[i]); + } + } + + private void revertToPreviousState() throws CoreException { + IProfile profile = initializeProfile(); + IProfileRegistry profileRegistry = targetAgent.getService(IProfileRegistry.class); + IProfile targetProfile = null; + if (revertToPreviousState == REVERT_TO_PREVIOUS) { + long[] profiles = profileRegistry.listProfileTimestamps(profile.getProfileId()); + if (profiles.length == 0) + return; + targetProfile = profileRegistry.getProfile(profile.getProfileId(), profiles[profiles.length - 1]); + } else { + targetProfile = profileRegistry.getProfile(profile.getProfileId(), + getTimestampToRevertTo(profileRegistry, profile.getProfileId())); + } + + if (targetProfile == null) + throw new CoreException(new Status(ERROR, ID, Messages.Missing_profile)); + IProvisioningPlan plan = planner.getDiffPlan(profile, targetProfile, new NullProgressMonitor()); + + ProvisioningContext context = new ProvisioningContext(targetAgent); + context.setMetadataRepositories( + metadataRepositoryLocations.toArray(new URI[metadataRepositoryLocations.size()])); + context.setArtifactRepositories( + artifactRepositoryLocations.toArray(new URI[artifactRepositoryLocations.size()])); + context.setProperty(ProvisioningContext.FOLLOW_REPOSITORY_REFERENCES, String.valueOf(followReferences)); + context.setProperty(FOLLOW_ARTIFACT_REPOSITORY_REFERENCES, String.valueOf(followReferences)); + executePlan(context, plan); + } + + private long getTimestampToRevertTo(IProfileRegistry profileRegistry, String profId) { + long timestampToRevertTo = -1; + try { + // Deal with the case where the revert points to a timestamp + timestampToRevertTo = Long.valueOf(revertToPreviousState).longValue(); + } catch (NumberFormatException e) { + // Deal with the case where the revert points to tag + Map tags = profileRegistry.getProfileStateProperties(profId, IProfile.STATE_PROP_TAG); + Set> entries = tags.entrySet(); + for (Entry entry : entries) { + if (entry.getValue().equals(revertToPreviousState)) + try { + long tmp = Long.valueOf(entry.getKey()).longValue(); + if (tmp > timestampToRevertTo) + timestampToRevertTo = tmp; + } catch (NumberFormatException e2) { + // Not expected since the value is supposed to be a timestamp as per API + } + } + } + return timestampToRevertTo; + } + + /** + * Sets a system property, using the EnvironmentInfo service if possible. + */ + private void setSystemProperty(String key, String value) { + EnvironmentInfo env = ServiceHelper.getService(Activator.getContext(), EnvironmentInfo.class); + if (env != null) { + env.setProperty(key, value); + } else { + System.getProperties().put(key, value); + } + } + + IQueryResult getInstallableUnits(URI location, IQuery query, + IProgressMonitor monitor) { + IQueryable queryable = null; + if (location == null) { + queryable = metadataManager; + } else { + try { + queryable = metadataManager.loadRepository(location, monitor); + } catch (ProvisionException e) { + // repository is not available - just return empty result + } + } + if (queryable != null) + return queryable.query(query, monitor); + return Collector.emptyCollector(); + } + + private static void performHelpInfo(boolean documentation) { + CommandLineOption[] allOptions = new CommandLineOption[] { // + OPTION_METADATAREPOS, // + OPTION_ARTIFACTREPOS, // + OPTION_REPOSITORIES, // + OPTION_INSTALL_IU, // + OPTION_UNINSTALL_IU, // + OPTION_REVERT, // + OPTION_PURGEHISTORY, // + OPTION_DESTINATION, // + OPTION_LIST, // + OPTION_LIST_TAGS, // + OPTION_LIST_INSTALLED, // + OPTION_LIST_FORMAT, // + OPTION_PROFILE, // + OPTION_PROFILE_PROPS, // + OPTION_IU_PROFILE_PROPS, // + OPTION_FLAVOR, // + OPTION_BUNDLEPOOL, // + OPTION_P2_OS, // + OPTION_P2_WS, // + OPTION_P2_ARCH, // + OPTION_P2_NL, // + OPTION_ROAMING, // + OPTION_SHARED, // + OPTION_TAG, // + OPTION_VERIFY_ONLY, // + OPTION_DOWNLOAD_ONLY, // + OPTION_FOLLOW_REFERENCES, // + OPTION_VERBOSE_TRUST, // + OPTION_TRUST_SIGNED_CONTENT_ONLY, // + OPTION_TRUSTED_AUTHORITIES, // + OPTION_TRUSTED_PGP_KEYS, // + OPTION_TRUSTED_CERTIFCATES, // + OPTION_HELP, // + }; + + for (CommandLineOption allOption : allOptions) { + if (documentation) { + allOption.appendHelpDocumentation(System.out); + } else { + allOption.appendHelp(System.out); + } + } + } + + /* + * Set the roaming property on the given profile. + */ + private IStatus setRoaming(IProfile profile) { + ProfileChangeRequest request = new ProfileChangeRequest(profile); + request.setProfileProperty(IProfile.PROP_ROAMING, "true"); //$NON-NLS-1$ + ProvisioningContext context = new ProvisioningContext(targetAgent); + context.setMetadataRepositories(new URI[0]); + context.setArtifactRepositories(new URI[0]); + IProvisioningPlan result = planner.getProvisioningPlan(request, context, new NullProgressMonitor()); + return PlanExecutionHelper.executePlan(result, engine, context, new NullProgressMonitor()); + } + + @Override + public Object start(IApplicationContext context) throws Exception { + return run((String[]) context.getArguments().get("application.args")); //$NON-NLS-1$ + } + + private String toString(Map context) { + StringBuilder result = new StringBuilder(); + for (Map.Entry entry : context.entrySet()) { + if (result.length() > 0) + result.append(','); + result.append(entry.getKey()); + result.append('='); + result.append(entry.getValue()); + } + return result.toString(); + } + + private void updateRoamingProperties(IProfile profile) throws CoreException { + // if the user didn't specify a destination path on the command-line + // then we assume they are installing into the currently running + // instance and we don't have anything to update + if (destination == null) + return; + + // if the user didn't set a profile id on the command-line this is ok if they + // also didn't set the destination path. (handled in the case above) otherwise + // throw an error. + if (noProfileId) // && destination != null + throw new ProvisionException(Messages.Missing_profileid); + + // make sure that we are set to be roaming before we update the values + if (!Boolean.parseBoolean(profile.getProperty(IProfile.PROP_ROAMING))) + return; + + ProfileChangeRequest request = new ProfileChangeRequest(profile); + if (!destination.equals(new File(profile.getProperty(IProfile.PROP_INSTALL_FOLDER)))) + request.setProfileProperty(IProfile.PROP_INSTALL_FOLDER, destination.getAbsolutePath()); + + File cacheLocation = null; + if (bundlePool == null) + cacheLocation = sharedLocation == null ? destination.getAbsoluteFile() : sharedLocation.getAbsoluteFile(); + else + cacheLocation = bundlePool.getAbsoluteFile(); + if (!cacheLocation.equals(new File(profile.getProperty(IProfile.PROP_CACHE)))) + request.setProfileProperty(IProfile.PROP_CACHE, cacheLocation.getAbsolutePath()); + if (request.getProfileProperties().size() == 0) + return; + + // otherwise we have to make a change so set the profile to be non-roaming so + // the + // values don't get recalculated to the wrong thing if we are flushed from + // memory - we + // will set it back later (see bug 269468) + request.setProfileProperty(IProfile.PROP_ROAMING, "false"); //$NON-NLS-1$ + + ProvisioningContext context = new ProvisioningContext(targetAgent); + context.setMetadataRepositories(new URI[0]); + context.setArtifactRepositories(new URI[0]); + IProvisioningPlan result = planner.getProvisioningPlan(request, context, new NullProgressMonitor()); + IStatus status = PlanExecutionHelper.executePlan(result, engine, context, new NullProgressMonitor()); + if (!status.isOK()) + throw new CoreException(new MultiStatus(ID, ERROR, new IStatus[] { status }, + NLS.bind(Messages.Cant_change_roaming, profile.getProfileId()), null)); + } + + @Override + public void stop() { + IProvisioningEventBus eventBus = targetAgent.getService(IProvisioningEventBus.class); + if (eventBus != null) { + eventBus.removeListener(this); + } + } + + private void performListInstalledRoots() throws CoreException { + IProfile profile = initializeProfile(); + IQueryResult roots = profile.query(new UserVisibleRootQuery(), null); + Set sorted = new TreeSet<>(roots.toUnmodifiableSet()); + for (IInstallableUnit iu : sorted) + log.printOut(iu.getId() + '/' + iu.getVersion()); + } + + private void performPrintTags() throws CoreException { + IProfile profile = initializeProfile(); + IProfileRegistry registry = targetAgent.getService(IProfileRegistry.class); + Map tags = registry.getProfileStateProperties(profile.getProfileId(), IProfile.STATE_PROP_TAG); + // Sort the tags from the most recent to the oldest + List timeStamps = new ArrayList<>(tags.keySet()); + timeStamps.sort(Collections.reverseOrder()); + for (String timestamp : timeStamps) { + log.printOut(tags.get(timestamp)); + } + } + + private void printRequest(IProfileChangeRequest request) { + Collection toAdd = request.getAdditions(); + for (IInstallableUnit added : toAdd) { + log.printOut(NLS.bind(Messages.Installing, added.getId(), added.getVersion())); + } + + Collection toRemove = request.getRemovals(); + for (IInstallableUnit removed : toRemove) { + log.printOut(NLS.bind(Messages.Uninstalling, removed.getId(), removed.getVersion())); + } + } + + private void printError(IStatus status, int level) { + String prefix = emptyString(level); + + String msg = status.getMessage(); + log.printErr(prefix + msg); + + Throwable cause = status.getException(); + if (cause != null) { + // TODO This is very unreliable. It assumes that if the IStatus message is the + // same as the IStatus cause + // message the cause exception has no more data to offer. Better to just print + // it. + boolean isCauseMsg = msg.equals(cause.getMessage()) || msg.equals(cause.toString()); + if (!isCauseMsg) { + log.printErr(prefix + "Caused by: "); //$NON-NLS-1$ + printError(cause, level); + } + } + + for (IStatus child : status.getChildren()) { + printError(child, level + 1); + } + } + + private void printError(Throwable trace, int level) { + if (trace instanceof CoreException) { + printError(((CoreException) trace).getStatus(), level); + } else { + String prefix = emptyString(level); + + log.printErr(prefix + trace.toString()); + + Throwable cause = trace.getCause(); + if (cause != null) { + log.printErr(prefix + "Caused by: "); //$NON-NLS-1$ + printError(cause, level); + } + } + } + + private static String emptyString(int size) { + return IntStream.range(0, size).mapToObj(i -> "\t").collect(Collectors.joining()); //$NON-NLS-1$ + } + + private boolean canInstallInDestination() throws CoreException { + // When we are provisioning what we are running. We can always install. + if (targetAgentIsSelfAndUp) + return true; + if (destination == null) + missingArgument("destination"); //$NON-NLS-1$ + return canWrite(destination); + } + + private static boolean canWrite(File installDir) { + installDir.mkdirs(); // Force create the folders because otherwise the call to canWrite fails on Mac + return installDir.isDirectory() && Files.isWritable(installDir.toPath()); + } + +} diff --git a/tycho-core/src/main/java/org/eclipse/tycho/p2tools/copiedfromp2/ILog.java b/tycho-core/src/main/java/org/eclipse/tycho/p2tools/copiedfromp2/ILog.java new file mode 100644 index 0000000000..1780fe1265 --- /dev/null +++ b/tycho-core/src/main/java/org/eclipse/tycho/p2tools/copiedfromp2/ILog.java @@ -0,0 +1,46 @@ +/******************************************************************************* + * Copyright (c) 2011 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 + *******************************************************************************/ +package org.eclipse.tycho.p2tools.copiedfromp2; + +import org.eclipse.core.runtime.IStatus; + +/** + * Manages all outputs of the director application: logs to a file as well as the standard streams + *

+ * This indirection is needed in order to manage the outputs when the director is called from ant, + * where the standard streams are handled differently. + */ +public interface ILog { + /** + * Send status to the standard log + */ + void log(IStatus status); + + /** + * Print status on stdout or stderr. + * + * By default calls {@link #log} + */ + void printOut(String line); + + /** + * Send line to stdout + * + * By default does nothing + * + * @param line + * Message line + */ + void printErr(String line); +} diff --git a/tycho-core/src/main/java/org/eclipse/tycho/p2tools/copiedfromp2/PhaseSetFactory.java b/tycho-core/src/main/java/org/eclipse/tycho/p2tools/copiedfromp2/PhaseSetFactory.java new file mode 100644 index 0000000000..f90a7d4f7f --- /dev/null +++ b/tycho-core/src/main/java/org/eclipse/tycho/p2tools/copiedfromp2/PhaseSetFactory.java @@ -0,0 +1,142 @@ +/******************************************************************************* + * Copyright (c) 2007, 2017 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 + *******************************************************************************/ +package org.eclipse.tycho.p2tools.copiedfromp2; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.eclipse.equinox.internal.p2.engine.Phase; +import org.eclipse.equinox.internal.p2.engine.PhaseSet; +import org.eclipse.equinox.internal.p2.engine.SizingPhaseSet; +import org.eclipse.equinox.internal.p2.engine.phases.CheckTrust; +import org.eclipse.equinox.internal.p2.engine.phases.Collect; +import org.eclipse.equinox.internal.p2.engine.phases.Configure; +import org.eclipse.equinox.internal.p2.engine.phases.Install; +import org.eclipse.equinox.internal.p2.engine.phases.Property; +import org.eclipse.equinox.internal.p2.engine.phases.Unconfigure; +import org.eclipse.equinox.internal.p2.engine.phases.Uninstall; +import org.eclipse.equinox.p2.engine.IPhaseSet; +import org.eclipse.equinox.p2.engine.ISizingPhaseSet; + +/** + * @since 2.0 + * @noextend This class is not intended to be subclassed by clients. + */ +public class PhaseSetFactory { + + private static final boolean forcedUninstall = Boolean + .parseBoolean(System.getProperty("org.eclipse.equinox.p2.engine.forcedUninstall")); //$NON-NLS-1$ + + /** + * A phase id (value "checkTrust") describing the certificate trust check phase. This phase + * examines the code signing certificates of the artifacts being installed to ensure they are + * signed and trusted by the running system. + */ + public static final String PHASE_CHECK_TRUST = "checkTrust"; //$NON-NLS-1$ + /** + * A phase id (value "collect") describing the collect phase. This phase gathers all the + * artifacts to be installed, typically by copying them from some repository into a suitable + * local location for the application being installed. + */ + public static final String PHASE_COLLECT = "collect"; //$NON-NLS-1$ + /** + * A phase id (value "configure") describing the configuration phase. This phase writes + * configuration data related to the software being provisioned. Until configuration occurs the + * end user of the software will be have access to the installed functionality. + */ + public static final String PHASE_CONFIGURE = "configure"; //$NON-NLS-1$ + /** + * A phase id (value "install") describing the install phase. This phase performs any necessary + * transformations on the downloaded artifacts to put them in the correct shape for the running + * application, such as decompressing or moving content, setting file permissions, etc). + */ + public static final String PHASE_INSTALL = "install"; //$NON-NLS-1$ + /** + * A phase id (value "property") describing the property modification phase. This phase performs + * changes to profile properties. + */ + public static final String PHASE_PROPERTY = "property"; //$NON-NLS-1$ + /** + * A phase id (value "unconfigure") describing the unconfigure phase. This phase removes + * configuration data related to the software being removed. This phase is the inverse of the + * changes performed in the configure phase. + */ + public static final String PHASE_UNCONFIGURE = "unconfigure"; //$NON-NLS-1$ + /** + * A phase id (value "uninstall") describing the uninstall phase. This phase removes artifacts + * from the system being provisioned that are no longer required in the new profile. + */ + public static final String PHASE_UNINSTALL = "uninstall"; //$NON-NLS-1$ + + private static final List ALL_PHASES_LIST = Arrays.asList(new String[] { PHASE_COLLECT, PHASE_UNCONFIGURE, + PHASE_UNINSTALL, PHASE_PROPERTY, PHASE_CHECK_TRUST, PHASE_INSTALL, PHASE_CONFIGURE }); + + /** + * Creates a default phase set that covers all the provisioning operations. Phases can be + * specified for exclusion. + * + * @param exclude + * - A set of bit options that specify the phases to exclude. See + * {@link PhaseSetFactory} for possible options + * @return the {@link PhaseSet} + */ + public static final IPhaseSet createDefaultPhaseSetExcluding(String[] exclude) { + if (exclude == null || exclude.length == 0) + return createDefaultPhaseSet(); + List excludeList = Arrays.asList(exclude); + List includeList = new ArrayList<>(ALL_PHASES_LIST); + includeList.removeAll(excludeList); + return createPhaseSetIncluding(includeList.toArray(new String[includeList.size()])); + } + + /** + * Creates a default phase set that covers all the provisioning operations. Phases can be + * specified for inclusion. + * + * @param include + * - A set of bit options that specify the phases to include. See + * {@link PhaseSetFactory} for possible options + * @return the {@link PhaseSet} + */ + public static final IPhaseSet createPhaseSetIncluding(String[] include) { + if (include == null || include.length == 0) + return new PhaseSet(new Phase[0]); + List includeList = Arrays.asList(include); + ArrayList phases = new ArrayList<>(); + if (includeList.contains(PHASE_COLLECT)) + phases.add(new Collect(100)); + if (includeList.contains(PHASE_CHECK_TRUST)) + phases.add(new CheckTrust(10)); + if (includeList.contains(PHASE_UNCONFIGURE)) + phases.add(new Unconfigure(10, forcedUninstall)); + if (includeList.contains(PHASE_UNINSTALL)) + phases.add(new Uninstall(50, forcedUninstall)); + if (includeList.contains(PHASE_PROPERTY)) + phases.add(new Property(1)); + if (includeList.contains(PHASE_INSTALL)) + phases.add(new Install(50)); + if (includeList.contains(PHASE_CONFIGURE)) + phases.add(new Configure(10)); + return new PhaseSet(phases.toArray(new Phase[phases.size()])); + } + + public static IPhaseSet createDefaultPhaseSet() { + return createPhaseSetIncluding(ALL_PHASES_LIST.toArray(new String[ALL_PHASES_LIST.size()])); + } + + public static ISizingPhaseSet createSizingPhaseSet() { + return new SizingPhaseSet(); + } +} diff --git a/tycho-extras/target-platform-validation-plugin/src/main/java/org/eclipse/tycho/extras/tpvalidator/TPValidationMojo.java b/tycho-extras/target-platform-validation-plugin/src/main/java/org/eclipse/tycho/extras/tpvalidator/TPValidationMojo.java index 009f10e3e5..21b6baff1a 100644 --- a/tycho-extras/target-platform-validation-plugin/src/main/java/org/eclipse/tycho/extras/tpvalidator/TPValidationMojo.java +++ b/tycho-extras/target-platform-validation-plugin/src/main/java/org/eclipse/tycho/extras/tpvalidator/TPValidationMojo.java @@ -187,7 +187,7 @@ private void validateTarget(File targetFile) throws TPError { // create resolver this.logger.info("Validating " + targetFile); RepositoryReferences ref = new RepositoryReferences(); - DirectorRuntime.Command directorCommand = director.newInstallCommand(); + DirectorRuntime.Command directorCommand = director.newInstallCommand(project.getName()); TargetDefinitionFile targetDefinition = TargetDefinitionFile.read(targetFile); TargetPlatformConfigurationStub tpConfiguration = new TargetPlatformConfigurationStub(); diff --git a/tycho-p2-director-plugin/src/main/java/org/eclipse/tycho/plugins/p2/director/DirectorMojo.java b/tycho-p2-director-plugin/src/main/java/org/eclipse/tycho/plugins/p2/director/DirectorMojo.java index 480fff91e7..341bfaf547 100644 --- a/tycho-p2-director-plugin/src/main/java/org/eclipse/tycho/plugins/p2/director/DirectorMojo.java +++ b/tycho-p2-director-plugin/src/main/java/org/eclipse/tycho/plugins/p2/director/DirectorMojo.java @@ -33,16 +33,20 @@ import org.apache.maven.plugins.annotations.Mojo; import org.apache.maven.plugins.annotations.Parameter; import org.apache.maven.project.MavenProject; +import org.eclipse.core.runtime.CoreException; import org.eclipse.equinox.app.IApplication; import org.eclipse.equinox.p2.core.IProvisioningAgent; import org.eclipse.equinox.p2.core.IProvisioningAgentProvider; import org.eclipse.equinox.p2.core.ProvisionException; import org.eclipse.tycho.TargetEnvironment; import org.eclipse.tycho.TychoConstants; +import org.eclipse.tycho.core.shared.StatusTool; import org.eclipse.tycho.p2.CommandLineArguments; import org.eclipse.tycho.p2.resolver.BundlePublisher; import org.eclipse.tycho.p2.tools.director.shared.DirectorRuntime; -import org.eclipse.tycho.p2tools.TychoDirectorApplication; +import org.eclipse.tycho.p2tools.MavenDirectorLog; +import org.eclipse.tycho.p2tools.copiedfromp2.DirectorApplication; +import org.eclipse.tycho.p2tools.copiedfromp2.PhaseSetFactory; /** * Allows to run the products = getProductConfig().getProducts(); - if (products.isEmpty()) { - getLog().info("No product definitions found, nothing to do"); - } - DirectorRuntime director = getDirectorRuntime(); - RepositoryReferences sources = getSourceRepositories(); - for (Product product : products) { - for (TargetEnvironment env : getEnvironments()) { - DirectorRuntime.Command command = director.newInstallCommand(); - - File destination = getProductMaterializeDirectory(product, env); - String rootFolder = product.getRootFolder(env.getOs()); - if (rootFolder != null && !rootFolder.isEmpty()) { - destination = new File(destination, rootFolder); - } - - command.setBundlePool(getProductBundlePoolDirectory(product)); - command.addMetadataSources(sources.getMetadataRepositories()); - command.addArtifactSources(sources.getArtifactRepositories()); - command.addUnitToInstall(product.getId()); - for (DependencySeed seed : product.getAdditionalInstallationSeeds()) { - command.addUnitToInstall(seed); + List products = getProductConfig().getProducts(); + if (products.isEmpty()) { + getLog().info("No product definitions found, nothing to do"); + return; + } + DirectorRuntime director = getDirectorRuntime(); + RepositoryReferences sources = getSourceRepositories(); + if (parallel) { + ExecutorService executorService = Executors.newWorkStealingPool(); + ExecutorCompletionService service = new ExecutorCompletionService<>(executorService); + try { + int tasks = 0; + for (Product product : products) { + for (TargetEnvironment env : getEnvironments()) { + service.submit(() -> { + buildProduct(director, sources, product, env); + return null; + }); + tasks++; } - command.setDestination(destination); - command.setProfileName(ProfileName.getNameForEnvironment(env, profileNames, profile)); - command.setEnvironment(env); - command.setInstallFeatures(installFeatures); - command.setProfileProperties(profileProperties); - getLog().info("Installing product " + product.getId() + " for environment " + env + " to " - + destination.getAbsolutePath()); - + } + for (int i = 0; i < tasks; i++) { try { - command.execute(); - } catch (DirectorCommandException e) { - throw new MojoFailureException( - "Installation of product " + product.getId() + " for environment " + env + " failed", - e); + service.take().get(); + } catch (InterruptedException e) { + return; + } catch (ExecutionException e) { + Throwable cause = e.getCause(); + if (cause instanceof RuntimeException rte) { + throw rte; + } + if (cause instanceof MojoFailureException mfe) { + throw mfe; + } + if (cause instanceof MojoExecutionException mee) { + throw mee; + } + throw new MojoFailureException("internal error", e); } } + } finally { + executorService.shutdown(); } + } else { + //all one by one... + synchronized (LOCK) { + for (Product product : products) { + for (TargetEnvironment env : getEnvironments()) { + buildProduct(director, sources, product, env); + } + } + } + } + } + + private void buildProduct(DirectorRuntime director, RepositoryReferences sources, Product product, + TargetEnvironment env) throws MojoFailureException { + DirectorRuntime.Command command = director + .newInstallCommand(execution.getExecutionId() + " - " + product.getId() + " - " + env); + command.setPhaseSet( + PhaseSetFactory.createDefaultPhaseSetExcluding(new String[] { PhaseSetFactory.PHASE_CHECK_TRUST })); + File destination = getProductMaterializeDirectory(product, env); + String rootFolder = product.getRootFolder(env.getOs()); + if (rootFolder != null && !rootFolder.isEmpty()) { + destination = new File(destination, rootFolder); + } + + command.setBundlePool(getProductBundlePoolDirectory(product)); + command.addMetadataSources(sources.getMetadataRepositories()); + command.addArtifactSources(sources.getArtifactRepositories()); + command.addUnitToInstall(product.getId()); + for (DependencySeed seed : product.getAdditionalInstallationSeeds()) { + command.addUnitToInstall(seed); + } + command.setDestination(destination); + command.setProfileName(ProfileName.getNameForEnvironment(env, profileNames, profile)); + command.setEnvironment(env); + command.setInstallFeatures(installFeatures); + command.setProfileProperties(profileProperties); + getLog().info("Installing product " + product.getId() + " for environment " + env + " to " + + destination.getAbsolutePath()); + try { + command.execute(); + } catch (DirectorCommandException e) { + throw new MojoFailureException( + "Installation of product " + product.getId() + " for environment " + env + " failed", e); } } diff --git a/tycho-p2-director-plugin/src/main/java/org/eclipse/tycho/plugins/p2/director/ProductArchiverMojo.java b/tycho-p2-director-plugin/src/main/java/org/eclipse/tycho/plugins/p2/director/ProductArchiverMojo.java index 1aafb940bb..b5b5e5af9b 100644 --- a/tycho-p2-director-plugin/src/main/java/org/eclipse/tycho/plugins/p2/director/ProductArchiverMojo.java +++ b/tycho-p2-director-plugin/src/main/java/org/eclipse/tycho/plugins/p2/director/ProductArchiverMojo.java @@ -17,6 +17,10 @@ import java.io.File; import java.io.IOException; import java.util.Map; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorCompletionService; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.MojoFailureException; @@ -115,6 +119,12 @@ public final class ProductArchiverMojo extends AbstractProductMojo { @Parameter private Map formats; + /** + * Controls if products are allowed to be build in parallel + */ + @Parameter + private boolean parallel; + @Component private MavenProjectHelper helper; @@ -126,14 +136,60 @@ public void execute() throws MojoExecutionException, MojoFailureException { + "Configure the attachId or select a subset of products. Current configuration: " + config.getProducts()); } - - for (Product product : config.getProducts()) { - File bundlePool = getProductBundlePoolDirectory(product); - if (bundlePool != null) { - materialize(product, null); - } else { - for (TargetEnvironment env : getEnvironments()) { - materialize(product, env); + if (parallel) { + ExecutorService executorService = Executors.newWorkStealingPool(); + ExecutorCompletionService service = new ExecutorCompletionService<>(executorService); + try { + int tasks = 0; + for (Product product : config.getProducts()) { + File bundlePool = getProductBundlePoolDirectory(product); + if (bundlePool != null) { + service.submit(() -> { + materialize(product, null); + return null; + }); + tasks++; + } else { + for (TargetEnvironment env : getEnvironments()) { + service.submit(() -> { + materialize(product, env); + return null; + }); + tasks++; + } + } + } + for (int i = 0; i < tasks; i++) { + try { + service.take().get(); + } catch (InterruptedException e) { + return; + } catch (ExecutionException e) { + Throwable cause = e.getCause(); + if (cause instanceof RuntimeException rte) { + throw rte; + } + if (cause instanceof MojoFailureException mfe) { + throw mfe; + } + if (cause instanceof MojoExecutionException mee) { + throw mee; + } + throw new MojoFailureException("internal error", e); + } + } + } finally { + executorService.shutdown(); + } + } else { + for (Product product : config.getProducts()) { + File bundlePool = getProductBundlePoolDirectory(product); + if (bundlePool != null) { + materialize(product, null); + } else { + for (TargetEnvironment env : getEnvironments()) { + materialize(product, env); + } } } } diff --git a/tycho-p2-director-plugin/src/main/java/org/eclipse/tycho/plugins/p2/director/runtime/StandaloneDirectorRuntime.java b/tycho-p2-director-plugin/src/main/java/org/eclipse/tycho/plugins/p2/director/runtime/StandaloneDirectorRuntime.java index 4e60c12902..68ef8507c7 100644 --- a/tycho-p2-director-plugin/src/main/java/org/eclipse/tycho/plugins/p2/director/runtime/StandaloneDirectorRuntime.java +++ b/tycho-p2-director-plugin/src/main/java/org/eclipse/tycho/plugins/p2/director/runtime/StandaloneDirectorRuntime.java @@ -45,7 +45,7 @@ public class StandaloneDirectorRuntime implements DirectorRuntime { } @Override - public Command newInstallCommand() { + public Command newInstallCommand(String name) { return new AbstractDirectorApplicationCommand() { @Override diff --git a/tycho-p2-director-plugin/src/main/java/org/eclipse/tycho/plugins/p2/director/runtime/StandaloneDirectorRuntimeFactory.java b/tycho-p2-director-plugin/src/main/java/org/eclipse/tycho/plugins/p2/director/runtime/StandaloneDirectorRuntimeFactory.java index 53c7384d1f..98f9a7c279 100644 --- a/tycho-p2-director-plugin/src/main/java/org/eclipse/tycho/plugins/p2/director/runtime/StandaloneDirectorRuntimeFactory.java +++ b/tycho-p2-director-plugin/src/main/java/org/eclipse/tycho/plugins/p2/director/runtime/StandaloneDirectorRuntimeFactory.java @@ -56,7 +56,7 @@ private void installStandaloneDirector(File installLocation, ArtifactRepository // ... install from a zipped p2 repository obtained via Maven ... URI directorRuntimeRepo = URI .create("jar:" + getDirectorRepositoryZip(localMavenRepository).toURI() + "!/"); - DirectorRuntime.Command command = bootstrapDirector.newInstallCommand(); + DirectorRuntime.Command command = bootstrapDirector.newInstallCommand("standalone"); command.addMetadataSources(Arrays.asList(directorRuntimeRepo)); command.addArtifactSources(Arrays.asList(directorRuntimeRepo)); diff --git a/tycho-surefire/tycho-surefire-plugin/src/main/java/org/eclipse/tycho/surefire/provisioning/ProvisionedInstallationBuilder.java b/tycho-surefire/tycho-surefire-plugin/src/main/java/org/eclipse/tycho/surefire/provisioning/ProvisionedInstallationBuilder.java index 448556f714..88eaee8b97 100644 --- a/tycho-surefire/tycho-surefire-plugin/src/main/java/org/eclipse/tycho/surefire/provisioning/ProvisionedInstallationBuilder.java +++ b/tycho-surefire/tycho-surefire-plugin/src/main/java/org/eclipse/tycho/surefire/provisioning/ProvisionedInstallationBuilder.java @@ -120,7 +120,7 @@ private void publishPlainBundleJars() throws Exception { } private void executeDirector(TargetEnvironment env) throws MojoFailureException { - DirectorRuntime.Command command = directorRuntime.newInstallCommand(); + DirectorRuntime.Command command = directorRuntime.newInstallCommand(String.valueOf(env)); command.addMetadataSources(metadataRepos); command.addArtifactSources(artifactRepos); for (String iu : ius) {