| # Copyright 2017 The Chromium Authors. All rights reserved. | 
 | # Use of this source code is governed by a BSD-style license that can be | 
 | # found in the LICENSE file. | 
 | # | 
 | # gn_meta_sln.py | 
 | #   Helper utility to combine GN-generated Visual Studio projects into | 
 | #   a single meta-solution. | 
 |  | 
 | import os | 
 | import glob | 
 | import re | 
 | import sys | 
 | from shutil import copyfile | 
 |  | 
 | # Helpers | 
 | def EnsureExists(path): | 
 |     try: | 
 |         os.makedirs(path) | 
 |     except OSError: | 
 |         pass | 
 |  | 
 | def WriteLinesToFile(lines, file_name): | 
 |     EnsureExists(os.path.dirname(file_name)) | 
 |     with open(file_name, "w") as f: | 
 |         f.writelines(lines) | 
 |  | 
 | def ExtractIdg(proj_file_name): | 
 |     result = [] | 
 |     with open(proj_file_name) as proj_file: | 
 |         lines = iter(proj_file) | 
 |         for p_line in lines: | 
 |             if "<ItemDefinitionGroup" in p_line: | 
 |                 while not "</ItemDefinitionGroup" in p_line: | 
 |                     result.append(p_line) | 
 |                     p_line = lines.next() | 
 |                 result.append(p_line) | 
 |                 return result | 
 |  | 
 | # [ (name, solution_name, vs_version), ... ] | 
 | configs = [] | 
 |  | 
 | def GetVSVersion(solution_file): | 
 |     with open(solution_file) as f: | 
 |         f.readline() | 
 |         comment = f.readline().strip() | 
 |         return comment[-4:] | 
 |  | 
 | # Find all directories that can be used as configs (and record if they have VS | 
 | # files present) | 
 | for root, dirs, files in os.walk("out"): | 
 |     for out_dir in dirs: | 
 |         gn_file = os.path.join("out", out_dir, "build.ninja.d") | 
 |         if os.path.exists(gn_file): | 
 |             solutions = glob.glob(os.path.join("out", out_dir, "*.sln")) | 
 |             for solution in solutions: | 
 |                 vs_version = GetVSVersion(solution) | 
 |                 configs.append((out_dir, os.path.basename(solution), | 
 |                                 vs_version)) | 
 |     break | 
 |  | 
 | # Every project has a GUID that encodes the type. We only care about C++. | 
 | cpp_type_guid = "8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942" | 
 |  | 
 | # Work around MSBuild limitations by always using a fixed arch. | 
 | hard_coded_arch = "x64" | 
 |  | 
 | # name -> [ (config, pathToProject, GUID, arch), ... ] | 
 | all_projects = {} | 
 | project_pattern = (r'Project\("\{' + cpp_type_guid + | 
 |                    r'\}"\) = "([^"]*)", "([^"]*)", "\{([^\}]*)\}"') | 
 |  | 
 | # We need something to work with. Typically, this will fail if no GN folders | 
 | # have IDE files | 
 | if len(configs) == 0: | 
 |     print("ERROR: At least one GN directory must have been built with --ide=vs") | 
 |     sys.exit() | 
 |  | 
 | # Filter out configs which don't match the name and vs version of the first. | 
 | name = configs[0][1] | 
 | vs_version = configs[0][2] | 
 |  | 
 | for config in configs: | 
 |     if config[1] != name or config[2] != vs_version: | 
 |         continue | 
 |  | 
 |     sln_lines = iter(open(os.path.join("out", config[0], config[1]))) | 
 |     for sln_line in sln_lines: | 
 |         match_obj = re.match(project_pattern, sln_line) | 
 |         if match_obj: | 
 |             proj_name = match_obj.group(1) | 
 |             if not all_projects.has_key(proj_name): | 
 |                 all_projects[proj_name] = [] | 
 |             all_projects[proj_name].append((config[0], match_obj.group(2), | 
 |                                             match_obj.group(3))) | 
 |  | 
 | # We need something to work with. Typically, this will fail if no GN folders | 
 | # have IDE files | 
 | if len(all_projects) == 0: | 
 |     print("ERROR: At least one GN directory must have been built with --ide=vs") | 
 |     sys.exit() | 
 |  | 
 | # Create a new solution. We arbitrarily use the first config as the GUID source | 
 | # (but we need to match that behavior later, when we copy/generate the project | 
 | # files). | 
 | new_sln_lines = [] | 
 | new_sln_lines.append( | 
 |     'Microsoft Visual Studio Solution File, Format Version 12.00\n') | 
 | new_sln_lines.append('# Visual Studio ' + vs_version + '\n') | 
 | for proj_name, proj_configs in all_projects.items(): | 
 |     new_sln_lines.append('Project("{' + cpp_type_guid + '}") = "' + proj_name + | 
 |                          '", "' + proj_configs[0][1] + '", "{' + | 
 |                          proj_configs[0][2] + '}"\n') | 
 |     new_sln_lines.append('EndProject\n') | 
 |  | 
 | new_sln_lines.append('Global\n') | 
 | new_sln_lines.append( | 
 |     '\tGlobalSection(SolutionConfigurationPlatforms) = preSolution\n') | 
 | for config in configs: | 
 |     match = config[0] + '|' + hard_coded_arch | 
 |     new_sln_lines.append('\t\t' + match + ' = ' + match + '\n') | 
 | new_sln_lines.append('\tEndGlobalSection\n') | 
 | new_sln_lines.append( | 
 |     '\tGlobalSection(ProjectConfigurationPlatforms) = postSolution\n') | 
 | for proj_name, proj_configs in all_projects.items(): | 
 |     proj_guid = proj_configs[0][2] | 
 |     for config in configs: | 
 |         match = config[0] + '|' + hard_coded_arch | 
 |         new_sln_lines.append('\t\t{' + proj_guid + '}.' + match + | 
 |                            '.ActiveCfg = ' + match + '\n') | 
 |         new_sln_lines.append('\t\t{' + proj_guid + '}.' + match + | 
 |                            '.Build.0 = ' + match + '\n') | 
 | new_sln_lines.append('\tEndGlobalSection\n') | 
 | new_sln_lines.append('\tGlobalSection(SolutionProperties) = preSolution\n') | 
 | new_sln_lines.append('\t\tHideSolutionNode = FALSE\n') | 
 | new_sln_lines.append('\tEndGlobalSection\n') | 
 | new_sln_lines.append('\tGlobalSection(NestedProjects) = preSolution\n') | 
 | new_sln_lines.append('\tEndGlobalSection\n') | 
 | new_sln_lines.append('EndGlobal\n') | 
 |  | 
 | # Write solution file | 
 | WriteLinesToFile(new_sln_lines, 'out/sln/' + name) | 
 |  | 
 | idg_hdr = "<ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='" | 
 |  | 
 | configuration_template = """    <ProjectConfiguration Include="{config}|{arch}"> | 
 |       <Configuration>{config}</Configuration> | 
 |       <Platform>{arch}</Platform> | 
 |     </ProjectConfiguration> | 
 | """ | 
 |  | 
 | def FormatProjectConfig(config): | 
 |     return configuration_template.format( | 
 |         config = config[0], arch = hard_coded_arch) | 
 |  | 
 | # Now, bring over the project files | 
 | for proj_name, proj_configs in all_projects.items(): | 
 |     # Paths to project and filter file in src and dst locations | 
 |     src_proj_path = os.path.join("out", proj_configs[0][0], proj_configs[0][1]) | 
 |     dst_proj_path = os.path.join("out", "sln", proj_configs[0][1]) | 
 |     src_filter_path = src_proj_path + ".filters" | 
 |     dst_filter_path = dst_proj_path + ".filters" | 
 |  | 
 |     # Copy the filter file unmodified | 
 |     EnsureExists(os.path.dirname(dst_proj_path)) | 
 |     copyfile(src_filter_path, dst_filter_path) | 
 |  | 
 |     preferred_tool_arch = None | 
 |     config_arch = {} | 
 |  | 
 |     # Bring over the project file, modified with extra configs | 
 |     with open(src_proj_path) as src_proj_file: | 
 |         proj_lines = iter(src_proj_file) | 
 |         new_proj_lines = [] | 
 |         for line in proj_lines: | 
 |             if "<ItemDefinitionGroup" in line: | 
 |                 # This is a large group that contains many settings. We need to | 
 |                 # replicate it, with conditions so it varies per configuration. | 
 |                 idg_lines = [] | 
 |                 while not "</ItemDefinitionGroup" in line: | 
 |                     idg_lines.append(line) | 
 |                     line = proj_lines.next() | 
 |                 idg_lines.append(line) | 
 |                 for proj_config in proj_configs: | 
 |                     config_idg_lines = ExtractIdg(os.path.join("out", | 
 |                                                              proj_config[0], | 
 |                                                              proj_config[1])) | 
 |                     match = proj_config[0] + '|' + hard_coded_arch | 
 |                     new_proj_lines.append(idg_hdr + match + "'\">\n") | 
 |                     for idg_line in config_idg_lines[1:]: | 
 |                         new_proj_lines.append(idg_line) | 
 |             elif "ProjectConfigurations" in line: | 
 |                 new_proj_lines.append(line) | 
 |                 proj_lines.next() | 
 |                 proj_lines.next() | 
 |                 proj_lines.next() | 
 |                 proj_lines.next() | 
 |                 for config in configs: | 
 |                     new_proj_lines.append(FormatProjectConfig(config)) | 
 |  | 
 |             elif "<OutDir" in line: | 
 |                 new_proj_lines.append(line.replace(proj_configs[0][0], | 
 |                                                  "$(Configuration)")) | 
 |             elif "<PreferredToolArchitecture" in line: | 
 |                 new_proj_lines.append("    <PreferredToolArchitecture>" + | 
 |                                       hard_coded_arch + | 
 |                                       "</PreferredToolArchitecture>\n") | 
 |             else: | 
 |                 new_proj_lines.append(line) | 
 |         with open(dst_proj_path, "w") as new_proj: | 
 |             new_proj.writelines(new_proj_lines) | 
 |  | 
 | print('Wrote meta solution to out/sln/' + name) |