1# Copyright 2016 The Chromium Authors. All rights reserved. 2# Use of this source code is governed by a BSD-style license that can be 3# found in the LICENSE file. 4 5from __future__ import print_function 6 7import os 8import glob 9import re 10import sys 11from shutil import copyfile 12 13# Helpers 14def ensureExists(path): 15 try: 16 os.makedirs(path) 17 except OSError: 18 pass 19 20def writeLinesToFile(lines, fileName): 21 ensureExists(os.path.dirname(fileName)) 22 with open(fileName, "w") as f: 23 f.writelines(lines) 24 25def extractIdg(projFileName): 26 result = [] 27 with open(projFileName) as projFile: 28 lines = iter(projFile) 29 for pLine in lines: 30 if "<ItemDefinitionGroup" in pLine: 31 while not "</ItemDefinitionGroup" in pLine: 32 result.append(pLine) 33 pLine = lines.next() 34 result.append(pLine) 35 return result 36 37# [ (name, hasSln), ... ] 38configs = [] 39 40# Find all directories that can be used as configs (and record if they have VS 41# files present) 42for root, dirs, files in os.walk("out"): 43 for outDir in dirs: 44 gnFile = os.path.join("out", outDir, "build.ninja.d") 45 if os.path.exists(gnFile): 46 slnFile = os.path.join("out", outDir, "all.sln") 47 configs.append((outDir, os.path.exists(slnFile))) 48 break 49 50# Every project has a GUID that encodes the type. We only care about C++. 51cppTypeGuid = "8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942" 52 53# name -> [ (config, pathToProject, GUID), ... ] 54allProjects = {} 55projectPattern = (r'Project\("\{' + cppTypeGuid + 56 r'\}"\) = "([^"]*)", "([^"]*)", "\{([^\}]*)\}"') 57projectNamePattern = (r'obj/(.*)\.vcxproj') 58 59for config in configs: 60 if config[1]: 61 slnLines = iter(open("out/" + config[0] + "/all.sln")) 62 for slnLine in slnLines: 63 matchObj = re.match(projectPattern, slnLine) 64 if matchObj: 65 projPath = matchObj.group(2) 66 nameObj = re.match(projectNamePattern, projPath) 67 if nameObj: 68 projName = nameObj.group(1).replace('/', '.') 69 if not allProjects.has_key(projName): 70 allProjects[projName] = [] 71 allProjects[projName].append((config[0], projPath, 72 matchObj.group(3))) 73 74# We need something to work with. Typically, this will fail if no GN folders 75# have IDE files 76if len(allProjects) == 0: 77 print("ERROR: At least one GN directory must have been built with --ide=vs") 78 sys.exit() 79 80# Create a new solution. We arbitrarily use the first config as the GUID source 81# (but we need to match that behavior later, when we copy/generate the project 82# files). 83newSlnLines = [] 84newSlnLines.append( 85 'Microsoft Visual Studio Solution File, Format Version 12.00\n') 86newSlnLines.append('# Visual Studio 2015\n') 87for projName, projConfigs in allProjects.items(): 88 newSlnLines.append('Project("{' + cppTypeGuid + '}") = "' + projName + 89 '", "' + projConfigs[0][1] + '", "{' + projConfigs[0][2] 90 + '}"\n') 91 newSlnLines.append('EndProject\n') 92 93newSlnLines.append('Global\n') 94newSlnLines.append( 95 '\tGlobalSection(SolutionConfigurationPlatforms) = preSolution\n') 96for config in configs: 97 newSlnLines.append('\t\t' + config[0] + '|x64 = ' + config[0] + '|x64\n') 98newSlnLines.append('\tEndGlobalSection\n') 99newSlnLines.append( 100 '\tGlobalSection(ProjectConfigurationPlatforms) = postSolution\n') 101for projName, projConfigs in allProjects.items(): 102 projGuid = projConfigs[0][2] 103 for config in configs: 104 newSlnLines.append('\t\t{' + projGuid + '}.' + config[0] + 105 '|x64.ActiveCfg = ' + config[0] + '|x64\n') 106 newSlnLines.append('\t\t{' + projGuid + '}.' + config[0] + 107 '|x64.Build.0 = ' + config[0] + '|x64\n') 108newSlnLines.append('\tEndGlobalSection\n') 109newSlnLines.append('\tGlobalSection(SolutionProperties) = preSolution\n') 110newSlnLines.append('\t\tHideSolutionNode = FALSE\n') 111newSlnLines.append('\tEndGlobalSection\n') 112newSlnLines.append('\tGlobalSection(NestedProjects) = preSolution\n') 113newSlnLines.append('\tEndGlobalSection\n') 114newSlnLines.append('EndGlobal\n') 115 116# Write solution file 117writeLinesToFile(newSlnLines, "out/sln/skia.sln") 118 119idgHdr = "<ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='" 120 121# Now, bring over the project files 122for projName, projConfigs in allProjects.items(): 123 # Paths to project and filter file in src and dst locations 124 srcProjPath = os.path.join("out", projConfigs[0][0], projConfigs[0][1]) 125 dstProjPath = os.path.join("out", "sln", projConfigs[0][1]) 126 srcFilterPath = srcProjPath + ".filters" 127 dstFilterPath = dstProjPath + ".filters" 128 129 # Copy the filter file unmodified 130 ensureExists(os.path.dirname(dstProjPath)) 131 copyfile(srcFilterPath, dstFilterPath) 132 133 # Bring over the project file, modified with extra configs 134 with open(srcProjPath) as srcProjFile: 135 projLines = iter(srcProjFile) 136 newProjLines = [] 137 for line in projLines: 138 if "<ItemDefinitionGroup" in line: 139 # This is a large group that contains many settings. We need to 140 # replicate it, with conditions so it varies per configuration. 141 idgLines = [] 142 while not "</ItemDefinitionGroup" in line: 143 idgLines.append(line) 144 line = projLines.next() 145 idgLines.append(line) 146 for projConfig in projConfigs: 147 configIdgLines = extractIdg(os.path.join("out", 148 projConfig[0], 149 projConfig[1])) 150 newProjLines.append(idgHdr + projConfig[0] + "|x64'\">\n") 151 for idgLine in configIdgLines[1:]: 152 newProjLines.append(idgLine) 153 elif "ProjectConfigurations" in line: 154 newProjLines.append(line) 155 projConfigLines = [ 156 projLines.next(), 157 projLines.next(), 158 projLines.next(), 159 projLines.next() ] 160 for config in configs: 161 for projConfigLine in projConfigLines: 162 newProjLines.append(projConfigLine.replace("GN", 163 config[0])) 164 elif "<OutDir" in line: 165 newProjLines.append(line.replace(projConfigs[0][0], 166 "$(Configuration)")) 167 else: 168 newProjLines.append(line) 169 with open(dstProjPath, "w") as newProj: 170 newProj.writelines(newProjLines) 171