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