Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
build_py.py407 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 log10 11from ..core import Command12from ..errors import DistutilsFileError, DistutilsOptionError13from ..util import convert_path14 15 16class build_py(Command):17    description = "\"build\" pure Python modules (copy to build directory)"18 19    user_options = [20        ('build-lib=', 'd', "directory to \"build\" (copy) to"),21        ('compile', 'c', "compile .py to .pyc"),22        ('no-compile', None, "don't compile .py files [default]"),23        (24            'optimize=',25            'O',26            "also compile with optimization: -O1 for \"python -O\", "27            "-O2 for \"python -OO\", and -O0 to disable [default: -O0]",28        ),29        ('force', 'f', "forcibly build everything (ignore file timestamps)"),30    ]31 32    boolean_options = ['compile', 'force']33    negative_opt = {'no-compile': 'compile'}34 35    def initialize_options(self):36        self.build_lib = None37        self.py_modules = None38        self.package = None39        self.package_data = None40        self.package_dir = None41        self.compile = 042        self.optimize = 043        self.force = None44 45    def finalize_options(self):46        self.set_undefined_options(47            'build', ('build_lib', 'build_lib'), ('force', 'force')48        )49 50        # Get the distribution options that are aliases for build_py51        # options -- list of packages and list of modules.52        self.packages = self.distribution.packages53        self.py_modules = self.distribution.py_modules54        self.package_data = self.distribution.package_data55        self.package_dir = {}56        if self.distribution.package_dir:57            for name, path in self.distribution.package_dir.items():58                self.package_dir[name] = convert_path(path)59        self.data_files = self.get_data_files()60 61        # Ick, copied straight from install_lib.py (fancy_getopt needs a62        # type system!  Hell, *everything* needs a type system!!!)63        if not isinstance(self.optimize, int):64            try:65                self.optimize = int(self.optimize)66                assert 0 <= self.optimize <= 267            except (ValueError, AssertionError):68                raise DistutilsOptionError("optimize must be 0, 1, or 2")69 70    def run(self):71        # XXX copy_file by default preserves atime and mtime.  IMHO this is72        # the right thing to do, but perhaps it should be an option -- in73        # particular, a site administrator might want installed files to74        # reflect the time of installation rather than the last75        # modification time before the installed release.76 77        # XXX copy_file by default preserves mode, which appears to be the78        # wrong thing to do: if a file is read-only in the working79        # directory, we want it to be installed read/write so that the next80        # installation of the same module distribution can overwrite it81        # without problems.  (This might be a Unix-specific issue.)  Thus82        # we turn off 'preserve_mode' when copying to the build directory,83        # since the build directory is supposed to be exactly what the84        # installation will look like (ie. we preserve mode when85        # installing).86 87        # Two options control which modules will be installed: 'packages'88        # and 'py_modules'.  The former lets us work with whole packages, not89        # specifying individual modules at all; the latter is for90        # specifying modules one-at-a-time.91 92        if self.py_modules:93            self.build_modules()94        if self.packages:95            self.build_packages()96            self.build_package_data()97 98        self.byte_compile(self.get_outputs(include_bytecode=0))99 100    def get_data_files(self):101        """Generate list of '(package,src_dir,build_dir,filenames)' tuples"""102        data = []103        if not self.packages:104            return data105        for package in self.packages:106            # Locate package source directory107            src_dir = self.get_package_dir(package)108 109            # Compute package build directory110            build_dir = os.path.join(*([self.build_lib] + package.split('.')))111 112            # Length of path to strip from found files113            plen = 0114            if src_dir:115                plen = len(src_dir) + 1116 117            # Strip directory from globbed filenames118            filenames = [file[plen:] for file in self.find_data_files(package, src_dir)]119            data.append((package, src_dir, build_dir, filenames))120        return data121 122    def find_data_files(self, package, src_dir):123        """Return filenames for package's data files in 'src_dir'"""124        globs = self.package_data.get('', []) + self.package_data.get(package, [])125        files = []126        for pattern in globs:127            # Each pattern has to be converted to a platform-specific path128            filelist = glob.glob(129                os.path.join(glob.escape(src_dir), convert_path(pattern))130            )131            # Files that match more than one pattern are only added once132            files.extend([133                fn for fn in filelist if fn not in files and os.path.isfile(fn)134            ])135        return files136 137    def build_package_data(self):138        """Copy data files into build directory"""139        for _package, src_dir, build_dir, filenames in self.data_files:140            for filename in filenames:141                target = os.path.join(build_dir, filename)142                self.mkpath(os.path.dirname(target))143                self.copy_file(144                    os.path.join(src_dir, filename), target, preserve_mode=False145                )146 147    def get_package_dir(self, package):148        """Return the directory, relative to the top of the source149        distribution, where package 'package' should be found150        (at least according to the 'package_dir' option, if any)."""151        path = package.split('.')152 153        if not self.package_dir:154            if path:155                return os.path.join(*path)156            else:157                return ''158        else:159            tail = []160            while path:161                try:162                    pdir = self.package_dir['.'.join(path)]163                except KeyError:164                    tail.insert(0, path[-1])165                    del path[-1]166                else:167                    tail.insert(0, pdir)168                    return os.path.join(*tail)169            else:170                # Oops, got all the way through 'path' without finding a171                # match in package_dir.  If package_dir defines a directory172                # for the root (nameless) package, then fallback on it;173                # otherwise, we might as well have not consulted174                # package_dir at all, as we just use the directory implied175                # by 'tail' (which should be the same as the original value176                # of 'path' at this point).177                pdir = self.package_dir.get('')178                if pdir is not None:179                    tail.insert(0, pdir)180 181                if tail:182                    return os.path.join(*tail)183                else:184                    return ''185 186    def check_package(self, package, package_dir):187        # Empty dir name means current directory, which we can probably188        # assume exists.  Also, os.path.exists and isdir don't know about189        # my "empty string means current dir" convention, so we have to190        # circumvent them.191        if package_dir != "":192            if not os.path.exists(package_dir):193                raise DistutilsFileError(194                    "package directory '%s' does not exist" % package_dir195                )196            if not os.path.isdir(package_dir):197                raise DistutilsFileError(198                    "supposed package directory '%s' exists, "199                    "but is not a directory" % package_dir200                )201 202        # Directories without __init__.py are namespace packages (PEP 420).203        if package:204            init_py = os.path.join(package_dir, "__init__.py")205            if os.path.isfile(init_py):206                return init_py207 208        # Either not in a package at all (__init__.py not expected), or209        # __init__.py doesn't exist -- so don't return the filename.210        return None211 212    def check_module(self, module, module_file):213        if not os.path.isfile(module_file):214            log.warning("file %s (for module %s) not found", module_file, module)215            return False216        else:217            return True218 219    def find_package_modules(self, package, package_dir):220        self.check_package(package, package_dir)221        module_files = glob.glob(os.path.join(glob.escape(package_dir), "*.py"))222        modules = []223        setup_script = os.path.abspath(self.distribution.script_name)224 225        for f in module_files:226            abs_f = os.path.abspath(f)227            if abs_f != setup_script:228                module = os.path.splitext(os.path.basename(f))[0]229                modules.append((package, module, f))230            else:231                self.debug_print("excluding %s" % setup_script)232        return modules233 234    def find_modules(self):235        """Finds individually-specified Python modules, ie. those listed by236        module name in 'self.py_modules'.  Returns a list of tuples (package,237        module_base, filename): 'package' is a tuple of the path through238        package-space to the module; 'module_base' is the bare (no239        packages, no dots) module name, and 'filename' is the path to the240        ".py" file (relative to the distribution root) that implements the241        module.242        """243        # Map package names to tuples of useful info about the package:244        #    (package_dir, checked)245        # package_dir - the directory where we'll find source files for246        #   this package247        # checked - true if we have checked that the package directory248        #   is valid (exists, contains __init__.py, ... ?)249        packages = {}250 251        # List of (package, module, filename) tuples to return252        modules = []253 254        # We treat modules-in-packages almost the same as toplevel modules,255        # just the "package" for a toplevel is empty (either an empty256        # string or empty list, depending on context).  Differences:257        #   - don't check for __init__.py in directory for empty package258        for module in self.py_modules:259            path = module.split('.')260            package = '.'.join(path[0:-1])261            module_base = path[-1]262 263            try:264                (package_dir, checked) = packages[package]265            except KeyError:266                package_dir = self.get_package_dir(package)267                checked = 0268 269            if not checked:270                init_py = self.check_package(package, package_dir)271                packages[package] = (package_dir, 1)272                if init_py:273                    modules.append((package, "__init__", init_py))274 275            # XXX perhaps we should also check for just .pyc files276            # (so greedy closed-source bastards can distribute Python277            # modules too)278            module_file = os.path.join(package_dir, module_base + ".py")279            if not self.check_module(module, module_file):280                continue281 282            modules.append((package, module_base, module_file))283 284        return modules285 286    def find_all_modules(self):287        """Compute the list of all modules that will be built, whether288        they are specified one-module-at-a-time ('self.py_modules') or289        by whole packages ('self.packages').  Return a list of tuples290        (package, module, module_file), just like 'find_modules()' and291        'find_package_modules()' do."""292        modules = []293        if self.py_modules:294            modules.extend(self.find_modules())295        if self.packages:296            for package in self.packages:297                package_dir = self.get_package_dir(package)298                m = self.find_package_modules(package, package_dir)299                modules.extend(m)300        return modules301 302    def get_source_files(self):303        return [module[-1] for module in self.find_all_modules()]304 305    def get_module_outfile(self, build_dir, package, module):306        outfile_path = [build_dir] + list(package) + [module + ".py"]307        return os.path.join(*outfile_path)308 309    def get_outputs(self, include_bytecode=1):310        modules = self.find_all_modules()311        outputs = []312        for package, module, _module_file in modules:313            package = package.split('.')314            filename = self.get_module_outfile(self.build_lib, package, module)315            outputs.append(filename)316            if include_bytecode:317                if self.compile:318                    outputs.append(319                        importlib.util.cache_from_source(filename, optimization='')320                    )321                if self.optimize > 0:322                    outputs.append(323                        importlib.util.cache_from_source(324                            filename, optimization=self.optimize325                        )326                    )327 328        outputs += [329            os.path.join(build_dir, filename)330            for package, src_dir, build_dir, filenames in self.data_files331            for filename in filenames332        ]333 334        return outputs335 336    def build_module(self, module, module_file, package):337        if isinstance(package, str):338            package = package.split('.')339        elif not isinstance(package, (list, tuple)):340            raise TypeError(341                "'package' must be a string (dot-separated), list, or tuple"342            )343 344        # Now put the module source file into the "build" area -- this is345        # easy, we just copy it somewhere under self.build_lib (the build346        # directory for Python source).347        outfile = self.get_module_outfile(self.build_lib, package, module)348        dir = os.path.dirname(outfile)349        self.mkpath(dir)350        return self.copy_file(module_file, outfile, preserve_mode=0)351 352    def build_modules(self):353        modules = self.find_modules()354        for package, module, module_file in modules:355            # Now "build" the module -- ie. copy the source file to356            # self.build_lib (the build directory for Python source).357            # (Actually, it gets copied to the directory for this package358            # under self.build_lib.)359            self.build_module(module, module_file, package)360 361    def build_packages(self):362        for package in self.packages:363            # Get list of (package, module, module_file) tuples based on364            # scanning the package directory.  'package' is only included365            # in the tuple so that 'find_modules()' and366            # 'find_package_tuples()' have a consistent interface; it's367            # ignored here (apart from a sanity check).  Also, 'module' is368            # the *unqualified* module name (ie. no dots, no package -- we369            # already know its package!), and 'module_file' is the path to370            # the .py file, relative to the current directory371            # (ie. including 'package_dir').372            package_dir = self.get_package_dir(package)373            modules = self.find_package_modules(package, package_dir)374 375            # Now loop over the modules we found, "building" each one (just376            # copy it to self.build_lib).377            for package_, module, module_file in modules:378                assert package == package_379                self.build_module(module, module_file, package)380 381    def byte_compile(self, files):382        if sys.dont_write_bytecode:383            self.warn('byte-compiling is disabled, skipping.')384            return385 386        from ..util import byte_compile387 388        prefix = self.build_lib389        if prefix[-1] != os.sep:390            prefix = prefix + os.sep391 392        # XXX this code is essentially the same as the 'byte_compile()393        # method of the "install_lib" command, except for the determination394        # of the 'prefix' string.  Hmmm.395        if self.compile:396            byte_compile(397                files, optimize=0, force=self.force, prefix=prefix, dry_run=self.dry_run398            )399        if self.optimize > 0:400            byte_compile(401                files,402                optimize=self.optimize,403                force=self.force,404                prefix=prefix,405                dry_run=self.dry_run,406            )407 
codekingpro/portable-devtools · Team Ai