Skip to content

python preprocessor #791

Closed
Closed
@jalvesz

Description

@jalvesz

Motivation

This proposal is meant to start a discussion on a replacement for the current fpm ci for stdlib. The idea would be to use a python script to preprocess stdlib before building with fpm or CMake. While CMake already has a customized fypp preprocessor, such script could serve as a replacement using a PRE_BUILD action with add_custom_command. Also, currently the fpm branch lacks the means for a flexible way of adding dependencies in the toml file. This proposal would try to remedy these shortcomings.

Prior Art

No response

Additional Information

#790 #787

Proposal

Say a fypp_deployement.py file is at the root of stdlib:

import os
import fypp
import argparse
from joblib import Parallel, delayed

def pre_process_toml(kargs):
    """
    Pre-process the fpm.toml
    """
    from tomlkit import table, dumps
    data = table()
    data.add("name", "stdlib")
    data.add("version", str(kargs.vmajor)+
                    "."+str(kargs.vminor)+
                    "."+str(kargs.vpatch) )
    data.add("license", "MIT")
    data.add("author", "stdlib contributors")
    data.add("maintainer", "@fortran-lang/stdlib")
    data.add("copyright", "2019-2021 stdlib contributors")

    if(kargs.with_blp):
        build = table()
        build.add("link", ["lapack", "blas"] )
        data.add("build", build)

    dev_dependencies = table()
    dev_dependencies.add("test-drive", {"git" : "https://github.com/fortran-lang/test-drive", 
                                        "tag" : "v0.4.0"})
    data.add("dev-dependencies", dev_dependencies)

    preprocess = table()
    preprocess.add("cpp", {} )
    preprocess['cpp'].add("suffixes", [".F90", ".f90", ".fypp"] )
    preprocess['cpp'].add("macros", ["MAXRANK="+str(kargs.maxrank), 
                                 "PROJECT_VERSION_MAJOR="+str(kargs.vmajor),
                                 "PROJECT_VERSION_MINOR="+str(kargs.vminor),
                                 "PROJECT_VERSION_PATCH="+str(kargs.vpatch)] )
    data.add("preprocess", preprocess)

    with open("fpm.toml", "w") as f:
        f.write(dumps(data))

C_PREPROCESSED = (
    "stdlib_linalg_constants" ,
    "stdlib_linalg_blas" ,
    "stdlib_linalg_blas_aux",
    "stdlib_linalg_blas_s",
    "stdlib_linalg_blas_d",
    "stdlib_linalg_blas_q",
    "stdlib_linalg_blas_c",
    "stdlib_linalg_blas_z",
    "stdlib_linalg_blas_w",
    "stdlib_linalg_lapack",
    "stdlib_linalg_lapack_aux",
    "stdlib_linalg_lapack_s",
    "stdlib_linalg_lapack_d",
    "stdlib_linalg_lapack_q",
    "stdlib_linalg_lapack_c",
    "stdlib_linalg_lapack_z",
    "stdlib_linalg_lapack_w"
)

def pre_process_fypp(kargs):
    kwd = []
    kwd.append("-DMAXRANK="+str(kargs.maxrank))
    kwd.append("-DPROJECT_VERSION_MAJOR="+str(kargs.vmajor))
    kwd.append("-DPROJECT_VERSION_MINOR="+str(kargs.vminor))
    kwd.append("-DPROJECT_VERSION_PATCH="+str(kargs.vpatch))
    if kargs.with_qp:
        kwd.append("-DWITH_QP=True")
    if kargs.with_xqp:
        kwd.append("-DWITH_XQP=True")
    
    optparser = fypp.get_option_parser()
    options, leftover = optparser.parse_args(args=kwd)
    options.includes = ['include']
    # options.line_numbering = True
    tool = fypp.Fypp(options)

    # Define the folders to search for *.fypp files
    folders = ['src', 'test', 'example']
    # Process all folders
    fypp_files = [os.path.join(root, file) for folder in folders
              for root, _, files in os.walk(folder)
              for file in files if file.endswith(".fypp")]
    
    def process_f(file):
        source_file = file
        basename = os.path.splitext(source_file)[0]
        sfx = 'f90' if os.path.basename(basename) not in C_PREPROCESSED else 'F90'
        target_file = basename + '.' + sfx
        tool.process_file(source_file, target_file)
    
    Parallel(n_jobs=kargs.njob)(delayed(process_f)(f) for f in fypp_files)
    
    return

if __name__ == "__main__":
    parser = argparse.ArgumentParser(description='Preprocess stdlib source files.')
    # fypp arguments
    parser.add_argument("--vmajor", type=int, default=0, help="Project Version Major")
    parser.add_argument("--vminor", type=int, default=4, help="Project Version Minor")
    parser.add_argument("--vpatch", type=int, default=0, help="Project Version Patch")

    parser.add_argument("--njob", type=int, default=4, help="Number of parallel jobs for preprocessing")
    parser.add_argument("--maxrank",type=int, default=7, help="Set the maximum allowed rank for arrays")
    parser.add_argument("--with_qp",type=bool, default=False, help="Include WITH_QP in the command")
    parser.add_argument("--with_xqp",type=bool, default=False, help="Include WITH_XQP in the command")
    # external libraries arguments
    parser.add_argument("--with_blp",type=bool, default=False, help="Link against OpenBLAS")

    
    args = parser.parse_args()

    pre_process_toml(args)
    pre_process_fypp(args)

Example:

python fypp_deployment.py --with_blp 1

Would produce a fpm.toml:

name = "stdlib"
version = "0.4.0"
license = "MIT"
author = "stdlib contributors"
maintainer = "@fortran-lang/stdlib"
copyright = "2019-2021 stdlib contributors"

[build]
link = ["lapack", "blas"]

[dev-dependencies]
[dev-dependencies.test-drive]
git = "https://github.com/fortran-lang/test-drive"
tag = "v0.4.0"

[preprocess]
[preprocess.cpp]
suffixes = [".F90", ".f90", ".fypp"]
macros = ["MAXRANK=7", "PROJECT_VERSION_MAJOR=0", "PROJECT_VERSION_MINOR=4", "PROJECT_VERSION_PATCH=0"]

Current limitations

  • In this first example the fypp files are being preprocessed into .f90 files at the same location. Should they go somewhere else?

Metadata

Metadata

Assignees

No one assigned

    Labels

    ideaProposition of an idea and opening an issue to discuss it

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions