import sys, os, glob, string, types
import grammar, generator, fortran_compiler, configuration, version
from distutils.core import setup, Extension
default_makefile = """\
SOURCES=%(sources)s
lib%(name)s.a: $(SOURCES)
\t$(Pyfort_COMPILER) $(Pyfort_COMPILER_OPTIONS) -c $(SOURCES)
\tld -r -o lib%(name)s.a *.o
clean:
\t/bin/rm -f lib%(name)s.a *.o
"""
def execute (line):
    print line
    result = os.system(line)
    if result: 
        raise ValueError, 'Error executing '+line

class pyf:
    """pyf(filename, 
           sources=[], 
           libraries = '',
           library_directories = '',
           compiler_options = '',
           python_directory = '',
           generate_as = '',
           package_name = '',
           freeform = 0,
           use_c_compiler = 0,
       )
       Represents a Pyfort input file that depends on the given sources.
       The sources can be given as a list and will be used as input to 
       glob.glob.
   
       libraries is a list of library objects.
       
       python_directory = directory holding associated python modules, if any.
       freeform = 1 or 0, indicating whether format of file is freeform or 
          column 1 has special meaning
       package_name is an optional name for the module into which the 
          Python code if any and the generated Pyfort extension should be
          installed.
"""

    def __init__ (self, filename, **kw):
        if filename:
            head, tail = os.path.split(filename)
            self.name, junk = os.path.splitext (tail)
        else:
            self.name = ''
        self.filename = filename
        self.sources = kw.get('sources', [])
        self.libraries = kw.get('libraries', '')
        self.library_directories = kw.get('library_directories', '')
        self.compiler_options = kw.get('compiler_options', '')
        self.generate_as = kw.get('generate_as', '')
        self.use_c_compiler = kw.get('use_c_compiler',0)
        self.freeform = kw.get('freeform', 0)
        self.python_directory = kw.get('python_directory', '')
        self.package_name = kw.get('package_name', '')

    def __repr__ (self):
        result = "pyf('%s'" % self.filename
        if self.sources:
            result = result + ", \n    sources=" + repr(self.sources)
        if self.libraries:
            result = result + ", \n    libraries=" + repr(self.libraries)
        if self.library_directories:
            result = result + ", \n    library_directories=" + repr(self.library_directories)
        if self.compiler_options:
            result = result + ", \n    compiler_options=" + repr(self.compiler_options)
        if self.python_directory != '':
            result = result + ", \n    python_directory=" + \
                                 repr(self.python_directory) 
        if self.package_name != '':
            result = result + ", \n    package_name=" + \
                                 repr(self.package_name) 
        if self.generate_as != '':
            result = result + ", \n    generate_as=" + \
                                 repr(self.generate_as) 
        if self.use_c_compiler != 0:
            result = result + ", \n    use_c_compiler="+repr(self.use_c_compiler) 
        if self.freeform != 0:
            result = result + ", \n    freeform=" + repr(self.freeform)
        result = result + ")"
        return result

    def is_advanced (self):
        if self.libraries or self.library_directories or \
               self.compiler_options or self.python_directory or \
               self.package_name:
            return 1
        else:
            return 0

    def generated_module_name (self):
        if self.generate_as: return self.generate_as
        return self.name

    def validate (self):
        "Check the pyf. If not valid, return an error message."
        if not os.path.isfile(self.filename):
            return self.filename + "does not exist."
        if self.python_directory:
            if not os.path.isdir(self.python_directory):
                return "In %s, Python directory %s specified does not exist."\
                      % (self.filename, self.python_directory,)
            if os.path.isfile(os.path.join(self.python_directory, '__init__.py')):
                # Package case
                if not self.package_name:
                    return """In re %s, Python directory %s
contains an __init__.py file, so  you must specify a module name.
""" % (self.filename, self.python_directory)
        else:  # do not have Python
            if (not self.sources) and (not self.libraries):
                return "In %s, no Python directory, libraries, or sources given."\
                      % (self.filename,)
            if self.package_name: 
                return """%s specifies a package name without a Python directory""" % (self.filename,)
        for s in self.sources:
            g = glob.glob(s)
            if not g:
                return self.name + ' source ' + s + \
                       ' does not match any existing file.'
            for x in g:
                name, ext = os.path.splitext(x)
                if not ext:
                    return self.name + ' source ' + s + \
                       ' lacks an extension. Cannot tell if C or Fortran.'
                if ext.lower() == ".c" and not self.use_c_compiler:
                    return self.name + ' source ' + s + \
                       ' is C, but C option not selected.'
                if ext != ".c" and self.use_c_compiler:
                    return self.name + ' source ' + s + \
                       ' is not C, but C option is selected.'
        return ''

    def expanded_sources (self):
        """The expanded list of source files."""
        sources = []
        for x in self.sources:
            sources += [os.path.abspath(\
                        os.path.expanduser(y)) \
                        for y in glob.glob(x)
                       ]
        return sources
        
    def build (self, flags):
        """ build(flags); flags is a dictionary set up by the outer build.

There are three cases:
1. Python package, install everything in it.
2. Python supplied but it isn't a package 
    -- install it and the C at top level
3. No python -- just install C extension at top level
For cleanliness and uninstall, put everything in an "extra_path" area.
"""
        print "Building Pyfort module", self.name
# This validation removes such coding from build itself.
        msg = self.validate()
        if msg: 
            raise SystemExit, msg
        library_directory_list = self.library_directories.split()
        library_names = self.libraries.split()
        libname = 'pyfort_'+self.name
        dir = os.path.abspath(os.path.join('build',libname))
        outdir = flags.get('outdir','')
        if not outdir: 
            outdir = dir
            if not os.path.isdir(outdir):
                os.makedirs(dir)
        minusg = flags.get('minusg', '') 
        if self.sources:
            library_names.insert(0, libname)
            if dir not in library_directory_list:
                library_directory_list.append(dir)
            if not os.path.isdir(dir):
                os.makedirs(dir)
            makefile = os.path.join(dir, "Makefile")
            if os.path.isfile(makefile):
                os.remove(makefile)
            d = { 'name': libname, 
                  'sources': string.join(self.expanded_sources()),
                }
            print >> sys.stderr, "Creating a simple Makefile at ", \
                                  os.path.join(dir, makefile)
            f = open(makefile, 'w')
            print >> f, default_makefile % d
            f.close()
            here = os.getcwd()
            os.chdir(dir)
            if self.use_c_compiler:
                if os.environ.has_key('CC'):
                    c = os.environ['CC']
                else:
                    c = 'cc'
                os.environ['Pyfort_COMPILER'] = c
            else:
                compiler_id = flags.get('fortran_compiler_id', 'default')
                compiler_object = fortran_compiler.get_compiler(compiler_id)
                compiler_name = compiler_object.executable_name()
                os.environ['Pyfort_COMPILER'] = compiler_name
            os.environ['Pyfort_COMPILER_OPTIONS'] = self.compiler_options + " " + \
                                             minusg
            try:
                execute('make')
            except ValueError, msg:
                print '****** Error occurred; your extension was not built.'
                raise SystemExit, 1
            os.chdir(here)

        command = flags.get('command')
        if configuration.prefix:
            command = command.replace('install', 
                                   'install --prefix='+configuration.prefix)
        project_name = flags.get('project_name')+configuration.project_suffix
        fortran_compiler_id = flags.get('fortran_compiler_id')
        if self.use_c_compiler:
            compiler = fortran_compiler.get_compiler('cc')
        else:
            compiler = fortran_compiler.get_compiler(fortran_compiler_id)
        lversion = flags.get('version', version.version)
        sys.argv = ["PYFORT"]+command.split() # Distutils uses sys.argv, sick.
        gmn = self.generated_module_name()
        setupargs = {'name': gmn + "_extension",
                     'version': lversion,
                    }
        if not configuration.prefix:
            setupargs['extra_path'] =  project_name

        if self.python_directory:
            if os.path.isfile(os.path.join(self.python_directory, '__init__.py')):
                # Package case
                module_id = self.package_name + "." + gmn
                setupargs['packages'] = [self.package_name]
                setupargs['package_dir'] = {self.package_name:self.python_directory}
            else:
                # Have Python but not a package
                module_id = gmn
                setupargs['packages'] = ['']
                setupargs['package_dir'] = {'':self.python_directory}
        else:
            # No Python at all
            module_id = gmn
        dfile, cfile = process(gmn, self.filename, compiler, 
                               outdir, not self.freeform)
        if cfile:
# Note we add in the Fortran runtime libraries here...
            extspec = { 
            'name': module_id,
            'library_dirs': library_directory_list + compiler.dirlist,
            'libraries': library_names + compiler.liblist, 
            'sources': [cfile],
             }
            eobj = apply(Extension, [], extspec)
            setupargs['ext_modules'] = [eobj]
        print "Executing setup ", command, "using these arguments:"
        for key, value in setupargs.items():
           if key == 'ext_modules':
               print "    ", "External module specified by:"
               for k, v in extspec.items():
                   print "        ", k, "=", repr(v)
           else:
               print "    ", key, "=", repr(value)
        print "---------------------------------"
        if command != "echo":
            apply (setup, [], setupargs)

def process(pyf_module_name, pathname, compiler, outdir, column1_comments):
    """Processes the input file pointed to by pathname to create desired module.
       Returns the names of the two files created.
    """
    tokens = grammar.scan(pathname, column1_comments)
    if not tokens:
        return '',''
    else:
        proclist = grammar.parse(tokens)
    print "Generating documentation file"
    d = generator.Document_Generator(pyf_module_name, ".", proclist, [])
    d.generate()
    print "Generating Pyfort interface in ", outdir
    w = generator.C_Module_Generator(compiler, pyf_module_name, outdir, proclist, [])
    w.generate()
    return (d.filename, w.filename)

class project:
    "A contributed project"
    def __init__ (self, filename):
        self.filename = filename
        dir, basename = os.path.split(filename)
        name, extension = os.path.splitext(basename)
        if not name:
            raise ValueError, 'Must supply a non-blank name for the project.'
        if extension != ".pfp":
            raise ValueError, 'Not a project file: '+ filename
        self.name = name
        self.pyfs = []
        print "Reading", self.filename
        if os.path.isfile(self.filename):
            self.read (self.filename)
        else:
            print "Note: ", self.filename, "does not exist."

    def read (self, filename):
        "Read a saved state from the file."
        d = {'pyf': self.add_pyf}
        execfile(filename, d)
       
    def save (self, filename=None):
        if filename is None:
            filename = self.filename
        f = open(filename, 'w')
        print >>f, str(self)
        f.close()
 
    def validate (self):
        "return a message if project not valid"
        for pyf in self.pyfs:
            msg = pyf.validate()
            if msg: return msg
        return ''

    def __str__ (self):
        result = ""
        for item in self.pyfs:
            result = result + repr(item) + '\n'
        return result

    def add_pyf (self, filename, **kw):
        self.pyfs.append(pyf(filename, **kw))

    def __repr__ (self):
        return "project(%s)" % self.filename

    def build (self, flags):
        flags['package'] = self.name
        for item in self.pyfs:
            item.build(flags)


def build(filename, flags):
    print "Building project", filename
    dir, base = os.path.split(filename)
    here = os.getcwd()
    if dir and dir != here: 
        print "Changing to directory", dir
        os.chdir(dir)
    for k in flags: print k, "=", flags[k]
    p = project(filename)
    p.build(flags)
    os.chdir(here)
    


syntax highlighted by Code2HTML, v. 0.9.1