Set up CMake configure dependencies on all JSON files used in the configuration (#409)

* Set up CMake configure dependencies on all JSON files used in the configuration

* Remove unneeded cmake-build-dir option, we already have output

* Oops use namespace

* Fix comment

* Don't explode if given paths outside the root dir
pull/15531/head
Jamie Smith 2024-12-30 00:02:25 -08:00 committed by GitHub
parent 2fd9d83098
commit 722c2f1196
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 133 additions and 590 deletions

View File

@ -27,58 +27,44 @@ else()
endif()
set(MBED_INTERNAL_LAST_MBED_TARGET "${MBED_TARGET}" CACHE INTERNAL "Previous mbed target this dir was configured with" FORCE)
# Convert all relative paths to absolute paths, rooted at CMAKE_SOURCE_DIR.
# This makes sure that they are interpreted the same way everywhere.
get_filename_component(MBED_APP_JSON_PATH "${MBED_APP_JSON_PATH}" ABSOLUTE BASE_DIR ${CMAKE_SOURCE_DIR})
get_filename_component(CUSTOM_TARGETS_JSON_PATH "${CUSTOM_TARGETS_JSON_PATH}" ABSOLUTE BASE_DIR ${CMAKE_SOURCE_DIR})
# Make it so that if mbed_app.json or custom_targets.json are modified, CMake is rerun.
set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS ${MBED_APP_JSON_PATH})
if(EXISTS "${CUSTOM_TARGETS_JSON_PATH}" AND (NOT IS_DIRECTORY "${CUSTOM_TARGETS_JSON_PATH}"))
set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS ${CUSTOM_TARGETS_JSON_PATH})
endif()
# Check if mbed_app.json was modified
# Note: if the path is an empty string, get_filename_component(ABSOLUTE) will convert it to a directory,
# so we have to verify that the path we have is a file, not a dir.
if(EXISTS "${MBED_APP_JSON_PATH}" AND (NOT IS_DIRECTORY "${MBED_APP_JSON_PATH}"))
file(TIMESTAMP "${MBED_APP_JSON_PATH}" MBED_APP_JSON_TIMESTAMP "%s" UTC)
else()
set(MBED_APP_JSON_TIMESTAMP "<none>")
endif()
if(NOT MBED_NEED_TO_RECONFIGURE)
if(NOT "${MBED_INTERNAL_LAST_MBED_APP_JSON_TIMESTAMP}" STREQUAL "${MBED_APP_JSON_TIMESTAMP}")
message(STATUS "Mbed: mbed_app.json modified, regenerating configs...")
set(MBED_NEED_TO_RECONFIGURE TRUE)
endif()
endif()
set(MBED_INTERNAL_LAST_MBED_APP_JSON_TIMESTAMP "${MBED_APP_JSON_TIMESTAMP}" CACHE INTERNAL "Previous UTC modification timestamp for mbed_app.json" FORCE)
# Check if custom_targets.json was modified
if(EXISTS "${CUSTOM_TARGETS_JSON_PATH}" AND (NOT IS_DIRECTORY "${CUSTOM_TARGETS_JSON_PATH}"))
file(TIMESTAMP "${CUSTOM_TARGETS_JSON_PATH}" CUSTOM_TARGETS_JSON_TIMESTAMP "%s" UTC)
else()
set(CUSTOM_TARGETS_JSON_TIMESTAMP "<none>")
endif()
if(NOT MBED_NEED_TO_RECONFIGURE)
if(NOT "${MBED_INTERNAL_LAST_CUSTOM_TARGETS_JSON_TIMESTAMP}" STREQUAL "${CUSTOM_TARGETS_JSON_TIMESTAMP}")
message(STATUS "Mbed: custom_targets.json modified, regenerating configs...")
set(MBED_NEED_TO_RECONFIGURE TRUE)
endif()
endif()
set(MBED_INTERNAL_LAST_CUSTOM_TARGETS_JSON_TIMESTAMP "${CUSTOM_TARGETS_JSON_TIMESTAMP}" CACHE INTERNAL "Previous UTC modification timestamp for custom_targets.json" FORCE)
# Check if the include file is missing (e.g. because a previous attempt to generate it failed)
if(NOT EXISTS ${CMAKE_CURRENT_BINARY_DIR}/mbed_config.cmake)
if(NOT MBED_NEED_TO_RECONFIGURE)
message(STATUS "Mbed: mbed_config.cmake not found, regenerating configs...")
set(MBED_NEED_TO_RECONFIGURE TRUE)
endif()
else()
# Include the old version of mbed_config.cmake to get the MBED_CONFIG_JSON_SOURCE_FILES variable used below
include(${CMAKE_CURRENT_BINARY_DIR}/mbed_config.cmake)
endif()
# Check timestamps on all JSON files used to generate the Mbed configuration
if(NOT MBED_NEED_TO_RECONFIGURE)
file(TIMESTAMP ${CMAKE_CURRENT_BINARY_DIR}/mbed_config.cmake MBED_CONFIG_CMAKE_TIMESTAMP "%s" UTC)
foreach(CONFIG_JSON ${MBED_CONFIG_JSON_SOURCE_FILES})
get_filename_component(CONFIG_JSON_ABSPATH ${CONFIG_JSON} ABSOLUTE)
if(NOT EXISTS ${CONFIG_JSON_ABSPATH})
message(STATUS "Mbed: ${CONFIG_JSON} deleted or renamed, regenerating configs...")
set(MBED_NEED_TO_RECONFIGURE TRUE)
break()
endif()
file(TIMESTAMP ${CONFIG_JSON_ABSPATH} CONFIG_JSON_TIMESTAMP "%s" UTC)
if(${CONFIG_JSON_TIMESTAMP} GREATER ${MBED_CONFIG_CMAKE_TIMESTAMP})
message(STATUS "Mbed: ${CONFIG_JSON} modified, regenerating configs...")
set(MBED_NEED_TO_RECONFIGURE TRUE)
break()
endif()
endforeach()
endif()
# Convert all relative paths to absolute paths, rooted at CMAKE_SOURCE_DIR.
# This makes sure that they are interpreted the same way everywhere.
get_filename_component(MBED_APP_JSON_PATH "${MBED_APP_JSON_PATH}" ABSOLUTE BASE_DIR ${CMAKE_SOURCE_DIR})
get_filename_component(CUSTOM_TARGETS_JSON_PATH "${CUSTOM_TARGETS_JSON_PATH}" ABSOLUTE BASE_DIR ${CMAKE_SOURCE_DIR})
if(MBED_NEED_TO_RECONFIGURE)
# Generate mbed_config.cmake for this target
if(EXISTS "${MBED_APP_JSON_PATH}" AND (NOT IS_DIRECTORY "${MBED_APP_JSON_PATH}"))
@ -98,7 +84,7 @@ if(MBED_NEED_TO_RECONFIGURE)
set(MBEDTOOLS_CONFIGURE_COMMAND ${Python3_EXECUTABLE}
-m mbed_tools.cli.main
-v # without -v, warnings (e.g. "you have tried to override a nonexistent parameter") do not get printed
-v -v # without at least -v, warnings (e.g. "you have tried to override a nonexistent parameter") do not get printed
configure
-t GCC_ARM # GCC_ARM is currently the only supported toolchain
-m "${MBED_TARGET}"
@ -126,4 +112,7 @@ if(MBED_NEED_TO_RECONFIGURE)
endif()
# Include the generated config file
include(${CMAKE_CURRENT_BINARY_DIR}/mbed_config.cmake)
include(${CMAKE_CURRENT_BINARY_DIR}/mbed_config.cmake)
# Make it so that if any config JSON files are modified, CMake is rerun.
set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS ${MBED_CONFIG_JSON_SOURCE_FILES})

View File

@ -30,7 +30,11 @@ def render_mbed_config_cmake_template(config: Config, toolchain_name: str, targe
env.filters["to_hex"] = to_hex
template = env.get_template(TEMPLATE_NAME)
config["supported_c_libs"] = [x for x in config["supported_c_libs"][toolchain_name.lower()]]
context = {"target_name": target_name, "toolchain_name": toolchain_name, **config}
context = {"target_name": target_name,
"toolchain_name": toolchain_name,
"json_sources": config.json_sources,
**config}
return template.render(context)

View File

@ -9,12 +9,15 @@ from dataclasses import dataclass
from pathlib import Path
from typing import Iterable, List, Optional, Set
from setuptools.build_meta import build_editable
from mbed_tools.project import MbedProgram
from mbed_tools.build._internal.config.config import Config
from mbed_tools.build._internal.config import source
from mbed_tools.build._internal.find_files import LabelFilter, RequiresFilter, filter_files, find_files
def assemble_config(target_attributes: dict, search_paths: Iterable[Path], mbed_app_file: Optional[Path]) -> Config:
def assemble_config(target_attributes: dict, program: MbedProgram) -> Config:
"""Assemble config for given target and program directory.
Mbed library and application specific config parameters are parsed from mbed_lib.json and mbed_app.json files
@ -29,16 +32,36 @@ def assemble_config(target_attributes: dict, search_paths: Iterable[Path], mbed_
Args:
target_attributes: Mapping of target specific config parameters.
search_paths: Iterable of paths to search for mbed_lib.json files.
mbed_app_file: The path to mbed_app.json. This can be None.
program: MbedProgram to build the config for
"""
mbed_lib_files: Set[Path] = set()
for path in search_paths:
for path in [program.root, program.mbed_os.root]:
mbed_lib_files.update(find_files("mbed_lib.json", path.absolute().resolve()))
mbed_lib_files.update(find_files("mbed_lib.json5", path.absolute().resolve()))
return _assemble_config_from_sources(target_attributes, list(mbed_lib_files), mbed_app_file)
config = _assemble_config_from_sources(target_attributes, list(mbed_lib_files), program.files.app_config_file)
# Set up the config source path list using the path to every JSON
config.json_sources.extend(mbed_lib_files)
if program.files.app_config_file is not None:
config.json_sources.append(program.files.app_config_file)
config.json_sources.append(program.mbed_os.targets_json_file)
config.json_sources.append(program.mbed_os.cmsis_mcu_descriptions_json_file)
if program.files.custom_targets_json.exists():
config.json_sources.append(program.files.custom_targets_json)
# Make all JSON sources relative paths to the program root
def make_relative_if_possible(path: Path):
# Sadly, Pathlib did not gain a better way to do this until newer python versions.
try:
return path.relative_to(program.root)
except ValueError:
return path
config.json_sources = [make_relative_if_possible(program.root) for json_source in config.json_sources]
return config
def _assemble_config_from_sources(

View File

@ -7,6 +7,7 @@ import logging
from collections import UserDict
from typing import Any, Iterable, Hashable, List
import pathlib
from mbed_tools.build._internal.config.source import Override, ConfigSetting
@ -21,6 +22,11 @@ class Config(UserDict):
Applies overrides, appends macros, and updates config settings.
"""
# List of JSON files used to create this config. Dumped to CMake at the end of configuration
# so that it can regenerate configuration if the JSONs change.
# All paths will be relative to the Mbed program root directory, or absolute if outside said directory.
json_sources: List[pathlib.Path] = []
def __setitem__(self, key: Hashable, item: Any) -> None:
"""Set an item based on its key."""
if key == CONFIG_SECTION:

View File

@ -6,13 +6,19 @@
include_guard(GLOBAL)
set(MBED_TOOLCHAIN "{{toolchain_name}}" CACHE STRING "")
set(MBED_TARGET "{{target_name}}" CACHE STRING "")
set(MBED_CPU_CORE "{{core}}" CACHE STRING "")
set(MBED_C_LIB "{{c_lib}}" CACHE STRING "")
set(MBED_PRINTF_LIB "{{printf_lib}}" CACHE STRING "")
set(MBED_OUTPUT_EXT "{{OUTPUT_EXT}}" CACHE STRING "")
set(MBED_GREENTEA_TEST_RESET_TIMEOUT "{{forced_reset_timeout}}" CACHE STRING "")
set(MBED_TOOLCHAIN "{{toolchain_name}}")
set(MBED_CPU_CORE "{{core}}")
set(MBED_C_LIB "{{c_lib}}")
set(MBED_PRINTF_LIB "{{printf_lib}}")
set(MBED_OUTPUT_EXT "{{OUTPUT_EXT}}")
set(MBED_GREENTEA_TEST_RESET_TIMEOUT "{{forced_reset_timeout}}")
# JSON files used to generate this config. If any of these change, the Python config generation
# scripts must be rerun.
set(MBED_CONFIG_JSON_SOURCE_FILES {% for json_source in json_sources | sort %}
"{{json_source.as_posix()}}"
{%- endfor %}
)
list(APPEND MBED_TARGET_SUPPORTED_C_LIBS {% for supported_c_lib in supported_c_libs %}
{{supported_c_lib}}

View File

@ -38,7 +38,7 @@ def generate_config(target_name: str, toolchain: str, program: MbedProgram) -> T
target_build_attributes = get_target_by_name(target_name, targets_data)
incorporate_memory_bank_data_from_cmsis(target_build_attributes, program)
config = assemble_config(
target_build_attributes, [program.root, program.mbed_os.root], program.files.app_config_file
target_build_attributes, program
)
# Process memory banks and save JSON data for other tools (e.g. memap) to use

View File

@ -1,140 +0,0 @@
#
# Copyright (c) 2020-2021 Arm Limited and Contributors. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
#
"""Command to build/compile an Mbed project using CMake."""
import os
import pathlib
import shutil
from typing import Optional, Tuple
import click
from mbed_tools.build import build_project, generate_build_system, generate_config, flash_binary
from mbed_tools.devices import find_connected_device, find_all_connected_devices
from mbed_tools.project import MbedProgram
from mbed_tools.sterm import terminal
@click.command(name="compile", help="Build an Mbed project.")
@click.option(
"-t",
"--toolchain",
type=click.Choice(["ARM", "GCC_ARM"], case_sensitive=False),
required=True,
help="The toolchain you are using to build your app.",
)
@click.option("-m", "--mbed-target", required=True, help="A build target for an Mbed-enabled device, e.g. K64F.")
@click.option("-b", "--profile", default="develop", help="The build type (release, develop or debug).")
@click.option("-c", "--clean", is_flag=True, default=False, help="Perform a clean build.")
@click.option(
"-p",
"--program-path",
default=os.getcwd(),
help="Path to local Mbed program. By default it is the current working directory.",
)
@click.option(
"--mbed-os-path", type=click.Path(), default=None, help="Path to local Mbed OS directory.",
)
@click.option(
"--custom-targets-json", type=click.Path(), default=None, help="Path to custom_targets.json.",
)
@click.option(
"--app-config", type=click.Path(), default=None, help="Path to application configuration file.",
)
@click.option(
"-f", "--flash", is_flag=True, default=False, help="Flash the binary onto a device",
)
@click.option(
"-s", "--sterm", is_flag=True, default=False, help="Launch a serial terminal to the device.",
)
@click.option(
"--baudrate",
default=9600,
show_default=True,
help="Change the serial baud rate (ignored unless --sterm is also given).",
)
def build(
program_path: str,
profile: str,
toolchain: str,
mbed_target: str,
clean: bool,
flash: bool,
sterm: bool,
baudrate: int,
mbed_os_path: str,
custom_targets_json: str,
app_config: str,
) -> None:
"""Configure and build an Mbed project using CMake and Ninja.
If the CMake configuration step has already been run previously (i.e a CMake build tree exists), then just try to
build the project immediately using Ninja.
Args:
program_path: Path to the Mbed project.
mbed_os_path: The path to the local Mbed OS directory.
profile: The Mbed build profile (debug, develop or release).
custom_targets_json: Path to custom_targets.json.
toolchain: The toolchain to use for the build.
mbed_target: The name of the Mbed target to build for.
app_config: the path to the application configuration file
clean: Perform a clean build.
flash: Flash the binary onto a device.
sterm: Open a serial terminal to the connected target.
baudrate: Change the serial baud rate (ignored unless --sterm is also given).
"""
mbed_target, target_id = _get_target_id(mbed_target)
cmake_build_subdir = pathlib.Path(mbed_target.upper(), profile.lower(), toolchain.upper())
if mbed_os_path is None:
program = MbedProgram.from_existing(pathlib.Path(program_path), cmake_build_subdir)
else:
program = MbedProgram.from_existing(pathlib.Path(program_path), cmake_build_subdir, pathlib.Path(mbed_os_path))
build_tree = program.files.cmake_build_dir
if clean and build_tree.exists():
shutil.rmtree(build_tree)
click.echo("Configuring project and generating build system...")
if custom_targets_json is not None:
program.files.custom_targets_json = pathlib.Path(custom_targets_json)
if app_config is not None:
program.files.app_config_file = pathlib.Path(app_config)
config, _ = generate_config(mbed_target.upper(), toolchain, program)
generate_build_system(program.root, build_tree, profile)
click.echo("Building Mbed project...")
build_project(build_tree)
if flash or sterm:
if target_id is not None or sterm:
devices = [find_connected_device(mbed_target, target_id)]
else:
devices = find_all_connected_devices(mbed_target)
if flash:
for dev in devices:
hex_file = "OUTPUT_EXT" in config and config["OUTPUT_EXT"] == "hex"
flashed_path = flash_binary(dev.mount_points[0].resolve(), program.root, build_tree, mbed_target, hex_file)
click.echo(f"Copied {str(flashed_path.resolve())} to {len(devices)} device(s).")
if sterm:
dev = devices[0]
if dev.serial_port is None:
raise click.ClickException(
f"The connected device {dev.mbed_board.board_name} does not have an associated serial port."
" Reconnect the device and try again."
)
terminal.run(dev.serial_port, baudrate)
def _get_target_id(target: str) -> Tuple[str, Optional[int]]:
if "[" in target:
target_name, target_id = target.replace("]", "").split("[", maxsplit=1)
if target_id.isdigit() and int(target_id) >= 0:
return (target_name, int(target_id))
raise click.ClickException("When using the format mbed-target[ID], ID must be a positive integer or 0.")
return (target, None)

View File

@ -25,8 +25,10 @@ from mbed_tools.build import generate_config
help="The toolchain you are using to build your app.",
)
@click.option("-m", "--mbed-target", required=True, help="A build target for an Mbed-enabled device, eg. K64F")
@click.option("-b", "--profile", default="develop", help="The build type (release, develop or debug).")
@click.option("-o", "--output-dir", type=click.Path(), default=None, help="Path to output directory.")
@click.option("-o", "--output-dir",
type=click.Path(path_type=pathlib.Path),
required=True,
help="Path to output directory (CMake binary dir)")
@click.option(
"-p",
"--program-path",
@ -43,10 +45,9 @@ from mbed_tools.build import generate_config
def configure(
toolchain: str,
mbed_target: str,
profile: str,
program_path: str,
mbed_os_path: str,
output_dir: str,
output_dir: pathlib.Path,
custom_targets_json: str,
app_config: str
) -> None:
@ -63,21 +64,17 @@ def configure(
custom_targets_json: the path to custom_targets.json
toolchain: the toolchain you are using (eg. GCC_ARM, ARM)
mbed_target: the target you are building for (eg. K64F)
profile: The Mbed build profile (debug, develop or release).
program_path: the path to the local Mbed program
mbed_os_path: the path to the local Mbed OS directory
output_dir: the path to the output directory
app_config: the path to the application configuration file
"""
cmake_build_subdir = pathlib.Path(mbed_target.upper(), profile.lower(), toolchain.upper())
if mbed_os_path is None:
program = MbedProgram.from_existing(pathlib.Path(program_path), cmake_build_subdir)
program = MbedProgram.from_existing(pathlib.Path(program_path), output_dir)
else:
program = MbedProgram.from_existing(pathlib.Path(program_path), cmake_build_subdir, pathlib.Path(mbed_os_path))
program = MbedProgram.from_existing(pathlib.Path(program_path), output_dir, pathlib.Path(mbed_os_path).resolve())
if custom_targets_json is not None:
program.files.custom_targets_json = pathlib.Path(custom_targets_json)
if output_dir is not None:
program.files.cmake_build_dir = pathlib.Path(output_dir)
if app_config is not None:
program.files.app_config_file = pathlib.Path(app_config)

View File

@ -15,7 +15,6 @@ from mbed_tools.lib.logging import set_log_level, MbedToolsHandler
from mbed_tools.cli.configure import configure
from mbed_tools.cli.list_connected_devices import list_connected_devices
from mbed_tools.cli.project_management import new, import_, deploy
from mbed_tools.cli.build import build
from mbed_tools.cli.sterm import sterm
from mbed_tools.cli.cmsis_mcu_descr import cmsis_mcu_descr
@ -78,7 +77,6 @@ cli.add_command(list_connected_devices, "detect")
cli.add_command(new, "new")
cli.add_command(deploy, "deploy")
cli.add_command(import_, "import")
cli.add_command(build, "compile")
cli.add_command(sterm, "sterm")
cli.add_command(cmsis_mcu_descr)

View File

@ -3,16 +3,24 @@
# SPDX-License-Identifier: Apache-2.0
#
"""Command to launch a serial terminal to a connected Mbed device."""
from typing import Any, Optional
from typing import Any, Optional, Tuple
import click
from mbed_tools.cli.build import _get_target_id
from mbed_tools.devices import find_connected_device, get_connected_devices
from mbed_tools.devices.exceptions import MbedDevicesError
from mbed_tools.sterm import terminal
def _get_target_id(target: str) -> Tuple[str, Optional[int]]:
if "[" in target:
target_name, target_id = target.replace("]", "").split("[", maxsplit=1)
if target_id.isdigit() and int(target_id) >= 0:
return (target_name, int(target_id))
raise click.ClickException("When using the format mbed-target[ID], ID must be a positive integer or 0.")
return (target, None)
@click.command(
help="Open a serial terminal to a connected Mbed Enabled device, or connect to a user-specified COM port."
)

View File

@ -99,12 +99,12 @@ class MbedProgramFiles:
)
@classmethod
def from_existing(cls, root_path: Path, build_subdir: Path) -> "MbedProgramFiles":
def from_existing(cls, root_path: Path, build_dir: Path) -> "MbedProgramFiles":
"""Create MbedProgramFiles from a directory containing an existing program.
Args:
root_path: The path containing the MbedProgramFiles.
build_subdir: The subdirectory of BUILD_DIR to use for CMake build.
build_dir: The directory to use for CMake build.
"""
app_config: Optional[Path] = None
if (root_path / APP_CONFIG_FILE_NAME_JSON5).exists():
@ -126,13 +126,12 @@ class MbedProgramFiles:
cmakelists_file = root_path / CMAKELISTS_FILE_NAME
if not cmakelists_file.exists():
logger.warning("No CMakeLists.txt found in the program root.")
cmake_build_dir = root_path / BUILD_DIR / build_subdir
return cls(
app_config_file=app_config,
mbed_os_ref=mbed_os_file,
cmakelists_file=cmakelists_file,
cmake_build_dir=cmake_build_dir,
cmake_build_dir=build_dir,
custom_targets_json=custom_targets_json,
)

View File

@ -4,6 +4,7 @@
#
"""Mbed Program abstraction layer."""
import logging
import pathlib
from pathlib import Path
from typing import Dict
@ -29,7 +30,7 @@ class MbedProgram:
* A collection of references to external libraries, defined in .lib files located in the program source tree
"""
def __init__(self, program_files: MbedProgramFiles, mbed_os: MbedOS) -> None:
def __init__(self, program_files: MbedProgramFiles, mbed_os: MbedOS, root_path: pathlib.Path) -> None:
"""Initialise the program attributes.
Args:
@ -37,7 +38,7 @@ class MbedProgram:
mbed_os: An instance of `MbedOS` holding paths to locations in the local copy of the Mbed OS source.
"""
self.files = program_files
self.root = self.files.mbed_os_ref.parent
self.root = root_path
self.mbed_os = mbed_os
@classmethod
@ -63,17 +64,17 @@ class MbedProgram:
program_files = MbedProgramFiles.from_new(dir_path)
logger.info(f"Creating git repository for the Mbed program '{dir_path}'")
mbed_os = MbedOS.from_new(dir_path / MBED_OS_DIR_NAME)
return cls(program_files, mbed_os)
return cls(program_files, mbed_os, dir_path)
@classmethod
def from_existing(
cls, dir_path: Path, build_subdir: Path, mbed_os_path: Path = None, check_mbed_os: bool = True,
cls, dir_path: Path, build_dir: Path, mbed_os_path: Path = None, check_mbed_os: bool = True,
) -> "MbedProgram":
"""Create an MbedProgram from an existing program directory.
Args:
dir_path: Directory containing an Mbed program.
build_subdir: The subdirectory for the CMake build tree.
build_dir: The directory for the CMake build tree.
mbed_os_path: Directory containing Mbed OS.
check_mbed_os: If True causes an exception to be raised if the Mbed OS source directory does not
exist.
@ -88,7 +89,7 @@ class MbedProgram:
program_root = dir_path
logger.info(f"Found existing Mbed program at path '{program_root}'")
program = MbedProgramFiles.from_existing(program_root, build_subdir)
program = MbedProgramFiles.from_existing(program_root, build_dir)
try:
mbed_os = MbedOS.from_existing(mbed_os_path, check_mbed_os)
@ -98,7 +99,7 @@ class MbedProgram:
"\nYou may need to resolve the mbed-os.lib reference. You can do this by performing a `deploy`."
)
return cls(program, mbed_os)
return cls(program, mbed_os, program_root)
def parse_url(name_or_url: str) -> Dict[str, str]:

View File

@ -1,338 +0,0 @@
#
# Copyright (c) 2020-2021 Arm Limited and Contributors. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
#
import contextlib
import os
import pathlib
from tempfile import TemporaryDirectory
from unittest import TestCase, mock
from click.testing import CliRunner
import sys
print(sys.path)
from mbed_tools.cli.build import build
from mbed_tools.project._internal.project_data import BUILD_DIR
from mbed_tools.build.config import CMAKE_CONFIG_FILE
DEFAULT_BUILD_ARGS = ["-t", "GCC_ARM", "-m", "K64F"]
DEFAULT_BUILD_SUBDIR = pathlib.Path("K64F", "develop", "GCC_ARM")
@contextlib.contextmanager
def mock_project_directory(
program, mbed_config_exists=False, build_tree_exists=False, build_subdir=DEFAULT_BUILD_SUBDIR
):
with TemporaryDirectory() as tmp_dir:
root = pathlib.Path(tmp_dir, "test-project")
root.mkdir()
program.root = root
program.files.cmake_build_dir = root / BUILD_DIR / build_subdir
program.files.cmake_config_file = root / BUILD_DIR / build_subdir / CMAKE_CONFIG_FILE
if mbed_config_exists:
program.files.cmake_config_file.parent.mkdir(exist_ok=True, parents=True)
program.files.cmake_config_file.touch(exist_ok=True)
if build_tree_exists:
program.files.cmake_build_dir.mkdir(exist_ok=True, parents=True)
yield
@mock.patch("mbed_tools.cli.build.generate_build_system")
@mock.patch("mbed_tools.cli.build.build_project")
@mock.patch("mbed_tools.cli.build.MbedProgram")
@mock.patch("mbed_tools.cli.build.generate_config")
class TestBuildCommand(TestCase):
def test_searches_for_mbed_program_at_default_project_path(
self, generate_config, mbed_program, build_project, generate_build_system
):
runner = CliRunner()
runner.invoke(build, DEFAULT_BUILD_ARGS)
mbed_program.from_existing.assert_called_once_with(pathlib.Path(os.getcwd()), DEFAULT_BUILD_SUBDIR)
def test_searches_for_mbed_program_at_user_defined_project_root(
self, generate_config, mbed_program, build_project, generate_build_system
):
project_path = "my-blinky"
runner = CliRunner()
runner.invoke(build, ["-p", project_path, *DEFAULT_BUILD_ARGS])
mbed_program.from_existing.assert_called_once_with(pathlib.Path(project_path), DEFAULT_BUILD_SUBDIR)
def test_calls_generate_build_system_if_build_tree_nonexistent(
self, generate_config, mbed_program, build_project, generate_build_system
):
program = mbed_program.from_existing()
with mock_project_directory(program, mbed_config_exists=True, build_tree_exists=False):
generate_config.return_value = [mock.MagicMock(), mock.MagicMock()]
runner = CliRunner()
runner.invoke(build, DEFAULT_BUILD_ARGS)
generate_build_system.assert_called_once_with(program.root, program.files.cmake_build_dir, "develop")
def test_generate_config_called_if_config_script_nonexistent(
self, generate_config, mbed_program, build_project, generate_build_system
):
program = mbed_program.from_existing()
with mock_project_directory(
program, mbed_config_exists=False, build_subdir=pathlib.Path("K64F", "develop", "GCC_ARM")
):
target = "k64f"
toolchain = "gcc_arm"
runner = CliRunner()
runner.invoke(build, ["-t", toolchain, "-m", target])
generate_config.assert_called_once_with(target.upper(), toolchain.upper(), program)
def test_raises_if_gen_config_toolchain_not_passed_when_required(
self, generate_config, mbed_program, build_project, generate_build_system
):
program = mbed_program.from_existing()
with mock_project_directory(program, mbed_config_exists=False):
target = "k64f"
runner = CliRunner()
result = runner.invoke(build, ["-m", target])
self.assertIsNotNone(result.exception)
self.assertRegex(result.output, "--toolchain")
def test_raises_if_gen_config_target_not_passed_when_required(
self, generate_config, mbed_program, build_project, generate_build_system
):
program = mbed_program.from_existing()
with mock_project_directory(program, mbed_config_exists=False):
toolchain = "gcc_arm"
runner = CliRunner()
result = runner.invoke(build, ["-t", toolchain])
self.assertIsNotNone(result.exception)
self.assertRegex(result.output, "--mbed-target")
def test_raises_if_target_identifier_not_int(
self, generate_config, mbed_program, build_project, generate_build_system
):
target = "K64F[foo]"
result = CliRunner().invoke(build, ["-m", target, "-t", "gcc_arm"])
self.assertIsNotNone(result.exception)
self.assertRegex(result.output, "ID")
def test_raises_if_target_identifier_negative(
self, generate_config, mbed_program, build_project, generate_build_system
):
target = "K64F[-1]"
result = CliRunner().invoke(build, ["-m", target, "-t", "gcc_arm"])
self.assertIsNotNone(result.exception)
self.assertRegex(result.output, "ID")
def test_build_system_regenerated_when_mbed_os_path_passed(
self, generate_config, mbed_program, build_project, generate_build_system
):
program = mbed_program.from_existing()
with mock_project_directory(
program,
mbed_config_exists=True,
build_tree_exists=True,
build_subdir=pathlib.Path("K64F", "develop", "GCC_ARM"),
):
generate_config.return_value = [mock.MagicMock(), mock.MagicMock()]
toolchain = "gcc_arm"
target = "k64f"
mbed_os_path = "./extern/mbed-os"
runner = CliRunner()
runner.invoke(build, ["-t", toolchain, "-m", target, "--mbed-os-path", mbed_os_path])
generate_config.assert_called_once_with(target.upper(), toolchain.upper(), program)
generate_build_system.assert_called_once_with(program.root, program.files.cmake_build_dir, "develop")
def test_custom_targets_location_used_when_passed(
self, generate_config, mbed_program, build_project, generate_build_system
):
program = mbed_program.from_existing()
with mock_project_directory(program, mbed_config_exists=True, build_tree_exists=True):
toolchain = "gcc_arm"
target = "k64f"
custom_targets_json_path = pathlib.Path("custom", "custom_targets.json")
runner = CliRunner()
runner.invoke(build, ["-t", toolchain, "-m", target, "--custom-targets-json", custom_targets_json_path])
generate_config.assert_called_once_with(target.upper(), toolchain.upper(), program)
self.assertEqual(program.files.custom_targets_json, custom_targets_json_path)
def test_app_config_used_when_passed(
self, generate_config, mbed_program, build_project, generate_build_system
):
program = mbed_program.from_existing()
with mock_project_directory(program, mbed_config_exists=True, build_tree_exists=True):
toolchain = "gcc_arm"
target = "k64f"
app_config_path = pathlib.Path("alternative_config.json")
runner = CliRunner()
runner.invoke(build, ["-t", toolchain, "-m", target, "--app-config", app_config_path])
generate_config.assert_called_once_with(target.upper(), toolchain.upper(), program)
self.assertEqual(program.files.app_config_file, app_config_path)
def test_profile_used_when_passed(
self, generate_config, mbed_program, build_project, generate_build_system
):
program = mbed_program.from_existing()
mbed_program.reset_mock() # clear call count from previous line
with mock_project_directory(program, mbed_config_exists=True, build_tree_exists=True):
generate_config.return_value = [mock.MagicMock(), mock.MagicMock()]
toolchain = "gcc_arm"
target = "k64f"
profile = "release"
runner = CliRunner()
runner.invoke(build, ["-t", toolchain, "-m", target, "--profile", profile])
mbed_program.from_existing.assert_called_once_with(
pathlib.Path(os.getcwd()),
pathlib.Path(target.upper(), profile, toolchain.upper())
)
generate_config.assert_called_once_with(target.upper(), toolchain.upper(), program)
generate_build_system.assert_called_once_with(program.root, program.files.cmake_build_dir, profile)
def test_build_folder_removed_when_clean_flag_passed(
self, generate_config, mbed_program, build_project, generate_build_system
):
program = mbed_program.from_existing()
with mock_project_directory(
program,
mbed_config_exists=True,
build_tree_exists=True,
build_subdir=pathlib.Path("K64F", "develop", "GCC_ARM"),
):
generate_config.return_value = [mock.MagicMock(), mock.MagicMock()]
toolchain = "gcc_arm"
target = "k64f"
runner = CliRunner()
runner.invoke(build, ["-t", toolchain, "-m", target, "-c"])
generate_config.assert_called_once_with(target.upper(), toolchain.upper(), program)
generate_build_system.assert_called_once_with(program.root, program.files.cmake_build_dir, "develop")
self.assertFalse(program.files.cmake_build_dir.exists())
@mock.patch("mbed_tools.cli.build.flash_binary")
@mock.patch("mbed_tools.cli.build.find_all_connected_devices")
def test_build_flash_options_bin_target(
self,
mock_find_devices,
flash_binary,
generate_config,
mbed_program,
build_project,
generate_build_system,
):
# A target with bin images does not need OUTPUT_EXT defined
generate_config.return_value = [mock.MagicMock(), mock.MagicMock()]
mock_find_devices.return_value = [mock.MagicMock()]
runner = CliRunner()
runner.invoke(build, ["--flash", *DEFAULT_BUILD_ARGS])
call_args = flash_binary.call_args
args, kwargs = call_args
flash_binary.assert_called_once_with(args[0], args[1], args[2], args[3], False)
@mock.patch("mbed_tools.cli.build.flash_binary")
@mock.patch("mbed_tools.cli.build.find_all_connected_devices")
def test_build_flash_options_hex_target(
self,
mock_find_devices,
flash_binary,
generate_config,
mbed_program,
build_project,
generate_build_system,
):
generate_config.return_value = [{"OUTPUT_EXT": "hex"}, mock.MagicMock()]
mock_find_devices.return_value = [mock.MagicMock()]
runner = CliRunner()
runner.invoke(build, ["--flash", *DEFAULT_BUILD_ARGS])
call_args = flash_binary.call_args
args, kwargs = call_args
flash_binary.assert_called_once_with(args[0], args[1], args[2], args[3], True)
@mock.patch("mbed_tools.cli.build.flash_binary")
@mock.patch("mbed_tools.cli.build.find_all_connected_devices")
def test_build_flash_both_two_devices(
self,
mock_find_devices,
flash_binary,
generate_config,
mbed_program,
build_project,
generate_build_system,
):
generate_config.return_value = [mock.MagicMock(), mock.MagicMock()]
mock_find_devices.return_value = [mock.MagicMock(), mock.MagicMock()]
runner = CliRunner()
runner.invoke(build, ["--flash", *DEFAULT_BUILD_ARGS])
self.assertEqual(flash_binary.call_count, 2)
@mock.patch("mbed_tools.cli.build.flash_binary")
@mock.patch("mbed_tools.cli.build.find_connected_device")
def test_build_flash_only_identifier_device(
self,
mock_find_device,
flash_binary,
generate_config,
mbed_program,
build_project,
generate_build_system,
):
generate_config.return_value = [mock.MagicMock(), mock.MagicMock()]
mock_find_device.return_value = mock.MagicMock()
runner = CliRunner()
runner.invoke(build, ["--flash", "-m", "K64F[1]", "-t", "GCC_ARM"])
self.assertEqual(flash_binary.call_count, 1)
@mock.patch("mbed_tools.cli.build.terminal")
@mock.patch("mbed_tools.cli.build.find_connected_device")
def test_sterm_is_started_when_flag_passed(
self, mock_find_device, mock_terminal, generate_config, mbed_program, build_project, generate_build_system
):
target = "K64F"
serial_port = "tty.k64f"
baud = 115200
mock_find_device.return_value = mock.Mock(serial_port=serial_port)
generate_config.return_value = [mock.MagicMock(), mock.MagicMock()]
CliRunner().invoke(build, ["-m", target, "-t", "gcc_arm", "--sterm", "--baudrate", baud])
mock_find_device.assert_called_once_with(target, None)
mock_terminal.run.assert_called_once_with(serial_port, baud)
@mock.patch("mbed_tools.cli.build.terminal")
@mock.patch("mbed_tools.cli.build.find_connected_device")
def test_raises_if_device_does_not_have_serial_port_and_sterm_flag_given(
self, mock_find_device, mock_terminal, generate_config, mbed_program, build_project, generate_build_system
):
target = "K64F"
serial_port = None
mock_find_device.return_value = mock.Mock(serial_port=serial_port)
generate_config.return_value = [mock.MagicMock(), mock.MagicMock()]
output = CliRunner().invoke(build, ["-m", target, "-t", "gcc_arm", "--sterm"])
self.assertEqual(type(output.exception), SystemExit)
mock_terminal.assert_not_called()

View File

@ -15,14 +15,14 @@ class TestConfigureCommand(TestCase):
@mock.patch("mbed_tools.cli.configure.generate_config")
@mock.patch("mbed_tools.cli.configure.MbedProgram")
def test_generate_config_called_with_correct_arguments(self, program, generate_config):
CliRunner().invoke(configure, ["-m", "k64f", "-t", "gcc_arm"])
CliRunner().invoke(configure, ["-m", "k64f", "-t", "gcc_arm", "-o", "some_output_dir"])
generate_config.assert_called_once_with("K64F", "GCC_ARM", program.from_existing())
@mock.patch("mbed_tools.cli.configure.generate_config")
@mock.patch("mbed_tools.cli.configure.MbedProgram")
def test_generate_config_called_with_mbed_os_path(self, program, generate_config):
CliRunner().invoke(configure, ["-m", "k64f", "-t", "gcc_arm", "--mbed-os-path", "./extern/mbed-os"])
CliRunner().invoke(configure, ["-m", "k64f", "-t", "gcc_arm", "--mbed-os-path", "./extern/mbed-os", "-o", "some_output_dir"])
generate_config.assert_called_once_with("K64F", "GCC_ARM", program.from_existing())
@ -32,29 +32,19 @@ class TestConfigureCommand(TestCase):
program = program.from_existing()
custom_targets_json_path = pathlib.Path("custom", "custom_targets.json")
CliRunner().invoke(
configure, ["-t", "gcc_arm", "-m", "k64f", "--custom-targets-json", custom_targets_json_path]
configure, ["-t", "gcc_arm", "-m", "k64f", "--custom-targets-json", custom_targets_json_path, "-o", "some_output_dir"]
)
generate_config.assert_called_once_with("K64F", "GCC_ARM", program)
self.assertEqual(program.files.custom_targets_json, custom_targets_json_path)
@mock.patch("mbed_tools.cli.configure.generate_config")
@mock.patch("mbed_tools.cli.configure.MbedProgram")
def test_custom_output_directory_used_when_passed(self, program, generate_config):
program = program.from_existing()
output_dir = pathlib.Path("build")
CliRunner().invoke(configure, ["-t", "gcc_arm", "-m", "k64f", "-o", output_dir])
generate_config.assert_called_once_with("K64F", "GCC_ARM", program)
self.assertEqual(program.files.cmake_build_dir, output_dir)
@mock.patch("mbed_tools.cli.configure.generate_config")
@mock.patch("mbed_tools.cli.configure.MbedProgram")
def test_app_config_used_when_passed(self, program, generate_config):
program = program.from_existing()
app_config_path = pathlib.Path("alternative_config.json")
CliRunner().invoke(
configure, ["-t", "gcc_arm", "-m", "k64f", "--app-config", app_config_path]
configure, ["-t", "gcc_arm", "-m", "k64f", "--app-config", app_config_path, "-o", "some_output_dir"]
)
generate_config.assert_called_once_with("K64F", "GCC_ARM", program)
@ -62,20 +52,19 @@ class TestConfigureCommand(TestCase):
@mock.patch("mbed_tools.cli.configure.generate_config")
@mock.patch("mbed_tools.cli.configure.MbedProgram")
def test_profile_used_when_passed(self, program, generate_config):
def test_output_dir_passed(self, program, generate_config):
test_program = program.from_existing()
program.reset_mock() # clear call count from previous line
toolchain = "gcc_arm"
target = "k64f"
profile = "release"
CliRunner().invoke(
configure, ["-t", toolchain, "-m", target, "--profile", profile]
configure, ["-t", toolchain, "-m", target, "-o", "some_output_dir"]
)
program.from_existing.assert_called_once_with(
pathlib.Path("."),
pathlib.Path(target.upper(), profile, toolchain.upper())
pathlib.Path("some_output_dir")
)
generate_config.assert_called_once_with("K64F", "GCC_ARM", test_program)

View File

@ -10,7 +10,7 @@ import pytest
from mbed_tools.project import MbedProgram
from mbed_tools.project.exceptions import ExistingProgram, ProgramNotFound, MbedOSNotFound
from mbed_tools.project.mbed_program import _find_program_root, parse_url
from mbed_tools.project._internal.project_data import MbedProgramFiles
from mbed_tools.project._internal.project_data import MbedProgramFiles, BUILD_DIR
from python_tests.mbed_tools.project.factories import make_mbed_program_files, make_mbed_os_files
@ -40,7 +40,7 @@ class TestInitialiseProgram:
program = from_new_set_target_toolchain(program_root)
assert program.files == MbedProgramFiles.from_existing(program_root, DEFAULT_BUILD_SUBDIR)
assert program.files == MbedProgramFiles.from_existing(program_root, program_root / BUILD_DIR / DEFAULT_BUILD_SUBDIR)
def test_from_new_local_dir_generates_valid_program_creating_directory_in_cwd(self, tmp_path):
old_cwd = os.getcwd()
@ -52,7 +52,7 @@ class TestInitialiseProgram:
program = from_new_set_target_toolchain(program_root)
assert program.files == MbedProgramFiles.from_existing(program_root, DEFAULT_BUILD_SUBDIR)
assert program.files == MbedProgramFiles.from_existing(program_root, program_root / BUILD_DIR / DEFAULT_BUILD_SUBDIR)
finally:
os.chdir(old_cwd)
@ -64,7 +64,7 @@ class TestInitialiseProgram:
program = from_new_set_target_toolchain(program_root)
assert program.files == MbedProgramFiles.from_existing(program_root, DEFAULT_BUILD_SUBDIR)
assert program.files == MbedProgramFiles.from_existing(program_root, program_root / BUILD_DIR / DEFAULT_BUILD_SUBDIR)
def test_from_existing_raises_if_path_is_not_a_program(self, tmp_path):
fs_root = pathlib.Path(tmp_path, "foo")
@ -72,21 +72,21 @@ class TestInitialiseProgram:
program_root = fs_root / "programfoo"
with pytest.raises(ProgramNotFound):
MbedProgram.from_existing(program_root, DEFAULT_BUILD_SUBDIR)
MbedProgram.from_existing(program_root, program_root / BUILD_DIR / DEFAULT_BUILD_SUBDIR)
def test_from_existing_raises_if_no_mbed_os_dir_found_and_check_mbed_os_is_true(self, tmp_path):
fs_root = pathlib.Path(tmp_path, "foo")
make_mbed_program_files(fs_root)
with pytest.raises(MbedOSNotFound):
MbedProgram.from_existing(fs_root, DEFAULT_BUILD_SUBDIR, check_mbed_os=True)
MbedProgram.from_existing(fs_root, fs_root / BUILD_DIR / DEFAULT_BUILD_SUBDIR, check_mbed_os=True)
def test_from_existing_returns_valid_program(self, tmp_path):
fs_root = pathlib.Path(tmp_path, "foo")
make_mbed_program_files(fs_root)
make_mbed_os_files(fs_root / "mbed-os")
program = MbedProgram.from_existing(fs_root, DEFAULT_BUILD_SUBDIR)
program = MbedProgram.from_existing(fs_root, fs_root / BUILD_DIR / DEFAULT_BUILD_SUBDIR)
assert program.files.app_config_file.exists()
assert program.mbed_os.root.exists()
@ -98,7 +98,7 @@ class TestInitialiseProgram:
make_mbed_program_files(fs_root)
make_mbed_os_files(mbed_os_path)
program = MbedProgram.from_existing(fs_root, DEFAULT_BUILD_SUBDIR, mbed_os_path)
program = MbedProgram.from_existing(fs_root, fs_root / BUILD_DIR / DEFAULT_BUILD_SUBDIR, mbed_os_path)
assert program.files.app_config_file.exists()
assert program.mbed_os.root.exists()

View File

@ -53,8 +53,9 @@ class TestConfigureRegression(TestCase):
pathlib.Path(tmpDirPath / "mbed-os" / "targets").mkdir()
pathlib.Path(tmpDirPath / "mbed-os" / "targets" / "targets.json5").write_text(target_json)
pathlib.Path(tmpDirPath / "mbed-os" / "targets" / "cmsis_mcu_descriptions.json5").write_text("{}")
pathlib.Path(tmpDirPath / "cmake-build-debug").mkdir()
result = CliRunner().invoke(
configure, ["-m", "Target", "-t", "gcc_arm", "-p", tmpDir], catch_exceptions=False
configure, ["-m", "Target", "-t", "gcc_arm", "-p", tmpDir, "-o", str(tmpDirPath / "cmake-build-debug")], catch_exceptions=False
)
self.assertIn("mbed_config.cmake has been generated and written to", result.output)