Repository: ARMmbed/mbed-tools

Issue:
mbed-tools fails to handle config options that contain quotes
### Description

From @rajkan01:

For the below mbed_lib.json config

```
"iotc-mqtt-host": { 	 
    "help": "IOTC MQTT host configuration. Defaults to mqtt.2030.ltsapis.goog host and port number 8883 if undefined", 	 
    "value": "{\"mqtt.2030.ltsapis.goog\", IOTC_MQTT_PORT}", 	 
    "macro_name": "IOTC_MQTT_HOST" 	 
 }
```

Mbedtools is generating `"-DIOTC_MQTT_HOST={"mqtt.2030.ltsapis.goog", IOTC_MQTT_PORT}"` config starts double-quotes from -D itself, and CMake prepossessing time this macro gets divided into multiple #define like below because of this define begin with double-quotes and also the value ("mqtt.2030.ltsapis.goog") with double-quote consider to be a string

```
#define IOTC_MQTT_HOST { 
#define mqtt .2030.ltsapis.goog, IOTC_MQTT_PORT} 1 
```

Could someone check this, why is the mbedtools generating macros starts with double-quotes which include `-D` and fix.

I've attached `main.ii` and `mbed_config.cmake`
[mbed_config.zip](https://github.com/ARMmbed/mbed-tools/files/5602300/mbed_config.zip)


### Issue request type

<!--
Please add only one `x` to one of the following types. Do not fill multiple types (split the issue otherwise).


For questions please use https://forums.mbed.com/
-->

- [ ] Enhancement
- [X] Bug


Discussion:
codecov[bot]: # [Codecov](https://codecov.io/gh/ARMmbed/mbed-tools/pull/138?src=pr&el=h1) Report
> Merging [#138](https://codecov.io/gh/ARMmbed/mbed-tools/pull/138?src=pr&el=desc) (1999bcd) into [master](https://codecov.io/gh/ARMmbed/mbed-tools/commit/1fbc64249a3650dd633fdf2631cc256aec6971b5?el=desc) (1fbc642) will **decrease** coverage by `0.07%`.
> The diff coverage is `n/a`.

[![Impacted file tree graph](https://codecov.io/gh/ARMmbed/mbed-tools/pull/138/graphs/tree.svg?width=650&height=150&src=pr&token=QFRIdRv91G)](https://codecov.io/gh/ARMmbed/mbed-tools/pull/138?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master     #138      +/-   ##
==========================================
- Coverage   96.52%   96.44%   -0.08%     
==========================================
  Files          95       95              
  Lines        2644     2644              
==========================================
- Hits         2552     2550       -2     
- Misses         92       94       +2     
```


| [Impacted Files](https://codecov.io/gh/ARMmbed/mbed-tools/pull/138?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [...ools/devices/\_internal/detect\_candidate\_devices.py](https://codecov.io/gh/ARMmbed/mbed-tools/pull/138/diff?src=pr&el=tree#diff-c3JjL21iZWRfdG9vbHMvZGV2aWNlcy9faW50ZXJuYWwvZGV0ZWN0X2NhbmRpZGF0ZV9kZXZpY2VzLnB5) | `88.23% <0.00%> (-11.77%)` | :arrow_down: |


Patch:
diff --git a/news/20201201142709.bugfix b/news/20201201142709.bugfix
new file mode 100644
index 0000000..0468f3e
--- /dev/null
+++ b/news/20201201142709.bugfix
@@ -0,0 +1,1 @@
+Fix bug where we failed to handle config options that contain quotes (#125)
diff --git a/src/mbed_tools/build/_internal/templates/mbed_config.tmpl b/src/mbed_tools/build/_internal/templates/mbed_config.tmpl
index e4820af..08ccced 100644
--- a/src/mbed_tools/build/_internal/templates/mbed_config.tmpl
+++ b/src/mbed_tools/build/_internal/templates/mbed_config.tmpl
@@ -65,7 +65,7 @@ set(MBED_CONFIG_DEFINITIONS
 # options
 {% for option in options -%}
 {% if option.value is not none -%}
-   {%if '{' in option.value|string or '(' in option.value|string %}"{% endif %}-D{{option.macro_name}}={{option.value}}{% if '}' in option.value|string or ')' in option.value|string %}"{% endif %}
+   "-D{{option.macro_name}}={{option.value|replace("\"", "\\\"")}}"
 {% endif %}
 {%- endfor %}
 # macros


Test patch:
diff --git a/tests/build/_internal/test_cmake_file.py b/tests/build/_internal/test_cmake_file.py
index 1f59cb3..b0247a8 100644
--- a/tests/build/_internal/test_cmake_file.py
+++ b/tests/build/_internal/test_cmake_file.py
@@ -2,67 +2,69 @@
 # Copyright (C) 2020 Arm Mbed. All rights reserved.
 # SPDX-License-Identifier: Apache-2.0
 #
-from unittest import TestCase
+import pytest
 
-from tests.build._internal.config.factories import ConfigFactory
+from tests.build._internal.config.factories import ConfigFactory, SourceFactory
 from mbed_tools.build._internal.cmake_file import generate_mbed_config_cmake_file, _render_mbed_config_cmake_template
+from mbed_tools.build._internal.config.config import _create_config_option
 
 
-class TestGenerateCMakeListsFile(TestCase):
-    def test_correct_arguments_passed(self):
-        target = dict()
-        target["labels"] = ["foo"]
-        target["extra_labels"] = ["morefoo"]
-        target["features"] = ["bar"]
-        target["components"] = ["baz"]
-        target["macros"] = ["macbaz"]
-        target["device_has"] = ["stuff"]
-        target["c_lib"] = ["c_lib"]
-        target["core"] = ["core"]
-        target["printf_lib"] = ["printf_lib"]
-        target["supported_form_factors"] = ["arduino"]
+TOOLCHAIN_NAME = "gcc"
+
+
+@pytest.fixture()
+def fake_target():
+    return {
+        "labels": ["foo"],
+        "extra_labels": ["morefoo"],
+        "features": ["bar"],
+        "components": ["baz"],
+        "macros": ["macbaz"],
+        "device_has": ["stuff"],
+        "c_lib": ["c_lib"],
+        "core": ["core"],
+        "printf_lib": ["printf_lib"],
+        "supported_form_factors": ["arduino"],
+        "supported_c_libs": {TOOLCHAIN_NAME: ["ginormous"]},
+        "supported_application_profiles": ["full", "bare-metal"],
+    }
+
+
+class TestGenerateCMakeListsFile:
+    def test_correct_arguments_passed(self, fake_target):
         config = ConfigFactory()
         mbed_target = "K64F"
-        toolchain_name = "GCC"
-        target["supported_c_libs"] = {toolchain_name.lower(): ["small", "std"]}
-        target["supported_application_profiles"] = ["full", "bare-metal"]
-
-        result = generate_mbed_config_cmake_file(mbed_target, target, config, toolchain_name)
-
-        self.assertEqual(
-            result, _render_mbed_config_cmake_template(target, config, toolchain_name, mbed_target,),
-        )
-
-
-class TestRendersCMakeListsFile(TestCase):
-    def test_returns_rendered_content(self):
-        target = dict()
-        target["labels"] = ["foo"]
-        target["extra_labels"] = ["morefoo"]
-        target["features"] = ["bar"]
-        target["components"] = ["baz"]
-        target["macros"] = ["macbaz"]
-        target["device_has"] = ["stuff"]
-        target["core"] = ["core"]
-        target["c_lib"] = ["c_lib"]
-        target["printf_lib"] = ["printf_lib"]
-        target["supported_form_factors"] = ["arduino"]
+
+        result = generate_mbed_config_cmake_file(mbed_target, fake_target, config, TOOLCHAIN_NAME)
+
+        assert result == _render_mbed_config_cmake_template(fake_target, config, TOOLCHAIN_NAME, mbed_target,)
+
+
+class TestRendersCMakeListsFile:
+    def test_returns_rendered_content(self, fake_target):
         config = ConfigFactory()
-        toolchain_name = "baz"
-        target["supported_c_libs"] = {toolchain_name.lower(): ["small", "std"]}
-        target["supported_application_profiles"] = ["full", "bare-metal"]
-        result = _render_mbed_config_cmake_template(target, config, toolchain_name, "target_name")
+        result = _render_mbed_config_cmake_template(fake_target, config, TOOLCHAIN_NAME, "target_name")
 
-        for label in target["labels"] + target["extra_labels"]:
-            self.assertIn(label, result)
+        for label in fake_target["labels"] + fake_target["extra_labels"]:
+            assert label in result
 
-        for macro in target["features"] + target["components"] + [toolchain_name]:
-            self.assertIn(macro, result)
+        for macro in fake_target["features"] + fake_target["components"] + [TOOLCHAIN_NAME]:
+            assert macro in result
 
-        for toolchain in target["supported_c_libs"]:
-            self.assertIn(toolchain, result)
+        for toolchain in fake_target["supported_c_libs"]:
+            assert toolchain in result
             for supported_c_libs in toolchain:
-                self.assertIn(supported_c_libs, result)
+                assert supported_c_libs in result
+
+        for supported_application_profiles in fake_target["supported_application_profiles"]:
+            assert supported_application_profiles in result
+
+    def test_returns_quoted_content(self, fake_target):
+        config = ConfigFactory()
+        source = SourceFactory()
+
+        # Add an option whose value contains quotes to the config.
+        _create_config_option(config, "iotc-mqtt-host", '{"mqtt.2030.ltsapis.goog", IOTC_MQTT_PORT}', source)
 
-        for supported_application_profiles in target["supported_application_profiles"]:
-            self.assertIn(supported_application_profiles, result)
+        result = _render_mbed_config_cmake_template(fake_target, config, TOOLCHAIN_NAME, "target_name")
+        assert '"-DMBED_CONF_IOTC_MQTT_HOST={\\"mqtt.2030.ltsapis.goog\\", IOTC_MQTT_PORT}"' in result