• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python3
2# Copyright 2022 The Chromium OS Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6import argparse
7from pathlib import Path
8import re
9import subprocess
10import sys
11
12
13USAGE = """\
14Checks code hygiene of a given directory.
15
16The tool verifies that
17- code under given directory has no conditionally compiled platform specific code.
18- crates in current directory, excluding crates in ./common/, do not depend on
19  on sys_util, sys_util_core or on win_sys_util.
20
21To check
22
23    $ ./tools/impl/check_code_hygiene ./common/sys_util_core
24
25On finding platform specific code, the tool prints the file, line number and the
26line containing conditional compilation.
27On finding dependency on sys_util, sys_util_core or on win_sys_util, the tool prints
28the names of crates.
29"""
30
31
32def has_platform_dependent_code(rootdir: Path):
33    """Recursively searches for target os specific code in the given rootdir.
34        Returns false and relative file path if target specific code is found.
35        Returns false and rootdir if rootdir does not exists or is not a directory.
36        Otherwise returns true and empty string is returned.
37
38    Args:
39        rootdir: Base directory path to search for.
40    """
41
42    if not rootdir.is_dir():
43        return False, "'" + str(rootdir) + "' does not exists or is not a directory"
44
45    cfg_unix = "cfg.*unix"
46    cfg_linux = "cfg.*linux"
47    cfg_windows = "cfg.*windows"
48    cfg_android = "cfg.*android"
49    target_os = "target_os = "
50
51    target_os_pattern = re.compile(
52        "%s|%s|%s|%s|%s" % (cfg_android, cfg_linux, cfg_unix, cfg_windows, target_os)
53    )
54
55    for file_path in rootdir.rglob("**/*.rs"):
56        for line_number, line in enumerate(open(file_path, encoding="utf8")):
57            if re.search(target_os_pattern, line):
58                return False, str(file_path) + ":" + str(line_number) + ":" + line
59    return True, ""
60
61
62def is_sys_util_independent():
63    """Recursively searches for that depend on sys_util, sys_util_core or win_util.
64    Does not search crates in common/ as they are allowed to be platform specific.
65    Returns false and a list of crates that depend on those crates. Otherwise
66    returns true and am empty list.
67
68    """
69
70    crates: list[str] = []
71    sys_util_crates = re.compile("sys_util|sys_util_core|win_sys_util")
72    files: list[Path] = list(Path(".").glob("**/Cargo.toml"))
73    files.extend(Path("src").glob("**/*.rs"))
74
75    # Exclude common as it is allowed to depend on sys_util and exclude Cargo.toml
76    # from root directory as it contains workspace related entries for sys_util.
77    files[:] = [
78        file for file in files if not file.is_relative_to("common") and str(file) != "Cargo.toml"
79    ]
80
81    for file_path in files:
82        with open(file_path) as open_file:
83            for line in open_file:
84                if sys_util_crates.match(line):
85                    crates.append(str(file_path))
86
87    return not crates, crates
88
89
90def has_crlf_line_endings():
91    """Searches for files with crlf(dos) line endings in a git repo. Returns
92    a list of files having crlf line endings.
93
94    """
95    process = subprocess.Popen(
96        "git ls-files --eol",
97        stdout=subprocess.PIPE,
98        stderr=subprocess.STDOUT,
99        text=True,
100        shell=True,
101    )
102
103    stdout, _ = process.communicate()
104    dos_files: list[str] = []
105
106    if process.returncode != 0:
107        return dos_files
108
109    crlf_re = re.compile("crlf|mixed")
110    assert process.stdout
111    for line in iter(stdout.splitlines()):
112        # A typical output of git ls-files --eol looks like below
113        # i/lf    w/lf    attr/                   vhost/Cargo.toml
114        fields = line.split()
115        if fields and crlf_re.search(fields[0] + fields[1]):
116            dos_files.append(fields[3] + "\n")
117
118    return dos_files
119
120
121def main():
122    parser = argparse.ArgumentParser(usage=USAGE)
123    parser.add_argument("path", type=Path, help="Path of the directory to check.")
124    args = parser.parse_args()
125
126    hygiene, error = has_platform_dependent_code(args.path)
127    if not hygiene:
128        print("Error: Platform dependent code not allowed in sys_util_core crate.")
129        print("Offending line: " + error)
130        sys.exit(-1)
131
132    hygiene, crates = is_sys_util_independent()
133    if not hygiene:
134        print("Error: Following files depend on sys_util, sys_util_core or on win_sys_util")
135        print(crates)
136        sys.exit(-1)
137
138    crlf_endings = has_crlf_line_endings()
139    if crlf_endings:
140        print("Error: Following files have crlf(dos) line encodings")
141        print(*crlf_endings)
142        sys.exit(-1)
143
144
145if __name__ == "__main__":
146    main()
147