37
37
import time
38
38
import types
39
39
import typing # MIN_PY=3.9: Switch e.g. typing.List[str] -> list[str]
40
+ import threading
41
+ import itertools
40
42
41
43
42
44
@enum .unique
@@ -182,6 +184,7 @@ def _get_cached_adjusted_modified_time(path: str):
182
184
# Roughly 1 year into the future. This is safely below bazel's 10 year margin, but large enough that no sane normal file should be past this.
183
185
BAZEL_INTERNAL_SOURCE_CUTOFF = time .time () + 60 * 60 * 24 * 365
184
186
187
+ BAZEL_INTERNAL_MAX_HEADER_SEARCH_COUNT = 500
185
188
186
189
def _get_headers_gcc (compile_args : typing .List [str ], source_path : str , action_key : str ):
187
190
"""Gets the headers used by a particular compile command that uses gcc arguments formatting (including clang.)
@@ -766,22 +769,28 @@ def _all_platform_patch(compile_args: typing.List[str]):
766
769
return compile_args
767
770
768
771
769
- def _get_cpp_command_for_files (compile_action ):
772
+ def _get_cpp_command_for_files (args ):
770
773
"""Reformat compile_action into a compile command clangd can understand.
771
774
772
775
Undo Bazel-isms and figures out which files clangd should apply the command to.
773
776
"""
777
+ (compile_action , event , should_stop_lambda ) = args
778
+ if event .is_set ():
779
+ return set (), set (), []
780
+
774
781
# Patch command by platform
775
782
compile_action .arguments = _all_platform_patch (compile_action .arguments )
776
783
compile_action .arguments = _apple_platform_patch (compile_action .arguments )
777
784
# Android and Linux and grailbio LLVM toolchains: Fine as is; no special patching needed.
778
785
779
786
source_files , header_files = _get_files (compile_action )
780
787
788
+ if not event .is_set () and should_stop_lambda (source_files , header_files ):
789
+ event .set ()
781
790
return source_files , header_files , compile_action .arguments
782
791
783
792
784
- def _convert_compile_commands (aquery_output ):
793
+ def _convert_compile_commands (aquery_output , should_stop_lambda ):
785
794
"""Converts from Bazel's aquery format to de-Bazeled compile_commands.json entries.
786
795
787
796
Input: jsonproto output from aquery, pre-filtered to (Objective-)C(++) compile actions for a given build.
@@ -805,8 +814,8 @@ def _convert_compile_commands(aquery_output):
805
814
with concurrent .futures .ThreadPoolExecutor (
806
815
max_workers = min (32 , (os .cpu_count () or 1 ) + 4 ) # Backport. Default in MIN_PY=3.8. See "using very large resources implicitly on many-core machines" in https://docs.python.org/3/library/concurrent.futures.html#concurrent.futures.ThreadPoolExecutor
807
816
) as threadpool :
808
- outputs = threadpool . map ( _get_cpp_command_for_files , aquery_output . actions )
809
-
817
+ event = threading . Event ( )
818
+ outputs = threadpool . map ( _get_cpp_command_for_files , map ( lambda action : ( action , event , should_stop_lambda ), aquery_output . actions ))
810
819
# Yield as compile_commands.json entries
811
820
header_files_already_written = set ()
812
821
for source_files , header_files , compile_command_args in outputs :
@@ -833,7 +842,19 @@ def _convert_compile_commands(aquery_output):
833
842
834
843
def _get_commands (target : str , flags : str ):
835
844
"""Return compile_commands.json entries for a given target and flags, gracefully tolerating errors."""
836
- def _get_commands (target_statment ):
845
+ lock = threading .RLock ()
846
+ counter = itertools .count ()
847
+ def _should_stop (headers , file_path ):
848
+ if file_path :
849
+ with lock :
850
+ tried_count = next (counter )
851
+ if tried_count >= BAZEL_INTERNAL_MAX_HEADER_SEARCH_COUNT :
852
+ log_warning (f""">>> Bazel lists no applicable compile commands for { file_path } in { target } under { tried_count } Attempt.""" )
853
+ return True
854
+ return any (header .endswith (file_path ) for header in headers )
855
+ return False
856
+
857
+ def _get_commands (target_statment , file_path ):
837
858
aquery_args = [
838
859
'bazel' ,
839
860
'aquery' ,
@@ -899,7 +920,7 @@ def _get_commands(target_statment):
899
920
Continuing gracefully...""" )
900
921
return []
901
922
902
- return _convert_compile_commands (parsed_aquery_output )
923
+ return _convert_compile_commands (parsed_aquery_output , lambda _ , headers : _should_stop ( headers , file_path ) )
903
924
904
925
# Log clear completion messages
905
926
log_info (f">>> Analyzing commands used in { target } " )
@@ -949,7 +970,7 @@ def _get_commands(target_statment):
949
970
])
950
971
951
972
for target_statment in target_statment_canidates :
952
- compile_commands .extend ( _get_commands (target_statment ))
973
+ compile_commands .extend ( _get_commands (target_statment , file_path ))
953
974
if any (command ['file' ].endswith (file_path ) for command in reversed (compile_commands )):
954
975
found = True
955
976
break
@@ -960,7 +981,7 @@ def _get_commands(target_statment):
960
981
if {exclude_external_sources }:
961
982
# For efficiency, have bazel filter out external targets (and therefore actions) before they even get turned into actions or serialized and sent to us. Note: this is a different mechanism than is used for excluding just external headers.
962
983
target_statment = f"filter('^(//|@//)',{ target_statment } )"
963
- compile_commands .extend (_get_commands (target_statment ))
984
+ compile_commands .extend (_get_commands (target_statment , None ))
964
985
if len (compile_commands ) == 0 :
965
986
log_warning (f""">>> Bazel lists no applicable compile commands for { target }
966
987
If this is a header-only library, please instead specify a test or binary target that compiles it (search "header-only" in README.md).
0 commit comments