The make_solution() uses several mini-templates to construct different parts of the .sln file. Here are the templates. The names pretty much explain the purpose of each template. The templates use the same principle as the project templates and are just segments of text with placeholder for substitution values that the make_solution() function populates with the proper values and weave together:
# A project template has a header and a list of project sections
# such as ProjectDependencies. The ProjectDependencies
# duplicate the dependency information in .vcproj files in VS2005.
# For generating a solution that contains only C++ projects, no other
# project section is needed.
project_template_with_dependencies = """\
Project("${TypeGUID}") = "${Name}", "${Filename}", "${GUID}"
ProjectSection(ProjectDependencies) = postProject
${ProjectDependencies}
EndProjectSection
EndProject
"""
project_template_without_dependencies = """\
Project("${TypeGUID}") = "${Name}", "${Filename}", "${GUID}"
EndProject
"""
project_configuration_platform_template = """\
\t\t${GUID}.Debug|Win32.ActiveCfg = Debug|Win32
\t\t${GUID}.Debug|Win32.Build.0 = Debug|Win32
\t\t${GUID}.Release|Win32.ActiveCfg = Release|Win32
\t\t${GUID}.Release|Win32.Build.0 = Release|Win32
"""
# This is the solution template for VS 2008
# The template arguments are:
#
# Projects
# ProjectConfigurationPlatforms
# NestedProjects
#
solution_template = """
Microsoft Visual Studio Solution File, Format Version 10.00
# Visual Studio 2008
${Projects}
Global
\tGlobalSection(SolutionConfigurationPlatforms) = preSolution
\t\tDebug|Win32 = Debug|Win32
\t\tRelease|Win32 = Release|Win32
\tEndGlobalSection
\tGlobalSection(ProjectConfigurationPlatforms) = postSolution
${Configurations}
\tEndGlobalSection
\tGlobalSection(SolutionProperties) = preSolution
\t\tHideSolutionNode = FALSE
\tEndGlobalSection
\tGlobalSection(NestedProjects) = preSolution
${NestedProjects}
\tEndGlobalSection
EndGlobal
"""
Whoever designed the Visual Studio build system was big on GUIDs. Almost every object is identified by a GUID including the types of various solution items like folders and projects:
# Guids for regular project and solution folder
project_type = '{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}'
folder_type = '{2150E333-8FDC-42A3-9474-1A3956D46DE8}'
The SolutionItem class itself is really just a named tuple, but since ibs is not limited to Python 2.6 and up (when named tuples were introduced into the language) I use a dedicated class:
class SolutionItem(object):
"""Represents a solution folder or project
The set of solution projects contain all the information
necessary to generate a solution file.
name - the name of the project/folder
type - folder_type or project_type
path - the relative path from the root dir to the .vcproj file for projects,
same as name for folders
guid - the GUID of the project/folder
dependencies - A list of project guids the project depends on.
It is empty for folders and projects with no dependencies.
projects - list of projects hosted by the folder. It is empty for projects.
"""
def __init__(self, item_type, name, path, guid, dependencies, projects):
title()
self.name = name
self.type = item_type
self.path = path
self.guid = guid
self.dependencies = dependencies
self.projects = projects
The make_solution() takes the source directory and the folders list to generate the solution file using a bunch of nested functions.
def make_solution(source_dir, folders):
"""Return a string representing the .sln file
It uses a lot of nested functions to make the different parts
of a solution file:
- make_project_dependencies
- make_projects
- make_configurations
- make nested_projects
@param folders - a dictionary whose keys are VS folders and the values
are the projects each folder contains. Each project must be an object that
has a directory path (relative to the root dir), a guid and a
list of dependencies (each dependency is another projects). This directory
should contain a .vcproj file whose name matches the directory name.
@param projects - a list of projects that don't have a folder and are contained
directly by the solution node.
"""
The get_existing_folders() nested function takes and existing .sln file and extracts the GUIDs of every project in it. It returns a dictionary of project names and GUIDs that can be used to regenerate a .sln file with identical GUIDs to the existing ones.
def get_existing_folders(sln_filename):
title()
lines = open(sln_filename).readlines()
results = {}
for line in lines:
if line.startswith('Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") ='):
tokens = line.split('"')
print tokens
name = tokens[-4]
guid = tokens[-2]
results[name] = guid
return results
The make_project_dependencies() nested function takes a list of dependency GUIDs of a project and returns the text fragment that is the ProjectDependencies sub-section of this project in the .sln file.
def make_project_dependencies(dependency_guids):
title()
if dependency_guids == []:
return ''
result = []
for g in dependency_guids:
result.append('\t\t%s = %s' % (g, g))
result = '\n'.join(result)
return result
The make_projects() nested function takes the source directory and the list of projects and generates a text fragment that represents all the projects in the .sln file. It uses the micro templates defined earlier and the make_project_dependencies() function.
def make_projects(source_dir, projects):
title()
result = ''
t1 = string.Template(project_template_with_dependencies)
t2 = string.Template(project_template_without_dependencies)
for p in projects:
if p.type == project_type:
filename = p.path[len(source_dir) + 1:].replace('/', '\\')
else:
filename = p.name
dependency_guids = [get_guid(p.path) for d in p.dependencies]
guid = get_guid(filename) if p.guid is None else p.guid
d = dict(TypeGUID=p.type,
Name=p.name,
Filename=filename,
GUID=guid,
ProjectDependencies=make_project_dependencies(p.dependencies))
t = t1 if p.dependencies != [] else t2
s = t.substitute(d)
result += s
return result[:-1]
The make_configurations() function returns a text fragment that represents all the project configuration platforms. It works by iterating over the projects list and populating the project_configuration_platform template with each project's GUID.
def make_configurations(projects):
title()
result = ''
t = string.Template(project_configuration_platform_template)
for p in projects:
d = dict(GUID=p.guid)
s = t.substitute(d)
result += s
return result[:-1]
The make_nested_projects() function returns a text fragment that represents all the nested projects in the .sln file. It works by iterating over the folders and populating the nested_project template with the guids of each nested project and its containing folder. Each folder is an object that has guid attribute and a projects attribute (which is a list of its contained projects):
def make_nested_projects(folders):
title()
for f in folders.values():
assert hasattr(f, 'guid') and type(f.guid) == str
assert hasattr(f, 'projects') and type(f.projects) in (list, tuple)
result = ''
nested_project = '\t\t${GUID} = ${FolderGUID}\n'
t = string.Template(nested_project)
for folder in folders.values():
for p in folder.projects:
d = dict(GUID=p.guid, FolderGUID=folder.guid)
s = t.substitute(d)
result += s
return result[:-1]
These were all the nested functions and here is how the containing make_solution() function puts them to good use.
try:
sln_filename = glob.glob(os.path.join(source_dir, '*.sln'))[0]
existing_folders = get_existing_folders(sln_filename)
except:
existing_folders = []
# Use folders GUIDs from existing .sln file (if there is any)
for name, f in folders.items():
if name in existing_folders:
f.guid = existing_folders[name]
else:
f.guid = make_guid()
# Prepare a flat list of all projects
all_projects =[]
for f in folders.values():
all_projects.append(f)
all_projects += f.projects
# Prepare the substitution dict for the solution template
projects = [p for p in all_projects if p.type == project_type]
all_projects = make_projects(source_dir, all_projects)
configurations = make_configurations(projects)
nested_projects = make_nested_projects(folders)
d = dict(Projects=all_projects,
Configurations=configurations,
NestedProjects=nested_projects)
# Create the final solution text by substituting the dict into the template
t = string.Template(solution_template)
solution = t.substitute(d)
return solution



