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