From 50ecba23b26d41e4c784233f29ed1a5d5f97fa5e Mon Sep 17 00:00:00 2001 From: Oliver Smith-Denny Date: Sun, 30 Jun 2024 08:43:34 -0700 Subject: [PATCH] Add Multiple Config Environment Build Support (#374) ## Description This updates the UpdateConfigHdr.py plugin to be able to generate multiple config headers from multiple XML files to support the use case where one PlatformBuild.py file is producing multiple config environments, such as non-secure world and StMM. This is backwards compatible with platforms that only build a single config environment. For each item, place an "x" in between `[` and `]` if true. Example: `[x]`. _(you can also check items in the GitHub UI)_ - [ ] Impacts functionality? - **Functionality** - Does the change ultimately impact how firmware functions? - Examples: Add a new library, publish a new PPI, update an algorithm, ... - [ ] Impacts security? - **Security** - Does the change have a direct security impact on an application, flow, or firmware? - Examples: Crypto algorithm change, buffer overflow fix, parameter validation improvement, ... - [ ] Breaking change? - **Breaking change** - Will anyone consuming this change experience a break in build or boot behavior? - Examples: Add a new library class, move a module to a different repo, call a function in a new library class in a pre-existing module, ... - [ ] Includes tests? - **Tests** - Does the change include any explicit test code? - Examples: Unit tests, integration tests, robot tests, ... - [x] Includes documentation? - **Documentation** - Does the change contain explicit documentation additions outside direct code modifications (and comments)? - Examples: Update readme file, add feature readme file, link to documentation on an a separate Web page, ... ## How This Was Tested Tested on a platform with multiple config environments and with a single. ## Integration Instructions See docs in this PR. --- .../PlatformIntegrationSteps.md | 19 ++++- SetupDataPkg/Docs/Profiles/Overview.md | 6 ++ .../UpdateConfigHdr/UpdateConfigHdr.py | 79 +++++++++++-------- 3 files changed, 72 insertions(+), 32 deletions(-) diff --git a/SetupDataPkg/Docs/PlatformIntegration/PlatformIntegrationSteps.md b/SetupDataPkg/Docs/PlatformIntegration/PlatformIntegrationSteps.md index 8f3ea7d4..77ce6c3c 100644 --- a/SetupDataPkg/Docs/PlatformIntegration/PlatformIntegrationSteps.md +++ b/SetupDataPkg/Docs/PlatformIntegration/PlatformIntegrationSteps.md @@ -111,12 +111,29 @@ queries variable storage for any appropriately sized overrides to config knobs. The platform must define `CONF_AUTOGEN_INCLUDE_PATH` in PlatformBuild.py. This is the absolute path that the autogenerated header files will be placed under as such: `CONF_AUTOGEN_INCLUDE_PATH\Generated\Config*.h`. The UpdateConfigHdr.py -build plugin will create the `Generated` directory if it does not exist. +build plugin will create the `Generated` directory if it does not exist. If there are multiple environments being built +in one build, e.g. StandaloneMM and DXE environments with separate config, PlatformBuild.py may put a semicolon +delimited list of include paths here such as: + +```python +self.env.SetValue("CONF_AUTOGEN_INCLUDE_PATH", "MyPkg/Include;MyPkg/Include/StandaloneMm", "Platform Defined") +``` + +This list must be the same length as the `MU_SCHEMA_FILE_NAME` and it will be processed in the same order; the first +schema file will get headers generated to the first include directory, etc. The platform must define `MU_SCHEMA_DIR` and `MU_SCHEMA_FILE_NAME` in PlatformBuild.py. These are the directory containing the XML configuration file and the file name of the XML configuration file, respectively. These are split apart to allow the CI build to discover a test schema to validate this process. +`MU_SCHEMA_FILE_NAME` may have multiple schema files that are semicolon delimited if the build has multiple environments +being built at the same time, e.g. StandaloneMM and DXE. If so, `CONF_AUTOGEN_INCLUDE_PATH` must have the same number of +entries, see above. An example is: + +```python +self.env.SetValue("MU_SCHEMA_FILE_NAME", "NonSecureConfig.xml;StMMConfig.xml", "Platform Defined") +``` + ### Autogenerated Header Files There are four autogenerated headers and one standard structure definition header: diff --git a/SetupDataPkg/Docs/Profiles/Overview.md b/SetupDataPkg/Docs/Profiles/Overview.md index 7ed44abf..122844ab 100644 --- a/SetupDataPkg/Docs/Profiles/Overview.md +++ b/SetupDataPkg/Docs/Profiles/Overview.md @@ -59,6 +59,12 @@ self.env.SetValue('CONF_PROFILE_PATHS', ) ``` +If `MU_SCHEMA_FILE_NAME` has multiple entries (see +[Platform Integration Steps](../PlatformIntegration/PlatformIntegrationSteps.md)), then there may be a semicolon +delimited list of `CONF_PROFILE_PATHS`. `UpdateConfigHdr.py` will match these in the same order as the schema files, +i.e. `CONF_PROFILE_PATHS` first entry will be applied to the first entry of `MU_SCHEMA_FILE_NAME`. If there is no +corresponding entry, it will be assumed there is no profile override to that schema file. + Platform owners can develop a configuration profile for their use case. Following examples and the format provided in the [ConfigurationFiles doc](../ConfigurationFiles/ConfigurationFiles.md), these owners can create an XML change file describing the set of configuration variables and their values that are in the profile that differ from the generic diff --git a/SetupDataPkg/Plugins/UpdateConfigHdr/UpdateConfigHdr.py b/SetupDataPkg/Plugins/UpdateConfigHdr/UpdateConfigHdr.py index 0d1c2cf8..e4ae607a 100644 --- a/SetupDataPkg/Plugins/UpdateConfigHdr/UpdateConfigHdr.py +++ b/SetupDataPkg/Plugins/UpdateConfigHdr/UpdateConfigHdr.py @@ -23,13 +23,17 @@ def do_pre_build(self, thebuilder): "SetupDataPkg", "Test", "Include" ) - final_dir = thebuilder.env.GetValue("CONF_AUTOGEN_INCLUDE_PATH", default_generated_path) + # we can have a semicolon delimited list of include paths to generate to + dirs = thebuilder.env.GetValue("CONF_AUTOGEN_INCLUDE_PATH", default_generated_path).split(";") + final_dirs = [] # Add Generated dir - final_dir = os.path.join(final_dir, "Generated") + for directory in dirs: + gen_dir = os.path.join(directory, "Generated") + final_dirs.append(gen_dir) - if not os.path.isdir(final_dir): - os.makedirs(final_dir) + if not os.path.isdir(gen_dir): + os.makedirs(gen_dir) # Add generate routine here cmd = thebuilder.edk2path.GetAbsolutePathOnThisSystemFromEdk2RelativePath( @@ -44,41 +48,54 @@ def do_pre_build(self, thebuilder): logging.error("MU_SCHEMA_DIR not set") return -1 - schema_file_name = thebuilder.env.GetValue("MU_SCHEMA_FILE_NAME", "testschema.xml") + # This may be a semicolon delimited string + schema_file_names = thebuilder.env.GetValue("MU_SCHEMA_FILE_NAME", "testschema.xml").split(";") + schema_files = [] - schema_file = thebuilder.edk2path.GetAbsolutePathOnThisSystemFromEdk2RelativePath(schema_dir, schema_file_name) + for name in schema_file_names: + file = thebuilder.edk2path.GetAbsolutePathOnThisSystemFromEdk2RelativePath(schema_dir, name) + schema_files.append(file) - if not os.path.isfile(schema_file): - logging.error(f"XML schema file \"{schema_file}\" specified is not found!!!") - return -1 + if not os.path.isfile(file): + logging.error(f"XML schema file \"{file}\" specified is not found!!!") + return -1 - # this is a space separated list of paths to CSV files describing the profiles for + # this is a semicolon delimited list of space separated lists of paths to CSV files describing the profiles for # this platform. It is allowed to be empty if there are no profiles. - profile_paths = thebuilder.env.GetValue("CONF_PROFILE_PATHS", "") + # e.g.: "CONF_PATH_1 CONF_PATH_2 CONF_PATH3;CONF_PATH4 CONF_PATH5 CONF_PATH6;CONF_PATH7 CONF_PATH8 CONF_PATH9" + profile_path_list = thebuilder.env.GetValue("CONF_PROFILE_PATHS", "").split(";") + + # this is a semicolon delimited list of comma separated 2 character profile names to pair with CSV files + # identifying the profiles. This field is optional. + profile_names = thebuilder.env.GetValue("CONF_PROFILE_NAMES", "").split(";") + profile_ids = thebuilder.env.GetValue("CONF_PROFILE_IDS", "").split(";") - # this is a comma separated 2 character profile names to pair with CSV files identifying - # the profiles. This field is optional. - profile_names = thebuilder.env.GetValue("CONF_PROFILE_NAMES", "") - profile_ids = thebuilder.env.GetValue("CONF_PROFILE_IDS", "") + if len(schema_files) != len(final_dirs): + logging.error("Differing number of items in CONF_AUTOGEN_INCLUDE_PATH and MU_SCHEMA_FILE_NAME!\ + They must be the same") + return -1 - params = ["generateheader_efi"] + for i in range(len(schema_files)): + params = ["generateheader_efi"] - params.append(schema_file) + params.append(schema_files[i]) - params.append("ConfigClientGenerated.h") - params.append("ConfigServiceGenerated.h") - params.append("ConfigDataGenerated.h") + params.append("ConfigClientGenerated.h") + params.append("ConfigServiceGenerated.h") + params.append("ConfigDataGenerated.h") - if profile_paths != "": - params.append("ConfigProfilesGenerated.h") - params.append(profile_paths) + if len(profile_path_list) > i and profile_path_list[i] != "": + params.append("ConfigProfilesGenerated.h") + params.append(profile_path_list[i]) - if profile_names != "": - params.append("-pn") - params.append(profile_names) - if profile_ids != "": - params.append("-pid") - params.append(profile_ids) + if len(profile_names) > i and profile_names[i] != "": + params.append("-pn") + params.append(profile_names[i]) + if len(profile_ids) > i and profile_ids[i] != "": + params.append("-pid") + params.append(profile_ids[i]) - ret = RunPythonScript(cmd, " ".join(params), workingdir=final_dir) - return ret + ret = RunPythonScript(cmd, " ".join(params), workingdir=final_dirs[i]) + if ret != 0: + return ret + return 0