2
"""Contains routines required to initialize mrv"""
6
from mrv.path import make_path, BasePath
10
log = logging.getLogger("mrv.cmd.base")
12
__docformat__ = "restructuredtext"
14
__all__ = [ 'is_supported_maya_version', 'python_version_of', 'parse_maya_version', 'update_env_path',
15
'maya_location', 'update_maya_environment', 'exec_python_interpreter', 'uses_mayapy',
16
'exec_maya_binary', 'available_maya_versions', 'python_executable', 'find_mrv_script',
17
'log_exception', 'SpawnedHelpFormatter', 'SpawnedOptionParser', 'SpawnedCommand']
20
maya_to_py_version_map = {
34
def is_supported_maya_version(version):
35
""":return: True if version is a supported maya version
36
:param version: float which is either 8.5 or 2008 to 20XX"""
40
return str(version)[:2] == "20"
43
""":return: True if the executable is mayapy"""
47
except EnvironmentError:
49
# END handle exceptions
51
def mayapy_maya_version():
52
""":return: float representing the maya version of the currently running
54
:raise EnvironmentError: If called from a 'normal' python interpreter"""
55
if 'maya' not in sys.executable.lower():
56
raise EnvironmentError("Not running mayapy")
57
# END quick first check
59
exec_path = make_path(os.path.realpath(sys.executable)) # Maya is capitalized on windows
61
version_token = [ t[4:] for t in exec_path.splitall() if t.lower().startswith('maya') ][0]
63
raise EnvironmentError("Not running mayapy or invalid path mayapy path: %s" % exec_path)
66
if version_token.endswith('-x64'):
67
version_token = version_token[:-4]
68
# END handle 64 bit paths
70
return float(version_token)
72
def parse_maya_version(arg, default):
73
""":return: tuple(bool, version) tuple of bool indicating whether the version could
74
be parsed and was valid, and a float representing the parsed or default version.
75
:param default: The desired default maya version"""
79
candidate = float(arg)
80
if is_supported_maya_version(candidate):
81
parsed_arg, version = True, candidate
84
# in that case, we don't claim the arg and just use the default
85
# END handle candidate
88
# END exception handling
90
return parsed_arg, version
92
def python_version_of(maya_version):
93
""":return: python version matching the given maya version
94
:raise EnvironmentError: If there is no known matching python version"""
96
return maya_to_py_version_map[maya_version]
98
raise EnvironmentError("Do not know python version matching the given maya version %g" % maya_version)
100
def update_env_path(environment, env_var, value, append=False):
101
"""Set the given env_var to the given value, but append the existing value
102
to it using the system path separator
104
:param append: if True, value will be appended to existing values, otherwise it will
106
curval = environment.get(env_var, None)
107
# rule out empty strings
110
value = curval + os.pathsep + value
112
value = value + os.pathsep + curval
114
# END handle existing value
115
environment[env_var] = value
117
def available_maya_versions():
118
""":return: list of installed maya versions which are locally available -
119
they can be used in methods that require the maya_version to be given.
120
Versions are ordered such that the latest version is given last."""
122
for version_candidate in sorted(maya_to_py_version_map.keys()):
124
loc = maya_location(version_candidate)
125
versions.append(version_candidate)
128
# END check maya location
129
# END for each version
132
def maya_location(maya_version):
133
""":return: string path to the existing maya installation directory for the
135
:raise EnvironmentError: if it was not found"""
139
if sys.platform.startswith('linux'):
140
mayaroot = "/usr/autodesk/maya"
141
if os.path.isdir('/lib64'):
143
# END handle 64 bit systems
144
elif sys.platform == 'darwin':
145
mayaroot = "/Applications/Autodesk/maya"
146
elif sys.platform.startswith('win'):
147
# try to find it in all kinds of program files, prefer 64 bit versions
149
for envvar in ('PROGRAMW6432', 'PROGRAMFILES','PROGRAMFILES(X86)'):
150
if envvar not in os.environ:
152
basepath = make_path(os.environ[envvar]) / "Autodesk"
154
mayaroot = basepath / 'Maya'
156
# END if we have found Autodesk installations
157
tried_paths.append(basepath)
158
# END for each envvar
160
raise EnvironmentError("Could not find any maya installation, searched %s" % (', '.join(tried_paths)))
161
# END os specific adjustments
164
raise EnvironmentError("Current platform %r is unsupported" % sys.platform)
165
# END assure existance of maya root
167
mayalocation = "%s%g%s" % (mayaroot, maya_version, suffix)
169
# OSX special handling
170
if sys.platform == 'darwin':
171
mayalocation=os.path.join(mayalocation, 'Maya.app', 'Contents')
173
if not os.path.isdir(mayalocation):
174
raise EnvironmentError("Could not find maya installation at %r" % mayalocation)
175
# END verfy maya location
179
def update_maya_environment(maya_version):
180
"""Configure os.environ to allow Maya to run in standalone mode
181
:param maya_version: The maya version to prepare to run, either 8.5 or 2008 to
182
20XX. This requires the respective maya version to be installed in a default location.
183
:raise EnvironmentError: If the platform is unsupported or if the maya installation could not be found"""
184
py_version = python_version_of(maya_version)
187
envppath = "PYTHONPATH"
189
if sys.platform.startswith('linux'):
191
elif sys.platform == 'darwin':
192
pylibdir = "Frameworks/Python.framework/Versions/Current/lib"
193
elif sys.platform.startswith('win'):
195
# END os specific adjustments
200
mayalocation = maya_location(maya_version)
202
if not os.path.isdir(mayalocation):
203
raise EnvironmentError("Could not find maya installation at %r" % mayalocation)
204
# END verfy maya location
209
# ADJUST LD_LIBRARY_PATH or PATH
210
################################
211
# Note: if you need something like LD_PRELOAD or equivalent, add the respective
212
# variables to the environment of this process before starting it
213
if sys.platform.startswith('linux'):
214
envld = "LD_LIBRARY_PATH"
215
ldpath = os.path.join(mayalocation, 'lib')
216
update_env_path(env, envld, ldpath)
217
elif sys.platform == 'darwin':
218
# adjust maya location to point to the actual directtoy
219
dldpath = os.path.join(mayalocation, 'MacOS')
220
update_env_path(env, "DYLD_LIBRARY_PATH", dldpath)
222
dldframeworkpath = os.path.join(mayalocation, 'Frameworks')
223
update_env_path(env, "DYLD_FRAMEWORK_PATH", dldframeworkpath)
225
env['MAYA_NO_BUNDLE_RESOURCES'] = "1"
227
# on osx, python will only use the main frameworks path and ignore
228
# its own sitelibraries. We put them onto the PYTHONPATH for that reason
229
# MayaRV will take care of the initialization
230
ppath = "/Library/Python/%s/site-packages" % py_version
231
update_env_path(env, envppath, ppath, append=True)
233
elif sys.platform.startswith('win'):
234
mayadll = os.path.join(mayalocation, 'bin')
235
mayapydll = os.path.join(mayalocation, 'Python', 'DLLs')
236
update_env_path(env, 'PATH', mayadll+os.pathsep+mayapydll, append=False)
238
raise EnvironmentError("Current platform %s is unsupported" % sys.platform)
244
# root project is already in the path, we add additional paths as well
245
ospd = os.path.dirname
246
if not sys.platform.startswith('win'):
247
ppath = os.path.join(mayalocation, pylibdir, "python%s"%py_version, "site-packages")
249
ppath = os.path.join(mayalocation, pylibdir, "lib", "site-packages")
250
# END windows special handling
252
# don't prepend, otherwise system-interpreter mrv versions will not be able
253
# to override the possibly existing mrv version installed in maya.
254
update_env_path(env, envppath, ppath, append=True)
258
# its important to do it here as osx adjusts it
259
env['MAYA_LOCATION'] = mayalocation
261
# export the actual maya version to allow scripts to pick it up even before maya is launched
262
env['MRV_MAYA_VERSION'] = "%g" % maya_version
264
def mangle_args(args):
265
"""Enclose arguments in quotes if they contain spaces ... on windows only
266
:return: tuple of possibly modified arguments
268
:todo: remove this function, its unused"""
269
if not sys.platform.startswith('win'):
276
# END put quotes around strings with spaces
279
return tuple(newargs)
281
def mangle_executable(executable):
282
""":return: possibly adjusted path to executable in order to allow its execution
283
This currently only kicks in on windows as we can't handle spaces properly.
285
:note: Will change working dir
286
:todo: remove this function, its unused"""
287
if not sys.platform.startswith('win'):
290
# execv appears to call the shell, hence we make sure we handle whitespaecs
291
# in the path, which usually happens on windows !
292
# Problem here is that it cannot find the executable if it has a space in the
293
# path as it will split it, and if quotes are put around, it can't find
294
# it either. Hence we chdir into it and use a relative path
295
if ' ' in executable:
296
os.chdir(os.path.dirname(executable))
297
executable = os.path.basename(executable)
298
# END handle freakin' spaces
301
def init_environment(args):
302
"""Intialize MRV up to the point where we can replace this process with the
305
:param args: commandline arguments excluding the executable ( usually first arg )
306
:return: tuple(use_this_interpreter, maya_version, args) tuple of Bool, maya_version, and the remaining args
307
The boolean indicates whether we have to reuse this interpreter, as it is mayapy"""
308
# see if first argument is the maya version
311
parsed_successfully, maya_version = parse_maya_version(args[0], default=None)
312
if parsed_successfully:
313
# if the user wants a specific maya version, he should get it no matter what
315
# END cut version arg
316
# END if there are args at all
318
# choose the newest available maya version if none was specified
319
if maya_version is None:
320
# If there is no version given, and we are in mayapy, we use the maypy
321
# version. Otherwise we use the newest available version
323
maya_version = mayapy_maya_version()
324
# in that case, we have a valid maya environment already. This also means
325
# that we must use this interpreter !
326
return (True, maya_version, tuple(args))
328
versions = available_maya_versions()
330
maya_version = versions[-1]
331
log.info("Using newest available maya version: %g" % maya_version)
333
# END handle maya version
334
# END set maya version
336
# The user has specified a maya version to start. As mayapy sets up everything in
337
# a distinctive way, we are kind of too late to alter that.
338
# Hence we must prevent the user from starting different maya versions than
339
# the one defined by mayapy.
340
# NOTE: If we use mayapy, the things mentioned above are an issue. If we
341
# use the delivered python-bin (linux) or Python (osx) executables, this
342
# is not an issue. This way, its consistent among all platforms though as
343
# windows always uses mayapy.
345
mayapy_version = mayapy_maya_version()
346
if mayapy_version != maya_version:
347
raise EnvironmentError("If using mayapy, you cannot run any other maya version than the one mayapy uses: %g" % mayapy_version)
350
if maya_version is None:
351
raise EnvironmentError("Maya version not specified on the commandline, couldn't find any maya version on this system")
352
# END abort if not installed
354
update_maya_environment(maya_version)
355
return (False, maya_version, tuple(args))
357
def _execute(executable, args):
358
"""Perform the actual execution of the executable with the given args.
359
This method does whatever is required to get it right on windows, which is
360
the only reason this method exists !
362
:param args: arguments, without the executable as first argument
363
:note: does not return """
364
# on windows we spawn, otherwise we don't get the interactive input right
365
actual_args = (executable, ) + args
366
if sys.platform.startswith('win'):
367
p = subprocess.Popen(actual_args, stdin=sys.stdin, stdout=sys.stdout, stderr=sys.stderr)
370
os.execvp(executable, actual_args)
373
def python_executable(py_version=None):
374
""":return: name or path to python executable in this system, deals with
375
linux and windows specials"""
376
if py_version is None:
378
# END handle simple case
380
py_executable = "python%g" % py_version
381
if sys.platform.startswith('win'):
382
# so, on windows the executables don't have a . in their name, most likely
383
# because windows threats the '.' in a special way as ... anyway.
384
py_executable = "python%g" % (py_version*10)
388
def find_mrv_script(name):
389
"""Find an mrv script of the given name. This method should be used if you
390
want to figure out where the mrv executable with the given name is located.
391
The returned path is either relative or absolute.
393
:return: Path to script
394
:raise EnvironmentError: if the executable could not be found
395
:note: Currently it only looks for executables, but handles projects
396
which use mrv as a subproject"""
398
mrvroot = os.path.dirname(mrv.__file__)
401
for base in ('', 'ext', mrvroot):
402
for subdir in ('bin', 'doc', os.path.join('test', 'bin')):
405
path = os.path.join(base, subdir, name)
407
path = os.path.join(subdir, name)
409
if os.path.isfile(path):
410
return make_path(path)
411
tried_paths.append(path)
412
# END for each subdir
415
raise EnvironmentError("Script named %s not found, looked at %s" % (name, ', '.join(tried_paths)))
417
def exec_python_interpreter(args, maya_version, mayapy_only=False):
418
"""Replace this process with a python process as determined by the given options.
419
This will either be the respective python interpreter, or mayapy.
420
If it works, the function does not return
422
:param args: remaining arguments which should be passed to the process
423
:param maya_version: float indicating the maya version to use
424
:param mayapy_only: If True, only mayapy will be considered for startup.
425
Use this option in case the python interpreter crashes for some reason.
426
:raise EnvironmentError: If no suitable executable could be started"""
427
py_version = python_version_of(maya_version)
428
py_executable = python_executable(py_version)
435
tried_paths.append(py_executable)
436
_execute(py_executable, args)
439
print "Python interpreter named %r not found, trying mayapy ..." % py_executable
440
# END print error message
441
mayalocation = maya_location(maya_version)
442
mayapy_executable = os.path.join(mayalocation, "bin", "mayapy")
445
tried_paths.append(mayapy_executable)
446
_execute(mayapy_executable, args)
448
raise EnvironmentError("Could not find suitable python interpreter at paths %s : %s" % (', '.join(tried_paths), e))
449
# END final exception handling
450
# END exception handling
452
def exec_maya_binary(args, maya_version):
453
"""Replace this process with the maya executable as specified by maya_version.
455
:param args: The arguments to be provided to maya
456
:param maya_version: Float identifying the maya version to be launched
457
:rase EnvironmentError: if the respective maya version could not be found"""
458
mayalocation = maya_location(maya_version)
459
mayabin = os.path.join(mayalocation, 'bin', 'maya')
461
# although execv would work on windows, we use our specialized _execute method
462
# in order to keep things consistent
463
_execute(mayabin, tuple(args))
466
#} END Maya initialization
470
def log_exception( func ):
471
"""Assures that exceptions result in a logging message.
472
Currently only works with a SpawnedCommand as we need a log instance.
473
On error, the server exits with status 64"""
474
def wrapper(self, *args, **kwargs):
476
return func(self, *args, **kwargs)
478
if self.parser.spawned:
479
self.log.critical("Program %r aborted with a unhandled exception: %s" % (self.k_log_application_id, str(e)), exc_info=True)
483
# END handle sysexit gracefully
486
wrapper.__name__ = func.__name__
493
class SpawnedHelpFormatter(optparse.TitledHelpFormatter):
494
"""Formatter assuring our help looks good"""
496
def _format_text(self, text):
497
"""Don't wrap the text at all"""
499
text = self.parser.expand_prog_name(text)
500
# END program name expansion if possible
504
lines = text.splitlines(True)
505
return ''.join(' '*self.level + l for l in lines)
507
def format_usage(self, usage):
508
return "usage: %s\n" % usage
511
class SpawnedOptionParser(optparse.OptionParser):
512
"""Customized version to ease use of SpawnedCommand
514
Initialized with the 'spawned' keyword in addition
515
to the default keywords to prevent a system exit"""
517
def __init__(self, *args, **kwargs):
518
self.spawned = kwargs.pop('spawned', False)
519
kwargs['formatter'] = SpawnedHelpFormatter()
520
optparse.OptionParser.__init__(self, *args, **kwargs)
522
def exit(self, status=0, msg=None):
524
sys.stderr.write(msg)
529
# reraise if possible as we have not been spawned
530
exc_type, value, traceback = sys.exc_info()
534
raise optparse.OptParseError(msg)
539
class SpawnedCommand(object):
540
"""Implements a command which can be started easily by specifying a class path
541
such as package.cmd.module.CommandClass whose instance should be started in
542
a standalone process.
544
The target command must be derived from this class and must implement the
547
To use this class, derive from it and change the configuration variables
550
The instance will always own a logger instance at its member called 'log',
551
the configuration will be applied according to k_log_application_id
553
The parser used to parse all options is vailable at its member called 'parser',
554
its set during ``option_parser``
556
The instance may also be created within an existing process and
557
executed manually - in that case it will not exit automatically if a
558
serious event occours"""
561
# If not None, the name will be available for printing help text, and other tasks
562
# such as application specific initialization of modules
563
k_log_application_id = None
565
# path at which your class is located. It must be derived from SpawnedCommand
566
k_class_path = "package.module.YourClass"
568
# An identifier for the version of your command
571
# Path to the executable
572
_exec_path = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'bin', 'mrv')
574
# If set, its the directory containing your project's info file. It must be set from the
575
# derived class, as it knows its actual position
578
# additional arguments to pass on to the newly created process
579
_add_args = ['--mrv-no-maya']
581
# The usage of your command in BMF
582
# i.e. %prog [options]
585
# a short description of your command printed below its usage
588
# the name of your program's actual executable
589
k_program_name = None
591
# File mode creation mask of the daemon.
592
# If None, current one will not be changed
595
# Default working directory for the daemon.
596
# If None, the current one will not be changed
597
_daemon_workdir = None
599
# Default maximum for the number of available file descriptors that we try to close
602
# The standard I/O file descriptors are redirected to /dev/null by default.
603
if (hasattr(os, "devnull")):
604
_daemon_redirect_to = os.devnull
606
_daemon_redirect_to = "/dev/null"
610
__slots__ = ('parser', 'log')
613
def __init__(self, *args, **kwargs):
615
:param _spawned: If True, default False, we assume we have our own process.
616
Otherwise we will do nothing that would adjust the current process, such as:
619
* change configuration of logging system"""
621
super(SpawnedCommand, self).__init__(*args, **kwargs)
623
# in python 2.6 and newer, object will not accept to be called with
624
# arguments anymore. Problem is that we don't know whether super is an
625
# object or some mixed in type, which is a design flaw, and which breaks
628
# END py 2.6 special case
631
spawned = kwargs.get('_spawned', False)
632
self.parser = SpawnedOptionParser( usage=self.k_usage, version=self.k_version,
633
description=self.k_description, add_help_option=True,
634
prog=self.k_program_name, spawned=spawned)
635
self.log = logging.getLogger(self.k_program_name)
638
def spawn(cls, *args, **kwargs):
639
"""Spawn a new standalone process of this command type
640
Additional arguments passed to the command process
642
:param kwargs: Additional keyword arguments to be passed to Subprocess.Popen,
643
use it to configure your IO
645
Returns: Subprocess.Popen instance"""
647
margs = [cls._exec_path, spcmd.__file__, cls.k_class_path]
649
margs.extend(cls._add_args)
652
margs.insert(0, sys.executable)
653
# END handle windows inabilitiess
655
old_mrvinfo_val = None
656
env_mrv_info = 'MRV_INFO_DIR'
657
if cls._mrv_info_dir is not None:
658
old_mrvinfo_val = os.environ.get(env_mrv_info)
659
os.environ[env_mrv_info] = cls._mrv_info_dir
663
return subprocess.Popen(margs, **kwargs)
665
if old_mrvinfo_val is not None:
666
os.environ[env_mrv_info] = old_mrvinfo_val
667
#END reset prevous value
668
#END handle environment change
671
def daemonize(cls, *args):
673
Damonize the spawned command, passing *args to the instanciated command's
676
:return: None in calling process, no return in the daemon
677
as sys.exit will be called.
678
:note: see configuration variables prefixed with _daemon_
679
:note: based on Chad J. Schroeder createDaemon method,
680
see http://code.activestate.com/recipes/278731-creating-a-daemon-the-python-way
682
if sys.platform.startswith("win"):
683
raise OSError("Cannot daemonize on windows")
684
# END handle operating system
687
# Fork a child process so the parent can exit. This returns control to
688
# the command-line or shell. It also guarantees that the child will not
689
# be a process group leader, since the child receives a new process ID
690
# and inherits the parent's process group ID. This step is required
691
# to insure that the next call to os.setsid is successful.
694
raise Exception, "%s [%d]" % (e.strerror, e.errno)
698
# _exit is like exit(), but it doesn't call any functions registered
699
# with atexit (and on_exit) or any registered signal handlers. It also
700
# closes any open file descriptors. Using exit() may cause all stdio
701
# streams to be flushed twice and any temporary files may be unexpectedly
702
# removed. It's therefore recommended that child branches of a fork()
703
# and the parent branch(es) of a daemon use _exit().
710
# To become the session leader of this new session and the process group
711
# leader of the new process group, we call os.setsid(). The process is
712
# also guaranteed not to have a controlling terminal.
715
# Is ignoring SIGHUP necessary?
717
# It's often suggested that the SIGHUP signal should be ignored before
718
# the second fork to avoid premature termination of the process. The
719
# reason is that when the first child terminates, all processes, e.g.
720
# the second child, in the orphaned group will be sent a SIGHUP.
722
# "However, as part of the session management system, there are exactly
723
# two cases where SIGHUP is sent on the death of a process:
725
# 1) When the process that dies is the session leader of a session that
726
# is attached to a terminal device, SIGHUP is sent to all processes
727
# in the foreground process group of that terminal device.
728
# 2) When the death of a process causes a process group to become
729
# orphaned, and one or more processes in the orphaned group are
730
# stopped, then SIGHUP and SIGCONT are sent to all members of the
731
# orphaned group." [2]
733
# The first case can be ignored since the child is guaranteed not to have
734
# a controlling terminal. The second case isn't so easy to dismiss.
735
# The process group is orphaned when the first child terminates and
736
# POSIX.1 requires that every STOPPED process in an orphaned process
737
# group be sent a SIGHUP signal followed by a SIGCONT signal. Since the
738
# second child is not STOPPED though, we can safely forego ignoring the
739
# SIGHUP signal. In any case, there are no ill-effects if it is ignored.
741
# import signal # Set handlers for asynchronous events.
742
# signal.signal(signal.SIGHUP, signal.SIG_IGN)
745
# Fork a second child and exit immediately to prevent zombies. This
746
# causes the second child process to be orphaned, making the init
747
# process responsible for its cleanup. And, since the first child is
748
# a session leader without a controlling terminal, it's possible for
749
# it to acquire one by opening a terminal in the future (System V-
750
# based systems). This second fork guarantees that the child is no
751
# longer a session leader, preventing the daemon from ever acquiring
752
# a controlling terminal.
753
pid = os.fork() # Fork a second child.
755
raise Exception, "%s [%d]" % (e.strerror, e.errno)
758
# exit() or _exit()? See below.
759
os._exit(0) # Exit parent (the first child) of the second child.
760
# END exit second child
765
# Since the current working directory may be a mounted filesystem, we
766
# avoid the issue of not being able to unmount the filesystem at
767
# shutdown time by changing it to the root directory.
768
if cls._daemon_workdir is not None:
769
os.chdir(cls._daemon_workdir)
770
# END set working dir
772
# We probably don't want the file mode creation mask inherited from
773
# the parent, so we give the child complete control over permissions.
774
if cls._daemon_umask is not None:
775
os.umask(cls._daemon_umask)
780
# Close all open file descriptors. This prevents the child from keeping
781
# open any file descriptors inherited from the parent. There is a variety
782
# of methods to accomplish this task. Three are listed below.
784
# Try the system configuration variable, SC_OPEN_MAX, to obtain the maximum
785
# number of open file descriptors to close. If it doesn't exists, use
786
# the default value (configurable).
789
# maxfd = os.sysconf("SC_OPEN_MAX")
790
# except (AttributeError, ValueError):
795
# if (os.sysconf_names.has_key("SC_OPEN_MAX")):
796
# maxfd = os.sysconf("SC_OPEN_MAX")
802
# Use the getrlimit method to retrieve the maximum file descriptor number
803
# that can be opened by this process. If there is not limit on the
804
# resource, use the default value.
806
import resource # Resource usage information.
807
maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
808
if (maxfd == resource.RLIM_INFINITY):
809
maxfd = cls._daemon_maxfd
813
# Iterate through and close all file descriptors.
814
for fd in range(debug_daemon*3, maxfd):
817
except OSError: # ERROR, fd wasn't open to begin with (ignored)
819
# END for each fd in range
821
# Redirect the standard I/O file descriptors to the specified file. Since
822
# the daemon has no controlling terminal, most daemons redirect stdin,
823
# stdout, and stderr to /dev/null. This is done to prevent side-effects
824
# from reads and writes to the standard I/O file descriptors.
826
# This call to open is guaranteed to return the lowest file descriptor,
827
# which will be 0 (stdin), since it was closed above.
829
os.open(cls._daemon_redirect_to, os.O_RDWR) # standard input (0)
831
# Duplicate standard input to standard output and standard error.
832
os.dup2(0, 1) # standard output (1)
833
os.dup2(0, 2) # standard error (2)
834
# END handle standard descriptors
836
# RUN THE SPAWNED COMMAND
837
#########################
838
cmdinstance = cls(_spawned=True)
839
return cmdinstance._execute(*args)
841
def _preprocess_args(self, options, args):
842
""":return: tuple(options, args) tuple of parsed options and remaining args
843
The arguments can be preprocessed"""
846
def _execute(self, *args):
847
"""internal method handling the basic arguments in a pre-process before
850
We will parse all options, process the default ones and pass on the
851
call to the ``execute`` method"""
852
options, args = self._preprocess_args(*self.option_parser().parse_args(list(args)))
854
# make sure we have a default setup at least !
855
logging.basicConfig()
857
# call the subclassed method
859
return self.execute(options, args)
861
if self.parser.spawned:
862
sys.stderr.write('%s: %s\n' % (type(e).__name__, str(e)))
866
# END help the user in case he provides invalid options
870
def execute(self, options, args):
871
"""Method implementing the actual functionality of the command
872
:param options: Values instance of the optparse module
873
:param args: remaining positional arguments passed to the process on the commandline
874
:note: if you like to terminate, raise an exception"""
877
def option_parser(self):
878
""":return: OptionParser Instance containing all supported options
879
:note: Should be overridden by subclass to add additional options and
880
option groups themselves after calling the base class implementation"""
882
#} END needing subclass