Commit f44a7804 authored by Greg Ward's avatar Greg Ward

Added 'force' and 'quiet' (negative alias for 'verbose') to the

  global options table.
Every Command instance now has its own copies of the global options,
  which automatically fallback to the Distribution instance.  Changes:
  - initialize them in constructor
  - added '__getattr__()' to handle the fallback logic
  - changed every 'self.distribution.{verbose,dry_run}' in Command to
    'self.{verbose,dry_run}'.
  - filesystem utility methods ('copy_file()' et al) don't take 'update'
    parameter anymore -- instead we pass 'not force' to the underlying
    function as 'update'
Changed parsing of command line so that global options apply to all
  commands as well -- that's how (eg.) Command.verbose will be initialized.
Simplified 'make_file()' to use 'newer_group()' (from util module).
Deleted some cruft.
Some docstring tweaks.
parent cbda2d01
...@@ -106,12 +106,21 @@ class Distribution: ...@@ -106,12 +106,21 @@ class Distribution:
those described below.""" those described below."""
# 'global_options' describes the command-line options that may # 'global_options' describes the command-line options that may be
# be supplied to the client (setup.py) prior to any actual # supplied to the client (setup.py) prior to any actual commands.
# commands. Eg. "./setup.py -nv" or "./setup.py --verbose" # Eg. "./setup.py -nv" or "./setup.py --verbose" both take advantage of
# both take advantage of these global options. # these global options. This list should be kept to a bare minimum,
global_options = [('verbose', 'v', "run verbosely"), # since every global option is also valid as a command option -- and we
('dry-run', 'n', "don't actually do anything"), # don't want to pollute the commands with too many options that they
# have minimal control over.
global_options = [('verbose', 'v',
"run verbosely"),
('quiet=!verbose', 'q',
"run quietly (turns verbosity off)"),
('dry-run', 'n',
"don't actually do anything"),
('force', 'f',
"skip dependency checking between files"),
] ]
...@@ -131,6 +140,7 @@ class Distribution: ...@@ -131,6 +140,7 @@ class Distribution:
# Default values for our command-line options # Default values for our command-line options
self.verbose = 0 self.verbose = 0
self.dry_run = 0 self.dry_run = 0
self.force = 0
# And the "distribution meta-data" options -- these can only # And the "distribution meta-data" options -- these can only
# come from setup.py (the caller), not the command line # come from setup.py (the caller), not the command line
...@@ -211,23 +221,22 @@ class Distribution: ...@@ -211,23 +221,22 @@ class Distribution:
def parse_command_line (self, args): def parse_command_line (self, args):
"""Parse the client's command line: set any Distribution """Parse the setup script's command line: set any Distribution
attributes tied to command-line options, create all command attributes tied to command-line options, create all command
objects, and set their options from the command-line. 'args' objects, and set their options from the command-line. 'args'
must be a list of command-line arguments, most likely must be a list of command-line arguments, most likely
'sys.argv[1:]' (see the 'setup()' function). This list is 'sys.argv[1:]' (see the 'setup()' function). This list is first
first processed for "global options" -- options that set processed for "global options" -- options that set attributes of
attributes of the Distribution instance. Then, it is the Distribution instance. Then, it is alternately scanned for
alternately scanned for Distutils command and options for Distutils command and options for that command. Each new
that command. Each new command terminates the options for command terminates the options for the previous command. The
the previous command. The allowed options for a command are allowed options for a command are determined by the 'options'
determined by the 'options' attribute of the command object attribute of the command object -- thus, we instantiate (and
-- thus, we instantiate (and cache) every command object cache) every command object here, in order to access its
here, in order to access its 'options' attribute. Any error 'options' attribute. Any error in that 'options' attribute
in that 'options' attribute raises DistutilsGetoptError; any raises DistutilsGetoptError; any error on the command-line
error on the command-line raises DistutilsArgError. If no raises DistutilsArgError. If no Distutils commands were found
Distutils commands were found on the command line, raises on the command line, raises DistutilsArgError."""
DistutilsArgError."""
# We have to parse the command line a bit at a time -- global # We have to parse the command line a bit at a time -- global
# options, then the first command, then its options, and so on -- # options, then the first command, then its options, and so on --
...@@ -268,7 +277,10 @@ class Distribution: ...@@ -268,7 +277,10 @@ class Distribution:
"(a list of tuples)") % \ "(a list of tuples)") % \
cmd_obj.__class__ cmd_obj.__class__
args = fancy_getopt (cmd_obj.options, cmd_obj, args[1:]) # Poof! like magic, all commands support the global
# options too, just by adding in 'global_options'.
args = fancy_getopt (self.global_options + cmd_obj.options,
cmd_obj, args[1:])
self.command_obj[command] = cmd_obj self.command_obj[command] = cmd_obj
self.have_run[command] = 0 self.have_run[command] = 0
...@@ -497,6 +509,17 @@ class Command: ...@@ -497,6 +509,17 @@ class Command:
self.distribution = dist self.distribution = dist
self.set_default_options () self.set_default_options ()
# Per-command versions of the global flags, so that the user can
# customize Distutils' behaviour command-by-command and let some
# commands fallback on the Distribution's behaviour. None means
# "not defined, check self.distribution's copy", while 0 or 1 mean
# false and true (duh). Note that this means figuring out the real
# value of each flag is a touch complicatd -- hence "self.verbose"
# (etc.) will be handled by __getattr__, below.
self._verbose = None
self._dry_run = None
self._force = None
# 'ready' records whether or not 'set_final_options()' has been # 'ready' records whether or not 'set_final_options()' has been
# called. 'set_final_options()' itself should not pay attention to # called. 'set_final_options()' itself should not pay attention to
# this flag: it is the business of 'ensure_ready()', which always # this flag: it is the business of 'ensure_ready()', which always
...@@ -506,6 +529,17 @@ class Command: ...@@ -506,6 +529,17 @@ class Command:
# end __init__ () # end __init__ ()
def __getattr__ (self, attr):
if attr in ('verbose', 'dry_run', 'force'):
myval = getattr (self, "_" + attr)
if myval is None:
return getattr (self.distribution, attr)
else:
return myval
else:
raise AttributeError, attr
def ensure_ready (self): def ensure_ready (self):
if not self.ready: if not self.ready:
self.set_final_options () self.set_final_options ()
...@@ -569,7 +603,8 @@ class Command: ...@@ -569,7 +603,8 @@ class Command:
"""If the Distribution instance to which this command belongs """If the Distribution instance to which this command belongs
has a verbosity level of greater than or equal to 'level' has a verbosity level of greater than or equal to 'level'
print 'msg' to stdout.""" print 'msg' to stdout."""
if self.distribution.verbose >= level:
if self.verbose >= level:
print msg print msg
...@@ -720,15 +755,13 @@ class Command: ...@@ -720,15 +755,13 @@ class Command:
def execute (self, func, args, msg=None, level=1): def execute (self, func, args, msg=None, level=1):
"""Perform some action that affects the outside world (eg. """Perform some action that affects the outside world (eg.
by writing to the filesystem). Such actions are special because by writing to the filesystem). Such actions are special because
they should be disabled by the "dry run" flag (carried around by they should be disabled by the "dry run" flag, and should
the Command's Distribution), and should announce themselves if announce themselves if the current verbosity level is high
the current verbosity level is high enough. This method takes enough. This method takes care of all that bureaucracy for you;
care of all that bureaucracy for you; all you have to do is all you have to do is supply the funtion to call and an argument
supply the funtion to call and an argument tuple for it (to tuple for it (to embody the "external action" being performed),
embody the "external action" being performed), a message to a message to print if the verbosity level is high enough, and an
print if the verbosity level is high enough, and an optional optional verbosity threshold."""
verbosity threshold."""
# Generate a message if we weren't passed one # Generate a message if we weren't passed one
if msg is None: if msg is None:
...@@ -740,7 +773,7 @@ class Command: ...@@ -740,7 +773,7 @@ class Command:
self.announce (msg, level) self.announce (msg, level)
# And do it, as long as we're not in dry-run mode # And do it, as long as we're not in dry-run mode
if not self.distribution.dry_run: if not self.dry_run:
apply (func, args) apply (func, args)
# execute() # execute()
...@@ -748,43 +781,45 @@ class Command: ...@@ -748,43 +781,45 @@ class Command:
def mkpath (self, name, mode=0777): def mkpath (self, name, mode=0777):
util.mkpath (name, mode, util.mkpath (name, mode,
self.distribution.verbose, self.distribution.dry_run) self.verbose, self.dry_run)
def copy_file (self, infile, outfile, def copy_file (self, infile, outfile,
preserve_mode=1, preserve_times=1, update=1, level=1): preserve_mode=1, preserve_times=1, level=1):
"""Copy a file respecting verbose and dry-run flags.""" """Copy a file respecting verbose, dry-run and force flags."""
return util.copy_file (infile, outfile, return util.copy_file (infile, outfile,
preserve_mode, preserve_times, preserve_mode, preserve_times,
update, self.distribution.verbose >= level, not self.force,
self.distribution.dry_run) self.verbose >= level,
self.dry_run)
def copy_tree (self, infile, outfile, def copy_tree (self, infile, outfile,
preserve_mode=1, preserve_times=1, preserve_symlinks=0, preserve_mode=1, preserve_times=1, preserve_symlinks=0,
update=1, level=1): level=1):
"""Copy an entire directory tree respecting verbose and dry-run """Copy an entire directory tree respecting verbose, dry-run,
flags.""" and force flags."""
return util.copy_tree (infile, outfile, return util.copy_tree (infile, outfile,
preserve_mode,preserve_times,preserve_symlinks, preserve_mode,preserve_times,preserve_symlinks,
update, self.distribution.verbose >= level, not self.force,
self.distribution.dry_run) self.verbose >= level,
self.dry_run)
def move_file (self, src, dst, level=1): def move_file (self, src, dst, level=1):
"""Move a file respecting verbose and dry-run flags.""" """Move a file respecting verbose and dry-run flags."""
return util.move_file (src, dst, return util.move_file (src, dst,
self.distribution.verbose >= level, self.verbose >= level,
self.distribution.dry_run) self.dry_run)
def spawn (self, cmd, search_path=1, level=1): def spawn (self, cmd, search_path=1, level=1):
from distutils.spawn import spawn from distutils.spawn import spawn
spawn (cmd, search_path, spawn (cmd, search_path,
self.distribution.verbose >= level, self.verbose >= level,
self.distribution.dry_run) self.dry_run)
def make_file (self, infiles, outfile, func, args, def make_file (self, infiles, outfile, func, args,
...@@ -811,29 +846,10 @@ class Command: ...@@ -811,29 +846,10 @@ class Command:
raise TypeError, \ raise TypeError, \
"'infiles' must be a string, or a list or tuple of strings" "'infiles' must be a string, or a list or tuple of strings"
# XXX this stuff should probably be moved off to a function # If 'outfile' must be regenerated (either because it doesn't
# in 'distutils.util' # exist, is out-of-date, or the 'force' flag is true) then
from stat import *
if os.path.exists (outfile):
out_mtime = os.stat (outfile)[ST_MTIME]
# Loop over all infiles. If any infile is newer than outfile,
# then we'll have to regenerate outfile
for f in infiles:
in_mtime = os.stat (f)[ST_MTIME]
if in_mtime > out_mtime:
runit = 1
break
else:
runit = 0
else:
runit = 1
# If we determined that 'outfile' must be regenerated, then
# perform the action that presumably regenerates it # perform the action that presumably regenerates it
if runit: if self.force or newer_group (infiles, outfile):
self.execute (func, args, exec_msg, level) self.execute (func, args, exec_msg, level)
# Otherwise, print the "skip" message # Otherwise, print the "skip" message
...@@ -842,19 +858,4 @@ class Command: ...@@ -842,19 +858,4 @@ class Command:
# make_file () # make_file ()
# def make_files (self, infiles, outfiles, func, args,
# exec_msg=None, skip_msg=None, level=1):
# """Special case of 'execute()' for operations that process one or
# more input files and generate one or more output files. Works
# just like 'execute()', except the operation is skipped and a
# different message printed if all files listed in 'outfiles'
# already exist and are newer than all files listed in
# 'infiles'."""
# pass
# end class Command # end class Command
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment