1#!/usr/bin/python3
2# This Source Code Form is subject to the terms of the Mozilla Public
3# License, v. 2.0. If a copy of the MPL was not distributed with this
4# file, You can obtain one at http://mozilla.org/MPL/2.0/.
5
6import os
7import sys
8import glob
9import shutil
10import errno
11
12import ThirdPartyPaths
13import ThreadAllows
14
15
16def copy_dir_contents(src, dest):
17    for f in glob.glob("%s/*" % src):
18        try:
19            destname = "%s/%s" % (dest, os.path.basename(f))
20            if os.path.isdir(f):
21                shutil.copytree(f, destname)
22            else:
23                shutil.copy2(f, destname)
24        except OSError as e:
25            if e.errno == errno.ENOTDIR:
26                shutil.copy2(f, destname)
27            elif e.errno == errno.EEXIST:
28                if os.path.isdir(f):
29                    copy_dir_contents(f, destname)
30                else:
31                    os.remove(destname)
32                    shutil.copy2(f, destname)
33            else:
34                raise Exception('Directory not copied. Error: %s' % e)
35
36
37def write_cmake(module_path, import_alpha):
38    names = ['  ' + os.path.basename(f) for f in glob.glob("%s/*.cpp" % module_path)]
39    names += ['  ' + os.path.basename(f) for f in glob.glob("%s/external/*.cpp" % module_path)]
40    if import_alpha:
41        alpha_names = ['  ' + os.path.join("alpha", os.path.basename(f))
42                       for f in glob.glob("%s/alpha/*.cpp" % module_path)]
43        names += alpha_names
44    with open(os.path.join(module_path, 'CMakeLists.txt'), 'w') as f:
45        f.write("""set(LLVM_LINK_COMPONENTS support)
46
47add_definitions( -DCLANG_TIDY )
48
49add_clang_library(clangTidyMozillaModule
50  ThirdPartyPaths.cpp
51%(names)s
52
53  LINK_LIBS
54  clangAST
55  clangASTMatchers
56  clangBasic
57  clangLex
58  clangTidy
59  clangTidyReadabilityModule
60  clangTidyUtils
61  clangTidyMPIModule
62  )""" % {'names': "\n".join(names)})
63
64
65def add_moz_module(cmake_path):
66    with open(cmake_path, 'r') as f:
67        lines = f.readlines()
68    f.close()
69
70    try:
71        idx = lines.index('set(ALL_CLANG_TIDY_CHECKS\n')
72        lines.insert(idx + 1, '  clangTidyMozillaModule\n')
73
74        with open(cmake_path, 'w') as f:
75            for line in lines:
76                f.write(line)
77    except ValueError:
78        raise Exception('Unable to find ALL_CLANG_TIDY_CHECKS in {}'.format(cmake_path))
79
80
81def write_third_party_paths(mozilla_path, module_path):
82    tpp_txt = os.path.join(
83        mozilla_path, '../../tools/rewriting/ThirdPartyPaths.txt')
84    generated_txt = os.path.join(
85        mozilla_path, '../../tools/rewriting/Generated.txt')
86    with open(os.path.join(module_path, 'ThirdPartyPaths.cpp'), 'w') as f:
87        ThirdPartyPaths.generate(f, tpp_txt, generated_txt)
88
89
90def generate_thread_allows(mozilla_path, module_path):
91    names = os.path.join(
92        mozilla_path, '../../build/clang-plugin/ThreadAllows.txt'
93    )
94    files = os.path.join(
95        mozilla_path, '../../build/clang-plugin/ThreadFileAllows.txt'
96    )
97    with open(os.path.join(module_path, 'ThreadAllows.h'), 'w') as f:
98        f.write(ThreadAllows.generate_allows({files, names}))
99
100
101def do_import(mozilla_path, clang_tidy_path, import_alpha):
102    module = 'mozilla'
103    module_path = os.path.join(clang_tidy_path, module)
104    try:
105        os.makedirs(module_path)
106    except OSError as e:
107        if e.errno != errno.EEXIST:
108            raise
109
110    copy_dir_contents(mozilla_path, module_path)
111    write_third_party_paths(mozilla_path, module_path)
112    generate_thread_allows(mozilla_path, module_path)
113    write_cmake(module_path, import_alpha)
114    add_moz_module(os.path.join(module_path, '..', 'CMakeLists.txt'))
115    with open(os.path.join(module_path, '..', 'CMakeLists.txt'), 'a') as f:
116        f.write('add_subdirectory(%s)\n' % module)
117    # A better place for this would be in `ClangTidyForceLinker.h` but `ClangTidyMain.cpp`
118    # is also OK.
119    with open(os.path.join(module_path, '..', 'tool', 'ClangTidyMain.cpp'), 'a') as f:
120        f.write('''
121// This anchor is used to force the linker to link the MozillaModule.
122extern volatile int MozillaModuleAnchorSource;
123static int LLVM_ATTRIBUTE_UNUSED MozillaModuleAnchorDestination =
124          MozillaModuleAnchorSource;
125''')
126
127
128def main():
129    if len(sys.argv) < 3 or len(sys.argv) > 4:
130        print("""\
131Usage: import_mozilla_checks.py <mozilla-clang-plugin-path> <clang-tidy-path> [import_alpha]
132Imports the Mozilla static analysis checks into a clang-tidy source tree.
133If `import_alpha` is specified then in-tree alpha checkers will be also imported.
134""")
135
136        return
137
138    mozilla_path = sys.argv[1]
139    if not os.path.isdir(mozilla_path):
140        print("Invalid path to mozilla clang plugin")
141
142    clang_tidy_path = sys.argv[2]
143    if not os.path.isdir(mozilla_path):
144        print("Invalid path to clang-tidy source directory")
145
146    import_alpha = False
147
148    if len(sys.argv) == 4 and sys.argv[3] == 'import_alpha':
149        import_alpha = True
150
151    do_import(mozilla_path, clang_tidy_path, import_alpha)
152
153
154if __name__ == '__main__':
155    main()
156