#!/usr/bin/python

#
# This source file is part of appleseed.
# Visit https://appleseedhq.net/ for additional information and resources.
#
# This software is released under the MIT license.
#
# Copyright (c) 2017-2018 Esteban Tovagliari, The appleseedhq Organization
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the 'Software'), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#

from __future__ import print_function
from distutils import archive_util, dir_util
from xml.etree.ElementTree import ElementTree
import argparse
import colorama
import datetime
import glob
import os
import platform
import re
import shutil
import stat
import subprocess
import sys
import time
import traceback
import urllib


#--------------------------------------------------------------------------------------------------
# Constants.
#--------------------------------------------------------------------------------------------------

VERSION = "1.1.0"
SETTINGS_FILENAME = "blenderseed.package.configuration.xml"


#--------------------------------------------------------------------------------------------------
# Utility functions.
#--------------------------------------------------------------------------------------------------

GREEN_CHECKMARK = u"{0}\u2713{1}".format(colorama.Style.BRIGHT + colorama.Fore.GREEN, colorama.Style.RESET_ALL)
RED_CROSSMARK = u"{0}\u2717{1}".format(colorama.Style.BRIGHT + colorama.Fore.RED, colorama.Style.RESET_ALL)


def trace(message):
    # encode('utf-8') is required to support output redirection to files or pipes.
    print(u"  {0}{1}{2}".format(colorama.Style.DIM + colorama.Fore.WHITE, message, colorama.Style.RESET_ALL).encode('utf-8'))


def info(message):
    print(u"  {0}".format(message).encode('utf-8'))


def progress(message):
    print(u"  {0}...".format(message).encode('utf-8'))


def warning(message):
    print(u"  {0}Warning: {1}.{2}".format(colorama.Style.BRIGHT + colorama.Fore.MAGENTA, message, colorama.Style.RESET_ALL).encode('utf-8'))


def fatal(message):
    print(u"{0}Fatal: {1}. Aborting.{2}".format(colorama.Style.BRIGHT + colorama.Fore.RED, message, colorama.Style.RESET_ALL).encode('utf-8'))
    if sys.exc_info()[0]:
        print(traceback.format_exc())
    sys.exit(1)


def exe(filepath):
    return filepath + ".exe" if os.name == "nt" else filepath


def safe_delete_file(path):
    try:
        if os.path.exists(path):
            os.remove(path)
    except OSError:
        fatal("Failed to delete file '" + path + "'")


def on_rmtree_error(func, path, exc_info):
    # path contains the path of the file that couldn't be removed.
    # Let's just assume that it's read-only and unlink it.
    os.chmod(path, stat.S_IWRITE)
    os.unlink(path)


def safe_delete_directory(path):
    Attempts = 10
    for attempt in range(Attempts):
        try:
            if os.path.exists(path):
                shutil.rmtree(path, onerror=on_rmtree_error)
            return
        except OSError:
            if attempt < Attempts - 1:
                time.sleep(0.5)
            else:
                fatal("Failed to delete directory '" + path + "'")


def safe_delete_directory_recursively(root_path, directory_name):
    safe_delete_directory(os.path.join(root_path, directory_name))

    for entry in os.listdir(root_path):
        subdirectory = os.path.join(root_path, entry)
        if os.path.isdir(subdirectory):
            safe_delete_directory_recursively(subdirectory, directory_name)


def safe_make_directory(path):
    if not os.path.isdir(path):
        os.makedirs(path)


def pushd(path):
    old_path = os.getcwd()
    os.chdir(path)
    return old_path


def copy_glob(input_pattern, output_path):
    for input_file in glob.glob(input_pattern):
        shutil.copy(input_file, output_path)


#--------------------------------------------------------------------------------------------------
# Settings.
#--------------------------------------------------------------------------------------------------

class Settings:

    def load(self):
        self.this_dir = os.path.dirname(os.path.realpath(__file__))
        self.root_dir = os.path.join(self.this_dir, "..")

        print("Loading settings from " + SETTINGS_FILENAME + "...")
        tree = ElementTree()
        try:
            tree.parse(SETTINGS_FILENAME)
        except IOError:
            fatal("Failed to load configuration file '" + SETTINGS_FILENAME + "'")
        self.__load_values(tree)

    def print_summary(self):
        print("")
        print("  Platform:                        " + self.platform)
        print("  Path to appleseed release:       " + self.appleseed_release_path)
        print("  Path to appleseed binaries:      " + self.appleseed_bin_path)
        print("  Path to appleseed libraries:     " + self.appleseed_lib_path)
        print("  Path to appleseed shaders:       " + self.appleseed_shaders_path)
        print("  Path to appleseed schemas:       " + self.appleseed_schemas_path)
        print("  Path to appleseed settings:      " + self.appleseed_settings_path)
        print("  Path to appleseed.python:        " + self.appleseed_python_path)
        print("  Path to maketx:                  " + self.maketx_path)
        print("  Output directory:                " + self.output_dir)
        print("")

    def __load_values(self, tree):
        self.platform = self.__get_required(tree, "platform")

        self.appleseed_release_path = self.__get_required(tree, "appleseed_release_path")
        os.environ['APPLESEED'] = self.appleseed_release_path

        self.appleseed_bin_path = os.path.expandvars(self.__get_required(tree, "appleseed_bin_path"))
        self.appleseed_lib_path = os.path.expandvars(self.__get_required(tree, "appleseed_lib_path"))
        self.appleseed_shaders_path = os.path.expandvars(self.__get_required(tree, "appleseed_shaders_path"))
        self.appleseed_schemas_path = os.path.expandvars(self.__get_required(tree, "appleseed_schemas_path"))
        self.appleseed_settings_path = os.path.expandvars(self.__get_required(tree, "appleseed_settings_path"))
        self.appleseed_python_path = os.path.expandvars(self.__get_required(tree, "appleseed_python_path"))
        self.maketx_path = os.path.expandvars(self.__get_required(tree, "maketx_path"))
        self.output_dir = os.path.expandvars(self.__get_required(tree, "output_dir"))

    def __get_required(self, tree, key):
        value = tree.findtext(key)
        if value is None:
            fatal("Missing value \"{0}\" in configuration file".format(key))
        return value


#--------------------------------------------------------------------------------------------------
# Base package builder.
#--------------------------------------------------------------------------------------------------

class PackageBuilder(object):

    def __init__(self, settings, package_version, build_date, no_release=False):
        self.settings = settings
        self.package_version = package_version
        self.build_date = build_date
        self.no_release = no_release

    def build_package(self):
        print("Building package:")
        print("")
        self.orchestrate()
        print("")
        print("The package was successfully built.")

    def orchestrate(self):
        self.remove_leftovers()
        self.copy_appleseed_python()
        self.copy_binaries()
        self.copy_dependencies()
        self.copy_schemas()
        self.copy_shaders()
        self.download_settings_files()
        self.remove_pyc_files()
        self.post_process_package()

        if not self.no_release:
            self.deploy_blenderseed_to_stage()
            self.clean_stage()
            self.build_final_zip_file()
            self.remove_stage()

    def remove_leftovers(self):
        progress("Removing leftovers from previous invocations")
        safe_delete_directory(os.path.join(self.settings.root_dir, "appleseed"))
        safe_delete_directory("blenderseed")

    def copy_appleseed_python(self):
        progress("Copying appleseed.python to root directory")

        # Create destination directory.
        lib_dir = os.path.join(self.settings.root_dir, "appleseed", "lib")
        safe_make_directory(lib_dir)

        # Copy appleseed.python.
        dir_util.copy_tree(self.settings.appleseed_python_path, lib_dir)

        # Remove _appleseedpython.so (Python 2) since blenderseed only needs _appleseedpython3.so (Python 3).
        # TODO: implement properly.
        safe_delete_file(os.path.join(lib_dir, "appleseed", "_appleseedpython.so"))
        safe_delete_file(os.path.join(lib_dir, "appleseed", "_appleseedpython.pyd"))

    def copy_binaries(self):
        progress("Copying binaries to root directory")

        # Create destination directory.
        bin_dir = os.path.join(self.settings.root_dir, "appleseed", "bin")
        safe_make_directory(bin_dir)

        # Copy appleseed binaries.
        for bin in [exe("appleseed.cli")]:
            shutil.copy(os.path.join(self.settings.appleseed_bin_path, bin), bin_dir)

        # Copy maketx.
        shutil.copy(exe(self.settings.maketx_path), bin_dir)

    def copy_schemas(self):
        progress("Copying schemas to root directory")

        dir_util.copy_tree(self.settings.appleseed_schemas_path, os.path.join(self.settings.root_dir, "appleseed", "schemas"))
        safe_delete_file(os.path.join(self.settings.root_dir, "appleseed", "schemas", ".gitignore"))

    def copy_shaders(self):
        progress("Copying shaders to root directory")

        # Create destination directory.
        shaders_dir = os.path.join(self.settings.root_dir, "appleseed", "shaders")
        safe_make_directory(shaders_dir)

        self.__do_copy_shaders(os.path.join(self.settings.appleseed_shaders_path, "appleseed"), shaders_dir)
        self.__do_copy_shaders(os.path.join(self.settings.appleseed_shaders_path, "blenderseed"), shaders_dir)

    def __do_copy_shaders(self, source_dir, target_dir):
        for root, dirs, files in os.walk(source_dir):
            for f in files:
                if f.endswith(".oso"):
                    shutil.copy(os.path.join(root, f), target_dir)

    def download_settings_files(self):
        progress("Downloading settings files to root directory")

        # Create destination directory.
        settings_dir = os.path.join(self.settings.root_dir, "appleseed", "settings")
        safe_make_directory(settings_dir)

        for file in ["appleseed.cli.xml"]:
            urllib.urlretrieve(
                "https://raw.githubusercontent.com/appleseedhq/appleseed/master/sandbox/settings/{0}".format(file),
                os.path.join(settings_dir, file))

    def remove_pyc_files(self):
        progress("Removing pyc files from root directory")
        for root, dirs, files in os.walk(os.path.join(self.settings.root_dir, "appleseed", "lib")):
            for f in files:
                if f.endswith(".pyc"):
                    safe_delete_file(os.path.join(root, f))

    def deploy_blenderseed_to_stage(self):
        progress("Deploying blenderseed to staging directory")
        shutil.copytree(self.settings.root_dir, "blenderseed", ignore=shutil.ignore_patterns("scripts"))

    def clean_stage(self):
        progress("Cleaning staging directory")

        safe_delete_directory_recursively("blenderseed", "__pycache__")

        for subdirectory in [".git", ".idea", "archives", "docs", "scripts", "tests"]:
            safe_delete_directory(os.path.join("blenderseed", subdirectory))

        for file in [".gitignore", "README.md"]:
            safe_delete_file(os.path.join("blenderseed", file))

    def build_final_zip_file(self):
        progress("Building final zip file from staging directory")
        package_name = "blenderseed-{0}-{1}-{2}".format(self.package_version, self.settings.platform, self.build_date)
        package_path = os.path.join(self.settings.output_dir, package_name)
        archive_util.make_zipfile(package_path, "blenderseed")
        info("Package path: {0}".format(package_path + ".zip"))

    def remove_stage(self):
        progress("Deleting staging directory")
        safe_delete_directory("blenderseed")

    def run(self, cmdline):
        trace("Running command line: {0}".format(cmdline))
        os.system(cmdline)

    def run_subprocess(self, cmdline):
        p = subprocess.Popen(cmdline, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        out, err = p.communicate()
        return p.returncode, out, err


#--------------------------------------------------------------------------------------------------
# Windows package builder.
#--------------------------------------------------------------------------------------------------

class WindowsPackageBuilder(PackageBuilder):

    def copy_dependencies(self):
        progress("Windows-specific: Copying dependencies")
        bin_dir = self.settings.appleseed_bin_path

        for dll in ["appleseed.dll", "appleseed.shared.dll"]:
            shutil.copy(os.path.join(bin_dir, dll), os.path.join(self.settings.root_dir, "appleseed", "bin"))

    def post_process_package(self):
        pass


#--------------------------------------------------------------------------------------------------
# Mac package builder.
#--------------------------------------------------------------------------------------------------

class MacPackageBuilder(PackageBuilder):

    SYSTEM_LIBS_PREFIXES = [
        "/System/Library/",
        "/usr/lib/libcurl",
        "/usr/lib/libc++",
        "/usr/lib/libbz2",
        "/usr/lib/libSystem",
        #"/usr/lib/libz",
        "/usr/lib/libncurses",
        "/usr/lib/libobjc.A.dylib"
    ]

    def copy_dependencies(self):
        progress("Mac-specific: Copying dependencies")

        # Create destination directory.
        lib_dir = os.path.join(self.settings.root_dir, "appleseed", "lib")
        safe_make_directory(lib_dir)

        # Copy appleseed libraries.
        for lib in ["libappleseed.dylib", "libappleseed.shared.dylib"]:
            shutil.copy(os.path.join(self.settings.appleseed_lib_path, lib), lib_dir)

        # Get shared libs needed by binaries.
        all_libs = set()
        for bin in glob.glob(os.path.join(self.settings.root_dir, "appleseed", "bin", "*")):
            libs = self.__get_dependencies_for_file(bin)
            all_libs = all_libs.union(libs)

        # Get shared libs needed by appleseed.python.
        appleseedpython_libs = self.__get_dependencies_for_file(
            os.path.join(self.settings.root_dir, "appleseed", "lib", "appleseed", "_appleseedpython3.so"))
        all_libs = all_libs.union(appleseedpython_libs)

        # Get shared libs needed by libraries.
        # TODO: we're not computing the full transitive closure here!
        lib_libs = set()
        for lib in all_libs:
            libs = self.__get_dependencies_for_file(lib)
            lib_libs = lib_libs.union(libs)
        all_libs = all_libs.union(lib_libs)

        if True:
            # Print dependencies.
            trace("    Dependencies:")
            for lib in all_libs:
                trace("      {0}".format(lib))

        # Copy needed libs to lib directory.
        for lib in all_libs:
            if True:
                trace("  Copying {0} to {1}...".format(lib, lib_dir))
            shutil.copy(lib, lib_dir)

    def post_process_package(self):
        progress("Mac-specific: Post-processing package")
        self.__fixup_binaries()

    def __fixup_binaries(self):
        progress("Mac-specific: Fixing up binaries")
        self.set_libraries_ids()
        self.__change_library_paths_in_libraries()
        self.__change_library_paths_in_executables()

    def set_libraries_ids(self):
        lib_dir = os.path.join(self.settings.root_dir, "appleseed", "lib")
        for dirpath, dirnames, filenames in os.walk(lib_dir):
            for filename in filenames:
                ext = os.path.splitext(filename)[1]
                if ext == ".dylib" or ext == ".so":
                    lib_path = os.path.join(dirpath, filename)
                    self.__set_library_id(lib_path, filename)

    def __change_library_paths_in_libraries(self):
        lib_dir = os.path.join(self.settings.root_dir, "appleseed", "lib")
        for dirpath, dirnames, filenames in os.walk(lib_dir):
            for filename in filenames:
                ext = os.path.splitext(filename)[1]
                if ext == ".dylib" or ext == ".so":
                    lib_path = os.path.join(dirpath, filename)
                    self.__change_library_paths_in_binary(lib_path)

    def __change_library_paths_in_executables(self):
        bin_dir = os.path.join(self.settings.root_dir, "appleseed", "bin")
        for dirpath, dirnames, filenames in os.walk(bin_dir):
            for filename in filenames:
                ext = os.path.splitext(filename)[1]
                if ext != ".py" and ext != ".conf":
                    exe_path = os.path.join(dirpath, filename)
                    self.__change_library_paths_in_binary(exe_path)

    # Can be used on executables and dynamic libraries.
    def __change_library_paths_in_binary(self, bin_path):
        progress("Patching {0}".format(bin_path))
        bin_dir = os.path.dirname(bin_path)
        lib_dir = os.path.join(self.settings.root_dir, "appleseed", "lib")
        path_to_appleseed_lib = os.path.relpath(lib_dir, bin_dir)
        # fix_paths set to False because we must retrieve the unmodified dependency in order to replace it by the correct one.
        for lib_path in self.__get_dependencies_for_file(bin_path, fix_paths=False):
            lib_name = os.path.basename(lib_path)
            if path_to_appleseed_lib == ".":
                self.__change_library_path(bin_path, lib_path, "@loader_path/{0}".format(lib_name))
            else:
                self.__change_library_path(bin_path, lib_path, "@loader_path/{0}/{1}".format(path_to_appleseed_lib, lib_name))

    def __set_library_id(self, target, name):
        self.run('install_name_tool -id "{0}" {1}'.format(name, target))

    def __change_library_path(self, target, old, new):
        self.run('install_name_tool -change "{0}" "{1}" {2}'.format(old, new, target))

    def __get_dependencies_for_file(self, filepath, fix_paths=True):
        filename = os.path.basename(filepath)

        loader_path = os.path.dirname(filepath)
        rpath = "/usr/local/lib/"  # TODO: a great simplification

        if True:
            trace("Gathering dependencies for file")
            trace("    {0}".format(filepath))
            trace("with @loader_path set to")
            trace("    {0}".format(loader_path))
            trace("and @rpath hardcoded to")
            trace("    {0}".format(rpath))

        returncode, out, err = self.run_subprocess(["otool", "-L", filepath])
        if returncode != 0:
            fatal("Failed to invoke otool(1) to get dependencies for {0}: {1}".format(filepath, err))

        libs = set()

        for line in out.split("\n")[1:]:    # skip the first line
            line = line.strip()

            # Ignore empty lines.
            if len(line) == 0:
                continue

            # Parse the line.
            m = re.match(r"(.*) \(compatibility version .*, current version .*\)", line)
            if not m:
                fatal("Failed to parse line from otool(1) output: " + line)
            lib = m.group(1)

            # Ignore self-references (why do these happen?).
            if lib == filename:
                continue

            # Ignore system libs.
            if self.__is_system_lib(lib):
                continue

            # Ignore Qt frameworks.
            if re.search(r"Qt.*\.framework", lib):
                continue

            if fix_paths:
                # Handle libs relative to @loader_path.
                lib = lib.replace("@loader_path", loader_path)

                # Handle libs relative to @rpath.
                lib = lib.replace("@rpath", rpath)

                # Try to handle other relative libs.
                if not os.path.isabs(lib):
                    # TODO: generalize to a collection of user-specified search paths.
                    candidate = os.path.join(loader_path, lib)
                    if not os.path.exists(candidate):
                        candidate = os.path.join("/usr/local/lib/", lib)
                    if os.path.exists(candidate):
                        info("Resolved relative dependency {0} as {1}".format(lib, candidate))
                        lib = candidate

            libs.add(lib)

        if True:
            trace("Dependencies for file {0}:".format(filepath))
            for lib in libs:
                if os.path.isfile(lib):
                    trace(u"    {0} {1}".format(GREEN_CHECKMARK, lib))
                else:
                    trace(u"    {0} {1}".format(RED_CROSSMARK, lib))

        # Don't check for missing dependencies if we didn't attempt to fix them.
        if fix_paths:
            for lib in libs:
                if not os.path.isfile(lib):
                    fatal("Dependency {0} could not be found on disk".format(lib))

        return libs

    def __is_system_lib(self, lib):
        for prefix in self.SYSTEM_LIBS_PREFIXES:
            if lib.startswith(prefix):
                return True
        return False


#--------------------------------------------------------------------------------------------------
# Linux package builder.
#--------------------------------------------------------------------------------------------------

class LinuxPackageBuilder(PackageBuilder):

    SYSTEM_LIBS_PREFIXES = [
        "linux",
        "librt",
        "libpthread",
        "libGL",
        "libX",
        "libselinux",
        "libICE",
        "libSM",
        "libdl",
        "libm.so",
        "libgcc",
        "libc.so",
        "/lib64/ld-linux-",
        "libstdc++",
        "libxcb",
        "libdrm",
        "libnsl",
        "libuuid",
        "libgthread",
        "libglib",
        "libgobject",
        "libglapi",
        "libffi",
        "libfontconfig",
        "libutil",
        "libpython",
        "libxshmfence.so"
    ]

    def plugin_extension(self):
        return ".so"

    def copy_dependencies(self):
        progress("Linux-specific: Copying dependencies")

        # Create destination directory.
        lib_dir = os.path.join(self.settings.root_dir, "appleseed", "lib")
        safe_make_directory(lib_dir)

        # Copy appleseed libraries.
        for lib in ["libappleseed.so", "libappleseed.shared.so"]:
            shutil.copy(os.path.join(self.settings.appleseed_lib_path, lib), lib_dir)

        # Get shared libs needed by binaries.
        all_libs = set()
        for bin in glob.glob(os.path.join(self.settings.root_dir, "appleseed", "bin", "*")):
            libs = self.__get_dependencies_for_file(bin)
            all_libs = all_libs.union(libs)

        # Get shared libs needed by appleseed.python.
        appleseedpython_libs = self.__get_dependencies_for_file(
            os.path.join(self.settings.root_dir, "appleseed", "lib", "appleseed", "_appleseedpython3.so"))
        all_libs = all_libs.union(appleseedpython_libs)

        # Get shared libs needed by libraries.
        lib_libs = set()
        for lib in all_libs:
            libs = self.__get_dependencies_for_file(lib)
            lib_libs = lib_libs.union(libs)
        all_libs = all_libs.union(lib_libs)

        # Copy all shared libraries.
        for lib in all_libs:
            shutil.copy(lib, lib_dir)

    def post_process_package(self):
        progress("Linux-specific: Post-processing package")

        for bin in glob.glob(os.path.join(self.settings.root_dir, "appleseed", "bin", "*")):
            self.run("chrpath -r \$ORIGIN/../lib " + bin)

        for lib in glob.glob(os.path.join(self.settings.root_dir, "appleseed", "lib", "*.so")):
             self.run("chrpath -d " + lib)

        appleseed_python_dir = os.path.join(self.settings.root_dir, "appleseed", "lib", "appleseed")
        for py_cpp_module in glob.glob(os.path.join(appleseed_python_dir, "*.so")):
            self.run("chrpath -r \$ORIGIN/../ " + py_cpp_module)

    def __is_system_lib(self, lib):
        for prefix in self.SYSTEM_LIBS_PREFIXES:
            if lib.startswith(prefix):
                return True
        return False

    def __get_dependencies_for_file(self, filepath):
        returncode, out, err = self.run_subprocess(["ldd", filepath])
        if returncode != 0:
            fatal("Failed to invoke ldd(1) to get dependencies for {0}: {1}".format(filepath, err))

        libs = set()

        for line in out.split("\n"):
            line = line.strip()

            # Ignore empty lines.
            if len(line) == 0:
                continue

            # Ignore system libs.
            if self.__is_system_lib(line):
                continue

            # Ignore appleseed libs.
            if "libappleseed" in line:
                continue

            libs.add(line.split()[2])

        return libs


#--------------------------------------------------------------------------------------------------
# Entry point.
#--------------------------------------------------------------------------------------------------

def main():
    colorama.init()

    parser = argparse.ArgumentParser(description="build a blenderseed package from sources")

    parser.add_argument("--nozip", action="store_true", help="copies appleseed binaries to blenderseed folder but does not build a release package")

    args = parser.parse_args()

    no_release = args.nozip
    package_version = subprocess.Popen("git describe --long", stdout=subprocess.PIPE, shell=True).stdout.read().strip()

    build_date = datetime.date.today().isoformat()

    print("blenderseed.package version " + VERSION)
    print("")

    settings = Settings()
    settings.load()
    settings.print_summary()

    if os.name == "nt":
        package_builder = WindowsPackageBuilder(settings, package_version, build_date, no_release)
    elif os.name == "posix" and platform.mac_ver()[0] != "":
        package_builder = MacPackageBuilder(settings, package_version, build_date, no_release)
    elif os.name == "posix" and platform.mac_ver()[0] == "":
        package_builder = LinuxPackageBuilder(settings, package_version, build_date, no_release)
    else:
        fatal("Unsupported platform: " + os.name)

    package_builder.build_package()

if __name__ == "__main__":
    main()

[((86, 4, 86, 15), 'sys.exit', 'sys.exit', ({(86, 13, 86, 14): '(1)'}, {}), '(1)', False, 'import sys\n'), ((104, 4, 104, 33), 'os.chmod', 'os.chmod', ({(104, 13, 104, 17): 'path', (104, 19, 104, 32): 'stat.S_IWRITE'}, {}), '(path, stat.S_IWRITE)', False, 'import os\n'), ((105, 4, 105, 19), 'os.unlink', 'os.unlink', ({(105, 14, 105, 18): 'path'}, {}), '(path)', False, 'import os\n'), ((125, 17, 125, 38), 'os.listdir', 'os.listdir', ({(125, 28, 125, 37): 'root_path'}, {}), '(root_path)', False, 'import os\n'), ((137, 15, 137, 26), 'os.getcwd', 'os.getcwd', ({}, {}), '()', False, 'import os\n'), ((138, 4, 138, 18), 'os.chdir', 'os.chdir', ({(138, 13, 138, 17): 'path'}, {}), '(path)', False, 'import os\n'), ((143, 22, 143, 46), 'glob.glob', 'glob.glob', ({(143, 32, 143, 45): 'input_pattern'}, {}), '(input_pattern)', False, 'import glob\n'), ((691, 4, 691, 19), 'colorama.init', 'colorama.init', ({}, {}), '()', False, 'import colorama\n'), ((693, 13, 693, 92), 'argparse.ArgumentParser', 'argparse.ArgumentParser', (), '', False, 'import argparse\n'), ((84, 7, 84, 21), 'sys.exc_info', 'sys.exc_info', ({}, {}), '()', False, 'import sys\n'), ((95, 11, 95, 31), 'os.path.exists', 'os.path.exists', ({(95, 26, 95, 30): 'path'}, {}), '(path)', False, 'import os\n'), ((123, 26, 123, 65), 'os.path.join', 'os.path.join', ({(123, 39, 123, 48): 'root_path', (123, 50, 123, 64): 'directory_name'}, {}), '(root_path, directory_name)', False, 'import os\n'), ((126, 23, 126, 53), 'os.path.join', 'os.path.join', ({(126, 36, 126, 45): 'root_path', (126, 47, 126, 52): 'entry'}, {}), '(root_path, entry)', False, 'import os\n'), ((127, 11, 127, 38), 'os.path.isdir', 'os.path.isdir', ({(127, 25, 127, 37): 'subdirectory'}, {}), '(subdirectory)', False, 'import os\n'), ((132, 11, 132, 30), 'os.path.isdir', 'os.path.isdir', ({(132, 25, 132, 29): 'path'}, {}), '(path)', False, 'import os\n'), ((133, 8, 133, 25), 'os.makedirs', 'os.makedirs', ({(133, 20, 133, 24): 'path'}, {}), '(path)', False, 'import os\n'), ((144, 8, 144, 44), 'shutil.copy', 'shutil.copy', ({(144, 20, 144, 30): 'input_file', (144, 32, 144, 43): 'output_path'}, {}), '(input_file, output_path)', False, 'import shutil\n'), ((155, 24, 155, 57), 'os.path.join', 'os.path.join', ({(155, 37, 155, 50): 'self.this_dir', (155, 52, 155, 56): '""".."""'}, {}), "(self.this_dir, '..')", False, 'import os\n'), ((158, 15, 158, 28), 'xml.etree.ElementTree.ElementTree', 'ElementTree', ({}, {}), '()', False, 'from xml.etree.ElementTree import ElementTree\n'), ((246, 18, 246, 74), 'os.path.join', 'os.path.join', ({(246, 31, 246, 53): 'self.settings.root_dir', (246, 55, 246, 66): '"""appleseed"""', (246, 68, 246, 73): '"""lib"""'}, {}), "(self.settings.root_dir, 'appleseed', 'lib')", False, 'import os\n'), ((250, 8, 250, 72), 'distutils.dir_util.copy_tree', 'dir_util.copy_tree', ({(250, 27, 250, 62): 'self.settings.appleseed_python_path', (250, 64, 250, 71): 'lib_dir'}, {}), '(self.settings.appleseed_python_path, lib_dir)', False, 'from distutils import archive_util, dir_util\n'), ((261, 18, 261, 74), 'os.path.join', 'os.path.join', ({(261, 31, 261, 53): 'self.settings.root_dir', (261, 55, 261, 66): '"""appleseed"""', (261, 68, 261, 73): '"""bin"""'}, {}), "(self.settings.root_dir, 'appleseed', 'bin')", False, 'import os\n'), ((281, 22, 281, 82), 'os.path.join', 'os.path.join', ({(281, 35, 281, 57): 'self.settings.root_dir', (281, 59, 281, 70): '"""appleseed"""', (281, 72, 281, 81): '"""shaders"""'}, {}), "(self.settings.root_dir, 'appleseed', 'shaders')", False, 'import os\n'), ((288, 33, 288, 52), 'os.walk', 'os.walk', ({(288, 41, 288, 51): 'source_dir'}, {}), '(source_dir)', False, 'import os\n'), ((297, 23, 297, 84), 'os.path.join', 'os.path.join', ({(297, 36, 297, 58): 'self.settings.root_dir', (297, 60, 297, 71): '"""appleseed"""', (297, 73, 297, 83): '"""settings"""'}, {}), "(self.settings.root_dir, 'appleseed', 'settings')", False, 'import os\n'), ((330, 23, 330, 75), 'os.path.join', 'os.path.join', ({(330, 36, 330, 60): 'self.settings.output_dir', (330, 62, 330, 74): 'package_name'}, {}), '(self.settings.output_dir, package_name)', False, 'import os\n'), ((331, 8, 331, 62), 'distutils.archive_util.make_zipfile', 'archive_util.make_zipfile', ({(331, 34, 331, 46): 'package_path', (331, 48, 331, 61): '"""blenderseed"""'}, {}), "(package_path, 'blenderseed')", False, 'from distutils import archive_util, dir_util\n'), ((340, 8, 340, 26), 'os.system', 'os.system', ({(340, 18, 340, 25): 'cmdline'}, {}), '(cmdline)', False, 'import os\n'), ((343, 12, 343, 85), 'subprocess.Popen', 'subprocess.Popen', (), '', False, 'import subprocess\n'), ((386, 18, 386, 74), 'os.path.join', 'os.path.join', ({(386, 31, 386, 53): 'self.settings.root_dir', (386, 55, 386, 66): '"""appleseed"""', (386, 68, 386, 73): '"""lib"""'}, {}), "(self.settings.root_dir, 'appleseed', 'lib')", False, 'import os\n'), ((435, 18, 435, 74), 'os.path.join', 'os.path.join', ({(435, 31, 435, 53): 'self.settings.root_dir', (435, 55, 435, 66): '"""appleseed"""', (435, 68, 435, 73): '"""lib"""'}, {}), "(self.settings.root_dir, 'appleseed', 'lib')", False, 'import os\n'), ((436, 44, 436, 60), 'os.walk', 'os.walk', ({(436, 52, 436, 59): 'lib_dir'}, {}), '(lib_dir)', False, 'import os\n'), ((444, 18, 444, 74), 'os.path.join', 'os.path.join', ({(444, 31, 444, 53): 'self.settings.root_dir', (444, 55, 444, 66): '"""appleseed"""', (444, 68, 444, 73): '"""lib"""'}, {}), "(self.settings.root_dir, 'appleseed', 'lib')", False, 'import os\n'), ((445, 44, 445, 60), 'os.walk', 'os.walk', ({(445, 52, 445, 59): 'lib_dir'}, {}), '(lib_dir)', False, 'import os\n'), ((453, 18, 453, 74), 'os.path.join', 'os.path.join', ({(453, 31, 453, 53): 'self.settings.root_dir', (453, 55, 453, 66): '"""appleseed"""', (453, 68, 453, 73): '"""bin"""'}, {}), "(self.settings.root_dir, 'appleseed', 'bin')", False, 'import os\n'), ((454, 44, 454, 60), 'os.walk', 'os.walk', ({(454, 52, 454, 59): 'bin_dir'}, {}), '(bin_dir)', False, 'import os\n'), ((464, 18, 464, 43), 'os.path.dirname', 'os.path.dirname', ({(464, 34, 464, 42): 'bin_path'}, {}), '(bin_path)', False, 'import os\n'), ((465, 18, 465, 74), 'os.path.join', 'os.path.join', ({(465, 31, 465, 53): 'self.settings.root_dir', (465, 55, 465, 66): '"""appleseed"""', (465, 68, 465, 73): '"""lib"""'}, {}), "(self.settings.root_dir, 'appleseed', 'lib')", False, 'import os\n'), ((466, 32, 466, 65), 'os.path.relpath', 'os.path.relpath', ({(466, 48, 466, 55): 'lib_dir', (466, 57, 466, 64): 'bin_dir'}, {}), '(lib_dir, bin_dir)', False, 'import os\n'), ((482, 19, 482, 45), 'os.path.basename', 'os.path.basename', ({(482, 36, 482, 44): 'filepath'}, {}), '(filepath)', False, 'import os\n'), ((484, 22, 484, 47), 'os.path.dirname', 'os.path.dirname', ({(484, 38, 484, 46): 'filepath'}, {}), '(filepath)', False, 'import os\n'), ((611, 18, 611, 74), 'os.path.join', 'os.path.join', ({(611, 31, 611, 53): 'self.settings.root_dir', (611, 55, 611, 66): '"""appleseed"""', (611, 68, 611, 73): '"""lib"""'}, {}), "(self.settings.root_dir, 'appleseed', 'lib')", False, 'import os\n'), ((649, 31, 649, 100), 'os.path.join', 'os.path.join', ({(649, 44, 649, 66): 'self.settings.root_dir', (649, 68, 649, 79): '"""appleseed"""', (649, 81, 649, 86): '"""lib"""', (649, 88, 649, 99): '"""appleseed"""'}, {}), "(self.settings.root_dir, 'appleseed', 'lib', 'appleseed')", False, 'import os\n'), ((85, 14, 85, 36), 'traceback.format_exc', 'traceback.format_exc', ({}, {}), '()', False, 'import traceback\n'), ((96, 12, 96, 27), 'os.remove', 'os.remove', ({(96, 22, 96, 26): 'path'}, {}), '(path)', False, 'import os\n'), ((112, 15, 112, 35), 'os.path.exists', 'os.path.exists', ({(112, 30, 112, 34): 'path'}, {}), '(path)', False, 'import os\n'), ((154, 40, 154, 66), 'os.path.realpath', 'os.path.realpath', ({(154, 57, 154, 65): '__file__'}, {}), '(__file__)', False, 'import os\n'), ((239, 30, 239, 79), 'os.path.join', 'os.path.join', ({(239, 43, 239, 65): 'self.settings.root_dir', (239, 67, 239, 78): '"""appleseed"""'}, {}), "(self.settings.root_dir, 'appleseed')", False, 'import os\n'), ((254, 25, 254, 82), 'os.path.join', 'os.path.join', ({(254, 38, 254, 45): 'lib_dir', (254, 47, 254, 58): '"""appleseed"""', (254, 60, 254, 81): '"""_appleseedpython.so"""'}, {}), "(lib_dir, 'appleseed', '_appleseedpython.so')", False, 'import os\n'), ((255, 25, 255, 83), 'os.path.join', 'os.path.join', ({(255, 38, 255, 45): 'lib_dir', (255, 47, 255, 58): '"""appleseed"""', (255, 60, 255, 82): '"""_appleseedpython.pyd"""'}, {}), "(lib_dir, 'appleseed', '_appleseedpython.pyd')", False, 'import os\n'), ((274, 65, 274, 125), 'os.path.join', 'os.path.join', ({(274, 78, 274, 100): 'self.settings.root_dir', (274, 102, 274, 113): '"""appleseed"""', (274, 115, 274, 124): '"""schemas"""'}, {}), "(self.settings.root_dir, 'appleseed', 'schemas')", False, 'import os\n'), ((275, 25, 275, 99), 'os.path.join', 'os.path.join', ({(275, 38, 275, 60): 'self.settings.root_dir', (275, 62, 275, 73): '"""appleseed"""', (275, 75, 275, 84): '"""schemas"""', (275, 86, 275, 98): '""".gitignore"""'}, {}), "(self.settings.root_dir, 'appleseed', 'schemas', '.gitignore')", False, 'import os\n'), ((284, 31, 284, 94), 'os.path.join', 'os.path.join', ({(284, 44, 284, 80): 'self.settings.appleseed_shaders_path', (284, 82, 284, 93): '"""appleseed"""'}, {}), "(self.settings.appleseed_shaders_path, 'appleseed')", False, 'import os\n'), ((285, 31, 285, 96), 'os.path.join', 'os.path.join', ({(285, 44, 285, 80): 'self.settings.appleseed_shaders_path', (285, 82, 285, 95): '"""blenderseed"""'}, {}), "(self.settings.appleseed_shaders_path, 'blenderseed')", False, 'import os\n'), ((307, 41, 307, 97), 'os.path.join', 'os.path.join', ({(307, 54, 307, 76): 'self.settings.root_dir', (307, 78, 307, 89): '"""appleseed"""', (307, 91, 307, 96): '"""lib"""'}, {}), "(self.settings.root_dir, 'appleseed', 'lib')", False, 'import os\n'), ((395, 29, 395, 90), 'os.path.join', 'os.path.join', ({(395, 42, 395, 64): 'self.settings.root_dir', (395, 66, 395, 77): '"""appleseed"""', (395, 79, 395, 84): '"""bin"""', (395, 86, 395, 89): '"""*"""'}, {}), "(self.settings.root_dir, 'appleseed', 'bin', '*')", False, 'import os\n'), ((401, 12, 401, 105), 'os.path.join', 'os.path.join', ({(401, 25, 401, 47): 'self.settings.root_dir', (401, 49, 401, 60): '"""appleseed"""', (401, 62, 401, 67): '"""lib"""', (401, 69, 401, 80): '"""appleseed"""', (401, 82, 401, 104): '"""_appleseedpython3.so"""'}, {}), "(self.settings.root_dir, 'appleseed', 'lib', 'appleseed',\n    '_appleseedpython3.so')", False, 'import os\n'), ((422, 12, 422, 37), 'shutil.copy', 'shutil.copy', ({(422, 24, 422, 27): 'lib', (422, 29, 422, 36): 'lib_dir'}, {}), '(lib, lib_dir)', False, 'import shutil\n'), ((469, 23, 469, 49), 'os.path.basename', 'os.path.basename', ({(469, 40, 469, 48): 'lib_path'}, {}), '(lib_path)', False, 'import os\n'), ((509, 16, 509, 88), 're.match', 're.match', ({(509, 25, 509, 81): '"""(.*) \\\\(compatibility version .*, current version .*\\\\)"""', (509, 83, 509, 87): 'line'}, {}), "('(.*) \\\\(compatibility version .*, current version .*\\\\)', line)", False, 'import re\n'), ((523, 15, 523, 49), 're.search', 're.search', ({(523, 25, 523, 43): '"""Qt.*\\\\.framework"""', (523, 45, 523, 48): 'lib'}, {}), "('Qt.*\\\\.framework', lib)", False, 'import re\n'), ((620, 29, 620, 90), 'os.path.join', 'os.path.join', ({(620, 42, 620, 64): 'self.settings.root_dir', (620, 66, 620, 77): '"""appleseed"""', (620, 79, 620, 84): '"""bin"""', (620, 86, 620, 89): '"""*"""'}, {}), "(self.settings.root_dir, 'appleseed', 'bin', '*')", False, 'import os\n'), ((626, 12, 626, 105), 'os.path.join', 'os.path.join', ({(626, 25, 626, 47): 'self.settings.root_dir', (626, 49, 626, 60): '"""appleseed"""', (626, 62, 626, 67): '"""lib"""', (626, 69, 626, 80): '"""appleseed"""', (626, 82, 626, 104): '"""_appleseedpython3.so"""'}, {}), "(self.settings.root_dir, 'appleseed', 'lib', 'appleseed',\n    '_appleseedpython3.so')", False, 'import os\n'), ((638, 12, 638, 37), 'shutil.copy', 'shutil.copy', ({(638, 24, 638, 27): 'lib', (638, 29, 638, 36): 'lib_dir'}, {}), '(lib, lib_dir)', False, 'import shutil\n'), ((643, 29, 643, 90), 'os.path.join', 'os.path.join', ({(643, 42, 643, 64): 'self.settings.root_dir', (643, 66, 643, 77): '"""appleseed"""', (643, 79, 643, 84): '"""bin"""', (643, 86, 643, 89): '"""*"""'}, {}), "(self.settings.root_dir, 'appleseed', 'bin', '*')", False, 'import os\n'), ((646, 29, 646, 93), 'os.path.join', 'os.path.join', ({(646, 42, 646, 64): 'self.settings.root_dir', (646, 66, 646, 77): '"""appleseed"""', (646, 79, 646, 84): '"""lib"""', (646, 86, 646, 92): '"""*.so"""'}, {}), "(self.settings.root_dir, 'appleseed', 'lib', '*.so')", False, 'import os\n'), ((650, 39, 650, 81), 'os.path.join', 'os.path.join', ({(650, 52, 650, 72): 'appleseed_python_dir', (650, 74, 650, 80): '"""*.so"""'}, {}), "(appleseed_python_dir, '*.so')", False, 'import os\n'), ((702, 17, 702, 38), 'datetime.date.today', 'datetime.date.today', ({}, {}), '()', False, 'import datetime\n'), ((113, 16, 113, 60), 'shutil.rmtree', 'shutil.rmtree', (), '', False, 'import shutil\n'), ((266, 24, 266, 75), 'os.path.join', 'os.path.join', ({(266, 37, 266, 69): 'self.settings.appleseed_bin_path', (266, 71, 266, 74): 'bin'}, {}), '(self.settings.appleseed_bin_path, bin)', False, 'import os\n'), ((303, 16, 303, 48), 'os.path.join', 'os.path.join', ({(303, 29, 303, 41): 'settings_dir', (303, 43, 303, 47): 'file'}, {}), '(settings_dir, file)', False, 'import os\n'), ((314, 70, 314, 103), 'shutil.ignore_patterns', 'shutil.ignore_patterns', ({(314, 93, 314, 102): '"""scripts"""'}, {}), "('scripts')", False, 'import shutil\n'), ((322, 34, 322, 75), 'os.path.join', 'os.path.join', ({(322, 47, 322, 60): '"""blenderseed"""', (322, 62, 322, 74): 'subdirectory'}, {}), "('blenderseed', subdirectory)", False, 'import os\n'), ((325, 29, 325, 62), 'os.path.join', 'os.path.join', ({(325, 42, 325, 55): '"""blenderseed"""', (325, 57, 325, 61): 'file'}, {}), "('blenderseed', file)", False, 'import os\n'), ((359, 24, 359, 50), 'os.path.join', 'os.path.join', ({(359, 37, 359, 44): 'bin_dir', (359, 46, 359, 49): 'dll'}, {}), '(bin_dir, dll)', False, 'import os\n'), ((359, 52, 359, 108), 'os.path.join', 'os.path.join', ({(359, 65, 359, 87): 'self.settings.root_dir', (359, 89, 359, 100): '"""appleseed"""', (359, 102, 359, 107): '"""bin"""'}, {}), "(self.settings.root_dir, 'appleseed', 'bin')", False, 'import os\n'), ((391, 24, 391, 75), 'os.path.join', 'os.path.join', ({(391, 37, 391, 69): 'self.settings.appleseed_lib_path', (391, 71, 391, 74): 'lib'}, {}), '(self.settings.appleseed_lib_path, lib)', False, 'import os\n'), ((548, 19, 548, 38), 'os.path.isfile', 'os.path.isfile', ({(548, 34, 548, 37): 'lib'}, {}), '(lib)', False, 'import os\n'), ((616, 24, 616, 75), 'os.path.join', 'os.path.join', ({(616, 37, 616, 69): 'self.settings.appleseed_lib_path', (616, 71, 616, 74): 'lib'}, {}), '(self.settings.appleseed_lib_path, lib)', False, 'import os\n'), ((117, 16, 117, 31), 'time.sleep', 'time.sleep', ({(117, 27, 117, 30): '(0.5)'}, {}), '(0.5)', False, 'import time\n'), ((438, 22, 438, 48), 'os.path.splitext', 'os.path.splitext', ({(438, 39, 438, 47): 'filename'}, {}), '(filename)', False, 'import os\n'), ((440, 31, 440, 62), 'os.path.join', 'os.path.join', ({(440, 44, 440, 51): 'dirpath', (440, 53, 440, 61): 'filename'}, {}), '(dirpath, filename)', False, 'import os\n'), ((447, 22, 447, 48), 'os.path.splitext', 'os.path.splitext', ({(447, 39, 447, 47): 'filename'}, {}), '(filename)', False, 'import os\n'), ((449, 31, 449, 62), 'os.path.join', 'os.path.join', ({(449, 44, 449, 51): 'dirpath', (449, 53, 449, 61): 'filename'}, {}), '(dirpath, filename)', False, 'import os\n'), ((456, 22, 456, 48), 'os.path.splitext', 'os.path.splitext', ({(456, 39, 456, 47): 'filename'}, {}), '(filename)', False, 'import os\n'), ((458, 31, 458, 62), 'os.path.join', 'os.path.join', ({(458, 44, 458, 51): 'dirpath', (458, 53, 458, 61): 'filename'}, {}), '(dirpath, filename)', False, 'import os\n'), ((534, 23, 534, 41), 'os.path.isabs', 'os.path.isabs', ({(534, 37, 534, 40): 'lib'}, {}), '(lib)', False, 'import os\n'), ((536, 32, 536, 62), 'os.path.join', 'os.path.join', ({(536, 45, 536, 56): 'loader_path', (536, 58, 536, 61): 'lib'}, {}), '(loader_path, lib)', False, 'import os\n'), ((539, 23, 539, 48), 'os.path.exists', 'os.path.exists', ({(539, 38, 539, 47): 'candidate'}, {}), '(candidate)', False, 'import os\n'), ((556, 23, 556, 42), 'os.path.isfile', 'os.path.isfile', ({(556, 38, 556, 41): 'lib'}, {}), '(lib)', False, 'import os\n'), ((713, 32, 713, 50), 'platform.mac_ver', 'platform.mac_ver', ({}, {}), '()', False, 'import platform\n'), ((291, 32, 291, 53), 'os.path.join', 'os.path.join', ({(291, 45, 291, 49): 'root', (291, 51, 291, 52): 'f'}, {}), '(root, f)', False, 'import os\n'), ((310, 37, 310, 58), 'os.path.join', 'os.path.join', ({(310, 50, 310, 54): 'root', (310, 56, 310, 57): 'f'}, {}), '(root, f)', False, 'import os\n'), ((537, 27, 537, 52), 'os.path.exists', 'os.path.exists', ({(537, 42, 537, 51): 'candidate'}, {}), '(candidate)', False, 'import os\n'), ((538, 36, 538, 72), 'os.path.join', 'os.path.join', ({(538, 49, 538, 66): '"""/usr/local/lib/"""', (538, 68, 538, 71): 'lib'}, {}), "('/usr/local/lib/', lib)", False, 'import os\n'), ((700, 22, 700, 97), 'subprocess.Popen', 'subprocess.Popen', (), '', False, 'import subprocess\n'), ((715, 32, 715, 50), 'platform.mac_ver', 'platform.mac_ver', ({}, {}), '()', False, 'import platform\n')]