ambuild2/frontend/base_gen.py
author David Anderson <dvander@alliedmods.net>
Fri Feb 07 22:54:58 2014 -0800 (2014-02-07)
changeset 236 aa23c47cb040
parent 226 185e3cd6740b
permissions -rw-r--r--
Add an originalCwd property to builders (bug 6037, r=ds).
     1 # vim: set ts=8 sts=2 sw=2 tw=99 et:
     2 #
     3 # This file is part of AMBuild.
     4 # 
     5 # AMBuild is free software: you can redistribute it and/or modify
     6 # it under the terms of the GNU General Public License as published by
     7 # the Free Software Foundation, either version 3 of the License, or
     8 # (at your option) any later version.
     9 # 
    10 # AMBuild is distributed in the hope that it will be useful,
    11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
    12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    13 # GNU General Public License for more details.
    14 # 
    15 # You should have received a copy of the GNU General Public License
    16 # along with AMBuild. If not, see <http://www.gnu.org/licenses/>.
    17 import os
    18 import sys, copy
    19 from ambuild2 import util
    20 from ambuild2.frontend import cpp
    21 
    22 # AMBuild 2 scripts are parsed recursively. Each script is supplied with a
    23 # "builder" object, which maps to a Context object. Each script gets its own
    24 # context. The context describes the parent build file generator, the local
    25 # input and output folders, and the global compiler that was detected in the
    26 # root script (if any).
    27 #
    28 # Contexts form a tree that matches the build script hierarchy. This can be
    29 # utilized by backends for minimal reparsing and DAG updates when build
    30 # scripts change.
    31 
    32 class ConfigureException(Exception):
    33   def __init__(self, *args, **kwargs):
    34     super(ConfigureException, self).__init__(*args, **kwargs)
    35 
    36 class Context(object):
    37   def __init__(self, generator, parent, script):
    38     self.generator = generator
    39     self.parent = parent
    40     self.script = script
    41     self.compiler = None
    42 
    43     if parent:
    44       self.compiler = parent.compiler
    45 
    46     # By default, all generated files for a build script are placed in a path
    47     # matching its layout in the source tree.
    48     path, name = os.path.split(script)
    49     if parent:
    50       self.currentSourcePath = os.path.join(parent.currentSourcePath, path)
    51       self.currentSourceFolder = os.path.join(parent.currentSourceFolder, path)
    52       self.buildFolder = os.path.join(parent.buildFolder, path)
    53     else:
    54       self.currentSourcePath = generator.sourcePath
    55       self.currentSourceFolder = ''
    56       self.buildFolder = ''
    57     self.buildScript = os.path.join(self.currentSourceFolder, name)
    58     self.localFolder_ = self.buildFolder
    59 
    60   # Root source folder.
    61   @property
    62   def sourcePath(self):
    63     return self.generator.sourcePath
    64 
    65   @property
    66   def options(self):
    67     return self.generator.options
    68 
    69   @property
    70   def buildPath(self):
    71     return self.generator.buildPath
    72 
    73   # In build systems with dependency graphs, this can return a node
    74   # representing buildFolder. Otherwise, it returns buildFolder.
    75   @property
    76   def localFolder(self):
    77     return self.generator.getLocalFolder(self)
    78 
    79   @property
    80   def target_platform(self):
    81     return self.generator.target_platform
    82 
    83   @property
    84   def host_platform(self):
    85     return self.generator.host_platform
    86 
    87   @property
    88   def originalCwd(self):
    89     return self.generator.originalCwd
    90 
    91   def SetBuildFolder(self, folder):
    92     if folder == '/' or folder == '.' or folder == './':
    93       self.buildFolder = ''
    94     else:
    95       self.buildFolder = os.path.normpath(folder)
    96     self.localFolder_ = self.buildFolder
    97 
    98   def DetectCompilers(self):
    99     if not self.compiler:
   100       self.compiler = self.generator.DetectCompilers()
   101     return self.compiler
   102 
   103   def RunScript(self, file, vars={}):
   104     return self.generator.evalScript(file, vars)
   105 
   106   def RunBuildScripts(self, files, vars={}):
   107     if util.IsString(files):
   108       self.generator.evalScript(files, vars)
   109     else:
   110       for script in files:
   111         self.generator.evalScript(script, vars)
   112 
   113   def Add(self, taskbuilder):
   114     taskbuilder.finish(self)
   115     return taskbuilder.generate(self.generator, self)
   116 
   117   def AddSource(self, source_path):
   118     return self.generator.addSource(self, source_path)
   119 
   120   def AddSymlink(self, source, output_path):
   121     return self.generator.addSymlink(self, source, output_path)
   122 
   123   def AddFolder(self, folder):
   124     return self.generator.addFolder(self, folder)
   125 
   126   def AddCopy(self, source, output_path):
   127     return self.generator.addCopy(self, source, output_path)
   128 
   129   def AddCommand(self, inputs, argv, outputs, folder=-1, dep_type=None, weak_inputs=[],
   130                  shared_outputs=[]):
   131     return self.generator.addShellCommand(
   132       self,
   133       inputs,
   134       argv,
   135       outputs,
   136       folder = folder,
   137       dep_type = dep_type,
   138       weak_inputs = weak_inputs,
   139       shared_outputs = shared_outputs
   140     )
   141 
   142   def AddConfigureFile(self, path):
   143     return self.generator.addConfigureFile(self, path)
   144 
   145 class Generator(object):
   146   def __init__(self, sourcePath, buildPath, originalCwd, options, args):
   147     self.sourcePath = sourcePath
   148     self.buildPath = os.path.normpath(buildPath)
   149     self.originalCwd = originalCwd
   150     self.options = options
   151     self.args = args
   152     self.compiler = None
   153     self.contextStack_ = [None]
   154     self.configure_failed = False
   155 
   156     # This is a hack... if we ever do cross-compiling or something, we'll have
   157     # to change this.
   158     self.host_platform = util.Platform()
   159     self.target_platform = util.Platform()
   160 
   161   def parseBuildScripts(self):
   162     root = os.path.join(self.sourcePath, 'AMBuildScript')
   163     self.evalScript(root)
   164 
   165   def pushContext(self, cx):
   166     self.contextStack_.append(cx)
   167 
   168   def popContext(self):
   169     self.contextStack_.pop()
   170 
   171   def evalScript(self, file, vars={}):
   172     cx = Context(self, self.contextStack_[-1], file)
   173     self.pushContext(cx)
   174 
   175     full_path = os.path.join(self.sourcePath, cx.buildScript)
   176 
   177     self.addConfigureFile(cx, full_path)
   178 
   179     new_vars = copy.copy(vars)
   180     new_vars['builder'] = cx
   181 
   182     # Run it.
   183     rvalue = None
   184     with open(full_path) as fp:
   185       chars = fp.read()
   186 
   187       # Python 2.6 can't compile() with Windows line endings?!?!!?
   188       chars = chars.replace('\r\n', '\n')
   189       chars = chars.replace('\r', '\n')
   190 
   191       code = compile(chars, full_path, 'exec')
   192 
   193     exec(code, new_vars)
   194     if 'rvalue' in new_vars:
   195       rvalue = new_vars['rvalue']
   196       del new_vars['rvalue']
   197 
   198     self.popContext()
   199     return rvalue
   200 
   201   def generateBuildFiles(self):
   202     build_py = os.path.join(self.buildPath, 'build.py')
   203     with open(build_py, 'w') as fp:
   204       fp.write("""
   205 #!{exe}
   206 # vim set: ts=8 sts=2 sw=2 tw=99 et:
   207 import sys
   208 from ambuild2 import run
   209 
   210 if not run.CompatBuild(r"{build}"):
   211   sys.exit(1)
   212 """.format(exe=sys.executable, build=self.buildPath))
   213 
   214     with open(os.path.join(self.buildPath, 'Makefile'), 'w') as fp:
   215       fp.write("""
   216 all:
   217 	"{exe}" "{py}"
   218 """.format(exe=sys.executable, py=build_py))
   219 
   220   def generate(self):
   221     self.preGenerate()
   222     self.parseBuildScripts()
   223     self.postGenerate()
   224     if self.options.make_scripts:
   225       self.generateBuildFiles()
   226     return True
   227 
   228   def DetectCompilers(self):
   229     if self.compiler:
   230       return self.compiler
   231 
   232     with util.FolderChanger('.ambuild2'):
   233       cc = cpp.DetectCompiler(self, os.environ, 'CC')
   234       cxx = cpp.DetectCompiler(self, os.environ, 'CXX')
   235     self.compiler = cpp.Compiler(cc, cxx)
   236     return self.compiler
   237 
   238   def getLocalFolder(self, context):
   239     return context.localFolder_
   240 
   241   def addSymlink(self, context, source, output_path):
   242     if util.host_platform == 'windows':
   243       # Windows pre-Vista does not support symlinks. Windows Vista+ supports
   244       # symlinks via mklink, but it's Administrator-only by default.
   245       return self.addCopy(context, source, output_path)
   246     raise Exception('Must be implemented!')
   247 
   248   def addFolder(self, context, folder):
   249     raise Exception('Must be implemented!')
   250 
   251   def addCopy(self, context, source, output_path):
   252     raise Exception('Must be implemented!')
   253 
   254   def addShellCommand(self, context, inputs, argv, outputs, folder=-1, dep_type=None,
   255                       weak_inputs=[], shared_outputs=[]):
   256     raise Exception('Must be implemented!')
   257 
   258   def addConfigureFile(self, context, path):
   259     raise Exception('Must be implemented!')