From b8309fd511045fbb83d7cd1bb449ee33719d627d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Leclercq?= Date: Tue, 26 Oct 2021 14:31:19 +0200 Subject: [PATCH] XMake: add auto-fixer script --- xmake/actions/checkfiles.lua | 376 ++++++++++++++++++++++++++++++ xmake/actions/generateheaders.lua | 2 +- xmake/actions/spirv.lua | 2 +- xmake/actions/unicode.lua | 2 +- 4 files changed, 379 insertions(+), 3 deletions(-) create mode 100644 xmake/actions/checkfiles.lua diff --git a/xmake/actions/checkfiles.lua b/xmake/actions/checkfiles.lua new file mode 100644 index 000000000..5e3ef4327 --- /dev/null +++ b/xmake/actions/checkfiles.lua @@ -0,0 +1,376 @@ + +task("check-files") + +set_menu({ + -- Settings menu usage + usage = "xmake check-files [options]", + description = "Check every file for consistency (can fix some errors)", + options = + { + -- Set k mode as key-only bool parameter + {'f', "fix", "k", nil, "Attempt to automatically fix files." } + } +}) + +local function CompareLines(referenceLines, lines, firstLine, lineCount) + firstLine = firstLine or 1 + lineCount = lineCount or (#lines - firstLine + 1) + if lineCount ~= #referenceLines then + return false + end + + for i = 1, lineCount do + if lines[firstLine + i - 1] ~= referenceLines[i] then + print(lines[firstLine + i]) + print(referenceLines[i]) + return false + end + end + + return true +end + +on_run(function () + import("core.base.option") + + local modules = os.dirs("include/Nazara/*") + + local fileLines = {} + local updatedFiles = {} + local function GetFile(filePath) + filePath = path.translate(filePath) + + local lines = fileLines[filePath] + if not lines then + lines = table.to_array(io.lines(filePath)) + if not lines then + os.raise("failed to open " .. filePath) + end + + fileLines[filePath] = lines + end + + return lines + end + + local function UpdateFile(filePath, lines) + filePath = path.translate(filePath) + + if lines then + fileLines[filePath] = lines + end + + updatedFiles[filePath] = true + end + + local checks = {} + + table.insert(checks, { + Name = "remove empty lines at the beginning", + Check = function (moduleName) + local files = table.join( + os.files("include/Nazara/" .. moduleName .. "/**.hpp"), + os.files("include/Nazara/" .. moduleName .. "/**.inl"), + os.files("src/Nazara/" .. moduleName .. "/**.hpp"), + os.files("src/Nazara/" .. moduleName .. "/**.inl"), + os.files("src/Nazara/" .. moduleName .. "/**.cpp") + ) + + local fixes = {} + + for _, filePath in pairs(files) do + local lines = GetFile(filePath) + + for i = 1, #lines do + if not lines[i]:match("^%s*$") then + if i ~= 1 then + print(filePath .. " starts with empty lines") + + table.insert(fixes, { + File = filePath, + Func = function (lines) + for j = 1, i - 1 do + table.remove(lines, 1) + end + + UpdateFile(filePath, lines) + end + }) + end + + break + end + end + end + + return fixes + end + }) + + table.insert(checks, { + Name = "copyright", + Check = function (moduleName) + local files = table.join( + os.files("include/Nazara/" .. moduleName .. "/**.hpp|Config.hpp"), + os.files("include/Nazara/" .. moduleName .. "/**.inl"), + os.files("src/Nazara/" .. moduleName .. "/**.hpp"), + os.files("src/Nazara/" .. moduleName .. "/**.inl"), + os.files("src/Nazara/" .. moduleName .. "/**.cpp") + ) + + local currentYear = os.date("%Y") + local engineAuthor = "Jérôme \"Lynix\" Leclercq (lynix680@gmail.com)" + local prevAuthor = "Jérôme Leclercq" + local fixes = {} + + local moduleDesc + if #moduleName > 8 and moduleName:endswith("Renderer") then + moduleDesc = moduleName:sub(1, -9) .. " renderer" + else + moduleDesc = moduleName .. " module" + end + + -- Config + do + local configFilePath = path.translate("include/Nazara/" .. moduleName .. "/Config.hpp") + local lines = GetFile(configFilePath) + local pragmaLine + for i = 1, #lines do + if lines[i] == "#pragma once" then + pragmaLine = i + break + end + end + + local canFix = true + + repeat + if not pragmaLine then + print(configFilePath .. ": pragma once not found") + canFix = false + break + end + + local licenseText = [[ + + Permission is hereby granted, free of charge, to any person obtaining a copy of + this software and associated documentation files (the "Software"), to deal in + the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do + so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + +#pragma once]] + + local licenseLines = licenseText:split("\r?\n", { strict = true }) + local shouldFix = false + + -- Try to retrieve year and authors + if lines[1] ~= "/*" then + print(configFilePath .. ": file doesn't begin with block comment") + break + end + + + if lines[2]:match("\tNazara Engine - " .. moduleDesc) then + print(configFilePath .. ": module description doesn't match") + shouldFix = true + end + + if lines[3] ~= "" then + -- Not really empty because of space characters? Not a big deal + if lines[3]:match("^%s*$") then + shouldFix = true + else + print(configFilePath .. ": expected space after project name") + break + end + end + + local year, moduleAuthor = lines[4]:match("^\tCopyright %(C%) (Y?E?A?R?%d*) (.+)$") + if not year then + print(configFilePath .. ": couldn't parse copyright date and author") + break + end + + if year ~= currentYear then + print(configFilePath .. ": incorrect copyright year") + shouldFix = true + end + + local additionalAuthors = {} + for i = 5, #lines do + if lines[i]:match("^%s*$") then + -- Empty line + if not CompareLines(licenseLines, lines, i, pragmaLine - i + 1) then + shouldFix = true + end + + break + end + + if lines[i]:match("%s*Permission is hereby granted") then + print(configFilePath .. ": missing empty line before licence text") + shouldFix = true + break + end + + table.insert(additionalAuthors, lines[i]:match("%s*(.+)")) + end + + if shouldFix then + table.insert(fixes, { + File = configFilePath, + Func = function (lines) + local newLines = { + "/*", + "\tNazara Engine - " .. moduleDesc, + "", + } + + local copyrightNotice = "Copyright (C) " .. currentYear + table.insert(newLines, "\t" .. copyrightNotice .. " " .. moduleAuthor) + + for _, author in ipairs(additionalAuthors) do + table.insert(newLines, "\t" .. string.rep(" ", #copyrightNotice) .. " " .. author) + end + + for _, line in ipairs(licenseLines) do + table.insert(newLines, line) + end + + for i = pragmaLine + 1, #lines do + table.insert(newLines, lines[i]) + end + + UpdateFile(configFilePath, newLines) + end + }) + end + until true + + if not canFix then + print(configFilePath .. ": header couldn't be parsed, no automated fix will be done") + end + end + + -- Headers + for _, filePath in pairs(files) do + local lines = GetFile(filePath) + + local hasCopyright + local shouldFix = false + + if lines[1]:lower():match("^// this file was automatically generated") then + goto skip + end + + local year, authors = lines[1]:match("^// Copyright %(C%) (Y?E?A?R?%d*) (.+)$") + hasCopyright = year ~= nil + + if authors == "AUTHORS" then + authors = engineAuthor + else + local fixedAuthors = authors:gsub(prevAuthor, engineAuthor) + if fixedAuthors ~= authors then + authors = fixedAuthors + shouldFix = true + end + end + + if hasCopyright then + if year ~= currentYear then + print(filePath .. ": copyright year error") + shouldFix = true + end + + local copyrightModule = lines[2]:match("^// This file is part of the \"Nazara Engine %- (.+)\"$") + if copyrightModule ~= moduleDesc then + print(filePath .. ": copyright module error") + shouldFix = true + end + + if lines[3] ~= "// For conditions of distribution and use, see copyright notice in Config.hpp" then + print(filePath .. ": copyright file reference error") + shouldFix = true + end + else + print(filePath .. ": copyright not found") + shouldFix = true + end + + if shouldFix then + table.insert(fixes, { + File = filePath, + Func = function(lines) + local copyrightLines = { + "// Copyright (C) " .. currentYear .. " " .. (authors or engineAuthor), + "// This file is part of the \"Nazara Engine - " .. moduleDesc .. "\"", + "// For conditions of distribution and use, see copyright notice in Config.hpp" + } + + if hasCopyright then + for i, line in ipairs(copyrightLines) do + lines[i] = line + end + else + for i, line in ipairs(copyrightLines) do + table.insert(lines, i, line) + end + table.insert(lines, #copyrightLines + 1, "") + end + + UpdateFile(filePath, lines) + end + }) + end + + ::skip:: + end + + return fixes + end + }) + + local shouldFix = option.get("fix") or false + + for _, check in pairs(checks) do + print("Running " .. check.Name .. " check...") + + local fixes = {} + for _, modulePath in pairs(modules) do + local moduleName = modulePath:match(".*[\\/](.*)") + + table.join2(fixes, check.Check(moduleName)) + end + + if shouldFix then + for _, fix in pairs(fixes) do + print("Fixing " .. fix.File) + fix.Func(assert(fileLines[fix.File])) + end + + for filePath, _ in pairs(updatedFiles) do + local lines = assert(fileLines[filePath]) + if lines[#lines] ~= "" then + table.insert(lines, "") + end + + print("Saving changes to " .. filePath) + io.writefile(filePath, table.concat(lines, "\n")) + end + end + end +end) diff --git a/xmake/actions/generateheaders.lua b/xmake/actions/generateheaders.lua index 872ab4eda..b3eaa16df 100644 --- a/xmake/actions/generateheaders.lua +++ b/xmake/actions/generateheaders.lua @@ -118,7 +118,7 @@ on_run(function () error("Failed to create header file (" .. v.Target .. "): " .. err) end - header:write("// This file was automatically generated\n\n") + header:write("// this file was automatically generated and should not be edited\n\n") if (v.Header) then header:write(v.Header) end diff --git a/xmake/actions/spirv.lua b/xmake/actions/spirv.lua index 4abd23b98..8c1e1f93e 100644 --- a/xmake/actions/spirv.lua +++ b/xmake/actions/spirv.lua @@ -215,7 +215,7 @@ headerFile:write([[ // Copyright (C) ]] .. os.date("%Y") .. [[ Jérôme Leclercq // This file is part of the "Nazara Engine - Shader module" -// For conditions of distribution and use, see copyright notice in Config.hpp" +// For conditions of distribution and use, see copyright notice in Config.hpp #include #include diff --git a/xmake/actions/unicode.lua b/xmake/actions/unicode.lua index 2d5c73884..9fa9b2ca0 100644 --- a/xmake/actions/unicode.lua +++ b/xmake/actions/unicode.lua @@ -182,7 +182,7 @@ local DirectionToString = {} // Copyright (C) ]] .. os.date("%Y") .. [[ Jérôme Leclercq // This file is part of the "Nazara Engine - Core module" -// For conditions of distribution and use, see copyright notice in Config.hpp" +// For conditions of distribution and use, see copyright notice in Config.hpp ]])