tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

gn_meta_sln.py (8314B)


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