Skip to content

Commit

Permalink
Add Multiple Config Environment Build Support (#374)
Browse files Browse the repository at this point in the history
## 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.
  • Loading branch information
os-d committed Jun 30, 2024
1 parent 11f63c3 commit 50ecba2
Show file tree
Hide file tree
Showing 3 changed files with 72 additions and 32 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
6 changes: 6 additions & 0 deletions SetupDataPkg/Docs/Profiles/Overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
79 changes: 48 additions & 31 deletions SetupDataPkg/Plugins/UpdateConfigHdr/UpdateConfigHdr.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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

0 comments on commit 50ecba2

Please sign in to comment.