aboutsummaryrefslogtreecommitdiff
path: root/bolt/utils/nfc-check-setup.py
blob: d8666e2158499dac38d4dd50d8ab5fd2c3f2c2c7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
#!/usr/bin/env python3
import argparse
import os
import re
import shlex
import subprocess
import sys
import textwrap

msg_prefix = "\n> NFC-Mode:"

def get_relevant_bolt_changes(dir: str) -> str:
    # Return a list of bolt source changes that are relevant to testing.
    all_changes = subprocess.run(
        shlex.split("git show HEAD --name-only --pretty=''"),
        cwd=dir,
        text=True,
        stdout=subprocess.PIPE,
    )
    keep_bolt = subprocess.run(
        shlex.split("grep '^bolt'"),
        input=all_changes.stdout,
        text=True,
        stdout=subprocess.PIPE,
    )
    keep_relevant = subprocess.run(
        shlex.split(
            "grep -v -e '^bolt/docs' -e '^bolt/utils/docker' -e '^bolt/utils/dot2html'"
        ),
        input=keep_bolt.stdout,
        text=True,
        stdout=subprocess.PIPE,
    )
    return keep_relevant.stdout

def get_git_ref_or_rev(dir: str) -> str:
    # Run 'git symbolic-ref -q --short HEAD || git rev-parse --short HEAD'
    cmd_ref = "git symbolic-ref -q --short HEAD"
    ref = subprocess.run(
        shlex.split(cmd_ref), cwd=dir, text=True, stdout=subprocess.PIPE
    )
    if not ref.returncode:
        return ref.stdout.strip()
    cmd_rev = "git rev-parse --short HEAD"
    return subprocess.check_output(shlex.split(cmd_rev), cwd=dir, text=True).strip()

def switch_back(
    switch_back: bool, stash: bool, source_dir: str, old_ref: str, new_ref: str
):
    # Switch back to the current revision if needed and inform the user of where
    # the HEAD is. Must be called after checking out the previous commit on all
    # exit paths.
    if switch_back:
        print(f"{msg_prefix} Switching back to current revision..")
        if stash:
            subprocess.run(shlex.split("git stash pop"), cwd=source_dir)
        subprocess.run(shlex.split(f"git checkout {old_ref}"), cwd=source_dir)
    else:
        print(
            f"The repository {source_dir} has been switched from {old_ref} "
            f"to {new_ref}. Local changes were stashed. Switch back using\n\t"
            f"git checkout {old_ref}\n"
        )

def main():
    parser = argparse.ArgumentParser(
        description=textwrap.dedent(
            """
            This script builds two versions of BOLT:
            llvm-bolt.new, using the current revision, and llvm-bolt.old using
            the previous revision. These can be used to check whether the
            current revision changes BOLT's functional behavior.
            """
        )
    )
    parser.add_argument(
        "build_dir",
        nargs="?",
        default=os.getcwd(),
        help="Path to BOLT build directory, default is current " "directory",
    )
    parser.add_argument(
        "--create-wrapper",
        default=False,
        action="store_true",
        help="Sets up llvm-bolt as a symlink to llvm-bolt-wrapper. Passes the options through to llvm-bolt-wrapper.",
    )
    parser.add_argument(
        "--check-bolt-sources",
        default=False,
        action="store_true",
        help="Create a marker file (.llvm-bolt.changes) if any relevant BOLT sources are modified",
    )
    parser.add_argument(
        "--switch-back",
        default=False,
        action="store_true",
        help="Checkout back to the starting revision",
    )
    parser.add_argument(
        "--cmp-rev",
        default="HEAD^",
        help="Revision to checkout to compare vs HEAD",
    )

    # When creating a wrapper, pass any unknown arguments to it. Otherwise, die.
    args, wrapper_args = parser.parse_known_args()
    if not args.create_wrapper and len(wrapper_args) > 0:
        parser.parse_args()

    # Find the repo directory.
    source_dir = None
    try:
        CMCacheFilename = f"{args.build_dir}/CMakeCache.txt"
        with open(CMCacheFilename) as f:
            for line in f:
                m = re.match(r"LLVM_SOURCE_DIR:STATIC=(.*)", line)
                if m:
                    source_dir = m.groups()[0]
        if not source_dir:
            raise Exception(f"Source directory not found: '{CMCacheFilename}'")
    except Exception as e:
        sys.exit(e)

    # Clean the previous llvm-bolt if it exists.
    bolt_path = f"{args.build_dir}/bin/llvm-bolt"
    if os.path.exists(bolt_path):
        os.remove(bolt_path)

    # Build the current commit.
    print(f"{msg_prefix} Building current revision..")
    subprocess.run(
        shlex.split("cmake --build . --target llvm-bolt"), cwd=args.build_dir
    )

    if not os.path.exists(bolt_path):
        sys.exit(f"Failed to build the current revision: '{bolt_path}'")

    # Rename llvm-bolt and memorize the old hash for logging.
    os.replace(bolt_path, f"{bolt_path}.new")
    old_ref = get_git_ref_or_rev(source_dir)

    if args.check_bolt_sources:
        marker = f"{args.build_dir}/.llvm-bolt.changes"
        if os.path.exists(marker):
            os.remove(marker)
        file_changes = get_relevant_bolt_changes(source_dir)
        # Create a marker file if any relevant BOLT source files changed.
        if len(file_changes) > 0:
            print(f"BOLT source changes were found:\n{file_changes}")
            open(marker, "a").close()

    # Determine whether a stash is needed.
    stash = subprocess.run(
        shlex.split("git status --porcelain"),
        cwd=source_dir,
        stdout=subprocess.PIPE,
        stderr=subprocess.STDOUT,
        text=True,
    ).stdout
    if stash:
        # Save local changes before checkout.
        subprocess.run(shlex.split("git stash push -u"), cwd=source_dir)

    # Check out the previous/cmp commit and get its commit hash for logging.
    subprocess.run(shlex.split(f"git checkout -f {args.cmp_rev}"), cwd=source_dir)
    new_ref = get_git_ref_or_rev(source_dir)

    # Build the previous commit.
    print(f"{msg_prefix} Building previous revision..")
    subprocess.run(
        shlex.split("cmake --build . --target llvm-bolt"), cwd=args.build_dir
    )

    # Rename llvm-bolt.
    if not os.path.exists(bolt_path):
        print(f"Failed to build the previous revision: '{bolt_path}'")
        switch_back(args.switch_back, stash, source_dir, old_ref, new_ref)
        sys.exit(1)
    os.replace(bolt_path, f"{bolt_path}.old")

    # Symlink llvm-bolt-wrapper
    if args.create_wrapper:
        print(f"{msg_prefix} Creating llvm-bolt wrapper..")
        script_dir = os.path.dirname(os.path.abspath(__file__))
        wrapper_path = f"{script_dir}/llvm-bolt-wrapper.py"
        try:
            # Set up llvm-bolt-wrapper.ini
            ini = subprocess.check_output(
                shlex.split(f"{wrapper_path} {bolt_path}.old {bolt_path}.new")
                + wrapper_args,
                text=True,
            )
            with open(f"{args.build_dir}/bin/llvm-bolt-wrapper.ini", "w") as f:
                f.write(ini)
            os.symlink(wrapper_path, bolt_path)
        except Exception as e:
            print("Failed to create a wrapper:\n" + str(e))
            switch_back(args.switch_back, stash, source_dir, old_ref, new_ref)
            sys.exit(1)

    switch_back(args.switch_back, stash, source_dir, old_ref, new_ref)

    print(
        f"{msg_prefix} Completed!\nBuild directory {args.build_dir} is ready for"
        " NFC-Mode comparison between the two revisions."
    )

    if args.create_wrapper:
        print(
            "Can run BOLT tests using:\n"
            "\tbin/llvm-lit -sv tools/bolt/test\nor\n"
            "\tbin/llvm-lit -sv tools/bolttests"
        )


if __name__ == "__main__":
    main()