├── .editorconfig ├── .github └── workflows │ ├── ci.yml │ └── release.yml ├── .gitignore ├── CONTRIBUTING.md ├── COPYING ├── README.md ├── cmake_file_api ├── __init__.py ├── cmake.py ├── errors.py ├── kinds │ ├── __init__.py │ ├── api.py │ ├── cache │ │ ├── __init__.py │ │ ├── api.py │ │ └── v2.py │ ├── cmakeFiles │ │ ├── __init__.py │ │ ├── api.py │ │ └── v1.py │ ├── codemodel │ │ ├── __init__.py │ │ ├── api.py │ │ ├── target │ │ │ ├── __init__.py │ │ │ ├── api.py │ │ │ └── v2.py │ │ └── v2.py │ ├── common.py │ ├── configureLog │ │ ├── __init__.py │ │ ├── api.py │ │ ├── target │ │ │ ├── __init__.py │ │ │ ├── api.py │ │ │ └── v2.py │ │ └── v1.py │ ├── kind.py │ └── toolchains │ │ ├── __init__.py │ │ ├── api.py │ │ └── v1.py ├── py.typed └── reply │ ├── __init__.py │ ├── api.py │ ├── index │ ├── __init__.py │ ├── api.py │ ├── file │ │ ├── __init__.py │ │ ├── api.py │ │ └── v1.py │ └── v1.py │ └── v1 │ ├── __init__.py │ └── api.py ├── example ├── .gitignore ├── README.md ├── project │ ├── CMakeLists.txt │ ├── base │ │ ├── CMakeLists.txt │ │ ├── base.cpp │ │ └── include │ │ │ └── base.hpp │ ├── dep │ │ ├── CMakeLists.txt │ │ ├── dep.cpp │ │ └── include │ │ │ └── dep.hpp │ └── exe │ │ ├── CMakeLists.txt │ │ └── exe.cpp └── script.py ├── mypy.ini ├── requirements-dev.txt ├── setup.cfg ├── setup.py └── tests ├── __init__.py └── test_regression.py /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | tab_width = 4 5 | indent_size = 4 6 | indent_style = space 7 | insert_final_newline = true 8 | trim_trailing_whitespace = true 9 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Run all tests 2 | on: 3 | pull_request: 4 | push: 5 | release: 6 | types: published 7 | schedule: 8 | - cron: 0 0 1 * * # Run once every month 9 | jobs: 10 | run-pytest: 11 | strategy: 12 | fail-fast: false 13 | matrix: 14 | py: [ '3.9', '3.10', '3.11', '3.12' ] 15 | os: [ 'ubuntu-latest', 'windows-latest', 'macos-latest' ] 16 | runs-on: ${{ matrix.os }} 17 | steps: 18 | - uses: actions/checkout@v4 19 | - uses: actions/setup-python@v5 20 | with: 21 | python-version: ${{ matrix.py }} 22 | - name: "Install requirements" 23 | run: | 24 | python -m pip install -r requirements-dev.txt 25 | - name: "Run pytest" 26 | run: | 27 | pytest 28 | 29 | check-typing: 30 | runs-on: ubuntu-latest 31 | steps: 32 | - uses: actions/checkout@v4 33 | - uses: actions/setup-python@v5 34 | with: 35 | python-version: "3.9" 36 | - run: pip install mypy 37 | - run: mypy --install-types --non-interactive 38 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Publish Release 3 | on: 4 | push: 5 | tags: 6 | - "v*" 7 | 8 | jobs: 9 | publish: 10 | name: Build and Publish 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v4 14 | - uses: actions/setup-python@v5 15 | with: 16 | python-version: 3.x 17 | - run: | 18 | python -m pip install setuptools wheel 19 | python setup.py sdist bdist_wheel 20 | - uses: pypa/gh-action-pypi-publish@release/v1 21 | with: 22 | password: ${{ secrets.PYPI_API_TOKEN }} 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /dist/ 2 | /build/ 3 | /.eggs/ 4 | /venv/ 5 | /.venv 6 | *.egg-info 7 | *.py[co] 8 | .idea 9 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | When contributing to this repository, please first discuss the change you wish to make via issue, 4 | email, or any other method with the owners of this repository before making a change. 5 | 6 | Please note we have a code of conduct, please follow it in all your interactions with the project. 7 | 8 | ## Pull Request Process 9 | 10 | 1. Ensure any install or build dependencies are removed before the end of the layer when doing a 11 | build. 12 | 2. Update the README.md with details of changes to the interface, this includes new environment 13 | variables, exposed ports, useful file locations and container parameters. 14 | 3. Increase the version numbers in any examples files and the README.md to the new version that this 15 | Pull Request would represent. The versioning scheme we use is [SemVer](http://semver.org/). 16 | 4. You may merge the Pull Request in once you have the sign-off of two other developers, or if you 17 | do not have permission to do that, you may request the second reviewer to merge it for you. 18 | 19 | ## Code of Conduct 20 | 21 | ### Our Pledge 22 | 23 | In the interest of fostering an open and welcoming environment, we as 24 | contributors and maintainers pledge to making participation in our project and 25 | our community a harassment-free experience for everyone, regardless of age, body 26 | size, disability, ethnicity, gender identity and expression, level of experience, 27 | nationality, personal appearance, race, religion, or sexual identity and 28 | orientation. 29 | 30 | ### Our Standards 31 | 32 | Examples of behavior that contributes to creating a positive environment 33 | include: 34 | 35 | * Using welcoming and inclusive language 36 | * Being respectful of differing viewpoints and experiences 37 | * Gracefully accepting constructive criticism 38 | * Focusing on what is best for the community 39 | * Showing empathy towards other community members 40 | 41 | Examples of unacceptable behavior by participants include: 42 | 43 | * The use of sexualized language or imagery and unwelcome sexual attention or 44 | advances 45 | * Trolling, insulting/derogatory comments, and personal or political attacks 46 | * Public or private harassment 47 | * Publishing others' private information, such as a physical or electronic 48 | address, without explicit permission 49 | * Other conduct which could reasonably be considered inappropriate in a 50 | professional setting 51 | 52 | ### Our Responsibilities 53 | 54 | Project maintainers are responsible for clarifying the standards of acceptable 55 | behavior and are expected to take appropriate and fair corrective action in 56 | response to any instances of unacceptable behavior. 57 | 58 | Project maintainers have the right and responsibility to remove, edit, or 59 | reject comments, commits, code, wiki edits, issues, and other contributions 60 | that are not aligned to this Code of Conduct, or to ban temporarily or 61 | permanently any contributor for other behaviors that they deem inappropriate, 62 | threatening, offensive, or harmful. 63 | 64 | ### Scope 65 | 66 | This Code of Conduct applies both within project spaces and in public spaces 67 | when an individual is representing the project or its community. Examples of 68 | representing a project or community include using an official project e-mail 69 | address, posting via an official social media account, or acting as an appointed 70 | representative at an online or offline event. Representation of a project may be 71 | further defined and clarified by project maintainers. 72 | 73 | ### Enforcement 74 | 75 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 76 | reported by contacting the project team at [Maarten](anonymous.maarten@gmail.com). All 77 | complaints will be reviewed and investigated and will result in a response that 78 | is deemed necessary and appropriate to the circumstances. The project team is 79 | obligated to maintain confidentiality with regard to the reporter of an incident. 80 | Further details of specific enforcement policies may be posted separately. 81 | 82 | Project maintainers who do not follow or enforce the Code of Conduct in good 83 | faith may face temporary or permanent repercussions as determined by other 84 | members of the project's leadership. 85 | 86 | ### Attribution 87 | 88 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 89 | available at [http://contributor-covenant.org/version/1/4][version] 90 | 91 | [homepage]: http://contributor-covenant.org 92 | [version]: http://contributor-covenant.org/version/1/4/ 93 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020 Maarten 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cmake-file-api 2 | 3 | This library reads and interprets the files generated by [cmake's file-based API](https://cmake.org/cmake/help/git-stage/manual/cmake-file-api.7.html). 4 | 5 | ## Requirements 6 | 7 | - python 3 8 | - CMake 3.14+ 9 | 10 | ## Example 11 | 12 | An example can be found in the [example](example/script.py) subdirectory. 13 | 14 | ## License 15 | 16 | This project is licensed using the MIT license. 17 | -------------------------------------------------------------------------------- /cmake_file_api/__init__.py: -------------------------------------------------------------------------------- 1 | from .cmake import CMakeProject 2 | from .errors import CMakeException 3 | from .kinds.kind import ObjectKind 4 | 5 | __all__ = ["CMakeProject", "CMakeException", "ObjectKind"] 6 | -------------------------------------------------------------------------------- /cmake_file_api/cmake.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | import subprocess 3 | from typing import Optional, Union 4 | 5 | from .reply.api import REPLY_API 6 | from .reply.v1.api import CMakeFileApiV1 7 | 8 | PathLike = Union[Path, str] 9 | 10 | 11 | class CMakeProject: 12 | __slots__ = ("_source_path", "_build_path", "_api_version", "_cmake") 13 | 14 | def __init__(self, build_path: PathLike, source_path: Optional[PathLike]=None, api_version: Optional[int]=None, cmake: Optional[str]=None): 15 | if not build_path: 16 | raise ValueError("Need a build folder") 17 | if isinstance(build_path, str): 18 | build_path = Path(build_path).resolve() 19 | self._build_path = build_path 20 | 21 | cache = self.build_path / "CMakeCache.txt" 22 | 23 | if source_path is None and cache.exists(): 24 | source_path = self._cache_lookup("CMAKE_HOME_DIRECTORY", cache) 25 | if isinstance(source_path, str): 26 | source_path = Path(source_path).resolve() 27 | self._source_path = source_path.resolve() if source_path else None 28 | 29 | self._api_version = api_version if api_version is not None else self.most_recent_api_version() 30 | 31 | if cmake is None and cache.exists(): 32 | cmake = self._cache_lookup("CMAKE_COMMAND", cache) 33 | 34 | self._cmake = cmake or "cmake" 35 | 36 | @property 37 | def source_path(self) -> Optional[Path]: 38 | return self._source_path 39 | 40 | @property 41 | def build_path(self) -> Path: 42 | return self._build_path 43 | 44 | @staticmethod 45 | def most_recent_api_version() -> int: 46 | return max(list(REPLY_API.keys())) 47 | 48 | @staticmethod 49 | def _cache_lookup(name: str, cache: PathLike) -> str: 50 | # Cache entries are formatted like: 51 | # := 52 | with open(cache) as f: 53 | line = next( 54 | line 55 | for line in f.read().splitlines() 56 | if line.startswith(name + ":") 57 | ) 58 | _, value = line.split("=", 1) 59 | return value 60 | 61 | def configure(self, args: Optional[list[str]]=None, quiet: bool = False) -> None: 62 | if self._source_path is None: 63 | raise ValueError("Cannot configure with no source path") 64 | stdout = subprocess.DEVNULL if quiet else None 65 | args = [str(self._cmake), str(self._source_path)] + (args if args else []) 66 | subprocess.check_call(args, cwd=str(self._build_path), stdout=stdout) 67 | 68 | def reconfigure(self, quiet: bool = False) -> None: 69 | stdout = subprocess.DEVNULL if quiet else None 70 | args = [str(self._cmake)] 71 | if self._source_path: 72 | args.append(str(self._source_path)) 73 | else: 74 | args.append(".") 75 | subprocess.check_call(args, cwd=str(self._build_path), stdout=stdout) 76 | 77 | @property 78 | def cmake_file_api(self) -> CMakeFileApiV1: 79 | return REPLY_API[self._api_version](self._build_path) 80 | -------------------------------------------------------------------------------- /cmake_file_api/errors.py: -------------------------------------------------------------------------------- 1 | class CMakeException(Exception): 2 | pass 3 | -------------------------------------------------------------------------------- /cmake_file_api/kinds/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madebr/python-cmake-file-api/fc02aa8522b7f793643412124ee938f8d0dc7ef9/cmake_file_api/kinds/__init__.py -------------------------------------------------------------------------------- /cmake_file_api/kinds/api.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | from typing import Protocol 3 | 4 | from .kind import ObjectKind 5 | from .cache.api import CACHE_API 6 | from .cmakeFiles.api import CMAKEFILES_API 7 | from .codemodel.api import CODEMODEL_API 8 | from .configureLog.api import CONFIGURELOG_API 9 | from .toolchains.api import TOOLCHAINS_API 10 | 11 | 12 | class CMakeApiType(Protocol): 13 | KIND: ObjectKind 14 | 15 | @classmethod 16 | def from_path(cls, path: Path, reply_path: Path) -> "CMakeApiType": 17 | ... 18 | 19 | OBJECT_KINDS_API: dict[ObjectKind, dict[int, CMakeApiType]] = { 20 | ObjectKind.CACHE: CACHE_API, 21 | ObjectKind.CMAKEFILES: CMAKEFILES_API, 22 | ObjectKind.CONFIGURELOG: CONFIGURELOG_API, 23 | ObjectKind.CODEMODEL: CODEMODEL_API, 24 | ObjectKind.TOOLCHAINS: TOOLCHAINS_API, 25 | } 26 | -------------------------------------------------------------------------------- /cmake_file_api/kinds/cache/__init__.py: -------------------------------------------------------------------------------- 1 | from .api import CACHE_API 2 | 3 | __all__ = ["CACHE_API"] 4 | -------------------------------------------------------------------------------- /cmake_file_api/kinds/cache/api.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | import typing 3 | 4 | from .v2 import CacheV2 5 | 6 | if typing.TYPE_CHECKING: 7 | from ..api import CMakeApiType 8 | 9 | CACHE_API: dict[int, CMakeApiType] = { 10 | 2: CacheV2, 11 | } 12 | -------------------------------------------------------------------------------- /cmake_file_api/kinds/cache/v2.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | import json 3 | from pathlib import Path 4 | from typing import Any 5 | 6 | from cmake_file_api.kinds.common import VersionMajorMinor 7 | from cmake_file_api.kinds.kind import ObjectKind 8 | 9 | 10 | class CacheEntryType(Enum): 11 | TYPE_BOOL = "BOOL" 12 | TYPE_FILEPATH = "FILEPATH" 13 | TYPE_PATH = "PATH" 14 | TYPE_STRING = "STRING" 15 | TYPE_INTERNAL = "INTERNAL" 16 | TYPE_STATIC = "STATIC" 17 | TYPE_UNINITIALIZED = "UNINITIALIZED" 18 | 19 | 20 | class CacheEntryProperty: 21 | __slots__ = ("name", "value") 22 | 23 | def __init__(self, name: str, value: str): 24 | self.name = name 25 | self.value = value 26 | 27 | @classmethod 28 | def from_dict(cls, dikt: dict[str, Any]) -> "CacheEntryProperty": 29 | name = dikt["name"] 30 | value = dikt["value"] 31 | return cls(name, value) 32 | 33 | def __repr__(self) -> str: 34 | return "{}(name='{}', value='{}')".format( 35 | type(self).__name__, 36 | self.name, 37 | self.value, 38 | ) 39 | 40 | 41 | class CacheEntry: 42 | __slots__ = ("name", "value", "type", "properties") 43 | 44 | def __init__(self, name: str, value: str, type: CacheEntryType, properties: list[CacheEntryProperty]): 45 | self.name = name 46 | self.value = value 47 | self.type = type 48 | self.properties = properties 49 | 50 | @classmethod 51 | def from_dict(cls, dikt: dict[str, Any]) -> "CacheEntry": 52 | name = dikt["name"] 53 | value = dikt["value"] 54 | type = CacheEntryType(dikt["type"]) 55 | properties = list(CacheEntryProperty.from_dict(cep) for cep in dikt["properties"]) 56 | return cls(name, value, type, properties) 57 | 58 | def __repr__(self) -> str: 59 | return "{}(name='{}', value='{}', type={}, properties={})".format( 60 | type(self).__name__, 61 | self.name, 62 | self.value, 63 | self.type.name, 64 | repr(self.properties), 65 | ) 66 | 67 | 68 | class CacheV2: 69 | KIND = ObjectKind.CACHE 70 | 71 | __slots__ = ("version", "entries") 72 | 73 | def __init__(self, version: VersionMajorMinor, entries: list[CacheEntry]): 74 | self.version = version 75 | self.entries = entries 76 | 77 | @classmethod 78 | def from_dict(cls, dikt: dict[str, Any], reply_path: Path) -> "CacheV2": 79 | if dikt["kind"] != cls.KIND.value: 80 | raise ValueError 81 | version = VersionMajorMinor.from_dict(dikt["version"]) 82 | entries = list(CacheEntry.from_dict(ce) for ce in dikt["entries"]) 83 | return cls(version, entries) 84 | 85 | @classmethod 86 | def from_path(cls, path: Path, reply_path: Path) -> "CacheV2": 87 | with path.open() as file: 88 | dikt = json.load(file) 89 | return cls.from_dict(dikt, reply_path) 90 | 91 | def __repr__(self) -> str: 92 | return "{}(version={}, entries={})".format( 93 | type(self).__name__, 94 | repr(self.version), 95 | repr(self.entries), 96 | ) 97 | -------------------------------------------------------------------------------- /cmake_file_api/kinds/cmakeFiles/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madebr/python-cmake-file-api/fc02aa8522b7f793643412124ee938f8d0dc7ef9/cmake_file_api/kinds/cmakeFiles/__init__.py -------------------------------------------------------------------------------- /cmake_file_api/kinds/cmakeFiles/api.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | import typing 3 | 4 | from .v1 import CMakeFilesV1 5 | 6 | if typing.TYPE_CHECKING: 7 | from ..api import CMakeApiType 8 | 9 | CMAKEFILES_API: dict[int, CMakeApiType] = { 10 | 1: CMakeFilesV1, 11 | } 12 | -------------------------------------------------------------------------------- /cmake_file_api/kinds/cmakeFiles/v1.py: -------------------------------------------------------------------------------- 1 | import json 2 | from pathlib import Path 3 | from typing import Any, Optional 4 | 5 | from cmake_file_api.kinds.common import CMakeSourceBuildPaths, VersionMajorMinor 6 | from cmake_file_api.kinds.kind import ObjectKind 7 | 8 | 9 | class CMakeFilesInput: 10 | __slots__ = ("path", "isGenerator", "isExternal", "isCMake") 11 | 12 | def __init__(self, path: Path, isGenerator: Optional[bool], isExternal: Optional[bool], isCMake: Optional[bool]): 13 | self.path = path 14 | self.isGenerator = isGenerator 15 | self.isExternal = isExternal 16 | self.isCMake = isCMake 17 | 18 | @classmethod 19 | def from_dict(cls, dikt: dict[str, Any]) -> "CMakeFilesInput": 20 | path = Path(dikt["path"]) 21 | isGenerator = dikt.get("isGenerator") 22 | isExternal = dikt.get("isExternal") 23 | isCMake = dikt.get("isExternal") 24 | return cls(path, isGenerator, isExternal, isCMake) 25 | 26 | def __repr__(self) -> str: 27 | return "{}(path='{}', generator={}, external={}, cmake={})".format( 28 | type(self).__name__, 29 | self.path, 30 | self.isGenerator, 31 | self.isExternal, 32 | self.isCMake, 33 | ) 34 | 35 | 36 | class CMakeFilesV1: 37 | KIND = ObjectKind.CMAKEFILES 38 | 39 | __slots__ = ("version", "paths", "inputs") 40 | 41 | def __init__(self, version: VersionMajorMinor, paths: CMakeSourceBuildPaths, inputs: list[CMakeFilesInput]): 42 | self.version = version 43 | self.paths = paths 44 | self.inputs = inputs 45 | 46 | @classmethod 47 | def from_dict(cls, dikt: dict[str, Any], reply_path: Path) -> "CMakeFilesV1": 48 | version = VersionMajorMinor.from_dict(dikt["version"]) 49 | paths = CMakeSourceBuildPaths.from_dict(dikt["paths"]) 50 | inputs = list(CMakeFilesInput.from_dict(cmi) for cmi in dikt["inputs"]) 51 | return cls(version, paths, inputs) 52 | 53 | @classmethod 54 | def from_path(cls, path: Path, reply_path: Path) -> "CMakeFilesV1": 55 | with path.open() as file: 56 | dikt = json.load(file) 57 | return cls.from_dict(dikt, reply_path) 58 | 59 | def __repr__(self) -> str: 60 | return "{}(version={}, paths={}, inputs={})".format( 61 | type(self).__name__, 62 | repr(self.version), 63 | self.paths, 64 | repr(self.inputs), 65 | ) 66 | -------------------------------------------------------------------------------- /cmake_file_api/kinds/codemodel/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madebr/python-cmake-file-api/fc02aa8522b7f793643412124ee938f8d0dc7ef9/cmake_file_api/kinds/codemodel/__init__.py -------------------------------------------------------------------------------- /cmake_file_api/kinds/codemodel/api.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | import typing 3 | 4 | from .v2 import CodemodelV2 5 | 6 | 7 | if typing.TYPE_CHECKING: 8 | from ..api import CMakeApiType 9 | 10 | CODEMODEL_API: dict[int, CMakeApiType] = { 11 | 2: CodemodelV2, 12 | } 13 | -------------------------------------------------------------------------------- /cmake_file_api/kinds/codemodel/target/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madebr/python-cmake-file-api/fc02aa8522b7f793643412124ee938f8d0dc7ef9/cmake_file_api/kinds/codemodel/target/__init__.py -------------------------------------------------------------------------------- /cmake_file_api/kinds/codemodel/target/api.py: -------------------------------------------------------------------------------- 1 | from .v2 import CodemodelTargetV2 2 | 3 | 4 | CODEMODELTARGET_API = { 5 | 2: CodemodelTargetV2, 6 | } 7 | -------------------------------------------------------------------------------- /cmake_file_api/kinds/codemodel/target/v2.py: -------------------------------------------------------------------------------- 1 | import enum 2 | import json 3 | from pathlib import Path 4 | from typing import Any, Optional 5 | 6 | from cmake_file_api.kinds.common import CMakeSourceBuildPaths 7 | 8 | 9 | class TargetType(enum.Enum): 10 | EXECUTABLE = "EXECUTABLE" 11 | STATIC = "STATIC_LIBRARY" 12 | SHARED = "SHARED_LIBRARY" 13 | MODULE = "MODULE_LIBRARY" 14 | OBJECT = "OBJECT_LIBRARY" 15 | INTERFACE_LIBRARY = "INTERFACE_LIBRARY" 16 | UTILITY = "UTILITY" 17 | 18 | 19 | class LinkFragmentRole(enum.Enum): 20 | LINK_FLAGS = "flags" 21 | LINK_LIBRARIES = "libraries" 22 | LIBRARY_PATHS = "libraryPath" 23 | MACOS_FRAMEWORK_PATH = "frameworkPath" 24 | 25 | 26 | class ArchiveFragmentRole(enum.Enum): 27 | ARCHIVER_FLAGS = "flags" 28 | 29 | 30 | class BacktraceNode: 31 | __slots__ = ("file", "line", "command", "parent") 32 | 33 | def __init__(self, file: Path, line: Optional[int], command: Optional[str]): 34 | self.file = file 35 | self.line = line 36 | self.command = command 37 | self.parent = None 38 | 39 | @classmethod 40 | def from_dict(cls, dikt: dict[str, Any], commands: list[str], files: list[Path]) -> "BacktraceNode": 41 | file = files[dikt["file"]] 42 | line = dikt.get("line") 43 | command = None 44 | if "command" in dikt: 45 | command = commands[dikt["command"]] 46 | return cls(file, line, command) 47 | 48 | def update_from_dict(self, dikt: dict[str, Any], nodes: list["BacktraceNode"]) -> None: 49 | if "parent" in dikt: 50 | self.parent = nodes[dikt["parent"]] 51 | 52 | def __repr__(self) -> str: 53 | return "{}(file='{}', line={}, command={}".format( 54 | type(self).__name__, 55 | self.file, 56 | self.line, 57 | self.command, 58 | ) 59 | 60 | 61 | class BacktraceGraph: 62 | __slots__ = ("nodes", ) 63 | 64 | def __init__(self, nodes: list[BacktraceNode]): 65 | self.nodes = nodes 66 | 67 | @classmethod 68 | def from_dict(cls, dikt: dict[str, Any]) -> "BacktraceGraph": 69 | commands = dikt["commands"] 70 | files = list(Path(f) for f in dikt["files"]) 71 | nodes = list(BacktraceNode.from_dict(btn, commands, files) for btn in dikt["nodes"]) 72 | for node, dikt_node in zip(nodes, dikt["nodes"]): 73 | node.update_from_dict(dikt_node, nodes) 74 | return cls(nodes) 75 | 76 | def __repr__(self) -> str: 77 | return "{}(nodes={})".format( 78 | type(self).__name__, 79 | self.nodes, 80 | ) 81 | 82 | 83 | class TargetDestination: 84 | __slots__ = ("path", "backtrace") 85 | 86 | def __init__(self, path: Path, backtrace: BacktraceNode): 87 | self.path = path 88 | self.backtrace = backtrace 89 | 90 | @classmethod 91 | def from_dict(cls, dikt: dict[str, Any], backtraceGraph: BacktraceGraph) -> "TargetDestination": 92 | path = Path(dikt["path"]) 93 | backtrace = backtraceGraph.nodes[dikt["backtrace"]] 94 | return cls(path, backtrace) 95 | 96 | def __repr__(self) -> str: 97 | return "{}(path='{}', backtrace={}".format( 98 | type(self).__name__, 99 | self.path, 100 | self.backtrace, 101 | ) 102 | 103 | 104 | class TargetInstall: 105 | __slots__ = ("prefix", "destinations") 106 | 107 | def __init__(self, prefix: Path, destinations: list[TargetDestination]): 108 | self.prefix = prefix 109 | self.destinations = destinations 110 | 111 | @classmethod 112 | def from_dict(cls, dikt: dict[str, Any], backtraceGraph: BacktraceGraph) -> "TargetInstall": 113 | prefix = Path(dikt["prefix"]["path"]) 114 | destinations = list(TargetDestination.from_dict(td, backtraceGraph) for td in dikt["destinations"]) 115 | return cls(prefix, destinations) 116 | 117 | def __repr__(self) -> str: 118 | return "{}(prefix='{}', destinations={}".format( 119 | type(self).__name__, 120 | self.prefix, 121 | self.destinations, 122 | ) 123 | 124 | 125 | class TargetLinkFragment: 126 | __slots__ = ("fragment", "role") 127 | 128 | def __init__(self, fragment: str, role: LinkFragmentRole): 129 | self.fragment = fragment 130 | self.role = role 131 | 132 | @classmethod 133 | def from_dict(cls, dikt: dict[str, Any]) -> "TargetLinkFragment": 134 | fragment = dikt["fragment"] 135 | role = LinkFragmentRole(dikt["role"]) 136 | return cls(fragment, role) 137 | 138 | def __repr__(self) -> str: 139 | return "{}(fragment='{}', role={})".format( 140 | type(self).__name__, 141 | self.fragment, 142 | self.role.name, 143 | ) 144 | 145 | 146 | class TargetLink: 147 | __slots__ = ("language", "commandFragments", "lto", "sysroot") 148 | 149 | def __init__(self, language: str, commandFragments: list[TargetLinkFragment], lto: Optional[bool], sysroot: Optional[Path]): 150 | self.language = language 151 | self.commandFragments = commandFragments 152 | self.lto = lto 153 | self.sysroot = sysroot 154 | 155 | @classmethod 156 | def from_dict(cls, dikt: dict[str, Any]) -> "TargetLink": 157 | language = dikt["language"] 158 | commandFragments = [] 159 | if "commandFragments" in dikt: 160 | commandFragments = list(TargetLinkFragment.from_dict(tlf) for tlf in dikt["commandFragments"]) 161 | lto = dikt.get("lto") 162 | sysroot = None 163 | if "sysroot" in dikt: 164 | sysroot = Path(dikt["sysroot"]["path"]) 165 | return cls(language, commandFragments, lto, sysroot) 166 | 167 | def __repr__(self) -> str: 168 | return "{}(language='{}', commandFragments={}, lto={}, sysroot={}".format( 169 | type(self).__name__, 170 | self.language, 171 | self.commandFragments, 172 | self.lto, 173 | f"'{self.sysroot}'" if self.sysroot else None, 174 | ) 175 | 176 | 177 | class TargetArchiveFragment: 178 | __slots__ = ("fragment", "role") 179 | 180 | def __init__(self, fragment: str, role: ArchiveFragmentRole): 181 | self.fragment = fragment 182 | self.role = role 183 | 184 | @classmethod 185 | def from_dict(cls, dikt: dict[str, Any]) -> "TargetArchiveFragment": 186 | fragment = dikt["fragment"] 187 | role = ArchiveFragmentRole(dikt["role"]) 188 | return cls(fragment, role) 189 | 190 | def __repr__(self) -> str: 191 | return "{}(fragment='{}', role={})".format( 192 | type(self).__name__, 193 | self.fragment, 194 | self.role.name, 195 | ) 196 | 197 | 198 | class TargetArchive: 199 | __slots__ = ("commandFragments", "lto") 200 | 201 | def __init__(self, commandFragments: list[TargetArchiveFragment], lto: Optional[bool]): 202 | self.commandFragments = commandFragments 203 | self.lto = lto 204 | 205 | @classmethod 206 | def from_dict(cls, dikt: dict[str, Any]) -> "TargetArchive": 207 | commandFragments = [] 208 | if "commandFragments" in dikt: 209 | commandFragments = list(TargetArchiveFragment.from_dict(tlf) for tlf in dikt["commandFragments"]) 210 | lto = dikt.get("lto") 211 | return cls(commandFragments, lto) 212 | 213 | def __repr__(self) -> str: 214 | return "{}(commandFragments={}, lto={})".format( 215 | type(self).__name__, 216 | self.commandFragments, 217 | self.lto, 218 | ) 219 | 220 | 221 | class TargetSourceGroup: 222 | __slots__ = ("name", "sources") 223 | 224 | def __init__(self, name: str, sources: list["TargetSource"]): 225 | self.name = name 226 | self.sources: list[TargetSource] = [] 227 | 228 | @classmethod 229 | def from_dict(cls, dikt: dict[str, Any], target_sources: list["TargetSource"]) -> "TargetSourceGroup": 230 | name = dikt["name"] 231 | sources = list(target_sources[tsi] for tsi in dikt["sourceIndexes"]) 232 | return cls(name, sources) 233 | 234 | def __repr__(self) -> str: 235 | return "{}(name='{}', sources={})".format( 236 | type(self).__name__, self.name, self.sources, 237 | ) 238 | 239 | 240 | class TargetCompileFragment: 241 | __slots__ = ("fragment", ) 242 | 243 | def __init__(self, fragment: str): 244 | self.fragment = fragment 245 | 246 | @classmethod 247 | def from_dict(cls, dikt: dict[str, Any]) -> "TargetCompileFragment": 248 | fragment = dikt["fragment"] 249 | return cls(fragment) 250 | 251 | def __repr__(self) -> str: 252 | return "{}(fragment={})".format( 253 | type(self).__name__, 254 | self.fragment, 255 | ) 256 | 257 | 258 | class TargetCompileGroupInclude: 259 | __slots__ = ("path", "isSystem", "backtrace") 260 | 261 | def __init__(self, path: Path, isSystem: Optional[bool], backtrace: Optional[BacktraceNode]): 262 | self.path = path 263 | self.isSystem = isSystem 264 | self.backtrace = backtrace 265 | 266 | @classmethod 267 | def from_dict(cls, dikt: dict[str, Any], backtraceGraph: BacktraceGraph) -> "TargetCompileGroupInclude": 268 | path = Path(dikt["path"]) 269 | isSystem = dikt.get("isSystem") 270 | backtrace = None 271 | if "backtrace" in dikt: 272 | backtrace = backtraceGraph.nodes[dikt["backtrace"]] 273 | return cls(path, isSystem, backtrace) 274 | 275 | def __repr__(self) -> str: 276 | return "{}(path={}, system={}, backtrace={})".format( 277 | type(self).__name__, 278 | f"'{self.path}'" if self.path else None, 279 | self.isSystem, 280 | self.backtrace, 281 | ) 282 | 283 | 284 | class TargetCompileGroupPCH: 285 | __slots__ = ("header", "backtrace") 286 | 287 | def __init__(self, header: Path, backtrace: Optional[BacktraceNode]): 288 | self.header = header 289 | self.backtrace = backtrace 290 | 291 | @classmethod 292 | def from_dict(cls, dikt: dict[str, Any], backtraceGraph: BacktraceGraph) -> "TargetCompileGroupPCH": 293 | header = Path(dikt["header"]) 294 | backtrace = None 295 | if "backtrace" in dikt: 296 | backtrace = backtraceGraph.nodes[dikt["backtrace"]] 297 | return cls(header, backtrace) 298 | 299 | def __repr__(self) -> str: 300 | return "{}(header='{}', backtrace={})".format( 301 | type(self).__name__, 302 | self.header, 303 | self.backtrace, 304 | ) 305 | 306 | 307 | class TargetCompileGroupDefine: 308 | __slots__ = ("define", "backtrace") 309 | 310 | def __init__(self, define: str, backtrace: Optional[BacktraceNode]): 311 | self.define = define 312 | self.backtrace = backtrace 313 | 314 | @classmethod 315 | def from_dict(cls, dikt: dict[str, Any], backtraceGraph: BacktraceGraph) -> "TargetCompileGroupDefine": 316 | define = dikt["define"] 317 | backtrace = None 318 | if "backtrace" in dikt: 319 | backtrace = backtraceGraph.nodes[dikt["backtrace"]] 320 | return cls(define, backtrace) 321 | 322 | def __repr__(self) -> str: 323 | return "{}(define='{}', backtrace={})".format( 324 | type(self).__name__, 325 | self.define, 326 | self.backtrace, 327 | ) 328 | 329 | 330 | class TargetCompileGroup: 331 | __slots__ = ("sources", "language", "compileCommandFragments", "includes", "precompileHeaders", "defines", "sysroot") 332 | 333 | def __init__(self, sources: list["TargetSource"], language: str, 334 | compileCommandFragments: list[TargetCompileFragment], includes: list[TargetCompileGroupInclude], 335 | precompileHeaders: list[TargetCompileGroupPCH], defines: list[TargetCompileGroupDefine], 336 | sysroot: Optional[Path]): 337 | self.sources = sources 338 | self.language = language 339 | self.compileCommandFragments = compileCommandFragments 340 | self.includes = includes 341 | self.precompileHeaders = precompileHeaders 342 | self.defines = defines 343 | self.sysroot = sysroot 344 | 345 | @classmethod 346 | def from_dict(cls, dikt: dict[str, Any], target_sources: list["TargetSource"], backtraceGraph: BacktraceGraph) -> "TargetCompileGroup": 347 | language = dikt["language"] 348 | compileCommandFragments = list(TargetCompileFragment.from_dict(tcf) for tcf in dikt.get("compileCommandFragments", ())) 349 | includes = list(TargetCompileGroupInclude.from_dict(tci, backtraceGraph) for tci in dikt.get("includes", ())) 350 | precompileHeaders = list(TargetCompileGroupPCH.from_dict(tcpch, backtraceGraph) for tcpch in dikt.get("precompileHeaders", ())) 351 | defines = list(TargetCompileGroupDefine.from_dict(tcdef, backtraceGraph) for tcdef in dikt.get("defines", ())) 352 | sysroot = Path(dikt["sysroot"]["path"]) if "sysroot" in dikt and "path" in dikt["sysroot"] else None 353 | sources = list(target_sources[tsi] for tsi in dikt["sourceIndexes"]) 354 | return cls(sources, language, compileCommandFragments, includes, precompileHeaders, defines, sysroot) 355 | 356 | def __repr__(self) -> str: 357 | return "{}(sources={}, language='{}', compileCommandFragments={}, #includes={}, #precompileHeaders={}, #defines={}, sysroot={})".format( 358 | type(self).__name__, 359 | self.sources, 360 | self.language, 361 | self.compileCommandFragments, 362 | len(self.includes), 363 | len(self.precompileHeaders), 364 | len(self.defines), 365 | f"'{self.sysroot}'" if self.sysroot else None, 366 | ) 367 | 368 | 369 | class TargetDependency: 370 | __slots__ = ("id", "target", "backtrace") 371 | 372 | def __init__(self, id: str, backtrace: Optional[BacktraceNode]): 373 | self.id = id 374 | self.target: Optional[CodemodelTargetV2] = None 375 | self.backtrace = backtrace 376 | 377 | def update_dependency(self, lut_id_target: dict[str, "CodemodelTargetV2"]) -> None: 378 | self.target = lut_id_target[self.id] 379 | 380 | @classmethod 381 | def from_dict(cls, dikt: dict[str, Any], backtraceGraph: BacktraceGraph) -> "TargetDependency": 382 | id = dikt["id"] 383 | backtrace = None 384 | if "backtrace" in dikt: 385 | backtrace = backtraceGraph.nodes[dikt["backtrace"]] 386 | return cls(id, backtrace) 387 | 388 | def __repr__(self) -> str: 389 | return "{}(id='{}', target='{}', backtrace={})".format( 390 | type(self).__name__, 391 | self.id, 392 | self.target.name if self.target else None, 393 | self.backtrace, 394 | ) 395 | 396 | 397 | class TargetSource: 398 | __slots__ = ("path", "isGenerated", "backtrace", "compileGroup", "sourceGroup") 399 | 400 | def __init__(self, path: Path, isGenerated: Optional[bool], backtrace: Optional[BacktraceNode]): 401 | self.path = path 402 | self.isGenerated = isGenerated 403 | self.backtrace = backtrace 404 | self.compileGroup: Optional[TargetCompileGroup] = None 405 | self.sourceGroup: Optional[TargetSourceGroup] = None 406 | 407 | @classmethod 408 | def from_dict(cls, dikt: dict[str, Any], backtraceGraph: BacktraceGraph) -> "TargetSource": 409 | path = Path(dikt["path"]) 410 | isGenerated = dikt.get("isGenerated") 411 | backtrace = None 412 | if "backtrace" in dikt: 413 | backtrace = backtraceGraph.nodes[dikt["backtrace"]] 414 | return cls(path, isGenerated, backtrace) 415 | 416 | def update_from_dict(self, dikt: dict[str, Any], modelTarget: "CodemodelTargetV2") -> None: 417 | if "compileGroupIndex" in dikt: 418 | self.compileGroup = modelTarget.compileGroups[dikt["compileGroupIndex"]] 419 | if "sourceGroupIndex" in dikt: 420 | self.sourceGroup = modelTarget.sourceGroups[dikt["sourceGroupIndex"]] 421 | 422 | def __repr__(self) -> str: 423 | return "{}(path='{}', isGenerated={}, backtrace={}, compileGroup={}, sourceGroup={})".format( 424 | type(self).__name__, 425 | self.path, 426 | self.isGenerated, 427 | self.backtrace, 428 | self.compileGroup, 429 | self.sourceGroup, 430 | ) 431 | 432 | 433 | class CodemodelTargetV2: 434 | __slots__ = ("name", "id", "type", "backtrace", "folder", "paths", "nameOnDisk", "artifacts", 435 | "isGeneratorProvided", "install", "link", "archive", "dependencies", "sources", 436 | "sourceGroups", "compileGroups") 437 | 438 | def __init__(self, name: str, id: str, type: TargetType, backtrace: Optional[BacktraceNode], folder: Optional[Path], 439 | paths: CMakeSourceBuildPaths, nameOnDisk: str, artifacts: list[Path], 440 | isGeneratorProvided: Optional[bool], install: Optional[TargetInstall], 441 | link: Optional[TargetLink], archive: Optional[TargetArchive], 442 | dependencies: list[TargetDependency], sources: list[TargetSource], 443 | sourceGroups: list[TargetSourceGroup], compileGroups: list[TargetCompileGroup]): 444 | self.name = name 445 | self.id = id 446 | self.type = type 447 | self.backtrace = backtrace 448 | self.folder = folder 449 | self.paths = paths 450 | self.nameOnDisk = nameOnDisk 451 | self.artifacts = artifacts 452 | self.isGeneratorProvided = isGeneratorProvided 453 | self.install = install 454 | self.link = link 455 | self.archive = archive 456 | self.dependencies = dependencies 457 | self.sources = sources 458 | self.sourceGroups = sourceGroups 459 | self.compileGroups = compileGroups 460 | 461 | def update_dependencies(self, lut_id_target: dict[str, "CodemodelTargetV2"]) -> None: 462 | for dependency in self.dependencies: 463 | dependency.update_dependency(lut_id_target) 464 | 465 | @classmethod 466 | def from_dict(cls, dikt: dict[str, Any], reply_path: Path) -> "CodemodelTargetV2": 467 | name = dikt["name"] 468 | id = dikt["id"] 469 | type = TargetType(dikt["type"]) 470 | backtraceGraph = BacktraceGraph.from_dict(dikt["backtraceGraph"]) 471 | backtrace = None 472 | if "backtrace" in dikt: 473 | backtrace = backtraceGraph.nodes[dikt["backtrace"]] 474 | folder = None 475 | if "folder" in dikt: 476 | folder = Path(dikt["folder"]["name"]) 477 | paths = CMakeSourceBuildPaths.from_dict(dikt["paths"]) 478 | nameOnDisk = dikt.get("nameOnDisk", "") 479 | artifacts = list(Path(p["path"]) for p in dikt.get("artifacts", ())) 480 | isGeneratorProvided = dikt.get("isGeneratorProvided") 481 | install = None 482 | if "install" in dikt: 483 | install = TargetInstall.from_dict(dikt["install"], backtraceGraph) 484 | link = None 485 | if "link" in dikt: 486 | link = TargetLink.from_dict(dikt["link"]) 487 | archive = None 488 | if "archive" in dikt: 489 | archive = TargetArchive.from_dict(dikt["archive"]) 490 | dependencies = [] 491 | if "dependencies" in dikt: 492 | dependencies = list(TargetDependency.from_dict(td, backtraceGraph) for td in dikt["dependencies"]) 493 | sources = list(TargetSource.from_dict(ts, backtraceGraph) for ts in dikt["sources"]) 494 | sourceGroups = list(TargetSourceGroup.from_dict(tsg, sources) for tsg in dikt.get("sourceGroups", ())) 495 | compileGroups = list(TargetCompileGroup.from_dict(tsg, sources, backtraceGraph) for tsg in dikt.get("compileGroups", ())) 496 | 497 | return cls(name, id, type, backtrace, folder, paths, nameOnDisk, artifacts, 498 | isGeneratorProvided, install, link, archive, dependencies, sources, sourceGroups, compileGroups) 499 | 500 | @classmethod 501 | def from_path(cls, file: Path, reply_path: Path) -> "CodemodelTargetV2": 502 | with file.open() as file: 503 | dikt = json.load(file) 504 | return cls.from_dict(dikt, reply_path) 505 | 506 | def __repr__(self) -> str: 507 | return "{}(name='{}', type={}, backtrace={})".format( 508 | type(self).__name__, 509 | self.name, 510 | self.type.name, 511 | self.backtrace, 512 | ) 513 | -------------------------------------------------------------------------------- /cmake_file_api/kinds/codemodel/v2.py: -------------------------------------------------------------------------------- 1 | import json 2 | from pathlib import Path 3 | from typing import Any, Optional 4 | 5 | from cmake_file_api.kinds.common import CMakeSourceBuildPaths, VersionMajorMinor 6 | from cmake_file_api.kinds.kind import ObjectKind 7 | from .target.v2 import CodemodelTargetV2 8 | 9 | 10 | class CMakeProject: 11 | __slots__ = ("name", "parentProject", "childProjects", "directories", "targets") 12 | 13 | def __init__(self, name: str): 14 | self.name = name 15 | self.parentProject: Optional[CMakeProject] = None 16 | self.childProjects: list[CMakeProject] = [] 17 | self.directories: list[CMakeDirectory] = [] 18 | self.targets: list[CMakeTarget] = [] 19 | 20 | @classmethod 21 | def from_dict(cls, dikt: dict[str, str]) -> "CMakeProject": 22 | name = dikt["name"] 23 | return cls(name) 24 | 25 | def update_from_dict(self, dikt: dict[str, Any], configuration: "CMakeConfiguration") -> None: 26 | if "parentIndex" in dikt: 27 | self.parentProject = configuration.projects[dikt["parentIndex"]] 28 | self.childProjects = list(configuration.projects[ti] for ti in dikt.get("childIndexes", ())) 29 | self.directories = list(configuration.directories[di] for di in dikt["directoryIndexes"]) 30 | self.targets = list(configuration.targets[ti] for ti in dikt.get("targetIndexes", ())) 31 | 32 | def __repr__(self) -> str: 33 | return "{}(name='{}', parentProject={}, #childProjects={}, #directories={}, #targets={})".format( 34 | type(self).__name__, 35 | self.name, 36 | repr(self.parentProject.name) if self.parentProject else None, 37 | len(self.childProjects), 38 | len(self.directories), 39 | len(self.targets), 40 | ) 41 | 42 | 43 | class CMakeDirectory: 44 | __slots__ = ("source", "build", "parentDirectory", "childDirectories", "project", "targets", "minimumCMakeVersion", "hasInstallRule") 45 | 46 | def __init__(self, source: Path, build: Path, minimumCMakeVersion: Optional[str], hasInstallRule: bool): 47 | self.source = source 48 | self.build = build 49 | self.parentDirectory: Optional[CMakeDirectory] = None 50 | self.childDirectories: list[CMakeDirectory] = [] 51 | self.project: Optional[CMakeProject] = None 52 | self.targets: list[CMakeTarget] = [] 53 | self.minimumCMakeVersion = minimumCMakeVersion 54 | self.hasInstallRule = hasInstallRule 55 | 56 | @classmethod 57 | def from_dict(cls, dikt: dict[str, Any]) -> "CMakeDirectory": 58 | source = Path(dikt["source"]) 59 | build = Path(dikt["build"]) 60 | minimumCMakeVersion = dikt.get("minimumCMakeVersion", None) 61 | hasInstallRule = dikt.get("hasInstallRule", False) 62 | return cls(source, build, minimumCMakeVersion, hasInstallRule) 63 | 64 | def update_from_dict(self, dikt: dict[str, Any], configuration: "CMakeConfiguration") -> None: 65 | if "parentIndex" in dikt: 66 | self.parentDirectory = configuration.directories[dikt["parentIndex"]] 67 | self.childDirectories = list(configuration.directories[di] for di in dikt.get("childIndexes", ())) 68 | self.project = configuration.projects[dikt["projectIndex"]] 69 | self.targets = list(configuration.targets[ti] for ti in dikt.get("targetIndexes", ())) 70 | 71 | def __repr__(self) -> str: 72 | return "{}(source='{}', build='{}', parentDirectory={}, #childDirectories={}, project={}, #targets={}, minimumCMakeVersion={}, hasInstallRule={})".format( 73 | type(self).__name__, 74 | self.source, 75 | self.build, 76 | f'{self.parentDirectory}' if self.parentDirectory else None, 77 | len(self.childDirectories), 78 | self.project.name if self.project else "", 79 | len(self.targets), 80 | self.minimumCMakeVersion, 81 | self.hasInstallRule, 82 | ) 83 | 84 | 85 | class CMakeTarget: 86 | __slots__ = ("name", "directory", "project", "jsonFile", "target") 87 | 88 | def __init__(self, name: str, directory: CMakeDirectory, project: CMakeProject, jsonFile: Path, target: CodemodelTargetV2): 89 | self.name = name 90 | self.directory = directory 91 | self.project = project 92 | self.jsonFile = jsonFile 93 | self.target = target 94 | 95 | def update_dependencies(self, lut_id_target: dict[str, "CMakeTarget"]) -> None: 96 | lut = {k: v.target for k, v in lut_id_target.items()} 97 | self.target.update_dependencies(lut) 98 | 99 | @classmethod 100 | def from_dict(cls, dikt: dict[str, Any], directories: list[CMakeDirectory], projects: list[CMakeProject], reply_path: Path) -> "CMakeTarget": 101 | name = dikt["name"] 102 | directory = directories[dikt["directoryIndex"]] 103 | project = projects[dikt["projectIndex"]] 104 | jsonFile = reply_path / dikt["jsonFile"] 105 | target = CodemodelTargetV2.from_path(jsonFile, reply_path) 106 | return cls(name, directory, project, jsonFile, target) 107 | 108 | def __repr__(self) -> str: 109 | return "{}(name='{}', directory={}, project={}, jsonFile='{}', target={})".format( 110 | type(self).__name__, 111 | self.name, 112 | repr(self.directory), 113 | repr(self.project), 114 | self.jsonFile, 115 | self.target, 116 | ) 117 | 118 | 119 | class CMakeConfiguration: 120 | __slots__ = ("name", "directories", "projects", "targets") 121 | 122 | def __init__(self, name: str, directories: list[CMakeDirectory], projects: list[CMakeProject], targets: list[CMakeTarget]): 123 | self.name = name 124 | self.directories = directories 125 | self.projects = projects 126 | self.targets = targets 127 | 128 | @classmethod 129 | def from_dict(cls, dikt: dict[str, Any], reply_path: Path) -> "CMakeConfiguration": 130 | name = dikt["name"] 131 | directories = list(CMakeDirectory.from_dict(d) for d in dikt["directories"]) 132 | projects = list(CMakeProject.from_dict(d) for d in dikt["projects"]) 133 | targets = list(CMakeTarget.from_dict(td, directories, projects, reply_path) for td in dikt["targets"]) 134 | lut_id_target = {target.target.id: target for target in targets} 135 | for target in targets: 136 | target.update_dependencies(lut_id_target) 137 | 138 | obj = cls(name, directories, projects, targets) 139 | for d, project in zip(dikt["projects"], projects): 140 | project.update_from_dict(d, obj) 141 | for d, directory in zip(dikt["directories"], directories): 142 | directory.update_from_dict(d, obj) 143 | 144 | return obj 145 | 146 | def __repr__(self) -> str: 147 | return "{}(name='{}', #directories={}, #projects={}, #targets={})".format( 148 | type(self).__name__, 149 | self.name, 150 | len(self.directories), 151 | len(self.projects), 152 | len(self.targets), 153 | ) 154 | 155 | 156 | class CodemodelV2: 157 | KIND = ObjectKind.CODEMODEL 158 | 159 | __slots__ = ("version", "paths", "configurations") 160 | 161 | def __init__(self, version: VersionMajorMinor, paths: CMakeSourceBuildPaths, configurations: list[CMakeConfiguration]): 162 | self.version = version 163 | self.paths = paths 164 | self.configurations = configurations 165 | 166 | @classmethod 167 | def from_dict(cls, dikt: dict[str, Any], reply_path: Path) -> "CodemodelV2": 168 | if dikt["kind"] != cls.KIND.value: 169 | raise ValueError 170 | paths = CMakeSourceBuildPaths.from_dict(dikt["paths"]) 171 | version = VersionMajorMinor.from_dict(dikt["version"]) 172 | configurations = [CMakeConfiguration.from_dict(c_dikt, reply_path) for c_dikt in dikt["configurations"]] 173 | return cls(version, paths, configurations) 174 | 175 | @classmethod 176 | def from_path(cls, path: Path, reply_path: Path) -> "CodemodelV2": 177 | with path.open() as file: 178 | dikt = json.load(file) 179 | return cls.from_dict(dikt, reply_path) 180 | 181 | def get_configuration(self, name: str) -> CMakeConfiguration: 182 | try: 183 | return next(c for c in self.configurations if c.name == name) 184 | except StopIteration: 185 | raise KeyError("Unknown configuration") 186 | 187 | def __repr__(self) -> str: 188 | return "{}(version={}, paths={}, configurations={})".format( 189 | type(self).__name__, 190 | self.version, 191 | self.paths, 192 | self.configurations, 193 | ) 194 | -------------------------------------------------------------------------------- /cmake_file_api/kinds/common.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | from typing import Any 3 | 4 | 5 | class VersionMajorMinor: 6 | __slots__ = ("major", "minor") 7 | 8 | def __init__(self, major: int, minor: int): 9 | self.major = major 10 | self.minor = minor 11 | 12 | @classmethod 13 | def from_dict(cls, d: dict[str, str]) -> "VersionMajorMinor": 14 | return cls(int(d["major"]), int(d["minor"])) 15 | 16 | def __repr__(self) -> str: 17 | return "Version({}.{})".format( 18 | self.major, 19 | self.minor, 20 | ) 21 | 22 | 23 | class CMakeSourceBuildPaths: 24 | __slots__ = ("source", "build") 25 | 26 | def __init__(self, source: Path, build: Path): 27 | self.source = source 28 | self.build = build 29 | 30 | @classmethod 31 | def from_dict(cls, d: dict[str, Any]) -> "CMakeSourceBuildPaths": 32 | return cls(Path(d["source"]), Path(d["build"])) 33 | 34 | def __repr__(self) -> str: 35 | return "CMakePaths(source='{}', build='{}')".format( 36 | self.source, 37 | self.build, 38 | ) 39 | -------------------------------------------------------------------------------- /cmake_file_api/kinds/configureLog/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madebr/python-cmake-file-api/fc02aa8522b7f793643412124ee938f8d0dc7ef9/cmake_file_api/kinds/configureLog/__init__.py -------------------------------------------------------------------------------- /cmake_file_api/kinds/configureLog/api.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | import typing 3 | 4 | from .v1 import ConfigureLogV1 5 | 6 | if typing.TYPE_CHECKING: 7 | from ..api import CMakeApiType 8 | 9 | CONFIGURELOG_API: dict[int, CMakeApiType] = { 10 | 1: ConfigureLogV1, 11 | } 12 | -------------------------------------------------------------------------------- /cmake_file_api/kinds/configureLog/target/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madebr/python-cmake-file-api/fc02aa8522b7f793643412124ee938f8d0dc7ef9/cmake_file_api/kinds/configureLog/target/__init__.py -------------------------------------------------------------------------------- /cmake_file_api/kinds/configureLog/target/api.py: -------------------------------------------------------------------------------- 1 | from .v2 import CodemodelTargetV2 2 | 3 | 4 | CODEMODELTARGET_API = { 5 | 2: CodemodelTargetV2, 6 | } 7 | -------------------------------------------------------------------------------- /cmake_file_api/kinds/configureLog/target/v2.py: -------------------------------------------------------------------------------- 1 | import enum 2 | import json 3 | from pathlib import Path 4 | from typing import Any, Optional 5 | 6 | from cmake_file_api.kinds.common import CMakeSourceBuildPaths 7 | 8 | 9 | class TargetType(enum.Enum): 10 | EXECUTABLE = "EXECUTABLE" 11 | STATIC = "STATIC_LIBRARY" 12 | SHARED = "SHARED_LIBRARY" 13 | MODULE = "MODULE_LIBRARY" 14 | OBJECT = "OBJECT_LIBRARY" 15 | UTILITY = "UTILITY" 16 | 17 | 18 | class LinkFragmentRole(enum.Enum): 19 | LINK_FLAGS = "flags" 20 | LINK_LIBRARIES = "libraries" 21 | LIBRARY_PATHS = "libraryPath" 22 | MACOS_FRAMEWORK_PATH = "frameworkPath" 23 | 24 | 25 | class ArchiveFragmentRole(enum.Enum): 26 | ARCHIVER_FLAGS = "flags" 27 | 28 | 29 | class BacktraceNode: 30 | __slots__ = ("file", "line", "command", "parent") 31 | 32 | def __init__(self, file: Path, line: Optional[int], command: Optional[str]): 33 | self.file = file 34 | self.line = line 35 | self.command = command 36 | self.parent = None 37 | 38 | @classmethod 39 | def from_dict(cls, dikt: dict[str, Any], commands: list[str], files: list[Path]) -> "BacktraceNode": 40 | file = files[dikt["file"]] 41 | line = dikt.get("line") 42 | command = None 43 | if "command" in dikt: 44 | command = commands[dikt["command"]] 45 | return cls(file, line, command) 46 | 47 | def update_from_dict(self, dikt: dict[str, Any], nodes: list["BacktraceNode"]) -> None: 48 | if "parent" in dikt: 49 | self.parent = nodes[dikt["parent"]] 50 | 51 | def __repr__(self) -> str: 52 | return "{}(file='{}', line={}, command={}".format( 53 | type(self).__name__, 54 | self.file, 55 | self.line, 56 | self.command, 57 | ) 58 | 59 | 60 | class BacktraceGraph: 61 | __slots__ = ("nodes", ) 62 | 63 | def __init__(self, nodes: list[BacktraceNode]): 64 | self.nodes: list[BacktraceNode] = nodes 65 | 66 | @classmethod 67 | def from_dict(cls, dikt: dict[str, Any]) -> "BacktraceGraph": 68 | commands = dikt["commands"] 69 | files = list(Path(f) for f in dikt["files"]) 70 | nodes = list(BacktraceNode.from_dict(btn, commands, files) for btn in dikt["nodes"]) 71 | for node, dikt_node in zip(nodes, dikt["nodes"]): 72 | node.update_from_dict(dikt_node, nodes) 73 | return cls(nodes) 74 | 75 | def __repr__(self) -> str: 76 | return "{}(nodes={})".format( 77 | type(self).__name__, 78 | self.nodes, 79 | ) 80 | 81 | 82 | class TargetDestination: 83 | __slots__ = ("path", "backtrace") 84 | 85 | def __init__(self, path: Path, backtrace: BacktraceNode): 86 | self.path = path 87 | self.backtrace = backtrace 88 | 89 | @classmethod 90 | def from_dict(cls, dikt: dict[str, Any], backtraceGraph: BacktraceGraph) -> "TargetDestination": 91 | path = Path(dikt["path"]) 92 | backtrace = backtraceGraph.nodes[dikt["backtrace"]] 93 | return cls(path, backtrace) 94 | 95 | def __repr__(self) -> str: 96 | return "{}(path='{}', backtrace={}".format( 97 | type(self).__name__, 98 | self.path, 99 | self.backtrace, 100 | ) 101 | 102 | 103 | class TargetInstall: 104 | __slots__ = ("prefix", "destinations") 105 | 106 | def __init__(self, prefix: Path, destinations: list[TargetDestination]): 107 | self.prefix = prefix 108 | self.destinations = destinations 109 | 110 | @classmethod 111 | def from_dict(cls, dikt: dict[str, Any], backtraceGraph: BacktraceGraph) -> "TargetInstall": 112 | prefix = Path(dikt["prefix"]["path"]) 113 | destinations = list(TargetDestination.from_dict(td, backtraceGraph) for td in dikt["destinations"]) 114 | return cls(prefix, destinations) 115 | 116 | def __repr__(self) -> str: 117 | return "{}(prefix='{}', destinations={}".format( 118 | type(self).__name__, 119 | self.prefix, 120 | self.destinations, 121 | ) 122 | 123 | 124 | class TargetLinkFragment: 125 | __slots__ = ("fragment", "role") 126 | 127 | def __init__(self, fragment: str, role: LinkFragmentRole): 128 | self.fragment = fragment 129 | self.role = role 130 | 131 | @classmethod 132 | def from_dict(cls, dikt: dict[str, Any]) -> "TargetLinkFragment": 133 | fragment = dikt["fragment"] 134 | role = LinkFragmentRole(dikt["role"]) 135 | return cls(fragment, role) 136 | 137 | def __repr__(self) -> str: 138 | return "{}(fragment='{}', role={})".format( 139 | type(self).__name__, 140 | self.fragment, 141 | self.role.name, 142 | ) 143 | 144 | 145 | class TargetLink: 146 | __slots__ = ("language", "commandFragments", "lto", "sysroot") 147 | 148 | def __init__(self, language: str, commandFragments: list[TargetLinkFragment], lto: Optional[bool], sysroot: Optional[Path]): 149 | self.language = language 150 | self.commandFragments = commandFragments 151 | self.lto = lto 152 | self.sysroot = sysroot 153 | 154 | @classmethod 155 | def from_dict(cls, dikt: dict[str, Any]) -> "TargetLink": 156 | language = dikt["language"] 157 | commandFragments = [] 158 | if "commandFragments" in dikt: 159 | commandFragments = list(TargetLinkFragment.from_dict(tlf) for tlf in dikt["commandFragments"]) 160 | lto = dikt.get("lto") 161 | sysroot = None 162 | if "sysroot" in dikt: 163 | sysroot = Path(dikt["sysroot"]["path"]) 164 | return cls(language, commandFragments, lto, sysroot) 165 | 166 | def __repr__(self) -> str: 167 | return "{}(language='{}', commandFragments={}, lto={}, sysroot={}".format( 168 | type(self).__name__, 169 | self.language, 170 | self.commandFragments, 171 | self.lto, 172 | f"'{self.sysroot}'" if self.sysroot else None, 173 | ) 174 | 175 | 176 | class TargetArchiveFragment: 177 | __slots__ = ("fragment", "role") 178 | 179 | def __init__(self, fragment: str, role: ArchiveFragmentRole): 180 | self.fragment = fragment 181 | self.role = role 182 | 183 | @classmethod 184 | def from_dict(cls, dikt: dict[str, Any]) -> "TargetArchiveFragment": 185 | fragment = dikt["fragment"] 186 | role = ArchiveFragmentRole(dikt["role"]) 187 | return cls(fragment, role) 188 | 189 | def __repr__(self) -> str: 190 | return "{}(fragment='{}', role={})".format( 191 | type(self).__name__, 192 | self.fragment, 193 | self.role.name, 194 | ) 195 | 196 | 197 | class TargetArchive: 198 | __slots__ = ("commandFragments", "lto") 199 | 200 | def __init__(self, commandFragments: list[TargetArchiveFragment], lto: Optional[bool]): 201 | self.commandFragments = commandFragments 202 | self.lto = lto 203 | 204 | @classmethod 205 | def from_dict(cls, dikt: dict[str, Any]) -> "TargetArchive": 206 | commandFragments = [] 207 | if "commandFragments" in dikt: 208 | commandFragments = list(TargetArchiveFragment.from_dict(tlf) for tlf in dikt["commandFragments"]) 209 | lto = dikt.get("lto") 210 | return cls(commandFragments, lto) 211 | 212 | def __repr__(self) -> str: 213 | return "{}(commandFragments={}, lto={})".format( 214 | type(self).__name__, 215 | self.commandFragments, 216 | self.lto, 217 | ) 218 | 219 | 220 | class TargetSourceGroup: 221 | __slots__ = ("name", "sources") 222 | 223 | def __init__(self, name: str, sources: list["TargetSource"]): 224 | self.name = name 225 | self.sources: list[TargetSource] = [] 226 | 227 | @classmethod 228 | def from_dict(cls, dikt: dict[str, Any], target_sources: list["TargetSource"]) -> "TargetSourceGroup": 229 | name = dikt["name"] 230 | sources = list(target_sources[tsi] for tsi in dikt["sourceIndexes"]) 231 | return cls(name, sources) 232 | 233 | def __repr__(self) -> str: 234 | return "{}(name='{}', sources={})".format( 235 | type(self).__name__, self.name, self.sources, 236 | ) 237 | 238 | 239 | class TargetCompileFragment: 240 | __slots__ = ("fragment", ) 241 | 242 | def __init__(self, fragment: str): 243 | self.fragment = fragment 244 | 245 | @classmethod 246 | def from_dict(cls, dikt: dict[str, Any]) -> "TargetCompileFragment": 247 | fragment = dikt["fragment"] 248 | return cls(fragment) 249 | 250 | def __repr__(self) -> str: 251 | return "{}(fragment={})".format( 252 | type(self).__name__, 253 | self.fragment, 254 | ) 255 | 256 | 257 | class TargetCompileGroupInclude: 258 | __slots__ = ("path", "isSystem", "backtrace") 259 | 260 | def __init__(self, path: Path, isSystem: Optional[bool], backtrace: Optional[BacktraceNode]): 261 | self.path = path 262 | self.isSystem = isSystem 263 | self.backtrace = backtrace 264 | 265 | @classmethod 266 | def from_dict(cls, dikt: dict[str, Any], backtraceGraph: BacktraceGraph) -> "TargetCompileGroupInclude": 267 | path = Path(dikt["path"]) 268 | isSystem = dikt.get("isSystem") 269 | backtrace = None 270 | if "backtrace" in dikt: 271 | backtrace = backtraceGraph.nodes[dikt["backtrace"]] 272 | return cls(path, isSystem, backtrace) 273 | 274 | def __repr__(self) -> str: 275 | return "{}(path={}, system={}, backtrace={})".format( 276 | type(self).__name__, 277 | f"'{self.path}'" if self.path else None, 278 | self.isSystem, 279 | self.backtrace, 280 | ) 281 | 282 | 283 | class TargetCompileGroupPCH: 284 | __slots__ = ("header", "backtrace") 285 | 286 | def __init__(self, header: Path, backtrace: Optional[BacktraceNode]): 287 | self.header = header 288 | self.backtrace = backtrace 289 | 290 | @classmethod 291 | def from_dict(cls, dikt: dict[str, Any], backtraceGraph: BacktraceGraph) -> "TargetCompileGroupPCH": 292 | header = Path(dikt["header"]) 293 | backtrace = None 294 | if "backtrace" in dikt: 295 | backtrace = backtraceGraph.nodes[dikt["backtrace"]] 296 | return cls(header, backtrace) 297 | 298 | def __repr__(self) -> str: 299 | return "{}(header='{}', backtrace={})".format( 300 | type(self).__name__, 301 | self.header, 302 | self.backtrace, 303 | ) 304 | 305 | 306 | class TargetCompileGroupDefine: 307 | __slots__ = ("define", "backtrace") 308 | 309 | def __init__(self, define: str, backtrace: Optional[BacktraceNode]): 310 | self.define = define 311 | self.backtrace = backtrace 312 | 313 | @classmethod 314 | def from_dict(cls, dikt: dict[str, Any], backtraceGraph: BacktraceGraph) -> "TargetCompileGroupDefine": 315 | define = dikt["define"] 316 | backtrace = None 317 | if "backtrace" in dikt: 318 | backtrace = backtraceGraph.nodes[dikt["backtrace"]] 319 | return cls(define, backtrace) 320 | 321 | def __repr__(self) -> str: 322 | return "{}(define='{}', backtrace={})".format( 323 | type(self).__name__, 324 | self.define, 325 | self.backtrace, 326 | ) 327 | 328 | 329 | class TargetCompileGroup: 330 | __slots__ = ("sources", "language", "compileCommandFragments", "includes", "precompileHeaders", "defines", "sysroot") 331 | 332 | def __init__(self, sources: list["TargetSource"], language: str, 333 | compileCommandFragments: list[TargetCompileFragment], includes: list[TargetCompileGroupInclude], 334 | precompileHeaders: list[TargetCompileGroupPCH], defines: list[TargetCompileGroupDefine], 335 | sysroot: Optional[Path]): 336 | self.sources = sources 337 | self.language = language 338 | self.compileCommandFragments = compileCommandFragments 339 | self.includes = includes 340 | self.precompileHeaders = precompileHeaders 341 | self.defines = defines 342 | self.sysroot = sysroot 343 | 344 | @classmethod 345 | def from_dict(cls, dikt: dict[str, Any], target_sources: list["TargetSource"], backtraceGraph: BacktraceGraph) -> "TargetCompileGroup": 346 | language = dikt["language"] 347 | compileCommandFragments = list(TargetCompileFragment.from_dict(tcf) for tcf in dikt.get("compileCommandFragments", ())) 348 | includes = list(TargetCompileGroupInclude.from_dict(tci, backtraceGraph) for tci in dikt.get("includes", ())) 349 | precompileHeaders = list(TargetCompileGroupPCH.from_dict(tcpch, backtraceGraph) for tcpch in dikt.get("precompileHeaders", ())) 350 | defines = list(TargetCompileGroupDefine.from_dict(tcdef, backtraceGraph) for tcdef in dikt.get("defines", ())) 351 | sysroot = Path(dikt["sysroot"]["path"]) if "sysroot" in dikt else None 352 | sources = list(target_sources[tsi] for tsi in dikt["sourceIndexes"]) 353 | return cls(sources, language, compileCommandFragments, includes, precompileHeaders, defines, sysroot) 354 | 355 | def __repr__(self) -> str: 356 | return "{}(sources={}, language='{}', compileCommandFragments={}, #includes={}, #precompileHeaders={}, #defines={}, sysroot={})".format( 357 | type(self).__name__, 358 | self.sources, 359 | self.language, 360 | self.compileCommandFragments, 361 | len(self.includes), 362 | len(self.precompileHeaders), 363 | len(self.defines), 364 | f"'{self.sysroot}'" if self.sysroot else None, 365 | ) 366 | 367 | 368 | class TargetDependency: 369 | __slots__ = ("id", "target", "backtrace") 370 | 371 | def __init__(self, id: str, backtrace: Optional[BacktraceNode]): 372 | self.id = id 373 | self.target: Optional[CodemodelTargetV2] = None 374 | self.backtrace = backtrace 375 | 376 | def update_dependency(self, lut_id_target: dict[str, "CodemodelTargetV2"]) -> None: 377 | self.target = lut_id_target[self.id] 378 | 379 | @classmethod 380 | def from_dict(cls, dikt: dict[str, Any], backtraceGraph: BacktraceGraph) -> "TargetDependency": 381 | id = dikt["id"] 382 | backtrace = None 383 | if "backtrace" in dikt: 384 | backtrace = backtraceGraph.nodes[dikt["backtrace"]] 385 | return cls(id, backtrace) 386 | 387 | def __repr__(self) -> str: 388 | return "{}(id='{}', target='{}', backtrace={})".format( 389 | type(self).__name__, 390 | self.id, 391 | self.target.name if self.target else None, 392 | self.backtrace, 393 | ) 394 | 395 | 396 | class TargetSource: 397 | __slots__ = ("path", "isGenerated", "backtrace", "compileGroup", "sourceGroup") 398 | 399 | def __init__(self, path: Path, isGenerated: Optional[bool], backtrace: Optional[BacktraceNode]): 400 | self.path = path 401 | self.isGenerated = isGenerated 402 | self.backtrace = backtrace 403 | self.compileGroup: Optional[TargetCompileGroup] = None 404 | self.sourceGroup: Optional[TargetSourceGroup] = None 405 | 406 | @classmethod 407 | def from_dict(cls, dikt: dict[str, Any], backtraceGraph: BacktraceGraph) -> "TargetSource": 408 | path = Path(dikt["path"]) 409 | isGenerated = dikt.get("isGenerated") 410 | backtrace = None 411 | if "backtrace" in dikt: 412 | backtrace = backtraceGraph.nodes[dikt["backtrace"]] 413 | return cls(path, isGenerated, backtrace) 414 | 415 | def update_from_dict(self, dikt: dict[str, Any], modelTarget: "CodemodelTargetV2") -> None: 416 | if "compileGroupIndex" in dikt: 417 | self.compileGroup = modelTarget.compileGroups[dikt["compileGroupIndex"]] 418 | if "sourceGroupIndex" in dikt: 419 | self.sourceGroup = modelTarget.sourceGroups[dikt["sourceGroupIndex"]] 420 | 421 | def __repr__(self) -> str: 422 | return "{}(path='{}', isGenerated={}, backtrace={}, compileGroup={}, sourceGroup={})".format( 423 | type(self).__name__, 424 | self.path, 425 | self.isGenerated, 426 | self.backtrace, 427 | self.compileGroup, 428 | self.sourceGroup, 429 | ) 430 | 431 | 432 | class CodemodelTargetV2: 433 | __slots__ = ("name", "id", "type", "backtrace", "folder", "paths", "nameOnDisk", "artifacts", 434 | "isGeneratorProvided", "install", "link", "archive", "dependencies", "sources", 435 | "sourceGroups", "compileGroups") 436 | 437 | def __init__(self, name: str, id: str, type: TargetType, backtrace: Optional[BacktraceNode], folder: Optional[Path], 438 | paths: CMakeSourceBuildPaths, nameOnDisk: str, artifacts: list[Path], 439 | isGeneratorProvided: Optional[bool], install: Optional[TargetInstall], 440 | link: Optional[TargetLink], archive: Optional[TargetArchive], 441 | dependencies: list[TargetDependency], sources: list[TargetSource], 442 | sourceGroups: list[TargetSourceGroup], compileGroups: list[TargetCompileGroup]): 443 | self.name = name 444 | self.id = id 445 | self.type = type 446 | self.backtrace = backtrace 447 | self.folder = folder 448 | self.paths = paths 449 | self.nameOnDisk = nameOnDisk 450 | self.artifacts = artifacts 451 | self.isGeneratorProvided = isGeneratorProvided 452 | self.install = install 453 | self.link = link 454 | self.archive = archive 455 | self.dependencies = dependencies 456 | self.sources = sources 457 | self.sourceGroups = sourceGroups 458 | self.compileGroups = compileGroups 459 | 460 | def update_dependencies(self, lut_id_target: dict[str, "CodemodelTargetV2"]) -> None: 461 | for dependency in self.dependencies: 462 | dependency.update_dependency(lut_id_target) 463 | 464 | @classmethod 465 | def from_dict(cls, dikt: dict[str, Any]) -> "CodemodelTargetV2": 466 | name = dikt["name"] 467 | id = dikt["id"] 468 | type = TargetType(dikt["type"]) 469 | backtraceGraph = BacktraceGraph.from_dict(dikt["backtraceGraph"]) 470 | backtrace: Optional[BacktraceNode] = None 471 | if "backtrace" in dikt: 472 | backtrace = backtraceGraph.nodes[dikt["backtrace"]] 473 | folder = None 474 | if "folder" in dikt: 475 | folder = Path(dikt["folder"]["name"]) 476 | paths = CMakeSourceBuildPaths.from_dict(dikt["paths"]) 477 | nameOnDisk = dikt.get("nameOnDisk", "") 478 | artifacts = list(Path(p["path"]) for p in dikt.get("artifacts", ())) 479 | isGeneratorProvided = dikt.get("isGeneratorProvided") 480 | install = None 481 | if "install" in dikt: 482 | install = TargetInstall.from_dict(dikt["install"], backtraceGraph) 483 | link = None 484 | if "link" in dikt: 485 | link = TargetLink.from_dict(dikt["link"]) 486 | archive = None 487 | if "archive" in dikt: 488 | archive = TargetArchive.from_dict(dikt["archive"]) 489 | dependencies = [] 490 | if "dependencies" in dikt: 491 | dependencies = list(TargetDependency.from_dict(td, backtraceGraph) for td in dikt["dependencies"]) 492 | sources = list(TargetSource.from_dict(ts, backtraceGraph) for ts in dikt["sources"]) 493 | sourceGroups = list(TargetSourceGroup.from_dict(tsg, sources) for tsg in dikt.get("sourceGroups", ())) 494 | compileGroups = list(TargetCompileGroup.from_dict(tsg, sources, backtraceGraph) for tsg in dikt.get("compileGroups", ())) 495 | 496 | return cls(name, id, type, backtrace, folder, paths, nameOnDisk, artifacts, 497 | isGeneratorProvided, install, link, archive, dependencies, sources, sourceGroups, compileGroups) 498 | 499 | @classmethod 500 | def from_path(cls, file: Path) -> "CodemodelTargetV2": 501 | with file.open() as file: 502 | dikt = json.load(file) 503 | return cls.from_dict(dikt) 504 | 505 | def __repr__(self) -> str: 506 | return "{}(name='{}', type={}, backtrace={})".format( 507 | type(self).__name__, 508 | self.name, 509 | self.type.name, 510 | self.backtrace, 511 | ) 512 | -------------------------------------------------------------------------------- /cmake_file_api/kinds/configureLog/v1.py: -------------------------------------------------------------------------------- 1 | import json 2 | from pathlib import Path 3 | from typing import Any 4 | 5 | from cmake_file_api.kinds.common import VersionMajorMinor 6 | from cmake_file_api.kinds.kind import ObjectKind 7 | 8 | 9 | class ConfigureLogV1: 10 | KIND = ObjectKind.CONFIGURELOG 11 | 12 | __slots__ = ("version", "path", "eventKindNames") 13 | 14 | def __init__(self, version: VersionMajorMinor, path: Path, eventKindNames: list[str]): 15 | self.version = version 16 | self.path = path 17 | self.eventKindNames = eventKindNames 18 | 19 | @classmethod 20 | def from_dict(cls, dikt: dict[str, Any], reply_path: Path) -> "ConfigureLogV1": 21 | if dikt["kind"] != cls.KIND.value: 22 | raise ValueError 23 | path = Path(dikt["path"]) 24 | version = VersionMajorMinor.from_dict(dikt["version"]) 25 | event_kind_names = dikt["eventKindNames"] 26 | return cls(version, path, event_kind_names) 27 | 28 | @classmethod 29 | def from_path(cls, path: Path, reply_path: Path) -> "ConfigureLogV1": 30 | with path.open() as file: 31 | dikt = json.load(file) 32 | return cls.from_dict(dikt, reply_path) 33 | 34 | def __repr__(self) -> str: 35 | return "{}(version={}, paths={}, configurations={})".format( 36 | type(self).__name__, 37 | self.version, 38 | self.path, 39 | self.eventKindNames, 40 | ) 41 | -------------------------------------------------------------------------------- /cmake_file_api/kinds/kind.py: -------------------------------------------------------------------------------- 1 | import enum 2 | 3 | 4 | class ObjectKind(enum.Enum): 5 | CACHE = "cache" 6 | CMAKEFILES = "cmakeFiles" 7 | CODEMODEL = "codemodel" 8 | CONFIGURELOG = "configureLog" 9 | TOOLCHAINS = "toolchains" 10 | -------------------------------------------------------------------------------- /cmake_file_api/kinds/toolchains/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madebr/python-cmake-file-api/fc02aa8522b7f793643412124ee938f8d0dc7ef9/cmake_file_api/kinds/toolchains/__init__.py -------------------------------------------------------------------------------- /cmake_file_api/kinds/toolchains/api.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | import typing 3 | 4 | from .v1 import ToolchainsV1 5 | 6 | if typing.TYPE_CHECKING: 7 | from ..api import CMakeApiType 8 | 9 | TOOLCHAINS_API: dict[int, CMakeApiType] = { 10 | 1: ToolchainsV1, 11 | } 12 | -------------------------------------------------------------------------------- /cmake_file_api/kinds/toolchains/v1.py: -------------------------------------------------------------------------------- 1 | import json 2 | from pathlib import Path 3 | from typing import Any, Optional 4 | 5 | from cmake_file_api.kinds.common import VersionMajorMinor 6 | from cmake_file_api.kinds.kind import ObjectKind 7 | 8 | 9 | class CMakeToolchainCompilerImplicit: 10 | __slots__ = ("includeDirectories", "linkDirectories", "linkFrameworkDirectories", "linkLibraries") 11 | 12 | def __init__(self) -> None: 13 | self.includeDirectories: list[Path] = [] 14 | self.linkDirectories: list[Path] = [] 15 | self.linkFrameworkDirectories: list[Path] = [] 16 | self.linkLibraries: list[str] = [] 17 | 18 | @classmethod 19 | def from_dict(cls, dikt: dict[str, Any]) -> "CMakeToolchainCompilerImplicit": 20 | res = cls() 21 | if "includeDirectories" in dikt: 22 | res.includeDirectories.extend(Path(p) for p in dikt["includeDirectories"]) 23 | if "linkDirectories" in dikt: 24 | res.linkDirectories.extend(Path(p) for p in dikt["linkDirectories"]) 25 | if "linkFrameworkDirectories" in dikt: 26 | res.linkFrameworkDirectories.extend(Path(p) for p in dikt["linkFrameworkDirectories"]) 27 | if "linkLibraries" in dikt: 28 | res.linkFrameworkDirectories.extend(dikt["linkLibraries"]) 29 | return res 30 | 31 | 32 | class CMakeToolchainCompiler: 33 | __slots__ = ("id", "path", "target", "version", "implicit") 34 | 35 | def __init__(self, id: Optional[str], path: Optional[Path], target: Optional[str], version: Optional[str], implicit: CMakeToolchainCompilerImplicit): 36 | self.id = id 37 | self.path = path 38 | self.target = target 39 | self.version = version 40 | self.implicit = implicit 41 | 42 | @classmethod 43 | def from_dict(cls, dikt: dict[str, Any]) -> "CMakeToolchainCompiler": 44 | id = dikt.get("id") 45 | path = Path(dikt["path"]) if "path" in dikt else None 46 | target = dikt.get("target") 47 | version = dikt.get("version") 48 | implicit = CMakeToolchainCompilerImplicit.from_dict(dikt.get("implicit", {})) 49 | return cls(id, path, target, version, implicit) 50 | 51 | def __repr__(self) -> str: 52 | return "{}(id='{}', path='{}', target='{}', version='{}')".format( 53 | type(self).__name__, 54 | self.id, 55 | self.path, 56 | self.target, 57 | self.version 58 | ) 59 | 60 | 61 | class CMakeToolchain: 62 | __slots__ = ("language", "compiler", "sourceFileExtensions") 63 | 64 | def __init__(self, language: str, compiler: CMakeToolchainCompiler, sourceFileExtensions: Optional[list[str]]): 65 | self.language = language 66 | self.compiler = compiler 67 | self.sourceFileExtensions = sourceFileExtensions 68 | 69 | @classmethod 70 | def from_dict(cls, dikt: dict[str, Any]) -> "CMakeToolchain": 71 | language = dikt["language"] 72 | compiler = CMakeToolchainCompiler.from_dict(dikt["compiler"]) 73 | sourceFileExtensions = dikt.get("sourceFileExtensions") 74 | 75 | return cls(language, compiler, sourceFileExtensions) 76 | 77 | def __repr__(self) -> str: 78 | return "{}(language='{}', compiler='{}')".format( 79 | type(self).__name__, 80 | self.language, 81 | self.compiler 82 | ) 83 | 84 | 85 | class ToolchainsV1: 86 | KIND = ObjectKind.TOOLCHAINS 87 | 88 | __slots__ = ("version", "toolchains") 89 | 90 | def __init__(self, version: VersionMajorMinor, toolchains: list[CMakeToolchain]): 91 | self.version = version 92 | self.toolchains = toolchains 93 | 94 | @classmethod 95 | def from_dict(cls, dikt: dict[str, Any], reply_path: Path) -> "ToolchainsV1": 96 | version = VersionMajorMinor.from_dict(dikt["version"]) 97 | toolchains = list(CMakeToolchain.from_dict(cmi) for cmi in dikt["toolchains"]) 98 | return cls(version, toolchains) 99 | 100 | @classmethod 101 | def from_path(cls, path: Path, reply_path: Path) -> "ToolchainsV1": 102 | with path.open() as file: 103 | dikt = json.load(file) 104 | return cls.from_dict(dikt, reply_path) 105 | 106 | def __repr__(self) -> str: 107 | return "{}(version={}, inputs={})".format( 108 | type(self).__name__, 109 | repr(self.version), 110 | repr(self.toolchains), 111 | ) 112 | -------------------------------------------------------------------------------- /cmake_file_api/py.typed: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madebr/python-cmake-file-api/fc02aa8522b7f793643412124ee938f8d0dc7ef9/cmake_file_api/py.typed -------------------------------------------------------------------------------- /cmake_file_api/reply/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madebr/python-cmake-file-api/fc02aa8522b7f793643412124ee938f8d0dc7ef9/cmake_file_api/reply/__init__.py -------------------------------------------------------------------------------- /cmake_file_api/reply/api.py: -------------------------------------------------------------------------------- 1 | from .v1.api import CMakeFileApiV1 2 | 3 | 4 | REPLY_API = { 5 | 1: CMakeFileApiV1, 6 | } 7 | -------------------------------------------------------------------------------- /cmake_file_api/reply/index/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madebr/python-cmake-file-api/fc02aa8522b7f793643412124ee938f8d0dc7ef9/cmake_file_api/reply/index/__init__.py -------------------------------------------------------------------------------- /cmake_file_api/reply/index/api.py: -------------------------------------------------------------------------------- 1 | from .v1 import CMakeReplyFileV1 2 | 3 | 4 | INDEX_API = { 5 | 1: CMakeReplyFileV1, 6 | } 7 | -------------------------------------------------------------------------------- /cmake_file_api/reply/index/file/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madebr/python-cmake-file-api/fc02aa8522b7f793643412124ee938f8d0dc7ef9/cmake_file_api/reply/index/file/__init__.py -------------------------------------------------------------------------------- /cmake_file_api/reply/index/file/api.py: -------------------------------------------------------------------------------- 1 | from .v1 import CMakeReplyFileReferenceV1 2 | 3 | 4 | REPLY_FILE_API = { 5 | 1: CMakeReplyFileReferenceV1, 6 | } 7 | -------------------------------------------------------------------------------- /cmake_file_api/reply/index/file/v1.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | from typing import Any 3 | 4 | from cmake_file_api.kinds.kind import ObjectKind 5 | from cmake_file_api.kinds.common import VersionMajorMinor 6 | 7 | 8 | class CMakeReplyFileReferenceV1: 9 | def __init__(self, kind: ObjectKind, version: VersionMajorMinor, jsonFile: Path): 10 | self.kind = kind 11 | self.version = version 12 | self.jsonFile = jsonFile 13 | 14 | @classmethod 15 | def from_dict(cls, dikt: dict[str, Any]) -> "CMakeReplyFileReferenceV1": 16 | kind = ObjectKind(dikt["kind"]) 17 | version = VersionMajorMinor.from_dict(dikt["version"]) 18 | jsonFile = Path(dikt["jsonFile"]) 19 | return cls(kind, version, jsonFile) 20 | -------------------------------------------------------------------------------- /cmake_file_api/reply/index/v1.py: -------------------------------------------------------------------------------- 1 | import json 2 | from pathlib import Path 3 | import re 4 | from typing import Any 5 | 6 | from cmake_file_api.kinds.kind import ObjectKind 7 | from .file.v1 import CMakeReplyFileReferenceV1 8 | 9 | 10 | class CMakeGenerator: 11 | __slots__ = ("name", "multiConfig") 12 | 13 | def __init__(self, name: str, multiConfig: bool): 14 | self.name = name 15 | self.multiConfig = multiConfig 16 | 17 | @classmethod 18 | def from_dict(cls, dikt: dict[str, Any]) -> "CMakeGenerator": 19 | name = dikt["name"] 20 | multiConfig = dikt["multiConfig"] 21 | return cls(name, multiConfig) 22 | 23 | def __repr__(self) -> str: 24 | return "{}(name='{}', multiConfig={})".format( 25 | type(self).__name__, 26 | self.name, 27 | self.multiConfig, 28 | ) 29 | 30 | 31 | class CMakeVersion: 32 | def __init__(self, major: int, minor: int, patch: int, string: str, suffix: str, isDirty: bool): 33 | self.major = major 34 | self.minor = minor 35 | self.patch = patch 36 | self.string = string 37 | self.suffix = suffix 38 | self.isDirty = isDirty 39 | 40 | @classmethod 41 | def from_dict(cls, dikt: dict[str, Any]) -> "CMakeVersion": 42 | major = dikt["major"] 43 | minor = dikt["minor"] 44 | patch = dikt["patch"] 45 | string = dikt["string"] 46 | suffix = dikt["suffix"] 47 | isDirty = dikt["isDirty"] 48 | return cls(major, minor, patch, string, suffix, isDirty) 49 | 50 | def __repr__(self) -> str: 51 | return "{}(major={}, minor={}, patch={}, string='{}', suffix='{}', isDirty={})".format( 52 | type(self).__name__, 53 | self.major, 54 | self.minor, 55 | self.patch, 56 | self.string, 57 | self.suffix, 58 | self.isDirty, 59 | ) 60 | 61 | 62 | class CMakePaths: 63 | __slots__ = ("cmake", "cpack", "ctest", "root") 64 | 65 | def __init__(self, cmake: Path, cpack: Path, ctest: Path, root: Path): 66 | self.cmake = cmake 67 | self.cpack = cpack 68 | self.ctest = ctest 69 | self.root = root 70 | 71 | @classmethod 72 | def from_dict(cls, dikt: dict[str, Any]) -> "CMakePaths": 73 | cmake = Path(dikt["cmake"]) 74 | cpack = Path(dikt["cpack"]) 75 | ctest = Path(dikt["ctest"]) 76 | root = Path(dikt["root"]) 77 | return cls(cmake, cpack, ctest, root) 78 | 79 | def __str__(self) -> str: 80 | return "{}(cmake='{}', cpack='{}', ctest='{}', root='{}')".format( 81 | type(self).__name__, 82 | self.cmake, 83 | self.cpack, 84 | self.ctest, 85 | self.root, 86 | ) 87 | 88 | 89 | class CMakeInfo: 90 | __slots__ = ("version", "paths", "generator") 91 | 92 | def __init__(self, version: CMakeVersion, paths: CMakePaths, generator: CMakeGenerator): 93 | self.version = version 94 | self.paths = paths 95 | self.generator = generator 96 | 97 | @classmethod 98 | def from_dict(cls, dikt: dict[str, Any]) -> "CMakeInfo": 99 | version = CMakeVersion.from_dict(dikt["version"]) 100 | paths = CMakePaths.from_dict(dikt["paths"]) 101 | generator = CMakeGenerator.from_dict(dikt["generator"]) 102 | return cls(version, paths, generator) 103 | 104 | def __str__(self) -> str: 105 | return "{}(version={}, paths={}, generator={})".format( 106 | type(self).__name__, 107 | self.version, 108 | self.paths, 109 | self.generator, 110 | ) 111 | 112 | 113 | class CMakeReply: 114 | __slots__ = ("stateless", "stateful", "unknowns") 115 | 116 | def __init__(self, stateless: dict[tuple[ObjectKind, int], CMakeReplyFileReferenceV1], 117 | stateful: dict[str, dict[str, Any]], unknowns: list[str]): 118 | self.stateless = stateless 119 | self.stateful = stateful 120 | self.unknowns = unknowns 121 | 122 | @classmethod 123 | def from_dict(cls, dikt: dict[str, Any]) -> "CMakeReply": 124 | stateless = {} 125 | stateful = {} 126 | unknowns = [] 127 | for k, v in dikt.items(): 128 | if "error" in v: 129 | unknowns.append(k) 130 | continue 131 | stateless_match = re.match(r"([a-zA-Z0-9]+)-v([0-9]+)", k) 132 | if stateless_match: 133 | kind, version = ObjectKind(stateless_match.group(1)), int(stateless_match.group(2)) 134 | stateless[(kind, version)] = CMakeReplyFileReferenceV1.from_dict(v) 135 | elif re.match(r"client-[a-zA-Z0-9]+", k): 136 | stateful[k] = v 137 | else: 138 | unknowns.append(k) 139 | 140 | return cls(stateless, stateful, unknowns) 141 | 142 | 143 | class CMakeReplyFileV1: 144 | __slots__ = ("cmake", "objects", "reply") 145 | 146 | def __init__(self, cmake: CMakeInfo, objects: list[CMakeReplyFileReferenceV1], reply: CMakeReply): 147 | self.cmake = cmake 148 | self.objects = objects 149 | self.reply = reply 150 | 151 | @classmethod 152 | def from_dict(cls, dikt: dict[str, Any]) -> "CMakeReplyFileV1": 153 | cmake = CMakeInfo.from_dict(dikt["cmake"]) 154 | objects = list(CMakeReplyFileReferenceV1.from_dict(do) for do in dikt["objects"]) 155 | reply = CMakeReply.from_dict(dikt["reply"]) 156 | return cls(cmake, objects, reply) 157 | 158 | @classmethod 159 | def from_path(cls, path: Path) -> "CMakeReplyFileV1": 160 | with path.open() as file: 161 | dikt = json.load(file) 162 | return cls.from_dict(dikt) 163 | -------------------------------------------------------------------------------- /cmake_file_api/reply/v1/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madebr/python-cmake-file-api/fc02aa8522b7f793643412124ee938f8d0dc7ef9/cmake_file_api/reply/v1/__init__.py -------------------------------------------------------------------------------- /cmake_file_api/reply/v1/api.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | from typing import Any, Optional 3 | 4 | from cmake_file_api.errors import CMakeException 5 | from cmake_file_api.kinds.api import CMakeApiType, OBJECT_KINDS_API 6 | from cmake_file_api.reply.index.api import INDEX_API 7 | from cmake_file_api.kinds.kind import ObjectKind 8 | from cmake_file_api.reply.index.v1 import CMakeReplyFileV1 9 | 10 | 11 | class CMakeFileApiV1: 12 | __slots__ = ("_build_path", ) 13 | 14 | def __init__(self, build_path: Path): 15 | self._build_path = build_path 16 | 17 | def _create_query_path(self) -> Path: 18 | result = self._build_path / ".cmake" / "api" / "v1" / "query" 19 | result.mkdir(parents=True, exist_ok=True) 20 | if not result.is_dir(): 21 | raise NotADirectoryError(f"Query path '{result}' is not a directory") 22 | return result 23 | 24 | def _create_reply_path(self) -> Path: 25 | result = self._build_path / ".cmake" / "api" / "v1" / "reply" 26 | result.mkdir(parents=True, exist_ok=True) 27 | if not result.is_dir(): 28 | raise NotADirectoryError(f"Reply path '{result}' is not a directory") 29 | return result 30 | 31 | @staticmethod 32 | def _instrument_query_path(query_path: Path, kind: ObjectKind, kind_version: int) -> None: 33 | (query_path / f"{kind.value}-v{kind_version}").touch() 34 | 35 | @staticmethod 36 | def _find_index_path(reply_path: Path) -> Optional[Path]: 37 | try: 38 | return next(reply_path.glob("index-*.json")) 39 | except StopIteration: 40 | return None 41 | 42 | def find_index_path(self) -> Optional[Path]: 43 | reply_path = self._create_reply_path() 44 | return self._find_index_path(reply_path) 45 | 46 | def instrument(self, kind: ObjectKind, kind_version: int) -> None: 47 | query_path = self._create_query_path() 48 | self._instrument_query_path(query_path, kind, kind_version) 49 | 50 | def instrument_all(self) -> None: 51 | query_path = self._create_query_path() 52 | for kind, kind_api in OBJECT_KINDS_API.items(): 53 | for kind_version in kind_api.keys(): 54 | self._instrument_query_path(query_path, kind, kind_version) 55 | 56 | def _index(self, reply_path: Path) -> CMakeReplyFileV1: 57 | index_path = self._find_index_path(reply_path) 58 | if index_path is None: 59 | raise CMakeException("CMake did not generate index file. Maybe your cmake version is too old?") 60 | 61 | index_api = INDEX_API.get(1) 62 | if not index_api: 63 | raise CMakeException("Unknown api version") 64 | return index_api.from_path(index_path) 65 | 66 | def index(self) -> CMakeReplyFileV1: 67 | reply_path = self._create_reply_path() 68 | return self._index(reply_path) 69 | 70 | def inspect(self, kind: ObjectKind, kind_version: int) -> Optional[CMakeApiType]: 71 | reply_path = self._create_reply_path() 72 | index = self._index(reply_path) 73 | 74 | data_path = index.reply.stateless.get((kind, kind_version), None) 75 | if data_path is None: 76 | return None 77 | api: Optional[CMakeApiType] = OBJECT_KINDS_API.get(kind, {}).get(kind_version, None) 78 | if api is None: 79 | return None 80 | return api.from_path(reply_path / str(data_path.jsonFile), reply_path) 81 | 82 | def inspect_all(self) -> dict[ObjectKind, dict[int, object]]: 83 | reply_path = self._create_reply_path() 84 | index = self._index(reply_path) 85 | 86 | result: dict[ObjectKind, dict[int, Any]] = {} 87 | for (kind, kind_version), reply_file_ref in index.reply.stateless.items(): 88 | api = OBJECT_KINDS_API.get(kind, {}).get(kind_version, None) 89 | if api is None: 90 | continue 91 | kind_data = api.from_path(reply_path / str(reply_file_ref.jsonFile), reply_path) 92 | result.setdefault(kind, {})[kind_version] = kind_data 93 | return result 94 | -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # Example usage 2 | 3 | The directory `project` contains a simple CMake project. 4 | 5 | The `script.py` file contains example usage of cmake file api. 6 | 7 | Running it should give a output like: 8 | ``` 9 | $ python3 ./script.py 10 | /usr/bin/python3.7 /home/maarten/programming/python-cmake-file-api/example/script.py 11 | cmake path: /usr/bin/cmake 12 | version: 3.17.2 13 | projects: ['test_project'] 14 | targets: ['base', 'dep', 'exe'] 15 | target dependencies: {'base': [], 'dep': ['base'], 'exe': ['base', 'dep']} 16 | ``` 17 | -------------------------------------------------------------------------------- /example/project/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.14) 2 | # The file-based api of cmake required CMake 3.14+ 3 | project(test_project) 4 | 5 | include(GNUInstallDirs) 6 | 7 | add_subdirectory(base) 8 | add_subdirectory(dep) 9 | add_subdirectory(exe) 10 | 11 | install(TARGETS base dep exe EXPORT Example 12 | ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}" 13 | LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" 14 | RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" 15 | ) 16 | install(EXPORT Example 17 | DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}" 18 | FILE ExampleTargets.cmake 19 | NAMESPACE "TestProject::" 20 | ) 21 | -------------------------------------------------------------------------------- /example/project/base/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(base base.cpp) 2 | target_include_directories(base PUBLIC 3 | "$" 4 | "$" 5 | ) 6 | 7 | install(FILES "include/base.hpp" DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}") 8 | -------------------------------------------------------------------------------- /example/project/base/base.cpp: -------------------------------------------------------------------------------- 1 | #include "base.hpp" 2 | 3 | std::string base_return_string(const std::string &s) { 4 | return std::string("This function returns a string containing '") + s + "' as a substring"; 5 | } 6 | -------------------------------------------------------------------------------- /example/project/base/include/base.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | std::string base_return_string(const std::string &s); 6 | -------------------------------------------------------------------------------- /example/project/dep/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(dep dep.cpp) 2 | target_include_directories(dep PUBLIC 3 | "$" 4 | "$" 5 | ) 6 | target_link_libraries(dep PRIVATE base) 7 | 8 | install(FILES "include/dep.hpp" DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}") 9 | -------------------------------------------------------------------------------- /example/project/dep/dep.cpp: -------------------------------------------------------------------------------- 1 | #include "dep.hpp" 2 | 3 | #include 4 | 5 | #include 6 | 7 | void dep_print_string(const std::string &s) { 8 | std::cout << base_return_string(s) << "\n"; 9 | } 10 | -------------------------------------------------------------------------------- /example/project/dep/include/dep.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | void dep_print_string(const std::string &s); 6 | -------------------------------------------------------------------------------- /example/project/exe/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(exe exe.cpp) 2 | target_link_libraries(exe PRIVATE dep) 3 | -------------------------------------------------------------------------------- /example/project/exe/exe.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main(int argc, char *argv[]) { 4 | for(int i = 1; i < argc; ++i) { 5 | dep_print_string(argv[1]); 6 | } 7 | return 0; 8 | } 9 | -------------------------------------------------------------------------------- /example/script.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | import shutil 3 | 4 | from cmake_file_api import CMakeProject, ObjectKind 5 | 6 | script_path = Path(__file__).resolve().parent 7 | source_path = script_path / "project" 8 | build_path = script_path / "build" 9 | 10 | try: 11 | shutil.rmtree(str(build_path)) 12 | except FileNotFoundError: 13 | pass 14 | build_path.mkdir(exist_ok=True) 15 | 16 | cmake_project = CMakeProject(build_path, source_path, api_version=1) 17 | cmake_project.cmake_file_api.instrument_all() 18 | cmake_project.configure(quiet=True) 19 | 20 | results = cmake_project.cmake_file_api.inspect_all() 21 | print("cmake path:", cmake_project.cmake_file_api.index().cmake.paths.cmake) 22 | print("version:", cmake_project.cmake_file_api.index().cmake.version.string) 23 | 24 | codemodel_v2 = results[ObjectKind.CODEMODEL][2] 25 | print("projects:", list(project.name for project in codemodel_v2.configurations[0].projects)) 26 | print("targets:", list(target.name for target in codemodel_v2.configurations[0].targets)) 27 | print("target dependencies:", {target.name: list(dependency.target.name for dependency in target.target.dependencies) for target in codemodel_v2.configurations[0].targets}) 28 | 29 | 30 | codemodel_v2 = results[ObjectKind.CODEMODEL][2] 31 | -------------------------------------------------------------------------------- /mypy.ini: -------------------------------------------------------------------------------- 1 | [mypy] 2 | 3 | files = cmake_file_api 4 | -------------------------------------------------------------------------------- /requirements-dev.txt: -------------------------------------------------------------------------------- 1 | pytest 2 | mypy 3 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | name = cmake-file-api 3 | license = MIT 4 | author = Anonymous Maarten 5 | author_email = anonymous.maarten@gmail.com 6 | description = Read and interpret CMake's file-based API 7 | long_description = file: README.md 8 | long_description_content_type = text/markdown 9 | url = http://github.com/madebr/python-cmake-file-api 10 | classifiers = 11 | Development Status :: 1 - Planning 12 | Programming Language :: Python :: 3 13 | Programming Language :: C 14 | Programming Language :: C++ 15 | License :: OSI Approved :: MIT License 16 | Operating System :: OS Independent 17 | 18 | [options] 19 | python_requires = >=3.9 20 | packages=find: 21 | setup_requires = 22 | setuptools_scm 23 | 24 | [options.packages.find] 25 | exclude = 26 | tests 27 | tests.* 28 | *.tests 29 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | 4 | setup(use_scm_version=True) 5 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madebr/python-cmake-file-api/fc02aa8522b7f793643412124ee938f8d0dc7ef9/tests/__init__.py -------------------------------------------------------------------------------- /tests/test_regression.py: -------------------------------------------------------------------------------- 1 | import collections 2 | import functools 3 | import re 4 | import subprocess 5 | import textwrap 6 | 7 | import pytest 8 | 9 | from cmake_file_api.cmake import CMakeProject 10 | from cmake_file_api.kinds.kind import ObjectKind 11 | from cmake_file_api.kinds.codemodel.api import CODEMODEL_API 12 | 13 | 14 | @functools.lru_cache(1) # FIXME: CPython 3.9 provides `functools.cache` 15 | def cmake_version(): 16 | cmake_version_raw = subprocess.check_output(["cmake", "--version"]).decode() 17 | cmake_version_match = next(re.finditer(r"cmake version ((?:[0-9.]+.)[0-9.]+)", cmake_version_raw, flags=re.I)) 18 | version_list = cmake_version_match.group(1).split(".") 19 | version_tuple = tuple(int(v) for v in version_list) 20 | return version_tuple 21 | 22 | 23 | CMAKE_SUPPORTS_TOOLCHAINS_V1 = cmake_version() >= (3, 20) 24 | 25 | 26 | @pytest.fixture 27 | def build_tree(tmp_path_factory): 28 | SrcBuild = collections.namedtuple("SrcBuild", ("source", "build")) 29 | src = tmp_path_factory.getbasetemp() 30 | build = tmp_path_factory.mktemp("build") 31 | return SrcBuild(src, build) 32 | 33 | 34 | @pytest.fixture 35 | def simple_cxx_project(build_tree): 36 | (build_tree.source / "CMakeLists.txt").write_text(textwrap.dedent("""\ 37 | cmake_minimum_required(VERSION 3.0...3.5) 38 | project(demoproject) 39 | add_library(alib alib.cpp) 40 | """)) 41 | (build_tree.source / "alib.cpp").write_text(textwrap.dedent(r"""\ 42 | #include 43 | #include 44 | void lib1_hello(const std::string &s) { 45 | std::cout << "A string:" << s << "\n"; 46 | }""")) 47 | return build_tree 48 | 49 | 50 | @pytest.fixture 51 | def complex_cxx_project(build_tree): 52 | (build_tree.source / "CMakeLists.txt").write_text(textwrap.dedent("""\ 53 | if(NOT CMAKE_HOST_SYSTEM_NAME STREQUAL "Darwin") 54 | set(CMAKE_SYSROOT "/usr/opt/toolchain") 55 | endif() 56 | cmake_minimum_required(VERSION 3.0...3.5) 57 | project(demoproject C) 58 | enable_language(CXX) 59 | 60 | add_library(lib_interface INTERFACE) 61 | target_include_directories(lib_interface INTERFACE "${CMAKE_CURRENT_SOURCE_DIR}") 62 | target_sources(lib_interface 63 | PUBLIC 64 | "${CMAKE_CURRENT_SOURCE_DIR}/interface.c" 65 | "${CMAKE_CURRENT_SOURCE_DIR}/interface.h" 66 | ) 67 | target_compile_definitions(lib_interface INTERFACE INTERFACE_HELLO) 68 | source_group("Source files" CMakeLists.txt) 69 | source_group(lib_interface FILES 70 | # "${CMAKE_CURRENT_SOURCE_DIR}/interface.c" 71 | "${CMAKE_CURRENT_SOURCE_DIR}/interface.h" 72 | ) 73 | set_target_properties(lib_interface PROPERTIES FOLDER "${CMAKE_CURRENT_SOURCE_DIR}") 74 | 75 | add_library(lib1_noinstall lib1.cpp) 76 | 77 | add_library(lib1_install lib1.cpp) 78 | install(TARGETS lib1_install) 79 | 80 | add_library(lib2_noinstall STATIC lib2.cpp) 81 | target_link_libraries(lib2_noinstall PRIVATE lib1_noinstall) 82 | 83 | add_library(lib2_install lib2.cpp) 84 | target_link_libraries(lib2_install PRIVATE lib1_install) 85 | install(TARGETS lib2_install) 86 | 87 | add_executable(exe1_noinstall exe1.cpp) 88 | 89 | add_executable(exe1_install exe1.cpp) 90 | install(TARGETS exe1_install) 91 | 92 | add_executable(exe2dep_noinstall exe2.cpp) 93 | target_link_libraries(exe2dep_noinstall PRIVATE lib1_noinstall) 94 | 95 | add_executable(exe2dep_install exe2.cpp) 96 | target_link_libraries(exe2dep_install lib1_install) 97 | install(TARGETS exe2dep_install) 98 | 99 | add_executable(exe3dep_noinstall exe3.cpp) 100 | target_link_libraries(exe3dep_noinstall PRIVATE lib2_noinstall lib_interface) 101 | 102 | add_executable(exe3dep_install exe3.cpp) 103 | target_link_libraries(exe3dep_install PRIVATE lib2_install lib_interface) 104 | install(TARGETS exe3dep_install) 105 | """)) 106 | (build_tree.source / "interface.h").write_text(textwrap.dedent(r"""\ 107 | #include 108 | #ifdef __cplusplus 109 | extern "C" { 110 | #endif 111 | extern void interface_hello(const char *s); 112 | #ifdef __cplusplus 113 | } 114 | #endif 115 | """)) 116 | (build_tree.source / "interface.c").write_text(textwrap.dedent(r"""\ 117 | #include "interface.h" 118 | void interface_hello(const char* s) { 119 | printf("Hello from interface: %s\n", s); 120 | } 121 | """)) 122 | (build_tree.source / "lib1.cpp").write_text(textwrap.dedent(r"""\ 123 | #include 124 | #include 125 | void lib1_hello(const std::string &s) { 126 | std::cout << "A string:" << s << "\n"; 127 | } 128 | """)) 129 | (build_tree.source / "lib2.cpp").write_text(textwrap.dedent(r"""\ 130 | #include 131 | #include 132 | void lib1_hello(const std::string &s); 133 | void lib2_hello(const std::string &s) { 134 | lib1_hello(s + " " + s); 135 | } 136 | """)) 137 | (build_tree.source / "exe1.cpp").write_text(textwrap.dedent(r"""\ 138 | #include 139 | int main() { 140 | std::cout << "Hello world\n"; 141 | return 0; 142 | } 143 | """)) 144 | (build_tree.source / "exe2.cpp").write_text(textwrap.dedent(r"""\ 145 | #include 146 | void lib1_hello(const std::string &s); 147 | int main() { 148 | lib1_hello("Hello from main"); 149 | return 0; 150 | } 151 | """)) 152 | (build_tree.source / "exe3.cpp").write_text(textwrap.dedent(r"""\ 153 | #include 154 | #include 155 | void lib2_hello(const std::string &s); 156 | int main() { 157 | lib2_hello("Hello from main!"); 158 | interface_hello("main"); 159 | return 0; 160 | } 161 | """)) 162 | return build_tree 163 | 164 | 165 | def test_codemodelV2(simple_cxx_project, capsys): 166 | project = CMakeProject(simple_cxx_project.build, simple_cxx_project.source, api_version=1) 167 | object_kind = ObjectKind.CODEMODEL 168 | kind_version = 2 169 | project.cmake_file_api.instrument(object_kind, kind_version) 170 | project.reconfigure(quiet=True) 171 | data = project.cmake_file_api.inspect(object_kind, kind_version) 172 | assert data is not None 173 | assert isinstance(data, CODEMODEL_API[kind_version]) 174 | 175 | 176 | def test_complete_project(complex_cxx_project, capsys): 177 | project = CMakeProject(complex_cxx_project.build, complex_cxx_project.source, api_version=1) 178 | project.cmake_file_api.instrument_all() 179 | project.reconfigure(quiet=True) 180 | data = project.cmake_file_api.inspect_all() 181 | assert data is not None 182 | 183 | # Check if project also works without specifying the source directory 184 | project2 = CMakeProject(complex_cxx_project.build, api_version=1) 185 | project2.cmake_file_api.instrument_all() 186 | project2.reconfigure(quiet=True) 187 | data2 = project2.cmake_file_api.inspect_all() 188 | assert data2 is not None 189 | 190 | 191 | @pytest.mark.skipif(not CMAKE_SUPPORTS_TOOLCHAINS_V1, reason="CMake does not support toolchains V1 kind") 192 | def test_toolchain_kind_cxx(complex_cxx_project, capsys): 193 | project = CMakeProject(complex_cxx_project.build, complex_cxx_project.source, api_version=1) 194 | project.cmake_file_api.instrument(ObjectKind.TOOLCHAINS, 1) 195 | project.reconfigure(quiet=True) 196 | kind_obj = project.cmake_file_api.inspect(ObjectKind.TOOLCHAINS, 1) 197 | 198 | from cmake_file_api.kinds.toolchains.v1 import ToolchainsV1 199 | from cmake_file_api.kinds.common import VersionMajorMinor 200 | 201 | assert isinstance(kind_obj, ToolchainsV1) 202 | assert isinstance(kind_obj.version, VersionMajorMinor) 203 | assert kind_obj.version.major == 1 204 | assert "CXX" in tuple(toolchain.language for toolchain in kind_obj.toolchains) 205 | --------------------------------------------------------------------------------