1*8975f5c5SAndroid Build Coastguard Worker# Copyright 2017 The Chromium Authors 2*8975f5c5SAndroid Build Coastguard Worker# Use of this source code is governed by a BSD-style license that can be 3*8975f5c5SAndroid Build Coastguard Worker# found in the LICENSE file. 4*8975f5c5SAndroid Build Coastguard Worker# 5*8975f5c5SAndroid Build Coastguard Worker# gn_meta_sln.py 6*8975f5c5SAndroid Build Coastguard Worker# Helper utility to combine GN-generated Visual Studio projects into 7*8975f5c5SAndroid Build Coastguard Worker# a single meta-solution. 8*8975f5c5SAndroid Build Coastguard Worker 9*8975f5c5SAndroid Build Coastguard Worker 10*8975f5c5SAndroid Build Coastguard Workerimport os 11*8975f5c5SAndroid Build Coastguard Workerimport glob 12*8975f5c5SAndroid Build Coastguard Workerimport re 13*8975f5c5SAndroid Build Coastguard Workerimport sys 14*8975f5c5SAndroid Build Coastguard Workerfrom shutil import copyfile 15*8975f5c5SAndroid Build Coastguard Worker 16*8975f5c5SAndroid Build Coastguard Worker# Helpers 17*8975f5c5SAndroid Build Coastguard Workerdef EnsureExists(path): 18*8975f5c5SAndroid Build Coastguard Worker try: 19*8975f5c5SAndroid Build Coastguard Worker os.makedirs(path) 20*8975f5c5SAndroid Build Coastguard Worker except OSError: 21*8975f5c5SAndroid Build Coastguard Worker pass 22*8975f5c5SAndroid Build Coastguard Worker 23*8975f5c5SAndroid Build Coastguard Workerdef WriteLinesToFile(lines, file_name): 24*8975f5c5SAndroid Build Coastguard Worker EnsureExists(os.path.dirname(file_name)) 25*8975f5c5SAndroid Build Coastguard Worker with open(file_name, "w") as f: 26*8975f5c5SAndroid Build Coastguard Worker f.writelines(lines) 27*8975f5c5SAndroid Build Coastguard Worker 28*8975f5c5SAndroid Build Coastguard Workerdef ExtractIdg(proj_file_name): 29*8975f5c5SAndroid Build Coastguard Worker result = [] 30*8975f5c5SAndroid Build Coastguard Worker with open(proj_file_name) as proj_file: 31*8975f5c5SAndroid Build Coastguard Worker lines = iter(proj_file) 32*8975f5c5SAndroid Build Coastguard Worker for p_line in lines: 33*8975f5c5SAndroid Build Coastguard Worker if "<ItemDefinitionGroup" in p_line: 34*8975f5c5SAndroid Build Coastguard Worker while not "</ItemDefinitionGroup" in p_line: 35*8975f5c5SAndroid Build Coastguard Worker result.append(p_line) 36*8975f5c5SAndroid Build Coastguard Worker p_line = lines.next() 37*8975f5c5SAndroid Build Coastguard Worker result.append(p_line) 38*8975f5c5SAndroid Build Coastguard Worker return result 39*8975f5c5SAndroid Build Coastguard Worker 40*8975f5c5SAndroid Build Coastguard Worker# [ (name, solution_name, vs_version), ... ] 41*8975f5c5SAndroid Build Coastguard Workerconfigs = [] 42*8975f5c5SAndroid Build Coastguard Worker 43*8975f5c5SAndroid Build Coastguard Workerdef GetVSVersion(solution_file): 44*8975f5c5SAndroid Build Coastguard Worker with open(solution_file) as f: 45*8975f5c5SAndroid Build Coastguard Worker f.readline() 46*8975f5c5SAndroid Build Coastguard Worker comment = f.readline().strip() 47*8975f5c5SAndroid Build Coastguard Worker return comment[-4:] 48*8975f5c5SAndroid Build Coastguard Worker 49*8975f5c5SAndroid Build Coastguard Worker# Find all directories that can be used as configs (and record if they have VS 50*8975f5c5SAndroid Build Coastguard Worker# files present) 51*8975f5c5SAndroid Build Coastguard Workerfor root, dirs, files in os.walk("out"): 52*8975f5c5SAndroid Build Coastguard Worker for out_dir in dirs: 53*8975f5c5SAndroid Build Coastguard Worker gn_file = os.path.join("out", out_dir, "build.ninja.d") 54*8975f5c5SAndroid Build Coastguard Worker if os.path.exists(gn_file): 55*8975f5c5SAndroid Build Coastguard Worker solutions = glob.glob(os.path.join("out", out_dir, "*.sln")) 56*8975f5c5SAndroid Build Coastguard Worker for solution in solutions: 57*8975f5c5SAndroid Build Coastguard Worker vs_version = GetVSVersion(solution) 58*8975f5c5SAndroid Build Coastguard Worker configs.append((out_dir, os.path.basename(solution), 59*8975f5c5SAndroid Build Coastguard Worker vs_version)) 60*8975f5c5SAndroid Build Coastguard Worker break 61*8975f5c5SAndroid Build Coastguard Worker 62*8975f5c5SAndroid Build Coastguard Worker# Every project has a GUID that encodes the type. We only care about C++. 63*8975f5c5SAndroid Build Coastguard Workercpp_type_guid = "8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942" 64*8975f5c5SAndroid Build Coastguard Worker 65*8975f5c5SAndroid Build Coastguard Worker# Work around MSBuild limitations by always using a fixed arch. 66*8975f5c5SAndroid Build Coastguard Workerhard_coded_arch = "x64" 67*8975f5c5SAndroid Build Coastguard Worker 68*8975f5c5SAndroid Build Coastguard Worker# name -> [ (config, pathToProject, GUID, arch), ... ] 69*8975f5c5SAndroid Build Coastguard Workerall_projects = {} 70*8975f5c5SAndroid Build Coastguard Workerproject_pattern = (r'Project\("\{' + cpp_type_guid + 71*8975f5c5SAndroid Build Coastguard Worker r'\}"\) = "([^"]*)", "([^"]*)", "\{([^\}]*)\}"') 72*8975f5c5SAndroid Build Coastguard Worker 73*8975f5c5SAndroid Build Coastguard Worker# We need something to work with. Typically, this will fail if no GN folders 74*8975f5c5SAndroid Build Coastguard Worker# have IDE files 75*8975f5c5SAndroid Build Coastguard Workerif len(configs) == 0: 76*8975f5c5SAndroid Build Coastguard Worker print("ERROR: At least one GN directory must have been built with --ide=vs") 77*8975f5c5SAndroid Build Coastguard Worker sys.exit() 78*8975f5c5SAndroid Build Coastguard Worker 79*8975f5c5SAndroid Build Coastguard Worker# Filter out configs which don't match the name and vs version of the first. 80*8975f5c5SAndroid Build Coastguard Workername = configs[0][1] 81*8975f5c5SAndroid Build Coastguard Workervs_version = configs[0][2] 82*8975f5c5SAndroid Build Coastguard Worker 83*8975f5c5SAndroid Build Coastguard Workerfor config in configs: 84*8975f5c5SAndroid Build Coastguard Worker if config[1] != name or config[2] != vs_version: 85*8975f5c5SAndroid Build Coastguard Worker continue 86*8975f5c5SAndroid Build Coastguard Worker 87*8975f5c5SAndroid Build Coastguard Worker sln_lines = iter(open(os.path.join("out", config[0], config[1]))) 88*8975f5c5SAndroid Build Coastguard Worker for sln_line in sln_lines: 89*8975f5c5SAndroid Build Coastguard Worker match_obj = re.match(project_pattern, sln_line) 90*8975f5c5SAndroid Build Coastguard Worker if match_obj: 91*8975f5c5SAndroid Build Coastguard Worker proj_name = match_obj.group(1) 92*8975f5c5SAndroid Build Coastguard Worker if proj_name not in all_projects: 93*8975f5c5SAndroid Build Coastguard Worker all_projects[proj_name] = [] 94*8975f5c5SAndroid Build Coastguard Worker all_projects[proj_name].append((config[0], match_obj.group(2), 95*8975f5c5SAndroid Build Coastguard Worker match_obj.group(3))) 96*8975f5c5SAndroid Build Coastguard Worker 97*8975f5c5SAndroid Build Coastguard Worker# We need something to work with. Typically, this will fail if no GN folders 98*8975f5c5SAndroid Build Coastguard Worker# have IDE files 99*8975f5c5SAndroid Build Coastguard Workerif len(all_projects) == 0: 100*8975f5c5SAndroid Build Coastguard Worker print("ERROR: At least one GN directory must have been built with --ide=vs") 101*8975f5c5SAndroid Build Coastguard Worker sys.exit() 102*8975f5c5SAndroid Build Coastguard Worker 103*8975f5c5SAndroid Build Coastguard Worker# Create a new solution. We arbitrarily use the first config as the GUID source 104*8975f5c5SAndroid Build Coastguard Worker# (but we need to match that behavior later, when we copy/generate the project 105*8975f5c5SAndroid Build Coastguard Worker# files). 106*8975f5c5SAndroid Build Coastguard Workernew_sln_lines = [] 107*8975f5c5SAndroid Build Coastguard Workernew_sln_lines.append( 108*8975f5c5SAndroid Build Coastguard Worker 'Microsoft Visual Studio Solution File, Format Version 12.00\n') 109*8975f5c5SAndroid Build Coastguard Workernew_sln_lines.append('# Visual Studio ' + vs_version + '\n') 110*8975f5c5SAndroid Build Coastguard Workerfor proj_name, proj_configs in all_projects.items(): 111*8975f5c5SAndroid Build Coastguard Worker new_sln_lines.append('Project("{' + cpp_type_guid + '}") = "' + proj_name + 112*8975f5c5SAndroid Build Coastguard Worker '", "' + proj_configs[0][1] + '", "{' + 113*8975f5c5SAndroid Build Coastguard Worker proj_configs[0][2] + '}"\n') 114*8975f5c5SAndroid Build Coastguard Worker new_sln_lines.append('EndProject\n') 115*8975f5c5SAndroid Build Coastguard Worker 116*8975f5c5SAndroid Build Coastguard Workernew_sln_lines.append('Global\n') 117*8975f5c5SAndroid Build Coastguard Workernew_sln_lines.append( 118*8975f5c5SAndroid Build Coastguard Worker '\tGlobalSection(SolutionConfigurationPlatforms) = preSolution\n') 119*8975f5c5SAndroid Build Coastguard Workerfor config in configs: 120*8975f5c5SAndroid Build Coastguard Worker match = config[0] + '|' + hard_coded_arch 121*8975f5c5SAndroid Build Coastguard Worker new_sln_lines.append('\t\t' + match + ' = ' + match + '\n') 122*8975f5c5SAndroid Build Coastguard Workernew_sln_lines.append('\tEndGlobalSection\n') 123*8975f5c5SAndroid Build Coastguard Workernew_sln_lines.append( 124*8975f5c5SAndroid Build Coastguard Worker '\tGlobalSection(ProjectConfigurationPlatforms) = postSolution\n') 125*8975f5c5SAndroid Build Coastguard Workerfor proj_name, proj_configs in all_projects.items(): 126*8975f5c5SAndroid Build Coastguard Worker proj_guid = proj_configs[0][2] 127*8975f5c5SAndroid Build Coastguard Worker for config in configs: 128*8975f5c5SAndroid Build Coastguard Worker match = config[0] + '|' + hard_coded_arch 129*8975f5c5SAndroid Build Coastguard Worker new_sln_lines.append('\t\t{' + proj_guid + '}.' + match + 130*8975f5c5SAndroid Build Coastguard Worker '.ActiveCfg = ' + match + '\n') 131*8975f5c5SAndroid Build Coastguard Worker new_sln_lines.append('\t\t{' + proj_guid + '}.' + match + 132*8975f5c5SAndroid Build Coastguard Worker '.Build.0 = ' + match + '\n') 133*8975f5c5SAndroid Build Coastguard Workernew_sln_lines.append('\tEndGlobalSection\n') 134*8975f5c5SAndroid Build Coastguard Workernew_sln_lines.append('\tGlobalSection(SolutionProperties) = preSolution\n') 135*8975f5c5SAndroid Build Coastguard Workernew_sln_lines.append('\t\tHideSolutionNode = FALSE\n') 136*8975f5c5SAndroid Build Coastguard Workernew_sln_lines.append('\tEndGlobalSection\n') 137*8975f5c5SAndroid Build Coastguard Workernew_sln_lines.append('\tGlobalSection(NestedProjects) = preSolution\n') 138*8975f5c5SAndroid Build Coastguard Workernew_sln_lines.append('\tEndGlobalSection\n') 139*8975f5c5SAndroid Build Coastguard Workernew_sln_lines.append('EndGlobal\n') 140*8975f5c5SAndroid Build Coastguard Worker 141*8975f5c5SAndroid Build Coastguard Worker# Write solution file 142*8975f5c5SAndroid Build Coastguard WorkerWriteLinesToFile(new_sln_lines, 'out/sln/' + name) 143*8975f5c5SAndroid Build Coastguard Worker 144*8975f5c5SAndroid Build Coastguard Workeridg_hdr = "<ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='" 145*8975f5c5SAndroid Build Coastguard Worker 146*8975f5c5SAndroid Build Coastguard Workerconfiguration_template = """ <ProjectConfiguration Include="{config}|{arch}"> 147*8975f5c5SAndroid Build Coastguard Worker <Configuration>{config}</Configuration> 148*8975f5c5SAndroid Build Coastguard Worker <Platform>{arch}</Platform> 149*8975f5c5SAndroid Build Coastguard Worker </ProjectConfiguration> 150*8975f5c5SAndroid Build Coastguard Worker""" 151*8975f5c5SAndroid Build Coastguard Worker 152*8975f5c5SAndroid Build Coastguard Workerdef FormatProjectConfig(config): 153*8975f5c5SAndroid Build Coastguard Worker return configuration_template.format( 154*8975f5c5SAndroid Build Coastguard Worker config = config[0], arch = hard_coded_arch) 155*8975f5c5SAndroid Build Coastguard Worker 156*8975f5c5SAndroid Build Coastguard Worker# Now, bring over the project files 157*8975f5c5SAndroid Build Coastguard Workerfor proj_name, proj_configs in all_projects.items(): 158*8975f5c5SAndroid Build Coastguard Worker # Paths to project and filter file in src and dst locations 159*8975f5c5SAndroid Build Coastguard Worker src_proj_path = os.path.join("out", proj_configs[0][0], proj_configs[0][1]) 160*8975f5c5SAndroid Build Coastguard Worker dst_proj_path = os.path.join("out", "sln", proj_configs[0][1]) 161*8975f5c5SAndroid Build Coastguard Worker src_filter_path = src_proj_path + ".filters" 162*8975f5c5SAndroid Build Coastguard Worker dst_filter_path = dst_proj_path + ".filters" 163*8975f5c5SAndroid Build Coastguard Worker 164*8975f5c5SAndroid Build Coastguard Worker # Copy the filter file unmodified 165*8975f5c5SAndroid Build Coastguard Worker EnsureExists(os.path.dirname(dst_proj_path)) 166*8975f5c5SAndroid Build Coastguard Worker copyfile(src_filter_path, dst_filter_path) 167*8975f5c5SAndroid Build Coastguard Worker 168*8975f5c5SAndroid Build Coastguard Worker preferred_tool_arch = None 169*8975f5c5SAndroid Build Coastguard Worker config_arch = {} 170*8975f5c5SAndroid Build Coastguard Worker 171*8975f5c5SAndroid Build Coastguard Worker # Bring over the project file, modified with extra configs 172*8975f5c5SAndroid Build Coastguard Worker with open(src_proj_path) as src_proj_file: 173*8975f5c5SAndroid Build Coastguard Worker proj_lines = iter(src_proj_file) 174*8975f5c5SAndroid Build Coastguard Worker new_proj_lines = [] 175*8975f5c5SAndroid Build Coastguard Worker for line in proj_lines: 176*8975f5c5SAndroid Build Coastguard Worker if "<ItemDefinitionGroup" in line: 177*8975f5c5SAndroid Build Coastguard Worker # This is a large group that contains many settings. We need to 178*8975f5c5SAndroid Build Coastguard Worker # replicate it, with conditions so it varies per configuration. 179*8975f5c5SAndroid Build Coastguard Worker idg_lines = [] 180*8975f5c5SAndroid Build Coastguard Worker while not "</ItemDefinitionGroup" in line: 181*8975f5c5SAndroid Build Coastguard Worker idg_lines.append(line) 182*8975f5c5SAndroid Build Coastguard Worker line = proj_lines.next() 183*8975f5c5SAndroid Build Coastguard Worker idg_lines.append(line) 184*8975f5c5SAndroid Build Coastguard Worker for proj_config in proj_configs: 185*8975f5c5SAndroid Build Coastguard Worker config_idg_lines = ExtractIdg(os.path.join("out", 186*8975f5c5SAndroid Build Coastguard Worker proj_config[0], 187*8975f5c5SAndroid Build Coastguard Worker proj_config[1])) 188*8975f5c5SAndroid Build Coastguard Worker match = proj_config[0] + '|' + hard_coded_arch 189*8975f5c5SAndroid Build Coastguard Worker new_proj_lines.append(idg_hdr + match + "'\">\n") 190*8975f5c5SAndroid Build Coastguard Worker for idg_line in config_idg_lines[1:]: 191*8975f5c5SAndroid Build Coastguard Worker new_proj_lines.append(idg_line) 192*8975f5c5SAndroid Build Coastguard Worker elif "ProjectConfigurations" in line: 193*8975f5c5SAndroid Build Coastguard Worker new_proj_lines.append(line) 194*8975f5c5SAndroid Build Coastguard Worker proj_lines.next() 195*8975f5c5SAndroid Build Coastguard Worker proj_lines.next() 196*8975f5c5SAndroid Build Coastguard Worker proj_lines.next() 197*8975f5c5SAndroid Build Coastguard Worker proj_lines.next() 198*8975f5c5SAndroid Build Coastguard Worker for config in configs: 199*8975f5c5SAndroid Build Coastguard Worker new_proj_lines.append(FormatProjectConfig(config)) 200*8975f5c5SAndroid Build Coastguard Worker 201*8975f5c5SAndroid Build Coastguard Worker elif "<OutDir" in line: 202*8975f5c5SAndroid Build Coastguard Worker new_proj_lines.append(line.replace(proj_configs[0][0], 203*8975f5c5SAndroid Build Coastguard Worker "$(Configuration)")) 204*8975f5c5SAndroid Build Coastguard Worker elif "<PreferredToolArchitecture" in line: 205*8975f5c5SAndroid Build Coastguard Worker new_proj_lines.append(" <PreferredToolArchitecture>" + 206*8975f5c5SAndroid Build Coastguard Worker hard_coded_arch + 207*8975f5c5SAndroid Build Coastguard Worker "</PreferredToolArchitecture>\n") 208*8975f5c5SAndroid Build Coastguard Worker else: 209*8975f5c5SAndroid Build Coastguard Worker new_proj_lines.append(line) 210*8975f5c5SAndroid Build Coastguard Worker with open(dst_proj_path, "w") as new_proj: 211*8975f5c5SAndroid Build Coastguard Worker new_proj.writelines(new_proj_lines) 212*8975f5c5SAndroid Build Coastguard Worker 213*8975f5c5SAndroid Build Coastguard Workerprint('Wrote meta solution to out/sln/' + name) 214