"""Generate vcxproj and vcxproj.filters files for browsing code in Visual Studio 2015. To build mongodb, you must use scons. You can use this project to navigate code during debugging. HOW TO USE First, you need a compile_commands.json file, to generate run the following command: scons compiledb Next, run the following command python buildscripts/make_vcxproj.py FILE_NAME where FILE_NAME is the of the file to generate e.g., "mongod" """ import json import os import re import sys import uuid VCXPROJ_FOOTER = r""" """ def get_defines(args): """Parse a compiler argument list looking for defines.""" ret = set() for arg in args: if arg.startswith('/D'): ret.add(arg[2:]) return ret def get_includes(args): """Parse a compiler argument list looking for includes.""" ret = set() for arg in args: if arg.startswith('/I'): ret.add(arg[2:]) return ret class ProjFileGenerator(object): # pylint: disable=too-many-instance-attributes """Generate a .vcxproj and .vcxprof.filters file.""" def __init__(self, target): """Initialize ProjFileGenerator.""" # we handle DEBUG in the vcxproj header: self.common_defines = set() self.common_defines.add("DEBUG") self.common_defines.add("_DEBUG") self.includes = set() self.target = target self.compiles = [] self.files = set() self.all_defines = set() self.vcxproj = None self.filters = None self.all_defines = set(self.common_defines) def __enter__(self): return self def __exit__(self, exc_type, exc_value, traceback): self.vcxproj = open(self.target + ".vcxproj", "wb") with open('buildscripts/vcxproj.header', 'r') as header_file: header_str = header_file.read() header_str = header_str.replace("%_TARGET_%", self.target) header_str = header_str.replace("%AdditionalIncludeDirectories%", ';'.join( sorted(self.includes))) self.vcxproj.write(header_str) common_defines = self.all_defines for comp in self.compiles: common_defines = common_defines.intersection(comp['defines']) self.vcxproj.write("\n") self.vcxproj.write("" + ';'.join(common_defines) + ";%(PreprocessorDefinitions)\n") self.vcxproj.write("\n") self.vcxproj.write(" \n") for command in self.compiles: defines = command["defines"].difference(common_defines) if defines: self.vcxproj.write( " " + ';'.join(defines) + ";%(PreprocessorDefinitions)" + "\n") else: self.vcxproj.write(" \n") self.vcxproj.write(" \n") self.filters = open(self.target + ".vcxproj.filters", "wb") self.filters.write("\n") self.filters.write("\n") self.__write_filters() self.vcxproj.write(VCXPROJ_FOOTER) self.vcxproj.close() self.filters.write("\n") self.filters.close() def parse_line(self, line): """Parse a build line.""" if line.startswith("cl"): self.__parse_cl_line(line[3:]) def __parse_cl_line(self, line): """Parse a compiler line.""" # Get the file we are compilong file_name = re.search(r"/c ([\w\\.-]+) ", line).group(1) # Skip files made by scons for configure testing if "sconf_temp" in file_name: return self.files.add(file_name) args = line.split(' ') file_defines = set() for arg in get_defines(args): if arg not in self.common_defines: file_defines.add(arg) self.all_defines = self.all_defines.union(file_defines) for arg in get_includes(args): self.includes.add(arg) self.compiles.append({"file": file_name, "defines": file_defines}) @staticmethod def __is_header(name): """Return True if this a header file.""" headers = [".h", ".hpp", ".hh", ".hxx"] for header in headers: if name.endswith(header): return True return False def __write_filters(self): # pylint: disable=too-many-branches """Generate the vcxproj.filters file.""" # 1. get a list of directories for all the files # 2. get all the headers in each of these dirs # 3. Output these lists of files to vcxproj and vcxproj.headers # Note: order of these lists does not matter, VS will sort them anyway dirs = set() scons_files = set() for file_name in self.files: dirs.add(os.path.dirname(file_name)) base_dirs = set() for directory in dirs: if not os.path.exists(directory): print(("Warning: skipping include file scan for directory '%s'" + " because it does not exist.") % str(directory)) continue # Get all the header files for file_name in os.listdir(directory): if self.__is_header(file_name): self.files.add(directory + "\\" + file_name) # Make sure the set also includes the base directories # (i.e. src/mongo and src as examples) base_name = os.path.dirname(directory) while base_name: base_dirs.add(base_name) base_name = os.path.dirname(base_name) dirs = dirs.union(base_dirs) # Get all the scons files for directory in dirs: if os.path.exists(directory): for file_name in os.listdir(directory): if file_name == "SConstruct" or "SConscript" in file_name: scons_files.add(directory + "\\" + file_name) scons_files.add("SConstruct") # Write a list of directory entries with unique guids self.filters.write(" \n") for file_name in sorted(dirs): self.filters.write(" \n" % file_name) self.filters.write(" {%s}\n" % uuid.uuid4()) self.filters.write(" \n") self.filters.write(" \n") # Write a list of files to compile self.filters.write(" \n") for file_name in sorted(self.files): if not self.__is_header(file_name): self.filters.write(" \n" % file_name) self.filters.write(" %s\n" % os.path.dirname(file_name)) self.filters.write(" \n") self.filters.write(" \n") # Write a list of headers self.filters.write(" \n") for file_name in sorted(self.files): if self.__is_header(file_name): self.filters.write(" \n" % file_name) self.filters.write(" %s\n" % os.path.dirname(file_name)) self.filters.write(" \n") self.filters.write(" \n") # Write a list of scons files self.filters.write(" \n") for file_name in sorted(scons_files): self.filters.write(" \n" % file_name) self.filters.write(" %s\n" % os.path.dirname(file_name)) self.filters.write(" \n") self.filters.write(" \n") # Write a list of headers into the vcxproj self.vcxproj.write(" \n") for file_name in sorted(self.files): if self.__is_header(file_name): self.vcxproj.write(" \n" % file_name) self.vcxproj.write(" \n") # Write a list of scons files into the vcxproj self.vcxproj.write(" \n") for file_name in sorted(scons_files): self.vcxproj.write(" \n" % file_name) self.vcxproj.write(" \n") def main(): """Execute Main program.""" if len(sys.argv) != 2: print r"Usage: python buildscripts\make_vcxproj.py FILE_NAME" return with ProjFileGenerator(sys.argv[1]) as projfile: with open("compile_commands.json", "rb") as sjh: contents = sjh.read().decode('utf-8') commands = json.loads(contents) for command in commands: command_str = command["command"] projfile.parse_line(command_str) main()