Lumiera  0.pre.03
»edit your freedom«
LumieraEnvironment.py
1 # -*- python -*-
2 
5 
6 # Copyright (C) Lumiera.org
7 # 2008, Hermann Vosseler <Ichthyostega@web.de>
8 #
9 # This program is free software; you can redistribute it and/or
10 # modify it under the terms of the GNU General Public License as
11 # published by the Free Software Foundation; either version 2 of
12 # the License, or (at your option) any later version.
13 #
14 # This program is distributed in the hope that it will be useful,
15 # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 # GNU General Public License for more details.
18 #
19 # You should have received a copy of the GNU General Public License
20 # along with this program; if not, write to the Free Software
21 # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
22 
23 
24 
25 from os import path
26 
27 import SCons.SConf
28 from SCons.Action import Action
29 from SCons.Environment import Environment
30 
31 from Buildhelper import *
32 
33 
34 
35 class LumieraEnvironment(Environment):
36  """ Custom SCons build environment for Lumiera
37  This allows us to carry structured config data without
38  using global vars. Idea inspired by Ardour.
39  """
40  def __init__(self, buildSetup, buildVars, **kw):
41  kw.update(VERSION = buildSetup.VERSION
42  ,TARGDIR = buildSetup.TARGDIR
43  ,DESTDIR = '$INSTALLDIR/$PREFIX'
44  ,toolpath = [buildSetup.TOOLDIR ]
45  ,variables = buildVars
46  )
47  Environment.__init__ (self, **kw)
48  self.path = Record (extract_localPathDefs(buildSetup)) # e.g. buildExe -> env.path.buildExe
49  self.libInfo = {}
50  self.Tool("BuilderGCH")
51  self.Tool("BuilderDoxygen")
52  self.Tool("ToolDistCC")
53  self.Tool("ToolCCache")
54  register_LumieraResourceBuilder(self)
55  register_LumieraCustomBuilders(self)
56 
57 
58  def Configure (self, *args, **kw):
59  kw['env'] = self
60  return apply(LumieraConfigContext, args, kw)
61 
62  def mergeConf (self,other):
63  """ extract the library/compiler flags from other Environment.
64  Optionally accepts a list or just sting(s) representing keys
65  in our own libInfo Dictionary
66  """
67  if isinstance(other, list):
68  for elm in other:
69  self.mergeConf(elm)
70  elif isinstance(other, str):
71  if other in self.libInfo:
72  self.mergeConf(self.libInfo[other])
73  else:
74  self.Append (LIBS = other.get ('LIBS',[]))
75  self.Append (LIBPATH = other.get ('LIBPATH', []))
76  self.Append (CPPPATH = other.get('CPPPATH', []))
77  self.Append (LINKFLAGS = other.get('LINKFLAGS', []))
78 
79  return self
80 
81 
82  def addLibInfo (self, libID, minVersion=0, alias=None):
83  """ use pkg-config to create an Environment describing the lib.
84  Don't add this defs to the current Environment, rather store
85  them in the libInfo Dictionary.
86  """
87  minVersion = str(minVersion)
88  if 0 != os.system('pkg-config --print-errors --exists "%s >= %s"' % (libID,minVersion)):
89  print "Problems configuring the Library %s (>= %s)" % (libID,minVersion)
90  return False
91 
92  self.libInfo[libID] = libInfo = Environment()
93  libInfo["ENV"]["PKG_CONFIG_PATH"] = os.environ.get("PKG_CONFIG_PATH")
94  libInfo.ParseConfig ('pkg-config --cflags --libs '+ libID )
95  if alias:
96  self.libInfo[alias] = libInfo
97  return libInfo
98 
99 
100 
101 
102 
103 # extending the 'Configure' functionality of SCons,
104 # especially for library dependency checking
105 ConfigBase = SCons.SConf.SConfBase
106 
107 
108 
109 class LumieraConfigContext(ConfigBase):
110  """ Extends the SCons Configure context with some convenience methods
111  """
112  def __init__(self, *args,**kw):
113  ConfigBase.__init__(self,*args,**kw)
114 
115  def CheckPkgConfig (self, libID, minVersion=0, alias=None):
116  print "Checking for library configuration: %s " % libID
117  # self.Message(self,"Checking for library configuration: %s " % libID)
118  return self.env.addLibInfo (libID, minVersion, alias)
119 
120 
121 
122 
124 
125 
126 def register_LumieraResourceBuilder(env):
127  """ Registers Custom Builders for generating and installing Icons.
128  Additionally you need to build the tool (rsvg-convert.c)
129  used to generate png from the svg source using librsvg.
130  """
131 
132  import IconSvgRenderer as renderer # load Joel's python script for invoking the rsvg-convert (SVG render)
133  renderer.rsvgPath = env.subst("$TARGDIR/rsvg-convert")
134 
135  def invokeRenderer(target, source, env):
136  source = str(source[0])
137  targetdir = env.subst(env.path.buildIcon)
138  if targetdir.startswith('#'): targetdir = targetdir[1:]
139  renderer.main([source,targetdir])
140  return 0
141 
142  def createIconTargets(target,source,env):
143  """ parse the SVG to get the target file names """
144  source = str(source[0])
145  targetdir = env.path.buildIcon
146  targetfiles = renderer.getTargetNames(source) # parse SVG
147 
148  # additionally create an installation task for each Icon to be generated
149  installLocation = env.path.installIcon
150  generateTargets = []
151  for icon in targetfiles:
152  icon = targetdir+icon
153  subdir = getDirname(str(icon))
154  env.Install (installLocation+subdir, icon)
155  generateTargets.append(icon)
156 
157  return (generateTargets, source)
158 
159  def IconResource(env, source):
160  """ copy icon pixmap to corresponding icon dir. """
161  subdir = getDirname(str(source))
162  toBuild = env.path.buildIcon+subdir
163  toInstall = env.path.installIcon+subdir
164  env.Install (toInstall, source)
165  return env.Install(toBuild, source)
166 
167  def GuiResource(env, source):
168  """ pick up giben source resource and install
169  them (flat) into the configured target
170  """
171  toBuild = env.path.buildUIRes
172  toInstall = env.path.installUIRes
173  env.Install (toInstall, source)
174  return env.Install(toBuild, source)
175 
176  def ConfigData(env, prefix, source, targetDir=None):
177  """ install (copy) configuration- and metadata.
178  target dir is either the install location configured (in SConstruct),
179  or an explicitly given absolute or relative path segment, which might refer
180  to the location of the executable through the $ORIGIN token
181  """
182  source = path.join(prefix,str(source))
183  subdir = getDirname(source, prefix) # removes source location path prefix
184  if targetDir:
185  if path.isabs(targetDir):
186  toBuild = toInstall = path.join(targetDir,subdir)
187  else:
188  if targetDir.startswith('$ORIGIN'):
189  targetDir = targetDir[len('$ORIGIN'):]
190  toBuild = path.join(env.path.buildExe, targetDir, subdir)
191  toInstall = path.join(env.path.installExe, targetDir, subdir)
192  else:
193  toBuild = path.join(env.path.buildConf, targetDir, subdir)
194  toInstall = path.join(env.path.installConf, targetDir, subdir)
195  else:
196  toBuild = path.join(env.path.buildConf,subdir)
197  toInstall = path.join(env.path.installConf,subdir)
198  env.Install (toInstall, source)
199  return env.Install(toBuild, source)
200 
201 
202  buildIcon = env.Builder( action = Action(invokeRenderer, "rendering Icon: $SOURCE --> $TARGETS")
203  , single_source = True
204  , emitter = createIconTargets
205  )
206  env.Append(BUILDERS = {'IconRender' : buildIcon})
207  env.AddMethod(IconResource)
208  env.AddMethod(GuiResource)
209  env.AddMethod(ConfigData)
210 
211 
212 
213 
214 class WrappedStandardExeBuilder(SCons.Util.Proxy):
215  """ Helper to add customisations and default configurations to SCons standard builders.
216  The original builder object is wrapped and most calls are simply forwarded to this
217  wrapped object by Python magic. But some calls are intercepted in order to inject
218  suitable default configuration based on the project setup.
219  """
220 
221  def __init__(self, originalBuilder):
222  SCons.Util.Proxy.__init__ (self, originalBuilder)
223 
224  def __nonzero__(self): return True
225 
226  def __call__(self, env, target=None, source=None, **kw):
227  """ when the builder gets invoked from the SConscript...
228  create a clone environment for specific configuration
229  and then pass on the call to the wrapped original builder.
230  Automatically define installation targets for build results.
231  @note only returning the build targets, not the install targets
232  """
233  customisedEnv = self.getCustomEnvironment(env, target=target, **kw) # defined in subclasses
234  buildTarget = self.buildLocation(customisedEnv, target)
235  buildTarget = self.invokeOriginalBuilder(customisedEnv, buildTarget, source, **kw)
236  self.installTarget(customisedEnv, buildTarget, **kw)
237  return buildTarget
238 
239 
240  def invokeOriginalBuilder(self, env, target, source, **kw):
241  return self.get().__call__ (env, target, source, **kw)
242 
243  def buildLocation(self, env, target):
244  """ prefix project output directory """
245  prefix = self.getBuildDestination(env)
246  return list(prefix+str(name) for name in target)
247 
248  def installTarget(self, env, buildTarget, **kw):
249  """ create an additional installation target
250  for the generated executable artifact
251  """
252  indeedInstall = lambda p: p and p.get('install')
253 
254  if indeedInstall(kw):
255  return env.Install (dir = self.getInstallDestination(env), source=buildTarget)
256  else:
257  return []
258 
259 
260 
261 
263 
264  def getCustomEnvironment(self, lumiEnv, **kw):
265  """ augments the built-in Program() builder to add a fixed rpath based on $ORIGIN
266  That is: after searching LD_LIBRARY_PATH, but before the standard linker search,
267  the directory relative to the position of the executable ($ORIGIN) is searched.
268  This search path is active not only for the executable, but for all libraries
269  it is linked with.
270  @note: enabling the new ELF dynamic tags. This causes a DT_RUNPATH to be set,
271  which results in LD_LIBRARY_PATH being searched *before* the RPATH
272  """
273  custEnv = lumiEnv.Clone()
274  custEnv.Append( LINKFLAGS = "-Wl,-rpath=\\$$ORIGIN/modules,--enable-new-dtags" )
275  custEnv.Append( LINKFLAGS = "-Wl,-rpath-link=target/modules" )
276  if 'addLibs' in kw:
277  custEnv.Append(LIBS = kw['addLibs'])
278  return custEnv
279 
280  def getBuildDestination(self, lumiEnv): return lumiEnv.path.buildExe
281  def getInstallDestination(self, lumiEnv): return lumiEnv.path.installExe
282 
283 
284 
285 
287 
288  def getCustomEnvironment(self, lumiEnv, target, **kw):
289  """ augments the built-in SharedLibrary() builder to add some tweaks missing in SCons 1.0,
290  like setting a SONAME proper instead of just passing the relative pathname to the linker.
291  Besides, we override the library search path to allow for transitive dependencies between
292  Lumiera modules; modules are assumed to reside in a subdirectory below the executable.
293  """
294  custEnv = lumiEnv.Clone()
295  custEnv.Append(LINKFLAGS = "-Wl,-soname="+self.defineSoname(target,**kw))
296  custEnv.Append( LINKFLAGS = "-Wl,-rpath=\\$$ORIGIN/../modules,--enable-new-dtags" )
297  if 'addLibs' in kw:
298  custEnv.Append(LIBS = kw['addLibs'])
299  return custEnv
300 
301  def getBuildDestination(self, lumiEnv): return lumiEnv.path.buildLib
302  def getInstallDestination(self, lumiEnv): return lumiEnv.path.installLib
303 
304 
305  def defineSoname (self, target, **kw):
306  """ internal helper to extract or guess
307  a suitable library SONAME, either using an
308  explicit spec, falling back on the lib filename
309  """
310  if 'soname' in kw:
311  soname = self.subst(kw['soname']) # explicitly defined by user
312  else: # else: use the library filename as DT_SONAME
313  if SCons.Util.is_String(target):
314  pathname = target.strip()
315  elif 1 == len(target):
316  pathname = str(target[0]).strip()
317  else:
318  raise SyntaxError("Lumiera Library builder requires exactly one target spec. Found target="+str(target))
319 
320  assert pathname
321  (dirprefix, libname) = path.split(pathname)
322  if not libname:
323  raise ValueError("Library name missing. Only got a directory: "+pathname)
324 
325  soname = "${SHLIBPREFIX}%s$SHLIBSUFFIX" % libname
326 
327  assert soname
328  return soname
329 
330 
331 
333 
334  def getCustomEnvironment(self, lumiEnv, target, **kw):
335  """ in addition to the ModuleBuilder, define the Lumiera plugin suffix
336  """
337  custEnv = LumieraModuleBuilder.getCustomEnvironment(self, lumiEnv, target, **kw)
338  custEnv.Append (CPPDEFINES='LUMIERA_PLUGIN')
339  custEnv.Replace(SHLIBPREFIX='', SHLIBSUFFIX='.lum')
340  return custEnv
341 
342  def getBuildDestination(self, lumiEnv): return lumiEnv.path.buildPlug
343  def getInstallDestination(self, lumiEnv): return lumiEnv.path.installPlug
344 
345 
346 
347 
348 
349 
350 def register_LumieraCustomBuilders (lumiEnv):
351  """ install the customised builder versions tightly integrated with our build system.
352  Especially, these builders automatically add the build and installation locations
353  and set the RPATH and SONAME in a way to allow a relocatable Lumiera directory structure
354  """
355  programBuilder = LumieraExeBuilder (lumiEnv['BUILDERS']['Program'])
356  libraryBuilder = LumieraModuleBuilder (lumiEnv['BUILDERS']['SharedLibrary'])
357  smoduleBuilder = LumieraModuleBuilder (lumiEnv['BUILDERS']['LoadableModule'])
358  lpluginBuilder = LumieraPluginBuilder (lumiEnv['BUILDERS']['LoadableModule'])
359 
360  lumiEnv['BUILDERS']['Program'] = programBuilder
361  lumiEnv['BUILDERS']['SharedLibrary'] = libraryBuilder
362  lumiEnv['BUILDERS']['LoadableModule'] = smoduleBuilder
363  lumiEnv['BUILDERS']['LumieraPlugin'] = lpluginBuilder
364 
365 
366  def SymLink(env, target, source, linktext=None):
367  """ use python to create a symlink
368  """
369  def makeLink(target,source,env):
370  if linktext:
371  dest = linktext
372  else:
373  dest = str(source[0])
374  link = str(target[0])
375  os.symlink(dest, link)
376 
377  if linktext: srcSpec=linktext
378  else: srcSpec='$SOURCE'
379  action = Action(makeLink, "Install link: $TARGET -> "+srcSpec)
380  env.Command (target,source, action)
381 
382  # adding SymLink directly as method on the environment object
383  # Probably that should better be a real builder, but I couldn't figure out
384  # how to get the linktext through literally, which is necessary for relative links.
385  # Judging from the sourcecode of SCons.Builder.BuilderBase, there seems to be no way
386  # to set the executor_kw, which are passed through to the action object.
387  lumiEnv.AddMethod(SymLink)
def getCustomEnvironment(self, lumiEnv, target, kw)
def getCustomEnvironment(self, lumiEnv, target, kw)
def invokeOriginalBuilder(self, env, target, source, kw)
def getCustomEnvironment(self, lumiEnv, kw)
def installTarget(self, env, buildTarget, kw)
def addLibInfo(self, libID, minVersion=0, alias=None)