#coding:utf-8
import os
import sys
import pwd
import grp
import time
import sunhpc
import shutil
import tarfile
import tempfile
import datetime
import subprocess

class RPMBUILD(object):

    def __init__(self, cmd, name, root, source, prefix, 
            version, release, arch, pyver, spec, tarfile, outdir, quiet):

        self.cmd        = cmd
        self.name       = name
        self.root       = root
        self.arch       = arch
        self.quiet      = quiet
        self.srcdir     = source
        self.outdir     = outdir
        self.prefix     = prefix
        self.version    = version
        self.release    = release
        self.pyver      = pyver
        self.specfile   = spec
        self.tarfile    = tarfile

        self.build      = os.path.join(self.root, 'BUILD')
        self.rpms       = os.path.join(self.root, 'RPMS')
        self.srpms      = os.path.join(self.root, 'SRPMS')
        self.spec       = os.path.join(self.root, 'SPECS')
        self.source     = os.path.join(self.root, 'SOURCES')
        self.buildroot  = os.path.join(self.root, 'BUILDROOT')

    def makedirs(self):
        rpmdirs = ['BUILD', 'RPMS', 'SOURCES', 'SPECS', 'SRPMS', 'BUILDROOT']
        [ os.makedirs(os.path.join(self.root, x))
            for x in rpmdirs if not os.path.exists(x)]

    def makeMacros(self):
        macros = os.path.join(self.root, '.rpmmacros')

        '''
        %_signature gpg
        %_gpg_path ~/.gnupg
        %_gpg_name xiubuzhe@sina.com
        %_gpgbin /usr/bin/gpg2
        %_gpg_digest_algo sha512%_topdir %{getenv:HOME}/rpmbuild
        '''
        with open(macros, 'w') as f:
            f.write('%%_topdir %s\n' % self.root)
            f.write('%debug_package %{nil}\n')

    def createHeader(self):
        # %_sourcedir SOURCE
        # %_specdir   SPECS
        # $RPM_SOURCE_DIR ="/root/rpmbuild/SOURCES"
        # $RPM_BUILD_DIR  ="/root/rpmbuild/BUILD"
        # $RPM_BUILD_ROOT ="/root/rpmbuild/BUILDROOT/test-0.1-1.x86_64"

        header  = '%%define _prefix %s\n' % self.prefix
        # 软件包的内容概要
        header += 'Summary: %s\n' % self.name
        # 软件包的名称
        header += 'Name: %s\n' % self.name
        # 软件包的版本
        header += 'Version: %s\n' % self.version
        # 软件包的实际版本号
        header += 'Release: %s\n' % self.release
        # 软件包的架构
        header += 'BuildArch: %s\n' % self.arch
        # 软件的授权方式和地址.
        header += 'License: %s\n' % self.name
        header += 'URL: https://www.sunhpc.com\n'
        # 发行商或者打包组织的信息
        header += 'Vendor: %s\n' % self.name
        # 软件分组
        header += 'Group: System Environment/Base\n'
        # 源代码包，可以带多个source0，source1等源，后面可以用%{source},%{source1}引用
        # Source0: http://nginx.org/download/%{name}-%{version}.tar.gz
        header += 'Source: %s.tar.gz\n' % self.name
        header += 'Buildroot: %s\n' % self.buildroot
        header += 'Prefix: %{_prefix}\n'
        header += 'Packager: Sunhpc cluster for %s\n' % self.name
        header += 'AutoReqProv: no\n'

        # 需要依赖的软件包
        # BuildRequires: zlib-devel

        # 指明本软件一些特定的功能，以便与其他rpm识别
        # Provides: webserver

        # 判断语句
        # if 0%{?aaaa} 使用0是先假设没有aaa变量.
        #   定义了aaa变量执行此处
        # else
        #   没有定义aaa变量执行此处

        return header

    def createDesc(self):
        text = '\n%description\n'
        text += 'The %s spec file make by Sunhpc OS\n' % self.name
        return text

    def createPrep(self):
        text = '\n%prep\n'
        text += '%setup -q -n %{name}\n'
        return text

    def createBuild(self):
        text = '\n%build\n'
        return text

    def createInstall(self):
        """制作Spec Install部分"""
        # 如果制作python,需要将/usr/bin/python 替换成python3版本命令.
        tree = sunhpc.core.files.RPMbuild(self.srcdir)
        root = tree.getRootMode()
        name = tree.getRootName()

        dirslist, filelist, usefiles = [], [], []
        for path in tree.getDirs():

            Dirsmode = tree.getPathMode(os.path.join(self.srcdir, path))
            PathUid  = pwd.getpwuid(Dirsmode[1]).pw_name
            PathGid  = grp.getgrgid(Dirsmode[2]).gr_name
            pathmode = 'install -d -m 0%s -o %s -g %s \"$RPM_BUILD_ROOT%%{_prefix}/%s\"\n' % \
                            (Dirsmode[0], PathUid, PathGid, path)
            if path: dirslist.append(pathmode)

            for f in tree.getFiles(path):

                Filename = f.getName()
                Filemode = f.getFileMode()
                FileUid  = pwd.getpwuid(Filemode[1]).pw_name
                FileGid  = grp.getgrgid(Filemode[2]).gr_name

                fullpath = os.path.join(path, Filename)
                #fullname = '%%attr(0%s, %s, %s) %%{_prefix}/%s' % (Filemode[0], FileUid, FileGid, fullpath)
                fullname = 'install -m 0%s -o %s -g %s \"%s\" \"$RPM_BUILD_ROOT%%{_prefix}/%s\"\n' % \
                                (Filemode[0], FileUid, FileGid, fullpath, fullpath)

                if fullname not in filelist: filelist.append(fullname)

                if fullpath not in usefiles: usefiles.append("/%s" % fullpath)

        # 提供给%files使用.
        self.usefiles = usefiles
        self.usepaths = dirslist

        # 配置根目录用户和权限.
        RootUid  = pwd.getpwuid(root[1]).pw_name
        RootGid  = grp.getgrgid(root[2]).gr_name
        rootdirs = 'install -d -m 0%s -o %s -g %s \"$RPM_BUILD_ROOT%%{_prefix}\"\n' % (root[0], RootUid, RootGid)

        text = ["\n%install\n"]
        text.append(rootdirs)
        text.extend(dirslist)
        text.extend(filelist)

        # 执行buildroot时查看目录结构.
        #text.append('\ntree %s\n' % self.buildroot)

        return ''.join(text)
    def createClean(self):
        # 清理临时文件
        text  = '\n[ "$RPM_BUILD_ROOT" != "/" ] && rm -rf "$RPM_BUILD_ROOT"\n'
        text += 'rm -rf $RPM_BUILD_DIR/%{name}-%{version}\n'
        return text

    def createPre(self):
        # rpm安装前执行的脚本.
        text  = "\n%pre\n"
        return text

    def createPost(self):
        # rpm安装后执行的脚本
        text  = "\n%post\n"
        return text

    def createPreun(self):
        # rpm卸载前执行的脚本
        text  = "\n%pre\n"
        return text
    def createPostun(self):
        # rpm卸载后执行的脚本
        text  = "\n%postun\n"
        return text

    def createFiles(self):
        """包含所以编译阶段中所产生的文件"""

        text  = "\n%files\n"
        # text += "%{_prefix}\n"
        # 不能直接包含这个根目录,软件如果不涉及到项系统目录写文件则没有问题
        # 但软件中的文件如果要放入到系统的目录,那么在安装时候
        # 就会产生文件冲突,无法安装.
        # 原因是当软件要卸载的时候,会将这里包含的所有文件进行删除
        # 所以如果你包含了系统目录,那么在卸载软件的时候必然会将系统目录一并删除
        # 那绝对是致命的问题.所以我们以每个文件作为包含对象.

        # 还需要注意一个问题,个人python程序,在rpmbuild的时候压缩包内只有源码.py文件
        # 但在执行rpmbuild的时候在buildroot下会产生pyc,pyo文件,所以在打包文件时候
        # 一般会报错需要包含pyc,pyo文件在files里面.一直无解.
        # 正常如果会产生pyc,pyo文件应该时在第一次编译时产生呀(BUILD)目录里面产生
        # 然后在install的时候将BUILD里面的所有文件安装到BUILDROOT里面,
        # 测试过BUILD目录一直不产生pyc,pyo,但是在BUILDROOT里面却产生.
        #   解析1: 源码中可能需要有make动作产生相关pyc,pyo文件
        #   解析2: SPEC配置文件没有build阶段,这个阶段需要结合软件自身的configure和make.
        for f in self.usefiles:
            (dname, fname) = os.path.split(f)
            filename = '%s' % os.path.join(self.prefix, dname[1:], fname)
            text += '%s\n' % filename

            '''
            name, ext = os.path.splitext(filename)
            if ext == '.py' and self.pyver == '2':
                text += '%so\n' % filename
                text += '%sc\n' % filename

            (pydir, init) = os.path.split(f)
            if ext == '.py' and self.pyver == '3':
                text += '\n%%dir %s/__pycache__\n' % pydir
                text += '\n%s/__pycache__/*\n' % pydir
                #text += '\n%s/__pycache__/__init__.cpython-36.opt-1.pyc\n' % pydir
                #text += '\n%s/__pycache__/__init__.cpython-36.pyc\n' % pydir
            '''
        return text

    def excludeFiles(self):
        # 列出不想打包到rpm中的文件,指定的文件不存在则会报错.
        text  = "\n%exclude\n"
        return text

    def createLogs(self):
        tims  = time.strftime("%a %b %d %Y", time.localtime())
        text  = "\n%changelog\n"
        text += "* %s kelvin <kelvin@sunhpc.com>\n" % tims
        text += "- Specfile auto-generated by sunhpcOS\n"
        return text

    def makeTargz(self, output_name):
        """打包目录成tar.gz格式
        :param output_name : 压缩文件名
        :param source_path : 需要打包的目录
        :return: bool
        """
        if not self.quiet:
            self.cmd.msg('\tCompress %s.tar.gz packages, wait....' % self.name)
        try:
            with tarfile.open(output_name, "w:gz") as tar:
                tar.add(self.srcdir, arcname=self.name)
        except Exception as e:
            self.cmd.abort('The %s.tar.gz compress pacakges failed. %s' % (self.name, repr(e)))

    def run(self):
        # 创建rpmbuild基础目录.
        self.makedirs()

        # 创建Rpmbuild 基础配置文件.
        self.makeMacros()

        # 配置SPEC文件内容
        contents  = self.createHeader()
        contents += self.createDesc()
        contents += self.createPrep()
        # contents += self.createBuild()
        contents += self.createInstall()
        # contents += self.createClean()
        # contents += self.createPre()     #rpm安装前执行的脚本
        contents += self.createPost()      #rpm安装后执行的脚本
        # contents += self.createPreun()   #rpm卸载前执行的脚本
        # contents += self.createPostun()  #rpm卸载后执行的脚本
        contents += self.createFiles()
        # contents += self.excludeFiles()  #列出不想打包到rpm中的文件,指定文件不存在,则会报错.
        contents += self.createLogs()

        # 写入Spec文件到SPEC目录.
        spec_file = os.path.join(self.spec, '%s.spec' % self.name)

        if self.specfile and self.tarfile:
            with open(self.specfile, 'r') as f:
                contents = f.read()

        with open(spec_file, 'w') as f:
            f.write(contents)

        # 将源文件夹压缩成tar.gz文件放入到SOURCE目录中.
        tar_file = os.path.join(self.source, '%s.tar.gz' % self.name)

        # 如果压缩包存在,则先删除.
        if os.path.isfile(tar_file):
            os.rmove(tar_file)

        if self.tarfile:
            dirname, srcname = os.path.split(self.tarfile)
            dstname = os.path.join(self.source, srcname)
            shutil.copyfile(self.tarfile, dstname)
        else:
            # 将路径内容进行打包.
            self.makeTargz(tar_file)

        # 设置Home变量
        HOME = os.environ['HOME']
        os.environ['HOME'] = self.root
        cwd = os.getcwd()

        # 开始执行命令进行建立rpm包
        os.chdir(self.root)
        # -bp 只作准备 （解压与打补丁）
        # -bc 准备并编译
        # -bi 编译并安装
        # -bl 检验文件是否齐全
        # -ba 编译后做成*.rpm和src.rpm
        # -bb 编译后做成*.rpm
        # -bs 只做成*.src.rpm
        rpmbuild = 'rpmbuild -bb SPECS/%s.spec' % self.name

        # rpmbuild command
        #self.cmd.execWithCommand(rpmbuild, quiet=not(self.cmd.debug))
        retval = subprocess.run(rpmbuild + ' > /tmp/make-sunhpc-rpm.log 2>&1', shell=True)
        if retval.returncode:
            self.cmd.msg('check /tmp/make-sunhpc-rpm.log file.','w')

        # 切换正常目录.
        os.environ['HOME'] = HOME
        os.chdir(cwd)

        rpm_name = "%s-%s-%s.%s.rpm" % (self.name, self.version, self.release, self.arch)
        rpm_file = os.path.join(self.rpms, self.arch, rpm_name)
        if not os.path.exists(rpm_file):
            self.cmd.msg(' %s Generate failed.' % rpm_name, 'a')

        new_rpm_dir = os.path.join(cwd, 'newRPMS')
        if self.outdir:
            new_rpm_dir = self.outdir

        if not os.path.exists(new_rpm_dir):
            os.makedirs(new_rpm_dir)

        shutil.copyfile(rpm_file, os.path.join(new_rpm_dir, rpm_name))

        new_rpm = os.path.join(new_rpm_dir, rpm_name)
        if os.path.exists(new_rpm):
            if not self.quiet:
                self.cmd.msg('The new "%s" in %s directory.' % (self.name, new_rpm_dir))

class Command(sunhpc.commands.create.command):
    """
    Create an rpm packages
    <param type="str" name="name">
    Must specify the rpm name.
    </param>

    <param type="str" name="source">
    Specify the rpm source path.
    </param>

    <param type="str" name="prefix">
    Specify the rpm install path.
    </param>

    <param type="str" name="arch">
    Specifies the rpm arch. The default is local machine arch
    </param>

    <param type="str" name="version">
    Specifies the rpm version.
    </param>

    <param type="str" name="release">
    Specifies the rpm release.
    </param>

    <param type="file" name="spec">
    Specifies the spec file for rpmbuild.
    </param>

    <param type="path" name="outdir">
    Specifies the rpm file out dir.
    </param>

    <example cmd='create rpm'>
    Create an rpm package.
    </example>
    """
    def run(self, params, args):

        (name, source, prefix, outdir, pyver, 
            spec, arch, tarfile, quiet, version, 
                        release) = self.fillParams([
                ('name', ),
                ('source',  None),
                ('prefix',  None),
                ('outdir',  None),
                ('pyver',   '2'),
                ('spec',   None),
                ('arch',    self.arch), # 指定arch=noarch 编译noarch包.
                ('tarfile', None),
                ('quiet', 'no'),
                ('version', sunhpc.version),
                ('release', sunhpc.version_micro)])

        quiet = self.str2bool(quiet)

        if spec and os.path.isfile(spec):
            if not tarfile:
                self.msg('spec and tarfile params must be supply', 'a')

        if not source:
            self.msg('must supply source package paths.', 'a')

        if not os.path.isdir(source):
            self.msg('The %s dirs is not exists or not dirs.' % source, 'a')

        if not prefix:
            self.msg('must supply rpm install(prefix) paths.', 'a')

        # 如果没有提供软件名称,则目录名作为软件名称.
        if not name:
            name = os.path.basename(os.path.abspath(source))

        # RPM最终的安装目录,需要提供全路径.
        # sunhpc create rpm source=gaussian-09-a01/ prefix=/works/apps/soft/gaussian-09-a01 version=9.1
        prefix = os.path.join(os.sep, prefix)

        # 创建临时编译目录.
        cwd   = os.getcwd()
        root  = tempfile.mktemp(dir=cwd)
        build = RPMBUILD(self, name, root, source, prefix, version, release, arch, pyver, spec, tarfile, outdir, quiet)

        src_brp = '/usr/lib/rpm/brp-python-bytecompile'
        dst_brp = '/usr/lib/rpm/bak-brp-python-bytecompile'
        try:
            # 如果Python保持也可以关闭python的检测.但是这样不明智,
            # 即使制作出来了RPM包,也不一定好用.
            # 注释掉 /usr/lib/rpm/redhat/macros 文件中的
            # /usr/lib/rpm/brp-python-bytecompile 这行.
            os.rename(src_brp, dst_brp)
            with open(src_brp, 'w') as f:
                f.write('#!/bin/bash\n')
                f.write('exit 0\n')
            os.chmod(src_brp, 0o755)

            # 如果未指定pyver版本, 则默认使用系统python版本制作rpm包
            # 如果制作Sunhpc相关的RPM包,必须使用python-3的版本.

            dst = '/usr/bin/python'
            org = '/usr/bin/python2'
            if int(pyver) == 3:
                src = '/opt/sunpy3/bin/python3'
            else:
                src = '/usr/bin/python2'

            # 删除python
            os.unlink(dst)
            # 建立软连接
            os.symlink(src, dst)

            build.run()

            shutil.rmtree(root)
        except Exception as e:
            self.abort('%s' % repr(e))

        finally:
            # 恢复python版本.
            os.unlink(dst)
            os.symlink(org, dst)
            os.rename(dst_brp, src_brp)
