diff --git a/Doc/dist/dist.tex b/Doc/dist/dist.tex index 7b91809a747..b692c069bf6 100644 --- a/Doc/dist/dist.tex +++ b/Doc/dist/dist.tex @@ -652,6 +652,55 @@ setup(... \end{verbatim} +\subsection{Installing Package Data} + +Often, additional files need to be installed into a package. These +files are often data that's closely related to the package's +implementation, or text files containing documentation that might be +of interest to programmers using the package. These files are called +\dfn{package data}. + +Package data can be added to packages using the \code{package_data} +keyword argument to the \function{setup()} function. The value must +be a mapping from package name to a list of relative path names that +should be copied into the package. The paths are interpreted as +relative to the directory containing the package (information from the +\code{package_dir} mapping is used if appropriate); that is, the files +are expected to be part of the package in the source directories. +They may contain glob patterns as well. + +The path names may contain directory portions; any necessary +directories will be created in the installation. + +For example, if a package should contain a subdirectory with several +data files, the files can be arranged like this in the source tree: + +\begin{verbatim} +setup.py +src/ + mypkg/ + __init__.py + module.py + data/ + tables.dat + spoons.dat + forks.dat +\end{verbatim} + +The corresponding call to \function{setup()} might be: + +\begin{verbatim} +setup(..., + packages=['mypkg'], + package_dir={'mypkg': 'src/mypkg'}, + package_data={'pypkg': ['data/*.dat']}, + ) +\end{verbatim} + + +\versionadded{2.4} + + \subsection{Installing Additional Files} The \option{data\_files} option can be used to specify additional diff --git a/Lib/distutils/command/build_py.py b/Lib/distutils/command/build_py.py index 6c007c6987b..329b55a9d56 100644 --- a/Lib/distutils/command/build_py.py +++ b/Lib/distutils/command/build_py.py @@ -37,6 +37,7 @@ class build_py (Command): self.build_lib = None self.py_modules = None self.package = None + self.package_data = None self.package_dir = None self.compile = 0 self.optimize = 0 @@ -51,6 +52,8 @@ class build_py (Command): # options -- list of packages and list of modules. self.packages = self.distribution.packages self.py_modules = self.distribution.py_modules + self.package_data = self.distribution.package_data + self.data_files = self.get_data_files() self.package_dir = {} if self.distribution.package_dir: for name, path in self.distribution.package_dir.items(): @@ -92,11 +95,53 @@ class build_py (Command): self.build_modules() if self.packages: self.build_packages() + self.build_package_data() self.byte_compile(self.get_outputs(include_bytecode=0)) # run () + def get_data_files (self): + """Generate list of '(package,src_dir,build_dir,filenames)' tuples""" + data = [] + for package in self.packages: + # Locate package source directory + src_dir = self.get_package_dir(package) + + # Compute package build directory + build_dir = os.path.join(*([self.build_lib] + package.split('.'))) + + # Length of path to strip from found files + plen = len(src_dir)+1 + + # Strip directory from globbed filenames + filenames = [ + file[plen:] for file in self.find_data_files(package, src_dir) + ] + data.append((package, src_dir, build_dir, filenames)) + return data + + def find_data_files (self, package, src_dir): + """Return filenames for package's data files in 'src_dir'""" + globs = (self.package_data.get('', []) + + self.package_data.get(package, [])) + files = [] + for pattern in globs: + # Each pattern has to be converted to a platform-specific path + filelist = glob(os.path.join(src_dir, convert_path(pattern))) + # Files that match more than one pattern are only added once + files.extend([fn for fn in filelist if fn not in files]) + return files + + def build_package_data (self): + """Copy data files into build directory""" + lastdir = None + for package, src_dir, build_dir, filenames in self.data_files: + for filename in filenames: + target = os.path.join(build_dir, filename) + self.mkpath(os.path.dirname(target)) + self.copy_file(os.path.join(src_dir, filename), target, + preserve_mode=False) def get_package_dir (self, package): """Return the directory, relative to the top of the source @@ -304,6 +349,12 @@ class build_py (Command): if self.optimize > 0: outputs.append(filename + "o") + outputs += [ + os.path.join(build_dir, filename) + for package, src_dir, build_dir, filenames in self.data_files + for filename in filenames + ] + return outputs diff --git a/Lib/distutils/dist.py b/Lib/distutils/dist.py index 2795b7bc830..7d0a7bad3cd 100644 --- a/Lib/distutils/dist.py +++ b/Lib/distutils/dist.py @@ -158,6 +158,7 @@ class Distribution: # than of the Distribution itself. We provide aliases for them in # Distribution as a convenience to the developer. self.packages = None + self.package_data = {} self.package_dir = None self.py_modules = None self.libraries = None