LSSTApplications  1.1.2+25,10.0+13,10.0+132,10.0+133,10.0+224,10.0+41,10.0+8,10.0-1-g0f53050+14,10.0-1-g4b7b172+19,10.0-1-g61a5bae+98,10.0-1-g7408a83+3,10.0-1-gc1e0f5a+19,10.0-1-gdb4482e+14,10.0-11-g3947115+2,10.0-12-g8719d8b+2,10.0-15-ga3f480f+1,10.0-2-g4f67435,10.0-2-gcb4bc6c+26,10.0-28-gf7f57a9+1,10.0-3-g1bbe32c+14,10.0-3-g5b46d21,10.0-4-g027f45f+5,10.0-4-g86f66b5+2,10.0-4-gc4fccf3+24,10.0-40-g4349866+2,10.0-5-g766159b,10.0-5-gca2295e+25,10.0-6-g462a451+1
LSSTDataManagementBasePackage
installation.py
Go to the documentation of this file.
1 ##
2 # @file installation.py
3 #
4 # Builders and path setup for installation targets.
5 ##
6 import os.path
7 import glob
8 import re
9 import sys
10 import shutil
11 
12 import SCons.Script
13 from SCons.Script.SConscript import SConsEnvironment
14 
15 from .vcs import svn
16 from .vcs import hg
17 from .vcs import git
18 
19 from . import state
20 from .utils import memberOf
21 
22 #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
23 
24 ##
25 # @brief return a path to use as the installation directory for a product
26 # @param pathFormat the format string to process
27 # @param env the scons environment
28 # @param versionString the versionString passed to MakeEnv
29 ##
30 def makeProductPath(env, pathFormat):
31  pathFormat = re.sub(r"%(\w)", r"%(\1)s", pathFormat)
32 
33  eupsPath = os.environ['PWD']
34  if env.has_key('eupsProduct') and env['eupsPath']:
35  eupsPath = env['eupsPath']
36 
37  return pathFormat % { "P": eupsPath,
38  "f": env['eupsFlavor'],
39  "p": env['eupsProduct'],
40  "v": env['version'],
41  "c": os.environ['PWD'] }
42 
43 #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
44 
45 ## @brief Set a version ID from env, or a version control ID string ($name$ or $HeadURL$)
46 def determineVersion(env, versionString):
47  version = "unknown"
48  if env.has_key('version'):
49  version = env['version']
50  elif not versionString:
51  version = "unknown"
52  elif re.search(r"^[$]Name:\s+", versionString):
53  # CVS. Extract the tagname
54  version = re.search(r"^[$]Name:\s+([^ $]*)", versionString).group(1)
55  if version == "":
56  version = "cvs"
57  elif re.search(r"^[$]HeadURL:\s+", versionString):
58  # SVN. Guess the tagname from the last part of the directory
59  HeadURL = re.search(r"^[$]HeadURL:\s+(.*)", versionString).group(1)
60  HeadURL = os.path.split(HeadURL)[0]
61  version = svn.guessVersionName(HeadURL)
62  elif versionString.lower() in ("hg", "mercurial"):
63  # Mercurial (hg).
64  version = hg.guessVersionName()
65  elif versionString.lower() in ("git",):
66  # git.
67  version = git.guessVersionName()
68  return version.replace("/", "_")
69 
70 #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
71 
72 ## @brief Return a unique fingerprint for a version (e.g. an SHA1); return None if unavailable
73 def getFingerprint(versionString):
74  if versionString.lower() in ("hg", "mercurial"):
75  fingerprint, modified = hg.guessFingerprint()
76  elif versionString.lower() in ("git",):
77  fingerprint, modified = git.guessFingerprint()
78  else:
79  fingerprint, modified = None, False
80 
81  if fingerprint and modified:
82  fingerprint += " *"
83 
84  return fingerprint
85 
86 ## @brief Set a prefix based on the EUPS_PATH, the product name, and a versionString from cvs or svn.
87 def setPrefix(env, versionString, eupsProductPath=None):
88  try:
89  env['version'] = determineVersion(env, versionString)
90  except RuntimeError as err:
91  env['version'] = "unknown"
92  if env.installing or env.declaring and not env['force']:
93  state.log.fail(
94  "%s\nFound problem with version number; update or specify force=True to proceed"
95  % err
96  )
97  if eupsProductPath:
98  eupsPrefix = makeProductPath(env, eupsProductPath)
99  elif env.has_key('eupsPath') and env['eupsPath']:
100  eupsPrefix = env['eupsPath']
101  flavor = env['eupsFlavor']
102  if not re.search("/" + flavor + "$", eupsPrefix):
103  eupsPrefix = os.path.join(eupsPrefix, flavor)
104  prodPath = env['eupsProduct']
105  if env.has_key('eupsProductPath') and env['eupsProductPath']:
106  prodPath = env['eupsProductPath']
107  eupsPrefix = os.path.join(eupsPrefix, prodPath, env["version"])
108  else:
109  eupsPrefix = None
110  if env.has_key('prefix'):
111  if env['version'] != "unknown" and eupsPrefix and eupsPrefix != env['prefix']:
112  print >> sys.stderr, "Ignoring prefix %s from EUPS_PATH" % eupsPrefix
113  return makeProductPath(env, env['prefix'])
114  elif env.has_key('eupsPath') and env['eupsPath']:
115  prefix = eupsPrefix
116  else:
117  prefix = "/usr/local"
118  return prefix
119 
120 #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
121 
122 ##
123 # Create current and declare targets for products. products
124 # may be a list of (product, version) tuples. If product is None
125 # it's taken to be self['eupsProduct']; if version is None it's
126 # taken to be self['version'].
127 ##
128 @memberOf(SConsEnvironment)
129 def Declare(self, products=None):
130 
131  if "undeclare" in SCons.Script.COMMAND_LINE_TARGETS and not self.GetOption("silent"):
132  state.log.warn("'scons undeclare' is deprecated; please use 'scons declare -c' instead")
133 
134  acts = []
135  if \
136  "declare" in SCons.Script.COMMAND_LINE_TARGETS or \
137  "undeclare" in SCons.Script.COMMAND_LINE_TARGETS or \
138  ("install" in SCons.Script.COMMAND_LINE_TARGETS and self.GetOption("clean")) or \
139  "current" in SCons.Script.COMMAND_LINE_TARGETS:
140  current = []; declare = []; undeclare = []
141 
142  if not products:
143  products = [None]
144 
145  for prod in products:
146  if not prod or isinstance(prod, str): # i.e. no version
147  product = prod
148 
149  if self.has_key('version'):
150  version = self['version']
151  else:
152  version = None
153  else:
154  product, version = prod
155 
156  if not product:
157  product = self['eupsProduct']
158 
159  if "EUPS_DIR" in os.environ.keys():
160  self['ENV']['PATH'] += os.pathsep + "%s/bin" % (os.environ["EUPS_DIR"])
161  self["ENV"]["EUPS_LOCK_PID"] = os.environ.get("EUPS_LOCK_PID", "-1")
162  if "undeclare" in SCons.Script.COMMAND_LINE_TARGETS or self.GetOption("clean"):
163  if version:
164  command = "eups undeclare --flavor %s %s %s" % \
165  (self['eupsFlavor'], product, version)
166  if ("current" in SCons.Script.COMMAND_LINE_TARGETS
167  and not "declare" in SCons.Script.COMMAND_LINE_TARGETS):
168  command += " --current"
169 
170  if self.GetOption("clean"):
171  self.Execute(command)
172  else:
173  undeclare += [command]
174  else:
175  state.log.warn("I don't know your version; not undeclaring to eups")
176  else:
177  command = "eups declare --force --flavor %s --root %s" % \
178  (self['eupsFlavor'], self['prefix'])
179 
180  if self.has_key('eupsPath'):
181  command += " -Z %s" % self['eupsPath']
182 
183  if version:
184  command += " %s %s" % (product, version)
185 
186  current += [command + " --current"]
187 
188  if self.GetOption("tag"):
189  command += " --tag=%s" % self.GetOption("tag")
190 
191  declare += [command]
192 
193  if current:
194  acts += self.Command("current", "", action=current)
195  if declare:
196  if "current" in SCons.Script.COMMAND_LINE_TARGETS:
197  acts += self.Command("declare", "", action="") # current will declare it for us
198  else:
199  acts += self.Command("declare", "", action=declare)
200  if undeclare:
201  acts += self.Command("undeclare", "", action=undeclare)
202 
203  return acts
204 
205 #=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
206 
207 ##
208 # @brief SCons Action callable to recursively install a directory.
209 #
210 # This is separate from the InstallDir function to allow the directory-walking
211 # to happen when installation is actually invoked, rather than when the SConscripts
212 # are parsed. This still does not ensure that all necessary files are built as
213 # prerequisites to installing, but if one explicitly marks the install targets
214 # as dependent on the build targets, that should be enough.
215 ##
216 class DirectoryInstaller(object):
217 
218  def __init__(self, ignoreRegex, recursive):
219  self.ignoreRegex = re.compile(ignoreRegex)
220  self.recursive = recursive
221 
222  def __call__(self, target, source, env):
223  prefix = os.path.abspath(os.path.join(target[0].abspath, ".."))
224  destpath = os.path.join(target[0].abspath)
225  if not os.path.isdir(destpath):
226  state.log.info("Creating directory %s" % destpath)
227  os.makedirs(destpath)
228  for root, dirnames, filenames in os.walk(source[0].path):
229  if not self.recursive:
230  dirnames[:] = []
231  else:
232  dirnames[:] = [d for d in dirnames if d != ".svn"] # ignore .svn tree
233  for dirname in dirnames:
234  destpath = os.path.join(prefix, root, dirname)
235  if not os.path.isdir(destpath):
236  state.log.info("Creating directory %s" % destpath)
237  os.makedirs(destpath)
238  for filename in filenames:
239  if self.ignoreRegex.search(filename):
240  continue
241  destpath = os.path.join(prefix, root)
242  srcpath = os.path.join(root, filename)
243  state.log.info("Copying %s to %s" % (srcpath, destpath))
244  shutil.copy(srcpath, destpath)
245  return 0
246 
247 
248 ##
249 # Install the directory dir into prefix, (along with all its descendents if recursive is True).
250 # Omit files and directories that match ignoreRegex
251 ##
252 @memberOf(SConsEnvironment)
253 def InstallDir(self, prefix, dir, ignoreRegex=r"(~$|\.pyc$|\.os?$)", recursive=True):
254  if not self.installing:
255  return []
256  result = self.Command(target=os.path.join(self.Dir(prefix).abspath, dir), source=dir,
257  action=DirectoryInstaller(ignoreRegex, recursive))
258  self.AlwaysBuild(result)
259  return result
260 
261 #=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
262 
263 ##
264 # Install a ups directory, setting absolute versions as appropriate
265 # (unless you're installing from the trunk, in which case no versions
266 # are expanded). Any build/table files present in "./ups" are automatically
267 # added to files.
268 #
269 # If presetup is provided, it's expected to be a dictionary with keys
270 # product names and values the version that should be installed into
271 # the table files, overriding eups expandtable's usual behaviour. E.g.
272 # env.InstallEups(os.path.join(env['prefix'], "ups"), presetup={"sconsUtils" : env['version']})
273 ##
274 @memberOf(SConsEnvironment)
275 def InstallEups(env, dest, files=[], presetup=""):
276 
277  acts = []
278  if not env.installing:
279  return acts
280 
281  if env.GetOption("clean"):
282  print >> sys.stderr, "Removing", dest
283  shutil.rmtree(dest, ignore_errors=True)
284  else:
285  presetupStr = []
286  for p in presetup:
287  presetupStr += ["--product %s=%s" % (p, presetup[p])]
288  presetup = " ".join(presetupStr)
289 
290  env = env.Clone(ENV = os.environ)
291  #
292  # Add any build/table/cfg files to the desired files
293  #
294  files = [str(f) for f in files] # in case the user used Glob not glob.glob
295  files += glob.glob(os.path.join("ups", "*.build")) + glob.glob(os.path.join("ups","*.table")) \
296  + glob.glob(os.path.join("ups", "*.cfg")) \
297  + glob.glob(os.path.join("ups", "eupspkg*"))
298  files = list(set(files)) # remove duplicates
299 
300  buildFiles = filter(lambda f: re.search(r"\.build$", f), files)
301  build_obj = env.Install(dest, buildFiles)
302  acts += build_obj
303 
304  tableFiles = filter(lambda f: re.search(r"\.table$", f), files)
305  table_obj = env.Install(dest, tableFiles)
306  acts += table_obj
307 
308  eupspkgFiles = filter(lambda f: re.search(r"^eupspkg", f), files)
309  eupspkg_obj = env.Install(dest, eupspkgFiles)
310  acts += eupspkg_obj
311 
312  miscFiles = filter(lambda f: not re.search(r"\.(build|table)$", f), files)
313  misc_obj = env.Install(dest, miscFiles)
314  acts += misc_obj
315 
316  try:
317  import eups.lock
318 
319  path = eups.Eups.setEupsPath()
320  if path:
321  locks = eups.lock.takeLocks("setup", path, eups.lock.LOCK_SH)
322  env["ENV"]["EUPS_LOCK_PID"] = os.environ.get("EUPS_LOCK_PID", "-1")
323  except ImportError:
324  state.log.warn("Unable to import eups; not locking")
325 
326  eupsTargets = []
327 
328  for i in build_obj:
329  env.AlwaysBuild(i)
330 
331  cmd = "eups expandbuild -i --version %s " % env['version']
332  if env.has_key('baseversion'):
333  cmd += " --repoversion %s " % env['baseversion']
334  cmd += str(i)
335  eupsTargets.extend(env.AddPostAction(build_obj, env.Action("%s" %(cmd), cmd)))
336 
337  for i in table_obj:
338  env.AlwaysBuild(i)
339 
340  cmd = "eups expandtable -i -W '^(?!LOCAL:)' " # version doesn't start "LOCAL:"
341  if presetup:
342  cmd += presetup + " "
343  cmd += str(i)
344 
345  act = env.Command("table", "", env.Action("%s" %(cmd), cmd))
346  eupsTargets.extend(act)
347  acts += act
348  env.Depends(act, i)
349 
350  # By declaring that all the Eups operations create a file called "eups" as a side-effect,
351  # even though they don't, SCons knows it can't run them in parallel (it thinks of the
352  # side-effect file as something like a log, and knows you shouldn't be appending to it
353  # in parallel). When Eups locking is working, we may be able to remove this.
354  env.SideEffect("eups", eupsTargets)
355 
356  return acts
357 
358 #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
359 
360 ## @brief Install directories in the usual LSST way, handling "ups" specially.
361 @memberOf(SConsEnvironment)
362 def InstallLSST(self, prefix, dirs, ignoreRegex=None):
363  results = []
364  for d in dirs:
365  if d == "ups":
366  t = self.InstallEups(os.path.join(prefix, "ups"))
367  else:
368  t = self.InstallDir(prefix, d, ignoreRegex=ignoreRegex)
369  self.Depends(t, d)
370  results.extend(t)
371  self.Alias("install", t)
372  self.Clean("install", prefix)
373  return results
def Declare
Create current and declare targets for products.
def getFingerprint
Return a unique fingerprint for a version (e.g.
Definition: installation.py:73
def memberOf
A Python decorator that injects functions into a class.
Definition: utils.py:84
def makeProductPath
return a path to use as the installation directory for a product
Definition: installation.py:30
def InstallLSST
Install directories in the usual LSST way, handling "ups" specially.
def determineVersion
Set a version ID from env, or a version control ID string ($name$ or $HeadURL$)
Definition: installation.py:46
def InstallEups
Install a ups directory, setting absolute versions as appropriate (unless you're installing from the ...
SCons Action callable to recursively install a directory.
def setPrefix
Set a prefix based on the EUPS_PATH, the product name, and a versionString from cvs or svn...
Definition: installation.py:87
def InstallDir
Install the directory dir into prefix, (along with all its descendents if recursive is True)...