35
35
import time
36
36
import types
37
37
import typing # MIN_PY=3.9: Switch e.g. typing.List[str] -> list[str]
38
+ import threading
39
+ import itertools
38
40
39
41
40
42
@enum .unique
@@ -178,6 +180,7 @@ def _get_cached_adjusted_modified_time(path: str):
178
180
# 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.
179
181
BAZEL_INTERNAL_SOURCE_CUTOFF = time .time () + 60 * 60 * 24 * 365
180
182
183
+ BAZEL_INTERNAL_MAX_HEADER_SEARCH_COUNT = 500
181
184
182
185
def _get_headers_gcc (compile_args : typing .List [str ], source_path : str , action_key : str ):
183
186
"""Gets the headers used by a particular compile command that uses gcc arguments formatting (including clang.)
@@ -759,22 +762,28 @@ def _all_platform_patch(compile_args: typing.List[str]):
759
762
return compile_args
760
763
761
764
762
- def _get_cpp_command_for_files (compile_action ):
765
+ def _get_cpp_command_for_files (args ):
763
766
"""Reformat compile_action into a compile command clangd can understand.
764
767
765
768
Undo Bazel-isms and figures out which files clangd should apply the command to.
766
769
"""
770
+ (compile_action , event , should_stop_lambda ) = args
771
+ if event .is_set ():
772
+ return set (), set (), []
773
+
767
774
# Patch command by platform
768
775
compile_action .arguments = _all_platform_patch (compile_action .arguments )
769
776
compile_action .arguments = _apple_platform_patch (compile_action .arguments )
770
777
# Android and Linux and grailbio LLVM toolchains: Fine as is; no special patching needed.
771
778
772
779
source_files , header_files = _get_files (compile_action )
773
780
781
+ if not event .is_set () and should_stop_lambda (source_files , header_files ):
782
+ event .set ()
774
783
return source_files , header_files , compile_action .arguments
775
784
776
785
777
- def _convert_compile_commands (aquery_output ):
786
+ def _convert_compile_commands (aquery_output , should_stop_lambda ):
778
787
"""Converts from Bazel's aquery format to de-Bazeled compile_commands.json entries.
779
788
780
789
Input: jsonproto output from aquery, pre-filtered to (Objective-)C(++) compile actions for a given build.
@@ -798,8 +807,8 @@ def _convert_compile_commands(aquery_output):
798
807
with concurrent .futures .ThreadPoolExecutor (
799
808
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
800
809
) as threadpool :
801
- outputs = threadpool . map ( _get_cpp_command_for_files , aquery_output . actions )
802
-
810
+ event = threading . Event ( )
811
+ outputs = threadpool . map ( _get_cpp_command_for_files , map ( lambda action : ( action , event , should_stop_lambda ), aquery_output . actions ))
803
812
# Yield as compile_commands.json entries
804
813
header_files_already_written = set ()
805
814
for source_files , header_files , compile_command_args in outputs :
@@ -826,7 +835,19 @@ def _convert_compile_commands(aquery_output):
826
835
827
836
def _get_commands (target : str , flags : str ):
828
837
"""Return compile_commands.json entries for a given target and flags, gracefully tolerating errors."""
829
- def _get_commands (target_statment ):
838
+ lock = threading .RLock ()
839
+ counter = itertools .count ()
840
+ def _should_stop (headers , file_path ):
841
+ if file_path :
842
+ with lock :
843
+ tried_count = next (counter )
844
+ if tried_count >= BAZEL_INTERNAL_MAX_HEADER_SEARCH_COUNT :
845
+ log_warning (f""">>> Bazel lists no applicable compile commands for { file_path } in { target } under { tried_count } Attempt.""" )
846
+ return True
847
+ return any (header .endswith (file_path ) for header in headers )
848
+ return False
849
+
850
+ def _get_commands (target_statment , file_path ):
830
851
aquery_args = [
831
852
'bazel' ,
832
853
'aquery' ,
@@ -887,7 +908,7 @@ def _get_commands(target_statment):
887
908
if not getattr (parsed_aquery_output , 'actions' , None ): # Unifies cases: No actions (or actions list is empty)
888
909
return []
889
910
890
- return _convert_compile_commands (parsed_aquery_output )
911
+ return _convert_compile_commands (parsed_aquery_output , lambda _ , headers : _should_stop ( headers , file_path ) )
891
912
892
913
893
914
# Log clear completion messages
@@ -938,7 +959,7 @@ def _get_commands(target_statment):
938
959
])
939
960
940
961
for target_statment in target_statment_canidates :
941
- compile_commands .extend ( _get_commands (target_statment ))
962
+ compile_commands .extend ( _get_commands (target_statment , file_path ))
942
963
if any (command ['file' ].endswith (file_path ) for command in reversed (compile_commands )):
943
964
found = True
944
965
break
@@ -950,7 +971,7 @@ def _get_commands(target_statment):
950
971
if {exclude_external_sources }:
951
972
# 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.
952
973
target_statment = f"filter('^(//|@//)',{ target_statment } )"
953
- compile_commands .extend (_get_commands (target_statment ))
974
+ compile_commands .extend (_get_commands (target_statment , None ))
954
975
if len (compile_commands ) == 0 :
955
976
log_warning (f""">>> Bazel lists no applicable compile commands for { target }
956
977
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