You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maven.apache.org by rf...@apache.org on 2019/06/08 08:43:18 UTC

[maven] branch master updated: [MNG-6665] toolchain.xml file should support environment variables

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

rfscholte 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 aed5130  [MNG-6665] toolchain.xml file should support environment variables
aed5130 is described below

commit aed51304870028e8bdbc3d85c9bd67cd741776e4
Author: MartinKanters <mk...@gmail.com>
AuthorDate: Sat Jun 8 10:43:12 2019 +0200

    [MNG-6665] toolchain.xml file should support environment variables
---
 .../building/DefaultToolchainsBuilder.java         | 105 +++++++++++++--
 .../toolchain/io/DefaultToolchainsWriter.java      |  54 ++++++++
 .../maven/toolchain/io/ToolchainsWriter.java       |  48 +++++++
 .../building/DefaultToolchainsBuilderTest.java     | 150 +++++++++++++++++----
 pom.xml                                            |   6 +
 5 files changed, 324 insertions(+), 39 deletions(-)

diff --git a/maven-core/src/main/java/org/apache/maven/toolchain/building/DefaultToolchainsBuilder.java b/maven-core/src/main/java/org/apache/maven/toolchain/building/DefaultToolchainsBuilder.java
index 7983388..beeee19 100644
--- a/maven-core/src/main/java/org/apache/maven/toolchain/building/DefaultToolchainsBuilder.java
+++ b/maven-core/src/main/java/org/apache/maven/toolchain/building/DefaultToolchainsBuilder.java
@@ -19,24 +19,30 @@ package org.apache.maven.toolchain.building;
  * under the License.
  */
 
-import java.io.IOException;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-
-import javax.inject.Inject;
-import javax.inject.Named;
-import javax.inject.Singleton;
-
 import org.apache.maven.building.Problem;
 import org.apache.maven.building.ProblemCollector;
 import org.apache.maven.building.ProblemCollectorFactory;
 import org.apache.maven.building.Source;
 import org.apache.maven.toolchain.io.ToolchainsParseException;
 import org.apache.maven.toolchain.io.ToolchainsReader;
+import org.apache.maven.toolchain.io.ToolchainsWriter;
 import org.apache.maven.toolchain.merge.MavenToolchainMerger;
 import org.apache.maven.toolchain.model.PersistedToolchains;
 import org.apache.maven.toolchain.model.TrackableBase;
+import org.codehaus.plexus.interpolation.EnvarBasedValueSource;
+import org.codehaus.plexus.interpolation.InterpolationException;
+import org.codehaus.plexus.interpolation.InterpolationPostProcessor;
+import org.codehaus.plexus.interpolation.RegexBasedInterpolator;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.inject.Singleton;
+import java.io.IOException;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
 
 /**
  * 
@@ -49,7 +55,10 @@ public class DefaultToolchainsBuilder
     implements ToolchainsBuilder
 {
     private MavenToolchainMerger toolchainsMerger = new MavenToolchainMerger();
-    
+
+    @Inject
+    private ToolchainsWriter toolchainsWriter;
+
     @Inject
     private ToolchainsReader toolchainsReader;
 
@@ -66,16 +75,86 @@ public class DefaultToolchainsBuilder
         toolchainsMerger.merge( userToolchains, globalToolchains, TrackableBase.GLOBAL_LEVEL );
         
         problems.setSource( "" );
-        
+
+        userToolchains = interpolate( userToolchains, problems );
+
         if ( hasErrors( problems.getProblems() ) )
         {
             throw new ToolchainsBuildingException( problems.getProblems() );
         }
-        
-        
+
+
         return new DefaultToolchainsBuildingResult( userToolchains, problems.getProblems() );
     }
 
+    private PersistedToolchains interpolate( PersistedToolchains toolchains, ProblemCollector problems )
+    {
+
+        StringWriter stringWriter = new StringWriter( 1024 * 4 );
+        try
+        {
+            toolchainsWriter.write( stringWriter, null, toolchains );
+        }
+        catch ( IOException e )
+        {
+            throw new IllegalStateException( "Failed to serialize toolchains to memory", e );
+        }
+
+        String serializedToolchains = stringWriter.toString();
+
+        RegexBasedInterpolator interpolator = new RegexBasedInterpolator();
+
+        try
+        {
+            interpolator.addValueSource( new EnvarBasedValueSource() );
+        }
+        catch ( IOException e )
+        {
+            problems.add( Problem.Severity.WARNING, "Failed to use environment variables for interpolation: "
+                    + e.getMessage(), -1, -1, e );
+        }
+
+        interpolator.addPostProcessor( new InterpolationPostProcessor()
+        {
+            @Override
+            public Object execute( String expression, Object value )
+            {
+                if ( value != null )
+                {
+                    // we're going to parse this back in as XML so we need to escape XML markup
+                    value = value.toString().replace( "&", "&amp;" ).replace( "<", "&lt;" ).replace( ">", "&gt;" );
+                    return value;
+                }
+                return null;
+            }
+        } );
+
+        try
+        {
+            serializedToolchains = interpolator.interpolate( serializedToolchains );
+        }
+        catch ( InterpolationException e )
+        {
+            problems.add( Problem.Severity.ERROR, "Failed to interpolate toolchains: " + e.getMessage(), -1, -1, e );
+            return toolchains;
+        }
+
+        PersistedToolchains result;
+        try
+        {
+            Map<String, ?> options = Collections.singletonMap( ToolchainsReader.IS_STRICT, Boolean.FALSE );
+
+            result = toolchainsReader.read( new StringReader( serializedToolchains ), options );
+        }
+        catch ( IOException e )
+        {
+            problems.add( Problem.Severity.ERROR, "Failed to interpolate toolchains: " + e.getMessage(), -1, -1, e );
+            return toolchains;
+        }
+
+        return result;
+    }
+
     private PersistedToolchains readToolchains( Source toolchainsSource, ToolchainsBuildingRequest request,
                                                 ProblemCollector problems )
     {
diff --git a/maven-core/src/main/java/org/apache/maven/toolchain/io/DefaultToolchainsWriter.java b/maven-core/src/main/java/org/apache/maven/toolchain/io/DefaultToolchainsWriter.java
new file mode 100644
index 0000000..a533766
--- /dev/null
+++ b/maven-core/src/main/java/org/apache/maven/toolchain/io/DefaultToolchainsWriter.java
@@ -0,0 +1,54 @@
+package org.apache.maven.toolchain.io;
+
+/*
+ * 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 org.apache.maven.toolchain.model.PersistedToolchains;
+import org.apache.maven.toolchain.model.io.xpp3.MavenToolchainsXpp3Writer;
+
+import javax.inject.Named;
+import javax.inject.Singleton;
+import java.io.IOException;
+import java.io.Writer;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * Handles serialization of toolchains into the default textual format.
+ *
+ * @author Mike Mol
+ * @author Martin Kanters
+ */
+@Named
+@Singleton
+public class DefaultToolchainsWriter implements ToolchainsWriter
+{
+
+    @Override
+    public void write( Writer output, Map<String, Object> options, PersistedToolchains toolchains ) throws IOException
+    {
+        Objects.requireNonNull( output, "output cannot be null" );
+        Objects.requireNonNull( toolchains, "toolchains cannot be null" );
+
+        try ( final Writer out = output )
+        {
+            new MavenToolchainsXpp3Writer().write( out, toolchains );
+        }
+    }
+}
diff --git a/maven-core/src/main/java/org/apache/maven/toolchain/io/ToolchainsWriter.java b/maven-core/src/main/java/org/apache/maven/toolchain/io/ToolchainsWriter.java
new file mode 100644
index 0000000..0b15f34
--- /dev/null
+++ b/maven-core/src/main/java/org/apache/maven/toolchain/io/ToolchainsWriter.java
@@ -0,0 +1,48 @@
+package org.apache.maven.toolchain.io;
+
+/*
+ * 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 org.apache.maven.toolchain.model.PersistedToolchains;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.util.Map;
+
+/**
+ * Handles serialization of toolchains into some kind of textual format like XML.
+ *
+ * @author Mike Mol
+ * @author Martin Kanters
+ */
+public interface ToolchainsWriter
+{
+
+    /**
+     * Writes the supplied toolchains to the specified character writer. The writer will be automatically closed before
+     * the method returns.
+     *
+     * @param output The writer to serialize the toolchains to, must not be {@code null}.
+     * @param options The options to use for serialization, may be {@code null} to use the default values.
+     * @param toolchains The toolchains to serialize, must not be {@code null}.
+     * @throws IOException If the toolchains could not be serialized.
+     */
+    void write( Writer output, Map<String, Object> options, PersistedToolchains toolchains )
+            throws IOException;
+}
diff --git a/maven-core/src/test/java/org/apache/maven/toolchain/building/DefaultToolchainsBuilderTest.java b/maven-core/src/test/java/org/apache/maven/toolchain/building/DefaultToolchainsBuilderTest.java
index fc530df..2c20d3c 100644
--- a/maven-core/src/test/java/org/apache/maven/toolchain/building/DefaultToolchainsBuilderTest.java
+++ b/maven-core/src/test/java/org/apache/maven/toolchain/building/DefaultToolchainsBuilderTest.java
@@ -19,40 +19,54 @@ package org.apache.maven.toolchain.building;
  * under the License.
  */
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.when;
-
-import java.io.IOException;
-import java.io.InputStream;
-
 import org.apache.maven.building.StringSource;
+import org.apache.maven.toolchain.io.DefaultToolchainsReader;
+import org.apache.maven.toolchain.io.DefaultToolchainsWriter;
 import org.apache.maven.toolchain.io.ToolchainsParseException;
-import org.apache.maven.toolchain.io.ToolchainsReader;
 import org.apache.maven.toolchain.model.PersistedToolchains;
 import org.apache.maven.toolchain.model.ToolchainModel;
+import org.codehaus.plexus.interpolation.os.OperatingSystemUtils;
+import org.codehaus.plexus.util.xml.Xpp3Dom;
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.ArgumentMatchers;
 import org.mockito.InjectMocks;
-import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
+import org.mockito.Spy;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
 
 public class DefaultToolchainsBuilderTest
 {
     private static final String LS = System.getProperty( "line.separator" );
-    
-    @Mock
-    private ToolchainsReader toolchainsReader;
+
+    @Spy
+    private DefaultToolchainsReader toolchainsReader;
+
+    @Spy
+    private DefaultToolchainsWriter toolchainsWriter;
 
     @InjectMocks
-    private DefaultToolchainsBuilder toolchainBuilder = new DefaultToolchainsBuilder();
+    private DefaultToolchainsBuilder toolchainBuilder;
 
     @Before
     public void onSetup()
     {
         MockitoAnnotations.initMocks( this );
+
+        Map<String, String> envVarMap = new HashMap<>();
+        envVarMap.put("testKey", "testValue");
+        envVarMap.put("testSpecialCharactersKey", "<test&Value>");
+        OperatingSystemUtils.setEnvVarSource(new TestEnvVarSource(envVarMap));
     }
 
     @Test
@@ -78,7 +92,7 @@ public class DefaultToolchainsBuilderTest
         toolchain.setType( "TYPE" );
         toolchain.addProvide( "key", "user_value" );
         userResult.addToolchain(  toolchain );
-        when( toolchainsReader.read( any( InputStream.class ), ArgumentMatchers.<String, Object>anyMap()) ).thenReturn( userResult );
+        doReturn(userResult).when( toolchainsReader ).read( any( InputStream.class ), ArgumentMatchers.<String, Object>anyMap());
 
         ToolchainsBuildingResult result = toolchainBuilder.build( request );
         assertNotNull( result.getEffectiveToolchains() );
@@ -101,7 +115,7 @@ public class DefaultToolchainsBuilderTest
         toolchain.setType( "TYPE" );
         toolchain.addProvide( "key", "global_value" );
         globalResult.addToolchain(  toolchain );
-        when( toolchainsReader.read( any( InputStream.class ), ArgumentMatchers.<String, Object>anyMap()) ).thenReturn( globalResult );
+        doReturn(globalResult).when( toolchainsReader ).read( any( InputStream.class ), ArgumentMatchers.<String, Object>anyMap());
 
         ToolchainsBuildingResult result = toolchainBuilder.build( request );
         assertNotNull( result.getEffectiveToolchains() );
@@ -131,7 +145,7 @@ public class DefaultToolchainsBuilderTest
         globalToolchain.setType( "TYPE" );
         globalToolchain.addProvide( "key", "global_value" );
         globalResult.addToolchain(  globalToolchain );
-        when( toolchainsReader.read( any( InputStream.class ), ArgumentMatchers.<String, Object>anyMap()) ).thenReturn( globalResult ).thenReturn( userResult );
+        doReturn(globalResult).doReturn(userResult).when( toolchainsReader ).read( any( InputStream.class ), ArgumentMatchers.<String, Object>anyMap());
 
         ToolchainsBuildingResult result = toolchainBuilder.build( request );
         assertNotNull( result.getEffectiveToolchains() );
@@ -143,43 +157,127 @@ public class DefaultToolchainsBuilderTest
         assertNotNull( result.getProblems() );
         assertEquals( 0, result.getProblems().size() );
     }
-    
+
     @Test
     public void testStrictToolchainsParseException() throws Exception
     {
         ToolchainsBuildingRequest request = new DefaultToolchainsBuildingRequest();
         request.setGlobalToolchainsSource( new StringSource( "" ) );
         ToolchainsParseException parseException = new ToolchainsParseException( "MESSAGE", 4, 2 );
-        when( toolchainsReader.read( any( InputStream.class ), ArgumentMatchers.<String, Object>anyMap()) ).thenThrow( parseException );
-        
+        doThrow(parseException).when( toolchainsReader ).read( any( InputStream.class ), ArgumentMatchers.<String, Object>anyMap());
+
         try
         {
             toolchainBuilder.build( request );
         }
         catch ( ToolchainsBuildingException e )
         {
-            assertEquals( "1 problem was encountered while building the effective toolchains" + LS + 
+            assertEquals( "1 problem was encountered while building the effective toolchains" + LS +
                 "[FATAL] Non-parseable toolchains (memory): MESSAGE @ line 4, column 2" + LS, e.getMessage() );
         }
     }
-    
+
     @Test
     public void testIOException() throws Exception
     {
         ToolchainsBuildingRequest request = new DefaultToolchainsBuildingRequest();
         request.setGlobalToolchainsSource( new StringSource( "", "LOCATION" ) );
         IOException ioException = new IOException( "MESSAGE" );
-        when( toolchainsReader.read( any( InputStream.class ), ArgumentMatchers.<String, Object>anyMap()) ).thenThrow( ioException );
-        
+        doThrow(ioException).when( toolchainsReader ).read( any( InputStream.class ), ArgumentMatchers.<String, Object>anyMap());
+
         try
         {
             toolchainBuilder.build( request );
         }
         catch ( ToolchainsBuildingException e )
         {
-            assertEquals( "1 problem was encountered while building the effective toolchains" + LS + 
+            assertEquals( "1 problem was encountered while building the effective toolchains" + LS +
                 "[FATAL] Non-readable toolchains LOCATION: MESSAGE" + LS, e.getMessage() );
         }
     }
-    
+
+    @Test
+    public void testEnvironmentVariablesAreInterpolated()
+            throws Exception
+    {
+        ToolchainsBuildingRequest request = new DefaultToolchainsBuildingRequest();
+        request.setUserToolchainsSource( new StringSource( "" ) );
+
+        PersistedToolchains persistedToolchains = new PersistedToolchains();
+        ToolchainModel toolchain = new ToolchainModel();
+        toolchain.setType( "TYPE" );
+        toolchain.addProvide( "key", "${env.testKey}" );
+
+        Xpp3Dom configurationChild = new Xpp3Dom("jdkHome");
+        configurationChild.setValue("${env.testKey}");
+        Xpp3Dom configuration = new Xpp3Dom("configuration");
+        configuration.addChild(configurationChild);
+        toolchain.setConfiguration(configuration);
+        persistedToolchains.addToolchain( toolchain );
+        doReturn(persistedToolchains).when( toolchainsReader ).read( any( InputStream.class ), ArgumentMatchers.<String, Object>anyMap());
+
+        ToolchainsBuildingResult result = toolchainBuilder.build( request );
+        String interpolatedValue = "testValue";
+        assertEquals(interpolatedValue, result.getEffectiveToolchains().getToolchains().get(0).getProvides().getProperty( "key" ) );
+        Xpp3Dom toolchainConfiguration = (Xpp3Dom) result.getEffectiveToolchains().getToolchains().get(0).getConfiguration();
+        assertEquals(interpolatedValue, toolchainConfiguration.getChild("jdkHome").getValue());
+        assertNotNull( result.getProblems() );
+        assertEquals( 0, result.getProblems().size() );
+    }
+
+    @Test
+    public void testNonExistingEnvironmentVariablesAreNotInterpolated()
+            throws Exception
+    {
+        ToolchainsBuildingRequest request = new DefaultToolchainsBuildingRequest();
+        request.setUserToolchainsSource( new StringSource( "" ) );
+
+        PersistedToolchains persistedToolchains = new PersistedToolchains();
+        ToolchainModel toolchain = new ToolchainModel();
+        toolchain.setType( "TYPE" );
+        toolchain.addProvide( "key", "${env.testNonExistingKey}" );
+
+        persistedToolchains.addToolchain( toolchain );
+        doReturn(persistedToolchains).when( toolchainsReader ).read( any( InputStream.class ), ArgumentMatchers.<String, Object>anyMap());
+
+        ToolchainsBuildingResult result = toolchainBuilder.build( request );
+        assertEquals("${env.testNonExistingKey}", result.getEffectiveToolchains().getToolchains().get(0).getProvides().getProperty( "key" ) );
+        assertNotNull( result.getProblems() );
+        assertEquals( 0, result.getProblems().size() );
+    }
+
+    @Test
+    public void testEnvironmentVariablesWithSpecialCharactersAreInterpolated()
+            throws Exception
+    {
+        ToolchainsBuildingRequest request = new DefaultToolchainsBuildingRequest();
+        request.setUserToolchainsSource( new StringSource( "" ) );
+
+        PersistedToolchains persistedToolchains = new PersistedToolchains();
+        ToolchainModel toolchain = new ToolchainModel();
+        toolchain.setType( "TYPE" );
+        toolchain.addProvide( "key", "${env.testSpecialCharactersKey}" );
+
+        persistedToolchains.addToolchain( toolchain );
+        doReturn(persistedToolchains).when( toolchainsReader ).read( any( InputStream.class ), ArgumentMatchers.<String, Object>anyMap());
+
+        ToolchainsBuildingResult result = toolchainBuilder.build( request );
+        String interpolatedValue = "<test&Value>";
+        assertEquals(interpolatedValue, result.getEffectiveToolchains().getToolchains().get(0).getProvides().getProperty( "key" ) );
+        assertNotNull( result.getProblems() );
+        assertEquals( 0, result.getProblems().size() );
+    }
+
+    static class TestEnvVarSource implements OperatingSystemUtils.EnvVarSource {
+        private final Map<String, String> envVarMap;
+
+        TestEnvVarSource(Map<String, String> envVarMap) {
+            this.envVarMap = envVarMap;
+        }
+
+        public Map<String, String> getEnvMap() {
+            return envVarMap;
+        }
+    }
+
 }
diff --git a/pom.xml b/pom.xml
index cb9dae5..e734a1d 100644
--- a/pom.xml
+++ b/pom.xml
@@ -150,6 +150,12 @@ under the License.
     <contributor>
       <name>Fabiano Cipriano de Oliveira (MNG-6261)</name>
     </contributor>
+    <contributor>
+      <name>Mike Mol (MNG-6665)</name>
+    </contributor>
+    <contributor>
+      <name>Martin Kanters (MNG-6665)</name>
+    </contributor>
   </contributors>
 
   <!--bootstrap-start-comment-->