ambuild2/frontend/base_gen.py
author David Anderson <danderson@mozilla.com>
Sat Dec 28 10:21:44 2013 -0500 (2013-12-28)
changeset 219 84822e8a3205
parent 218 032616e296da
child 220 0aada1a7b9ec
permissions -rw-r--r--
Allow custom shell commands to specify weak dependencies (bug 5997 part 7, 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@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
danderson@219
   125
  def AddCommand(self, inputs, argv, outputs, dep_type=None, weak_inputs=[]):
danderson@219
   126
    return self.generator.addShellCommand(
danderson@219
   127
      self,
danderson@219
   128
      inputs,
danderson@219
   129
      argv,
danderson@219
   130
      outputs,
danderson@219
   131
      dep_type = dep_type,
danderson@219
   132
      weak_inputs = weak_inputs
danderson@219
   133
    )
dvander@117
   134
dvander@194
   135
  def AddConfigureFile(self, path):
dvander@194
   136
    return self.generator.addConfigureFile(self, path)
dvander@194
   137
dvander@65
   138
class Generator(object):
dvander@65
   139
  def __init__(self, sourcePath, buildPath, options, args):
dvander@65
   140
    self.sourcePath = sourcePath
dvander@128
   141
    self.buildPath = os.path.normpath(buildPath)
dvander@65
   142
    self.options = options
dvander@65
   143
    self.args = args
dvander@65
   144
    self.compiler = None
dvander@65
   145
    self.contextStack_ = [None]
dvander@109
   146
    self.configure_failed = False
dvander@65
   147
dvander@144
   148
    # This is a hack... if we ever do cross-compiling or something, we'll have
dvander@144
   149
    # to change this.
dvander@153
   150
    self.host_platform = util.Platform()
dvander@144
   151
    self.target_platform = util.Platform()
dvander@144
   152
dvander@65
   153
  def parseBuildScripts(self):
dvander@65
   154
    root = os.path.join(self.sourcePath, 'AMBuildScript')
dvander@134
   155
    self.evalScript(root)
dvander@65
   156
dvander@65
   157
  def pushContext(self, cx):
dvander@65
   158
    self.contextStack_.append(cx)
dvander@65
   159
dvander@65
   160
  def popContext(self):
dvander@65
   161
    self.contextStack_.pop()
dvander@65
   162
dvander@134
   163
  def evalScript(self, file, vars={}):
dvander@65
   164
    cx = Context(self, self.contextStack_[-1], file)
dvander@65
   165
    self.pushContext(cx)
dvander@65
   166
dvander@211
   167
    full_path = os.path.join(self.sourcePath, cx.buildScript)
dvander@211
   168
dvander@211
   169
    self.addConfigureFile(cx, full_path)
dvander@157
   170
dvander@113
   171
    new_vars = copy.copy(vars)
dvander@113
   172
    new_vars['builder'] = cx
dvander@113
   173
dvander@65
   174
    # Run it.
dvander@134
   175
    rvalue = None
dvander@211
   176
    with open(full_path) as fp:
dvander@134
   177
      chars = fp.read()
dvander@195
   178
dvander@195
   179
      # Python 2.6 can't compile() with Windows line endings?!?!!?
dvander@195
   180
      chars = chars.replace('\r\n', '\n')
dvander@195
   181
      chars = chars.replace('\r', '\n')
dvander@195
   182
dvander@211
   183
      code = compile(chars, full_path, 'exec')
dvander@195
   184
dvander@113
   185
    exec(code, new_vars)
dvander@134
   186
    if 'rvalue' in new_vars:
dvander@134
   187
      rvalue = new_vars['rvalue']
dvander@134
   188
      del new_vars['rvalue']
dvander@65
   189
dvander@65
   190
    self.popContext()
dvander@134
   191
    return rvalue
dvander@65
   192
dvander@148
   193
  def generateBuildFiles(self):
dvander@148
   194
    build_py = os.path.join(self.buildPath, 'build.py')
dvander@148
   195
    with open(build_py, 'w') as fp:
dvander@148
   196
      fp.write("""
dvander@148
   197
#!{exe}
dvander@148
   198
# vim set: ts=8 sts=2 sw=2 tw=99 et:
dvander@148
   199
import sys
dvander@148
   200
from ambuild2 import run
dvander@148
   201
dvander@165
   202
if not run.CompatBuild(r"{build}"):
dvander@148
   203
  sys.exit(1)
dvander@148
   204
""".format(exe=sys.executable, build=self.buildPath))
dvander@148
   205
dvander@148
   206
    with open(os.path.join(self.buildPath, 'Makefile'), 'w') as fp:
dvander@148
   207
      fp.write("""
dvander@148
   208
all:
dvander@148
   209
	"{exe}" "{py}"
dvander@148
   210
""".format(exe=sys.executable, py=build_py))
dvander@148
   211
dvander@157
   212
  def generate(self):
dvander@158
   213
    self.preGenerate()
dvander@158
   214
    self.parseBuildScripts()
dvander@158
   215
    self.postGenerate()
dvander@158
   216
    if self.options.make_scripts:
dvander@158
   217
      self.generateBuildFiles()
dvander@159
   218
    return True
dvander@67
   219
dvander@65
   220
  def DetectCompilers(self):
dvander@65
   221
    if self.compiler:
dvander@65
   222
      return self.compiler
dvander@65
   223
dvander@110
   224
    with util.FolderChanger('.ambuild2'):
dvander@110
   225
      cc = cpp.DetectCompiler(self, os.environ, 'CC')
dvander@110
   226
      cxx = cpp.DetectCompiler(self, os.environ, 'CXX')
dvander@65
   227
    self.compiler = cpp.Compiler(cc, cxx)
dvander@65
   228
    return self.compiler
dvander@115
   229
dvander@161
   230
  def getLocalFolder(self, context):
dvander@161
   231
    return context.localFolder_
dvander@161
   232
dvander@157
   233
  def addSymlink(self, context, source, output_path):
dvander@153
   234
    if util.host_platform == 'windows':
dvander@135
   235
      # Windows pre-Vista does not support symlinks. Windows Vista+ supports
dvander@135
   236
      # symlinks via mklink, but it's Administrator-only by default.
dvander@157
   237
      return self.addCopy(context, source, output_path)
dvander@153
   238
    raise Exception('Must be implemented!')
dvander@117
   239
dvander@157
   240
  def addFolder(self, context, folder):
dvander@157
   241
    raise Exception('Must be implemented!')
dvander@157
   242
dvander@157
   243
  def addCopy(self, context, source, output_path):
dvander@153
   244
    raise Exception('Must be implemented!')
dvander@117
   245
danderson@219
   246
  def addShellCommand(self, context, inputs, argv, outputs, dep_type=None, weak_inputs=[]):
dvander@153
   247
    raise Exception('Must be implemented!')
dvander@119
   248
dvander@157
   249
  def addConfigureFile(self, context, path):
dvander@153
   250
    raise Exception('Must be implemented!')