Skip to content

Commit

Permalink
Add Environment Variable Expansion in settings.ini (#225)
Browse files Browse the repository at this point in the history
* Implement environment variables in settings.ini. Fixes #3019

* Adding UTs

* Formatting
  • Loading branch information
LeStarch authored Nov 21, 2024
1 parent eb07f23 commit 5835c80
Show file tree
Hide file tree
Showing 3 changed files with 65 additions and 4 deletions.
39 changes: 37 additions & 2 deletions src/fprime/fbuild/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,37 @@
from typing import Any, Callable, Dict, List, Union


class EnvironmentVariableInterpolation(configparser.BasicInterpolation):
"""Interpolation for environment variables embedded in setting.ini
settings.ini will extrapolate $/${} as an environment variable. This will allow basic environment variable
replacement. It is illegal to supply an environment variable with a $ or %( as part of the value because that might
trigger unanticipated recursive parsing.
Note: environment variable substitution is only performed in the environment section and nowhere else.
Based off: https://stackoverflow.com/questions/26586801/configparser-and-string-interpolation-with-env-variable
"""

def before_get(self, parser, section, option, value, defaults):
"""Pre-process sections to replace environment variables
Runs before the value is gotten to replace environment variables. It will use os.path.expandvars to do the
actual substitution on any value in the "environment" section. Other pre-processing is done first.
Args:
parser: unused
section: environment substitution will be done only when set to "environment"
option: unused
value: will be searched for and replace environment variables
defaults: unused
Returns:
the value post substitution
"""
value = super().before_get(parser, section, option, value, defaults)
return os.path.expandvars(value) if section == "environment" else value


class SettingType(Enum):
"""Designates the type of the setting"""

Expand Down Expand Up @@ -171,7 +202,9 @@ def load(settings_file: Path, platform: str = "native", is_ut: bool = False):
# Setup a config parser, or none if the settings file does not exist
confparse = None
if settings_file.exists():
confparse = configparser.ConfigParser()
confparse = configparser.ConfigParser(
interpolation=EnvironmentVariableInterpolation()
)
confparse.read(settings_file)
else:
print(f"[WARNING] {settings_file} does not exist", file=sys.stderr)
Expand Down Expand Up @@ -235,7 +268,9 @@ def load_environment(env_file):
:param env_file: load environment from this file
:return: environment dictionary
"""
parser = configparser.ConfigParser()
parser = configparser.ConfigParser(
interpolation=EnvironmentVariableInterpolation()
)
parser.optionxform = str
parser.read(env_file)
env_dict = {}
Expand Down
6 changes: 6 additions & 0 deletions test/fprime/fbuild/settings-data/settings-environment.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[fprime]
framework_path: ../..

[environment]
MY_VARIABLE: my value
MY_VARIABLE_2: ${TEST_SETTING_1}:$TEST_SETTING_2
24 changes: 22 additions & 2 deletions test/fprime/fbuild/test_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
Tests the F prime settings module.
@author joshuaa
"""

import os
from pathlib import Path

from fprime.fbuild.settings import IniSettings
Expand Down Expand Up @@ -126,8 +126,28 @@ def test_settings():
"default_cmake_options": "OPTION1=ABC\nOPTION2=123\nOPTION3=Something",
},
},
{
"file": "settings-environment.ini",
"expected": {
"settings_file": full_path("settings-data/settings-environment.ini"),
"default_toolchain": "native",
"default_ut_toolchain": "native",
"framework_path": full_path(".."),
"install_destination": full_path("settings-data/build-artifacts"),
"library_locations": [],
"environment_file": full_path("settings-data/settings-environment.ini"),
"environment": {"MY_VARIABLE": "my value", "MY_VARIABLE_2": "abc:123"},
"component_cookiecutter": "default",
"deployment_cookiecutter": "default",
"project_root": full_path(".."),
"config_directory": full_path("..") / "config",
"default_cmake_options": "",
},
},
]

# Prep for substitution
os.environ["TEST_SETTING_1"] = "abc"
os.environ["TEST_SETTING_2"] = "123"
for case in test_cases:
fp = full_path("settings-data/" + case["file"])
results = IniSettings.load(fp)
Expand Down

0 comments on commit 5835c80

Please sign in to comment.