Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

1991 better compilation support #2026

Merged
merged 94 commits into from
May 2, 2023
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
94 commits
Select commit Hold shift + click to select a range
ff2acdf
#1991 Added first WIP version of inlining all required modules.
hiker Jan 12, 2023
122bb52
#1991 Updated example to support inlined modules.
hiker Jan 12, 2023
b1fe078
Merge branch '1483_lfric_extraction_driver-test-merge-psyir' into 199…
hiker Jan 12, 2023
3f9896b
Merge branch '1483_lfric_extraction_driver-test-merge-psyir' into 199…
hiker Jan 12, 2023
4db5602
#1991 Renamed module information to module manager, moved it to the p…
hiker Jan 12, 2023
a3abce0
#1991 Made the module manager a singleton.
hiker Jan 12, 2023
316f6eb
#1991 Make the ModuleManager work with search directories instead of …
hiker Jan 13, 2023
db4c96a
#1991 Added tests and make sure to cover all lines.
hiker Jan 16, 2023
ad6a5ae
Merge branch '1483_lfric_extraction_driver-test-merge-psyir' into 199…
hiker Jan 16, 2023
c2ea7b0
#1991 Use os.access isntead of exists, which is a stricter test.
hiker Jan 16, 2023
f7d3517
Merge branch '1483_lfric_extraction_driver-test-merge-psyir' into 199…
hiker Jan 16, 2023
6ec0ba3
#1991 Fixed handling of modules that are not found.
hiker Jan 16, 2023
6baceaa
#1991 Added missing docstring.
hiker Jan 18, 2023
fcc2fd8
#1991 Started to add a new ModuleInfo object to better encapsulate ca…
hiker Jan 18, 2023
788ddb3
#1991 Moved parse tree handling to module info.
hiker Jan 19, 2023
6eb10d7
#1991 Moved used module detection into ModuleInformation.
hiker Jan 19, 2023
cff0173
#1991 Split ModuleManager and ModuleInformation into two files.
hiker Jan 19, 2023
b901548
#1991 Split the module manager tests into two files, each one testing…
hiker Jan 19, 2023
9c98768
#1991 Moved recursive collection of dependencies into module manager,…
hiker Jan 19, 2023
0dd95be
#1991 Make the module dependency function a static method in the Modu…
hiker Jan 20, 2023
6bc09ce
#1991 Simplified search loop.
hiker Jan 20, 2023
23fdc4a
#1991 Fixed test to handle empty modules as expected.
hiker Jan 20, 2023
722b408
#1991 Updated driver tests to work with the .F90 file extension.
hiker Jan 20, 2023
78941b8
#1991 Removed old precision handling in driver creation which is not …
hiker Jan 20, 2023
121c88d
#1991 Added r_second which is required for gungho.
hiker Jan 20, 2023
805bd51
Merge branch '1483_lfric_extraction_driver-test-merge-psyir' into 199…
hiker Jan 22, 2023
c042a20
Merge branch '1483_lfric_extraction_driver-test-merge-psyir' into 199…
hiker Jan 23, 2023
6f618ce
#1991 Add l_def to the list of imported symbols.
hiker Jan 23, 2023
7a43fc8
#1991 Use 2008 Fortran standard, not 2003.
hiker Jan 23, 2023
5d29837
#1991 Fixed handling of invalid paths as -d command line parameter, t…
hiker Jan 24, 2023
3620ec4
Merge branch '1483_lfric_extraction_driver-test-merge-psyir' into 199…
hiker Jan 24, 2023
1ae4a9f
Merge branch '1483_lfric_extraction_driver-test-merge-psyir' into 199…
hiker Jan 24, 2023
b971bc8
Merge branch '1483_lfric_extraction_driver-test-merge-psyir' into 199…
hiker Jan 30, 2023
a5fe136
#1991 Removed exception handling that could never happen.
hiker Jan 30, 2023
66dc3a9
Merge branch '1483_lfric_extraction_driver-test-merge-psyir' into 199…
hiker Jan 31, 2023
087ab63
#1991 Removed work-around for l_def not being in the precision list.
hiker Jan 31, 2023
3ffa560
#1991 Added comments, minor code cleanup.
hiker Jan 31, 2023
0467210
#1991 Fixed cleanup of example.
hiker Jan 31, 2023
772a06e
Merge branch '1483_lfric_extraction_driver-test-merge-psyir' into 199…
hiker Jan 31, 2023
91da2c0
Merge branch '1483_lfric_extraction_driver-test-merge-psyir' into 199…
hiker Feb 2, 2023
66adfcb
Merge branch '1483_lfric_extraction_driver-test-merge-psyir' into 199…
hiker Feb 6, 2023
b7cad2d
Merge branch '1483_lfric_extraction_driver-test-merge-psyir' into 199…
hiker Feb 6, 2023
e1fb8a3
Merge branch '1483_lfric_extraction_driver-test-merge-psyir' into 199…
hiker Feb 9, 2023
8c8c5a0
Merge branch '1483_lfric_extraction_driver-test-merge-psyir' into 199…
hiker Feb 13, 2023
54dac14
Merge branch '1483_lfric_extraction_driver-test-merge-psyir' into 199…
hiker Feb 21, 2023
fa13d03
Merge branch '1483_lfric_extraction_driver-test-merge-psyir' into 199…
hiker Mar 6, 2023
d12fa45
Merge branch '1483_lfric_extraction_driver-test-merge-psyir' into 199…
hiker Mar 19, 2023
a679b76
Merge branch '1483_lfric_extraction_driver-test-merge-psyir' into 199…
hiker Mar 21, 2023
8a61a2f
#1991 Add first compilation test.
hiker Mar 24, 2023
158ad9a
Merge branch '1483_lfric_extraction_driver-test-merge-psyir' into 199…
hiker Mar 24, 2023
ca5813d
1991 Removed debug print.
hiker Mar 24, 2023
80e25bf
#1991 Added compilation tests for driver creation.
hiker Mar 24, 2023
ab619c6
#1991 Renamed extension for auto-created files in compiler_strings fr…
hiker Mar 24, 2023
6f7b01c
#1991 Fixed pylint issues.
hiker Mar 24, 2023
86317da
#1991 Test that the expected modules are in the created driver.
hiker Mar 24, 2023
664b5d1
#1483 Ignore doxygen directory which is auto-created.
hiker Mar 28, 2023
f71f6ee
Merge branch '1483_lfric_extraction_driver-test-merge-psyir' into 199…
hiker Mar 28, 2023
86fdb71
#1991 Added documentation for module manager to user guide.
hiker Mar 28, 2023
284d82f
#1911 Updated documentation for LFRic extract example.
hiker Mar 28, 2023
d5777b5
#1911 Fixed unecessary options and fixed some bugs.
hiker Mar 28, 2023
8adc6dd
#1911 Updated comment.
hiker Mar 28, 2023
261ba88
#1991 Added debug information to check why CI cannot find files.
hiker Mar 28, 2023
e9bdd3f
Revert "#1991 Added debug information to check why CI cannot find fil…
hiker Mar 28, 2023
10bfcf8
#1991 Add the pre-processed read_kerbel_data_mod.f90 files to the rep…
hiker Mar 28, 2023
24015eb
#1991 Fixed incorrect documentation.
hiker Mar 29, 2023
c86ecf6
#1991 Updated documentation.
hiker Mar 29, 2023
fd90d24
#1991 Fixed pylint issues.
hiker Mar 29, 2023
1e04d01
Merge remote-tracking branch 'origin/master' into 1991_better_compila…
hiker Mar 29, 2023
0580908
#1991 Fixed doctests.
hiker Mar 29, 2023
513aba4
#1991 Restrict output line length of driver.
hiker Apr 5, 2023
6fd441b
#1991 Support more than one module in a file, and remove unused test …
hiker Apr 5, 2023
c4aa35e
#1991 Move initialisation of module manager to be after verification …
hiker Apr 5, 2023
dab68e8
#1991 Updated comments as indicated in review.
hiker Apr 5, 2023
a79faee
Merge remote-tracking branch 'origin/master' into 1991_better_compila…
hiker Apr 5, 2023
0b7e7e5
Merge remote-tracking branch 'origin/master' into 1991_better_compila…
hiker Apr 18, 2023
6021825
#1991 Fix some documentation issues raised in review.
hiker Apr 24, 2023
3f98609
#1991 Use OrderedDict to avoid checking if an entry already exists.
hiker Apr 24, 2023
f631964
#1991 Pre-process all .F90 LFRic infrastructure files so all these fi…
hiker Apr 26, 2023
d6a8cac
#1991 Fixed comments.
hiker Apr 26, 2023
4d8358e
#1991 Prefer pre-processed files over non-preprocessed files.
hiker Apr 26, 2023
c8e4a4f
#1991 Fixed preprocessing to exclude additinal markups like line numb…
hiker Apr 26, 2023
48cbedc
#1991 Addressed remaining reviewer comments about documentation.
hiker Apr 26, 2023
ee20965
#1991 Renamed _search_paths to _remaining_search_paths.
hiker Apr 26, 2023
213a09d
#1991 Added TODO for supporting an abort option.
hiker Apr 26, 2023
6032f40
Merge remote-tracking branch 'origin/master' into 1991_better_compila…
hiker Apr 26, 2023
1582ad3
#1991 Fixed failing doc test due to usage of pre-processed files.
hiker Apr 26, 2023
2287151
#1991 Moved module manager setup into conftest file.
hiker Apr 27, 2023
02c4ca5
#1991 Add search path to error message.
hiker Apr 28, 2023
b7948c2
#1991 Updated comment.
hiker Apr 28, 2023
ae2c74e
#1991 Updated README to justify why we add automatically created file…
hiker Apr 28, 2023
8368993
#1991 Replace '..' with os.path.dirname.
hiker Apr 28, 2023
e08aed0
Merge remote-tracking branch 'origin/master' into 1991_better_compila…
hiker May 2, 2023
834ee09
#2026 fix language in code block in psyke.rst
arporter May 2, 2023
0f40b13
#2026 update changelog and UG
arporter May 2, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions doc/developer_guide/psy_data.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1281,7 +1281,7 @@ which prints the filenames of all modules used in ``tl_testkern_mod``:

.. testoutput::

Module: argument_mod argument_mod.F90
Module: constants_mod constants_mod.F90
Module: fs_continuity_mod fs_continuity_mod.F90
Module: kernel_mod kernel_mod.F90
Module: argument_mod argument_mod.f90
Module: constants_mod constants_mod.f90
Module: fs_continuity_mod fs_continuity_mod.f90
Module: kernel_mod kernel_mod.f90
2 changes: 1 addition & 1 deletion src/psyclone/domain/lfric/lfric_extract_driver_creator.py
Original file line number Diff line number Diff line change
Expand Up @@ -880,7 +880,7 @@ def collect_all_required_modules(file_container):

:returns: a dictionary, with the required module names as key, and \
as value a set of all modules required by the key module.
:rtype: Set[str]
:rtype: Dict[str, Set[str]]

'''
all_mods = set()
Expand Down
5 changes: 2 additions & 3 deletions src/psyclone/parse/module_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,9 +88,8 @@ def __init__(self, name, filename):
# of all modules used by this module. Type: Set[str]
self._used_modules = None

# This is a dictionary, with the modules from the list of all used
# modules as key, and it stores the set of all symbols imported from
# this module: Dict[str, Set(str)]
# This is a dictionary containing the sets of symbols imported from
# each module, indexed by the module names: Dict[str, Set(str)].
self._used_symbols_from_module = None

# ------------------------------------------------------------------------
Expand Down
66 changes: 39 additions & 27 deletions src/psyclone/parse/module_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
which module is contained in which file (including full location). '''


from collections import OrderedDict
import copy
import os

Expand Down Expand Up @@ -69,8 +70,13 @@ def __init__(self):
if ModuleManager._instance is not None:
raise InternalError("You need to use 'ModuleManager.get()' "
"to get the singleton instance.")
# Cached mapping from module name to filename.
self._mod_2_filename = {}
arporter marked this conversation as resolved.
Show resolved Hide resolved
self._search_paths = []

# The list of all search paths which have not yet all their files
# checked. It is stored as an ordered dict to make it easier to avoid
# duplicating entries.
self._remaining_search_paths = OrderedDict()

# ------------------------------------------------------------------------
def add_search_path(self, directories, recursive=True):
Expand All @@ -91,16 +97,14 @@ def add_search_path(self, directories, recursive=True):

for directory in directories:
if not os.access(directory, os.R_OK):
raise IOError(f"Directory '{directory}' does not exist.")
if directory not in self._search_paths:
self._search_paths.append(directory)
if not recursive:
break
for root, dirs, _ in os.walk(directory):
for current_dir in dirs:
new_dir = os.path.join(root, current_dir)
if new_dir not in self._search_paths:
self._search_paths.append(new_dir)
raise IOError(f"Directory '{directory}' does not exist or "
f"cannot be read.")
self._remaining_search_paths[directory] = 1
if recursive:
for root, dirs, _ in os.walk(directory):
for current_dir in dirs:
new_dir = os.path.join(root, current_dir)
self._remaining_search_paths[new_dir] = 1

# ------------------------------------------------------------------------
def _add_all_files_from_dir(self, directory):
Expand All @@ -109,19 +113,24 @@ def _add_all_files_from_dir(self, directory):
module names are based on the filename using `get_modules_in_file()`.
By default it is assumed that `a_mod.f90` contains the module `a_mod`.

:param str directory: the directory to list all files from
'''
:param str directory: the directory containing Fortran files \
to analyse.

'''
with os.scandir(directory) as all_entries:
for entry in all_entries:
_, ext = os.path.splitext(entry.name)
if (not entry.is_file()) or \
ext not in [".F90", ".f90", ".X90", ".x90"]:
continue
full_path = os.path.join(directory, entry.name)
# Obtain the names of all modules defined in this source file.
all_modules = self.get_modules_in_file(full_path)
arporter marked this conversation as resolved.
Show resolved Hide resolved
for module in all_modules:
if module not in self._mod_2_filename:
# Pre-processed file should always take precedence
# over non-pre-processed files:
if module not in self._mod_2_filename or \
arporter marked this conversation as resolved.
Show resolved Hide resolved
ext in [".f90", ".x90"]:
mod_info = ModuleInfo(module, full_path)
self._mod_2_filename[module] = mod_info

Expand All @@ -141,7 +150,7 @@ def get_module_info(self, module_name):
'''
mod_lower = module_name.lower()

# First check if we already know about this file:
# First check if we have already cached this file:
mod_info = self._mod_2_filename.get(mod_lower, None)
if mod_info:
return mod_info
Expand All @@ -150,9 +159,9 @@ def get_module_info(self, module_name):
# the directories, we search directories one at a time, and
# add the list of all files in that directory to our cache
# _mod_2_filename
while self._search_paths:
while self._remaining_search_paths:
# Get the first element from the search path list:
directory = self._search_paths.pop(0)
directory, _ = self._remaining_search_paths.popitem(last=False)
self._add_all_files_from_dir(directory)
mod_info = self._mod_2_filename.get(mod_lower, None)
if mod_info:
Expand All @@ -163,12 +172,11 @@ def get_module_info(self, module_name):

# ------------------------------------------------------------------------
def get_modules_in_file(self, filename):
# pylint: disable=no-self-use
'''This function returns the list of modules defined in the specified
file. The base implementation uses the coding style: the file
`a_mod.f90` implements the module `a_mod`. This function can be
implemented in a derived class to actually parse the source file if
required.
file. The base implementation assumes the use of the LFRic coding
style: the file `a_mod.f90` implements the module `a_mod`. This
function can be implemented in a derived class to actually parse the
source file if required.

:param str filename: the file name for which to find the list \
of modules it contains.
Expand All @@ -179,7 +187,7 @@ def get_modules_in_file(self, filename):
'''
basename = os.path.basename(filename)
root, _ = os.path.splitext(basename)
if root[-4:].lower() == "_mod":
if root.lower().endswith("_mod"):
return [root]

return []
Expand All @@ -191,18 +199,19 @@ def get_all_dependencies_recursively(self, all_mods):
add all modules used by any module listed in ``all_mods``,
and any modules used by the just added modules etc. In the end,
it will return a dictionary that for each module lists which
module this module depends on. This dictionary will be complete,
modules it depends on. This dictionary will be complete,
i.e. all modules that are required for the original set of modules
(and that could be found) will be a key in the dictionary. It will
include the original set of modules as well.

If a module cannot be found (e.g. its path was not given to the
ModuleManager, or it might be a system module for which the sources
are not available, a message will be printed, and this module will
be ignored (i.e. not listed in any dependencies)
be ignored (i.e. not listed in any dependencies).
# TODO 2120: allow a choice to abort or ignore.

:param Set[str] all_mods: the set of all modules to collect the \
modules they use from.
:param Set[str] all_mods: the set of all modules for which to collect
module dependencies.

:returns: a dictionary with all modules that are required (directly \
or indirectly) for the modules in ``all_mods``.
Expand All @@ -229,6 +238,7 @@ def get_all_dependencies_recursively(self, all_mods):
if module not in not_found:
# We don't have any information about this module,
# ignore it.
# TODO 2120: allow a choice to abort or ignore.
print(f"Could not find module '{module}'.")
arporter marked this conversation as resolved.
Show resolved Hide resolved
not_found.add(module)
# Remove this module as dependencies from any other
Expand Down Expand Up @@ -289,6 +299,7 @@ def sort_modules(module_dependencies):
if dep not in todo:
print(f"Module '{module}' contains a dependency to "
arporter marked this conversation as resolved.
Show resolved Hide resolved
f"'{dep}', for which we have no dependencies.")
# TODO 2120: allow a choice to abort or ignore.
dependencies.remove(dep)

while todo:
Expand All @@ -302,6 +313,7 @@ def sort_modules(module_dependencies):
# is a circular dependency
print(f"Circular dependency - cannot sort "
arporter marked this conversation as resolved.
Show resolved Hide resolved
f"module dependencies: {todo}")
# TODO 2120: allow a choice to abort or ignore.
# In this case pick a module with the least number of
# dependencies, the best we can do in this case - and
# it's better to provide all modules (even if they cannot)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,8 @@ def init_module_manager():
'''

infrastructure_path = get_base_path(API)
# Define the path to the read-kernel relative to the infrastructure path:
# Define the path to the ReadKernelData module (which contains functions
# to read extracted data from a file) relative to the infrastructure path:
read_mod_path = os.path.join(infrastructure_path, "..", "..", "..", "..",
arporter marked this conversation as resolved.
Show resolved Hide resolved
"..", "lib", "extract", "standalone")
# Enforce loading of the default ModuleManager
Expand Down Expand Up @@ -267,6 +268,7 @@ def test_lfric_driver_simple_test():
"call extract_psy_data%ReadVariable('cell_post', cell_post)"]:
assert line in driver

# Check that all module dependencies have been inlined:
for mod in ["read_kernel_data_mod", "constants_mod", "kernel_mod",
arporter marked this conversation as resolved.
Show resolved Hide resolved
"argument_mod", "log_mod", "fs_continuity_mod",
"testkern_mod"]:
Expand Down
74 changes: 74 additions & 0 deletions src/psyclone/tests/parse/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# -----------------------------------------------------------------------------
# BSD 3-Clause License
#
# Copyright (c) 2023, Science and Technology Facilities Council.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# * Neither the name of the copyright holder nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
# COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
# -----------------------------------------------------------------------------
# Author J. Henrichs, Bureau of Meteorology


''' Module which provides a fixture to setup a test environment for
the module manager.
'''

import os

import pytest


# ----------------------------------------------------------------------------
@pytest.fixture(scope='function')
def mod_man_test_setup_directories():
'''Sets up a directory and file structure for several of the following
tests. The following structure is created - note that each Fortran file
declares a module of the same name as the basename of the file:
tmp/d1/a_mod.f90 : no dependencies
tmp/d1/d3/b_mod.F90 : no dependencies
tmp/d1/d3/c_mod.x90 : depends on a_mod/b_mod
tmp/d2/d_mod.X90 : depends on c_mod
tmp/d2/d4/e_mod.F90 : depends on netcdf
tmp/d2/d4/f_mod.ignore
'''

os.makedirs("d1/d3")
os.makedirs("d2/d4")
for (name, path, dependencies) in [("a_mod.f90", "d1", []),
("b_mod.F90", "d1/d3", []),
("c_mod.x90", "d1/d3", ["a_mod",
"b_mod"]),
("d_mod.X90", "d2", ["c_mod"]),
("e_mod.F90", "d2/d4", ["netcdf"]),
("f_mod.ignore", "d2/d4", [])]:
# Create a list of "use a_mod, only: a_mod_symbol" statements
uses = "\n".join(f"use {dep}, only: {dep}_symbol"
for dep in dependencies)
base, _ = os.path.splitext(name)
with open(os.path.join(path, name), "w", encoding="utf-8") as f_out:
f_out.write(f"module {base}\n{uses}\nend module {base}")
Loading