1 """distutils.command.build_py
3 Implements the Distutils 'build_py' command."""
5 # created 1999/03/08, Greg Ward
13 from distutils
.core
import Command
14 from distutils
.errors
import *
17 class BuildPy (Command
):
19 description
= "\"build\" pure Python modules (copy to build directory)"
21 options
= [('build-dir=', 'd', "directory for platform-shared files"),
25 def set_default_options (self
):
29 self
.package_dir
= None
31 def set_final_options (self
):
32 self
.set_undefined_options ('build',
33 ('build_lib', 'build_dir'))
35 # Get the distribution options that are aliases for build_py
36 # options -- list of packages and list of modules.
37 self
.packages
= self
.distribution
.packages
38 self
.modules
= self
.distribution
.py_modules
39 self
.package_dir
= self
.distribution
.package_dir
44 # XXX copy_file by default preserves atime and mtime. IMHO this is
45 # the right thing to do, but perhaps it should be an option -- in
46 # particular, a site administrator might want installed files to
47 # reflect the time of installation rather than the last
48 # modification time before the installed release.
50 # XXX copy_file by default preserves mode, which appears to be the
51 # wrong thing to do: if a file is read-only in the working
52 # directory, we want it to be installed read/write so that the next
53 # installation of the same module distribution can overwrite it
54 # without problems. (This might be a Unix-specific issue.) Thus
55 # we turn off 'preserve_mode' when copying to the build directory,
56 # since the build directory is supposed to be exactly what the
57 # installation will look like (ie. we preserve mode when
60 # XXX copy_file does *not* preserve MacOS-specific file metadata.
61 # If this is a problem for building/installing Python modules, then
62 # we'll have to fix copy_file. (And what about installing scripts,
63 # when the time comes for that -- does MacOS use its special
64 # metadata to know that a file is meant to be interpreted by
71 # Two options control which modules will be installed: 'packages'
72 # and 'modules'. The former lets us work with whole packages, not
73 # specifying individual modules at all; the latter is for
74 # specifying modules one-at-a-time. Currently they are mutually
75 # exclusive: you can define one or the other (or neither), but not
76 # both. It remains to be seen how limiting this is.
78 # Dispose of the two "unusual" cases first: no pure Python modules
79 # at all (no problem, just return silently), and over-specified
80 # 'packages' and 'modules' options.
82 if not self
.modules
and not self
.packages
:
84 if self
.modules
and self
.packages
:
85 raise DistutilsOptionError
, \
86 "build_py: supplying both 'packages' and 'modules' " + \
87 "options is not allowed"
89 # Now we're down to two cases: 'modules' only and 'packages' only.
93 self
.build_packages ()
98 def get_package_dir (self
, package
):
99 """Return the directory, relative to the top of the source
100 distribution, where package 'package' should be found
101 (at least according to the 'package_dir' option, if any)."""
103 if type (package
) is StringType
:
104 path
= string
.split (package
, '.')
105 elif type (package
) in (TupleType
, ListType
):
106 path
= list (package
)
108 raise TypeError, "'package' must be a string, list, or tuple"
110 if not self
.package_dir
:
112 return apply (os
.path
.join
, path
)
119 pdir
= self
.package_dir
[string
.join (path
, '.')]
121 tail
.insert (0, path
[-1])
124 tail
.insert (0, pdir
)
125 return apply (os
.path
.join
, tail
)
127 # arg! everything failed, we might as well have not even
128 # looked in package_dir -- oh well
130 return apply (os
.path
.join
, tail
)
137 def check_package (self
, package
, package_dir
):
139 # Empty dir name means current directory, which we can probably
140 # assume exists. Also, os.path.exists and isdir don't know about
141 # my "empty string means current dir" convention, so we have to
143 if package_dir
!= "":
144 if not os
.path
.exists (package_dir
):
145 raise DistutilsFileError
, \
146 "package directory '%s' does not exist" % package_dir
147 if not os
.path
.isdir (package_dir
):
148 raise DistutilsFileErorr
, \
149 ("supposed package directory '%s' exists, " +
150 "but is not a directory") % package_dir
152 # Require __init__.py for all but the "root package"
154 init_py
= os
.path
.join (package_dir
, "__init__.py")
155 if not os
.path
.isfile (init_py
):
156 self
.warn (("package init file '%s' not found " +
157 "(or not a regular file)") % init_py
)
161 def check_module (self
, module
, module_file
):
162 if not os
.path
.isfile (module_file
):
163 self
.warn ("file %s (for module %s) not found" %
164 (module_file
, module
))
172 def find_package_modules (self
, package
, package_dir
):
173 module_files
= glob (os
.path
.join (package_dir
, "*.py"))
175 setup_script
= os
.path
.abspath (sys
.argv
[0])
177 for f
in module_files
:
178 abs_f
= os
.path
.abspath (f
)
179 if abs_f
!= setup_script
:
180 module
= os
.path
.splitext (os
.path
.basename (f
))[0]
181 module_pairs
.append ((module
, f
))
185 def find_modules (self
):
186 # Map package names to tuples of useful info about the package:
187 # (package_dir, checked)
188 # package_dir - the directory where we'll find source files for
190 # checked - true if we have checked that the package directory
191 # is valid (exists, contains __init__.py, ... ?)
194 # List of (module, package, filename) tuples to return
197 # We treat modules-in-packages almost the same as toplevel modules,
198 # just the "package" for a toplevel is empty (either an empty
199 # string or empty list, depending on context). Differences:
200 # - don't check for __init__.py in directory for empty package
202 for module
in self
.modules
:
203 path
= string
.split (module
, '.')
204 package
= tuple (path
[0:-1])
205 module_base
= path
[-1]
208 (package_dir
, checked
) = packages
[package
]
210 package_dir
= self
.get_package_dir (package
)
214 self
.check_package (package
, package_dir
)
215 packages
[package
] = (package_dir
, 1)
217 # XXX perhaps we should also check for just .pyc files
218 # (so greedy closed-source bastards can distribute Python
220 module_file
= os
.path
.join (package_dir
, module_base
+ ".py")
221 if not self
.check_module (module
, module_file
):
224 modules
.append ((module
, package
, module_file
))
231 def get_source_files (self
):
234 modules
= self
.find_modules ()
237 for package
in self
.packages
:
238 package_dir
= self
.get_package_dir (package
)
239 m
= self
.find_package_modules (package
, package_dir
)
242 # Both find_modules() and find_package_modules() return a list of
243 # tuples where the last element of each tuple is the filename --
244 # what a happy coincidence!
246 for module
in modules
:
247 filenames
.append (module
[-1])
252 def build_module (self
, module
, module_file
, package
):
254 if type (package
) is StringType
:
255 package
= string
.split (package
, '.')
256 elif type (package
) not in (ListType
, TupleType
):
258 "'package' must be a string (dot-separated), list, or tuple"
260 # Now put the module source file into the "build" area -- this is
261 # easy, we just copy it somewhere under self.build_dir (the build
262 # directory for Python source).
263 outfile_path
= list (package
)
264 outfile_path
.append (module
+ ".py")
265 outfile_path
.insert (0, self
.build_dir
)
266 outfile
= apply (os
.path
.join
, outfile_path
)
268 dir = os
.path
.dirname (outfile
)
270 self
.copy_file (module_file
, outfile
, preserve_mode
=0)
273 def build_modules (self
):
275 modules
= self
.find_modules()
276 for (module
, package
, module_file
) in modules
:
278 # Now "build" the module -- ie. copy the source file to
279 # self.build_dir (the build directory for Python source).
280 # (Actually, it gets copied to the directory for this package
281 # under self.build_dir.)
282 self
.build_module (module
, module_file
, package
)
287 def build_packages (self
):
289 for package
in self
.packages
:
290 package_dir
= self
.get_package_dir (package
)
291 self
.check_package (package
, package_dir
)
293 # Get list of (module, module_file) tuples based on scanning
294 # the package directory. Here, 'module' is the *unqualified*
295 # module name (ie. no dots, no package -- we already know its
296 # package!), and module_file is the path to the .py file,
297 # relative to the current directory (ie. including
299 modules
= self
.find_package_modules (package
, package_dir
)
301 # Now loop over the modules we found, "building" each one (just
302 # copy it to self.build_dir).
303 for (module
, module_file
) in modules
:
304 self
.build_module (module
, module_file
, package
)