123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353 |
- #!/usr/bin/env python3
- # ***** BEGIN GPL LICENSE BLOCK *****
- #
- # This program is free software; you can redistribute it and/or
- # modify it under the terms of the GNU General Public License
- # as published by the Free Software Foundation; either version 2
- # of the License, or (at your option) any later version.
- #
- # This program is distributed in the hope that it will be useful,
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- # GNU General Public License for more details.
- #
- # You should have received a copy of the GNU General Public License
- # along with this program; if not, write to the Free Software Foundation,
- # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- #
- # ***** END GPL LICENSE BLOCK *****
- # <pep8 compliant>
- import sys
- if not sys.version.startswith("3"):
- print("\nPython3.x needed, found %s.\nAborting!\n" %
- sys.version.partition(" ")[0])
- sys.exit(1)
- from cmake_consistency_check_config import (
- IGNORE_SOURCE,
- IGNORE_CMAKE,
- UTF8_CHECK,
- SOURCE_DIR,
- BUILD_DIR,
- )
- import os
- from os.path import join, dirname, normpath, splitext
- global_h = set()
- global_c = set()
- global_refs = {}
- def replace_line(f, i, text, keep_indent=True):
- file_handle = open(f, 'r')
- data = file_handle.readlines()
- file_handle.close()
- l = data[i]
- ws = l[:len(l) - len(l.lstrip())]
- data[i] = "%s%s\n" % (ws, text)
- file_handle = open(f, 'w')
- file_handle.writelines(data)
- file_handle.close()
- def source_list(path, filename_check=None):
- for dirpath, dirnames, filenames in os.walk(path):
- # skip '.git'
- dirnames[:] = [d for d in dirnames if not d.startswith(".")]
- for filename in filenames:
- if filename_check is None or filename_check(filename):
- yield os.path.join(dirpath, filename)
- # extension checking
- def is_cmake(filename):
- ext = splitext(filename)[1]
- return (ext == ".cmake") or (filename == "CMakeLists.txt")
- def is_c_header(filename):
- ext = splitext(filename)[1]
- return (ext in {".h", ".hpp", ".hxx", ".hh"})
- def is_c(filename):
- ext = splitext(filename)[1]
- return (ext in {".c", ".cpp", ".cxx", ".m", ".mm", ".rc", ".cc", ".inl"})
- def is_c_any(filename):
- return is_c(filename) or is_c_header(filename)
- def cmake_get_src(f):
- sources_h = []
- sources_c = []
- filen = open(f, "r", encoding="utf8")
- it = iter(filen)
- found = False
- i = 0
- # print(f)
- def is_definition(l, f, i, name):
- if l.startswith("unset("):
- return False
- if ('set(%s' % name) in l or ('set(' in l and l.endswith(name)):
- if len(l.split()) > 1:
- raise Exception("strict formatting not kept 'set(%s*' %s:%d" % (name, f, i))
- return True
- if ("list(APPEND %s" % name) in l or ('list(APPEND ' in l and l.endswith(name)):
- if l.endswith(")"):
- raise Exception("strict formatting not kept 'list(APPEND %s...)' on 1 line %s:%d" % (name, f, i))
- return True
- while it is not None:
- context_name = ""
- while it is not None:
- i += 1
- try:
- l = next(it)
- except StopIteration:
- it = None
- break
- l = l.strip()
- if not l.startswith("#"):
- found = is_definition(l, f, i, "SRC")
- if found:
- context_name = "SRC"
- break
- found = is_definition(l, f, i, "INC")
- if found:
- context_name = "INC"
- break
- if found:
- cmake_base = dirname(f)
- cmake_base_bin = os.path.join(BUILD_DIR, os.path.relpath(cmake_base, SOURCE_DIR))
- while it is not None:
- i += 1
- try:
- l = next(it)
- except StopIteration:
- it = None
- break
- l = l.strip()
- if not l.startswith("#"):
- if ")" in l:
- if l.strip() != ")":
- raise Exception("strict formatting not kept '*)' %s:%d" % (f, i))
- break
- # replace dirs
- l = l.replace("${CMAKE_SOURCE_DIR}", SOURCE_DIR)
- l = l.replace("${CMAKE_CURRENT_SOURCE_DIR}", cmake_base)
- l = l.replace("${CMAKE_CURRENT_BINARY_DIR}", cmake_base_bin)
- l = l.strip('"')
- if not l:
- pass
- elif l.startswith("$"):
- if context_name == "SRC":
- # assume if it ends with context_name we know about it
- if not l.split("}")[0].endswith(context_name):
- print("Can't use var '%s' %s:%d" % (l, f, i))
- elif len(l.split()) > 1:
- raise Exception("Multi-line define '%s' %s:%d" % (l, f, i))
- else:
- new_file = normpath(join(cmake_base, l))
- if context_name == "SRC":
- if is_c_header(new_file):
- sources_h.append(new_file)
- global_refs.setdefault(new_file, []).append((f, i))
- elif is_c(new_file):
- sources_c.append(new_file)
- global_refs.setdefault(new_file, []).append((f, i))
- elif l in {"PARENT_SCOPE", }:
- # cmake var, ignore
- pass
- elif new_file.endswith(".list"):
- pass
- elif new_file.endswith(".def"):
- pass
- elif new_file.endswith(".cl"): # opencl
- pass
- elif new_file.endswith(".cu"): # cuda
- pass
- elif new_file.endswith(".osl"): # open shading language
- pass
- elif new_file.endswith(".glsl"):
- pass
- else:
- raise Exception("unknown file type - not c or h %s -> %s" % (f, new_file))
- elif context_name == "INC":
- if new_file.startswith(BUILD_DIR):
- # assume generated path
- pass
- elif os.path.isdir(new_file):
- new_path_rel = os.path.relpath(new_file, cmake_base)
- if new_path_rel != l:
- print("overly relative path:\n %s:%d\n %s\n %s" % (f, i, l, new_path_rel))
- # # Save time. just replace the line
- # replace_line(f, i - 1, new_path_rel)
- else:
- raise Exception("non existent include %s:%d -> %s" % (f, i, new_file))
- # print(new_file)
- global_h.update(set(sources_h))
- global_c.update(set(sources_c))
- '''
- if not sources_h and not sources_c:
- raise Exception("No sources %s" % f)
- sources_h_fs = list(source_list(cmake_base, is_c_header))
- sources_c_fs = list(source_list(cmake_base, is_c))
- '''
- # find missing C files:
- '''
- for ff in sources_c_fs:
- if ff not in sources_c:
- print(" missing: " + ff)
- '''
- # reset
- del sources_h[:]
- del sources_c[:]
- filen.close()
- def is_ignore_source(f, ignore_used):
- for index, ig in enumerate(IGNORE_SOURCE):
- if ig in f:
- ignore_used[index] = True
- return True
- return False
- def is_ignore_cmake(f, ignore_used):
- for index, ig in enumerate(IGNORE_CMAKE):
- if ig in f:
- ignore_used[index] = True
- return True
- return False
- def main():
- print("Scanning:", SOURCE_DIR)
- ignore_used_source = [False] * len(IGNORE_SOURCE)
- ignore_used_cmake = [False] * len(IGNORE_CMAKE)
- for cmake in source_list(SOURCE_DIR, is_cmake):
- if not is_ignore_cmake(cmake, ignore_used_cmake):
- cmake_get_src(cmake)
- # First do stupid check, do these files exist?
- print("\nChecking for missing references:")
- is_err = False
- errs = []
- for f in (global_h | global_c):
- if f.startswith(BUILD_DIR):
- continue
- if not os.path.exists(f):
- refs = global_refs[f]
- if refs:
- for cf, i in refs:
- errs.append((cf, i))
- else:
- raise Exception("CMake referenecs missing, internal error, aborting!")
- is_err = True
- errs.sort()
- errs.reverse()
- for cf, i in errs:
- print("%s:%d" % (cf, i))
- # Write a 'sed' script, useful if we get a lot of these
- # print("sed '%dd' '%s' > '%s.tmp' ; mv '%s.tmp' '%s'" % (i, cf, cf, cf, cf))
- if is_err:
- raise Exception("CMake referenecs missing files, aborting!")
- del is_err
- del errs
- # now check on files not accounted for.
- print("\nC/C++ Files CMake does not know about...")
- for cf in sorted(source_list(SOURCE_DIR, is_c)):
- if not is_ignore_source(cf, ignore_used_source):
- if cf not in global_c:
- print("missing_c: ", cf)
- # check if automake builds a corrasponding .o file.
- '''
- if cf in global_c:
- out1 = os.path.splitext(cf)[0] + ".o"
- out2 = os.path.splitext(cf)[0] + ".Po"
- out2_dir, out2_file = out2 = os.path.split(out2)
- out2 = os.path.join(out2_dir, ".deps", out2_file)
- if not os.path.exists(out1) and not os.path.exists(out2):
- print("bad_c: ", cf)
- '''
- print("\nC/C++ Headers CMake does not know about...")
- for hf in sorted(source_list(SOURCE_DIR, is_c_header)):
- if not is_ignore_source(hf, ignore_used_source):
- if hf not in global_h:
- print("missing_h: ", hf)
- if UTF8_CHECK:
- # test encoding
- import traceback
- for files in (global_c, global_h):
- for f in sorted(files):
- if os.path.exists(f):
- # ignore outside of our source tree
- if "extern" not in f:
- i = 1
- try:
- for l in open(f, "r", encoding="utf8"):
- i += 1
- except UnicodeDecodeError:
- print("Non utf8: %s:%d" % (f, i))
- if i > 1:
- traceback.print_exc()
- # Check ignores aren't stale
- print("\nCheck for unused 'IGNORE_SOURCE' paths...")
- for index, ig in enumerate(IGNORE_SOURCE):
- if not ignore_used_source[index]:
- print("unused ignore: %r" % ig)
- # Check ignores aren't stale
- print("\nCheck for unused 'IGNORE_CMAKE' paths...")
- for index, ig in enumerate(IGNORE_CMAKE):
- if not ignore_used_cmake[index]:
- print("unused ignore: %r" % ig)
- if __name__ == "__main__":
- main()
|