This is an automated email from the ASF dual-hosted git repository.

martinkanters pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/maven.git


The following commit(s) were added to refs/heads/master by this push:
     new 8defd16  [MNG-7051] Optionally skip non-existing profiles and break on 
missing required profiles.
8defd16 is described below

commit 8defd169651db30fe0ac3ad3f318e471f0241d02
Author: Maarten Mulders <mthmuld...@apache.org>
AuthorDate: Wed Dec 9 14:43:15 2020 +0100

    [MNG-7051] Optionally skip non-existing profiles and break on missing 
required profiles.
---
 .../main/java/org/apache/maven/DefaultMaven.java   | 142 ++++++++++++---
 .../org/apache/maven/MissingProfilesException.java |  33 ++++
 .../execution/DefaultMavenExecutionRequest.java    |  36 ++--
 .../maven/execution/MavenExecutionRequest.java     |  40 ++++
 .../apache/maven/execution/ProfileActivation.java  | 201 +++++++++++++++++++++
 .../main/java/org/apache/maven/cli/MavenCli.java   |  72 +++-----
 .../java/org/apache/maven/cli/MavenCliTest.java    |  37 ++--
 .../apache/maven/model/building/ModelProblem.java  |   3 +-
 .../model/validation/DefaultModelValidator.java    |   2 +
 .../validation/DefaultModelValidatorTest.java      |  10 +
 .../poms/validation/invalid-profile-id.xml         |  32 ++++
 11 files changed, 496 insertions(+), 112 deletions(-)

diff --git a/maven-core/src/main/java/org/apache/maven/DefaultMaven.java 
b/maven-core/src/main/java/org/apache/maven/DefaultMaven.java
index 2e1a684..7b758f5 100644
--- a/maven-core/src/main/java/org/apache/maven/DefaultMaven.java
+++ b/maven-core/src/main/java/org/apache/maven/DefaultMaven.java
@@ -19,22 +19,6 @@ package org.apache.maven;
  * under the License.
  */
 
-import java.io.File;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Date;
-import java.util.HashSet;
-import java.util.LinkedHashMap;
-import java.util.LinkedHashSet;
-import java.util.List;
-import java.util.Map;
-
-import javax.inject.Inject;
-import javax.inject.Named;
-import javax.inject.Singleton;
-
 import org.apache.maven.artifact.ArtifactUtils;
 import org.apache.maven.execution.BuildResumptionAnalyzer;
 import org.apache.maven.execution.BuildResumptionDataRepository;
@@ -44,15 +28,19 @@ import org.apache.maven.execution.ExecutionEvent;
 import org.apache.maven.execution.MavenExecutionRequest;
 import org.apache.maven.execution.MavenExecutionResult;
 import org.apache.maven.execution.MavenSession;
+import org.apache.maven.execution.ProfileActivation;
 import org.apache.maven.execution.ProjectDependencyGraph;
 import org.apache.maven.graph.GraphBuilder;
 import org.apache.maven.internal.aether.DefaultRepositorySystemSessionFactory;
 import org.apache.maven.lifecycle.LifecycleExecutionException;
 import org.apache.maven.lifecycle.internal.ExecutionEventCatapult;
 import org.apache.maven.lifecycle.internal.LifecycleStarter;
+import org.apache.maven.model.Model;
 import org.apache.maven.model.Prerequisites;
+import org.apache.maven.model.Profile;
 import org.apache.maven.model.building.ModelProblem;
 import org.apache.maven.model.building.Result;
+import org.apache.maven.model.superpom.SuperPomProvider;
 import org.apache.maven.plugin.LegacySupport;
 import org.apache.maven.project.MavenProject;
 import org.apache.maven.project.ProjectBuilder;
@@ -66,6 +54,26 @@ import org.eclipse.aether.RepositorySystemSession;
 import org.eclipse.aether.repository.WorkspaceReader;
 import org.eclipse.aether.util.repository.ChainedWorkspaceReader;
 
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.inject.Singleton;
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.stream.Stream;
+
+import static java.util.stream.Collectors.toSet;
+
 /**
  * @author Jason van Zyl
  */
@@ -109,6 +117,9 @@ public class DefaultMaven
     @Inject
     private BuildResumptionDataRepository buildResumptionDataRepository;
 
+    @Inject
+    private SuperPomProvider superPomProvider;
+
     @Override
     public MavenExecutionResult execute( MavenExecutionRequest request )
     {
@@ -316,11 +327,17 @@ public class DefaultMaven
 
             validatePrerequisitesForNonMavenPluginProjects( 
session.getProjects() );
 
-            validateActivatedProfiles( session.getProjects(), 
request.getActiveProfiles() );
+            validateRequiredProfiles( session, request.getProfileActivation() 
);
+            if ( session.getResult().hasExceptions() )
+            {
+                return result;
+            }
+
+            validateOptionalProfiles( session, request.getProfileActivation() 
);
 
             lifecycleStarter.execute( session );
 
-            validateActivatedProfiles( session.getProjects(), 
request.getActiveProfiles() );
+            validateOptionalProfiles( session, request.getProfileActivation() 
);
 
             if ( session.getResult().hasExceptions() )
             {
@@ -493,22 +510,93 @@ public class DefaultMaven
         }
     }
 
-    private void validateActivatedProfiles( List<MavenProject> projects, 
List<String> activeProfileIds )
+    /**
+     * Get all profiles that are detected in the projects, any parent of the 
projects, or the settings.
+     * @param session The Maven session
+     * @return A {@link Set} of profile identifiers, never {@code null}.
+     */
+    private Set<String> getAllProfiles( MavenSession session )
     {
-        Collection<String> notActivatedProfileIds = new LinkedHashSet<>( 
activeProfileIds );
-
-        for ( MavenProject project : projects )
+        final Model superPomModel = superPomProvider.getSuperModel( "4.0.0" );
+        final Set<MavenProject> projectsIncludingParents = new HashSet<>();
+        for ( MavenProject project : session.getProjects() )
         {
-            for ( List<String> profileIds : 
project.getInjectedProfileIds().values() )
+            boolean isAdded = projectsIncludingParents.add( project );
+            MavenProject parent = project.getParent();
+            while ( isAdded && parent != null )
             {
-                notActivatedProfileIds.removeAll( profileIds );
+                isAdded = projectsIncludingParents.add( parent );
+                parent = parent.getParent();
             }
         }
 
-        for ( String notActivatedProfileId : notActivatedProfileIds )
+        final Stream<String> projectProfiles = 
projectsIncludingParents.stream()
+                .map( MavenProject::getModel )
+                .map( Model::getProfiles )
+                .flatMap( Collection::stream )
+                .map( Profile::getId );
+        final Stream<String> settingsProfiles = 
session.getSettings().getProfiles().stream()
+                .map( org.apache.maven.settings.Profile::getId );
+        final Stream<String> superPomProfiles = 
superPomModel.getProfiles().stream()
+                .map( Profile::getId );
+
+        return Stream.of( projectProfiles, settingsProfiles, superPomProfiles )
+                .flatMap( Function.identity() )
+                .collect( toSet() );
+    }
+
+    /**
+     * Check whether the required profiles were found in any of the projects 
we're building or the settings.
+     * @param session the Maven session.
+     * @param profileActivation the requested optional and required profiles.
+     */
+    private void validateRequiredProfiles( MavenSession session, 
ProfileActivation profileActivation )
+    {
+        final Set<String> allAvailableProfiles = getAllProfiles( session );
+
+        final Set<String> requiredProfiles = new HashSet<>( );
+        requiredProfiles.addAll( 
profileActivation.getRequiredActiveProfileIds() );
+        requiredProfiles.addAll( 
profileActivation.getRequiredInactiveProfileIds() );
+
+        // Check whether the required profiles were found in any of the 
projects we're building.
+        final Set<String> notFoundRequiredProfiles = requiredProfiles.stream()
+                .filter( rap -> !allAvailableProfiles.contains( rap ) )
+                .collect( toSet() );
+
+        if ( !notFoundRequiredProfiles.isEmpty() )
+        {
+            final String message = String.format(
+                    "The requested profiles [%s] could not be activated or 
deactivated because they do not exist.",
+                    String.join( ", ", notFoundRequiredProfiles )
+            );
+            addExceptionToResult( session.getResult(), new 
MissingProfilesException( message ) );
+        }
+    }
+
+    /**
+     * Check whether any of the requested optional profiles were not activated 
or deactivated.
+     * @param session the Maven session.
+     * @param profileActivation the requested optional and required profiles.
+     */
+    private void validateOptionalProfiles( MavenSession session, 
ProfileActivation profileActivation )
+    {
+        final Set<String> allAvailableProfiles = getAllProfiles( session );
+
+        final Set<String> optionalProfiles = new HashSet<>( );
+        optionalProfiles.addAll( 
profileActivation.getOptionalActiveProfileIds() );
+        optionalProfiles.addAll( 
profileActivation.getOptionalInactiveProfileIds() );
+
+        final Set<String> notFoundOptionalProfiles = optionalProfiles.stream()
+                .filter( rap -> !allAvailableProfiles.contains( rap ) )
+                .collect( toSet() );
+
+        if ( !notFoundOptionalProfiles.isEmpty() )
         {
-            logger.warn( "The requested profile \"" + notActivatedProfileId
-                + "\" could not be activated because it does not exist." );
+            final String message = String.format(
+                    "The requested optional profiles [%s] could not be 
activated or deactivated because they "
+                            + "do not exist.", String.join( ", ", 
notFoundOptionalProfiles )
+            );
+            logger.warn( message );
         }
     }
 
diff --git 
a/maven-core/src/main/java/org/apache/maven/MissingProfilesException.java 
b/maven-core/src/main/java/org/apache/maven/MissingProfilesException.java
new file mode 100644
index 0000000..6e9ee76
--- /dev/null
+++ b/maven-core/src/main/java/org/apache/maven/MissingProfilesException.java
@@ -0,0 +1,33 @@
+package org.apache.maven;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ * Signals that the user referenced one or more Maven profiles that could not 
be located in either the project or the
+ * settings.
+ */
+public class MissingProfilesException
+        extends Exception
+{
+    public MissingProfilesException( String message )
+    {
+        super( message );
+    }
+}
diff --git 
a/maven-core/src/main/java/org/apache/maven/execution/DefaultMavenExecutionRequest.java
 
b/maven-core/src/main/java/org/apache/maven/execution/DefaultMavenExecutionRequest.java
index f344359..554928b 100644
--- 
a/maven-core/src/main/java/org/apache/maven/execution/DefaultMavenExecutionRequest.java
+++ 
b/maven-core/src/main/java/org/apache/maven/execution/DefaultMavenExecutionRequest.java
@@ -76,6 +76,8 @@ public class DefaultMavenExecutionRequest
 
     private List<Profile> profiles;
 
+    private final ProfileActivation profileActivation = new 
ProfileActivation();
+
     private List<String> pluginGroups;
 
     private boolean isProjectPresent = true;
@@ -130,10 +132,6 @@ public class DefaultMavenExecutionRequest
 
     private boolean showErrors = false;
 
-    private List<String> activeProfiles;
-
-    private List<String> inactiveProfiles;
-
     private TransferListener transferListener;
 
     private int loggingLevel = LOGGING_LEVEL_INFO;
@@ -343,11 +341,7 @@ public class DefaultMavenExecutionRequest
     {
         if ( activeProfiles != null )
         {
-            this.activeProfiles = new ArrayList<>( activeProfiles );
-        }
-        else
-        {
-            this.activeProfiles = null;
+            this.profileActivation.overwriteActiveProfiles( activeProfiles );
         }
 
         return this;
@@ -358,17 +352,19 @@ public class DefaultMavenExecutionRequest
     {
         if ( inactiveProfiles != null )
         {
-            this.inactiveProfiles = new ArrayList<>( inactiveProfiles );
-        }
-        else
-        {
-            this.inactiveProfiles = null;
+            this.profileActivation.overwriteInactiveProfiles( inactiveProfiles 
);
         }
 
         return this;
     }
 
     @Override
+    public ProfileActivation getProfileActivation()
+    {
+        return this.profileActivation;
+    }
+
+    @Override
     public MavenExecutionRequest setRemoteRepositories( 
List<ArtifactRepository> remoteRepositories )
     {
         if ( remoteRepositories != null )
@@ -406,21 +402,13 @@ public class DefaultMavenExecutionRequest
     @Override
     public List<String> getActiveProfiles()
     {
-        if ( activeProfiles == null )
-        {
-            activeProfiles = new ArrayList<>();
-        }
-        return activeProfiles;
+        return this.profileActivation.getActiveProfiles();
     }
 
     @Override
     public List<String> getInactiveProfiles()
     {
-        if ( inactiveProfiles == null )
-        {
-            inactiveProfiles = new ArrayList<>();
-        }
-        return inactiveProfiles;
+        return this.profileActivation.getInactiveProfiles();
     }
 
     @Override
diff --git 
a/maven-core/src/main/java/org/apache/maven/execution/MavenExecutionRequest.java
 
b/maven-core/src/main/java/org/apache/maven/execution/MavenExecutionRequest.java
index 1ccd7ec..f31e33f 100644
--- 
a/maven-core/src/main/java/org/apache/maven/execution/MavenExecutionRequest.java
+++ 
b/maven-core/src/main/java/org/apache/maven/execution/MavenExecutionRequest.java
@@ -277,22 +277,62 @@ public interface MavenExecutionRequest
 
     MavenExecutionRequest setProfiles( List<Profile> profiles );
 
+    /**
+     * @deprecated Use {@link #getProfileActivation()}.
+     */
+    @Deprecated
     MavenExecutionRequest addActiveProfile( String profile );
 
+    /**
+     * @deprecated Use {@link #getProfileActivation()}.
+     */
+    @Deprecated
     MavenExecutionRequest addActiveProfiles( List<String> profiles );
 
+    /**
+     * @deprecated Use {@link #getProfileActivation()}.
+     */
+    @Deprecated
     MavenExecutionRequest setActiveProfiles( List<String> profiles );
 
+    /**
+     * @return The list of profiles that the user wants to activate.
+     * @deprecated Use {@link #getProfileActivation()}.
+     */
+    @Deprecated
     List<String> getActiveProfiles();
 
+    /**
+     * @deprecated Use {@link #getProfileActivation()}.
+     */
+    @Deprecated
     MavenExecutionRequest addInactiveProfile( String profile );
 
+    /**
+     * @deprecated Use {@link #getProfileActivation()}.
+     */
+    @Deprecated
     MavenExecutionRequest addInactiveProfiles( List<String> profiles );
 
+    /**
+     * @deprecated Use {@link #getProfileActivation()}.
+     */
+    @Deprecated
     MavenExecutionRequest setInactiveProfiles( List<String> profiles );
 
+    /**
+     * @return The list of profiles that the user wants to de-activate.
+     * @deprecated Use {@link #getProfileActivation()}.
+     */
+    @Deprecated
     List<String> getInactiveProfiles();
 
+    /**
+     * Return the requested activation(s) of profile(s) in this execution.
+     * @return requested (de-)activation(s) of profile(s) in this execution. 
Never {@code null}.
+     */
+    ProfileActivation getProfileActivation();
+
     // Proxies
     List<Proxy> getProxies();
 
diff --git 
a/maven-core/src/main/java/org/apache/maven/execution/ProfileActivation.java 
b/maven-core/src/main/java/org/apache/maven/execution/ProfileActivation.java
new file mode 100644
index 0000000..21530bd
--- /dev/null
+++ b/maven-core/src/main/java/org/apache/maven/execution/ProfileActivation.java
@@ -0,0 +1,201 @@
+package org.apache.maven.execution;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Predicate;
+
+import static java.util.stream.Collectors.toSet;
+
+/**
+ * Container for storing the request from the user to activate or de-activate 
certain profiles and optionally fail the
+ * build if those profiles do not exist.
+ */
+public class ProfileActivation
+{
+    private enum ActivationSettings
+    {
+        ACTIVATION_OPTIONAL( true, true ),
+        ACTIVATION_REQUIRED( true, false ),
+        DEACTIVATION_OPTIONAL( false, true ),
+        DEACTIVATION_REQUIRED( false, false );
+
+        /** Should the profile be active? */
+        final boolean active;
+        /** Should the build continue if the profile is not present? */
+        final boolean optional;
+
+        ActivationSettings( final boolean active, final boolean optional )
+        {
+            this.active = active;
+            this.optional = optional;
+        }
+
+        static ActivationSettings of( final boolean active, final boolean 
optional )
+        {
+            if ( optional )
+            {
+                return active ? ACTIVATION_OPTIONAL : DEACTIVATION_OPTIONAL;
+            }
+            else
+            {
+                return active ? ACTIVATION_REQUIRED : DEACTIVATION_REQUIRED;
+            }
+        }
+    }
+
+    private final Map<String, ActivationSettings> activations = new 
HashMap<>();
+
+    /**
+     * Mimics the pre-Maven 4 "active profiles" list.
+     * @deprecated Use {@link #getRequiredActiveProfileIds()} and {@link 
#getOptionalActiveProfileIds()} instead.
+     */
+    @Deprecated
+    public List<String> getActiveProfiles()
+    {
+        return new ArrayList<>( getProfileIds( pa -> pa.active ) );
+    }
+
+    /**
+     * Mimics the pre-Maven 4 "inactive profiles" list.
+     * @deprecated Use {@link #getRequiredInactiveProfileIds()} and {@link 
#getOptionalInactiveProfileIds()} instead.
+     */
+    @Deprecated
+    public List<String> getInactiveProfiles()
+    {
+        return new ArrayList<>( getProfileIds( pa -> !pa.active ) );
+    }
+
+    /**
+     * Overwrites the active profiles based on a pre-Maven 4 "active profiles" 
list.
+     * @param activeProfileIds A {@link List} of profile IDs that must be 
activated.
+     * @deprecated Use {@link #activateOptionalProfile(String)} or {@link 
#activateRequiredProfile(String)} instead.
+     */
+    @Deprecated
+    public void overwriteActiveProfiles( List<String> activeProfileIds )
+    {
+        getActiveProfiles().forEach( this.activations::remove );
+        activeProfileIds.forEach( this::activateOptionalProfile );
+    }
+
+    /**
+     * Overwrites the inactive profiles based on a pre-Maven 4 "inactive 
profiles" list.
+     * @param inactiveProfileIds A {@link List} of profile IDs that must be 
deactivated.
+     * @deprecated Use {@link #deactivateOptionalProfile(String)} or {@link 
#deactivateRequiredProfile(String)} instead.
+     */
+    @Deprecated
+    public void overwriteInactiveProfiles( List<String> inactiveProfileIds )
+    {
+        getInactiveProfiles().forEach( this.activations::remove );
+        inactiveProfileIds.forEach( this::deactivateOptionalProfile );
+    }
+
+    /**
+     * Mark a profile as required and activated.
+     * @param id The identifier of the profile.
+     */
+    public void activateRequiredProfile( String id )
+    {
+        this.activations.put( id, ActivationSettings.ACTIVATION_REQUIRED );
+    }
+
+    /**
+     * Mark a profile as optional and activated.
+     * @param id The identifier of the profile.
+     */
+    public void activateOptionalProfile( String id )
+    {
+        this.activations.put( id, ActivationSettings.ACTIVATION_OPTIONAL );
+    }
+
+    /**
+     * Mark a profile as required and deactivated.
+     * @param id The identifier of the profile.
+     */
+    public void deactivateRequiredProfile( String id )
+    {
+        this.activations.put( id, ActivationSettings.DEACTIVATION_REQUIRED );
+    }
+
+    /**
+     * Mark a profile as optional and deactivated.
+     * @param id The identifier of the profile.
+     */
+    public void deactivateOptionalProfile( String id )
+    {
+        this.activations.put( id, ActivationSettings.DEACTIVATION_OPTIONAL );
+    }
+
+    /**
+     * Adds a profile activation to the request.
+     * @param id The identifier of the profile.
+     * @param active Should the profile be activated?
+     * @param optional Can the build continue if the profile does not exist?
+     */
+    public void addProfileActivation( String id, boolean active, boolean 
optional )
+    {
+        final ActivationSettings settings = ActivationSettings.of( active, 
optional );
+        this.activations.put( id, settings );
+    }
+
+    private Set<String> getProfileIds( final Predicate<ActivationSettings> 
predicate )
+    {
+        return this.activations.entrySet().stream()
+                .filter( e -> predicate.test( e.getValue() ) )
+                .map( e -> e.getKey() )
+                .collect( toSet() );
+    }
+
+    /**
+     * @return Required active profile identifiers, never {@code null}.
+     */
+    public Set<String> getRequiredActiveProfileIds()
+    {
+        return getProfileIds( pa -> !pa.optional && pa.active );
+    }
+
+    /**
+     * @return Optional active profile identifiers, never {@code null}.
+     */
+    public Set<String> getOptionalActiveProfileIds()
+    {
+        return getProfileIds( pa -> pa.optional && pa.active );
+    }
+
+    /**
+     * @return Required inactive profile identifiers, never {@code null}.
+     */
+    public Set<String> getRequiredInactiveProfileIds()
+    {
+        return getProfileIds( pa -> !pa.optional && !pa.active );
+    }
+
+    /**
+     * @return Optional inactive profile identifiers, never {@code null}.
+     */
+    public Set<String> getOptionalInactiveProfileIds()
+    {
+        return getProfileIds( pa -> pa.optional && !pa.active );
+    }
+}
diff --git a/maven-embedder/src/main/java/org/apache/maven/cli/MavenCli.java 
b/maven-embedder/src/main/java/org/apache/maven/cli/MavenCli.java
index 7afe073..792a831 100644
--- a/maven-embedder/src/main/java/org/apache/maven/cli/MavenCli.java
+++ b/maven-embedder/src/main/java/org/apache/maven/cli/MavenCli.java
@@ -54,6 +54,7 @@ import org.apache.maven.execution.MavenExecutionRequest;
 import org.apache.maven.execution.MavenExecutionRequestPopulationException;
 import org.apache.maven.execution.MavenExecutionRequestPopulator;
 import org.apache.maven.execution.MavenExecutionResult;
+import org.apache.maven.execution.ProfileActivation;
 import org.apache.maven.execution.scope.internal.MojoExecutionScopeModule;
 import org.apache.maven.extension.internal.CoreExports;
 import org.apache.maven.extension.internal.CoreExtensionEntry;
@@ -1379,9 +1380,7 @@ public class MavenCli
         request.setSelectedProjects( projectActivation.activeProjects );
         request.setExcludedProjects( projectActivation.inactiveProjects );
 
-        final ProfileActivation profileActivation = 
determineProfileActivation( commandLine );
-        request.addActiveProfiles( profileActivation.activeProfiles );
-        request.addInactiveProfiles( profileActivation.inactiveProfiles );
+        performProfileActivation( commandLine, request.getProfileActivation() 
);
 
         final String localRepositoryPath = determineLocalRepositoryPath( 
request );
         if ( localRepositoryPath != null )
@@ -1507,41 +1506,41 @@ public class MavenCli
     }
 
     // Visible for testing
-    static ProfileActivation determineProfileActivation( final CommandLine 
commandLine )
+    static void performProfileActivation( final CommandLine commandLine,
+                                          final ProfileActivation 
profileActivation )
     {
-        final ProfileActivation result = new ProfileActivation();
-
         if ( commandLine.hasOption( CLIManager.ACTIVATE_PROFILES ) )
         {
-            String[] profileOptionValues = commandLine.getOptionValues( 
CLIManager.ACTIVATE_PROFILES );
-            if ( profileOptionValues != null )
+            final String[] optionValues = commandLine.getOptionValues( 
CLIManager.ACTIVATE_PROFILES );
+
+            if ( optionValues == null || optionValues.length == 0 )
             {
-                for ( String profileOptionValue : profileOptionValues )
-                {
-                    StringTokenizer profileTokens = new StringTokenizer( 
profileOptionValue, "," );
+                return;
+            }
 
-                    while ( profileTokens.hasMoreTokens() )
+            for ( final String optionValue : optionValues )
+            {
+                for ( String token : optionValue.split( "," ) )
+                {
+                    String profileId = token.trim();
+                    boolean active = true;
+                    if ( profileId.charAt( 0 ) == '-' || profileId.charAt( 0 ) 
== '!' )
                     {
-                        String profileAction = 
profileTokens.nextToken().trim();
-
-                        if ( profileAction.startsWith( "-" ) || 
profileAction.startsWith( "!" ) )
-                        {
-                            result.deactivate( profileAction.substring( 1 ) );
-                        }
-                        else if ( profileAction.startsWith( "+" ) )
-                        {
-                            result.activate( profileAction.substring( 1 ) );
-                        }
-                        else
-                        {
-                            result.activate( profileAction );
-                        }
+                        active = false;
+                        profileId = profileId.substring( 1 );
+                    }
+                    else if ( token.charAt( 0 ) == '+' )
+                    {
+                        profileId = profileId.substring( 1 );
                     }
+
+                    boolean optional = profileId.charAt( 0 ) == '?';
+                    profileId = profileId.substring( optional ? 1 : 0 );
+
+                    profileActivation.addProfileActivation( profileId, active, 
optional );
                 }
             }
         }
-
-        return result;
     }
 
     private ExecutionListener determineExecutionListener()
@@ -1797,23 +1796,6 @@ public class MavenCli
     }
 
     // Visible for testing
-    static class ProfileActivation
-    {
-        final List<String> activeProfiles = new ArrayList<>();
-        final List<String> inactiveProfiles = new ArrayList<>();
-
-        public void deactivate( final String profile )
-        {
-            inactiveProfiles.add( profile );
-        }
-
-        public void activate( final String profile )
-        {
-            activeProfiles.add( profile );
-        }
-    }
-
-    // Visible for testing
     static class ProjectActivation
     {
         List<String> activeProjects;
diff --git 
a/maven-embedder/src/test/java/org/apache/maven/cli/MavenCliTest.java 
b/maven-embedder/src/test/java/org/apache/maven/cli/MavenCliTest.java
index c7a41a5..de7485c 100644
--- a/maven-embedder/src/test/java/org/apache/maven/cli/MavenCliTest.java
+++ b/maven-embedder/src/test/java/org/apache/maven/cli/MavenCliTest.java
@@ -20,7 +20,7 @@ package org.apache.maven.cli;
  */
 
 import static java.util.Arrays.asList;
-import static org.apache.maven.cli.MavenCli.determineProfileActivation;
+import static org.apache.maven.cli.MavenCli.performProfileActivation;
 import static org.apache.maven.cli.MavenCli.determineProjectActivation;
 import static org.hamcrest.CoreMatchers.is;
 import static org.hamcrest.CoreMatchers.nullValue;
@@ -33,6 +33,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.junit.jupiter.api.Assumptions.assumeTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.hamcrest.collection.IsIterableContainingInOrder.contains;
+import static 
org.hamcrest.collection.IsIterableContainingInAnyOrder.containsInAnyOrder;
 import static org.mockito.Mockito.inOrder;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.times;
@@ -46,9 +47,11 @@ import org.apache.commons.cli.GnuParser;
 import org.apache.commons.cli.Option;
 import org.apache.commons.cli.Options;
 import org.apache.commons.cli.ParseException;
+import org.apache.commons.cli.Parser;
 import org.apache.maven.Maven;
 import org.apache.maven.eventspy.internal.EventSpyDispatcher;
 import org.apache.maven.execution.MavenExecutionRequest;
+import org.apache.maven.execution.ProfileActivation;
 import org.apache.maven.project.MavenProject;
 import org.apache.maven.shared.utils.logging.MessageUtils;
 import org.apache.maven.toolchain.building.ToolchainsBuildingRequest;
@@ -88,25 +91,29 @@ public class MavenCliTest
     }
 
     @Test
-    public void testDetermineProfileActivation() throws ParseException
+    public void testPerformProfileActivation() throws ParseException
     {
-        MavenCli.ProfileActivation result;
-        Options options = new Options();
+        final Parser parser = new GnuParser();
+
+        final Options options = new Options();
         options.addOption( Option.builder( Character.toString( 
CLIManager.ACTIVATE_PROFILES ) ).hasArg().build() );
 
-        result = determineProfileActivation( new GnuParser().parse( options, 
new String[]{ "-P", "test1,+test2" } ) );
-        assertThat( result.activeProfiles.size(), is( 2 ) );
-        assertThat( result.activeProfiles, contains( "test1", "test2" ) );
+        ProfileActivation activation;
+
+        activation = new ProfileActivation();
+        performProfileActivation( parser.parse( options, new String[]{ "-P", 
"test1,+test2,?test3,+?test4" } ), activation );
+        assertThat( activation.getRequiredActiveProfileIds(), 
containsInAnyOrder( "test1", "test2" ) );
+        assertThat( activation.getOptionalActiveProfileIds(), 
containsInAnyOrder( "test3", "test4" ) );
 
-        result = determineProfileActivation( new GnuParser().parse( options, 
new String[]{ "-P", "!test1,-test2" } ) );
-        assertThat( result.inactiveProfiles.size(), is( 2 ) );
-        assertThat( result.inactiveProfiles, contains( "test1", "test2" ) );
+        activation = new ProfileActivation();
+        performProfileActivation( parser.parse( options, new String[]{ "-P", 
"!test1,-test2,-?test3,!?test4" } ), activation );
+        assertThat( activation.getRequiredInactiveProfileIds(), 
containsInAnyOrder( "test1", "test2" ) );
+        assertThat( activation.getOptionalInactiveProfileIds(), 
containsInAnyOrder( "test3", "test4" ) );
 
-        result = determineProfileActivation( new GnuParser().parse( options, 
new String[]{ "-P", "-test1,+test2" } ) );
-        assertThat( result.activeProfiles.size(), is( 1 ) );
-        assertThat( result.activeProfiles, contains( "test2" ) );
-        assertThat( result.inactiveProfiles.size(), is( 1 ) );
-        assertThat( result.inactiveProfiles, contains( "test1" ) );
+        activation = new ProfileActivation();
+        performProfileActivation( parser.parse( options, new String[]{ "-P", 
"-test1,+test2" } ), activation );
+        assertThat( activation.getRequiredActiveProfileIds(), 
containsInAnyOrder( "test2" ) );
+        assertThat( activation.getRequiredInactiveProfileIds(), 
containsInAnyOrder( "test1" ) );
     }
 
     @Test
diff --git 
a/maven-model-builder/src/main/java/org/apache/maven/model/building/ModelProblem.java
 
b/maven-model-builder/src/main/java/org/apache/maven/model/building/ModelProblem.java
index 30b6724..a08b5ef 100644
--- 
a/maven-model-builder/src/main/java/org/apache/maven/model/building/ModelProblem.java
+++ 
b/maven-model-builder/src/main/java/org/apache/maven/model/building/ModelProblem.java
@@ -51,7 +51,8 @@ public interface ModelProblem
         V20,
         V30,
         V31,
-        V37
+        V37,
+        V40
     }
 
     /**
diff --git 
a/maven-model-builder/src/main/java/org/apache/maven/model/validation/DefaultModelValidator.java
 
b/maven-model-builder/src/main/java/org/apache/maven/model/validation/DefaultModelValidator.java
index a4c3cda..ba812b5 100644
--- 
a/maven-model-builder/src/main/java/org/apache/maven/model/validation/DefaultModelValidator.java
+++ 
b/maven-model-builder/src/main/java/org/apache/maven/model/validation/DefaultModelValidator.java
@@ -190,6 +190,8 @@ public class DefaultModelValidator
             {
                 String prefix = "profiles.profile[" + profile.getId() + "].";
 
+                validateId( prefix, "id", problems, Severity.ERROR, 
Version.V40, profile.getId(), null, m );
+
                 if ( !profileIds.add( profile.getId() ) )
                 {
                     addViolation( problems, errOn30, Version.V20, 
"profiles.profile.id", null,
diff --git 
a/maven-model-builder/src/test/java/org/apache/maven/model/validation/DefaultModelValidatorTest.java
 
b/maven-model-builder/src/test/java/org/apache/maven/model/validation/DefaultModelValidatorTest.java
index 5f17a8a..d73dcb1 100644
--- 
a/maven-model-builder/src/test/java/org/apache/maven/model/validation/DefaultModelValidatorTest.java
+++ 
b/maven-model-builder/src/test/java/org/apache/maven/model/validation/DefaultModelValidatorTest.java
@@ -403,6 +403,16 @@ public class DefaultModelValidatorTest
     }
 
     @Test
+    public void testInvalidProfileId()
+        throws Exception
+    {
+        SimpleProblemCollector result = validateRaw( "invalid-profile-id.xml" 
);
+
+        assertViolations( result, 0, 1, 0 );
+
+        assertTrue( result.getErrors().get( 0 ).contains( "?invalid-id" ) );
+    }
+
     public void testDuplicateProfileId()
         throws Exception
     {
diff --git 
a/maven-model-builder/src/test/resources/poms/validation/invalid-profile-id.xml 
b/maven-model-builder/src/test/resources/poms/validation/invalid-profile-id.xml
new file mode 100644
index 0000000..6e96026
--- /dev/null
+++ 
b/maven-model-builder/src/test/resources/poms/validation/invalid-profile-id.xml
@@ -0,0 +1,32 @@
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied.  See the License for the
+specific language governing permissions and limitations
+under the License.
+-->
+
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <artifactId>aid</artifactId>
+    <groupId>gid</groupId>
+    <version>0.1</version>
+    <packaging>pom</packaging>
+
+    <profiles>
+        <profile>
+            <id>?invalid-id</id>
+        </profile>
+    </profiles>
+</project>

Reply via email to