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