1#!/usr/bin/env python3 2# -- coding: utf-8 -- 3# Copyright (c) 2022-2024 Huawei Device Co., Ltd. 4# Licensed under the Apache License, Version 2.0 (the "License"); 5# you may not use this file except in compliance with the License. 6# You may obtain a copy of the License at 7# 8# http://www.apache.org/licenses/LICENSE-2.0 9# 10# Unless required by applicable law or agreed to in writing, software 11# distributed under the License is distributed on an "AS IS" BASIS, 12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13# See the License for the specific language governing permissions and 14# limitations under the License. 15 16# cmake-checker.py checks that panda wrappers are used instead of standard 17# cmake functions. 18# cmake-checker.py <DIRECTORY> [TEST] 19# - <DIRECTORY> 20# Root directory for checking 21# - [TEST] 22# Test that cmake-checker work properly 23 24import os 25import re 26import sys 27import tempfile 28import shutil 29import subprocess 30 31# List of functions to search for 32function_list = [("add_library", "panda_add_library"), 33 ("add_executable", "panda_add_executable"), 34 ("target_link_libraries", "panda_target_link_libraries"), 35 ("target_include_directories", "panda_target_include_directories"), 36 ("target_compile_options", "panda_target_compile_options"), 37 ("target_compile_definitions", "panda_target_compile_definitions"), 38 ("target_sources", "panda_target_sources")] 39 40ERROR_MESSAGE = "Found restricted native CMake function usage:" 41 42 43def run_cmake_checker(directory): 44 # don't search in following files and folders 45 ignore_files = ["cmake/PandaCmakeFunctions.cmake", "cmake/Sanitizers.cmake"] 46 ignore_files = [os.path.join(directory, s) for s in ignore_files] 47 ignore_paths = ["third_party"] 48 49 # search in following third_party folders 50 white_list_paths = ["cmake/third_party"] 51 52 cmake_files = [] 53 54 # Fetch all cmake files in directory 55 for root, dirs, files in os.walk(directory, followlinks=True): 56 ignore = False 57 for white_path in white_list_paths: 58 # Check if root directory matches any white list path 59 if root.startswith(os.path.join(directory, white_path)): 60 continue 61 else: 62 # Check if root directory matches any ignore path 63 for ignore_path in ignore_paths: 64 if root.startswith(os.path.join(directory, ignore_path)) or "third_party" in root: 65 ignore = True 66 dirs.clear() 67 break 68 69 if ignore: 70 continue 71 72 for file in files: 73 if file == "CMakeLists.txt" or file.endswith(".cmake") and os.path.join(root, file) not in ignore_files: 74 cmake_files.append(os.path.join(root, file)) 75 76 error = False 77 # Grep for function in each cmake file 78 for file in cmake_files: 79 with open(file, 'r') as f: 80 lines = f.readlines() 81 line_number = 1 82 for line in lines: 83 for function_name in function_list: 84 if re.search(r'\b{}\b'.format(function_name[0]), line.split('#')[0]): 85 if not error: 86 print(ERROR_MESSAGE) 87 error = True 88 print(" {} instead of {} at {}:{}".format(function_name[0], function_name[1], 89 os.path.relpath(file, directory), line_number), file=sys.stderr) 90 line_number += 1 91 92 if error: 93 exit(1) 94 95 print("cmake-checker passed successfully!") 96 97 98# create file that uses standard cmake functions and check that cmake_checker will find them 99def test_cmake_checker(directory): 100 source_file = os.path.join(directory, "CMakeLists.txt") 101 102 temp_dir = tempfile.mkdtemp(dir=directory) 103 temp_file = os.path.join(temp_dir, "CMakeLists.txt") 104 105 try: 106 shutil.copy(source_file, temp_file) 107 108 with open(temp_file, 'r') as file: 109 content = file.read() 110 111 # replace wrappers with standard cmake functions and add standard functions in the end 112 # in case we don't have them in main CMakeLists.txt 113 for function in function_list: 114 content = content.replace(function[1], function[0]) 115 content = f"{content}{function[0]}\n" 116 117 with os.fdopen(os.open(temp_file, os.O_RDWR | os.O_CREAT, 0o755), 'w') as file: 118 file.write(content) 119 120 args = [sys.argv[0], directory] 121 process = subprocess.run(args, capture_output=True) 122 123 if process.returncode == 1 and ERROR_MESSAGE in process.stdout.decode(): 124 print("test-cmake-checker passed successfully!") 125 else: 126 sys.exit("Failed: cmake-checker doesn't work properly.") 127 128 finally: 129 shutil.rmtree(temp_dir) 130 131if __name__ == "__main__": 132 arg_directory = sys.argv[1] 133 if len(sys.argv) == 3: 134 test_cmake_checker(arg_directory) 135 else: 136 run_cmake_checker(arg_directory) 137