Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
build_py.py405 linesDownload Raw Back to command
1"""distutils.command.build_py2 3Implements the Distutils 'build_py' command."""4 5import glob6import importlib.util7import os8import sys9from distutils._log import log10from typing import ClassVar11 12from ..core import Command13from ..errors import DistutilsFileError, DistutilsOptionError14from ..util import convert_path15 16 17class build_py(Command):18    description = "\"build\" pure Python modules (copy to build directory)"19 20    user_options = [21        ('build-lib=', 'd', "directory to \"build\" (copy) to"),22        ('compile', 'c', "compile .py to .pyc"),23        ('no-compile', None, "don't compile .py files [default]"),24        (25            'optimize=',26            'O',27            "also compile with optimization: -O1 for \"python -O\", "28            "-O2 for \"python -OO\", and -O0 to disable [default: -O0]",29        ),30        ('force', 'f', "forcibly build everything (ignore file timestamps)"),31    ]32 33    boolean_options: ClassVar[list[str]] = ['compile', 'force']34    negative_opt: ClassVar[dict[str, str]] = {'no-compile': 'compile'}35 36    def initialize_options(self):37        self.build_lib = None38        self.py_modules = None39        self.package = None40        self.package_data = None41        self.package_dir = None42        self.compile = False43        self.optimize = 044        self.force = None45 46    def finalize_options(self) -> None:47        self.set_undefined_options(48            'build', ('build_lib', 'build_lib'), ('force', 'force')49        )50 51        # Get the distribution options that are aliases for build_py52        # options -- list of packages and list of modules.53        self.packages = self.distribution.packages54        self.py_modules = self.distribution.py_modules55        self.package_data = self.distribution.package_data56        self.package_dir = {}57        if self.distribution.package_dir:58            for name, path in self.distribution.package_dir.items():59                self.package_dir[name] = convert_path(path)60        self.data_files = self.get_data_files()61 62        # Ick, copied straight from install_lib.py (fancy_getopt needs a63        # type system!  Hell, *everything* needs a type system!!!)64        if not isinstance(self.optimize, int):65            try:66                self.optimize = int(self.optimize)67                assert 0 <= self.optimize <= 268            except (ValueError, AssertionError):69                raise DistutilsOptionError("optimize must be 0, 1, or 2")70 71    def run(self) -> None:72        # XXX copy_file by default preserves atime and mtime.  IMHO this is73        # the right thing to do, but perhaps it should be an option -- in74        # particular, a site administrator might want installed files to75        # reflect the time of installation rather than the last76        # modification time before the installed release.77 78        # XXX copy_file by default preserves mode, which appears to be the79        # wrong thing to do: if a file is read-only in the working80        # directory, we want it to be installed read/write so that the next81        # installation of the same module distribution can overwrite it82        # without problems.  (This might be a Unix-specific issue.)  Thus83        # we turn off 'preserve_mode' when copying to the build directory,84        # since the build directory is supposed to be exactly what the85        # installation will look like (ie. we preserve mode when86        # installing).87 88        # Two options control which modules will be installed: 'packages'89        # and 'py_modules'.  The former lets us work with whole packages, not90        # specifying individual modules at all; the latter is for91        # specifying modules one-at-a-time.92 93        if self.py_modules:94            self.build_modules()95        if self.packages:96            self.build_packages()97            self.build_package_data()98 99        self.byte_compile(self.get_outputs(include_bytecode=False))100 101    def get_data_files(self):102        """Generate list of '(package,src_dir,build_dir,filenames)' tuples"""103        data = []104        if not self.packages:105            return data106        for package in self.packages:107            # Locate package source directory108            src_dir = self.get_package_dir(package)109 110            # Compute package build directory111            build_dir = os.path.join(*([self.build_lib] + package.split('.')))112 113            # Length of path to strip from found files114            plen = 0115            if src_dir:116                plen = len(src_dir) + 1117 118            # Strip directory from globbed filenames119            filenames = [file[plen:] for file in self.find_data_files(package, src_dir)]120            data.append((package, src_dir, build_dir, filenames))121        return data122 123    def find_data_files(self, package, src_dir):124        """Return filenames for package's data files in 'src_dir'"""125        globs = self.package_data.get('', []) + self.package_data.get(package, [])126        files = []127        for pattern in globs:128            # Each pattern has to be converted to a platform-specific path129            filelist = glob.glob(130                os.path.join(glob.escape(src_dir), convert_path(pattern))131            )132            # Files that match more than one pattern are only added once133            files.extend([134                fn for fn in filelist if fn not in files and os.path.isfile(fn)135            ])136        return files137 138    def build_package_data(self) -> None:139        """Copy data files into build directory"""140        for _package, src_dir, build_dir, filenames in self.data_files:141            for filename in filenames:142                target = os.path.join(build_dir, filename)143                self.mkpath(os.path.dirname(target))144                self.copy_file(145                    os.path.join(src_dir, filename), target, preserve_mode=False146                )147 148    def get_package_dir(self, package):149        """Return the directory, relative to the top of the source150        distribution, where package 'package' should be found151        (at least according to the 'package_dir' option, if any)."""152        path = package.split('.')153 154        if not self.package_dir:155            if path:156                return os.path.join(*path)157            else:158                return ''159        else:160            tail = []161            while path:162                try:163                    pdir = self.package_dir['.'.join(path)]164                except KeyError:165                    tail.insert(0, path[-1])166                    del path[-1]167                else:168                    tail.insert(0, pdir)169                    return os.path.join(*tail)170            else:171                # Oops, got all the way through 'path' without finding a172                # match in package_dir.  If package_dir defines a directory173                # for the root (nameless) package, then fallback on it;174                # otherwise, we might as well have not consulted175                # package_dir at all, as we just use the directory implied176                # by 'tail' (which should be the same as the original value177                # of 'path' at this point).178                pdir = self.package_dir.get('')179                if pdir is not None:180                    tail.insert(0, pdir)181 182                if tail:183                    return os.path.join(*tail)184                else:185                    return ''186 187    def check_package(self, package, package_dir):188        # Empty dir name means current directory, which we can probably189        # assume exists.  Also, os.path.exists and isdir don't know about190        # my "empty string means current dir" convention, so we have to191        # circumvent them.192        if package_dir != "":193            if not os.path.exists(package_dir):194                raise DistutilsFileError(195                    f"package directory '{package_dir}' does not exist"196                )197            if not os.path.isdir(package_dir):198                raise DistutilsFileError(199                    f"supposed package directory '{package_dir}' exists, "200                    "but is not a directory"201                )202 203        # Directories without __init__.py are namespace packages (PEP 420).204        if package:205            init_py = os.path.join(package_dir, "__init__.py")206            if os.path.isfile(init_py):207                return init_py208 209        # Either not in a package at all (__init__.py not expected), or210        # __init__.py doesn't exist -- so don't return the filename.211        return None212 213    def check_module(self, module, module_file):214        if not os.path.isfile(module_file):215            log.warning("file %s (for module %s) not found", module_file, module)216            return False217        else:218            return True219 220    def find_package_modules(self, package, package_dir):221        self.check_package(package, package_dir)222        module_files = glob.glob(os.path.join(glob.escape(package_dir), "*.py"))223        modules = []224        setup_script = os.path.abspath(self.distribution.script_name)225 226        for f in module_files:227            abs_f = os.path.abspath(f)228            if abs_f != setup_script:229                module = os.path.splitext(os.path.basename(f))[0]230                modules.append((package, module, f))231            else:232                self.debug_print(f"excluding {setup_script}")233        return modules234 235    def find_modules(self):236        """Finds individually-specified Python modules, ie. those listed by237        module name in 'self.py_modules'.  Returns a list of tuples (package,238        module_base, filename): 'package' is a tuple of the path through239        package-space to the module; 'module_base' is the bare (no240        packages, no dots) module name, and 'filename' is the path to the241        ".py" file (relative to the distribution root) that implements the242        module.243        """244        # Map package names to tuples of useful info about the package:245        #    (package_dir, checked)246        # package_dir - the directory where we'll find source files for247        #   this package248        # checked - true if we have checked that the package directory249        #   is valid (exists, contains __init__.py, ... ?)250        packages = {}251 252        # List of (package, module, filename) tuples to return253        modules = []254 255        # We treat modules-in-packages almost the same as toplevel modules,256        # just the "package" for a toplevel is empty (either an empty257        # string or empty list, depending on context).  Differences:258        #   - don't check for __init__.py in directory for empty package259        for module in self.py_modules:260            path = module.split('.')261            package = '.'.join(path[0:-1])262            module_base = path[-1]263 264            try:265                (package_dir, checked) = packages[package]266            except KeyError:267                package_dir = self.get_package_dir(package)268                checked = False269 270            if not checked:271                init_py = self.check_package(package, package_dir)272                packages[package] = (package_dir, 1)273                if init_py:274                    modules.append((package, "__init__", init_py))275 276            # XXX perhaps we should also check for just .pyc files277            # (so greedy closed-source bastards can distribute Python278            # modules too)279            module_file = os.path.join(package_dir, module_base + ".py")280            if not self.check_module(module, module_file):281                continue282 283            modules.append((package, module_base, module_file))284 285        return modules286 287    def find_all_modules(self):288        """Compute the list of all modules that will be built, whether289        they are specified one-module-at-a-time ('self.py_modules') or290        by whole packages ('self.packages').  Return a list of tuples291        (package, module, module_file), just like 'find_modules()' and292        'find_package_modules()' do."""293        modules = []294        if self.py_modules:295            modules.extend(self.find_modules())296        if self.packages:297            for package in self.packages:298                package_dir = self.get_package_dir(package)299                m = self.find_package_modules(package, package_dir)300                modules.extend(m)301        return modules302 303    def get_source_files(self):304        return [module[-1] for module in self.find_all_modules()]305 306    def get_module_outfile(self, build_dir, package, module):307        outfile_path = [build_dir] + list(package) + [module + ".py"]308        return os.path.join(*outfile_path)309 310    def get_outputs(self, include_bytecode: bool = True) -> list[str]:311        modules = self.find_all_modules()312        outputs = []313        for package, module, _module_file in modules:314            package = package.split('.')315            filename = self.get_module_outfile(self.build_lib, package, module)316            outputs.append(filename)317            if include_bytecode:318                if self.compile:319                    outputs.append(320                        importlib.util.cache_from_source(filename, optimization='')321                    )322                if self.optimize > 0:323                    outputs.append(324                        importlib.util.cache_from_source(325                            filename, optimization=self.optimize326                        )327                    )328 329        outputs += [330            os.path.join(build_dir, filename)331            for package, src_dir, build_dir, filenames in self.data_files332            for filename in filenames333        ]334 335        return outputs336 337    def build_module(self, module, module_file, package):338        if isinstance(package, str):339            package = package.split('.')340        elif not isinstance(package, (list, tuple)):341            raise TypeError(342                "'package' must be a string (dot-separated), list, or tuple"343            )344 345        # Now put the module source file into the "build" area -- this is346        # easy, we just copy it somewhere under self.build_lib (the build347        # directory for Python source).348        outfile = self.get_module_outfile(self.build_lib, package, module)349        dir = os.path.dirname(outfile)350        self.mkpath(dir)351        return self.copy_file(module_file, outfile, preserve_mode=False)352 353    def build_modules(self) -> None:354        modules = self.find_modules()355        for package, module, module_file in modules:356            # Now "build" the module -- ie. copy the source file to357            # self.build_lib (the build directory for Python source).358            # (Actually, it gets copied to the directory for this package359            # under self.build_lib.)360            self.build_module(module, module_file, package)361 362    def build_packages(self) -> None:363        for package in self.packages:364            # Get list of (package, module, module_file) tuples based on365            # scanning the package directory.  'package' is only included366            # in the tuple so that 'find_modules()' and367            # 'find_package_tuples()' have a consistent interface; it's368            # ignored here (apart from a sanity check).  Also, 'module' is369            # the *unqualified* module name (ie. no dots, no package -- we370            # already know its package!), and 'module_file' is the path to371            # the .py file, relative to the current directory372            # (ie. including 'package_dir').373            package_dir = self.get_package_dir(package)374            modules = self.find_package_modules(package, package_dir)375 376            # Now loop over the modules we found, "building" each one (just377            # copy it to self.build_lib).378            for package_, module, module_file in modules:379                assert package == package_380                self.build_module(module, module_file, package)381 382    def byte_compile(self, files) -> None:383        if sys.dont_write_bytecode:384            self.warn('byte-compiling is disabled, skipping.')385            return386 387        from ..util import byte_compile388 389        prefix = self.build_lib390        if prefix[-1] != os.sep:391            prefix = prefix + os.sep392 393        # XXX this code is essentially the same as the 'byte_compile()394        # method of the "install_lib" command, except for the determination395        # of the 'prefix' string.  Hmmm.396        if self.compile:397            byte_compile(files, optimize=0, force=self.force, prefix=prefix)398        if self.optimize > 0:399            byte_compile(400                files,401                optimize=self.optimize,402                force=self.force,403                prefix=prefix,404            )405 
codekingpro/portable-devtools · Team Ai