Loading docs/getting-started/installation.md +3 −0 Original line number Diff line number Diff line Loading @@ -69,6 +69,9 @@ Useful setup script options: ./setup-env.sh --help ./setup-env.sh --env-dir /path/to/env ./setup-env.sh --force ./setup-env.sh --install dune-functions ./setup-env.sh --install dune-functions --with-suggestions ./setup-env.sh --remove dune-functions ``` ## 4. General source-based setup Loading setup-env.sh +264 −33 Original line number Diff line number Diff line Loading @@ -4,12 +4,53 @@ set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" ENV_DIR="${SCRIPT_DIR}/_env" FORCE=0 MODE="setup" MODULE="" WITH_SUGGESTIONS=0 declare -a INSTALLED_MODULES=() declare -A MODULE_REPOS=( ["dune-common"]="https://gitlab.dune-project.org/core/dune-common.git" ["dune-geometry"]="https://gitlab.dune-project.org/core/dune-geometry.git" ["dune-grid"]="https://gitlab.dune-project.org/core/dune-grid.git" ["dune-istl"]="https://gitlab.dune-project.org/core/dune-istl.git" ["dune-localfunctions"]="https://gitlab.dune-project.org/core/dune-localfunctions.git" ["dune-typetree"]="https://gitlab.dune-project.org/staging/dune-typetree.git" ["dune-functions"]="https://gitlab.dune-project.org/staging/dune-functions.git" ["dune-assembler"]="https://gitlab.dune-project.org/staging/dune-assembler.git" ["dune-uggrid"]="https://gitlab.dune-project.org/staging/dune-uggrid.git" ["dune-alugrid"]="https://gitlab.dune-project.org/extensions/dune-alugrid.git" ["dune-foamgrid"]="https://gitlab.dune-project.org/extensions/dune-foamgrid.git" ["dune-polygongrid"]="https://gitlab.dune-project.org/extensions/dune-polygongrid.git" ["dune-spgrid"]="https://gitlab.dune-project.org/extensions/dune-spgrid.git" ["dune-subgrid"]="https://gitlab.dune-project.org/extensions/dune-subgrid.git" ["dune-grid-glue"]="https://gitlab.dune-project.org/extensions/dune-grid-glue.git" ["dune-iga"]="https://gitlab.dune-project.org/extensions/dune-iga.git" ["dune-p4estgrid"]="https://gitlab.dune-project.org/extensions/dune-p4estgrid.git" ["dune-periodic"]="https://gitlab.dune-project.org/extensions/dune-periodic.git" ["dune-vtk"]="https://gitlab.dune-project.org/extensions/dune-vtk.git" ["dune-curvedgrid"]="https://gitlab.dune-project.org/extensions/dune-curvedgrid.git" ["dune-curvedgeometry"]="https://gitlab.dune-project.org/extensions/dune-curvedgeometry.git" ["dune-gmsh4"]="https://gitlab.dune-project.org/extensions/dune-gmsh4.git" ["dune-mmesh"]="https://gitlab.dune-project.org/extensions/dune-mmesh.git" ["dune-fem"]="https://gitlab.dune-project.org/dune-fem/dune-fem.git" ["dune-fufem"]="https://gitlab.dune-project.org/fufem/dune-fufem.git" ["dune-matrix-vector"]="https://gitlab.dune-project.org/fufem/dune-matrix-vector.git" ) usage() { cat <<'EOF' Usage: ./setup-env.sh [--env-dir PATH] [--force] Usage: ./setup-env.sh [--env-dir PATH] [--force] ./setup-env.sh [-i|--install] [--with-suggestions] [--env-dir PATH] <module> ./setup-env.sh [-r|--remove] [--env-dir PATH] <module> Commands: (default) Set up tutorial base environment (core modules + scripts) -i, --install Install one module and its dependencies -r, --remove Remove one installed module directory Set up a local DUNE tutorial environment: Setup command: 1. Create _env (or custom --env-dir) 2. Clone DUNE core modules (if missing) 3. Create debug/release opts files Loading @@ -18,6 +59,7 @@ Set up a local DUNE tutorial environment: Options: --env-dir PATH Target environment directory (default: ./_env) --force Overwrite generated files (opts + activation scripts) --with-suggestions Also install modules listed in Suggests: -h, --help Show this help References: Loading @@ -26,6 +68,126 @@ References: EOF } normalize_module_name() { local name="$1" if [[ "${name}" != dune-* ]]; then name="dune-${name}" fi if [[ ! "${name}" =~ ^dune-[a-zA-Z0-9-]+$ ]]; then echo "Invalid module name: ${1}" >&2 exit 1 fi printf '%s\n' "${name}" } lookup_repo_url() { local module="$1" if [[ -n "${MODULE_REPOS[${module}]+x}" ]]; then printf '%s\n' "${MODULE_REPOS[${module}]}" return 0 fi echo "No repository configured for ${module}" >&2 echo "Add it to MODULE_REPOS in setup-env.sh." >&2 exit 1 } extract_dune_modules_from_field() { local dune_module_file="$1" local field="$2" awk -v field="${field}" ' BEGIN { in_field = 0 } $0 ~ ("^" field ":[[:space:]]*") { in_field = 1 sub("^" field ":[[:space:]]*", "", $0) print next } in_field && $0 ~ "^[[:space:]]+" { sub("^[[:space:]]+", "", $0) print next } in_field { in_field = 0 } ' "${dune_module_file}" | grep -Eo 'dune-[a-zA-Z0-9-]+' || true } clone_module_if_needed() { local module="$1" local target="${ENV_DIR}/${module}" if [[ -d "${target}/.git" ]]; then echo "Skipping ${module} (already cloned)" return 0 fi if [[ -e "${target}" ]]; then echo "Path exists but is not a git clone: ${target}" >&2 exit 1 fi local repo_url repo_url="$(lookup_repo_url "${module}")" echo "Cloning ${module} ..." git clone "${repo_url}" "${target}" INSTALLED_MODULES+=("${module}") } install_module_recursive() { local requested_module requested_module="$(normalize_module_name "$1")" local -a queue=("${requested_module}") local -A seen=() local idx=0 while [[ ${idx} -lt ${#queue[@]} ]]; do local module="${queue[${idx}]}" idx=$((idx + 1)) if [[ -n "${seen[${module}]+x}" ]]; then continue fi seen["${module}"]=1 clone_module_if_needed "${module}" local dune_module_file="${ENV_DIR}/${module}/dune.module" if [[ ! -f "${dune_module_file}" ]]; then echo "Warning: ${module} has no dune.module, skipping dependency scan." >&2 continue fi mapfile -t deps < <(extract_dune_modules_from_field "${dune_module_file}" "Depends") for dep in "${deps[@]}"; do queue+=("${dep}") done if [[ "${WITH_SUGGESTIONS}" -eq 1 ]]; then mapfile -t suggests < <(extract_dune_modules_from_field "${dune_module_file}" "Suggests") for suggestion in "${suggests[@]}"; do queue+=("${suggestion}") done fi done } remove_module() { local module module="$(normalize_module_name "$1")" local target="${ENV_DIR}/${module}" if [[ ! -e "${target}" ]]; then echo "Skipping ${module} (not present in ${ENV_DIR})" return 0 fi if [[ "${target}" == "${ENV_DIR}" || "${target}" == "/" ]]; then echo "Refusing to remove unsafe target: ${target}" >&2 exit 1 fi rm -rf "${target}" echo "Removed ${module}" } while [[ $# -gt 0 ]]; do case "$1" in --env-dir) Loading @@ -35,39 +197,98 @@ while [[ $# -gt 0 ]]; do --force) FORCE=1 ;; --with-suggestions) WITH_SUGGESTIONS=1 ;; -i|--install) if [[ "${MODE}" != "setup" ]]; then echo "Only one command can be specified." >&2 usage exit 1 fi MODE="install" shift MODULE="${1:-}" if [[ -z "${MODULE}" ]]; then echo "Missing module for ${MODE} command" >&2 usage exit 1 fi ;; -r|--remove) if [[ "${MODE}" != "setup" ]]; then echo "Only one command can be specified." >&2 usage exit 1 fi MODE="remove" shift MODULE="${1:-}" if [[ -z "${MODULE}" ]]; then echo "Missing module for ${MODE} command" >&2 usage exit 1 fi ;; -h|--help) usage exit 0 ;; *) if [[ -n "${MODULE}" ]]; then echo "Unknown argument: $1" >&2 usage exit 1 fi MODULE="$1" ;; esac shift done if [[ "${MODE}" == "setup" && -n "${MODULE}" ]]; then echo "Unexpected positional argument for setup: ${MODULE}" >&2 usage exit 1 fi if [[ "${MODE}" != "install" && "${WITH_SUGGESTIONS}" -eq 1 ]]; then echo "--with-suggestions is only supported with --install" >&2 usage exit 1 fi mkdir -p "${ENV_DIR}" ENV_DIR="$(cd "${ENV_DIR}" && pwd)" core_modules=(common geometry grid istl localfunctions) core_modules=(dune-common dune-geometry dune-grid dune-istl dune-localfunctions) echo "Environment directory: ${ENV_DIR}" if [[ "${MODE}" == "setup" ]]; then for mod in "${core_modules[@]}"; do target="${ENV_DIR}/dune-${mod}" if [[ -d "${target}/.git" ]]; then echo "Skipping dune-${mod} (already cloned)" continue fi if [[ -e "${target}" ]]; then echo "Path exists but is not a git clone: ${target}" >&2 clone_module_if_needed "${mod}" done elif [[ "${MODE}" == "install" ]]; then install_module_recursive "${MODULE}" elif [[ "${MODE}" == "remove" ]]; then remove_module "${MODULE}" echo echo "Remove complete." exit 0 else echo "Unknown mode: ${MODE}" >&2 exit 1 fi echo "Cloning dune-${mod} ..." git clone "https://gitlab.dune-project.org/core/dune-${mod}.git" "${target}" done print_install_summary() { echo if [[ "${#INSTALLED_MODULES[@]}" -gt 0 ]]; then echo "Installed modules (${#INSTALLED_MODULES[@]}): ${INSTALLED_MODULES[*]}" else echo "Installed modules (0): none (all requested modules already present)" fi } write_file() { local path="$1" Loading @@ -89,6 +310,7 @@ DEBUG_OPTS='CMAKE_FLAGS="\ -DCMAKE_BUILD_TYPE=RelWithDebInfo \ -DCMAKE_CXX_STANDARD=20 \ -DCMAKE_EXPORT_COMPILE_COMMANDS:BOOL=1 \ -DDUNE_GRID_GRIDTYPE_SELECTOR:BOOL=1 \ -DCMAKE_CXX_FLAGS=\"-Wall -DDUNE_CHECK_BOUNDS=1 -DDUNE_ISTL_WITH_CHECKING=1 -DDUNE_FMatrix_WITH_CHECKING=1 -DCHECK_RESERVEDVECTOR\"" BUILDDIR="build/debug" ' Loading @@ -97,6 +319,7 @@ RELEASE_OPTS='CMAKE_FLAGS="\ -G Ninja \ -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_CXX_STANDARD=20 \ -DDUNE_GRID_GRIDTYPE_SELECTOR:BOOL=1 \ -DCMAKE_EXPORT_COMPILE_COMMANDS:BOOL=1" BUILDDIR="build/release" ' Loading Loading @@ -170,15 +393,23 @@ echo "Deactivated DUNE tutorial environment" echo " dunecontrol=$(command -v dunecontrol 2>/dev/null || echo '\''not in PATH'\'')" ' if [[ "${MODE}" == "setup" ]]; then write_file "${ENV_DIR}/debug.opts" 0644 "${DEBUG_OPTS}" write_file "${ENV_DIR}/release.opts" 0644 "${RELEASE_OPTS}" write_file "${ENV_DIR}/activate.sh" 0755 "${ACTIVATE_SH}" write_file "${ENV_DIR}/deactivate.sh" 0755 "${DEACTIVATE_SH}" echo print_install_summary echo "Setup complete." echo "Next steps:" echo " source ${ENV_DIR}/activate.sh" echo " dunecontrol all" echo " # optional release mode:" echo " export DUNE_OPTS_FILE=\"${ENV_DIR}/release.opts\"" elif [[ "${MODE}" == "install" ]]; then print_install_summary echo "Install complete." echo "After installing new modules:" echo " source ${ENV_DIR}/activate.sh" echo " dunecontrol all" fi Loading
docs/getting-started/installation.md +3 −0 Original line number Diff line number Diff line Loading @@ -69,6 +69,9 @@ Useful setup script options: ./setup-env.sh --help ./setup-env.sh --env-dir /path/to/env ./setup-env.sh --force ./setup-env.sh --install dune-functions ./setup-env.sh --install dune-functions --with-suggestions ./setup-env.sh --remove dune-functions ``` ## 4. General source-based setup Loading
setup-env.sh +264 −33 Original line number Diff line number Diff line Loading @@ -4,12 +4,53 @@ set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" ENV_DIR="${SCRIPT_DIR}/_env" FORCE=0 MODE="setup" MODULE="" WITH_SUGGESTIONS=0 declare -a INSTALLED_MODULES=() declare -A MODULE_REPOS=( ["dune-common"]="https://gitlab.dune-project.org/core/dune-common.git" ["dune-geometry"]="https://gitlab.dune-project.org/core/dune-geometry.git" ["dune-grid"]="https://gitlab.dune-project.org/core/dune-grid.git" ["dune-istl"]="https://gitlab.dune-project.org/core/dune-istl.git" ["dune-localfunctions"]="https://gitlab.dune-project.org/core/dune-localfunctions.git" ["dune-typetree"]="https://gitlab.dune-project.org/staging/dune-typetree.git" ["dune-functions"]="https://gitlab.dune-project.org/staging/dune-functions.git" ["dune-assembler"]="https://gitlab.dune-project.org/staging/dune-assembler.git" ["dune-uggrid"]="https://gitlab.dune-project.org/staging/dune-uggrid.git" ["dune-alugrid"]="https://gitlab.dune-project.org/extensions/dune-alugrid.git" ["dune-foamgrid"]="https://gitlab.dune-project.org/extensions/dune-foamgrid.git" ["dune-polygongrid"]="https://gitlab.dune-project.org/extensions/dune-polygongrid.git" ["dune-spgrid"]="https://gitlab.dune-project.org/extensions/dune-spgrid.git" ["dune-subgrid"]="https://gitlab.dune-project.org/extensions/dune-subgrid.git" ["dune-grid-glue"]="https://gitlab.dune-project.org/extensions/dune-grid-glue.git" ["dune-iga"]="https://gitlab.dune-project.org/extensions/dune-iga.git" ["dune-p4estgrid"]="https://gitlab.dune-project.org/extensions/dune-p4estgrid.git" ["dune-periodic"]="https://gitlab.dune-project.org/extensions/dune-periodic.git" ["dune-vtk"]="https://gitlab.dune-project.org/extensions/dune-vtk.git" ["dune-curvedgrid"]="https://gitlab.dune-project.org/extensions/dune-curvedgrid.git" ["dune-curvedgeometry"]="https://gitlab.dune-project.org/extensions/dune-curvedgeometry.git" ["dune-gmsh4"]="https://gitlab.dune-project.org/extensions/dune-gmsh4.git" ["dune-mmesh"]="https://gitlab.dune-project.org/extensions/dune-mmesh.git" ["dune-fem"]="https://gitlab.dune-project.org/dune-fem/dune-fem.git" ["dune-fufem"]="https://gitlab.dune-project.org/fufem/dune-fufem.git" ["dune-matrix-vector"]="https://gitlab.dune-project.org/fufem/dune-matrix-vector.git" ) usage() { cat <<'EOF' Usage: ./setup-env.sh [--env-dir PATH] [--force] Usage: ./setup-env.sh [--env-dir PATH] [--force] ./setup-env.sh [-i|--install] [--with-suggestions] [--env-dir PATH] <module> ./setup-env.sh [-r|--remove] [--env-dir PATH] <module> Commands: (default) Set up tutorial base environment (core modules + scripts) -i, --install Install one module and its dependencies -r, --remove Remove one installed module directory Set up a local DUNE tutorial environment: Setup command: 1. Create _env (or custom --env-dir) 2. Clone DUNE core modules (if missing) 3. Create debug/release opts files Loading @@ -18,6 +59,7 @@ Set up a local DUNE tutorial environment: Options: --env-dir PATH Target environment directory (default: ./_env) --force Overwrite generated files (opts + activation scripts) --with-suggestions Also install modules listed in Suggests: -h, --help Show this help References: Loading @@ -26,6 +68,126 @@ References: EOF } normalize_module_name() { local name="$1" if [[ "${name}" != dune-* ]]; then name="dune-${name}" fi if [[ ! "${name}" =~ ^dune-[a-zA-Z0-9-]+$ ]]; then echo "Invalid module name: ${1}" >&2 exit 1 fi printf '%s\n' "${name}" } lookup_repo_url() { local module="$1" if [[ -n "${MODULE_REPOS[${module}]+x}" ]]; then printf '%s\n' "${MODULE_REPOS[${module}]}" return 0 fi echo "No repository configured for ${module}" >&2 echo "Add it to MODULE_REPOS in setup-env.sh." >&2 exit 1 } extract_dune_modules_from_field() { local dune_module_file="$1" local field="$2" awk -v field="${field}" ' BEGIN { in_field = 0 } $0 ~ ("^" field ":[[:space:]]*") { in_field = 1 sub("^" field ":[[:space:]]*", "", $0) print next } in_field && $0 ~ "^[[:space:]]+" { sub("^[[:space:]]+", "", $0) print next } in_field { in_field = 0 } ' "${dune_module_file}" | grep -Eo 'dune-[a-zA-Z0-9-]+' || true } clone_module_if_needed() { local module="$1" local target="${ENV_DIR}/${module}" if [[ -d "${target}/.git" ]]; then echo "Skipping ${module} (already cloned)" return 0 fi if [[ -e "${target}" ]]; then echo "Path exists but is not a git clone: ${target}" >&2 exit 1 fi local repo_url repo_url="$(lookup_repo_url "${module}")" echo "Cloning ${module} ..." git clone "${repo_url}" "${target}" INSTALLED_MODULES+=("${module}") } install_module_recursive() { local requested_module requested_module="$(normalize_module_name "$1")" local -a queue=("${requested_module}") local -A seen=() local idx=0 while [[ ${idx} -lt ${#queue[@]} ]]; do local module="${queue[${idx}]}" idx=$((idx + 1)) if [[ -n "${seen[${module}]+x}" ]]; then continue fi seen["${module}"]=1 clone_module_if_needed "${module}" local dune_module_file="${ENV_DIR}/${module}/dune.module" if [[ ! -f "${dune_module_file}" ]]; then echo "Warning: ${module} has no dune.module, skipping dependency scan." >&2 continue fi mapfile -t deps < <(extract_dune_modules_from_field "${dune_module_file}" "Depends") for dep in "${deps[@]}"; do queue+=("${dep}") done if [[ "${WITH_SUGGESTIONS}" -eq 1 ]]; then mapfile -t suggests < <(extract_dune_modules_from_field "${dune_module_file}" "Suggests") for suggestion in "${suggests[@]}"; do queue+=("${suggestion}") done fi done } remove_module() { local module module="$(normalize_module_name "$1")" local target="${ENV_DIR}/${module}" if [[ ! -e "${target}" ]]; then echo "Skipping ${module} (not present in ${ENV_DIR})" return 0 fi if [[ "${target}" == "${ENV_DIR}" || "${target}" == "/" ]]; then echo "Refusing to remove unsafe target: ${target}" >&2 exit 1 fi rm -rf "${target}" echo "Removed ${module}" } while [[ $# -gt 0 ]]; do case "$1" in --env-dir) Loading @@ -35,39 +197,98 @@ while [[ $# -gt 0 ]]; do --force) FORCE=1 ;; --with-suggestions) WITH_SUGGESTIONS=1 ;; -i|--install) if [[ "${MODE}" != "setup" ]]; then echo "Only one command can be specified." >&2 usage exit 1 fi MODE="install" shift MODULE="${1:-}" if [[ -z "${MODULE}" ]]; then echo "Missing module for ${MODE} command" >&2 usage exit 1 fi ;; -r|--remove) if [[ "${MODE}" != "setup" ]]; then echo "Only one command can be specified." >&2 usage exit 1 fi MODE="remove" shift MODULE="${1:-}" if [[ -z "${MODULE}" ]]; then echo "Missing module for ${MODE} command" >&2 usage exit 1 fi ;; -h|--help) usage exit 0 ;; *) if [[ -n "${MODULE}" ]]; then echo "Unknown argument: $1" >&2 usage exit 1 fi MODULE="$1" ;; esac shift done if [[ "${MODE}" == "setup" && -n "${MODULE}" ]]; then echo "Unexpected positional argument for setup: ${MODULE}" >&2 usage exit 1 fi if [[ "${MODE}" != "install" && "${WITH_SUGGESTIONS}" -eq 1 ]]; then echo "--with-suggestions is only supported with --install" >&2 usage exit 1 fi mkdir -p "${ENV_DIR}" ENV_DIR="$(cd "${ENV_DIR}" && pwd)" core_modules=(common geometry grid istl localfunctions) core_modules=(dune-common dune-geometry dune-grid dune-istl dune-localfunctions) echo "Environment directory: ${ENV_DIR}" if [[ "${MODE}" == "setup" ]]; then for mod in "${core_modules[@]}"; do target="${ENV_DIR}/dune-${mod}" if [[ -d "${target}/.git" ]]; then echo "Skipping dune-${mod} (already cloned)" continue fi if [[ -e "${target}" ]]; then echo "Path exists but is not a git clone: ${target}" >&2 clone_module_if_needed "${mod}" done elif [[ "${MODE}" == "install" ]]; then install_module_recursive "${MODULE}" elif [[ "${MODE}" == "remove" ]]; then remove_module "${MODULE}" echo echo "Remove complete." exit 0 else echo "Unknown mode: ${MODE}" >&2 exit 1 fi echo "Cloning dune-${mod} ..." git clone "https://gitlab.dune-project.org/core/dune-${mod}.git" "${target}" done print_install_summary() { echo if [[ "${#INSTALLED_MODULES[@]}" -gt 0 ]]; then echo "Installed modules (${#INSTALLED_MODULES[@]}): ${INSTALLED_MODULES[*]}" else echo "Installed modules (0): none (all requested modules already present)" fi } write_file() { local path="$1" Loading @@ -89,6 +310,7 @@ DEBUG_OPTS='CMAKE_FLAGS="\ -DCMAKE_BUILD_TYPE=RelWithDebInfo \ -DCMAKE_CXX_STANDARD=20 \ -DCMAKE_EXPORT_COMPILE_COMMANDS:BOOL=1 \ -DDUNE_GRID_GRIDTYPE_SELECTOR:BOOL=1 \ -DCMAKE_CXX_FLAGS=\"-Wall -DDUNE_CHECK_BOUNDS=1 -DDUNE_ISTL_WITH_CHECKING=1 -DDUNE_FMatrix_WITH_CHECKING=1 -DCHECK_RESERVEDVECTOR\"" BUILDDIR="build/debug" ' Loading @@ -97,6 +319,7 @@ RELEASE_OPTS='CMAKE_FLAGS="\ -G Ninja \ -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_CXX_STANDARD=20 \ -DDUNE_GRID_GRIDTYPE_SELECTOR:BOOL=1 \ -DCMAKE_EXPORT_COMPILE_COMMANDS:BOOL=1" BUILDDIR="build/release" ' Loading Loading @@ -170,15 +393,23 @@ echo "Deactivated DUNE tutorial environment" echo " dunecontrol=$(command -v dunecontrol 2>/dev/null || echo '\''not in PATH'\'')" ' if [[ "${MODE}" == "setup" ]]; then write_file "${ENV_DIR}/debug.opts" 0644 "${DEBUG_OPTS}" write_file "${ENV_DIR}/release.opts" 0644 "${RELEASE_OPTS}" write_file "${ENV_DIR}/activate.sh" 0755 "${ACTIVATE_SH}" write_file "${ENV_DIR}/deactivate.sh" 0755 "${DEACTIVATE_SH}" echo print_install_summary echo "Setup complete." echo "Next steps:" echo " source ${ENV_DIR}/activate.sh" echo " dunecontrol all" echo " # optional release mode:" echo " export DUNE_OPTS_FILE=\"${ENV_DIR}/release.opts\"" elif [[ "${MODE}" == "install" ]]; then print_install_summary echo "Install complete." echo "After installing new modules:" echo " source ${ENV_DIR}/activate.sh" echo " dunecontrol all" fi