View Javadoc

1   /*
2    * #%L
3    * Native ARchive plugin for Maven
4    * %%
5    * Copyright (C) 2002 - 2014 NAR Maven Plugin developers.
6    * %%
7    * Licensed under the Apache License, Version 2.0 (the "License");
8    * you may not use this file except in compliance with the License.
9    * You may obtain a copy of the License at
10   * 
11   * http://www.apache.org/licenses/LICENSE-2.0
12   * 
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the License is distributed on an "AS IS" BASIS,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the License for the specific language governing permissions and
17   * limitations under the License.
18   * #L%
19   */
20  package com.github.maven_nar;
21  
22  import java.io.File;
23  import java.io.IOException;
24  import java.util.ArrayList;
25  import java.util.Arrays;
26  import java.util.HashSet;
27  import java.util.List;
28  import java.util.Set;
29  
30  import org.apache.maven.plugin.MojoExecutionException;
31  import org.apache.maven.plugin.MojoFailureException;
32  import org.apache.maven.plugins.annotations.Parameter;
33  import org.apache.maven.project.MavenProject;
34  import org.codehaus.plexus.util.StringUtils;
35  
36  import com.github.maven_nar.cpptasks.CUtil;
37  import com.github.maven_nar.cpptasks.CompilerDef;
38  import com.github.maven_nar.cpptasks.CompilerEnum;
39  import com.github.maven_nar.cpptasks.OptimizationEnum;
40  import com.github.maven_nar.cpptasks.types.CompilerArgument;
41  import com.github.maven_nar.cpptasks.types.ConditionalFileSet;
42  import com.github.maven_nar.cpptasks.types.DefineArgument;
43  import com.github.maven_nar.cpptasks.types.DefineSet;
44  
45  /**
46   * Abstract Compiler class
47   *
48   * @author Mark Donszelmann
49   */
50  public abstract class Compiler {
51  
52    public static final String MAIN = "main";
53  
54    public static final String TEST = "test";
55  
56    /**
57     * The name of the compiler. Some choices are: "msvc", "g++", "gcc", "CC",
58     * "cc", "icc", "icpc", ... Default is
59     * Architecture-OS-Linker specific: FIXME: table missing
60     */
61    @Parameter
62    private String name;
63  
64    /**
65     * The prefix for the compiler.
66     */
67    @Parameter
68    private String prefix;
69  
70    /**
71     * Path location of the compile tool
72     */
73    @Parameter
74    private String toolPath;
75  
76    /**
77     * Source directory for native files
78     */
79    @Parameter(defaultValue = "${basedir}/src/main", required = true)
80    private File sourceDirectory;
81  
82    /**
83     * Source directory for native test files
84     */
85    @Parameter(defaultValue = "${basedir}/src/test", required = true)
86    private File testSourceDirectory;
87  
88    /**
89     * Include patterns for sources
90     */
91    @Parameter(required = true)
92    private Set<String> includes = new HashSet<>();
93  
94    /**
95     * Exclude patterns for sources
96     */
97    @Parameter(required = true)
98    private Set<String> excludes = new HashSet<>();
99  
100   /**
101    * Include patterns for test sources
102    */
103   @Parameter(required = true)
104   private Set<String> testIncludes = new HashSet<>();
105 
106   /**
107    * Exclude patterns for test sources
108    */
109   @Parameter(required = true)
110   private Set<String> testExcludes = new HashSet<>();
111 
112   @Parameter(defaultValue = "false", required = false)
113   private boolean ccache = false;
114 
115   /**
116    * Compile with debug information.
117    */
118   @Parameter(required = true)
119   private boolean debug = false;
120 
121   /**
122    * Enables generation of exception handling code.
123    */
124   @Parameter(defaultValue = "true", required = true)
125   private boolean exceptions = true;
126 
127   /**
128    * Enables run-time type information.
129    */
130   @Parameter(defaultValue = "true", required = true)
131   private boolean rtti = true;
132 
133   /**
134    * Sets optimization. Possible choices are: "none", "size", "minimal",
135    * "speed", "full", "aggressive", "extreme",
136    * "unsafe".
137    */
138   @Parameter(defaultValue = "none", required = true)
139   private String optimize = "none";
140 
141   /**
142    * Enables or disables generation of multi-threaded code. Default value:
143    * false, except on Windows.
144    */
145   @Parameter(required = true)
146   private boolean multiThreaded = false;
147 
148   /**
149    * Defines
150    */
151   @Parameter
152   private List<String> defines;
153 
154   /**
155    * Defines for the compiler as a comma separated list of name[=value] pairs,
156    * where the value is optional. Will work
157    * in combination with &lt;defines&gt;.
158    */
159   @Parameter
160   private String defineSet;
161 
162   /**
163    * Clears default defines
164    */
165   @Parameter(required = true)
166   private boolean clearDefaultDefines;
167 
168   /**
169    * Undefines
170    */
171   @Parameter
172   private List<String> undefines;
173 
174   /**
175    * Undefines for the compiler as a comma separated list of name[=value] pairs
176    * where the value is optional. Will work
177    * in combination with &lt;undefines&gt;.
178    */
179   @Parameter
180   private String undefineSet;
181 
182   /**
183    * Clears default undefines
184    */
185   @Parameter
186   private boolean clearDefaultUndefines;
187 
188   /**
189    * Include Paths. Defaults to "${sourceDirectory}/include"
190    */
191   @Parameter
192   private List<IncludePath> includePaths;
193 
194   /**
195    * Test Include Paths. Defaults to "${testSourceDirectory}/include"
196    */
197   @Parameter
198   private List<IncludePath> testIncludePaths;
199 
200   /**
201    * System Include Paths, which are added at the end of all include paths
202    */
203   @Parameter
204   private List<String> systemIncludePaths;
205 
206   /**
207    * Additional options for the C++ compiler Defaults to Architecture-OS-Linker
208    * specific values. FIXME table missing
209    */
210   @Parameter
211   private List<String> options;
212 
213   /**
214    * Additional options for the compiler when running in the nar-testCompile
215    * phase.
216    */
217   @Parameter
218   private List<String> testOptions;
219 
220   /**
221    * Options for the compiler as a whitespace separated list. Will work in
222    * combination with &lt;options&gt;.
223    */
224   @Parameter
225   private String optionSet;
226 
227   /**
228    * Clears default options
229    */
230   @Parameter(required = true)
231   private boolean clearDefaultOptions;
232 
233   /**
234    * Comma separated list of filenames to compile in order
235    */
236   @Parameter
237   private String compileOrder;
238   private AbstractCompileMojo mojo;
239 
240   protected Compiler() {
241   }
242 
243   /**
244    * Filter elements such as cr\lf that are problematic when used inside a `define`
245    *  
246    * @param value  define value to be cleaned
247    * @return
248    */
249   private String cleanDefineValue(final String value) {
250     return value.replaceAll("\r", "").replaceAll("\n", ""); // ?maybe replace with chars \\n
251   }
252   
253   public final void copyIncludeFiles(final MavenProject mavenProject, final File targetDirectory) throws IOException {
254     for (final IncludePath includePath : getIncludePaths("dummy")) {
255       if (includePath.exists()) {
256         NarUtil.copyDirectoryStructure(includePath.getFile(), targetDirectory, includePath.getIncludes(),
257             NarUtil.DEFAULT_EXCLUDES);
258       }
259     }
260   }
261 
262   /**
263    * Generates a new {@link CompilerDef} and populates it give the parameters
264    * provided.
265    * 
266    * @param type
267    *          - main or test library - used to determine include and exclude
268    *          paths.
269    * @param output
270    *          - TODO Not sure..
271    * @return {@link CompilerDef} which contains the configuration for this
272    *         compiler given the type and output.
273    * @throws MojoFailureException
274    *           TODO
275    * @throws MojoExecutionException
276    *           TODO
277    */
278   public final CompilerDef getCompiler(final String type, final String output)
279       throws MojoFailureException, MojoExecutionException {
280     final String name = getName();
281     if (name == null) {
282       return null;
283     }
284 
285     final CompilerDef compilerDef = new CompilerDef();
286     compilerDef.setProject(this.mojo.getAntProject());
287     final CompilerEnum compilerName = new CompilerEnum();
288     compilerName.setValue(name);
289     compilerDef.setName(compilerName);
290 
291     // tool path
292     if (this.toolPath != null) {
293       compilerDef.setToolPath(this.toolPath);
294     } else if ("msvc".equalsIgnoreCase( this.mojo.getLinker().getName())){
295       mojo.getMsvc().setToolPath(compilerDef,getLanguage());
296     }
297 
298     // debug, exceptions, rtti, multiThreaded
299     compilerDef.setCompilerPrefix(this.prefix);
300     compilerDef.setCcache(this.ccache);
301     compilerDef.setDebug(this.debug);
302     compilerDef.setExceptions(this.exceptions);
303     compilerDef.setRtti(this.rtti);
304     compilerDef.setMultithreaded(this.mojo.getOS().equals("Windows") || this.multiThreaded);
305 
306     // optimize
307     final OptimizationEnum optimization = new OptimizationEnum();
308     optimization.setValue(this.optimize);
309     compilerDef.setOptimize(optimization);
310 
311     // add options
312     if (this.options != null) {
313       for (final String string : this.options) {
314         final CompilerArgument arg = new CompilerArgument();
315         arg.setValue(string);
316         compilerDef.addConfiguredCompilerArg(arg);
317       }
318     }
319 
320     if (this.optionSet != null) {
321 
322       final String[] opts = this.optionSet.split("\\s");
323 
324       for (final String opt : opts) {
325 
326         final CompilerArgument arg = new CompilerArgument();
327 
328         arg.setValue(opt);
329         compilerDef.addConfiguredCompilerArg(arg);
330       }
331     }
332 
333     compilerDef.setClearDefaultOptions(this.clearDefaultOptions);
334     if (!this.clearDefaultOptions) {
335       final String optionsProperty = NarProperties.getInstance(this.mojo.getMavenProject()).getProperty(
336           getPrefix() + "options");
337       if (optionsProperty != null) {
338         final String[] option = optionsProperty.split(" ");
339         for (final String element : option) {
340           final CompilerArgument arg = new CompilerArgument();
341           arg.setValue(element);
342           compilerDef.addConfiguredCompilerArg(arg);
343         }
344       }
345     }
346 
347     // add defines
348     if (this.defines != null) {
349       final DefineSet ds = new DefineSet();
350       for (final String string : this.defines) {
351         final DefineArgument define = new DefineArgument();
352         final String[] pair = string.split("=", 2);
353         define.setName(pair[0]);
354         define.setValue(pair.length > 1 ? cleanDefineValue(pair[1]) : null);
355         ds.addDefine(define);
356       }
357       compilerDef.addConfiguredDefineset(ds);
358     }
359 
360     if (this.defineSet != null) {
361 
362       final String[] defList = this.defineSet.split(",");
363       final DefineSet defSet = new DefineSet();
364 
365       for (final String element : defList) {
366 
367         final String[] pair = element.trim().split("=", 2);
368         final DefineArgument def = new DefineArgument();
369 
370         def.setName(pair[0]);
371         def.setValue(pair.length > 1 ? cleanDefineValue(pair[1]) : null);
372 
373         defSet.addDefine(def);
374       }
375 
376       compilerDef.addConfiguredDefineset(defSet);
377     }
378 
379     if (!this.clearDefaultDefines) {
380       final DefineSet ds = new DefineSet();
381       final String defaultDefines = NarProperties.getInstance(this.mojo.getMavenProject()).getProperty(
382           getPrefix() + "defines");
383       if (defaultDefines != null) {
384         ds.setDefine(new CUtil.StringArrayBuilder(defaultDefines));
385       }
386       compilerDef.addConfiguredDefineset(ds);
387     }
388 
389     // add undefines
390     if (this.undefines != null) {
391       final DefineSet us = new DefineSet();
392       for (final String string : this.undefines) {
393         final DefineArgument undefine = new DefineArgument();
394         final String[] pair = string.split("=", 2);
395         undefine.setName(pair[0]);
396         undefine.setValue(pair.length > 1 ? pair[1] : null);
397         us.addUndefine(undefine);
398       }
399       compilerDef.addConfiguredDefineset(us);
400     }
401 
402     if (this.undefineSet != null) {
403 
404       final String[] undefList = this.undefineSet.split(",");
405       final DefineSet undefSet = new DefineSet();
406 
407       for (final String element : undefList) {
408 
409         final String[] pair = element.trim().split("=", 2);
410         final DefineArgument undef = new DefineArgument();
411 
412         undef.setName(pair[0]);
413         undef.setValue(pair.length > 1 ? pair[1] : null);
414 
415         undefSet.addUndefine(undef);
416       }
417 
418       compilerDef.addConfiguredDefineset(undefSet);
419     }
420 
421     if (!this.clearDefaultUndefines) {
422       final DefineSet us = new DefineSet();
423       final String defaultUndefines = NarProperties.getInstance(this.mojo.getMavenProject()).getProperty(
424           getPrefix() + "undefines");
425       if (defaultUndefines != null) {
426         us.setUndefine(new CUtil.StringArrayBuilder(defaultUndefines));
427       }
428       compilerDef.addConfiguredDefineset(us);
429     }
430 
431     // add include path
432     for (final IncludePath includePath : getIncludePaths(type)) {
433       // Darren Sargent, 30Jan2008 - fail build if invalid include path(s)
434       // specified.
435       if (!includePath.exists()) {
436         throw new MojoFailureException("NAR: Include path not found: " + includePath);
437       }
438       compilerDef.createIncludePath().setPath(includePath.getPath());
439     }
440 
441     // add system include path (at the end)
442     if (this.systemIncludePaths != null) {
443       for (final String path : this.systemIncludePaths) {
444         compilerDef.createSysIncludePath().setPath(path);
445       }
446     }
447 
448     // Add default fileset (if exists)
449     final List<File> srcDirs = getSourceDirectories(type);
450     final Set<String> includeSet = getIncludes(type);
451     final Set<String> excludeSet = getExcludes(type);
452 
453     // now add all but the current test to the excludes
454     for (final Object o : this.mojo.getTests()) {
455       final Test test = (Test) o;
456       if (!test.getName().equals(output)) {
457         excludeSet.add("**/" + test.getName() + ".*");
458       }
459     }
460 
461     for (final File srcDir : srcDirs) {
462       this.mojo.getLog().debug("Checking for existence of " + getLanguage() + " source directory: " + srcDir);
463       if (srcDir.exists()) {
464         if (this.compileOrder != null) {
465           compilerDef.setOrder(Arrays.asList(StringUtils.split(this.compileOrder, ", ")));
466         }
467 
468         final ConditionalFileSet fileSet = new ConditionalFileSet();
469         fileSet.setProject(this.mojo.getAntProject());
470         fileSet.setIncludes(StringUtils.join(includeSet.iterator(), ","));
471         fileSet.setExcludes(StringUtils.join(excludeSet.iterator(), ","));
472         fileSet.setDir(srcDir);
473         compilerDef.addFileset(fileSet);
474       }
475     }
476     
477     if (type.equals(TEST)) {
478       if (this.testSourceDirectory.exists()) {
479         compilerDef.setWorkDir(this.testSourceDirectory);
480       }
481     } else {
482       if (this.sourceDirectory.exists()) {
483         compilerDef.setWorkDir(this.sourceDirectory);
484       }
485     }
486     return compilerDef;
487   }
488 
489   public final Set<String> getExcludes() throws MojoFailureException, MojoExecutionException {
490     return getExcludes("main");
491   }
492 
493   protected final Set<String> getExcludes(final String type) throws MojoFailureException, MojoExecutionException {
494     final Set<String> result = new HashSet<>();
495     if (type.equals(TEST) && !this.testExcludes.isEmpty()) {
496       result.addAll(this.testExcludes);
497     } else if (!this.excludes.isEmpty()) {
498       result.addAll(this.excludes);
499     } else {
500       final String defaultExcludes = NarProperties.getInstance(this.mojo.getMavenProject()).getProperty(
501           getPrefix() + "excludes");
502       if (defaultExcludes != null) {
503         final String[] exclude = defaultExcludes.split(" ");
504         for (final String element : exclude) {
505           result.add(element.trim());
506         }
507       }
508     }
509 
510     return result;
511   }
512 
513   protected final List<IncludePath> getIncludePaths(final String type) {
514     List<IncludePath> includeList = type.equals(TEST) ? this.testIncludePaths : this.includePaths;
515 
516     if (includeList != null && includeList.size() != 0) {
517       return includeList;
518     }
519 
520     includeList = new ArrayList<>();
521     for (final File file2 : getSourceDirectories(type)) {
522       // VR 20100318 only add include directories that exist - we now fail the
523       // build fast if an include directory does not exist
524       final File file = new File(file2, "include");
525       if (file.isDirectory()) {
526         final IncludePath includePath = new IncludePath();
527         includePath.setPath(file.getPath());
528         includeList.add(includePath);
529       }
530     }
531     return includeList;
532   }
533 
534   public final Set<String> getIncludes() throws MojoFailureException, MojoExecutionException {
535     return getIncludes("main");
536   }
537 
538   protected final Set<String> getIncludes(final String type) throws MojoFailureException, MojoExecutionException {
539     final Set<String> result = new HashSet<>();
540     if (!type.equals(TEST) && !this.includes.isEmpty()) {
541       result.addAll(this.includes);
542     } else if (type.equals(TEST) && !this.testIncludes.isEmpty()) {
543       result.addAll(this.testIncludes);
544     } else {
545       final String defaultIncludes = NarProperties.getInstance(this.mojo.getMavenProject()).getProperty(
546           getPrefix() + "includes");
547       if (defaultIncludes != null) {
548         final String[] include = defaultIncludes.split(" ");
549         for (final String element : include) {
550           result.add(element.trim());
551         }
552       }
553     }
554     return result;
555   }
556 
557   protected abstract String getLanguage();
558 
559   public String getName() throws MojoFailureException, MojoExecutionException {
560     // adjust default values
561     if (this.name == null) {
562       this.name = NarProperties.getInstance(this.mojo.getMavenProject()).getProperty(getPrefix() + "compiler");
563     }
564     if (this.prefix == null) {
565       this.prefix = NarProperties.getInstance(this.mojo.getMavenProject()).getProperty(getPrefix() + "prefix");
566     }
567     return this.name;
568   }
569 
570   protected final String getPrefix() throws MojoFailureException, MojoExecutionException {
571     return this.mojo.getAOL().getKey() + "." + getLanguage() + ".";
572   }
573 
574   public final List<File> getSourceDirectories() {
575     return getSourceDirectories("dummy");
576   }
577 
578   private List<File> getSourceDirectories(final String type) {
579     final List<File> sourceDirectories = new ArrayList<>();
580     final File baseDir = this.mojo.getMavenProject().getBasedir();
581 
582     if (type.equals(TEST)) {
583       if (this.testSourceDirectory == null) {
584         this.testSourceDirectory = new File(baseDir, "/src/test");
585       }
586       if (this.testSourceDirectory.exists()) {
587         sourceDirectories.add(this.testSourceDirectory);
588       }
589 
590       for (final Object element : this.mojo.getMavenProject().getTestCompileSourceRoots()) {
591         final File extraTestSourceDirectory = new File((String) element);
592         if (extraTestSourceDirectory.exists()) {
593           sourceDirectories.add(extraTestSourceDirectory);
594         }
595       }
596     } else {
597       if (this.sourceDirectory == null) {
598         this.sourceDirectory = new File(baseDir, "src/main");
599       }
600       if (this.sourceDirectory.exists()) {
601         sourceDirectories.add(this.sourceDirectory);
602       }
603 
604       for (final Object element : this.mojo.getMavenProject().getCompileSourceRoots()) {
605         final File extraSourceDirectory = new File((String) element);
606         if (extraSourceDirectory.exists()) {
607           sourceDirectories.add(extraSourceDirectory);
608         }
609       }
610     }
611 
612     if (this.mojo.getLog().isDebugEnabled()) {
613       for (final File file : sourceDirectories) {
614         this.mojo.getLog().debug("Added to sourceDirectory: " + file.getPath());
615       }
616     }
617     return sourceDirectories;
618   }
619 
620   /**
621    * @return The standard Compiler configuration with 'testOptions' added to the
622    *         argument list.
623    */
624   public final CompilerDef getTestCompiler(final String type, final String output)
625       throws MojoFailureException, MojoExecutionException {
626     final CompilerDef compiler = getCompiler(type, output);
627     if (compiler != null && this.testOptions != null) {
628       for (final String string : this.testOptions) {
629         final CompilerArgument arg = new CompilerArgument();
630         arg.setValue(string);
631         compiler.addConfiguredCompilerArg(arg);
632       }
633     }
634     return compiler;
635   }
636 
637   public final void setAbstractCompileMojo(final AbstractCompileMojo mojo) {
638     this.mojo = mojo;
639   }
640 
641   @Override
642   public String toString() {
643     return NarUtil.prettyMavenString(this);
644   }
645 }