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.cpptasks.borland;
21  
22  import java.io.File;
23  import java.io.FileOutputStream;
24  import java.io.IOException;
25  import java.io.OutputStream;
26  import java.util.List;
27  import java.util.Locale;
28  import java.util.Map;
29  
30  import org.apache.tools.ant.BuildException;
31  import org.apache.xml.serialize.OutputFormat;
32  import org.apache.xml.serialize.Serializer;
33  import org.apache.xml.serialize.XMLSerializer;
34  import org.xml.sax.ContentHandler;
35  import org.xml.sax.SAXException;
36  import org.xml.sax.helpers.AttributesImpl;
37  
38  import com.github.maven_nar.cpptasks.CCTask;
39  import com.github.maven_nar.cpptasks.CUtil;
40  import com.github.maven_nar.cpptasks.TargetInfo;
41  import com.github.maven_nar.cpptasks.compiler.CommandLineCompilerConfiguration;
42  import com.github.maven_nar.cpptasks.compiler.CommandLineLinkerConfiguration;
43  import com.github.maven_nar.cpptasks.compiler.ProcessorConfiguration;
44  import com.github.maven_nar.cpptasks.gcc.GccCCompiler;
45  import com.github.maven_nar.cpptasks.ide.ProjectDef;
46  import com.github.maven_nar.cpptasks.ide.ProjectWriter;
47  
48  /**
49   * Writes a CBuilderX 1.0 project file.
50   *
51   * @author curta
52   *
53   */
54  public final class CBuilderXProjectWriter implements ProjectWriter {
55    /**
56     * Utility class to generate property elements.
57     */
58    private static class PropertyWriter {
59      /**
60       * Content handler.
61       */
62      private final ContentHandler content;
63  
64      /**
65       * Attributes list.
66       */
67      private final AttributesImpl propertyAttributes;
68  
69      /**
70       * Constructor.
71       *
72       * @param contentHandler
73       *          ContentHandler content handler
74       */
75      public PropertyWriter(final ContentHandler contentHandler) {
76        this.content = contentHandler;
77        this.propertyAttributes = new AttributesImpl();
78        this.propertyAttributes.addAttribute(null, "category", "category", "#PCDATA", "");
79        this.propertyAttributes.addAttribute(null, "name", "name", "#PCDATA", "");
80        this.propertyAttributes.addAttribute(null, "value", "value", "#PCDATA", "");
81      }
82  
83      /**
84       * Write property element.
85       *
86       * @param category
87       *          String category
88       * @param name
89       *          String property name
90       * @param value
91       *          String property value
92       * @throws SAXException
93       *           if I/O error or illegal content
94       */
95      public final void write(final String category, final String name, final String value) throws SAXException {
96        this.propertyAttributes.setValue(0, category);
97        this.propertyAttributes.setValue(1, name);
98        this.propertyAttributes.setValue(2, value);
99        this.content.startElement(null, "property", "property", this.propertyAttributes);
100       this.content.endElement(null, "property", "property");
101     }
102   }
103 
104   /**
105    * Constructor.
106    */
107   public CBuilderXProjectWriter() {
108   }
109 
110   /**
111    * Gets active platform.
112    * 
113    * @param task
114    *          CCTask cc task
115    * @return String platform identifier
116    */
117   private String getActivePlatform(final CCTask task) {
118     final String osName = System.getProperty("os.name").toLowerCase(Locale.US);
119     if (osName.contains("windows")) {
120       return "win32";
121     }
122     return "linux";
123   }
124 
125   /**
126    * Gets the first recognized compiler from the
127    * compilation targets.
128    * 
129    * @param targets
130    *          compilation targets
131    * @return representative (hopefully) compiler configuration
132    */
133   private CommandLineCompilerConfiguration getBaseCompilerConfiguration(final Map<String, TargetInfo> targets) {
134     //
135     // find first target with an gcc or bcc compilation
136     //
137     CommandLineCompilerConfiguration compilerConfig = null;
138     //
139     // get the first target and assume that it is representative
140     //
141     for (final TargetInfo targetInfo : targets.values()) {
142       final ProcessorConfiguration config = targetInfo.getConfiguration();
143       final String identifier = config.getIdentifier();
144       //
145       // for the first gcc or bcc compiler
146       //
147       if (config instanceof CommandLineCompilerConfiguration) {
148         compilerConfig = (CommandLineCompilerConfiguration) config;
149         if (compilerConfig.getCompiler() instanceof GccCCompiler || compilerConfig
150             .getCompiler() instanceof BorlandCCompiler) {
151           return compilerConfig;
152         }
153       }
154     }
155     return null;
156   }
157 
158   /**
159    * Gets build type from link target.
160    * 
161    * @param task
162    *          CCTask current task
163    * @return String build type
164    */
165   private String getBuildType(final CCTask task) {
166     final String outType = task.getOuttype();
167     if ("executable".equals(outType)) {
168       return "exeproject";
169     } else if ("static".equals(outType)) {
170       return "libraryproject";
171     }
172     return "dllproject";
173   }
174 
175   private String getWin32Toolset(final CommandLineCompilerConfiguration compilerConfig) {
176     if (compilerConfig != null && compilerConfig.getCompiler() instanceof BorlandCCompiler) {
177       return "win32b";
178     }
179     return "MinGW";
180   }
181 
182   /**
183    * Writes elements corresponding to compilation options.
184    *
185    * @param baseDir
186    *          String base directory
187    * @param writer
188    *          PropertyWriter property writer
189    * @param compilerConfig
190    *          representative configuration
191    * @throws SAXException
192    *           if I/O error or illegal content
193    */
194   private void writeCompileOptions(final String baseDir, final PropertyWriter writer,
195       final CommandLineCompilerConfiguration compilerConfig) throws SAXException {
196     boolean isBcc = false;
197     boolean isUnix = true;
198     String compileID = "linux.Debug_Build.gnuc++.g++compile";
199     if (compilerConfig.getCompiler() instanceof BorlandCCompiler) {
200       compileID = "win32.Debug_Build.win32b.bcc32";
201       isUnix = false;
202       isBcc = true;
203     }
204 
205     final File[] includePath = compilerConfig.getIncludePath();
206     int includeIndex = 1;
207     if (isUnix) {
208       writer.write(compileID, "option.I.arg." + includeIndex++, "/usr/include");
209       writer.write(compileID, "option.I.arg." + includeIndex++, "/usr/include/g++-3");
210     }
211     for (final File element : includePath) {
212       final String relPath = CUtil.getRelativePath(baseDir, element);
213       writer.write(compileID, "option.I.arg." + includeIndex++, relPath);
214     }
215     if (includePath.length > 0) {
216       writer.write(compileID, "option.I.enabled", "1");
217     }
218 
219     String defineBase = "option.D_MACRO_VALUE";
220     if (isBcc) {
221       defineBase = "option.D";
222     }
223     final String defineOption = defineBase + ".arg.";
224     int defineIndex = 1;
225     int undefineIndex = 1;
226     final String[] preArgs = compilerConfig.getPreArguments();
227     for (final String preArg : preArgs) {
228       if (preArg.startsWith("-D")) {
229         writer.write(compileID, defineOption + defineIndex++, preArg.substring(2));
230       } else if (preArg.startsWith("-U")) {
231         writer.write(compileID, "option.U.arg." + undefineIndex++, preArg.substring(2));
232       } else if (!(preArg.startsWith("-I") || preArg.startsWith("-o"))) {
233         //
234         // any others (-g, -fno-rtti, -w, -Wall, etc)
235         //
236         writer.write(compileID, "option." + preArg.substring(1) + ".enabled", "1");
237       }
238     }
239     if (defineIndex > 1) {
240       writer.write(compileID, defineBase + ".enabled", "1");
241     }
242     if (undefineIndex > 1) {
243       writer.write(compileID, "option.U.enabled", "1");
244     }
245   }
246 
247   /**
248    * Writes ilink32 linker options to project file.
249    *
250    * @param writer
251    *          PropertyWriter property writer
252    * @param linkID
253    *          String linker identifier
254    * @param preArgs
255    *          String[] linker arguments
256    * @throws SAXException
257    *           thrown if unable to write option
258    */
259   private void writeIlinkArgs(final PropertyWriter writer, final String linkID, final String[] args)
260       throws SAXException {
261     for (final String arg : args) {
262       if (arg.charAt(0) == '/' || arg.charAt(0) == '-') {
263         final int equalsPos = arg.indexOf('=');
264         if (equalsPos > 0) {
265           final String option = "option." + arg.substring(0, equalsPos - 1);
266           writer.write(linkID, option + ".enabled", "1");
267           writer.write(linkID, option + ".value", arg.substring(equalsPos + 1));
268         } else {
269           writer.write(linkID, "option." + arg.substring(1) + ".enabled", "1");
270         }
271       }
272     }
273   }
274 
275   /**
276    * Writes ld linker options to project file.
277    *
278    * @param writer
279    *          PropertyWriter property writer
280    * @param linkID
281    *          String linker identifier
282    * @param preArgs
283    *          String[] linker arguments
284    * @throws SAXException
285    *           thrown if unable to write option
286    */
287   private void writeLdArgs(final PropertyWriter writer, final String linkID, final String[] preArgs)
288       throws SAXException {
289     int objnameIndex = 1;
290     int libnameIndex = 1;
291     int libpathIndex = 1;
292     for (final String preArg : preArgs) {
293       if (preArg.startsWith("-o")) {
294         writer.write(linkID, "option.o.arg." + objnameIndex++, preArg.substring(2));
295       } else if (preArg.startsWith("-l")) {
296         writer.write(linkID, "option.l.arg." + libnameIndex++, preArg.substring(2));
297       } else if (preArg.startsWith("-L")) {
298         writer.write(linkID, "option.L.arg." + libpathIndex++, preArg.substring(2));
299       } else {
300         //
301         // any others
302         //
303         writer.write(linkID, "option." + preArg.substring(1) + ".enabled", "1");
304       }
305     }
306     if (objnameIndex > 1) {
307       writer.write(linkID, "option.o.enabled", "1");
308     }
309     if (libnameIndex > 1) {
310       writer.write(linkID, "option.l.enabled", "1");
311     }
312     if (libpathIndex > 1) {
313       writer.write(linkID, "option.L.enabled", "1");
314     }
315   }
316 
317   /**
318    * Writes elements corresponding to link options.
319    *
320    * @param baseDir
321    *          String base directory
322    * @param writer
323    *          PropertyWriter property writer
324    * @param linkTarget
325    *          TargetInfo link target
326    * @throws SAXException
327    *           if I/O error or illegal content
328    */
329   private void writeLinkOptions(final String baseDir, final PropertyWriter writer, final TargetInfo linkTarget)
330       throws SAXException {
331     if (linkTarget != null) {
332       final ProcessorConfiguration config = linkTarget.getConfiguration();
333       if (config instanceof CommandLineLinkerConfiguration) {
334         final CommandLineLinkerConfiguration linkConfig = (CommandLineLinkerConfiguration) config;
335 
336         if (linkConfig.getLinker() instanceof BorlandLinker) {
337           final String linkID = "win32.Debug_Build.win32b.ilink32";
338           writeIlinkArgs(writer, linkID, linkConfig.getPreArguments());
339           writeIlinkArgs(writer, linkID, linkConfig.getEndArguments());
340           writer.write(linkID, "param.libfiles.1", "cw32mt.lib");
341           writer.write(linkID, "param.libfiles.2", "import32.lib");
342           int libIndex = 3;
343           final String[] libNames = linkConfig.getLibraryNames();
344           for (final String libName : libNames) {
345             writer.write(linkID, "param.libfiles." + libIndex++, libName);
346           }
347           final String startup = linkConfig.getStartupObject();
348           if (startup != null) {
349             writer.write(linkID, "param.objfiles.1", startup);
350           }
351         } else {
352           final String linkID = "linux.Debug_Build.gnuc++.g++link";
353           writeLdArgs(writer, linkID, linkConfig.getPreArguments());
354           writeLdArgs(writer, linkID, linkConfig.getEndArguments());
355         }
356       }
357     }
358   }
359 
360   /**
361    * Writes a project definition file.
362    *
363    * @param fileName
364    *          project name for file, should has .cbx extension
365    * @param task
366    *          cc task for which to write project
367    * @param projectDef
368    *          project element
369    * @param sources
370    *          source files
371    * @param targets
372    *          compilation targets
373    * @param linkTarget
374    *          link target
375    * @throws IOException
376    *           if I/O error
377    * @throws SAXException
378    *           if XML serialization error
379    */
380   @Override
381   public void writeProject(final File fileName, final CCTask task, final ProjectDef projectDef,
382       final List<File> sources, final Map<String, TargetInfo> targets, final TargetInfo linkTarget)
383       throws IOException, SAXException {
384 
385     String projectName = projectDef.getName();
386     if (projectName == null) {
387       projectName = fileName.getName();
388     }
389     final String basePath = fileName.getAbsoluteFile().getParent();
390 
391     final File projectFile = new File(fileName + ".cbx");
392     if (!projectDef.getOverwrite() && projectFile.exists()) {
393       throw new BuildException("Not allowed to overwrite project file " + projectFile.toString());
394     }
395 
396     final CommandLineCompilerConfiguration compilerConfig = getBaseCompilerConfiguration(targets);
397     if (compilerConfig == null) {
398       throw new BuildException("Unable to generate C++ BuilderX project when gcc or bcc is not used.");
399     }
400 
401     final OutputStream outStream = new FileOutputStream(projectFile);
402     final OutputFormat format = new OutputFormat("xml", "UTF-8", true);
403     final Serializer serializer = new XMLSerializer(outStream, format);
404     final ContentHandler content = serializer.asContentHandler();
405     content.startDocument();
406     final AttributesImpl emptyAttrs = new AttributesImpl();
407     content.startElement(null, "project", "project", emptyAttrs);
408     final PropertyWriter propertyWriter = new PropertyWriter(content);
409     propertyWriter.write("build.config", "active", "0");
410     propertyWriter.write("build.config", "count", "0");
411     propertyWriter.write("build.config", "excludedefaultforzero", "0");
412     propertyWriter.write("build.config.0", "builddir", "Debug");
413     propertyWriter.write("build.config.0", "key", "Debug_Build");
414     propertyWriter.write("build.config.0", "linux.builddir", "linux/Debug_Build");
415     propertyWriter.write("build.config.0", "settings.MinGW", "default;debug");
416     propertyWriter.write("build.config.0", "settings.gnuc++", "default;debug");
417     propertyWriter.write("build.config.0", "settings.intellinia32", "default;debug");
418     propertyWriter.write("build.config.0", "settings.mswin32", "default;debug");
419     propertyWriter.write("build.config.0", "type", "Toolset");
420     propertyWriter.write("build.config.0", "win32.builddir", "windows/Debug_Build");
421     propertyWriter.write("build.node", "name", projectDef.getName());
422     final String buildType = getBuildType(task);
423     propertyWriter.write("build.node", "type", buildType);
424     propertyWriter.write("build.platform", "active", getActivePlatform(task));
425     propertyWriter.write("build.platform", "linux.Debug_Build.toolset", "gnuc++");
426     propertyWriter.write("build.platform", "linux.Release_Build.toolset", "gnuc++");
427     propertyWriter.write("build.platform", "linux.default", "gnuc++");
428     propertyWriter.write("build.platform", "linux.gnuc++.enabled", "1");
429     propertyWriter.write("build.platform", "linux.mswin32.enabled", "1");
430     propertyWriter.write("build.platform", "linux.win32b.enabled", "1");
431     propertyWriter.write("build.platform", "solaris.default", "gnuc++");
432     propertyWriter.write("build.platform", "solaris.enabled", "1");
433     final String toolset = getWin32Toolset(compilerConfig);
434     propertyWriter.write("build.platform", "win32.default", toolset);
435     propertyWriter.write("build.platform", "win32." + toolset + ".enabled", "1");
436 
437     propertyWriter.write("cbproject", "version", "X.1.0");
438     if ("dllproject".equals(buildType)) {
439       propertyWriter.write("gnuc++.g++compile", "option.fpic_using_GOT.enabled", "1");
440       propertyWriter.write("gnuc++.g++link", "option.shared.enabled", "1");
441       propertyWriter.write("intellinia32.icc", "option.minus_Kpic.enabled", "1");
442       propertyWriter.write("intellinia32.icclink", "option.minus_shared.enabled", "1");
443     }
444     //
445     // assume the first target is representative of all compilation tasks
446     //
447     writeCompileOptions(basePath, propertyWriter, compilerConfig);
448     writeLinkOptions(basePath, propertyWriter, linkTarget);
449     propertyWriter.write("linux.gnuc++.Debug_Build", "saved", "1");
450     if ("dllproject".equals(buildType)) {
451       propertyWriter.write("runtime", "ExcludeDefaultForZero", "1");
452       // propertyWriter.write("unique", "id", "852");
453     } else if ("exeproject".equals(buildType)) {
454       propertyWriter.write("runtime.0", "BuildTargetOnRun", "com.borland.cbuilder.build."
455           + "CBProjectBuilder$ProjectBuildAction;make");
456       propertyWriter.write("runtime.0", "ConfigurationName", projectDef.getName());
457       propertyWriter.write("runtime.0", "RunnableType", "com.borland.cbuilder.runtime.ExecutableRunner");
458     }
459     final AttributesImpl fileAttributes = new AttributesImpl();
460     fileAttributes.addAttribute(null, "path", "path", "#PCDATA", "");
461     AttributesImpl gccAttributes = null;
462     if (!"g++".equals(compilerConfig.getCommand())) {
463       gccAttributes = new AttributesImpl();
464       gccAttributes.addAttribute(null, "category", "category", "#PCDATA", "build.basecmd");
465       gccAttributes.addAttribute(null, "name", "name", "#PCDATA", "linux.gnuc++.Debug_Build.g++_key");
466       gccAttributes.addAttribute(null, "value", "value", "#PCDATA", compilerConfig.getCommand());
467     }
468 
469     for (final TargetInfo info : targets.values()) {
470       final File[] targetsources = info.getSources();
471       for (final File targetsource : targetsources) {
472         final String relativePath = CUtil.getRelativePath(basePath, targetsource);
473         fileAttributes.setValue(0, relativePath);
474         content.startElement(null, "file", "file", fileAttributes);
475 
476         //
477         // if file ends with .c, use gcc instead of g++
478         //
479         if (gccAttributes != null) {
480           content.startElement(null, "property", "property", gccAttributes);
481           content.endElement(null, "property", "property");
482         }
483         content.endElement(null, "file", "file");
484       }
485     }
486     content.endElement(null, "project", "project");
487     content.endDocument();
488   }
489 
490 }