View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.maven.shared.release.exec;
20  
21  import javax.inject.Inject;
22  import javax.inject.Named;
23  import javax.inject.Singleton;
24  
25  import java.io.File;
26  import java.io.FileWriter;
27  import java.io.IOException;
28  import java.io.InputStream;
29  import java.io.OutputStream;
30  import java.nio.file.Files;
31  import java.util.List;
32  
33  import org.apache.maven.settings.io.xpp3.SettingsXpp3Writer;
34  import org.apache.maven.shared.release.ReleaseResult;
35  import org.apache.maven.shared.release.env.ReleaseEnvironment;
36  import org.apache.maven.shared.release.util.MavenCrypto;
37  import org.codehaus.plexus.util.cli.CommandLineException;
38  import org.codehaus.plexus.util.cli.Commandline;
39  
40  import static java.util.Objects.requireNonNull;
41  
42  /**
43   * Fork Maven to execute a series of goals.
44   *
45   * @author <a href="mailto:brett@apache.org">Brett Porter</a>
46   */
47  @Singleton
48  @Named("forked-path")
49  public class ForkedMavenExecutor extends AbstractMavenExecutor {
50      /**
51       * Command line factory.
52       */
53      private final CommandLineFactory commandLineFactory;
54  
55      @Inject
56      public ForkedMavenExecutor(MavenCrypto mavenCrypto, CommandLineFactory commandLineFactory) {
57          super(mavenCrypto);
58          this.commandLineFactory = requireNonNull(commandLineFactory);
59      }
60  
61      /*
62       * @noinspection UseOfSystemOutOrSystemErr
63       */
64      @Override
65      public void executeGoals(
66              File workingDirectory,
67              List<String> goals,
68              ReleaseEnvironment releaseEnvironment,
69              boolean interactive,
70              String additionalArguments,
71              String pomFileName,
72              ReleaseResult relResult)
73              throws MavenExecutorException {
74          String mavenPath;
75          // if null we use the current one
76          if (releaseEnvironment.getMavenHome() != null) {
77              mavenPath = releaseEnvironment.getMavenHome().getAbsolutePath();
78          } else {
79              mavenPath = System.getProperty("maven.home");
80          }
81  
82          File settingsFile = null;
83          if (releaseEnvironment.getSettings() != null) {
84              // Have to serialize to a file as if Maven is embedded, there may not actually be a settings.xml on disk
85              try {
86                  settingsFile = Files.createTempFile("release-settings", ".xml").toFile();
87                  SettingsXpp3Writer writer = getSettingsWriter();
88  
89                  try (FileWriter fileWriter = new FileWriter(settingsFile)) {
90                      writer.write(fileWriter, encryptSettings(releaseEnvironment.getSettings()));
91                  }
92              } catch (IOException e) {
93                  throw new MavenExecutorException("Could not create temporary file for release settings.xml", e);
94              }
95          }
96          try {
97  
98              Commandline cl =
99                      commandLineFactory.createCommandLine(mavenPath + File.separator + "bin" + File.separator + "mvn");
100 
101             cl.setWorkingDirectory(workingDirectory.getAbsolutePath());
102 
103             // FIX for MRELEASE-1105
104             // cl.addEnvironment( "MAVEN_DEBUG_OPTS", "" );
105 
106             cl.addEnvironment("MAVEN_TERMINATE_CMD", "on");
107 
108             if (releaseEnvironment.getJavaHome() != null) {
109                 cl.addEnvironment("JAVA_HOME", releaseEnvironment.getJavaHome().getAbsolutePath());
110             }
111 
112             if (settingsFile != null) {
113                 cl.createArg().setValue("-s");
114                 cl.createArg().setFile(settingsFile);
115             }
116 
117             if (pomFileName != null) {
118                 cl.createArg().setValue("-f");
119                 cl.createArg().setValue(pomFileName);
120             }
121 
122             for (String goal : goals) {
123                 cl.createArg().setValue(goal);
124             }
125 
126             if (!interactive) {
127                 cl.createArg().setValue("--batch-mode");
128             }
129 
130             if (!(additionalArguments == null || additionalArguments.isEmpty())) {
131                 cl.createArg().setLine(additionalArguments);
132             }
133 
134             TeeOutputStream stdOut = new TeeOutputStream(System.out);
135 
136             TeeOutputStream stdErr = new TeeOutputStream(System.err);
137 
138             try {
139                 relResult.appendInfo("Executing: " + cl);
140                 getLogger().info("Executing: " + cl);
141 
142                 int result = executeCommandLine(cl, System.in, stdOut, stdErr);
143 
144                 if (result != 0) {
145                     throw new MavenExecutorException("Maven execution failed, exit code: '" + result + "'", result);
146                 }
147             } catch (CommandLineException e) {
148                 throw new MavenExecutorException("Can't run goal " + goals, e);
149             } finally {
150                 relResult.appendOutput(stdOut.toString());
151             }
152         } finally {
153             if (settingsFile != null && settingsFile.exists() && !settingsFile.delete()) {
154                 settingsFile.deleteOnExit();
155             }
156         }
157     }
158 
159     /**
160      * <p>executeCommandLine.</p>
161      *
162      * @param cl        a {@link org.codehaus.plexus.util.cli.Commandline} object
163      * @param systemIn  a {@link java.io.InputStream} object
164      * @param systemOut a {@link java.io.OutputStream} object
165      * @param systemErr a {@link java.io.OutputStream} object
166      * @return a int
167      * @throws org.codehaus.plexus.util.cli.CommandLineException if any.
168      */
169     public static int executeCommandLine(
170             Commandline cl, InputStream systemIn, OutputStream systemOut, OutputStream systemErr)
171             throws CommandLineException {
172         if (cl == null) {
173             throw new IllegalArgumentException("cl cannot be null.");
174         }
175 
176         Process p = cl.execute();
177 
178         // processes.put( new Long( cl.getPid() ), p );
179 
180         RawStreamPumper inputFeeder = null;
181 
182         if (systemIn != null) {
183             inputFeeder = new RawStreamPumper(systemIn, p.getOutputStream(), true);
184         }
185 
186         RawStreamPumper outputPumper = new RawStreamPumper(p.getInputStream(), systemOut);
187         RawStreamPumper errorPumper = new RawStreamPumper(p.getErrorStream(), systemErr);
188 
189         if (inputFeeder != null) {
190             inputFeeder.start();
191         }
192 
193         outputPumper.start();
194 
195         errorPumper.start();
196 
197         try {
198             int returnValue = p.waitFor();
199 
200             if (inputFeeder != null) {
201                 inputFeeder.setDone();
202             }
203             outputPumper.setDone();
204             errorPumper.setDone();
205 
206             // processes.remove( new Long( cl.getPid() ) );
207 
208             return returnValue;
209         } catch (InterruptedException ex) {
210             // killProcess( cl.getPid() );
211             throw new CommandLineException("Error while executing external command, process killed.", ex);
212         } finally {
213             try {
214                 errorPumper.closeInput();
215             } catch (IOException e) {
216                 // ignore
217             }
218             try {
219                 outputPumper.closeInput();
220             } catch (IOException e) {
221                 // ignore
222             }
223             if (inputFeeder != null) {
224                 try {
225                     inputFeeder.closeOutput();
226                 } catch (IOException e) {
227                     // ignore
228                 }
229             }
230         }
231     }
232 }