#!/usr/bin/env python

# Read gtk jhbuild xml module that has been produced by
# gtk_xml_recursive_process.py from stdin and transform it into data
# required for epa_build using the ElementTree XML API.

# Copyright (C) 2013 Alan W. Irwin

# This file is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.

# This file 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
# Lesser General Public License for more details.

# You should have received a copy of the GNU Lesser General Public
# License along with this file; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA

import sys
import xml.etree.ElementTree as ET

def parse_jhbuild(root, id, depend_track, if_dependencies, called):
    # find all necessary data to build.  If xml is in slightly non-standard
    # form or if there is any other trouble, then immediately return
    # with no output to stdout.
    if called.has_key(id):
        raise RuntimeError, "parse_jhbuild recursively called with the same id"
    called[id] = None
    #sys.stderr.write("id = %s\n" % id)
    package = root.findall(".//*[@id='%s']" % id)
    if len(package) < 1:
        # Cannot find this package.  Do nothing.
        return None
    elif len(package) > 1:
        raise RuntimeError, "non-unique id found for %s" % id
    # From here on drop the reference to the first (and only) element
    # of package for convenience.
    package = package[0]

    # From now only handle two build types and only a subset of
    # attributes for both, and only look for
    # branch and dependencies elements (of various kinds) below
    # packages.
    config_type = package.tag
    if config_type == "autotools":
        config_arguments = package.get("autogenargs")

        make_arguments = package.get("makeargs")
        if make_arguments == None:
            make_arguments = ""

        supports_non_srcdir_builds = package.get("supports-non-srcdir-builds")
        if supports_non_srcdir_builds == "no":
           supports_non_srcdir_builds = "OFF"
        else:
           supports_non_srcdir_builds = "ON" 

        supports_parallel_builds = package.get("supports-parallel-builds")
        if supports_parallel_builds == "no":
           supports_parallel_builds = "OFF"
        else:
           supports_parallel_builds = "ON" 
    elif config_type == "cmake":
        config_arguments = package.get("cmakeargs")
        make_arguments = ""
        # Assume both non-source builds and parallel builds work for
        # CMake-based build systems.
        supports_non_srcdir_builds = "ON" 
        supports_parallel_builds = "ON" 
    elif config_type == "tarball":
        config_arguments = ""
        make_arguments = ""
        # Assume both non-source builds and parallel builds work for
        # the tarball config type.  This may require review.
        supports_non_srcdir_builds = "ON" 
        supports_parallel_builds = "ON" 
    else:
        return None

    if config_arguments == None:
        config_arguments = ""

    # Parse branch or source element of package
    if config_type == "tarball":
        branch = package.findall("source[@hash]")
    else:
        branch = package.findall("branch[@hash]")
    if len(branch) < 1:
        # Cannot find branch so do nothing.
        return None
    elif len(branch) > 1:
        raise RuntimeError, "non-unique branch element found with hash attribute for %s" % id
    
    # From here on drop the reference to the first (and only) element
    # of branch for convenience.
    branch = branch[0]
    
    download_hash = branch.get("hash")
    if len(download_hash) == 0:
        return None
    index = download_hash.find(":")
    if index <= 0:
        download_hash_type = "UNKNOWN"
    else:
        download_hash_type = download_hash[0:index].upper()
    if index >= 0:
        download_hash = download_hash[index+1:]
    
    if config_type == "tarball":
        download_href = branch.get("href")
    else:
        download_repo = branch.get("repo")
        if len(download_repo) == 0:
            return None
    
        download_module = branch.get("module")
        if len(download_module) == 0:
            return None
        if repository_dictionary.has_key(download_repo):
            download_repo = repository_dictionary[download_repo]

        # Make sure there is a trailing "/" on the repo
        index = download_repo.rfind("/")
        if len(download_repo)-1 != index:
            download_repo = download_repo + "/"
        download_href = download_repo + download_module
    
    # Replace ${version} string that is sometimes in download_href
    index = download_href.find("${version}")
    if index >=0:
        version = branch.get("version")
        if version == None:
            return None
        download_href = download_href.replace("${version}", version)
    
    # Parse various kinds of jhbuild dependencies.
    # Note from 
    # http://stackoverflow.com/questions/9974957/what-is-the-after-element-used-for-in-jhbuild
    # "dependencies are hard dependencies. Packages that are
    # required to build a module.  suggests are soft
    # dependencies. Packages might use them if they are installed
    # (detected at build time) but if they are not present, they
    # do not present a problem. These dependencies will be built,
    # but they can be ignored without problems by using the
    # argument --ignore-suggests. For instance, evolution can be
    # built with or without nss support.  after are not strict
    # dependencies, but they are needed at runtime to get some
    # features. For instance, metacity is needed by mutter to have
    # key binding settings in mutter."

    # Create dependencies dictionary and populate it as needed.
    dependencies={}
    if depend_track&1:
        # Add a dependency for pkg-config if there is a subelement named that.
        if package.find("pkg-config") != None:
            dependencies["pkg-config"] = None
        for dep_element in package.findall("dependencies/dep"):
            dependencies[dep_element.get("package")] = None

    # Create suggests dictionary and populate it as needed.
    suggests={}
    if depend_track&2:
        for dep_element in package.findall("suggests/dep"):
            suggests[dep_element.get("package")] = None
        # Repeat for suggest target name (a mistake for one package
        # with id=gdm).
        for dep_element in package.findall("suggest/dep"):
            suggests[dep_element.get("package")] = None
        
    # Create after dictionary and populate it as needed.
    after={}
    if depend_track&4:
        for dep_element in package.findall("after/dep"):
            after[dep_element.get("package")] = None
    
    if if_dependencies:
        overall_dependencies = {}
        overall_dependencies.update(dependencies)
        overall_dependencies.update(suggests)
        overall_dependencies.update(after)

        found_packages = {}
        found_packages[id] = None
        not_found_packages = {}
        dependencies_list = overall_dependencies.keys()
        for dep in dependencies_list:
            if called.has_key(dep):
                # ignore any package that has already been processed
                # by parse_jhbuild.  This avoids calling parse_jhbuild
                # twice for the case of two dependent packages
                # depending on a common third package and also ignores
                # circular dependencies.
                continue
            extra = parse_jhbuild(root, dep, depend_track, if_dependencies, called)
            if extra == None:
                not_found_packages[dep] = None
            else:
                found_packages.update(extra[0]) 
                not_found_packages.update(extra[1]) 

        return (found_packages, not_found_packages)
    else:
        sys.stdout.write(id + "\n")
        sys.stdout.write(config_type + "\n")
        sys.stdout.write(supports_non_srcdir_builds + "\n")
        sys.stdout.write(supports_parallel_builds + "\n")
        sys.stdout.write(config_arguments + "\n")
        sys.stdout.write(make_arguments + "\n")
        sys.stdout.write(download_hash_type + "\n")
        sys.stdout.write(download_hash + "\n")
        sys.stdout.write(download_href + "\n")

        # Output dependency lists as sorted colon-separated strings
        dependencies_list = dependencies.keys()
        dependencies_list.sort()
        dependencies_string = ":".join(dependencies_list)
        sys.stdout.write(dependencies_string + "\n")
        suggests_list = suggests.keys()
        suggests_list.sort()
        suggests_string = ":".join(suggests_list)
        sys.stdout.write(suggests_string + "\n")
        after_list = after.keys()
        after_list.sort()
        after_string = ":".join(after_list)
        sys.stdout.write(after_string + "\n")

tree = ET.parse(sys.stdin)
root = tree.getroot()

# Create repository dictionary.  Note, there is sometimes more than
# one definition.  Eyeballing the file, it appears those definitions
# are completely redundant so it is fine to take the last definition
# (which we do here).
repository_dictionary={}
for repository in root.findall("repository"):
    repository_dictionary[repository.get("name")] = repository.get("href")

if len(sys.argv) < 3:
        raise RuntimeError, "must specify a starting package name as the first argument and dependency tracking flag as the second argument on the command line"
start_package = sys.argv[1]
# Important!
# The least significant bit of depend_track shows whether to pay
# attention to the dependencies attribute.
# The next least significant bit of depend_track shows whether to pay
# attention to the suggests attribute.
# The next least significant bit of depend_track shows whether to pay
# attention to the after attribute.
depend_track = int(sys.argv[2])
dependency_dictionary = parse_jhbuild(root, start_package, depend_track, True, {})
if dependency_dictionary == None:
    sys.stderr.write("some failure for start_package = %s or else no dependencies for that start_package\n" % start_package)
else:
    found_packages_list = dependency_dictionary[0].keys()
    not_found_packages_list = dependency_dictionary[1].keys()
    found_packages_list.sort()
    not_found_packages_list.sort()
    sys.stderr.write("number of packages that are found = %s\n" % len(found_packages_list))
    sys.stderr.write("packages that are found = " + ":".join(found_packages_list) + "\n")
    sys.stderr.write("number of packages that are not found = %s\n" % len(not_found_packages_list))
    sys.stderr.write("packages that are not found = " + ":".join(not_found_packages_list) + "\n")

    # Output on stdout results for the packages that have been found.
    for id in found_packages_list:
       parse_jhbuild(root, id, depend_track, False, {}) 
