blob: 2d0ad1ce0952d8e062c205fc129c2526fb51aa7b [file] [log] [blame]
Greg Ward13ae1c81999-03-22 14:55:25 +00001"""distutils.command.build_py
2
3Implements the Distutils 'build_py' command."""
4
5# created 1999/03/08, Greg Ward
6
Greg Ward3ce77fd2000-03-02 01:49:45 +00007__revision__ = "$Id$"
Greg Ward13ae1c81999-03-22 14:55:25 +00008
Greg Ward9b454431999-12-12 17:03:59 +00009import sys, string, os
Greg Ward17dc6e71999-09-21 18:22:34 +000010from types import *
11from glob import glob
12
Greg Ward13ae1c81999-03-22 14:55:25 +000013from distutils.core import Command
14from distutils.errors import *
Greg Ward13ae1c81999-03-22 14:55:25 +000015
16
Greg Ward1993f9a2000-02-18 00:13:53 +000017class build_py (Command):
Greg Ward13ae1c81999-03-22 14:55:25 +000018
Greg Ward37bc8152000-01-30 18:34:15 +000019 description = "\"build\" pure Python modules (copy to build directory)"
20
Greg Wardbbeceea2000-02-18 00:25:39 +000021 user_options = [
Greg Warde6916512000-03-01 01:19:37 +000022 ('build-lib=', 'd', "directory to \"build\" (copy) to"),
Greg Wardbbeceea2000-02-18 00:25:39 +000023 ]
Greg Ward13ae1c81999-03-22 14:55:25 +000024
25
Greg Warde01149c2000-02-18 00:35:22 +000026 def initialize_options (self):
Greg Warde6916512000-03-01 01:19:37 +000027 self.build_lib = None
Greg Ward71eb8641999-09-08 02:42:30 +000028 self.modules = None
29 self.package = None
Greg Ward17dc6e71999-09-21 18:22:34 +000030 self.package_dir = None
Greg Ward13ae1c81999-03-22 14:55:25 +000031
Greg Warde01149c2000-02-18 00:35:22 +000032 def finalize_options (self):
Greg Ward13ae1c81999-03-22 14:55:25 +000033 self.set_undefined_options ('build',
Greg Warde6916512000-03-01 01:19:37 +000034 ('build_lib', 'build_lib'))
Greg Ward17dc6e71999-09-21 18:22:34 +000035
36 # Get the distribution options that are aliases for build_py
37 # options -- list of packages and list of modules.
38 self.packages = self.distribution.packages
39 self.modules = self.distribution.py_modules
40 self.package_dir = self.distribution.package_dir
Greg Ward13ae1c81999-03-22 14:55:25 +000041
42
43 def run (self):
44
Greg Ward9b454431999-12-12 17:03:59 +000045 # XXX copy_file by default preserves atime and mtime. IMHO this is
46 # the right thing to do, but perhaps it should be an option -- in
47 # particular, a site administrator might want installed files to
48 # reflect the time of installation rather than the last
49 # modification time before the installed release.
50
51 # XXX copy_file by default preserves mode, which appears to be the
52 # wrong thing to do: if a file is read-only in the working
53 # directory, we want it to be installed read/write so that the next
54 # installation of the same module distribution can overwrite it
55 # without problems. (This might be a Unix-specific issue.) Thus
56 # we turn off 'preserve_mode' when copying to the build directory,
57 # since the build directory is supposed to be exactly what the
58 # installation will look like (ie. we preserve mode when
59 # installing).
Greg Ward13ae1c81999-03-22 14:55:25 +000060
61 # XXX copy_file does *not* preserve MacOS-specific file metadata.
62 # If this is a problem for building/installing Python modules, then
63 # we'll have to fix copy_file. (And what about installing scripts,
64 # when the time comes for that -- does MacOS use its special
65 # metadata to know that a file is meant to be interpreted by
66 # Python?)
67
Greg Ward13ae1c81999-03-22 14:55:25 +000068 infiles = []
69 outfiles = []
70 missing = []
71
Greg Ward17dc6e71999-09-21 18:22:34 +000072 # Two options control which modules will be installed: 'packages'
73 # and 'modules'. The former lets us work with whole packages, not
74 # specifying individual modules at all; the latter is for
75 # specifying modules one-at-a-time. Currently they are mutually
76 # exclusive: you can define one or the other (or neither), but not
77 # both. It remains to be seen how limiting this is.
Greg Ward13ae1c81999-03-22 14:55:25 +000078
Greg Ward17dc6e71999-09-21 18:22:34 +000079 # Dispose of the two "unusual" cases first: no pure Python modules
80 # at all (no problem, just return silently), and over-specified
81 # 'packages' and 'modules' options.
82
83 if not self.modules and not self.packages:
Greg Ward5d60fcf1999-08-29 18:19:01 +000084 return
Greg Ward17dc6e71999-09-21 18:22:34 +000085 if self.modules and self.packages:
86 raise DistutilsOptionError, \
87 "build_py: supplying both 'packages' and 'modules' " + \
Greg Ward9b454431999-12-12 17:03:59 +000088 "options is not allowed"
Greg Ward17dc6e71999-09-21 18:22:34 +000089
90 # Now we're down to two cases: 'modules' only and 'packages' only.
91 if self.modules:
92 self.build_modules ()
93 else:
94 self.build_packages ()
95
Greg Ward17dc6e71999-09-21 18:22:34 +000096 # run ()
Greg Ward5d60fcf1999-08-29 18:19:01 +000097
Greg Ward17dc6e71999-09-21 18:22:34 +000098
99 def get_package_dir (self, package):
100 """Return the directory, relative to the top of the source
101 distribution, where package 'package' should be found
102 (at least according to the 'package_dir' option, if any)."""
103
104 if type (package) is StringType:
105 path = string.split (package, '.')
106 elif type (package) in (TupleType, ListType):
Greg Ward631e6a01999-12-03 16:18:56 +0000107 path = list (package)
Greg Ward17dc6e71999-09-21 18:22:34 +0000108 else:
109 raise TypeError, "'package' must be a string, list, or tuple"
110
111 if not self.package_dir:
Greg Ward631e6a01999-12-03 16:18:56 +0000112 if path:
113 return apply (os.path.join, path)
114 else:
115 return ''
Greg Ward17dc6e71999-09-21 18:22:34 +0000116 else:
117 tail = []
118 while path:
119 try:
120 pdir = self.package_dir[string.join (path, '.')]
121 except KeyError:
122 tail.insert (0, path[-1])
123 del path[-1]
124 else:
125 tail.insert (0, pdir)
126 return apply (os.path.join, tail)
Greg Ward13ae1c81999-03-22 14:55:25 +0000127 else:
Greg Ward17dc6e71999-09-21 18:22:34 +0000128 # arg! everything failed, we might as well have not even
129 # looked in package_dir -- oh well
Greg Ward631e6a01999-12-03 16:18:56 +0000130 if tail:
131 return apply (os.path.join, tail)
132 else:
133 return ''
Greg Ward13ae1c81999-03-22 14:55:25 +0000134
Greg Ward17dc6e71999-09-21 18:22:34 +0000135 # get_package_dir ()
Greg Ward13ae1c81999-03-22 14:55:25 +0000136
Greg Ward13ae1c81999-03-22 14:55:25 +0000137
Greg Ward17dc6e71999-09-21 18:22:34 +0000138 def check_package (self, package, package_dir):
139
140 # Empty dir name means current directory, which we can probably
141 # assume exists. Also, os.path.exists and isdir don't know about
142 # my "empty string means current dir" convention, so we have to
143 # circumvent them.
144 if package_dir != "":
145 if not os.path.exists (package_dir):
146 raise DistutilsFileError, \
147 "package directory '%s' does not exist" % package_dir
148 if not os.path.isdir (package_dir):
149 raise DistutilsFileErorr, \
150 ("supposed package directory '%s' exists, " +
151 "but is not a directory") % package_dir
152
153 # Require __init__.py for all but the "root package"
Greg Ward631e6a01999-12-03 16:18:56 +0000154 if package:
Greg Ward17dc6e71999-09-21 18:22:34 +0000155 init_py = os.path.join (package_dir, "__init__.py")
156 if not os.path.isfile (init_py):
157 self.warn (("package init file '%s' not found " +
158 "(or not a regular file)") % init_py)
159 # check_package ()
160
161
162 def check_module (self, module, module_file):
163 if not os.path.isfile (module_file):
Greg Ward113e70e2000-02-02 00:07:14 +0000164 self.warn ("file %s (for module %s) not found" %
165 (module_file, module))
Greg Ward17dc6e71999-09-21 18:22:34 +0000166 return 0
167 else:
168 return 1
169
170 # check_module ()
171
172
Greg Ward2a612061999-09-29 12:44:57 +0000173 def find_package_modules (self, package, package_dir):
Greg Ward17dc6e71999-09-21 18:22:34 +0000174 module_files = glob (os.path.join (package_dir, "*.py"))
175 module_pairs = []
Greg Ward9b454431999-12-12 17:03:59 +0000176 setup_script = os.path.abspath (sys.argv[0])
177
Greg Ward17dc6e71999-09-21 18:22:34 +0000178 for f in module_files:
Greg Ward9b454431999-12-12 17:03:59 +0000179 abs_f = os.path.abspath (f)
180 if abs_f != setup_script:
181 module = os.path.splitext (os.path.basename (f))[0]
182 module_pairs.append ((module, f))
Greg Ward17dc6e71999-09-21 18:22:34 +0000183 return module_pairs
184
185
Greg Ward2a612061999-09-29 12:44:57 +0000186 def find_modules (self):
Greg Ward17dc6e71999-09-21 18:22:34 +0000187 # Map package names to tuples of useful info about the package:
188 # (package_dir, checked)
189 # package_dir - the directory where we'll find source files for
190 # this package
191 # checked - true if we have checked that the package directory
192 # is valid (exists, contains __init__.py, ... ?)
Greg Ward17dc6e71999-09-21 18:22:34 +0000193 packages = {}
194
Greg Ward2a612061999-09-29 12:44:57 +0000195 # List of (module, package, filename) tuples to return
196 modules = []
197
Greg Ward17dc6e71999-09-21 18:22:34 +0000198 # We treat modules-in-packages almost the same as toplevel modules,
199 # just the "package" for a toplevel is empty (either an empty
200 # string or empty list, depending on context). Differences:
201 # - don't check for __init__.py in directory for empty package
202
203 for module in self.modules:
204 path = string.split (module, '.')
205 package = tuple (path[0:-1])
Greg Ward2a612061999-09-29 12:44:57 +0000206 module_base = path[-1]
Greg Ward17dc6e71999-09-21 18:22:34 +0000207
208 try:
209 (package_dir, checked) = packages[package]
210 except KeyError:
211 package_dir = self.get_package_dir (package)
212 checked = 0
213
214 if not checked:
215 self.check_package (package, package_dir)
216 packages[package] = (package_dir, 1)
217
218 # XXX perhaps we should also check for just .pyc files
219 # (so greedy closed-source bastards can distribute Python
220 # modules too)
Greg Ward2a612061999-09-29 12:44:57 +0000221 module_file = os.path.join (package_dir, module_base + ".py")
Greg Ward17dc6e71999-09-21 18:22:34 +0000222 if not self.check_module (module, module_file):
223 continue
224
Greg Ward2a612061999-09-29 12:44:57 +0000225 modules.append ((module, package, module_file))
226
227 return modules
228
229 # find_modules ()
230
231
232 def get_source_files (self):
233
234 if self.modules:
235 modules = self.find_modules ()
236 else:
237 modules = []
238 for package in self.packages:
239 package_dir = self.get_package_dir (package)
240 m = self.find_package_modules (package, package_dir)
241 modules.extend (m)
242
243 # Both find_modules() and find_package_modules() return a list of
244 # tuples where the last element of each tuple is the filename --
245 # what a happy coincidence!
246 filenames = []
247 for module in modules:
248 filenames.append (module[-1])
249
250 return filenames
251
252
253 def build_module (self, module, module_file, package):
254
255 if type (package) is StringType:
256 package = string.split (package, '.')
Greg Ward631e6a01999-12-03 16:18:56 +0000257 elif type (package) not in (ListType, TupleType):
258 raise TypeError, \
259 "'package' must be a string (dot-separated), list, or tuple"
Greg Ward2a612061999-09-29 12:44:57 +0000260
261 # Now put the module source file into the "build" area -- this is
Greg Warde6916512000-03-01 01:19:37 +0000262 # easy, we just copy it somewhere under self.build_lib (the build
Greg Ward2a612061999-09-29 12:44:57 +0000263 # directory for Python source).
Greg Ward631e6a01999-12-03 16:18:56 +0000264 outfile_path = list (package)
Greg Ward2a612061999-09-29 12:44:57 +0000265 outfile_path.append (module + ".py")
Greg Warde6916512000-03-01 01:19:37 +0000266 outfile_path.insert (0, self.build_lib)
Greg Ward2a612061999-09-29 12:44:57 +0000267 outfile = apply (os.path.join, outfile_path)
268
269 dir = os.path.dirname (outfile)
270 self.mkpath (dir)
Greg Ward9b454431999-12-12 17:03:59 +0000271 self.copy_file (module_file, outfile, preserve_mode=0)
Greg Ward2a612061999-09-29 12:44:57 +0000272
273
274 def build_modules (self):
275
276 modules = self.find_modules()
277 for (module, package, module_file) in modules:
278
Greg Ward17dc6e71999-09-21 18:22:34 +0000279 # Now "build" the module -- ie. copy the source file to
Greg Warde6916512000-03-01 01:19:37 +0000280 # self.build_lib (the build directory for Python source).
Greg Ward2a612061999-09-29 12:44:57 +0000281 # (Actually, it gets copied to the directory for this package
Greg Warde6916512000-03-01 01:19:37 +0000282 # under self.build_lib.)
Greg Ward17dc6e71999-09-21 18:22:34 +0000283 self.build_module (module, module_file, package)
284
285 # build_modules ()
286
287
288 def build_packages (self):
289
290 for package in self.packages:
291 package_dir = self.get_package_dir (package)
292 self.check_package (package, package_dir)
293
294 # Get list of (module, module_file) tuples based on scanning
295 # the package directory. Here, 'module' is the *unqualified*
296 # module name (ie. no dots, no package -- we already know its
297 # package!), and module_file is the path to the .py file,
298 # relative to the current directory (ie. including
299 # 'package_dir').
Greg Ward2a612061999-09-29 12:44:57 +0000300 modules = self.find_package_modules (package, package_dir)
Greg Ward17dc6e71999-09-21 18:22:34 +0000301
302 # Now loop over the modules we found, "building" each one (just
Greg Warde6916512000-03-01 01:19:37 +0000303 # copy it to self.build_lib).
Greg Ward17dc6e71999-09-21 18:22:34 +0000304 for (module, module_file) in modules:
305 self.build_module (module, module_file, package)
306
307 # build_packages ()
Greg Ward13ae1c81999-03-22 14:55:25 +0000308
309# end class BuildPy