#! /usr/bin/python
"""
#+
# NAME:
#	mk
# CALLING SEQUENCE:
#	mk.py main sub1 sub2 ... -dir=<dir>
# INPUTS:
#	main sub1 sub2 ...
#		name of main program, followed by additional
#		files needed for the compilation.
#		The file type, if specified, is ignored and is
#		replaced by .f
# OPTIONAL INPUTS:
#	-g77		use g77 compiler
#	-intel		use Intel Fortran 90 compiler
#	-pgi		use Portland compiler
#	-absoft 	use Absoft Fortran compiler
#
#	-nogen		do not include the forgen library
#	-nofits		do not include the cfitsio library
#	-noincl		do not use default include paths
#
#	If none of these command line switches is set then
#	the script will try to locate g77 first, then the Intel
#	compiler.
#
#	-dir=dir	destination directory for object files or executable/
#			The default destination is the current directory.
#	-cc		include libraries for linking C/C++ code
#	-static		adds switch -static to compile command
#	-obj		only creates object files (used by olb script)
#	-verbose	verbose mode
#	-keep_id	if set, the file "get_compiler.f" is NOT deleted.
# OUTPUTS:
#	The executable will be named 'main' (the name of the main program).
# RESTRICTIONS:
#	The g77 command includes the switches
#		-ffixed-line-length-none
# SIDE EFFECTS:
#	Cannot be executed in the 'root' account.
#
#	Include directories:
#		$SMEI_FOR/h, $SMEI_FOR/h/linux and $SMEI/for/h
#	Library directory  :
#		Libraries should be located in $SMEI_LIB or $FOR
#		Two libraries are expected: gen and cfitsio.
#		$SMEI_LIB should always contain these.
# MODIFICATION HISTORY:
#	JAN-2000, Paul Hick (UCSD/CASS)
#	JAN-2001, Paul Hick (UCSD/CASS)
#		Added loop to handle compilation of  multiple files.
#	SEP-2002, Paul Hick (UCSD/CASS)
#		Removed -fdollar-ok option from g77 compilations (all dollar
#		signs in variable names should have been removed by now).
#	DEC-2003, Paul Hick (UCSD/CASS)
#		Added -lcfitsio to compile command (for Fortran FITS routines).
#	MAY-2004, Paul Hick (UCSD/CASS)
#		Modifications to get this to run on the stesun Unix machines.
#		Use awk instead of gawk (on our Linux boxes awk is a soft link
#		to gawk). Changed the syntax for variable assignment from
#			awk -v i=$i '{print $i}' to awk '{print $i}' i=$i
#		The first one doesn't work properly on the stesun machines.
#	JUL-2004, Paul Hick (UCSD/CASS)
#		Added -Vaxlib switch for Intel compiler
#	SEP-2004, Paul Hick (UCSD/CASS)
#		The default location for the libgen library is now $SMEI_LIB
#		(was $FOR).
#	OCT-2004, Paul Hick (UCSD/CASS)
#		Added Portland compiler.
#	JAN-2005, Paul Hick (UCSD/CASS)
#		Added -cc switch (will add a couple of C/C++ libraries
#		needed to cross compile Fortran and C/C++ code).
#	JAN-2005, Paul Hick (UCSD/CASS)
#		Converted from bash to Python.
#	JUL-2005, Paul Hick (UCSD/CASS)
#		Added code to handle .o, .c and .cpp files
#		The main program still must be a Fortran program.
#	MAR-2007, Paul Hick (UCSD/CASS)
#		SpatialIndex library is no longer included for linking.
#	OCT-2007, Paul Hick (UCSD/CASS; pphick@ucsd.edu)
#		Added -nogen and -nofits keyword
#		Added test for existence of get_compiler_<f77id> function.
#		If it exists already, use it. If not, create one.
#		Previously the file was always created, resulting with
#		a conflict with an existing one.
#	MAR-2011, John Clover (UCSD/CASS)
#		Modified f77cc for intel compiler to build for 64 bit only
#- 
"""
import os
from tiny import is_there, start, args, hide_env, which, run_cmd

def set_compiler( f77id ):

	import tempfile
	tempfile.tempdir = os.environ['TUB']

	tempfile = tempfile.mktemp('get_compiler.f')
	content = [	\
		'	subroutine get_compiler(f77id)'	, \
		'	character	f77id*(*)'			, \
		"	f77id = '"+f77id+"'"			, \
		'	return'							, \
		'	end'							]

	(open(tempfile,'w')).write('\n'.join(content))

	return tempfile

def mk( argv ):

	no_result = dict (	[ ('f77', []), ('obj', []) ] ) 
	say  = 'mk, '
	user = os.environ['USER']

	if user == 'root':
		print say+'do not use from root account'
		return no_result

	# Check for keywords

	incl_cc = is_there( '-cc'     , argv )
	static  = is_there( '-static' , argv )
	verbose = is_there( '-verbose', argv )
	obj     = is_there( '-obj'    , argv )
	all		= is_there( '-all'    , argv )
	nogen	= is_there( '-nogen'  , argv )
	nofits	= is_there( '-nofits' , argv )
	noincl	= is_there( '-noincl' , argv )
	keep_id = is_there( '-keep_id', argv )

	out_dir = start   ( '-dir='  , argv )
	if out_dir == '':
		to_remote = False
		out_dir = os.environ['PWD']
	else:
		to_remote = out_dir.find( ':' ) != -1
		if to_remote:
			remote_dir = out_dir
			out_dir = os.environ['TUB']

	fortran_files = []
	cminus_files  = []
	cplus_files   = []
	object_files  = []

	if all:

		names = os.listdir( os.environ['PWD'] )

		for name in names:
			tmp = os.path.splitext( name )
			if tmp[1] == '.f':
				fortran_files.append( tmp[0] )
			if tmp[1] == '.c':
				cminus_files.append ( tmp[0] )
			if tmp[1] == '.cpp':
				cplus_files.append  ( tmp[0] )
			if tmp[1] == '.o':
				object_files.append ( name )

		found = 0

		for main in fortran_files:
			tmp = (open( os.path.join( '.', main+'.f') )).read()
			tmp = tmp.split('\n')
			for line in tmp:
				if len(line) > 9:
					found = line.expandtabs(7).lower().find('       program ') == 0
					if found:
						break
			if found:
				break
		else:
			print say+'no main program found'
			return no_result

		fortran_files.remove(main)
		fortran_files.insert(0,main)

	else:

		names = args( argv )
		names = names[1:len(names)]

		for name in names:
			tmp = os.path.splitext( name )
			if tmp[1] == '.f' or tmp[1] == '':
				fortran_files.append( tmp[0] )
			if tmp[1] == '.c':
				cminus_files.append ( tmp[0] )
			if tmp[1] == '.cpp':
				cplus_files.append  ( tmp[0] )
			if tmp[1] == '.o':
				object_files.append ( name )

	if len(fortran_files) == 0:
		print say+'usage: mk.py main file1 file2 -dir=<dir>'
		return no_result

	if not os.path.isdir( out_dir ):	# Exit if out_dir does not exist
		print say+hide_env(out_dir)+' does not exist'
		return no_result

	if   is_there( '-g77'     , argv ):
		f77 = 'g77'
	elif is_there( '-gfortran', argv ):
		f77 = 'gfortran'
	elif is_there( '-intel'   , argv ):
		f77 = 'ifort'
	elif is_there( '-pgi'     , argv ):
		f77 = 'pgf77'
	elif is_there( '-absoft'  , argv ):
		f77 = 'abf77'
	else:
		f77 = ''

	if f77 != '':

		f77_save = f77

		if which( f77 ) == '':
			f77 = ''
			if f77_save == 'ifort':
				if which( 'ifc' ) != '':
					f77 = 'ifc'

		if f77 == '':
			print say+'compiler '+f77_save+' not available' 
			return no_result

	elif which( 'g77' ):
		f77 = 'g77'

	elif which( 'gfortran' ):
		f77 = 'gfortran'

	elif which( 'ifort' ):
		f77 = 'ifort'

	elif which( 'ifc' ):
		f77 = 'ifc'

	elif which( 'pgf77' ):
		f77 = 'pgf77'

	elif which( 'abf77' ):
		f77 = 'abf77'

	else:
		print say+'no Fortran compiler found'
		return no_result

	if f77 == 'g77':
		cc  = 'gcc'
		cpp = 'g++'
	elif f77 == 'gfortran':
		cc  = 'gcc'
		cpp = 'g++'
	elif f77 == 'ifort':
		cc  = 'icc'
		cpp = 'icpc'
	elif f77 == 'pgf77':
		cc  = 'pgcc'
		cpp = 'pgCC'
	elif f77 == 'abf77':
		cc  = ''
		cpp = ''


	f77link = []

	if f77 == 'g77':
		f77cc = f77+' -w -ffixed-line-length-none'
		f77id = '_'+os.environ['GNU_COMPILER']
	elif f77 == 'gfortran':
		f77cc = f77+' -w -ffixed-line-length-none'
		f77id = '_'+os.environ['GNU_COMPILER']
	elif f77 == 'ifc':
		f77cc = f77+' -w -132'
		f77id = '_'+os.environ['INTEL_COMPILER']
		f77link.append('-Vaxlib')
	elif f77 == 'ifort':
		f77cc = f77+' -O2 -shared-intel -mcmodel=large -parallel -w -132 -assume byterecl'
		f77id = '_'+os.environ['INTEL_COMPILER']
		f77link.append('-Vaxlib')

		# Fix known bug in V9.0 compiler.
		# http://support.intel.com/support/performancetools/fortran/linux/sb/CS-021696.htm
		if os.environ['INTEL_COMPILER'].find('intel_9.0') != -1:
			f77link.append('-no-ipo')

		# Suppress warning messages for unused statment function declarations

		if os.environ['INTEL_COMPILER'].find('intel_9.1') != -1:
			f77cc = f77cc+' -warn nouncalled'

	elif f77 == 'pgf77':
		f77cc = f77+' -w -Mextend'
		f77id = '_'+os.environ['PGI_COMPILER']
	elif f77 == 'abf77':
		f77cc = f77+' -W -V -N109'
		f77id = '_'+os.environ['ABSOFT_COMPILER']
		f77link.append('-lU77')
		f77link.append('-lV77')

	print say+'using '+which( f77 )

	# Kludges needed to cross compile with C/C++

	if incl_cc:
		if f77 == 'g77':
			f77link.append('-lstdc++')
		elif f77 == 'gfortran':
			f77link.append('-lstdc++')
		elif f77 == 'ifort':
			f77link.append('-cxxlib-gcc')
			f77link.append('-lstdc++')
		elif f77 == 'pgf77':
			f77link.append('-lC')
			f77link.append('-lstd')

	if noincl:
		include_f77 = ''
	else:
		include_f77 = '-I'+os.path.join(os.environ['SMEI'],'for','h')+\
			' -I'+os.path.join(os.environ['SMEI_FOR'],'h')+\
			' -I'+os.path.join(os.environ['SMEI_FOR'],'h','linux')

	#include_cminus = '-I'+os.path.join(os.environ['SMEI'],'cpp','htmIndex20','include')
	#include_cplus  = '-I'+os.path.join(os.environ['SMEI'],'cpp','htmIndex20','include')

	if static:
		f77link.append('-static')

	sources = []
	objects = []

	top_dirs = [ 'PWD','FOR','SMEI_FOR']
	source_dirs = []

	for source_dir in top_dirs:
		if os.environ.has_key( source_dir ):
			tmp = os.environ[ source_dir ]
			if os.path.isdir( tmp ):
				source_dirs.append( tmp )

	for entry in fortran_files:

		base_name = os.path.splitext(entry)
		if base_name[1] == '.f':
			base_name = base_name[0]
		else:
			base_name = entry

		found = 0

		for source_dir in source_dirs:

			for root, dirs, files in os.walk( source_dir ):

				for tmp in ['win','unix','vms','CVS']:
					if dirs.count(tmp) == 1:
						dirs.remove(tmp)
	
				found = files.count( base_name+'.f' ) == 1

				if found:
					source = os.path.join( root, base_name+'.f' )

					sources.append( source )
					print say+hide_env(source)

					object = os.path.join( out_dir, base_name+'.o' )

					cmd = 'rm -f '+object+'; '+f77cc+' -c '+include_f77+' '+source+' -o'+object
					run_cmd( cmd, verbose)

					if os.path.isfile( object ):
						objects.append( object )
					else:
						if not obj and entry == fortran_files[0]:
							print say+' error compiling main program'
							return

					break

			if found:
				break

	top_dirs = [ 'PWD','SMEI_CPP']
	source_dirs = []

	for source_dir in top_dirs:
		if os.environ.has_key( source_dir ):
			tmp = os.environ[ source_dir ]
			if os.path.isdir( tmp ):
				source_dirs.append( tmp )

	for entry in cminus_files:

		base_name = os.path.splitext(entry)
		if base_name[1] == '.c':
			base_name = base_name[0]
		else:
			base_name = entry

		found = 0

		for source_dir in source_dirs:

			for root, dirs, files in os.walk( source_dir ):

				for tmp in ['CVS']:
					if dirs.count(tmp) == 1:
						dirs.remove(tmp)
	
				found = files.count( base_name+'.c' ) == 1

				if found:
					source = os.path.join( root, base_name+'.c' )

					sources.append( source )
					print say+hide_env(source)

					object = os.path.join( out_dir, base_name+'.o' )

					#cmd = 'rm -f '+object+'; '+cc+' -c '+include_cminus+' '+source+' -o'+object
					cmd = 'rm -f '+object+'; '+cc+' -c '+source+' -o'+object

					run_cmd( cmd, verbose)

					if os.path.isfile( object ):
						objects.append( object )

					break

			if found:
				break

	top_dirs = [ 'PWD','SMEI_CPP']
	source_dirs = []

	for source_dir in top_dirs:
		if os.environ.has_key( source_dir ):
			tmp = os.environ[ source_dir ]
			if os.path.isdir( tmp ):
				source_dirs.append( tmp )

	for entry in cplus_files:

		base_name = os.path.splitext(entry)
		if base_name[1] == '.cpp':
			base_name = base_name[0]
		else:
			base_name = entry

		found = 0

		for source_dir in source_dirs:

			for root, dirs, files in os.walk( source_dir ):

				for tmp in ['CVS']:
					if dirs.count(tmp) == 1:
						dirs.remove(tmp)
	
				found = files.count( base_name+'.cpp' ) == 1

				if found:
					source = os.path.join( root, base_name+'.cpp' )

					sources.append( source )
					print say+hide_env(source)

					object = os.path.join( out_dir, base_name+'.o' )

					#cmd = 'rm -f '+object+'; '+cpp+' -c '+include_cplus+' '+source+' -o'+object
					cmd = 'rm -f '+object+'; '+cpp+' -c '+' '+source+' -o'+object
					run_cmd( cmd, verbose)

					if os.path.isfile( object ):
						objects.append( object )

					break

			if found:
				break


	objects.extend(object_files)

	if len(sources) == 0:
		print say+'no source file(s) specified'
		return no_result

	if len(objects) == 0:
		print say+'no objects file(s) created'
		return no_result

	if obj:
		status = dict (	[ ('f77', [f77,f77cc,f77id]), ('obj', objects) ] ) 
		return status

	# Extract the file name part as name for the executable

	exename = start  ( '-name=' , argv )
	if exename == '':
		exename = fortran_files[0]
		exename = (os.path.split   (exename))[1]
		exename = (os.path.splitext(exename))[0]

	exename = os.path.join(out_dir,exename)

	libsdir = ['-L'+os.environ['SMEI_LIB']]

	if os.environ.has_key('FOR'):
		tmp = os.environ['FOR']
		if os.path.isdir( tmp ):
			libsdir.append( '-L'+tmp )

	all_libs = []
	if not nogen : all_libs.append( 'gen' )
	if not nofits: all_libs.append( 'cfitsio' )

	libs = []
	for lib in all_libs:
		tmp = os.path.join(os.environ['SMEI_LIB'],'lib'+lib+f77id+'.a')
		if os.path.isfile( tmp ):
			libs.append( '-l'+lib+f77id )
		else:
			print say+hide_env(tmp)+' not found'

	source = 'get_compiler_'+f77id

	if fortran_files.count(source) == 0:
		source = set_compiler(f77id)
		object = os.path.splitext(source)[0]+'.o'
		cmd = f77cc+' -c '+source+' -o'+object
		run_cmd( cmd, verbose)

		if os.path.isfile( object ):
			objects.append( object )

		if keep_id:
			print say+hide_env(source)+' not deleted'
		else:
			os.remove(source)

	cmd = f77cc+			  \
		' '+include_f77		 + \
		' '+' '.join(libsdir)+ \
		' '+' '.join(objects)+ \
		' '+' '.join(libs)	 + \
		' '+' '.join(f77link)+ \
		' -o'+exename

	run_cmd( cmd, verbose)

	if os.path.isfile(exename):
		print say+'exec: '+hide_env(exename)
		if to_remote:
			run_cmd( 'scp '+exename+' '+remote_dir, True )
			os.remove(exename)
	else:
		print say+'fail: '+hide_env(exename)

	run_cmd( 'rm -f '+' '.join(objects), verbose )

	status = dict (	[ ('f77', [f77,f77cc,f77id]), ('exe', exename) ] ) 

	return status


if __name__ == '__main__':

	import sys

	mk( sys.argv )

	sys.exit()
