1 # vim: set ts=8 sts=2 sw=2 tw=99 et:
3 # This file is part of AMBuild.
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.
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.
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/>.
19 from ambuild2 import util
20 from ambuild2.frontend import cpp
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).
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
32 class ConfigureException(Exception):
33 def __init__(self, *args, **kwargs):
34 super(ConfigureException, self).__init__(*args, **kwargs)
36 class Context(object):
37 def __init__(self, generator, parent, script):
38 self.generator = generator
44 self.compiler = parent.compiler
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)
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)
54 self.currentSourcePath = generator.sourcePath
55 self.currentSourceFolder = ''
57 self.buildScript = os.path.join(self.currentSourceFolder, name)
58 self.localFolder_ = self.buildFolder
63 return self.generator.sourcePath
67 return self.generator.options
71 return self.generator.buildPath
73 # In build systems with dependency graphs, this can return a node
74 # representing buildFolder. Otherwise, it returns buildFolder.
76 def localFolder(self):
77 return self.generator.getLocalFolder(self)
80 def target_platform(self):
81 return self.generator.target_platform
84 def host_platform(self):
85 return self.generator.host_platform
88 def originalCwd(self):
89 return self.generator.originalCwd
91 def SetBuildFolder(self, folder):
92 if folder == '/' or folder == '.' or folder == './':
95 self.buildFolder = os.path.normpath(folder)
96 self.localFolder_ = self.buildFolder
98 def DetectCompilers(self):
100 self.compiler = self.generator.DetectCompilers()
103 def RunScript(self, file, vars={}):
104 return self.generator.evalScript(file, vars)
106 def RunBuildScripts(self, files, vars={}):
107 if util.IsString(files):
108 self.generator.evalScript(files, vars)
111 self.generator.evalScript(script, vars)
113 def Add(self, taskbuilder):
114 taskbuilder.finish(self)
115 return taskbuilder.generate(self.generator, self)
117 def AddSource(self, source_path):
118 return self.generator.addSource(self, source_path)
120 def AddSymlink(self, source, output_path):
121 return self.generator.addSymlink(self, source, output_path)
123 def AddFolder(self, folder):
124 return self.generator.addFolder(self, folder)
126 def AddCopy(self, source, output_path):
127 return self.generator.addCopy(self, source, output_path)
129 def AddCommand(self, inputs, argv, outputs, folder=-1, dep_type=None, weak_inputs=[],
131 return self.generator.addShellCommand(
138 weak_inputs = weak_inputs,
139 shared_outputs = shared_outputs
142 def AddConfigureFile(self, path):
143 return self.generator.addConfigureFile(self, path)
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
153 self.contextStack_ = [None]
154 self.configure_failed = False
156 # This is a hack... if we ever do cross-compiling or something, we'll have
158 self.host_platform = util.Platform()
159 self.target_platform = util.Platform()
161 def parseBuildScripts(self):
162 root = os.path.join(self.sourcePath, 'AMBuildScript')
163 self.evalScript(root)
165 def pushContext(self, cx):
166 self.contextStack_.append(cx)
168 def popContext(self):
169 self.contextStack_.pop()
171 def evalScript(self, file, vars={}):
172 cx = Context(self, self.contextStack_[-1], file)
175 full_path = os.path.join(self.sourcePath, cx.buildScript)
177 self.addConfigureFile(cx, full_path)
179 new_vars = copy.copy(vars)
180 new_vars['builder'] = cx
184 with open(full_path) as fp:
187 # Python 2.6 can't compile() with Windows line endings?!?!!?
188 chars = chars.replace('\r\n', '\n')
189 chars = chars.replace('\r', '\n')
191 code = compile(chars, full_path, 'exec')
194 if 'rvalue' in new_vars:
195 rvalue = new_vars['rvalue']
196 del new_vars['rvalue']
201 def generateBuildFiles(self):
202 build_py = os.path.join(self.buildPath, 'build.py')
203 with open(build_py, 'w') as fp:
206 # vim set: ts=8 sts=2 sw=2 tw=99 et:
208 from ambuild2 import run
210 if not run.CompatBuild(r"{build}"):
212 """.format(exe=sys.executable, build=self.buildPath))
214 with open(os.path.join(self.buildPath, 'Makefile'), 'w') as fp:
218 """.format(exe=sys.executable, py=build_py))
222 self.parseBuildScripts()
224 if self.options.make_scripts:
225 self.generateBuildFiles()
228 def DetectCompilers(self):
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)
238 def getLocalFolder(self, context):
239 return context.localFolder_
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!')
248 def addFolder(self, context, folder):
249 raise Exception('Must be implemented!')
251 def addCopy(self, context, source, output_path):
252 raise Exception('Must be implemented!')
254 def addShellCommand(self, context, inputs, argv, outputs, folder=-1, dep_type=None,
255 weak_inputs=[], shared_outputs=[]):
256 raise Exception('Must be implemented!')
258 def addConfigureFile(self, context, path):
259 raise Exception('Must be implemented!')