#!/usr/bin/env python3

# Copyright (c) 2012 Wind River Systems, Inc.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 as
# published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

import os
import sys
import optparse
import re
import subprocess
import shutil

pkg_cur_dirs = {}
obsolete_dirs = []
parser = None

def err_quit(msg):
    print(msg)
    parser.print_usage()
    sys.exit(1)

def parse_version(verstr):
    elems = verstr.split(':')
    epoch = elems[0]
    if len(epoch) == 0:
        return elems[1]
    else:
        return epoch + '_' + elems[1]

def run_command(cmd):
    pipe = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True)
    output = pipe.communicate()[0]
    if pipe.returncode != 0:
        print("Execute command '%s' failed." % cmd)
        sys.exit(1)
    return output.decode('utf-8')

def get_cur_arch_dirs(workdir, arch_dirs):
    pattern = workdir + '/(.*?)/'

    cmd = "bitbake -e | grep ^SDK_ARCH="
    output = run_command(cmd)
    sdk_arch = output.split('"')[1]

    # select thest 5 packages to get the dirs of current arch
    pkgs = ['hicolor-icon-theme', 'base-files', 'acl-native', 'binutils-crosssdk-' + sdk_arch, 'nativesdk-autoconf']

    for pkg in pkgs:
        cmd = "bitbake -e " + pkg + " | grep ^IMAGE_ROOTFS="
        output = run_command(cmd)
        output = output.split('"')[1]
        m = re.match(pattern, output)
        arch_dirs.append(m.group(1))

def main():
    global parser
    parser = optparse.OptionParser(
        usage = """%prog

%prog removes the obsolete packages' build directories in WORKDIR.
This script must be ran under BUILDDIR after source file \"oe-init-build-env\".

Any file or directory under WORKDIR which is not created by Yocto
will be deleted. Be CAUTIOUS.""")

    options, args = parser.parse_args(sys.argv)

    builddir = run_command('echo $BUILDDIR').strip()
    if len(builddir) == 0:
        err_quit("Please source file \"oe-init-build-env\" first.\n")

    if os.getcwd() != builddir:
        err_quit("Please run %s under: %s\n" % (os.path.basename(args[0]), builddir))

    print('Updating bitbake caches...')
    cmd = "bitbake -s"
    output = run_command(cmd)

    output = output.split('\n')
    index = 0
    while len(output[index]) > 0:
        index += 1
    alllines = output[index+1:]

    for line in alllines:
        # empty again means end of the versions output
        if len(line) == 0:
            break
        line = line.strip()
        line = re.sub('\s+', ' ', line)
        elems = line.split(' ')
        if len(elems) == 2:
            version = parse_version(elems[1])
        else:
            version = parse_version(elems[2])
        pkg_cur_dirs[elems[0]] = version

    cmd = "bitbake -e"
    output = run_command(cmd)

    tmpdir = None
    image_rootfs = None
    output = output.split('\n')
    for line in output:
        if tmpdir and image_rootfs:
            break

        if not tmpdir:
            m = re.match('TMPDIR="(.*)"', line)
            if m:
                tmpdir = m.group(1)

        if not image_rootfs:
            m = re.match('IMAGE_ROOTFS="(.*)"', line)
            if m:
                image_rootfs = m.group(1)

    # won't fail just in case
    if not tmpdir or not image_rootfs:
        print("Can't get TMPDIR or IMAGE_ROOTFS.")
        return 1

    pattern = tmpdir + '/(.*?)/(.*?)/'
    m = re.match(pattern, image_rootfs)
    if not m:
        print("Can't get WORKDIR.")
        return 1

    workdir = os.path.join(tmpdir, m.group(1))

    # we only deal the dirs of current arch, total numbers of dirs are 6
    cur_arch_dirs = [m.group(2)]
    get_cur_arch_dirs(workdir, cur_arch_dirs)

    for workroot, dirs, files in os.walk(workdir):
        # For the files, they should NOT exist in WORKDIR. Remove them.
        for f in files:
            obsolete_dirs.append(os.path.join(workroot, f))

        for d in dirs:
            if d not in cur_arch_dirs:
                continue

            for pkgroot, pkgdirs, filenames in os.walk(os.path.join(workroot, d)):
                for f in filenames:
                    obsolete_dirs.append(os.path.join(pkgroot, f))

                for pkgdir in sorted(pkgdirs):
                    if pkgdir not in pkg_cur_dirs:
                        obsolete_dirs.append(os.path.join(pkgroot, pkgdir))
                    else:
                        for verroot, verdirs, verfiles in os.walk(os.path.join(pkgroot, pkgdir)):
                            for f in verfiles:
                                obsolete_dirs.append(os.path.join(pkgroot, f))
                            for v in sorted(verdirs):
                               if v not in pkg_cur_dirs[pkgdir]:
                                   obsolete_dirs.append(os.path.join(pkgroot, pkgdir, v))
                            break

                # just process the top dir of every package under tmp/work/*/,
                # then jump out of the above os.walk()
                break

        # it is convenient to use os.walk() to get dirs and files at same time
        # both of them have been dealed in the loop, so jump out
        break

    for d in obsolete_dirs:
        print("Deleting %s" % d)
        shutil.rmtree(d, True)

    if len(obsolete_dirs):
        print('\nTotal %d items.' % len(obsolete_dirs))
    else:
        print('\nNo obsolete directory found under %s.' % workdir)

    return 0

if __name__ == '__main__':
    try:
        ret = main()
    except Exception:
        ret = 2
        import traceback
        traceback.print_exc()
    sys.exit(ret)