Commit f305d52c authored by Lorenz Huedepohl's avatar Lorenz Huedepohl

Add 'fdep/' from commit 'f81bd5c8'

git-subtree-dir: fdep
git-subtree-mainline: 38fe3a85
git-subtree-split: f81bd5c8
parents 38fe3a85 f81bd5c8
Copyright (c) 2013 Lorenz Hüdepohl
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.
fdep
----
fdep is a small set of scripts to teach autoconf/automake (using GNU make)
about the additional dependencies in Fortran 90 files due to modules.
With this, Fortran files can be listed in any order in Makefile.am and parallel
builds work.
Usage
-----
Put this project as a directory "fdep" in your source code, place the two
lines
m4_include([fdep/fortran_dependencies.m4])
FDEP_F90_GNU_MAKE_DEPS
in your configure.ac, and add a single line
@FORTRAN_MODULE_DEPS@
in your Makefile.am. All .F90 files of all programs in bin_PROGRAMS and all
libraries in lib_LTLIBRARIES will now be scanned for modules and the
resulting dependencies will be honoured.
What is the problem with Fortran 90 modules and make dependencies?
------------------------------------------------------------------
In Fortran 90 source files one can define any number of "modules", containing
variable and function definitions. The names of the modules defined in a file
can be arbitrary.
In another source file these modules can be used, informing the Fortran
compiler about the definitions in these modules (e.g. to do type-checking).
This creates a problem, as the compiler has to know somehow where the module
is defined.
The usual solution employed by almost every Fortran compiler is to create
special "module" files for each module contained in a source file during
compilation. Their file name is derived by a compiler-specific recipe of the
modules identifier (usually the lower-cased module's identifier plus ".mod",
so "foo_module.mod" and "some_other_module.mod"). When the compiler
encounters a "use" statement during the compilation of another file, it
confers to this file to import the definitions of the module.
That means, you cannot compile files using modules defined in yet un-compiled
files, one has to tell make about this dependency.
(A primitive solution to this problem is listing the file in a pre-sorted
order, so that files defining modules are compiled first.
However, that way the dependency-graph make knows about is incomplete and
parallel builds will fail with a high probability)
How does fdep solve this problem technically?
---------------------------------------------
As the name of the module files can be an arbitrary (and some compilers might
even save the module definitions in some completely different way), fdep
tells make about the module dependencies as a relation directly between
object files, e.g. when a file 'b.f90' is using any module of file 'a.f90',
fdep adds a dependency of
b.o: a.o
More specifically, the perl-script fortran_dependencies.pl is run by make to
create a file .fortran_dependencies/dependencies.mk, which is then included.
To do this, first every source file (for every defined program and library)
is scanned for lines with "module" or "use" statements. These are saved in
two additional files (.use_mods and .def_mods) per source file and contain
lists of defined and required modules. The perl script then reads these in
and produces the appropriate rules.
Drawbacks
---------
GNU make is required. The detailed dependency graph due to "module" and "use"
statements is only available after pre-processing, when autoconf and even
configure is long over. To still get proper dependencies, fdep uses GNU
make's feature to include generated sub-Makefiles during a running make
invocation.
License
-------
fdep is released under the MIT License. See the LICENSE file for details.
Contributing
------------
Send your patches or pull-request to dev@stellardeath.org
dnl Copyright 2015 Lorenz Hüdepohl
dnl
dnl This file is part of fdep and licensed under the MIT license
dnl see the file LICENSE for more information
dnl
AC_DEFUN([FDEP_F90_GNU_MAKE_DEPS],[
AC_MSG_CHECKING([for GNU make])
for a in "$MAKE" make gmake gnumake ; do
if test -z "$a" ; then continue ; fi ;
if ( sh -c "$a --version" 2> /dev/null | grep GNU 2>&1 > /dev/null ) ; then
_fdep_gnu_make_command=$a ;
break;
fi
done ;
AC_MSG_RESULT([$_fdep_gnu_make_command])
if test x$_fdep_gnu_make_command = x ; then
AC_MSG_ERROR([Need GNU Make])
fi
AC_SUBST([FORTRAN_MODULE_DEPS], ["
CLEANFILES +=
include ${srcdir}/fdep/fortran_dependencies.mk
"])
AM_SUBST_NOTMAKE([FORTRAN_MODULE_DEPS])
])
# Copyright 2015 Lorenz Hüdepohl
#
# This file is part of fdep and licensed under the MIT license
# see the file LICENSE for more information
#
define translate_name
$(subst -,_,$(subst .,_,$1))
endef
_f90_verbose = $(_f90_verbose_$(V))
_f90_verbose_ = $(_f90_verbose_$(AM_DEFAULT_VERBOSITY))
_f90_verbose_0 = @echo " $1";
_f90_targets = $(call translate_name,$(PROGRAMS) $(LTLIBRARIES))
FORTRAN_CPP ?= cpp -P -traditional -Wall -Werror
# $1 source files
#
# returns: file without any .F90 .f90 .F .f extension
define strip_fortran_ext
$(patsubst %.F90,%,$(patsubst %.f90,%,$(patsubst %.F,%,$(patsubst %.f,%,$1))))
endef
# $1 program
#
# returns:
# '1' if object files for target $1 are prefixed due to 'per-target' flags,
# '' (the empty string) otherwise. See the automake manual for 'per-target'
# compilation
#
define is_per_target
$(if $(filter $(call strip_fortran_ext,$(firstword $(call fortran_sources,$1))),$(patsubst %.o,%,$(patsubst %.lo,%,$($1_OBJECTS)))),,1)
endef
# $1 top-level target name (i.e. an entry of _f90_targets)
#
# returns: all target source files matching *.F90 *.f90 *.F *.f
define fortran_sources
$(filter %.F90 %.f90 %.F %.f,$($1_SOURCES))
endef
# $1 top-level target name
#
# returns: the appropriate extension (i.e. 'o' for normal programs, '.lo' for libraries)
define object_extension
$(if $(filter $1,$(PROGRAMS)),o,lo)
endef
# $1 source file
# $2 stem
# $3 program
# $4 kind of file ('use' or 'def')
define modinfo_name
$(dir $1)$(2)$(call strip_fortran_ext,$(notdir $1)).$4_mods_$(patsubst .,_,$3).$(call object_extension,$3)
endef
# $1 source_file
# $2 stem
# $3 program
define module_targets
$(eval _$(3)_use_mods += $(call modinfo_name,$1,$2,$3,use))
$(call modinfo_name,$1,$2,$3,use): $1 $(dir $1)$(am__dirstamp)
$(call _f90_verbose,F90 USE [$3] $$<)$(FORTRAN_CPP) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $($p_CPPFLAGS) $(CPPFLAGS) -o /dev/stdout $$< | \
grep -i -o '^ *use [^ ,!:]*' | sed 's/^[[:space:]]*//;' | tr '[:upper:]' '[:lower:]' | sort -u > $$@
$(eval _$(3)_def_mods += $(call modinfo_name,$1,$2,$3,def))
$(call modinfo_name,$1,$2,$3,def): $1 $(dir $1)$(am__dirstamp)
$(call _f90_verbose,F90 MOD [$3] $$<)$(FORTRAN_CPP) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $($p_CPPFLAGS) $(CPPFLAGS) -o /dev/stdout $$< | \
grep -i -o '^ *module [^!]*' | sed 's/^[[:space:]]*//;' | tr '[:upper:]' '[:lower:]' | grep -v "\<procedure\>\|\<intrinsic\>" > $$@ || true
endef
$(foreach p,$(_f90_targets),$(if $(call is_per_target,$p),$(foreach s,$(call fortran_sources,$p),$(eval $(call module_targets,$s,$p-,$p))),$(foreach s,$(call fortran_sources,$p),$(eval $(call module_targets,$s,,$p)))))
_f90_depdir=$(abs_builddir)/.fortran_dependencies
_f90_depfile = $(_f90_depdir)/dependencies.mk
# $1 target-name
define recursive_lib_deps
$(foreach l,$(call translate_name,$($1_LDADD) $($1_LIBADD)),$l $(call recursive_lib_deps,$l))
endef
define is_clean
$(if $(filter-out mostlyclean clean distclean maintainer-clean,$(MAKECMDGOALS)),0,1)
endef
ifneq ($(call is_clean),1)
include $(_f90_depfile)
endif
$(_f90_depfile): $(top_srcdir)/fdep/fortran_dependencies.pl $(foreach p,$(_f90_targets),$(_$p_use_mods) $(_$p_def_mods)) | $(foreach p,$(_f90_targets),$(_f90_depdir)/$p)
$(call _f90_verbose,F90 DEPS $@)echo > $@; $(foreach p,$(_f90_targets),$(top_srcdir)/fdep/fortran_dependencies.pl $p $(_$p_use_mods) $(_$p_def_mods) $(foreach l,$(call recursive_lib_deps,$p),$(_$l_use_mods) $(_$l_def_mods)) >> $@; )
$(_f90_depdir):
@mkdir $@
$(foreach p,$(_f90_targets),$(_f90_depdir)/$p): | $(_f90_depdir)
@mkdir $@
CLEANFILES += $(foreach p,$(_f90_targets),$(_$p_def_mods) $(_$p_use_mods))
CLEANFILES += $(foreach p,$(_f90_targets),$(_f90_depdir)/$p/*)
CLEANFILES += $(_f90_depfile)
#!/usr/bin/perl -w
#
# Copyright 2015 Lorenz Hüdepohl
#
# This file is part of fdep and licensed under the MIT license
# see the file LICENSE for more information
#
use strict;
my %defs = ();
my %uses = ();
my %files = ();
my $use_re = qr/^\s*use\s+(\S+)\s*$/;
my $def_re = qr/^\s*module\s+(\S+)\s*$/;
sub add_use {
my ($file, $module) = @_;
if (defined($defs{$module}) && $defs{$module} eq $file) {
# do not add self-dependencies
return;
}
if (!defined($uses{$file})) {
$uses{$file} = { $module => 1 };
} else {
$uses{$file}{$module} = 1;
}
}
sub add_def {
my ($file, $module) = @_;
if (!defined($defs{$module})) {
$defs{$module} = $file;
if (defined($uses{$file}) && defined($uses{$file}{$module})) {
delete $uses{$file}{$module};
}
} else {
die "Module $module both defined in $file, $defs{$module}";
}
}
my $target = shift;
foreach my $file (@ARGV) {
if (exists $files{$file}) {
next;
} else {
$files{$file} = 1;
}
my $re;
my $add;
my $object;
if (defined($ENV{V}) && $ENV{V} ge "2") {
print STDERR "fdep: Considering file $file for target $target\n";
}
if ($file =~ /^(.*)\.def_mods_[^.]*(\..*)$/) {
$re = $def_re;
$add = \&add_def;
$object = $1 . $2;
} elsif ($file =~ /^(.*)\.use_mods_[^.]*(\..*)$/) {
$re = $use_re;
$add = \&add_use;
$object = $1 . $2;
} else {
die "Unrecognized file extension for '$file'";
}
open(FILE,"<",$file) || die "\nCan't open $file: $!\n\n";
while(<FILE>) {
chomp;
$_ = lc($_);
if ($_ =~ $re) {
&$add($object, $1);
} else {
die "At $file:$.\nCannot parse module statement '$_', was expecting $re";
}
}
close(FILE)
}
foreach my $object (sort keys %uses) {
for my $m (keys %{$uses{$object}}) {
if (defined $defs{$m}) {
print "$object: ", $defs{$m}, "\n";
} elsif (defined($ENV{V}) && $ENV{V} ge "1") {
print STDERR "Warning: Cannot find definition of module $m in files for current target $target, might be external\n";
}
}
}
*.def_mods
*.la
*.lo
*.o
*.use_mods
*~
.deps
.dirstamp
.fortran_dependencies
.libs
Makefile
Makefile.in
aclocal.m4
ar-lib
autom4te.cache
bar
compile
config.guess
config.h
config.h.in
config.log
config.status
config.sub
configure
foo
fortran_mod_files
install-sh
libtool
ltmain.sh
m4/libtool.m4
m4/ltoptions.m4
m4/ltsugar.m4
m4/ltversion.m4
m4/lt~obsolete.m4
missing
stamp-h1
test_bar
test_baz
ACLOCAL_AMFLAGS = ${ACLOCAL_FLAGS} -I m4
AM_CFLAGS = @AM_CFLAGS@
AM_LDFLAGS = @AM_LDFLAGS@
# programs
bin_PROGRAMS = foo test_bar test_baz
foo_SOURCES = src/foo.F90 src2/baz.F90 src/bar.F90
foo_CPPFLAGS = -DPROGRAM_foo
foo_FCFLAGS = $(FC_MODOUT)./fortran_mod_files/foo $(FC_MODINC)./fortran_mod_files/foo
test_bar_SOURCES = src/bar.F90
test_bar_CPPFLAGS = -DPROGRAM_test_bar
test_bar_FCFLAGS = $(FC_MODOUT)./fortran_mod_files/test_bar $(FC_MODINC)./fortran_mod_files/test_bar
test_baz_SOURCES = src2/baz.F90 src/bar.F90
test_baz_CPPFLAGS = -DPROGRAM_test_baz
test_baz_FCFLAGS = $(FC_MODOUT)./fortran_mod_files/test_baz $(FC_MODINC)./fortran_mod_files/test_baz
# a library
lib_LTLIBRARIES = libdings-2.la
libdings_2_la_SOURCES = src2/baz.F90 src/bar.F90
libdings_2_la_FCFLAGS = $(FC_MODOUT)./fortran_mod_files/libdings-2.la $(FC_MODINC)./fortran_mod_files/libdings-2.la
CLEANFILES = fortran_mod_files/*/*
@FORTRAN_MODULE_DEPS@
#!/bin/sh -e
mkdir -p m4/
test -n "$srcdir" || srcdir=`dirname "$0"`
test -n "$srcdir" || srcdir=.
autoreconf --force --install --verbose "$srcdir"
AC_INIT([fdep_test], [0.1])
AC_PREREQ([2.59])
AM_INIT_AUTOMAKE([foreign -Wall subdir-objects])
AC_CONFIG_MACRO_DIR([m4])
AC_CONFIG_HEADERS([config.h])
AM_SILENT_RULES([yes])
# gnu-make fortran module dependencies
m4_include([fdep/fortran_dependencies.m4])
FDEP_F90_GNU_MAKE_DEPS
# necessary tools
AC_PROG_FC
AC_PROG_INSTALL
AM_PROG_CC_C_O
AM_PROG_AR
# fortran stuff
AC_FC_MODULE_FLAG
AC_FC_MODULE_OUTPUT_FLAG
AC_FC_FREEFORM
# libtool
LT_INIT
AC_SUBST([AM_CFLAGS])
AC_SUBST([AM_LDFLAGS])
AC_SUBST([FC_MODINC])
AC_SUBST([FC_MODOUT])
AC_CONFIG_FILES([Makefile])
AC_OUTPUT
for target in foo test_bar test_baz libdings-2.la; do
mkdir -p fortran_mod_files/$target
done
..
\ No newline at end of file
module bar
implicit none
contains
function two() result(t)
integer :: t
t = 2
end function
end module
#ifdef PROGRAM_test_bar
program test_bar
use bar, only : two
if (two() /= 2) then
stop 1
endif
end program
#endif
program foo
use bar
use baz
write(*,*) "Nop"
end program
module baz
use bar
implicit none
contains
function two_times_two() result(t)
integer :: t
t = 2 * two()
end function
end module
#ifdef PROGRAM_test_baz
program test_bar
use baz
if (two_times_two() /= 4) then
stop 1
endif
end program
#endif
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment