Skip to content

Commit

Permalink
If available, use CMake file API to discover targets (#144)
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 authored Nov 22, 2024
1 parent e2e679a commit 60aa79c
Show file tree
Hide file tree
Showing 3 changed files with 75 additions and 0 deletions.
72 changes: 72 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,73 @@ 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

config_data = codemodel_data.get('configurations', ())
if len(config_data) != 1:
return None

targets = []

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

for target_data in config_data[0].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 +121,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
configs
Expand Down

0 comments on commit 60aa79c

Please sign in to comment.