diff --git a/.github/workflows/fpm-deployment.yml b/.github/workflows/fpm-deployment.yml index de19693f2..2e4a3203c 100644 --- a/.github/workflows/fpm-deployment.yml +++ b/.github/workflows/fpm-deployment.yml @@ -1,51 +1,48 @@ name: fpm-deployment on: [push, pull_request] -env: - GCC_V: "10" jobs: - Build: - runs-on: ubuntu-latest + test: + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + include: + - os: ubuntu-latest + toolchain: {compiler: gcc, version: 13} steps: - - name: Checkout 🛎️ - uses: actions/checkout@v2.3.1 - - - name: Set up Python 3.x - uses: actions/setup-python@v1 - with: - python-version: 3.x - - - name: Install fypp - run: pip install --upgrade fypp - - - name: Generate stdlib-fpm package 🔧 - run: | - bash ./ci/fpm-deployment.sh - - - name: Install GFortran - run: | - sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-${{ env.GCC_V }} 100 \ - --slave /usr/bin/gfortran gfortran /usr/bin/gfortran-${{ env.GCC_V }} \ - --slave /usr/bin/gcov gcov /usr/bin/gcov-${{ env.GCC_V }} - - - name: Install fpm latest release - uses: fortran-lang/setup-fpm@v5 - with: - fpm-version: 'v0.10.0' - - - name: Run fpm test ⚙ - run: | - cp -r stdlib-fpm stdlib-fpm-test - cd stdlib-fpm-test - fpm test - fpm test --profile release + - name: Checkout code + uses: actions/checkout@v2.3.1 + + - name: Set up Python 3.x + uses: actions/setup-python@v1 + with: + python-version: 3.x + + - name: Install requirements + run: pip install --upgrade -r config/requirements.txt + + - uses: fortran-lang/setup-fortran@main + id: setup-fortran + with: + compiler: ${{ matrix.toolchain.compiler }} + version: ${{ matrix.toolchain.version }} + + - name: Setup Fortran Package Manager + uses: fortran-lang/setup-fpm@v5 + with: + fpm-version: 'v0.10.0' + + - run: | + python config/fypp_deployment.py --deploy_stdlib_fpm + fpm test --profile release # Update and deploy the f90 files generated by github-ci to the `stdlib-fpm` branch. - - name: Deploy 🚀 - uses: JamesIves/github-pages-deploy-action@4.1.5 - if: github.event_name != 'pull_request' - with: - BRANCH: stdlib-fpm - FOLDER: stdlib-fpm + - name: Deploy 🚀 + uses: JamesIves/github-pages-deploy-action@4.1.5 + if: github.event_name != 'pull_request' + with: + BRANCH: stdlib-fpm + FOLDER: stdlib-fpm \ No newline at end of file diff --git a/.gitignore b/.gitignore index 144ecbe56..246122db7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ build/ - +**/temp/ # FORD generated documentation # !WARNING! This folder gets deleted and overwritten API-doc/ diff --git a/README.md b/README.md index 6180bea0e..b329d2713 100644 --- a/README.md +++ b/README.md @@ -179,19 +179,47 @@ earlier builds do not affect the new build. Fortran Package Manager (fpm) is a package manager and build system for Fortran. You can build `stdlib` using provided `fpm.toml`: +**Option 1**: From root folder + +As `fpm` does not currently support `fypp` natively, `stdlib` now proposes a python script to preprocess and build it. +This script enables modification of the different `fypp` macros available in `stdlib`. The preprocessed files will be dumped at `/temp/*.f90` or `*.F90`. + +Make sure to install the dependencies from the `requirement.txt` ```sh -git checkout stdlib-fpm -fpm build --profile release +pip install --upgrade -r config/requirements.txt ``` -**Alternative**: as `fpm` does not currently support `fypp` natively, building `stdlib` with `fpm` can be done in two steps: a) launch the preprocessor through the `fpm-deployment.sh` script, which creates a subfolder `stdlib-fpm` and b) build the project using the processed files within the latter subfolder. This process can be done with the following commands: +To build, you can use the following command line: ```sh -source ./ci/fpm-deployment.sh -cd stdlib-fpm/ +python config/fypp_deployment.py fpm build --profile release ``` +or the short-cut + +```sh +python config/fypp_deployment.py --build +``` + +To modify the `maxrank` macro for instance: +```sh +python config/fypp_deployment.py --maxrank 7 --build +``` + +To see all the options: +```sh +python config/fypp_deployment.py --help +``` + +**Note**: If you use a compiler different than GNU compilers, the script will try to catch it from the environment variables `FPM_FC`, `FPM_CC`, `FPM_CXX`. + +**Option 2**: From the `stdlib-fpm` branch which has already been preprocessed with default macros: +```sh +git checkout stdlib-fpm +fpm build --profile release +``` +#### Runing the examples You can run the examples with `fpm` as: ```sh diff --git a/ci/.gitignore b/ci/.gitignore deleted file mode 100644 index 160063cfb..000000000 --- a/ci/.gitignore +++ /dev/null @@ -1,7 +0,0 @@ -# Files generated by tests -scratch.txt -*.bin -*log*.txt -*test*.txt -*.dat -*.stream diff --git a/ci/fpm-deployment.sh b/ci/fpm-deployment.sh deleted file mode 100644 index 608a2b9be..000000000 --- a/ci/fpm-deployment.sh +++ /dev/null @@ -1,99 +0,0 @@ -#!/usr/bin/env bash - -set -ex - -# Target directory to deploy stdlib to -destdir="${DESTDIR:-stdlib-fpm}" - -# Get fypp preprocessor -fypp="${FYPP:-$(which fypp)}" - -# Arguments for the fypp preprocessor -maxrank=4 -fyflags="${FYFLAGS:--DMAXRANK=$maxrank}" - -# Number of parallel jobs for preprocessing -if [ $(uname) = "Darwin" ]; then - njob="$(sysctl -n hw.ncpu)" -else - njob="$(nproc)" -fi - -# Additional files to include -include=( - "ci/fpm.toml" - "ci/.gitignore" - "LICENSE" - "VERSION" -) - -# Files to remove from collection -prune=( - "$destdir/test/test_always_fail.f90" - "$destdir/test/test_always_skip.f90" - "$destdir/test/test_hash_functions.f90" - "$destdir/src/f18estop.f90" -) - -# Files that need preprocessed Fortran extension -> .F90 -preprocessed=( - "$destdir/src/stdlib_linalg_constants" - "$destdir/src/stdlib_linalg_blas" - "$destdir/src/stdlib_linalg_blas_aux" - "$destdir/src/stdlib_linalg_blas_s" - "$destdir/src/stdlib_linalg_blas_d" - "$destdir/src/stdlib_linalg_blas_q" - "$destdir/src/stdlib_linalg_blas_c" - "$destdir/src/stdlib_linalg_blas_z" - "$destdir/src/stdlib_linalg_blas_w" - "$destdir/src/stdlib_linalg_lapack" - "$destdir/src/stdlib_linalg_lapack_aux" - "$destdir/src/stdlib_linalg_lapack_s" - "$destdir/src/stdlib_linalg_lapack_d" - "$destdir/src/stdlib_linalg_lapack_q" - "$destdir/src/stdlib_linalg_lapack_c" - "$destdir/src/stdlib_linalg_lapack_z" - "$destdir/src/stdlib_linalg_lapack_w" -) - -major=$(cut -d. -f1 VERSION) -minor=$(cut -d. -f2 VERSION) -patch=$(cut -d. -f3 VERSION) -fyflags="${fyflags} -DPROJECT_VERSION_MAJOR=${major} -DPROJECT_VERSION_MINOR=${minor} -DPROJECT_VERSION_PATCH=${patch} -I include" - -mkdir -p "$destdir/src" "$destdir/test" "$destdir/example" - -# Preprocess stdlib sources -find src -maxdepth 1 -iname "*.fypp" \ - | cut -f1 -d. | xargs -P "$njob" -I{} "$fypp" "{}.fypp" "$destdir/{}.f90" $fyflags - -find test -name "test_*.fypp" -exec cp {} "$destdir/test/" \; -fyflags="${fyflags} -I src" -find $destdir/test -maxdepth 1 -iname "*.fypp" \ - | cut -f1 -d. | xargs -P "$njob" -I{} "$fypp" "{}.fypp" "{}.f90" $fyflags -find $destdir/test -name "test_*.fypp" -exec rm {} \; - -# Collect stdlib source files -find src -maxdepth 1 -iname "*.f90" -exec cp {} "$destdir/src/" \; -find test -name "test_*.f90" -exec cp {} "$destdir/test/" \; -find test -name "*.dat" -exec cp {} "$destdir/" \; -find example -name "example_*.f90" -exec cp {} "$destdir/example/" \; -find example -name "*.dat" -exec cp {} "$destdir/" \; -find example -name "*.npy" -exec cp {} "$destdir/" \; - -# Include additional files -cp "${include[@]}" "$destdir/" - -# Source file workarounds for fpm; ignore missing files -rm "${prune[@]}" - -# Capitalize .f90 -> .F90 for preprocessed files -for pp_source in "${preprocessed[@]}" -do - # workaround for case-insensitive fs - mv "$pp_source.f90" "$pp_source.rename" - mv "$pp_source.rename" "$pp_source.F90" -done - -# List stdlib-fpm package contents -ls -R "$destdir" diff --git a/config/fypp_deployment.py b/config/fypp_deployment.py new file mode 100644 index 000000000..9f3549e4f --- /dev/null +++ b/config/fypp_deployment.py @@ -0,0 +1,165 @@ +import os +import fypp +import argparse +from joblib import Parallel, delayed + +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(args): + """use fypp to preprocess all source files. + + Processed files will be dumped at /temp/ or + + Parameters + ---------- + args : + CLI arguments. + """ + kwd = [] + kwd.append("-DMAXRANK="+str(args.maxrank)) + kwd.append("-DPROJECT_VERSION_MAJOR="+str(args.vmajor)) + kwd.append("-DPROJECT_VERSION_MINOR="+str(args.vminor)) + kwd.append("-DPROJECT_VERSION_PATCH="+str(args.vpatch)) + if args.with_qp: + kwd.append("-DWITH_QP=True") + if args.with_xdp: + kwd.append("-DWITH_XDP=True") + + optparser = fypp.get_option_parser() + options, leftover = optparser.parse_args(args=kwd) + options.includes = ['include'] + if args.lnumbering: + options.line_numbering = True + tool = fypp.Fypp(options) + + # Check destination folder for preprocessing. if not 'stdlib-fpm', it is assumed to be the root folder. + if not os.path.exists('src'+os.sep+'temp'): + os.makedirs('src'+os.sep+'temp') + if not os.path.exists('test'+os.sep+'temp'): + os.makedirs('test'+os.sep+'temp') + + # Define the folders to search for *.fypp files + folders = ['src','test'] + # 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 + root = os.path.dirname(file) + if not os.path.exists(root+os.sep+'temp'): + os.makedirs(root+os.sep+'temp') + basename = os.path.splitext(os.path.basename(source_file))[0] + sfx = 'f90' if basename not in C_PREPROCESSED else 'F90' + target_file = root+os.sep+'temp' + os.sep + basename + '.' + sfx + tool.process_file(source_file, target_file) + + Parallel(n_jobs=args.njob)(delayed(process_f)(f) for f in fypp_files) + + return + + +def deploy_stdlib_fpm(): + """create the stdlib-fpm folder for backwards compatibility (to be deprecated) + """ + import shutil + prune=( + "test_hash_functions.f90", + "f18estop.f90", + ) + if not os.path.exists('stdlib-fpm'+os.sep+'src'): + os.makedirs('stdlib-fpm'+os.sep+'src') + if not os.path.exists('stdlib-fpm'+os.sep+'test'): + os.makedirs('stdlib-fpm'+os.sep+'test') + if not os.path.exists('stdlib-fpm'+os.sep+'example'): + os.makedirs('stdlib-fpm'+os.sep+'example') + + def recursive_copy(folder): + for root, _, files in os.walk(folder): + for file in files: + if file not in prune: + if file.endswith(".f90") or file.endswith(".F90") or file.endswith(".dat") or file.endswith(".npy"): + shutil.copy2(os.path.join(root, file), 'stdlib-fpm'+os.sep+folder+os.sep+file) + recursive_copy('src') + recursive_copy('test') + recursive_copy('example') + for file in ['.gitignore','fpm.toml','LICENSE','VERSION']: + shutil.copy2(file, 'stdlib-fpm'+os.sep+file) + return + +def fpm_build(args,unknown): + import subprocess + #========================================== + # check compilers + FPM_FC = os.environ['FPM_FC'] if "FPM_FC" in os.environ else "gfortran" + FPM_CC = os.environ['FPM_CC'] if "FPM_CC" in os.environ else "gcc" + FPM_CXX = os.environ['FPM_CXX'] if "FPM_CXX" in os.environ else "gcc" + #========================================== + # Filter out flags + preprocessor = { 'gfortran':'-cpp ' , 'ifort':'-fpp ' , 'ifx':'-fpp ' } + flags = preprocessor[FPM_FC] + for idx, arg in enumerate(unknown): + if arg.startswith("--flag"): + flags= flags + unknown[idx+1] + #========================================== + # build with fpm + subprocess.run(["fpm build"]+ + [" --compiler "]+[FPM_FC]+ + [" --c-compiler "]+[FPM_CC]+ + [" --cxx-compiler "]+[FPM_CXX]+ + [" --flag "]+[flags], shell=True, check=True) + 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=5, 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",action='store_true', help="Include WITH_QP in the command") + parser.add_argument("--with_xdp",action='store_true', help="Include WITH_XDP in the command") + parser.add_argument("--lnumbering",action='store_true', help="Add line numbering in preprocessed files") + parser.add_argument("--deploy_stdlib_fpm",action='store_true', help="create the stdlib-fpm folder") + # external libraries arguments + parser.add_argument("--build", action='store_true', help="Build the project") + + args, unknown = parser.parse_known_args() + #========================================== + # read current manifest + with open('VERSION', 'r') as file: + version = file.read().split(".") + vmajor, vminor, vpatch = [int(value) for value in version] + args.vmajor = max(vmajor,args.vmajor) + args.vminor = max(vminor,args.vminor) + args.vpatch = max(vpatch,args.vpatch) + #========================================== + # pre process the meta programming fypp files + pre_process_fypp(args) + if args.deploy_stdlib_fpm: + deploy_stdlib_fpm() + #========================================== + # build using fpm + if args.build: + fpm_build(args,unknown) \ No newline at end of file diff --git a/config/requirements.txt b/config/requirements.txt new file mode 100644 index 000000000..74e6082b0 --- /dev/null +++ b/config/requirements.txt @@ -0,0 +1,3 @@ +fypp +argparse +joblib \ No newline at end of file diff --git a/ci/fpm.toml b/fpm.toml similarity index 85% rename from ci/fpm.toml rename to fpm.toml index 6a0509007..bf8dfc530 100644 --- a/ci/fpm.toml +++ b/fpm.toml @@ -11,3 +11,5 @@ test-drive.tag = "v0.4.0" [preprocess] [preprocess.cpp] +suffixes = [".F90", ".f90"] +macros = ["MAXRANK=7"] diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 8e199182d..857aaa142 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -29,9 +29,4 @@ add_subdirectory(system) add_subdirectory(quadrature) add_subdirectory(math) add_subdirectory(stringlist) -add_subdirectory(terminal) - -ADDTEST(always_skip) -set_tests_properties(always_skip PROPERTIES SKIP_RETURN_CODE 77) -ADDTEST(always_fail) -set_tests_properties(always_fail PROPERTIES WILL_FAIL true) +add_subdirectory(terminal) \ No newline at end of file diff --git a/test/test_always_fail.f90 b/test/test_always_fail.f90 deleted file mode 100644 index c52b5788d..000000000 --- a/test/test_always_fail.f90 +++ /dev/null @@ -1,8 +0,0 @@ -program test_always_fail - -use stdlib_error, only: check -implicit none - -call check(.false.) - -end program diff --git a/test/test_always_skip.f90 b/test/test_always_skip.f90 deleted file mode 100644 index 2d10c3daa..000000000 --- a/test/test_always_skip.f90 +++ /dev/null @@ -1,8 +0,0 @@ -program test_always_skip - -use stdlib_error, only: check -implicit none - -call check(.false., code=77) - -end program