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.Collections;
26  import java.util.Iterator;
27  import java.util.LinkedList;
28  import java.util.List;
29  import java.util.Vector;
30  
31  import org.apache.maven.artifact.Artifact;
32  import org.apache.maven.execution.MavenSession;
33  import org.apache.maven.plugin.MojoExecutionException;
34  import org.apache.maven.plugin.MojoFailureException;
35  import org.apache.maven.plugins.annotations.Component;
36  import org.apache.maven.plugins.annotations.LifecyclePhase;
37  import org.apache.maven.plugins.annotations.Mojo;
38  import org.apache.maven.plugins.annotations.Parameter;
39  import org.apache.maven.plugins.annotations.ResolutionScope;
40  import org.apache.maven.shared.artifact.filter.collection.ScopeFilter;
41  import org.apache.tools.ant.BuildException;
42  import org.apache.tools.ant.Project;
43  import org.codehaus.plexus.util.FileUtils;
44  import org.codehaus.plexus.util.StringUtils;
45  
46  import com.github.maven_nar.cpptasks.CCTask;
47  import com.github.maven_nar.cpptasks.CUtil;
48  import com.github.maven_nar.cpptasks.CompilerDef;
49  import com.github.maven_nar.cpptasks.LinkerDef;
50  import com.github.maven_nar.cpptasks.OutputTypeEnum;
51  import com.github.maven_nar.cpptasks.RuntimeType;
52  import com.github.maven_nar.cpptasks.SubsystemEnum;
53  import com.github.maven_nar.cpptasks.types.LibrarySet;
54  import com.github.maven_nar.cpptasks.types.LinkerArgument;
55  import com.github.maven_nar.cpptasks.types.SystemLibrarySet;
56  
57  /**
58   * Compiles native source files.
59   * 
60   * @requiresSession
61   * @author Mark Donszelmann
62   */
63  @Mojo(name = "nar-compile", defaultPhase = LifecyclePhase.COMPILE, requiresProject = true,
64    requiresDependencyResolution = ResolutionScope.COMPILE)
65  public class NarCompileMojo extends AbstractCompileMojo {
66    /**
67     * Specify that the final manifest should be embedded in the output (default
68     * true) or false for side by side.
69     */
70    @Parameter(property = "nar.embedManifest", defaultValue = "true")
71    protected boolean embedManifest = true;
72  
73    /**
74     * The current build session instance.
75     */
76    @Component
77    protected MavenSession session;
78  
79    private void copyInclude(final Compiler c) throws IOException, MojoExecutionException, MojoFailureException {
80      if (c == null) {
81        return;
82      }
83      c.copyIncludeFiles(
84          getMavenProject(),
85          getLayout().getIncludeDirectory(getTargetDirectory(), getMavenProject().getArtifactId(),
86              getMavenProject().getVersion()));
87    }
88  
89    private void createLibrary(final Project antProject, final Library library)
90        throws MojoExecutionException, MojoFailureException {
91      getLog().debug("Creating Library " + library);
92      // configure task
93      final CCTask task = new CCTask();
94      task.setCommandLogLevel(this.commandLogLevel);
95      task.setProject(antProject);
96  
97      task.setDecorateLinkerOptions(this.decorateLinkerOptions);
98  
99      // subsystem
100     final SubsystemEnum subSystem = new SubsystemEnum();
101     subSystem.setValue(library.getSubSystem());
102     task.setSubsystem(subSystem);
103 
104     // set max cores
105     task.setMaxCores(getMaxCores(getAOL()));
106 
107     // outtype
108     final OutputTypeEnum outTypeEnum = new OutputTypeEnum();
109     final String type = library.getType();
110     outTypeEnum.setValue(type);
111     task.setOuttype(outTypeEnum);
112 
113     // stdc++
114     task.setLinkCPP(library.linkCPP());
115 
116     // fortran
117     task.setLinkFortran(library.linkFortran());
118     task.setLinkFortranMain(library.linkFortranMain());
119 
120     // outDir
121     File outDir;
122     if (type.equals(Library.EXECUTABLE)) {
123       outDir = getLayout().getBinDirectory(getTargetDirectory(), getMavenProject().getArtifactId(),
124           getMavenProject().getVersion(), getAOL().toString());
125     } else {
126       outDir = getLayout().getLibDirectory(getTargetDirectory(), getMavenProject().getArtifactId(),
127           getMavenProject().getVersion(), getAOL().toString(), type);
128     }
129     outDir.mkdirs();
130 
131     // outFile
132     // FIXME NAR-90 we could get the final name from layout
133     final File outFile = new File(outDir, getOutput(getAOL(), type));
134     getLog().debug("NAR - output: '" + outFile + "'");
135     task.setOutfile(outFile);
136 
137     // object directory
138     File objDir = new File(getTargetDirectory(), "obj");
139     objDir = new File(objDir, getAOL().toString());
140     objDir.mkdirs();
141     task.setObjdir(objDir);
142 
143     // failOnError, libtool
144     task.setFailonerror(failOnError(getAOL()));
145     task.setLibtool(useLibtool(getAOL()));
146 
147     // runtime
148     final RuntimeType runtimeType = new RuntimeType();
149     runtimeType.setValue(getRuntime(getAOL()));
150     task.setRuntime(runtimeType);
151 
152     // IDL, MC, RC compilations should probably be 'generate source' type
153     // actions, seperate from main build.
154     // Needs resolution of handling for generate sources.
155     // Order is somewhat important here, IDL and MC generate outputs that are
156     // (often) included in the RC compilation
157     if (getIdl() != null) {
158       final CompilerDef idl = getIdl().getCompiler(Compiler.MAIN, null);
159       if (idl != null) {
160         task.addConfiguredCompiler(idl);
161         task.createIncludePath().setPath(objDir.getPath()); // generated
162                                                             // 'sources'
163       }
164     }
165     if (getMessage() != null) {
166       final CompilerDef mc = getMessage().getCompiler(Compiler.MAIN, null);
167       if (mc != null) {
168         task.addConfiguredCompiler(mc);
169         task.createIncludePath().setPath(objDir.getPath()); // generated
170                                                             // 'sources'
171       }
172     }
173     if (getResource() != null) {
174       final CompilerDef res = getResource().getCompiler(Compiler.MAIN, null);
175       if (res != null) {
176         task.addConfiguredCompiler(res);
177       }
178     }
179 
180     // Darren Sargent Feb 11 2010: Use Compiler.MAIN for "type"...appears the
181     // wrong "type" variable was being used
182     // since getCompiler() expects "main" or "test", whereas the "type" variable
183     // here is "executable", "shared" etc.
184     // add C++ compiler
185     if (getCpp() != null) {
186       final CompilerDef cpp = getCpp().getCompiler(Compiler.MAIN, null);
187       if (cpp != null) {
188         task.addConfiguredCompiler(cpp);
189       }
190     }
191 
192     // add C compiler
193     if (getC() != null) {
194       final CompilerDef c = getC().getCompiler(Compiler.MAIN, null);
195       if (c != null) {
196         task.addConfiguredCompiler(c);
197       }
198     }
199 
200     // add Fortran compiler
201     if (getFortran() != null) {
202       final CompilerDef fortran = getFortran().getCompiler(Compiler.MAIN, null);
203       if (fortran != null) {
204         task.addConfiguredCompiler(fortran);
205       }
206     }
207     // end Darren
208 
209     // add javah include path
210     final File jniDirectory = getJavah().getJniDirectory();
211     if (jniDirectory.exists()) {
212       task.createIncludePath().setPath(jniDirectory.getPath());
213     }
214 
215     // add java include paths
216     getJava().addIncludePaths(task, type);
217 
218     getMsvc().configureCCTask(task);
219 
220     final List<NarArtifact> dependencies = getNarArtifacts();
221     // add dependency include paths
222     for (final Object element : dependencies) {
223       // FIXME, handle multiple includes from one NAR
224       final NarArtifact narDependency = (NarArtifact) element;
225       final String binding = getBinding(library, narDependency);
226       getLog().debug("Looking for " + narDependency + " found binding " + binding);
227       if (!binding.equals(Library.JNI)) {
228         final File unpackDirectory = getUnpackDirectory();
229         final File include = getLayout().getIncludeDirectory(unpackDirectory, narDependency.getArtifactId(),
230             narDependency.getBaseVersion());
231         getLog().debug("Looking for include directory: " + include);
232         if (include.exists()) {
233           task.createIncludePath().setPath(include.getPath());
234         } else {
235           // Ideally includes are used from lib (static or shared)
236           // however it's not required.
237           // make a note in the log if something has gone wrong,
238           // but don't block compilation
239           getLog().warn(String.format("Unable to locate %1$s lib include path '%2$s'", binding, include));
240         }
241       }
242     }
243 
244     // add linker
245     final LinkerDef linkerDefinition = getLinker().getLinker(this, task, getOS(), getAOL().getKey() + ".linker.", type);
246     task.addConfiguredLinker(linkerDefinition);
247 
248     // add dependency libraries
249     // FIXME: what about PLUGIN and STATIC, depending on STATIC, should we
250     // not add all libraries, see NARPLUGIN-96
251     final boolean skipDepLink = linkerDefinition.isSkipDepLink();
252     if (((type.equals(Library.SHARED) || type.equals(Library.JNI) || type.equals(Library.EXECUTABLE))) && !skipDepLink) {
253 
254       final List depLibOrder = getDependencyLibOrder();
255       List depLibs = dependencies;
256 
257       // reorder the libraries that come from the nar dependencies
258       // to comply with the order specified by the user
259       if (depLibOrder != null && !depLibOrder.isEmpty()) {
260         final List tmp = new LinkedList();
261 
262         for (final Object aDepLibOrder : depLibOrder) {
263           final String depToOrderName = (String) aDepLibOrder;
264 
265           for (final Iterator j = depLibs.iterator(); j.hasNext(); ) {
266             final NarArtifact dep = (NarArtifact) j.next();
267             final String depName = dep.getGroupId() + ":" + dep.getArtifactId();
268 
269             if (depName.equals(depToOrderName)) {
270               tmp.add(dep);
271               j.remove();
272             }
273           }
274         }
275 
276         tmp.addAll(depLibs);
277         depLibs = tmp;
278       }
279 
280       for (final Object depLib : depLibs) {
281         final NarArtifact dependency = (NarArtifact) depLib;
282 
283         // FIXME no handling of "local"
284 
285         // FIXME, no way to override this at this stage
286         final String binding = dependency.getNarInfo().getBinding(getAOL(), Library.NONE);
287         getLog().debug("Using Binding: " + binding);
288         AOL aol = getAOL();
289         aol = dependency.getNarInfo().getAOL(getAOL());
290         getLog().debug("Using Library AOL: " + aol.toString());
291 
292         if (!binding.equals(Library.JNI) && !binding.equals(Library.NONE) && !binding.equals(Library.EXECUTABLE)) {
293           final File unpackDirectory = getUnpackDirectory();
294 
295           final File dir = getLayout()
296               .getLibDirectory(unpackDirectory, dependency.getArtifactId(), dependency.getBaseVersion(), aol.toString(),
297                   binding);
298 
299           getLog().debug("Looking for Library Directory: " + dir);
300           if (dir.exists()) {
301             final LibrarySet libSet = new LibrarySet();
302             libSet.setProject(antProject);
303 
304             // FIXME, no way to override
305             final String libs = dependency.getNarInfo().getLibs(getAOL());
306             if (libs != null && !libs.equals("")) {
307               getLog().debug("Using LIBS = " + libs);
308               libSet.setLibs(new CUtil.StringArrayBuilder(libs));
309               libSet.setDir(dir);
310               task.addLibset(libSet);
311             }
312           } else {
313             getLog().debug("Library Directory " + dir + " does NOT exist.");
314           }
315 
316           // FIXME, look again at this, for multiple dependencies we may need to
317           // remove duplicates
318           final String options = dependency.getNarInfo().getOptions(getAOL());
319           if (options != null && !options.equals("")) {
320             getLog().debug("Using OPTIONS = " + options);
321             final LinkerArgument arg = new LinkerArgument();
322             arg.setValue(options);
323             linkerDefinition.addConfiguredLinkerArg(arg);
324           }
325 
326           final String sysLibs = dependency.getNarInfo().getSysLibs(getAOL());
327           if (sysLibs != null && !sysLibs.equals("")) {
328             getLog().debug("Using SYSLIBS = " + sysLibs);
329             final SystemLibrarySet sysLibSet = new SystemLibrarySet();
330             sysLibSet.setProject(antProject);
331 
332             sysLibSet.setLibs(new CUtil.StringArrayBuilder(sysLibs));
333             task.addSyslibset(sysLibSet);
334           }
335         }
336       }
337     }
338 
339     // Add JVM to linker
340     getJava().addRuntime(task, getJavaHome(getAOL()), getOS(), getAOL().getKey() + ".java.");
341 
342     // execute
343     try {
344       task.execute();
345     } catch (final BuildException e) {
346       throw new MojoExecutionException("NAR: Compile failed", e);
347     }
348 
349     // FIXME, this should be done in CPPTasks at some point
350     // getRuntime(getAOL()).equals("dynamic") &&
351     if ((isEmbedManifest() || getLinker().isGenerateManifest()) && getOS().equals(OS.WINDOWS)
352         && getLinker().getName().equals("msvc") && !getLinker().getVersion(this).startsWith("6.")) {
353       final String[] env = new String[] {
354         "PATH=" + getMsvc().getPathVariable().getValue()
355       };
356       final String libType = library.getType();
357       if (Library.JNI.equals(libType) || Library.SHARED.equals(libType) || Library.EXECUTABLE.equals(libType)) {
358         Vector<String> commandlineArgs = new Vector<>();
359         commandlineArgs.add("/manifest");
360         getManifests(outFile.getPath(), commandlineArgs);
361         if (commandlineArgs.size() == 1) {
362           if (isEmbedManifest())
363             getLog().warn("Embed manifest requested, no source manifests to embed, no manifest generated");
364         } else {
365           if (Library.JNI.equals(libType) || Library.SHARED.equals(libType)) {
366             String dll = outFile.getPath() + ".dll";
367             if (isEmbedManifest()) {
368               commandlineArgs.add("/outputresource:" + dll + ";#2");
369             } else {
370               commandlineArgs.add("/out:" + dll + ".manifest");
371             }
372           } else // if (Library.EXECUTABLE.equals( libType ))
373           {
374             String exe = outFile.getPath() + ".exe";
375             if (isEmbedManifest()) {
376               commandlineArgs.add("/outputresource:" + exe + ";#1");
377             } else {
378               commandlineArgs.add("/out:" + exe + ".manifest");
379             }
380           }
381           String[] commandlineArgsArray = commandlineArgs.toArray(new String[0]);
382           String mtexe = "mt.exe";
383           if (getMsvc().compareVersion( getMsvc().getWindowsSdkVersion(),"7.0")<0 && getLinker().getVersion(this).startsWith("8.")) { // VS2005 VC8 only one that includes mt.exe
384             File mtexeFile = new File(getMsvc().getToolPath(), mtexe);
385             if (mtexeFile.exists())
386               mtexe = mtexeFile.getAbsolutePath();
387           } else {
388             File mtexeFile = new File(getMsvc().getSDKToolPath(), mtexe);
389             if (mtexeFile.exists())
390               mtexe = mtexeFile.getAbsolutePath();
391           }
392           int result = NarUtil.runCommand(mtexe, commandlineArgsArray, null, null, getLog());
393           if (result != 0) {
394             throw new MojoFailureException("MT.EXE failed with exit code: " + result);
395           }
396         }
397       }
398     }
399     if( getOS().equals(OS.WINDOWS) && Library.STATIC.equals(library.getType()) ){  // option? should debug symbols always be provided.
400       getLog().debug( "Copy static pdbs from intermediat dir to " + task.getOutfile().getParentFile() );
401       try {
402         NarUtil.copyDirectoryStructure(task.getObjdir(), task.getOutfile().getParentFile(), "**/*.pdb", NarUtil.DEFAULT_EXCLUDES );
403       } catch (IOException e) {
404         getLog().info( "Failed to copy pdbs from " + task.getObjdir() + "\nexception" + e.getMessage() );
405       }
406     }
407   }
408 
409   /**
410    * List the dependencies needed for compilation, those dependencies are used
411    * to get the include paths needed for
412    * compilation and to get the libraries paths and names needed for linking.
413    */
414   @Override
415   protected ScopeFilter getArtifactScopeFilter() {
416     return new ScopeFilter(Artifact.SCOPE_COMPILE, null);
417   }
418 
419   private List getSourcesFor(final Compiler compiler) throws MojoFailureException, MojoExecutionException {
420     if (compiler == null) {
421       return Collections.emptyList();
422     }
423 
424     try {
425       final List files = new ArrayList();
426       final List srcDirs = compiler.getSourceDirectories();
427       for (final Object srcDir : srcDirs) {
428         final File dir = (File) srcDir;
429         if (dir.exists()) {
430           files.addAll(FileUtils.getFiles(dir, StringUtils.join(compiler.getIncludes().iterator(), ","), null));
431         }
432       }
433       return files;
434     } catch (final IOException e) {
435       return Collections.emptyList();
436     }
437   }
438 
439   @Override
440   public final void narExecute() throws MojoExecutionException, MojoFailureException {
441 
442     // make sure destination is there
443     getTargetDirectory().mkdirs();
444 
445     // check for source files
446     int noOfSources = 0;
447     noOfSources += getSourcesFor(getCpp()).size();
448     noOfSources += getSourcesFor(getC()).size();
449     noOfSources += getSourcesFor(getFortran()).size();
450     if (noOfSources > 0) {
451       getLog().info("Compiling " + noOfSources + " native files");
452       for (final Library library : getLibraries()) {
453         createLibrary(getAntProject(), library);
454       }
455     } else {
456       getLog().info("Nothing to compile");
457     }
458 
459     try {
460       // FIXME, should the include paths be defined at a higher level ?
461       copyInclude(getCpp());
462       copyInclude(getC());
463       copyInclude(getFortran());
464     } catch (final IOException e) {
465       throw new MojoExecutionException("NAR: could not copy include files", e);
466     }
467 
468     getNarInfo().writeToDirectory(this.classesDirectory);
469   }
470 
471   public boolean isEmbedManifest() {
472     return embedManifest;
473   }
474 
475   private void getManifests(String generated, Vector<String> manifests) {
476     // TODO: /manifest should be followed by the list of manifest files
477     // - the one generated by link, any others provided in source.
478     // search the source for .manifest files.
479     if (getLinker().isGenerateManifest())
480       manifests.add(generated + ".manifest");
481   }
482 
483 }