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                 Properties userProperties = session.getExecutionProperties(); // this is added twice....
201 
202                 if (userProperties != null) {
203                     return FixedStringSearchInterpolator.create(new PropertiesBasedValueSource(userProperties));
204                 }
205             }
206         }
207         return FixedStringSearchInterpolator.empty();
208     }
209 
210     private static FixedStringSearchInterpolator mainProjectOnlyInterpolator(MavenProject mainProject) {
211         if (mainProject != null) {
212             // 5
213             return FixedStringSearchInterpolator.create(
214                     new org.codehaus.plexus.interpolation.fixed.PrefixedObjectValueSource(
215                             InterpolationConstants.PROJECT_PREFIXES, mainProject, true));
216         } else {
217             return FixedStringSearchInterpolator.empty();
218         }
219     }
220 
221     /**
222      * ORDER OF INTERPOLATION PRECEDENCE:
223      * <ol>
224      * <li>prefixed with "module.", if moduleProject != null
225      * <ol>
226      * <li>Artifact instance for module, if moduleArtifact != null</li>
227      * <li>ArtifactHandler instance for module, if moduleArtifact != null</li>
228      * <li>MavenProject instance for module</li>
229      * </ol>
230      * </li>
231      * <li>prefixed with "artifact."
232      * <ol>
233      * <li>Artifact instance</li>
234      * <li>ArtifactHandler instance for artifact</li>
235      * <li>MavenProject instance for artifact</li>
236      * </ol>
237      * </li>
238      * <li>prefixed with "pom." or "project."
239      * <ol>
240      * <li>MavenProject instance from current build</li>
241      * </ol>
242      * </li>
243      * <li>no prefix, using main project instance
244      * <ol>
245      * <li>MavenProject instance from current build</li>
246      * </ol>
247      * </li>
248      * <li>Support for special expressions, like ${dashClassifier?}</li>
249      * <li>user-defined properties from the command line</li>
250      * <li>properties from main project</li>
251      * <li>system properties, from the MavenSession instance (to support IDEs)</li>
252      * <li>environment variables.</li>
253      * </ol>
254      */
255     public static String fixRelativeRefs(String src) {
256         String value = src;
257 
258         String[] separators = {"/", "\\"};
259 
260         String finalSep = null;
261         for (String sep : separators) {
262             if (value.endsWith(sep)) {
263                 finalSep = sep;
264             }
265 
266             if (value.contains("." + sep)) {
267                 List<String> parts = new ArrayList<>();
268                 parts.addAll(Arrays.asList(value.split(sep.replace("\\", "\\\\"))));
269 
270                 for (ListIterator<String> it = parts.listIterator(); it.hasNext(); ) {
271                     String part = it.next();
272                     if (".".equals(part)) {
273                         it.remove();
274                     } else if ("..".equals(part)) {
275                         it.remove();
276                         if (it.hasPrevious()) {
277                             it.previous();
278                             it.remove();
279                         }
280                     }
281                 }
282 
283                 value = StringUtils.join(parts.iterator(), sep);
284             }
285         }
286 
287         if (finalSep != null && value.length() > 0 && !value.endsWith(finalSep)) {
288             value += finalSep;
289         }
290 
291         return value;
292     }
293 
294     /**
295      * ORDER OF INTERPOLATION PRECEDENCE:
296      * <ol>
297      * <li>prefixed with "module.", if moduleProject != null
298      * <ol>
299      * <li>Artifact instance for module, if moduleArtifact != null</li>
300      * <li>ArtifactHandler instance for module, if moduleArtifact != null</li>
301      * <li>MavenProject instance for module</li>
302      * </ol>
303      * </li>
304      * <li>prefixed with "artifact."
305      * <ol>
306      * <li>Artifact instance</li>
307      * <li>ArtifactHandler instance for artifact</li>
308      * <li>MavenProject instance for artifact</li>
309      * </ol>
310      * </li>
311      * <li>prefixed with "pom." or "project."
312      * <ol>
313      * <li>MavenProject instance from current build</li>
314      * </ol>
315      * </li>
316      * <li>no prefix, using main project instance
317      * <ol>
318      * <li>MavenProject instance from current build</li>
319      * </ol>
320      * </li>
321      * <li>Support for special expressions, like ${dashClassifier?}</li>
322      * <li>user-defined properties from the command line</li>
323      * <li>properties from main project</li>
324      * <li>system properties, from the MavenSession instance (to support IDEs)</li>
325      * <li>environment variables.</li>
326      * </ol>
327      */
328     public static String evaluateFileNameMapping(
329             final String expression,
330             final Artifact artifact,
331             /* nullable */ final MavenProject mainProject,
332             /* nullable */ final Artifact moduleArtifact,
333             final AssemblerConfigurationSource configSource,
334             FixedStringSearchInterpolator moduleProjectInterpolator,
335             FixedStringSearchInterpolator artifactProjectInterpolator) {
336         String value = expression;
337 
338         final FixedStringSearchInterpolator interpolator = FixedStringSearchInterpolator.create(
339                 moduleArtifactInterpolator(moduleArtifact),
340                 moduleProjectInterpolator,
341                 artifactInterpolator(artifact),
342                 artifactProjectInterpolator,
343                 mainProjectOnlyInterpolator(mainProject),
344                 classifierRules(artifact),
345                 executionPropertiesInterpolator(configSource),
346                 configSource.getMainProjectInterpolator(),
347                 configSource.getCommandLinePropsInterpolator(),
348                 configSource.getEnvInterpolator());
349 
350         value = interpolator.interpolate(value);
351 
352         value = StringUtils.replace(value, "//", "/");
353         value = StringUtils.replace(value, "\\\\", "\\");
354         value = fixRelativeRefs(value);
355 
356         return value;
357     }
358 
359     /**
360      * ORDER OF INTERPOLATION PRECEDENCE:
361      * <ol>
362      * <li>Support for special expressions, like ${finalName} (use the assembly plugin configuration not the build
363      * config)</li>
364      * <li>prefixed with "module." if moduleProject is non-null
365      * <ol>
366      * <li>MavenProject instance for module being assembled</li>
367      * </ol>
368      * </li>
369      * <li>prefixed with "artifact." if artifactProject is non-null
370      * <ol>
371      * <li>MavenProject instance for artifact</li>
372      * </ol>
373      * </li>
374      * <li>user-defined properties from the command line</li>
375      * <li>prefixed with "pom." or "project.", or no prefix at all
376      * <ol>
377      * <li>MavenProject instance from current build</li>
378      * </ol>
379      * </li>
380      * <li>properties from main project</li>
381      * <li>system properties, from the MavenSession instance (to support IDEs)</li>
382      * <li>environment variables.</li>
383      * </ol>
384      */
385     public static String getOutputDirectory(
386             final String output,
387             final String finalName,
388             final AssemblerConfigurationSource configSource,
389             FixedStringSearchInterpolator moduleProjectIntrpolator,
390             FixedStringSearchInterpolator artifactProjectInterpolator)
391             throws AssemblyFormattingException {
392         String value = output;
393         if (value == null) {
394             value = "";
395         }
396 
397         final FixedStringSearchInterpolator interpolator = FixedStringSearchInterpolator.create(
398                 finalNameInterpolator(finalName),
399                 moduleProjectIntrpolator,
400                 artifactProjectInterpolator,
401                 executionPropertiesInterpolator(configSource),
402                 configSource.getMainProjectInterpolator(),
403                 configSource.getCommandLinePropsInterpolator(),
404                 configSource.getEnvInterpolator());
405 
406         value = interpolator.interpolate(value);
407 
408         if ((value.length() > 0) && !value.endsWith("/") && !value.endsWith("\\")) {
409             value += "/";
410         }
411 
412         if ((value.length() > 0) && (value.startsWith("/") || value.startsWith("\\"))) {
413             value = value.substring(1);
414         }
415 
416         value = StringUtils.replace(value, "//", "/");
417         value = StringUtils.replace(value, "\\\\", "\\");
418         value = fixRelativeRefs(value);
419 
420         return value;
421     }
422 
423     public static void warnForPlatformSpecifics(Logger logger, String destDirectory) {
424         if (Os.isFamily(Os.FAMILY_WINDOWS)) {
425             if (isUnixRootReference(destDirectory)) {
426                 logger.error("OS=Windows and the assembly descriptor contains a *nix-specific root-relative reference"
427                         + " (starting with slash): " + destDirectory);
428             } else if (isWindowsPath(destDirectory)) {
429                 logger.warn("The assembly descriptor contains a Windows-specific directory reference"
430                         + " (with a drive letter). This is not portable and will fail on non-Windows: "
431                         + destDirectory);
432             }
433         } else {
434             if (isWindowsPath(destDirectory)) {
435                 logger.error(
436                         "OS=Non-Windows and the assembly descriptor contains a Windows-specific directory reference"
437                                 + " (with a drive letter): " + destDirectory);
438             } else if (isUnixRootReference(destDirectory)) {
439                 logger.warn("The assembly descriptor contains a *nix-specific root-relative reference"
440                         + " (starting with slash). This is not portable and might fail on Windows: "
441                         + destDirectory);
442             }
443         }
444     }
445 
446     static boolean isWindowsPath(String destDirectory) {
447         return (destDirectory != null && destDirectory.length() >= 2 && destDirectory.charAt(1) == ':');
448     }
449 
450     static boolean isUnixRootReference(String destDirectory) {
451         return (destDirectory != null && destDirectory.startsWith("/"));
452     }
453 }