Project.h

// Project.h
//
// Author David Barrett-Lennard
// (C)opyright Cedanet Pty Ltd 2006

#ifndef Ceda_Build_cxBuild_Project_H
#define Ceda_Build_cxBuild_Project_H

#include "cxBuild.h"
#include "Ceda/cxUtils/MsWindows.h"
#include "Ceda/cxUtils/xvector.h"
#include "Ceda/cxUtils/xstring.h"
#include "Ceda/cxUtils/SubString.h"
#include "Ceda/cxUtils/StringStream.h"
#include "Ceda/cxUtils/AutoPtrExt.h"
#include "Ceda/cxUtils/Guid.h"
#include <map>
#include <vector>

namespace ceda
{
    #define MAP_PATHS_TO_LOWERCASE 0

    /*
    Proposed approach.  Note that this should be done carefully to avoid breaking the code for
    long periods.
    
    1.  Write ParseCompilerSwitch()
        
    2.  DONT remove 
            *   m_addSettings, m_subSettings from ProjConfigDelta
            *   m_addSettings, m_subSettings from ProjFile 
            *   m_cpp from ProjConfig
        (yet).
        Otherwise all the code will break --> not compatible with incremental development.
        
        Instead only ADD the member
        
           CompilerSwitchesDelta m_compilerSwitchesDelta
        
        to ProjConfigDelta, ProjFile, ProjConfig.
        
    3.  Replace the parser
    
    4.  Fix WriteVcProj
    
    5.  Fix WriteDsp
    
    6.  Fix ScannedFileMgr
    
    7.  Remove string members referred to in step 2.
    */

typedef xstring CompilerSwitchKey;
typedef xstring CompilerSwitchValue;
typedef std::map<CompilerSwitchKey, CompilerSwitchValue> CompilerSwitchDelta;
    
void SubtractVc89CompilerSwitchDelta(CompilerSwitchDelta& base, const CompilerSwitchDelta& delta);

// Replace base with base' satisfying
// (reference + base')  =  (reference + base) - delta
void SubtractVc89CompilerSwitchDelta(
    const CompilerSwitchDelta& reference, 
    CompilerSwitchDelta& base,                   /*inout*/
    const CompilerSwitchDelta& delta);

void AddVc89CompilerSwitchDelta(CompilerSwitchDelta& base, const CompilerSwitchDelta& delta);

void SubtractResourceCompilerSwitchDelta(CompilerSwitchDelta& base, const CompilerSwitchDelta& delta);
void AddResourceCompilerSwitchDelta(CompilerSwitchDelta& base, const CompilerSwitchDelta& delta);

inline bool TestForKeyValuePair(const CompilerSwitchDelta& s, const CompilerSwitchKey& k, const CompilerSwitchValue& v)
{
    CompilerSwitchDelta::const_iterator i = s.find(k);
    return i != s.end() && i->second == v;
} 

/*
We need filenames to be unique so they can reliably be used as a key in a map.  Dependency 
analysis can involve files in external projects so therefore the path is significant.

For uniqueness of representation, we assume the following...

1.  All paths are relative to source root
2.  Only forward slash delimiters are permitted
3.  Names are converted to lower case.

Example:   ceda/cxobject/iobject.h
*/
typedef xstring LOWERCASE_LOGICAL_PATH;

class StringStream;
struct Project;
struct WorkSpace;
struct ProjConfig;

enum ECompileFlags
{
    CF_EXCLUDE,
    CF_NO_PCH,
    CF_MAKE_PCH,
};

enum EFileType
{
    FT_OTHER,
    FT_C,
    FT_CPP,
    FT_H,
    FT_RC,
    FT_ASM,
    FT_DEF,
    FT_XCPJ,
    FT_PY,
};

///////////////////////////////////////////////////////////////////////////////////////////////////
// ETargetType

enum ETargetType
{
    TT_APPLICATION,
    TT_STATIC_LIBRARY,
    TT_CONSOLE_APPLICATION,
    TT_DYNAMIC_LINK_LIBRARY,
    TT_UTILITY_PROJECT,

    TT_NUMBER
};

cxBuild_API ConstStringZ TargetTypeToString(ETargetType type);
cxBuild_API ConstStringZ TargetTypeToNum(ETargetType type);
cxBuild_API ConstStringZ TargetTypeToExtension(ETargetType type);

///////////////////////////////////////////////////////////////////////////////////////////////////
// ProjConfigDelta

// Used to make changes to the compile settings for a given configuration

struct ProjConfigDelta
{
    ProjConfigDelta() : m_settings(0) {}
    
    /*
    For VC8, VC9 this contains entries like
    
        "WarningLevel" --> "2"

    For gcc (and the future approach we intend to take) this would be
    
        "WarningLevel" --> "-Wall"
    */
    CompilerSwitchDelta m_cppAdd;
    
    // deprecated - used by vc6 dsp
    xstring m_addSettings;       // Compile settings to add
    xstring m_subSettings;       // Compile settings to subtract
    
    ProjConfig* m_config;        // Associated config that is being decorated
    
    uint32 m_settings;            // Should file be excluded from the build?
};

///////////////////////////////////////////////////////////////////////////////////////////////////
// ProjFile

struct cxBuild_API ProjFile
{
    ProjFile(Project& project, const xstring& name, const xstring& pathFromRoot);
    
    ProjConfigDelta* GetConfigDelta(ProjConfig* config);

    EFileType GetFileType() const { return m_fileType; }
    bool IsHeader() const;
    bool IsCpp() const;
    bool IsPchCpp() const;

    // Returns
    //      -1      undefined
    //       0      no
    //       1      create
    //       2      use
    int GetPchMode() const;
    
    // Returns true if the file is a cpp file that matches the project name.
    // This is important for registration code.
    bool IsMainProjectFile() const { return m_isMainProjectFile; }

    // Returns true if the file is a public header file of a library (i.e. doesn't appear under
    // the 'src' folder).
    bool IsPublicHeaderFile() const { return m_isPublicHeaderFile; }
    
    const xstring& GetName() const { return m_name; }
    const xstring& GetLogicalPath() const { return m_pathFromRoot; }
    xstring GetLocalPath() const;
    const xstring& GetFilenameWithoutExtension() const { return m_nameWithoutExtension; }
        
    // local path for translated version of the file written by xcpp
    // Returns:  xcppsource/<logical-path>
    xstring GetXcppDestPath(const ProjConfig& config) const;
    
    ////////////////////// State

    bool m_isMainProjectFile;
    bool m_isPublicHeaderFile;
    Project& m_project;
    EFileType m_fileType;
    xstring m_name;
    xstring m_nameWithoutExtension;     // Cache the value returned by GetFilenameWithoutExtension()
    xstring m_lowercaseExtension;
    xstring m_pathFromRoot;
    AutoCollectionOfPtr< xvector<ProjConfigDelta*> > m_configDeltas;
    
    ProjFile* m_associatedHeader;       // For a .cpp file, points at the .h with the same name if any
    ProjFile* m_associatedCpp;          // For a .h file, points at the .cpp with the same name if any

    // deprecated TODO - remove
    // Per file settings shared by all configurations
    xstring m_addSettings;       // Compile settings to add
    xstring m_subSettings;       // Compile settings to subtract
    uint32 m_settings;            // Should file be excluded from the build?
};

///////////////////////////////////////////////////////////////////////////////////////////////////
// ProjDirectory

struct ProjDirectory
{
    ProjDirectory(Project& project) : m_project(project) {}
    
    Project& m_project;
    
    // Get index of the file with the given name, or -1 if not found.
    ssize_t FindFile(SubString name) const;
    
    // Get index of the subdir with the given name, or -1 if not found.
    ssize_t FindDir(SubString name) const;

    xstring m_name;
    AutoCollectionOfPtr<xvector<ProjDirectory*> > m_subDirs;
    AutoCollectionOfPtr<xvector<ProjFile*> > m_files;
};

///////////////////////////////////////////////////////////////////////////////////////////////////
// BuildEvent

struct BuildEvent
{
    BuildEvent() : m_enabled(false) {}
    bool m_enabled;
    xstring m_desc;
    xstring m_command;
};

///////////////////////////////////////////////////////////////////////////////////////////////////
// LinkerInfo

// Information to be provided to the linker
struct LinkerInfo
{
    // Set of name,value pairs
    std::map<xstring,xstring> m;
};

///////////////////////////////////////////////////////////////////////////////////////////////////
// ProjConfig

struct ProjConfig
{
    xstring m_platform;             // Target platform E.g. "Win32" or "Pocket PC 2003 (ARMV4)"
    xstring m_configName;           // Name of the configuration.  Eg "Debug"

    xvector<xstring> m_tags;    // Additional tags E.g. "WinCE"
    
    BuildEvent m_preBuild;
    BuildEvent m_preLink;
    BuildEvent m_postBuild;
    
    // General setttings for this config
    CompilerSwitchDelta m_general;
    
    // C++ compiler switches for this config
    CompilerSwitchDelta m_cpp;

    // Resource compiler switches for this config
    CompilerSwitchDelta m_resourceCompiler;
    
    // Only used for VC11
    LinkerInfo m_linkerSwitches;

    // deprecated - used by vc6 dsp
    xstring m_vc6CppCommandLine;       // Command line for cpp
    
    xstring m_link;      // Command line for link
    
    // The path from the build folder to the root folder containing the translated source code
    // This path depends on the configuration so that the xcpp translation can be dependent
    // on the configuration.
    // [Was 'source', then changed to 'xcppsource', then changed to 
    //  'xcppsource\Win32\Debug Unicode' etc]
    xstring GetPathToTranslatedSourceRoot() const;

    // s is split into strings delimited by '|' characters.  Each string must match one of the following:  
    //      *   the name of the config
    //      *   the platform of the config
    //      *   one of the tags
    //      *   the target type of the project.
    //      *   the compiler
    bool Match(ECompiler compiler, ETargetType target, const xstring& spec) const;

    // Apply the $(PLATFORM) and $(CONFIG) macros for this configuration to string s
    void ApplyMacros(xstring& s) const;
    
    bool IsWin64() const;
};

void WriteVc89CompilerOptions(xostream& os, ProjFile& file, ProjConfig& config);

///////////////////////////////////////////////////////////////////////////////////////////////////
// Project

struct cxBuild_API Project
{
    Project(WorkSpace& ws);

    // Add preprocessor directive so that 'projname' is treated as a static library
    void TreatAsStaticLibrary(SubString projname);

    /// Add the subproject with the given path to the project file.  Returns false if already added.
    bool AddSubProject(const xstring& path);

    const xstring& GetName() const { return m_projName; }
    
    bool IsLibrary() const { return m_targetType == TT_STATIC_LIBRARY || m_targetType == TT_DYNAMIC_LINK_LIBRARY; }
    bool IsDynamicLinkLibrary() const { return m_targetType == TT_DYNAMIC_LINK_LIBRARY; }
    bool IsStaticLibrary() const { return m_targetType == TT_STATIC_LIBRARY; }

    // Calculate m_allDirs and m_allFiles
    void CalcAllDirectoriesAndFiles();

    void WriteExportXcpj(xostream& os) const;
    
    // Is this a project using /clr?
    bool IsCLR() const;

    void GetPlatforms(xvector<xstring>& platforms) const;

    // Find the config for the given platform and configName or returns NULL if not found
    ProjConfig* FindConfig(SubString configName, SubString platform) const;
    
    // Find the file with the given name.  The name must not be prefixed with a path, but it should
    // include the extension.
    ProjFile* FindFile(SubString name) const;

    const xstring& GetTargetNameAsIdentifier() const;
    
    //////////////////////////// State ///////////////////////////////
    
    WorkSpace& m_ws;
    XcppBuildSettings& m_bs;
 	MpfwFrame m_mpfwFrame;
    
    bool m_ignoreResourceFiles;

    bool m_xcpp;        // True then xcpp language extensions are used in the project
    bool m_export;      // True then this project has been exported.

    ETargetType m_targetType;
    
    // local path to $ for the physical tree that contains this project.
    xstring m_localPathToRoot;

    xstring m_pathToXcpj;

    xstring m_pathRootToProjDir;        // "Ceda/cxObject"
    xstring m_pathProjDirToRoot;        // "../../.."
    
    xstring m_pathRootToSrcDir;         // Ceda/cxObject/src

    Guid m_guid;

    // derived
    xstring m_projName;                 // "cxObject"

    ProjDirectory m_parentDir;
    AutoCollectionOfPtr<xvector<ProjConfig*> > m_configs;

    // Subprojects as paths relative to the root
    xvector<xstring> m_subProjectPaths;

    xvector<xstring> m_toolFiles;

    mutable bool m_calculated;
    
    // Redundant state that is convenient for iterating through all directories in the project.  
    typedef xvector<ProjDirectory*> DIRS;
    mutable DIRS m_allDirs;

    // Redundant state that is convenient for iterating through all files in the project.
    // Doesn't include files that have been excluded from the build.
    // All the h files appear before the cpp files.
    // The cpp file used to build the pch (typically StdAfx.cpp) appears before any other cpp 
    // files.
    typedef xvector<ProjFile*> FILES;
    mutable FILES m_allFiles;
    
    mutable std::map<xstring, ProjFile*> m_projFileMap;
    
	// Record all files in the project
	std::map<LOWERCASE_LOGICAL_PATH, ProjFile*> m_projectFilemap;

    std::vector<int> m_disabledWarnings;

private:
    mutable xstring m_targetNameAsIdentifier;   // "Ceda_Core_cxObject"
};

cxBuild_API void ExportPublicHeaders(Project& project,ProjConfig& config);

inline xstring GetPathToDependenciesFile(const Project& project, const ProjConfig& config)
{
    return cxMakeString(
        config.GetPathToTranslatedSourceRoot() << '/' 
        << project.m_projName << ".xcppdep");
}

///////////////////////////////////////////////////////////////////////////////////////////////////
// WorkSpace

struct cxBuild_API WorkSpace
{
    WorkSpace(XcppBuildSettings& bs);

    // Sort the projects so that dependent projects come after independent projects
    void TopologicalSortProjects();

    // Returns true iff at least one of the projects in the workspace is built using Xcpp.
    bool ExistsXcppProject() const;

    // Test whether the workspace contains the project with the given name
    bool ContainsProject(const xstring& name) const;

    Project* FindProject(const xstring& name) const;
    ssize_t FindProject2(const xstring& name) const;

    // Add the given project (and recursively any sub projects) to this workspace (if not added already).
    // The given path is from the root.
    // If the project has already been added then returns a pointer to the project without adding it again.
    // Returns NULL if already added.
    Project* AddProject(const xstring& projPath);
    
    bool CalcFiles();
    
    // Test whether the workspace contains a file with the given logical path
    bool HasFile(SubString logicalPath) const;

    // Find the file in the workspace with the given logicalPath, or returns NULL if
    // not found.
    ProjFile* FindFile(SubString logicalPath) const;

    //////////////////// state

    AutoCollectionOfPtr<xvector<Project*> > m_projects;

	XcppBuildSettings& m_bs;
	MpfwFrame m_mpfwFrame;
	
	xstring m_name;
	
    // local path to $ for the physical tree that contains this workspace.
    xstring m_localPathToRoot;
    
    xstring m_pathRootToWorkspaceDir;
    
	// All files across all projects in the workspace.
	std::map<LOWERCASE_LOGICAL_PATH, ProjFile*> m_filemap;
};

cxBuild_API void UpdateGuids(WorkSpace &ws);

///////////////////////////////////////////////////////////////////////////////////////////////////


cxBuild_API bool WriteFileIfDifferent(StringStream& output, const xstring& path);

cxBuild_API void WriteProjectFile(Project& project, bool buildForXcpp);
cxBuild_API void WriteWorkspaceFile(WorkSpace& ws, bool buildForXcpp);

} // namespace ceda

#endif // include guard