3
# Copyright (c) 2009 Google Inc. All rights reserved.
4
# Use of this source code is governed by a BSD-style license that can be
5
# found in the LICENSE file.
7
"""New implementation of Visual Studio project generation for SCons."""
13
# hashlib is supplied as of Python 2.5 as the replacement interface for md5
14
# and other secure hashes. In 2.6, md5 is deprecated. Import hashlib if
15
# available, avoiding a deprecation warning under 2.6. Import md5 otherwise,
16
# preserving 2.4 compatibility.
19
_new_md5 = hashlib.md5
25
# Initialize random number generator
28
# GUIDs for project types
30
'project': '{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}',
31
'folder': '{2150E333-8FDC-42A3-9474-1A3956D46DE8}',
34
#------------------------------------------------------------------------------
38
def MakeGuid(name, seed='msvs_new'):
39
"""Returns a GUID for the specified target name.
43
seed: Seed for MD5 hash.
45
A GUID-line string calculated from the name and seed.
47
This generates something which looks like a GUID, but depends only on the
48
name and seed. This means the same name/seed will always generate the same
49
GUID, so that projects and solutions which refer to each other can explicitly
50
determine the GUID to refer to explicitly. It also means that the GUID will
51
not change when the project for a target is rebuilt.
53
# Calculate a MD5 signature for the seed and name.
54
d = _new_md5(str(seed) + str(name)).hexdigest().upper()
55
# Convert most of the signature to GUID form (discard the rest)
56
guid = ('{' + d[:8] + '-' + d[8:12] + '-' + d[12:16] + '-' + d[16:20]
57
+ '-' + d[20:32] + '}')
60
#------------------------------------------------------------------------------
64
"""Folder in a Visual Studio project or solution."""
66
def __init__(self, path, name = None, entries = None,
67
guid = None, items = None):
68
"""Initializes the folder.
71
path: Full path to the folder.
72
name: Name of the folder.
73
entries: List of folder entries to nest inside this folder. May contain
74
Folder or Project objects. May be None, if the folder is empty.
75
guid: GUID to use for folder, if not None.
76
items: List of solution items to include in the folder project. May be
77
None, if the folder does not directly contain items.
83
self.name = os.path.basename(path)
88
# Copy passed lists (or set to empty lists)
89
self.entries = list(entries or [])
90
self.items = list(items or [])
92
self.entry_type_guid = ENTRY_TYPE_GUIDS['folder']
96
# Use consistent guids for folders (so things don't regenerate).
97
self.guid = MakeGuid(self.path, seed='msvs_folder')
101
#------------------------------------------------------------------------------
105
"""Visual Studio project."""
107
def __init__(self, path, name = None, dependencies = None, guid = None,
108
config_platform_overrides = None):
109
"""Initializes the project.
112
path: Relative path to project file.
113
name: Name of project. If None, the name will be the same as the base
114
name of the project file.
115
dependencies: List of other Project objects this project is dependent
117
guid: GUID to use for project, if not None.
118
config_platform_overrides: optional dict of configuration platforms to
119
used in place of the default for this target.
127
# Use project filename
128
self.name = os.path.splitext(os.path.basename(path))[0]
130
# Copy passed lists (or set to empty lists)
131
self.dependencies = list(dependencies or [])
133
self.entry_type_guid = ENTRY_TYPE_GUIDS['project']
135
if config_platform_overrides:
136
self.config_platform_overrides = config_platform_overrides
138
self.config_platform_overrides = {}
141
if self.guid is None:
143
# TODO(rspangler): This is fragile.
144
# 1. We can't just use the project filename sans path, since there could
145
# be multiple projects with the same base name (for example,
146
# foo/unittest.vcproj and bar/unittest.vcproj).
147
# 2. The path needs to be relative to $SOURCE_ROOT, so that the project
148
# GUID is the same whether it's included from base/base.sln or
149
# foo/bar/baz/baz.sln.
150
# 3. The GUID needs to be the same each time this builder is invoked, so
151
# that we don't need to rebuild the solution when the project changes.
152
# 4. We should be able to handle pre-built project files by reading the
153
# GUID from the files.
154
self.guid = MakeGuid(self.name)
157
#------------------------------------------------------------------------------
161
"""Visual Studio solution."""
163
def __init__(self, path, version, entries=None, variants=None,
164
websiteProperties=True):
165
"""Initializes the solution.
168
path: Path to solution file.
169
version: Format version to emit.
170
entries: List of entries in solution. May contain Folder or Project
171
objects. May be None, if the folder is empty.
172
variants: List of build variant strings. If none, a default list will
174
websiteProperties: Flag to decide if the website properties section
178
self.websiteProperties = websiteProperties
179
self.version = version
181
# Copy passed lists (or set to empty lists)
182
self.entries = list(entries or [])
186
self.variants = variants[:]
189
self.variants = ['Debug|Win32', 'Release|Win32']
190
# TODO(rspangler): Need to be able to handle a mapping of solution config
191
# to project config. Should we be able to handle variants being a dict,
192
# or add a separate variant_map variable? If it's a dict, we can't
193
# guarantee the order of variants since dict keys aren't ordered.
196
# TODO(rspangler): Automatically write to disk for now; should delay until
197
# node-evaluation time.
201
def Write(self, writer=common.WriteOnDiff):
202
"""Writes the solution file to disk.
205
IndexError: An entry appears multiple times.
207
# Walk the entry tree and collect all the folders and projects.
209
entries_to_check = self.entries[:]
210
while entries_to_check:
211
# Pop from the beginning of the list to preserve the user's order.
212
e = entries_to_check.pop(0)
214
# A project or folder can only appear once in the solution's folder tree.
215
# This also protects from cycles.
217
#raise IndexError('Entry "%s" appears more than once in solution' %
221
all_entries.append(e)
223
# If this is a folder, check its entries too.
224
if isinstance(e, MSVSFolder):
225
entries_to_check += e.entries
227
# Sort by name then guid (so things are in order on vs2008).
228
def NameThenGuid(a, b):
229
if a.name < b.name: return -1
230
if a.name > b.name: return 1
231
if a.get_guid() < b.get_guid(): return -1
232
if a.get_guid() > b.get_guid(): return 1
235
all_entries = sorted(all_entries, NameThenGuid)
237
# Open file and print header
238
f = writer(self.path)
239
f.write('Microsoft Visual Studio Solution File, '
240
'Format Version %s\r\n' % self.version.SolutionVersion())
241
f.write('# %s\r\n' % self.version.Description())
244
for e in all_entries:
245
f.write('Project("%s") = "%s", "%s", "%s"\r\n' % (
246
e.entry_type_guid, # Entry type GUID
247
e.name, # Folder name
248
e.path.replace('/', '\\'), # Folder name (again)
249
e.get_guid(), # Entry GUID
252
# TODO(rspangler): Need a way to configure this stuff
253
if self.websiteProperties:
254
f.write('\tProjectSection(WebsiteProperties) = preProject\r\n'
255
'\t\tDebug.AspNetCompiler.Debug = "True"\r\n'
256
'\t\tRelease.AspNetCompiler.Debug = "False"\r\n'
257
'\tEndProjectSection\r\n')
259
if isinstance(e, MSVSFolder):
261
f.write('\tProjectSection(SolutionItems) = preProject\r\n')
263
f.write('\t\t%s = %s\r\n' % (i, i))
264
f.write('\tEndProjectSection\r\n')
266
if isinstance(e, MSVSProject):
268
f.write('\tProjectSection(ProjectDependencies) = postProject\r\n')
269
for d in e.dependencies:
270
f.write('\t\t%s = %s\r\n' % (d.get_guid(), d.get_guid()))
271
f.write('\tEndProjectSection\r\n')
273
f.write('EndProject\r\n')
276
f.write('Global\r\n')
278
# Configurations (variants)
279
f.write('\tGlobalSection(SolutionConfigurationPlatforms) = preSolution\r\n')
280
for v in self.variants:
281
f.write('\t\t%s = %s\r\n' % (v, v))
282
f.write('\tEndGlobalSection\r\n')
284
# Sort config guids for easier diffing of solution changes.
286
config_guids_overrides = {}
287
for e in all_entries:
288
if isinstance(e, MSVSProject):
289
config_guids.append(e.get_guid())
290
config_guids_overrides[e.get_guid()] = e.config_platform_overrides
293
f.write('\tGlobalSection(ProjectConfigurationPlatforms) = postSolution\r\n')
294
for g in config_guids:
295
for v in self.variants:
296
nv = config_guids_overrides[g].get(v, v)
297
# Pick which project configuration to build for this solution
299
f.write('\t\t%s.%s.ActiveCfg = %s\r\n' % (
301
v, # Solution build configuration
302
nv, # Project build config for that solution config
305
# Enable project in this solution configuration.
306
f.write('\t\t%s.%s.Build.0 = %s\r\n' % (
308
v, # Solution build configuration
309
nv, # Project build config for that solution config
311
f.write('\tEndGlobalSection\r\n')
313
# TODO(rspangler): Should be able to configure this stuff too (though I've
314
# never seen this be any different)
315
f.write('\tGlobalSection(SolutionProperties) = preSolution\r\n')
316
f.write('\t\tHideSolutionNode = FALSE\r\n')
317
f.write('\tEndGlobalSection\r\n')
320
# TODO(rspangler): Should omit this section if there are no folders
321
f.write('\tGlobalSection(NestedProjects) = preSolution\r\n')
322
for e in all_entries:
323
if not isinstance(e, MSVSFolder):
324
continue # Does not apply to projects, only folders
325
for subentry in e.entries:
326
f.write('\t\t%s = %s\r\n' % (subentry.get_guid(), e.get_guid()))
327
f.write('\tEndGlobalSection\r\n')
329
f.write('EndGlobal\r\n')