Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(79)

Side by Side Diff: site_scons/site_tools/_Node_MSVS.py

Issue 14467: Underlying functionality for generating native Visual Studio solution files:... (Closed) Base URL: svn://chrome-svn/chrome/trunk/src/
Patch Set: '' Created 12 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « site_scons/site_tools/MSVSNew.py ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Property Changes:
Added: svn:eol-style
+ LF
OLDNEW
(Empty)
1 #!/usr/bin/python2.4
2 # Copyright 2008, Google Inc.
3 # All rights reserved.
4
5 __doc__ = """SCons.Node.MSVS
6
7 Microsoft Visual Studio nodes.
8 """
9
10 import SCons.Node.FS
11 import SCons.Script
12
13
14 """New implementation of Visual Studio project generation for SCons."""
15
16 import md5
17 import os
18 import random
19
20
21 # Initialize random number generator
22 random.seed()
23
24
25 #------------------------------------------------------------------------------
26 # Entry point for supplying a fixed map of GUIDs for testing.
27
28 GUIDMap = {}
29
30
31 #------------------------------------------------------------------------------
32 # Helper functions
33
34
35 def MakeGuid(name, seed='msvs_new'):
36 """Returns a GUID for the specified target name.
37
38 Args:
39 name: Target name.
40 seed: Seed for MD5 hash.
41 Returns:
42 A GUID-line string calculated from the name and seed.
43
44 This generates something which looks like a GUID, but depends only on the
45 name and seed. This means the same name/seed will always generate the same
46 GUID, so that projects and solutions which refer to each other can explicitly
47 determine the GUID to refer to explicitly. It also means that the GUID will
48 not change when the project for a target is rebuilt.
49 """
50 # Calculate a MD5 signature for the seed and name.
51 d = md5.new(str(seed) + str(name)).hexdigest().upper()
52 # Convert most of the signature to GUID form (discard the rest)
53 guid = ('{' + d[:8] + '-' + d[8:12] + '-' + d[12:16] + '-' + d[16:20]
54 + '-' + d[20:32] + '}')
55 return guid
56
57
58 #------------------------------------------------------------------------------
59 # Global look up of string names.
60
61 class LookupError(Exception):
62 def __str__(self):
63 string, expanded = self.args
64 if string == expanded:
65 return string
66 else:
67 return '%s (%s)' % (string, expanded)
68
69 _lookup_dict = {}
70
71 def LookupAdd(item, result):
72 _lookup_dict[item] = result
73 _lookup_dict[result] = result
74
75 def Lookup(item):
76 """Looks up an MSVS item in the global dictionary.
77
78 Args:
79 item: A path (string) or instance for looking up.
80 Returns:
81 An instance from the global _lookup_dict.
82
83 Raises an exception if the item does not exist in the _lookup_dict.
84 """
85 global _lookup_dict
86 try:
87 return _lookup_dict[item]
88 except KeyError:
89 return SCons.Node.FS.default_fs.Entry(item, create=False)
90
91 def LookupCreate(klass, item, *args, **kw):
92 """Looks up an MSVS item, creating it if it doesn't already exist.
93
94 Args:
95 klass: The class of item being looked up, or created if it
96 doesn't already exist in the global _lookup_dict.
97 item: The a string (or instance) being looked up.
98 *args: positional arguments passed to the klass.initialize() method.
99 **kw: keyword arguments passed to the klass.initialize() method.
100 Returns:
101 An instance from the global _lookup_dict, or None if the item does
102 not exist in the _lookup_dict.
103
104 This raises a LookupError if the found instance doesn't match the
105 requested klass.
106
107 When creating a new instance, this populates the _lookup_dict with
108 both the item and the instance itself as keys, so that looking up
109 the instance will return itself.
110 """
111 global _lookup_dict
112 result = _lookup_dict.get(item)
113 if result:
114 if not isinstance(result, klass):
115 raise LookupError, "tried to redefine %s as a %s" % (item, klass)
116 return result
117 result = klass()
118 result.initialize(item, *args, **kw)
119 LookupAdd(item, result)
120 return result
121
122
123 #------------------------------------------------------------------------------
124
125 class _MSVSFolder(SCons.Node.Node):
126 """Folder in a Visual Studio project or solution."""
127
128 entry_type_guid = '{2150E333-8FDC-42A3-9474-1A3956D46DE8}'
129
130 def initialize(self, path, name = None, entries = None, guid = None,
131 items = None):
132 """Initializes the folder.
133
134 Args:
135 path: The unique name of the folder, by which other MSVS Nodes can
136 refer to it. This is not necessarily the name that gets printed
137 in the .sln file.
138 name: The name of this folder as actually written in a generated
139 .sln file. The default is
140 entries: List of folder entries to nest inside this folder. May contain
141 Folder or Project objects. May be None, if the folder is empty.
142 guid: GUID to use for folder, if not None.
143 items: List of solution items to include in the folder project. May be
144 None, if the folder does not directly contain items.
145 """
146 # For folder entries, the path is the same as the name
147 self.msvs_path = path
148 self.msvs_name = name or path
149
150 self.guid = guid
151
152 # Copy passed lists (or set to empty lists)
153 self.entries = list(entries or [])
154 self.items = list(items or [])
155
156 def get_guid(self):
157 if self.guid is None:
158 guid = GUIDMap.get(self.msvs_path)
159 if not guid:
160 # The GUID for the folder can be random, since it's used only inside
161 # solution files and doesn't need to be consistent across runs.
162 guid = MakeGuid(random.random())
163 self.guid = guid
164 return self.guid
165
166 def get_msvs_path(self, sln):
167 return self.msvs_name
168
169 def MSVSFolder(env, item, *args, **kw):
170 return LookupCreate(_MSVSFolder, item, *args, **kw)
171
172 #------------------------------------------------------------------------------
173
174
175 class _MSVSProject(SCons.Node.FS.File):
176 """Visual Studio project."""
177
178 entry_type_guid = '{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}'
179
180 def initialize(self, path, name = None, dependencies = None, guid = None):
181 """Initializes the project.
182
183 Args:
184 path: Relative path to project file.
185 name: Name of project. If None, the name will be the same as the base
186 name of the project file.
187 dependencies: List of other Project objects this project is dependent
188 upon, if not None.
189 guid: GUID to use for project, if not None.
190 """
191 self.msvs_path = path
192 self.msvs_name = name or os.path.splitext(os.path.basename(self.name))[0]
193
194 self.guid = guid
195
196 # Copy passed lists (or set to empty lists)
197 self.dependencies = list(dependencies or [])
198
199 def get_guid(self):
200 if self.guid is None:
201 guid = GUIDMap.get(self.msvs_path)
202 if not guid:
203 # Set GUID from path
204 # TODO(rspangler): This is fragile.
205 # 1. We can't just use the project filename sans path, since there
206 # could be multiple projects with the same base name (for example,
207 # foo/unittest.vcproj and bar/unittest.vcproj).
208 # 2. The path needs to be relative to $SOURCE_ROOT, so that the project
209 # GUID is the same whether it's included from base/base.sln or
210 # foo/bar/baz/baz.sln.
211 # 3. The GUID needs to be the same each time this builder is invoked,
212 # so that we don't need to rebuild the solution when the
213 # project changes.
214 # 4. We should be able to handle pre-built project files by reading the
215 # GUID from the files.
216 guid = MakeGuid(self.msvs_path)
217 self.guid = guid
218 return self.guid
219
220 def get_msvs_path(self, sln):
221 return sln.rel_path(self).replace('/', '\\')
222
223 def MSVSProject(env, item, *args, **kw):
224 if not SCons.Util.is_String(item):
225 return item
226 item = env.subst(item)
227 result = env.fs._lookup(item, None, _MSVSProject, create=1)
228 result.initialize(item, *args, **kw)
229 LookupAdd(item, result)
230 return result
231
232 #------------------------------------------------------------------------------
233
234 def MSVSAction(target, source, env):
235 target[0].Write(env)
236
237 MSVSSolutionAction = SCons.Script.Action(MSVSAction,
238 "Generating Visual Studio solution `$TA RGET' ...")
239
240 class _MSVSSolution(SCons.Node.FS.File):
241 """Visual Studio solution."""
242
243 def initialize(self, env, path, entries = None, variants = None,
244 websiteProperties = True):
245 """Initializes the solution.
246
247 Args:
248 path: Path to solution file.
249 entries: List of entries in solution. May contain Folder or Project
250 objects. May be None, if the folder is empty.
251 variants: List of build variant strings. If none, a default list will
252 be used.
253 """
254 self.msvs_path = path
255 self.websiteProperties = websiteProperties
256
257 # Copy passed lists (or set to empty lists)
258 self.entries = list(entries or [])
259
260 if variants:
261 # Copy passed list
262 self.variants = variants[:]
263 else:
264 # Use default
265 self.variants = ['Debug|Win32', 'Release|Win32']
266 # TODO(rspangler): Need to be able to handle a mapping of solution config
267 # to project config. Should we be able to handle variants being a dict,
268 # or add a separate variant_map variable? If it's a dict, we can't
269 # guarantee the order of variants since dict keys aren't ordered.
270
271 env.Command(self, [], MSVSSolutionAction)
272
273 def Write(self, env):
274 """Writes the solution file to disk.
275
276 Raises:
277 IndexError: An entry appears multiple times.
278 """
279 r = []
280 errors = []
281
282 def lookup_subst(item, env=env, errors=errors):
283 if SCons.Util.is_String(item):
284 lookup_item = env.subst(item)
285 else:
286 lookup_item = item
287 try:
288 return Lookup(lookup_item)
289 except SCons.Errors.UserError:
290 raise LookupError(item, lookup_item)
291
292 # Walk the entry tree and collect all the folders and projects.
293 all_entries = []
294 entries_to_check = self.entries[:]
295 while entries_to_check:
296 # Pop from the beginning of the list to preserve the user's order.
297 entry = entries_to_check.pop(0)
298 try:
299 entry = lookup_subst(entry)
300 except LookupError, e:
301 errors.append("Could not look up entry `%s'." % e)
302 continue
303
304 # A project or folder can only appear once in the solution's folder tree.
305 # This also protects from cycles.
306 if entry in all_entries:
307 #raise IndexError('Entry "%s" appears more than once in solution' %
308 # e.name)
309 continue
310
311 all_entries.append(entry)
312
313 # If this is a folder, check its entries too.
314 if isinstance(entry, _MSVSFolder):
315 entries_to_check += entry.entries
316
317 # Header
318 r.append('Microsoft Visual Studio Solution File, Format Version 9.00\n')
319 r.append('# Visual Studio 2005\n')
320
321 # Project entries
322 for e in all_entries:
323 r.append('Project("%s") = "%s", "%s", "%s"\n' % (
324 e.entry_type_guid, # Entry type GUID
325 e.msvs_name, # Folder name
326 e.get_msvs_path(self), # Folder name (again)
327 e.get_guid(), # Entry GUID
328 ))
329
330 # TODO(rspangler): Need a way to configure this stuff
331 if self.websiteProperties:
Nicolas Sylvain 2008/12/17 00:49:27 Please don't add this stuff. It burns my eyes to s
332 r.append('\tProjectSection(WebsiteProperties) = preProject\n'
333 '\t\tDebug.AspNetCompiler.Debug = "True"\n'
334 '\t\tRelease.AspNetCompiler.Debug = "False"\n'
335 '\tEndProjectSection\n')
336
337 if isinstance(e, _MSVSFolder):
338 if e.items:
339 r.append('\tProjectSection(SolutionItems) = preProject\n')
340 for i in e.items:
341 i = i.replace('/', '\\')
342 r.append('\t\t%s = %s\n' % (i, i))
343 r.append('\tEndProjectSection\n')
344
345 if isinstance(e, _MSVSProject):
346 if e.dependencies:
347 r.append('\tProjectSection(ProjectDependencies) = postProject\n')
348 for d in e.dependencies:
349 try:
350 d = lookup_subst(d)
351 except LookupError, e:
352 errors.append("Could not look up dependency `%s'." % e)
353 else:
354 r.append('\t\t%s = %s\n' % (d.get_guid(), d.get_guid()))
355 r.append('\tEndProjectSection\n')
356
357 r.append('EndProject\n')
358
359 # Global section
360 r.append('Global\n')
361
362 # Configurations (variants)
363 r.append('\tGlobalSection(SolutionConfigurationPlatforms) = preSolution\n')
364 for v in self.variants:
365 r.append('\t\t%s = %s\n' % (v, v))
366 r.append('\tEndGlobalSection\n')
367
368 # Sort config guids for easier diffing of solution changes.
369 config_guids = []
370 for e in all_entries:
371 if isinstance(e, _MSVSProject):
372 config_guids.append(e.get_guid())
373 config_guids.sort()
374
375 r.append('\tGlobalSection(ProjectConfigurationPlatforms) = postSolution\n')
376 for g in config_guids:
377 for v in self.variants:
378 r.append('\t\t%s.%s.ActiveCfg = %s\n' % (
379 g, # Project GUID
380 v, # Solution build configuration
381 v, # Project build config for that solution config
382 ))
383
384 # Enable project in this solution configuratation
385 r.append('\t\t%s.%s.Build.0 = %s\n' % (
386 g, # Project GUID
387 v, # Solution build configuration
388 v, # Project build config for that solution config
389 ))
390 r.append('\tEndGlobalSection\n')
391
392 # TODO(rspangler): Should be able to configure this stuff too (though I've
393 # never seen this be any different)
394 r.append('\tGlobalSection(SolutionProperties) = preSolution\n')
395 r.append('\t\tHideSolutionNode = FALSE\n')
396 r.append('\tEndGlobalSection\n')
397
398 # Folder mappings
399 # TODO(rspangler): Should omit this section if there are no folders
400 folder_mappings = []
401 for e in all_entries:
402 if not isinstance(e, _MSVSFolder):
403 continue # Does not apply to projects, only folders
404 for subentry in e.entries:
405 try:
406 subentry = lookup_subst(subentry)
407 except LookupError, e:
408 errors.append("Could not look up subentry `%s'." % subentry)
409 else:
410 folder_mappings.append((subentry.get_guid(), e.get_guid()))
411 folder_mappings.sort()
412 r.append('\tGlobalSection(NestedProjects) = preSolution\n')
413 for fm in folder_mappings:
414 r.append('\t\t%s = %s\n' % fm)
415 r.append('\tEndGlobalSection\n')
416
417 r.append('EndGlobal\n')
418
419 if errors:
420 errors = ['Errors while generating solution file:'] + errors
421 raise SCons.Errors.UserError, '\n\t'.join(errors)
422
423 f = open(self.path, 'wt')
424 f.write(''.join(r))
425 f.close()
426
427 def MSVSSolution(env, item, *args, **kw):
428 if not SCons.Util.is_String(item):
429 return item
430 item = env.subst(item)
431 result = env.fs._lookup(item, None, _MSVSSolution, create=1)
432 result.initialize(env, item, *args, **kw)
433 LookupAdd(item, result)
434 return result
OLDNEW
« no previous file with comments | « site_scons/site_tools/MSVSNew.py ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698