Skip to content

Commit

Permalink
If available, use CMake file API to discover targets
Browse files Browse the repository at this point in the history
The CMake file API was added in CMake 3.14. It can be used (among other
things) to get a list of targets for the project that was configured. In
my local tests, this appears to be significantly faster than crafting a
special invocation of the generator and should also work the same for
any generator.
  • Loading branch information
cottsay committed Nov 19, 2024
1 parent 454124f commit 8cb9497
Show file tree
Hide file tree
Showing 3 changed files with 76 additions and 0 deletions.
73 changes: 73 additions & 0 deletions colcon_cmake/task/cmake/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
# Copyright 2016-2018 Dirk Thomas
# Licensed under the Apache License, Version 2.0

import json
import os
from pathlib import Path
import re
import shutil
import subprocess
Expand Down Expand Up @@ -43,6 +45,74 @@ def which_executable(environment_variable, executable_name):
MSBUILD_EXECUTABLE = shutil.which('msbuild')


FILE_API_CLIENT_NAME = 'client-colcon-cmake'


def add_api_queries(path):
"""
Create or update CMake file API queries.
:param str path: The path of the directory contain the generated build
system
"""
api_base = Path(path) / '.cmake' / 'api' / 'v1'
query_base = api_base / 'query' / FILE_API_CLIENT_NAME

query_base.mkdir(parents=True, exist_ok=True)
(query_base / 'codemodel-v2').touch()


def _read_codemodel(path):
api_base = Path(path) / '.cmake' / 'api' / 'v1'
reply_base = api_base / 'reply'

for index_path in sorted(reply_base.glob('index-*.json'), reverse=True):
break
else:
return None

with index_path.open('r') as f:
index_data = json.load(f)

try:
codemodel_file = (
index_data['reply']
[FILE_API_CLIENT_NAME]
['codemodel-v2']
['jsonFile']
)
except KeyError:
return None

with (reply_base / codemodel_file).open('r') as f:
return json.load(f)


def _get_codemodel_targets(path):
codemodel_data = _read_codemodel(path)
if codemodel_data is None:
return None

try:
config_data = codemodel_data['configurations'][0]
except LookupError:
return None

targets = []

for dir_data in config_data.get('directories') or ():
if dir_data.get('hasInstallRule') is True:
targets.append('install')
break

for target_data in config_data.get('targets') or ():
target_name = target_data.get('name')
if target_name is not None:
targets.append(target_name)

return targets


async def has_target(path, target):
"""
Check if the CMake generated build system has a specific target.
Expand All @@ -52,6 +122,9 @@ async def has_target(path, target):
:param str target: The name of the target
:rtype: bool
"""
codemodel_targets = _get_codemodel_targets(path)
if codemodel_targets is not None:
return target in codemodel_targets
generator = get_generator(path)
if 'Unix Makefiles' in generator:
return target in await get_makefile_targets(path)
Expand Down
2 changes: 2 additions & 0 deletions colcon_cmake/task/cmake/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from pathlib import Path
import re

from colcon_cmake.task.cmake import add_api_queries
from colcon_cmake.task.cmake import CMAKE_EXECUTABLE
from colcon_cmake.task.cmake import get_buildfile
from colcon_cmake.task.cmake import get_cmake_version
Expand Down Expand Up @@ -172,6 +173,7 @@ async def _reconfigure(self, args, env):
if CMAKE_EXECUTABLE is None:
raise RuntimeError("Could not find 'cmake' executable")
os.makedirs(args.build_base, exist_ok=True)
add_api_queries(args.build_base)
completed = await run(
self.context,
[CMAKE_EXECUTABLE] + cmake_args,
Expand Down
1 change: 1 addition & 0 deletions test/spell_check.words
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ basepath
buildfile
cmake
cmakelists
codemodel
colcon
completers
contextlib
Expand Down

0 comments on commit 8cb9497

Please sign in to comment.