ffi-clang 0.14.0 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/lib/ffi/clang/args/args.rb +209 -0
- data/lib/ffi/clang/args/darwin.rb +57 -0
- data/lib/ffi/clang/args/linux.rb +33 -0
- data/lib/ffi/clang/args/mingw.rb +21 -0
- data/lib/ffi/clang/args/mswin.rb +195 -0
- data/lib/ffi/clang/code_completion.rb +64 -14
- data/lib/ffi/clang/comment.rb +11 -5
- data/lib/ffi/clang/compilation_database.rb +19 -14
- data/lib/ffi/clang/cursor.rb +293 -38
- data/lib/ffi/clang/cursor_set.rb +38 -0
- data/lib/ffi/clang/diagnostic.rb +7 -11
- data/lib/ffi/clang/diagnostic_set.rb +52 -0
- data/lib/ffi/clang/evaluation.rb +61 -0
- data/lib/ffi/clang/file.rb +59 -0
- data/lib/ffi/clang/index.rb +249 -34
- data/lib/ffi/clang/index_action.rb +552 -0
- data/lib/ffi/clang/invocation_support.rb +44 -0
- data/lib/ffi/clang/lib/code_completion.rb +10 -4
- data/lib/ffi/clang/lib/comment.rb +3 -1
- data/lib/ffi/clang/lib/compilation_database.rb +6 -6
- data/lib/ffi/clang/lib/cursor.rb +159 -30
- data/lib/ffi/clang/lib/cursor_set.rb +19 -0
- data/lib/ffi/clang/lib/evaluation.rb +32 -0
- data/lib/ffi/clang/lib/file.rb +6 -1
- data/lib/ffi/clang/lib/inclusions.rb +4 -1
- data/lib/ffi/clang/lib/index.rb +122 -13
- data/lib/ffi/clang/lib/indexing.rb +319 -0
- data/lib/ffi/clang/lib/printing_policy.rb +35 -28
- data/lib/ffi/clang/lib/source_location.rb +6 -1
- data/lib/ffi/clang/lib/source_range.rb +14 -1
- data/lib/ffi/clang/lib/string.rb +11 -0
- data/lib/ffi/clang/lib/token.rb +3 -1
- data/lib/ffi/clang/lib/translation_unit.rb +12 -1
- data/lib/ffi/clang/lib/type.rb +41 -3
- data/lib/ffi/clang/lib.rb +15 -43
- data/lib/ffi/clang/overridden_cursors.rb +67 -0
- data/lib/ffi/clang/platform.rb +6 -3
- data/lib/ffi/clang/source_location.rb +19 -4
- data/lib/ffi/clang/source_range.rb +28 -2
- data/lib/ffi/clang/string_set.rb +55 -0
- data/lib/ffi/clang/token.rb +50 -16
- data/lib/ffi/clang/translation_unit.rb +71 -15
- data/lib/ffi/clang/types/pointer.rb +23 -15
- data/lib/ffi/clang/types/type.rb +129 -2
- data/lib/ffi/clang/version.rb +2 -1
- data/lib/ffi/clang.rb +8 -1
- data/license.md +1 -1
- data/readme.md +101 -8
- data/releases.md +164 -1
- data.tar.gz.sig +0 -0
- metadata +22 -7
- metadata.gz.sig +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f49d102b63151e047ed3158af59e2ab793ea3a422f13a8a83515305f7c8f5025
|
|
4
|
+
data.tar.gz: df66a932286bb412577e176757d79ceea6b82dc2859407cc5abcb720595f57ff
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 00a0370a903ff0d26d60f3b17dee7a3d94cfaabbf836db4cfb8eba7a56ae966f86a95ab426ef7de1a475b7cb00dd77e2e65ca3553be2d16d05e7e2c13ee4cafa
|
|
7
|
+
data.tar.gz: 3efc05ec2a7da83578cff2bdb963f20dc5ff02168f471cf417672aac0715abf570fbc9744fb696a065a4dbd5f63a453fef7af09d6467a1342d392cddb58d2074
|
checksums.yaml.gz.sig
CHANGED
|
Binary file
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Released under the MIT License.
|
|
4
|
+
# Copyright, 2026, by Charlie Savage.
|
|
5
|
+
|
|
6
|
+
require "open3"
|
|
7
|
+
|
|
8
|
+
module FFI
|
|
9
|
+
module Clang
|
|
10
|
+
# Platform-specific clang configuration: finding libclang, locating the
|
|
11
|
+
# resource directory, and injecting extra command-line arguments into
|
|
12
|
+
# parse_translation_unit.
|
|
13
|
+
#
|
|
14
|
+
# All discovery is lazy — nothing runs until a method is called.
|
|
15
|
+
class Args
|
|
16
|
+
# Set the loaded libclang path after ffi_lib succeeds,
|
|
17
|
+
# so resource dir probing can use it.
|
|
18
|
+
# @parameter path [String | Nil] Path to the loaded libclang library.
|
|
19
|
+
attr_writer :libclang_loaded_path
|
|
20
|
+
|
|
21
|
+
# Factory: returns the platform-appropriate subclass instance.
|
|
22
|
+
# @returns [Args] A platform-specific instance.
|
|
23
|
+
def self.create
|
|
24
|
+
case FFI::Clang.platform
|
|
25
|
+
when :darwin
|
|
26
|
+
DarwinArgs.new
|
|
27
|
+
when :mingw
|
|
28
|
+
MingwArgs.new
|
|
29
|
+
when :mswin
|
|
30
|
+
MswinArgs.new
|
|
31
|
+
else
|
|
32
|
+
LinuxArgs.new
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Ordered list of library paths for ffi_lib.
|
|
37
|
+
# @returns [Array(String)] Paths to try when loading libclang.
|
|
38
|
+
def libclang_paths
|
|
39
|
+
@libclang_paths ||= if ENV["LIBCLANG"]
|
|
40
|
+
[ENV["LIBCLANG"]]
|
|
41
|
+
else
|
|
42
|
+
find_libclang_paths
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Extra args to inject into parse_translation_unit.
|
|
47
|
+
# Includes -resource-dir (unless already present) plus any
|
|
48
|
+
# platform-specific flags.
|
|
49
|
+
# @parameter command_line_args [Array(String)] The existing command line arguments.
|
|
50
|
+
# @returns [Array(String)] Additional args to append.
|
|
51
|
+
def command_line_args(command_line_args = [])
|
|
52
|
+
args = []
|
|
53
|
+
|
|
54
|
+
if !command_line_args.include?("-resource-dir") && resource_dir
|
|
55
|
+
args.push("-resource-dir", resource_dir)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
args.concat(extra_args(command_line_args))
|
|
59
|
+
args
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# The resolved resource directory path.
|
|
63
|
+
# @returns [String | Nil] The resource directory path, or nil if not found.
|
|
64
|
+
def resource_dir
|
|
65
|
+
if defined?(@resource_dir)
|
|
66
|
+
@resource_dir
|
|
67
|
+
else
|
|
68
|
+
@resource_dir = find_resource_dir
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Called after ffi_lib successfully loads libclang.
|
|
73
|
+
# Subclasses may override to perform post-load setup.
|
|
74
|
+
#
|
|
75
|
+
# @parameter library [FFI::DynamicLibrary] The loaded libclang library.
|
|
76
|
+
def post_load(library)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
private
|
|
80
|
+
|
|
81
|
+
def versions
|
|
82
|
+
22.downto(17)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Platform-specific extra args beyond -resource-dir.
|
|
86
|
+
# Subclasses override as needed.
|
|
87
|
+
# @parameter command_line_args [Array(String)] The existing command line arguments.
|
|
88
|
+
# @returns [Array(String)] Additional platform-specific args.
|
|
89
|
+
def extra_args(command_line_args)
|
|
90
|
+
[]
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# --- Shared helpers ---
|
|
94
|
+
|
|
95
|
+
# Find the llvm-config binary. Checks LLVM_CONFIG env, then PATH
|
|
96
|
+
# (unless LLVM_VERSION is set to pin a specific version).
|
|
97
|
+
# @returns [String | Nil] Path to llvm-config, or nil.
|
|
98
|
+
def llvm_config
|
|
99
|
+
if defined?(@llvm_config)
|
|
100
|
+
return @llvm_config
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
@llvm_config = ENV["LLVM_CONFIG"]
|
|
104
|
+
|
|
105
|
+
unless @llvm_config || ENV["LLVM_VERSION"]
|
|
106
|
+
@llvm_config = "llvm-config"
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
@llvm_config
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Query llvm-config for its library directory.
|
|
113
|
+
# @returns [String | Nil] The library directory, or nil.
|
|
114
|
+
def llvm_library_dir
|
|
115
|
+
return nil unless llvm_config
|
|
116
|
+
|
|
117
|
+
@llvm_library_dir ||= run_command(llvm_config, "--libdir")
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Query llvm-config for its binary directory.
|
|
121
|
+
# @returns [String | Nil] The binary directory, or nil.
|
|
122
|
+
def llvm_bin_dir
|
|
123
|
+
return nil unless llvm_config
|
|
124
|
+
|
|
125
|
+
@llvm_bin_dir ||= run_command(llvm_config, "--bindir")
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# Ask a clang binary for its resource directory.
|
|
129
|
+
# @parameter clang [String] Path to or name of the clang binary.
|
|
130
|
+
# @returns [String | Nil] The resource directory path, or nil.
|
|
131
|
+
def resource_dir_from_clang(clang)
|
|
132
|
+
dir = run_command(clang, "-print-resource-dir")
|
|
133
|
+
valid_resource_dir?(dir) ? dir : nil
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def run_command(*command)
|
|
137
|
+
stdout, _stderr, status = Open3.capture3(*command)
|
|
138
|
+
return nil unless status.success?
|
|
139
|
+
|
|
140
|
+
output = stdout.strip
|
|
141
|
+
output.empty? ? nil : output
|
|
142
|
+
rescue Errno::ENOENT
|
|
143
|
+
nil
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# Probe for the resource directory relative to the libclang shared library.
|
|
147
|
+
# @parameter libclang_path [String] Path to the libclang shared library.
|
|
148
|
+
# @returns [String | Nil] The resource directory path, or nil.
|
|
149
|
+
def probe_from_libclang(libclang_path)
|
|
150
|
+
base = ::File.expand_path(::File.dirname(libclang_path))
|
|
151
|
+
|
|
152
|
+
candidates = []
|
|
153
|
+
candidates.concat Dir.glob(::File.join(base, "..", "lib", "clang", "*"))
|
|
154
|
+
candidates.concat Dir.glob(::File.join(base, "..", "..", "lib", "clang", "*"))
|
|
155
|
+
candidates.concat Dir.glob(::File.join(base, "clang", "*"))
|
|
156
|
+
|
|
157
|
+
candidates = candidates.map{|p| ::File.expand_path(p)}.uniq
|
|
158
|
+
|
|
159
|
+
candidates
|
|
160
|
+
.select{|dir| valid_resource_dir?(dir)}
|
|
161
|
+
.sort
|
|
162
|
+
.last
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
# Check whether a directory looks like a valid clang resource directory.
|
|
166
|
+
# @parameter dir [String | Nil] The directory to check.
|
|
167
|
+
# @returns [Boolean] True if the directory contains expected compiler headers.
|
|
168
|
+
def valid_resource_dir?(dir)
|
|
169
|
+
return false unless dir && ::File.directory?(dir)
|
|
170
|
+
|
|
171
|
+
inc = ::File.join(dir, "include")
|
|
172
|
+
return false unless ::File.directory?(inc)
|
|
173
|
+
|
|
174
|
+
::File.exist?(::File.join(inc, "stddef.h")) ||
|
|
175
|
+
::File.exist?(::File.join(inc, "__stddef_size_t.h")) ||
|
|
176
|
+
::File.exist?(::File.join(inc, "stdint.h"))
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
# Common resource dir search: env override, clang from llvm-config,
|
|
180
|
+
# clang on PATH, probe from loaded libclang.
|
|
181
|
+
# @returns [String | Nil] The resource directory path, or nil.
|
|
182
|
+
def find_resource_dir
|
|
183
|
+
# 1. Explicit override via environment variable.
|
|
184
|
+
env = ENV["LIBCLANG_RESOURCE_DIR"]
|
|
185
|
+
return env if valid_resource_dir?(env)
|
|
186
|
+
|
|
187
|
+
# 2. Clang binary from llvm-config.
|
|
188
|
+
if (bin_dir = llvm_bin_dir)
|
|
189
|
+
clang_path = ::File.join(bin_dir, "clang")
|
|
190
|
+
if (dir = resource_dir_from_clang(clang_path))
|
|
191
|
+
return dir
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
# 3. clang on PATH.
|
|
196
|
+
if (dir = resource_dir_from_clang("clang"))
|
|
197
|
+
return dir
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
# 4. Probe relative to the loaded libclang shared library.
|
|
201
|
+
if @libclang_loaded_path && (dir = probe_from_libclang(@libclang_loaded_path))
|
|
202
|
+
return dir
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
nil
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Released under the MIT License.
|
|
4
|
+
# Copyright, 2026, by Charlie Savage.
|
|
5
|
+
|
|
6
|
+
module FFI
|
|
7
|
+
module Clang
|
|
8
|
+
# macOS-specific clang configuration using Xcode toolchain paths.
|
|
9
|
+
class DarwinArgs < Args
|
|
10
|
+
private
|
|
11
|
+
|
|
12
|
+
def find_libclang_paths
|
|
13
|
+
if llvm_library_dir
|
|
14
|
+
return [::File.join(llvm_library_dir, "libclang.dylib")]
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Try xcode-select paths.
|
|
18
|
+
begin
|
|
19
|
+
xcode_dir = `xcode-select -p`.chomp
|
|
20
|
+
%W[
|
|
21
|
+
#{xcode_dir}/Toolchains/XcodeDefault.xctoolchain/usr/lib/libclang.dylib
|
|
22
|
+
#{xcode_dir}/usr/lib/libclang.dylib
|
|
23
|
+
].each do |f|
|
|
24
|
+
return [f] if ::File.exist?(f)
|
|
25
|
+
end
|
|
26
|
+
rescue Errno::ENOENT
|
|
27
|
+
# xcode-select not available
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
["clang"]
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def isysroot
|
|
34
|
+
if defined?(@isysroot)
|
|
35
|
+
return @isysroot
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
@isysroot = begin
|
|
39
|
+
stdout, _stderr, status = Open3.capture3("xcrun", "--show-sdk-path")
|
|
40
|
+
status.success? ? stdout.strip : nil
|
|
41
|
+
rescue Errno::ENOENT
|
|
42
|
+
nil
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def extra_args(command_line_args)
|
|
47
|
+
args = []
|
|
48
|
+
|
|
49
|
+
if !command_line_args.include?("-isysroot") && isysroot
|
|
50
|
+
args.push("-isysroot", isysroot)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
args
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Released under the MIT License.
|
|
4
|
+
# Copyright, 2026, by Charlie Savage.
|
|
5
|
+
|
|
6
|
+
module FFI
|
|
7
|
+
module Clang
|
|
8
|
+
# Linux-specific clang configuration.
|
|
9
|
+
class LinuxArgs < Args
|
|
10
|
+
private
|
|
11
|
+
|
|
12
|
+
def find_libclang_paths
|
|
13
|
+
prefix = llvm_library_dir
|
|
14
|
+
|
|
15
|
+
paths = versions.flat_map do |version|
|
|
16
|
+
5.downto(0).map do |minor|
|
|
17
|
+
libclang_path(prefix, "libclang.so.#{version}.#{minor}")
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
paths.concat(versions.map do |version|
|
|
22
|
+
libclang_path(prefix, "libclang.so.#{version}")
|
|
23
|
+
end)
|
|
24
|
+
|
|
25
|
+
paths << libclang_path(prefix, "libclang.so")
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def libclang_path(prefix, name)
|
|
29
|
+
prefix ? ::File.join(prefix, name) : name
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Released under the MIT License.
|
|
4
|
+
# Copyright, 2026, by Charlie Savage.
|
|
5
|
+
|
|
6
|
+
module FFI
|
|
7
|
+
module Clang
|
|
8
|
+
# MinGW/MSYS2-specific clang configuration.
|
|
9
|
+
class MingwArgs < Args
|
|
10
|
+
private
|
|
11
|
+
|
|
12
|
+
def find_libclang_paths
|
|
13
|
+
if llvm_bin_dir
|
|
14
|
+
return [::File.join(llvm_bin_dir, "libclang.dll")]
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
["libclang.dll"]
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Released under the MIT License.
|
|
4
|
+
# Copyright, 2026, by Charlie Savage.
|
|
5
|
+
|
|
6
|
+
module FFI
|
|
7
|
+
module Clang
|
|
8
|
+
# MSVC-specific clang configuration. Discovers libclang and system
|
|
9
|
+
# include paths from the Visual Studio installation:
|
|
10
|
+
#
|
|
11
|
+
# 1. Find the VS installation path via vswhere.exe
|
|
12
|
+
# 2. Call vcvarsall.bat to set up the MSVC developer environment
|
|
13
|
+
# 3. Run clang-cl -v -E -x c++ NUL in that environment
|
|
14
|
+
# 4. Parse the "#include <...> search starts here:" block from the output
|
|
15
|
+
# 5. Inject each discovered path as -I into parse_translation_unit
|
|
16
|
+
class MswinArgs < Args
|
|
17
|
+
VSWHERE = "C:/Program Files (x86)/Microsoft Visual Studio/Installer/vswhere.exe"
|
|
18
|
+
|
|
19
|
+
# Pin libclang in memory so Windows will not unload it at exit.
|
|
20
|
+
#
|
|
21
|
+
# LLVM's rpmalloc allocator registers Fiber Local Storage (FLS)
|
|
22
|
+
# callbacks via FlsAlloc but does not call FlsFree on
|
|
23
|
+
# DLL_PROCESS_DETACH (LLVM bug #154361, fixed in LLVM 22.1.0 by
|
|
24
|
+
# https://github.com/llvm/llvm-project/pull/171465).
|
|
25
|
+
# Pinning with GET_MODULE_HANDLE_EX_FLAG_PIN prevents the unload
|
|
26
|
+
# so the FLS callbacks remain valid through process shutdown.
|
|
27
|
+
#
|
|
28
|
+
# @parameter library [FFI::DynamicLibrary] The loaded libclang library.
|
|
29
|
+
def post_load(library)
|
|
30
|
+
symbol = library.find_symbol("clang_getClangVersion")
|
|
31
|
+
return unless symbol
|
|
32
|
+
|
|
33
|
+
kernel32 = FFI::DynamicLibrary.open("kernel32", 0)
|
|
34
|
+
get_module_handle_ex_w = kernel32.find_function("GetModuleHandleExW")
|
|
35
|
+
return unless get_module_handle_ex_w
|
|
36
|
+
|
|
37
|
+
get_module_handle_ex_flag_from_address = 0x4
|
|
38
|
+
get_module_handle_ex_flag_pin = 0x1
|
|
39
|
+
flags = get_module_handle_ex_flag_from_address | get_module_handle_ex_flag_pin
|
|
40
|
+
handle_out = FFI::MemoryPointer.new(:pointer)
|
|
41
|
+
pin = FFI::Function.new(:bool, [:uint, :pointer, :pointer], get_module_handle_ex_w)
|
|
42
|
+
pin.call(flags, symbol, handle_out)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
private
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def find_libclang_paths
|
|
49
|
+
if llvm_bin_dir
|
|
50
|
+
return [::File.join(llvm_bin_dir, "libclang.dll")]
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
if (vs_llvm = vs_llvm_dir)
|
|
54
|
+
return [::File.join(vs_llvm, "bin", "libclang.dll")]
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
["libclang.dll"]
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Mswin skips "clang on PATH" — uses clang-cl probe instead.
|
|
61
|
+
def find_resource_dir
|
|
62
|
+
# 1. Explicit override via environment variable.
|
|
63
|
+
env = ENV["LIBCLANG_RESOURCE_DIR"]
|
|
64
|
+
return env if valid_resource_dir?(env)
|
|
65
|
+
|
|
66
|
+
# 2. clang-cl next to the loaded libclang.
|
|
67
|
+
if @libclang_loaded_path
|
|
68
|
+
clang_cl = ::File.join(::File.dirname(@libclang_loaded_path), "clang-cl.exe")
|
|
69
|
+
if ::File.exist?(clang_cl) && (dir = resource_dir_from_clang(clang_cl))
|
|
70
|
+
return dir
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# 3. Probe relative to the loaded libclang shared library.
|
|
75
|
+
if @libclang_loaded_path && (dir = probe_from_libclang(@libclang_loaded_path))
|
|
76
|
+
return dir
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
nil
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def extra_args(command_line_args)
|
|
83
|
+
args = []
|
|
84
|
+
|
|
85
|
+
system_includes.each do |path|
|
|
86
|
+
unless command_line_args.include?(path)
|
|
87
|
+
args.push("-I", path)
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
args
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Parse system include paths from clang-cl running in a VS developer environment.
|
|
95
|
+
# @returns [Array(String)] System include directories.
|
|
96
|
+
def system_includes
|
|
97
|
+
@system_includes ||= find_system_includes
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def find_system_includes
|
|
101
|
+
vs_path = vs_installation_path
|
|
102
|
+
return [] unless vs_path
|
|
103
|
+
|
|
104
|
+
vcvarsall = ::File.join(vs_path, "VC", "Auxiliary", "Build", "vcvarsall.bat")
|
|
105
|
+
return [] unless ::File.exist?(vcvarsall)
|
|
106
|
+
|
|
107
|
+
clang_cl = find_clang_cl
|
|
108
|
+
return [] unless clang_cl
|
|
109
|
+
|
|
110
|
+
arch = RbConfig::CONFIG["target_cpu"] == "x64" ? "x64" : "arm64"
|
|
111
|
+
cmd = "cmd /c \"call \"#{vcvarsall}\" #{arch} >nul 2>&1 && \"#{clang_cl}\" -v -E -x c++ NUL 2>&1\""
|
|
112
|
+
|
|
113
|
+
stdout, _status = Open3.capture2(cmd)
|
|
114
|
+
parse_include_paths(stdout)
|
|
115
|
+
rescue Errno::ENOENT
|
|
116
|
+
[]
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# Find clang-cl.exe — next to loaded libclang, or in VS LLVM dir.
|
|
120
|
+
# @returns [String | Nil] Path to clang-cl.exe, or nil.
|
|
121
|
+
def find_clang_cl
|
|
122
|
+
if @libclang_loaded_path
|
|
123
|
+
path = ::File.join(::File.dirname(@libclang_loaded_path), "clang-cl.exe")
|
|
124
|
+
return path if ::File.exist?(path)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
if (vs_llvm = vs_llvm_dir)
|
|
128
|
+
path = ::File.join(vs_llvm, "bin", "clang-cl.exe")
|
|
129
|
+
return path if ::File.exist?(path)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
nil
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# Parse the #include <...> search paths from clang -v output.
|
|
136
|
+
# @parameter output [String] The combined stdout/stderr from clang-cl -v.
|
|
137
|
+
# @returns [Array(String)] The include directories.
|
|
138
|
+
def parse_include_paths(output)
|
|
139
|
+
paths = []
|
|
140
|
+
in_search_list = false
|
|
141
|
+
|
|
142
|
+
output.each_line do |line|
|
|
143
|
+
line = line.strip
|
|
144
|
+
|
|
145
|
+
if line == "#include <...> search starts here:"
|
|
146
|
+
in_search_list = true
|
|
147
|
+
elsif line == "End of search list."
|
|
148
|
+
break
|
|
149
|
+
elsif in_search_list && !line.empty?
|
|
150
|
+
paths << line
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
paths
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# Find the VS installation path using vswhere.
|
|
158
|
+
# @returns [String | Nil] Path like "C:/Program Files/Microsoft Visual Studio/18/Insiders", or nil.
|
|
159
|
+
def vs_installation_path
|
|
160
|
+
if defined?(@vs_installation_path)
|
|
161
|
+
return @vs_installation_path
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
@vs_installation_path = find_vs_installation_path
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def find_vs_installation_path
|
|
168
|
+
return nil unless ::File.exist?(VSWHERE)
|
|
169
|
+
|
|
170
|
+
stdout, _stderr, status = Open3.capture3(
|
|
171
|
+
VSWHERE, "-latest", "-products", "*", "-prerelease",
|
|
172
|
+
"-requires", "Microsoft.VisualStudio.Component.VC.Llvm.Clang",
|
|
173
|
+
"-property", "installationPath"
|
|
174
|
+
)
|
|
175
|
+
return nil unless status.success?
|
|
176
|
+
|
|
177
|
+
path = stdout.strip
|
|
178
|
+
path.empty? ? nil : path
|
|
179
|
+
rescue Errno::ENOENT
|
|
180
|
+
nil
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
# Find the VS-bundled LLVM directory.
|
|
184
|
+
# @returns [String | Nil] Path like ".../VC/Tools/Llvm/x64", or nil.
|
|
185
|
+
def vs_llvm_dir
|
|
186
|
+
vs_path = vs_installation_path
|
|
187
|
+
return nil unless vs_path
|
|
188
|
+
|
|
189
|
+
arch = RbConfig::CONFIG["target_cpu"] == "x64" ? "x64" : "ARM64"
|
|
190
|
+
llvm_dir = ::File.join(vs_path, "VC", "Tools", "Llvm", arch)
|
|
191
|
+
::File.directory?(llvm_dir) ? llvm_dir : nil
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
end
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
# Released under the MIT License.
|
|
4
4
|
# Copyright, 2014, by Masahiro Sano.
|
|
5
5
|
# Copyright, 2014-2025, by Samuel Williams.
|
|
6
|
-
# Copyright, 2023-
|
|
6
|
+
# Copyright, 2023-2026, by Charlie Savage.
|
|
7
7
|
|
|
8
8
|
require_relative "lib/code_completion"
|
|
9
9
|
|
|
@@ -21,12 +21,19 @@ module FFI
|
|
|
21
21
|
class Results < FFI::AutoPointer
|
|
22
22
|
include Enumerable
|
|
23
23
|
|
|
24
|
-
# @attribute [
|
|
24
|
+
# @attribute [r] size
|
|
25
|
+
# @returns [Integer] The number of completion results.
|
|
25
26
|
attr_reader :size
|
|
26
27
|
|
|
27
|
-
# @attribute [
|
|
28
|
+
# @attribute [r] results
|
|
29
|
+
# @returns [Array(Result)] The array of completion results.
|
|
28
30
|
attr_reader :results
|
|
29
31
|
|
|
32
|
+
# @attribute [r] code_complete_results
|
|
33
|
+
# @returns [Lib::CXCodeCompleteResults] The underlying results structure.
|
|
34
|
+
# @private
|
|
35
|
+
attr_reader :code_complete_results
|
|
36
|
+
|
|
30
37
|
# Initialize code completion results.
|
|
31
38
|
# @parameter code_complete_results [Lib::CXCodeCompleteResults] The completion results structure.
|
|
32
39
|
# @parameter translation_unit [TranslationUnit] The parent translation unit.
|
|
@@ -47,10 +54,11 @@ module FFI
|
|
|
47
54
|
# Iterate over each completion result.
|
|
48
55
|
# @yields {|result| ...} Each completion result.
|
|
49
56
|
# @parameter result [Result] The completion result.
|
|
57
|
+
# @returns [Enumerator] If no block is given.
|
|
50
58
|
def each(&block)
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
59
|
+
return to_enum(__method__) unless block_given?
|
|
60
|
+
|
|
61
|
+
@results.each(&block)
|
|
54
62
|
end
|
|
55
63
|
|
|
56
64
|
# Get the number of diagnostics.
|
|
@@ -69,9 +77,9 @@ module FFI
|
|
|
69
77
|
# Get all diagnostics.
|
|
70
78
|
# @returns [Array(Diagnostic)] Array of diagnostics.
|
|
71
79
|
def diagnostics
|
|
72
|
-
num_diagnostics.times.map
|
|
80
|
+
num_diagnostics.times.map do |i|
|
|
73
81
|
Diagnostic.new(@translation_unit, Lib.get_code_complete_get_diagnostic(@code_complete_results, i))
|
|
74
|
-
|
|
82
|
+
end
|
|
75
83
|
end
|
|
76
84
|
|
|
77
85
|
# Get the completion contexts.
|
|
@@ -126,9 +134,11 @@ module FFI
|
|
|
126
134
|
@size = @code_complete_results[:num]
|
|
127
135
|
cur_ptr = @code_complete_results[:results]
|
|
128
136
|
@results = []
|
|
129
|
-
@size.times
|
|
130
|
-
|
|
131
|
-
|
|
137
|
+
@size.times do |i|
|
|
138
|
+
completion_result = Lib::CXCompletionResult.new(cur_ptr)
|
|
139
|
+
@results << Result.new(completion_result, self, i)
|
|
140
|
+
cur_ptr += Lib::CXCompletionResult.size
|
|
141
|
+
end
|
|
132
142
|
end
|
|
133
143
|
end
|
|
134
144
|
|
|
@@ -136,8 +146,12 @@ module FFI
|
|
|
136
146
|
class Result
|
|
137
147
|
# Initialize a completion result.
|
|
138
148
|
# @parameter result [Lib::CXCompletionResult] The completion result structure.
|
|
139
|
-
|
|
149
|
+
# @parameter owner [Results | Nil] The owning Results object (prevents GC of the parent allocation).
|
|
150
|
+
# @parameter index [Integer | Nil] The index of this result in the parent.
|
|
151
|
+
def initialize(result, owner = nil, index = nil)
|
|
140
152
|
@result = result
|
|
153
|
+
@owner = owner
|
|
154
|
+
@index = index
|
|
141
155
|
end
|
|
142
156
|
|
|
143
157
|
# Get the kind of completion.
|
|
@@ -152,6 +166,25 @@ module FFI
|
|
|
152
166
|
CodeCompletion::String.new @result[:string]
|
|
153
167
|
end
|
|
154
168
|
|
|
169
|
+
# Get the number of fix-its required before this completion can be applied.
|
|
170
|
+
# @returns [Integer] The number of fix-its.
|
|
171
|
+
def num_fix_its
|
|
172
|
+
return 0 unless @owner
|
|
173
|
+
|
|
174
|
+
Lib.get_completion_num_fix_its(@owner.code_complete_results, @index)
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# Get all fix-its for this completion result.
|
|
178
|
+
# @returns [Array(FixIt)] The fix-its.
|
|
179
|
+
def fix_its
|
|
180
|
+
num_fix_its.times.map do |i|
|
|
181
|
+
range_ptr = MemoryPointer.new(Lib::CXSourceRange, 1)
|
|
182
|
+
text = Lib.extract_string(Lib.get_completion_fix_it(@owner.code_complete_results, @index, i, range_ptr))
|
|
183
|
+
range = SourceRange.new(Lib::CXSourceRange.new(range_ptr))
|
|
184
|
+
FixIt.new(text, range)
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
|
|
155
188
|
# Get a string representation of this result.
|
|
156
189
|
# @returns [String] The result as a string.
|
|
157
190
|
def inspect
|
|
@@ -159,6 +192,23 @@ module FFI
|
|
|
159
192
|
end
|
|
160
193
|
end
|
|
161
194
|
|
|
195
|
+
# Represents a fix-it that must be applied before a completion can be inserted.
|
|
196
|
+
class FixIt
|
|
197
|
+
# @attribute [r] text
|
|
198
|
+
# @returns [String] The replacement text.
|
|
199
|
+
# @attribute [r] range
|
|
200
|
+
# @returns [SourceRange] The source range to replace.
|
|
201
|
+
attr_reader :text, :range
|
|
202
|
+
|
|
203
|
+
# Initialize a fix-it.
|
|
204
|
+
# @parameter text [String] The replacement text.
|
|
205
|
+
# @parameter range [SourceRange] The source range to replace.
|
|
206
|
+
def initialize(text, range)
|
|
207
|
+
@text = text
|
|
208
|
+
@range = range
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
|
|
162
212
|
# Represents a code completion string with chunks.
|
|
163
213
|
class String
|
|
164
214
|
# Initialize a completion string.
|
|
@@ -197,7 +247,7 @@ module FFI
|
|
|
197
247
|
# Get all chunks as an array of hashes.
|
|
198
248
|
# @returns [Array(Hash)] Array of chunk hashes with `:kind`, `:text`, and `:completion` keys.
|
|
199
249
|
def chunks
|
|
200
|
-
num_chunks.times.map
|
|
250
|
+
num_chunks.times.map{|i|
|
|
201
251
|
{ kind: chunk_kind(i), text: chunk_text(i), completion: chunk_completion(i) }
|
|
202
252
|
}
|
|
203
253
|
end
|
|
@@ -230,7 +280,7 @@ module FFI
|
|
|
230
280
|
# Get all annotations.
|
|
231
281
|
# @returns [Array(String)] Array of annotation strings.
|
|
232
282
|
def annotations
|
|
233
|
-
num_annotations.times.map
|
|
283
|
+
num_annotations.times.map{|i|
|
|
234
284
|
Lib.extract_string Lib.get_completion_annotation(@pointer, i)
|
|
235
285
|
}
|
|
236
286
|
end
|