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