aboutsummaryrefslogtreecommitdiff
path: root/pw_ide/py/pw_ide/settings.py
diff options
context:
space:
mode:
Diffstat (limited to 'pw_ide/py/pw_ide/settings.py')
-rw-r--r--pw_ide/py/pw_ide/settings.py323
1 files changed, 323 insertions, 0 deletions
diff --git a/pw_ide/py/pw_ide/settings.py b/pw_ide/py/pw_ide/settings.py
new file mode 100644
index 000000000..549bf8c8f
--- /dev/null
+++ b/pw_ide/py/pw_ide/settings.py
@@ -0,0 +1,323 @@
+# Copyright 2022 The Pigweed Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+"""pw_ide settings."""
+
+import enum
+from inspect import cleandoc
+import glob
+import os
+from pathlib import Path
+from typing import Any, cast, Dict, List, Literal, Optional, Union
+import yaml
+
+from pw_cli.yaml_config_loader_mixin import YamlConfigLoaderMixin
+
+PW_IDE_DIR_NAME = '.pw_ide'
+PW_IDE_DEFAULT_DIR = (
+ Path(os.path.expandvars('$PW_PROJECT_ROOT')) / PW_IDE_DIR_NAME
+)
+
+PW_PIGWEED_CIPD_INSTALL_DIR = Path(
+ os.path.expandvars('$PW_PIGWEED_CIPD_INSTALL_DIR')
+)
+
+PW_ARM_CIPD_INSTALL_DIR = Path(os.path.expandvars('$PW_ARM_CIPD_INSTALL_DIR'))
+
+_DEFAULT_BUILD_DIR_NAME = 'out'
+_DEFAULT_BUILD_DIR = (
+ Path(os.path.expandvars('$PW_PROJECT_ROOT')) / _DEFAULT_BUILD_DIR_NAME
+)
+
+_DEFAULT_COMPDB_PATHS = [_DEFAULT_BUILD_DIR]
+_DEFAULT_TARGET_INFERENCE = '?'
+
+SupportedEditorName = Literal['vscode']
+
+
+class SupportedEditor(enum.Enum):
+ VSCODE = 'vscode'
+
+
+_DEFAULT_SUPPORTED_EDITORS: Dict[SupportedEditorName, bool] = {
+ 'vscode': True,
+}
+
+_DEFAULT_CONFIG: Dict[str, Any] = {
+ 'clangd_additional_query_drivers': [],
+ 'build_dir': _DEFAULT_BUILD_DIR,
+ 'compdb_paths': _DEFAULT_BUILD_DIR_NAME,
+ 'default_target': None,
+ 'editors': _DEFAULT_SUPPORTED_EDITORS,
+ 'setup': ['pw --no-banner ide cpp --gn --set-default --no-override'],
+ 'targets': [],
+ 'target_inference': _DEFAULT_TARGET_INFERENCE,
+ 'working_dir': PW_IDE_DEFAULT_DIR,
+}
+
+_DEFAULT_PROJECT_FILE = Path('$PW_PROJECT_ROOT/.pw_ide.yaml')
+_DEFAULT_PROJECT_USER_FILE = Path('$PW_PROJECT_ROOT/.pw_ide.user.yaml')
+_DEFAULT_USER_FILE = Path('$HOME/.pw_ide.yaml')
+
+
+class PigweedIdeSettings(YamlConfigLoaderMixin):
+ """Pigweed IDE features settings storage class."""
+
+ def __init__(
+ self,
+ project_file: Union[Path, bool] = _DEFAULT_PROJECT_FILE,
+ project_user_file: Union[Path, bool] = _DEFAULT_PROJECT_USER_FILE,
+ user_file: Union[Path, bool] = _DEFAULT_USER_FILE,
+ default_config: Optional[Dict[str, Any]] = None,
+ ) -> None:
+ self.config_init(
+ config_section_title='pw_ide',
+ project_file=project_file,
+ project_user_file=project_user_file,
+ user_file=user_file,
+ default_config=_DEFAULT_CONFIG
+ if default_config is None
+ else default_config,
+ environment_var='PW_IDE_CONFIG_FILE',
+ )
+
+ @property
+ def working_dir(self) -> Path:
+ """Path to the ``pw_ide`` working directory.
+
+ The working directory holds C++ compilation databases and caches, and
+ other supporting files. This should not be a directory that's regularly
+ deleted or manipulated by other processes (e.g. the GN ``out``
+ directory) nor should it be committed to source control.
+ """
+ return Path(self._config.get('working_dir', PW_IDE_DEFAULT_DIR))
+
+ @property
+ def build_dir(self) -> Path:
+ """The build system's root output directory.
+
+ We will use this as the output directory when automatically running
+ build system commands, and will use it to resolve target names using
+ target name inference when processing compilation databases. This can
+ be the same build directory used for general-purpose builds, but it
+ does not have to be.
+ """
+ return Path(self._config.get('build_dir', _DEFAULT_BUILD_DIR))
+
+ @property
+ def compdb_paths(self) -> str:
+ """A path glob to search for compilation databases.
+
+ These paths can be to files or to directories. Paths that are
+ directories will be appended with the default file name for
+ ``clangd`` compilation databases, ``compile_commands.json``.
+ """
+ return self._config.get('compdb_paths', _DEFAULT_BUILD_DIR_NAME)
+
+ @property
+ def compdb_paths_expanded(self) -> List[Path]:
+ return [Path(node) for node in glob.iglob(self.compdb_paths)]
+
+ @property
+ def targets(self) -> List[str]:
+ """The list of targets that should be enabled for code analysis.
+
+ In this case, "target" is analogous to a GN target, i.e., a particular
+ build configuration. By default, all available targets are enabled. By
+ adding targets to this list, you can constrain the targets that are
+ enabled for code analysis to a subset of those that are available, which
+ may be useful if your project has many similar targets that are
+ redundant from a code analysis perspective.
+
+ Target names need to match the name of the directory that holds the
+ build system artifacts for the target. For example, GN outputs build
+ artifacts for the ``pw_strict_host_clang_debug`` target in a directory
+ with that name in its output directory. So that becomes the canonical
+ name for the target.
+ """
+ return self._config.get('targets', list())
+
+ @property
+ def target_inference(self) -> str:
+ """A glob-like string for extracting a target name from an output path.
+
+ Build systems and projects have varying ways of organizing their build
+ directory structure. For a given compilation unit, we need to know how
+ to extract the build's target name from the build artifact path. A
+ simple example:
+
+ .. code-block:: none
+
+ clang++ hello.cc -o host/obj/hello.cc.o
+
+ The top-level directory ``host`` is the target name we want. The same
+ compilation unit might be used with another build target:
+
+ .. code-block:: none
+
+ gcc-arm-none-eabi hello.cc -o arm_dev_board/obj/hello.cc.o
+
+ In this case, this compile command is associated with the
+ ``arm_dev_board`` target.
+
+ When importing and processing a compilation database, we assume by
+ default that for each compile command, the corresponding target name is
+ the name of the top level directory within the build directory root
+ that contains the build artifact. This is the default behavior for most
+ build systems. However, if your project is structured differently, you
+ can provide a glob-like string that indicates how to extract the target
+ name from build artifact path.
+
+ A ``*`` indicates any directory, and ``?`` indicates the directory that
+ has the name of the target. The path is resolved from the build
+ directory root, and anything deeper than the target directory is
+ ignored. For example, a glob indicating that the directory two levels
+ down from the build directory root has the target name would be
+ expressed with ``*/*/?``.
+ """
+ return self._config.get('target_inference', _DEFAULT_TARGET_INFERENCE)
+
+ @property
+ def default_target(self) -> Optional[str]:
+ """The default target to use when calling ``--set-default``.
+
+ This target will be selected when ``pw ide cpp --set-default`` is
+ called. You can define an explicit default target here. If that command
+ is invoked without a default target definition, ``pw_ide`` will try to
+ infer the best choice of default target. Currently, it selects the
+ target with the broadest compilation unit coverage.
+ """
+ return self._config.get('default_target', None)
+
+ @property
+ def setup(self) -> List[str]:
+ """A sequence of commands to automate IDE features setup.
+
+ ``pw ide setup`` should do everything necessary to get the project from
+ a fresh checkout to a working default IDE experience. This defines the
+ list of commands that makes that happen, which will be executed
+ sequentially in subprocesses. These commands should be idempotent, so
+ that the user can run them at any time to update their IDE features
+ configuration without the risk of putting those features in a bad or
+ unexpected state.
+ """
+ return self._config.get('setup', list())
+
+ @property
+ def clangd_additional_query_drivers(self) -> List[str]:
+ """Additional query driver paths that clangd should use.
+
+ By default, ``pw_ide`` supplies driver paths for the toolchains included
+ in Pigweed. If you are using toolchains that are not supplied by
+ Pigweed, you should include path globs to your toolchains here. These
+ paths will be given higher priority than the Pigweed toolchain paths.
+ """
+ return self._config.get('clangd_additional_query_drivers', list())
+
+ def clangd_query_drivers(self) -> List[str]:
+ return [
+ *[str(Path(p)) for p in self.clangd_additional_query_drivers],
+ str(PW_PIGWEED_CIPD_INSTALL_DIR / 'bin' / '*'),
+ str(PW_ARM_CIPD_INSTALL_DIR / 'bin' / '*'),
+ ]
+
+ def clangd_query_driver_str(self) -> str:
+ return ','.join(self.clangd_query_drivers())
+
+ @property
+ def editors(self) -> Dict[str, bool]:
+ """Enable or disable automated support for editors.
+
+ Automatic support for some editors is provided by ``pw_ide``, which is
+ accomplished through generating configuration files in your project
+ directory. All supported editors are enabled by default, but you can
+ disable editors by adding an ``'<editor>': false`` entry.
+ """
+ return self._config.get('editors', _DEFAULT_SUPPORTED_EDITORS)
+
+ def editor_enabled(self, editor: SupportedEditorName) -> bool:
+ """True if the provided editor is enabled in settings.
+
+ This module will integrate the project with all supported editors by
+ default. If the project or user want to disable particular editors,
+ they can do so in the appropriate settings file.
+ """
+ return self._config.get('editors', {}).get(editor, False)
+
+
+def _docstring_set_default(
+ obj: Any, default: Any, literal: bool = False
+) -> None:
+ """Add a default value annotation to a docstring.
+
+ Formatting isn't allowed in docstrings, so by default we can't inject
+ variables that we would like to appear in the documentation, like the
+ default value of a property. But we can use this function to add it
+ separately.
+ """
+ if obj.__doc__ is not None:
+ default = str(default)
+
+ if literal:
+ lines = default.splitlines()
+
+ if len(lines) == 0:
+ return
+ if len(lines) == 1:
+ default = f'Default: ``{lines[0]}``'
+ else:
+ default = 'Default:\n\n.. code-block::\n\n ' + '\n '.join(
+ lines
+ )
+
+ doc = cast(str, obj.__doc__)
+ obj.__doc__ = f'{cleandoc(doc)}\n\n{default}'
+
+
+_docstring_set_default(
+ PigweedIdeSettings.working_dir, PW_IDE_DIR_NAME, literal=True
+)
+_docstring_set_default(
+ PigweedIdeSettings.build_dir, _DEFAULT_BUILD_DIR_NAME, literal=True
+)
+_docstring_set_default(
+ PigweedIdeSettings.compdb_paths,
+ _DEFAULT_CONFIG['compdb_paths'],
+ literal=True,
+)
+_docstring_set_default(
+ PigweedIdeSettings.targets, _DEFAULT_CONFIG['targets'], literal=True
+)
+_docstring_set_default(
+ PigweedIdeSettings.default_target,
+ _DEFAULT_CONFIG['default_target'],
+ literal=True,
+)
+_docstring_set_default(
+ PigweedIdeSettings.target_inference,
+ _DEFAULT_CONFIG['target_inference'],
+ literal=True,
+)
+_docstring_set_default(
+ PigweedIdeSettings.setup, _DEFAULT_CONFIG['setup'], literal=True
+)
+_docstring_set_default(
+ PigweedIdeSettings.clangd_additional_query_drivers,
+ _DEFAULT_CONFIG['clangd_additional_query_drivers'],
+ literal=True,
+)
+_docstring_set_default(
+ PigweedIdeSettings.editors,
+ yaml.dump(_DEFAULT_SUPPORTED_EDITORS),
+ literal=True,
+)