ยปCore Development>Code coverage>Lib/distutils/command/build_py.py

Python code coverage for Lib/distutils/command/build_py.py

#countcontent
1n/a"""distutils.command.build_py
2n/a
3n/aImplements the Distutils 'build_py' command."""
4n/a
5n/aimport os
6n/aimport importlib.util
7n/aimport sys
8n/afrom glob import glob
9n/a
10n/afrom distutils.core import Command
11n/afrom distutils.errors import *
12n/afrom distutils.util import convert_path, Mixin2to3
13n/afrom distutils import log
14n/a
15n/aclass build_py (Command):
16n/a
17n/a description = "\"build\" pure Python modules (copy to build directory)"
18n/a
19n/a user_options = [
20n/a ('build-lib=', 'd', "directory to \"build\" (copy) to"),
21n/a ('compile', 'c', "compile .py to .pyc"),
22n/a ('no-compile', None, "don't compile .py files [default]"),
23n/a ('optimize=', 'O',
24n/a "also compile with optimization: -O1 for \"python -O\", "
25n/a "-O2 for \"python -OO\", and -O0 to disable [default: -O0]"),
26n/a ('force', 'f', "forcibly build everything (ignore file timestamps)"),
27n/a ]
28n/a
29n/a boolean_options = ['compile', 'force']
30n/a negative_opt = {'no-compile' : 'compile'}
31n/a
32n/a def initialize_options(self):
33n/a self.build_lib = None
34n/a self.py_modules = None
35n/a self.package = None
36n/a self.package_data = None
37n/a self.package_dir = None
38n/a self.compile = 0
39n/a self.optimize = 0
40n/a self.force = None
41n/a
42n/a def finalize_options(self):
43n/a self.set_undefined_options('build',
44n/a ('build_lib', 'build_lib'),
45n/a ('force', 'force'))
46n/a
47n/a # Get the distribution options that are aliases for build_py
48n/a # options -- list of packages and list of modules.
49n/a self.packages = self.distribution.packages
50n/a self.py_modules = self.distribution.py_modules
51n/a self.package_data = self.distribution.package_data
52n/a self.package_dir = {}
53n/a if self.distribution.package_dir:
54n/a for name, path in self.distribution.package_dir.items():
55n/a self.package_dir[name] = convert_path(path)
56n/a self.data_files = self.get_data_files()
57n/a
58n/a # Ick, copied straight from install_lib.py (fancy_getopt needs a
59n/a # type system! Hell, *everything* needs a type system!!!)
60n/a if not isinstance(self.optimize, int):
61n/a try:
62n/a self.optimize = int(self.optimize)
63n/a assert 0 <= self.optimize <= 2
64n/a except (ValueError, AssertionError):
65n/a raise DistutilsOptionError("optimize must be 0, 1, or 2")
66n/a
67n/a def run(self):
68n/a # XXX copy_file by default preserves atime and mtime. IMHO this is
69n/a # the right thing to do, but perhaps it should be an option -- in
70n/a # particular, a site administrator might want installed files to
71n/a # reflect the time of installation rather than the last
72n/a # modification time before the installed release.
73n/a
74n/a # XXX copy_file by default preserves mode, which appears to be the
75n/a # wrong thing to do: if a file is read-only in the working
76n/a # directory, we want it to be installed read/write so that the next
77n/a # installation of the same module distribution can overwrite it
78n/a # without problems. (This might be a Unix-specific issue.) Thus
79n/a # we turn off 'preserve_mode' when copying to the build directory,
80n/a # since the build directory is supposed to be exactly what the
81n/a # installation will look like (ie. we preserve mode when
82n/a # installing).
83n/a
84n/a # Two options control which modules will be installed: 'packages'
85n/a # and 'py_modules'. The former lets us work with whole packages, not
86n/a # specifying individual modules at all; the latter is for
87n/a # specifying modules one-at-a-time.
88n/a
89n/a if self.py_modules:
90n/a self.build_modules()
91n/a if self.packages:
92n/a self.build_packages()
93n/a self.build_package_data()
94n/a
95n/a self.byte_compile(self.get_outputs(include_bytecode=0))
96n/a
97n/a def get_data_files(self):
98n/a """Generate list of '(package,src_dir,build_dir,filenames)' tuples"""
99n/a data = []
100n/a if not self.packages:
101n/a return data
102n/a for package in self.packages:
103n/a # Locate package source directory
104n/a src_dir = self.get_package_dir(package)
105n/a
106n/a # Compute package build directory
107n/a build_dir = os.path.join(*([self.build_lib] + package.split('.')))
108n/a
109n/a # Length of path to strip from found files
110n/a plen = 0
111n/a if src_dir:
112n/a plen = len(src_dir)+1
113n/a
114n/a # Strip directory from globbed filenames
115n/a filenames = [
116n/a file[plen:] for file in self.find_data_files(package, src_dir)
117n/a ]
118n/a data.append((package, src_dir, build_dir, filenames))
119n/a return data
120n/a
121n/a def find_data_files(self, package, src_dir):
122n/a """Return filenames for package's data files in 'src_dir'"""
123n/a globs = (self.package_data.get('', [])
124n/a + self.package_data.get(package, []))
125n/a files = []
126n/a for pattern in globs:
127n/a # Each pattern has to be converted to a platform-specific path
128n/a filelist = glob(os.path.join(src_dir, convert_path(pattern)))
129n/a # Files that match more than one pattern are only added once
130n/a files.extend([fn for fn in filelist if fn not in files
131n/a and os.path.isfile(fn)])
132n/a return files
133n/a
134n/a def build_package_data(self):
135n/a """Copy data files into build directory"""
136n/a lastdir = None
137n/a for package, src_dir, build_dir, filenames in self.data_files:
138n/a for filename in filenames:
139n/a target = os.path.join(build_dir, filename)
140n/a self.mkpath(os.path.dirname(target))
141n/a self.copy_file(os.path.join(src_dir, filename), target,
142n/a preserve_mode=False)
143n/a
144n/a def get_package_dir(self, package):
145n/a """Return the directory, relative to the top of the source
146n/a distribution, where package 'package' should be found
147n/a (at least according to the 'package_dir' option, if any)."""
148n/a path = package.split('.')
149n/a
150n/a if not self.package_dir:
151n/a if path:
152n/a return os.path.join(*path)
153n/a else:
154n/a return ''
155n/a else:
156n/a tail = []
157n/a while path:
158n/a try:
159n/a pdir = self.package_dir['.'.join(path)]
160n/a except KeyError:
161n/a tail.insert(0, path[-1])
162n/a del path[-1]
163n/a else:
164n/a tail.insert(0, pdir)
165n/a return os.path.join(*tail)
166n/a else:
167n/a # Oops, got all the way through 'path' without finding a
168n/a # match in package_dir. If package_dir defines a directory
169n/a # for the root (nameless) package, then fallback on it;
170n/a # otherwise, we might as well have not consulted
171n/a # package_dir at all, as we just use the directory implied
172n/a # by 'tail' (which should be the same as the original value
173n/a # of 'path' at this point).
174n/a pdir = self.package_dir.get('')
175n/a if pdir is not None:
176n/a tail.insert(0, pdir)
177n/a
178n/a if tail:
179n/a return os.path.join(*tail)
180n/a else:
181n/a return ''
182n/a
183n/a def check_package(self, package, package_dir):
184n/a # Empty dir name means current directory, which we can probably
185n/a # assume exists. Also, os.path.exists and isdir don't know about
186n/a # my "empty string means current dir" convention, so we have to
187n/a # circumvent them.
188n/a if package_dir != "":
189n/a if not os.path.exists(package_dir):
190n/a raise DistutilsFileError(
191n/a "package directory '%s' does not exist" % package_dir)
192n/a if not os.path.isdir(package_dir):
193n/a raise DistutilsFileError(
194n/a "supposed package directory '%s' exists, "
195n/a "but is not a directory" % package_dir)
196n/a
197n/a # Require __init__.py for all but the "root package"
198n/a if package:
199n/a init_py = os.path.join(package_dir, "__init__.py")
200n/a if os.path.isfile(init_py):
201n/a return init_py
202n/a else:
203n/a log.warn(("package init file '%s' not found " +
204n/a "(or not a regular file)"), init_py)
205n/a
206n/a # Either not in a package at all (__init__.py not expected), or
207n/a # __init__.py doesn't exist -- so don't return the filename.
208n/a return None
209n/a
210n/a def check_module(self, module, module_file):
211n/a if not os.path.isfile(module_file):
212n/a log.warn("file %s (for module %s) not found", module_file, module)
213n/a return False
214n/a else:
215n/a return True
216n/a
217n/a def find_package_modules(self, package, package_dir):
218n/a self.check_package(package, package_dir)
219n/a module_files = glob(os.path.join(package_dir, "*.py"))
220n/a modules = []
221n/a setup_script = os.path.abspath(self.distribution.script_name)
222n/a
223n/a for f in module_files:
224n/a abs_f = os.path.abspath(f)
225n/a if abs_f != setup_script:
226n/a module = os.path.splitext(os.path.basename(f))[0]
227n/a modules.append((package, module, f))
228n/a else:
229n/a self.debug_print("excluding %s" % setup_script)
230n/a return modules
231n/a
232n/a def find_modules(self):
233n/a """Finds individually-specified Python modules, ie. those listed by
234n/a module name in 'self.py_modules'. Returns a list of tuples (package,
235n/a module_base, filename): 'package' is a tuple of the path through
236n/a package-space to the module; 'module_base' is the bare (no
237n/a packages, no dots) module name, and 'filename' is the path to the
238n/a ".py" file (relative to the distribution root) that implements the
239n/a module.
240n/a """
241n/a # Map package names to tuples of useful info about the package:
242n/a # (package_dir, checked)
243n/a # package_dir - the directory where we'll find source files for
244n/a # this package
245n/a # checked - true if we have checked that the package directory
246n/a # is valid (exists, contains __init__.py, ... ?)
247n/a packages = {}
248n/a
249n/a # List of (package, module, filename) tuples to return
250n/a modules = []
251n/a
252n/a # We treat modules-in-packages almost the same as toplevel modules,
253n/a # just the "package" for a toplevel is empty (either an empty
254n/a # string or empty list, depending on context). Differences:
255n/a # - don't check for __init__.py in directory for empty package
256n/a for module in self.py_modules:
257n/a path = module.split('.')
258n/a package = '.'.join(path[0:-1])
259n/a module_base = path[-1]
260n/a
261n/a try:
262n/a (package_dir, checked) = packages[package]
263n/a except KeyError:
264n/a package_dir = self.get_package_dir(package)
265n/a checked = 0
266n/a
267n/a if not checked:
268n/a init_py = self.check_package(package, package_dir)
269n/a packages[package] = (package_dir, 1)
270n/a if init_py:
271n/a modules.append((package, "__init__", init_py))
272n/a
273n/a # XXX perhaps we should also check for just .pyc files
274n/a # (so greedy closed-source bastards can distribute Python
275n/a # modules too)
276n/a module_file = os.path.join(package_dir, module_base + ".py")
277n/a if not self.check_module(module, module_file):
278n/a continue
279n/a
280n/a modules.append((package, module_base, module_file))
281n/a
282n/a return modules
283n/a
284n/a def find_all_modules(self):
285n/a """Compute the list of all modules that will be built, whether
286n/a they are specified one-module-at-a-time ('self.py_modules') or
287n/a by whole packages ('self.packages'). Return a list of tuples
288n/a (package, module, module_file), just like 'find_modules()' and
289n/a 'find_package_modules()' do."""
290n/a modules = []
291n/a if self.py_modules:
292n/a modules.extend(self.find_modules())
293n/a if self.packages:
294n/a for package in self.packages:
295n/a package_dir = self.get_package_dir(package)
296n/a m = self.find_package_modules(package, package_dir)
297n/a modules.extend(m)
298n/a return modules
299n/a
300n/a def get_source_files(self):
301n/a return [module[-1] for module in self.find_all_modules()]
302n/a
303n/a def get_module_outfile(self, build_dir, package, module):
304n/a outfile_path = [build_dir] + list(package) + [module + ".py"]
305n/a return os.path.join(*outfile_path)
306n/a
307n/a def get_outputs(self, include_bytecode=1):
308n/a modules = self.find_all_modules()
309n/a outputs = []
310n/a for (package, module, module_file) in modules:
311n/a package = package.split('.')
312n/a filename = self.get_module_outfile(self.build_lib, package, module)
313n/a outputs.append(filename)
314n/a if include_bytecode:
315n/a if self.compile:
316n/a outputs.append(importlib.util.cache_from_source(
317n/a filename, optimization=''))
318n/a if self.optimize > 0:
319n/a outputs.append(importlib.util.cache_from_source(
320n/a filename, optimization=self.optimize))
321n/a
322n/a outputs += [
323n/a os.path.join(build_dir, filename)
324n/a for package, src_dir, build_dir, filenames in self.data_files
325n/a for filename in filenames
326n/a ]
327n/a
328n/a return outputs
329n/a
330n/a def build_module(self, module, module_file, package):
331n/a if isinstance(package, str):
332n/a package = package.split('.')
333n/a elif not isinstance(package, (list, tuple)):
334n/a raise TypeError(
335n/a "'package' must be a string (dot-separated), list, or tuple")
336n/a
337n/a # Now put the module source file into the "build" area -- this is
338n/a # easy, we just copy it somewhere under self.build_lib (the build
339n/a # directory for Python source).
340n/a outfile = self.get_module_outfile(self.build_lib, package, module)
341n/a dir = os.path.dirname(outfile)
342n/a self.mkpath(dir)
343n/a return self.copy_file(module_file, outfile, preserve_mode=0)
344n/a
345n/a def build_modules(self):
346n/a modules = self.find_modules()
347n/a for (package, module, module_file) in modules:
348n/a # Now "build" the module -- ie. copy the source file to
349n/a # self.build_lib (the build directory for Python source).
350n/a # (Actually, it gets copied to the directory for this package
351n/a # under self.build_lib.)
352n/a self.build_module(module, module_file, package)
353n/a
354n/a def build_packages(self):
355n/a for package in self.packages:
356n/a # Get list of (package, module, module_file) tuples based on
357n/a # scanning the package directory. 'package' is only included
358n/a # in the tuple so that 'find_modules()' and
359n/a # 'find_package_tuples()' have a consistent interface; it's
360n/a # ignored here (apart from a sanity check). Also, 'module' is
361n/a # the *unqualified* module name (ie. no dots, no package -- we
362n/a # already know its package!), and 'module_file' is the path to
363n/a # the .py file, relative to the current directory
364n/a # (ie. including 'package_dir').
365n/a package_dir = self.get_package_dir(package)
366n/a modules = self.find_package_modules(package, package_dir)
367n/a
368n/a # Now loop over the modules we found, "building" each one (just
369n/a # copy it to self.build_lib).
370n/a for (package_, module, module_file) in modules:
371n/a assert package == package_
372n/a self.build_module(module, module_file, package)
373n/a
374n/a def byte_compile(self, files):
375n/a if sys.dont_write_bytecode:
376n/a self.warn('byte-compiling is disabled, skipping.')
377n/a return
378n/a
379n/a from distutils.util import byte_compile
380n/a prefix = self.build_lib
381n/a if prefix[-1] != os.sep:
382n/a prefix = prefix + os.sep
383n/a
384n/a # XXX this code is essentially the same as the 'byte_compile()
385n/a # method of the "install_lib" command, except for the determination
386n/a # of the 'prefix' string. Hmmm.
387n/a if self.compile:
388n/a byte_compile(files, optimize=0,
389n/a force=self.force, prefix=prefix, dry_run=self.dry_run)
390n/a if self.optimize > 0:
391n/a byte_compile(files, optimize=self.optimize,
392n/a force=self.force, prefix=prefix, dry_run=self.dry_run)
393n/a
394n/aclass build_py_2to3(build_py, Mixin2to3):
395n/a def run(self):
396n/a self.updated_files = []
397n/a
398n/a # Base class code
399n/a if self.py_modules:
400n/a self.build_modules()
401n/a if self.packages:
402n/a self.build_packages()
403n/a self.build_package_data()
404n/a
405n/a # 2to3
406n/a self.run_2to3(self.updated_files)
407n/a
408n/a # Remaining base class code
409n/a self.byte_compile(self.get_outputs(include_bytecode=0))
410n/a
411n/a def build_module(self, module, module_file, package):
412n/a res = build_py.build_module(self, module, module_file, package)
413n/a if res[1]:
414n/a # file was copied
415n/a self.updated_files.append(res[0])
416n/a return res