mirror of
https://github.com/nasa/fprime.git
synced 2025-12-10 17:47:10 -06:00
mstarch: adding in missing file and correcting gitignore
This commit is contained in:
parent
29074e4827
commit
48d7be9043
6
.gitignore
vendored
6
.gitignore
vendored
@ -1,5 +1,5 @@
|
|||||||
build
|
build/
|
||||||
build*
|
build*/
|
||||||
|
|
||||||
AutoXML/
|
AutoXML/
|
||||||
test_harness/src/test_harness-C/application.out
|
test_harness/src/test_harness-C/application.out
|
||||||
@ -72,4 +72,4 @@ build-fprime-automatic*
|
|||||||
TesterBase.*
|
TesterBase.*
|
||||||
GTestBase.*
|
GTestBase.*
|
||||||
/Fw/Python/.eggs
|
/Fw/Python/.eggs
|
||||||
/Gds/src/fprime_gds.egg-info
|
/Gds/src/fprime_gds.egg-info
|
||||||
|
|||||||
437
Fw/Python/src/fprime/fbuild/builder.py
Normal file
437
Fw/Python/src/fprime/fbuild/builder.py
Normal file
@ -0,0 +1,437 @@
|
|||||||
|
"""
|
||||||
|
Supplies high-level build functions to the greater fprime helper CLI. This maps from user command space to the specific
|
||||||
|
build system handler underneath.
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import functools
|
||||||
|
from abc import ABC
|
||||||
|
from enum import Enum
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Iterable, List, Set, Union
|
||||||
|
|
||||||
|
from fprime.common.error import FprimeException
|
||||||
|
from fprime.fbuild.settings import IniSettings
|
||||||
|
from fprime.fbuild.cmake import CMakeHandler, CMakeException
|
||||||
|
|
||||||
|
|
||||||
|
class BuildType(Enum):
|
||||||
|
"""
|
||||||
|
An enumeration used to represent the various build types used to build fprime. These types can support different
|
||||||
|
types of targets underneath. i.e. the unit-test build may build unit test executables.
|
||||||
|
"""
|
||||||
|
|
||||||
|
""" Normal build normal binaries for a deployment mapping to CMake 'Release'""" # pylint: disable=W0105
|
||||||
|
BUILD_NORMAL = 0,
|
||||||
|
""" Testing build allowing unit testing mapping to CMake 'Testing'""" # pylint: disable=W0105
|
||||||
|
BUILD_TESTING = 1
|
||||||
|
|
||||||
|
def get_suffix(self):
|
||||||
|
""" Get the suffix of a directory supporting this build """
|
||||||
|
if self == BuildType.BUILD_NORMAL:
|
||||||
|
return ""
|
||||||
|
elif self == BuildType.BUILD_TESTING:
|
||||||
|
return "-ut"
|
||||||
|
assert False, "Invalid build type"
|
||||||
|
|
||||||
|
def get_cmake_build_type(self):
|
||||||
|
""" Get the suffix of a directory supporting this build """
|
||||||
|
if self == BuildType.BUILD_NORMAL:
|
||||||
|
return "Release"
|
||||||
|
elif self == BuildType.BUILD_TESTING:
|
||||||
|
return "Testing"
|
||||||
|
assert False, "Invalid build type"
|
||||||
|
|
||||||
|
|
||||||
|
class Target(ABC):
|
||||||
|
"""Generic build target base class
|
||||||
|
|
||||||
|
A target can be specified by the user using a mnemonic and flags. The mnemonic is the command typed in by the user,
|
||||||
|
and the flags allow the user to remember fewer mnemonics by changing the build target using a modifier. Each build
|
||||||
|
target is available in certain build types.
|
||||||
|
|
||||||
|
Targets can be global, using the GlobalTarget base class. Global targets don't use contextual information to modify
|
||||||
|
the target, but apply to the whole deployment. Note: global targets are also engaged at the deployment level should
|
||||||
|
that be the context.
|
||||||
|
|
||||||
|
Targets may also be local. These targets use context information to figure out what to build. This allows for one
|
||||||
|
target to represent a class of targets. i.e. build can be used as a local target to build any given sub directory.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, mnemonic:str, desc:str, build_types:List[BuildType] = None, flags:set = None, cmake:str = None):
|
||||||
|
""" Constructs a build target
|
||||||
|
|
||||||
|
Args:
|
||||||
|
mnemonic: mnemonic used to engage build targets. Is not unique, but mnemonic + flags must be.
|
||||||
|
desc: help description of this build target
|
||||||
|
build_types: supported build types for target. Defaults to [BuildType.BUILD_NORMAL, BuildType.BUILD_TESTING]
|
||||||
|
flags: flags used to uniquely identify build targets who share logical mnemonics. Defaults to None.
|
||||||
|
cmake: cmake target override to handle oddly named cmake targets
|
||||||
|
"""
|
||||||
|
self.mnemonic = mnemonic
|
||||||
|
self.desc = desc
|
||||||
|
self.build_types = build_types if build_types is not None else [BuildType.BUILD_NORMAL, BuildType.BUILD_TESTING]
|
||||||
|
self.flags = flags if flags is not None else set()
|
||||||
|
self.cmake_target = cmake if cmake is not None else mnemonic
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
""" Makes this target into a string """
|
||||||
|
return self.config_string(self.mnemonic, self.flags)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def config_string(mnemonic, flags):
|
||||||
|
"""Converts a mnemonic and set of flags to string
|
||||||
|
|
||||||
|
Args:
|
||||||
|
mnemonic: mnemonic of the target
|
||||||
|
flags: sset of flags to pair with mnemonic
|
||||||
|
Returns:
|
||||||
|
string of format "mnemonic --flag1 --flag2 ..."
|
||||||
|
"""
|
||||||
|
flag_string = " ".join(["--{}".format(flag) for flag in flags])
|
||||||
|
flag_string = "" if flag_string == "" else " " + flag_string
|
||||||
|
return "{}{}".format(mnemonic, flag_string)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_all_possible_flags(cls) -> Set[str]:
|
||||||
|
""" Gets list of all targets' flags used
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List of targets's supported by the system
|
||||||
|
"""
|
||||||
|
return functools.reduce(lambda agg, item: agg.union(item.flags), cls.get_all_targets(), set())
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_all_targets(cls) -> List['Target']:
|
||||||
|
""" Gets list of all targets registered
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List of targets supported by the system
|
||||||
|
"""
|
||||||
|
return BUILD_TARGETS
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_target(cls, mnemonic:str, flags: Set[str]) -> 'Target':
|
||||||
|
""" Gets the actual build target given the parsed namespace
|
||||||
|
|
||||||
|
Using the global list of build targets and the flags supplied to the namespace, attempt to determine which build
|
||||||
|
targets can be used. If more than one are found, then generate exception.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
mnemonic: mnemonic of command to look for
|
||||||
|
flags: flags to narrow down target
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
single matching target
|
||||||
|
"""
|
||||||
|
#matching = [target for target in cls.get_all_targets() if target.mnemonic == mnemonic and flags == target.flags]
|
||||||
|
matching = []
|
||||||
|
for target in cls.get_all_targets():
|
||||||
|
if target.mnemonic == mnemonic and flags == target.flags:
|
||||||
|
matching.append(target)
|
||||||
|
if not matching:
|
||||||
|
raise NoSuchTargetExcetion("Could not find target '{}'".format(cls.config_string(mnemonic, flags)))
|
||||||
|
assert len(matching) == 1, "Conflicting targets specified in code"
|
||||||
|
return matching[0]
|
||||||
|
|
||||||
|
|
||||||
|
class GlobalTarget(Target):
|
||||||
|
"""Represents a global build target
|
||||||
|
|
||||||
|
Build targets are global if they do not apply to a specific directory, but rather to a full deployment.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class LocalTarget(Target):
|
||||||
|
"""Represents a local build target
|
||||||
|
|
||||||
|
Build targets are local if they do apply to a specific directory whose context drives the build setup.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class Build:
|
||||||
|
"""Represents a build configuration
|
||||||
|
|
||||||
|
Builds in F´ consist of a build type (normal, testing), a deployment directory, a set of settings, and a target
|
||||||
|
platform. These are tracked as part of this Build class. This helps setup a build cache directory, load default
|
||||||
|
settings, and track what type of build is being run.
|
||||||
|
|
||||||
|
BuildType represents the type of build as explained in that enum type.
|
||||||
|
Deployments are an individual build of fprime, and should define the CMakeLists.txt file as a child of this
|
||||||
|
directory. A default settings.ini file may be found here.
|
||||||
|
Platforms represent the target hardware to build from. This is translated to the CMake toolchain file.
|
||||||
|
|
||||||
|
After creation, a user must use invent to handle new builds (e.g. during the generation step), or load to load a
|
||||||
|
previously generated build.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
To use in generation run the following code.
|
||||||
|
|
||||||
|
build = Build(BuildType.BUILD_NORMAL, path/to/deployment)
|
||||||
|
build.invent("raspberrypi")
|
||||||
|
|
||||||
|
To use at any step after generation:
|
||||||
|
|
||||||
|
build = Build(BuildType.BUILD_NORMAL, path/to/deployment)
|
||||||
|
build.load()
|
||||||
|
"""
|
||||||
|
VALID_CMAKE_LIST = re.compile(r"(?sm)\sproject\(.*\)")
|
||||||
|
CMAKE_DEFAULT_BUILD_NAME = "build-fprime-automatic-{platform}{suffix}"
|
||||||
|
|
||||||
|
def __init__(self, build_type: BuildType, deployment: Path, verbose:bool = False):
|
||||||
|
""" Constructs a build object from its constituent parts
|
||||||
|
|
||||||
|
Args:
|
||||||
|
build_type: member of the enum BuildType specifying fprime build type
|
||||||
|
deployment: path to deployment that this build represents
|
||||||
|
"""
|
||||||
|
self.build_type = build_type
|
||||||
|
self.deployment = deployment
|
||||||
|
self.settings = None
|
||||||
|
self.platform = None
|
||||||
|
self.build_dir = None
|
||||||
|
self.cmake = CMakeHandler()
|
||||||
|
self.cmake.set_verbose(verbose)
|
||||||
|
|
||||||
|
def invent(self, platform: str = None, build_dir: Path = None):
|
||||||
|
""" Invents a build path from a given platform
|
||||||
|
|
||||||
|
Sets this build up as a new build that would be used as as part of a generate step. This directory must not
|
||||||
|
already exist. If platform is None, a default will be chosen from the settings.ini file. If the settings.ini
|
||||||
|
file does not exist, or does not specify a default_toolchain, then "native" will be used. Settings are loaded in
|
||||||
|
this step for further uses of this build.
|
||||||
|
|
||||||
|
build_dir is used to specify an exact build directory to use as part of this step. This allows directories to be
|
||||||
|
specified by the caller, but is typically not used.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
platform: name of platform to build against. None will use default from settings.ini or without this
|
||||||
|
setting, "native". Defaults to None.
|
||||||
|
build_dir: explicitly sets the build path to allow for user override of default
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
InvalidBuildCacheException: a build cache already exists as it should not
|
||||||
|
"""
|
||||||
|
self.__setup_default()
|
||||||
|
if self.build_dir.exists():
|
||||||
|
raise InvalidBuildCacheException("{} already exists.".format(self.build_dir))
|
||||||
|
|
||||||
|
def load(self, platform: str = None, build_dir: Path = None):
|
||||||
|
""" Load an existing build cache
|
||||||
|
|
||||||
|
Sets this build up from an existing build cache. This can be used after a previous run that has generated a
|
||||||
|
build cache in order to prepare for other build steps.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
platform: name of platform to build against. None will use default from settings.ini or without this
|
||||||
|
setting, "native". Defaults to None.
|
||||||
|
build_dir: explicitly sets the build path to allow for user override of default
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
InvalidBuildCacheException: the build cache does not exist as it must
|
||||||
|
"""
|
||||||
|
self.__setup_default()
|
||||||
|
if not self.build_dir.exists() or not (self.build_dir / "CMakeCache.txt").exists():
|
||||||
|
raise InvalidBuildCacheException("{} invalid build cache. Please (re)generate.".format(build_dir))
|
||||||
|
|
||||||
|
def get_settings(self, setting: Union[str, Iterable[str]], default: Union[str, Iterable[str]]) -> Union[str, Iterable[str]]:
|
||||||
|
""" Fetches settings in the settings file
|
||||||
|
|
||||||
|
Reads settings loaded from the settings file and returns them to the caller. If a single string is submitted,
|
||||||
|
then a single string is returned. If a list of strings is submitted a list is returned. default provides default
|
||||||
|
values to supply in the case that a setting is unavailable.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
setting: a string or set of string settings to return
|
||||||
|
default: a string or set of string settings to return if no setting is found
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
a single string setting or a list of string settings to match request with defaults subbed ins
|
||||||
|
"""
|
||||||
|
if isinstance(setting, str):
|
||||||
|
return self.settings.get(setting, default)
|
||||||
|
return [self.get_settings(req, back) for req, back in zip(setting, default)]
|
||||||
|
|
||||||
|
def find_hashed_file(self, hash_value: int) -> List[str]:
|
||||||
|
""" Retrieves the file associated with a hash
|
||||||
|
|
||||||
|
In order to reduce space and memory footprint, filenames are associated with hashes automatically as part of the
|
||||||
|
build. This function will retrieve the file name given a has integer.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
hash_value: hash number to lookup
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
stored file path(s) associated with hash
|
||||||
|
"""
|
||||||
|
hashes_file = self.build_dir / "hashes.txt"
|
||||||
|
if not hashes_file.exists():
|
||||||
|
raise InvalidBuildCacheException(
|
||||||
|
"Failed to find {}, was the build generated.".format(hashes_file)
|
||||||
|
)
|
||||||
|
with open(hashes_file) as file_handle:
|
||||||
|
lines = filter(
|
||||||
|
lambda line: "{:x}".format(hash_value) in line, file_handle.readlines()
|
||||||
|
)
|
||||||
|
return list(lines)
|
||||||
|
|
||||||
|
def get_build_cache(self) -> Path:
|
||||||
|
"""Generates build cache path for this build
|
||||||
|
|
||||||
|
Generates the build path for this build. This will expect a valid build path to exist unless validate is
|
||||||
|
specified as false. A valid build cache has been created from the generate step, and thus when using this call
|
||||||
|
as part of the generate step, validate should be set to false.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Path to a build cache directory
|
||||||
|
|
||||||
|
"""
|
||||||
|
return self.deployment / Build.CMAKE_DEFAULT_BUILD_NAME.format(platform=self.platform,
|
||||||
|
suffix=self.build_type.get_suffix())
|
||||||
|
|
||||||
|
def get_build_info(self, context:Path) -> dict:
|
||||||
|
""" Constructs an informational packet about this build
|
||||||
|
|
||||||
|
Constructs a packet that allows for users to get meta-build information. This includes: location of build, file
|
||||||
|
and other constructs, available make targets, and other items.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
context: contextual path to list various information about the build
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
valid_cmake_targets = self.cmake.get_available_targets(str(self.build_dir), context)
|
||||||
|
local_targets = [target for target in BUILD_TARGETS if target.cmake_target in valid_cmake_targets and isinstance(target, LocalTarget)]
|
||||||
|
global_targets = [target for target in BUILD_TARGETS if isinstance(target, GlobalTarget)]
|
||||||
|
|
||||||
|
relative_path = self.cmake.get_project_relative_path(str(context), self.build_dir)
|
||||||
|
|
||||||
|
for possible in ["F-Prime", "."]:
|
||||||
|
auto_location = self.build_dir / possible / relative_path
|
||||||
|
if auto_location.exists():
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
auto_location = None
|
||||||
|
return {"local_targets": local_targets, "global_targets": global_targets, "auto_location": auto_location}
|
||||||
|
|
||||||
|
def execute(self, target:Target, context:Path, make_args:dict):
|
||||||
|
""" Execute a build target
|
||||||
|
|
||||||
|
Executes a target within the build system. This will execute the target by calling into the make system. Context
|
||||||
|
is supplied such that the system can match local targets to the global target list.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
target: target to run
|
||||||
|
context: context path for local targets
|
||||||
|
make_args: make system arguments directly supplied
|
||||||
|
"""
|
||||||
|
self.cmake.execute_known_target(target.cmake_target, self.build_dir, context.absolute(), make_args=make_args,
|
||||||
|
top_target=isinstance(target, GlobalTarget))
|
||||||
|
|
||||||
|
def generate(self, cmake_args):
|
||||||
|
try:
|
||||||
|
cmake_args.update({"CMAKE_BUILD_TYPE": self.build_type.get_cmake_build_type()})
|
||||||
|
self.cmake.generate_build(self.deployment, self.build_dir, cmake_args)
|
||||||
|
except CMakeException:
|
||||||
|
self.purge()
|
||||||
|
|
||||||
|
|
||||||
|
def purge(self):
|
||||||
|
self.cmake.purge(self.build_dir)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def find_nearest_deployment(path: Path) -> Path:
|
||||||
|
""" Recurse up the directory stack looking for a valid deployment
|
||||||
|
|
||||||
|
Recurse up the directory tree from the given path, looking for a deployment definition directory. This means it
|
||||||
|
defines a CMakeLists.txt with a project call. This finds where the automatic build directories are allowed to
|
||||||
|
exist.
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
This replaced the former build directory recursive detector as that can "slip" past a deployment should the
|
||||||
|
build directory not be generated yet.
|
||||||
|
|
||||||
|
Returns;
|
||||||
|
Path to the nearest deployment directory searching up the directory tree
|
||||||
|
|
||||||
|
Raises;
|
||||||
|
UnableToDetectDeploymentException: was unable to detect a deployment directory
|
||||||
|
"""
|
||||||
|
list_file = path / "CMakeLists.txt"
|
||||||
|
if path == Path.anchor:
|
||||||
|
raise UnableToDetectDeploymentException()
|
||||||
|
elif list_file.exists():
|
||||||
|
with open(list_file) as file_handle:
|
||||||
|
text = file_handle.read()
|
||||||
|
if Build.VALID_CMAKE_LIST.search(text):
|
||||||
|
return path
|
||||||
|
return Build.find_nearest_deployment(path.parent)
|
||||||
|
|
||||||
|
def __setup_default(self, platform: str = None, build_dir: Path = None):
|
||||||
|
""" Sets up default build
|
||||||
|
|
||||||
|
Sets this build up before determining if it is a pre-generated, or post-generated build.
|
||||||
|
|
||||||
|
build_dir is used to specify an exact build directory to use as part of this step. This allows directories to be
|
||||||
|
specified by the caller, but is typically not used.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
platform: name of platform to build against. None will use default from settings.ini or without this
|
||||||
|
setting, "native". Defaults to None.
|
||||||
|
build_dir: explicitly sets the build path to allow for user override of default
|
||||||
|
"""
|
||||||
|
assert self.settings is None, "Already setup it is invalid to re-setup"
|
||||||
|
assert self.platform is None, "Already setup it is invalid to re-setup"
|
||||||
|
assert self.build_dir is None, "Already setup it is invalid to re-setup"
|
||||||
|
|
||||||
|
self.settings = IniSettings.load(self.deployment / "settings.ini")
|
||||||
|
self.platform = platform if platform is not None else self.settings.get("default_toolchain", "native")
|
||||||
|
self.build_dir = build_dir if build_dir is not None else self.get_build_cache()
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidBuildCacheException(FprimeException):
|
||||||
|
""" An exception indicating a build cache """
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class UnableToDetectDeploymentException(FprimeException):
|
||||||
|
""" An exception indicating a build cache """
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class NoValidBuildTypeException(FprimeException):
|
||||||
|
""" An build type matching the user request could not be found """
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class NoSuchTargetExcetion(FprimeException):
|
||||||
|
""" Could not find a matching build target """
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
""" Defined set of build targets available to the system"""
|
||||||
|
BUILD_TARGETS = [
|
||||||
|
# Various "build" target
|
||||||
|
LocalTarget("build", "Build components, ports, and deployments", cmake=""),
|
||||||
|
GlobalTarget("build", "Build the top-level delpoyment targets", flags={"deployment"}),
|
||||||
|
GlobalTarget("build", "Build all deployment targets", flags={"all"}, cmake="build-all"),
|
||||||
|
LocalTarget("build", "Build unit tests", build_types=[BuildType.BUILD_TESTING],
|
||||||
|
flags={"ut"}, cmake="ut_exe"),
|
||||||
|
GlobalTarget("build", "Build deployment unit tests", build_types=[BuildType.BUILD_TESTING],
|
||||||
|
flags={"deployment", "ut"}, cmake="ut_exe"),
|
||||||
|
# Implementation targets
|
||||||
|
LocalTarget("impl", "Generate implementation template files"),
|
||||||
|
LocalTarget("impl", "Generate unit test files", flags={"ut"}, cmake="testimpl"),
|
||||||
|
# Check targets and unittest targets
|
||||||
|
LocalTarget("check", "Run unit tests", build_types=[BuildType.BUILD_TESTING]),
|
||||||
|
LocalTarget("check", "Run unit tests with memory checking",
|
||||||
|
build_types=[BuildType.BUILD_TESTING], flags={"leak"}, cmake="check_leak"),
|
||||||
|
GlobalTarget("check", "Run all deployment unit tests", build_types=[BuildType.BUILD_TESTING], flags={"all"}),
|
||||||
|
GlobalTarget("check", "Run all deployment unit tests with memory checking",
|
||||||
|
build_types=[BuildType.BUILD_TESTING], flags={"all", "leak"}, cmake="check_leak"),
|
||||||
|
LocalTarget("coverage", "Generate unit test coverage reports",
|
||||||
|
build_types=[BuildType.BUILD_TESTING]),
|
||||||
|
# Installation target
|
||||||
|
GlobalTarget("install", "Install the current deployment build artifacts", build_types=[BuildType.BUILD_NORMAL])
|
||||||
|
]
|
||||||
Loading…
x
Reference in New Issue
Block a user