__init__.py
Go to the documentation of this file.
1 __author__ = "Marco Clemencic <marco.clemencic@cern.ch>"
2 
3 import os
4 import sys
5 
6 assert sys.version_info >= (2, 6), "Python 2.6 required"
7 
8 import logging
9 
10 from string import Template
11 
12 __all__ = []
13 
14 # Prepare the search path for environment XML files
15 path = ['.']
16 if 'ENVXMLPATH' in os.environ:
17  path.extend(os.environ['ENVXMLPATH'].split(os.pathsep))
18 
19 import Control
20 
21 class EnvError(RuntimeError):
22  '''
23  Simple class to wrap errors in the environment configuration.
24  '''
25  pass
26 
27 def splitNameValue(name_value):
28  """split the "NAME=VALUE" string into the tuple ("NAME", "VALUE")
29  replacing '[:]' with os.pathsep in VALUE"""
30  if '=' not in name_value:
31  raise EnvError("Invalid variable argument '%s'." % name_value)
32  n, v = name_value.split('=', 1)
33  return n, v.replace('[:]', os.pathsep)
34 
35 class Script(object):
36  '''
37  Environment Script class used to control the logic of the script and allow
38  extensions.
39  '''
40  __usage__ = "Usage: %prog [OPTION]... [NAME=VALUE]... [COMMAND [ARG]...]"
41  __desc__ = "Set each NAME to VALUE in the environment and run COMMAND."
42  __epilog__ = ("The operations are performed in the order they appear on the "
43  "command line. If no COMMAND is provided, print the resulting "
44  "environment. (Note: this command is modeled after the Unix "
45  "command 'env', see \"man env\")")
46  def __init__(self, args=None):
47  '''
48  Initializes the script instance parsing the command line arguments (or
49  the explicit arguments provided).
50  '''
51  self.parser = None
52  self.opts = None
53  self.cmd = None
54  self.control = None
55  self.log = None
56  self.env = {}
57  # Run the core code of the script
58  self._prepare_parser()
59  self._parse_args(args)
60  self._check_args()
61 
62  def _prepare_parser(self):
63  '''
64  Prepare an OptionParser instance used to analyze the command line
65  options and arguments.
66  '''
67  from optparse import OptionParser, OptionValueError
68  parser = OptionParser(prog=os.path.basename(sys.argv[0]),
69  usage=self.__usage__,
70  description=self.__desc__,
71  epilog=self.__epilog__)
72  self.log = logging.getLogger(parser.prog)
73 
74  def addOperation(option, opt, value, parser, action):
75  '''
76  Append to the list of actions the tuple (action, (<args>, ...)).
77  '''
78  if action not in ('unset', 'loadXML'):
79  try:
80  value = splitNameValue(value)
81  except EnvError:
82  raise OptionValueError("Invalid value for option %s: '%s', it requires NAME=VALUE." % (opt, value))
83  else:
84  value = (value,)
85  parser.values.actions.append((action, value))
86 
87  parser.add_option("-i", "--ignore-environment",
88  action="store_true",
89  help="start with an empty environment")
90  parser.add_option("-u", "--unset",
91  metavar="NAME",
92  action="callback", callback=addOperation,
93  type="str", nargs=1, callback_args=('unset',),
94  help="remove variable from the environment")
95  parser.add_option("-s", "--set",
96  metavar="NAME=VALUE",
97  action="callback", callback=addOperation,
98  type="str", nargs=1, callback_args=('set',),
99  help="set the variable NAME to VALUE")
100  parser.add_option("-a", "--append",
101  metavar="NAME=VALUE",
102  action="callback", callback=addOperation,
103  type="str", nargs=1, callback_args=('append',),
104  help="append VALUE to the variable NAME (with a '%s' as separator)" % os.pathsep)
105  parser.add_option("-p", "--prepend",
106  metavar="NAME=VALUE",
107  action="callback", callback=addOperation,
108  type="str", nargs=1, callback_args=('prepend',),
109  help="prepend VALUE to the variable NAME (with a '%s' as separator)" % os.pathsep)
110  parser.add_option("-x", "--xml",
111  action="callback", callback=addOperation,
112  type="str", nargs=1, callback_args=('loadXML',),
113  help="XML file describing the changes to the environment")
114  parser.add_option("--sh",
115  action="store_const", const="sh", dest="shell",
116  help="Print the environment as shell commands for 'sh'-derived shells.")
117  parser.add_option("--csh",
118  action="store_const", const="csh", dest="shell",
119  help="Print the environment as shell commands for 'csh'-derived shells.")
120  parser.add_option("--py",
121  action="store_const", const="py", dest="shell",
122  help="Print the environment as Python dictionary.")
123 
124  parser.add_option('--verbose', action='store_const',
125  const=logging.INFO, dest='log_level',
126  help='print more information')
127  parser.add_option('--debug', action='store_const',
128  const=logging.DEBUG, dest='log_level',
129  help='print debug messages')
130  parser.add_option('--quiet', action='store_const',
131  const=logging.WARNING, dest='log_level',
132  help='print only warning messages (default)')
133 
134  parser.disable_interspersed_args()
135  parser.set_defaults(actions=[],
136  ignore_environment=False,
137  log_level=logging.WARNING)
138 
139  self.parser = parser
140 
141  def _parse_args(self, args=None):
142  '''
143  Parse the command line arguments.
144  '''
145  opts, args = self.parser.parse_args(args)
146 
147  # set the logging level
148  logging.basicConfig(level=opts.log_level)
149 
150  cmd = []
151  # find the (implicit) 'set' arguments in the list of arguments
152  # and put the rest in the command
153  try:
154  for i, a in enumerate(args):
155  opts.actions.append(('set', splitNameValue(a)))
156  except EnvError:
157  cmd = args[i:]
158 
159  self.opts, self.cmd = opts, cmd
160 
161  def _check_args(self):
162  '''
163  Check consistency of command line options and arguments.
164  '''
165  if self.opts.shell and self.cmd:
166  self.parser.error("Invalid arguments: --%s cannot be used with a command." % self.opts.shell)
167 
168  def _makeEnv(self):
169  '''
170  Generate a dictionary of the environment variables after applying all
171  the required actions.
172  '''
173  # prepare the environment control instance
174  control = Control.Environment()
175  if not self.opts.ignore_environment:
176  control.presetFromSystem()
177 
178  # apply all the actions
179  for action, args in self.opts.actions:
180  apply(getattr(control, action), args)
181 
182  # extract the result env dictionary
183  env = control.vars()
184 
185  # set the library search path correctly for the non-Linux platforms
186  if "LD_LIBRARY_PATH" in env:
187  # replace LD_LIBRARY_PATH with the corresponding one on other systems
188  if sys.platform.startswith("win"):
189  other = "PATH"
190  elif sys.platform.startswith("darwin"):
191  other = "DYLD_LIBRARY_PATH"
192  else:
193  other = None
194  if other:
195  if other in env:
196  env[other] = env[other] + os.pathsep + env["LD_LIBRARY_PATH"]
197  else:
198  env[other] = env["LD_LIBRARY_PATH"]
199  del env["LD_LIBRARY_PATH"]
200 
201  self.env = env
202 
203  def dump(self):
204  '''
205  Print to standard output the final environment in the required format.
206  '''
207  if self.opts.shell == 'py':
208  from pprint import pprint
209  pprint(self.env)
210  else:
211  template = {'sh': "export %s='%s'",
212  'csh': "setenv %s '%s';"}.get(self.opts.shell, "%s=%s")
213  for nv in sorted(self.env.items()):
214  print template % nv
215 
216  def expandEnvVars(self, iterable):
217  '''
218  Return a copy of iterable where all the elements have the environment
219  variables expanded.
220 
221  >>> s = Script([])
222  >>> s.env = {'A': '1', 'B': 'test'}
223  >>> s.expandEnvVars(['$A', '${B}-$A', '$DUMMY-$A', '$$B'])
224  ['1', 'test-1', '$DUMMY-1', '$B']
225  '''
226  return [Template(elem).safe_substitute(self.env) for elem in iterable]
227 
228  def runCmd(self):
229  '''
230  Execute a command in the modified environment and return the exit code.
231  '''
232  from subprocess import Popen
233  cmd = self.expandEnvVars(self.cmd)
234  rc = Popen(cmd, env=self.env).wait()
235  # There is a mismatch between Popen return code and sys.exit argument in
236  # case of signal.
237  # E.g. Popen returns -6 that is translated to 250 instead of 134
238  if rc < 0:
239  rc = 128 - rc
240  return rc
241 
242  def main(self):
243  '''
244  Main function of the script.
245  '''
246  self._makeEnv()
247  if not self.cmd:
248  self.dump()
249  else:
250  sys.exit(self.runCmd())
def runCmd(self)
Definition: __init__.py:228
tuple __epilog__
Definition: __init__.py:42
def dump(self)
Definition: __init__.py:203
def _makeEnv(self)
Definition: __init__.py:168
def main(self)
Definition: __init__.py:242
string __usage__
Definition: __init__.py:40
def expandEnvVars(self, iterable)
Definition: __init__.py:216
def _check_args(self)
Definition: __init__.py:161
string __desc__
Definition: __init__.py:41
def splitNameValue(name_value)
Definition: __init__.py:27
def _prepare_parser(self)
Definition: __init__.py:62