Repository: randombit/botan
Pull request #3061 (2022-10-18 11:37:15)

VS Code: Basic Integrations and Helpers
This adds some convenience functionality for Visual Studio Code. Note that this is somewhat best-effort and might need further adaptions for other people. In my current setup (Windows host + Ubuntu 22.04 in WSL) works reasonably well with this.

### Included

* C++ formatting adapter for AStyle (using chiehyu.vscode-astyle plugin)
* Tasks for:
  * Configure/Building
  * Running Unittests
    * either all tests at once or just the selected test source file
    * problem matcher to visualize test failures in the editor
  * Running BoGo TLS Tests
* Debug configuration for Unittests using GDB
* Code snippets for the copyright headers in Python/C++ files
* IntelliSense finds and uses `compile_commands.json`
* PyLint integration

### Usage

Create a symlink in the project root like so: `ln -s src/editors/vscode .vscode`, then start VS Code and profit. It might be beneficial to also use `ln -s src/editors/editorconfig/.editorconfig .editorconfig`.

### Examples

#### Unittest Problem Matcher

![image](https://user-images.githubusercontent.com/1562139/196420796-225aad83-2727-4c84-9539-31dcf1b84a9e.png)

#### Running BoGo Tests

![image](https://user-images.githubusercontent.com/1562139/196421280-4a48d609-2bdc-4d58-9aa9-12c655193800.png)




Patch:
diff --git a/.gitignore b/.gitignore
index 7973cba7c..20297993a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,6 +12,7 @@ libbotan*.so.*
 *.exe
 *.manifest
 build
+build_deps
 build.log
 botan
 botan-test
diff --git a/src/editors/editorconfig/.editorconfig b/src/editors/editorconfig/.editorconfig
index 0dab9d754..9cf06978c 100644
--- a/src/editors/editorconfig/.editorconfig
+++ b/src/editors/editorconfig/.editorconfig
@@ -14,4 +14,4 @@ tab_width = 3
 
 [*.py]
 indent_size = 4
-tab_width = 47
+tab_width = 4
diff --git a/src/editors/vscode/botan.code-snippets b/src/editors/vscode/botan.code-snippets
new file mode 100644
index 000000000..40ee2eb45
--- /dev/null
+++ b/src/editors/vscode/botan.code-snippets
@@ -0,0 +1,37 @@
+{
+    "Botan Copyright Notice (C++)": {
+        "prefix": [
+            "copyright",
+            "header"
+        ],
+        "body": [
+            "/**",
+            " * ${1:<brief file content description>}",
+            " * (C) ${2:<year>} Jack Lloyd",
+            " *     ${3:<year>} ${4:<your name(s)>} - ${5:<your company name (optional)>}",
+            " *",
+            " * Botan is released under the Simplified BSD License (see license.txt)",
+            " */"
+        ],
+        "description": "The copyright header provided in all of Botan's C++ and header files",
+        "scope": "c,cpp"
+    },
+    "Botan Copyright Notice (Python)": {
+        "prefix": [
+            "copyright",
+            "header"
+        ],
+        "body": [
+            "\"\"\"",
+            "${1:<brief file content description>}",
+            "",
+            "(C) ${2:<year>} Jack Lloyd",
+            "    ${3:<year>} ${4:<your name(s)>} (${5:<your company name (optional)>})",
+            "",
+            "Botan is released under the Simplified BSD License (see license.txt)",
+            "\"\"\""
+        ],
+        "description": "The copyright header provided in all of Botan's Python scripts",
+        "scope": "python"
+    }
+}
diff --git a/src/editors/vscode/extensions.json b/src/editors/vscode/extensions.json
new file mode 100644
index 000000000..35e9e329e
--- /dev/null
+++ b/src/editors/vscode/extensions.json
@@ -0,0 +1,13 @@
+{
+    // See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations.
+    // Extension identifier format: ${publisher}.${name}. Example: vscode.csharp
+    // List of extensions which should be recommended for users of this workspace.
+    "recommendations": [
+        "ms-vscode.cpptools",
+        "ms-python.python",
+        "chiehyu.vscode-astyle",
+        "EditorConfig.EditorConfig"
+    ],
+    // List of extensions recommended by VS Code that should not be recommended for users of this workspace.
+    "unwantedRecommendations": []
+}
diff --git a/src/editors/vscode/launch.json b/src/editors/vscode/launch.json
new file mode 100644
index 000000000..18b3b84ca
--- /dev/null
+++ b/src/editors/vscode/launch.json
@@ -0,0 +1,30 @@
+{
+    "configurations": [
+        {
+            "name": "Debug Unittests",
+            "type": "cppdbg",
+            "request": "launch",
+            "program": "${workspaceFolder}/botan-test",
+            "args": [
+                "--test-threads=1"
+            ],
+            "stopAtEntry": false,
+            "cwd": "${workspaceFolder}",
+            "environment": [],
+            "externalConsole": false,
+            "MIMode": "gdb",
+            "setupCommands": [
+                {
+                    "description": "Enable pretty-printing for gdb",
+                    "text": "-enable-pretty-printing",
+                    "ignoreFailures": true
+                },
+                {
+                    "description": "Set Disassembly Flavor to Intel",
+                    "text": "-gdb-set disassembly-flavor intel",
+                    "ignoreFailures": true
+                }
+            ]
+        }
+    ]
+}
\ No newline at end of file
diff --git a/src/editors/vscode/scripts/bogo.py b/src/editors/vscode/scripts/bogo.py
new file mode 100755
index 000000000..4e2183fdc
--- /dev/null
+++ b/src/editors/vscode/scripts/bogo.py
@@ -0,0 +1,31 @@
+#!/usr/bin/env python3
+
+import os
+from common import run_cmd, get_concurrency
+
+
+BORING_REPO = "https://github.com/reneme/boringssl.git"
+BORING_BRANCH = "rene/runner-20220322"
+
+BORING_PATH = "build_deps/boringssl"
+BOGO_PATH = os.path.join(BORING_PATH, "ssl", "test", "runner")
+
+SHIM_PATH = "./botan_bogo_shim"
+SHIM_CONFIG = "src/bogo_shim/config.json"
+
+
+def main():
+    if not os.path.isdir(BORING_PATH):
+        # check out our fork of boring ssl
+        run_cmd("git clone --depth 1 --branch %s %s %s" %
+                (BORING_BRANCH, BORING_REPO, BORING_PATH))
+
+    # make doubly sure we're on the correct branch
+    run_cmd("git -C %s checkout %s" % (BORING_PATH, BORING_BRANCH))
+
+    run_cmd("go test -pipe -num-workers %d -shim-path %s -shim-config %s" %
+            (get_concurrency(), os.path.abspath(SHIM_PATH), os.path.abspath(SHIM_CONFIG)), BOGO_PATH)
+
+
+if __name__ == '__main__':
+    main()
diff --git a/src/editors/vscode/scripts/common.py b/src/editors/vscode/scripts/common.py
new file mode 100644
index 000000000..1fe13663a
--- /dev/null
+++ b/src/editors/vscode/scripts/common.py
@@ -0,0 +1,57 @@
+import os
+import sys
+import subprocess
+import re
+
+
+class BuildError(Exception):
+    pass
+
+
+_MAKEFILE = "Makefile"
+
+
+def get_concurrency():
+    def_concurrency = 2
+    max_concurrency = 16
+
+    try:
+        import multiprocessing
+        return min(max_concurrency, multiprocessing.cpu_count())
+    except ImportError:
+        return def_concurrency
+
+
+def run_cmd(cmd, workingdir=None):
+    if isinstance(cmd, str):
+        print('> running: ' + cmd)
+        shell = True
+    else:
+        print('> running: ' + ' '.join(cmd))
+        shell = False
+    sys.stdout.flush()
+
+    try:
+        subprocess.run(cmd, shell=shell, check=True, cwd=workingdir)
+    except subprocess.CalledProcessError:
+        raise BuildError('External command failed, aborting...')
+
+
+def _find_regex_in_makefile(regex):
+    if not os.path.exists(_MAKEFILE):
+        raise BuildError('No Makefile found. Maybe run ./configure.py?')
+
+    with open(_MAKEFILE, 'r', encoding="utf-8") as f:
+        return re.search(regex, f.read())
+
+
+def get_test_binary_name():
+    match = _find_regex_in_makefile(r'TEST\s*=\s*([^\n]+)\n')
+    if not match:
+        raise BuildError('Test binary name not found in Makefile')
+    test_file = os.path.split(match.group(1))[1]
+    if not test_file:
+        raise BuildError(
+            'Cannot make sense of test binary name: ' + match.group(0))
+
+    return test_file
diff --git a/src/editors/vscode/settings.json b/src/editors/vscode/settings.json
new file mode 100644
index 000000000..bc0eac0de
--- /dev/null
+++ b/src/editors/vscode/settings.json
@@ -0,0 +1,17 @@
+{
+    "[cpp]": {
+        "editor.defaultFormatter": "chiehyu.vscode-astyle"
+    },
+    "astyle.astylerc": "${workspaceRoot}/src/configs/astyle.rc",
+    "astyle.cmd_options": [
+        "--suffix=none"
+    ],
+    "C_Cpp.default.cppStandard": "c++17",
+    "C_Cpp.default.cStandard": "c17",
+    "python.linting.pylintEnabled": true,
+    "python.linting.enabled": true,
+    "python.linting.pylintArgs": [
+        "--rcfile=src/configs/pylint.rc"
+    ],
+    "python.linting.lintOnSave": true
+}
diff --git a/src/editors/vscode/tasks.json b/src/editors/vscode/tasks.json
new file mode 100644
index 000000000..2c4508abc
--- /dev/null
+++ b/src/editors/vscode/tasks.json
@@ -0,0 +1,190 @@
+{
+    "version": "2.0.0",
+    "tasks": [
+        {
+            "label": "Configure (gcc)",
+            "detail": "Default ./configure.py invocation. Run your own if you want.",
+            "group": "build",
+            "type": "shell",
+            "command": "python3 ./configure.py --cc gcc --compiler-cache=ccache --without-documentation --debug-mode --build-targets=\"static,tests,bogo_shim\"",
+            "presentation": {
+                "reveal": "always",
+                "panel": "shared",
+                "close": true
+            }
+        },
+        {
+            "label": "Build All",
+            "group": "build",
+            "type": "shell",
+            "linux": {
+                "command": "make -j $(nproc)"
+            },
+            "osx": {
+                "command": "make -j $(sysctl -n hw.logicalcpu)"
+            },
+            "presentation": {
+                "reveal": "always",
+                "panel": "shared",
+                "close": true
+            },
+            "problemMatcher": "$gcc"
+        },
+        {
+            "label": "Build Unittests",
+            "group": "build",
+            "type": "shell",
+            "linux": {
+                "command": "make -j $(nproc) tests"
+            },
+            "osx": {
+                "command": "make -j $(sysctl -n hw.logicalcpu) tests"
+            },
+            "presentation": {
+                "reveal": "always",
+                "panel": "shared",
+                "close": false
+            },
+            "problemMatcher": "$gcc"
+        },
+        {
+            "label": "Build BoGo Shim",
+            "group": "build",
+            "type": "shell",
+            "linux": {
+                "command": "make -j $(nproc) bogo_shim"
+            },
+            "osx": {
+                "command": "make -j $(sysctl -n hw.logicalcpu) bogo_shim"
+            },
+            "presentation": {
+                "reveal": "always",
+                "panel": "shared",
+                "close": true
+            },
+            "problemMatcher": "$gcc"
+        },
+        {
+            "label": "Run Unittests",
+            "detail": "run all unittests",
+            "group": "test",
+            "type": "shell",
+            "command": "python3 ${workspaceFolder}/src/editors/vscode/scripts/test.py",
+            "dependsOn": "Build Unittests",
+            "presentation": {
+                "reveal": "always",
+                "panel": "shared",
+                "close": false
+            },
+            "problemMatcher": [
+                {
+                    "owner": "cpp",
+                    "pattern": {
+                        "regexp": "^Failure \\d+: (.+Internal error: False assertion .*) @(.*):(.*)$",
+                        "message": 1,
+                        "file": 2,
+                        "line": 3,
+                        "column": 0,
+                        "endColumn": 0
+                    }
+                },
+                {
+                    "owner": "cpp",
+                    "pattern": {
+                        "regexp": "Failure \\d+: (.*) \\(at ([^:]+):(\\d+)\\)",
+                        "message": 1,
+                        "file": 2,
+                        "line": 3,
+                        "column": 0,
+                        "endColumn": 0
+                    }
+                },
+                {
+                    "owner": "cpp",
+                    "pattern": {
+                        "regexp": "Failure \\d+: (.*)",
+                        "message": 1,
+                        "file": 0,
+                        "line": 0,
+                        "column": 0,
+                        "endColumn": 0
+                    }
+                }
+            ]
+        },
+        {
+            "label": "Run Current Unittest File",
+            "detail": "run the unittest file that is currently in focus",
+            "group": "test",
+            "type": "shell",
+            "command": "python3 ${workspaceFolder}/src/editors/vscode/scripts/test.py ${relativeFile}",
+            "dependsOn": "Build Unittests",
+            "presentation": {
+                "reveal": "always",
+                "panel": "shared",
+                "close": false
+            },
+            "problemMatcher": [
+                {
+                    "owner": "cpp",
+                    "pattern": {
+                        "regexp": "^Failure \\d+: (.+Internal error: False assertion .*) @(.*):(.*)$",
+                        "message": 1,
+                        "file": 2,
+                        "line": 3,
+                        "column": 0,
+                        "endColumn": 0
+                    }
+                },
+                {
+                    "owner": "cpp",
+                    "pattern": {
+                        "regexp": "Failure \\d+: (.*) \\(at ([^:]+):(\\d+)\\)",
+                        "message": 1,
+                        "file": 2,
+                        "line": 3,
+                        "column": 0,
+                        "endColumn": 0
+                    }
+                },
+                {
+                    "owner": "cpp",
+                    "pattern": {
+                        "regexp": "Failure \\d+: (.*)",
+                        "message": 1,
+                        "file": 0,
+                        "line": 0,
+                        "column": 0,
+                        "endColumn": 0
+                    }
+                }
+            ]
+        },
+        {
+            "label": "Run BoGo Tests",
+            "group": "test",
+            "type": "shell",
+            "command": "python3 ${workspaceFolder}/src/editors/vscode/scripts/bogo.py",
+            "dependsOn": "Build BoGo Shim",
+            "presentation": {
+                "reveal": "always",
+                "panel": "shared",
+                "close": false
+            },
+            "problemMatcher": {
+                "owner": "cpp",
+                "pattern": [
+                    {
+                        "regexp": "^FAILED \\(([^\\)]+)\\)$",
+                        "kind": "file",
+                        "file": 1
+                    },
+                    {
+                        "regexp": "^(.*)stdout:$",
+                        "message": 1
+                    }
+                ]
+            }
+        }
+    ]
+}
diff --git a/src/scripts/ci_build.py b/src/scripts/ci_build.py
index 0e5d1a3cf..850799922 100755
--- a/src/scripts/ci_build.py
+++ b/src/scripts/ci_build.py
@@ -518,7 +518,10 @@ def main(args=None):
             'src/scripts/test_cli.py',
             'src/scripts/python_unittests.py',
             'src/scripts/python_unittests_unix.py',
-            'src/editors/sublime/build.py']
+            'src/editors/sublime/build.py',
+            'src/editors/vscode/scripts/bogo.py',
+            'src/editors/vscode/scripts/common.py',
+            'src/editors/vscode/scripts/test.py']
 
         full_paths = [os.path.join(root_dir, s) for s in py_scripts]
         cmds.append([py_interp, '-m', 'pylint'] + pylint_flags + full_paths)


Test patch:
diff --git a/src/editors/vscode/scripts/test.py b/src/editors/vscode/scripts/test.py
new file mode 100644
index 000000000..c74b12a2e
--- /dev/null
+++ b/src/editors/vscode/scripts/test.py
@@ -0,0 +1,45 @@
+#!/usr/bin/env python3
+
+import os
+import re
+import sys
+
+import common
+
+
+TESTS_DIR = "src/tests"
+
+
+def get_test_names_from(test_file):
+    if not os.path.dirname(test_file) == TESTS_DIR:
+        raise common.BuildError(
+            'Given file path is not a Botan unit test: ' + test_file)
+
+    with open(test_file, 'r', encoding='utf-8') as f:
+        find_test_registration = \
+            re.compile(
+                r'BOTAN_REGISTER_TEST(_FN)?\s*\(\s*\"(.+)\",\s*\"(.+)\",[^)]+\)')
+
+        matches = find_test_registration.findall(f.read())
+        tests = [match[-1] for match in matches]
+
+    if not tests:
+        raise common.BuildError(
+            'Failed to find a BOTAN_REGISTER_TEST in the given test file: ' + test_file)
+
+    return tests
+
+
+def main():
+    test_binary = os.path.join('.', common.get_test_binary_name())
+
+    if len(sys.argv) == 2:
+        test_src_file = sys.argv[1]
+        test_names = get_test_names_from(test_src_file)
+        common.run_cmd("%s %s" % (test_binary, ' '.join(test_names)))
+    else:
+        common.run_cmd(test_binary)
+
+
+if __name__ == '__main__':
+    main()