123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340 |
- # Copyright (c) 2012 Google Inc. All rights reserved.
- # Use of this source code is governed by a BSD-style license that can be
- # found in the LICENSE file.
- """New implementation of Visual Studio project generation."""
- import os
- import random
- import gyp.common
- # hashlib is supplied as of Python 2.5 as the replacement interface for md5
- # and other secure hashes. In 2.6, md5 is deprecated. Import hashlib if
- # available, avoiding a deprecation warning under 2.6. Import md5 otherwise,
- # preserving 2.4 compatibility.
- try:
- import hashlib
- _new_md5 = hashlib.md5
- except ImportError:
- import md5
- _new_md5 = md5.new
- # Initialize random number generator
- random.seed()
- # GUIDs for project types
- ENTRY_TYPE_GUIDS = {
- 'project': '{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}',
- 'folder': '{2150E333-8FDC-42A3-9474-1A3956D46DE8}',
- }
- #------------------------------------------------------------------------------
- # Helper functions
- def MakeGuid(name, seed='msvs_new'):
- """Returns a GUID for the specified target name.
- Args:
- name: Target name.
- seed: Seed for MD5 hash.
- Returns:
- A GUID-line string calculated from the name and seed.
- This generates something which looks like a GUID, but depends only on the
- name and seed. This means the same name/seed will always generate the same
- GUID, so that projects and solutions which refer to each other can explicitly
- determine the GUID to refer to explicitly. It also means that the GUID will
- not change when the project for a target is rebuilt.
- """
- # Calculate a MD5 signature for the seed and name.
- d = _new_md5(str(seed) + str(name)).hexdigest().upper()
- # Convert most of the signature to GUID form (discard the rest)
- guid = ('{' + d[:8] + '-' + d[8:12] + '-' + d[12:16] + '-' + d[16:20]
- + '-' + d[20:32] + '}')
- return guid
- #------------------------------------------------------------------------------
- class MSVSSolutionEntry(object):
- def __cmp__(self, other):
- # Sort by name then guid (so things are in order on vs2008).
- return cmp((self.name, self.get_guid()), (other.name, other.get_guid()))
- class MSVSFolder(MSVSSolutionEntry):
- """Folder in a Visual Studio project or solution."""
- def __init__(self, path, name = None, entries = None,
- guid = None, items = None):
- """Initializes the folder.
- Args:
- path: Full path to the folder.
- name: Name of the folder.
- entries: List of folder entries to nest inside this folder. May contain
- Folder or Project objects. May be None, if the folder is empty.
- guid: GUID to use for folder, if not None.
- items: List of solution items to include in the folder project. May be
- None, if the folder does not directly contain items.
- """
- if name:
- self.name = name
- else:
- # Use last layer.
- self.name = os.path.basename(path)
- self.path = path
- self.guid = guid
- # Copy passed lists (or set to empty lists)
- self.entries = sorted(list(entries or []))
- self.items = list(items or [])
- self.entry_type_guid = ENTRY_TYPE_GUIDS['folder']
- def get_guid(self):
- if self.guid is None:
- # Use consistent guids for folders (so things don't regenerate).
- self.guid = MakeGuid(self.path, seed='msvs_folder')
- return self.guid
- #------------------------------------------------------------------------------
- class MSVSProject(MSVSSolutionEntry):
- """Visual Studio project."""
- def __init__(self, path, name = None, dependencies = None, guid = None,
- spec = None, build_file = None, config_platform_overrides = None,
- fixpath_prefix = None):
- """Initializes the project.
- Args:
- path: Absolute path to the project file.
- name: Name of project. If None, the name will be the same as the base
- name of the project file.
- dependencies: List of other Project objects this project is dependent
- upon, if not None.
- guid: GUID to use for project, if not None.
- spec: Dictionary specifying how to build this project.
- build_file: Filename of the .gyp file that the vcproj file comes from.
- config_platform_overrides: optional dict of configuration platforms to
- used in place of the default for this target.
- fixpath_prefix: the path used to adjust the behavior of _fixpath
- """
- self.path = path
- self.guid = guid
- self.spec = spec
- self.build_file = build_file
- # Use project filename if name not specified
- self.name = name or os.path.splitext(os.path.basename(path))[0]
- # Copy passed lists (or set to empty lists)
- self.dependencies = list(dependencies or [])
- self.entry_type_guid = ENTRY_TYPE_GUIDS['project']
- if config_platform_overrides:
- self.config_platform_overrides = config_platform_overrides
- else:
- self.config_platform_overrides = {}
- self.fixpath_prefix = fixpath_prefix
- self.msbuild_toolset = None
- def set_dependencies(self, dependencies):
- self.dependencies = list(dependencies or [])
- def get_guid(self):
- if self.guid is None:
- # Set GUID from path
- # TODO(rspangler): This is fragile.
- # 1. We can't just use the project filename sans path, since there could
- # be multiple projects with the same base name (for example,
- # foo/unittest.vcproj and bar/unittest.vcproj).
- # 2. The path needs to be relative to $SOURCE_ROOT, so that the project
- # GUID is the same whether it's included from base/base.sln or
- # foo/bar/baz/baz.sln.
- # 3. The GUID needs to be the same each time this builder is invoked, so
- # that we don't need to rebuild the solution when the project changes.
- # 4. We should be able to handle pre-built project files by reading the
- # GUID from the files.
- self.guid = MakeGuid(self.name)
- return self.guid
- def set_msbuild_toolset(self, msbuild_toolset):
- self.msbuild_toolset = msbuild_toolset
- #------------------------------------------------------------------------------
- class MSVSSolution(object):
- """Visual Studio solution."""
- def __init__(self, path, version, entries=None, variants=None,
- websiteProperties=True):
- """Initializes the solution.
- Args:
- path: Path to solution file.
- version: Format version to emit.
- entries: List of entries in solution. May contain Folder or Project
- objects. May be None, if the folder is empty.
- variants: List of build variant strings. If none, a default list will
- be used.
- websiteProperties: Flag to decide if the website properties section
- is generated.
- """
- self.path = path
- self.websiteProperties = websiteProperties
- self.version = version
- # Copy passed lists (or set to empty lists)
- self.entries = list(entries or [])
- if variants:
- # Copy passed list
- self.variants = variants[:]
- else:
- # Use default
- self.variants = ['Debug|Win32', 'Release|Win32']
- # TODO(rspangler): Need to be able to handle a mapping of solution config
- # to project config. Should we be able to handle variants being a dict,
- # or add a separate variant_map variable? If it's a dict, we can't
- # guarantee the order of variants since dict keys aren't ordered.
- # TODO(rspangler): Automatically write to disk for now; should delay until
- # node-evaluation time.
- self.Write()
- def Write(self, writer=gyp.common.WriteOnDiff):
- """Writes the solution file to disk.
- Raises:
- IndexError: An entry appears multiple times.
- """
- # Walk the entry tree and collect all the folders and projects.
- all_entries = set()
- entries_to_check = self.entries[:]
- while entries_to_check:
- e = entries_to_check.pop(0)
- # If this entry has been visited, nothing to do.
- if e in all_entries:
- continue
- all_entries.add(e)
- # If this is a folder, check its entries too.
- if isinstance(e, MSVSFolder):
- entries_to_check += e.entries
- all_entries = sorted(all_entries)
- # Open file and print header
- f = writer(self.path)
- f.write('Microsoft Visual Studio Solution File, '
- 'Format Version %s\r\n' % self.version.SolutionVersion())
- f.write('# %s\r\n' % self.version.Description())
- # Project entries
- sln_root = os.path.split(self.path)[0]
- for e in all_entries:
- relative_path = gyp.common.RelativePath(e.path, sln_root)
- # msbuild does not accept an empty folder_name.
- # use '.' in case relative_path is empty.
- folder_name = relative_path.replace('/', '\\') or '.'
- f.write('Project("%s") = "%s", "%s", "%s"\r\n' % (
- e.entry_type_guid, # Entry type GUID
- e.name, # Folder name
- folder_name, # Folder name (again)
- e.get_guid(), # Entry GUID
- ))
- # TODO(rspangler): Need a way to configure this stuff
- if self.websiteProperties:
- f.write('\tProjectSection(WebsiteProperties) = preProject\r\n'
- '\t\tDebug.AspNetCompiler.Debug = "True"\r\n'
- '\t\tRelease.AspNetCompiler.Debug = "False"\r\n'
- '\tEndProjectSection\r\n')
- if isinstance(e, MSVSFolder):
- if e.items:
- f.write('\tProjectSection(SolutionItems) = preProject\r\n')
- for i in e.items:
- f.write('\t\t%s = %s\r\n' % (i, i))
- f.write('\tEndProjectSection\r\n')
- if isinstance(e, MSVSProject):
- if e.dependencies:
- f.write('\tProjectSection(ProjectDependencies) = postProject\r\n')
- for d in e.dependencies:
- f.write('\t\t%s = %s\r\n' % (d.get_guid(), d.get_guid()))
- f.write('\tEndProjectSection\r\n')
- f.write('EndProject\r\n')
- # Global section
- f.write('Global\r\n')
- # Configurations (variants)
- f.write('\tGlobalSection(SolutionConfigurationPlatforms) = preSolution\r\n')
- for v in self.variants:
- f.write('\t\t%s = %s\r\n' % (v, v))
- f.write('\tEndGlobalSection\r\n')
- # Sort config guids for easier diffing of solution changes.
- config_guids = []
- config_guids_overrides = {}
- for e in all_entries:
- if isinstance(e, MSVSProject):
- config_guids.append(e.get_guid())
- config_guids_overrides[e.get_guid()] = e.config_platform_overrides
- config_guids.sort()
- f.write('\tGlobalSection(ProjectConfigurationPlatforms) = postSolution\r\n')
- for g in config_guids:
- for v in self.variants:
- nv = config_guids_overrides[g].get(v, v)
- # Pick which project configuration to build for this solution
- # configuration.
- f.write('\t\t%s.%s.ActiveCfg = %s\r\n' % (
- g, # Project GUID
- v, # Solution build configuration
- nv, # Project build config for that solution config
- ))
- # Enable project in this solution configuration.
- f.write('\t\t%s.%s.Build.0 = %s\r\n' % (
- g, # Project GUID
- v, # Solution build configuration
- nv, # Project build config for that solution config
- ))
- f.write('\tEndGlobalSection\r\n')
- # TODO(rspangler): Should be able to configure this stuff too (though I've
- # never seen this be any different)
- f.write('\tGlobalSection(SolutionProperties) = preSolution\r\n')
- f.write('\t\tHideSolutionNode = FALSE\r\n')
- f.write('\tEndGlobalSection\r\n')
- # Folder mappings
- # Omit this section if there are no folders
- if any([e.entries for e in all_entries if isinstance(e, MSVSFolder)]):
- f.write('\tGlobalSection(NestedProjects) = preSolution\r\n')
- for e in all_entries:
- if not isinstance(e, MSVSFolder):
- continue # Does not apply to projects, only folders
- for subentry in e.entries:
- f.write('\t\t%s = %s\r\n' % (subentry.get_guid(), e.get_guid()))
- f.write('\tEndGlobalSection\r\n')
- f.write('EndGlobal\r\n')
- f.close()
|