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.assembly.utils;
20  
21  import java.util.ArrayList;
22  import java.util.Arrays;
23  import java.util.Collections;
24  import java.util.List;
25  import java.util.ListIterator;
26  import java.util.Properties;
27  
28  import org.apache.maven.artifact.Artifact;
29  import org.apache.maven.execution.MavenSession;
30  import org.apache.maven.plugins.assembly.AssemblerConfigurationSource;
31  import org.apache.maven.plugins.assembly.format.AssemblyFormattingException;
32  import org.apache.maven.plugins.assembly.model.Assembly;
33  import org.apache.maven.project.MavenProject;
34  import org.codehaus.plexus.interpolation.fixed.FixedStringSearchInterpolator;
35  import org.codehaus.plexus.interpolation.fixed.MapBasedValueSource;
36  import org.codehaus.plexus.interpolation.fixed.PrefixedObjectValueSource;
37  import org.codehaus.plexus.interpolation.fixed.PrefixedPropertiesValueSource;
38  import org.codehaus.plexus.interpolation.fixed.PropertiesBasedValueSource;
39  import org.codehaus.plexus.util.Os;
40  import org.codehaus.plexus.util.StringUtils;
41  import org.slf4j.Logger;
42  
43  /**
44   *
45   */
46  public final class AssemblyFormatUtils {
47  
48      private AssemblyFormatUtils() {}
49  
50      /**
51       * Get the full name of the distribution artifact
52       *
53       * @param assembly the assembly
54       * @return the distribution name
55       */
56      public static String getDistributionName(final Assembly assembly, final AssemblerConfigurationSource configSource) {
57          final String finalName = configSource.getFinalName();
58          final boolean appendAssemblyId = configSource.isAssemblyIdAppended();
59  
60          String distributionName = finalName;
61          if (appendAssemblyId) {
62              distributionName = finalName + "-" + assembly.getId();
63          }
64  
65          return distributionName;
66      }
67  
68      public static FixedStringSearchInterpolator finalNameInterpolator(String finalName) {
69          final Properties specialExpressionOverrides = new Properties();
70  
71          if (finalName != null) {
72              specialExpressionOverrides.setProperty("finalName", finalName);
73              specialExpressionOverrides.setProperty("build.finalName", finalName);
74          } else {
75              return FixedStringSearchInterpolator.empty();
76          }
77  
78          return FixedStringSearchInterpolator.create(new PropertiesBasedValueSource(specialExpressionOverrides));
79      }
80  
81      public static FixedStringSearchInterpolator moduleProjectInterpolator(final MavenProject moduleProject) {
82          if (moduleProject != null) {
83              return FixedStringSearchInterpolator.createWithPermittedNulls(
84                      new PrefixedObjectValueSource("module.", moduleProject),
85                      new PrefixedPropertiesValueSource("module.properties.", moduleProject.getProperties()),
86                      moduleProject.getArtifact() != null
87                              ? new PrefixedObjectValueSource("module.", moduleProject.getArtifact())
88                              : null);
89          } else {
90              return FixedStringSearchInterpolator.empty();
91          }
92      }
93  
94      public static FixedStringSearchInterpolator moduleArtifactInterpolator(Artifact moduleArtifact) {
95          if (moduleArtifact != null) {
96              final String groupIdPath = moduleArtifact.getGroupId().replace('.', '/');
97  
98              return FixedStringSearchInterpolator.create(
99                      new MapBasedValueSource(Collections.singletonMap("module.groupIdPath", groupIdPath)),
100                     new PrefixedObjectValueSource("module.", moduleArtifact),
101                     new PrefixedObjectValueSource("module.", moduleArtifact.getArtifactHandler()),
102                     new PrefixedObjectValueSource("module.handler.", moduleArtifact.getArtifactHandler()));
103         } else {
104             return FixedStringSearchInterpolator.empty();
105         }
106     }
107 
108     public static FixedStringSearchInterpolator artifactProjectInterpolator(final MavenProject artifactProject) {
109         if (artifactProject != null) {
110             PrefixedObjectValueSource vs = null;
111             if (artifactProject.getArtifact() != null) {
112                 vs = new PrefixedObjectValueSource("artifact.", artifactProject.getArtifact());
113             }
114 
115             final String groupIdPath = artifactProject.getGroupId().replace('.', '/');
116 
117             return FixedStringSearchInterpolator.createWithPermittedNulls(
118                     new MapBasedValueSource(Collections.singletonMap("artifact.groupIdPath", groupIdPath)),
119                     new PrefixedObjectValueSource("artifact.", artifactProject),
120                     new PrefixedPropertiesValueSource("artifact.properties.", artifactProject.getProperties()),
121                     vs);
122         } else {
123             return FixedStringSearchInterpolator.empty();
124         }
125     }
126 
127     public static FixedStringSearchInterpolator artifactInterpolator(final Artifact artifact) {
128         final String groupIdPath = artifact.getGroupId().replace('.', '/');
129 
130         return FixedStringSearchInterpolator.create(
131                 new MapBasedValueSource(Collections.singletonMap("artifact.groupIdPath", groupIdPath)),
132                 new PrefixedObjectValueSource("artifact.", artifact),
133                 new PrefixedObjectValueSource("artifact.", artifact.getArtifactHandler()),
134                 new PrefixedObjectValueSource("artifact.handler.", artifact.getArtifactHandler()));
135     }
136 
137     public static FixedStringSearchInterpolator classifierRules(final Artifact artifact) {
138         final Properties specialRules = new Properties();
139 
140         final String classifier = ProjectUtils.getClassifier(artifact);
141         if (classifier != null) {
142             specialRules.setProperty("dashClassifier?", "-" + classifier);
143             specialRules.setProperty("dashClassifier", "-" + classifier);
144         } else {
145             specialRules.setProperty("dashClassifier?", "");
146             specialRules.setProperty("dashClassifier", "");
147         }
148 
149         return FixedStringSearchInterpolator.create(new PropertiesBasedValueSource(specialRules));
150     }
151 
152     /**
153      * ORDER OF INTERPOLATION PRECEDENCE:
154      * <ol>
155      * <li>Support for special expressions, like ${finalName} (use the assembly plugin configuration not the build
156      * config)</li>
157      * <li>prefixed with "module." if moduleProject is non-null
158      * <ol>
159      * <li>MavenProject instance for module being assembled</li>
160      * </ol>
161      * </li>
162      * <li>prefixed with "artifact." if artifactProject is non-null
163      * <ol>
164      * <li>MavenProject instance for artifact</li>
165      * </ol>
166      * </li>
167      * <li>user-defined properties from the command line</li>
168      * <li>prefixed with "pom." or "project.", or no prefix at all
169      * <ol>
170      * <li>MavenProject instance from current build</li>
171      * </ol>
172      * </li>
173      * <li>properties from main project</li>
174      * <li>system properties, from the MavenSession instance (to support IDEs)</li>
175      * <li>environment variables.</li>
176      * </ol>
177      */
178     public static String getOutputDirectory(
179             final String output,
180             final MavenProject artifactProject,
181             final String finalName,
182             final AssemblerConfigurationSource configSource)
183             throws AssemblyFormattingException {
184         return getOutputDirectory(
185                 output,
186                 finalName,
187                 configSource,
188                 moduleProjectInterpolator(null),
189                 artifactProjectInterpolator(artifactProject));
190     }
191 
192     private static FixedStringSearchInterpolator executionPropertiesInterpolator(
193             AssemblerConfigurationSource configSource) {
194         MavenSession session;
195 
196         if (configSource != null) {
197             session = configSource.getMavenSession();
198 
199             if (session != null) {
200                 return FixedStringSearchInterpolator.create(
201                         new PropertiesBasedValueSource(session.getUserProperties()),
202                         new PropertiesBasedValueSource(session.getSystemProperties()));
203             }
204         }
205         return FixedStringSearchInterpolator.empty();
206     }
207 
208     private static FixedStringSearchInterpolator mainProjectOnlyInterpolator(MavenProject mainProject) {
209         if (mainProject != null) {
210             // 5
211             return FixedStringSearchInterpolator.create(
212                     new org.codehaus.plexus.interpolation.fixed.PrefixedObjectValueSource(
213                             InterpolationConstants.PROJECT_PREFIXES, mainProject, true));
214         } else {
215             return FixedStringSearchInterpolator.empty();
216         }
217     }
218 
219     /**
220      * ORDER OF INTERPOLATION PRECEDENCE:
221      * <ol>
222      * <li>prefixed with "module.", if moduleProject != null
223      * <ol>
224      * <li>Artifact instance for module, if moduleArtifact != null</li>
225      * <li>ArtifactHandler instance for module, if moduleArtifact != null</li>
226      * <li>MavenProject instance for module</li>
227      * </ol>
228      * </li>
229      * <li>prefixed with "artifact."
230      * <ol>
231      * <li>Artifact instance</li>
232      * <li>ArtifactHandler instance for artifact</li>
233      * <li>MavenProject instance for artifact</li>
234      * </ol>
235      * </li>
236      * <li>prefixed with "pom." or "project."
237      * <ol>
238      * <li>MavenProject instance from current build</li>
239      * </ol>
240      * </li>
241      * <li>no prefix, using main project instance
242      * <ol>
243      * <li>MavenProject instance from current build</li>
244      * </ol>
245      * </li>
246      * <li>Support for special expressions, like ${dashClassifier?}</li>
247      * <li>user-defined properties from the command line</li>
248      * <li>properties from main project</li>
249      * <li>system properties, from the MavenSession instance (to support IDEs)</li>
250      * <li>environment variables.</li>
251      * </ol>
252      */
253     public static String fixRelativeRefs(String src) {
254         String value = src;
255 
256         String[] separators = {"/", "\\"};
257 
258         String finalSep = null;
259         for (String sep : separators) {
260             if (value.endsWith(sep)) {
261                 finalSep = sep;
262             }
263 
264             if (value.contains("." + sep)) {
265                 List<String> parts = new ArrayList<>(Arrays.asList(value.split(sep.replace("\\", "\\\\"))));
266 
267                 for (ListIterator<String> it = parts.listIterator(); it.hasNext(); ) {
268                     String part = it.next();
269                     if (".".equals(part)) {
270                         it.remove();
271                     } else if ("..".equals(part)) {
272                         it.remove();
273                         if (it.hasPrevious()) {
274                             it.previous();
275                             it.remove();
276                         }
277                     }
278                 }
279 
280                 value = StringUtils.join(parts.iterator(), sep);
281             }
282         }
283 
284         if (finalSep != null && value.length() > 0 && !value.endsWith(finalSep)) {
285             value += finalSep;
286         }
287 
288         return value;
289     }
290 
291     /**
292      * ORDER OF INTERPOLATION PRECEDENCE:
293      * <ol>
294      * <li>prefixed with "module.", if moduleProject != null
295      * <ol>
296      * <li>Artifact instance for module, if moduleArtifact != null</li>
297      * <li>ArtifactHandler instance for module, if moduleArtifact != null</li>
298      * <li>MavenProject instance for module</li>
299      * </ol>
300      * </li>
301      * <li>prefixed with "artifact."
302      * <ol>
303      * <li>Artifact instance</li>
304      * <li>ArtifactHandler instance for artifact</li>
305      * <li>MavenProject instance for artifact</li>
306      * </ol>
307      * </li>
308      * <li>prefixed with "pom." or "project."
309      * <ol>
310      * <li>MavenProject instance from current build</li>
311      * </ol>
312      * </li>
313      * <li>no prefix, using main project instance
314      * <ol>
315      * <li>MavenProject instance from current build</li>
316      * </ol>
317      * </li>
318      * <li>Support for special expressions, like ${dashClassifier?}</li>
319      * <li>user-defined properties from the command line</li>
320      * <li>properties from main project</li>
321      * <li>system properties, from the MavenSession instance (to support IDEs)</li>
322      * <li>environment variables.</li>
323      * </ol>
324      */
325     public static String evaluateFileNameMapping(
326             final String expression,
327             final Artifact artifact,
328             /* nullable */ final MavenProject mainProject,
329             /* nullable */ final Artifact moduleArtifact,
330             final AssemblerConfigurationSource configSource,
331             FixedStringSearchInterpolator moduleProjectInterpolator,
332             FixedStringSearchInterpolator artifactProjectInterpolator) {
333         String value = expression;
334 
335         final FixedStringSearchInterpolator interpolator = FixedStringSearchInterpolator.create(
336                 moduleArtifactInterpolator(moduleArtifact),
337                 moduleProjectInterpolator,
338                 artifactInterpolator(artifact),
339                 artifactProjectInterpolator,
340                 mainProjectOnlyInterpolator(mainProject),
341                 classifierRules(artifact),
342                 executionPropertiesInterpolator(configSource),
343                 configSource.getMainProjectInterpolator(),
344                 configSource.getCommandLinePropsInterpolator(),
345                 configSource.getEnvInterpolator());
346 
347         value = interpolator.interpolate(value);
348 
349         value = StringUtils.replace(value, "//", "/");
350         value = StringUtils.replace(value, "\\\\", "\\");
351         value = fixRelativeRefs(value);
352 
353         return value;
354     }
355 
356     /**
357      * ORDER OF INTERPOLATION PRECEDENCE:
358      * <ol>
359      * <li>Support for special expressions, like ${finalName} (use the assembly plugin configuration not the build
360      * config)</li>
361      * <li>prefixed with "module." if moduleProject is non-null
362      * <ol>
363      * <li>MavenProject instance for module being assembled</li>
364      * </ol>
365      * </li>
366      * <li>prefixed with "artifact." if artifactProject is non-null
367      * <ol>
368      * <li>MavenProject instance for artifact</li>
369      * </ol>
370      * </li>
371      * <li>user-defined properties from the command line</li>
372      * <li>prefixed with "pom." or "project.", or no prefix at all
373      * <ol>
374      * <li>MavenProject instance from current build</li>
375      * </ol>
376      * </li>
377      * <li>properties from main project</li>
378      * <li>system properties, from the MavenSession instance (to support IDEs)</li>
379      * <li>environment variables.</li>
380      * </ol>
381      */
382     public static String getOutputDirectory(
383             final String output,
384             final String finalName,
385             final AssemblerConfigurationSource configSource,
386             FixedStringSearchInterpolator moduleProjectIntrpolator,
387             FixedStringSearchInterpolator artifactProjectInterpolator)
388             throws AssemblyFormattingException {
389         String value = output;
390         if (value == null) {
391             value = "";
392         }
393 
394         final FixedStringSearchInterpolator interpolator = FixedStringSearchInterpolator.create(
395                 finalNameInterpolator(finalName),
396                 moduleProjectIntrpolator,
397                 artifactProjectInterpolator,
398                 executionPropertiesInterpolator(configSource),
399                 configSource.getMainProjectInterpolator(),
400                 configSource.getCommandLinePropsInterpolator(),
401                 configSource.getEnvInterpolator());
402 
403         value = interpolator.interpolate(value);
404 
405         if ((value.length() > 0) && !value.endsWith("/") && !value.endsWith("\\")) {
406             value += "/";
407         }
408 
409         if ((value.length() > 0) && (value.startsWith("/") || value.startsWith("\\"))) {
410             value = value.substring(1);
411         }
412 
413         value = StringUtils.replace(value, "//", "/");
414         value = StringUtils.replace(value, "\\\\", "\\");
415         value = fixRelativeRefs(value);
416 
417         return value;
418     }
419 
420     public static void warnForPlatformSpecifics(Logger logger, String destDirectory) {
421         if (Os.isFamily(Os.FAMILY_WINDOWS)) {
422             if (isUnixRootReference(destDirectory)) {
423                 logger.error("OS=Windows and the assembly descriptor contains a *nix-specific root-relative reference"
424                         + " (starting with slash): " + destDirectory);
425             } else if (isWindowsPath(destDirectory)) {
426                 logger.warn("The assembly descriptor contains a Windows-specific directory reference"
427                         + " (with a drive letter). This is not portable and will fail on non-Windows: "
428                         + destDirectory);
429             }
430         } else {
431             if (isWindowsPath(destDirectory)) {
432                 logger.error(
433                         "OS=Non-Windows and the assembly descriptor contains a Windows-specific directory reference"
434                                 + " (with a drive letter): " + destDirectory);
435             } else if (isUnixRootReference(destDirectory)) {
436                 logger.warn("The assembly descriptor contains a *nix-specific root-relative reference"
437                         + " (starting with slash). This is not portable and might fail on Windows: "
438                         + destDirectory);
439             }
440         }
441     }
442 
443     static boolean isWindowsPath(String destDirectory) {
444         return (destDirectory != null && destDirectory.length() >= 2 && destDirectory.charAt(1) == ':');
445     }
446 
447     static boolean isUnixRootReference(String destDirectory) {
448         return (destDirectory != null && destDirectory.startsWith("/"));
449     }
450 }