From 9f1a7ea926047a49aa5eff8c0f1664290f238697 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoph=20L=C3=A4ubrich?= Date: Thu, 25 Jan 2024 17:01:25 +0100 Subject: [PATCH] Skip CHECK_TRUST phase and allow parallel execution of product assembly Currently the DirectorApplication effectively accepts all content but executing the CHECK_TRUST already takes considerable amount of time, also each product is assembled one by one even though they operate on independent output folders and files. This now skips the CHECK_TRUST phase and adds a new parameter that can be set to enable the parallel execution of assembly and archiving different product variants. --- RELEASE_NOTES.md | 1 + .../AbstractDirectorApplicationCommand.java | 15 + .../shared/DirectorCommandException.java | 4 + .../director/shared/DirectorRuntime.java | 5 +- .../p2tools/DirectorApplicationWrapper.java | 79 +- .../tycho/p2tools/MavenDirectorLog.java | 59 + .../p2tools/TychoDirectorApplication.java | 32 - .../copiedfromp2/DirectorApplication.java | 3475 ++++++++--------- .../tycho/p2tools/copiedfromp2/ILog.java | 61 +- .../p2tools/copiedfromp2/PhaseSetFactory.java | 17 +- .../extras/tpvalidator/TPValidationMojo.java | 2 +- .../plugins/p2/director/DirectorMojo.java | 22 +- .../p2/director/MaterializeProductsMojo.java | 132 +- .../p2/director/ProductArchiverMojo.java | 72 +- .../runtime/StandaloneDirectorRuntime.java | 2 +- .../StandaloneDirectorRuntimeFactory.java | 2 +- .../ProvisionedInstallationBuilder.java | 2 +- 17 files changed, 2098 insertions(+), 1884 deletions(-) create mode 100644 tycho-core/src/main/java/org/eclipse/tycho/p2tools/MavenDirectorLog.java delete mode 100644 tycho-core/src/main/java/org/eclipse/tycho/p2tools/TychoDirectorApplication.java diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 2cd5f56f75..b542b002f1 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -6,6 +6,7 @@ This page describes the noteworthy improvements provided by each release of Ecli ### backports: +- support for parallel execution of product assembly / archiving - new `repo-to-runnable` mojo - support for embedded target locations - using javac as the compiler for Tycho 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 index 17e3400b36..7e0953a8f0 100644 --- 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 @@ -18,12 +18,21 @@ * SAP AG - list formatting (bug 423538) * Todor Boev - Software AG *******************************************************************************/ -package org.eclipse.equinox.internal.p2.director.app; +package org.eclipse.tycho.p2tools.copiedfromp2; -import static org.eclipse.core.runtime.IStatus.*; +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.*; +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; @@ -32,18 +41,45 @@ import java.security.NoSuchAlgorithmException; import java.security.cert.Certificate; import java.security.cert.CertificateEncodingException; -import java.util.*; +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.*; +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; @@ -51,15 +87,32 @@ 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.*; -import org.eclipse.equinox.p2.engine.*; +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.*; +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.*; +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; @@ -68,1738 +121,1678 @@ import org.eclipse.equinox.p2.repository.spi.PGPPublicKeyService; import org.eclipse.osgi.service.environment.EnvironmentInfo; import org.eclipse.osgi.util.NLS; -import org.osgi.framework.*; 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$ - - public static final String LINE_SEPARATOR = System.lineSeparator(); - - 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 ILog log = new DefaultLog(); - - private IProvisioningAgent targetAgent; - private boolean targetAgentIsSelfAndUp; - private boolean noArtifactRepositorySpecified; - private AvoidTrustPromptService trustService; - - 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 { - BundleContext context = Activator.getContext(); - final String currentAgentFiler = String.format("(%s=true)", IProvisioningAgent.SERVICE_CURRENT); //$NON-NLS-1$ - try { - Collection> refs = context - .getServiceReferences(IProvisioningAgent.class, currentAgentFiler); - for (ServiceReference serviceReference : refs) { - IProvisioningAgent service = context.getService(serviceReference); - if (service != null) { - context.ungetService(serviceReference); - return service; - } - } - } catch (InvalidSyntaxException e) { - // Can't happen the filter never changes - throw new CoreException(Status.error("Internal error", e)); //$NON-NLS-1$ - } - throw new CoreException(Status.error("Can't fetch the default agent")); //$NON-NLS-1$ - } - - /** - * 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 { - BundleContext context = Activator.getContext(); - ServiceReference agentProviderRef = context - .getServiceReference(IProvisioningAgentProvider.class); - IProvisioningAgentProvider provider = context.getService(agentProviderRef); - - IProvisioningAgent agent = provider.createAgent(p2DataArea); - agent.registerService(IProvisioningAgent.INSTALLER_AGENT, provider.createAgent(null)); - context.ungetService(agentProviderRef); - 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); - System.out.println(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; - if (!downloadOnly) - operationStatus = PlanExecutionHelper.executePlan(result, engine, context, new NullProgressMonitor()); - else - operationStatus = PlanExecutionHelper.executePlan(result, engine, - PhaseSetFactory.createPhaseSetIncluding( - new String[] { PhaseSetFactory.PHASE_COLLECT, PhaseSetFactory.PHASE_CHECK_TRUST }), - 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) { - 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; - } catch (CoreException e) { - IStatus error = e.getStatus(); - - log.printOut(Messages.Operation_failed); - printError(error, 0); - - log.log(error); - - // If the operation was canceled, and we aren't verbose printing the trust - // checking information, then print it now so that the user has details about - // which authorities, certificates, keys, and/or unsigned content is involved. - if (error.getSeverity() == IStatus.CANCEL && !verboseTrust) { - trustService.dump(); - } - - // set empty exit data to suppress error dialog from launcher - setSystemProperty("eclipse.exitdata", ""); //$NON-NLS-1$ //$NON-NLS-2$ - return EXIT_ERROR; - } finally { - log.close(); - 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); - } - - if (log != null) { - log.close(); - } - } - - public void setLog(ILog log) { - this.log = log; - } - - 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) - System.out.println(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) { - System.out.println(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()); - } - -// @SuppressWarnings("nls") -// public static void main(String[] args) { -// System.out.println( -// ""); -// System.out.println("
"); -// System.out.println("
"); -// System.out.println("-application org.eclipse.equinox.p2.director"); -// System.out.println("
"); -// System.out.println("
"); -// System.out.println("The application ID."); -// System.out.println("
"); -// performHelpInfo(true); -// System.out.println("
"); -// } + 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 index c434681c7b..1780fe1265 100644 --- 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 @@ -11,53 +11,36 @@ * Contributors: * IBM Corporation - initial API and implementation *******************************************************************************/ -package org.eclipse.equinox.internal.p2.director.app; +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. + * 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); + /** + * Send status to the standard log + */ + void log(IStatus status); - /** - * - * @deprecated Use {@link ILog#printOut()} or {@link ILog#printErr()} - */ - @Deprecated - default void log(String message) { - printOut(message); - } + /** + * Print status on stdout or stderr. + * + * By default calls {@link #log} + */ + void printOut(String line); - /** - * Notify that logging is completed & cleanup resources - */ - void close(); - - /** - * Print status on stdout or stderr. - * - * By default calls {@link #log} - */ - default void printOut(String line) { - System.out.println(line); - } - - /** - * Send line to stdout - * - * By default does nothing - * - * @param line Message line - */ - default void printErr(String line) { - System.err.println(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 index d0edaeffc4..f90a7d4f7f 100644 --- 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 @@ -17,7 +17,6 @@ import java.util.Arrays; import java.util.List; -import org.eclipse.equinox.internal.p2.engine.EngineActivator; import org.eclipse.equinox.internal.p2.engine.Phase; import org.eclipse.equinox.internal.p2.engine.PhaseSet; import org.eclipse.equinox.internal.p2.engine.SizingPhaseSet; @@ -38,48 +37,48 @@ public class PhaseSetFactory { private static final boolean forcedUninstall = Boolean - .parseBoolean(EngineActivator.getContext().getProperty("org.eclipse.equinox.p2.engine.forcedUninstall")); //$NON-NLS-1$ + .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 String PHASE_CHECK_TRUST = "checkTrust"; //$NON-NLS-1$ + 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 String PHASE_COLLECT = "collect"; //$NON-NLS-1$ + 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 String PHASE_CONFIGURE = "configure"; //$NON-NLS-1$ + 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 String PHASE_INSTALL = "install"; //$NON-NLS-1$ + 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 String PHASE_PROPERTY = "property"; //$NON-NLS-1$ + 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 String PHASE_UNCONFIGURE = "unconfigure"; //$NON-NLS-1$ + 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 String PHASE_UNINSTALL = "uninstall"; //$NON-NLS-1$ + 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 }); 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 1104f5b457..4b688a8dd9 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) {