Add a utility checking uses of require()s

Currently, "everything can require everything". It's an unstructured
mess which sometimes causes problems.

This commit adds a tool that enforces a white-list of require() uses. It
uses depgraph to scan the source code and then each use of require()
that is found is checked. If any violations are found, the tool returns
a failure.

This tool is wired up to a new target "make check-requires" which is
included in "make check". Thus, Travis will run this.

Reference: https://github.com/awesomeWM/awesome/issues/1400
Signed-off-by: Uli Schlachter <psychon@znc.in>
This commit is contained in:
Uli Schlachter 2017-02-06 18:35:20 +01:00
parent fe4d6404a6
commit 0afed66ccf
3 changed files with 128 additions and 0 deletions

View File

@ -88,6 +88,8 @@ install:
- travis_retry sudo luarocks install busted
# Install luacheck for "make check-qa".
- if [ "$DO_CHECKQA" = 1 ]; then travis_retry sudo luarocks install luacheck; fi
# Install depgraph for "make check-qa".
- if [ "$DO_CHECKQA" = 1 ]; then travis_retry sudo luarocks install depgraph; fi
# Install ldoc for building docs.
- |

View File

@ -381,6 +381,13 @@ add_custom_target(check-integration
COMMENT "Running integration tests"
USES_TERMINAL
VERBATIM)
add_custom_target(check-requires
lua "${CMAKE_SOURCE_DIR}/build-utils/check_for_invalid_requires.lua"
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
COMMENT "Checking use of require()"
USES_TERMINAL
VERBATIM)
list(APPEND CHECK_QA_TARGETS check-requires)
a_find_program(BUSTED_EXECUTABLE busted FALSE)
if(BUSTED_EXECUTABLE)
# Keep the arguments in sync with the version below!

View File

@ -0,0 +1,119 @@
#!/usr/bin/env lua
-- This uses the "depgraph" package to parse all source code and look for calls
-- to require(). For all these uses, we make sure that it is allowed by the
-- white-list below. This is done to mitigate the risk of cyclic dependencies
-- between modules and to (hopefully) enforce a bit of sane code structure.
local have_depgraph, depgraph = pcall(require, "depgraph")
if not have_depgraph then
print("depgraph not found")
print(depgraph)
print("(this error is non-fatal; we just skip its use)")
os.exit(0)
end
local allowed_deps = {
gears = {
lgi = true,
},
beautiful = {
gears = true,
lgi = true,
},
wibox = {
beautiful = true,
gears = true,
lgi = true,
},
awful = {
beautiful = true,
gears = true,
lgi = true,
wibox = true,
},
naughty = {
awful = true,
beautiful = true,
gears = true,
lgi = true,
wibox = true,
},
menubar = {
awful = true,
beautiful = true,
gears = true,
lgi = true,
wibox = true,
},
-- TODO: Get rid of these
["beautiful.xresources"] = { ["awful.util"] = true },
["gears.object"] = { ["awful.util"] = true },
["wibox.container"] = { ["awful.util"] = true },
["wibox.layout"] = { ["awful.util"] = true },
["wibox.widget"] = { ["awful.util"] = true },
["gears.surface"] = { ["wibox.hierarchy"] = true },
}
-- Turn "foo.bar.baz" into "foo.bar". Returns nil if there is nothing more to
-- remove.
local function get_supermodule(module)
return string.match(module, "(.+)%.%a+")
end
-- Check if "module" (or one of its parents) is allowed to depend on
-- "dependency" (or one of its parents).
local function is_allowed(module, dependency)
assert(module ~= nil)
-- If both have a common parent, the dependency is allowed
do
local last_mod, mod = module, module
while mod do
last_mod, mod = mod, get_supermodule(mod)
end
local last_dep, dep = dependency, dependency
while dep do
last_dep, dep = dep, get_supermodule(dep)
end
if last_mod == last_dep then
return true
end
end
-- Check the allowed_deps table
while module ~= nil do
local allowed = allowed_deps[module]
if allowed then
local dep = dependency
while dep ~= nil do
if allowed[dep] then
return true
end
dep = get_supermodule(dep)
end
end
module = get_supermodule(module)
end
return false
end
-- Generate the dependency graph
local graph = assert(depgraph.make_graph({"lib"}, {}, "lib", nil, nil))
-- Check if all require's are allowed by the above table
local had_violation = false
for _, module in ipairs(graph.modules) do
for _, dep in ipairs(module.deps) do
if not is_allowed(module.name, dep.name) then
had_violation = true
print(string.format("Dependency from %s onto %s is not allowed",
module.name, dep.name))
end
end
end
if had_violation then
os.exit(1)
end
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80