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.apple;
21  
22  import java.io.File;
23  import java.io.IOException;
24  import java.text.NumberFormat;
25  import java.util.ArrayList;
26  import java.util.Arrays;
27  import java.util.Collections;
28  import java.util.Comparator;
29  import java.util.HashMap;
30  import java.util.List;
31  import java.util.Locale;
32  import java.util.Map;
33  
34  import javax.xml.transform.TransformerConfigurationException;
35  
36  import org.apache.tools.ant.BuildException;
37  import org.xml.sax.SAXException;
38  
39  import com.github.maven_nar.cpptasks.CCTask;
40  import com.github.maven_nar.cpptasks.CUtil;
41  import com.github.maven_nar.cpptasks.TargetInfo;
42  import com.github.maven_nar.cpptasks.compiler.CommandLineCompilerConfiguration;
43  import com.github.maven_nar.cpptasks.compiler.CommandLineLinkerConfiguration;
44  import com.github.maven_nar.cpptasks.compiler.ProcessorConfiguration;
45  import com.github.maven_nar.cpptasks.gcc.GccCCompiler;
46  import com.github.maven_nar.cpptasks.ide.CommentDef;
47  import com.github.maven_nar.cpptasks.ide.DependencyDef;
48  import com.github.maven_nar.cpptasks.ide.ProjectDef;
49  import com.github.maven_nar.cpptasks.ide.ProjectWriter;
50  
51  /**
52   * Writes a Apple Xcode 2.1+ project directory. XCode stores project
53   * configuration as a PropertyList. Though it will always write the project
54   * as a Cocoa Old-Style ASCII property list, it will read projects
55   * stored using Cocoa's XML Property List format.
56   */
57  public final class XcodeProjectWriter implements ProjectWriter {
58  
59    /**
60     * Represents a property map with an 96 bit identity.
61     * When placed in a property list, this object will
62     * output the string representation of the identity
63     * which XCode uses to find the corresponding property
64     * bag in the "objects" property of the top-level property list.
65     */
66    private static final class PBXObjectRef {
67      /**
68       * Next available identifier.
69       */
70      private static int nextID = 0;
71      /**
72       * Identifier.
73       */
74      private final String id;
75      /**
76       * Properties.
77       */
78      private final Map properties;
79  
80      /**
81       * Create reference.
82       * 
83       * @param props
84       *          properties.
85       */
86      public PBXObjectRef(final Map props) {
87        if (props == null) {
88          throw new NullPointerException("props");
89        }
90        final StringBuffer buf = new StringBuffer("000000000000000000000000");
91        final String idStr = Integer.toHexString(nextID++);
92        buf.replace(buf.length() - idStr.length(), buf.length(), idStr);
93        this.id = buf.toString();
94        this.properties = props;
95      }
96  
97      /**
98       * Get object identifier.
99       * 
100      * @return object identifier.
101      */
102     public String getID() {
103       return this.id;
104     }
105 
106     /**
107      * Get properties.
108      * 
109      * @return properties.
110      */
111     public Map getProperties() {
112       return this.properties;
113     }
114 
115     /**
116      * Get object identifier.
117      * 
118      * @return identifier.
119      */
120     @Override
121     public String toString() {
122       return this.id;
123     }
124   }
125 
126   /**
127    * Create PBXBuildFile.
128    * 
129    * @param fileRef
130    *          source file.
131    * @param settings
132    *          build settings.
133    * @return PBXBuildFile.
134    */
135   private static PBXObjectRef createPBXBuildFile(final PBXObjectRef fileRef, final Map settings) {
136     final Map map = new HashMap();
137     map.put("fileRef", fileRef);
138     map.put("isa", "PBXBuildFile");
139     if (settings != null) {
140       map.put("settings", settings);
141     }
142     return new PBXObjectRef(map);
143   }
144 
145   /**
146    * Create a proxy for a file in a different project.
147    * 
148    * @param containerPortal
149    *          XcodeProject containing file.
150    * @param proxyType
151    *          proxy type.
152    * @return PBXContainerItemProxy.
153    */
154   private static PBXObjectRef createPBXContainerItemProxy(final PBXObjectRef containerPortal, final int proxyType,
155       final String remoteInfo) {
156     final Map map = new HashMap();
157     map.put("isa", "PBXContainerItemProxy");
158     map.put("containerPortal", containerPortal);
159     map.put("proxyType", NumberFormat.getIntegerInstance(Locale.US).format(proxyType));
160     map.put("remoteInfo", remoteInfo);
161     return new PBXObjectRef(map);
162   }
163 
164   /**
165    * Create a build phase that copies files to a destination.
166    * 
167    * @param buildActionMask
168    *          build action mask.
169    * @param dstPath
170    *          destination path.
171    * @param dstSubfolderSpec
172    *          subfolder spec.
173    * @param files
174    *          files.
175    * @param runOnly
176    *          if true, phase should only be run on deployment.
177    * @return PBXCopyFileBuildPhase.
178    */
179   private static PBXObjectRef createPBXCopyFilesBuildPhase(final int buildActionMask, final String dstPath,
180       final String dstSubfolderSpec, final List files, final boolean runOnly) {
181     final Map map = new HashMap();
182     map.put("isa", "PBXCopyFilesBuildPhase");
183     map.put("buildActionMask", NumberFormat.getIntegerInstance(Locale.US).format(buildActionMask));
184     map.put("dstPath", dstPath);
185     map.put("dstSubfolderSpec", dstSubfolderSpec);
186     map.put("files", files);
187     map.put("runOnlyForDeploymentPostprocessing", toString(runOnly));
188     return new PBXObjectRef(map);
189   }
190 
191   /**
192    * Create PBXFileReference.
193    * 
194    * @param sourceTree
195    *          source tree.
196    * @param baseDir
197    *          base directory.
198    * @param file
199    *          file.
200    * @return PBXFileReference object.
201    */
202   private static PBXObjectRef createPBXFileReference(final String sourceTree, final String baseDir, final File file) {
203     final Map map = new HashMap();
204     map.put("isa", "PBXFileReference");
205 
206     final String relPath = CUtil.toUnixPath(CUtil.getRelativePath(baseDir, file));
207     map.put("path", relPath);
208     map.put("name", file.getName());
209     map.put("sourceTree", sourceTree);
210     return new PBXObjectRef(map);
211   }
212 
213   /**
214    * Create PBXFrameworksBuildPhase.
215    * 
216    * @param buildActionMask
217    *          build action mask.
218    * @param files
219    *          files.
220    * @param runOnly
221    *          if true, phase should only be run on deployment.
222    * @return PBXFrameworkBuildPhase.
223    */
224   private static PBXObjectRef createPBXFrameworksBuildPhase(final int buildActionMask, final List files,
225       final boolean runOnly) {
226     final Map map = new HashMap();
227     map.put("isa", "PBXFrameworksBuildPhase");
228     map.put("buildActionMask", NumberFormat.getIntegerInstance(Locale.US).format(buildActionMask));
229     map.put("files", files);
230     map.put("runOnlyForDeploymentPostprocessing", toString(runOnly));
231     return new PBXObjectRef(map);
232   }
233 
234   /**
235    * Create PBXGroup.
236    * 
237    * @param name
238    *          group name.
239    * @param sourceTree
240    *          source tree.
241    * @param children
242    *          list of PBXFileReferences.
243    * @return group.
244    */
245   private static PBXObjectRef createPBXGroup(final String name, final String sourceTree, final List children) {
246     final Map map = new HashMap();
247     map.put("isa", "PBXGroup");
248     map.put("name", name);
249     map.put("sourceTree", sourceTree);
250     map.put("children", children);
251     return new PBXObjectRef(map);
252   }
253 
254   /**
255    * Create PBXNativeTarget.
256    * 
257    * @param name
258    *          name.
259    * @param buildConfigurationList
260    *          build configuration list.
261    * @param buildPhases
262    *          build phases.
263    * @param buildRules
264    *          build rules.
265    * @param dependencies
266    *          dependencies.
267    * @param productInstallPath
268    *          product install path.
269    * @param productName
270    *          product name.
271    * @param productReference
272    *          file reference for product.
273    * @param productType
274    *          product type.
275    * @return native target.
276    */
277   private static PBXObjectRef createPBXNativeTarget(final String name, final PBXObjectRef buildConfigurationList,
278       final List buildPhases, final List buildRules, final List dependencies, final String productInstallPath,
279       final String productName, final PBXObjectRef productReference, final String productType) {
280     final Map map = new HashMap();
281     map.put("isa", "PBXNativeTarget");
282     map.put("buildConfigurationList", buildConfigurationList);
283     map.put("buildPhases", buildPhases);
284     map.put("buildRules", buildRules);
285     map.put("dependencies", dependencies);
286     map.put("name", name);
287     map.put("productInstallPath", productInstallPath);
288     map.put("productName", productName);
289     map.put("productReference", productReference);
290     map.put("productType", productType);
291     return new PBXObjectRef(map);
292   }
293 
294   /**
295    * Create PBXProject.
296    * 
297    * @param buildConfigurationList
298    *          build configuration list.
299    * @param mainGroup
300    *          main group.
301    * @param projectDirPath
302    *          project directory path.
303    * @param targets
304    *          targets.
305    * @param projectRoot
306    *          projectRoot directory relative to
307    * @return project.
308    */
309   private static PBXObjectRef createPBXProject(final PBXObjectRef buildConfigurationList, final PBXObjectRef mainGroup,
310       final String projectDirPath, final String projectRoot, final List targets) {
311     final Map map = new HashMap();
312     map.put("isa", "PBXProject");
313     map.put("buildConfigurationList", buildConfigurationList.getID());
314     map.put("hasScannedForEncodings", "0");
315     map.put("mainGroup", mainGroup.getID());
316     map.put("projectDirPath", projectDirPath);
317     map.put("targets", targets);
318     map.put("projectRoot", projectRoot);
319     return new PBXObjectRef(map);
320   }
321 
322   /**
323    * Create a proxy for a file in a different project.
324    * 
325    * @param remoteRef
326    *          PBXContainerItemProxy for reference.
327    * @param dependency
328    *          dependency.
329    * @return PBXContainerItemProxy.
330    */
331   private static PBXObjectRef createPBXReferenceProxy(final PBXObjectRef remoteRef, final DependencyDef dependency) {
332     final Map map = new HashMap();
333     map.put("isa", "PBXReferenceProxy");
334     final String fileType = "compiled.mach-o.dylib";
335     map.put("fileType", fileType);
336     map.put("remoteRef", remoteRef);
337     map.put("path", dependency.getFile().getName() + ".dylib");
338     map.put("sourceTree", "BUILT_PRODUCTS_DIR");
339     return new PBXObjectRef(map);
340   }
341 
342   /**
343    * Create PBXSourcesBuildPhase.
344    * 
345    * @param buildActionMask
346    *          build action mask.
347    * @param files
348    *          source files.
349    * @param runOnly
350    *          if true, phase should only be run on deployment.
351    * @return PBXSourcesBuildPhase.
352    */
353   private static PBXObjectRef createPBXSourcesBuildPhase(final int buildActionMask, final List files,
354       final boolean runOnly) {
355     final Map map = new HashMap();
356     map.put("buildActionMask", String.valueOf(buildActionMask));
357     map.put("files", files);
358     map.put("isa", "PBXSourcesBuildPhase");
359     map.put("runOnlyForDeploymentPostprocessing", toString(runOnly));
360     return new PBXObjectRef(map);
361   }
362 
363   /**
364    * Create XCBuildConfiguration.
365    * 
366    * @param name
367    *          name.
368    * @param buildSettings
369    *          build settings.
370    * @return build configuration.
371    */
372   private static PBXObjectRef createXCBuildConfiguration(final String name, final Map<String, ?> buildSettings) {
373     final Map map = new HashMap();
374     map.put("isa", "XCBuildConfiguration");
375     map.put("buildSettings", buildSettings);
376     map.put("name", name);
377     return new PBXObjectRef(map);
378   }
379 
380   /**
381    * Create XCConfigurationList.
382    * 
383    * @param buildConfigurations
384    *          build configurations.
385    * @return configuration list.
386    */
387   private static PBXObjectRef createXCConfigurationList(final List buildConfigurations) {
388     final Map map = new HashMap();
389     map.put("isa", "XCConfigurationList");
390     map.put("buildConfigurations", buildConfigurations);
391     return new PBXObjectRef(map);
392   }
393 
394   /**
395    * Method returns "1" for true, "0" for false.
396    * 
397    * @param b
398    *          boolean value.
399    * @return "1" for true, "0" for false.
400    */
401   private static String toString(final boolean b) {
402     if (b) {
403       return "1";
404     } else {
405       return "0";
406     }
407   }
408 
409   /**
410    * Constructor.
411    */
412   public XcodeProjectWriter() {
413   }
414 
415   /**
416    * Adds a dependency to the object graph.
417    * 
418    * @param objects
419    * @param project
420    * @param mainGroupChildren
421    * @param baseDir
422    * @param dependency
423    * @return PBXBuildFile to add to PBXFrameworksBuildPhase.
424    */
425   private PBXObjectRef addDependency(final Map objects, final PBXObjectRef project, final List mainGroupChildren,
426       final String baseDir, final DependencyDef dependency) {
427     if (dependency.getFile() != null) {
428       final File xcodeDir = new File(dependency.getFile().getAbsolutePath() + ".xcodeproj");
429       if (xcodeDir.exists()) {
430         final PBXObjectRef xcodePrj = createPBXFileReference("SOURCE_ROOT", baseDir, xcodeDir);
431         mainGroupChildren.add(xcodePrj);
432         objects.put(xcodePrj.getID(), xcodePrj.getProperties());
433 
434         final int proxyType = 2;
435         final PBXObjectRef proxy = createPBXContainerItemProxy(xcodePrj, proxyType, dependency.getName());
436         objects.put(proxy.getID(), proxy.getProperties());
437 
438         final PBXObjectRef referenceProxy = createPBXReferenceProxy(proxy, dependency);
439         objects.put(referenceProxy.getID(), referenceProxy.getProperties());
440 
441         final PBXObjectRef buildFile = createPBXBuildFile(referenceProxy, Collections.emptyMap());
442         objects.put(buildFile.getID(), buildFile.getProperties());
443 
444         final List productsChildren = new ArrayList();
445         productsChildren.add(referenceProxy);
446         final PBXObjectRef products = createPBXGroup("Products", "<group>", productsChildren);
447         objects.put(products.getID(), products.getProperties());
448 
449         final Map projectReference = new HashMap();
450         projectReference.put("ProductGroup", products);
451         projectReference.put("ProjectRef", xcodePrj);
452 
453         List projectReferences = (List) project.getProperties().get("ProjectReferences");
454         if (projectReferences == null) {
455           projectReferences = new ArrayList();
456           project.getProperties().put("ProjectReferences", projectReferences);
457         }
458         projectReferences.add(projectReference);
459         return buildFile;
460       }
461     }
462     return null;
463   }
464 
465   /**
466    * Add documentation group to map of objects.
467    * 
468    * @param objects
469    *          object map.
470    * @param sourceTree
471    *          source tree description.
472    * @return documentation group.
473    */
474   private PBXObjectRef addDocumentationGroup(final Map objects, final String sourceTree) {
475     final List productsList = new ArrayList();
476     final PBXObjectRef products = createPBXGroup("Documentation", sourceTree, productsList);
477     objects.put(products.getID(), products.getProperties());
478     return products;
479   }
480 
481   /**
482    * Add native target to map of objects.
483    * 
484    * @param objects
485    *          map of objects.
486    * @param linkTarget
487    *          description of executable or shared library.
488    * @param product
489    *          product.
490    * @param projectName
491    *          project name.
492    * @param sourceGroupChildren
493    *          source files needed to build product.
494    * @return native target.
495    */
496   private PBXObjectRef addNativeTarget(final Map objects, final TargetInfo linkTarget, final PBXObjectRef product,
497       final String projectName, final List<PBXObjectRef> sourceGroupChildren,
498       final List<PBXObjectRef> frameworkBuildFiles) {
499 
500     final PBXObjectRef buildConfigurations = addNativeTargetConfigurationList(objects, projectName);
501 
502     int buildActionMask = 2147483647;
503     final List<PBXObjectRef> buildPhases = new ArrayList<>();
504 
505     final Map settings = new HashMap();
506     settings.put("ATTRIBUTES", new ArrayList());
507     final List buildFiles = new ArrayList();
508     for (final PBXObjectRef sourceFile : sourceGroupChildren) {
509       final PBXObjectRef buildFile = createPBXBuildFile(sourceFile, settings);
510       buildFiles.add(buildFile);
511       objects.put(buildFile.getID(), buildFile.getProperties());
512     }
513 
514     final PBXObjectRef sourcesBuildPhase = createPBXSourcesBuildPhase(buildActionMask, buildFiles, false);
515     objects.put(sourcesBuildPhase.getID(), sourcesBuildPhase.getProperties());
516     buildPhases.add(sourcesBuildPhase);
517 
518     buildActionMask = 8;
519     final PBXObjectRef frameworksBuildPhase = createPBXFrameworksBuildPhase(buildActionMask, frameworkBuildFiles, false);
520     objects.put(frameworksBuildPhase.getID(), frameworksBuildPhase.getProperties());
521     buildPhases.add(frameworksBuildPhase);
522 
523     final PBXObjectRef copyFilesBuildPhase = createPBXCopyFilesBuildPhase(8, "/usr/share/man/man1", "0",
524         new ArrayList(), true);
525     objects.put(copyFilesBuildPhase.getID(), copyFilesBuildPhase.getProperties());
526     buildPhases.add(copyFilesBuildPhase);
527 
528     final List buildRules = new ArrayList();
529 
530     final List dependencies = new ArrayList();
531 
532     final String productInstallPath = "$(HOME)/bin";
533 
534     final String productType = getProductType(linkTarget);
535 
536     final PBXObjectRef nativeTarget = createPBXNativeTarget(projectName, buildConfigurations, buildPhases, buildRules,
537         dependencies, productInstallPath, projectName, product, productType);
538     objects.put(nativeTarget.getID(), nativeTarget.getProperties());
539 
540     return nativeTarget;
541   }
542 
543   /**
544    * Add native target configuration list.
545    * 
546    * @param objects
547    *          map of objects.
548    * @param projectName
549    *          project name.
550    * @return build configurations for native target.
551    */
552   private PBXObjectRef addNativeTargetConfigurationList(final Map objects, final String projectName) {
553 
554     //
555     // Create a configuration list with
556     // two stock configurations: Debug and Release
557     //
558     final List<PBXObjectRef> configurations = new ArrayList<>();
559     final Map debugSettings = new HashMap();
560     debugSettings.put("COPY_PHASE_STRIP", "NO");
561     debugSettings.put("GCC_DYNAMIC_NO_PIC", "NO");
562     debugSettings.put("GCC_ENABLE_FIX_AND_CONTINUE", "YES");
563     debugSettings.put("GCC_MODEL_TUNING", "G5");
564     debugSettings.put("GCC_OPTIMIZATION_LEVEL", "0");
565     debugSettings.put("INSTALL_PATH", "$(HOME)/bin");
566     debugSettings.put("PRODUCT_NAME", projectName);
567     debugSettings.put("ZERO_LINK", "YES");
568     final PBXObjectRef debugConfig = createXCBuildConfiguration("Debug", debugSettings);
569     objects.put(debugConfig.getID(), debugConfig.getProperties());
570     configurations.add(debugConfig);
571 
572     final Map<String, Object> releaseSettings = new HashMap<>();
573     final List<String> archs = new ArrayList<>();
574     archs.add("ppc");
575     archs.add("i386");
576     releaseSettings.put("ARCHS", archs);
577     releaseSettings.put("GCC_GENERATE_DEBUGGING_SYMBOLS", "NO");
578     releaseSettings.put("GCC_MODEL_TUNING", "G5");
579     releaseSettings.put("INSTALL_PATH", "$(HOME)/bin");
580     releaseSettings.put("PRODUCT_NAME", projectName);
581     final PBXObjectRef releaseConfig = createXCBuildConfiguration("Release", releaseSettings);
582     objects.put(releaseConfig.getID(), releaseConfig.getProperties());
583     configurations.add(releaseConfig);
584 
585     final PBXObjectRef configurationList = createXCConfigurationList(configurations);
586     objects.put(configurationList.getID(), configurationList.getProperties());
587     return configurationList;
588   }
589 
590   /**
591    * Add file reference of product to map of objects.
592    * 
593    * @param objects
594    *          object map.
595    * @param linkTarget
596    *          build description for executable or shared library.
597    * @return file reference to generated executable or shared library.
598    */
599   private PBXObjectRef addProduct(final Map objects, final TargetInfo linkTarget) {
600 
601     //
602     // create file reference for executable file
603     // forget Ant's location, just place in XCode's default location
604     final PBXObjectRef executable = createPBXFileReference("BUILD_PRODUCTS_DIR", linkTarget.getOutput().getParent(),
605         linkTarget.getOutput());
606     final Map executableProperties = executable.getProperties();
607 
608     final String fileType = getFileType(linkTarget);
609     executableProperties.put("explicitFileType", fileType);
610     executableProperties.put("includeInIndex", "0");
611     objects.put(executable.getID(), executableProperties);
612 
613     return executable;
614   }
615 
616   /**
617    * Add project configuration list.
618    * 
619    * @param objects
620    *          map of objects.
621    * @param baseDir
622    *          base directory.
623    * @param compilerConfig
624    *          compiler configuration.
625    * @return project configuration object.
626    */
627   private PBXObjectRef addProjectConfigurationList(final Map objects, final String baseDir,
628       final List<DependencyDef> dependencies, final CommandLineCompilerConfiguration compilerConfig,
629       final CommandLineLinkerConfiguration linkerConfig) {
630     //
631     // Create a configuration list with
632     // two stock configurations: Debug and Release
633     //
634     final List configurations = new ArrayList();
635     final Map<String, Object> debugSettings = new HashMap<>();
636     debugSettings.put("GCC_WARN_ABOUT_RETURN_TYPE", "YES");
637     debugSettings.put("GCC_WARN_UNUSED_VARIABLE", "YES");
638     debugSettings.put("PREBINDING", "NO");
639     debugSettings.put("SDKROOT", "/Developer/SDKs/MacOSX10.4u.sdk");
640 
641     final PBXObjectRef debugConfig = createXCBuildConfiguration("Debug", debugSettings);
642     objects.put(debugConfig.getID(), debugConfig.getProperties());
643     configurations.add(debugConfig);
644 
645     final Map<String, Object> releaseSettings = new HashMap<>();
646     releaseSettings.put("GCC_WARN_ABOUT_RETURN_TYPE", "YES");
647     releaseSettings.put("GCC_WARN_UNUSED_VARIABLE", "YES");
648     releaseSettings.put("PREBINDING", "NO");
649     releaseSettings.put("SDKROOT", "/Developer/SDKs/MacOSX10.4u.sdk");
650     final PBXObjectRef releaseConfig = createXCBuildConfiguration("Release", releaseSettings);
651     objects.put(releaseConfig.getID(), releaseConfig.getProperties());
652     configurations.add(releaseConfig);
653     final PBXObjectRef configurationList = createXCConfigurationList(configurations);
654     final Map projectConfigurationListProperties = configurationList.getProperties();
655     projectConfigurationListProperties.put("defaultConfigurationIsVisible", "0");
656     projectConfigurationListProperties.put("defaultConfigurationName", "Debug");
657     objects.put(configurationList.getID(), configurationList.getProperties());
658 
659     //
660     // add include paths to both configurations
661     //
662     final File[] includeDirs = compilerConfig.getIncludePath();
663     if (includeDirs.length > 0) {
664       final List<String> includePaths = new ArrayList<>();
665       final Map<String, String> includePathMap = new HashMap<>();
666       for (final File includeDir : includeDirs) {
667         if (!CUtil.isSystemPath(includeDir)) {
668           final String absPath = includeDir.getAbsolutePath();
669           if (!includePathMap.containsKey(absPath)) {
670             if (absPath.startsWith("/usr/")) {
671               includePaths.add(CUtil.toUnixPath(absPath));
672             } else {
673               final String relPath = CUtil.toUnixPath(CUtil.getRelativePath(baseDir, includeDir));
674               includePaths.add(relPath);
675             }
676             includePathMap.put(absPath, absPath);
677           }
678         }
679       }
680       includePaths.add("${inherited)");
681       debugSettings.put("HEADER_SEARCH_PATHS", includePaths);
682       releaseSettings.put("HEADER_SEARCH_PATHS", includePaths);
683     }
684 
685     //
686     // add preprocessor definitions to both configurations
687     //
688     //
689     final String[] preArgs = compilerConfig.getPreArguments();
690     final List<String> defines = new ArrayList<>();
691     for (final String preArg : preArgs) {
692       if (preArg.startsWith("-D")) {
693         defines.add(preArg.substring(2));
694       }
695     }
696     if (defines.size() > 0) {
697       defines.add("$(inherited)");
698       debugSettings.put("GCC_PREPROCESSOR_DEFINITIONS", defines);
699       releaseSettings.put("GCC_PREPROCESSOR_DEFINITIONS", defines);
700     }
701 
702     if (linkerConfig != null) {
703       final Map<String, String> librarySearchMap = new HashMap<>();
704       final List<String> librarySearchPaths = new ArrayList<>();
705       final List<String> otherLdFlags = new ArrayList<>();
706       final String[] linkerArgs = linkerConfig.getEndArguments();
707       for (final String linkerArg : linkerArgs) {
708         if (linkerArg.startsWith("-L")) {
709           final String libDir = linkerArg.substring(2);
710           if (!librarySearchMap.containsKey(libDir)) {
711             if (!libDir.equals("/usr/lib")) {
712               librarySearchPaths.add(CUtil.toUnixPath(CUtil.getRelativePath(baseDir, new File(libDir))));
713             }
714             librarySearchMap.put(libDir, libDir);
715 
716           }
717         } else if (linkerArg.startsWith("-l")) {
718           //
719           // check if library is in dependencies list
720           //
721           final String libName = linkerArg.substring(2);
722           boolean found = false;
723           for (final DependencyDef dependency : dependencies) {
724             if (libName.startsWith(dependency.getName())) {
725               final File dependencyFile = dependency.getFile();
726               if (dependencyFile != null && new File(dependencyFile.getAbsolutePath() + ".xcodeproj").exists()) {
727                 found = true;
728                 break;
729               }
730             }
731           }
732           if (!found) {
733             otherLdFlags.add(linkerArg);
734           }
735         }
736       }
737 
738       debugSettings.put("LIBRARY_SEARCH_PATHS", librarySearchPaths);
739       debugSettings.put("OTHER_LDFLAGS", otherLdFlags);
740       releaseSettings.put("LIBRARY_SEARCH_PATHS", librarySearchPaths);
741       releaseSettings.put("OTHER_LDFLAGS", otherLdFlags);
742     }
743     return configurationList;
744   }
745 
746   /**
747    * Add file references for all source files to map of objects.
748    * 
749    * @param objects
750    *          map of objects.
751    * @param sourceTree
752    *          source tree.
753    * @param basePath
754    *          parent of XCode project dir
755    * @param targets
756    *          build targets.
757    * @return list containing file references of source files.
758    */
759   private List<PBXObjectRef> addSources(final Map objects, final String sourceTree, final String basePath,
760       final Map<String, TargetInfo> targets) {
761     final List<PBXObjectRef> sourceGroupChildren = new ArrayList<>();
762 
763     final List<File> sourceList = new ArrayList<>(targets.size());
764     for (final TargetInfo info : targets.values()) {
765       final File[] targetsources = info.getSources();
766       Collections.addAll(sourceList, targetsources);
767     }
768     final File[] sortedSources = sourceList.toArray(new File[sourceList.size()]);
769     Arrays.sort(sortedSources, new Comparator<File>() {
770       @Override
771       public int compare(final File o1, final File o2) {
772         return o1.getName().compareTo(o2.getName());
773       }
774     });
775     for (final File sortedSource : sortedSources) {
776       final PBXObjectRef fileRef = createPBXFileReference(sourceTree, basePath, sortedSource);
777       sourceGroupChildren.add(fileRef);
778       objects.put(fileRef.getID(), fileRef.getProperties());
779     }
780 
781     return sourceGroupChildren;
782   }
783 
784   /**
785    * Gets the first recognized compiler from the
786    * compilation targets.
787    *
788    * @param targets
789    *          compilation targets
790    * @return representative (hopefully) compiler configuration
791    */
792   private CommandLineCompilerConfiguration getBaseCompilerConfiguration(final Map targets) {
793     //
794     // find first target with an GNU C++ compilation
795     //
796     CommandLineCompilerConfiguration compilerConfig;
797     //
798     // get the first target and assume that it is representative
799     //
800     for (final Object o : targets.values()) {
801       final TargetInfo targetInfo = (TargetInfo) o;
802       final ProcessorConfiguration config = targetInfo.getConfiguration();
803       //
804       // for the first cl compiler
805       //
806       if (config instanceof CommandLineCompilerConfiguration) {
807         compilerConfig = (CommandLineCompilerConfiguration) config;
808         if (compilerConfig.getCompiler() instanceof GccCCompiler) {
809           return compilerConfig;
810         }
811       }
812     }
813     return null;
814   }
815 
816   private String getFileType(final TargetInfo linkTarget) {
817     switch (getProductTypeIndex(linkTarget)) {
818       case 1:
819         return "archive.ar";
820       case 2:
821         return "compiled.mach-o.dylib";
822       default:
823         return "compiled.mach-o.executable";
824     }
825   }
826 
827   private String getProductType(final TargetInfo linkTarget) {
828     switch (getProductTypeIndex(linkTarget)) {
829       case 1:
830         return "com.apple.product-type.library.static";
831       case 2:
832         return "com.apple.product-type.library.dynamic";
833       default:
834         return "com.apple.product-type.tool";
835     }
836   }
837 
838   private int getProductTypeIndex(final TargetInfo linkTarget) {
839     final String outPath = linkTarget.getOutput().getPath();
840     String outExtension = null;
841     final int lastDot = outPath.lastIndexOf('.');
842     if (lastDot != -1) {
843       outExtension = outPath.substring(lastDot);
844     }
845     if (".a".equalsIgnoreCase(outExtension) || ".lib".equalsIgnoreCase(outExtension)) {
846       return 1;
847     } else if (".dylib".equalsIgnoreCase(outExtension) || ".so".equalsIgnoreCase(outExtension)
848         || ".dll".equalsIgnoreCase(outExtension)) {
849       return 2;
850     }
851     return 0;
852   }
853 
854   /**
855    * Writes a project definition file.
856    *
857    * @param fileName
858    *          File name base, writer may append appropriate extension
859    * @param task
860    *          cc task for which to write project
861    * @param projectDef
862    *          project element
863    * @param targets
864    *          compilation targets
865    * @param linkTarget
866    *          link target
867    * @throws IOException
868    *           if error writing project file
869    */
870   @Override
871   public void writeProject(final File fileName, final CCTask task, final ProjectDef projectDef,
872       final List<File> sources, final Map<String, TargetInfo> targets, final TargetInfo linkTarget) throws IOException {
873 
874     final File xcodeDir = new File(fileName + ".xcodeproj");
875     if (!projectDef.getOverwrite() && xcodeDir.exists()) {
876       throw new BuildException("Not allowed to overwrite project file " + xcodeDir.toString());
877     }
878 
879     final CommandLineCompilerConfiguration compilerConfig = getBaseCompilerConfiguration(targets);
880     if (compilerConfig == null) {
881       throw new BuildException("Unable to find compilation target using GNU C++ compiler");
882     }
883 
884     CommandLineLinkerConfiguration linkerConfig = null;
885     if (linkTarget.getConfiguration() instanceof CommandLineLinkerConfiguration) {
886       linkerConfig = (CommandLineLinkerConfiguration) linkTarget.getConfiguration();
887     }
888 
889     String projectName = projectDef.getName();
890     if (projectName == null) {
891       projectName = fileName.getName();
892     }
893     final String basePath = fileName.getAbsoluteFile().getParent();
894 
895     xcodeDir.mkdir();
896 
897     final File xcodeProj = new File(xcodeDir, "project.pbxproj");
898 
899     //
900     // create property list
901     //
902     final Map<String, Object> propertyList = new HashMap<>();
903     propertyList.put("archiveVersion", "1");
904     propertyList.put("classes", new HashMap());
905     propertyList.put("objectVersion", "42");
906     final Map objects = new HashMap();
907 
908     final String sourceTree = "<source>";
909 
910     //
911     // add source files and source group to property list
912     //
913     final List<PBXObjectRef> sourceGroupChildren = addSources(objects, "SOURCE_ROOT", basePath, targets);
914     final PBXObjectRef sourceGroup = createPBXGroup("Source", sourceTree, sourceGroupChildren);
915     objects.put(sourceGroup.getID(), sourceGroup.getProperties());
916 
917     //
918     // add product to property list
919     //
920     final PBXObjectRef product = addProduct(objects, linkTarget);
921     final List<PBXObjectRef> productsList = new ArrayList<>();
922     productsList.add(product);
923     final PBXObjectRef productsGroup = createPBXGroup("Products", sourceTree, productsList);
924     objects.put(productsGroup.getID(), productsGroup.getProperties());
925 
926     //
927     // add documentation group to property list
928     //
929     final PBXObjectRef documentationGroup = addDocumentationGroup(objects, sourceTree);
930 
931     //
932     // add main group containing source, products and documentation group
933     //
934     final List<PBXObjectRef> groups = new ArrayList<>(3);
935     groups.add(sourceGroup);
936     groups.add(documentationGroup);
937     groups.add(productsGroup);
938     final PBXObjectRef mainGroup = createPBXGroup(projectName, sourceTree, groups);
939     final StringBuffer comments = new StringBuffer();
940     for (final CommentDef commentDef : projectDef.getComments()) {
941       comments.append(commentDef);
942     }
943     if (comments.length() > 0) {
944       mainGroup.getProperties().put("comments", comments.toString());
945     }
946     objects.put(mainGroup.getID(), mainGroup.getProperties());
947 
948     //
949     // add project configurations
950     //
951     final PBXObjectRef compilerConfigurations = addProjectConfigurationList(objects, basePath,
952         projectDef.getDependencies(), compilerConfig, linkerConfig);
953 
954     final String projectDirPath = "";
955     final List<PBXObjectRef> projectTargets = new ArrayList<>();
956 
957     //
958     // add project to property list
959     //
960     //
961     // Calculate path (typically several ../..) of the root directory
962     // (where build.xml lives) relative to the XCode project directory.
963     // XCode 3.0 will now prompt user to supply the value if not specified.
964     final String projectRoot = CUtil.toUnixPath(CUtil.getRelativePath(basePath, projectDef.getProject().getBaseDir()));
965     final PBXObjectRef project = createPBXProject(compilerConfigurations, mainGroup, projectDirPath, projectRoot,
966         projectTargets);
967     objects.put(project.getID(), project.getProperties());
968 
969     final List<PBXObjectRef> frameworkBuildFiles = new ArrayList<>();
970     for (final DependencyDef dependency : projectDef.getDependencies()) {
971       final PBXObjectRef buildFile = addDependency(objects, project, groups, basePath, dependency);
972       if (buildFile != null) {
973         frameworkBuildFiles.add(buildFile);
974       }
975     }
976     //
977     // add description of native target (that is the executable or
978     // shared library)
979     //
980     final PBXObjectRef nativeTarget = addNativeTarget(objects, linkTarget, product, projectName, sourceGroupChildren,
981         frameworkBuildFiles);
982     projectTargets.add(nativeTarget);
983 
984     //
985     // finish up overall property list
986     //
987     propertyList.put("objects", objects);
988     propertyList.put("rootObject", project.getID());
989 
990     //
991     // write property list out to XML file
992     //
993     try {
994       PropertyListSerialization.serialize(propertyList, projectDef.getComments(), xcodeProj);
995     } catch (final TransformerConfigurationException ex) {
996       throw new IOException(ex.toString());
997     } catch (final SAXException ex) {
998       if (ex.getException() instanceof IOException) {
999         throw (IOException) ex.getException();
1000       }
1001       throw new IOException(ex.toString());
1002     }
1003   }
1004 
1005 }