Repository: ARMmbed/mbed-tools

Issue:
Allow setting custom output directory in "configure" sub command
**Is your feature request related to a problem? Please describe.**
I use a custom build tool (that calls cmake under the hood) to build packages in my project. The fact mbed-tools use such an unusual build directory makes things awkward to integrate. 

**Describe the solution you'd like**
It would be great if mbed-tools could allow passing an "-o/--output-dir" option to configure that would allow us to customize where mbed_config.cmake will be written to.

**Describe alternatives you've considered**
Heuristically trying to find out where mbed_config.cmake was generated and moving to an appropriate location. This solution is suboptimal, at best.

Discussion:
codecov[bot]: # [Codecov](https://codecov.io/gh/ARMmbed/mbed-tools/pull/288?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ARMmbed) Report
> Merging [#288](https://codecov.io/gh/ARMmbed/mbed-tools/pull/288?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ARMmbed) (0b72511) into [master](https://codecov.io/gh/ARMmbed/mbed-tools/commit/71e9707b908c393691a4e509ced90ce608e68b81?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ARMmbed) (71e9707) will **increase** coverage by `0.00%`.
> The diff coverage is `100.00%`.

[![Impacted file tree graph](https://codecov.io/gh/ARMmbed/mbed-tools/pull/288/graphs/tree.svg?width=650&height=150&src=pr&token=QFRIdRv91G&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ARMmbed)](https://codecov.io/gh/ARMmbed/mbed-tools/pull/288?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ARMmbed)

```diff
@@           Coverage Diff           @@
##           master     #288   +/-   ##
=======================================
  Coverage   97.12%   97.12%           
=======================================
  Files          92       92           
  Lines        2813     2819    +6     
=======================================
+ Hits         2732     2738    +6     
  Misses         81       81           
```


| [Impacted Files](https://codecov.io/gh/ARMmbed/mbed-tools/pull/288?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ARMmbed) | Coverage Δ | |
|---|---|---|
| [src/mbed\_tools/cli/configure.py](https://codecov.io/gh/ARMmbed/mbed-tools/pull/288/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ARMmbed#diff-c3JjL21iZWRfdG9vbHMvY2xpL2NvbmZpZ3VyZS5weQ==) | `100.00% <100.00%> (ø)` | |

rwalton-arm: Thanks for the PR. Could you squash the commit "Fix flake8 linter offenses" so the history is clean? We merge the patchset as is, and we'd prefer not to have "fixup" commits in the history. You'll also need to rebase as the branch is out of date with the base branch.
g-arjones: No problem. Updated...
g-arjones: Done.
g-arjones: Did I miss anything?
g-arjones: Rebased.
rwalton-arm: We had an automated release this morning and the branch is out of date again. Sorry! If you could rebase one more time we will get this in today.
g-arjones: Rebased, again.

Patch:
diff --git a/news/286.bugfix b/news/286.bugfix
new file mode 100644
index 0000000..745bfb1
--- /dev/null
+++ b/news/286.bugfix
@@ -0,0 +1,1 @@
+Properly handle --custom-targets-json in configure sub command
diff --git a/news/287.bugfix b/news/287.bugfix
new file mode 100644
index 0000000..586d532
--- /dev/null
+++ b/news/287.bugfix
@@ -0,0 +1,1 @@
+Allow choosing output directory in configure sub command
diff --git a/src/mbed_tools/cli/configure.py b/src/mbed_tools/cli/configure.py
index 71e2c79..553266e 100644
--- a/src/mbed_tools/cli/configure.py
+++ b/src/mbed_tools/cli/configure.py
@@ -14,6 +14,9 @@ from mbed_tools.build import generate_config
 @click.command(
     help="Generate an Mbed OS config CMake file and write it to a .mbedbuild folder in the program directory."
 )
+@click.option(
+    "--custom-targets-json", type=click.Path(), default=None, help="Path to custom_targets.json.",
+)
 @click.option(
     "-t",
     "--toolchain",
@@ -22,6 +25,7 @@ from mbed_tools.build import generate_config
     help="The toolchain you are using to build your app.",
 )
 @click.option("-m", "--mbed-target", required=True, help="A build target for an Mbed-enabled device, eg. K64F")
+@click.option("-o", "--output-dir", type=click.Path(), default=None, help="Path to output directory.")
 @click.option(
     "-p",
     "--program-path",
@@ -32,7 +36,9 @@ from mbed_tools.build import generate_config
 @click.option(
     "--mbed-os-path", type=click.Path(), default=None, help="Path to local Mbed OS directory.",
 )
-def configure(toolchain: str, mbed_target: str, program_path: str, mbed_os_path: str) -> None:
+def configure(
+    toolchain: str, mbed_target: str, program_path: str, mbed_os_path: str, output_dir: str, custom_targets_json: str
+) -> None:
     """Exports a mbed_config.cmake file to build directory in the program root.
 
     The parameters set in the CMake file will be dependent on the combination of
@@ -43,16 +49,23 @@ def configure(toolchain: str, mbed_target: str, program_path: str, mbed_os_path:
     exist.
 
     Args:
+        custom_targets_json: the path to custom_targets.json
         toolchain: the toolchain you are using (eg. GCC_ARM, ARM)
         mbed_target: the target you are building for (eg. K64F)
         program_path: the path to the local Mbed program
         mbed_os_path: the path to the local Mbed OS directory
+        output_dir: the path to the output directory
     """
     cmake_build_subdir = pathlib.Path(mbed_target.upper(), "develop", toolchain.upper())
     if mbed_os_path is None:
         program = MbedProgram.from_existing(pathlib.Path(program_path), cmake_build_subdir)
     else:
         program = MbedProgram.from_existing(pathlib.Path(program_path), cmake_build_subdir, pathlib.Path(mbed_os_path))
+    if custom_targets_json is not None:
+        program.files.custom_targets_json = pathlib.Path(custom_targets_json)
+    if output_dir is not None:
+        program.files.cmake_build_dir = pathlib.Path(output_dir)
+
     mbed_target = mbed_target.upper()
     output_path = generate_config(mbed_target, toolchain, program)
     click.echo(f"mbed_config.cmake has been generated and written to '{str(output_path.resolve())}'")


Test patch:
diff --git a/tests/cli/test_configure.py b/tests/cli/test_configure.py
index edb2341..a0c61fd 100644
--- a/tests/cli/test_configure.py
+++ b/tests/cli/test_configure.py
@@ -2,6 +2,8 @@
 # Copyright (c) 2020-2021 Arm Limited and Contributors. All rights reserved.
 # SPDX-License-Identifier: Apache-2.0
 #
+import pathlib
+
 from unittest import TestCase, mock
 
 from click.testing import CliRunner
@@ -23,3 +25,25 @@ class TestConfigureCommand(TestCase):
         CliRunner().invoke(configure, ["-m", "k64f", "-t", "gcc_arm", "--mbed-os-path", "./extern/mbed-os"])
 
         generate_config.assert_called_once_with("K64F", "GCC_ARM", program.from_existing())
+
+    @mock.patch("mbed_tools.cli.configure.generate_config")
+    @mock.patch("mbed_tools.cli.configure.MbedProgram")
+    def test_custom_targets_location_used_when_passed(self, program, generate_config):
+        program = program.from_existing()
+        custom_targets_json_path = pathlib.Path("custom", "custom_targets.json")
+        CliRunner().invoke(
+            configure, ["-t", "gcc_arm", "-m", "k64f", "--custom-targets-json", custom_targets_json_path]
+        )
+
+        generate_config.assert_called_once_with("K64F", "GCC_ARM", program)
+        self.assertEqual(program.files.custom_targets_json, custom_targets_json_path)
+
+    @mock.patch("mbed_tools.cli.configure.generate_config")
+    @mock.patch("mbed_tools.cli.configure.MbedProgram")
+    def test_custom_output_directory_used_when_passed(self, program, generate_config):
+        program = program.from_existing()
+        output_dir = pathlib.Path("build")
+        CliRunner().invoke(configure, ["-t", "gcc_arm", "-m", "k64f", "-o", output_dir])
+
+        generate_config.assert_called_once_with("K64F", "GCC_ARM", program)
+        self.assertEqual(program.files.cmake_build_dir, output_dir)