@@ -312,17 +312,29 @@ def compare_fheader_to_mheader(meta_header, fort_header, logger):
312
312
# end if
313
313
for mind , mvar in enumerate (mlist ):
314
314
lname = mvar .get_prop_value ('local_name' )
315
+ mname = mvar .get_prop_value ('standard_name' )
315
316
arrayref = is_arrayspec (lname )
316
317
fvar , find = find_var_in_list (lname , flist )
317
318
# Check for consistency between optional variables in metadata and
318
319
# optional variables in fortran. Error if optional attribute is
319
320
# missing from fortran declaration.
321
+ # first check: if metadata says the variable is optional, does the fortran match?
320
322
mopt = mvar .get_prop_value ('optional' )
321
323
if find and mopt :
322
324
fopt = fvar .get_prop_value ('optional' )
323
325
if (not fopt ):
324
- errmsg = 'Missing optional attribute in fortran declaration for variable {}, in file {}'
325
- errors_found = add_error (errors_found , errmsg .format (mname ,title ))
326
+ errmsg = f'Missing "optional" attribute in fortran declaration for variable { mname } , ' \
327
+ f'for { title } '
328
+ errors_found = add_error (errors_found , errmsg )
329
+ # end if
330
+ # end if
331
+ # now check: if fortran says the variable is optional, does the metadata match?
332
+ if fvar :
333
+ fopt = fvar .get_prop_value ('optional' )
334
+ if (fopt and not mopt ):
335
+ errmsg = f'Missing "optional" metadata property for variable { mname } , ' \
336
+ f'for { title } '
337
+ errors_found = add_error (errors_found , errmsg )
326
338
# end if
327
339
# end if
328
340
if mind >= flen :
@@ -387,7 +399,8 @@ def compare_fheader_to_mheader(meta_header, fort_header, logger):
387
399
388
400
###############################################################################
389
401
def check_fortran_against_metadata (meta_headers , fort_headers ,
390
- mfilename , ffilename , logger ):
402
+ mfilename , ffilename , logger ,
403
+ dyn_routines = None , fortran_routines = None ):
391
404
###############################################################################
392
405
"""Compare a set of metadata headers from <mfilename> against the
393
406
code in the associated Fortran file, <ffilename>.
@@ -439,6 +452,17 @@ def check_fortran_against_metadata(meta_headers, fort_headers,
439
452
's' if num_errors > 1 else '' ,
440
453
mfilename , ffilename ))
441
454
# end if
455
+ # Check that any dynamic constituent routines declared in the metadata are
456
+ # present in the Fortran
457
+ if dyn_routines :
458
+ for routine in dyn_routines :
459
+ if routine not in fortran_routines :
460
+ # throw an error - it's not in the Fortran
461
+ errmsg = f"Dynamic constituent routine { routine } not found in fortran { ffilename } "
462
+ raise CCPPError (errmsg )
463
+ # end if
464
+ # end for
465
+ # end if
442
466
# No return, an exception is raised on error
443
467
444
468
###############################################################################
@@ -470,7 +494,7 @@ def parse_host_model_files(host_filenames, host_name, run_env):
470
494
# parse metadata file
471
495
mtables = parse_metadata_file (filename , known_ddts , run_env )
472
496
fort_file = find_associated_fortran_file (filename )
473
- ftables = parse_fortran_file (fort_file , run_env )
497
+ ftables , _ = parse_fortran_file (fort_file , run_env )
474
498
# Check Fortran against metadata (will raise an exception on error)
475
499
mheaders = list ()
476
500
for sect in [x .sections () for x in mtables ]:
@@ -511,7 +535,7 @@ def parse_host_model_files(host_filenames, host_name, run_env):
511
535
return host_model
512
536
513
537
###############################################################################
514
- def parse_scheme_files (scheme_filenames , run_env ):
538
+ def parse_scheme_files (scheme_filenames , run_env , skip_ddt_check = False ):
515
539
###############################################################################
516
540
"""
517
541
Gather information from scheme files (e.g., init, run, and finalize
@@ -524,9 +548,10 @@ def parse_scheme_files(scheme_filenames, run_env):
524
548
for filename in scheme_filenames :
525
549
logger .info ('Reading CCPP schemes from {}' .format (filename ))
526
550
# parse metadata file
527
- mtables = parse_metadata_file (filename , known_ddts , run_env )
551
+ mtables = parse_metadata_file (filename , known_ddts , run_env ,
552
+ skip_ddt_check = skip_ddt_check )
528
553
fort_file = find_associated_fortran_file (filename )
529
- ftables = parse_fortran_file (fort_file , run_env )
554
+ ftables , additional_routines = parse_fortran_file (fort_file , run_env )
530
555
# Check Fortran against metadata (will raise an exception on error)
531
556
mheaders = list ()
532
557
for sect in [x .sections () for x in mtables ]:
@@ -536,8 +561,16 @@ def parse_scheme_files(scheme_filenames, run_env):
536
561
for sect in [x .sections () for x in ftables ]:
537
562
fheaders .extend (sect )
538
563
# end for
564
+ dyn_routines = []
565
+ for table in mtables :
566
+ if table .dyn_const_routine :
567
+ dyn_routines .append (table .dyn_const_routine )
568
+ # end if
569
+ # end for
539
570
check_fortran_against_metadata (mheaders , fheaders ,
540
- filename , fort_file , logger )
571
+ filename , fort_file , logger ,
572
+ dyn_routines = dyn_routines ,
573
+ fortran_routines = additional_routines )
541
574
# Check for duplicate tables, then add to dict
542
575
for table in mtables :
543
576
if table .table_name in table_dict :
@@ -560,6 +593,23 @@ def parse_scheme_files(scheme_filenames, run_env):
560
593
# end if
561
594
# end for
562
595
# end for
596
+ # Check for duplicate dynamic constituent routine names
597
+ dyn_val_dict = {}
598
+ for table in table_dict :
599
+ routine_name = table_dict [table ].dyn_const_routine
600
+ if routine_name :
601
+ if routine_name in dyn_val_dict :
602
+ # dynamic constituent routines must have unique names
603
+ scheme_name = dyn_val_dict [routine_name ]
604
+ errmsg = f"ERROR: Dynamic constituent routine names must be unique. Cannot add " \
605
+ f"{ routine_name } for { table } . Routine already exists in { scheme_name } . "
606
+ raise CCPPError (errmsg )
607
+ else :
608
+ dyn_val_dict [routine_name ] = table
609
+ # end if
610
+ # end if
611
+ # end for
612
+
563
613
return header_dict .values (), table_dict
564
614
565
615
###############################################################################
@@ -623,13 +673,25 @@ def capgen(run_env, return_db=False):
623
673
# end if
624
674
# First up, handle the host files
625
675
host_model = parse_host_model_files (host_files , host_name , run_env )
676
+ # Next, parse the scheme files
626
677
# We always need to parse the ccpp_constituent_prop_ptr_t DDT
627
678
const_prop_mod = os .path .join (src_dir , "ccpp_constituent_prop_mod.meta" )
628
679
if const_prop_mod not in scheme_files :
629
- scheme_files = [const_prop_mod ] + scheme_files
680
+ scheme_files = [const_prop_mod ] + scheme_files
630
681
# end if
631
- # Next, parse the scheme files
632
682
scheme_headers , scheme_tdict = parse_scheme_files (scheme_files , run_env )
683
+ # Pull out the dynamic constituent routines, if any
684
+ dyn_const_dict = {}
685
+ dyn_val_dict = {}
686
+ for table in scheme_tdict :
687
+ routine_name = scheme_tdict [table ].dyn_const_routine
688
+ if routine_name is not None :
689
+ if routine_name not in dyn_val_dict :
690
+ dyn_const_dict [table ] = routine_name
691
+ dyn_val_dict [routine_name ] = table
692
+ # end if
693
+ # end if
694
+ # end for
633
695
if run_env .verbose :
634
696
ddts = host_model .ddt_lib .keys ()
635
697
if ddts :
@@ -660,7 +722,7 @@ def capgen(run_env, return_db=False):
660
722
# end if
661
723
os .makedirs (outtemp_dir )
662
724
# end if
663
- ccpp_api = API (sdfs , host_model , scheme_headers , run_env )
725
+ ccpp_api = API (sdfs , host_model , scheme_headers , run_env , dyn_const_dict )
664
726
cap_filenames = ccpp_api .write (outtemp_dir , run_env )
665
727
if run_env .generate_host_cap :
666
728
# Create a cap file
0 commit comments