├── .gitattributes
├── .adr-dir
├── requirements-dev.txt
├── src
└── mbed_tools
│ ├── project
│ ├── _internal
│ │ ├── templates
│ │ │ ├── gitignore.tmpl
│ │ │ ├── __init__.py
│ │ │ ├── main.tmpl
│ │ │ └── CMakeLists.tmpl
│ │ ├── __init__.py
│ │ ├── render_templates.py
│ │ └── progress.py
│ ├── __init__.py
│ ├── exceptions.py
│ └── project.py
│ ├── lib
│ ├── __init__.py
│ ├── exceptions.py
│ ├── json_helpers.py
│ ├── python_helpers.py
│ └── logging.py
│ ├── sterm
│ └── __init__.py
│ ├── __init__.py
│ ├── devices
│ ├── _internal
│ │ ├── linux
│ │ │ ├── __init__.py
│ │ │ └── device_detector.py
│ │ ├── darwin
│ │ │ ├── __init__.py
│ │ │ ├── ioreg.py
│ │ │ ├── diskutil.py
│ │ │ └── system_profiler.py
│ │ ├── windows
│ │ │ ├── __init__.py
│ │ │ ├── component_descriptor_utils.py
│ │ │ ├── serial_port_data_loader.py
│ │ │ ├── logical_disk.py
│ │ │ ├── disk_partition_logical_disk_relationships.py
│ │ │ ├── usb_controller.py
│ │ │ ├── device_detector.py
│ │ │ ├── disk_partition.py
│ │ │ ├── usb_hub.py
│ │ │ ├── device_instance_id.py
│ │ │ ├── serial_port.py
│ │ │ ├── system_data_loader.py
│ │ │ ├── volume_set.py
│ │ │ └── usb_hub_data_loader.py
│ │ ├── __init__.py
│ │ ├── base_detector.py
│ │ ├── exceptions.py
│ │ ├── detect_candidate_devices.py
│ │ ├── candidate_device.py
│ │ └── resolve_board.py
│ ├── exceptions.py
│ ├── __init__.py
│ └── devices.py
│ ├── build
│ ├── _internal
│ │ ├── __init__.py
│ │ ├── config
│ │ │ ├── __init__.py
│ │ │ └── config.py
│ │ ├── templates
│ │ │ ├── __init__.py
│ │ │ └── mbed_config.tmpl
│ │ ├── write_files.py
│ │ └── cmake_file.py
│ ├── __init__.py
│ ├── exceptions.py
│ ├── build.py
│ ├── flash.py
│ └── config.py
│ ├── targets
│ ├── _internal
│ │ ├── __init__.py
│ │ ├── exceptions.py
│ │ └── targets_json_parsers
│ │ │ └── __init__.py
│ ├── exceptions.py
│ ├── __init__.py
│ ├── get_target.py
│ ├── env.py
│ ├── boards.py
│ └── board.py
│ └── cli
│ ├── __init__.py
│ ├── sterm.py
│ ├── main.py
│ ├── configure.py
│ └── project_management.py
├── docs
├── howto
│ └── mbed-tools-howto.md
└── architecture
│ └── decisions
│ ├── 0001-record-architecture-decisions.md
│ └── 0002-move-mbed-config-header-defines-to-cmake.md
├── azure-pipelines
├── steps
│ ├── override-checkout.yml
│ ├── publish-code-coverage-results.yml
│ └── determine-current-branch.yml
├── prep-release.yml
├── build-and-deploy.yml
└── analyse-and-test.yml
├── tests
├── __init__.py
├── build
│ ├── __init__.py
│ ├── _internal
│ │ ├── __init__.py
│ │ ├── config
│ │ │ ├── __init__.py
│ │ │ └── factories.py
│ │ ├── test_write_files.py
│ │ └── test_cmake_file.py
│ ├── factories.py
│ ├── test_build.py
│ └── test_flash.py
├── cli
│ ├── __init__.py
│ ├── test_devices_command_integration.py
│ ├── test_sterm.py
│ └── test_configure.py
├── ci_scripts
│ ├── __init__.py
│ └── test_bump_version.py
├── project
│ ├── __init__.py
│ ├── _internal
│ │ ├── __init__.py
│ │ ├── test_progress.py
│ │ └── test_render_templates.py
│ ├── factories.py
│ └── test_mbed_project.py
├── devices
│ ├── _internal
│ │ ├── darwin
│ │ │ ├── __init__.py
│ │ │ └── test_ioreg.py
│ │ ├── linux
│ │ │ └── __init__.py
│ │ ├── __init__.py
│ │ ├── windows
│ │ │ ├── __init__.py
│ │ │ ├── test_serial_port.py
│ │ │ ├── test_system_data_loader.py
│ │ │ ├── test_component_descriptor_utils.py
│ │ │ ├── test_windows_component.py
│ │ │ └── test_disk_identifier.py
│ │ ├── test_detect_candidate_devices.py
│ │ └── test_candidate_device.py
│ ├── __init__.py
│ ├── markers.py
│ └── factories.py
├── targets
│ ├── __init__.py
│ ├── test_config.py
│ ├── factories.py
│ └── test_get_target.py
├── lib
│ ├── test_json_helpers.py
│ └── test_python_helpers.py
└── regression
│ └── test_configure.py
├── requirements-test.txt
├── ci_scripts
├── __init__.py
└── prep-release
├── .github
├── ISSUE_TEMPLATE
│ ├── config.yml
│ ├── feature_request.md
│ └── bug_report.md
├── license_config.yml
├── PULL_REQUEST_TEMPLATE.md
└── workflows
│ └── scancode.yml
├── pytest.ini
├── news
└── README.md
├── MANIFEST.in
├── .pre-commit-config.yaml
├── ci
└── test-data
│ ├── TARGET_IMAGINARYBOARD
│ └── CMakeLists.txt
│ └── custom_targets.json
├── .gitignore
├── codecov.yml
├── .codeclimate.yml
├── tox.ini
├── setup.cfg
├── pyproject.toml
└── setup.py
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text=auto
2 |
--------------------------------------------------------------------------------
/.adr-dir:
--------------------------------------------------------------------------------
1 | docs/architecture/decisions
2 |
--------------------------------------------------------------------------------
/requirements-dev.txt:
--------------------------------------------------------------------------------
1 | flake8
2 | flake8-docstrings
3 | black
4 | mypy
5 | pre-commit
6 |
--------------------------------------------------------------------------------
/src/mbed_tools/project/_internal/templates/gitignore.tmpl:
--------------------------------------------------------------------------------
1 | .mbedbuild
2 | cmake_build/
3 |
--------------------------------------------------------------------------------
/docs/howto/mbed-tools-howto.md:
--------------------------------------------------------------------------------
1 | This document has moved to: https://os.mbed.com/docs/mbed-os/latest/build-tools/mbed-cli-2.html
2 |
--------------------------------------------------------------------------------
/azure-pipelines/steps/override-checkout.yml:
--------------------------------------------------------------------------------
1 | steps:
2 | - checkout: self
3 | submodules: recursive
4 | persistCredentials: true
5 |
--------------------------------------------------------------------------------
/tests/__init__.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2020-2021 Arm Limited and Contributors. All rights reserved.
3 | # SPDX-License-Identifier: Apache-2.0
4 | #
5 |
--------------------------------------------------------------------------------
/tests/build/__init__.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2020-2021 Arm Limited and Contributors. All rights reserved.
3 | # SPDX-License-Identifier: Apache-2.0
4 | #
5 |
--------------------------------------------------------------------------------
/tests/cli/__init__.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2020-2021 Arm Limited and Contributors. All rights reserved.
3 | # SPDX-License-Identifier: Apache-2.0
4 | #
5 |
--------------------------------------------------------------------------------
/tests/ci_scripts/__init__.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2020-2021 Arm Limited and Contributors. All rights reserved.
3 | # SPDX-License-Identifier: Apache-2.0
4 | #
5 |
--------------------------------------------------------------------------------
/tests/project/__init__.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2020-2021 Arm Limited and Contributors. All rights reserved.
3 | # SPDX-License-Identifier: Apache-2.0
4 | #
5 |
--------------------------------------------------------------------------------
/tests/build/_internal/__init__.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2020-2021 Arm Limited and Contributors. All rights reserved.
3 | # SPDX-License-Identifier: Apache-2.0
4 | #
5 |
--------------------------------------------------------------------------------
/requirements-test.txt:
--------------------------------------------------------------------------------
1 | pytest
2 | coverage[toml]
3 | requests-mock
4 | pytest-cov
5 | factory_boy
6 | boto3
7 | jinja2
8 | PyGithub
9 | mbed-tools-ci-scripts
10 |
--------------------------------------------------------------------------------
/tests/project/_internal/__init__.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2020-2021 Arm Limited and Contributors. All rights reserved.
3 | # SPDX-License-Identifier: Apache-2.0
4 | #
5 |
--------------------------------------------------------------------------------
/tests/build/_internal/config/__init__.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2020-2021 Arm Limited and Contributors. All rights reserved.
3 | # SPDX-License-Identifier: Apache-2.0
4 | #
5 |
--------------------------------------------------------------------------------
/tests/devices/_internal/darwin/__init__.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2020-2021 Arm Limited and Contributors. All rights reserved.
3 | # SPDX-License-Identifier: Apache-2.0
4 | #
5 |
--------------------------------------------------------------------------------
/tests/devices/_internal/linux/__init__.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2020-2021 Arm Limited and Contributors. All rights reserved.
3 | # SPDX-License-Identifier: Apache-2.0
4 | #
5 |
--------------------------------------------------------------------------------
/tests/devices/__init__.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2020-2021 Arm Limited and Contributors. All rights reserved.
3 | # SPDX-License-Identifier: Apache-2.0
4 | #
5 | """test module."""
6 |
--------------------------------------------------------------------------------
/ci_scripts/__init__.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2020-2021 Arm Limited and Contributors. All rights reserved.
3 | # SPDX-License-Identifier: Apache-2.0
4 | #
5 | """Scripts to be run by the CI pipeline."""
6 |
--------------------------------------------------------------------------------
/tests/targets/__init__.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2020-2021 Arm Limited and Contributors. All rights reserved.
3 | # SPDX-License-Identifier: Apache-2.0
4 | #
5 | """Tests for the mbed-targets modules."""
6 |
--------------------------------------------------------------------------------
/src/mbed_tools/lib/__init__.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2020-2021 Arm Limited and Contributors. All rights reserved.
3 | # SPDX-License-Identifier: Apache-2.0
4 | #
5 | """Provides a library of common code."""
6 |
--------------------------------------------------------------------------------
/tests/devices/_internal/__init__.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2020-2021 Arm Limited and Contributors. All rights reserved.
3 | # SPDX-License-Identifier: Apache-2.0
4 | #
5 | """Internal implementation tests."""
6 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/config.yml:
--------------------------------------------------------------------------------
1 | blank_issues_enabled: false
2 | contact_links:
3 | - name: Arm Mbed OS support forum
4 | url: https://forums.mbed.com/
5 | about: You can ask and answer questions here.
6 |
--------------------------------------------------------------------------------
/pytest.ini:
--------------------------------------------------------------------------------
1 | [pytest]
2 | addopts = --cov mbed_tools --cov-report xml:coverage/coverage.xml --cov-report=html
3 | junit_family = xunit2
4 | testpaths = tests
5 | norecursedirs = .* dist CVS _darcs {arch} *.egg venv
6 |
--------------------------------------------------------------------------------
/src/mbed_tools/sterm/__init__.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2020-2021 Arm Limited and Contributors. All rights reserved.
3 | # SPDX-License-Identifier: Apache-2.0
4 | #
5 | """Package containing sterm functionality."""
6 |
--------------------------------------------------------------------------------
/src/mbed_tools/__init__.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2020-2021 Arm Limited and Contributors. All rights reserved.
3 | # SPDX-License-Identifier: Apache-2.0
4 | #
5 | """Exposes the primary interfaces for the library."""
6 |
--------------------------------------------------------------------------------
/tests/devices/_internal/windows/__init__.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2020-2021 Arm Limited and Contributors. All rights reserved.
3 | # SPDX-License-Identifier: Apache-2.0
4 | #
5 | """Tests for Windows implementation."""
6 |
--------------------------------------------------------------------------------
/src/mbed_tools/devices/_internal/linux/__init__.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2020-2021 Arm Limited and Contributors. All rights reserved.
3 | # SPDX-License-Identifier: Apache-2.0
4 | #
5 | """Linux specific device detection."""
6 |
--------------------------------------------------------------------------------
/src/mbed_tools/devices/_internal/darwin/__init__.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2020-2021 Arm Limited and Contributors. All rights reserved.
3 | # SPDX-License-Identifier: Apache-2.0
4 | #
5 | """Darwin specific device detection."""
6 |
--------------------------------------------------------------------------------
/src/mbed_tools/devices/_internal/windows/__init__.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2020-2021 Arm Limited and Contributors. All rights reserved.
3 | # SPDX-License-Identifier: Apache-2.0
4 | #
5 | """Windows specific device detection."""
6 |
--------------------------------------------------------------------------------
/src/mbed_tools/build/_internal/__init__.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2020-2021 Arm Limited and Contributors. All rights reserved.
3 | # SPDX-License-Identifier: Apache-2.0
4 | #
5 | """Code not to be accessed by external applications."""
6 |
--------------------------------------------------------------------------------
/src/mbed_tools/build/_internal/config/__init__.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2020-2021 Arm Limited and Contributors. All rights reserved.
3 | # SPDX-License-Identifier: Apache-2.0
4 | #
5 | """Use this module to assemble build configuration."""
6 |
--------------------------------------------------------------------------------
/src/mbed_tools/devices/_internal/__init__.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2020-2021 Arm Limited and Contributors. All rights reserved.
3 | # SPDX-License-Identifier: Apache-2.0
4 | #
5 | """Code not to be accessed by external applications."""
6 |
--------------------------------------------------------------------------------
/src/mbed_tools/project/_internal/__init__.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2020-2021 Arm Limited and Contributors. All rights reserved.
3 | # SPDX-License-Identifier: Apache-2.0
4 | #
5 | """Code not to be accessed by external applications."""
6 |
--------------------------------------------------------------------------------
/src/mbed_tools/targets/_internal/__init__.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2020-2021 Arm Limited and Contributors. All rights reserved.
3 | # SPDX-License-Identifier: Apache-2.0
4 | #
5 | """Code not to be accessed by external applications."""
6 |
--------------------------------------------------------------------------------
/src/mbed_tools/build/_internal/templates/__init__.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2020-2021 Arm Limited and Contributors. All rights reserved.
3 | # SPDX-License-Identifier: Apache-2.0
4 | #
5 | """Integration with https://github.com/ARMmbed/mbed-tools."""
6 |
--------------------------------------------------------------------------------
/src/mbed_tools/cli/__init__.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2020-2021 Arm Limited and Contributors. All rights reserved.
3 | # SPDX-License-Identifier: Apache-2.0
4 | #
5 | """mbed_tools command line interface."""
6 |
7 | from mbed_tools.cli.main import cli, LOGGER
8 |
--------------------------------------------------------------------------------
/src/mbed_tools/project/_internal/templates/__init__.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2020-2021 Arm Limited and Contributors. All rights reserved.
3 | # SPDX-License-Identifier: Apache-2.0
4 | #
5 | """Package containing jinja templates used by mbed_tools.project."""
6 |
--------------------------------------------------------------------------------
/azure-pipelines/steps/publish-code-coverage-results.yml:
--------------------------------------------------------------------------------
1 | steps:
2 | - bash: |
3 | bash <(curl -s https://codecov.io/bash)
4 | condition: and(succeeded(), eq(variables['uploadCoverage'], 'true'))
5 | displayName: 'Upload test coverage to codecov.io'
6 |
--------------------------------------------------------------------------------
/news/README.md:
--------------------------------------------------------------------------------
1 | # News directory
2 |
3 | This directory comprises information about all the changes that happened since the last release. A file should be added to this directory for each PR. On release of the package the content of the file becomes part of the change log and this directory is reset.
4 |
--------------------------------------------------------------------------------
/src/mbed_tools/lib/exceptions.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2020-2021 Arm Limited and Contributors. All rights reserved.
3 | # SPDX-License-Identifier: Apache-2.0
4 | #
5 | """Exceptions raised by mbed tools."""
6 |
7 |
8 | class ToolsError(Exception):
9 | """Base class for tools errors."""
10 |
--------------------------------------------------------------------------------
/.github/license_config.yml:
--------------------------------------------------------------------------------
1 | license:
2 | main: apache-2.0
3 | category: Permissive
4 | exclude:
5 | extensions:
6 | - yml
7 | - yaml
8 | - html
9 | - rst
10 | - conf
11 | - cfg
12 | - ini
13 | - toml
14 | - in
15 | - txt
16 | langs:
17 | - HTML
18 |
--------------------------------------------------------------------------------
/src/mbed_tools/project/_internal/templates/main.tmpl:
--------------------------------------------------------------------------------
1 | /* mbed Microcontroller Library
2 | * Copyright (c) {{year}} ARM Limited
3 | * SPDX-License-Identifier: Apache-2.0
4 | */
5 |
6 | #include "mbed.h"
7 |
8 |
9 | int main()
10 | {
11 | printf("Hello, Mbed!\n");
12 | return 0;
13 | }
14 |
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | global-exclude *.py[cod] __pycache__ *.so
2 | prune .git
3 | prune .github
4 | prune news
5 | prune docs
6 | prune azure-pipelines
7 | prune travis-ci
8 | exclude .pre-commit-config.yaml
9 | exclude .travis.yml
10 | exclude .coverage.yaml
11 | exclude codecov.yml
12 | exclude .codeclimate.yml
13 | exclude .adr-dir
14 |
--------------------------------------------------------------------------------
/azure-pipelines/steps/determine-current-branch.yml:
--------------------------------------------------------------------------------
1 | steps:
2 | - bash: |
3 | if [[ -z "$(System.PullRequest.SourceBranch)" ]]; then
4 | branch_name="$(Build.SourceBranchName)"
5 | else
6 | branch_name="$(System.PullRequest.SourceBranch)"
7 | fi
8 | echo "Determined branch name: $branch_name"
9 | echo "##vso[task.setvariable variable=current_branch]$branch_name"
10 | displayName: "Set current_branch variable"
11 |
--------------------------------------------------------------------------------
/.pre-commit-config.yaml:
--------------------------------------------------------------------------------
1 | repos:
2 | - repo: https://github.com/psf/black
3 | rev: 22.6.0
4 | hooks:
5 | - id: black
6 |
7 | - repo: https://gitlab.com/pycqa/flake8
8 | rev: 3.8.3
9 | hooks:
10 | - id: flake8
11 | additional_dependencies: [flake8-docstrings]
12 |
13 | - repo: https://github.com/pre-commit/mirrors-mypy
14 | rev: v0.782
15 | hooks:
16 | - id: mypy
17 | entry: mypy src
18 | pass_filenames: false
19 |
--------------------------------------------------------------------------------
/ci/test-data/TARGET_IMAGINARYBOARD/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2020-2021 Arm Limited and Contributors. All rights reserved.
2 | # SPDX-License-Identifier: Apache-2.0
3 |
4 | add_library(mbed-imaginaryboard INTERFACE)
5 |
6 | target_sources(mbed-imaginaryboard
7 | INTERFACE
8 | PeripheralPins.c
9 | )
10 |
11 | target_include_directories(mbed-imaginaryboard
12 | INTERFACE
13 | .
14 | )
15 |
16 | target_link_libraries(mbed-imaginaryboard INTERFACE mbed-stm32l475xg)
17 |
--------------------------------------------------------------------------------
/tests/lib/test_json_helpers.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2020-2021 Arm Limited and Contributors. All rights reserved.
3 | # SPDX-License-Identifier: Apache-2.0
4 | #
5 | import json
6 |
7 | import pytest
8 |
9 | from mbed_tools.lib.json_helpers import decode_json_file
10 |
11 |
12 | def test_invalid_json(tmp_path):
13 | lib_json_path = tmp_path / "mbed_lib.json"
14 | lib_json_path.write_text("name")
15 |
16 | with pytest.raises(json.JSONDecodeError):
17 | decode_json_file(lib_json_path)
18 |
--------------------------------------------------------------------------------
/tests/devices/markers.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2020-2021 Arm Limited and Contributors. All rights reserved.
3 | # SPDX-License-Identifier: Apache-2.0
4 | #
5 | """Shared pytest functionality."""
6 | import platform
7 | import unittest
8 |
9 |
10 | windows_only = unittest.skipIf(platform.system() != "Windows", reason="Windows required")
11 | darwin_only = unittest.skipIf(platform.system() != "Darwin", reason="Darwin required")
12 | linux_only = unittest.skipIf(platform.system() != "Linux", reason="Linux required")
13 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Don't lock the versions of this library or installation gridlock will ensue
2 | Pipfile.lock
3 |
4 | # PyCharm
5 | .idea/
6 |
7 | # macOS
8 | .DS_Store
9 |
10 | # Python
11 | *.pyc
12 | __pycache__/
13 | *.egg-info/
14 | /build/
15 |
16 | # Coverage.py
17 | .coverage*
18 | coverage/
19 | junit/
20 | htmlcov/
21 |
22 | # Package
23 | dist/
24 | release-dist/
25 |
26 | # Temporary file used by CI
27 | dev-requirements.txt
28 |
29 | # Local docs output
30 | local_docs/
31 |
32 | # Tox envs
33 | .tox/
34 |
35 | # User envs
36 | .venv/
37 |
--------------------------------------------------------------------------------
/src/mbed_tools/project/__init__.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2020-2021 Arm Limited and Contributors. All rights reserved.
3 | # SPDX-License-Identifier: Apache-2.0
4 | #
5 | """Creation and management of Mbed OS projects.
6 |
7 | * Creation of a new Mbed OS application.
8 | * Cloning of an existing Mbed OS program.
9 | * Deploy of a specific version of Mbed OS or library.
10 | """
11 |
12 | from mbed_tools.project.project import initialise_project, import_project, deploy_project, get_known_libs
13 | from mbed_tools.project.mbed_program import MbedProgram
14 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | ### Description
2 |
3 |
6 |
7 |
8 |
9 | ### Test Coverage
10 |
11 |
14 |
15 | - [ ] This change is covered by existing or additional automated tests.
16 | - [ ] Manual testing has been performed (and evidence provided) as automated testing was not feasible.
17 | - [ ] Additional tests are not required for this change (e.g. documentation update).
18 |
--------------------------------------------------------------------------------
/src/mbed_tools/devices/_internal/base_detector.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2020-2021 Arm Limited and Contributors. All rights reserved.
3 | # SPDX-License-Identifier: Apache-2.0
4 | #
5 | """Interface for device detectors."""
6 | from abc import ABC, abstractmethod
7 | from typing import List
8 |
9 | from mbed_tools.devices._internal.candidate_device import CandidateDevice
10 |
11 |
12 | class DeviceDetector(ABC):
13 | """Object in charge of finding USB devices."""
14 |
15 | @abstractmethod
16 | def find_candidates(self) -> List[CandidateDevice]:
17 | """Returns CandidateDevices."""
18 | pass
19 |
--------------------------------------------------------------------------------
/docs/architecture/decisions/0001-record-architecture-decisions.md:
--------------------------------------------------------------------------------
1 | # 1. Record architecture decisions
2 |
3 | Date: 2020-07-23
4 |
5 | ## Status
6 |
7 | Accepted
8 |
9 | ## Context
10 |
11 | We need to record the architectural decisions made on this project.
12 |
13 | ## Decision
14 |
15 | We will use Architecture Decision Records, as [described by Michael Nygard](http://thinkrelevance.com/blog/2011/11/15/documenting-architecture-decisions).
16 |
17 | ## Consequences
18 |
19 | See Michael Nygard's article, linked above. For a lightweight ADR toolset, see Nat Pryce's [adr-tools](https://github.com/npryce/adr-tools).
20 |
--------------------------------------------------------------------------------
/src/mbed_tools/devices/_internal/exceptions.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2020-2021 Arm Limited and Contributors. All rights reserved.
3 | # SPDX-License-Identifier: Apache-2.0
4 | #
5 | """Exceptions internal to the package."""
6 |
7 | from mbed_tools.lib.exceptions import ToolsError
8 |
9 |
10 | class SystemException(ToolsError):
11 | """Exception with regards to the underlying operating system."""
12 |
13 |
14 | class NoBoardForCandidate(ToolsError):
15 | """Raised when board data cannot be determined for a candidate."""
16 |
17 |
18 | class ResolveBoardError(ToolsError):
19 | """There was an error resolving the board for a device."""
20 |
--------------------------------------------------------------------------------
/src/mbed_tools/targets/_internal/exceptions.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2020-2021 Arm Limited and Contributors. All rights reserved.
3 | # SPDX-License-Identifier: Apache-2.0
4 | #
5 | """Exceptions used internally by the mbed-targets package."""
6 |
7 | from mbed_tools.targets.exceptions import BoardDatabaseError, MbedTargetsError
8 |
9 |
10 | class BoardAPIError(BoardDatabaseError):
11 | """API request failed."""
12 |
13 |
14 | class ResponseJSONError(BoardDatabaseError):
15 | """HTTP response JSON parsing failed."""
16 |
17 |
18 | class TargetsJsonConfigurationError(MbedTargetsError):
19 | """The target definition is invalid."""
20 |
--------------------------------------------------------------------------------
/tests/devices/factories.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2020-2021 Arm Limited and Contributors. All rights reserved.
3 | # SPDX-License-Identifier: Apache-2.0
4 | #
5 | import factory
6 | import pathlib
7 |
8 | from mbed_tools.devices._internal.candidate_device import CandidateDevice
9 |
10 |
11 | class CandidateDeviceFactory(factory.Factory):
12 | class Meta:
13 | model = CandidateDevice
14 |
15 | product_id = factory.Faker("hexify")
16 | vendor_id = factory.Faker("hexify")
17 | mount_points = [pathlib.Path(".")]
18 | serial_number = factory.Faker("hexify", text=("^" * 20)) # 20 characters serial number
19 | serial_port = None
20 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | labels: 'enhancement'
5 | ---
6 | **Is your feature request related to a problem? Please describe.**
7 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
8 |
9 | **Describe the solution you'd like**
10 | A clear and concise description of what you want to happen.
11 |
12 | **Describe alternatives you've considered**
13 | A clear and concise description of any alternative solutions or features you've considered.
14 |
15 | **Additional context**
16 | Add any other context or screenshots about the feature request here.
17 |
--------------------------------------------------------------------------------
/.github/workflows/scancode.yml:
--------------------------------------------------------------------------------
1 | name: Scancode
2 |
3 | on: [pull_request]
4 |
5 | jobs:
6 | scancode_job:
7 | runs-on: ubuntu-latest
8 | name: Scan code for licenses
9 | steps:
10 | - uses: actions/checkout@v1
11 | - name: Scan the code
12 | id: scancode
13 | uses: zephyrproject-rtos/action_scancode@v1
14 | with:
15 | directory-to-scan: 'scan/'
16 | - name: Artifact Upload
17 | uses: actions/upload-artifact@v1
18 | with:
19 | name: scancode
20 | path: ./artifacts
21 |
22 | - name: Verify
23 | run: |
24 | test ! -s ./artifacts/report.txt || (cat ./artifacts/report.txt && exit 1 )
25 |
--------------------------------------------------------------------------------
/ci/test-data/custom_targets.json:
--------------------------------------------------------------------------------
1 | {
2 | "IMAGINARYBOARD": {
3 | "inherits": [
4 | "MCU_STM32L475xG"
5 | ],
6 | "components_add": [
7 | "BlueNRG_MS",
8 | "QSPIF"
9 | ],
10 | "extra_labels_add": [
11 | "CORDIO",
12 | "MX25R6435F"
13 | ],
14 | "supported_form_factors": [
15 | "ARDUINO_UNO"
16 | ],
17 | "detect_code": [
18 | "1234"
19 | ],
20 | "device_has_add": [
21 | "QSPI"
22 | ],
23 | "features": [
24 | "BLE"
25 | ],
26 | "device_name": "STM32L475VG"
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/mbed_tools/devices/exceptions.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2020-2021 Arm Limited and Contributors. All rights reserved.
3 | # SPDX-License-Identifier: Apache-2.0
4 | #
5 | """Public exceptions raised by the package."""
6 | from mbed_tools.lib.exceptions import ToolsError
7 |
8 |
9 | class MbedDevicesError(ToolsError):
10 | """Base public exception for the mbed-devices package."""
11 |
12 |
13 | class DeviceLookupFailed(MbedDevicesError):
14 | """Failed to look up data associated with the device."""
15 |
16 |
17 | class NoDevicesFound(MbedDevicesError):
18 | """No Mbed Enabled devices were found."""
19 |
20 |
21 | class UnknownOSError(MbedDevicesError):
22 | """The current OS is not supported."""
23 |
--------------------------------------------------------------------------------
/src/mbed_tools/lib/json_helpers.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2020-2021 Arm Limited and Contributors. All rights reserved.
3 | # SPDX-License-Identifier: Apache-2.0
4 | #
5 | """Helpers for json related functions."""
6 | import json
7 | import logging
8 |
9 | from pathlib import Path
10 | from typing import Any
11 |
12 | logger = logging.getLogger(__name__)
13 |
14 |
15 | def decode_json_file(path: Path) -> Any:
16 | """Return the contents of json file."""
17 | try:
18 | logger.debug(f"Loading JSON file {path}")
19 | return json.loads(path.read_text())
20 | except json.JSONDecodeError:
21 | logger.error(f"Failed to decode JSON data in the file located at '{path}'")
22 | raise
23 |
--------------------------------------------------------------------------------
/codecov.yml:
--------------------------------------------------------------------------------
1 | codecov:
2 | require_ci_to_pass: yes
3 | notify:
4 | after_n_builds: 3
5 |
6 | coverage:
7 | precision: 2
8 | round: down
9 | range: "0...100"
10 | status:
11 | project:
12 | default:
13 | # basic
14 | target: auto
15 | threshold: 2%
16 | base: auto
17 | # advanced
18 | branches:
19 | - master
20 | if_not_found: success
21 | if_ci_failed: error
22 | informational: true
23 | only_pulls: false
24 | parsers:
25 | gcov:
26 | branch_detection:
27 | conditional: yes
28 | loop: yes
29 | method: no
30 | macro: no
31 |
32 | comment:
33 | layout: "reach,diff,flags,tree"
34 | behavior: default
35 | require_changes: no
36 |
--------------------------------------------------------------------------------
/.codeclimate.yml:
--------------------------------------------------------------------------------
1 | version: "2"
2 | checks:
3 | argument-count:
4 | config:
5 | threshold: 4
6 | complex-logic:
7 | config:
8 | threshold: 4
9 | file-lines:
10 | config:
11 | threshold: 250
12 | method-complexity:
13 | config:
14 | threshold: 5
15 | method-count:
16 | config:
17 | threshold: 20
18 | method-lines:
19 | config:
20 | threshold: 25
21 | nested-control-flow:
22 | config:
23 | threshold: 4
24 | return-statements:
25 | config:
26 | threshold: 4
27 | similar-code:
28 | config:
29 | threshold: # language-specific defaults. an override will affect all languages.
30 | identical-code:
31 | config:
32 | threshold: # language-specific defaults. an override will affect all languages.
33 |
--------------------------------------------------------------------------------
/src/mbed_tools/targets/exceptions.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2020-2021 Arm Limited and Contributors. All rights reserved.
3 | # SPDX-License-Identifier: Apache-2.0
4 | #
5 | """Public exceptions exposed by the package."""
6 |
7 | from mbed_tools.lib.exceptions import ToolsError
8 |
9 |
10 | class MbedTargetsError(ToolsError):
11 | """Base exception for mbed-targets."""
12 |
13 |
14 | class TargetError(ToolsError):
15 | """Target definition cannot be retrieved."""
16 |
17 |
18 | class UnknownBoard(MbedTargetsError):
19 | """Requested board was not found."""
20 |
21 |
22 | class UnsupportedMode(MbedTargetsError):
23 | """The Database Mode is unsupported."""
24 |
25 |
26 | class BoardDatabaseError(MbedTargetsError):
27 | """Failed to get the board data from the database."""
28 |
--------------------------------------------------------------------------------
/src/mbed_tools/project/_internal/templates/CMakeLists.tmpl:
--------------------------------------------------------------------------------
1 | # Copyright (c) {{year}} ARM Limited. All rights reserved.
2 | # SPDX-License-Identifier: Apache-2.0
3 |
4 | cmake_minimum_required(VERSION 3.19.0)
5 |
6 | set(MBED_PATH ${CMAKE_CURRENT_SOURCE_DIR}/mbed-os CACHE INTERNAL "")
7 | set(MBED_CONFIG_PATH ${CMAKE_CURRENT_BINARY_DIR} CACHE INTERNAL "")
8 | set(APP_TARGET {{program_name}})
9 |
10 | include(${MBED_PATH}/tools/cmake/app.cmake)
11 |
12 | project(${APP_TARGET})
13 |
14 | add_subdirectory(${MBED_PATH})
15 |
16 | add_executable(${APP_TARGET}
17 | main.cpp
18 | )
19 |
20 | target_link_libraries(${APP_TARGET} mbed-os)
21 |
22 | mbed_set_post_build(${APP_TARGET})
23 |
24 | option(VERBOSE_BUILD "Have a verbose build process")
25 | if(VERBOSE_BUILD)
26 | set(CMAKE_VERBOSE_MAKEFILE ON)
27 | endif()
28 |
--------------------------------------------------------------------------------
/src/mbed_tools/build/__init__.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2020-2021 Arm Limited and Contributors. All rights reserved.
3 | # SPDX-License-Identifier: Apache-2.0
4 | #
5 | """Provides the core build system for Mbed OS, which relies on CMake and Ninja as underlying technologies.
6 |
7 | The functionality covered in this package includes the following:
8 |
9 | - Execution of Mbed Pre-Build stages to determine appropriate configuration for Mbed OS and the build process.
10 | - Invocation of the build process for the command line tools and online build service.
11 | - Export of build instructions to third party command line tools and IDEs.
12 | """
13 | from mbed_tools.build.build import build_project, generate_build_system
14 | from mbed_tools.build.config import generate_config
15 | from mbed_tools.build.flash import flash_binary
16 |
--------------------------------------------------------------------------------
/tests/lib/test_python_helpers.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2020-2021 Arm Limited and Contributors. All rights reserved.
3 | # SPDX-License-Identifier: Apache-2.0
4 | #
5 | import pytest
6 |
7 | from mbed_tools.lib.python_helpers import flatten_nested
8 |
9 |
10 | @pytest.mark.parametrize(
11 | "input_list, expected_result",
12 | (
13 | ([1, 2, 3, 4, 5], [1, 2, 3, 4, 5]),
14 | ([1, [2], 3, 4, 5], [1, 2, 3, 4, 5]),
15 | ([[1, 2, 3], [4, 5]], [1, 2, 3, 4, 5]),
16 | ([(1, 2, 3), (4, 5)], [1, 2, 3, 4, 5]),
17 | ([[1, [[2, [3]]]], [4, 5]], [1, 2, 3, 4, 5]),
18 | ([["alan", [["bob", ["sally"]]]], ["jim", "jenny"]], ["alan", "bob", "sally", "jim", "jenny"]),
19 | ),
20 | )
21 | def test_flatten_nested_list(input_list, expected_result):
22 | assert flatten_nested(input_list) == expected_result
23 |
--------------------------------------------------------------------------------
/src/mbed_tools/project/exceptions.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2020-2021 Arm Limited and Contributors. All rights reserved.
3 | # SPDX-License-Identifier: Apache-2.0
4 | #
5 | """Public exceptions exposed by the package."""
6 |
7 | from mbed_tools.lib.exceptions import ToolsError
8 |
9 |
10 | class MbedProjectError(ToolsError):
11 | """Base exception for mbed-project."""
12 |
13 |
14 | class VersionControlError(MbedProjectError):
15 | """Raised when a source control management operation failed."""
16 |
17 |
18 | class ExistingProgram(MbedProjectError):
19 | """Raised when a program already exists at a given path."""
20 |
21 |
22 | class ProgramNotFound(MbedProjectError):
23 | """Raised when an expected program is not found."""
24 |
25 |
26 | class MbedOSNotFound(MbedProjectError):
27 | """A valid copy of MbedOS was not found."""
28 |
--------------------------------------------------------------------------------
/src/mbed_tools/targets/_internal/targets_json_parsers/__init__.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2020-2021 Arm Limited and Contributors. All rights reserved.
3 | # SPDX-License-Identifier: Apache-2.0
4 | #
5 | """Package for parsing Mbed OS library's targets.json.
6 |
7 | targets.json contains the attribute data for all supported targets.
8 |
9 | Targets are defined using inheritance. Multiple inheritance is allowed (though not encouraged).
10 |
11 | Attributes come in two types - overriding and accumulating. These
12 | use two completely different ways of inheriting so each has its own parser.
13 |
14 | The combined results of these two parsers makes a complete set of attributes for
15 | a target as defined in targets.json.
16 |
17 | For more information about targets.json structure see
18 | https://os.mbed.com/docs/mbed-os/latest/reference/adding-and-configuring-targets.html
19 | """
20 |
--------------------------------------------------------------------------------
/tests/targets/test_config.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2020-2021 Arm Limited and Contributors. All rights reserved.
3 | # SPDX-License-Identifier: Apache-2.0
4 | #
5 | import os
6 | from unittest import TestCase, mock
7 |
8 | from mbed_tools.targets.env import env
9 |
10 |
11 | class TestMbedApiAuthToken(TestCase):
12 | @mock.patch.dict(os.environ, {"MBED_API_AUTH_TOKEN": "sometoken"})
13 | def test_returns_api_token_set_in_env(self):
14 | self.assertEqual(env.MBED_API_AUTH_TOKEN, "sometoken")
15 |
16 |
17 | class TestDatabaseMode(TestCase):
18 | @mock.patch.dict(os.environ, {"MBED_DATABASE_MODE": "ONLINE"})
19 | def test_returns_database_mode_set_in_env(self):
20 | self.assertEqual(env.MBED_DATABASE_MODE, "ONLINE")
21 |
22 | def test_returns_default_database_mode_if_not_set_in_env(self):
23 | self.assertEqual(env.MBED_DATABASE_MODE, "AUTO")
24 |
--------------------------------------------------------------------------------
/tests/devices/_internal/windows/test_serial_port.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2020-2021 Arm Limited and Contributors. All rights reserved.
3 | # SPDX-License-Identifier: Apache-2.0
4 | #
5 | from tests.devices.markers import windows_only
6 | from unittest import TestCase
7 | import random
8 |
9 |
10 | @windows_only
11 | class TestSerialPort(TestCase):
12 | def test_retrieve_port_name(self):
13 | from mbed_tools.devices._internal.windows.serial_port import parse_caption
14 | from mbed_tools.devices._internal.windows.component_descriptor import UNKNOWN_VALUE
15 |
16 | self.assertEqual(UNKNOWN_VALUE, parse_caption(UNKNOWN_VALUE))
17 | self.assertEqual("COM13", parse_caption("Serial Port for Barcode Scanner (COM13)"))
18 | port_name = f"COM{random.choice(range(0, 1000))}"
19 | self.assertEqual(port_name, parse_caption(f"mbed Serial Port ({port_name})"))
20 |
--------------------------------------------------------------------------------
/src/mbed_tools/build/exceptions.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2020-2021 Arm Limited and Contributors. All rights reserved.
3 | # SPDX-License-Identifier: Apache-2.0
4 | #
5 | """Public exceptions raised by the package."""
6 | from mbed_tools.lib.exceptions import ToolsError
7 |
8 |
9 | class MbedBuildError(ToolsError):
10 | """Base public exception for the mbed-build package."""
11 |
12 |
13 | class InvalidExportOutputDirectory(MbedBuildError):
14 | """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."""
23 |
24 |
25 | class InvalidConfigOverride(MbedBuildError):
26 | """A given config setting was invalid."""
27 |
--------------------------------------------------------------------------------
/src/mbed_tools/devices/__init__.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2020-2021 Arm Limited and Contributors. All rights reserved.
3 | # SPDX-License-Identifier: Apache-2.0
4 | #
5 | """API to detect any Mbed OS devices connected to the host computer.
6 |
7 | It is expected that this package will be used by developers of Mbed OS tooling rather than by users of Mbed OS.
8 | This package uses the https://github.com/ARMmbed/mbed-targets interface to identify valid Mbed Enabled Devices.
9 | Please see the documentation for mbed-targets for information on configuration options.
10 |
11 | For the command line interface to the API see the package https://github.com/ARMmbed/mbed-tools
12 | """
13 | from mbed_tools.devices.devices import (
14 | get_connected_devices,
15 | find_connected_device,
16 | find_all_connected_devices,
17 | )
18 | from mbed_tools.devices.device import Device
19 | from mbed_tools.devices import exceptions
20 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | labels: 'bug'
5 | ---
6 | **Describe the bug**
7 | A clear and concise description of what the bug is.
8 |
9 | **To Reproduce**
10 | Steps to reproduce the behavior:
11 | 1. Go to '...'
12 | 2. Click on '....'
13 | 3. Scroll down to '....'
14 | 4. See error
15 |
16 | **Expected behavior**
17 | A clear and concise description of what you expected to happen.
18 |
19 | **Screenshots**
20 | If applicable, add screenshots to help explain your problem.
21 |
22 | **Desktop (please complete the following information):**
23 | - OS: [e.g. Windows]
24 | - Version: [e.g. 2000]
25 |
26 | **Mbed (please complete the following information):**
27 | - Device: [e.g. `DISCO_L475VG_IOT01A`]
28 | - Mbed OS Version: [e.g. 6.8.0]
29 | - Mbed CLI 2 Version: [e.g. 7.3.1]
30 |
31 | **Additional context**
32 | Add any other context about the problem here.
33 |
--------------------------------------------------------------------------------
/tests/targets/factories.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2020-2021 Arm Limited and Contributors. All rights reserved.
3 | # SPDX-License-Identifier: Apache-2.0
4 | #
5 | from mbed_tools.targets import Board
6 |
7 |
8 | def make_board(
9 | board_type="BoardType",
10 | board_name="BoardName",
11 | mbed_os_support=None,
12 | mbed_enabled=None,
13 | product_code="9999",
14 | slug="BoardSlug",
15 | target_type="TargetType",
16 | ):
17 | return Board(
18 | board_type=board_type,
19 | product_code=product_code,
20 | board_name=board_name,
21 | target_type=target_type,
22 | slug=slug,
23 | mbed_os_support=mbed_os_support if mbed_os_support else (),
24 | mbed_enabled=mbed_enabled if mbed_enabled else (),
25 | build_variant=(),
26 | )
27 |
28 |
29 | def make_dummy_internal_board_data():
30 | return [dict(attributes=dict(board_type=str(i), board_name=str(i), product_code=str(i))) for i in range(10)]
31 |
--------------------------------------------------------------------------------
/src/mbed_tools/lib/python_helpers.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2020-2021 Arm Limited and Contributors. All rights reserved.
3 | # SPDX-License-Identifier: Apache-2.0
4 | #
5 | """Helpers for python language related functions."""
6 | from typing import Iterable, List
7 |
8 |
9 | def flatten_nested(input_iter: Iterable) -> List:
10 | """Flatten a nested Iterable with arbitrary levels of nesting.
11 |
12 | If the input is an iterator then this function will exhaust it.
13 |
14 | Args:
15 | input_iter: The input Iterable which may or may not be nested.
16 |
17 | Returns:
18 | A flat list created from the input_iter.
19 | If input_iter has no nesting its elements are appended to a list and returned.
20 | """
21 | output = []
22 | for elem in input_iter:
23 | if isinstance(elem, Iterable) and not isinstance(elem, str):
24 | output += flatten_nested(elem)
25 | else:
26 | output.append(elem)
27 |
28 | return output
29 |
--------------------------------------------------------------------------------
/azure-pipelines/prep-release.yml:
--------------------------------------------------------------------------------
1 | trigger:
2 | - none
3 |
4 | pr:
5 | - none
6 |
7 | schedules:
8 | - cron: "00 5 * * *"
9 | displayName: "Prepare and tag release"
10 | branches:
11 | include:
12 | - master
13 | always: true
14 |
15 | jobs:
16 | - job: TagRelease
17 | displayName: 'Prepare mbed-tools release'
18 | pool:
19 | vmImage: 'ubuntu-latest'
20 |
21 | steps:
22 | - task: UsePythonVersion@0
23 | inputs:
24 | versionSpec: '3.7'
25 |
26 | - script: |
27 | # Set our user to the release bot account and ensure we've checked
28 | # out the master branch.
29 | git config --global user.name "Monty Bot"
30 | git config --global user.email "monty-bot@arm.com"
31 | git checkout master
32 |
33 | python -m pip install --upgrade tox
34 |
35 | tox -e preprelease
36 | displayName: "Prepare mbed-tools release"
37 | env:
38 | GIT_TOKEN: $(GIT_TOKEN)
39 |
--------------------------------------------------------------------------------
/tests/devices/_internal/windows/test_system_data_loader.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2020-2021 Arm Limited and Contributors. All rights reserved.
3 | # SPDX-License-Identifier: Apache-2.0
4 | #
5 | import unittest
6 | from unittest.mock import patch
7 |
8 | from tests.devices.markers import windows_only
9 |
10 |
11 | @windows_only
12 | class TestSystemDataLoader(unittest.TestCase):
13 | @patch("mbed_tools.devices._internal.windows.system_data_loader.load_all")
14 | def test_system_data_load(self, load_all):
15 | from mbed_tools.devices._internal.windows.system_data_loader import SystemDataLoader, SYSTEM_DATA_TYPES
16 |
17 | def mock_system_element_fetcher(arg):
18 | return (arg, list())
19 |
20 | load_all.side_effect = mock_system_element_fetcher
21 |
22 | loader = SystemDataLoader()
23 | for type in SYSTEM_DATA_TYPES:
24 | self.assertIsNotNone(loader.get_system_data(type))
25 | self.assertTrue(isinstance(loader.get_system_data(type), list))
26 | load_all.assert_called()
27 |
--------------------------------------------------------------------------------
/src/mbed_tools/targets/__init__.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2020-2021 Arm Limited and Contributors. All rights reserved.
3 | # SPDX-License-Identifier: Apache-2.0
4 | #
5 | """An abstraction layer describing hardware supported by Mbed OS.
6 |
7 | Querying board database
8 | -----------------------
9 |
10 | For the interface to query board database, look at `mbed_tools.targets.get_board`.
11 |
12 | Fetching target data
13 | ____________________
14 |
15 | For the interface to extract target data from their definitions in Mbed OS,
16 | look at `mbed_tools.targets.get_target`.
17 |
18 | Configuration
19 | -------------
20 |
21 | For details about configuration of this module, look at `mbed_tools.targets.config`.
22 | """
23 | from mbed_tools.targets import exceptions
24 | from mbed_tools.targets.get_target import (
25 | get_target_by_name,
26 | get_target_by_board_type,
27 | )
28 | from mbed_tools.targets.get_board import (
29 | get_board_by_product_code,
30 | get_board_by_online_id,
31 | get_board_by_jlink_slug,
32 | )
33 | from mbed_tools.targets.board import Board
34 |
--------------------------------------------------------------------------------
/src/mbed_tools/build/_internal/write_files.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2020-2021 Arm Limited and Contributors. All rights reserved.
3 | # SPDX-License-Identifier: Apache-2.0
4 | #
5 | """Writes out files to specified locations."""
6 | import pathlib
7 | from mbed_tools.build.exceptions import InvalidExportOutputDirectory
8 |
9 |
10 | def write_file(file_path: pathlib.Path, file_contents: str) -> None:
11 | """Writes out a string to a file.
12 |
13 | If the intermediate directories to the output directory don't exist,
14 | this function will create them.
15 |
16 | This function will overwrite any existing file of the same name in the
17 | output directory.
18 |
19 | Raises:
20 | InvalidExportOutputDirectory: it's not possible to export to the output directory provided
21 | """
22 | output_directory = file_path.parent
23 | if output_directory.is_file():
24 | raise InvalidExportOutputDirectory("Output directory cannot be a path to a file.")
25 |
26 | output_directory.mkdir(parents=True, exist_ok=True)
27 | file_path.write_text(file_contents)
28 |
--------------------------------------------------------------------------------
/tests/build/factories.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2020-2021 Arm Limited and Contributors. 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]
44 |
--------------------------------------------------------------------------------
/tests/build/_internal/config/factories.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2020-2021 Arm Limited and Contributors. All rights reserved.
3 | # SPDX-License-Identifier: Apache-2.0
4 | #
5 | import factory
6 |
7 | from mbed_tools.build._internal.config.config import Config, Option, Macro
8 | from mbed_tools.build._internal.config.source import Source
9 |
10 |
11 | class SourceFactory(factory.Factory):
12 | class Meta:
13 | model = Source
14 |
15 | human_name = factory.Faker("name")
16 | config = factory.Dict({})
17 | overrides = factory.Dict({})
18 | macros = factory.List([])
19 |
20 |
21 | class OptionFactory(factory.Factory):
22 | class Meta:
23 | model = Option
24 |
25 | value = "0"
26 | macro_name = "MACRO_NAME"
27 | help_text = factory.Faker("text")
28 | set_by = "libname"
29 | key = "key"
30 |
31 |
32 | class MacroFactory(factory.Factory):
33 | class Meta:
34 | model = Macro
35 |
36 | value = "0"
37 | name = "A_MACRO"
38 | set_by = "source"
39 |
40 |
41 | class ConfigFactory(factory.Factory):
42 | class Meta:
43 | model = Config
44 |
45 | options = factory.Dict({})
46 | macros = factory.Dict({})
47 |
--------------------------------------------------------------------------------
/tests/build/_internal/test_write_files.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2020-2021 Arm Limited and Contributors. All rights reserved.
3 | # SPDX-License-Identifier: Apache-2.0
4 | #
5 | import pathlib
6 | import tempfile
7 | from unittest import TestCase
8 |
9 | from mbed_tools.build._internal.write_files import write_file
10 | from mbed_tools.build.exceptions import InvalidExportOutputDirectory
11 |
12 |
13 | class TestWriteFile(TestCase):
14 | def test_writes_content_to_file(self):
15 | with tempfile.TemporaryDirectory() as directory:
16 | content = "Some rendered content"
17 | export_path = pathlib.Path(directory, "output", "some_file.txt")
18 |
19 | write_file(export_path, content)
20 |
21 | created_file = pathlib.Path(export_path)
22 | self.assertEqual(created_file.read_text(), content)
23 |
24 | def test_output_dir_is_file(self):
25 | with tempfile.TemporaryDirectory() as directory:
26 | bad_export_dir = pathlib.Path(directory, "some_file.txt", ".txt")
27 | bad_export_dir.parent.touch()
28 | with self.assertRaises(InvalidExportOutputDirectory):
29 | write_file(bad_export_dir, "some contents")
30 |
--------------------------------------------------------------------------------
/docs/architecture/decisions/0002-move-mbed-config-header-defines-to-cmake.md:
--------------------------------------------------------------------------------
1 | # 2. Move mbed config header defines to CMake
2 |
3 | Date: 2020-07-23
4 |
5 | ## Status
6 |
7 | Accepted
8 |
9 | ## Context
10 |
11 | `mbed_config.h` was created because the command line length limit on older versions of Windows prevented passing many "-D" defines to the compiler and linker.
12 | Additionally, passing many "-D" defines globally is a bad idea in itself. Therefore, `mbed_config.h` was a workaround to make a bad practice work.
13 | On modern versions of Windows, the command line limit isn't an issue. CMake can also do what's necessary to prevent exceeding the command line length limit where needed.
14 |
15 | ## Decision
16 |
17 | We will remove the generation of `mbed_config.h` and simply pass the "-D" defines using CMake directives. This is acheived by moving the "-D" definitions to the tools generated `mbed_config.cmake`.
18 |
19 | ## Consequences
20 |
21 | CMake will be in control of passing the defines to the compiler. The tools generate a single file containing all dynamic configuration parameters.
22 | This model will make it easier to modularise the build system later, as we are removing a "global" header that is included everywhere.
23 |
--------------------------------------------------------------------------------
/tox.ini:
--------------------------------------------------------------------------------
1 | [tox]
2 | envlist = dev,linting,checknews,py36,py38,py39
3 | minversion = 3.3.0
4 | # Activate isolated build environment. tox will use a virtual environment
5 | # to build a source distribution from the source tree.
6 | isolated_build = True
7 |
8 | [testenv:linting]
9 | skip_install = True
10 | deps =
11 | pre-commit
12 | commands = pre-commit run --all-files
13 |
14 | [testenv]
15 | deps =
16 | -rrequirements-test.txt
17 | commands = pytest {posargs}
18 |
19 | [testenv:dev]
20 | usedevelop = True
21 | envdir = .venv
22 | commands =
23 | deps =
24 | -rrequirements-test.txt
25 | -rrequirements-dev.txt
26 |
27 | [testenv:checknews]
28 | skip_install = True
29 | deps =
30 | towncrier
31 | commands = python -m towncrier.check
32 |
33 | [testenv:preprelease]
34 | usedevelop = True
35 | allowlist_externals = git
36 | passenv =
37 | HOME
38 | GIT_TOKEN
39 | deps =
40 | mbed-tools-ci-scripts
41 | pre-commit
42 | commands =
43 | {toxinidir}/ci_scripts/prep-release
44 |
45 | [testenv:pypirelease]
46 | usedevelop = True
47 | deps =
48 | twine
49 | setuptools>=42
50 | wheel
51 | setuptools_scm[toml]>=3.4
52 | commands =
53 | python setup.py clean --all sdist --formats gztar bdist_wheel
54 | python -m twine check dist/*
55 | python -m twine upload {posargs} dist/*
56 |
--------------------------------------------------------------------------------
/azure-pipelines/build-and-deploy.yml:
--------------------------------------------------------------------------------
1 | trigger:
2 | branches:
3 | include:
4 | - master
5 | tags:
6 | include:
7 | - '*'
8 |
9 | pr:
10 | - none
11 |
12 | stages:
13 | - stage: ReleaseToPyPI
14 | condition: not(and(eq(variables['Build.SourceBranch'], 'refs/heads/master'), contains(variables['Build.SourceVersionMessage'], 'Prepare mbed-tools release')))
15 | displayName: 'Production Release'
16 | jobs:
17 | - deployment: PyPIRelease
18 | displayName: 'PyPI Release'
19 | environment: 'pypi-release'
20 | strategy:
21 | runOnce:
22 | deploy:
23 | pool:
24 | vmImage: 'ubuntu-latest'
25 | steps:
26 | - task: UsePythonVersion@0
27 | inputs:
28 | versionSpec: '3.7'
29 |
30 | - template: steps/determine-current-branch.yml
31 |
32 | - template: steps/override-checkout.yml
33 |
34 | - script: |
35 | python -m pip install --upgrade tox
36 | tox -e py37
37 | tox -e pypirelease -- -p $TWINE_PASSWORD -u $TWINE_USERNAME
38 | displayName: "PyPI release"
39 | env:
40 | TWINE_USERNAME: $(TWINE_USERNAME)
41 | TWINE_PASSWORD: $(TWINE_PASSWORD)
42 |
--------------------------------------------------------------------------------
/tests/project/factories.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2020-2021 Arm Limited and Contributors. All rights reserved.
3 | # SPDX-License-Identifier: Apache-2.0
4 | #
5 | from mbed_tools.project._internal.libraries import MbedLibReference
6 | from mbed_tools.project._internal.project_data import (
7 | CMAKELISTS_FILE_NAME,
8 | APP_CONFIG_FILE_NAME,
9 | MBED_OS_REFERENCE_FILE_NAME,
10 | )
11 |
12 |
13 | def make_mbed_program_files(root, config_file_name=APP_CONFIG_FILE_NAME):
14 | if not root.exists():
15 | root.mkdir()
16 |
17 | (root / MBED_OS_REFERENCE_FILE_NAME).touch()
18 | (root / config_file_name).touch()
19 | (root / CMAKELISTS_FILE_NAME).touch()
20 |
21 |
22 | def make_mbed_lib_reference(root, name="mylib.lib", resolved=False, ref_url=None):
23 | ref_file = root / name
24 | source_dir = ref_file.with_suffix("")
25 | if not root.exists():
26 | root.mkdir()
27 |
28 | ref_file.touch()
29 |
30 | if resolved:
31 | source_dir.mkdir()
32 |
33 | if ref_url is not None:
34 | ref_file.write_text(ref_url)
35 |
36 | return MbedLibReference(reference_file=ref_file, source_code_path=source_dir)
37 |
38 |
39 | def make_mbed_os_files(root):
40 | if not root.exists():
41 | root.mkdir()
42 |
43 | targets_dir = root / "targets"
44 | targets_dir.mkdir()
45 | (targets_dir / "targets.json").touch()
46 |
--------------------------------------------------------------------------------
/tests/cli/test_devices_command_integration.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2020-2021 Arm Limited and Contributors. All rights reserved.
3 | # SPDX-License-Identifier: Apache-2.0
4 | #
5 | from unittest import TestCase, mock
6 |
7 | import click
8 | from click.testing import CliRunner
9 |
10 | from mbed_tools.cli import cli
11 | from mbed_tools.cli.list_connected_devices import list_connected_devices
12 | from mbed_tools.lib.exceptions import ToolsError
13 |
14 |
15 | class TestDevicesCommandIntegration(TestCase):
16 | def test_devices_is_integrated(self):
17 | self.assertEqual(cli.commands["detect"], list_connected_devices)
18 |
19 |
20 | class TestClickGroupWithExceptionHandling(TestCase):
21 | @mock.patch("mbed_tools.cli.LOGGER.error", autospec=True)
22 | def test_logs_tools_errors(self, logger_error):
23 | def callback():
24 | raise ToolsError()
25 |
26 | mock_cli = click.Command("test", callback=callback)
27 | cli.add_command(mock_cli, "test")
28 |
29 | runner = CliRunner()
30 | result = runner.invoke(cli, ["test"])
31 |
32 | self.assertEqual(1, result.exit_code)
33 | logger_error.assert_called_once()
34 |
35 |
36 | class TestVersionCommand(TestCase):
37 | def test_version_command(self):
38 | runner = CliRunner()
39 | result = runner.invoke(cli, ["--version"])
40 | self.assertTrue(result.output)
41 | self.assertEqual(0, result.exit_code)
42 |
--------------------------------------------------------------------------------
/src/mbed_tools/devices/_internal/windows/component_descriptor_utils.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2020-2021 Arm Limited and Contributors. All rights reserved.
3 | # SPDX-License-Identifier: Apache-2.0
4 | #
5 | """Utilities with regards to Win32 component descriptors."""
6 | from typing import Any, NamedTuple, Union
7 | from collections import OrderedDict
8 |
9 | UNKNOWN_VALUE = "Unknown"
10 |
11 |
12 | def is_undefined_value(value: Any) -> bool:
13 | """Checks whether a field value is considered as undefined or not."""
14 | return value in [None, UNKNOWN_VALUE, 0, False]
15 |
16 |
17 | def is_undefined_data_object(data_object: NamedTuple) -> bool:
18 | """Checks whether a data object is considered as undefined or not."""
19 | for f in data_object._fields:
20 | if not is_undefined_value(getattr(data_object, f)):
21 | is_undefined = False
22 | break
23 | else:
24 | is_undefined = True
25 | return is_undefined
26 |
27 |
28 | def retain_value_or_default(value: Any) -> Any:
29 | """Returns the value if set otherwise returns a default value."""
30 | return UNKNOWN_VALUE if is_undefined_value(value) else value
31 |
32 |
33 | def data_object_to_dict(data_object: NamedTuple) -> dict:
34 | """Gets a data object as a dictionary."""
35 | internal_dictionary: Union[dict, OrderedDict] = data_object._asdict()
36 | return dict(internal_dictionary) if isinstance(internal_dictionary, OrderedDict) else internal_dictionary
37 |
--------------------------------------------------------------------------------
/src/mbed_tools/build/_internal/cmake_file.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2020-2021 Arm Limited and Contributors. All rights reserved.
3 | # SPDX-License-Identifier: Apache-2.0
4 | #
5 | """Module in charge of CMake file generation."""
6 | import pathlib
7 |
8 | from typing import Any
9 |
10 | import jinja2
11 |
12 | from mbed_tools.build._internal.config.config import Config
13 |
14 | TEMPLATES_DIRECTORY = pathlib.Path("_internal", "templates")
15 | TEMPLATE_NAME = "mbed_config.tmpl"
16 |
17 |
18 | def render_mbed_config_cmake_template(config: Config, toolchain_name: str, target_name: str) -> str:
19 | """Renders the mbed_config jinja template with the target and project config settings.
20 |
21 | Args:
22 | config: Config object holding information parsed from the mbed config system.
23 | toolchain_name: Name of the toolchain being used.
24 | target_name: Name of the target.
25 |
26 | Returns:
27 | The rendered mbed_config template.
28 | """
29 | env = jinja2.Environment(loader=jinja2.PackageLoader("mbed_tools.build", str(TEMPLATES_DIRECTORY)),)
30 | env.filters["to_hex"] = to_hex
31 | template = env.get_template(TEMPLATE_NAME)
32 | config["supported_c_libs"] = [x for x in config["supported_c_libs"][toolchain_name.lower()]]
33 | context = {"target_name": target_name, "toolchain_name": toolchain_name, **config}
34 | return template.render(context)
35 |
36 |
37 | def to_hex(s: Any) -> str:
38 | """Filter to convert integers to hex."""
39 | return hex(int(s, 0))
40 |
--------------------------------------------------------------------------------
/setup.cfg:
--------------------------------------------------------------------------------
1 | [metadata]
2 | license_file = LICENSE
3 |
4 | [mypy]
5 | # flake8-mypy expects the two following for sensible formatting
6 | show_column_numbers = True
7 | show_error_context = False
8 |
9 | # do not follow imports (except for ones found in typeshed)
10 | follow_imports = skip
11 |
12 | # since we're ignoring imports, writing .mypy_cache doesn't make any sense
13 | cache_dir = /dev/null
14 |
15 |
16 | ignore_missing_imports = True
17 | disallow_untyped_calls = True
18 | warn_return_any = True
19 | strict_optional = True
20 | warn_no_return = True
21 | warn_redundant_casts = True
22 | warn_unused_ignores = True
23 | disallow_untyped_defs = True
24 | check_untyped_defs = True
25 |
26 | [flake8]
27 | exclude =
28 | .git,
29 | .tox,
30 | .venv,
31 | __pycache__,
32 | docs,
33 | news,
34 | dist
35 | ignore =
36 | # H301: one import per line
37 | H301,
38 | # H306: imports not in alphabetical order
39 | H306,
40 | # W503: line break before binary operator (this is no longer PEP8 compliant)
41 | W503,
42 | # E203: whitespace before ':' (this is not PEP8 compliant)
43 | E203,
44 | per-file-ignores =
45 | # Package level init files improve user experience by short cutting imports.
46 | # F401: imported but unused
47 | __init__.py:F401
48 | # Don't require docstrings in tests.
49 | # We evaluate the need for them on case by case basis.
50 | tests/*.py:D1
51 | max-line-length = 120
52 | docstring-convention = google
53 | copyright-check = True
54 | select = E,F,W,C,B,H,D
55 |
--------------------------------------------------------------------------------
/tests/project/_internal/test_progress.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2020-2021 Arm Limited and Contributors. All rights reserved.
3 | # SPDX-License-Identifier: Apache-2.0
4 | #
5 | from unittest import TestCase, mock
6 |
7 | from mbed_tools.project._internal.progress import ProgressReporter, ProgressBar
8 |
9 |
10 | class TestProgressBar(TestCase):
11 | @mock.patch("mbed_tools.project._internal.progress.ProgressBar.update")
12 | def test_updates_progress_bar_with_correct_block_size(self, mock_bar_update):
13 | bar = ProgressBar(total=100)
14 | bar.update_progress(1, 1)
15 |
16 | mock_bar_update.assert_called_once_with(1)
17 |
18 | def test_sets_total_attribute_to_value_of_total_size(self):
19 | bar = ProgressBar()
20 |
21 | self.assertIsNone(bar.total)
22 |
23 | bar.update_progress(1, 2, total_size=33)
24 |
25 | self.assertEqual(bar.total, 33)
26 |
27 |
28 | @mock.patch("mbed_tools.project._internal.progress.ProgressBar", autospec=True)
29 | class TestProgressReporter(TestCase):
30 | def test_creates_progress_bar_on_begin_opcode(self, mock_progress_bar):
31 | reporter = ProgressReporter()
32 | reporter._cur_line = "begin"
33 | reporter.update(reporter.BEGIN, 1)
34 |
35 | mock_progress_bar.assert_called_once()
36 |
37 | def test_closes_progress_bar_on_end_opcode(self, mock_progress_bar):
38 | reporter = ProgressReporter()
39 | reporter.bar = mock_progress_bar()
40 | reporter.update(reporter.END, 1)
41 |
42 | reporter.bar.close.assert_called_once()
43 | reporter.bar.update.assert_not_called()
44 |
--------------------------------------------------------------------------------
/tests/project/_internal/test_render_templates.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2020-2021 Arm Limited and Contributors. All rights reserved.
3 | # SPDX-License-Identifier: Apache-2.0
4 | #
5 | from pathlib import Path
6 | from unittest import mock
7 |
8 | from mbed_tools.project._internal.render_templates import (
9 | render_cmakelists_template,
10 | render_main_cpp_template,
11 | render_gitignore_template,
12 | )
13 |
14 |
15 | @mock.patch("mbed_tools.project._internal.render_templates.datetime")
16 | class TestRenderTemplates:
17 | def test_renders_cmakelists_template(self, mock_datetime, tmp_path):
18 | the_year = 3999
19 | mock_datetime.datetime.now.return_value.year = the_year
20 | program_name = "mytestprogram"
21 | file_path = Path(tmp_path, "mytestpath")
22 |
23 | render_cmakelists_template(file_path, program_name)
24 | output = file_path.read_text()
25 |
26 | assert str(the_year) in output
27 | assert program_name in output
28 |
29 | def test_renders_main_cpp_template(self, mock_datetime, tmp_path):
30 | the_year = 3999
31 | mock_datetime.datetime.now.return_value.year = the_year
32 | file_path = Path(tmp_path, "mytestpath")
33 |
34 | render_main_cpp_template(file_path)
35 |
36 | assert str(the_year) in file_path.read_text()
37 |
38 | def test_renders_gitignore_template(self, _, tmp_path):
39 | file_path = Path(tmp_path, "mytestpath")
40 |
41 | render_gitignore_template(file_path)
42 |
43 | assert "cmake_build" in file_path.read_text()
44 | assert ".mbedbuild" in file_path.read_text()
45 |
--------------------------------------------------------------------------------
/src/mbed_tools/devices/_internal/detect_candidate_devices.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2020-2021 Arm Limited and Contributors. All rights reserved.
3 | # SPDX-License-Identifier: Apache-2.0
4 | #
5 | """Detect Mbed devices connected to host computer."""
6 | import platform
7 | from typing import Iterable
8 |
9 | from mbed_tools.devices._internal.candidate_device import CandidateDevice
10 | from mbed_tools.devices._internal.base_detector import DeviceDetector
11 | from mbed_tools.devices.exceptions import UnknownOSError
12 |
13 |
14 | def detect_candidate_devices() -> Iterable[CandidateDevice]:
15 | """Returns Candidates connected to host computer."""
16 | detector = _get_detector_for_current_os()
17 | return detector.find_candidates()
18 |
19 |
20 | def _get_detector_for_current_os() -> DeviceDetector:
21 | """Returns DeviceDetector for current operating system."""
22 | if platform.system() == "Windows":
23 | from mbed_tools.devices._internal.windows.device_detector import WindowsDeviceDetector
24 |
25 | return WindowsDeviceDetector()
26 | if platform.system() == "Linux":
27 | from mbed_tools.devices._internal.linux.device_detector import LinuxDeviceDetector
28 |
29 | return LinuxDeviceDetector()
30 | if platform.system() == "Darwin":
31 | from mbed_tools.devices._internal.darwin.device_detector import DarwinDeviceDetector
32 |
33 | return DarwinDeviceDetector()
34 |
35 | raise UnknownOSError(
36 | f"We have detected the OS you are running is '{platform.system()}'. "
37 | "Unfortunately we haven't implemented device detection support for this OS yet. Sorry!"
38 | )
39 |
--------------------------------------------------------------------------------
/src/mbed_tools/devices/_internal/darwin/ioreg.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2020-2021 Arm Limited and Contributors. All rights reserved.
3 | # SPDX-License-Identifier: Apache-2.0
4 | #
5 | """Interactions with `ioreg`."""
6 | import plistlib
7 | import subprocess
8 | from typing import Any, Dict, Iterable, List, Optional, cast
9 | from xml.parsers.expat import ExpatError
10 |
11 |
12 | def get_data(device_name: str) -> List[Dict]:
13 | """Returns parsed output of `ioreg` call for a given device name."""
14 | output = subprocess.check_output(["ioreg", "-a", "-r", "-n", device_name, "-l"])
15 | if output:
16 | try:
17 | return cast(List[Dict], plistlib.loads(output))
18 | except ExpatError:
19 | # Some devices seem to produce corrupt data
20 | pass
21 | return []
22 |
23 |
24 | def get_io_dialin_device(device_name: str) -> Optional[str]:
25 | """Returns the value of "IODialinDevice" for a given device name."""
26 | ioreg_data = get_data(device_name)
27 | dialin_device: Optional[str] = _find_first_property_value("IODialinDevice", ioreg_data)
28 | return dialin_device
29 |
30 |
31 | def _find_first_property_value(property_name: str, data: Iterable[Dict]) -> Any:
32 | """Finds a first value of a given proprety name in data from `ioreg`, returns None if not found."""
33 | found_value = None
34 | for item in data:
35 | found_value = item.get(
36 | property_name,
37 | _find_first_property_value(property_name=property_name, data=item.get("IORegistryEntryChildren", [])),
38 | )
39 | if found_value:
40 | break
41 | return found_value
42 |
--------------------------------------------------------------------------------
/src/mbed_tools/devices/_internal/windows/serial_port_data_loader.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2020-2021 Arm Limited and Contributors. All rights reserved.
3 | # SPDX-License-Identifier: Apache-2.0
4 | #
5 | """Loads serial port data."""
6 | from typing import Optional, Generator, cast, List
7 |
8 | from mbed_tools.devices._internal.windows.system_data_loader import SystemDataLoader, ComponentsLoader
9 | from mbed_tools.devices._internal.windows.usb_device_identifier import UsbIdentifier, parse_device_id
10 | from mbed_tools.devices._internal.windows.serial_port import SerialPort
11 |
12 |
13 | class SystemSerialPortInformation:
14 | """All information about the serial ports of the current system."""
15 |
16 | def __init__(self, data_loader: SystemDataLoader) -> None:
17 | """Initialiser."""
18 | self._serial_port_by_usb_id: Optional[dict] = None
19 | self._data_loader = data_loader
20 |
21 | def _load_data(self) -> None:
22 | self._serial_port_by_usb_id = {
23 | parse_device_id(p.pnp_id): p
24 | for p in cast(
25 | Generator[SerialPort, None, None], ComponentsLoader(self._data_loader, SerialPort).element_generator()
26 | )
27 | }
28 |
29 | @property
30 | def serial_port_data_by_id(self) -> dict:
31 | """Gets system's serial ports by usb id."""
32 | if not self._serial_port_by_usb_id:
33 | self._load_data()
34 | return self._serial_port_by_usb_id if self._serial_port_by_usb_id else dict()
35 |
36 | def get_serial_port_information(self, usb_id: UsbIdentifier) -> List[SerialPort]:
37 | """Gets all disk information for a given serial number."""
38 | port = self.serial_port_data_by_id.get(usb_id)
39 | return [port] if port else list()
40 |
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (C) 2020 Arm Mbed. All rights reserved.
3 | # SPDX-License-Identifier: Apache-2.0
4 | #
5 |
6 | [build-system]
7 | requires = ["setuptools>=42", "wheel", "setuptools_scm[toml]>=3.4"]
8 | build-backend = "setuptools.build_meta"
9 |
10 | [ProjectConfig]
11 | # Only path variables can and must contain 'DIR', 'PATH' or 'ROOT' in their name as
12 | # these tokens are used to identify path variables from other variable types.
13 | PROJECT_ROOT = "."
14 | PROJECT_NAME = "Mbed Tools"
15 | PROJECT_UUID = "a58676f2-0505-4916-84ee-2ef116b07352"
16 | PACKAGE_NAME = "mbed-tools"
17 | NEWS_DIR = "news/"
18 | SOURCE_DIR = "src/mbed_tools"
19 | RELEASE_BRANCH_PATTERN = "^release.*$"
20 | MODULE_TO_DOCUMENT = "mbed_tools"
21 | CHANGELOG_FILE_PATH = "CHANGELOG.md"
22 |
23 | [tool.setuptools_scm]
24 | local_scheme = "no-local-version"
25 |
26 | [tool.towncrier]
27 | directory = "news"
28 | filename = "CHANGELOG.md"
29 | package = "mbed_tools"
30 | title_format = "{version} ({project_date})"
31 | start_string = """
32 | [//]: # (begin_release_notes)
33 | """
34 |
35 | [[tool.towncrier.type]]
36 | directory = "major"
37 | name = "Major changes"
38 | showcontent = true
39 |
40 | [[tool.towncrier.type]]
41 | directory = "feature"
42 | name = "Features"
43 | showcontent = true
44 |
45 | [[tool.towncrier.type]]
46 | directory = "bugfix"
47 | name = "Bugfixes"
48 | showcontent = true
49 |
50 | [[tool.towncrier.type]]
51 | directory = "doc"
52 | name = "Improved Documentation"
53 | showcontent = true
54 |
55 | [[tool.towncrier.type]]
56 | directory = "removal"
57 | name = "Deprecations and Removals"
58 | showcontent = true
59 |
60 | [[tool.towncrier.type]]
61 | directory = "misc"
62 | name = "Misc"
63 | showcontent = false
64 |
65 | [tool.black]
66 | diff = true
67 | color = true
68 | line-length = 120
69 |
--------------------------------------------------------------------------------
/src/mbed_tools/targets/get_target.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2020-2021 Arm Limited and Contributors. All rights reserved.
3 | # SPDX-License-Identifier: Apache-2.0
4 | #
5 | """Interface for accessing Targets from Mbed OS's targets.json.
6 |
7 | An instance of `mbed_tools.targets.target.Target`
8 | can be retrieved by calling one of the public functions.
9 | """
10 | from mbed_tools.targets.exceptions import TargetError
11 | from mbed_tools.targets._internal import target_attributes
12 |
13 |
14 | def get_target_by_name(name: str, targets_json_data: dict) -> dict:
15 | """Returns a dictionary of attributes for the target whose name matches the name given.
16 |
17 | The target is as defined in the targets.json file found in the Mbed OS library.
18 |
19 | Args:
20 | name: the name of the Target to be returned
21 | targets_json_data: target definitions from targets.json
22 |
23 | Raises:
24 | TargetError: an error has occurred while fetching target
25 | """
26 | try:
27 | return target_attributes.get_target_attributes(targets_json_data, name)
28 | except (FileNotFoundError, target_attributes.TargetAttributesError) as e:
29 | raise TargetError(e) from e
30 |
31 |
32 | def get_target_by_board_type(board_type: str, targets_json_data: dict) -> dict:
33 | """Returns the target whose name matches a board's build_type.
34 |
35 | The target is as defined in the targets.json file found in the Mbed OS library.
36 |
37 | Args:
38 | board_type: a board's board_type (see `mbed_tools.targets.board.Board`)
39 | targets_json_data: target definitions from targets.json
40 |
41 | Raises:
42 | TargetError: an error has occurred while fetching target
43 | """
44 | return get_target_by_name(board_type, targets_json_data)
45 |
--------------------------------------------------------------------------------
/src/mbed_tools/devices/_internal/windows/logical_disk.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2020-2021 Arm Limited and Contributors. All rights reserved.
3 | # SPDX-License-Identifier: Apache-2.0
4 | #
5 | """Defines a Logical disk."""
6 |
7 | from typing import NamedTuple, cast
8 |
9 | from mbed_tools.devices._internal.windows.component_descriptor import ComponentDescriptor
10 |
11 |
12 | class LogicalDiskMsdnDefinition(NamedTuple):
13 | """Msdn definition of a logical disk.
14 |
15 | See https://docs.microsoft.com/en-us/windows/win32/cimwin32prov/cim-logicaldisk
16 | """
17 |
18 | Access: int
19 | Availability: int
20 | BlockSize: int
21 | Caption: str
22 | ConfigManagerErrorCode: int
23 | ConfigManagerUserConfig: bool
24 | CreationClassName: str
25 | Description: str
26 | DeviceID: str
27 | ErrorCleared: bool
28 | ErrorDescription: str
29 | ErrorMethodology: str
30 | FreeSpace: int
31 | InstallDate: int
32 | LastErrorCode: int
33 | Name: str
34 | NumberOfBlocks: int
35 | PNPDeviceID: str
36 | PowerManagementCapabilities: list
37 | PowerManagementSupported: bool
38 | Purpose: str
39 | Size: int
40 | Status: str
41 | StatusInfo: int
42 | SystemCreationClassName: str
43 | SystemName: str
44 |
45 |
46 | class LogicalDisk(ComponentDescriptor):
47 | """Logical disk as defined in Windows API.
48 |
49 | See https://docs.microsoft.com/en-us/windows/win32/cimwin32prov/cim-logicaldisk
50 | """
51 |
52 | def __init__(self) -> None:
53 | """Initialiser."""
54 | super().__init__(LogicalDiskMsdnDefinition, win32_class_name="CIM_LogicalDisk")
55 |
56 | @property
57 | def component_id(self) -> str:
58 | """Returns the device id field."""
59 | return cast(str, self.get("DeviceID"))
60 |
--------------------------------------------------------------------------------
/tests/devices/_internal/windows/test_component_descriptor_utils.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2020-2021 Arm Limited and Contributors. All rights reserved.
3 | # SPDX-License-Identifier: Apache-2.0
4 | #
5 | import unittest
6 | from mbed_tools.devices._internal.windows.component_descriptor_utils import (
7 | is_undefined_data_object,
8 | is_undefined_value,
9 | data_object_to_dict,
10 | UNKNOWN_VALUE,
11 | )
12 | from collections import namedtuple
13 | import random
14 |
15 |
16 | def generate_valid_values():
17 | return random.choice(["a test", 4646.454, 54, True])
18 |
19 |
20 | def generate_undefined_values():
21 | return random.choice([0, None, False, UNKNOWN_VALUE])
22 |
23 |
24 | class TestUtilities(unittest.TestCase):
25 | def test_is_value_undefined(self):
26 | self.assertTrue(is_undefined_value(generate_undefined_values()))
27 | self.assertFalse(is_undefined_value(generate_valid_values()))
28 |
29 | def test_is_data_object_undefined(self):
30 | field_number = 30
31 | DataObjectType = namedtuple("data_object_example", [f"field{i}" for i in range(0, field_number)])
32 | test1 = DataObjectType(*[generate_undefined_values() for i in range(0, field_number)])
33 | self.assertTrue(is_undefined_data_object(test1))
34 | test2 = DataObjectType(*[generate_valid_values() for i in range(0, field_number)])
35 | self.assertFalse(is_undefined_data_object(test2))
36 |
37 | def test_to_dict(self):
38 | field_number = 30
39 | DataObjectType = namedtuple("data_object_example", [f"field{i}" for i in range(0, field_number)])
40 | expected_dictionary = {
41 | f"field{i}": random.choice([generate_valid_values(), generate_undefined_values()])
42 | for i in range(0, field_number)
43 | }
44 | test = DataObjectType(**expected_dictionary)
45 | self.assertDictEqual(data_object_to_dict(test), expected_dictionary)
46 |
--------------------------------------------------------------------------------
/src/mbed_tools/devices/_internal/windows/disk_partition_logical_disk_relationships.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2020-2021 Arm Limited and Contributors. All rights reserved.
3 | # SPDX-License-Identifier: Apache-2.0
4 | #
5 | """Defines a Disk partition/Logical disk relationship."""
6 |
7 | from typing import NamedTuple
8 |
9 | from mbed_tools.devices._internal.windows.component_descriptor import ComponentDescriptor
10 |
11 |
12 | class DiskToPartitionMsdnDefinition(NamedTuple):
13 | """Msdn definition of a disk partition - logical disk relationship.
14 |
15 | See https://docs.microsoft.com/en-us/windows/win32/cimwin32prov/win32-logicaldisktopartition
16 | """
17 |
18 | EndingAddress: int
19 | StartingAddress: int
20 | Antecedent: str
21 | Dependent: str
22 |
23 |
24 | class DiskPartitionLogicalDiskRelationship(ComponentDescriptor):
25 | """Disk partition as defined in Windows API.
26 |
27 | See https://docs.microsoft.com/en-us/windows/win32/cimwin32prov/win32-logicaldisktopartition
28 | """
29 |
30 | def __init__(self) -> None:
31 | """Initialiser."""
32 | super().__init__(DiskToPartitionMsdnDefinition, win32_class_name="Win32_LogicalDiskToPartition")
33 |
34 | @property
35 | def component_id(self) -> str:
36 | """Returns the device id field."""
37 | return f"{self.get('Antecedent')}->{self.get('Dependent')}"
38 |
39 | @staticmethod
40 | def _parse_reference(ref: str) -> str:
41 | """Parses a Win32 to only retain the value."""
42 | return ref.replace("'", "").replace('"', "").split("=")[1]
43 |
44 | @property
45 | def logical_disk_id(self) -> str:
46 | """Gets the logical disk id."""
47 | return DiskPartitionLogicalDiskRelationship._parse_reference(self.get("Dependent"))
48 |
49 | @property
50 | def disk_partition_id(self) -> str:
51 | """Gets the disk partition id."""
52 | return DiskPartitionLogicalDiskRelationship._parse_reference(self.get("Antecedent"))
53 |
--------------------------------------------------------------------------------
/src/mbed_tools/devices/_internal/windows/usb_controller.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2020-2021 Arm Limited and Contributors. All rights reserved.
3 | # SPDX-License-Identifier: Apache-2.0
4 | #
5 | """Defines a USB controller."""
6 |
7 | from typing import NamedTuple, cast
8 |
9 | from mbed_tools.devices._internal.windows.component_descriptor import ComponentDescriptor
10 |
11 |
12 | class UsbControllerMsdnDefinition(NamedTuple):
13 | """Msdn definition of a USB controller.
14 |
15 | See https://docs.microsoft.com/en-gb/windows/win32/cimwin32prov/win32-usbcontroller?redirectedfrom=MSDN
16 | Similar to https://docs.microsoft.com/en-gb/windows/win32/cimwin32prov/win32-usbcontrollerdevice
17 | """
18 |
19 | Availability: int
20 | Caption: str
21 | ConfigManagerErrorCode: int
22 | ConfigManagerUserConfig: bool
23 | CreationClassName: str
24 | Description: str
25 | DeviceID: str
26 | ErrorCleared: bool
27 | ErrorDescription: str
28 | InstallDate: int
29 | LastErrorCode: int
30 | Manufacturer: str
31 | MaxNumberControlled: int
32 | Name: str
33 | PNPDeviceID: str
34 | PowerManagementCapabilities: list
35 | PowerManagementSupported: bool
36 | ProtocolSupported: int
37 | Status: str
38 | StatusInfo: int
39 | SystemCreationClassName: str
40 | SystemName: str
41 | TimeOfLastReset: int
42 |
43 |
44 | class UsbController(ComponentDescriptor):
45 | """USB Controller as defined in Windows API.
46 |
47 | See https://docs.microsoft.com/en-gb/windows/win32/cimwin32prov/win32-usbcontroller?redirectedfrom=MSDN
48 | Similar to https://docs.microsoft.com/en-gb/windows/win32/cimwin32prov/win32-usbcontrollerdevice
49 | """
50 |
51 | def __init__(self) -> None:
52 | """Initialiser."""
53 | super().__init__(UsbControllerMsdnDefinition, win32_class_name="Win32_USBController")
54 |
55 | @property
56 | def component_id(self) -> str:
57 | """Returns the device id field."""
58 | return cast(str, self.get("DeviceID"))
59 |
--------------------------------------------------------------------------------
/src/mbed_tools/devices/_internal/windows/device_detector.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2020-2021 Arm Limited and Contributors. All rights reserved.
3 | # SPDX-License-Identifier: Apache-2.0
4 | #
5 | """Defines a device detector for Windows."""
6 | from pathlib import Path
7 | from typing import List
8 |
9 | from mbed_tools.devices._internal.base_detector import DeviceDetector
10 | from mbed_tools.devices._internal.candidate_device import CandidateDevice
11 | from mbed_tools.devices._internal.windows.system_data_loader import SystemDataLoader
12 | from mbed_tools.devices._internal.windows.usb_data_aggregation import SystemUsbData, AggregatedUsbData
13 |
14 |
15 | class WindowsDeviceDetector(DeviceDetector):
16 | """Windows specific implementation of device detection."""
17 |
18 | def __init__(self) -> None:
19 | """Initialiser."""
20 | self._data_loader = SystemDataLoader()
21 |
22 | def find_candidates(self) -> List[CandidateDevice]:
23 | """Return a generator of Candidates."""
24 | return [
25 | WindowsDeviceDetector.map_to_candidate(usb)
26 | for usb in SystemUsbData(data_loader=self._data_loader).all()
27 | if WindowsDeviceDetector.is_valid_candidate(usb)
28 | ]
29 |
30 | @staticmethod
31 | def map_to_candidate(usb_data: AggregatedUsbData) -> CandidateDevice:
32 | """Maps a USB device to a candidate."""
33 | serial_port = next(iter(usb_data.get("serial_port")), None)
34 | uid = usb_data.uid
35 | return CandidateDevice(
36 | product_id=uid.product_id,
37 | vendor_id=uid.vendor_id,
38 | mount_points=tuple(Path(disk.component_id) for disk in usb_data.get("disks")),
39 | serial_number=uid.uid.presumed_serial_number,
40 | serial_port=serial_port.port_name if serial_port else None,
41 | )
42 |
43 | @staticmethod
44 | def is_valid_candidate(usb_data: AggregatedUsbData) -> bool:
45 | """States whether the usb device is a valid candidate or not."""
46 | return usb_data.is_composite and usb_data.is_associated_with_disk
47 |
--------------------------------------------------------------------------------
/src/mbed_tools/project/_internal/render_templates.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2020-2021 Arm Limited and Contributors. All rights reserved.
3 | # SPDX-License-Identifier: Apache-2.0
4 | #
5 | """Render jinja templates required by the project package."""
6 | import datetime
7 |
8 | from pathlib import Path
9 |
10 | import jinja2
11 |
12 | TEMPLATES_DIRECTORY = Path("_internal", "templates")
13 |
14 |
15 | def render_cmakelists_template(cmakelists_file: Path, program_name: str) -> None:
16 | """Render CMakeLists.tmpl with the copyright year and program name as the app target name.
17 |
18 | Args:
19 | cmakelists_file: The path where CMakeLists.txt will be written.
20 | program_name: The name of the program, will be used as the app target name.
21 | """
22 | cmakelists_file.write_text(
23 | render_jinja_template(
24 | "CMakeLists.tmpl", {"program_name": program_name, "year": str(datetime.datetime.now().year)}
25 | )
26 | )
27 |
28 |
29 | def render_main_cpp_template(main_cpp: Path) -> None:
30 | """Render a basic main.cpp which prints a hello message and returns.
31 |
32 | Args:
33 | main_cpp: Path where the main.cpp file will be written.
34 | """
35 | main_cpp.write_text(render_jinja_template("main.tmpl", {"year": str(datetime.datetime.now().year)}))
36 |
37 |
38 | def render_gitignore_template(gitignore: Path) -> None:
39 | """Write out a basic gitignore file ignoring the build and config directory.
40 |
41 | Args:
42 | gitignore: The path where the gitignore file will be written.
43 | """
44 | gitignore.write_text(render_jinja_template("gitignore.tmpl", {}))
45 |
46 |
47 | def render_jinja_template(template_name: str, context: dict) -> str:
48 | """Render a jinja template.
49 |
50 | Args:
51 | template_name: The name of the template being rendered.
52 | context: Data to render into the jinja template.
53 | """
54 | env = jinja2.Environment(loader=jinja2.PackageLoader("mbed_tools.project", str(TEMPLATES_DIRECTORY)))
55 | template = env.get_template(template_name)
56 | return template.render(context)
57 |
--------------------------------------------------------------------------------
/tests/devices/_internal/test_detect_candidate_devices.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2020-2021 Arm Limited and Contributors. All rights reserved.
3 | # SPDX-License-Identifier: Apache-2.0
4 | #
5 | import pytest
6 | from unittest import mock
7 |
8 | from tests.devices.markers import windows_only, darwin_only, linux_only
9 | from mbed_tools.devices._internal.base_detector import DeviceDetector
10 | from mbed_tools.devices.exceptions import UnknownOSError
11 | from mbed_tools.devices._internal.detect_candidate_devices import (
12 | detect_candidate_devices,
13 | _get_detector_for_current_os,
14 | )
15 |
16 |
17 | class TestDetectCandidateDevices:
18 | @mock.patch("mbed_tools.devices._internal.detect_candidate_devices._get_detector_for_current_os")
19 | def test_returns_candidates_using_os_specific_detector(self, _get_detector_for_current_os):
20 | detector = mock.Mock(spec_set=DeviceDetector)
21 | _get_detector_for_current_os.return_value = detector
22 | assert detect_candidate_devices() == detector.find_candidates.return_value
23 |
24 |
25 | class TestGetDetectorForCurrentOS:
26 | @windows_only
27 | def test_windows_uses_correct_module(self):
28 | from mbed_tools.devices._internal.windows.device_detector import WindowsDeviceDetector
29 |
30 | assert isinstance(_get_detector_for_current_os(), WindowsDeviceDetector)
31 |
32 | @darwin_only
33 | def test_darwin_uses_correct_module(self):
34 | from mbed_tools.devices._internal.darwin.device_detector import DarwinDeviceDetector
35 |
36 | assert isinstance(_get_detector_for_current_os(), DarwinDeviceDetector)
37 |
38 | @linux_only
39 | def test_linux_uses_correct_module(self):
40 | from mbed_tools.devices._internal.linux.device_detector import LinuxDeviceDetector
41 |
42 | assert isinstance(_get_detector_for_current_os(), LinuxDeviceDetector)
43 |
44 | @mock.patch("platform.system")
45 | def test_raises_when_os_is_unknown(self, platform_system):
46 | os_name = "SomethingNobodyUses"
47 | platform_system.return_value = os_name
48 |
49 | with pytest.raises(UnknownOSError):
50 | _get_detector_for_current_os()
51 |
--------------------------------------------------------------------------------
/src/mbed_tools/build/build.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2020-2021 Arm Limited and Contributors. All rights reserved.
3 | # SPDX-License-Identifier: Apache-2.0
4 | #
5 | """Configure and build a CMake project."""
6 | import logging
7 | import pathlib
8 | import subprocess
9 |
10 | from typing import Optional
11 |
12 | from mbed_tools.build.exceptions import MbedBuildError
13 |
14 |
15 | logger = logging.getLogger(__name__)
16 |
17 |
18 | def build_project(build_dir: pathlib.Path, target: Optional[str] = None) -> None:
19 | """Build a project using CMake to invoke Ninja.
20 |
21 | Args:
22 | build_dir: Path to the CMake build tree.
23 | target: The CMake target to build (e.g 'install')
24 | """
25 | _check_ninja_found()
26 | target_flag = ["--target", target] if target is not None else []
27 | _cmake_wrapper("--build", str(build_dir), *target_flag)
28 |
29 |
30 | def generate_build_system(source_dir: pathlib.Path, build_dir: pathlib.Path, profile: str) -> None:
31 | """Configure a project using CMake.
32 |
33 | Args:
34 | source_dir: Path to the CMake source tree.
35 | build_dir: Path to the CMake build tree.
36 | profile: The Mbed build profile (develop, debug or release).
37 | """
38 | _check_ninja_found()
39 | _cmake_wrapper("-S", str(source_dir), "-B", str(build_dir), "-GNinja", f"-DCMAKE_BUILD_TYPE={profile}")
40 |
41 |
42 | def _cmake_wrapper(*cmake_args: str) -> None:
43 | try:
44 | logger.debug("Running CMake with args: %s", cmake_args)
45 | subprocess.run(["cmake", *cmake_args], check=True)
46 | except FileNotFoundError:
47 | raise MbedBuildError("Could not find CMake. Please ensure CMake is installed and added to PATH.")
48 | except subprocess.CalledProcessError:
49 | raise MbedBuildError("CMake invocation failed!")
50 |
51 |
52 | def _check_ninja_found() -> None:
53 | try:
54 | subprocess.run(["ninja", "--version"], check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
55 | except FileNotFoundError:
56 | raise MbedBuildError(
57 | "Could not find the 'Ninja' build program. Please ensure 'Ninja' is installed and added to PATH."
58 | )
59 |
--------------------------------------------------------------------------------
/src/mbed_tools/devices/_internal/windows/disk_partition.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2020-2021 Arm Limited and Contributors. All rights reserved.
3 | # SPDX-License-Identifier: Apache-2.0
4 | #
5 | """Defines a Disk partition."""
6 |
7 | from typing import NamedTuple, cast
8 |
9 | from mbed_tools.devices._internal.windows.component_descriptor import ComponentDescriptor
10 |
11 |
12 | class DiskPartitionMsdnDefinition(NamedTuple):
13 | """Msdn definition of a disk partition.
14 |
15 | See https://docs.microsoft.com/en-us/windows/win32/cimwin32prov/win32-diskpartition
16 | """
17 |
18 | AdditionalAvailability: int
19 | Availability: int
20 | PowerManagementCapabilities: list
21 | IdentifyingDescriptions: list
22 | MaxQuiesceTime: int
23 | OtherIdentifyingInfo: int
24 | StatusInfo: int
25 | PowerOnHours: int
26 | TotalPowerOnHours: int
27 | Access: int
28 | BlockSize: int
29 | Bootable: bool
30 | BootPartition: bool
31 | Caption: str
32 | ConfigManagerErrorCode: int
33 | ConfigManagerUserConfig: bool
34 | CreationClassName: str
35 | Description: str
36 | DeviceID: str
37 | DiskIndex: int
38 | ErrorCleared: bool
39 | ErrorDescription: str
40 | ErrorMethodology: str
41 | HiddenSectors: int
42 | Index: int
43 | InstallDate: int
44 | LastErrorCode: int
45 | Name: str
46 | NumberOfBlocks: int
47 | PNPDeviceID: str
48 | PowerManagementSupported: bool
49 | PrimaryPartition: bool
50 | Purpose: str
51 | RewritePartition: bool
52 | Size: int
53 | StartingOffset: int
54 | Status: str
55 | SystemCreationClassName: str
56 | SystemName: str
57 | Type: str
58 |
59 |
60 | class DiskPartition(ComponentDescriptor):
61 | """Disk partition as defined in Windows API.
62 |
63 | See https://docs.microsoft.com/en-us/windows/win32/cimwin32prov/win32-diskpartition
64 | """
65 |
66 | def __init__(self) -> None:
67 | """Initialiser."""
68 | super().__init__(DiskPartitionMsdnDefinition, win32_class_name="Win32_DiskPartition")
69 |
70 | @property
71 | def component_id(self) -> str:
72 | """Returns the device id field."""
73 | return cast(str, self.get("DeviceID"))
74 |
--------------------------------------------------------------------------------
/src/mbed_tools/build/flash.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2020-2021 Arm Limited and Contributors. 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.build.exceptions import BinaryFileNotFoundError
13 |
14 |
15 | def _flash_dev(disk: pathlib.Path, image_path: pathlib.Path) -> None:
16 | """Flash device using copy method.
17 |
18 | Args:
19 | disk: Device mount point.
20 | image_path: Image file to be copied to device.
21 | """
22 | shutil.copy(image_path, disk, follow_symlinks=False)
23 | if not platform.system() == "Windows":
24 | os.sync()
25 |
26 |
27 | def _build_binary_file_path(program_path: pathlib.Path, build_dir: pathlib.Path, hex_file: bool) -> pathlib.Path:
28 | """Build binary file name.
29 |
30 | Args:
31 | program_path: Path to the Mbed project.
32 | build_dir: Path to the CMake build folder.
33 | hex_file: Use hex file.
34 |
35 | Returns:
36 | Path to binary file.
37 |
38 | Raises:
39 | BinaryFileNotFoundError: binary file not found in the path specified
40 | """
41 | fw_fbase = build_dir / program_path.name
42 | fw_file = fw_fbase.with_suffix(".hex" if hex_file else ".bin")
43 | if not fw_file.exists():
44 | raise BinaryFileNotFoundError(f"Build program file (firmware) not found {fw_file}")
45 | return fw_file
46 |
47 |
48 | def flash_binary(
49 | mount_point: pathlib.Path, program_path: pathlib.Path, build_dir: pathlib.Path, mbed_target: str, hex_file: bool
50 | ) -> pathlib.Path:
51 | """Flash binary onto a device.
52 |
53 | Look through the connected devices and flash the binary if the connected and built target matches.
54 |
55 | Args:
56 | mount_point: Mount point of the target device.
57 | program_path: Path to the Mbed project.
58 | build_dir: Path to the CMake build folder.
59 | mbed_target: The name of the Mbed target to build for.
60 | hex_file: Use hex file.
61 | """
62 | fw_file = _build_binary_file_path(program_path, build_dir, hex_file)
63 | _flash_dev(mount_point, fw_file)
64 | return fw_file
65 |
--------------------------------------------------------------------------------
/src/mbed_tools/devices/_internal/windows/usb_hub.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2020-2021 Arm Limited and Contributors. All rights reserved.
3 | # SPDX-License-Identifier: Apache-2.0
4 | #
5 | """Defines a USB hub."""
6 |
7 | from typing import NamedTuple, cast
8 |
9 | from mbed_tools.devices._internal.windows.component_descriptor import ComponentDescriptor
10 |
11 |
12 | class UsbHubMsdnDefinition(NamedTuple):
13 | """Msdn definition of a Usb hub.
14 |
15 | See https://docs.microsoft.com/en-gb/previous-versions/windows/desktop/cimwin32a/win32-usbhub?redirectedfrom=MSDN
16 | See also https://docs.microsoft.com/en-us/windows/win32/cimwin32prov/cim-usbdevice
17 | """
18 |
19 | Availability: int
20 | Caption: str
21 | ClassCode: int
22 | ConfigManagerUserConfig: bool
23 | CreationClassName: str
24 | CurrentAlternateSettings: list
25 | CurrentConfigValue: int
26 | Description: str
27 | ErrorCleared: bool
28 | ErrorDescription: str
29 | GangSwitched: bool
30 | InstallDate: int
31 | LastErrorCode: int
32 | NumberOfConfigs: int
33 | NumberOfPorts: int
34 | PNPDeviceID: str
35 | PowerManagementCapabilities: list
36 | PowerManagementSupported: bool
37 | ProtocolCode: int
38 | Status: str
39 | StatusInfo: int
40 | SubclassCode: int
41 | SystemCreationClassName: str
42 | SystemName: str
43 | USBVersion: int
44 | ConfigManagerErrorCode: int
45 | DeviceID: str
46 | Name: str
47 |
48 |
49 | class UsbHub(ComponentDescriptor):
50 | """USB Hub as defined in Windows API.
51 |
52 | See https://docs.microsoft.com/en-gb/previous-versions/windows/desktop/cimwin32a/win32-usbhub?redirectedfrom=MSDN
53 | Seems similar to https://docs.microsoft.com/en-us/windows/win32/cimwin32prov/cim-usbhub
54 | """
55 |
56 | def __init__(self) -> None:
57 | """Initialiser."""
58 | super().__init__(UsbHubMsdnDefinition, win32_class_name="Win32_USBHub")
59 |
60 | @property
61 | def component_id(self) -> str:
62 | """Returns the device id field."""
63 | return cast(str, self.get("DeviceID"))
64 |
65 | @property
66 | def pnp_id(self) -> str:
67 | """Returns the plug and play id field."""
68 | return cast(str, self.get("PNPDeviceID"))
69 |
--------------------------------------------------------------------------------
/ci_scripts/prep-release:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | set -e
4 |
5 | INITIAL_HEAD=$(git rev-parse HEAD)
6 | printf "HEAD before auto generating docs and target_database = ${INITIAL_HEAD}\n"
7 |
8 | printf "Attempting to update the target database snapshot with the latest changes\n"
9 | python ci_scripts/sync_board_database.py -vvv
10 | git add src/mbed_tools/targets/_internal/data/board_database_snapshot.json
11 | git add news/
12 |
13 | printf "Adding license headers\n"
14 | pre-commit run licenseheaders --all-files > /dev/null 2>&1 || true
15 |
16 | if ! NEW_VERSION=$(python ci_scripts/bump_version.py -vvv -n news/); then
17 | printf "No news files detected. Exiting.\n"
18 | exit 0
19 | fi
20 |
21 | # Update the index so we can check any recently staged changes
22 | git update-index -q --ignore-submodules --refresh
23 |
24 | if ! git diff-index --cached --quiet HEAD --ignore-submodules --
25 | then
26 | printf "Found staged changes to commit after target database update: $(git diff-index --cached --name-status --ignore-submodules HEAD)\n"
27 | # Towncrier will fail if it detects a news file that has not been committed to
28 | # the repository. So we need to ensure the news file generated by
29 | # sync_board_database.py was committed before running towncrier.
30 | git commit -m "Prepare mbed-tools release ${NEW_VERSION}"
31 | fi
32 |
33 | # Generate changelog from news fragments
34 | towncrier --yes --version "${NEW_VERSION}"
35 | git add ./CHANGELOG.md
36 |
37 | LATEST_HEAD=$(git rev-parse HEAD)
38 | if [ "$LATEST_HEAD" = "$INITIAL_HEAD" ]; then
39 | printf "No updates were made during the release process, committing CHANGELOG\n"
40 | git commit -m "Prepare mbed-tools release ${NEW_VERSION}"
41 | else
42 | printf "A commit was made to update the docs and/or target database. Amending the commit to include CHANGELOG\n"
43 | git commit --amend -C HEAD
44 | fi
45 |
46 | printf "Tagging version ${NEW_VERSION}"
47 | git tag "${NEW_VERSION}"
48 |
49 | # Push the commit and tag to the remote. Relies on `GIT_TOKEN` environment
50 | # variable being set.
51 | printf "Pushing tag and master branch to remote\n"
52 | git push "https://${GIT_TOKEN}@github.com/ARMmbed/mbed-tools.git" master
53 | git push "https://${GIT_TOKEN}@github.com/ARMmbed/mbed-tools.git" "${NEW_VERSION}"
54 |
--------------------------------------------------------------------------------
/tests/regression/test_configure.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2020-2021 Arm Limited and Contributors. All rights reserved.
3 | # SPDX-License-Identifier: Apache-2.0
4 | #
5 |
6 | import tempfile
7 | import pathlib
8 | import json
9 |
10 | from unittest import TestCase
11 |
12 | from click.testing import CliRunner
13 |
14 | from mbed_tools.cli.configure import configure
15 |
16 | target_json = json.dumps(
17 | {
18 | "TARGET": {
19 | "core": None,
20 | "trustzone": False,
21 | "default_toolchain": "ARM",
22 | "supported_toolchains": None,
23 | "extra_labels": [],
24 | "supported_form_factors": [],
25 | "components": [],
26 | "is_disk_virtual": False,
27 | "macros": [],
28 | "device_has": [],
29 | "features": [],
30 | "detect_code": [],
31 | "c_lib": "std",
32 | "bootloader_supported": False,
33 | "static_memory_defines": True,
34 | "printf_lib": "minimal-printf",
35 | "supported_c_libs": {"arm": ["std"], "gcc_arm": ["std", "small"], "iar": ["std"]},
36 | "supported_application_profiles": ["full"],
37 | }
38 | }
39 | )
40 |
41 | mbed_app_json = json.dumps(
42 | {"target_overrides": {"*": {"target.c_lib": "small", "target.printf_lib": "minimal-printf"}}}
43 | )
44 |
45 |
46 | class TestConfigureRegression(TestCase):
47 | def test_generate_config_called_with_correct_arguments(self):
48 | with tempfile.TemporaryDirectory() as tmpDir:
49 | tmpDirPath = pathlib.Path(tmpDir)
50 | pathlib.Path(tmpDirPath / "mbed-os.lib").write_text("https://github.com/ARMmbed/mbed-os")
51 | pathlib.Path(tmpDirPath / "mbed_app.json").write_text(mbed_app_json)
52 | pathlib.Path(tmpDirPath / "mbed-os").mkdir()
53 | pathlib.Path(tmpDirPath / "mbed-os" / "targets").mkdir()
54 | pathlib.Path(tmpDirPath / "mbed-os" / "targets" / "targets.json").write_text(target_json)
55 |
56 | result = CliRunner().invoke(
57 | configure, ["-m", "Target", "-t", "gcc_arm", "-p", tmpDir], catch_exceptions=False
58 | )
59 | self.assertIn("mbed_config.cmake has been generated and written to", result.output)
60 |
--------------------------------------------------------------------------------
/src/mbed_tools/cli/sterm.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2020-2021 Arm Limited and Contributors. All rights reserved.
3 | # SPDX-License-Identifier: Apache-2.0
4 | #
5 | """Command to launch a serial terminal to a connected Mbed device."""
6 | from typing import Any, Optional
7 |
8 | import click
9 |
10 | from mbed_tools.cli.build import _get_target_id
11 | from mbed_tools.devices import find_connected_device, get_connected_devices
12 | from mbed_tools.devices.exceptions import MbedDevicesError
13 | from mbed_tools.sterm import terminal
14 |
15 |
16 | @click.command(
17 | help="Open a serial terminal to a connected Mbed Enabled device, or connect to a user-specified COM port."
18 | )
19 | @click.option(
20 | "-p",
21 | "--port",
22 | type=str,
23 | help="Communication port. Default: auto-detect. Specifying this will also ignore the -m/--mbed-target option.",
24 | )
25 | @click.option("-b", "--baudrate", type=int, default=9600, show_default=True, help="Communication baudrate.")
26 | @click.option(
27 | "-e",
28 | "--echo",
29 | default="on",
30 | show_default=True,
31 | type=click.Choice(["on", "off"], case_sensitive=False),
32 | help="Switch local echo on/off.",
33 | )
34 | @click.option("-m", "--mbed-target", type=str, help="Mbed target to detect. Example: K64F, NUCLEO_F401RE, NRF51822...")
35 | def sterm(port: str, baudrate: int, echo: str, mbed_target: str) -> None:
36 | """Launches a serial terminal to a connected device."""
37 | if port is None:
38 | port = _find_target_serial_port_or_default(mbed_target)
39 |
40 | terminal.run(port, baudrate, echo=True if echo == "on" else False)
41 |
42 |
43 | def _get_connected_mbed_devices() -> Any:
44 | connected_devices = get_connected_devices()
45 | if not connected_devices.identified_devices:
46 | raise MbedDevicesError("No Mbed enabled devices found.")
47 |
48 | return connected_devices.identified_devices
49 |
50 |
51 | def _find_target_serial_port_or_default(target: Optional[str]) -> Any:
52 | if target is None:
53 | # just return the first valid device found
54 | device, *_ = _get_connected_mbed_devices()
55 | else:
56 | target_name, target_id = _get_target_id(target)
57 | device = find_connected_device(target_name.upper(), target_id)
58 | return device.serial_port
59 |
--------------------------------------------------------------------------------
/src/mbed_tools/project/_internal/progress.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2020-2021 Arm Limited and Contributors. All rights reserved.
3 | # SPDX-License-Identifier: Apache-2.0
4 | #
5 | """Progress bar for git operations."""
6 | import sys
7 |
8 | from typing import Optional, Any
9 |
10 | from git import RemoteProgress
11 | from tqdm import tqdm
12 |
13 |
14 | class ProgressBar(tqdm):
15 | """tqdm progress bar that can be used in a callback context."""
16 |
17 | total: Any
18 |
19 | def update_progress(self, block_num: float = 1, block_size: float = 1, total_size: float = None) -> None:
20 | """Update the progress bar.
21 |
22 | Args:
23 | block_num: Number of the current block.
24 | block_size: Size of the current block.
25 | total_size: Total size of all expected blocks.
26 | """
27 | if total_size is not None and self.total != total_size:
28 | self.total = total_size
29 | self.update(block_num * block_size - self.n)
30 |
31 |
32 | class ProgressReporter(RemoteProgress):
33 | """GitPython RemoteProgress subclass that displays a progress bar for git fetch and push operations."""
34 |
35 | def __init__(self, *args: Any, name: str = "", **kwargs: Any) -> None:
36 | """Initialiser.
37 |
38 | Args:
39 | name: The name of the git repository to report progress on.
40 | """
41 | self.name = name
42 | super().__init__(*args, **kwargs)
43 |
44 | def update(self, op_code: int, cur_count: float, max_count: Optional[float] = None, message: str = "") -> None:
45 | """Called whenever the progress changes.
46 |
47 | Args:
48 | op_code: Integer describing the stage of the current operation.
49 | cur_count: Current item count.
50 | max_count: Maximum number of items expected.
51 | message: Message string describing the number of bytes transferred in the WRITING operation.
52 | """
53 | if self.BEGIN & op_code:
54 | self.bar = ProgressBar(total=max_count, file=sys.stderr, leave=False)
55 |
56 | self.bar.desc = f"{self.name} {self._cur_line}"
57 | self.bar.update_progress(block_num=cur_count, total_size=max_count)
58 |
59 | if self.END & op_code:
60 | self.bar.close()
61 |
--------------------------------------------------------------------------------
/tests/targets/test_get_target.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2020-2021 Arm Limited and Contributors. All rights reserved.
3 | # SPDX-License-Identifier: Apache-2.0
4 | #
5 | from unittest import TestCase, mock
6 | from mbed_tools.targets.get_target import get_target_by_board_type, get_target_by_name
7 | from mbed_tools.targets.exceptions import TargetError
8 | from mbed_tools.targets._internal.target_attributes import TargetAttributesError
9 |
10 |
11 | class TestGetTarget(TestCase):
12 | @mock.patch("mbed_tools.targets.get_target.target_attributes.get_target_attributes")
13 | def test_get_by_name(self, mock_target_attrs):
14 | target_name = "Target"
15 | targets_json_file_path = "targets.json"
16 |
17 | result = get_target_by_name(target_name, targets_json_file_path)
18 |
19 | self.assertEqual(result, mock_target_attrs.return_value)
20 | mock_target_attrs.assert_called_once_with(targets_json_file_path, target_name)
21 |
22 | @mock.patch("mbed_tools.targets.get_target.target_attributes.get_target_attributes")
23 | def test_get_by_name_raises_target_error_when_target_json_not_found(self, mock_target_attrs):
24 | target_name = "Target"
25 | targets_json_file_path = "not-targets.json"
26 | mock_target_attrs.side_effect = FileNotFoundError
27 |
28 | with self.assertRaises(TargetError):
29 | get_target_by_name(target_name, targets_json_file_path)
30 |
31 | @mock.patch("mbed_tools.targets.get_target.target_attributes.get_target_attributes")
32 | def test_get_by_name_raises_target_error_when_target_attr_collection_fails(self, mock_target_attrs):
33 | target_name = "Target"
34 | targets_json_file_path = "targets.json"
35 | mock_target_attrs.side_effect = TargetAttributesError
36 |
37 | with self.assertRaises(TargetError):
38 | get_target_by_name(target_name, targets_json_file_path)
39 |
40 | @mock.patch("mbed_tools.targets.get_target.get_target_by_name")
41 | def test_get_by_board_type(self, mock_get_target_by_name):
42 | board_type = "Board"
43 | path_to_mbed_program = "somewhere"
44 |
45 | result = get_target_by_board_type(board_type, path_to_mbed_program)
46 |
47 | self.assertEqual(result, mock_get_target_by_name.return_value)
48 | mock_get_target_by_name.assert_called_once_with(board_type, path_to_mbed_program)
49 |
--------------------------------------------------------------------------------
/tests/devices/_internal/darwin/test_ioreg.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2020-2021 Arm Limited and Contributors. All rights reserved.
3 | # SPDX-License-Identifier: Apache-2.0
4 | #
5 | import plistlib
6 | from unittest import TestCase, mock
7 |
8 | from mbed_tools.devices._internal.darwin.ioreg import get_data, get_io_dialin_device
9 |
10 |
11 | @mock.patch("mbed_tools.devices._internal.darwin.ioreg.subprocess.check_output")
12 | class TestGetData(TestCase):
13 | def test_returns_data_from_ioreg_call(self, check_output):
14 | check_output.return_value = b"""
15 |
16 | foo
17 |
18 | """
19 |
20 | self.assertEqual(get_data("some device"), ["foo"])
21 | check_output.assert_called_once_with(["ioreg", "-a", "-r", "-n", "some device", "-l"])
22 |
23 | def test_handles_corrupt_data_gracefully(self, check_output):
24 | check_output.return_value = b"""
25 | S\xc3\xbfn\x06P\xc2\x87TT%A\t\xc2\x87
26 | """
27 |
28 | self.assertEqual(get_data("doesn't matter"), [])
29 |
30 |
31 | class TestGetIoDialinDevice(TestCase):
32 | @mock.patch("mbed_tools.devices._internal.darwin.ioreg.get_data")
33 | def test_identifies_nested_io_dialin_device_in_given_ioreg_data(self, get_data):
34 | plist = b"""
35 |
36 |
37 | IORegistryEntryChildren
38 |
39 |
40 |
41 | IORegistryEntryChildren
42 |
43 |
44 | IORegistryEntryChildren
45 |
46 |
47 | IODialinDevice
48 | /dev/tty.usbmodem1234
49 |
50 |
51 |
52 |
53 |
54 |
55 | """
56 | data = plistlib.loads(plist)
57 | get_data.return_value = data
58 |
59 | self.assertEqual(get_io_dialin_device("some_device"), "/dev/tty.usbmodem1234")
60 | get_data.assert_called_once_with("some_device")
61 |
--------------------------------------------------------------------------------
/tests/build/test_build.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2020-2021 Arm Limited and Contributors. All rights reserved.
3 | # SPDX-License-Identifier: Apache-2.0
4 | #
5 | import subprocess
6 |
7 | from unittest import mock
8 |
9 | import pytest
10 |
11 | from mbed_tools.build.build import build_project, generate_build_system
12 | from mbed_tools.build.exceptions import MbedBuildError
13 |
14 |
15 | @pytest.fixture
16 | def subprocess_run():
17 | with mock.patch("mbed_tools.build.build.subprocess.run", autospec=True) as subproc:
18 | yield subproc
19 |
20 |
21 | class TestBuildProject:
22 | def test_invokes_cmake_with_correct_args(self, subprocess_run):
23 | build_project(build_dir="cmake_build", target="install")
24 |
25 | subprocess_run.assert_called_with(["cmake", "--build", "cmake_build", "--target", "install"], check=True)
26 |
27 | def test_invokes_cmake_with_correct_args_if_no_target_passed(self, subprocess_run):
28 | build_project(build_dir="cmake_build")
29 |
30 | subprocess_run.assert_called_with(["cmake", "--build", "cmake_build"], check=True)
31 |
32 | def test_raises_build_error_if_cmake_invocation_fails(self, subprocess_run):
33 | subprocess_run.side_effect = (None, subprocess.CalledProcessError(1, ""))
34 |
35 | with pytest.raises(MbedBuildError, match="CMake invocation failed"):
36 | build_project(build_dir="cmake_build")
37 |
38 |
39 | class TestConfigureProject:
40 | def test_invokes_cmake_with_correct_args(self, subprocess_run):
41 | source_dir = "source_dir"
42 | build_dir = "cmake_build"
43 | profile = "debug"
44 |
45 | generate_build_system(source_dir, build_dir, profile)
46 |
47 | subprocess_run.assert_called_with(
48 | ["cmake", "-S", source_dir, "-B", build_dir, "-GNinja", f"-DCMAKE_BUILD_TYPE={profile}"], check=True
49 | )
50 |
51 | def test_raises_when_ninja_cannot_be_found(self, subprocess_run):
52 | subprocess_run.side_effect = FileNotFoundError
53 |
54 | with pytest.raises(MbedBuildError, match="Ninja"):
55 | generate_build_system("", "", "")
56 |
57 | def test_raises_when_cmake_cannot_be_found(self, subprocess_run):
58 | subprocess_run.side_effect = (None, FileNotFoundError)
59 |
60 | with pytest.raises(MbedBuildError, match="Could not find CMake"):
61 | generate_build_system("", "", "")
62 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2020-2021 Arm Limited and Contributors. All rights reserved.
3 | # SPDX-License-Identifier: Apache-2.0
4 | #
5 | """Package definition for PyPI."""
6 | import os
7 |
8 | from setuptools import setup, find_packages
9 |
10 | PROJECT_SLUG = "mbed-tools"
11 | SOURCE_DIR = "src/mbed_tools"
12 |
13 | repository_dir = os.path.dirname(__file__)
14 |
15 | # Use readme needed as long description in PyPI
16 | with open(os.path.join(repository_dir, "README.md"), encoding="utf8") as fh:
17 | long_description = fh.read()
18 |
19 | setup(
20 | author="Mbed team",
21 | author_email="support@mbed.com",
22 | classifiers=[
23 | "Development Status :: 4 - Beta",
24 | "Intended Audience :: Developers",
25 | "License :: OSI Approved :: Apache Software License",
26 | "Programming Language :: Python",
27 | "Programming Language :: Python :: 3 :: Only",
28 | "Programming Language :: Python :: 3.6",
29 | "Programming Language :: Python :: 3.7",
30 | "Programming Language :: Python :: 3.8",
31 | "Programming Language :: Python :: 3.9",
32 | "Topic :: Software Development :: Build Tools",
33 | "Topic :: Software Development :: Embedded Systems",
34 | ],
35 | description="Command line interface for Mbed OS.",
36 | keywords="Arm Mbed OS MbedOS cli command line tools",
37 | include_package_data=True,
38 | install_requires=[
39 | "python-dotenv",
40 | "Click>=7.1,<9",
41 | "GitPython",
42 | "tqdm",
43 | "tabulate",
44 | "dataclasses; python_version<'3.7'",
45 | "requests>=2.20",
46 | "pywin32; platform_system=='Windows'",
47 | "psutil; platform_system=='Linux'",
48 | "pyudev; platform_system=='Linux'",
49 | "typing-extensions",
50 | "Jinja2",
51 | "pyserial",
52 | ],
53 | license="Apache 2.0",
54 | long_description_content_type="text/markdown",
55 | long_description=long_description,
56 | name=PROJECT_SLUG,
57 | package_dir={"": "src"},
58 | packages=find_packages(where="src"),
59 | python_requires=">=3.6,<4",
60 | url=f"https://github.com/ARMmbed/{PROJECT_SLUG}",
61 | entry_points={
62 | "console_scripts": [
63 | "mbedtools=mbed_tools.cli.main:cli",
64 | "mbed-tools=mbed_tools.cli.main:cli",
65 | "mbed_tools=mbed_tools.cli.main:cli",
66 | ]
67 | },
68 | )
69 |
--------------------------------------------------------------------------------
/tests/build/_internal/test_cmake_file.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2020-2021 Arm Limited and Contributors. All rights reserved.
3 | # SPDX-License-Identifier: Apache-2.0
4 | #
5 | import pytest
6 |
7 | from mbed_tools.build._internal.cmake_file import render_mbed_config_cmake_template
8 | from mbed_tools.build._internal.config.config import Config
9 | from mbed_tools.build._internal.config.source import ConfigSetting, prepare
10 |
11 |
12 | TOOLCHAIN_NAME = "gcc"
13 |
14 |
15 | @pytest.fixture()
16 | def fake_target():
17 | return {
18 | "labels": ["foo"],
19 | "extra_labels": ["morefoo"],
20 | "features": ["bar"],
21 | "components": ["baz"],
22 | "macros": ["macbaz"],
23 | "device_has": ["stuff"],
24 | "c_lib": ["c_lib"],
25 | "core": ["core"],
26 | "printf_lib": ["printf_lib"],
27 | "supported_form_factors": ["arduino"],
28 | "supported_c_libs": {TOOLCHAIN_NAME: ["ginormous"]},
29 | "supported_application_profiles": ["full", "bare-metal"],
30 | }
31 |
32 |
33 | class TestRendersCMakeListsFile:
34 | def test_returns_rendered_content(self, fake_target):
35 | config = Config(prepare(fake_target))
36 | result = render_mbed_config_cmake_template(config, TOOLCHAIN_NAME, "target_name")
37 |
38 | for label in fake_target["labels"] + fake_target["extra_labels"]:
39 | assert label in result
40 |
41 | for macro in fake_target["features"] + fake_target["components"] + [TOOLCHAIN_NAME]:
42 | assert macro in result
43 |
44 | for toolchain in fake_target["supported_c_libs"]:
45 | assert toolchain in result
46 | for supported_c_libs in toolchain:
47 | assert supported_c_libs in result
48 |
49 | for supported_application_profiles in fake_target["supported_application_profiles"]:
50 | assert supported_application_profiles in result
51 |
52 | def test_returns_quoted_content(self, fake_target):
53 | config = Config(prepare(fake_target))
54 |
55 | # Add an option whose value contains quotes to the config.
56 | config["config"] = [
57 | ConfigSetting(
58 | name="mqtt-host", namespace="iotc", help_text="", value='{"mqtt.2030.ltsapis.goog", IOTC_MQTT_PORT}',
59 | )
60 | ]
61 |
62 | result = render_mbed_config_cmake_template(config, TOOLCHAIN_NAME, "target_name")
63 | assert '"MBED_CONF_IOTC_MQTT_HOST={\\"mqtt.2030.ltsapis.goog\\", IOTC_MQTT_PORT}"' in result
64 |
--------------------------------------------------------------------------------
/src/mbed_tools/devices/_internal/darwin/diskutil.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2020-2021 Arm Limited and Contributors. All rights reserved.
3 | # SPDX-License-Identifier: Apache-2.0
4 | #
5 | """Interactions with `diskutil`."""
6 | import plistlib
7 | import subprocess
8 | from typing import Dict, Iterable, List, Optional, cast
9 | from typing_extensions import TypedDict
10 |
11 |
12 | VolumeTree = Dict # mypy does not work with recursive types, which nested "Partitions" would require
13 |
14 |
15 | class Volume(TypedDict, total=False):
16 | """Representation of mounted volume."""
17 |
18 | MountPoint: str # example: /Volumes/SomeName
19 | DeviceIdentifier: str # example: disk2
20 |
21 |
22 | def get_all_external_disks_data() -> List[VolumeTree]:
23 | """Returns parsed output of `diskutil` call, fetching only information of interest."""
24 | output = subprocess.check_output(["diskutil", "list", "-plist", "external"], stderr=subprocess.DEVNULL)
25 | if output:
26 | data: Dict = plistlib.loads(output)
27 | return data.get("AllDisksAndPartitions", [])
28 | return []
29 |
30 |
31 | def get_all_external_volumes_data() -> List[Volume]:
32 | """Returns all external volumes data.
33 |
34 | Reduces structure returned by `diskutil` call to one which will only contain data about Volumes.
35 | Useful for determining MountPoints and DeviceIdentifiers.
36 | """
37 | data = get_all_external_disks_data()
38 | return _filter_volumes(data)
39 |
40 |
41 | def get_external_volume_data(device_identifier: str) -> Optional[Volume]:
42 | """Returns external volume data for a given identifier."""
43 | data = get_all_external_volumes_data()
44 | for device in data:
45 | if device.get("DeviceIdentifier") == device_identifier:
46 | return device
47 | return None
48 |
49 |
50 | def get_mount_point(device_identifier: str) -> Optional[str]:
51 | """Returns mount point of a given device."""
52 | device_data = get_external_volume_data(device_identifier)
53 | if device_data and "MountPoint" in device_data:
54 | return device_data["MountPoint"]
55 | return None
56 |
57 |
58 | def _filter_volumes(data: Iterable[VolumeTree]) -> List[Volume]:
59 | """Flattens the structure returned by `diskutil` call.
60 |
61 | Expected input will contain both partitioned an unpartitioned devices.
62 | Partitioned devices list mounted partitions under an arbitrary key,
63 | flattening the data helps finding actual end devices later on.
64 | """
65 | devices = []
66 | for device in data:
67 | if "Partitions" in device:
68 | devices.extend(_filter_volumes(device["Partitions"]))
69 | else:
70 | devices.append(cast(Volume, device))
71 | return devices
72 |
--------------------------------------------------------------------------------
/src/mbed_tools/devices/_internal/darwin/system_profiler.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2020-2021 Arm Limited and Contributors. All rights reserved.
3 | # SPDX-License-Identifier: Apache-2.0
4 | #
5 | """Interactions with `system_profiler`."""
6 | import plistlib
7 | import re
8 | import subprocess
9 | from typing import Dict, Iterable, List, cast
10 | from typing_extensions import TypedDict
11 |
12 | USBDeviceTree = Dict # mypy does not work with recursive types, which "_items" would require
13 |
14 |
15 | class USBDeviceMedia(TypedDict, total=False):
16 | """Representation of usb device storage."""
17 |
18 | bsd_name: str
19 |
20 |
21 | class USBDevice(TypedDict, total=False):
22 | """Representation of usb device."""
23 |
24 | _name: str
25 | location_id: str
26 | vendor_id: str
27 | product_id: str
28 | serial_num: str
29 | Media: List[USBDeviceMedia]
30 |
31 |
32 | def get_all_usb_devices_data() -> List[USBDeviceTree]:
33 | """Returns parsed output of `system_profiler` call."""
34 | output = subprocess.check_output(["system_profiler", "-xml", "SPUSBDataType"], stderr=subprocess.DEVNULL)
35 | if output:
36 | return cast(List[USBDeviceTree], plistlib.loads(output))
37 | return []
38 |
39 |
40 | def get_end_usb_devices_data() -> List[USBDevice]:
41 | """Returns only end devices from the output of `system_profiler` call."""
42 | data = get_all_usb_devices_data()
43 | leaf_devices = _extract_leaf_devices(data)
44 | end_devices = _filter_end_devices(leaf_devices)
45 | return end_devices
46 |
47 |
48 | def _extract_leaf_devices(data: Iterable[USBDeviceTree]) -> List[USBDevice]:
49 | """Flattens the structure returned by `system_profiler` call.
50 |
51 | Expected input will contain a tree-like structures, this function will return their leaf nodes.
52 | """
53 | end_devices = []
54 | for device in data:
55 | if "_items" in device:
56 | child_devices = _extract_leaf_devices(device["_items"])
57 | end_devices.extend(child_devices)
58 | else:
59 | end_devices.append(cast(USBDevice, device))
60 | return end_devices
61 |
62 |
63 | def _filter_end_devices(data: Iterable[USBDevice]) -> List[USBDevice]:
64 | """Removes devices that don't look like end devices.
65 |
66 | An end device is a device that shouldn't have child devices.
67 | I.e.: a hub IS NOT an end device, a mouse IS an end device.
68 | """
69 | return [device for device in data if not _is_hub(device) and not _is_bus(device)]
70 |
71 |
72 | def _is_hub(data: USBDevice) -> bool:
73 | return bool(re.match(r"USB\d.\d Hub", data.get("_name", "")))
74 |
75 |
76 | def _is_bus(data: USBDevice) -> bool:
77 | return bool(re.match(r"USB\d\dBus", data.get("_name", "")))
78 |
--------------------------------------------------------------------------------
/src/mbed_tools/devices/_internal/linux/device_detector.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2020-2021 Arm Limited and Contributors. All rights reserved.
3 | # SPDX-License-Identifier: Apache-2.0
4 | #
5 | """Defines a device detector for Linux."""
6 | import logging
7 | from pathlib import Path
8 | from typing import Tuple, List, Optional, cast
9 |
10 | import psutil
11 | import pyudev
12 |
13 | from mbed_tools.devices._internal.base_detector import DeviceDetector
14 | from mbed_tools.devices._internal.candidate_device import CandidateDevice, FilesystemMountpointError
15 |
16 |
17 | logger = logging.getLogger(__name__)
18 |
19 |
20 | class LinuxDeviceDetector(DeviceDetector):
21 | """Linux specific implementation of device detection."""
22 |
23 | def find_candidates(self) -> List[CandidateDevice]:
24 | """Return a list of CandidateDevices."""
25 | context = pyudev.Context()
26 | candidates = []
27 | for disk in context.list_devices(subsystem="block", ID_BUS="usb"):
28 | serial_number = disk.properties.get("ID_SERIAL_SHORT")
29 | try:
30 | candidates.append(
31 | CandidateDevice(
32 | mount_points=_find_fs_mounts_for_device(disk.properties.get("DEVNAME")),
33 | product_id=disk.properties.get("ID_MODEL_ID"),
34 | vendor_id=disk.properties.get("ID_VENDOR_ID"),
35 | serial_number=serial_number,
36 | serial_port=_find_serial_port_for_device(serial_number),
37 | )
38 | )
39 | except FilesystemMountpointError:
40 | logger.warning(
41 | f"A USB block device was detected at path {disk.properties.get('DEVNAME')}. However, the"
42 | " file system has failed to mount. Please disconnect and reconnect your device and try again."
43 | "If this problem persists, try running fsck.vfat on your block device, as the file system may be "
44 | "corrupted."
45 | )
46 | continue
47 | return candidates
48 |
49 |
50 | def _find_serial_port_for_device(disk_serial_id: str) -> Optional[str]:
51 | """Try to find a serial port associated with the given device."""
52 | for tty_dev in pyudev.Context().list_devices(subsystem="tty"):
53 | if tty_dev.properties.get("ID_SERIAL_SHORT") == disk_serial_id:
54 | return cast(str, tty_dev.properties.get("DEVNAME"))
55 | return None
56 |
57 |
58 | def _find_fs_mounts_for_device(device_file_path: str) -> Tuple[Path, ...]:
59 | """Find the file system mount point for a block device file path."""
60 | return tuple(Path(part.mountpoint) for part in psutil.disk_partitions() if part.device == device_file_path)
61 |
--------------------------------------------------------------------------------
/src/mbed_tools/cli/main.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2020-2021 Arm Limited and Contributors. All rights reserved.
3 | # SPDX-License-Identifier: Apache-2.0
4 | #
5 | """Main cli entry point."""
6 | import logging
7 | import sys
8 |
9 | from pkg_resources import get_distribution
10 | from typing import Union, Any
11 |
12 | import click
13 |
14 | from mbed_tools.lib.logging import set_log_level, MbedToolsHandler
15 |
16 | from mbed_tools.cli.configure import configure
17 | from mbed_tools.cli.list_connected_devices import list_connected_devices
18 | from mbed_tools.cli.project_management import new, import_, deploy
19 | from mbed_tools.cli.build import build
20 | from mbed_tools.cli.sterm import sterm
21 |
22 | CONTEXT_SETTINGS = dict(help_option_names=["-h", "--help"])
23 | LOGGER = logging.getLogger(__name__)
24 |
25 |
26 | class GroupWithExceptionHandling(click.Group):
27 | """A click.Group which handles ToolsErrors and logging."""
28 |
29 | def invoke(self, context: click.Context) -> None:
30 | """Invoke the command group.
31 |
32 | Args:
33 | context: The current click context.
34 | """
35 | # Use the context manager to ensure tools exceptions (expected behaviour) are shown as messages to the user,
36 | # but all other exceptions (unexpected behaviour) are shown as errors.
37 | with MbedToolsHandler(LOGGER, context.params["traceback"]) as handler:
38 | super().invoke(context)
39 |
40 | sys.exit(handler.exit_code)
41 |
42 |
43 | def print_version(context: click.Context, param: Union[click.Option, click.Parameter], value: bool) -> Any:
44 | """Print the version of mbed-tools."""
45 | if not value or context.resilient_parsing:
46 | return
47 |
48 | version_string = get_distribution("mbed-tools").version
49 | click.echo(version_string)
50 | context.exit()
51 |
52 |
53 | @click.group(cls=GroupWithExceptionHandling, context_settings=CONTEXT_SETTINGS)
54 | @click.option(
55 | "--version",
56 | is_flag=True,
57 | callback=print_version,
58 | expose_value=False,
59 | is_eager=True,
60 | help="Display versions of all Mbed Tools packages.",
61 | )
62 | @click.option(
63 | "-v",
64 | "--verbose",
65 | default=0,
66 | count=True,
67 | help="Set the verbosity level, enter multiple times to increase verbosity.",
68 | )
69 | @click.option("-t", "--traceback", is_flag=True, show_default=True, help="Show a traceback when an error is raised.")
70 | def cli(verbose: int, traceback: bool) -> None:
71 | """Command line tool for interacting with Mbed OS."""
72 | set_log_level(verbose)
73 |
74 |
75 | cli.add_command(configure, "configure")
76 | cli.add_command(list_connected_devices, "detect")
77 | cli.add_command(new, "new")
78 | cli.add_command(deploy, "deploy")
79 | cli.add_command(import_, "import")
80 | cli.add_command(build, "compile")
81 | cli.add_command(sterm, "sterm")
82 |
--------------------------------------------------------------------------------
/src/mbed_tools/build/config.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2020-2021 Arm Limited and Contributors. All rights reserved.
3 | # SPDX-License-Identifier: Apache-2.0
4 | #
5 | """Parses the Mbed configuration system and generates a CMake config script."""
6 | import pathlib
7 |
8 | from typing import Any, Tuple
9 |
10 | from mbed_tools.lib.json_helpers import decode_json_file
11 | from mbed_tools.project import MbedProgram
12 | from mbed_tools.targets import get_target_by_name
13 | from mbed_tools.build._internal.cmake_file import render_mbed_config_cmake_template
14 | from mbed_tools.build._internal.config.assemble_build_config import Config, assemble_config
15 | from mbed_tools.build._internal.write_files import write_file
16 | from mbed_tools.build.exceptions import MbedBuildError
17 |
18 | CMAKE_CONFIG_FILE = "mbed_config.cmake"
19 | MBEDIGNORE_FILE = ".mbedignore"
20 |
21 |
22 | def generate_config(target_name: str, toolchain: str, program: MbedProgram) -> Tuple[Config, pathlib.Path]:
23 | """Generate an Mbed config file after parsing the Mbed config system.
24 |
25 | Args:
26 | target_name: Name of the target to configure for.
27 | toolchain: Name of the toolchain to use.
28 | program: The MbedProgram to configure.
29 |
30 | Returns:
31 | Config object (UserDict).
32 | Path to the generated config file.
33 | """
34 | targets_data = _load_raw_targets_data(program)
35 | target_build_attributes = get_target_by_name(target_name, targets_data)
36 | config = assemble_config(
37 | target_build_attributes, [program.root, program.mbed_os.root], program.files.app_config_file
38 | )
39 | cmake_file_contents = render_mbed_config_cmake_template(
40 | target_name=target_name, config=config, toolchain_name=toolchain,
41 | )
42 | cmake_config_file_path = program.files.cmake_build_dir / CMAKE_CONFIG_FILE
43 | write_file(cmake_config_file_path, cmake_file_contents)
44 | mbedignore_path = program.files.cmake_build_dir / MBEDIGNORE_FILE
45 | write_file(mbedignore_path, "*")
46 | return config, cmake_config_file_path
47 |
48 |
49 | def _load_raw_targets_data(program: MbedProgram) -> Any:
50 | targets_data = decode_json_file(program.mbed_os.targets_json_file)
51 | if program.files.custom_targets_json.exists():
52 | custom_targets_data = decode_json_file(program.files.custom_targets_json)
53 | for custom_target in custom_targets_data:
54 | if custom_target in targets_data:
55 | raise MbedBuildError(
56 | f"Error found in {program.files.custom_targets_json}.\n"
57 | f"A target with the name '{custom_target}' already exists in targets.json. "
58 | "Please give your custom target a unique name so it can be identified."
59 | )
60 |
61 | targets_data.update(custom_targets_data)
62 |
63 | return targets_data
64 |
--------------------------------------------------------------------------------
/tests/project/test_mbed_project.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2020-2021 Arm Limited and Contributors. All rights reserved.
3 | # SPDX-License-Identifier: Apache-2.0
4 | #
5 | import pathlib
6 |
7 | import pytest
8 |
9 | from unittest import mock
10 |
11 | from mbed_tools.project import initialise_project, import_project, deploy_project, get_known_libs
12 |
13 |
14 | @pytest.fixture
15 | def mock_libs():
16 | with mock.patch("mbed_tools.project.project.LibraryReferences") as libs:
17 | yield libs
18 |
19 |
20 | @pytest.fixture
21 | def mock_program():
22 | with mock.patch("mbed_tools.project.project.MbedProgram") as prog:
23 | yield prog
24 |
25 |
26 | @pytest.fixture
27 | def mock_git():
28 | with mock.patch("mbed_tools.project.project.git_utils") as gutils:
29 | yield gutils
30 |
31 |
32 | class TestInitialiseProject:
33 | def test_fetches_mbed_os_when_create_only_is_false(self, mock_libs, mock_program):
34 | path = pathlib.Path()
35 | initialise_project(path, create_only=False)
36 |
37 | mock_program.from_new.assert_called_once_with(path)
38 | mock_libs().fetch.assert_called_once()
39 |
40 | def test_skips_mbed_os_when_create_only_is_true(self, mock_libs, mock_program):
41 | path = pathlib.Path()
42 | initialise_project(path, create_only=True)
43 |
44 | mock_program.from_new.assert_called_once_with(path)
45 | mock_libs().fetch.assert_not_called()
46 |
47 |
48 | class TestImportProject:
49 | def test_clones_from_remote(self, mock_git):
50 | url = "https://git.com/gitorg/repo"
51 | import_project(url, recursive=False)
52 |
53 | mock_git.clone.assert_called_once_with(url, pathlib.Path(url.rsplit("/", maxsplit=1)[-1]))
54 |
55 | def test_resolves_libs_when_recursive_is_true(self, mock_git, mock_libs):
56 | url = "https://git.com/gitorg/repo"
57 | import_project(url, recursive=True)
58 |
59 | mock_git.clone.assert_called_once_with(url, pathlib.Path(url.rsplit("/", maxsplit=1)[-1]))
60 | mock_libs().fetch.assert_called_once()
61 |
62 |
63 | class TestDeployProject:
64 | def test_checks_out_libraries(self, mock_libs):
65 | path = pathlib.Path("somewhere")
66 | deploy_project(path, force=False)
67 |
68 | mock_libs().checkout.assert_called_once_with(force=False)
69 |
70 | def test_resolves_libs_if_unresolved_detected(self, mock_libs):
71 | mock_libs().iter_unresolved.return_value = [1]
72 | path = pathlib.Path("somewhere")
73 | deploy_project(path)
74 |
75 | mock_libs().fetch.assert_called_once()
76 |
77 |
78 | class TestPrintLibs:
79 | def test_list_libraries_gets_known_lib_list(self, mock_libs):
80 | path = pathlib.Path("somewhere")
81 | mock_libs().iter_resolved.return_value = ["", ""]
82 |
83 | libs = get_known_libs(path)
84 |
85 | assert libs == ["", ""]
86 |
--------------------------------------------------------------------------------
/src/mbed_tools/targets/env.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2020-2021 Arm Limited and Contributors. All rights reserved.
3 | # SPDX-License-Identifier: Apache-2.0
4 | #
5 | """Environment options for `mbed-targets`.
6 |
7 | All the env configuration options can be set either via environment variables or using a `.env` file
8 | containing the variable definitions as follows:
9 |
10 | ```
11 | VARIABLE=value
12 | ```
13 |
14 | Environment variables take precendence, meaning the values set in the file will be overriden
15 | by any values previously set in your environment.
16 |
17 | .. WARNING::
18 | Do not upload `.env` files containing private tokens to version control! If you use this package
19 | as a dependency of your project, please ensure to include the `.env` in your `.gitignore`.
20 | """
21 | import os
22 |
23 | import dotenv
24 |
25 | dotenv.load_dotenv(dotenv.find_dotenv(usecwd=True))
26 |
27 |
28 | class Env:
29 | """Provides access to environment variables.
30 |
31 | Ensures variables are reloaded when environment changes during runtime.
32 | """
33 |
34 | @property
35 | def MBED_API_AUTH_TOKEN(self) -> str:
36 | """Token to use when accessing online API.
37 |
38 | Mbed Targets uses the online mbed board database at os.mbed.com as its data source.
39 | A snapshot of the board database is shipped with the package, for faster lookup of known
40 | boards. Only public boards are stored in the database snapshot. If you are fetching data
41 | for a private board, mbed-targets will need to contact the online database.
42 |
43 | To fetch data about private boards from the online database, the user must have an account
44 | on os.mbed.com and be member of a vendor team that has permissions to see the private board.
45 | An authentication token for the team member must be provided in an environment variable named
46 | `MBED_API_AUTH_TOKEN`.
47 | """
48 | return os.getenv("MBED_API_AUTH_TOKEN", "")
49 |
50 | @property
51 | def MBED_DATABASE_MODE(self) -> str:
52 | """Database mode to use when retrieving board data.
53 |
54 | Mbed Targets supports an online and offline mode, which controls where to look up the board database.
55 |
56 | The board lookup can be from either the online or offline database, depending
57 | on the value of an environment variable called `MBED_DATABASE_MODE`.
58 |
59 | The mode can be set to one of the following:
60 |
61 | - `AUTO`: the offline database is searched first, if the board isn't found the online database is searched.
62 | - `ONLINE`: the online database is always used.
63 | - `OFFLINE`: the offline database is always used.
64 |
65 | If `MBED_DATABASE_MODE` is not set, it defaults to `AUTO`.
66 | """
67 | return os.getenv("MBED_DATABASE_MODE", "AUTO")
68 |
69 |
70 | env = Env()
71 | """Instance of `Env` class."""
72 |
--------------------------------------------------------------------------------
/tests/ci_scripts/test_bump_version.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2020-2021 Arm Limited and Contributors. All rights reserved.
3 | # SPDX-License-Identifier: Apache-2.0
4 | #
5 | import pathlib
6 |
7 | from unittest import mock
8 |
9 | import pytest
10 |
11 | from ci_scripts.bump_version import bump_version_from_news_files, news_ext_to_version_type, get_current_version, main
12 |
13 |
14 | @pytest.mark.parametrize(
15 | "files,expected_version",
16 | (
17 | ((pathlib.Path("blah.feature"), pathlib.Path("blah.misc"), pathlib.Path("blah.bugfix")), "0.1.0"),
18 | ((pathlib.Path("blah.major"), pathlib.Path("blah.feature"), pathlib.Path("blah.bugfix")), "1.0.0"),
19 | ((pathlib.Path("blah.doc"), pathlib.Path("blah.misc"), pathlib.Path("blah.bugfix")), "0.0.1"),
20 | ),
21 | )
22 | def test_bumps_version_according_to_news_file_extension(files, expected_version):
23 | ver = bump_version_from_news_files("0.0.0", files)
24 |
25 | assert ver == expected_version
26 |
27 |
28 | @pytest.mark.parametrize(
29 | "files",
30 | (
31 | tuple(),
32 | (pathlib.Path("blah.doc"), pathlib.Path("blah.removal"), pathlib.Path("blah.misc")),
33 | (pathlib.Path("blah.md"),),
34 | ),
35 | )
36 | def test_raises_when_significant_news_file_not_found(files):
37 | with pytest.raises(ValueError):
38 | bump_version_from_news_files("0.0.0", files)
39 |
40 |
41 | @pytest.mark.parametrize(
42 | "news_exts,expected",
43 | (((".feature", ".major", ".bugfix"), "major"), ((".feature", ".bugfix"), "minor"), ((".misc", ".bugfix"), "patch")),
44 | )
45 | def test_converts_news_ext_to_version_type(news_exts, expected):
46 | version_type = news_ext_to_version_type(news_exts)
47 |
48 | assert version_type == expected
49 |
50 |
51 | @mock.patch("ci_scripts.bump_version.git.Repo")
52 | def test_get_version_returns_version_from_git(mock_repo):
53 | mock_version = "0.0.0"
54 | mock_repo().__enter__().git.describe.return_value = mock_version
55 |
56 | version_str = get_current_version()
57 |
58 | assert version_str == mock_version
59 |
60 |
61 | @mock.patch("ci_scripts.bump_version.set_log_level")
62 | @mock.patch("ci_scripts.bump_version.bump_version_from_news_files")
63 | @mock.patch("ci_scripts.bump_version.get_current_version")
64 | def test_main_execution_context(get_current_version, bump_version_from_news_files, set_log_level, tmp_path):
65 | class Args:
66 | verbosity = 0
67 | news_file_directory = pathlib.Path(tmp_path, "fake", "news")
68 |
69 | fake_version = "0.0.0"
70 | fake_args = Args()
71 | fake_args.news_file_directory.mkdir(parents=True)
72 | get_current_version.return_value = fake_version
73 |
74 | ret = main(fake_args)
75 |
76 | set_log_level.assert_called_once_with(fake_args.verbosity)
77 | bump_version_from_news_files.assert_called_once_with(fake_version, list(fake_args.news_file_directory.iterdir()))
78 | assert ret == 0
79 |
--------------------------------------------------------------------------------
/src/mbed_tools/project/project.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2020-2021 Arm Limited and Contributors. All rights reserved.
3 | # SPDX-License-Identifier: Apache-2.0
4 | #
5 | """Defines the public API of the package."""
6 | import pathlib
7 | import logging
8 |
9 | from typing import List, Any
10 |
11 | from mbed_tools.project.mbed_program import MbedProgram, parse_url
12 | from mbed_tools.project._internal.libraries import LibraryReferences
13 | from mbed_tools.project._internal import git_utils
14 |
15 | logger = logging.getLogger(__name__)
16 |
17 |
18 | def import_project(url: str, dst_path: Any = None, recursive: bool = False) -> pathlib.Path:
19 | """Clones an Mbed project from a remote repository.
20 |
21 | Args:
22 | url: URL of the repository to clone.
23 | dst_path: Destination path for the repository.
24 | recursive: Recursively clone all project dependencies.
25 |
26 | Returns:
27 | The path the project was cloned to.
28 | """
29 | git_data = parse_url(url)
30 | url = git_data["url"]
31 | if not dst_path:
32 | dst_path = pathlib.Path(git_data["dst_path"])
33 |
34 | git_utils.clone(url, dst_path)
35 | if recursive:
36 | libs = LibraryReferences(root=dst_path, ignore_paths=["mbed-os"])
37 | libs.fetch()
38 |
39 | return dst_path
40 |
41 |
42 | def initialise_project(path: pathlib.Path, create_only: bool) -> None:
43 | """Create a new Mbed project, optionally fetching and adding mbed-os.
44 |
45 | Args:
46 | path: Path to the project folder. Created if it doesn't exist.
47 | create_only: Flag which suppreses fetching mbed-os. If the value is `False`, fetch mbed-os from the remote.
48 | """
49 | program = MbedProgram.from_new(path)
50 | if not create_only:
51 | libs = LibraryReferences(root=program.root, ignore_paths=["mbed-os"])
52 | libs.fetch()
53 |
54 |
55 | def deploy_project(path: pathlib.Path, force: bool = False) -> None:
56 | """Deploy a specific revision of the current Mbed project.
57 |
58 | This function also resolves and syncs all library dependencies to the revision specified in the library reference
59 | files.
60 |
61 | Args:
62 | path: Path to the Mbed project.
63 | force: Force overwrite uncommitted changes. If False, the deploy will fail if there are uncommitted local
64 | changes.
65 | """
66 | libs = LibraryReferences(path, ignore_paths=["mbed-os"])
67 | libs.checkout(force=force)
68 | if list(libs.iter_unresolved()):
69 | logger.info("Unresolved libraries detected, downloading library source code.")
70 | libs.fetch()
71 |
72 |
73 | def get_known_libs(path: pathlib.Path) -> List:
74 | """List all resolved library dependencies.
75 |
76 | This function will not resolve dependencies. This will only generate a list of resolved dependencies.
77 |
78 | Args:
79 | path: Path to the Mbed project.
80 |
81 | Returns:
82 | A list of known dependencies.
83 | """
84 | libs = LibraryReferences(path, ignore_paths=["mbed-os"])
85 | return list(sorted(libs.iter_resolved()))
86 |
--------------------------------------------------------------------------------
/src/mbed_tools/targets/boards.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2020-2021 Arm Limited and Contributors. All rights reserved.
3 | # SPDX-License-Identifier: Apache-2.0
4 | #
5 | """Interface to the Board Database."""
6 | import json
7 |
8 | from dataclasses import asdict
9 | from collections.abc import Set
10 | from typing import Iterator, Iterable, Any, Callable
11 |
12 | from mbed_tools.targets._internal import board_database
13 |
14 | from mbed_tools.targets.exceptions import UnknownBoard
15 | from mbed_tools.targets.board import Board
16 |
17 |
18 | class Boards(Set):
19 | """Interface to the Board Database.
20 |
21 | Boards is initialised with an Iterable[Board]. The classmethods
22 | can be used to construct Boards with data from either the online or offline database.
23 | """
24 |
25 | @classmethod
26 | def from_offline_database(cls) -> "Boards":
27 | """Initialise with the offline board database.
28 |
29 | Raises:
30 | BoardDatabaseError: Could not retrieve data from the board database.
31 | """
32 | return cls(Board.from_offline_board_entry(b) for b in board_database.get_offline_board_data())
33 |
34 | @classmethod
35 | def from_online_database(cls) -> "Boards":
36 | """Initialise with the online board database.
37 |
38 | Raises:
39 | BoardDatabaseError: Could not retrieve data from the board database.
40 | """
41 | return cls(Board.from_online_board_entry(b) for b in board_database.get_online_board_data())
42 |
43 | def __init__(self, boards_data: Iterable["Board"]) -> None:
44 | """Initialise with a list of boards.
45 |
46 | Args:
47 | boards_data: iterable of board data from a board database source.
48 | """
49 | self._boards_data = tuple(boards_data)
50 |
51 | def __iter__(self) -> Iterator["Board"]:
52 | """Yield an Board on each iteration."""
53 | for board in self._boards_data:
54 | yield board
55 |
56 | def __len__(self) -> int:
57 | """Return the number of boards."""
58 | return len(self._boards_data)
59 |
60 | def __contains__(self, board: object) -> Any:
61 | """Check if a board is in the collection of boards.
62 |
63 | Args:
64 | board: An instance of Board.
65 | """
66 | if not isinstance(board, Board):
67 | return False
68 |
69 | return any(x == board for x in self)
70 |
71 | def get_board(self, matching: Callable) -> Board:
72 | """Returns first Board for which `matching` returns True.
73 |
74 | Args:
75 | matching: A function which will be called for each board in database
76 |
77 | Raises:
78 | UnknownBoard: the given product code was not found in the board database.
79 | """
80 | try:
81 | return next(board for board in self if matching(board))
82 | except StopIteration:
83 | raise UnknownBoard()
84 |
85 | def json_dump(self) -> str:
86 | """Return the contents of the board database as a json string."""
87 | return json.dumps([asdict(b) for b in self], indent=4)
88 |
--------------------------------------------------------------------------------
/src/mbed_tools/devices/_internal/windows/device_instance_id.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2020-2021 Arm Limited and Contributors. All rights reserved.
3 | # SPDX-License-Identifier: Apache-2.0
4 | #
5 | """Utility in charge of finding the instance ID of a device."""
6 | import win32con
7 | import win32api
8 | from mbed_tools.devices._internal.exceptions import SystemException
9 | import logging
10 |
11 | from typing import Optional, Any
12 |
13 | logger = logging.getLogger(__name__)
14 |
15 |
16 | class RegKey(object):
17 | """Context manager in charge of opening and closing registry keys."""
18 |
19 | def __init__(self, sub_registry_key: str) -> None:
20 | """Initialiser."""
21 | access = win32con.KEY_READ | win32con.KEY_ENUMERATE_SUB_KEYS | win32con.KEY_QUERY_VALUE
22 | try:
23 | self._hkey = win32api.RegOpenKey(win32con.HKEY_LOCAL_MACHINE, sub_registry_key, 0, access)
24 | except win32api.error as e:
25 | raise SystemException(f"Could not read key [{sub_registry_key}] in the registry: {e}")
26 |
27 | def __enter__(self) -> Any:
28 | """Actions on entry."""
29 | return self._hkey
30 |
31 | def __exit__(self, type: Any, value: Any, traceback: Any) -> None:
32 | """Actions on exit."""
33 | win32api.RegCloseKey(self._hkey)
34 | self._hkey.close()
35 |
36 |
37 | def get_children_instance_id(pnpid: str) -> Optional[str]:
38 | """Gets the USB children instance ID from the plug and play ID.
39 |
40 | See https://docs.microsoft.com/en-us/windows-hardware/drivers/install/instance-ids.
41 | """
42 | # Although the registry should not be accessed directly
43 | # (See https://docs.microsoft.com/en-us/windows-hardware/drivers/install/hklm-system-currentcontrolset-enum-registry-tree), # noqa E501
44 | # and SetupDi functions/APIs should be used instead in a similar fashion to Miro utility
45 | # (See https://github.com/cool-RR/Miro/blob/7b9ecd9bc0878e463f5a5e26e8b00b675e3f98ac/tv/windows/plat/usbutils.py)
46 | # Most libraries seems to be reading the registry:
47 | # - Pyserial: https://github.com/pyserial/pyserial/blob/master/serial/tools/list_ports_windows.py
48 | # - Node serialport: https://github.com/serialport/node-serialport/blob/cd112ca5a3a3fe186e1ac6fa78eeeb5ea7396185/packages/bindings/src/serialport_win.cpp # noqa E501
49 | # - USB device forensics: https://github.com/woanware/usbdeviceforensics/blob/master/pyTskusbdeviceforensics.py
50 | # For more details about the registry key actually looked at, See:
51 | # - https://stackoverflow.com/questions/3331043/get-list-of-connected-usb-devices
52 | # - https://docs.microsoft.com/en-us/windows-hardware/drivers/usbcon/usb-device-specific-registry-settings
53 | key_path = f"SYSTEM\\CurrentControlSet\\Enum\\{pnpid}"
54 | with RegKey(key_path) as hkey:
55 | try:
56 | value = win32api.RegQueryValueEx(hkey, "ParentIdPrefix")
57 | return str(value[0]) if value else None
58 | except win32api.error as e:
59 | logger.debug(f"Error occurred reading `ParentIdPrefix` field of key [{key_path}] in the registry: {e}")
60 | return None
61 |
--------------------------------------------------------------------------------
/tests/devices/_internal/test_candidate_device.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2020-2021 Arm Limited and Contributors. All rights reserved.
3 | # SPDX-License-Identifier: Apache-2.0
4 | #
5 | import pathlib
6 | from unittest import TestCase
7 | from mbed_tools.devices._internal.candidate_device import CandidateDevice
8 |
9 |
10 | def build_candidate_data(**overrides):
11 | defaults = {
12 | "product_id": "0x1234",
13 | "vendor_id": "0x5678",
14 | "mount_points": (pathlib.Path("./foo"),),
15 | "serial_number": "qwer",
16 | "serial_port": "COM1",
17 | }
18 | return {**defaults, **overrides}
19 |
20 |
21 | class TestCandidateDevice(TestCase):
22 | def test_produces_a_valid_candidate(self):
23 | candidate_data = build_candidate_data()
24 | candidate = CandidateDevice(**candidate_data)
25 |
26 | self.assertEqual(candidate.product_id, candidate_data["product_id"])
27 | self.assertEqual(candidate.vendor_id, candidate_data["vendor_id"])
28 | self.assertEqual(candidate.mount_points, candidate_data["mount_points"])
29 | self.assertEqual(candidate.serial_number, candidate_data["serial_number"])
30 | self.assertEqual(candidate.serial_port, candidate_data["serial_port"])
31 |
32 | def test_raises_when_product_id_is_empty(self):
33 | candidate_data = build_candidate_data(product_id="")
34 | with self.assertRaisesRegex(ValueError, "product_id"):
35 | CandidateDevice(**candidate_data)
36 |
37 | def test_raises_when_product_id_is_not_hex(self):
38 | candidate_data = build_candidate_data(product_id="TEST")
39 | with self.assertRaisesRegex(ValueError, "product_id"):
40 | CandidateDevice(**candidate_data)
41 |
42 | def test_prefixes_product_id_hex_value(self):
43 | candidate_data = build_candidate_data(product_id="ff01")
44 | candidate = CandidateDevice(**candidate_data)
45 | self.assertEqual(candidate.product_id, "0xff01")
46 |
47 | def test_raises_when_vendor_id_is_empty(self):
48 | candidate_data = build_candidate_data(vendor_id="")
49 | with self.assertRaisesRegex(ValueError, "vendor_id"):
50 | CandidateDevice(**candidate_data)
51 |
52 | def test_raises_when_vendor_id_is_not_hex(self):
53 | candidate_data = build_candidate_data(vendor_id="TEST")
54 | with self.assertRaisesRegex(ValueError, "vendor_id"):
55 | CandidateDevice(**candidate_data)
56 |
57 | def test_prefixes_vendor_id_hex_value(self):
58 | candidate_data = build_candidate_data(vendor_id="cbad")
59 | candidate = CandidateDevice(**candidate_data)
60 | self.assertEqual(candidate.vendor_id, "0xcbad")
61 |
62 | def test_raises_when_mount_points_are_empty(self):
63 | with self.assertRaisesRegex(ValueError, "mount_points"):
64 | CandidateDevice(product_id="1234", vendor_id="1234", mount_points=[], serial_number="1234")
65 |
66 | def test_raises_when_serial_number_is_empty(self):
67 | candidate_data = build_candidate_data(serial_number="")
68 | with self.assertRaisesRegex(ValueError, "serial_number"):
69 | CandidateDevice(**candidate_data)
70 |
--------------------------------------------------------------------------------
/src/mbed_tools/build/_internal/templates/mbed_config.tmpl:
--------------------------------------------------------------------------------
1 | # Copyright (C) 2020 Arm Mbed. All rights reserved.
2 | # SPDX-License-Identifier: Apache-2.0
3 |
4 | # Automatically generated configuration file.
5 | # DO NOT EDIT. Content may be overwritten.
6 |
7 | include_guard(GLOBAL)
8 |
9 | set(MBED_TOOLCHAIN "{{toolchain_name}}" CACHE STRING "")
10 | set(MBED_TARGET "{{target_name}}" CACHE STRING "")
11 | set(MBED_CPU_CORE "{{core}}" CACHE STRING "")
12 | set(MBED_C_LIB "{{c_lib}}" CACHE STRING "")
13 | set(MBED_PRINTF_LIB "{{printf_lib}}" CACHE STRING "")
14 | set(MBED_OUTPUT_EXT "{{OUTPUT_EXT}}" CACHE STRING "")
15 | set(MBED_GREENTEA_TEST_RESET_TIMEOUT "{{forced_reset_timeout}}" CACHE STRING "")
16 |
17 | list(APPEND MBED_TARGET_SUPPORTED_C_LIBS {% for supported_c_lib in supported_c_libs %}
18 | {{supported_c_lib}}
19 | {%- endfor %}
20 | )
21 |
22 | list(APPEND MBED_TARGET_SUPPORTED_APPLICATION_PROFILES {% for supported_application_profile in supported_application_profiles %}
23 | {{supported_application_profile}}
24 | {%- endfor %}
25 | )
26 |
27 | list(APPEND MBED_TARGET_LABELS{% for label in labels %}
28 | {{label}}
29 | {%- endfor %}
30 | {% for extra_label in extra_labels %}
31 | {{extra_label}}
32 | {%- endfor %}
33 | {% for component in components %}
34 | {{component}}
35 | {%- endfor %}
36 | {% for feature in features %}
37 | {{feature}}
38 | {%- endfor %}
39 | )
40 |
41 | # target
42 | set(MBED_TARGET_DEFINITIONS{% for component in components %}
43 | COMPONENT_{{component}}=1
44 | {%- endfor %}
45 | {% for feature in features %}
46 | FEATURE_{{feature}}=1
47 | {%- endfor %}
48 | {% for device in device_has %}
49 | DEVICE_{{device}}=1
50 | {%- endfor %}
51 | {% for label in labels %}
52 | TARGET_{{label}}
53 | {%- endfor %}
54 | {% for extra_label in extra_labels %}
55 | TARGET_{{extra_label}}
56 | {%- endfor %}
57 | {% for form_factor in supported_form_factors %}
58 | TARGET_FF_{{form_factor}}
59 | {%- endfor %}
60 | {% if mbed_rom_start is defined %}
61 | MBED_ROM_START={{ mbed_rom_start | to_hex }}
62 | {%- endif %}
63 | {% if mbed_rom_size is defined %}
64 | MBED_ROM_SIZE={{ mbed_rom_size | to_hex }}
65 | {%- endif %}
66 | {% if mbed_ram_start is defined %}
67 | MBED_RAM_START={{ mbed_ram_start | to_hex }}
68 | {%- endif %}
69 | {% if mbed_ram_size is defined %}
70 | MBED_RAM_SIZE={{ mbed_ram_size | to_hex }}
71 | {%- endif %}
72 | TARGET_LIKE_MBED
73 | __MBED__=1
74 | )
75 |
76 | # config
77 | set(MBED_CONFIG_DEFINITIONS
78 | {% for setting in config %}
79 | {%- if setting.macro_name -%}
80 | {%- set setting_name = setting.macro_name -%}
81 | {%- else -%}
82 | {%- set setting_name = "MBED_CONF_{}_{}".format(setting.namespace.upper()|replace('-', '_'), setting.name.upper()|replace('-', '_')) -%}
83 | {%- endif -%}
84 | {%- if setting.value is sameas true or setting.value is sameas false -%}
85 | {% set value = setting.value|int %}
86 | {%- else -%}
87 | {% set value = setting.value|replace("\"", "\\\"") -%}
88 | {%- endif -%}
89 | {%- if setting.value is not none -%}
90 | "{{setting_name}}={{value}}"
91 | {% endif -%}
92 | {%- endfor -%}
93 | {% for macro in macros %}
94 | "{{macro|replace("\"", "\\\"")}}"
95 | {%- endfor %}
96 | )
97 |
--------------------------------------------------------------------------------
/src/mbed_tools/devices/_internal/windows/serial_port.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2020-2021 Arm Limited and Contributors. All rights reserved.
3 | # SPDX-License-Identifier: Apache-2.0
4 | #
5 | """Defines a Serial Port.
6 |
7 | On Windows, Win32_SerialPort only represents physical serial Ports and hence, USB connections are not listed.
8 | https://superuser.com/questions/835848/how-to-view-serial-com-ports-but-not-through-device-manager
9 | https://stackoverflow.com/Questions/1388871/how-do-i-get-a-list-of-available-serial-ports-in-win32
10 | https://stackoverflow.com/questions/1205383/listing-serial-com-ports-on-windows
11 | https://serverfault.com/questions/398469/what-are-the-minimum-permissions-to-read-the-wmi-class-msserial-portname
12 | """
13 |
14 | import re
15 | from typing import NamedTuple, cast
16 |
17 | from mbed_tools.devices._internal.windows.component_descriptor import (
18 | ComponentDescriptor,
19 | UNKNOWN_VALUE,
20 | )
21 |
22 | CAPTION_PATTERN = re.compile(r"^.* [(](.*)[)]$")
23 |
24 |
25 | def parse_caption(caption: str) -> str:
26 | """Parses the caption string and returns the Port Name."""
27 | match = CAPTION_PATTERN.fullmatch(caption)
28 | return match.group(1) if match else UNKNOWN_VALUE
29 |
30 |
31 | class PnPEntityMsdnDefinition(NamedTuple):
32 | """Msdn definition of a PnPEntity.
33 |
34 | See https://docs.microsoft.com/en-us/windows/win32/cimwin32prov/win32-pnpentity
35 | """
36 |
37 | Availability: int
38 | Caption: str
39 | ClassGuid: str
40 | CompatibleID: list
41 | ConfigManagerErrorCode: int
42 | ConfigManagerUserConfig: bool
43 | CreationClassName: str
44 | Description: str
45 | DeviceID: str
46 | ErrorCleared: bool
47 | ErrorDescription: str
48 | HardwareID: list
49 | InstallDate: int
50 | LastErrorCode: int
51 | Manufacturer: str
52 | Name: str
53 | PNPClass: str
54 | PNPDeviceID: str
55 | PowerManagementCapabilities: int
56 | PowerManagementSupported: bool
57 | Present: bool
58 | Service: str
59 | Status: str
60 | StatusInfo: int
61 | SystemCreationClassName: str
62 | SystemName: str
63 |
64 |
65 | class SerialPort(ComponentDescriptor):
66 | """Serial Port as defined in Windows API.
67 |
68 | As can be seen in Windows documentation,
69 | https://docs.microsoft.com/en-us/windows-hardware/drivers/install/system-defined-device-setup-classes-available-to-vendors#ports--com---lpt-ports--,
70 | ports are devices with ClassGuid = {4d36e978-e325-11ce-bfc1-08002be10318}. Hence the filter below.
71 | """
72 |
73 | def __init__(self) -> None:
74 | """Initialiser."""
75 | super().__init__(
76 | PnPEntityMsdnDefinition,
77 | win32_class_name="Win32_PnPEntity",
78 | win32_filter='ClassGuid="{4d36e978-e325-11ce-bfc1-08002be10318}"',
79 | )
80 |
81 | @property
82 | def component_id(self) -> str:
83 | """Returns the device id field."""
84 | return cast(str, self.get("DeviceID"))
85 |
86 | @property
87 | def port_name(self) -> str:
88 | """Gets the port name."""
89 | return parse_caption(self.get("Caption"))
90 |
91 | @property
92 | def pnp_id(self) -> str:
93 | """Gets the Plug and play id."""
94 | return cast(str, self.get("PNPDeviceID"))
95 |
--------------------------------------------------------------------------------
/src/mbed_tools/lib/logging.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2020-2021 Arm Limited and Contributors. All rights reserved.
3 | # SPDX-License-Identifier: Apache-2.0
4 | #
5 | """Helpers for logging errors according to severity of the exception."""
6 | from typing import Type, Optional, cast
7 | from types import TracebackType
8 | import logging
9 | from mbed_tools.lib.exceptions import ToolsError
10 |
11 | LOGGING_FORMAT = "%(levelname)s: %(message)s"
12 |
13 | VERBOSITY_HELP = {
14 | logging.CRITICAL: "-v",
15 | logging.ERROR: "-v",
16 | logging.WARNING: "-vv",
17 | logging.INFO: "-vvv",
18 | logging.DEBUG: "--traceback",
19 | }
20 |
21 |
22 | def _exception_message(err: BaseException, log_level: int, traceback: bool) -> str:
23 | """Generate a user facing message with help on how to get more information from the logs."""
24 | error_msg = str(err)
25 | if log_level != logging.DEBUG or not traceback:
26 | cli_option = VERBOSITY_HELP.get(log_level, "-v")
27 | error_msg += f"\n\nMore information may be available by using the command line option '{cli_option}'."
28 | return error_msg
29 |
30 |
31 | class MbedToolsHandler:
32 | """Context Manager to catch Mbed Tools exceptions and generate a helpful user facing message."""
33 |
34 | def __init__(self, logger: logging.Logger, traceback: bool = False):
35 | """Keep track of the logger to use and whether or not a traceback should be generated."""
36 | self._logger = logger
37 | self._traceback = traceback
38 | self.exit_code = 0
39 |
40 | def __enter__(self) -> "MbedToolsHandler":
41 | """Return the Context Manager."""
42 | return self
43 |
44 | def __exit__(
45 | self,
46 | exc_type: Optional[Type[BaseException]],
47 | exc_value: Optional[BaseException],
48 | exc_traceback: Optional[TracebackType],
49 | ) -> bool:
50 | """Handle any raised exceptions, suppressing Tools errors and generating an error message instead."""
51 | if exc_type and issubclass(exc_type, ToolsError):
52 | error_msg = _exception_message(cast(BaseException, exc_value), logging.root.level, self._traceback)
53 | self._logger.error(error_msg, exc_info=self._traceback)
54 | # Do not propagate exceptions derived from ToolsError
55 | self.exit_code = 1
56 | return True
57 |
58 | # Propagate all other exceptions
59 | return False
60 |
61 |
62 | def log_exception(logger: logging.Logger, exception: Exception, show_traceback: bool = False) -> None:
63 | """Logs an exception in both normal and verbose forms.
64 |
65 | Args:
66 | logger: logger
67 | exception: exception to log
68 | show_traceback: show the full traceback.
69 | """
70 | logger.error(exception, exc_info=show_traceback)
71 |
72 |
73 | def set_log_level(verbose_count: int) -> None:
74 | """Sets the log level.
75 |
76 | Args:
77 | verbose_count: number of `-v` flags used
78 | """
79 | if verbose_count > 2:
80 | log_level = logging.DEBUG
81 | elif verbose_count == 2:
82 | log_level = logging.INFO
83 | elif verbose_count == 1:
84 | log_level = logging.WARNING
85 | else:
86 | log_level = logging.ERROR
87 | logging.basicConfig(level=log_level, format=LOGGING_FORMAT)
88 |
--------------------------------------------------------------------------------
/src/mbed_tools/devices/_internal/windows/system_data_loader.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2020-2021 Arm Limited and Contributors. All rights reserved.
3 | # SPDX-License-Identifier: Apache-2.0
4 | #
5 | """Loads system data in parallel and all at once in order to improve performance."""
6 | from concurrent.futures import ThreadPoolExecutor
7 | from typing import List, Tuple, Dict, Generator, Optional, cast
8 |
9 | from mbed_tools.devices._internal.windows.component_descriptor import ComponentDescriptorWrapper, ComponentDescriptor
10 | from mbed_tools.devices._internal.windows.disk_drive import DiskDrive
11 | from mbed_tools.devices._internal.windows.disk_partition import DiskPartition
12 | from mbed_tools.devices._internal.windows.disk_partition_logical_disk_relationships import (
13 | DiskPartitionLogicalDiskRelationship,
14 | )
15 | from mbed_tools.devices._internal.windows.logical_disk import LogicalDisk
16 | from mbed_tools.devices._internal.windows.serial_port import SerialPort
17 | from mbed_tools.devices._internal.windows.usb_controller import UsbController
18 | from mbed_tools.devices._internal.windows.usb_hub import UsbHub
19 |
20 | # All Windows system data of interest in order to retrieve the information for DeviceCandidate.
21 | SYSTEM_DATA_TYPES = [
22 | UsbHub,
23 | UsbController,
24 | DiskDrive,
25 | DiskPartition,
26 | LogicalDisk,
27 | DiskPartitionLogicalDiskRelationship,
28 | SerialPort,
29 | ]
30 |
31 |
32 | def load_all(cls: type) -> Tuple[type, List[ComponentDescriptor]]:
33 | """Loads all elements present in the system referring to a specific type."""
34 | return (cls, [element for element in ComponentDescriptorWrapper(cls).element_generator()])
35 |
36 |
37 | class SystemDataLoader:
38 | """Object in charge of loading all system data with regards to Usb, Disk or serial port.
39 |
40 | It loads all the data in parallel and all at once in order to improve performance.
41 | """
42 |
43 | def __init__(self) -> None:
44 | """Initialiser."""
45 | self._system_data: Optional[Dict[type, List[ComponentDescriptor]]] = None
46 |
47 | def _load(self) -> None:
48 | """Loads all system data in parallel."""
49 | with ThreadPoolExecutor() as executor:
50 | results = executor.map(load_all, SYSTEM_DATA_TYPES)
51 | self._system_data = {k: v for (k, v) in results}
52 |
53 | @property
54 | def system_data(self) -> Dict[type, List[ComponentDescriptor]]:
55 | """Gets all system data."""
56 | if not self._system_data:
57 | self._load()
58 | return cast(Dict[type, List[ComponentDescriptor]], self._system_data)
59 |
60 | def get_system_data(self, cls: type) -> List[ComponentDescriptor]:
61 | """Gets the system data for a particular type."""
62 | return self.system_data.get(cls, list())
63 |
64 |
65 | class ComponentsLoader:
66 | """Loads system components."""
67 |
68 | def __init__(self, data_loader: SystemDataLoader, cls: type) -> None:
69 | """initialiser."""
70 | self._cls = cls
71 | self._data_loader = data_loader
72 |
73 | def element_generator(self) -> Generator["ComponentDescriptor", None, None]:
74 | """Gets a generator over all elements currently registered in the system."""
75 | for component in self._data_loader.get_system_data(self._cls):
76 | yield component
77 |
--------------------------------------------------------------------------------
/src/mbed_tools/devices/_internal/windows/volume_set.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2020-2021 Arm Limited and Contributors. All rights reserved.
3 | # SPDX-License-Identifier: Apache-2.0
4 | #
5 | """Defines a Volume Set.
6 |
7 | CIM_VolumeSet should be the data model to use but does not seem to actually return the data that we are looking for:
8 | https://docs.microsoft.com/en-us/windows/win32/cimwin32prov/cim-volumeset
9 | Therefore, a specific data model needs to be constructed using other Windows methods.
10 | """
11 |
12 | from enum import Enum
13 | from typing import NamedTuple, List
14 | from mbed_tools.devices._internal.windows.component_descriptor import UNKNOWN_VALUE
15 |
16 | import logging
17 | import win32.win32api
18 | import win32.win32file
19 |
20 | logger = logging.getLogger(__name__)
21 |
22 |
23 | class DriveType(Enum):
24 | """Drive type as defined in Win32 API.
25 |
26 | See https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getdrivetypea.
27 | """
28 |
29 | DRIVE_UNKNOWN = 0 # The drive type cannot be determined.
30 | DRIVE_NO_ROOT_DIR = 1 # The root path is invalid; for example, there is no volume mounted at the specified path.
31 | DRIVE_REMOVABLE = (
32 | 2
33 | # The drive has removable media; for example, a floppy drive, thumb drive, or flash card reader.
34 | )
35 | DRIVE_FIXED = 3 # The drive has fixed media; for example, a hard disk drive or flash drive.
36 | DRIVE_REMOTE = 4 # The drive is a remote (network) drive.
37 | DRIVE_CDROM = 5 # The drive is a CD-ROM drive.
38 | DRIVE_RAMDISK = 6 # The drive is a RAM disk.
39 |
40 |
41 | class VolumeInformation(NamedTuple):
42 | """Volume information.
43 |
44 | See http://timgolden.me.uk/pywin32-docs/win32api__GetVolumeInformation_meth.html
45 | See also http://timgolden.me.uk/python/win32_how_do_i/find-drive-types.html
46 | """
47 |
48 | Name: str
49 | SerialNumber: int
50 | MaxComponentLengthOfAFileName: int
51 | SysFlags: int
52 | FileSystem: str
53 | UniqueName: str # As defined by GetVolumeNameForVolumeMountPoint
54 | DriveType: DriveType # As defined by GetDriveType
55 |
56 |
57 | def _get_windows_volume_information(volume: str) -> List[str]:
58 | try:
59 | return list(win32.win32api.GetVolumeInformation(volume))
60 | except Exception as e:
61 | logger.debug(f"Cannot retrieve information about volume {volume}. Reason: {e}")
62 | return [UNKNOWN_VALUE] * 5
63 |
64 |
65 | def _get_volume_name_for_mount_point(volume: str) -> str:
66 | try:
67 | return str(win32.win32file.GetVolumeNameForVolumeMountPoint(volume))
68 | except Exception as e:
69 | logger.debug(f"Cannot retrieve the real name of volume {volume}. Reason: {e}")
70 | return UNKNOWN_VALUE
71 |
72 |
73 | def _get_drive_type(volume: str) -> DriveType:
74 | try:
75 | return DriveType(win32.win32file.GetDriveType(volume))
76 | except Exception as e:
77 | logger.debug(f"Cannot retrieve the type of volume {volume}. Reason: {e}")
78 | return DriveType.DRIVE_UNKNOWN
79 |
80 |
81 | def get_volume_information(volume: str) -> VolumeInformation:
82 | """Gets the volume information."""
83 | if not volume.endswith("\\"):
84 | volume = f"{volume}\\"
85 | values: list = _get_windows_volume_information(volume) + [
86 | _get_volume_name_for_mount_point(volume),
87 | _get_drive_type(volume), # type: ignore
88 | ]
89 | return VolumeInformation(*values)
90 |
--------------------------------------------------------------------------------
/tests/devices/_internal/windows/test_windows_component.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2020-2021 Arm Limited and Contributors. All rights reserved.
3 | # SPDX-License-Identifier: Apache-2.0
4 | #
5 | from typing import NamedTuple, Any
6 | from unittest import TestCase
7 |
8 | from mbed_tools.devices._internal.windows.component_descriptor_utils import is_undefined_data_object, is_undefined_value
9 | from tests.devices._internal.windows.test_component_descriptor_utils import (
10 | generate_undefined_values,
11 | generate_valid_values,
12 | )
13 | from tests.devices.markers import windows_only
14 |
15 |
16 | class ComponentDefinition(NamedTuple):
17 | field1: str
18 | field2: str
19 | field3: str
20 | field4: Any
21 | field5: bool
22 | field6: int
23 |
24 |
25 | def get_test_class():
26 | from mbed_tools.devices._internal.windows.component_descriptor import ComponentDescriptor
27 |
28 | class AComponentForTest(ComponentDescriptor):
29 | def __init__(self) -> None:
30 | """Initialiser."""
31 | super().__init__(ComponentDefinition, win32_class_name="Win32_ComputerSystem")
32 |
33 | @property
34 | def component_id(self) -> str:
35 | """Returns the device id field."""
36 | return self.get("field1")
37 |
38 | return AComponentForTest
39 |
40 |
41 | @windows_only
42 | class TestComponentDescriptor(TestCase):
43 | def test_init(self):
44 | self.assertIsNotNone(get_test_class()())
45 | self.assertTrue(get_test_class()().__class__, "AComponentForTest")
46 |
47 | def test_parameters(self):
48 | self.assertListEqual([name for name in ComponentDefinition._fields], get_test_class()().field_names)
49 |
50 | def test_set_values(self):
51 |
52 | a_component = get_test_class()()
53 | self.assertTrue(is_undefined_data_object(a_component.to_tuple()))
54 | valid_values = {k: generate_valid_values() for k in a_component.field_names}
55 | a_component.set_data_values(valid_values)
56 | self.assertFalse(is_undefined_data_object(a_component.to_tuple()))
57 | self.assertTupleEqual(a_component.to_tuple(), tuple(valid_values.values()))
58 |
59 | def test_is_undefined(self):
60 | a_component = get_test_class()()
61 | self.assertTrue(a_component.is_undefined)
62 | self.assertTrue(is_undefined_value(a_component.component_id))
63 | a_component_with_undefined_values = get_test_class()()
64 | undefined_values = {k: generate_undefined_values() for k in a_component.field_names}
65 | a_component_with_undefined_values.set_data_values(undefined_values)
66 | self.assertTrue(a_component_with_undefined_values.is_undefined)
67 | a_defined_component = get_test_class()()
68 | valid_values = {k: generate_valid_values() for k in a_component.field_names}
69 | a_defined_component.set_data_values(valid_values)
70 | self.assertIsNotNone(a_defined_component.component_id)
71 | self.assertFalse(a_defined_component.is_undefined)
72 |
73 | def test_iterator(self):
74 | from mbed_tools.devices._internal.windows.component_descriptor import ComponentDescriptorWrapper
75 |
76 | generator = ComponentDescriptorWrapper(get_test_class()).element_generator()
77 | self.assertIsNotNone(generator)
78 |
79 | for element in generator:
80 | # The generator should be defined but none of the element should be defined as fields should not exist
81 | self.assertTrue(element.is_undefined)
82 |
--------------------------------------------------------------------------------
/tests/build/test_flash.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2020-2021 Arm Limited and Contributors. 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.build.flash import flash_binary, _build_binary_file_path, _flash_dev
12 | from mbed_tools.build.exceptions import BinaryFileNotFoundError
13 |
14 | from tests.build.factories import DeviceFactory
15 |
16 |
17 | @mock.patch("mbed_tools.build.flash._build_binary_file_path")
18 | @mock.patch("mbed_tools.build.flash._flash_dev")
19 | class TestFlashBinary(TestCase):
20 | def test_check_flashing(self, _flash_dev, _build_binary_file_path):
21 | test_device = DeviceFactory()
22 |
23 | _flash_dev.return_value = True
24 |
25 | with tempfile.TemporaryDirectory() as tmpDir:
26 | base_dir = pathlib.Path(tmpDir)
27 | build_dir = base_dir / "cmake_build"
28 | build_dir.mkdir()
29 | bin_file = base_dir.name + ".bin"
30 | bin_file = build_dir / bin_file
31 | bin_file.touch()
32 | _build_binary_file_path.return_value = bin_file
33 |
34 | flash_binary(test_device.mount_points[0].resolve(), base_dir, build_dir, "TEST", False)
35 |
36 | _build_binary_file_path.assert_called_once_with(base_dir, build_dir, False)
37 | _flash_dev.assert_called_once_with(test_device.mount_points[0].resolve(), bin_file)
38 |
39 |
40 | class TestBuildBinFilePath(TestCase):
41 | def test_build_bin_file_path(self):
42 | with tempfile.TemporaryDirectory() as tmpDir:
43 | base_dir = pathlib.Path(tmpDir)
44 | build_dir = base_dir / "cmake_build"
45 | build_dir.mkdir()
46 | bin_file = base_dir.name + ".bin"
47 | bin_file = build_dir / bin_file
48 | bin_file.touch()
49 |
50 | self.assertEqual(_build_binary_file_path(base_dir, build_dir, False), bin_file)
51 |
52 | def test_build_hex_file_path(self):
53 | with tempfile.TemporaryDirectory() as tmpDir:
54 | base_dir = pathlib.Path(tmpDir)
55 | build_dir = base_dir / "cmake_build"
56 | build_dir.mkdir()
57 | bin_file = base_dir.name + ".hex"
58 | bin_file = build_dir / bin_file
59 | bin_file.touch()
60 |
61 | self.assertEqual(_build_binary_file_path(base_dir, build_dir, True), bin_file)
62 |
63 | def test_missing_binary_file(self):
64 | with tempfile.TemporaryDirectory() as tmpDir:
65 | base_dir = pathlib.Path(tmpDir)
66 | build_dir = base_dir / "cmake_build"
67 | build_dir.mkdir()
68 |
69 | with self.assertRaises(BinaryFileNotFoundError):
70 | _build_binary_file_path(base_dir, build_dir, False)
71 |
72 |
73 | @mock.patch("mbed_tools.build.flash.shutil.copy")
74 | class TestCopyToDevice(TestCase):
75 | def test_copy_to_device(self, copy):
76 | test_device = DeviceFactory()
77 |
78 | with tempfile.TemporaryDirectory() as tmpDir:
79 | base_dir = pathlib.Path(tmpDir)
80 | build_dir = base_dir / "cmake_build"
81 | build_dir.mkdir()
82 | bin_file = base_dir.name + ".bin"
83 | bin_file = build_dir / bin_file
84 | bin_file.touch()
85 | _flash_dev(test_device.mount_points[0].resolve(), bin_file)
86 |
87 | copy.assert_called_once_with(bin_file, test_device.mount_points[0].resolve(), follow_symlinks=False)
88 |
--------------------------------------------------------------------------------
/src/mbed_tools/build/_internal/config/config.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2020-2021 Arm Limited and Contributors. All rights reserved.
3 | # SPDX-License-Identifier: Apache-2.0
4 | #
5 | """Build configuration representation."""
6 | import logging
7 |
8 | from collections import UserDict
9 | from typing import Any, Iterable, Hashable, List
10 |
11 | from mbed_tools.build._internal.config.source import Override, ConfigSetting
12 |
13 | logger = logging.getLogger(__name__)
14 |
15 |
16 | class Config(UserDict):
17 | """Mapping of config settings.
18 |
19 | This object understands how to populate the different 'config sections' which all have different rules for how the
20 | settings are collected.
21 | Applies overrides, appends macros, and updates config settings.
22 | """
23 |
24 | def __setitem__(self, key: Hashable, item: Any) -> None:
25 | """Set an item based on its key."""
26 | if key == CONFIG_SECTION:
27 | self._update_config_section(item)
28 | elif key == OVERRIDES_SECTION:
29 | self._handle_overrides(item)
30 | elif key == MACROS_SECTION:
31 | self.data[MACROS_SECTION] = self.data.get(MACROS_SECTION, set()) | item
32 | elif key == REQUIRES_SECTION:
33 | self.data[REQUIRES_SECTION] = self.data.get(REQUIRES_SECTION, set()) | item
34 | else:
35 | super().__setitem__(key, item)
36 |
37 | def _handle_overrides(self, overrides: Iterable[Override]) -> None:
38 | for override in overrides:
39 | logger.debug("Applying override '%s.%s'='%s'", override.namespace, override.name, repr(override.value))
40 | if override.name in self.data:
41 | _apply_override(self.data, override)
42 | continue
43 |
44 | setting = next(
45 | filter(
46 | lambda x: x.name == override.name and x.namespace == override.namespace,
47 | self.data.get(CONFIG_SECTION, []),
48 | ),
49 | None,
50 | )
51 | if setting is None:
52 | logger.warning(
53 | f"You are attempting to override an undefined config parameter "
54 | f"`{override.namespace}.{override.name}`.\n"
55 | "It is an error to override an undefined configuration parameter. "
56 | "Please check your target_overrides are correct.\n"
57 | f"The parameter `{override.namespace}.{override.name}` will not be added to the configuration."
58 | )
59 | else:
60 | setting.value = override.value
61 |
62 | def _update_config_section(self, config_settings: List[ConfigSetting]) -> None:
63 | for setting in config_settings:
64 | logger.debug("Adding config setting: '%s.%s'", setting.namespace, setting.name)
65 | if setting in self.data.get(CONFIG_SECTION, []):
66 | raise ValueError(
67 | f"Setting {setting.namespace}.{setting.name} already defined. You cannot duplicate config settings!"
68 | )
69 |
70 | self.data[CONFIG_SECTION] = self.data.get(CONFIG_SECTION, []) + config_settings
71 |
72 |
73 | CONFIG_SECTION = "config"
74 | MACROS_SECTION = "macros"
75 | OVERRIDES_SECTION = "overrides"
76 | REQUIRES_SECTION = "requires"
77 |
78 |
79 | def _apply_override(data: dict, override: Override) -> None:
80 | if override.modifier == "add":
81 | data[override.name] |= override.value
82 | elif override.modifier == "remove":
83 | data[override.name] -= override.value
84 | else:
85 | data[override.name] = override.value
86 |
--------------------------------------------------------------------------------
/azure-pipelines/analyse-and-test.yml:
--------------------------------------------------------------------------------
1 | # Azure Pipeline for Build and Release a Python package.
2 | #
3 | # This pipeline performs multiple actions to ensure the quality of the package:
4 | # - Performs static analysis and runs test on multiple Python versions and host platforms.
5 | # - Uploads test coverage to Code Climate
6 |
7 | # Trigger on a PR to master, beta or releases branches.
8 | pr:
9 | - master
10 | - beta
11 | - releases/*
12 |
13 | stages:
14 | - stage: AnalyseTest
15 | displayName: 'Analyse and Test'
16 | jobs:
17 | - job: Test
18 | strategy:
19 | maxParallel: 10
20 | matrix:
21 | Assert_news:
22 | python.version: '3.7'
23 | vmImageName: ubuntu-latest
24 | uploadCoverage: "false"
25 | tox.env: checknews
26 |
27 | Linting_Py_3_7:
28 | python.version: '3.7'
29 | vmImageName: ubuntu-latest
30 | uploadCoverage: "false"
31 | tox.env: linting
32 |
33 | Linting_Py_3_8:
34 | python.version: '3.8'
35 | vmImageName: ubuntu-latest
36 | uploadCoverage: "false"
37 | tox.env: linting
38 |
39 | Linting_Py_3_9:
40 | python.version: '3.9'
41 | vmImageName: ubuntu-latest
42 | uploadCoverage: "false"
43 | tox.env: linting
44 |
45 | Linux_Py_3_7:
46 | python.version: '3.7'
47 | vmImageName: ubuntu-latest
48 | uploadCoverage: "false"
49 | tox.env: py37
50 |
51 | Linux_Py_3_8:
52 | python.version: '3.8'
53 | vmImageName: ubuntu-latest
54 | uploadCoverage: "false"
55 | tox.env: py38
56 |
57 | Linux_Py_3_9:
58 | python.version: '3.9'
59 | vmImageName: ubuntu-latest
60 | uploadCoverage: "true"
61 | tox.env: py39
62 |
63 | Windows_Py_3_7:
64 | python.version: '3.7'
65 | vmImageName: windows-latest
66 | uploadCoverage: "false"
67 | tox.env: py37
68 |
69 | Windows_Py_3_8:
70 | python.version: '3.8'
71 | vmImageName: windows-latest
72 | uploadCoverage: "false"
73 | tox.env: py38
74 |
75 | Windows_Py_3_9:
76 | python.version: '3.9'
77 | vmImageName: windows-latest
78 | uploadCoverage: "true"
79 | tox.env: py39
80 |
81 | macOS_Py_3_7:
82 | python.version: '3.7'
83 | vmImageName: macOS-latest
84 | uploadCoverage: "false"
85 | tox.env: py37
86 |
87 | macOS_Py_3_8:
88 | python.version: '3.8'
89 | vmImageName: macOS-latest
90 | uploadCoverage: "false"
91 | tox.env: py38
92 |
93 | macOS_Py_3_9:
94 | python.version: '3.9'
95 | vmImageName: macOS-latest
96 | uploadCoverage: "true"
97 | tox.env: py39
98 |
99 | pool:
100 | vmImage: $(vmImageName)
101 |
102 | steps:
103 | - task: UsePythonVersion@0
104 | inputs:
105 | versionSpec: '$(python.version)'
106 | displayName: 'Use Python $(python.version)'
107 |
108 | - script: |
109 | python -m pip install --upgrade tox
110 | tox -e $(tox.env)
111 | displayName: run tox -e $(tox.env)
112 |
113 | - template: steps/publish-code-coverage-results.yml
114 |
--------------------------------------------------------------------------------
/src/mbed_tools/cli/configure.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2020-2021 Arm Limited and Contributors. All rights reserved.
3 | # SPDX-License-Identifier: Apache-2.0
4 | #
5 | """Command to generate the application CMake configuration script used by the build/compile system."""
6 | import pathlib
7 |
8 | import click
9 |
10 | from mbed_tools.project import MbedProgram
11 | from mbed_tools.build import generate_config
12 |
13 |
14 | @click.command(
15 | help="Generate an Mbed OS config CMake file and write it to a .mbedbuild folder in the program directory."
16 | )
17 | @click.option(
18 | "--custom-targets-json", type=click.Path(), default=None, help="Path to custom_targets.json.",
19 | )
20 | @click.option(
21 | "-t",
22 | "--toolchain",
23 | type=click.Choice(["ARM", "GCC_ARM"], case_sensitive=False),
24 | required=True,
25 | help="The toolchain you are using to build your app.",
26 | )
27 | @click.option("-m", "--mbed-target", required=True, help="A build target for an Mbed-enabled device, eg. K64F")
28 | @click.option("-b", "--profile", default="develop", help="The build type (release, develop or debug).")
29 | @click.option("-o", "--output-dir", type=click.Path(), default=None, help="Path to output directory.")
30 | @click.option(
31 | "-p",
32 | "--program-path",
33 | type=click.Path(),
34 | default=".",
35 | help="Path to local Mbed program. By default is the current working directory.",
36 | )
37 | @click.option(
38 | "--mbed-os-path", type=click.Path(), default=None, help="Path to local Mbed OS directory.",
39 | )
40 | @click.option(
41 | "--app-config", type=click.Path(), default=None, help="Path to application configuration file.",
42 | )
43 | def configure(
44 | toolchain: str,
45 | mbed_target: str,
46 | profile: str,
47 | program_path: str,
48 | mbed_os_path: str,
49 | output_dir: str,
50 | custom_targets_json: str,
51 | app_config: str
52 | ) -> None:
53 | """Exports a mbed_config.cmake file to build directory in the program root.
54 |
55 | The parameters set in the CMake file will be dependent on the combination of
56 | toolchain and Mbed target provided and these can then control which parts of
57 | Mbed OS are included in the build.
58 |
59 | This command will create the .mbedbuild directory at the program root if it doesn't
60 | exist.
61 |
62 | Args:
63 | custom_targets_json: the path to custom_targets.json
64 | toolchain: the toolchain you are using (eg. GCC_ARM, ARM)
65 | mbed_target: the target you are building for (eg. K64F)
66 | profile: The Mbed build profile (debug, develop or release).
67 | program_path: the path to the local Mbed program
68 | mbed_os_path: the path to the local Mbed OS directory
69 | output_dir: the path to the output directory
70 | app_config: the path to the application configuration file
71 | """
72 | cmake_build_subdir = pathlib.Path(mbed_target.upper(), profile.lower(), toolchain.upper())
73 | if mbed_os_path is None:
74 | program = MbedProgram.from_existing(pathlib.Path(program_path), cmake_build_subdir)
75 | else:
76 | program = MbedProgram.from_existing(pathlib.Path(program_path), cmake_build_subdir, pathlib.Path(mbed_os_path))
77 | if custom_targets_json is not None:
78 | program.files.custom_targets_json = pathlib.Path(custom_targets_json)
79 | if output_dir is not None:
80 | program.files.cmake_build_dir = pathlib.Path(output_dir)
81 | if app_config is not None:
82 | program.files.app_config_file = pathlib.Path(app_config)
83 |
84 | mbed_target = mbed_target.upper()
85 | _, output_path = generate_config(mbed_target, toolchain, program)
86 | click.echo(f"mbed_config.cmake has been generated and written to '{str(output_path.resolve())}'")
87 |
--------------------------------------------------------------------------------
/tests/cli/test_sterm.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2020-2021 Arm Limited and Contributors. All rights reserved.
3 | # SPDX-License-Identifier: Apache-2.0
4 | #
5 | from unittest import mock
6 |
7 | import pytest
8 |
9 | from click.testing import CliRunner
10 |
11 | from mbed_tools.cli.sterm import sterm
12 | from mbed_tools.devices.exceptions import MbedDevicesError
13 |
14 |
15 | @pytest.fixture
16 | def mock_terminal():
17 | with mock.patch("mbed_tools.cli.sterm.terminal") as term:
18 | yield term
19 |
20 |
21 | @pytest.fixture
22 | def mock_get_devices():
23 | with mock.patch("mbed_tools.cli.sterm.get_connected_devices") as get_devs:
24 | yield get_devs
25 |
26 |
27 | @pytest.fixture
28 | def mock_find_device():
29 | with mock.patch("mbed_tools.cli.sterm.find_connected_device") as find_dev:
30 | yield find_dev
31 |
32 |
33 | def test_launches_terminal_on_given_serial_port(mock_terminal):
34 | port = "tty.1111"
35 | CliRunner().invoke(sterm, ["--port", port])
36 |
37 | mock_terminal.run.assert_called_once_with(port, 9600, echo=True)
38 |
39 |
40 | def test_launches_terminal_with_given_baud_rate(mock_terminal):
41 | port = "tty.1111"
42 | baud = 115200
43 | CliRunner().invoke(sterm, ["--port", port, "--baudrate", baud])
44 |
45 | mock_terminal.run.assert_called_once_with(port, baud, echo=True)
46 |
47 |
48 | def test_launches_terminal_with_echo_off_when_specified(mock_terminal):
49 | port = "tty.1111"
50 | CliRunner().invoke(sterm, ["--port", port, "--echo", "off"])
51 |
52 | mock_terminal.run.assert_called_once_with(port, 9600, echo=False)
53 |
54 |
55 | def test_attempts_to_detect_device_if_no_port_given(mock_get_devices, mock_terminal):
56 | CliRunner().invoke(sterm, [])
57 |
58 | mock_get_devices.assert_called_once()
59 |
60 |
61 | def test_attempts_to_find_connected_target_if_target_given(mock_find_device, mock_terminal):
62 | expected_port = "tty.k64f"
63 | mock_find_device.return_value = mock.Mock(serial_port=expected_port, mbed_board=mock.Mock(board_type="K64F"))
64 |
65 | CliRunner().invoke(sterm, ["-m", "K64F"])
66 |
67 | mock_terminal.run.assert_called_once_with(expected_port, 9600, echo=True)
68 |
69 |
70 | def test_returns_serial_port_for_first_device_detected_if_no_target_given(mock_get_devices, mock_terminal):
71 | expected_port = "tty.k64f"
72 | mock_get_devices.return_value = mock.Mock(
73 | identified_devices=[
74 | mock.Mock(serial_port=expected_port, mbed_board=mock.Mock(board_type="K64F")),
75 | mock.Mock(serial_port="tty.disco", mbed_board=mock.Mock(board_type="DISCO")),
76 | ]
77 | )
78 |
79 | CliRunner().invoke(sterm, [])
80 |
81 | mock_terminal.run.assert_called_once_with(expected_port, 9600, echo=True)
82 |
83 |
84 | def test_returns_serial_port_for_device_if_identifier_given(mock_find_device, mock_terminal):
85 | expected_port = "tty.k64f"
86 | mock_find_device.return_value = mock.Mock(serial_port=expected_port, mbed_board=mock.Mock(board_type="K64F"))
87 |
88 | CliRunner().invoke(sterm, ["-m", "K64F[1]"])
89 |
90 | mock_terminal.run.assert_called_once_with(expected_port, 9600, echo=True)
91 |
92 |
93 | def test_raises_when_fails_to_find_default_device(mock_get_devices, mock_terminal):
94 | mock_get_devices.return_value = mock.Mock(identified_devices=[])
95 |
96 | with pytest.raises(MbedDevicesError):
97 | CliRunner().invoke(sterm, [], catch_exceptions=False)
98 |
99 |
100 | def test_not_run_if_target_identifier_not_int(mock_get_devices, mock_terminal):
101 | target = "K64F[foo]"
102 | CliRunner().invoke(sterm, ["-m", target], catch_exceptions=False)
103 | mock_get_devices.assert_not_called()
104 | mock_terminal.assert_not_called()
105 |
--------------------------------------------------------------------------------
/tests/cli/test_configure.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2020-2021 Arm Limited and Contributors. All rights reserved.
3 | # SPDX-License-Identifier: Apache-2.0
4 | #
5 | import pathlib
6 |
7 | from unittest import TestCase, mock
8 |
9 | from click.testing import CliRunner
10 |
11 | from mbed_tools.cli.configure import configure
12 |
13 |
14 | class TestConfigureCommand(TestCase):
15 | @mock.patch("mbed_tools.cli.configure.generate_config")
16 | @mock.patch("mbed_tools.cli.configure.MbedProgram")
17 | def test_generate_config_called_with_correct_arguments(self, program, generate_config):
18 | CliRunner().invoke(configure, ["-m", "k64f", "-t", "gcc_arm"])
19 |
20 | generate_config.assert_called_once_with("K64F", "GCC_ARM", program.from_existing())
21 |
22 | @mock.patch("mbed_tools.cli.configure.generate_config")
23 | @mock.patch("mbed_tools.cli.configure.MbedProgram")
24 | def test_generate_config_called_with_mbed_os_path(self, program, generate_config):
25 | CliRunner().invoke(configure, ["-m", "k64f", "-t", "gcc_arm", "--mbed-os-path", "./extern/mbed-os"])
26 |
27 | generate_config.assert_called_once_with("K64F", "GCC_ARM", program.from_existing())
28 |
29 | @mock.patch("mbed_tools.cli.configure.generate_config")
30 | @mock.patch("mbed_tools.cli.configure.MbedProgram")
31 | def test_custom_targets_location_used_when_passed(self, program, generate_config):
32 | program = program.from_existing()
33 | custom_targets_json_path = pathlib.Path("custom", "custom_targets.json")
34 | CliRunner().invoke(
35 | configure, ["-t", "gcc_arm", "-m", "k64f", "--custom-targets-json", custom_targets_json_path]
36 | )
37 |
38 | generate_config.assert_called_once_with("K64F", "GCC_ARM", program)
39 | self.assertEqual(program.files.custom_targets_json, custom_targets_json_path)
40 |
41 | @mock.patch("mbed_tools.cli.configure.generate_config")
42 | @mock.patch("mbed_tools.cli.configure.MbedProgram")
43 | def test_custom_output_directory_used_when_passed(self, program, generate_config):
44 | program = program.from_existing()
45 | output_dir = pathlib.Path("build")
46 | CliRunner().invoke(configure, ["-t", "gcc_arm", "-m", "k64f", "-o", output_dir])
47 |
48 | generate_config.assert_called_once_with("K64F", "GCC_ARM", program)
49 | self.assertEqual(program.files.cmake_build_dir, output_dir)
50 |
51 | @mock.patch("mbed_tools.cli.configure.generate_config")
52 | @mock.patch("mbed_tools.cli.configure.MbedProgram")
53 | def test_app_config_used_when_passed(self, program, generate_config):
54 | program = program.from_existing()
55 | app_config_path = pathlib.Path("alternative_config.json")
56 | CliRunner().invoke(
57 | configure, ["-t", "gcc_arm", "-m", "k64f", "--app-config", app_config_path]
58 | )
59 |
60 | generate_config.assert_called_once_with("K64F", "GCC_ARM", program)
61 | self.assertEqual(program.files.app_config_file, app_config_path)
62 |
63 | @mock.patch("mbed_tools.cli.configure.generate_config")
64 | @mock.patch("mbed_tools.cli.configure.MbedProgram")
65 | def test_profile_used_when_passed(self, program, generate_config):
66 | test_program = program.from_existing()
67 | program.reset_mock() # clear call count from previous line
68 |
69 | toolchain = "gcc_arm"
70 | target = "k64f"
71 | profile = "release"
72 |
73 | CliRunner().invoke(
74 | configure, ["-t", toolchain, "-m", target, "--profile", profile]
75 | )
76 |
77 | program.from_existing.assert_called_once_with(
78 | pathlib.Path("."),
79 | pathlib.Path(target.upper(), profile, toolchain.upper())
80 | )
81 | generate_config.assert_called_once_with("K64F", "GCC_ARM", test_program)
82 |
--------------------------------------------------------------------------------
/src/mbed_tools/devices/_internal/candidate_device.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2020-2021 Arm Limited and Contributors. All rights reserved.
3 | # SPDX-License-Identifier: Apache-2.0
4 | #
5 | """Defines CandidateDevice model used for device detection."""
6 | from dataclasses import dataclass
7 | from typing import Optional, Tuple, Any, Union, cast
8 | from pathlib import Path
9 |
10 |
11 | class CandidateDeviceError(ValueError):
12 | """Base exception raised by a CandidateDevice."""
13 |
14 |
15 | class USBDescriptorError(CandidateDeviceError):
16 | """USB descriptor field was not found."""
17 |
18 |
19 | class FilesystemMountpointError(CandidateDeviceError):
20 | """Filesystem mount point was not found."""
21 |
22 |
23 | class DataField:
24 | """CandidateDevice data attribute descriptor."""
25 |
26 | def __set_name__(self, owner: object, name: str) -> None:
27 | """Sets the descriptor name, this is called by magic in the owners.__new__ method."""
28 | self.name = name
29 |
30 | def __get__(self, instance: object, owner: object = None) -> Any:
31 | """Get the attribute value from the instance."""
32 | return instance.__dict__.setdefault(self.name, None)
33 |
34 |
35 | class USBDescriptorHex(DataField):
36 | """USB descriptor field which cannot be set to an empty value, or an invalid hex value."""
37 |
38 | def __set__(self, instance: object, value: Any) -> None:
39 | """Prevent setting the descriptor to an empty or invalid hex value."""
40 | try:
41 | instance.__dict__[self.name] = _format_hex(value)
42 | except ValueError:
43 | raise USBDescriptorError(f"{self.name} cannot be an empty and must be valid hex.")
44 |
45 |
46 | class USBDescriptorString(DataField):
47 | """USB descriptor field which cannot be set to an empty value."""
48 |
49 | def __set__(self, instance: object, value: str) -> None:
50 | """Prevent setting the descriptor to a non-string or empty value."""
51 | if not value or not isinstance(value, str):
52 | raise USBDescriptorError(f"{self.name} cannot be an empty field and must be a string.")
53 |
54 | instance.__dict__[self.name] = value
55 |
56 |
57 | class FilesystemMountpoints(DataField):
58 | """Data descriptor which must be set to a non-empty list or tuple."""
59 |
60 | def __set__(self, instance: object, value: Union[tuple, list]) -> None:
61 | """Prevent setting the descriptor to a non-sequence or empty sequence value."""
62 | if not value or not isinstance(value, (list, tuple)):
63 | raise FilesystemMountpointError(f"{self.name} must be set to a non-empty list or tuple.")
64 |
65 | instance.__dict__[self.name] = tuple(value)
66 |
67 |
68 | @dataclass(frozen=True, order=True)
69 | class CandidateDevice:
70 | """Valid candidate device connected to the host computer.
71 |
72 | We define a CandidateDevice as any USB mass storage device which mounts a filesystem.
73 | The device may or may not present a serial port.
74 |
75 | Attributes:
76 | product_id: USB device product ID.
77 | vendor_id: USB device vendor ID.
78 | serial_number: USB device serial number.
79 | mount_points: Filesystem mount points associated with the device.
80 | serial_port: Serial port associated with the device, this could be None.
81 | """
82 |
83 | product_id: str = cast(str, USBDescriptorHex())
84 | vendor_id: str = cast(str, USBDescriptorHex())
85 | serial_number: str = cast(str, USBDescriptorString())
86 | mount_points: Tuple[Path, ...] = cast(Tuple[Path], FilesystemMountpoints())
87 | serial_port: Optional[str] = None
88 |
89 |
90 | def _format_hex(hex_value: str) -> str:
91 | """Return hex value with a prefix.
92 |
93 | Accepts hex_value in prefixed (0xff) and unprefixed (ff) formats.
94 | """
95 | return hex(int(hex_value, 16))
96 |
--------------------------------------------------------------------------------
/src/mbed_tools/targets/board.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2020-2021 Arm Limited and Contributors. All rights reserved.
3 | # SPDX-License-Identifier: Apache-2.0
4 | #
5 | """Representation of an Mbed-Enabled Development Board and related utilities."""
6 | from dataclasses import dataclass
7 | from typing import Tuple
8 |
9 |
10 | @dataclass(frozen=True, order=True)
11 | class Board:
12 | """Representation of an Mbed-Enabled Development Board.
13 |
14 | Attributes:
15 | board_type: Type of board in format that allows cross-referencing with target definitions.
16 | board_name: Human readable name.
17 | product_code: Uniquely identifies a board for the online compiler.
18 | target_type: A confusing term that is not related to 'target' in other parts of the code.
19 | Identifies if a board in the online database is a `module` or a `platform` (modules are more
20 | complex and often have supplementary sensors, displays etc. A platform is a normal development board).
21 | slug: Used with target_type to identify a board's page on the mbed website.
22 | build_variant: Available build variants for the board.
23 | Can be used in conjunction with board_type for referencing targets.
24 | mbed_os_support: The versions of Mbed OS supported.
25 | mbed_enabled: Whether Mbed OS is supported or not.
26 | """
27 |
28 | board_type: str
29 | board_name: str
30 | product_code: str
31 | target_type: str
32 | slug: str
33 | build_variant: Tuple[str, ...]
34 | mbed_os_support: Tuple[str, ...]
35 | mbed_enabled: Tuple[str, ...]
36 |
37 | @classmethod
38 | def from_online_board_entry(cls, board_entry: dict) -> "Board":
39 | """Create a new instance of Board from an online database entry.
40 |
41 | Args:
42 | board_entry: A single entity retrieved from the board database API.
43 | """
44 | board_attrs = board_entry.get("attributes", {})
45 | board_features = board_attrs.get("features", {})
46 |
47 | return cls(
48 | # Online database has inconsistently cased board types.
49 | # Since this field is used to match against `targets.json`, we need to ensure consistency is maintained.
50 | board_type=board_attrs.get("board_type", "").upper(),
51 | board_name=board_attrs.get("name", ""),
52 | mbed_os_support=tuple(board_features.get("mbed_os_support", [])),
53 | mbed_enabled=tuple(board_features.get("mbed_enabled", [])),
54 | product_code=board_attrs.get("product_code", ""),
55 | target_type=board_attrs.get("target_type", ""),
56 | slug=board_attrs.get("slug", ""),
57 | # TODO: Remove this hard-coded example after demoing.
58 | # NOTE: Currently we hard-code the build variant for a single board type.
59 | # This is simply so we can demo the tools to PE. This must be removed and replaced with a proper mechanism
60 | # for determining the build variant for all platforms. We probably need to add this information to the
61 | # boards database.
62 | build_variant=tuple(),
63 | )
64 |
65 | @classmethod
66 | def from_offline_board_entry(cls, board_entry: dict) -> "Board":
67 | """Construct an Board with data from the offline database snapshot."""
68 | return cls(
69 | board_type=board_entry.get("board_type", ""),
70 | board_name=board_entry.get("board_name", ""),
71 | product_code=board_entry.get("product_code", ""),
72 | target_type=board_entry.get("target_type", ""),
73 | slug=board_entry.get("slug", ""),
74 | mbed_os_support=tuple(board_entry.get("mbed_os_support", [])),
75 | mbed_enabled=tuple(board_entry.get("mbed_enabled", [])),
76 | build_variant=tuple(board_entry.get("build_variant", [])),
77 | )
78 |
--------------------------------------------------------------------------------
/tests/devices/_internal/windows/test_disk_identifier.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2020-2021 Arm Limited and Contributors. All rights reserved.
3 | # SPDX-License-Identifier: Apache-2.0
4 | #
5 | import unittest
6 | from tests.devices.markers import windows_only
7 |
8 |
9 | @windows_only
10 | class TestDiskUid(unittest.TestCase):
11 | def test_disk_uid_parsing(self):
12 | from mbed_tools.devices._internal.windows.disk_drive import Win32DiskIdParser, WindowsUID
13 |
14 | # Tests the parsing of real data.
15 | # ST Link
16 | pnpid1 = "USBSTOR\\DISK&VEN_MBED&PROD_MICROCONTROLLER&REV_1.0\\9&175DDF0B&0&066DFF555654725187095153&0"
17 | serial_number1 = "066DFF555654725187095153"
18 | self.assertEqual(
19 | Win32DiskIdParser().parse(pnpid=pnpid1, serial_number=serial_number1),
20 | WindowsUID(
21 | uid="066dff555654725187095153",
22 | raw_uid="9&175DDF0B&0&066DFF555654725187095153&0",
23 | serial_number="066DFF555654725187095153",
24 | ),
25 | )
26 | pnpid2 = "USBSTOR\\DISK&VEN_MBED&PROD_MICROCONTROLLER&REV_1.0\\9&3849C7A8&0&0672FF574953867567051035&0"
27 | serial_number2 = "0672FF574953867567051035"
28 | self.assertEqual(
29 | Win32DiskIdParser().parse(pnpid=pnpid2, serial_number=serial_number2),
30 | WindowsUID(
31 | uid="0672ff574953867567051035",
32 | raw_uid="9&3849C7A8&0&0672FF574953867567051035&0",
33 | serial_number="0672FF574953867567051035",
34 | ),
35 | )
36 | # System disk
37 | pnpid3 = "SCSI\\DISK&VEN_SAMSUNG&PROD_MZNLN512HMJP-000\\4&143821B1&0&000100"
38 | serial_number3 = "S2XANX0J211020"
39 | self.assertEqual(
40 | Win32DiskIdParser().parse(pnpid=pnpid3, serial_number=serial_number3),
41 | WindowsUID(uid="4&143821b1&0&000100", raw_uid="4&143821B1&0&000100", serial_number="S2XANX0J211020"),
42 | )
43 | # Daplink
44 | pnpid4 = "USBSTOR\\DISK&VEN_MBED&PROD_VFS&REV_0.1\\0240000034544E45001A00018AA900292011000097969900&0"
45 | serial_number4 = "0240000034544E45001A00018AA900292011000097969900"
46 | self.assertEqual(
47 | Win32DiskIdParser().parse(pnpid=pnpid4, serial_number=serial_number4),
48 | WindowsUID(
49 | uid="0240000034544e45001a00018aa900292011000097969900",
50 | raw_uid="0240000034544E45001A00018AA900292011000097969900&0",
51 | serial_number="0240000034544E45001A00018AA900292011000097969900",
52 | ),
53 | )
54 | # J Link
55 | pnpid5 = "USBSTOR\\DISK&VEN_SEGGER&PROD_MSD_VOLUME&REV_1.00\\9&DBDECF6&0&000440112138&0"
56 | serial_number5 = " 134657890"
57 | self.assertEqual(
58 | Win32DiskIdParser().parse(pnpid=pnpid5, serial_number=serial_number5),
59 | WindowsUID(
60 | uid="000440112138",
61 | raw_uid="9&DBDECF6&0&000440112138&0",
62 | serial_number=" 134657890",
63 | ),
64 | )
65 |
66 | def test_uid_linking_between_usb_and_disk(self):
67 | from mbed_tools.devices._internal.windows.usb_device_identifier import UsbIdentifier, WindowsUID
68 |
69 | disk_uid = WindowsUID(
70 | uid="000440112138",
71 | raw_uid="9&DBDECF6&0&000440112138&0",
72 | serial_number=" 134657890",
73 | )
74 |
75 | usb_uid = UsbIdentifier(
76 | UID=WindowsUID(uid="000440112138", raw_uid="000440112138", serial_number="8&2f125ec6&0"),
77 | VID="1366",
78 | PID="1015",
79 | REV=None,
80 | MI=None,
81 | )
82 | self.assertEqual(disk_uid.presumed_serial_number, usb_uid.uid.presumed_serial_number)
83 | self.assertEqual(usb_uid.uid.presumed_serial_number, disk_uid.presumed_serial_number)
84 |
--------------------------------------------------------------------------------
/src/mbed_tools/devices/_internal/resolve_board.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2020-2021 Arm Limited and Contributors. All rights reserved.
3 | # SPDX-License-Identifier: Apache-2.0
4 | #
5 | """Resolve targets for `CandidateDevice`.
6 |
7 | Resolving a target involves looking up an `MbedTarget` from the `mbed-targets` API, using data found in the "htm file"
8 | located on an "Mbed Enabled" device's USB MSD.
9 |
10 | For more information on the mbed-targets package visit https://github.com/ARMmbed/mbed-targets
11 | """
12 | import logging
13 |
14 | from typing import Optional
15 |
16 | from mbed_tools.targets import (
17 | Board,
18 | get_board_by_product_code,
19 | get_board_by_online_id,
20 | get_board_by_jlink_slug,
21 | )
22 | from mbed_tools.targets.exceptions import UnknownBoard, MbedTargetsError
23 |
24 | from mbed_tools.devices._internal.exceptions import NoBoardForCandidate, ResolveBoardError
25 | from mbed_tools.devices._internal.file_parser import OnlineId
26 |
27 |
28 | logger = logging.getLogger(__name__)
29 |
30 |
31 | def resolve_board(
32 | product_code: Optional[str] = None, online_id: Optional[OnlineId] = None, serial_number: str = ""
33 | ) -> Board:
34 | """Resolves a board object from the platform database.
35 |
36 | We have multiple ways to identify boards from various metadata sources Mbed provides. This is because there are
37 | many supported Mbed device families, each with slightly different ways of identifying themselves as Mbed enabled.
38 | Because of this we need to try each input in turn, falling back to the next lookup method in the priority order if
39 | the previous one was unsuccessful.
40 |
41 | The rules are as follows:
42 |
43 | 1. Use the product code from the mbed.htm file or details.txt if available
44 | 2. Use online ID from the htm file or Board.html if available
45 | 3. Try to use the first 4 chars of the USB serial number as the product code
46 | """
47 | if product_code:
48 | try:
49 | return get_board_by_product_code(product_code)
50 | except UnknownBoard:
51 | logger.error(f"Could not identify a board with the product code: '{product_code}'.")
52 | except MbedTargetsError as e:
53 | logger.error(
54 | f"There was an error looking up the product code `{product_code}` from the target database.\nError: {e}"
55 | )
56 | raise ResolveBoardError() from e
57 |
58 | if online_id:
59 | slug = online_id.slug
60 | target_type = online_id.target_type
61 | try:
62 | if target_type == "jlink":
63 | return get_board_by_jlink_slug(slug=slug)
64 | else:
65 | return get_board_by_online_id(slug=slug, target_type=target_type)
66 | except UnknownBoard:
67 | logger.error(f"Could not identify a board with the slug: '{slug}' and target type: '{target_type}'.")
68 | except MbedTargetsError as e:
69 | logger.error(
70 | f"There was an error looking up the online ID `{online_id!r}` from the target database.\nError: {e}"
71 | )
72 | raise ResolveBoardError() from e
73 |
74 | # Product code might be the first 4 characters of the serial number
75 | product_code = serial_number[:4]
76 | if product_code:
77 | try:
78 | return get_board_by_product_code(product_code)
79 | except UnknownBoard:
80 | # Most devices have a serial number so this may not be a problem
81 | logger.info(
82 | f"The device with the Serial Number: '{serial_number}' (Product Code: '{product_code}') "
83 | f"does not appear to be an Mbed development board."
84 | )
85 | except MbedTargetsError as e:
86 | logger.error(
87 | f"There was an error looking up the product code `{product_code}` from the target database.\nError: {e}"
88 | )
89 | raise ResolveBoardError() from e
90 |
91 | raise NoBoardForCandidate
92 |
--------------------------------------------------------------------------------
/src/mbed_tools/devices/_internal/windows/usb_hub_data_loader.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2020-2021 Arm Limited and Contributors. All rights reserved.
3 | # SPDX-License-Identifier: Apache-2.0
4 | #
5 | """Loads System's USB hub."""
6 |
7 | from typing import Dict, List, cast, Optional, Set, Generator
8 |
9 | from mbed_tools.devices._internal.windows.component_descriptor import ComponentDescriptor
10 | from mbed_tools.devices._internal.windows.device_instance_id import get_children_instance_id
11 | from mbed_tools.devices._internal.windows.system_data_loader import SystemDataLoader, ComponentsLoader
12 | from mbed_tools.devices._internal.windows.usb_controller import UsbController
13 | from mbed_tools.devices._internal.windows.usb_device_identifier import parse_device_id, UsbIdentifier
14 | from mbed_tools.devices._internal.windows.usb_hub import UsbHub
15 |
16 |
17 | class SystemUsbDeviceInformation:
18 | """Usb Hub cache for this system.
19 |
20 | On Windows, each interface e.g. Composite, Mass storage, Port is defined as
21 | a separate independent UsbHub although they are related to the same device.
22 | This cache tries to reduce the list of UsbHubs to only genuinely different devices.
23 | """
24 |
25 | def __init__(self, data_loader: SystemDataLoader) -> None:
26 | """Initialiser."""
27 | self._cache: Optional[Dict[UsbIdentifier, List[UsbHub]]] = None
28 | self._ids_cache: Optional[Set[UsbIdentifier]] = None
29 | self._data_loader = data_loader
30 |
31 | def _list_usb_controller_ids(self) -> List[UsbIdentifier]:
32 | return cast(
33 | List[UsbIdentifier],
34 | [
35 | parse_device_id(cast(UsbController, usbc).component_id)
36 | for usbc in ComponentsLoader(self._data_loader, UsbController).element_generator()
37 | ],
38 | )
39 |
40 | def _iterate_over_hubs(self) -> Generator[ComponentDescriptor, None, None]:
41 | return ComponentsLoader(self._data_loader, UsbHub).element_generator()
42 |
43 | def _populate_id_cache_with_non_serialnumbers(self) -> None:
44 | if not self._cache or not self._ids_cache:
45 | return
46 | for usb_id in self._cache:
47 | if usb_id not in self._ids_cache:
48 | self._ids_cache.add(usb_id)
49 |
50 | def _determine_potential_serial_number(self, usb_device: UsbHub) -> Optional[str]:
51 | return get_children_instance_id(usb_device.pnp_id)
52 |
53 | def _load(self) -> None:
54 | """Populates the cache."""
55 | self._cache = cast(Dict[UsbIdentifier, List[UsbHub]], dict())
56 | self._ids_cache = cast(Set[UsbIdentifier], set())
57 | controllers = self._list_usb_controller_ids()
58 | for usb_device in self._iterate_over_hubs():
59 | usb_id = parse_device_id(
60 | usb_device.component_id, serial_number=self._determine_potential_serial_number(cast(UsbHub, usb_device))
61 | )
62 | if usb_id in controllers:
63 | continue
64 | entry = self._cache.get(usb_id, list())
65 | entry.append(cast(UsbHub, usb_device))
66 | self._cache[usb_id] = entry
67 |
68 | if usb_id.contains_genuine_serial_number():
69 | self._ids_cache.add(usb_id)
70 | self._populate_id_cache_with_non_serialnumbers()
71 |
72 | @property
73 | def usb_devices(self) -> Dict[UsbIdentifier, List[UsbHub]]:
74 | """Usb devices present in the system."""
75 | if not self._cache:
76 | self._load()
77 | return cast(Dict[UsbIdentifier, List[UsbHub]], self._cache)
78 |
79 | def get_usb_devices(self, uid: UsbIdentifier) -> List[UsbHub]:
80 | """Gets all USB devices related to an identifier."""
81 | return self.usb_devices.get(uid, list())
82 |
83 | def usb_device_ids(self) -> List[UsbIdentifier]:
84 | """Gets system usb device IDs."""
85 | if not self._ids_cache:
86 | self._load()
87 | return cast(List[UsbIdentifier], self._ids_cache)
88 |
--------------------------------------------------------------------------------
/src/mbed_tools/cli/project_management.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2020-2021 Arm Limited and Contributors. All rights reserved.
3 | # SPDX-License-Identifier: Apache-2.0
4 | #
5 | """Project management commands: new, import_, deploy and libs."""
6 | import os
7 | import pathlib
8 |
9 | from typing import Any, List
10 |
11 | import click
12 | import tabulate
13 |
14 | from mbed_tools.project import initialise_project, import_project, get_known_libs, deploy_project
15 | from mbed_tools.project._internal import git_utils
16 |
17 |
18 | @click.command()
19 | @click.option("--create-only", "-c", is_flag=True, show_default=True, help="Create a program without fetching mbed-os.")
20 | @click.argument("path", type=click.Path(resolve_path=True))
21 | def new(path: str, create_only: bool) -> None:
22 | """Creates a new Mbed project at the specified path. Downloads mbed-os and adds it to the project.
23 |
24 | PATH: Path to the destination directory for the project. Will be created if it does not exist.
25 | """
26 | click.echo(f"Creating a new Mbed program at path '{path}'.")
27 | if not create_only:
28 | click.echo("Downloading mbed-os and adding it to the project.")
29 |
30 | initialise_project(pathlib.Path(path), create_only)
31 |
32 |
33 | @click.command()
34 | @click.argument("url")
35 | @click.argument("path", type=click.Path(), default="")
36 | @click.option(
37 | "--skip-resolve-libs",
38 | "-s",
39 | is_flag=True,
40 | show_default=True,
41 | help="Skip resolving program library dependencies after cloning.",
42 | )
43 | def import_(url: str, path: Any, skip_resolve_libs: bool) -> None:
44 | """Clone an Mbed project and library dependencies.
45 |
46 | URL: The git url of the remote project to clone.
47 |
48 | PATH: Destination path for the clone. If not given the destination path is set to the project name in the cwd.
49 | """
50 | click.echo(f"Cloning Mbed program '{url}'")
51 | if not skip_resolve_libs:
52 | click.echo("Resolving program library dependencies.")
53 |
54 | if path:
55 | click.echo(f"Destination path is '{path}'")
56 | path = pathlib.Path(path)
57 |
58 | dst_path = import_project(url, path, not skip_resolve_libs)
59 | if not skip_resolve_libs:
60 | libs = get_known_libs(dst_path)
61 | _print_dependency_table(libs)
62 |
63 |
64 | @click.command()
65 | @click.argument("path", type=click.Path(), default=os.getcwd())
66 | @click.option(
67 | "--force",
68 | "-f",
69 | is_flag=True,
70 | show_default=True,
71 | help="Forces checkout of all library repositories at specified commit in the .lib file, overwrites local changes.",
72 | )
73 | def deploy(path: str, force: bool) -> None:
74 | """Checks out Mbed program library dependencies at the revision specified in the ".lib" files.
75 |
76 | Ensures all dependencies are resolved and the versions are synchronised to the version specified in the library
77 | reference.
78 |
79 | PATH: Path to the Mbed project [default: CWD]
80 | """
81 | click.echo("Checking out all libraries to revisions specified in .lib files. Resolving any unresolved libraries.")
82 | root_path = pathlib.Path(path)
83 | deploy_project(root_path, force)
84 | libs = get_known_libs(root_path)
85 | _print_dependency_table(libs)
86 |
87 |
88 | def _print_dependency_table(libs: List) -> None:
89 | click.echo("The following library dependencies were fetched: \n")
90 | table = []
91 | for lib in libs:
92 | table.append(
93 | [
94 | lib.reference_file.stem,
95 | lib.get_git_reference().repo_url,
96 | lib.source_code_path,
97 | git_utils.get_default_branch(git_utils.get_repo(lib.source_code_path))
98 | if not lib.get_git_reference().ref
99 | else lib.get_git_reference().ref,
100 | ]
101 | )
102 |
103 | headers = ("Library Name", "Repository URL", "Path", "Git Reference")
104 | click.echo(tabulate.tabulate(table, headers=headers))
105 |
--------------------------------------------------------------------------------
/src/mbed_tools/devices/devices.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2020-2021 Arm Limited and Contributors. All rights reserved.
3 | # SPDX-License-Identifier: Apache-2.0
4 | #
5 | """API for listing devices."""
6 |
7 | from operator import attrgetter
8 | from typing import List, Optional
9 |
10 | from mbed_tools.devices._internal.detect_candidate_devices import detect_candidate_devices
11 |
12 | from mbed_tools.devices.device import ConnectedDevices, Device
13 | from mbed_tools.devices.exceptions import DeviceLookupFailed, NoDevicesFound
14 |
15 |
16 | def get_connected_devices() -> ConnectedDevices:
17 | """Returns Mbed Devices connected to host computer.
18 |
19 | Connected devices which have been identified as Mbed Boards and also connected devices which are potentially
20 | Mbed Boards (but not could not be identified in the database) are returned.
21 | """
22 | connected_devices = ConnectedDevices()
23 |
24 | for candidate_device in detect_candidate_devices():
25 | device = Device.from_candidate(candidate_device)
26 | connected_devices.add_device(device)
27 |
28 | return connected_devices
29 |
30 |
31 | def find_connected_device(target_name: str, identifier: Optional[int] = None) -> Device:
32 | """Find a connected device matching the given target_name, if there is only one.
33 |
34 | Args:
35 | target_name: The Mbed target name of the device.
36 | identifier: Where multiple of the same Mbed device are connected, the associated [id].
37 |
38 | Raise:
39 | DeviceLookupFailed: Could not find device matching target_name.
40 |
41 | Returns:
42 | The first Device found matching target_name.
43 | """
44 | devices = find_all_connected_devices(target_name)
45 | if identifier is None and len(devices) == 1:
46 | return devices[0]
47 | elif identifier is not None and len(devices) > identifier:
48 | return devices[identifier]
49 |
50 | detected_targets = "\n".join(
51 | f"target: {dev.mbed_board.board_type}[{i}]," f" port: {dev.serial_port}, mount point(s): {dev.mount_points}"
52 | for i, dev in enumerate(devices)
53 | )
54 | if identifier is None:
55 | msg = (
56 | f"`Multiple matching, please select a connected target with [n] identifier.\n"
57 | f"The following {target_name}s were detected:\n{detected_targets}"
58 | )
59 | else:
60 | msg = (
61 | f"`{target_name}[{identifier}]` is not a valid connected target.\n"
62 | f"The following {target_name}s were detected:\n{detected_targets}"
63 | )
64 | raise DeviceLookupFailed(msg)
65 |
66 |
67 | def find_all_connected_devices(target_name: str) -> List[Device]:
68 | """Find all connected devices matching the given target_name.
69 |
70 | Args:
71 | target_name: The Mbed target name of the device.
72 |
73 | Raises:
74 | NoDevicesFound: Could not find any connected devices.
75 | DeviceLookupFailed: Could not find a connected device matching target_name.
76 |
77 | Returns:
78 | List of Devices matching target_name.
79 | """
80 | connected = get_connected_devices()
81 | if not connected.identified_devices:
82 | raise NoDevicesFound("No Mbed enabled devices found.")
83 |
84 | matching_devices = sorted(
85 | [device for device in connected.identified_devices if device.mbed_board.board_type == target_name.upper()],
86 | key=attrgetter("serial_number"),
87 | )
88 | if matching_devices:
89 | return matching_devices
90 |
91 | detected_targets = "\n".join(
92 | f"target: {dev.mbed_board.board_type}, port: {dev.serial_port}, mount point(s): {dev.mount_points}"
93 | for dev in connected.identified_devices
94 | )
95 | msg = (
96 | f"Target '{target_name}' was not detected.\n"
97 | "Check the device is connected by USB, and that the name is entered correctly.\n"
98 | f"The following devices were detected:\n{detected_targets}"
99 | )
100 | raise DeviceLookupFailed(msg)
101 |
--------------------------------------------------------------------------------