0
点赞
收藏
分享

微信扫一扫

自定义python django编译器

夕颜合欢落 2022-03-11 阅读 48

此文件是根据python编译器修改而来,作为记录。可用于后续的自动化操作。

# -*- coding: UTF-8 -*-
# Desc   : python项目编译文件,该文件放在编译项目的根目录下,也可配置编译项目路径,需要配置见TODO
# TODO
# 1. 设置python版本
# 2. 需要跳过的文件夹
# 3. 不需要编译的py文件列表
# 4. 项目路径(默认该文件的目录)

import os
import sys
import importlib.util
import py_compile
import struct

__all__ = ["compile_dir", "compile_my_project"]


def _walk_dir(dir, ddir=None, maxlevels=10, quiet=0, exclude_dir=[], exclude_files=[]):
    if quiet < 2 and isinstance(dir, os.PathLike):
        dir = os.fspath(dir)
    if not quiet:
        print('Listing {!r}...'.format(dir))
    try:
        names = os.listdir(dir)
    except OSError:
        if quiet < 2:
            print("Can't list {!r}".format(dir))
        names = []
    names.sort()
    for name in names:
        if name == '__pycache__':
            continue
         # 排除路径
        if name in exclude_dir:
            continue
        fullname = os.path.join(dir, name)
        # 排除不需要编译文件
        if name in exclude_files:
            print("非编译文件:%s " % (name,))
            print("复制文件:%s 到 %s " % (name, os.path.join(ddir, name)))
            _copy_file(fullname, os.path.join(ddir, name))
            continue
        # 非py文件
        if os.path.isfile(fullname) and not name.endswith(".py"):
            print("非py文件:%s " % (name,))
            print("复制文件:%s %s " % (fullname, os.path.join(ddir, name)))
            os.makedirs(ddir, exist_ok=True)
            _copy_file(fullname, os.path.join(ddir, name))
            continue
        if ddir is not None:
            dfile = os.path.join(ddir, name)
        else:
            dfile = None
        if not os.path.isdir(fullname):
            yield fullname, ddir
        elif (maxlevels > 0 and name != os.curdir and name != os.pardir and
              os.path.isdir(fullname) and not os.path.islink(fullname)):
            yield from _walk_dir(fullname, ddir=dfile,
                                 maxlevels=maxlevels - 1, quiet=quiet, exclude_dir=exclude_dir,
                                 exclude_files=exclude_files)


def compile_dir(dir, maxlevels=10, ddir=None, force=False, rx=None,
                quiet=0, legacy=False, optimize=-1,
                invalidation_mode=None, exclude_dir=[],
                exclude_files=[]):
    """Byte-compile all modules in the given directory tree.

    Arguments (only dir is required):

    dir:       the directory to byte-compile
    maxlevels: maximum recursion level (default 10)
    ddir:      the directory that will be prepended to the path to the
               file as it is compiled into each byte-code file.
    force:     if True, force compilation, even if timestamps are up-to-date
    quiet:     full output with False or 0, errors only with 1,
               no output with 2
    legacy:    if True, produce legacy pyc paths instead of PEP 3147 paths
    optimize:  optimization level or -1 for level of the interpreter
    workers:   maximum number of parallel workers
    invalidation_mode: how the up-to-dateness of the pyc will be checked
    exclude_dir: 不进行编译的文件目录
    exclude_files: 不进行编译的文件
    """

    files_and_ddirs = _walk_dir(dir, quiet=quiet, maxlevels=maxlevels,
                                ddir=ddir, exclude_dir=exclude_dir, exclude_files=exclude_files)
    success = True

    for file, dfile in files_and_ddirs:
        if not compile_file(file, dfile, force, rx, quiet,
                            legacy, optimize, invalidation_mode):
            success = False
    return success


def compile_file(fullname, ddir=None, force=False, rx=None, quiet=0,
                 legacy=False, optimize=-1,
                 invalidation_mode=None):
    """Byte-compile one file.

    Arguments (only fullname is required):

    fullname:  the file to byte-compile
    ddir:      if given, the directory name compiled in to the
               byte-code file.
    force:     if True, force compilation, even if timestamps are up-to-date
    quiet:     full output with False or 0, errors only with 1,
               no output with 2
    legacy:    if True, produce legacy pyc paths instead of PEP 3147 paths
    optimize:  optimization level or -1 for level of the interpreter
    invalidation_mode: how the up-to-dateness of the pyc will be checked  3.6.10 版本不能要此参数
    """
    success = True
    if quiet < 2 and isinstance(fullname, os.PathLike):
        fullname = os.fspath(fullname)
    name = os.path.basename(fullname)
    if ddir is not None:
        dfile = os.path.join(ddir, name)
    else:
        dfile = None
    if rx is not None:
        mo = rx.search(fullname)
        if mo:
            return success
    if os.path.isfile(fullname):
        if legacy:
            cfile = fullname + 'c'
        else:
            if optimize >= 0:
                opt = optimize if optimize >= 1 else ''
                cfile = importlib.util.cache_from_source(
                    fullname, optimization=opt)
            else:
                # cfile = importlib.util.cache_from_source(fullname)
                cfile = dfile + "c"

        head, tail = name[:-3], name[-3:]
        if tail == '.py':
            if not force:
                try:
                    mtime = int(os.stat(fullname).st_mtime)
                    expect = struct.pack('<4sll', importlib.util.MAGIC_NUMBER,
                                         0, mtime)
                    print("dfile====", dfile)
                    with open(dfile, 'rb') as chandle:
                        actual = chandle.read(12)
                    if expect == actual:
                        print("success---------")
                        return success
                except OSError:
                    pass
            if not quiet:
                print('编译文件 {!r}...'.format(fullname))
                print("编译目标文件:", cfile)
            try:
                ok = py_compile.compile(fullname, cfile, dfile, True,
                                        optimize=optimize,
                                        )
            except py_compile.PyCompileError as err:
                success = False
                if quiet >= 2:
                    return success
                elif quiet:
                    print('*** Error compiling {!r}...'.format(fullname))
                else:
                    print('*** ', end='')
                # escape non-printable characters in msg
                msg = err.msg.encode(sys.stdout.encoding,
                                     errors='backslashreplace')
                msg = msg.decode(sys.stdout.encoding)
                print(msg)
            except (SyntaxError, UnicodeError, OSError) as e:
                success = False
                if quiet >= 2:
                    return success
                elif quiet:
                    print('*** Error compiling {!r}...'.format(fullname))
                else:
                    print('*** ', end='')
                print(e.__class__.__name__ + ':', e)
            else:
                if ok == 0:
                    success = False
    return success


def _copy_file(from_file, to_file):
    # 复制文件
    os.system("cp %s %s " % (from_file, to_file))


def _check_python_version(version):
    # 检校python环境版本
    if version == sys.version.split(" ")[0]:
        return True
    raise Exception(f"python 环境版本不对,期望版本为 {version}")


def compile_my_project(python_version, project_home=None,
                       exclude_dirs=["migrations", ],
                       exclude_files=["manage.py", "asgi.py", "wsgi.py", "settings.py"]):
    """

    :param python_version: python版本
    :param project_home: 项目路径(默认该文件的目录)
    :param exclude_dirs: 需要跳过的文件夹
    :param exclude_files: 不需要编译的py文件列表
    :return:
    """
    # 1.检校python环境版本
    _check_python_version(python_version)
    # 解析项目路径(文件存放的文件夹路径)
    if not project_home:
        project_home = os.path.dirname(os.path.abspath(__file__))
    exclude_files.append(os.path.basename(os.path.abspath(__file__)))

    # 项目文件夹名称
    project_name = project_home.split(os.sep)[-1]
    ##=== 编译文件存放路径(放在项目路径的同级目录下,新建 {project_name}_compile/{project_name})
    project_compile_home = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
                                        project_name + '_compile', project_name)
    print("编译源文件路径:", project_home)
    print("编译文件存放路径:", project_compile_home)
    os.makedirs(project_compile_home, exist_ok=True)
    compile_dir(dir=project_home, ddir=project_compile_home, force=True, exclude_dir=exclude_dirs,
                exclude_files=exclude_files)
    print("=====编译项目完成。。。")


if __name__ == '__main__':
    # 该文件放在编译项目的根目录下
    # 编译文件存放路径(放在项目路径的同级目录下,新建 {dir_name}_compile/{dir_name})
    # 0. TODO 设置python版本
    MY_PROJECT_PYTHON_VERSION = "3.6.10"
    # 0. TODO 需要跳过的文件夹
    exclude_dir = ["migrations", ]
    # 0. TODO 不需要编译的py文件列表
    exclude_files = ["manage.py", "asgi.py", "wsgi.py", "settings.py", "settings_prd.py"]

    compile_my_project(MY_PROJECT_PYTHON_VERSION, exclude_dirs=exclude_dir, exclude_files=exclude_files)

举报

相关推荐

0 条评论