#! /usr/bin/env python

"""Script for the validation of Mac OS X Frameworks by checking all binary file architectures."""

# Python module imports.
from os import getcwd, path, waitpid, walk
from re import search
from subprocess import PIPE, Popen
from sys import argv, exit


# The arch to check for.
TRUE_ARCH = '3-way [i386, ppc, x86_64]'


# Blacklist of files not to check (for speedup).
blacklist = [
    '.py$',
    '.pyc$',
    '.txt$',
    '.h$',
    '.i$',
    '.m$',
    '.mat$',
    '.arff$'
]

# Command line args.
verbosity = False
if len(argv) != 1:
    # Verbosity. 
    if argv[1] == '-v':
        verbosity = True

    # Bad usage.
    else:
        print("Usage:\nframework_bin_test [-v]\n")
        exit(1)

# Initial printout.
cwd = getcwd()
print("Checking binary objects in %s" % cwd)

# Walk through all the directories.
for (dirpath, dirnames, filenames) in walk(cwd):
    # Init the dir bin info.
    bin_info = []
    full_info = []

    # The relative path.
    rel_path = path.relpath(dirpath, cwd)

    # Loop over the files.
    for file in filenames:
        # Blacklist.
        skip = False
        for name in blacklist:
            if search(name, file):
                skip = True
                break
        if skip:
            continue

        # The command.
        name = path.join(dirpath, file)
        cmd = 'file -b %s' % name

        # Execute.
        pipe = Popen(cmd, shell=True, stdout=PIPE, close_fds=False)
        waitpid(pipe.pid, 0)

        # The STDOUT data.
        data = pipe.stdout.readlines()

        # Skip non-binaries.
        if not search('^Mach-O', data[0]):
            continue

        # The type.
        file_type = None
        if data[0][:-1] == 'Mach-O universal binary with 4 architectures':
            file_type = '4-way'

            # Arch.
            arch = [None, None, None, None]
            for i in range(4):
                row = data[i+1].split('\t')
                arch[i] = row[1][:-1]
            arch.sort()

            # The full file type printout.
            if arch == ['Mach-O 64-bit executable ppc64', 'Mach-O 64-bit executable x86_64', 'Mach-O executable i386', 'Mach-O executable ppc']:
                part_type = '%s [i386, ppc, x86_64, ppc64]' % file_type
                full_type = '%s exec [i386, ppc, x86_64, ppc64]' % file_type
            elif arch == ['Mach-O 64-bit bundle ppc64', 'Mach-O 64-bit bundle x86_64', 'Mach-O bundle i386', 'Mach-O bundle ppc']:
                part_type = '%s [i386, ppc, x86_64, ppc64]' % file_type
                full_type = '%s bundle [i386, ppc, x86_64, ppc64]' % file_type
            elif arch == ['Mach-O 64-bit dynamically linked shared library ppc64', 'Mach-O 64-bit dynamically linked shared library x86_64', 'Mach-O dynamically linked shared library i386', 'Mach-O dynamically linked shared library ppc']:
                part_type = '%s [i386, ppc, x86_64, ppc64]' % file_type
                full_type = '%s lib [i386, ppc, x86_64, ppc64]' % file_type
            elif arch == ['Mach-O 64-bit object ppc64', 'Mach-O 64-bit object x86_64', 'Mach-O object i386', 'Mach-O object ppc']:
                part_type = '%s [i386, ppc, x86_64, ppc64]' % file_type
                full_type = '%s obj [i386, ppc, x86_64, ppc64]' % file_type
            elif arch == ['current ar archive random library', 'current ar archive random library', 'current ar archive random library', 'current ar archive random library']:
                part_type = '%s ar' % file_type
                full_type = '%s current ar archive random library' % file_type
            else:
                full_type = '%s %s' % (file_type, arch)
                part_type = full_type

        elif data[0][:-1] == 'Mach-O universal binary with 3 architectures':
            file_type = '3-way'

            # Arch.
            arch = [None, None, None]
            for i in range(3):
                row = data[i+1].split('\t')
                arch[i] = row[1][:-1]
            arch.sort()

            # The full file type printout.
            if arch == ['Mach-O 64-bit executable x86_64', 'Mach-O executable i386', 'Mach-O executable ppc']:
                part_type = '%s [i386, ppc, x86_64]' % file_type
                full_type = '%s exec [i386, ppc, x86_64]' % file_type
            elif arch == ['Mach-O 64-bit bundle x86_64', 'Mach-O bundle i386', 'Mach-O bundle ppc']:
                part_type = '%s [i386, ppc, x86_64]' % file_type
                full_type = '%s bundle [i386, ppc, x86_64]' % file_type
            elif arch == ['Mach-O 64-bit dynamically linked shared library x86_64', 'Mach-O dynamically linked shared library i386', 'Mach-O dynamically linked shared library ppc']:
                part_type = '%s [i386, ppc, x86_64]' % file_type
                full_type = '%s lib [i386, ppc, x86_64]' % file_type
            elif arch == ['Mach-O 64-bit object x86_64', 'Mach-O object i386', 'Mach-O object ppc']:
                part_type = '%s [i386, ppc, x86_64]' % file_type
                full_type = '%s obj [i386, ppc, x86_64]' % file_type
            elif arch == ['current ar archive random library', 'current ar archive random library', 'current ar archive random library']:
                part_type = '%s ar' % file_type
                full_type = '%s current ar archive random library' % file_type
            else:
                full_type = '%s %s' % (file_type, arch)
                part_type = full_type

        elif data[0][:-1] == 'Mach-O universal binary with 2 architectures':
            file_type = '2-way'

            # Arch.
            arch = [None, None]
            for i in range(2):
                row = data[i+1].split('\t')
                arch[i] = row[1][:-1]
            arch.sort()

            # The full file type printout.
            if arch == ['Mach-O executable i386', 'Mach-O executable ppc']:
                part_type = '%s [i386, ppc]' % file_type
                full_type = '%s exec [i386, ppc]' % file_type
            elif arch == ['Mach-O bundle i386', 'Mach-O bundle ppc']:
                part_type = '%s [i386, ppc]' % file_type
                full_type = '%s bundle [i386, ppc]' % file_type
            elif arch == ['Mach-O dynamically linked shared library i386', 'Mach-O dynamically linked shared library ppc']:
                part_type = '%s [i386, ppc]' % file_type
                full_type = '%s lib [i386, ppc]' % file_type
            elif arch == ['Mach-O object i386', 'Mach-O object ppc']:
                part_type = '%s [i386, ppc]' % file_type
                full_type = '%s obj [i386, ppc]' % file_type
            elif arch == ['current ar archive random library', 'current ar archive random library']:
                part_type = '%s ar' % file_type
                full_type = '%s current ar archive random library' % file_type
            else:
                full_type = '%s %s' % (file_type, arch)
                part_type = full_type

        else:
            full_type = data[0][:-1]
            for i in range(1, len(data)):
                row = data[i].split('\t')
                arch[i] = row[1][:-1]
                full_type += " %s" % arch

            part_type = full_type

        # Add the file type.
        if part_type not in bin_info:
            bin_info.append(part_type)

        # Store the data for error printouts.
        full_info.append("    %-40s %s" % (full_type, path.relpath(name, cwd)))

    # Print outs.
    if len(bin_info) == 0:
        if verbosity:
            print("%-40s %s" % ("No binary files", rel_path))

    elif len(bin_info) == 1 and bin_info[0] == 'Unknown':
        print("%-40s %s" % (bin_info[0], rel_path))
        for i in range(len(full_info)):
            print(full_info[i])

    elif len(bin_info) == 1 and bin_info[0] == TRUE_ARCH:
        if verbosity:
            print("%-40s %s" % (bin_info[0], rel_path))

    elif len(bin_info) == 1:
        print("%-40s %s" % (bin_info[0], rel_path))

    else:
        print("Error:  Mixed arch in %s" % rel_path)
        for i in range(len(full_info)):
            print(full_info[i])
