Newer
Older
# DOC: \page Plumedcheck Validating syntax with plumedcheck
# DOC:
# DOC: Here is a list of coding rules that are enforced by the plumedcheck script.
# DOC: These rules are necessary to be sure that the codebase is consistent.
# DOC: Code not satisfying these rules will make the automatic test on travis-ci fail.
# DOC: In the following we provide a list of errors that are detected automatically,
# DOC: together with a short explanation of the reason we consider these as errors.
# DOC: The names used are the same ones reported by plumedcheck.
# DOC: The `plumedcheck` script can be used as is, but the simplest way to obtain a report
# DOC: is to go to the `buildroot/src` directory and type `make plumedcheck`.
# DOC: In case you think you have a valid reason for a code not passing the check
# DOC: to be merged, please contact the developers.
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
# print usage
function usage(){
print "Usage:"
print " plumedcheck [options] [files]"
print
print "Possible options are:"
print " -h/--help: print help"
print " --global-check: run global check; requires all sources and ac files to be passed"
print
print "Files can be"
print " .h files: interpreted as c++ header file"
print " .cpp files: interpreted as c++ source file"
print " .ac files: interpreted as autoconf files"
exit(0)
}
# print message
function message(severity,id,mes){
if(global_check){
printf("[global_check] (%s) :plmd_%s: %s\n",severity,id,mes) > "/dev/stderr";
} else {
printf("[%s:%s] (%s) :plmd_%s: %s\n",FILENAME,FNR,severity,id,mes) > "/dev/stderr";
}
}
# print error message
# error messages make codecheck fail
function error(id,mes){
message("error",id,mes)
}
# print information message
# information messages are neutral
function information(id,mes){
message("information",id,mes)
}
# print style message
# style messages are possibly bad ideas but do not trigger errors
function style(id,mes){
message("style",id,mes)
}
BEGIN{
# interpret command line arguments
newargv[0]=ARGV[0]
pre=""
opt_global_check=0
for(i=1;i<ARGC;i++){
opt=pre ARGV[i]
pre=""
switch(opt){
case "-h":
case "--help":
usage(); break;
case "--global-check":
opt_global_check=1; break;
case /--astyle=.*/:
sub("^--astyle=","",opt);
astyle=opt;
case /--astyle-options=.*/:
sub("^--astyle-options=","",opt);
astyle_options=opt;
break;
break;
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
case /-.*/:
print "Unknown option " opt; exit(1)
default:
newargv[length(newargv)]=opt
}
}
# copy new command arguments
ARGC=length(newargv)
for(i=1;i<ARGC;i++) ARGV[i]=newargv[i]
# no arguments, print help
if(ARGC==1) usage()
# setup:
# list of deprecated c++ headers
deprecated_headers["complex.h"]=1
deprecated_headers["ctype.h"]=1
deprecated_headers["errno.h"]=1
deprecated_headers["fenv.h"]=1
deprecated_headers["float.h"]=1
deprecated_headers["inttypes.h"]=1
deprecated_headers["iso646.h"]=1
deprecated_headers["limits.h"]=1
deprecated_headers["locale.h"]=1
deprecated_headers["math.h"]=1
deprecated_headers["setjmp.h"]=1
deprecated_headers["signal.h"]=1
deprecated_headers["stdalign.h"]=1
deprecated_headers["stdarg.h"]=1
deprecated_headers["stdbool.h"]=1
deprecated_headers["stddef.h"]=1
deprecated_headers["stdint.h"]=1
deprecated_headers["stdio.h"]=1
deprecated_headers["stdlib.h"]=1
deprecated_headers["string.h"]=1
deprecated_headers["tgmath.h"]=1
# list of allowed module types
allowed_module_types["always"]=1
allowed_module_types["default-on"]=1
allowed_module_types["default-off"]=1
# list of "outer modules", that is those
# that are not included in the kernel library
outer_modules["wrapper"]=1
outer_modules["main"]=1
# list of "core modules", that is those for which
# a specific module namespace is not required
core_modules["core"]=1
core_modules["tools"]=1
core_modules["reference"]=1
# create tmp dir for future usage
"mktemp -d" | getline tmpdir
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
}
# for each input file
BEGINFILE{
# these variables are used to track if a file containes at least one item of a specific kind
# as such they should be reset for every new input file
found_namespace_plmd=0
found_namespace_module=0
found_guard_ifdef=0
found_non_preprocessor_lines=0
## not used, see below
# found_include_system_header=0
# report_include_system_headers=0
# guess module name from directory
# only works when path is specified
filename=FILENAME
gsub("/[.]/","/",filename);
gsub("^[.]/","",filename);
if(!match(filename,".*/.*")) filename=ENVIRON["PWD"] "/" filename
gsub("/+","/",filename); # fix multiple slashes
module=""
nf=split(filename,ff,"/");
if(nf>1){
module=ff[nf-1];
if(!(module in module_type)){
path=filename
sub("/.*$","/module.type",path);
if((getline < path > 0) ){
module_type[module]=$0
information("module_type","module " module " has type '" module_type[module] "'" );
# DOC: :wrong_module_type:
# DOC: Module type as indicated in the `module.type` file located in the module directory is not valid.
# DOC: The admitted types are: `default-on`, which means the module is on by default, `default-off`, which means that
# DOC: they should be explicitly enabled, and `always`, which means they are always required.
if(!(module_type[module] in allowed_module_types)) error("wrong_module_type","module " module " has a wrong module type '" module_type[module] "'");
}
}
}
file=ff[nf];
filebase=file;
sub("\\..*","",filebase);
# guess type of file from extension
filetype=""
if(match(FILENAME,".*\\.h")) filetype="header"
if(match(FILENAME,".*\\.cpp")) filetype="source"
if(match(FILENAME,".*\\.ac")) filetype="autoconf"
# checks that are done only on some modules will be skipped if the module name is not known
if((filetype=="source" || filetype=="header") && !module) information("missing_module_name","module name is not know, some check will be skipped");
if(filetype=="source" || filetype=="header"){
tempfile = tmpdir "/astyle"
system(astyle " --options=" astyle_options " < " FILENAME " > " tempfile)
s=system("diff -q " FILENAME " " tempfile ">/dev/null 2>/dev/null")
# check if astyle has been applied correctly
# DOC: :astyle:
# DOC: In order to keep the code readable, we use the astyle program to format
# DOC: all source files. This error indicates that the reported file has not been
# DOC: formatted correctly. It is important that the version of astyle and the options
# DOC: exactly match the ones we use for testing. To this aim, you should use
# DOC: a command such as `make astyle` from the plumed root directory.
if(s!=0) error("astyle","astyle not satisfied")
system("rm " tempfile)
}
}
# line by line analysis
{
if(filetype=="source" || filetype=="header"){
# detect plumed manual
if(match($0,"^//[+]PLUMEDOC ")) in_plumed_doc=$NF
# detect doxygen manual - at least with /** and */ on separate lines
if($1=="/**") in_doxygen=1
# detect copyright lines
if($0=="/* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++") in_copyright=1;
# check direct inclusion of headers that would bypass module dependencies
# DOC: :include_dots:
# DOC: It is considered an error to include a file using a path starting with `..`.
# DOC: The reason is that this would allow to include PLUMED modules that have not been
# DOC: explicitly declared in the Makefile (e.g. using `#include "../tools/Vector.h"`).
# DOC: This might result in undetected dependences between the modules, which means that
# DOC: by enabling some set of modules one would result in a non-linkable library.
if(match($0,"^# *include +\\\"\\.\\.")) error("include_dots","cannot include files using .. path");
# check if there is anything behind copyright and preprocessor commands
if(!in_copyright && !match($0,"^# *include") && !match($0,"^# *ifndef") && !match($0,"^# *endif") && !match($0,"^# *define") && !match($0,"^ *$")) found_non_preprocessor_lines=1
## check if system headers are included after local headers.
## I am not sure it is a good idea, so I let it commented for now.
# if(match($0,"^# *include +<")) found_include_system_header=1
## it is a bad habit to include local headers after system headers
## notice that I explicitly check for the final "h" in the included file so as to exclude
## included files ending with tmp that are somewhere used in plumed to include automatically generated source code
# if(match($0,"^# *include +\\\".*h\\\"")){
# if(found_include_system_header && !report_include_system_headers){
# style("include_system_headers","include system headers before local headers is discouraged");
# report_include_system_headers=1 # only report once per file
# }
# }
# check #include <file>
if(!in_doxygen && match($0,"^# *include +<[^>]+>.*$")){
h=$0
sub("^# *include +<","",h)
sub(">.*$","",h)
# check if deprecated headers are included
# DOC: :deprecated_headers:
# DOC: There is a list of headers that have been deprecated in C++ and should not be
# DOC: included. These headers should be replaced with their equivalent from the stdlib.
# DOC: E.g., use `#include <cstdlib>` instead of `#include <stdlib.h>`. Notice that
# DOC: using the modern header all the functions are declared inside the `std` namespace.
if(h in deprecated_headers) error("deprecated_header","including deprecated header " h);
# DOC: :external_header: (style)
# DOC: Header files should possibly not include other header files from external libraries.
# DOC: Indeed, once PLUMED is installed, if paths are not set correctly the include files of the other libraries
# DOC: might not be reachable anymore. This is only a stylistic warning now. Notice that
# DOC: with the current implementation of class Communicator it is necessary to include
# DOC: `mpi.h` in a header file.
if(filetype=="header" && h~"\\.h") style("external_header","including external header " h " in a header file");
}
# check #include "file"
if(!in_doxygen && match($0,"^# *include +\"[^\"]+\".*$")){
h=$0
sub("^# *include +\"","",h)
sub("\".*$","",h)
# check if a .inc file, which is temporary and not installed, is included in a header file.
# this might create inconsistencies in the installed plumed header files.
# DOC: :non_h_header:
# DOC: Files with `.inc` extension are generated by PLUMED while it is compiled and are
# DOC: not installed. They should not be directly included in header files, otherwise
# DOC: those header files could be not usable once PLUMED is installed.
if(filetype=="header" && h!~"\\.h") error("non_h_header","including non '.h' file " h " in a header file");
}
# check if namespace PLMD is present
if(match($0,"^ *namespace *PLMD")) found_namespace_plmd=1
# check if namespace for module is present
if(match($0,"^ *namespace *" module)) found_namespace_module=1
# take note of registered actions
if(match($0,"^ *PLUMED_REGISTER_ACTION")){
action=$0
sub("^ *PLUMED_REGISTER_ACTION\\(.*, *[\"]","",action);
sub("[\"].*$","",action);
information("registered_action","action " action);
# DOC: :multi_registered_action:
# DOC: Each action should be registered (with `PLUMED_REGISTER_ACTION`) once and only once.
if(action in registered_actions) error("multi_registered_action","action " action " already registered at "registered_actions[action]);
registered_actions[action]=FILENAME ":" FNR;
}
# take note of registered cltools
if(match($0,"^ *PLUMED_REGISTER_CLTOOL")){
cltool=$0
sub("^ *PLUMED_REGISTER_CLTOOL\\(.*, *[\"]","",cltool);
sub("[\"].*$","",cltool);
information("registered_cltool","cltool " cltool);
# DOC: :multi_registered_cltool:
# DOC: Each cltool should be registered (with `PLUMED_REGISTER_CLTOOL`) once and only once.
if(cltool in registered_cltools) error("multi_registered_cltool","cltool " cltool " already registered at "registered_cltools[cltool]);
registered_cltools[cltool]=FILENAME ":" FNR;
}
# take note of registered vessels
if(match($0,"^ *PLUMED_REGISTER_VESSEL")){
vessel=$0
sub("^ *PLUMED_REGISTER_VESSEL\\(.*, *[\"]","",vessel);
sub("[\"].*$","",vessel);
information("registered_vessel","vessel " vessel);
# DOC: :multi_registered_vessel:
# DOC: Each vessel should be registered (with `PLUMED_REGISTER_VESSEL`) once and only once.
if(vessel in registered_vessels) error("multi_registered_vessel","vessel " vessel " already registered at "registered_vessels[vessel]);
registered_vessels[vessel]=FILENAME ":" FNR;
}
# take note of registered metrics
if(match($0,"^ *PLUMED_REGISTER_METRIC")){
metric=$0
sub("^ *PLUMED_REGISTER_METRIC\\(.*, *[\"]","",metric);
sub("[\"].*$","",metric);
information("registered_metric","metric " metric);
# DOC: :multi_registered_metric:
# DOC: Each metric should be registered (with `PLUMED_REGISTER_METRIC`) once and only once.
if(metric in registered_metrics) error("multi_registered_metric","metric " metric " already registered at "registered_metrics[metric]);
registered_metrics[metric]=FILENAME ":" FNR;
}
# take note of documented actions or cltools
if(match($0,"^//[+]PLUMEDOC ")){
doc=$NF;
# DOC: :multi_doc:
# DOC: An action or cltool should be documented once and only once. Notice that because of the
# DOC: way the manual is generated, a cltool cannot have the same name of an action.
if(doc in plumed_doc) error("multi_doc","doc " doc " already at "plumed_doc[action]);
plumed_doc[doc]=FILENAME ":" FNR;
switch($(NF-1)){
case "TOOLS":
plumed_doc_cltools[doc]=FILENAME ":" FNR;
information("documented_cltool","doc " doc);
break;
case "INTERNAL":
plumed_doc_internal[doc]=FILENAME ":" FNR;
information("documented_internal","doc " doc);
break;
default:
plumed_doc_action[doc]=FILENAME ":" FNR;
information("documented_action","doc " doc);
}
}
# check if, besides the \par Examples text, there is a verbatim command
if(in_plumed_doc && (in_plumed_doc in provide_examples) && match($0,"^ *\\\\verbatim *$")){
provide_verbatim[in_plumed_doc]=1
}
#print match($0,"^ *\\\\verbatim *$"),"X" $0 "X"
# take note of actions or cltools that provide examples
if(match($0,"\\par Examples")){
provide_examples[in_plumed_doc]=FILENAME ":" FNR;
}
# check if guard is present
guard="^#ifndef __PLUMED_" module "_" filebase "_h"
if(match($0,guard)) found_guard_ifdef=1
# check that "using namespace" statements are not used in header files
# DOC: :using_namespace_in_header:
# DOC: A statement `using namespace` in header files is considered bad practice. Indeed, this would imply that
# DOC: any other file including that header file would be using the same namespace.
# DOC: This is true also for `std` namespace, and that's why you typically find full specifications
# DOC: such as `std::string` in header files. In source files you are free to use `using namespace PLMD` and/or
# DOC: `using namespace std` if you like, since these commands will only affect the source file where they are used.
if(filetype=="header" && match($0,"using *namespace") && !in_plumed_doc && !in_doxygen) error("using_namespace_in_header","using namespace statement in header file")
}
# analyze HAS macros (bouth source and autoconf)
if(match($0,"__PLUMED_HAS_[A-Z_]*")){
string=substr( $0, RSTART, RLENGTH )
sub("__PLUMED_HAS_","",string);
if(filetype=="autoconf"){
# take note of defined HAS macros
if(plumed_definehas_file[string,FILENAME]!="used") information("defined_has",string);
plumed_definehas_file[string,FILENAME]="used";
plumed_definehas[string]=FILENAME ":" FNR;
} else {
# take note of used HAS macros
if(plumed_usehas_file[string,FILENAME]!="used") information("used_has",string);
# DOC: :used_has_in_header: (style)
# DOC: Using `__PLUMED_HAS_SOMETHING` macros in headers should be avoided since
# DOC: it could make it difficult to make sure the same macros are correctly defined
# DOC: when using PLUMED as a library. This is only a stylistic warning now. Notice that
# DOC: with the current implementation of class Communicator it is necessary to include
# DOC: `mpi.h` in a header file and thus to use `__PLUMED_HAS_MPI`.
if(plumed_usehas_file[string,FILENAME]!="used" && filetype=="header") style("used_has_in_header",string);
plumed_usehas_file[string,FILENAME]="used";
plumed_usehas[string]=FILENAME ":" FNR;
}
}
if(filetype=="source" || filetype=="header"){
# detect plumed manual
if(match($0,"^//[+]ENDPLUMEDOC")) in_plumed_doc=0
# detect doxygen manual - at least with /** and */ on separate lines
if($1=="*/") in_doxygen=0
# detect copyright lines
if($0=="+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */") in_copyright=0;
}
}
# at the end of each file:
ENDFILE{
# check for namespaces
if(found_non_preprocessor_lines && (filetype=="source" || filetype=="header") && !(module in outer_modules) ){
# DOC: :missing_namespace_plmd:
# DOC: Every source file should contain a `PLMD` namespace.
# DOC: Notice that files in the "outer modules" `wrapper` and `main`, that is those that
# DOC: are not included in the kernel library, are exempted from this rule.
if(!found_namespace_plmd) error("missing_namespace_plmd","missing PLMD namespace");
# DOC: :missing_namespace_module:
# DOC: Every source file should contain a sub namespace with the same name of the module itself.
# DOC: Notice that there are important exceptions to this rule:
# DOC: - "outer modules" `wrapper` and `main`, that are not included in the kernel library.
# DOC: - "core modules" that are part of the core of PLUMED. Since the classes implemented here are
# DOC: widely used within PLUMED, they are directly defined in the PLMD namespace.
if(module && !(module in core_modules) && !found_namespace_module) error("missing_namespace_module","missing " module " namespace")
# this can be done only if module name is known:
# DOC: :missing_guard:
# DOC: Every header file should contain a proper guard to avoid double inclusion.
# DOC: The format of the guard is fixed and should be `__PLUMED_modulename_filename_h`, where
# DOC: `modulename` is the name of the module and `filename` is the name of the file, without suffix.
# DOC: This choice makes guards unique within the whole codebase.
if(module && !found_guard_ifdef) error("missing_guard","missing ifndef guard");
}
# check if every header has a corresponding cpp
# this is done only for non-trivial headers (i.e. we skip headers that just include another header)
if(filetype=="header" && found_non_preprocessor_lines){
notfound=0
cppfile=FILENAME
sub("\\.h$",".cpp",cppfile);
if((getline < cppfile < 0)){
cppfile=FILENAME
sub("\\.h$",".c",cppfile);
if((getline < cppfile < 0)) notfound=1
}
if(notfound){
# DOC: :non_existing_cpp:
# DOC: For every header file there should exist a corresponding source file with the same name.
# DOC: Notice that dummy headers that only include another header or which only define preprocessor
# DOC: macros are exempted from this rule.
error("non_existing_cpp","file " file " is a header but there is no corresponding source");
}
while((getline < cppfile)>=0){
if(match($0,"^ *#include")){
sub("^ *#include","");
# DOC: :non_included_h:
# DOC: The source file corresponding to a header file (that is: with the same name) should include it as the first included file.
# DOC: This is to make sure that all the header files that we install can be individually included and compiled.
if($1!="\"" file "\"") error("non_included_h","file " file " is a header but " cppfile " does not include it as first include");
break;
}
}
}
}
# after processing all files, perform some global check
END{
# checks are only done if enabled
if(!opt_global_check) exit(0)
# this is required to properly report messages
global_check=1
for(action in registered_actions){
if(!(action in plumed_doc_action)){
# DOC: :undocumented_action:
# DOC: Every action that is registered with `PLUMED_REGISTER_ACTION` should also be documented
# DOC: in a PLUMEDOC page.
error("undocumented_action","action " action " at " registered_actions[action] " is not documented")
} else if(!(action in provide_examples)){
# DOC: :action_without_examples:
# DOC: Every action that is registered should contain an `Example` section in its documentation.
# DOC: Notice that if the `Example` section is not added the manual will not show the registered keywords.
error("action_without_examples","action " action " at " plumed_doc_action[action] " has no example")
# DOC: :action_without_verbatim:
# DOC: Every action that is registered should contain an example in the form of a `verbatim` section.
# DOC: This is currently a stylistic warning since there are many actions not satisfying this requirement.
style("action_without_verbatim","action " action " at " provide_examples[action] " has no verbatim")
}
}
for(action in plumed_doc_action){
if(!(action in registered_actions))
# DOC: :unregistered_action:
# DOC: Every action that is documented should be also registered using `PLUMED_REGISTER_ACTION`.
error("unregistered_action"," action " action " at " plumed_doc_action[action] " is not registered");
}
if(!(cltool in plumed_doc_cltools)){
# DOC: :undocumented_cltool:
# DOC: Every cltool that is registered with `PLUMED_REGISTER_CLTOOL` should also be documented
# DOC: in a PLUMEDOC page.
error("undocumented_cltool","cltool " cltool " at " registered_cltools[cltool] " is not documented")
} else if(!(cltool in provide_examples)){
# DOC: :cltool_without_examples:
# DOC: Every cltool that is registered should contain an `Example` section in its documentation.
# DOC: Notice that if the `Example` section is not added the manual will not show the registered options.
error("cltool_without_examples","cltool " cltool " at " plumed_doc_cltools[cltool] " has no example")
# DOC: :cltool_without_verbatim:
# DOC: Every cltool that is registered should contain an example in the form of a `verbatim` section.
error("cltool_without_verbatim","cltool " cltool " at " provide_examples[cltool] " has no verbatim")
}
}
for(cltool in plumed_doc_cltools){
if(!(cltool in registered_cltools))
# DOC: :unregistered_cltool:
# DOC: Every cltool that is documented should be also registered using `PLUMED_REGISTER_CLTOOL`.
error("unregistered_cltool"," cltool " cltool " at " plumed_doc_cltools[cltool] " is not registered");
}
for(has in plumed_usehas){
if(!(has in plumed_definehas)){
# DOC: :undefined_has:
# DOC: Every macro in the form `__PLUMED_HAS_SOMETHING` that is used in the code
# DOC: should be defined in configure.ac. This check is made to avoid errors with mis-typed macros.
error("undefined_has","has " has " at " plumed_usehas[has] " is not defined")
}
}
for(has in plumed_definehas){
if(!(has in plumed_usehas)){
# DOC: :unused_has:
# DOC: Every macro in the form `__PLUMED_HAS_SOMETHING` that is defined in configure.ac
# DOC: should be used at least once in the code. This check is made to avoid errors with mis-typed macros.
error("unused_has","has " has " at " plumed_definehas[has] " is not used")
}
}
# remove temporary directory
system("rmdir " tmpdir);