mirror of
https://github.com/JuliaFEM/JuliaFEM.jl.git
synced 2026-08-06 04:21:33 +00:00
d647a565f7
scripts/check_layer_contract.jl | 127 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 127 insertions(+)
128 lines
3.4 KiB
Julia
128 lines
3.4 KiB
Julia
#!/usr/bin/env julia
|
||
#
|
||
# Static audit for docs/src/developer/architecture_layers.md (layers A–C).
|
||
# Run from anywhere:
|
||
# julia scripts/check_layer_contract.jl [REPO_ROOT]
|
||
#
|
||
# Uses only Base — no --project required.
|
||
|
||
const REPO_ROOT = isempty(ARGS) ? dirname(dirname(@__FILE__)) : abspath(ARGS[1])
|
||
|
||
const INCLUDE_IO_REGEX = r"include\s*\(\s*\"[^\"]*\bio/"
|
||
|
||
function jl_files(dir::AbstractString)
|
||
list = String[]
|
||
isdir(dir) || return list
|
||
for (root, _, files) in walkdir(dir)
|
||
for f in files
|
||
endswith(f, ".jl") || continue
|
||
push!(list, joinpath(root, f))
|
||
end
|
||
end
|
||
sort!(list)
|
||
end
|
||
|
||
function code_fragment(line::AbstractString)::SubString{String}
|
||
# Ignore trailing `# …` so notes do not false-positive the auditors.
|
||
parts = split(line, '#'; limit = 2)
|
||
return rstrip(parts[1])
|
||
end
|
||
|
||
function skip_line(line::AbstractString)::Bool
|
||
s = strip(line)
|
||
isempty(s) && return true
|
||
startswith(s, '#') && return true
|
||
return false
|
||
end
|
||
|
||
const DOMAIN_FORBIDDEN_SUBSTRINGS = [
|
||
"GmshReader",
|
||
"read_gmsh_mesh",
|
||
"GmshMesh",
|
||
"JuliaFEM.Legacy",
|
||
"KernelAbstractions",
|
||
"CUDABackend",
|
||
"MetalBackend",
|
||
"AMDGPUBackend",
|
||
"oneAPIBackend",
|
||
]
|
||
|
||
const FOUNDATION_EXTRA_SUBSTRINGS = [
|
||
"DOFHandler",
|
||
"create_structured_box_mesh",
|
||
]
|
||
|
||
function violations_for_line(line::AbstractString, extra_substrings)::Vector{String}
|
||
hits = String[]
|
||
if occursin(INCLUDE_IO_REGEX, line)
|
||
push!(hits, "include(\"…io/…\")")
|
||
end
|
||
for tok in DOMAIN_FORBIDDEN_SUBSTRINGS
|
||
if occursin(tok, line)
|
||
push!(hits, tok)
|
||
end
|
||
end
|
||
for tok in extra_substrings
|
||
if occursin(tok, line)
|
||
push!(hits, tok)
|
||
end
|
||
end
|
||
return hits
|
||
end
|
||
|
||
function audit_tree(label::AbstractString, dir::AbstractString, extra_substrings)::Bool
|
||
ok = true
|
||
rel = relpath(dir, REPO_ROOT)
|
||
for path in jl_files(dir)
|
||
lineno = 0
|
||
for line in eachline(path)
|
||
lineno += 1
|
||
skip_line(line) && continue
|
||
frag = code_fragment(line)
|
||
isempty(strip(frag)) && continue
|
||
hits = violations_for_line(frag, extra_substrings)
|
||
isempty(hits) && continue
|
||
ok = false
|
||
relfile = relpath(path, REPO_ROOT)
|
||
println(stderr, "$relfile:$lineno: [layer contract] $(join(sort!(unique(hits)), ", "))")
|
||
end
|
||
end
|
||
ok || println(stderr, "($label) violations under $rel")
|
||
return ok
|
||
end
|
||
|
||
function main()::Int
|
||
src = joinpath(REPO_ROOT, "src")
|
||
isdir(src) || error("expected src/ under $REPO_ROOT")
|
||
|
||
println("Layer contract audit (see docs/src/developer/architecture_layers.md)")
|
||
println("Repository: ", REPO_ROOT)
|
||
|
||
all_ok = true
|
||
|
||
domains = joinpath(src, "domains")
|
||
all_ok &= audit_tree("Layer C (domains)", domains, String[])
|
||
|
||
foundation_dirs = [
|
||
joinpath(src, "topology"),
|
||
joinpath(src, "quadrature"),
|
||
joinpath(src, "geometry"),
|
||
joinpath(src, "basis"),
|
||
joinpath(src, "sparse"),
|
||
]
|
||
for d in foundation_dirs
|
||
all_ok &= audit_tree("Layer A (foundation)", d, FOUNDATION_EXTRA_SUBSTRINGS)
|
||
end
|
||
|
||
if all_ok
|
||
println("OK — no forbidden coupling patterns found.")
|
||
return 0
|
||
else
|
||
println(stderr, "")
|
||
println(stderr, "FAILED — fix imports/includes or move code to an outer layer.")
|
||
return 1
|
||
end
|
||
end
|
||
|
||
exit(main())
|