Skip to content

Commit 0865b38

Browse files
urutvaPatater
authored andcommitted
build: Add flash option to build command
Add `-f/--flash` option to `build` command to flash the generated binary onto the device connected to host machine. By default `.bin` file will be used for flashing. If `--hex-file` option is passed on the command line, then `.hex` will be used.
1 parent 3d7411f commit 0865b38

File tree

8 files changed

+293
-2
lines changed

8 files changed

+293
-2
lines changed

news/20201112095757.feature

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add flash option to build command

src/mbed_tools/build/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,4 @@
1212
"""
1313
from mbed_tools.build.build import build_project, generate_build_system
1414
from mbed_tools.build.config import generate_config
15+
from mbed_tools.build.flash import flash_binary

src/mbed_tools/build/exceptions.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,11 @@ class MbedBuildError(ToolsError):
1212

1313
class InvalidExportOutputDirectory(MbedBuildError):
1414
"""It is not possible to export to the provided output directory."""
15+
16+
17+
class BinaryFileNotFoundError(MbedBuildError):
18+
"""The binary file (.bin/.hex) cannot be found in cmake_build directory."""
19+
20+
21+
class DeviceNotFoundError(MbedBuildError):
22+
"""The requested device is not connected to your system."""

src/mbed_tools/build/flash.py

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
#
2+
# Copyright (C) 2020 Arm Mbed. All rights reserved.
3+
# SPDX-License-Identifier: Apache-2.0
4+
#
5+
"""Flash binary onto the connected device."""
6+
7+
import shutil
8+
import os
9+
import pathlib
10+
import platform
11+
12+
from mbed_tools.devices.device import Device
13+
from mbed_tools.devices.devices import get_connected_devices
14+
from mbed_tools.build.exceptions import BinaryFileNotFoundError, DeviceNotFoundError
15+
16+
17+
def _flash_dev(disk: pathlib.Path, image_path: pathlib.Path) -> None:
18+
"""Flash device using copy method.
19+
20+
Args:
21+
disk: Device mount point.
22+
image_path: Image file to be copied to device.
23+
"""
24+
shutil.copy(image_path, disk, follow_symlinks=False)
25+
if not platform.system() == "Windows":
26+
os.sync()
27+
28+
29+
def _find_connected_device(mbed_target: str) -> Device:
30+
"""Check if requested device is connected to the system.
31+
32+
Look through the devices connected to the system and if a requested device is found then return it.
33+
34+
Args:
35+
mbed_target: The name of the Mbed target to build for.
36+
37+
Returns:
38+
Device if requested device is connected to system.
39+
40+
Raises:
41+
DeviceNotFoundError: the requested board is not connected to the system
42+
"""
43+
connected_devices = get_connected_devices()
44+
for device in connected_devices.identified_devices:
45+
if device.mbed_board.board_type.upper() == mbed_target.upper():
46+
return device
47+
48+
raise DeviceNotFoundError("The target board you compiled for is not connected to your system.")
49+
50+
51+
def _build_binary_file_path(program_path: pathlib.Path, build_dir: pathlib.Path, hex_file: bool) -> pathlib.Path:
52+
"""Build binary file name.
53+
54+
Args:
55+
program_path: Path to the Mbed project.
56+
build_dir: Path to the CMake build folder.
57+
hex_file: Use hex file.
58+
59+
Returns:
60+
Path to binary file.
61+
62+
Raises:
63+
BinaryFileNotFoundError: binary file not found in the path specified
64+
"""
65+
fw_fbase = build_dir / program_path.name
66+
fw_file = fw_fbase.with_suffix(".hex" if hex_file else ".bin")
67+
if not fw_file.exists():
68+
raise BinaryFileNotFoundError(f"Build program file (firmware) not found {fw_file}")
69+
return fw_file
70+
71+
72+
def flash_binary(program_path: pathlib.Path, build_dir: pathlib.Path, mbed_target: str, hex_file: bool) -> None:
73+
"""Flash binary onto a device.
74+
75+
Look through the connected devices and flash the binary if the connected and built target matches.
76+
77+
Args:
78+
program_path: Path to the Mbed project.
79+
build_dir: Path to the CMake build folder.
80+
mbed_target: The name of the Mbed target to build for.
81+
hex_file: Use hex file.
82+
"""
83+
device = _find_connected_device(mbed_target)
84+
fw_file = _build_binary_file_path(program_path, build_dir, hex_file)
85+
_flash_dev(device.mount_points[0].resolve(), fw_file)

src/mbed_tools/cli/build.py

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
import click
1111

12-
from mbed_tools.build import build_project, generate_build_system, generate_config
12+
from mbed_tools.build import build_project, generate_build_system, generate_config, flash_binary
1313
from mbed_tools.project import MbedProgram
1414

1515

@@ -29,7 +29,21 @@
2929
default=os.getcwd(),
3030
help="Path to local Mbed program. By default it is the current working directory.",
3131
)
32-
def build(program_path: str, build_type: str, toolchain: str = "", mbed_target: str = "", clean: bool = False) -> None:
32+
@click.option(
33+
"-f", "--flash", is_flag=True, default=False, help="Flash the binary onto a device",
34+
)
35+
@click.option(
36+
"--hex-file", is_flag=True, default=False, help="Use hex file, this option should be used with '-f/--flash' option",
37+
)
38+
def build(
39+
program_path: str,
40+
build_type: str,
41+
toolchain: str = "",
42+
mbed_target: str = "",
43+
clean: bool = False,
44+
flash: bool = False,
45+
hex_file: bool = False,
46+
) -> None:
3347
"""Configure and build an Mbed project using CMake and Ninja.
3448
3549
If the project has already been configured and contains '.mbedbuild/mbed_config.cmake', this command will skip the
@@ -44,6 +58,8 @@ def build(program_path: str, build_type: str, toolchain: str = "", mbed_target:
4458
toolchain: The toolchain to use for the build.
4559
mbed_target: The name of the Mbed target to build for.
4660
clean: Perform a clean build.
61+
flash: Flash the binary onto a device.
62+
hex_file: Use hex file, this option should be used with '-f/--flash' option.
4763
"""
4864
program = MbedProgram.from_existing(pathlib.Path(program_path))
4965
mbed_config_file = program.files.cmake_config_file
@@ -60,6 +76,11 @@ def build(program_path: str, build_type: str, toolchain: str = "", mbed_target:
6076
click.echo("Building Mbed project...")
6177
build_project(build_tree)
6278

79+
if flash:
80+
flash_binary(program.root, build_tree, mbed_target, hex_file)
81+
elif hex_file:
82+
click.echo("'--hex-file' option should be used with '-f/--flash' option")
83+
6384

6485
def _validate_target_and_toolchain_args(target: str, toolchain: str) -> None:
6586
if not all([toolchain, target]):

tests/build/factories.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
#
2+
# Copyright (C) 2020 Arm Mbed. All rights reserved.
3+
# SPDX-License-Identifier: Apache-2.0
4+
#
5+
import factory
6+
import pathlib
7+
8+
from typing import List
9+
10+
from mbed_tools.devices.device import Device, ConnectedDevices
11+
from mbed_tools.targets.board import Board
12+
13+
14+
class BoardFactory(factory.Factory):
15+
class Meta:
16+
model = Board
17+
18+
board_type = "TEST"
19+
board_name = ""
20+
product_code = ""
21+
target_type = ""
22+
slug = ""
23+
build_variant = []
24+
mbed_os_support = []
25+
mbed_enabled = []
26+
27+
28+
class DeviceFactory(factory.Factory):
29+
class Meta:
30+
model = Device
31+
32+
serial_port = ""
33+
serial_number = ""
34+
mount_points = [pathlib.Path(".")]
35+
mbed_board = BoardFactory()
36+
37+
38+
class ConnectedDevicesFactory(factory.Factory):
39+
class Meta:
40+
model = ConnectedDevices
41+
42+
identified_devices: List[Device]
43+
unidentified_devices: List[Device]

tests/build/test_flash.py

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
#
2+
# Copyright (C) 2020 Arm Mbed. All rights reserved.
3+
# SPDX-License-Identifier: Apache-2.0
4+
#
5+
6+
import pathlib
7+
import tempfile
8+
9+
from unittest import TestCase, mock
10+
11+
from mbed_tools.devices.device import ConnectedDevices
12+
from mbed_tools.build.flash import flash_binary, _build_binary_file_path, _find_connected_device, _flash_dev
13+
from mbed_tools.build.exceptions import BinaryFileNotFoundError, DeviceNotFoundError
14+
15+
from tests.build.factories import DeviceFactory, ConnectedDevicesFactory
16+
17+
18+
@mock.patch("mbed_tools.build.flash._find_connected_device")
19+
@mock.patch("mbed_tools.build.flash._build_binary_file_path")
20+
@mock.patch("mbed_tools.build.flash._flash_dev")
21+
class TestFlashBinary(TestCase):
22+
def test_check_flashing(self, _flash_dev, _build_binary_file_path, _find_connected_device):
23+
test_device = DeviceFactory()
24+
25+
_find_connected_device.return_value = test_device
26+
_flash_dev.return_value = True
27+
28+
with tempfile.TemporaryDirectory() as tmpDir:
29+
base_dir = pathlib.Path(tmpDir)
30+
build_dir = base_dir / "cmake_build"
31+
build_dir.mkdir()
32+
bin_file = base_dir.name + ".bin"
33+
bin_file = build_dir / bin_file
34+
bin_file.touch()
35+
_build_binary_file_path.return_value = bin_file
36+
37+
flash_binary(base_dir, build_dir, "TEST", False)
38+
39+
_find_connected_device.assert_called_once_with("TEST")
40+
_build_binary_file_path.assert_called_once_with(base_dir, build_dir, False)
41+
_flash_dev.assert_called_once_with(test_device.mount_points[0].resolve(), bin_file)
42+
43+
44+
class TestBuildBinFilePath(TestCase):
45+
def test_build_bin_file_path(self):
46+
with tempfile.TemporaryDirectory() as tmpDir:
47+
base_dir = pathlib.Path(tmpDir)
48+
build_dir = base_dir / "cmake_build"
49+
build_dir.mkdir()
50+
bin_file = base_dir.name + ".bin"
51+
bin_file = build_dir / bin_file
52+
bin_file.touch()
53+
54+
self.assertEqual(_build_binary_file_path(base_dir, build_dir, False), bin_file)
55+
56+
def test_build_hex_file_path(self):
57+
with tempfile.TemporaryDirectory() as tmpDir:
58+
base_dir = pathlib.Path(tmpDir)
59+
build_dir = base_dir / "cmake_build"
60+
build_dir.mkdir()
61+
bin_file = base_dir.name + ".hex"
62+
bin_file = build_dir / bin_file
63+
bin_file.touch()
64+
65+
self.assertEqual(_build_binary_file_path(base_dir, build_dir, True), bin_file)
66+
67+
def test_missing_binary_file(self):
68+
with tempfile.TemporaryDirectory() as tmpDir:
69+
base_dir = pathlib.Path(tmpDir)
70+
build_dir = base_dir / "cmake_build"
71+
build_dir.mkdir()
72+
73+
with self.assertRaises(BinaryFileNotFoundError):
74+
_build_binary_file_path(base_dir, build_dir, False)
75+
76+
77+
@mock.patch("mbed_tools.build.flash.get_connected_devices")
78+
class TestFindConnectedDevices(TestCase):
79+
def test_check_missing_device(self, get_connected_devices):
80+
get_connected_devices.return_value = ConnectedDevicesFactory()
81+
with self.assertRaises(DeviceNotFoundError):
82+
_find_connected_device("TEST")
83+
84+
def test_connected_device(self, get_connected_devices):
85+
test_device = DeviceFactory()
86+
87+
test_connected_devices = ConnectedDevices()
88+
test_connected_devices.identified_devices = [test_device]
89+
get_connected_devices.return_value = test_connected_devices
90+
91+
self.assertEqual(_find_connected_device("TEST"), test_device)
92+
93+
94+
@mock.patch("mbed_tools.build.flash.shutil.copy")
95+
class TestCopyToDevice(TestCase):
96+
def test_copy_to_device(self, copy):
97+
test_device = DeviceFactory()
98+
99+
with tempfile.TemporaryDirectory() as tmpDir:
100+
base_dir = pathlib.Path(tmpDir)
101+
build_dir = base_dir / "cmake_build"
102+
build_dir.mkdir()
103+
bin_file = base_dir.name + ".bin"
104+
bin_file = build_dir / bin_file
105+
bin_file.touch()
106+
_flash_dev(test_device.mount_points[0].resolve(), bin_file)
107+
108+
copy.assert_called_once_with(bin_file, test_device.mount_points[0].resolve(), follow_symlinks=False)

tests/cli/test_build.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,3 +143,27 @@ def test_build_folder_removed_when_clean_flag_passed(
143143
generate_config.assert_called_once_with(target.upper(), toolchain.upper(), program)
144144
generate_build_system.assert_called_once_with(program.root, program.files.cmake_build_dir, "develop")
145145
self.assertFalse(program.files.cmake_build_dir.exists())
146+
147+
@mock.patch("mbed_tools.cli.build.flash_binary")
148+
def test_build_flash_option(
149+
self, flash_binary, generate_config, mbed_program, build_project, generate_build_system
150+
):
151+
runner = CliRunner()
152+
runner.invoke(build, ["--flash"])
153+
flash_binary.assert_called_once()
154+
155+
@mock.patch("mbed_tools.cli.build.flash_binary")
156+
def test_build_flash_and_hex_file_options(
157+
self, flash_binary, generate_config, mbed_program, build_project, generate_build_system
158+
):
159+
runner = CliRunner()
160+
runner.invoke(build, ["--flash", "--hex-file"])
161+
call_args = flash_binary.call_args
162+
args, kwargs = call_args
163+
flash_binary.assert_called_once_with(args[0], args[1], args[2], True)
164+
165+
def test_build_only_hex_file_option(self, generate_config, mbed_program, build_project, generate_build_system):
166+
runner = CliRunner()
167+
result = runner.invoke(build, ["--hex-file"])
168+
169+
self.assertRegex(result.output, "-f/--flash")

0 commit comments

Comments
 (0)