#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Dehydra and Treehydra scriptable static analysis tools
# Copyright (C) 2007-2010 The Mozilla Foundation
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# 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.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
# Author: Taras Glek
# OSX support by Vlad Sukhoy
import getopt, sys, os.path, os, platform, popen2, tempfile

def usage():
    print """
Usage: ./configure ...
--js-name= part between lib and .so in libjs.so on your system (Usually js, mozjs on Ubuntu)
--js-libs= Location of libjs.so
--js-headers= SpiderMonkey headers, location of jsapi.h. Might get autodetected correctly from --js-libs.
--gcc-build= GCC build directory. Defaults to ../gcc-build
--gcc-src= GCC source directory. Autodetected from build dir
--enable-C Enable experimental C support""".lstrip()
    pass

def error(msg):
    print >> sys.stderr, "Error: " + msg
    sys.exit(1)

def checkdir(path,opt):
    print "Checking " + opt + ": " + path
    if not os.path.isdir (path):
        error("directory '"+path+"' doesn't exist, specify correct directory with --" + opt)

def get_gcc_srcdir(build_dir):
    checkdir(build_dir, "gcc-build")
    makefile = os.path.join(build_dir, "Makefile")
    try:
        for line in open (makefile).readlines():
            arr = line.split(" = ")
            if len(arr) and arr[0] == "srcdir":
                src_dir = arr[1].rstrip()
                if not os.path.isabs(src_dir):
                    src_dir = os.path.join(build_dir, src_dir)
                return os.path.realpath(src_dir)
        raise "Couldn't find srcdir in gcc Makefile: " + makefile
    except:
        error("Invalid gcc build dir " + build_dir)

def check_gcc_srcdir(config, gcc_src):
    sys.stdout.write ("Checking if GCC supports plugins via mozilla patch: ")
    if os.path.isfile (os.path.join (gcc_src, "gcc/tree-plugin-pass.c")):
        print ("Mozilla plugin framework patch")
        config["PLUGINS_MOZ"] = "Using Mozilla's patch to pre-plugin GCCs'"
        config["PLUGIN_ARG"] = "-fplugin-arg"
    else:
        sys.stdout.flush()
        error("GCC sources in %s do not have the plugin patch applied" % gcc_src)

def getjsdirs(jsname, jslibs, jsheaders):
    if jslibs == None:
        libjs = "/usr/lib/lib" + jsname + dynamic_lib_suffix
        if (os.path.isfile (libjs)):
            jslibs = os.path.dirname(libjs)
            print "Detected JavaScript library: " + libjs
    else:
        jslibs = os.path.realpath(jslibs)
        checkdir (jslibs, "js-libs")

    if jsheaders != None:
        checkdir (jsheaders, "js-headers")
    elif jslibs != None:
        jsapi = os.path.dirname(os.path.normpath(jslibs)) + "/include/" + jsname + "/jsapi.h"
        if (os.path.isfile (jsapi)):
            jsheaders = os.path.dirname(jsapi)
            print "Detected JavaScript headers: " + jsheaders

    return (jslibs, jsheaders)

def detect_cxx(config):
    sys.stdout.write ("Checking for a C++ compiler: ")
    cxx="g++"
    if platform.system() == 'Darwin':
        cxx = "g++-4.2"
    try:
        cxx = os.environ["CXX"]
    except:
        pass
    try:
        cxx.find("../")
        cxx = os.path.abspath(cxx)
    except:
        pass
    config["CXX"] = cxx

def try_enable_treehydra(config):
    cxx = config["CXX"]
    if "PLUGINS_MOZ" in config:
        sys.stdout.write ("Checking if %s accepts -fplugin for building treehydra: " % cxx)
        cout, cin, cerr = popen2.popen3([cxx, '-fplugin=', 'dehydra.c'])
        if cerr.read().find("missing argument") != -1:
            config["BUILD_TREEHYDRA"] = "gcc_treehydra.so"
            print("Yes")
        else:
            print("No")
    else:
        config["BUILD_TREEHYDRA"] = "gcc_treehydra.so"
        print "Will use %s to build Treehydra" % cxx

def try_enable_finish_decl(config):
    print("Checking for FINISH_DECL callback:")
    cxx = config["CXX"]
    file = tempfile.NamedTemporaryFile("w+b", -1, ".c")
    file.write(
"""#include "gcc-plugin.h"
enum plugin_event p = PLUGIN_FINISH_DECL;
""")
    file.flush()
    po = popen2.Popen3("cc -c -o /dev/null " + config["GCC_PLUGIN_HEADERS"] + " " + file.name)
    if po.wait() == 0:
        config['FINISH_DECL'] = "yes"
        print("FINISH_DECL callback enabled");
    else:
        print("FINISH_DECL callback disabled");
    file.close()

def try_gcc_plugin_headers(config):
    cxx = config["CXX"]
    sys.stdout.write ("Checking for %s plugin headers: " % cxx)
    cout, cin, cerr = popen2.popen3([cxx, '-print-file-name=plugin'])
    path = cout.read().strip()
    if os.path.exists(path):
        path = os.path.abspath(path +"/include")
        config["GCC_PLUGIN_HEADERS"] = "-I%s" % (path)
        config["PLUGIN_ARG"] = "-fplugin-arg-gcc_dehydra-script"
        print(path)
        try_enable_finish_decl(config)
    else:
        print("No. Will rely on for Mozilla plugin API and build/src dirs")
        config["GCCBUILDDIR"] = os.path.realpath(config["GCCBUILDDIR"])
        if config["GCCDIR"] == None:
            config["GCCDIR"] = get_gcc_srcdir (config["GCCBUILDDIR"])
        checkdir(config["GCCDIR"], "gcc-src")
        check_gcc_srcdir(config, config["GCCDIR"])
        config["GCC_PLUGIN_HEADERS"] ="""-I$(GCCBUILDDIR)/$(GCCSTAGE) -I$(GCCDIR)/gcc \
-I$(GCCDIR)/gcc/. -I$(GCCDIR)/gcc/../include -I$(GCCDIR)/gcc/../libcpp/include \
-I$(GCCDIR)/gcc/../libdecnumber -I$(GCCDIR)/gcc/../libdecnumber/bid \
-I$(GCCBUILDDIR)/libdecnumber -I$(GCCBUILDDIR)""".replace("\\\n","")



if __name__ == "__main__":
    dynamic_lib_suffix, shared_link_flags = ('.so', '-shared') 
    cflags = ''
    if platform.system() == 'Darwin' :
        dynamic_lib_suffix, shared_link_flags = ('.dylib',
            '-bundle -flat_namespace -undefined suppress')
        cflags += '-fnested-functions'

    try:
        opts, args = getopt.getopt(sys.argv[1:], "h",
                                   ["js-name=", "js-libs=", "js-headers=", "gcc-src=", "gcc-build=", "enable-C"])
    except getopt.GetoptError, err:
        # print help information and exit:
        print str(err) # will print something like "option -a not recognized"
        usage()
        sys.exit(2)
    jsname = "mozjs"
    jsnamels = [jsname, "js"]
    jslibs = None
    jsheaders = None
    gcc_build = "../gcc-build"
    gcc_src = None
    Csupport = False
    for o, val in opts:
        if o == "--js-name":
            jsname = val
            jsnamels = [jsname]
        elif o == "--js-libs":
            jslibs = os.path.expanduser(val)
        elif o == "--js-headers":
            jsheaders = os.path.expanduser(val)
        elif o == "--gcc-build":
            gcc_build = os.path.expanduser(val)
        elif o == "--enable-C":
            Csupport = True
        elif o in ("-h", "--help"):
            usage()
            exit(0)
        else:
            error("unhandled option " +  o)
    #Detect js stuff
    for loop_jsname in jsnamels:
        before = [jslibs, jsheaders]
        jslibs, jsheaders = getjsdirs(loop_jsname, jslibs, jsheaders)
        jsname = loop_jsname
        if (jslibs != None and jsheaders != None) or [jslibs, jsheaders] != before:
            break
    if jslibs == None:
        error("Must indicate javascript library directory with --js-libs=")
    if jsheaders == None:
        error("Must indicate javascript header directory with --js-headers=")
    
    if os.environ.has_key("CFLAGS"):
        cflags += ' ' + os.environ["CFLAGS"]
    config =dict(GCCDIR=gcc_src, GCCBUILDDIR=gcc_build, 
              SM_NAME=jsname, SM_SUFFIX=dynamic_lib_suffix,
              SM_INCLUDE=jsheaders, SM_LIBDIR=jslibs,
              SHARED_LINK_FLAGS=shared_link_flags, 
              CONFIGURE_CFLAGS=cflags)
    if Csupport:
        config['C_SUPPORT'] = "yes"
        print("Experimental C testsuite is enabled");
    else:
        print("Disabling C testsuite");
    detect_cxx(config)
    try_gcc_plugin_headers(config)
    try_enable_treehydra(config)
    f = open("config.mk", "w")
    f.write("""# %s
%s
""" % (" ".join(["'" + a + "'" for a in sys.argv]),
       "\n".join([k + "=" + str(v) for k, v in config.iteritems()])))
    f.close()
    f = open("dehydra-config.h", "w")
    f.write("""#ifndef DEHYDRA_CONFIG_H
#define DEHYDRA_CONFIG_H
%s
#endif
""" % ("\n".join(["#define CFG_%s \"%s\"" % (k,str(v).replace('"','\\"')) 
                  for k, v in config.iteritems()])))
    f.close()

    if os.path.exists("Makefile"):
        os.unlink("Makefile")
    os.symlink ("Makefile.in", "Makefile")
    
