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.plugins.jlink;
20  
21  /*
22   * Licensed to the Apache Software Foundation (ASF) under one
23   * or more contributor license agreements.  See the NOTICE file
24   * distributed with this work for additional information
25   * regarding copyright ownership.  The ASF licenses this file
26   * to you under the Apache License, Version 2.0 (the
27   * "License"); you may not use this file except in compliance
28   * with the License.  You may obtain a copy of the License at
29   *
30   *   http://www.apache.org/licenses/LICENSE-2.0
31   *
32   * Unless required by applicable law or agreed to in writing,
33   * software distributed under the License is distributed on an
34   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
35   * KIND, either express or implied.  See the License for the
36   * specific language governing permissions and limitations
37   * under the License.
38   */
39  
40  import java.io.File;
41  import java.util.List;
42  import java.util.NoSuchElementException;
43  import java.util.Optional;
44  import java.util.stream.Collectors;
45  
46  import org.apache.maven.plugin.MojoExecutionException;
47  import org.apache.maven.plugin.logging.Log;
48  import org.apache.maven.shared.utils.cli.CommandLineException;
49  import org.apache.maven.shared.utils.cli.CommandLineUtils;
50  import org.apache.maven.shared.utils.cli.Commandline;
51  import org.apache.maven.toolchain.Toolchain;
52  
53  abstract class AbstractJLinkToolchainExecutor extends AbstractJLinkExecutor {
54      private final Toolchain toolchain;
55  
56      AbstractJLinkToolchainExecutor(Toolchain toolchain, Log log) {
57          super(log);
58          this.toolchain = toolchain;
59      }
60  
61      protected Optional<Toolchain> getToolchain() {
62          return Optional.ofNullable(this.toolchain);
63      }
64  
65      /**
66       * Execute JLink via toolchain.
67       *
68       * @return the exit code ({@code 0} on success)
69       */
70      @Override
71      public int executeJlink(List<String> jlinkArgs) throws MojoExecutionException {
72          File jlinkExecutable = getJlinkExecutable();
73          getLog().info("Toolchain in maven-jlink-plugin: jlink [ " + jlinkExecutable + " ]");
74          Commandline cmd = createJLinkCommandLine(jlinkExecutable, jlinkArgs);
75  
76          return executeCommand(cmd);
77      }
78  
79      private File getJlinkExecutable() {
80          return new File(getJLinkExecutable());
81      }
82  
83      @Override
84      public Optional<File> getJmodsFolder(/* nullable */ File sourceJdkModules) {
85          // Really Hacky...do we have a better solution to find the jmods directory of the JDK?
86          File jLinkParent = getJlinkExecutable().getParentFile().getParentFile();
87          File jmodsFolder;
88          if (sourceJdkModules != null && sourceJdkModules.isDirectory()) {
89              jmodsFolder = new File(sourceJdkModules, JMODS);
90          } else {
91              jmodsFolder = new File(jLinkParent, JMODS);
92          }
93  
94          getLog().debug(" Parent: " + jLinkParent.getAbsolutePath());
95          getLog().debug(" jmodsFolder: " + jmodsFolder.getAbsolutePath());
96  
97          return Optional.of(jmodsFolder);
98      }
99  
100     static Commandline createJLinkCommandLine(File jlinkExecutable, List<String> jlinkArgs) {
101         Commandline cmd = new Commandline();
102         // Don't quote every argument with single quote, but instead quote them with double quotes
103         // and enclose all of them with single quotes to then be passed to the shell command as
104         // /bin/sh -c '<all arguments, each one quoted with double quotes>'
105         cmd.getShell().setQuotedArgumentsEnabled(false);
106 
107         String jlinkArgsStr = jlinkArgs.stream().map(arg -> "\"" + arg + "\"").collect(Collectors.joining(" "));
108         cmd.setExecutable(jlinkExecutable.getAbsolutePath() + " " + jlinkArgsStr);
109         return cmd;
110     }
111 
112     private String getJLinkExecutable() {
113         Optional<Toolchain> toolchain = getToolchain();
114 
115         if (!toolchain.isPresent()) {
116             getLog().error("Either JDK9+ or a toolchain "
117                     + "pointing to a JDK9+ containing a jlink binary is required.");
118             getLog().info("See https://maven.apache.org/guides/mini/guide-using-toolchains.html "
119                     + "for mor information.");
120             throw new IllegalStateException("Running on JDK8 and no toolchain found.");
121         }
122 
123         String jLinkExecutable =
124                 toolchain.orElseThrow(NoSuchElementException::new).findTool("jlink");
125 
126         if (jLinkExecutable.isEmpty()) {
127             throw new IllegalStateException(
128                     "The jlink executable '" + jLinkExecutable + "' doesn't exist or is not a file.");
129         }
130 
131         // TODO: Check if there exist a more elegant way?
132         String jLinkCommand = "jlink" + (isOSWindows() ? ".exe" : "");
133 
134         File jLinkExe = new File(jLinkExecutable);
135 
136         if (jLinkExe.isDirectory()) {
137             jLinkExe = new File(jLinkExe, jLinkCommand);
138         }
139 
140         if (isOSWindows() && jLinkExe.getName().indexOf('.') < 0) {
141             jLinkExe = new File(jLinkExe.getPath() + ".exe");
142         }
143 
144         if (!jLinkExe.isFile()) {
145             throw new IllegalStateException("The jlink executable '" + jLinkExe + "' doesn't exist or is not a file.");
146         }
147         return jLinkExe.getAbsolutePath();
148     }
149 
150     private int executeCommand(Commandline cmd) throws MojoExecutionException {
151         if (getLog().isDebugEnabled()) {
152             // no quoted arguments ???
153             getLog().debug(CommandLineUtils.toString(cmd.getCommandline()));
154         }
155 
156         CommandLineUtils.StringStreamConsumer err = new CommandLineUtils.StringStreamConsumer();
157         CommandLineUtils.StringStreamConsumer out = new CommandLineUtils.StringStreamConsumer();
158         try {
159             int exitCode = CommandLineUtils.executeCommandLine(cmd, out, err);
160 
161             String output = out.getOutput().trim();
162             output = output.isEmpty() ? null : '\n' + output;
163 
164             if (exitCode != 0) {
165 
166                 if (output != null && !output.isEmpty()) {
167                     // Reconsider to use WARN / ERROR ?
168                     //  getLog().error( output );
169                     for (String outputLine : output.split("\n")) {
170                         getLog().error(outputLine);
171                     }
172                 }
173 
174                 StringBuilder msg = new StringBuilder("\nExit code: ");
175                 msg.append(exitCode);
176                 if (!err.getOutput().trim().isEmpty()) {
177                     msg.append(" - ").append(err.getOutput());
178                 }
179                 msg.append('\n');
180                 msg.append("Command line was: ").append(cmd).append('\n').append('\n');
181 
182                 throw new MojoExecutionException(msg.toString());
183             }
184 
185             if (output != null && !output.isEmpty()) {
186                 // getLog().info( output );
187                 for (String outputLine : output.split("\n")) {
188                     getLog().info(outputLine);
189                 }
190             }
191 
192             return exitCode;
193         } catch (CommandLineException e) {
194             throw new MojoExecutionException("Unable to execute jlink command: " + e.getMessage(), e);
195         }
196     }
197 
198     private static boolean isOSWindows() {
199         try {
200             String osName = System.getProperty("os.name");
201             if (osName == null) {
202                 return false;
203             }
204             return osName.startsWith("Windows");
205         } catch (final SecurityException ex) {
206             // we are not allowed to look at this property
207             return false;
208         }
209     }
210 }