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 glob
8import shutil
9import errno
10
11import ThirdPartyPaths
12import ThreadAllows
13
14
15def copy_dir_contents(src, dest):
16    for f in glob.glob("%s/*" % src):
17        try:
18            destname = "%s/%s" % (dest, os.path.basename(f))
19            if os.path.isdir(f):
20                shutil.copytree(f, destname)
21            else:
22                shutil.copy2(f, destname)
23        except OSError as e:
24            if e.errno == errno.ENOTDIR:
25                shutil.copy2(f, destname)
26            elif e.errno == errno.EEXIST:
27                if os.path.isdir(f):
28                    copy_dir_contents(f, destname)
29                else:
30                    os.remove(destname)
31                    shutil.copy2(f, destname)
32            else:
33                raise Exception("Directory not copied. Error: %s" % e)
34
35
36def write_cmake(module_path, import_options):
37    names = ["  " + os.path.basename(f) for f in glob.glob("%s/*.cpp" % module_path)]
38
39    if import_options["external"]:
40        names += [
41            "  " + os.path.join("external", os.path.basename(f))
42            for f in glob.glob("%s/external/*.cpp" % (module_path))
43        ]
44
45    if import_options["alpha"]:
46        names += [
47            "  " + os.path.join("alpha", os.path.basename(f))
48            for f in glob.glob("%s/alpha/*.cpp" % (module_path))
49        ]
50
51    with open(os.path.join(module_path, "CMakeLists.txt"), "w") as f:
52        f.write(
53            """set(LLVM_LINK_COMPONENTS support)
54
55add_definitions( -DCLANG_TIDY )
56
57add_clang_library(clangTidyMozillaModule
58  ThirdPartyPaths.cpp
59%(names)s
60
61  LINK_LIBS
62  clangAST
63  clangASTMatchers
64  clangBasic
65  clangLex
66  clangTidy
67  clangTidyReadabilityModule
68  clangTidyUtils
69  clangTidyMPIModule
70  )"""
71            % {"names": "\n".join(names)}
72        )
73
74
75def add_moz_module(cmake_path):
76    with open(cmake_path, "r") as f:
77        lines = f.readlines()
78    f.close()
79
80    try:
81        idx = lines.index("set(ALL_CLANG_TIDY_CHECKS\n")
82        lines.insert(idx + 1, "  clangTidyMozillaModule\n")
83
84        with open(cmake_path, "w") as f:
85            for line in lines:
86                f.write(line)
87    except ValueError:
88        raise Exception("Unable to find ALL_CLANG_TIDY_CHECKS in {}".format(cmake_path))
89
90
91def write_third_party_paths(mozilla_path, module_path):
92    tpp_txt = os.path.join(mozilla_path, "../../tools/rewriting/ThirdPartyPaths.txt")
93    generated_txt = os.path.join(mozilla_path, "../../tools/rewriting/Generated.txt")
94    with open(os.path.join(module_path, "ThirdPartyPaths.cpp"), "w") as f:
95        ThirdPartyPaths.generate(f, tpp_txt, generated_txt)
96
97
98def generate_thread_allows(mozilla_path, module_path):
99    names = os.path.join(mozilla_path, "../../build/clang-plugin/ThreadAllows.txt")
100    files = os.path.join(mozilla_path, "../../build/clang-plugin/ThreadFileAllows.txt")
101    with open(os.path.join(module_path, "ThreadAllows.h"), "w") as f:
102        f.write(ThreadAllows.generate_allows({files, names}))
103
104
105def do_import(mozilla_path, clang_tidy_path, import_options):
106    module = "mozilla"
107    module_path = os.path.join(clang_tidy_path, module)
108    try:
109        os.makedirs(module_path)
110    except OSError as e:
111        if e.errno != errno.EEXIST:
112            raise
113
114    copy_dir_contents(mozilla_path, module_path)
115    write_third_party_paths(mozilla_path, module_path)
116    generate_thread_allows(mozilla_path, module_path)
117    write_cmake(module_path, import_options)
118    add_moz_module(os.path.join(module_path, "..", "CMakeLists.txt"))
119    with open(os.path.join(module_path, "..", "CMakeLists.txt"), "a") as f:
120        f.write("add_subdirectory(%s)\n" % module)
121    # A better place for this would be in `ClangTidyForceLinker.h` but `ClangTidyMain.cpp`
122    # is also OK.
123    with open(os.path.join(module_path, "..", "tool", "ClangTidyMain.cpp"), "a") as f:
124        f.write(
125            """
126// This anchor is used to force the linker to link the MozillaModule.
127extern volatile int MozillaModuleAnchorSource;
128static int LLVM_ATTRIBUTE_UNUSED MozillaModuleAnchorDestination =
129          MozillaModuleAnchorSource;
130"""
131        )
132
133
134def main():
135    import argparse
136
137    parser = argparse.ArgumentParser(
138        usage="import_mozilla_checks.py <mozilla-clang-plugin-path> <clang-tidy-path> [option]",
139        description="Imports the Mozilla static analysis checks into a clang-tidy source tree.",
140    )
141    parser.add_argument(
142        "mozilla_path", help="Full path to mozilla-central/build/clang-plugin"
143    )
144    parser.add_argument(
145        "clang_tidy_path", help="Full path to llvm-project/clang-tools-extra/clang-tidy"
146    )
147    parser.add_argument(
148        "--import-alpha",
149        help="Enable import of in-tree alpha checks",
150        action="store_true",
151    )
152    parser.add_argument(
153        "--import-external",
154        help="Enable import of in-tree external checks",
155        action="store_true",
156    )
157    args = parser.parse_args()
158
159    if not os.path.isdir(args.mozilla_path):
160        print("Invalid path to mozilla clang plugin")
161
162    if not os.path.isdir(args.clang_tidy_path):
163        print("Invalid path to clang-tidy source directory")
164
165    import_options = {"alpha": args.import_alpha, "external": args.import_external}
166
167    do_import(args.mozilla_path, args.clang_tidy_path, import_options)
168
169
170if __name__ == "__main__":
171    main()
172