From a2baf28dc906150334d71c7c1fc4d62fec846306 Mon Sep 17 00:00:00 2001 From: Cristian Le Date: Wed, 5 Jul 2023 20:51:36 +0200 Subject: [PATCH 01/11] Use -D option like pre-processor -DFOO=some_text - For backwards compatibility, -D will continue to be interpreted as Python expression until 4.0 - Introduced a temporary variable -d/--define-value-type to switch to the proper interpretation when set to `str` Signed-off-by: Cristian Le --- bin/fypp | 51 ++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 40 insertions(+), 11 deletions(-) diff --git a/bin/fypp b/bin/fypp index 4207219..4c2b9f6 100755 --- a/bin/fypp +++ b/bin/fypp @@ -62,6 +62,7 @@ import optparse import io import platform import builtins +import warnings # Prevent cluttering user directory with Python bytecode sys.dont_write_bytecode = True @@ -2509,7 +2510,10 @@ class Fypp: self._import_modules(options.modules, evaluator, syspath, options.moduledirs) if options.defines: - self._apply_definitions(options.defines, evaluator) + # For normal text reuse the evaluator, but interpret the values as string + self._apply_python_definitions(options.defines, evaluator, as_string=True) + if options.python_defines: + self._apply_python_definitions(options.python_defines, evaluator) if inspect.signature(parser_factory) == inspect.signature(Parser): parser = parser_factory(includedirs=options.includes, encoding=self._encoding) @@ -2588,18 +2592,21 @@ class Fypp: @staticmethod - def _apply_definitions(defines, evaluator): + def _apply_python_definitions(defines, evaluator, as_string=False): for define in defines: words = define.split('=', 1) name = words[0] value = None if len(words) > 1: - try: - value = evaluator.evaluate(words[1]) - except Exception as exc: - msg = "exception at evaluating '{0}' in definition for " \ - "'{1}'".format(words[1], name) - raise FyppFatalError(msg) from exc + if as_string: + value = words[1] + else: + try: + value = evaluator.evaluate(words[1]) + except Exception as exc: + msg = "exception at evaluating '{0}' in definition for " \ + "'{1}'".format(words[1], name) + raise FyppFatalError(msg) from exc evaluator.define(name, value) @@ -2671,6 +2678,8 @@ class FyppOptions(optparse.Values): def __init__(self): optparse.Values.__init__(self) self.defines = [] + self.python_defines = [] + self.define_type = 'python' self.includes = [] self.line_numbering = False self.line_numbering_mode = 'full' @@ -2824,12 +2833,24 @@ def get_option_parser(): parser = optparse.OptionParser(prog=fypp_name, description=fypp_desc, version=fypp_version, usage=usage) - msg = 'define variable, value is interpreted as ' \ - 'Python expression (e.g \'-DDEBUG=1\' sets DEBUG to the ' \ - 'integer 1) or set to None if omitted' + msg = 'define variable, equivalent to the -D pre-processor flags. Depending on ' \ + 'the value of -d/--define-value-type, the value is either a string ' \ + 'or a Python expression' parser.add_option('-D', '--define', action='append', dest='defines', metavar='VAR[=VALUE]', default=defs.defines, help=msg) + msg = 'equivalent to -D/--define, but interprets the string value as ' \ + 'a Python expression' + parser.add_option('-P', '--python-define', action='append', dest='python_defines', + metavar='VAR[=VALUE]', default=defs.defines, help=msg) + + msg = 'whether to parse -D/--define values as string or Python expressions. ' \ + 'Default is \'python\' to preserve backwards compatibility, but **NOTE**, ' \ + 'this will be reversed in 4.0 and later.' + parser.add_option('-d', '--define-value-type', dest='define_type', + choices=['str', 'python'], + default=defs.define_type, help=msg) + msg = 'add directory to the search paths for include files' parser.add_option('-I', '--include', action='append', dest='includes', metavar='INCDIR', default=defs.includes, help=msg) @@ -2921,6 +2942,14 @@ def run_fypp(): opts, leftover = optparser.parse_args(values=options) infile = leftover[0] if len(leftover) > 0 else '-' outfile = leftover[1] if len(leftover) > 1 else '-' + + # Deprecation messages + if opts.define_type == 'python' and opts.defines: + warnings.warn("Interpreting -D/--define values as Python expressions is deprecated.\n" + "Please use use -P/--python-define instead, or consider using the values as simple strings", + DeprecationWarning) + opts.python_defines += opts.defines + opts.defines = [] try: tool = Fypp(opts) tool.process_file(infile, outfile) From 6040d711370ac9f03227ac27294f0b2ada8edbb8 Mon Sep 17 00:00:00 2001 From: Cristian Le Date: Tue, 4 Jul 2023 09:02:21 +0200 Subject: [PATCH 02/11] Remove travis Signed-off-by: Cristian Le --- .travis.yml | 10 ---------- 1 file changed, 10 deletions(-) delete mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 595e383..0000000 --- a/.travis.yml +++ /dev/null @@ -1,10 +0,0 @@ -language: python - -python: - - "3.5" - - "3.6" - - "3.7" - - "3.8" - - "3.9" - -script: test/runtests.sh From df910f297280be0e3a17b9f246737ce78c2fe8a7 Mon Sep 17 00:00:00 2001 From: Cristian Le Date: Tue, 4 Jul 2023 09:00:25 +0200 Subject: [PATCH 03/11] PEP621 Signed-off-by: Cristian Le --- .readthedocs.yaml | 4 ++- MANIFEST.in | 6 ---- docs/requirements.in | 2 -- docs/requirements.txt | 57 ------------------------------ pyproject.toml | 80 +++++++++++++++++++++++++++++++++++++++++++ setup.py | 54 ----------------------------- test/runtests.sh | 38 -------------------- tox.ini | 10 ------ 8 files changed, 83 insertions(+), 168 deletions(-) delete mode 100644 MANIFEST.in delete mode 100644 docs/requirements.in delete mode 100644 docs/requirements.txt create mode 100644 pyproject.toml delete mode 100644 setup.py delete mode 100755 test/runtests.sh delete mode 100644 tox.ini diff --git a/.readthedocs.yaml b/.readthedocs.yaml index c85c8d8..2f96010 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -18,4 +18,6 @@ sphinx: # https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html python: install: - - requirements: docs/requirements.txt + - path: . + extra_requirements: + - docs diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index ed630c2..0000000 --- a/MANIFEST.in +++ /dev/null @@ -1,6 +0,0 @@ -include bin/fypp -include LICENSE.txt -include CHANGELOG.rst -recursive-include test *.sh *.inc *.py -global-exclude *.pyc -global-exclude *~ diff --git a/docs/requirements.in b/docs/requirements.in deleted file mode 100644 index 7c475e9..0000000 --- a/docs/requirements.in +++ /dev/null @@ -1,2 +0,0 @@ -sphinx==6.2.1 -sphinx-rtd-theme diff --git a/docs/requirements.txt b/docs/requirements.txt deleted file mode 100644 index 89b855e..0000000 --- a/docs/requirements.txt +++ /dev/null @@ -1,57 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.10 -# by the following command: -# -# pip-compile requirements.in -# -alabaster==0.7.13 - # via sphinx -babel==2.12.1 - # via sphinx -certifi==2023.7.22 - # via requests -charset-normalizer==3.2.0 - # via requests -docutils==0.18.1 - # via - # sphinx - # sphinx-rtd-theme -idna==3.4 - # via requests -imagesize==1.4.1 - # via sphinx -jinja2==3.1.2 - # via sphinx -markupsafe==2.1.3 - # via jinja2 -packaging==23.1 - # via sphinx -pygments==2.15.1 - # via sphinx -requests==2.31.0 - # via sphinx -snowballstemmer==2.2.0 - # via sphinx -sphinx==6.2.1 - # via - # -r requirements.in - # sphinx-rtd-theme - # sphinxcontrib-jquery -sphinx-rtd-theme==1.2.2 - # via -r requirements.in -sphinxcontrib-applehelp==1.0.4 - # via sphinx -sphinxcontrib-devhelp==1.0.2 - # via sphinx -sphinxcontrib-htmlhelp==2.0.1 - # via sphinx -sphinxcontrib-jquery==4.1 - # via sphinx-rtd-theme -sphinxcontrib-jsmath==1.0.1 - # via sphinx -sphinxcontrib-qthelp==1.0.3 - # via sphinx -sphinxcontrib-serializinghtml==1.1.5 - # via sphinx -urllib3==2.0.4 - # via requests diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..b1a42fa --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,80 @@ +[build-system] +requires = ["hatchling", "hatch-vcs"] +build-backend = "hatchling.build" + +[project] +name = "fypp" +description = "Python powered Fortran preprocessor" +dynamic = ["version"] +authors = [ + { name = "Bálint Aradi", email = "aradi@uni-bremen.de" }, +] +requires-python = ">=3.5" +license = { text = "BSD-2-Clause" } +license-files = { paths = ["LICENSE.txt"] } +readme = "README.rst" + +classifiers = [ + 'Development Status :: 5 - Production/Stable', + 'Intended Audience :: Developers', + 'Intended Audience :: Science/Research', + 'Topic :: Software Development :: Code Generators', + 'Topic :: Software Development :: Pre-processors', + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3 :: Only", + 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', + 'Programming Language :: Python :: 3.9', + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", +] +keywords=[ + "fortran", + "metaprogramming", + "pre-processor", +] + +[project.urls] +homepage = "https://github.com/aradi/fypp" +documentation = "https://fypp.readthedocs.io/" +repository = "https://github.com/aradi/fypp" + +[project.scripts] +fypp = "fypp:run_fypp" + +[project.optional-dependencies] +test = [ + "pytest", +] +docs = [ + "sphinx", + "sphinx-rtd-theme", +] +dev = [ + "fypp[test]", +] + +[tool.hatch] +version.source = "vcs" +build.hooks.vcs.version-file = "src/fypp/_version.py" + +[tool.pytest.ini_options] +testpaths = ["test"] +python_files = "test_*.py" + +[tool.tox] +legacy_tox_ini = """ + [tox] + envlist = py34, py35, py36, py37, py38, py39 + + [testenv] + skip_missing_interpreters = + true + setenv = + PYTHONPATH = {toxinidir}/src + changedir=test + commands=python test_fypp.py +""" diff --git a/setup.py b/setup.py deleted file mode 100644 index ff587cd..0000000 --- a/setup.py +++ /dev/null @@ -1,54 +0,0 @@ -# -*- coding: utf-8 -*- -from setuptools import setup -from codecs import open -from os import path - -here = path.abspath(path.dirname(__file__)) - -with open(path.join(here, 'README.rst'), encoding='utf-8') as f: - long_description = f.read() - -setup( - name='fypp', - - version='3.2', - - description='Python powered Fortran preprocessor', - long_description=long_description, - - url='https://github.com/aradi/fypp', - - author='Bálint Aradi', - author_email='aradi@uni-bremen.de', - - license='BSD', - - classifiers=[ - 'Development Status :: 5 - Production/Stable', - - 'Intended Audience :: Developers', - 'Intended Audience :: Science/Research', - 'Topic :: Software Development :: Code Generators', - 'Topic :: Software Development :: Pre-processors', - - 'License :: OSI Approved :: BSD License', - - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.5', - 'Programming Language :: Python :: 3.6', - 'Programming Language :: Python :: 3.7', - 'Programming Language :: Python :: 3.8', - 'Programming Language :: Python :: 3.9', - ], - - keywords='fortran metaprogramming pre-processor', - - package_dir={'': 'src'}, - py_modules=['fypp'], - - entry_points={ - 'console_scripts': [ - 'fypp=fypp:run_fypp', - ], - }, -) diff --git a/test/runtests.sh b/test/runtests.sh deleted file mode 100755 index 422e171..0000000 --- a/test/runtests.sh +++ /dev/null @@ -1,38 +0,0 @@ -#!/bin/bash -testdir="$(dirname $0)" -if [ $# -gt 0 ]; then - pythons=$* -else - pythons="python3" -fi -root=".." -if [ -z "$PYTHONPATH" ]; then - export PYTHONPATH="$root/src" -else - export PYTHONPATH="$root/src:$PYTHONPATH" -fi -cd $testdir -failed="0" -failing_pythons="" -for python in $pythons; do - echo "Testing with interpreter '$python'" - $python test_fypp.py - exitcode=$? - if [ $exitcode != 0 ]; then - failed="$(($failed + 1))" - if [ -z "$failing_pythons" ]; then - failing_pythons=$python - else - failing_pythons="$failing_pythons, $python" - fi - fi -done -echo -if [ $failed -gt 0 ]; then - echo "Failing test runs: $failed" >&2 - echo "Failing interpreter(s): $failing_pythons" >&2 - exit 1 -else - echo "All test runs finished successfully" - exit 0 -fi diff --git a/tox.ini b/tox.ini deleted file mode 100644 index 295aed6..0000000 --- a/tox.ini +++ /dev/null @@ -1,10 +0,0 @@ -[tox] -envlist = py34, py35, py36, py37, py38, py39 - -[testenv] -skip_missing_interpreters = - true -setenv = - PYTHONPATH = {toxinidir}/src -changedir=test -commands=python test_fypp.py From 3d0c337d5567f5d56c595439413e0e58845068d0 Mon Sep 17 00:00:00 2001 From: Cristian Le Date: Tue, 4 Jul 2023 09:01:31 +0200 Subject: [PATCH 04/11] dynamic version Signed-off-by: Cristian Le --- .git_archival.txt | 4 +++ .gitignore | 3 ++ utils/bump-version.py | 65 ------------------------------------------- 3 files changed, 7 insertions(+), 65 deletions(-) create mode 100644 .git_archival.txt delete mode 100755 utils/bump-version.py diff --git a/.git_archival.txt b/.git_archival.txt new file mode 100644 index 0000000..8fb235d --- /dev/null +++ b/.git_archival.txt @@ -0,0 +1,4 @@ +node: $Format:%H$ +node-date: $Format:%cI$ +describe-name: $Format:%(describe:tags=true,match=*[0-9]*)$ +ref-names: $Format:%D$ diff --git a/.gitignore b/.gitignore index 7294e66..d1272c3 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,6 @@ docs/_build .tox _gitmsg.saved.txt + +### Project specific +src/fypp/_version.py diff --git a/utils/bump-version.py b/utils/bump-version.py deleted file mode 100755 index de4e739..0000000 --- a/utils/bump-version.py +++ /dev/null @@ -1,65 +0,0 @@ -#!/usr/bin/env python3 -import sys -import re -import os - -VERSION_PATTERN = r'\d+\.\d+(?:\.\d+)?(?:-\w+)?' -FILES_PATTERNS = [ ('bin/fypp', - r'^VERSION\s*=\s*([\'"]){}\1'.format(VERSION_PATTERN), - "VERSION = '{version}'"), - ('docs/fypp.rst', - r'Fypp Version[ ]*{}.'.format(VERSION_PATTERN), - 'Fypp Version {shortversion}.'), - ('setup.py', - r'version\s*=\s*([\'"]){}\1'.format(VERSION_PATTERN), - "version='{version}'"), - ('docs/conf.py', - r'version\s*=\s*([\'"]){}\1'.format(VERSION_PATTERN), - "version = '{shortversion}'"), - ('docs/conf.py', - r'release\s*=\s*([\'"]){}\1'.format(VERSION_PATTERN), - "release = '{version}'"), -] - -if len(sys.argv) < 2: - print("Missing version string") - sys.exit(1) - - -version = sys.argv[1] -shortversion = '.'.join(version.split('.')[0:2]) - -match = re.match(VERSION_PATTERN, version) -if match is None: - print("Invalid version string") - sys.exit(1) - -rootdir = os.path.join(os.path.dirname(sys.argv[0]), '..') -for fname, regexp, repl in FILES_PATTERNS: - fname = os.path.join(rootdir, fname) - print("Replacments in '{}': ".format(fname), end='') - fp = open(fname, 'r') - txt = fp.read() - fp.close() - replacement = repl.format(version=version, shortversion=shortversion) - newtxt, nsub = re.subn(regexp, replacement, txt, flags=re.MULTILINE) - print(nsub) - fp = open(fname, 'w') - fp.write(newtxt) - fp.close() - - -# Replace version number in Change Log and adapt decoration below -fname = os.path.join(rootdir, 'CHANGELOG.rst') -print("Replacments in '{}': ".format(fname), end='') -fp = open(fname, 'r') -txt = fp.read() -fp.close() -decoration = '=' * len(version) -newtxt, nsub = re.subn( - '^Unreleased\s*\n=+', version + '\n' + decoration, txt, - count=1, flags=re.MULTILINE) -print(nsub) -fp = open(fname, 'w') -fp.write(newtxt) -fp.close() From ffa0111fb853517fcced965bbe60c3d5656ded20 Mon Sep 17 00:00:00 2001 From: Cristian Le Date: Tue, 4 Jul 2023 09:02:06 +0200 Subject: [PATCH 05/11] Reorganize fypp package/module Signed-off-by: Cristian Le --- docs/fypp.rst | 4 +- pyproject.toml | 2 +- src/fypp.py | 1 - src/fypp/__init__.py | 6 ++ src/fypp/__main__.py | 3 + src/fypp/cli.py | 161 +++++++++++++++++++++++++++++++++ bin/fypp => src/fypp/fypp.py | 167 ++--------------------------------- test/test_fypp.py | 15 ++-- 8 files changed, 188 insertions(+), 171 deletions(-) delete mode 120000 src/fypp.py create mode 100644 src/fypp/__init__.py create mode 100644 src/fypp/__main__.py create mode 100644 src/fypp/cli.py rename bin/fypp => src/fypp/fypp.py (93%) diff --git a/docs/fypp.rst b/docs/fypp.rst index a35576b..8b57100 100644 --- a/docs/fypp.rst +++ b/docs/fypp.rst @@ -2145,10 +2145,10 @@ Fypp :members: -FyppOptions +FyppDefaults =========== -.. autoclass:: FyppOptions +.. autoclass:: FyppDefaults :members: diff --git a/pyproject.toml b/pyproject.toml index b1a42fa..df84a92 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -43,7 +43,7 @@ documentation = "https://fypp.readthedocs.io/" repository = "https://github.com/aradi/fypp" [project.scripts] -fypp = "fypp:run_fypp" +fypp = "fypp.cli:run_fypp" [project.optional-dependencies] test = [ diff --git a/src/fypp.py b/src/fypp.py deleted file mode 120000 index fea8343..0000000 --- a/src/fypp.py +++ /dev/null @@ -1 +0,0 @@ -../bin/fypp \ No newline at end of file diff --git a/src/fypp/__init__.py b/src/fypp/__init__.py new file mode 100644 index 0000000..308f088 --- /dev/null +++ b/src/fypp/__init__.py @@ -0,0 +1,6 @@ +from __future__ import annotations + +from ._version import version as __version__ +from .fypp import Fypp + +__all__ = ["__version__", "Fypp"] diff --git a/src/fypp/__main__.py b/src/fypp/__main__.py new file mode 100644 index 0000000..de33fa1 --- /dev/null +++ b/src/fypp/__main__.py @@ -0,0 +1,3 @@ +from .cli import run_fypp + +run_fypp() diff --git a/src/fypp/cli.py b/src/fypp/cli.py new file mode 100644 index 0000000..8da7f15 --- /dev/null +++ b/src/fypp/cli.py @@ -0,0 +1,161 @@ +import argparse +import sys +from .fypp import FyppDefaults, Fypp, FyppStopRequest, FyppFatalError, _formatted_exception, USER_ERROR_EXIT_CODE, \ + ERROR_EXIT_CODE +from ._version import __version__ + + +def get_option_parser() -> argparse.ArgumentParser: + """ + Returns an option parser for the Fypp command line tool. + + :return: Parser which can create an optparse.Values object with + Fypp settings based on command line arguments. + """ + defs = FyppDefaults() + parser = argparse.ArgumentParser( + prog="fypp", + description=""" + Preprocesses source code with Fypp directives. The input is + read from INFILE (default: \'-\', stdin) and written to + OUTFILE (default: \'-\', stdout). + """) + parser.add_argument('--version', action='version', version=__version__) + + parser.add_argument('-D', '--define', action='append', dest='defines', + metavar='VAR[=VALUE]', default=defs.defines, + help=""" + define variable, equivalent to the -D pre-processor flags. Depending on + the value of -d/--define-value-type, the value is either a string + or a Python expression + """) + parser.add_argument('-P', '--python-define', action='append', dest='python_defines', + metavar='VAR[=VALUE]', default=defs.defines, + help=""" + equivalent to -D/--define, but interprets the string value as + a Python expression + """) + parser.add_argument('-d', '--define-value-type', dest='define_type', + choices=['str', 'python'], default=defs.define_type, + help=""" + whether to parse -D/--define values as string or Python expressions. + Default is 'python' to preserve backwards compatibility, but **NOTE**, + this will be reversed in 4.0 and later. + """) + + parser.add_argument('-I', '--include', action='append', dest='includes', + metavar='INCDIR', default=defs.includes, + help=""" + add directory to the search paths for include files + """) + parser.add_argument('-m', '--module', action='append', dest='modules', + metavar='MOD', default=defs.modules, + help=""" + import a python module at startup (import only trustworthy modules + as they have access to an **unrestricted** Python environment!) + """) + + parser.add_argument('-M', '--module-dir', action='append', + dest='moduledirs', metavar='MODDIR', + default=defs.moduledirs, + help=""" + directory to be searched for user imported modules before + looking up standard locations in sys.path + """) + + parser.add_argument('-n', '--line-numbering', action='store_true', + dest='line_numbering', default=defs.line_numbering, + help=""" + emit line numbering markers + """) + parser.add_argument('-N', '--line-numbering-mode', metavar='MODE', + choices=['full', 'nocontlines'], + default=defs.line_numbering_mode, + dest='line_numbering_mode', + help=""" + line numbering mode, 'full' (default): line numbering + markers generated whenever source and output lines are out + of sync, 'nocontlines': line numbering markers omitted + for continuation lines + """) + parser.add_argument('--line-marker-format', metavar='FMT', + choices=['cpp', 'gfortran5', 'std'], + dest='line_marker_format', + default=defs.line_marker_format, + help=""" + line numbering marker format, currently 'std', 'cpp' and + 'gfortran5' are supported, where 'std' emits #line pragmas + similar to standard tools, 'cpp' produces line directives as + emitted by GNU cpp, and 'gfortran5' cpp line directives with a + workaround for a bug introduced in GFortran 5. Default: 'cpp'. + """) + parser.add_argument('-l', '--line-length', type=int, metavar='LEN', + dest='line_length', default=defs.line_length, + help=""" + maximal line length (default: 132), lines modified by the + preprocessor are folded if becoming longer + """) + parser.add_argument('-f', '--folding-mode', metavar='MODE', + choices=['smart', 'simple', 'brute'], dest='folding_mode', + default=defs.folding_mode, + help=""" + line folding mode, 'smart' (default): indentation context + and whitespace aware, 'simple': indentation context aware, + 'brute': mechnical folding + """) + + parser.add_argument('-F', '--no-folding', action='store_true', + dest='no_folding', default=defs.no_folding, + help=""" + suppress line folding + """) + parser.add_argument('--indentation', type=int, metavar='IND', + dest='indentation', default=defs.indentation, + help=""" + indentation to use for continuation lines (default 4) + """) + parser.add_argument('--fixed-format', action='store_true', + dest='fixed_format', default=defs.fixed_format, + help=""" + produce fixed format output (any settings for options + --line-length, --folding-method and --indentation are ignored) + """) + parser.add_argument('--encoding', metavar='ENC', default=defs.encoding, + help=""" + character encoding for reading/writing files. Default: 'utf-8'. + Note: reading from stdin and writing to stdout is encoded + according to the current locale and is not affected by this setting. + """) + parser.add_argument('-p', '--create-parents', action='store_true', + dest='create_parent_folder', + default=defs.create_parent_folder, + help=""" + create parent folders of the output file if they do not exist + """) + parser.add_argument('--file-var-root', metavar='DIR', + dest='file_var_root', + default=defs.file_var_root, + help=""" + in variables _FILE_ and _THIS_FILE_, use relative paths with DIR + as root directory. Note: the input file and all included files + must be in DIR or in a directory below. + """) + + return parser + + +def run_fypp(): + """Run the Fypp command line tool.""" + parser = get_option_parser() + opts, leftover = parser.parse_known_args() + infile = leftover[0] if len(leftover) > 0 else '-' + outfile = leftover[1] if len(leftover) > 1 else '-' + try: + tool = Fypp(opts) + tool.process_file(infile, outfile) + except FyppStopRequest as exc: + sys.stderr.write(_formatted_exception(exc)) + sys.exit(USER_ERROR_EXIT_CODE) + except FyppFatalError as exc: + sys.stderr.write(_formatted_exception(exc)) + sys.exit(ERROR_EXIT_CODE) diff --git a/bin/fypp b/src/fypp/fypp.py similarity index 93% rename from bin/fypp rename to src/fypp/fypp.py index 4c2b9f6..f7263dc 100755 --- a/bin/fypp +++ b/src/fypp/fypp.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- ################################################################################ # # fypp -- Python powered Fortran preprocessor @@ -37,7 +35,7 @@ * `Fypp`_: The actual Fypp preprocessor. It returns for a given input the preprocessed output. -* `FyppOptions`_: Contains customizable settings controlling the behaviour of +* `FyppDefaults`_: Contains customizable settings controlling the behaviour of `Fypp`_. Alternatively, the function `get_option_parser()`_ can be used to obtain an option parser, which can create settings based on command line arguments. @@ -67,8 +65,6 @@ # Prevent cluttering user directory with Python bytecode sys.dont_write_bytecode = True -VERSION = '3.2' - STDIN = '' FILEOBJ = '' @@ -2436,10 +2432,10 @@ class Fypp: tool = fypp.Fypp() output = tool.process_text('#:if DEBUG > 0\\nprint *, "DEBUG"\\n#:endif\\n') - If you want to fine tune Fypps behaviour, pass a customized `FyppOptions`_ + If you want to fine tune Fypps behaviour, pass a customized `FyppDefaults`_ instance at initialization:: - options = fypp.FyppOptions() + options = fypp.FyppDefaults() options.fixed_format = True tool = fypp.Fypp(options) @@ -2477,9 +2473,9 @@ def __init__(self): Args: options (object): Object containing the settings for Fypp. You typically - would pass a customized `FyppOptions`_ instance or an + would pass a customized `FyppDefaults`_ instance or an ``optparse.Values`` object as returned by the option parser. If not - present, the default settings in `FyppOptions`_ are used. + present, the default settings in `FyppDefaults`_ are used. evaluator_factory (function): Factory function that returns an Evaluator object. Its call signature must match that of the Evaluator constructor. If not present, ``Evaluator`` is used. @@ -2500,7 +2496,7 @@ def __init__(self, options=None, evaluator_factory=Evaluator, syspath = self._get_syspath_without_scriptdir() self._adjust_syspath(syspath) if options is None: - options = FyppOptions() + options = FyppDefaults() if inspect.signature(evaluator_factory) == inspect.signature(Evaluator): evaluator = evaluator_factory() else: @@ -2637,7 +2633,7 @@ def _adjust_syspath(syspath): sys.path = syspath -class FyppOptions(optparse.Values): +class FyppDefaults: '''Container for Fypp options with default values. @@ -2816,151 +2812,6 @@ def __call__(self, line): return [line] -def get_option_parser(): - '''Returns an option parser for the Fypp command line tool. - - Returns: - OptionParser: Parser which can create an optparse.Values object with - Fypp settings based on command line arguments. - ''' - defs = FyppOptions() - fypp_name = 'fypp' - fypp_desc = 'Preprocesses source code with Fypp directives. The input is '\ - 'read from INFILE (default: \'-\', stdin) and written to '\ - 'OUTFILE (default: \'-\', stdout).' - fypp_version = fypp_name + ' ' + VERSION - usage = '%prog [options] [INFILE] [OUTFILE]' - parser = optparse.OptionParser(prog=fypp_name, description=fypp_desc, - version=fypp_version, usage=usage) - - msg = 'define variable, equivalent to the -D pre-processor flags. Depending on ' \ - 'the value of -d/--define-value-type, the value is either a string ' \ - 'or a Python expression' - parser.add_option('-D', '--define', action='append', dest='defines', - metavar='VAR[=VALUE]', default=defs.defines, help=msg) - - msg = 'equivalent to -D/--define, but interprets the string value as ' \ - 'a Python expression' - parser.add_option('-P', '--python-define', action='append', dest='python_defines', - metavar='VAR[=VALUE]', default=defs.defines, help=msg) - - msg = 'whether to parse -D/--define values as string or Python expressions. ' \ - 'Default is \'python\' to preserve backwards compatibility, but **NOTE**, ' \ - 'this will be reversed in 4.0 and later.' - parser.add_option('-d', '--define-value-type', dest='define_type', - choices=['str', 'python'], - default=defs.define_type, help=msg) - - msg = 'add directory to the search paths for include files' - parser.add_option('-I', '--include', action='append', dest='includes', - metavar='INCDIR', default=defs.includes, help=msg) - - msg = 'import a python module at startup (import only trustworthy modules '\ - 'as they have access to an **unrestricted** Python environment!)' - parser.add_option('-m', '--module', action='append', dest='modules', - metavar='MOD', default=defs.modules, help=msg) - - msg = 'directory to be searched for user imported modules before '\ - 'looking up standard locations in sys.path' - parser.add_option('-M', '--module-dir', action='append', - dest='moduledirs', metavar='MODDIR', - default=defs.moduledirs, help=msg) - - msg = 'emit line numbering markers' - parser.add_option('-n', '--line-numbering', action='store_true', - dest='line_numbering', default=defs.line_numbering, - help=msg) - - msg = 'line numbering mode, \'full\' (default): line numbering '\ - 'markers generated whenever source and output lines are out '\ - 'of sync, \'nocontlines\': line numbering markers omitted '\ - 'for continuation lines' - parser.add_option('-N', '--line-numbering-mode', metavar='MODE', - choices=['full', 'nocontlines'], - default=defs.line_numbering_mode, - dest='line_numbering_mode', help=msg) - - msg = 'line numbering marker format, currently \'std\', \'cpp\' and '\ - '\'gfortran5\' are supported, where \'std\' emits #line pragmas '\ - 'similar to standard tools, \'cpp\' produces line directives as '\ - 'emitted by GNU cpp, and \'gfortran5\' cpp line directives with a '\ - 'workaround for a bug introduced in GFortran 5. Default: \'cpp\'.' - parser.add_option('--line-marker-format', metavar='FMT', - choices=['cpp', 'gfortran5', 'std'], - dest='line_marker_format', - default=defs.line_marker_format, help=msg) - - msg = 'maximal line length (default: 132), lines modified by the '\ - 'preprocessor are folded if becoming longer' - parser.add_option('-l', '--line-length', type=int, metavar='LEN', - dest='line_length', default=defs.line_length, help=msg) - - msg = 'line folding mode, \'smart\' (default): indentation context '\ - 'and whitespace aware, \'simple\': indentation context aware, '\ - '\'brute\': mechnical folding' - parser.add_option('-f', '--folding-mode', metavar='MODE', - choices=['smart', 'simple', 'brute'], dest='folding_mode', - default=defs.folding_mode, help=msg) - - msg = 'suppress line folding' - parser.add_option('-F', '--no-folding', action='store_true', - dest='no_folding', default=defs.no_folding, help=msg) - - msg = 'indentation to use for continuation lines (default 4)' - parser.add_option('--indentation', type=int, metavar='IND', - dest='indentation', default=defs.indentation, help=msg) - - msg = 'produce fixed format output (any settings for options '\ - '--line-length, --folding-method and --indentation are ignored)' - parser.add_option('--fixed-format', action='store_true', - dest='fixed_format', default=defs.fixed_format, help=msg) - - msg = 'character encoding for reading/writing files. Default: \'utf-8\'. '\ - 'Note: reading from stdin and writing to stdout is encoded '\ - 'according to the current locale and is not affected by this setting.' - parser.add_option('--encoding', metavar='ENC', default=defs.encoding, - help=msg) - - msg = 'create parent folders of the output file if they do not exist' - parser.add_option('-p', '--create-parents', action='store_true', - dest='create_parent_folder', - default=defs.create_parent_folder, help=msg) - - msg = 'in variables _FILE_ and _THIS_FILE_, use relative paths with DIR '\ - 'as root directory. Note: the input file and all included files '\ - 'must be in DIR or in a directory below.' - parser.add_option('--file-var-root', metavar='DIR', dest='file_var_root', - default=defs.file_var_root, help=msg) - - return parser - - -def run_fypp(): - '''Run the Fypp command line tool.''' - options = FyppOptions() - optparser = get_option_parser() - opts, leftover = optparser.parse_args(values=options) - infile = leftover[0] if len(leftover) > 0 else '-' - outfile = leftover[1] if len(leftover) > 1 else '-' - - # Deprecation messages - if opts.define_type == 'python' and opts.defines: - warnings.warn("Interpreting -D/--define values as Python expressions is deprecated.\n" - "Please use use -P/--python-define instead, or consider using the values as simple strings", - DeprecationWarning) - opts.python_defines += opts.defines - opts.defines = [] - try: - tool = Fypp(opts) - tool.process_file(infile, outfile) - except FyppStopRequest as exc: - sys.stderr.write(_formatted_exception(exc)) - sys.exit(USER_ERROR_EXIT_CODE) - except FyppFatalError as exc: - sys.stderr.write(_formatted_exception(exc)) - sys.exit(ERROR_EXIT_CODE) - - def linenumdir_cpp(linenr, fname, flag=None): """Returns a GNU cpp style line directive. @@ -3111,7 +2962,3 @@ def _formatted_exception(exc): out.append('\n' + _formatted_exception(exc.__cause__)) out.append('\n') return ''.join(out) - - -if __name__ == '__main__': - run_fypp() diff --git a/test/test_fypp.py b/test/test_fypp.py index 1dcc3ac..474b963 100644 --- a/test/test_fypp.py +++ b/test/test_fypp.py @@ -2,7 +2,8 @@ from pathlib import Path import platform import unittest -import fypp +import fypp.fypp as fypp +from fypp.cli import get_option_parser def _linenum(linenr, fname=None, flag=None): @@ -2957,8 +2958,8 @@ def _get_test_output_method(args, inp, out): def test_output(self): '''Tests whether Fypp result matches expected output.''' - optparser = fypp.get_option_parser() - options, leftover = optparser.parse_args(args) + parser = get_option_parser() + options, leftover = parser.parse_known_args(args) self.assertEqual(len(leftover), 0) tool = fypp.Fypp(options) result = tool.process_text(inp) @@ -2980,8 +2981,8 @@ def _get_test_output_from_file_input_method(args, inputfile, out): def test_output_from_file_input(self): '''Tests whether Fypp result matches expected output when input is in a file.''' - optparser = fypp.get_option_parser() - options, leftover = optparser.parse_args(args) + parser = get_option_parser() + options, leftover = parser.parse_known_args(args) self.assertEqual(len(leftover), 0) tool = fypp.Fypp(options) result = tool.process_file(inputfile) @@ -3006,8 +3007,8 @@ def _get_test_exception_method(args, inp, exceptions): def test_exception(self): '''Tests whether Fypp throws the correct exception.''' - optparser = fypp.get_option_parser() - options, leftover = optparser.parse_args(args) + parser = get_option_parser() + options, leftover = parser.parse_known_args(args) self.assertEqual(len(leftover), 0) try: tool = fypp.Fypp(options) From 90c1612fa726decc648e7986dc15a719a850f33d Mon Sep 17 00:00:00 2001 From: Cristian Le Date: Fri, 8 Sep 2023 11:37:24 +0200 Subject: [PATCH 06/11] Basic Github Action Signed-off-by: Cristian Le --- .codecov.yaml | 3 ++ .github/dependabot.yml | 7 ++++ .github/workflows/ci.yaml | 40 +++++++++++++++++++ .github/workflows/release.yaml | 52 +++++++++++++++++++++++++ .github/workflows/step_build-wheel.yaml | 29 ++++++++++++++ .github/workflows/step_test.yaml | 45 +++++++++++++++++++++ pyproject.toml | 4 ++ 7 files changed, 180 insertions(+) create mode 100644 .codecov.yaml create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/ci.yaml create mode 100644 .github/workflows/release.yaml create mode 100644 .github/workflows/step_build-wheel.yaml create mode 100644 .github/workflows/step_test.yaml diff --git a/.codecov.yaml b/.codecov.yaml new file mode 100644 index 0000000..6a4917b --- /dev/null +++ b/.codecov.yaml @@ -0,0 +1,3 @@ +codecov: + notify: + after_n_builds: 5 diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..491deae --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,7 @@ +version: 2 +updates: +- package-ecosystem: pip + directory: "/" + schedule: + interval: daily + open-pull-requests-limit: 10 diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 0000000..77681a6 --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,40 @@ +name: CI + +on: + workflow_dispatch: + inputs: + upload-wheel: + type: boolean + required: false + default: false + description: Upload wheel as an artifact + pull_request: + branches: [ main ] + push: + branches: [ main ] + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + tests: + uses: ./.github/workflows/step_test.yaml + + build-wheel: + uses: ./.github/workflows/step_build-wheel.yaml + needs: [ tests ] + with: + upload: ${{ inputs.upload-wheel || false }} + pass: + needs: [tests, build-wheel] + runs-on: ubuntu-latest + steps: + - name: Check all CI action + uses: re-actors/alls-green@release/v1 + with: + jobs: ${{ toJSON(needs) }} + if: always() diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml new file mode 100644 index 0000000..343df84 --- /dev/null +++ b/.github/workflows/release.yaml @@ -0,0 +1,52 @@ +name: Prepare release + +on: + push: + tags: + - "v[0-9]+.[0-9]+.[0-9]+" + - "v[0-9]+.[0-9]+.[0-9]+-rc[0-9]+" + workflow_dispatch: + inputs: + ref: + description: Tag to release + required: true + type: string + +permissions: + contents: read + +jobs: + tests: + uses: ./.github/workflows/step_test.yaml + build-wheel: + needs: [ tests ] + uses: ./.github/workflows/step_build-wheel.yaml + with: + ref: ${{ inputs.ref }} + upload_pypi: + name: Upload to PyPI repository + needs: [ tests, build-wheel ] + runs-on: ubuntu-latest + environment: + name: pypi + url: https://pypi.org/project/click-option-group/ + permissions: + id-token: write + steps: + - uses: actions/download-artifact@v3 + with: + name: artifact + path: dist + - name: Publish package to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + release: + needs: [ upload_pypi ] + name: Create release + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: softprops/action-gh-release@v1 + with: + name: click-option-group ${{ github.ref_name }} + prerelease: ${{ contains(github.ref, 'rc') }} + generate_release_notes: true diff --git a/.github/workflows/step_build-wheel.yaml b/.github/workflows/step_build-wheel.yaml new file mode 100644 index 0000000..b1cb720 --- /dev/null +++ b/.github/workflows/step_build-wheel.yaml @@ -0,0 +1,29 @@ +on: + workflow_call: + inputs: + upload: + description: Upload wheel as artifact + required: false + type: boolean + default: true + ref: + description: Tag to release + required: false + type: string + +permissions: + contents: read + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ inputs.ref }} + - name: Build package + run: pipx run build + - uses: actions/upload-artifact@v3 + with: + path: dist/* + if: ${{ inputs.upload }} diff --git a/.github/workflows/step_test.yaml b/.github/workflows/step_test.yaml new file mode 100644 index 0000000..a9cd07d --- /dev/null +++ b/.github/workflows/step_test.yaml @@ -0,0 +1,45 @@ +on: + workflow_call: + +permissions: + contents: read + +jobs: + tests: + name: Check with Python ${{ matrix.python-version }} ${{ matrix.experimental && '(Experimental)' }} + needs: [ pre-commit ] + continue-on-error: ${{ matrix.experimental || false }} + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python-version: [ "3.7", "3.8", "3.9", "3.10", "3.11", "3.12" ] + include: + - python-version: "3.12" + experimental: true + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + allow-prereleases: true + + - name: Install package + run: pip install -e .[test-cov] + - name: Test package + run: pytest --cov --cov-report=xml + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v3 + with: + name: python ${{ matrix.python-version }} + flags: python-${{ matrix.python-version }} + + pass: + needs: [ tests ] + runs-on: ubuntu-latest + steps: + - name: Check test jobs + uses: re-actors/alls-green@release/v1 + with: + jobs: ${{ toJSON(needs) }} + if: always() diff --git a/pyproject.toml b/pyproject.toml index df84a92..3199392 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,6 +49,10 @@ fypp = "fypp.cli:run_fypp" test = [ "pytest", ] +test-cov = [ + "fypp[test]", + "pytest-cov", +] docs = [ "sphinx", "sphinx-rtd-theme", From 276af573f3a90d40a9b54f977f94a4b568f67979 Mon Sep 17 00:00:00 2001 From: Cristian Le Date: Tue, 4 Jul 2023 09:00:56 +0200 Subject: [PATCH 07/11] Fix pytest Signed-off-by: Cristian Le --- test/test_fypp.py | 47 ++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 38 insertions(+), 9 deletions(-) diff --git a/test/test_fypp.py b/test/test_fypp.py index 474b963..13eee71 100644 --- a/test/test_fypp.py +++ b/test/test_fypp.py @@ -1,10 +1,33 @@ '''Unit tests for testing Fypp.''' +from contextlib import contextmanager +from os import getcwd, chdir from pathlib import Path import platform import unittest import fypp.fypp as fypp from fypp.cli import get_option_parser +DIR = Path(__file__).parent.resolve() +BASE = DIR.parent + + +@contextmanager +def cd(target): + """ + Manage cd in a pushd/popd fashion. + + Usage: + + with cd(tmpdir): + do something in tmpdir + """ + curdir = getcwd() + chdir(target) + try: + yield + finally: + chdir(curdir) + def _linenum(linenr, fname=None, flag=None): if fname is None: @@ -1413,7 +1436,7 @@ def _importmodule(module): ), ('escape_comment', ([], - 'A\n #\! Comment\n', + 'A\n #\\! Comment\n', 'A\n #! Comment\n', ) ), @@ -2151,8 +2174,8 @@ def _importmodule(module): ) ), ('file_var_root_abs', - ([f"--file-var-root={Path.cwd()}"], - f"{Path.cwd() / 'input/filevarroot.fypp'}", + ([f"--file-var-root={DIR}"], + f"{DIR / 'input/filevarroot.fypp'}", 'FILE: input/filevarroot.fypp:1\n' 'THIS_FILE: input/filevarroot.fypp:2\n' '---\n' @@ -2961,8 +2984,10 @@ def test_output(self): parser = get_option_parser() options, leftover = parser.parse_known_args(args) self.assertEqual(len(leftover), 0) - tool = fypp.Fypp(options) - result = tool.process_text(inp) + # TODO: Use proper test fixture to temporary folder + with cd(DIR): + tool = fypp.Fypp(options) + result = tool.process_text(inp) self.assertEqual(out, result) return test_output @@ -2984,8 +3009,10 @@ def test_output_from_file_input(self): parser = get_option_parser() options, leftover = parser.parse_known_args(args) self.assertEqual(len(leftover), 0) - tool = fypp.Fypp(options) - result = tool.process_file(inputfile) + # TODO: Use proper test fixture to temporary folder + with cd(DIR): + tool = fypp.Fypp(options) + result = tool.process_file(inputfile) self.assertEqual(out, result) return test_output_from_file_input @@ -3011,8 +3038,10 @@ def test_exception(self): options, leftover = parser.parse_known_args(args) self.assertEqual(len(leftover), 0) try: - tool = fypp.Fypp(options) - _ = tool.process_text(inp) + # TODO: Use proper test fixture to temporary folder + with cd(DIR): + tool = fypp.Fypp(options) + _ = tool.process_text(inp) except Exception as e: raised = e else: From 63658ce940c4c55594fe8c4d27cc10868a0b9a09 Mon Sep 17 00:00:00 2001 From: Cristian Le Date: Wed, 5 Jul 2023 21:04:04 +0200 Subject: [PATCH 08/11] Main Fypp cmake module Signed-off-by: Cristian Le --- cmake/Fypp.cmake | 319 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 319 insertions(+) create mode 100644 cmake/Fypp.cmake diff --git a/cmake/Fypp.cmake b/cmake/Fypp.cmake new file mode 100644 index 0000000..9cf0157 --- /dev/null +++ b/cmake/Fypp.cmake @@ -0,0 +1,319 @@ +include_guard(GLOBAL) +cmake_minimum_required(VERSION 3.20) +# CMake version compatibility +# TODO: Remove when cmake 3.25 is commonly distributed +if (POLICY CMP0140) + cmake_policy(SET CMP0140 NEW) +endif () +if (POLICY CMP0118) + cmake_policy(SET CMP0118 NEW) +endif () + +#[==============================================================================================[ +# Preparations # +]==============================================================================================] + +# Find the appropriate fypp executable +find_package(Python3 REQUIRED) +cmake_path(GET Python3_EXECUTABLE PARENT_PATH Python3_BINDIR) +find_program( + Fypp_EXECUTABLE + NAMES fypp + HINTS "${Python3_BINDIR}" + DOC "Fypp preprocessor" +) +mark_as_advanced(Fypp_EXECUTABLE) + +#[==============================================================================================[ +# Main interface # +]==============================================================================================] + +function(Fypp_target_sources target) + #[===[ + # Fypp_target_sources + + Equivalent to `target_sources`. Separates the fypp file sources from the non-fypp files, passing the latter + directly to the `target_sources`. Does not support `FILE_SET` interface yet. + + This is the preferred interface format over `Fypp_add_library` and `Fypp_add_executable`. + + ## Notes + + - Due to cmake limitations only the target properties derived up to the point of this function call are parsed, + including those defined with `target_compile_definitions`. + [Upstream discussion](https://discourse.cmake.org/t/add-custom-command-with-target-properties-at-build-time/8464) + + ## Synopsis + ```cmake + ``` + TODO: Documentation + ]===] + + set(ARGS_Options "") + set(ARGS_OneValue "FILE_SET") + set(ARGS_MultiValue "INTERFACE;PUBLIC;PRIVATE") + cmake_parse_arguments(PARSE_ARGV 1 ARGS "${ARGS_Options}" "${ARGS_OneValue}" "${ARGS_MultiValue}") + + if (DEFINED ARGS_FILE_SET) + # FILE_SET parsing is not yet supported + # TODO: Figure out how to parse FILE_SET + message(FATAL_ERROR + "Fypp: FILE_SET is not yet supported in Fypp_target_sources") + endif () + + # Without FILE_SET, all values in ARGS_INTERFACE/PUBLIC/PRIVATE are source files + # Forward the files to _Fypp_add_source + foreach (type IN ITEMS INTERFACE PUBLIC PRIVATE) + if (DEFINED ARGS_${type}) + get_Fypp_sources(FYPP_SOURCES_VAR fypp_sources OTHER_SOURCES_VAR other_sources + SOURCES ${ARGS_${type}}) + target_sources(${target} ${type} ${other_sources}) + _Fypp_add_source(${target} ${type} ${fypp_sources}) + endif () + endforeach () +endfunction() + +function(Fypp_add_library name) + #[===[ + # Fypp_add_library + + Equivalent to `add_library( [])` and `Fypp_target_sources( PRIVATE )` + + ## Synopsis + ```cmake + ``` + TODO: Documentation + ]===] + set(ARGS_Options "STATIC;SHARED;OBJECT;MODULE;EXCLUDE_FROM_ALL;IMPORTED;ALIAS") + set(ARGS_OneValue "") + set(ARGS_MultiValue "") + cmake_parse_arguments(PARSE_ARGV 1 ARGS "${ARGS_Options}" "${ARGS_OneValue}" "${ARGS_MultiValue}") + + if (ARGS_IMPORTED OR ARGS_ALIAS) + # IMPORTED and ALIAS library types are ill-defined with Fypp + message(FATAL_ERROR + "Fypp: IMPORTED and ALIAS library types are ill-defined as Fypp libraries") + endif () + + # Gather the add_library inputs + # Input sanitization will be done by base add_library + set(add_library_inputs "") + if (ARGS_STATIC) + list(APPEND add_library_inputs STATIC) + endif () + if (ARGS_SHARED) + list(APPEND add_library_inputs SHARED) + endif () + if (ARGS_MODULE) + list(APPEND add_library_inputs MODULE) + endif () + if (ARGS_OBJECT) + list(APPEND add_library_inputs OBJECT) + endif () + if (ARGS_EXCLUDE_FROM_ALL) + list(APPEND add_library_inputs EXCLUDE_FROM_ALL) + endif () + + # Create the base library target + add_library(${name} ${add_library_inputs}) + + # All other arguments should be source files + if (DEFINED ARGS_UNPARSED_ARGUMENTS) + Fypp_target_sources(${name} PRIVATE ${ARGS_UNPARSED_ARGUMENTS}) + endif () +endfunction() + +function(Fypp_add_executable name) + #[===[ + # Fypp_add_executable + + Equivalent to `add_executable()` and `Fypp_target_sources( PRIVATE )` + + ## Synopsis + ```cmake + ``` + TODO: Documentation + ]===] + set(ARGS_Options "WIN32;MACOSX_BUNDLE;EXCLUDE_FROM_ALL;IMPORTED;ALIAS") + set(ARGS_OneValue "") + set(ARGS_MultiValue "") + cmake_parse_arguments(PARSE_ARGV 1 ARGS "${ARGS_Options}" "${ARGS_OneValue}" "${ARGS_MultiValue}") + + if (ARGS_IMPORTED OR ARGS_ALIAS) + # IMPORTED and ALIAS library types are ill-defined with Fypp + message(FATAL_ERROR + "Fypp: IMPORTED and ALIAS library types are ill-defined as Fypp libraries") + endif () + + # Gather the add_executable inputs + # Input sanitization will be done by base add_executable + set(add_executable_inputs "") + if (ARGS_WIN32) + list(APPEND add_executable_inputs WIN32) + endif () + if (ARGS_MACOSX_BUNDLE) + list(APPEND add_executable_inputs MACOSX_BUNDLE) + endif () + if (ARGS_EXCLUDE_FROM_ALL) + list(APPEND add_executable_inputs EXCLUDE_FROM_ALL) + endif () + + # Create the base executable target + add_executable(${name} ${add_executable_inputs}) + + # All other arguments should be source files + if (DEFINED ARGS_UNPARSED_ARGUMENTS) + Fypp_target_sources(${name} PRIVATE ${ARGS_UNPARSED_ARGUMENTS}) + endif () +endfunction() + +#[==============================================================================================[ +# Auxiliary interface # +]==============================================================================================] + +function(get_Fypp_sources) + #[===[ + # get_Fypp_sources + + Separate the fypp files from the other source files + + ## Synopsis + ```cmake + ``` + TODO: Documentation + ]===] + set(ARGS_Options "") + set(ARGS_OneValue "FYPP_SOURCES_VAR;OTHER_SOURCES_VAR") + set(ARGS_MultiValue "SOURCES") + cmake_parse_arguments(PARSE_ARGV 0 ARGS "${ARGS_Options}" "${ARGS_OneValue}" "${ARGS_MultiValue}") + + if (NOT DEFINED ARGS_FYPP_SOURCES_VAR) + message(FATAL_ERROR + "Fypp: FYPP_SOURCES_VAR is required for get_Fypp_sources() function") + endif () + if (NOT DEFINED ARGS_SOURCES) + message(FATAL_ERROR + "Fypp: SOURCES is required for get_Fypp_sources() function") + endif () + + # Initialize the output variables to empty + set(${ARGS_FYPP_SOURCES_VAR} "") + if (DEFINED ARGS_OTHER_SOURCES_VAR) + set(${ARGS_OTHER_SOURCES_VAR} "") + endif () + + # Loop through the sources and check the extension + foreach (source IN LISTS ARGS_SOURCES) + cmake_path(GET source EXTENSION LAST_ONLY source_ext) + if (source_ext MATCHES fypp|fpp|FYPP|FPP) + list(APPEND ${ARGS_FYPP_SOURCES_VAR} ${source}) + elseif (DEFINED ARGS_OTHER_SOURCES_VAR) + list(APPEND ${ARGS_OTHER_SOURCES_VAR} ${source}) + endif () + endforeach () + + # Return the values to the caller + if (CMAKE_VERSION VERSION_LESS 3.25) + # TODO: Remove when cmake 3.25 is commonly distributed + set(${ARGS_FYPP_SOURCES_VAR} ${${ARGS_FYPP_SOURCES_VAR}} PARENT_SCOPE) + if (DEFINED ARGS_OTHER_SOURCES_VAR) + set(${ARGS_OTHER_SOURCES_VAR} ${${ARGS_OTHER_SOURCES_VAR}} PARENT_SCOPE) + endif () + else () + return(PROPAGATE + ${ARGS_FYPP_SOURCES_VAR} + # TODO: Does this one work when none is defined? + ${ARGS_OTHER_SOURCES_VAR} + ) + endif () +endfunction() + +#[==============================================================================================[ +# Private interface # +]==============================================================================================] + +# Main implementation +function(_Fypp_add_source target type) + #[===[ + # _Fypp_add_source + + Main implementation of the fypp wrapper. + Adds fypp generated source to target with appropriate add_custom_command dependencies + ]===] + + # Early return if no sources are passed + if (NOT ARGN) + return() + endif () + + # Create a pseudo file with the fypp metadata + get_property(target_bindir TARGET ${target} + PROPERTY BINARY_DIR) + # TODO: Get the appropriate path to `target.dir` folder + # TODO: back-port cmake_path or convert to get_filename_component + cmake_path(APPEND target_bindir CMakeFiles ${target}.dir OUTPUT_VARIABLE target_cmake_bindir) + cmake_path(APPEND target_cmake_bindir fypp_files OUTPUT_VARIABLE target_fypp_files) + if (NOT EXISTS ${target_fypp_files}) + # Create the main custom_command with appropriate COMMENT + # TODO: Add depfile support + add_custom_command(OUTPUT ${target_fypp_files} + # Make sure all the parent directories exist + COMMAND ${CMAKE_COMMAND} -E make_directory ${target_cmake_bindir} + # Initialize the file fypp_files with the name of the target + COMMAND ${CMAKE_COMMAND} -E echo "${target}:" ${target_fypp_files} + COMMENT "Parsing the fypp files for ${target} target" + WORKING_DIRECTORY ${target_bindir} + ) + endif () + + # Get the target properties to forward them to fypp + # Note: This does not pick up the final properties used at build time or genex + # https://discourse.cmake.org/t/add-custom-command-with-target-properties-at-build-time/8464 + get_property(target_defines TARGET ${target} + PROPERTY COMPILE_DEFINITIONS) + get_property(target_includes TARGET ${target} + PROPERTY INCLUDE_DIRECTORIES) + # Normalize the properties to fypp format + set(define_flags "") + foreach (define IN LISTS target_defines) +# string(REGEX REPLACE "=(.*)" "=\"\\1\"" define_sanitized ${define}) +# list(APPEND define_flags "-D${define_sanitized}") + string(REGEX REPLACE "=(.*)" "=\"\\1\"" define_sanitized ${define}) + list(APPEND define_flags "-D${define}") + endforeach () + set(include_flags "") + foreach (include IN LISTS target_includes) + list(APPEND include_flags "-I${include}") + endforeach () + + set(fypp_compiled_files "") + # Add each fypp file to add_custom_command + foreach (source IN LISTS ARGN) + # Get the absolute path of the source file + if (IS_ABSOLUTE source) + cmake_path(NATIVE_PATH source NORMALIZE source_path) + else () + cmake_path(ABSOLUTE_PATH source NORMALIZE OUTPUT_VARIABLE source_path_abs) + cmake_path(NATIVE_PATH source_path_abs NORMALIZE source_path) + endif () + # Create a variable for the output file + cmake_path(RELATIVE_PATH source_path BASE_DIRECTORY ${PROJECT_SOURCE_DIR} + OUTPUT_VARIABLE rel_path) + cmake_path(APPEND target_cmake_bindir ${rel_path}.f90 OUTPUT_VARIABLE out_file) + cmake_path(NATIVE_PATH out_file NORMALIZE out_path) + # Append fypp command to the main target custom_command + add_custom_command(OUTPUT ${out_file} + # Append the name of the generated files to the fypp_files + # TODO: This might duplicate the file names when dependency is rerun + COMMAND ${CMAKE_COMMAND} -E echo ${out_file} >> ${target_fypp_files} + COMMAND ${Fypp_EXECUTABLE} -p ${define_flags} ${include_flags} ${source_path} ${out_path} + DEPENDS ${source} + COMMENT "Adding file ${out_file}") + set_source_files_properties(${out_file} PROPERTIES + GENERATED True) + list(APPEND fypp_compiled_files ${out_file}) + endforeach () + # Finally add the generated source to the target + # TODO: Sanitize the output files better to not duplicate folder structure + target_sources(${target} ${type} ${fypp_compiled_files}) +endfunction() From 54e2989f3ca734255df66fc6ceede7072ac4acea Mon Sep 17 00:00:00 2001 From: Cristian Le Date: Wed, 5 Jul 2023 21:13:23 +0200 Subject: [PATCH 09/11] Add basic CMake project files Signed-off-by: Cristian Le --- .gitignore | 2 + CMakeLists.txt | 98 +++++++++++++++++++++++++++++++++++++++ cmake/FyppConfig.cmake.in | 4 ++ test/CMakeLists.txt | 2 + 4 files changed, 106 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 cmake/FyppConfig.cmake.in create mode 100644 test/CMakeLists.txt diff --git a/.gitignore b/.gitignore index d1272c3..5fc909f 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,8 @@ docs/_build .tox _gitmsg.saved.txt +### Basic setups +cmake-build-* ### Project specific src/fypp/_version.py diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..9309f4f --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,98 @@ +cmake_minimum_required(VERSION 3.15) +# CMake version compatibility +# TODO: Remove when cmake 3.25 is commonly distributed +if (POLICY CMP0140) + cmake_policy(SET CMP0140 NEW) +endif () + +#[==============================================================================================[ +# Basic project defintion # +]==============================================================================================] + +project(Fypp + VERSION 3.1.0 + LANGUAGES NONE) + +# Back-porting to PROJECT_IS_TOP_LEVEL to older cmake +# TODO: Remove when requiring cmake >= 3.21 +if (NOT DEFINED Fypp_IS_TOP_LEVEL) + if (CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME) + set(PROJECT_IS_TOP_LEVEL ON) + else () + set(PROJECT_IS_TOP_LEVEL OFF) + endif () +endif () + +#[==============================================================================================[ +# Options # +]==============================================================================================] + +option(FYPP_TESTS "Fypp: Build unit tests" ${PROJECT_IS_TOP_LEVEL}) +option(FYPP_INSTALL "Fypp: Install project" ${PROJECT_IS_TOP_LEVEL}) + +#[==============================================================================================[ +# Project configuration # +]==============================================================================================] + +# Include basic tools +if (FYPP_INSTALL) + include(CMakePackageConfigHelpers) + if (UNIX) + include(GNUInstallDirs) + endif () +endif () +list(PREPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}/cmake) + +if (FYPP_TESTS) + enable_testing() + add_subdirectory(test) +endif () + +#[==============================================================================================[ +# Install or Export # +]==============================================================================================] +# Installation +if (FYPP_INSTALL) + # cmake export files + write_basic_package_version_file( + FyppConfigVersion.cmake + VERSION ${PROJECT_VERSION} + COMPATIBILITY SameMinorVersion + ARCH_INDEPENDENT) + configure_package_config_file( + cmake/FyppConfig.cmake.in + FyppConfig.cmake + INSTALL_DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/cmake/Fypp) + install(FILES ${CMAKE_CURRENT_BINARY_DIR}/FyppConfigVersion.cmake + ${CMAKE_CURRENT_BINARY_DIR}/FyppConfig.cmake + cmake/CMakeDetermineFyppCompiler.cmake + cmake/CMakeFyppCompiler.cmake.in + cmake/CMakeFyppInformation.cmake + cmake/CMakeTestFyppCompiler.cmake + DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/cmake/Fypp) +endif () + +# Make project available for FetchContent +if (NOT PROJECT_IS_TOP_LEVEL) + # Set variables for FetchContent + # All variables have to be consistent with FyppConfig.cmake + # Propagate variables + if (CMAKE_VERSION VERSION_LESS 3.25) + # TODO: Remove when cmake 3.25 is commonly distributed + set(Fypp_VERSION ${Fypp_VERSION} PARENT_SCOPE) + set(Fypp_VERSION_MAJOR ${Fypp_VERSION_MAJOR} PARENT_SCOPE) + set(Fypp_VERSION_MINOR ${Fypp_VERSION_MINOR} PARENT_SCOPE) + set(Fypp_VERSION_PATCH ${Fypp_VERSION_PATCH} PARENT_SCOPE) + set(Fypp_VERSION_TWEAK ${Fypp_VERSION_TWEAK} PARENT_SCOPE) + set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} PARENT_SCOPE) + else () + return(PROPAGATE + Fypp_VERSION + Fypp_VERSION_MAJOR + Fypp_VERSION_MINOR + Fypp_VERSION_PATCH + Fypp_VERSION_TWEAK + CMAKE_MODULE_PATH + ) + endif () +endif () diff --git a/cmake/FyppConfig.cmake.in b/cmake/FyppConfig.cmake.in new file mode 100644 index 0000000..dd37934 --- /dev/null +++ b/cmake/FyppConfig.cmake.in @@ -0,0 +1,4 @@ +@PACKAGE_INIT@ + +list(PREPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}) +include(Fypp) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt new file mode 100644 index 0000000..0c099b7 --- /dev/null +++ b/test/CMakeLists.txt @@ -0,0 +1,2 @@ +add_test(NAME version + COMMAND ${CMAKE_Fypp_COMPILER} --version) From 6c4ce2cd4f2b7066f91f79a308f9a18319eefa92 Mon Sep 17 00:00:00 2001 From: Cristian Le Date: Wed, 5 Jul 2023 21:06:58 +0200 Subject: [PATCH 10/11] Add basic CMakePresets Signed-off-by: Cristian Le --- .gitignore | 1 + CMakePresets.json | 12 ++ cmake/CMakePresets-CI.json | 193 +++++++++++++++++++++++++++++++ cmake/CMakePresets-defaults.json | 50 ++++++++ 4 files changed, 256 insertions(+) create mode 100644 CMakePresets.json create mode 100644 cmake/CMakePresets-CI.json create mode 100644 cmake/CMakePresets-defaults.json diff --git a/.gitignore b/.gitignore index 5fc909f..78e0626 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,4 @@ cmake-build-* ### Project specific src/fypp/_version.py +CMakeUserPresets.json diff --git a/CMakePresets.json b/CMakePresets.json new file mode 100644 index 0000000..3db1fed --- /dev/null +++ b/CMakePresets.json @@ -0,0 +1,12 @@ +{ + "version": 6, + "cmakeMinimumRequired": { + "major": 3, + "minor": 25, + "patch": 0 + }, + "include": [ + "cmake/CMakePresets-defaults.json", + "cmake/CMakePresets-CI.json" + ] +} diff --git a/cmake/CMakePresets-CI.json b/cmake/CMakePresets-CI.json new file mode 100644 index 0000000..04b3580 --- /dev/null +++ b/cmake/CMakePresets-CI.json @@ -0,0 +1,193 @@ +{ + "version": 6, + "include": [ + "CMakePresets-defaults.json" + ], + "configurePresets": [ + { + "name": "ci-base", + "hidden": true, + "generator": "Ninja", + "inherits": [ + "default" + ], + "cacheVariables": { + "FYPP_TESTS": { + "type": "BOOL", + "value": true + } + }, + "errors": { + "deprecated": true + } + }, + { + "name": "gcc-ci", + "displayName": "Configure preset for GCC toolchain", + "inherits": [ + "ci-base" + ], + "binaryDir": "cmake-build-release-gcc", + "cacheVariables": { + "CMAKE_Fortran_COMPILER": { + "type": "FILEPATH", + "value": "gfortran" + } + } + }, + { + "name": "intel-ci", + "displayName": "Configure preset for Intel toolchain", + "inherits": [ + "ci-base" + ], + "binaryDir": "cmake-build-release-intel", + "cacheVariables": { + "CMAKE_Fortran_COMPILER": { + "type": "FILEPATH", + "value": "ifx" + } + } + }, + { + "name": "llvm-ci", + "displayName": "Configure preset for LLVM (Clang, Flang) toolchain", + "inherits": [ + "ci-base" + ], + "binaryDir": "cmake-build-release-llvm", + "cacheVariables": { + "CMAKE_Fortran_COMPILER": { + "type": "FILEPATH", + "value": "fortran-new" + } + } + } + ], + "buildPresets": [ + { + "name": "ci-base", + "hidden": true, + "inherits": [ + "default" + ], + "cleanFirst": true + }, + { + "name": "gcc-ci", + "displayName": "Build preset for GCC toolchain", + "inherits": [ + "ci-base" + ], + "configurePreset": "gcc-ci" + }, + { + "name": "intel-ci", + "displayName": "Build preset for Intel toolchain", + "inherits": [ + "ci-base" + ], + "configurePreset": "intel-ci" + }, + { + "name": "llvm-ci", + "displayName": "Build preset for LLVM (Clang, Flang) toolchain", + "inherits": [ + "ci-base" + ], + "configurePreset": "llvm-ci" + } + ], + "testPresets": [ + { + "name": "ci-base", + "hidden": true, + "inherits": [ + "default" + ], + "output": { + "outputOnFailure": true + } + }, + { + "name": "gcc-ci", + "displayName": "Test preset for GCC toolchain", + "inherits": [ + "ci-base" + ], + "configurePreset": "gcc-ci" + }, + { + "name": "intel-ci", + "displayName": "Test preset for Intel toolchain", + "inherits": [ + "ci-base" + ], + "configurePreset": "intel-ci" + }, + { + "name": "llvm-ci", + "displayName": "Test preset for LLVM (Clang, Flang) toolchain", + "inherits": [ + "ci-base" + ], + "configurePreset": "llvm-ci" + } + ], + "workflowPresets": [ + { + "name": "gcc-ci", + "displayName": "CI test for GCC toolchain", + "steps": [ + { + "type": "configure", + "name": "gcc-ci" + }, + { + "type": "build", + "name": "gcc-ci" + }, + { + "type": "test", + "name": "gcc-ci" + } + ] + }, + { + "name": "intel-ci", + "displayName": "CI test for Intel toolchain", + "steps": [ + { + "type": "configure", + "name": "intel-ci" + }, + { + "type": "build", + "name": "intel-ci" + }, + { + "type": "test", + "name": "intel-ci" + } + ] + }, + { + "name": "llvm-ci", + "displayName": "CI test for LLVM (Clang, Flang) toolchain", + "steps": [ + { + "type": "configure", + "name": "llvm-ci" + }, + { + "type": "build", + "name": "llvm-ci" + }, + { + "type": "test", + "name": "llvm-ci" + } + ] + } + ] +} diff --git a/cmake/CMakePresets-defaults.json b/cmake/CMakePresets-defaults.json new file mode 100644 index 0000000..bfc642c --- /dev/null +++ b/cmake/CMakePresets-defaults.json @@ -0,0 +1,50 @@ +{ + "version": 6, + "configurePresets": [ + { + "name": "default", + "displayName": "Default configuration preset", + "binaryDir": "cmake-build-release", + "cacheVariables": { + "CMAKE_BUILD_TYPE": { + "type": "STRING", + "value": "Release" + } + } + } + ], + "buildPresets": [ + { + "name": "default", + "displayName": "Default build preset", + "configurePreset": "default" + } + ], + "testPresets": [ + { + "name": "default", + "displayName": "Default test preset", + "configurePreset": "default" + } + ], + "workflowPresets": [ + { + "name": "default", + "displayName": "Default workflow", + "steps": [ + { + "type": "configure", + "name": "default" + }, + { + "type": "build", + "name": "default" + }, + { + "type": "test", + "name": "default" + } + ] + } + ] +} From 03eba55ff3d03c10f72c782eb36303cb401b6777 Mon Sep 17 00:00:00 2001 From: Cristian Le Date: Wed, 5 Jul 2023 21:37:41 +0200 Subject: [PATCH 11/11] Add a simple ctest Signed-off-by: Cristian Le --- test/CMakeLists.txt | 4 ++ test/cmake/CMakeLists.txt | 73 +++++++++++++++++++++++++++ test/cmake/module/CMakeLists.txt | 13 +++++ test/cmake/module/fypp/CMakeLists.txt | 15 ++++++ test/cmake/module/fypp/src | 1 + test/cmake/src/test_bare.f90 | 3 ++ test/cmake/src/test_exec.fypp | 3 ++ test/cmake/src/test_obj.fypp | 3 ++ test/cmake/src/test_shared.fypp | 3 ++ test/cmake/src/test_static.fypp | 3 ++ 10 files changed, 121 insertions(+) create mode 100644 test/cmake/CMakeLists.txt create mode 100644 test/cmake/module/CMakeLists.txt create mode 100644 test/cmake/module/fypp/CMakeLists.txt create mode 120000 test/cmake/module/fypp/src create mode 100644 test/cmake/src/test_bare.f90 create mode 100644 test/cmake/src/test_exec.fypp create mode 100644 test/cmake/src/test_obj.fypp create mode 100644 test/cmake/src/test_shared.fypp create mode 100644 test/cmake/src/test_static.fypp diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 0c099b7..2a2a702 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -1,2 +1,6 @@ +# Basic tests add_test(NAME version COMMAND ${CMAKE_Fypp_COMPILER} --version) + +# Integration tests +add_subdirectory(cmake) diff --git a/test/cmake/CMakeLists.txt b/test/cmake/CMakeLists.txt new file mode 100644 index 0000000..0006170 --- /dev/null +++ b/test/cmake/CMakeLists.txt @@ -0,0 +1,73 @@ +function(Fypp_add_test test_dir) + #[===[.md: + # Fypp_add_test + + Internal function for adding Fypp cmake tests + + ## Synopsis + ```cmake + Main interface + Fypp_add_test( + [TEST_NAME ] + [CTEST_OPTIONS ...] + [BUILD_OPTIONS ...]) + ``` + + ## Options + `` + Path to the test CMake project + + `TEST_NAME` [Default ``] + Name of the test + + `CTEST_OPTIONS` + Additional test commands passed to the nested ctest call + + `BUILD_OPTIONS` + CMake configure options passed to the testing cmake + + ## See also + - + + ]===] + + + set(ARGS_Options + ) + set(ARGS_OneValue + TEST_NAME + ) + set(ARGS_MultiValue + BUILD_OPTIONS + CTEST_OPTIONS + ) + + cmake_parse_arguments(PARSE_ARGV 1 ARGS "${ARGS_Options}" "${ARGS_OneValue}" "${ARGS_MultiValue}") + + # Resolve default options + if (NOT DEFINED ARGS_TEST_NAME) + set(ARGS_TEST_NAME ${test_dir}) + endif () + + add_test(NAME ${ARGS_TEST_NAME} COMMAND ${CMAKE_CTEST_COMMAND} + --build-and-test + ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/${test_dir} + ${CMAKE_CURRENT_BINARY_DIR}/${test_dir} + --build-generator "${CMAKE_GENERATOR}" + --build-options + ${ARGS_BUILD_OPTIONS} + --test-command ${CMAKE_CTEST_COMMAND} --test-dir=${CMAKE_CURRENT_BINARY_DIR}/${test_dir} --no-tests=ignore ${ARGS_CTEST_OPTIONS} + ) +endfunction() + +set(build_options) +set(ctest_options) +set(test_dir_rel) +foreach (test_dir IN ITEMS + module +) + block() + cmake_path(APPEND test_dir_rel ${test_dir}) + add_subdirectory(${test_dir}) + endblock() +endforeach () diff --git a/test/cmake/module/CMakeLists.txt b/test/cmake/module/CMakeLists.txt new file mode 100644 index 0000000..eada8c2 --- /dev/null +++ b/test/cmake/module/CMakeLists.txt @@ -0,0 +1,13 @@ +list(APPEND build_options + "-DCMAKE_MODULE_PATH=${PROJECT_SOURCE_DIR}/cmake" +) +foreach (test IN ITEMS fypp) + set(args) + if (build_options) + list(APPEND args BUILD_OPTIONS ${build_options}) + endif () + if (test_command) + list(APPEND args TEST_COMMAND ${test_command}) + endif () + Fypp_add_test(${test_dir_rel}/${test} ${args}) +endforeach () diff --git a/test/cmake/module/fypp/CMakeLists.txt b/test/cmake/module/fypp/CMakeLists.txt new file mode 100644 index 0000000..1bc609b --- /dev/null +++ b/test/cmake/module/fypp/CMakeLists.txt @@ -0,0 +1,15 @@ +cmake_minimum_required(VERSION 3.15) + +project(test_fypp LANGUAGES Fortran) +include(Fypp) + +Fypp_add_executable(exec src/test_exec.fypp) +#Fypp_add_library(test_obj OBJECT src/test_obj.fypp src/test_bare.f90) +Fypp_add_library(test_static STATIC src/test_static.fypp) +Fypp_add_library(test_shared SHARED src/test_shared.fypp) +add_library(test_obj_other OBJECT) +target_compile_definitions(test_obj_other PRIVATE FOO BAR=some_value) +Fypp_target_sources(test_obj_other PRIVATE src/test_obj.fypp src/test_bare.f90) + +enable_testing() +add_test(NAME test_exec COMMAND $) diff --git a/test/cmake/module/fypp/src b/test/cmake/module/fypp/src new file mode 120000 index 0000000..929cb3d --- /dev/null +++ b/test/cmake/module/fypp/src @@ -0,0 +1 @@ +../../src \ No newline at end of file diff --git a/test/cmake/src/test_bare.f90 b/test/cmake/src/test_bare.f90 new file mode 100644 index 0000000..ee494d3 --- /dev/null +++ b/test/cmake/src/test_bare.f90 @@ -0,0 +1,3 @@ +module test_bare_m + integer :: a +end module test_bare_m diff --git a/test/cmake/src/test_exec.fypp b/test/cmake/src/test_exec.fypp new file mode 100644 index 0000000..64b945e --- /dev/null +++ b/test/cmake/src/test_exec.fypp @@ -0,0 +1,3 @@ +program test + print *, "hello" +end program test diff --git a/test/cmake/src/test_obj.fypp b/test/cmake/src/test_obj.fypp new file mode 100644 index 0000000..e68fd75 --- /dev/null +++ b/test/cmake/src/test_obj.fypp @@ -0,0 +1,3 @@ +module test_obj_m + integer :: a +end module test_obj_m diff --git a/test/cmake/src/test_shared.fypp b/test/cmake/src/test_shared.fypp new file mode 100644 index 0000000..f4f1639 --- /dev/null +++ b/test/cmake/src/test_shared.fypp @@ -0,0 +1,3 @@ +module test_shared_m + integer :: a +end module test_shared_m diff --git a/test/cmake/src/test_static.fypp b/test/cmake/src/test_static.fypp new file mode 100644 index 0000000..c1a546b --- /dev/null +++ b/test/cmake/src/test_static.fypp @@ -0,0 +1,3 @@ +module test_static_m + integer :: a +end module test_static_m