1 """distutils.command.build_py 
   3 Implements the Distutils 'build_py' command.""" 
   5 # This module should be kept compatible with Python 1.5.2. 
  13 from distutils
.core 
import Command
 
  14 from distutils
.errors 
import * 
  15 from distutils
.util 
import convert_path
 
  16 from distutils 
import log
 
  18 class build_py (Command
): 
  20     description 
= "\"build\" pure Python modules (copy to build directory)" 
  23         ('build-lib=', 'd', "directory to \"build\" (copy) to"), 
  24         ('compile', 'c', "compile .py to .pyc"), 
  25         ('no-compile', None, "don't compile .py files [default]"), 
  27          "also compile with optimization: -O1 for \"python -O\", " 
  28          "-O2 for \"python -OO\", and -O0 to disable [default: -O0]"), 
  29         ('force', 'f', "forcibly build everything (ignore file timestamps)"), 
  32     boolean_options 
= ['compile', 'force'] 
  33     negative_opt 
= {'no-compile' : 'compile'}
 
  36     def initialize_options (self
): 
  38         self
.py_modules 
= None 
  40         self
.package_dir 
= None 
  45     def finalize_options (self
): 
  46         self
.set_undefined_options('build', 
  47                                    ('build_lib', 'build_lib'), 
  50         # Get the distribution options that are aliases for build_py 
  51         # options -- list of packages and list of modules. 
  52         self
.packages 
= self
.distribution
.packages
 
  53         self
.py_modules 
= self
.distribution
.py_modules
 
  55         if self
.distribution
.package_dir
: 
  56             for name
, path 
in self
.distribution
.package_dir
.items(): 
  57                 self
.package_dir
[name
] = convert_path(path
) 
  59         # Ick, copied straight from install_lib.py (fancy_getopt needs a 
  60         # type system!  Hell, *everything* needs a type system!!!) 
  61         if type(self
.optimize
) is not IntType
: 
  63                 self
.optimize 
= int(self
.optimize
) 
  64                 assert 0 <= self
.optimize 
<= 2 
  65             except (ValueError, AssertionError): 
  66                 raise DistutilsOptionError
, "optimize must be 0, 1, or 2" 
  70         # XXX copy_file by default preserves atime and mtime.  IMHO this is 
  71         # the right thing to do, but perhaps it should be an option -- in 
  72         # particular, a site administrator might want installed files to 
  73         # reflect the time of installation rather than the last 
  74         # modification time before the installed release. 
  76         # XXX copy_file by default preserves mode, which appears to be the 
  77         # wrong thing to do: if a file is read-only in the working 
  78         # directory, we want it to be installed read/write so that the next 
  79         # installation of the same module distribution can overwrite it 
  80         # without problems.  (This might be a Unix-specific issue.)  Thus 
  81         # we turn off 'preserve_mode' when copying to the build directory, 
  82         # since the build directory is supposed to be exactly what the 
  83         # installation will look like (ie. we preserve mode when 
  86         # Two options control which modules will be installed: 'packages' 
  87         # and 'py_modules'.  The former lets us work with whole packages, not 
  88         # specifying individual modules at all; the latter is for 
  89         # specifying modules one-at-a-time. 
  96         self
.byte_compile(self
.get_outputs(include_bytecode
=0)) 
 101     def get_package_dir (self
, package
): 
 102         """Return the directory, relative to the top of the source 
 103            distribution, where package 'package' should be found 
 104            (at least according to the 'package_dir' option, if any).""" 
 106         path 
= string
.split(package
, '.') 
 108         if not self
.package_dir
: 
 110                 return apply(os
.path
.join
, path
) 
 117                     pdir 
= self
.package_dir
[string
.join(path
, '.')] 
 119                     tail
.insert(0, path
[-1]) 
 123                     return apply(os
.path
.join
, tail
) 
 125                 # Oops, got all the way through 'path' without finding a 
 126                 # match in package_dir.  If package_dir defines a directory 
 127                 # for the root (nameless) package, then fallback on it; 
 128                 # otherwise, we might as well have not consulted 
 129                 # package_dir at all, as we just use the directory implied 
 130                 # by 'tail' (which should be the same as the original value 
 131                 # of 'path' at this point). 
 132                 pdir 
= self
.package_dir
.get('') 
 137                     return apply(os
.path
.join
, tail
) 
 144     def check_package (self
, package
, package_dir
): 
 146         # Empty dir name means current directory, which we can probably 
 147         # assume exists.  Also, os.path.exists and isdir don't know about 
 148         # my "empty string means current dir" convention, so we have to 
 150         if package_dir 
!= "": 
 151             if not os
.path
.exists(package_dir
): 
 152                 raise DistutilsFileError
, \
 
 153                       "package directory '%s' does not exist" % package_dir
 
 154             if not os
.path
.isdir(package_dir
): 
 155                 raise DistutilsFileError
, \
 
 156                       ("supposed package directory '%s' exists, " + 
 157                        "but is not a directory") % package_dir
 
 159         # Require __init__.py for all but the "root package" 
 161             init_py 
= os
.path
.join(package_dir
, "__init__.py") 
 162             if os
.path
.isfile(init_py
): 
 165                 log
.warn(("package init file '%s' not found " + 
 166                           "(or not a regular file)"), init_py
) 
 168         # Either not in a package at all (__init__.py not expected), or 
 169         # __init__.py doesn't exist -- so don't return the filename. 
 175     def check_module (self
, module
, module_file
): 
 176         if not os
.path
.isfile(module_file
): 
 177             log
.warn("file %s (for module %s) not found", module_file
, module
) 
 185     def find_package_modules (self
, package
, package_dir
): 
 186         self
.check_package(package
, package_dir
) 
 187         module_files 
= glob(os
.path
.join(package_dir
, "*.py")) 
 189         setup_script 
= os
.path
.abspath(self
.distribution
.script_name
) 
 191         for f 
in module_files
: 
 192             abs_f 
= os
.path
.abspath(f
) 
 193             if abs_f 
!= setup_script
: 
 194                 module 
= os
.path
.splitext(os
.path
.basename(f
))[0] 
 195                 modules
.append((package
, module
, f
)) 
 197                 self
.debug_print("excluding %s" % setup_script
) 
 201     def find_modules (self
): 
 202         """Finds individually-specified Python modules, ie. those listed by 
 203         module name in 'self.py_modules'.  Returns a list of tuples (package, 
 204         module_base, filename): 'package' is a tuple of the path through 
 205         package-space to the module; 'module_base' is the bare (no 
 206         packages, no dots) module name, and 'filename' is the path to the 
 207         ".py" file (relative to the distribution root) that implements the 
 211         # Map package names to tuples of useful info about the package: 
 212         #    (package_dir, checked) 
 213         # package_dir - the directory where we'll find source files for 
 215         # checked - true if we have checked that the package directory 
 216         #   is valid (exists, contains __init__.py, ... ?) 
 219         # List of (package, module, filename) tuples to return 
 222         # We treat modules-in-packages almost the same as toplevel modules, 
 223         # just the "package" for a toplevel is empty (either an empty 
 224         # string or empty list, depending on context).  Differences: 
 225         #   - don't check for __init__.py in directory for empty package 
 227         for module 
in self
.py_modules
: 
 228             path 
= string
.split(module
, '.') 
 229             package 
= string
.join(path
[0:-1], '.') 
 230             module_base 
= path
[-1] 
 233                 (package_dir
, checked
) = packages
[package
] 
 235                 package_dir 
= self
.get_package_dir(package
) 
 239                 init_py 
= self
.check_package(package
, package_dir
) 
 240                 packages
[package
] = (package_dir
, 1) 
 242                     modules
.append((package
, "__init__", init_py
)) 
 244             # XXX perhaps we should also check for just .pyc files 
 245             # (so greedy closed-source bastards can distribute Python 
 247             module_file 
= os
.path
.join(package_dir
, module_base 
+ ".py") 
 248             if not self
.check_module(module
, module_file
): 
 251             modules
.append((package
, module_base
, module_file
)) 
 258     def find_all_modules (self
): 
 259         """Compute the list of all modules that will be built, whether 
 260         they are specified one-module-at-a-time ('self.py_modules') or 
 261         by whole packages ('self.packages').  Return a list of tuples 
 262         (package, module, module_file), just like 'find_modules()' and 
 263         'find_package_modules()' do.""" 
 267             modules
.extend(self
.find_modules()) 
 269             for package 
in self
.packages
: 
 270                 package_dir 
= self
.get_package_dir(package
) 
 271                 m 
= self
.find_package_modules(package
, package_dir
) 
 276     # find_all_modules () 
 279     def get_source_files (self
): 
 281         modules 
= self
.find_all_modules() 
 283         for module 
in modules
: 
 284             filenames
.append(module
[-1]) 
 289     def get_module_outfile (self
, build_dir
, package
, module
): 
 290         outfile_path 
= [build_dir
] + list(package
) + [module 
+ ".py"] 
 291         return apply(os
.path
.join
, outfile_path
) 
 294     def get_outputs (self
, include_bytecode
=1): 
 295         modules 
= self
.find_all_modules() 
 297         for (package
, module
, module_file
) in modules
: 
 298             package 
= string
.split(package
, '.') 
 299             filename 
= self
.get_module_outfile(self
.build_lib
, package
, module
) 
 300             outputs
.append(filename
) 
 303                     outputs
.append(filename 
+ "c") 
 304                 if self
.optimize 
> 0: 
 305                     outputs
.append(filename 
+ "o") 
 310     def build_module (self
, module
, module_file
, package
): 
 311         if type(package
) is StringType
: 
 312             package 
= string
.split(package
, '.') 
 313         elif type(package
) not in (ListType
, TupleType
): 
 315                   "'package' must be a string (dot-separated), list, or tuple" 
 317         # Now put the module source file into the "build" area -- this is 
 318         # easy, we just copy it somewhere under self.build_lib (the build 
 319         # directory for Python source). 
 320         outfile 
= self
.get_module_outfile(self
.build_lib
, package
, module
) 
 321         dir = os
.path
.dirname(outfile
) 
 323         return self
.copy_file(module_file
, outfile
, preserve_mode
=0) 
 326     def build_modules (self
): 
 328         modules 
= self
.find_modules() 
 329         for (package
, module
, module_file
) in modules
: 
 331             # Now "build" the module -- ie. copy the source file to 
 332             # self.build_lib (the build directory for Python source). 
 333             # (Actually, it gets copied to the directory for this package 
 334             # under self.build_lib.) 
 335             self
.build_module(module
, module_file
, package
) 
 340     def build_packages (self
): 
 342         for package 
in self
.packages
: 
 344             # Get list of (package, module, module_file) tuples based on 
 345             # scanning the package directory.  'package' is only included 
 346             # in the tuple so that 'find_modules()' and 
 347             # 'find_package_tuples()' have a consistent interface; it's 
 348             # ignored here (apart from a sanity check).  Also, 'module' is 
 349             # the *unqualified* module name (ie. no dots, no package -- we 
 350             # already know its package!), and 'module_file' is the path to 
 351             # the .py file, relative to the current directory 
 352             # (ie. including 'package_dir'). 
 353             package_dir 
= self
.get_package_dir(package
) 
 354             modules 
= self
.find_package_modules(package
, package_dir
) 
 356             # Now loop over the modules we found, "building" each one (just 
 357             # copy it to self.build_lib). 
 358             for (package_
, module
, module_file
) in modules
: 
 359                 assert package 
== package_
 
 360                 self
.build_module(module
, module_file
, package
) 
 365     def byte_compile (self
, files
): 
 366         from distutils
.util 
import byte_compile
 
 367         prefix 
= self
.build_lib
 
 368         if prefix
[-1] != os
.sep
: 
 369             prefix 
= prefix 
+ os
.sep
 
 371         # XXX this code is essentially the same as the 'byte_compile() 
 372         # method of the "install_lib" command, except for the determination 
 373         # of the 'prefix' string.  Hmmm. 
 376             byte_compile(files
, optimize
=0, 
 377                          force
=self
.force
, prefix
=prefix
, dry_run
=self
.dry_run
) 
 378         if self
.optimize 
> 0: 
 379             byte_compile(files
, optimize
=self
.optimize
, 
 380                          force
=self
.force
, prefix
=prefix
, dry_run
=self
.dry_run
)