Skip to content

Commit 0f82327

Browse files
authored
Merge pull request #549 from peverwhee/add_const_interface
Constituent updates
2 parents ccfefcd + 97c3cc5 commit 0f82327

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+2464
-364
lines changed

.github/workflows/python.yaml

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
name: Python package
2+
3+
on:
4+
workflow_dispatch:
5+
pull_request:
6+
branches: [feature/capgen, main]
7+
8+
jobs:
9+
build:
10+
runs-on: ubuntu-latest
11+
strategy:
12+
matrix:
13+
python-version: ['3.8', '3.9', '3.10', '3.11', '3.12']
14+
15+
steps:
16+
- uses: actions/checkout@v3
17+
- name: Set up Python ${{ matrix.python-version }}
18+
uses: actions/setup-python@v4
19+
with:
20+
python-version: ${{ matrix.python-version }}
21+
- name: Install dependencies
22+
run: |
23+
python -m pip install --upgrade pip
24+
pip install pytest
25+
- name: Test with pytest
26+
run: |
27+
export PYTHONPATH=$(pwd)/scripts:$(pwd)/scripts/parse_tools
28+
pytest -v test/
29+
30+
doctest:
31+
runs-on: ubuntu-latest
32+
strategy:
33+
matrix:
34+
python-version: ['3.8', '3.9', '3.10', '3.11', '3.12']
35+
36+
steps:
37+
- uses: actions/checkout@v3
38+
- name: Set up Python ${{ matrix.python-version }}
39+
uses: actions/setup-python@v4
40+
with:
41+
python-version: ${{ matrix.python-version }}
42+
- name: Install dependencies
43+
run: |
44+
python -m pip install --upgrade pip
45+
pip install pytest
46+
- name: Doctest
47+
run: |
48+
export PYTHONPATH=$(pwd)/scripts:$(pwd)/scripts/parse_tools
49+
pytest -v scripts/ --doctest-modules

pytest.ini

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[pytest]
2+
addopts = -ra --ignore=scripts/metadata2html.py --ignore-glob=test/**/test_reports.py
3+

scripts/ccpp_capgen.py

Lines changed: 73 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -312,17 +312,29 @@ def compare_fheader_to_mheader(meta_header, fort_header, logger):
312312
# end if
313313
for mind, mvar in enumerate(mlist):
314314
lname = mvar.get_prop_value('local_name')
315+
mname = mvar.get_prop_value('standard_name')
315316
arrayref = is_arrayspec(lname)
316317
fvar, find = find_var_in_list(lname, flist)
317318
# Check for consistency between optional variables in metadata and
318319
# optional variables in fortran. Error if optional attribute is
319320
# missing from fortran declaration.
321+
# first check: if metadata says the variable is optional, does the fortran match?
320322
mopt = mvar.get_prop_value('optional')
321323
if find and mopt:
322324
fopt = fvar.get_prop_value('optional')
323325
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)
326338
# end if
327339
# end if
328340
if mind >= flen:
@@ -387,7 +399,8 @@ def compare_fheader_to_mheader(meta_header, fort_header, logger):
387399

388400
###############################################################################
389401
def check_fortran_against_metadata(meta_headers, fort_headers,
390-
mfilename, ffilename, logger):
402+
mfilename, ffilename, logger,
403+
dyn_routines=None, fortran_routines=None):
391404
###############################################################################
392405
"""Compare a set of metadata headers from <mfilename> against the
393406
code in the associated Fortran file, <ffilename>.
@@ -439,6 +452,17 @@ def check_fortran_against_metadata(meta_headers, fort_headers,
439452
's' if num_errors > 1 else '',
440453
mfilename, ffilename))
441454
# 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
442466
# No return, an exception is raised on error
443467

444468
###############################################################################
@@ -470,7 +494,7 @@ def parse_host_model_files(host_filenames, host_name, run_env):
470494
# parse metadata file
471495
mtables = parse_metadata_file(filename, known_ddts, run_env)
472496
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)
474498
# Check Fortran against metadata (will raise an exception on error)
475499
mheaders = list()
476500
for sect in [x.sections() for x in mtables]:
@@ -511,7 +535,7 @@ def parse_host_model_files(host_filenames, host_name, run_env):
511535
return host_model
512536

513537
###############################################################################
514-
def parse_scheme_files(scheme_filenames, run_env):
538+
def parse_scheme_files(scheme_filenames, run_env, skip_ddt_check=False):
515539
###############################################################################
516540
"""
517541
Gather information from scheme files (e.g., init, run, and finalize
@@ -524,9 +548,10 @@ def parse_scheme_files(scheme_filenames, run_env):
524548
for filename in scheme_filenames:
525549
logger.info('Reading CCPP schemes from {}'.format(filename))
526550
# 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)
528553
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)
530555
# Check Fortran against metadata (will raise an exception on error)
531556
mheaders = list()
532557
for sect in [x.sections() for x in mtables]:
@@ -536,8 +561,16 @@ def parse_scheme_files(scheme_filenames, run_env):
536561
for sect in [x.sections() for x in ftables]:
537562
fheaders.extend(sect)
538563
# 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
539570
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)
541574
# Check for duplicate tables, then add to dict
542575
for table in mtables:
543576
if table.table_name in table_dict:
@@ -560,6 +593,23 @@ def parse_scheme_files(scheme_filenames, run_env):
560593
# end if
561594
# end for
562595
# 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+
563613
return header_dict.values(), table_dict
564614

565615
###############################################################################
@@ -623,13 +673,25 @@ def capgen(run_env, return_db=False):
623673
# end if
624674
# First up, handle the host files
625675
host_model = parse_host_model_files(host_files, host_name, run_env)
676+
# Next, parse the scheme files
626677
# We always need to parse the ccpp_constituent_prop_ptr_t DDT
627678
const_prop_mod = os.path.join(src_dir, "ccpp_constituent_prop_mod.meta")
628679
if const_prop_mod not in scheme_files:
629-
scheme_files = [const_prop_mod] + scheme_files
680+
scheme_files= [const_prop_mod] + scheme_files
630681
# end if
631-
# Next, parse the scheme files
632682
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
633695
if run_env.verbose:
634696
ddts = host_model.ddt_lib.keys()
635697
if ddts:
@@ -660,7 +722,7 @@ def capgen(run_env, return_db=False):
660722
# end if
661723
os.makedirs(outtemp_dir)
662724
# 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)
664726
cap_filenames = ccpp_api.write(outtemp_dir, run_env)
665727
if run_env.generate_host_cap:
666728
# Create a cap file

0 commit comments

Comments
 (0)