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