# Copyright (C) 2013 Jaedyn K. Draper # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the "Software"), # to deal in the Software without restriction, including without limitation # the rights to use, copy, modify, merge, publish, distribute, sublicense, # and/or sell copies of the Software, and to permit persons to whom the # Software is furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL # THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import xml.etree.ElementTree as ET import xml.dom.minidom as minidom import os import sys import uuid import codecs import tempfile import hashlib from . import project_generator from . import projectSettings from . import log from . import _shared_globals import csbuild def GenerateNewUuid(uuidList, name): nameIndex = 0 nameToHash = name # Keep generating new UUIDs until we've found one that isn't already in use. This is only useful in cases where we have a pool of objects # and each one needs to be guaranteed to have a UUID that doesn't collide with any other object in the same pool. Though, because of the # way UUIDs work, having a collision should be extremely rare anyway. while True: newUuid = uuid.uuid5(uuid.NAMESPACE_OID, nameToHash) if not newUuid in uuidList: uuidList.add(newUuid) return newUuid # Name collision! The easy solution here is to slightly modify the name in a predictable way. nameToHash = "{}{}".format(name, nameIndex) ++nameIndex def GetPlatformName(toolchain, architecture): # The mapped architectures have special names in Visual Studio. platformNameMap = { "msvc": { "x86": "Win32", "arm": "ARM", }, # Android architectures will always build under the Tegra-Android platform. "android": { "x86": "Tegra-Android", "armeabi": "Tegra-Android", "armeabi-v7a": "Tegra-Android", "armeabi-v7a-hard": "Tegra-Android", "mips": "Tegra-Android", }, } # If the given architecture is mapped to a name, use that name. if toolchain in platformNameMap and architecture in platformNameMap[toolchain]: return platformNameMap[toolchain][architecture] # Otherwise, use the architecture itself as a fallback. return architecture def IsMicrosoftPlatform(platformName): microsoftPlatforms = set(["Win32", "x64", "ARM"]) return platformName in microsoftPlatforms def GetPlatformToolsetString(versionNumber): platformToolsetMap = { 2010: "vc100", 2012: "vc110", 2013: "vc120", } return platformToolsetMap[versionNumber] def CorrectConfigName(configName): # Visual Studio can be exceptionally picky about configuration names. For instance, if your build script has the "debug" target, # you may run into problems with Visual Studio showing that alongside it's own "Debug" configuration, which is may have decided # to just add alongside your own. The solution is to just put the configurations in a format it expects (first letter upper case, # the rest lowercase). That way, it will see "Debug" already there and won't try to silently 'fix' that up for you. return configName.capitalize() class Project: """ Container class for Visual Studio projects. """ def __init__(self, name, globalProjectUuidList): self.name = name self.outputPath = "" self.dependencyList = set() self.id = "{{{}}}".format(str(GenerateNewUuid(globalProjectUuidList, name)).upper()) self.isFilter = False self.isBuildAllProject = False self.isRegenProject = False self.platformConfigList = [] self.fullSourceFileList = set() self.fullHeaderFileList = set() self.fullIncludePathList = set() self.fileFilterMap = {} self.makefilePath = "" def HasConfigAndPlatform(self, config, platform): for configName, platformName, _ in self.platformConfigList: if configName == config and platformName == platform: return True return False class CachedFileData: def __init__(self, outputFilePath, fileData, isUserFile): self._outputFilePath = outputFilePath self._fileData = fileData self._isUserFile = isUserFile self._md5FileDataHash = hashlib.md5() self._md5FileDataHash.update(self._fileData) self._md5FileDataHash = self._md5FileDataHash.hexdigest() def SaveFile(self): canWriteOutputFile = True if os.access(self._outputFilePath, os.F_OK): # Any user files that already exist will be ignored to preserve user debug settings. if self._isUserFile and not csbuild.GetOption("do-not-ignore-user-files"): log.LOG_BUILD("Ignoring: {}".format(self._outputFilePath)) return with open(self._outputFilePath, "rb") as fileHandle: fileData = fileHandle.read() fileDataHash = hashlib.md5() fileDataHash.update(fileData) fileDataHash = fileDataHash.hexdigest() canWriteOutputFile = (fileDataHash != self._md5FileDataHash) if canWriteOutputFile: log.LOG_BUILD("Writing file {}...".format(self._outputFilePath)) with open(self._outputFilePath, "wb") as fileHandle: fileHandle.write(self._fileData) else: log.LOG_BUILD("Up-to-date: {}".format(self._outputFilePath)) class ProjectFileType: Unknown = None VCPROJ = "vcproj" VCXPROJ = "vcxproj" class VisualStudioVersion: v2005 = 2005 v2008 = 2008 v2010 = 2010 v2012 = 2012 v2013 = 2013 All = [v2005, v2008, v2010, v2012, v2013] class project_generator_visual_studio(project_generator.project_generator): """ Generator used to create Visual Studio project files. """ def __init__(self, path, solutionName, extraArgs): project_generator.project_generator.__init__(self, path, solutionName, extraArgs) versionNumber = csbuild.GetOption("visual-studio-version") self._createNativeProject = False # csbuild.GetOption("create-native-project") self._visualStudioVersion = versionNumber self._projectMap = {} self._configList = [] self._platformList = [] self._archMap = {} self._reverseConfigMap = {} # A reverse look-up map for the configuration names. self._projectFileType = ProjectFileType.Unknown self._extraBuildArgs = self.extraargs.replace(",", " ") self._fullIncludePathList = set() # Should be a set starting out so duplicates are eliminated. self._projectUuidList = set() self._orderedProjectList = [] # Compile a list of the targets and a reverse-lookup list. for configName in _shared_globals.alltargets: correctedConfigName = CorrectConfigName(configName) self._configList.append(correctedConfigName) self._reverseConfigMap[correctedConfigName] = configName # Compile a list of the platforms for the solution. for toolchainName in _shared_globals.selectedToolchains: for archName in csbuild.Toolchain(toolchainName).GetValidArchitectures(): platformName = GetPlatformName(toolchainName, archName) if not platformName in self._archMap: self._archMap[platformName] = archName self._platformList.append(platformName) self._configList = sorted(self._configList) self._platformList = sorted(self._platformList) # Try to convert the version provided by the user into a string. If that fails, do nothing because we want to keep the original value for error reporting. try: self._visualStudioVersion = int(self._visualStudioVersion) except: pass @staticmethod def AdditionalArgs(parser): parser.add_argument("--visual-studio-version", help = "Select the version of Visual Studio the generated solution will be compatible with.", choices = VisualStudioVersion.All, default = VisualStudioVersion.v2012, type = int, ) parser.add_argument("--do-not-ignore-user-files", help = "When generating project files, do not ignore existing .vcxproj.user files.", action = "store_true", ) #parser.add_argument("--create-native-project", # help = "Create a native solution that calls into MSBuild and NOT the makefiles.", # action = "store_true, #) def WriteProjectFiles(self): def recurseGroups(projectMap_out, parentFilter_out, projectOutputPath, projectGroup): # Setup the projects first. for projectName, projectSettingsMap in projectGroup.projects.items(): # Fill in the project data. projectData = Project(projectName, self._projectUuidList) # Create a new object to contain project data. projectData.name = projectName projectData.outputPath = os.path.join(self.rootpath, projectOutputPath) # Add the current project to the parent filter dependency list. In the case of filters, # this isn't really a depencency list, it's just a list of nested projects. if parentFilter_out: parentFilter_out.dependencyList.add(projectName) # Because the list of sources and headers can differ between configurations and architectures, # we need to generate a complete list so the project can reference them all. Also, keep track # of the project settings per configuration and supported architecture. for toolchainName, configMap in projectSettingsMap.items(): for configName, archMap in configMap.items(): for archName, settings in archMap.items(): platformName = GetPlatformName(toolchainName, archName) configName = CorrectConfigName(configName) projectData.platformConfigList.append((configName, platformName, toolchainName, settings)) projectData.fullSourceFileList.update(set(settings.allsources)) projectData.fullHeaderFileList.update(set(settings.allheaders)) projectData.fullIncludePathList.update(set(settings.includeDirs)) for source in projectData.fullSourceFileList: projectData.fileFilterMap[source] = os.path.join("Source Files", os.path.dirname(os.path.relpath(source, settings.workingDirectory)).replace(".." + os.path.sep, "")).rstrip(os.path.sep) for header in projectData.fullHeaderFileList: projectData.fileFilterMap[header] = os.path.join("Header Files", os.path.dirname(os.path.relpath(header, settings.workingDirectory)).replace(".." + os.path.sep, "")).rstrip(os.path.sep) projectData.fullSourceFileList = sorted(projectData.fullSourceFileList) projectData.fullHeaderFileList = sorted(projectData.fullHeaderFileList) projectData.platformConfigList = sorted(projectData.platformConfigList) # Grab the projectSettings for the first key in the config map and fill in a set of names for dependent projects. # We only need the names for now. We can handle resolving them after we have all of the projects mapped. configMap = list(projectSettingsMap.values())[0] archMap = list(configMap.values())[0] settings = list(archMap.values())[0] for dependentProjectKey in settings.linkDepends: projectData.dependencyList.add(_shared_globals.projects[dependentProjectKey].name) # Grab the path to this project's makefile. projectData.makefilePath = settings.scriptFile projectData.dependencyList = set(sorted(projectData.dependencyList)) # Sort the dependency list. projectMap_out[projectData.name] = projectData # Sort the parent filter dependency list. if parentFilter_out: parentFilter_out.dependencyList = set(sorted(parentFilter_out.dependencyList)) # Next, iterate through each subgroup and handle each one recursively. for subGroupName, subGroup in projectGroup.subgroups.items(): groupPath = os.path.join(projectOutputPath, subGroupName) groupPathFinal = os.path.join(self.rootpath, groupPath) filterData = Project(subGroupName, self._projectUuidList) # Subgroups should be treated as project filters in the solution. filterData.isFilter = True # Explicitly map the filter names with a different name to help avoid possible naming collisions. projectMap_out["{}.Filter".format(filterData.name)] = filterData # Create the group path if it doesn't exist. if not os.access(groupPathFinal, os.F_OK): os.makedirs(groupPathFinal) recurseGroups(projectMap_out, filterData, groupPath, subGroup) def resolveDependencies(projectMap): for projectId, projectData in projectMap.items(): resolvedDependencyList = [] # Sort the dependency name list before parsing it. projectData.dependencyList = sorted(projectData.dependencyList) # Resolve each project name to their associated project objects. for dependentProjectName in projectData.dependencyList: resolvedDependencyList.append(projectMap[dependentProjectName]) # Replace the old name list with the new resolved list. projectData.dependencyList = resolvedDependencyList # Create the base output directory if necessary. if not os.access(self.rootpath, os.F_OK): os.makedirs(self.rootpath) # When not creating a native project, a custom project must be injected in order to achieve full solution builds # since Visual Studio doesn't give us a way to override the behavior of the "Build Solution" command. if not self._createNativeProject: # Fill in the project data. buildAllProjectName = "(BUILD_ALL)" buildAllProjectData = Project(buildAllProjectName, self._projectUuidList) # Create a new object to contain project data. buildAllProjectData.outputPath = self.rootpath buildAllProjectData.isBuildAllProject = True buildAllProjectData.makefilePath = csbuild.scriptFiles[0] # Reference the main makefile. regenProjectName = "(REGENERATE_SOLUTION)" regenProjectData = Project(regenProjectName, self._projectUuidList) # Create a new object to contain project data. regenProjectData.outputPath = self.rootpath regenProjectData.isRegenProject = True regenProjectData.makefilePath = csbuild.scriptFiles[0] # Reference the main makefile. # The Build All project doesn't have any project settings, but it still needs all of the platforms and configurations. for platformName in self._platformList: for configName in self._configList: buildAllProjectData.platformConfigList.append((configName, platformName, "", None)) regenProjectData.platformConfigList.append((configName, platformName, "", None)) # Add the Build All and Regen projects to the project map. self._projectMap[buildAllProjectData.name] = buildAllProjectData self._projectMap[regenProjectData.name] = regenProjectData recurseGroups(self._projectMap, None, "", projectSettings.rootGroup) resolveDependencies(self._projectMap) # Copy the project names into a list. for projectName, projectData in self._projectMap.items(): self._orderedProjectList.append(projectName) # Sort the list of project names. self._orderedProjectList = sorted(self._orderedProjectList) # Replace the list of names in the project list with the actual project data. for i in range(0, len(self._orderedProjectList)): projectName = self._orderedProjectList[i] self._orderedProjectList[i] = self._projectMap[projectName] # Create a single list of every include search path. for projectName, projectData in self._projectMap.items(): self._fullIncludePathList.update(projectData.fullIncludePathList) self._fullIncludePathList = sorted(self._fullIncludePathList) is2005 = (self._visualStudioVersion == VisualStudioVersion.v2005) is2008 = (self._visualStudioVersion == VisualStudioVersion.v2008) is2010 = (self._visualStudioVersion == VisualStudioVersion.v2010) is2012 = (self._visualStudioVersion == VisualStudioVersion.v2012) is2013 = (self._visualStudioVersion == VisualStudioVersion.v2013) if is2005: self._GenerateFilesForVs2005() elif is2008: self._GenerateFilesForVs2008() elif is2010: self._GenerateFilesForVs2010() elif is2012: self._GenerateFilesForVs2012() elif is2013: self._GenerateFilesForVs2013() else: log.LOG_ERROR("Invalid Visual Studio version: {}".format(self._visualStudioVersion)); def _GenerateFilesForVs2005(self): self._projectFileType = ProjectFileType.VCPROJ self._WriteSolutionFile() def _GenerateFilesForVs2008(self): self._projectFileType = ProjectFileType.VCPROJ self._WriteSolutionFile() def _GenerateFilesForVs2010(self): self._projectFileType = ProjectFileType.VCXPROJ self._WriteSolutionFile() self._WriteVcxprojFiles() self._WriteVcxprojFiltersFiles() self._WriteVcxprojUserFiles() def _GenerateFilesForVs2012(self): self._projectFileType = ProjectFileType.VCXPROJ self._WriteSolutionFile() self._WriteVcxprojFiles() self._WriteVcxprojFiltersFiles() self._WriteVcxprojUserFiles() def _GenerateFilesForVs2013(self): self._projectFileType = ProjectFileType.VCXPROJ self._WriteSolutionFile() self._WriteVcxprojFiles() self._WriteVcxprojFiltersFiles() self._WriteVcxprojUserFiles() def _WriteSolutionFile(self): def writeLineToFile(indentLevel, fileHandle, stringToWrite): fileHandle.write("{}{}\r\n".format("\t" * indentLevel, stringToWrite)) fileFormatVersionNumber = { 2005: "9.00", 2008: "10.00", 2010: "11.00", 2012: "12.00", 2013: "12.00", } tempRootPath = tempfile.mkdtemp() finalSolutionPath = "{}.sln".format(os.path.join(self.rootpath, self.solutionname)) tempSolutionPath = "{}.sln".format(tempRootPath, self.solutionname) # Create the temporary root path. if not os.access(tempRootPath, os.F_OK): os.makedirs(tempRootPath) # Visual Studio solution files need to be UTF-8 with the byte order marker because Visual Studio is VERY picky about these files. # If ANYTHING is missing or not formatted properly, the Visual Studio version selector may not open the right version or Visual # Studio itself may refuse to even attempt to load the file. with codecs.open(tempSolutionPath, "w", "utf-8-sig") as fileHandle: writeLineToFile(0, fileHandle, "") # Required empty line. writeLineToFile(0, fileHandle, "Microsoft Visual Studio Solution File, Format Version {}".format(fileFormatVersionNumber[self._visualStudioVersion])) writeLineToFile(0, fileHandle, "# Visual Studio {}".format(self._visualStudioVersion)) projectFilterList = [] for projectData in self._orderedProjectList: if not projectData.isFilter: relativeProjectPath = os.path.relpath(projectData.outputPath, self.rootpath) projectFilePath = os.path.join(relativeProjectPath, "{}.{}".format(projectData.name, self._projectFileType)) nodeId = "{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}" else: projectFilePath = projectData.name nodeId = "{2150E333-8FDC-42A3-9474-1A3956D46DE8}" # Keep track of the filters because we need to record those after the projects. projectFilterList.append(projectData) beginProject = 'Project("{}") = "{}", "{}", "{}"'.format(nodeId, projectData.name, projectFilePath, projectData.id) writeLineToFile(0, fileHandle, beginProject) # Only write out project dependency information if the current project has any dependencies and if we're creating a native project. # If we're not creating a native project, it doesn't matter since csbuild will take care of all that for us behind the scenes. # Also, don't list any dependencies for filters. Those will be written out under NestedProjects. if self._createNativeProject and not projectData.isFilter and len(projectData.dependencyList) > 0: writeLineToFile(1, fileHandle, "ProjectSection(ProjectDependencies) = postProject") # Write out the IDs for each dependent project. for dependentProjectData in projectData.dependencyList: writeLineToFile(2, fileHandle, "{0} = {0}".format(dependentProjectData.id)) writeLineToFile(1, fileHandle, "EndProjectSection") writeLineToFile(0, fileHandle, "EndProject") writeLineToFile(0, fileHandle, "Global") # Write all of the supported configurations and platforms. writeLineToFile(1, fileHandle, "GlobalSection(SolutionConfigurationPlatforms) = preSolution") for buildTarget in self._configList: for platformName in self._platformList: writeLineToFile(2, fileHandle, "{0}|{1} = {0}|{1}".format(buildTarget, platformName)) writeLineToFile(1, fileHandle, "EndGlobalSection") writeLineToFile(1, fileHandle, "GlobalSection(ProjectConfigurationPlatforms) = postSolution") for projectData in self._orderedProjectList: if not projectData.isFilter: for buildTarget in self._configList: for platformName in self._platformList: writeLineToFile(2, fileHandle, "{0}.{1}|{2}.ActiveCfg = {1}|{2}".format(projectData.id, buildTarget, platformName)) # A project is only enabled for a given platform if it's the Build All project (only applies to non-native solutions) # or if the project is listed under the current configuration and platform. if projectData.isBuildAllProject or (self._createNativeProject and projectData.HasConfigAndPlatform(buildTarget, platformName)): writeLineToFile(2, fileHandle, "{0}.{1}|{2}.Build.0 = {1}|{2}".format(projectData.id, buildTarget, platformName)) writeLineToFile(1, fileHandle, "EndGlobalSection") writeLineToFile(1, fileHandle, "GlobalSection(SolutionProperties) = preSolution") writeLineToFile(2, fileHandle, "HideSolutionNode = FALSE") writeLineToFile(1, fileHandle, "EndGlobalSection") # Write out any information about nested projects. if len(projectFilterList) > 0: writeLineToFile(1, fileHandle, "GlobalSection(NestedProjects) = preSolution") for filterData in projectFilterList: for nestedProjectData in filterData.dependencyList: writeLineToFile(2, fileHandle, "{} = {}".format(nestedProjectData.id, filterData.id)) writeLineToFile(1, fileHandle, "EndGlobalSection") writeLineToFile(0, fileHandle, "EndGlobal") with open(tempSolutionPath, "rb") as fileHandle: fileData = fileHandle.read() cachedFile = CachedFileData(finalSolutionPath, fileData, False) cachedFile.SaveFile() if os.access(tempSolutionPath, os.F_OK): os.remove(tempSolutionPath) try: # Attempt to remove the temp directory. This will only fail if the directory already existed with files in it. # In that case, just catch the exception and move on. os.rmdir(tempRootPath) except: pass def _WriteVcxprojFiles(self): CreateRootNode = ET.Element AddNode = ET.SubElement for projectData in self._orderedProjectList: if not projectData.isFilter: rootNode = CreateRootNode("Project") rootNode.set("DefaultTargets", "Build") rootNode.set("ToolsVersion", "4.0") rootNode.set("xmlns", "http://schemas.microsoft.com/developer/msbuild/2003") itemGroupNode = AddNode(rootNode, "ItemGroup") itemGroupNode.set("Label", "ProjectConfigurations") # Add the project configurations for configName, platformName, toolchainName, _ in projectData.platformConfigList: projectConfigNode = AddNode(itemGroupNode, "ProjectConfiguration") configNode = AddNode(projectConfigNode, "Configuration") platformNode = AddNode(projectConfigNode, "Platform") projectConfigNode.set("Include", "{}|{}".format(configName, platformName)) configNode.text = configName platformNode.text = platformName # Add the project's source files. if len(projectData.fullSourceFileList) > 0: itemGroupNode = AddNode(rootNode, "ItemGroup") for sourceFilePath in projectData.fullSourceFileList: sourceFileNode = AddNode(itemGroupNode, "ClCompile") sourceFileNode.set("Include", os.path.relpath(sourceFilePath, projectData.outputPath)) # Handle any configuration or platform excludes for the current file. for configName, platformName, toolchainName, settings in projectData.platformConfigList: if not sourceFilePath in settings.allsources: excludeNode = AddNode(sourceFileNode, "ExcludedFromBuild") excludeNode.text = "true" excludeNode.set("Condition", "'$(Configuration)|$(Platform)'=='{}|{}'".format(configName, platformName)) # Add the project's header files. if len(projectData.fullHeaderFileList) > 0: itemGroupNode = AddNode(rootNode, "ItemGroup") for sourceFilePath in projectData.fullHeaderFileList: sourceFileNode = AddNode(itemGroupNode, "ClInclude") sourceFileNode.set("Include", os.path.relpath(sourceFilePath, projectData.outputPath)) # Handle any configuration or platform excludes for the current file. for configName, platformName, toolchainName, settings in projectData.platformConfigList: if not sourceFilePath in settings.allheaders: excludeNode = AddNode(sourceFileNode, "ExcludedFromBuild") excludeNode.text = "true" excludeNode.set("Condition", "'$(Configuration)|$(Platform)'=='{}|{}'".format(configName, platformName)) if not self._createNativeProject: itemGroupNode = AddNode(rootNode, "ItemGroup") makefileNode = AddNode(itemGroupNode, "None") makefileNode.set("Include", os.path.relpath(projectData.makefilePath, projectData.outputPath)) propertyGroupNode = AddNode(rootNode, "PropertyGroup") importNode = AddNode(rootNode, "Import") projectGuidNode = AddNode(propertyGroupNode, "ProjectGuid") namespaceNode = AddNode(propertyGroupNode, "RootNamespace") propertyGroupNode.set("Label", "Globals") importNode.set("Project", r"$(VCTargetsPath)\Microsoft.Cpp.Default.props") projectGuidNode.text = projectData.id namespaceNode.text = projectData.name # If we're not creating a native project, Visual Studio needs to know this is a makefile project. if not self._createNativeProject: keywordNode = AddNode(propertyGroupNode, "Keyword") keywordNode.text = "MakeFileProj" for configName, platformName, toolchainName, _ in projectData.platformConfigList: propertyGroupNode = AddNode(rootNode, "PropertyGroup") propertyGroupNode.set("Label", "Configuration") propertyGroupNode.set("Condition", "'$(Configuration)|$(Platform)'=='{}|{}'".format(configName, platformName)) if IsMicrosoftPlatform(platformName): platformToolsetNode = AddNode(propertyGroupNode, "PlatformToolset") platformToolsetNode.text = GetPlatformToolsetString(self._visualStudioVersion) if self._createNativeProject: # TODO: Add properties for native projects. pass else: configTypeNode = AddNode(propertyGroupNode, "ConfigurationType") configTypeNode.text = "Makefile" importNode = AddNode(rootNode, "Import") importNode.set("Project", r"$(VCTargetsPath)\Microsoft.Cpp.props") for configName, platformName, toolchainName, _ in projectData.platformConfigList: importGroupNode = AddNode(rootNode, "ImportGroup") importGroupNode.set("Label", "PropertySheets") importGroupNode.set("Condition", "'$(Configuration)|$(Platform)'=='{}|{}'".format(configName, platformName)) # Microsoft platforms import special property sheets. if IsMicrosoftPlatform(platformName): importNode = AddNode(importGroupNode, "Import") importNode.set("Label", "LocalAppDataPlatform") importNode.set("Project", r"$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props") importNode.set("Condition", "exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')") for configName, platformName, toolchainName, settings in projectData.platformConfigList: propertyGroupNode = AddNode(rootNode, "PropertyGroup") propertyGroupNode.set("Condition", "'$(Configuration)|$(Platform)'=='{}|{}'".format(configName, platformName)) if self._createNativeProject: # TODO: Add properties for non-native projects. pass else: buildCommandNode = AddNode(propertyGroupNode, "NMakeBuildCommandLine") cleanCommandNode = AddNode(propertyGroupNode, "NMakeCleanCommandLine") rebuildCommandNode = AddNode(propertyGroupNode, "NMakeReBuildCommandLine") includePathNode = AddNode(propertyGroupNode, "NMakeIncludeSearchPath") outDirNode = AddNode(propertyGroupNode, "OutDir") intDirNode = AddNode(propertyGroupNode, "IntDir") pythonExePath = os.path.normcase(sys.executable) mainMakefile = os.path.relpath(os.path.join(os.getcwd(), csbuild.mainFile), projectData.outputPath) if not projectData.isRegenProject: archName = self._archMap[platformName] projectArg = " --project={}".format(projectData.name) if not projectData.isBuildAllProject else "" properConfigName = self._reverseConfigMap[configName] buildCommandNode.text = '"{}" "{}" --target={} --architecture={}{} {}'.format(pythonExePath, mainMakefile, properConfigName, archName, projectArg, self._extraBuildArgs) cleanCommandNode.text = '"{}" "{}" --clean --target={} --architecture={}{} {}'.format(pythonExePath, mainMakefile, properConfigName, archName, projectArg, self._extraBuildArgs) rebuildCommandNode.text = '"{}" "{}" --rebuild --target={} --architecture={}{} {}'.format(pythonExePath, mainMakefile, properConfigName, archName, projectArg, self._extraBuildArgs) includePathNode.text = ";".join(self._fullIncludePathList) else: argList = [] # The Windows command line likes to remove any quotes around arguments, so we need to re-add them. # And because we can't know which args need quotes, we'll just add them to all of the arguments. for arg in sys.argv[1:]: argPair = arg.split("=", 2) for element in argPair: argList.append('"{}"'.format(element)) buildCommandNode.text = '"{}" "{}" {}'.format(pythonExePath, mainMakefile, " ".join(argList)) rebuildCommandNode.text = buildCommandNode.text cleanCommandNode.text = buildCommandNode.text if settings and settings.defines and len(settings.defines) > 0: preprocessorNode = AddNode(propertyGroupNode, "NMakePreprocessorDefinitions") preprocessorNode.text = "" for define in settings.defines: preprocessorNode.text += "{};".format(define) preprocessorNode.text += "$(NMakePreprocessorDefinitions)" if not projectData.isBuildAllProject and not projectData.isRegenProject: outputNode = AddNode(propertyGroupNode, "NMakeOutput") outDirNode.text = os.path.relpath(settings.outputDir, projectData.outputPath) intDirNode.text = os.path.relpath(settings.objDir, projectData.outputPath) outputNode.text = os.path.relpath(os.path.join(settings.outputDir, settings.outputName), projectData.outputPath) else: # Gotta put this stuff somewhere for the Build All project. outDirNode.text = projectData.name + "_log" intDirNode.text = "$(OutDir)" importNode = AddNode(rootNode, "Import") importNode.set("Project", r"$(VCTargetsPath)\Microsoft.Cpp.targets") self._SaveXmlFile(rootNode, os.path.join(projectData.outputPath, "{}.{}".format(projectData.name, self._projectFileType)), False) def _WriteVcxprojFiltersFiles(self): CreateRootNode = ET.Element AddNode = ET.SubElement for projectData in self._orderedProjectList: if not projectData.isFilter: rootNode = CreateRootNode("Project") rootNode.set("ToolsVersion", "4.0") rootNode.set("xmlns", "http://schemas.microsoft.com/developer/msbuild/2003") if not projectData.isBuildAllProject and not projectData.isRegenProject: itemGroupNode = AddNode(rootNode, "ItemGroup") # Gather a list of filter paths. filterList = set() for file, filter in projectData.fileFilterMap.items(): filterList.add(filter) # Visual Studio requires that each directory level be registered as individual filters. while True: filter = os.path.dirname(filter) if filter: filterList.add(filter) else: break # Add each path as a new filter node. filterGuidList = set() for filter in sorted(filterList): filterNode = AddNode(itemGroupNode, "Filter") uniqueIdNode = AddNode(filterNode, "UniqueIdentifier") filterNode.set("Include", filter) uniqueIdNode.text = "{{{}}}".format(GenerateNewUuid(filterGuidList, filter)) # Add the project's source files. if len(projectData.fullSourceFileList) > 0: itemGroupNode = AddNode(rootNode, "ItemGroup") for sourceFilePath in projectData.fullSourceFileList: sourceFileNode = AddNode(itemGroupNode, "ClCompile") filterNode = AddNode(sourceFileNode, "Filter") sourceFileNode.set("Include", os.path.relpath(sourceFilePath, projectData.outputPath)) filterNode.text = projectData.fileFilterMap[sourceFilePath] # Add the project's header files. if len(projectData.fullHeaderFileList) > 0: itemGroupNode = AddNode(rootNode, "ItemGroup") for headerFilePath in projectData.fullHeaderFileList: headerFileNode = AddNode(itemGroupNode, "ClInclude") filterNode = AddNode(headerFileNode, "Filter") headerFileNode.set("Include", os.path.relpath(headerFilePath, projectData.outputPath)) filterNode.text = projectData.fileFilterMap[headerFilePath] self._SaveXmlFile(rootNode, os.path.join(projectData.outputPath, "{}.{}.filters".format(projectData.name, self._projectFileType)), False) def _WriteVcxprojUserFiles(self): CreateRootNode = ET.Element AddNode = ET.SubElement for projectData in self._orderedProjectList: if not projectData.isFilter: rootNode = CreateRootNode("Project") rootNode.set("ToolsVersion", "4.0") rootNode.set("xmlns", "http://schemas.microsoft.com/developer/msbuild/2003") if not projectData.isBuildAllProject and not projectData.isRegenProject: for configName, platformName, toolchainName, _ in projectData.platformConfigList: if IsMicrosoftPlatform(platformName): propertyGroupNode = AddNode(rootNode, "PropertyGroup") workingDirNode = AddNode(propertyGroupNode, "LocalDebuggerWorkingDirectory") debuggerTypeNode = AddNode(propertyGroupNode, "LocalDebuggerDebuggerType") debuggerFlavorNode = AddNode(propertyGroupNode, "DebuggerFlavor") propertyGroupNode.set("Condition", "'$(Configuration)|$(Platform)'=='{}|{}'".format(configName, platformName)) workingDirNode.text = "$(OutDir)" debuggerTypeNode.text = "NativeOnly" debuggerFlavorNode.text = "WindowsLocalDebugger" self._SaveXmlFile(rootNode, os.path.join(projectData.outputPath, "{}.{}.user".format(projectData.name, self._projectFileType)), True) def _SaveXmlFile(self, rootNode, xmlFilename, isUserFile): # Grab a string of the XML document we've created and save it. xmlString = ET.tostring(rootNode) # Convert to the original XML to a string on Python3. if sys.version_info >= (3, 0): xmlString = xmlString.decode("utf-8") # Use minidom to reformat the XML since ElementTree doesn't do it for us. formattedXmlString = minidom.parseString(xmlString).toprettyxml("\t", "\n", encoding = "utf-8") if sys.version_info >= (3, 0): formattedXmlString = formattedXmlString.decode("utf-8") inputLines = formattedXmlString.split("\n") outputLines = [] # Copy each line of the XML to a list of strings. for line in inputLines: outputLines.append(line) # Concatenate each string with a newline. finalXmlString = "\n".join(outputLines) if sys.version_info >= (3, 0): finalXmlString = finalXmlString.encode("utf-8") cachedFile = CachedFileData(xmlFilename, finalXmlString, isUserFile) cachedFile.SaveFile()