| #! /usr/bin/env python |
| # encoding: utf-8 |
| # Avalanche Studios 2009-2011 |
| # Thomas Nagy 2011 |
| |
| """ |
| Redistribution and use in source and binary forms, with or without |
| modification, are permitted provided that the following conditions |
| are met: |
| |
| 1. Redistributions of source code must retain the above copyright |
| notice, this list of conditions and the following disclaimer. |
| |
| 2. Redistributions in binary form must reproduce the above copyright |
| notice, this list of conditions and the following disclaimer in the |
| documentation and/or other materials provided with the distribution. |
| |
| 3. The name of the author may not be used to endorse or promote products |
| derived from this software without specific prior written permission. |
| |
| THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR |
| IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
| WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
| DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, |
| INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
| (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR |
| SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) |
| HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, |
| STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING |
| IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
| POSSIBILITY OF SUCH DAMAGE. |
| """ |
| |
| """ |
| To add this tool to your project: |
| def options(conf): |
| opt.load('msvs') |
| |
| It can be a good idea to add the sync_exec tool too. |
| |
| To generate solution files: |
| $ waf configure msvs |
| |
| To customize the outputs, provide subclasses in your wscript files: |
| |
| from waflib.extras import msvs |
| class vsnode_target(msvs.vsnode_target): |
| def get_build_command(self, props): |
| # likely to be required |
| return "waf.bat build" |
| def collect_source(self): |
| # likely to be required |
| ... |
| class msvs_bar(msvs.msvs_generator): |
| def init(self): |
| msvs.msvs_generator.init(self) |
| self.vsnode_target = vsnode_target |
| |
| The msvs class re-uses the same build() function for reading the targets (task generators), |
| you may therefore specify msvs settings on the context object: |
| |
| def build(bld): |
| bld.solution_name = 'foo.sln' |
| bld.waf_command = 'waf.bat' |
| bld.projects_dir = bld.srcnode.make_node('.depproj') |
| bld.projects_dir.mkdir() |
| |
| For visual studio 2008, the command is called 'msvs2008', and the classes |
| such as vsnode_target are wrapped by a decorator class 'wrap_2008' to |
| provide special functionality. |
| |
| ASSUMPTIONS: |
| * a project can be either a directory or a target, vcxproj files are written only for targets that have source files |
| * each project is a vcxproj file, therefore the project uuid needs only to be a hash of the absolute path |
| """ |
| |
| import os, re, sys |
| import uuid # requires python 2.5 |
| from waflib.Build import BuildContext |
| from waflib import Utils, TaskGen, Logs, Task, Context, Node, Options |
| |
| HEADERS_GLOB = '**/(*.h|*.hpp|*.H|*.inl)' |
| |
| PROJECT_TEMPLATE = r'''<?xml version="1.0" encoding="UTF-8"?> |
| <Project DefaultTargets="Build" ToolsVersion="4.0" |
| xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> |
| |
| <ItemGroup Label="ProjectConfigurations"> |
| ${for b in project.build_properties} |
| <ProjectConfiguration Include="${b.configuration}|${b.platform}"> |
| <Configuration>${b.configuration}</Configuration> |
| <Platform>${b.platform}</Platform> |
| </ProjectConfiguration> |
| ${endfor} |
| </ItemGroup> |
| |
| <PropertyGroup Label="Globals"> |
| <ProjectGuid>{${project.uuid}}</ProjectGuid> |
| <Keyword>MakeFileProj</Keyword> |
| <ProjectName>${project.name}</ProjectName> |
| </PropertyGroup> |
| <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" /> |
| |
| ${for b in project.build_properties} |
| <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='${b.configuration}|${b.platform}'" Label="Configuration"> |
| <ConfigurationType>Makefile</ConfigurationType> |
| <OutDir>${b.outdir}</OutDir> |
| <PlatformToolset>v110</PlatformToolset> |
| </PropertyGroup> |
| ${endfor} |
| |
| <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" /> |
| <ImportGroup Label="ExtensionSettings"> |
| </ImportGroup> |
| |
| ${for b in project.build_properties} |
| <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='${b.configuration}|${b.platform}'"> |
| <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> |
| </ImportGroup> |
| ${endfor} |
| |
| ${for b in project.build_properties} |
| <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='${b.configuration}|${b.platform}'"> |
| <NMakeBuildCommandLine>${xml:project.get_build_command(b)}</NMakeBuildCommandLine> |
| <NMakeReBuildCommandLine>${xml:project.get_rebuild_command(b)}</NMakeReBuildCommandLine> |
| <NMakeCleanCommandLine>${xml:project.get_clean_command(b)}</NMakeCleanCommandLine> |
| <NMakeIncludeSearchPath>${xml:b.includes_search_path}</NMakeIncludeSearchPath> |
| <NMakePreprocessorDefinitions>${xml:b.preprocessor_definitions};$(NMakePreprocessorDefinitions)</NMakePreprocessorDefinitions> |
| <IncludePath>${xml:b.includes_search_path}</IncludePath> |
| <ExecutablePath>$(ExecutablePath)</ExecutablePath> |
| |
| ${if getattr(b, 'output_file', None)} |
| <NMakeOutput>${xml:b.output_file}</NMakeOutput> |
| ${endif} |
| ${if getattr(b, 'deploy_dir', None)} |
| <RemoteRoot>${xml:b.deploy_dir}</RemoteRoot> |
| ${endif} |
| </PropertyGroup> |
| ${endfor} |
| |
| ${for b in project.build_properties} |
| ${if getattr(b, 'deploy_dir', None)} |
| <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='${b.configuration}|${b.platform}'"> |
| <Deploy> |
| <DeploymentType>CopyToHardDrive</DeploymentType> |
| </Deploy> |
| </ItemDefinitionGroup> |
| ${endif} |
| ${endfor} |
| |
| <ItemGroup> |
| ${for x in project.source} |
| <${project.get_key(x)} Include='${x.win32path()}' /> |
| ${endfor} |
| </ItemGroup> |
| <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> |
| <ImportGroup Label="ExtensionTargets"> |
| </ImportGroup> |
| </Project> |
| ''' |
| |
| FILTER_TEMPLATE = '''<?xml version="1.0" encoding="UTF-8"?> |
| <Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> |
| <ItemGroup> |
| ${for x in project.source} |
| <${project.get_key(x)} Include="${x.win32path()}"> |
| <Filter>${project.get_filter_name(x.parent)}</Filter> |
| </${project.get_key(x)}> |
| ${endfor} |
| </ItemGroup> |
| <ItemGroup> |
| ${for x in project.dirs()} |
| <Filter Include="${project.get_filter_name(x)}"> |
| <UniqueIdentifier>{${project.make_uuid(x.win32path())}}</UniqueIdentifier> |
| </Filter> |
| ${endfor} |
| </ItemGroup> |
| </Project> |
| ''' |
| |
| PROJECT_2008_TEMPLATE = r'''<?xml version="1.0" encoding="UTF-8"?> |
| <VisualStudioProject ProjectType="Visual C++" Version="9,00" |
| Name="${xml: project.name}" ProjectGUID="{${project.uuid}}" |
| Keyword="MakeFileProj" |
| TargetFrameworkVersion="196613"> |
| <Platforms> |
| ${if project.build_properties} |
| ${for b in project.build_properties} |
| <Platform Name="${xml: b.platform}" /> |
| ${endfor} |
| ${else} |
| <Platform Name="Win32" /> |
| ${endif} |
| </Platforms> |
| <ToolFiles> |
| </ToolFiles> |
| <Configurations> |
| ${if project.build_properties} |
| ${for b in project.build_properties} |
| <Configuration |
| Name="${xml: b.configuration}|${xml: b.platform}" |
| IntermediateDirectory="$ConfigurationName" |
| OutputDirectory="${xml: b.outdir}" |
| ConfigurationType="0"> |
| <Tool |
| Name="VCNMakeTool" |
| BuildCommandLine="${xml: project.get_build_command(b)}" |
| ReBuildCommandLine="${xml: project.get_rebuild_command(b)}" |
| CleanCommandLine="${xml: project.get_clean_command(b)}" |
| ${if getattr(b, 'output_file', None)} |
| Output="${xml: b.output_file}" |
| ${endif} |
| PreprocessorDefinitions="${xml: b.preprocessor_definitions}" |
| IncludeSearchPath="${xml: b.includes_search_path}" |
| ForcedIncludes="" |
| ForcedUsingAssemblies="" |
| AssemblySearchPath="" |
| CompileAsManaged="" |
| /> |
| </Configuration> |
| ${endfor} |
| ${else} |
| <Configuration Name="Release|Win32" > |
| </Configuration> |
| ${endif} |
| </Configurations> |
| <References> |
| </References> |
| <Files> |
| ${project.display_filter()} |
| </Files> |
| </VisualStudioProject> |
| ''' |
| |
| SOLUTION_TEMPLATE = '''Microsoft Visual Studio Solution File, Format Version ${project.numver} |
| # Visual Studio ${project.vsver} |
| ${for p in project.all_projects} |
| Project("{${p.ptype()}}") = "${p.name}", "${p.title}", "{${p.uuid}}" |
| EndProject${endfor} |
| Global |
| GlobalSection(SolutionConfigurationPlatforms) = preSolution |
| ${if project.all_projects} |
| ${for (configuration, platform) in project.all_projects[0].ctx.project_configurations()} |
| ${configuration}|${platform} = ${configuration}|${platform} |
| ${endfor} |
| ${endif} |
| EndGlobalSection |
| GlobalSection(ProjectConfigurationPlatforms) = postSolution |
| ${for p in project.all_projects} |
| ${if hasattr(p, 'source')} |
| ${for b in p.build_properties} |
| {${p.uuid}}.${b.configuration}|${b.platform}.ActiveCfg = ${b.configuration}|${b.platform} |
| ${if getattr(p, 'is_active', None)} |
| {${p.uuid}}.${b.configuration}|${b.platform}.Build.0 = ${b.configuration}|${b.platform} |
| ${endif} |
| ${if getattr(p, 'is_deploy', None)} |
| {${p.uuid}}.${b.configuration}|${b.platform}.Deploy.0 = ${b.configuration}|${b.platform} |
| ${endif} |
| ${endfor} |
| ${endif} |
| ${endfor} |
| EndGlobalSection |
| GlobalSection(SolutionProperties) = preSolution |
| HideSolutionNode = FALSE |
| EndGlobalSection |
| GlobalSection(NestedProjects) = preSolution |
| ${for p in project.all_projects} |
| ${if p.parent} |
| {${p.uuid}} = {${p.parent.uuid}} |
| ${endif} |
| ${endfor} |
| EndGlobalSection |
| EndGlobal |
| ''' |
| |
| COMPILE_TEMPLATE = '''def f(project): |
| lst = [] |
| def xml_escape(value): |
| return value.replace("&", "&").replace('"', """).replace("'", "'").replace("<", "<").replace(">", ">") |
| |
| %s |
| |
| #f = open('cmd.txt', 'w') |
| #f.write(str(lst)) |
| #f.close() |
| return ''.join(lst) |
| ''' |
| reg_act = re.compile(r"(?P<backslash>\\)|(?P<dollar>\$\$)|(?P<subst>\$\{(?P<code>[^}]*?)\})", re.M) |
| def compile_template(line): |
| """ |
| Compile a template expression into a python function (like jsps, but way shorter) |
| """ |
| extr = [] |
| def repl(match): |
| g = match.group |
| if g('dollar'): return "$" |
| elif g('backslash'): |
| return "\\" |
| elif g('subst'): |
| extr.append(g('code')) |
| return "<<|@|>>" |
| return None |
| |
| line2 = reg_act.sub(repl, line) |
| params = line2.split('<<|@|>>') |
| assert(extr) |
| |
| |
| indent = 0 |
| buf = [] |
| app = buf.append |
| |
| def app(txt): |
| buf.append(indent * '\t' + txt) |
| |
| for x in range(len(extr)): |
| if params[x]: |
| app("lst.append(%r)" % params[x]) |
| |
| f = extr[x] |
| if f.startswith('if') or f.startswith('for'): |
| app(f + ':') |
| indent += 1 |
| elif f.startswith('py:'): |
| app(f[3:]) |
| elif f.startswith('endif') or f.startswith('endfor'): |
| indent -= 1 |
| elif f.startswith('else') or f.startswith('elif'): |
| indent -= 1 |
| app(f + ':') |
| indent += 1 |
| elif f.startswith('xml:'): |
| app('lst.append(xml_escape(%s))' % f[4:]) |
| else: |
| #app('lst.append((%s) or "cannot find %s")' % (f, f)) |
| app('lst.append(%s)' % f) |
| |
| if extr: |
| if params[-1]: |
| app("lst.append(%r)" % params[-1]) |
| |
| fun = COMPILE_TEMPLATE % "\n\t".join(buf) |
| #print(fun) |
| return Task.funex(fun) |
| |
| |
| re_blank = re.compile('(\n|\r|\\s)*\n', re.M) |
| def rm_blank_lines(txt): |
| txt = re_blank.sub('\r\n', txt) |
| return txt |
| |
| BOM = '\xef\xbb\xbf' |
| try: |
| BOM = bytes(BOM, 'iso8859-1') # python 3 |
| except TypeError: |
| pass |
| |
| def stealth_write(self, data, flags='wb'): |
| try: |
| unicode |
| except NameError: |
| data = data.encode('utf-8') # python 3 |
| else: |
| data = data.decode(sys.getfilesystemencoding(), 'replace') |
| data = data.encode('utf-8') |
| |
| if self.name.endswith('.vcproj') or self.name.endswith('.vcxproj'): |
| data = BOM + data |
| |
| try: |
| txt = self.read(flags='rb') |
| if txt != data: |
| raise ValueError('must write') |
| except (IOError, ValueError): |
| self.write(data, flags=flags) |
| else: |
| Logs.debug('msvs: skipping %s' % self.win32path()) |
| Node.Node.stealth_write = stealth_write |
| |
| re_win32 = re.compile(r'^([/\\]cygdrive)?[/\\]([a-z])([^a-z0-9_-].*)', re.I) |
| def win32path(self): |
| p = self.abspath() |
| m = re_win32.match(p) |
| if m: |
| return "%s:%s" % (m.group(2).upper(), m.group(3)) |
| return p |
| Node.Node.win32path = win32path |
| |
| re_quote = re.compile("[^a-zA-Z0-9-]") |
| def quote(s): |
| return re_quote.sub("_", s) |
| |
| def xml_escape(value): |
| return value.replace("&", "&").replace('"', """).replace("'", "'").replace("<", "<").replace(">", ">") |
| |
| def make_uuid(v, prefix = None): |
| """ |
| simple utility function |
| """ |
| if isinstance(v, dict): |
| keys = list(v.keys()) |
| keys.sort() |
| tmp = str([(k, v[k]) for k in keys]) |
| else: |
| tmp = str(v) |
| d = Utils.md5(tmp.encode()).hexdigest().upper() |
| if prefix: |
| d = '%s%s' % (prefix, d[8:]) |
| gid = uuid.UUID(d, version = 4) |
| return str(gid).upper() |
| |
| def diff(node, fromnode): |
| # difference between two nodes, but with "(..)" instead of ".." |
| c1 = node |
| c2 = fromnode |
| |
| c1h = c1.height() |
| c2h = c2.height() |
| |
| lst = [] |
| up = 0 |
| |
| while c1h > c2h: |
| lst.append(c1.name) |
| c1 = c1.parent |
| c1h -= 1 |
| |
| while c2h > c1h: |
| up += 1 |
| c2 = c2.parent |
| c2h -= 1 |
| |
| while id(c1) != id(c2): |
| lst.append(c1.name) |
| up += 1 |
| |
| c1 = c1.parent |
| c2 = c2.parent |
| |
| for i in range(up): |
| lst.append('(..)') |
| lst.reverse() |
| return tuple(lst) |
| |
| class build_property(object): |
| pass |
| |
| class vsnode(object): |
| """ |
| Abstract class representing visual studio elements |
| We assume that all visual studio nodes have a uuid and a parent |
| """ |
| def __init__(self, ctx): |
| self.ctx = ctx # msvs context |
| self.name = '' # string, mandatory |
| self.vspath = '' # path in visual studio (name for dirs, absolute path for projects) |
| self.uuid = '' # string, mandatory |
| self.parent = None # parent node for visual studio nesting |
| |
| def get_waf(self): |
| """ |
| Override in subclasses... |
| """ |
| return 'cd /d "%s" & %s' % (self.ctx.srcnode.win32path(), getattr(self.ctx, 'waf_command', 'waf.bat')) |
| |
| def ptype(self): |
| """ |
| Return a special uuid for projects written in the solution file |
| """ |
| pass |
| |
| def write(self): |
| """ |
| Write the project file, by default, do nothing |
| """ |
| pass |
| |
| def make_uuid(self, val): |
| """ |
| Alias for creating uuid values easily (the templates cannot access global variables) |
| """ |
| return make_uuid(val) |
| |
| class vsnode_vsdir(vsnode): |
| """ |
| Nodes representing visual studio folders (which do not match the filesystem tree!) |
| """ |
| VS_GUID_SOLUTIONFOLDER = "2150E333-8FDC-42A3-9474-1A3956D46DE8" |
| def __init__(self, ctx, uuid, name, vspath=''): |
| vsnode.__init__(self, ctx) |
| self.title = self.name = name |
| self.uuid = uuid |
| self.vspath = vspath or name |
| |
| def ptype(self): |
| return self.VS_GUID_SOLUTIONFOLDER |
| |
| class vsnode_project(vsnode): |
| """ |
| Abstract class representing visual studio project elements |
| A project is assumed to be writable, and has a node representing the file to write to |
| """ |
| VS_GUID_VCPROJ = "8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942" |
| def ptype(self): |
| return self.VS_GUID_VCPROJ |
| |
| def __init__(self, ctx, node): |
| vsnode.__init__(self, ctx) |
| self.path = node |
| self.uuid = make_uuid(node.win32path()) |
| self.name = node.name |
| self.title = self.path.win32path() |
| self.source = [] # list of node objects |
| self.build_properties = [] # list of properties (nmake commands, output dir, etc) |
| |
| def dirs(self): |
| """ |
| Get the list of parent folders of the source files (header files included) |
| for writing the filters |
| """ |
| lst = [] |
| def add(x): |
| if x.height() > self.tg.path.height() and x not in lst: |
| lst.append(x) |
| add(x.parent) |
| for x in self.source: |
| add(x.parent) |
| return lst |
| |
| def write(self): |
| Logs.debug('msvs: creating %r' % self.path) |
| |
| # first write the project file |
| template1 = compile_template(PROJECT_TEMPLATE) |
| proj_str = template1(self) |
| proj_str = rm_blank_lines(proj_str) |
| self.path.stealth_write(proj_str) |
| |
| # then write the filter |
| template2 = compile_template(FILTER_TEMPLATE) |
| filter_str = template2(self) |
| filter_str = rm_blank_lines(filter_str) |
| tmp = self.path.parent.make_node(self.path.name + '.filters') |
| tmp.stealth_write(filter_str) |
| |
| def get_key(self, node): |
| """ |
| required for writing the source files |
| """ |
| name = node.name |
| if name.endswith('.cpp') or name.endswith('.c'): |
| return 'ClCompile' |
| return 'ClInclude' |
| |
| def collect_properties(self): |
| """ |
| Returns a list of triplet (configuration, platform, output_directory) |
| """ |
| ret = [] |
| for c in self.ctx.configurations: |
| for p in self.ctx.platforms: |
| x = build_property() |
| x.outdir = '' |
| |
| x.configuration = c |
| x.platform = p |
| |
| x.preprocessor_definitions = '' |
| x.includes_search_path = '' |
| |
| # can specify "deploy_dir" too |
| ret.append(x) |
| self.build_properties = ret |
| |
| def get_build_params(self, props): |
| opt = '--execsolution=%s' % self.ctx.get_solution_node().win32path() |
| return (self.get_waf(), opt) |
| |
| def get_build_command(self, props): |
| return "%s build %s" % self.get_build_params(props) |
| |
| def get_clean_command(self, props): |
| return "%s clean %s" % self.get_build_params(props) |
| |
| def get_rebuild_command(self, props): |
| return "%s clean build %s" % self.get_build_params(props) |
| |
| def get_filter_name(self, node): |
| lst = diff(node, self.tg.path) |
| return '\\'.join(lst) or '.' |
| |
| class vsnode_alias(vsnode_project): |
| def __init__(self, ctx, node, name): |
| vsnode_project.__init__(self, ctx, node) |
| self.name = name |
| self.output_file = '' |
| |
| class vsnode_build_all(vsnode_alias): |
| """ |
| Fake target used to emulate the behaviour of "make all" (starting one process by target is slow) |
| This is the only alias enabled by default |
| """ |
| def __init__(self, ctx, node, name='build_all_projects'): |
| vsnode_alias.__init__(self, ctx, node, name) |
| self.is_active = True |
| |
| class vsnode_install_all(vsnode_alias): |
| """ |
| Fake target used to emulate the behaviour of "make install" |
| """ |
| def __init__(self, ctx, node, name='install_all_projects'): |
| vsnode_alias.__init__(self, ctx, node, name) |
| |
| def get_build_command(self, props): |
| return "%s build install %s" % self.get_build_params(props) |
| |
| def get_clean_command(self, props): |
| return "%s clean %s" % self.get_build_params(props) |
| |
| def get_rebuild_command(self, props): |
| return "%s clean build install %s" % self.get_build_params(props) |
| |
| class vsnode_project_view(vsnode_alias): |
| """ |
| Fake target used to emulate a file system view |
| """ |
| def __init__(self, ctx, node, name='project_view'): |
| vsnode_alias.__init__(self, ctx, node, name) |
| self.tg = self.ctx() # fake one, cannot remove |
| self.exclude_files = Node.exclude_regs + ''' |
| waf-1.8.* |
| waf3-1.8.*/** |
| .waf-1.8.* |
| .waf3-1.8.*/** |
| **/*.sdf |
| **/*.suo |
| **/*.ncb |
| **/%s |
| ''' % Options.lockfile |
| |
| def collect_source(self): |
| # this is likely to be slow |
| self.source = self.ctx.srcnode.ant_glob('**', excl=self.exclude_files) |
| |
| def get_build_command(self, props): |
| params = self.get_build_params(props) + (self.ctx.cmd,) |
| return "%s %s %s" % params |
| |
| def get_clean_command(self, props): |
| return "" |
| |
| def get_rebuild_command(self, props): |
| return self.get_build_command(props) |
| |
| class vsnode_target(vsnode_project): |
| """ |
| Visual studio project representing a targets (programs, libraries, etc) and bound |
| to a task generator |
| """ |
| def __init__(self, ctx, tg): |
| """ |
| A project is more or less equivalent to a file/folder |
| """ |
| base = getattr(ctx, 'projects_dir', None) or tg.path |
| node = base.make_node(quote(tg.name) + ctx.project_extension) # the project file as a Node |
| vsnode_project.__init__(self, ctx, node) |
| self.name = quote(tg.name) |
| self.tg = tg # task generator |
| |
| def get_build_params(self, props): |
| """ |
| Override the default to add the target name |
| """ |
| opt = '--execsolution=%s' % self.ctx.get_solution_node().win32path() |
| if getattr(self, 'tg', None): |
| opt += " --targets=%s" % self.tg.name |
| return (self.get_waf(), opt) |
| |
| def collect_source(self): |
| tg = self.tg |
| source_files = tg.to_nodes(getattr(tg, 'source', [])) |
| include_dirs = Utils.to_list(getattr(tg, 'msvs_includes', [])) |
| include_files = [] |
| for x in include_dirs: |
| if isinstance(x, str): |
| x = tg.path.find_node(x) |
| if x: |
| lst = [y for y in x.ant_glob(HEADERS_GLOB, flat=False)] |
| include_files.extend(lst) |
| |
| # remove duplicates |
| self.source.extend(list(set(source_files + include_files))) |
| self.source.sort(key=lambda x: x.win32path()) |
| |
| def collect_properties(self): |
| """ |
| Visual studio projects are associated with platforms and configurations (for building especially) |
| """ |
| super(vsnode_target, self).collect_properties() |
| for x in self.build_properties: |
| x.outdir = self.path.parent.win32path() |
| x.preprocessor_definitions = '' |
| x.includes_search_path = '' |
| |
| try: |
| tsk = self.tg.link_task |
| except AttributeError: |
| pass |
| else: |
| x.output_file = tsk.outputs[0].win32path() |
| x.preprocessor_definitions = ';'.join(tsk.env.DEFINES) |
| x.includes_search_path = ';'.join(self.tg.env.INCPATHS) |
| |
| class msvs_generator(BuildContext): |
| '''generates a visual studio 2010 solution''' |
| cmd = 'msvs' |
| fun = 'build' |
| |
| def init(self): |
| """ |
| Some data that needs to be present |
| """ |
| if not getattr(self, 'configurations', None): |
| self.configurations = ['Release'] # LocalRelease, RemoteDebug, etc |
| if not getattr(self, 'platforms', None): |
| self.platforms = ['Win32'] |
| if not getattr(self, 'all_projects', None): |
| self.all_projects = [] |
| if not getattr(self, 'project_extension', None): |
| self.project_extension = '.vcxproj' |
| if not getattr(self, 'projects_dir', None): |
| self.projects_dir = self.srcnode.make_node('.depproj') |
| self.projects_dir.mkdir() |
| |
| # bind the classes to the object, so that subclass can provide custom generators |
| if not getattr(self, 'vsnode_vsdir', None): |
| self.vsnode_vsdir = vsnode_vsdir |
| if not getattr(self, 'vsnode_target', None): |
| self.vsnode_target = vsnode_target |
| if not getattr(self, 'vsnode_build_all', None): |
| self.vsnode_build_all = vsnode_build_all |
| if not getattr(self, 'vsnode_install_all', None): |
| self.vsnode_install_all = vsnode_install_all |
| if not getattr(self, 'vsnode_project_view', None): |
| self.vsnode_project_view = vsnode_project_view |
| |
| self.numver = '11.00' |
| self.vsver = '2010' |
| |
| def execute(self): |
| """ |
| Entry point |
| """ |
| self.restore() |
| if not self.all_envs: |
| self.load_envs() |
| self.recurse([self.run_dir]) |
| |
| # user initialization |
| self.init() |
| |
| # two phases for creating the solution |
| self.collect_projects() # add project objects into "self.all_projects" |
| self.write_files() # write the corresponding project and solution files |
| |
| def collect_projects(self): |
| """ |
| Fill the list self.all_projects with project objects |
| Fill the list of build targets |
| """ |
| self.collect_targets() |
| self.add_aliases() |
| self.collect_dirs() |
| default_project = getattr(self, 'default_project', None) |
| def sortfun(x): |
| if x.name == default_project: |
| return '' |
| return getattr(x, 'path', None) and x.path.win32path() or x.name |
| self.all_projects.sort(key=sortfun) |
| |
| def write_files(self): |
| """ |
| Write the project and solution files from the data collected |
| so far. It is unlikely that you will want to change this |
| """ |
| for p in self.all_projects: |
| p.write() |
| |
| # and finally write the solution file |
| node = self.get_solution_node() |
| node.parent.mkdir() |
| Logs.warn('Creating %r' % node) |
| template1 = compile_template(SOLUTION_TEMPLATE) |
| sln_str = template1(self) |
| sln_str = rm_blank_lines(sln_str) |
| node.stealth_write(sln_str) |
| |
| def get_solution_node(self): |
| """ |
| The solution filename is required when writing the .vcproj files |
| return self.solution_node and if it does not exist, make one |
| """ |
| try: |
| return self.solution_node |
| except AttributeError: |
| pass |
| |
| solution_name = getattr(self, 'solution_name', None) |
| if not solution_name: |
| solution_name = getattr(Context.g_module, Context.APPNAME, 'project') + '.sln' |
| if os.path.isabs(solution_name): |
| self.solution_node = self.root.make_node(solution_name) |
| else: |
| self.solution_node = self.srcnode.make_node(solution_name) |
| return self.solution_node |
| |
| def project_configurations(self): |
| """ |
| Helper that returns all the pairs (config,platform) |
| """ |
| ret = [] |
| for c in self.configurations: |
| for p in self.platforms: |
| ret.append((c, p)) |
| return ret |
| |
| def collect_targets(self): |
| """ |
| Process the list of task generators |
| """ |
| for g in self.groups: |
| for tg in g: |
| if not isinstance(tg, TaskGen.task_gen): |
| continue |
| |
| if not hasattr(tg, 'msvs_includes'): |
| tg.msvs_includes = tg.to_list(getattr(tg, 'includes', [])) + tg.to_list(getattr(tg, 'export_includes', [])) |
| tg.post() |
| if not getattr(tg, 'link_task', None): |
| continue |
| |
| p = self.vsnode_target(self, tg) |
| p.collect_source() # delegate this processing |
| p.collect_properties() |
| self.all_projects.append(p) |
| |
| def add_aliases(self): |
| """ |
| Add a specific target that emulates the "make all" necessary for Visual studio when pressing F7 |
| We also add an alias for "make install" (disabled by default) |
| """ |
| base = getattr(self, 'projects_dir', None) or self.tg.path |
| |
| node_project = base.make_node('build_all_projects' + self.project_extension) # Node |
| p_build = self.vsnode_build_all(self, node_project) |
| p_build.collect_properties() |
| self.all_projects.append(p_build) |
| |
| node_project = base.make_node('install_all_projects' + self.project_extension) # Node |
| p_install = self.vsnode_install_all(self, node_project) |
| p_install.collect_properties() |
| self.all_projects.append(p_install) |
| |
| node_project = base.make_node('project_view' + self.project_extension) # Node |
| p_view = self.vsnode_project_view(self, node_project) |
| p_view.collect_source() |
| p_view.collect_properties() |
| self.all_projects.append(p_view) |
| |
| n = self.vsnode_vsdir(self, make_uuid(self.srcnode.win32path() + 'build_aliases'), "build_aliases") |
| p_build.parent = p_install.parent = p_view.parent = n |
| self.all_projects.append(n) |
| |
| def collect_dirs(self): |
| """ |
| Create the folder structure in the Visual studio project view |
| """ |
| seen = {} |
| def make_parents(proj): |
| # look at a project, try to make a parent |
| if getattr(proj, 'parent', None): |
| # aliases already have parents |
| return |
| x = proj.iter_path |
| if x in seen: |
| proj.parent = seen[x] |
| return |
| |
| # There is not vsnode_vsdir for x. |
| # So create a project representing the folder "x" |
| n = proj.parent = seen[x] = self.vsnode_vsdir(self, make_uuid(x.win32path()), x.name) |
| n.iter_path = x.parent |
| self.all_projects.append(n) |
| |
| # recurse up to the project directory |
| if x.height() > self.srcnode.height() + 1: |
| make_parents(n) |
| |
| for p in self.all_projects[:]: # iterate over a copy of all projects |
| if not getattr(p, 'tg', None): |
| # but only projects that have a task generator |
| continue |
| |
| # make a folder for each task generator |
| p.iter_path = p.tg.path |
| make_parents(p) |
| |
| def wrap_2008(cls): |
| class dec(cls): |
| def __init__(self, *k, **kw): |
| cls.__init__(self, *k, **kw) |
| self.project_template = PROJECT_2008_TEMPLATE |
| |
| def display_filter(self): |
| |
| root = build_property() |
| root.subfilters = [] |
| root.sourcefiles = [] |
| root.source = [] |
| root.name = '' |
| |
| @Utils.run_once |
| def add_path(lst): |
| if not lst: |
| return root |
| child = build_property() |
| child.subfilters = [] |
| child.sourcefiles = [] |
| child.source = [] |
| child.name = lst[-1] |
| |
| par = add_path(lst[:-1]) |
| par.subfilters.append(child) |
| return child |
| |
| for x in self.source: |
| # this crap is for enabling subclasses to override get_filter_name |
| tmp = self.get_filter_name(x.parent) |
| tmp = tmp != '.' and tuple(tmp.split('\\')) or () |
| par = add_path(tmp) |
| par.source.append(x) |
| |
| def display(n): |
| buf = [] |
| for x in n.source: |
| buf.append('<File RelativePath="%s" FileType="%s"/>\n' % (xml_escape(x.win32path()), self.get_key(x))) |
| for x in n.subfilters: |
| buf.append('<Filter Name="%s">' % xml_escape(x.name)) |
| buf.append(display(x)) |
| buf.append('</Filter>') |
| return '\n'.join(buf) |
| |
| return display(root) |
| |
| def get_key(self, node): |
| """ |
| If you do not want to let visual studio use the default file extensions, |
| override this method to return a value: |
| 0: C/C++ Code, 1: C++ Class, 2: C++ Header File, 3: C++ Form, |
| 4: C++ Control, 5: Text File, 6: DEF File, 7: IDL File, |
| 8: Makefile, 9: RGS File, 10: RC File, 11: RES File, 12: XSD File, |
| 13: XML File, 14: HTML File, 15: CSS File, 16: Bitmap, 17: Icon, |
| 18: Resx File, 19: BSC File, 20: XSX File, 21: C++ Web Service, |
| 22: ASAX File, 23: Asp Page, 24: Document, 25: Discovery File, |
| 26: C# File, 27: eFileTypeClassDiagram, 28: MHTML Document, |
| 29: Property Sheet, 30: Cursor, 31: Manifest, 32: eFileTypeRDLC |
| """ |
| return '' |
| |
| def write(self): |
| Logs.debug('msvs: creating %r' % self.path) |
| template1 = compile_template(self.project_template) |
| proj_str = template1(self) |
| proj_str = rm_blank_lines(proj_str) |
| self.path.stealth_write(proj_str) |
| |
| return dec |
| |
| class msvs_2008_generator(msvs_generator): |
| '''generates a visual studio 2008 solution''' |
| cmd = 'msvs2008' |
| fun = msvs_generator.fun |
| |
| def init(self): |
| if not getattr(self, 'project_extension', None): |
| self.project_extension = '_2008.vcproj' |
| if not getattr(self, 'solution_name', None): |
| self.solution_name = getattr(Context.g_module, Context.APPNAME, 'project') + '_2008.sln' |
| |
| if not getattr(self, 'vsnode_target', None): |
| self.vsnode_target = wrap_2008(vsnode_target) |
| if not getattr(self, 'vsnode_build_all', None): |
| self.vsnode_build_all = wrap_2008(vsnode_build_all) |
| if not getattr(self, 'vsnode_install_all', None): |
| self.vsnode_install_all = wrap_2008(vsnode_install_all) |
| if not getattr(self, 'vsnode_project_view', None): |
| self.vsnode_project_view = wrap_2008(vsnode_project_view) |
| |
| msvs_generator.init(self) |
| self.numver = '10.00' |
| self.vsver = '2008' |
| |
| def options(ctx): |
| """ |
| If the msvs option is used, try to detect if the build is made from visual studio |
| """ |
| ctx.add_option('--execsolution', action='store', help='when building with visual studio, use a build state file') |
| |
| old = BuildContext.execute |
| def override_build_state(ctx): |
| def lock(rm, add): |
| uns = ctx.options.execsolution.replace('.sln', rm) |
| uns = ctx.root.make_node(uns) |
| try: |
| uns.delete() |
| except OSError: |
| pass |
| |
| uns = ctx.options.execsolution.replace('.sln', add) |
| uns = ctx.root.make_node(uns) |
| try: |
| uns.write('') |
| except EnvironmentError: |
| pass |
| |
| if ctx.options.execsolution: |
| ctx.launch_dir = Context.top_dir # force a build for the whole project (invalid cwd when called by visual studio) |
| lock('.lastbuildstate', '.unsuccessfulbuild') |
| old(ctx) |
| lock('.unsuccessfulbuild', '.lastbuildstate') |
| else: |
| old(ctx) |
| BuildContext.execute = override_build_state |
| |