ambuild2/frontend/base_gen.py
author David Anderson <dvander@alliedmods.net>
Mon Nov 18 10:13:25 2013 -0800 (2013-11-18)
changeset 195 74dedf99c458
parent 194 2cd13298c896
child 211 7df3be7fbab3
permissions -rw-r--r--
Workaround Python 2.6 bug.
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@65
    51
      self.buildFolder = os.path.join(parent.buildFolder, path)
dvander@65
    52
    else:
dvander@109
    53
      self.currentSourcePath = generator.sourcePath
dvander@65
    54
      self.buildFolder = ''
dvander@161
    55
    self.localFolder_ = self.buildFolder
dvander@65
    56
dvander@109
    57
  # Root source folder.
dvander@109
    58
  @property
dvander@109
    59
  def sourcePath(self):
dvander@109
    60
    return self.generator.sourcePath
dvander@109
    61
dvander@109
    62
  @property
dvander@109
    63
  def options(self):
dvander@109
    64
    return self.generator.options
dvander@109
    65
dvander@65
    66
  @property
dvander@65
    67
  def buildPath(self):
dvander@65
    68
    return self.generator.buildPath
dvander@65
    69
dvander@161
    70
  # In build systems with dependency graphs, this can return a node
dvander@161
    71
  # representing buildFolder. Otherwise, it returns buildFolder.
dvander@161
    72
  @property
dvander@161
    73
  def localFolder(self):
dvander@161
    74
    return self.generator.getLocalFolder(self)
dvander@161
    75
dvander@144
    76
  @property
dvander@144
    77
  def target_platform(self):
dvander@144
    78
    return self.generator.target_platform
dvander@144
    79
danderson@175
    80
  @property
danderson@175
    81
  def host_platform(self):
danderson@175
    82
    return self.generator.host_platform
danderson@175
    83
dvander@117
    84
  def SetBuildFolder(self, folder):
dvander@130
    85
    if folder == '/' or folder == '.' or folder == './':
dvander@130
    86
      self.buildFolder = ''
dvander@130
    87
    else:
dvander@130
    88
      self.buildFolder = os.path.normpath(folder)
dvander@161
    89
    self.localFolder_ = self.buildFolder
dvander@117
    90
dvander@65
    91
  def DetectCompilers(self):
dvander@65
    92
    if not self.compiler:
dvander@65
    93
      self.compiler = self.generator.DetectCompilers()
dvander@109
    94
    return self.compiler
dvander@65
    95
dvander@134
    96
  def RunScript(self, file, vars={}):
dvander@134
    97
    return self.generator.evalScript(file, vars)
dvander@134
    98
dvander@129
    99
  def RunBuildScripts(self, files, vars={}):
dvander@163
   100
    if util.IsString(files):
dvander@134
   101
      self.generator.evalScript(files, vars)
dvander@129
   102
    else:
dvander@129
   103
      for script in files:
dvander@134
   104
        self.generator.evalScript(script, vars)
dvander@65
   105
dvander@65
   106
  def Add(self, taskbuilder):
dvander@65
   107
    taskbuilder.finish(self)
dvander@118
   108
    return taskbuilder.generate(self.generator, self)
dvander@65
   109
dvander@115
   110
  def AddSource(self, source_path):
dvander@157
   111
    return self.generator.addSource(self, source_path)
dvander@115
   112
dvander@115
   113
  def AddSymlink(self, source, output_path):
dvander@157
   114
    return self.generator.addSymlink(self, source, output_path)
dvander@115
   115
dvander@117
   116
  def AddFolder(self, folder):
dvander@157
   117
    return self.generator.addFolder(self, folder)
dvander@117
   118
dvander@119
   119
  def AddCopy(self, source, output_path):
dvander@157
   120
    return self.generator.addCopy(self, source, output_path)
dvander@119
   121
dvander@130
   122
  def AddCommand(self, inputs, argv, outputs):
dvander@157
   123
    return self.generator.addShellCommand(self, inputs, argv, outputs)
dvander@117
   124
dvander@194
   125
  def AddConfigureFile(self, path):
dvander@194
   126
    return self.generator.addConfigureFile(self, path)
dvander@194
   127
dvander@65
   128
class Generator(object):
dvander@65
   129
  def __init__(self, sourcePath, buildPath, options, args):
dvander@65
   130
    self.sourcePath = sourcePath
dvander@128
   131
    self.buildPath = os.path.normpath(buildPath)
dvander@65
   132
    self.options = options
dvander@65
   133
    self.args = args
dvander@65
   134
    self.compiler = None
dvander@65
   135
    self.contextStack_ = [None]
dvander@109
   136
    self.configure_failed = False
dvander@65
   137
dvander@144
   138
    # This is a hack... if we ever do cross-compiling or something, we'll have
dvander@144
   139
    # to change this.
dvander@153
   140
    self.host_platform = util.Platform()
dvander@144
   141
    self.target_platform = util.Platform()
dvander@144
   142
dvander@65
   143
  def parseBuildScripts(self):
dvander@65
   144
    root = os.path.join(self.sourcePath, 'AMBuildScript')
dvander@134
   145
    self.evalScript(root)
dvander@65
   146
dvander@65
   147
  def pushContext(self, cx):
dvander@65
   148
    self.contextStack_.append(cx)
dvander@65
   149
dvander@65
   150
  def popContext(self):
dvander@65
   151
    self.contextStack_.pop()
dvander@65
   152
dvander@134
   153
  def evalScript(self, file, vars={}):
dvander@65
   154
    cx = Context(self, self.contextStack_[-1], file)
dvander@65
   155
    self.pushContext(cx)
dvander@65
   156
dvander@157
   157
    self.addConfigureFile(cx, os.path.join(self.sourcePath, file))
dvander@157
   158
dvander@113
   159
    new_vars = copy.copy(vars)
dvander@113
   160
    new_vars['builder'] = cx
dvander@113
   161
dvander@65
   162
    # Run it.
dvander@134
   163
    rvalue = None
dvander@134
   164
    with open(os.path.join(self.sourcePath, file)) as fp:
dvander@134
   165
      chars = fp.read()
dvander@195
   166
dvander@195
   167
      # Python 2.6 can't compile() with Windows line endings?!?!!?
dvander@195
   168
      chars = chars.replace('\r\n', '\n')
dvander@195
   169
      chars = chars.replace('\r', '\n')
dvander@195
   170
dvander@134
   171
      code = compile(chars, file, 'exec')
dvander@195
   172
dvander@113
   173
    exec(code, new_vars)
dvander@134
   174
    if 'rvalue' in new_vars:
dvander@134
   175
      rvalue = new_vars['rvalue']
dvander@134
   176
      del new_vars['rvalue']
dvander@65
   177
dvander@65
   178
    self.popContext()
dvander@134
   179
    return rvalue
dvander@65
   180
dvander@148
   181
  def generateBuildFiles(self):
dvander@148
   182
    build_py = os.path.join(self.buildPath, 'build.py')
dvander@148
   183
    with open(build_py, 'w') as fp:
dvander@148
   184
      fp.write("""
dvander@148
   185
#!{exe}
dvander@148
   186
# vim set: ts=8 sts=2 sw=2 tw=99 et:
dvander@148
   187
import sys
dvander@148
   188
from ambuild2 import run
dvander@148
   189
dvander@165
   190
if not run.CompatBuild(r"{build}"):
dvander@148
   191
  sys.exit(1)
dvander@148
   192
""".format(exe=sys.executable, build=self.buildPath))
dvander@148
   193
dvander@148
   194
    with open(os.path.join(self.buildPath, 'Makefile'), 'w') as fp:
dvander@148
   195
      fp.write("""
dvander@148
   196
all:
dvander@148
   197
	"{exe}" "{py}"
dvander@148
   198
""".format(exe=sys.executable, py=build_py))
dvander@148
   199
dvander@157
   200
  def generate(self):
dvander@158
   201
    self.preGenerate()
dvander@158
   202
    self.parseBuildScripts()
dvander@158
   203
    self.postGenerate()
dvander@158
   204
    if self.options.make_scripts:
dvander@158
   205
      self.generateBuildFiles()
dvander@159
   206
    return True
dvander@67
   207
dvander@65
   208
  def DetectCompilers(self):
dvander@65
   209
    if self.compiler:
dvander@65
   210
      return self.compiler
dvander@65
   211
dvander@110
   212
    with util.FolderChanger('.ambuild2'):
dvander@110
   213
      cc = cpp.DetectCompiler(self, os.environ, 'CC')
dvander@110
   214
      cxx = cpp.DetectCompiler(self, os.environ, 'CXX')
dvander@65
   215
    self.compiler = cpp.Compiler(cc, cxx)
dvander@65
   216
    return self.compiler
dvander@115
   217
dvander@161
   218
  def getLocalFolder(self, context):
dvander@161
   219
    return context.localFolder_
dvander@161
   220
dvander@157
   221
  def addSymlink(self, context, source, output_path):
dvander@153
   222
    if util.host_platform == 'windows':
dvander@135
   223
      # Windows pre-Vista does not support symlinks. Windows Vista+ supports
dvander@135
   224
      # symlinks via mklink, but it's Administrator-only by default.
dvander@157
   225
      return self.addCopy(context, source, output_path)
dvander@153
   226
    raise Exception('Must be implemented!')
dvander@117
   227
dvander@157
   228
  def addFolder(self, context, folder):
dvander@157
   229
    raise Exception('Must be implemented!')
dvander@157
   230
dvander@157
   231
  def addCopy(self, context, source, output_path):
dvander@153
   232
    raise Exception('Must be implemented!')
dvander@117
   233
dvander@157
   234
  def addShellCommand(self, context, inputs, argv, outputs):
dvander@153
   235
    raise Exception('Must be implemented!')
dvander@119
   236
dvander@157
   237
  def addConfigureFile(self, context, path):
dvander@153
   238
    raise Exception('Must be implemented!')