1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
|
--!A cross-platform build utility based on Lua
--
-- Licensed under the Apache License, Version 2.0 (the "License");
-- you may not use this file except in compliance with the License.
-- You may obtain a copy of the License at
--
-- http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and
-- limitations under the License.
--
-- Copyright (C) 2015-present, Xmake Open Source Community.
--
-- @author ruki
-- @file api_checker.lua
--
-- imports
import("core.base.option")
import("core.base.hashset")
import("core.package.package")
import("core.project.project")
import("private.check.checker")
import("private.utils.target", {alias = "target_utils"})
function _get_project_packages()
local project_packages = _g.project_packages
if not project_packages then
project_packages = {}
for name, _ in pairs(project.required_packages()) do
local pkg, errors = package.load_from_project(name)
if pkg then
table.insert(project_packages, pkg)
elseif errors then
raise(errors)
end
end
_g.project_packages = project_packages
end
return project_packages
end
-- get the most probable value
function _get_most_probable_value(value, valueset)
local result
local mindist
for v in valueset:keys() do
local dist = value:levenshtein(v)
if not mindist or dist < mindist then
mindist = dist
result = v
end
end
return result
end
function _do_show(str, opt)
_g.showed = _g.showed or {}
local showed = _g.showed
local infostr
if str then
infostr = string.format("%s: %s: %s", opt.sourcetips, opt.level_tips, str)
else
infostr = string.format("%s: %s: unknown %s value '%s'", opt.sourcetips, opt.level_tips, opt.apiname, opt.value)
end
if opt.probable_value then
infostr = string.format("%s, it may be '%s'", infostr, opt.probable_value)
end
if not showed[infostr] then
cprint(infostr)
showed[infostr] = true
return true
end
end
-- show result
function _show(apiname, value, instance, opt)
opt = opt or {}
-- match level? verbose: note/warning/error, default: warning/error
local level = opt.level
if not option.get("verbose") and level == "note" then
return
end
-- get source information
local sourceinfo = instance:sourceinfo(apiname, value) or {}
local sourcetips = sourceinfo.file or ""
if sourceinfo.line then
sourcetips = sourcetips .. ":" .. (sourceinfo.line or -1)
end
if #sourcetips == 0 then
sourcetips = string.format("%s(%s)", instance:type(), instance:name())
end
-- get level tips
local level_tips = "note"
if level == "warning" then
level_tips = "${color.warning}${text.warning}${clear}"
elseif level == "error" then
level_tips = "${color.error}${text.error}${clear}"
end
-- get probable value
local probable_value
if opt.valueset then
probable_value = _get_most_probable_value(value, opt.valueset)
end
if apiname:endswith("s") then
apiname = apiname:sub(1, #apiname - 1)
end
-- do show
return (opt.show or _do_show)(opt.showstr, {
apiname = apiname,
sourcetips = sourcetips,
level_tips = level_tips,
value = value,
probable_value = probable_value})
end
-- report the invalid value on the given instance
function _report(instance, apiname, value, level, opt)
local reported = _show(apiname, value, instance, {
show = opt.show,
showstr = opt.showstr,
valueset = opt.valueset,
level = level})
if reported then
checker.update_stats(level)
end
end
-- check instance
function _check_instance(instance, apiname, valueset, level, opt)
local instance_valueset = valueset
if type(opt.values) == "function" then
local instance_values = opt.values(instance)
if instance_values then
instance_valueset = hashset.from(instance_values)
end
end
local values = instance:get(apiname)
-- check the keyvalues api, e.g. set_toolset("cxx", "clang")
-- the values is a dictionary, e.g. {cxx = "clang"}, so we report it on the key
-- @see https://github.com/xmake-io/xmake/pull/7597
if opt.keyvalues then
if opt.check then
for key, value in pairs(table.wrap(values)) do
local ok, errors = opt.check(instance, key, value)
if not ok then
_report(instance, apiname, key, level, {show = opt.show, showstr = errors})
end
end
end
return
end
for _, value in ipairs(values) do
if opt.check then
local ok, errors = opt.check(instance, value)
if not ok then
_report(instance, apiname, value, level, {show = opt.show, showstr = errors})
end
elseif not instance_valueset:has(value) then
_report(instance, apiname, value, level, {show = opt.show, valueset = instance_valueset})
end
end
end
-- check api configuration in instances
function _check_instances(apiname, instance, instances_func, opt)
local level = opt.level or "warning"
local valueset
if opt.values and type(opt.values) ~= "function" then
valueset = hashset.from(opt.values)
else
valueset = hashset.new()
end
if instance then
_check_instance(instance, apiname, valueset, level, opt)
else
for _, instance in pairs(instances_func()) do
_check_instance(instance, apiname, valueset, level, opt)
end
end
end
-- check flag
-- @see https://github.com/xmake-io/xmake/issues/3594
function check_flag(target, toolinst, flagkind, flag)
local extraconf = target:extraconf(flagkind)
flag = target_utils.flag_belong_to_tool(flag, toolinst, extraconf)
if flag then
extraconf = extraconf and extraconf[flag]
if not extraconf or not extraconf.force then
return toolinst:has_flags(flag)
end
end
return true
end
-- check api configuration in targets
function check_targets(apiname, opt)
opt = opt or {}
_check_instances(apiname, opt.target, project.targets, opt)
end
-- check api configuration in packages
function check_packages(apiname, opt)
opt = opt or {}
_check_instances(apiname, opt.package, _get_project_packages, opt)
end
|