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

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

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