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