-
-
Notifications
You must be signed in to change notification settings - Fork 14.1k
tools: add script to sync -O opt-level in rustc manpage with rustc --… #149899
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,197 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| #!/usr/bin/env python3 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # SPDX-License-Identifier: MIT OR Apache-2.0 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| r""" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Update the rustc manpage "-O" description to match `rustc --help`. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Usage (dry-run by default): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ./src/tools/update-rustc-man-opt-level.py --man-file src/doc/man/rustc.1 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Apply changes (creates a timestamped backup): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ./src/tools/update-rustc-man-opt-level.py --man-file src/doc/man/rustc.1 --apply | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Force a level instead of querying rustc: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ./src/tools/update-rustc-man-opt-level.py --man-file ... --expected-level 3 --apply | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from __future__ import annotations | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import argparse | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import datetime | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import difflib | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import shutil | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import subprocess | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import sys | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import re | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from pathlib import Path | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from typing import Tuple | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| DEFAULT_RUSTC = "rustc" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # ANSI color codes | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| _CLR = { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "reset": "\033[0m", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "red": "\033[31m", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "green": "\033[32m", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "yellow": "\033[33m", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "bold": "\033[1m", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def colorize(line: str, color: str, enabled: bool) -> str: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if not enabled or color not in _CLR: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return line | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return f"{_CLR[color]}{line}{_CLR['reset']}" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def get_rustc_opt_level(rustc_cmd: str = DEFAULT_RUSTC) -> int: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """Query `rustc --help` and parse the opt-level mapped to -O.""" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| proc = subprocess.run([rustc_cmd, "--help"], capture_output=True, text=True, check=True) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| except FileNotFoundError: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| raise RuntimeError(f"rustc not found at '{rustc_cmd}'") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| except subprocess.CalledProcessError as e: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| stderr = (e.stderr or "").strip() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| raise RuntimeError(f"rustc --help failed: {stderr or e}") from e | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| help_text = (proc.stdout or "") + "\n" + (proc.stderr or "") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| m = re.search(r'-O[^\n]*opt(?:\\-)?level\s*=\s*(\d+)', help_text, flags=re.IGNORECASE) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if not m: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| m2 = re.search(r'Equivalent to\s+-C\s+opt(?:-)?level\s*=\s*(\d+)', help_text, flags=re.IGNORECASE) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if not m2: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| raise RuntimeError("Could not find '-O' opt-level mapping in rustc --help output") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return int(m2.group(1)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return int(m.group(1)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def find_and_replace_manpage_content(content: str, new_level: int) -> Tuple[str, int]: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Replace opt-level numbers in 'Equivalent to ... opt-level=N.' sentences tied to -O. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Conservative heuristic: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - Locate sentences starting with 'Equivalent to' up to the next period. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - Ensure the sentence mentions opt-level (accepting escaped '\-'). | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - Confirm a -O header appears within a lookback window before the sentence. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| replacements = 0 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| out_parts = [] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| last_index = 0 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| sentence_pattern = re.compile(r'Equivalent to([^\n\.]{0,800}?)\.', flags=re.IGNORECASE) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| sentence_pattern = re.compile(r'Equivalent to([^\n\.]{0,800}?)\.', flags=re.IGNORECASE) | |
| sentence_pattern = re.compile(r'Equivalent to([^\n\.]{0,200}?)\.', flags=re.IGNORECASE) |
Copilot
AI
Dec 12, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Lines 97-103 check for various patterns to detect the "-O" flag in the lookback window, but they don't use an early exit pattern. The condition uses not (A or B or C or D or E) which means all patterns must be checked even if the first one matches. Consider restructuring to if not any([...]) or using early return/continue logic for better readability and potential performance improvement.
| if not ( | |
| re.search(r'(^|\n)\s*-O\b', window) | |
| or re.search(r'\\fB\\-?O\\fR', window) | |
| or re.search(r'\\-O\b', window) | |
| or re.search(r'\.B\s+-O\b', window) | |
| or re.search(r'\\fB-?O\\fP', window) | |
| ): | |
| if not any([ | |
| re.search(r'(^|\n)\s*-O\b', window), | |
| re.search(r'\\fB\\-?O\\fR', window), | |
| re.search(r'\\-O\b', window), | |
| re.search(r'\.B\s+-O\b', window), | |
| re.search(r'\\fB-?O\\fP', window), | |
| ]): |
Copilot
AI
Dec 12, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The replacement logic on line 106 uses count=1 to replace only the first digit in the sentence. However, this could be problematic if the sentence structure changes to include other numbers before the opt-level value (e.g., "Equivalent to -C opt-level=3 (level 3 optimization)"). While unlikely, a more robust approach would be to specifically target the digit immediately following "opt-level=" or use the matched number's position from line 87-90.
| new_sentence = re.sub(r'(\d+)', str(new_level), sentence, count=1) | |
| # Replace only the number immediately following "opt-level=" (or "opt\-level=") | |
| new_sentence = re.sub( | |
| r'(opt(?:\\-)?level\s*=\s*)\d+', | |
| r'\g<1>{}'.format(new_level), | |
| sentence, | |
| count=1, | |
| ) |
Copilot
AI
Dec 12, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The function extracts the first number found in the sentence (line 87-90) and checks if it equals new_level to skip replacement (line 91-92). However, it then uses a generic regex substitution (line 106) that replaces the first digit in the entire sentence. If the sentence structure is unusual and contains digits before "opt-level=", this could cause a mismatch between what is checked and what is replaced. Consider using the span information from num_match to ensure the same number is replaced.
| new_sentence = re.sub(r'(\d+)', str(new_level), sentence, count=1) | |
| # Replace the specific number matched by num_match | |
| num_start, num_end = num_match.span(1) | |
| new_sentence = sentence[:num_start] + str(new_level) + sentence[num_end:] |
Copilot
AI
Dec 12, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The unified diff output in line 119 sets lineterm="" but the generator still yields lines with potential line endings from the splitlines(keepends=True) call. Each line is then manually stripped with rstrip("\n") in the print loop (lines 122-131). This is inconsistent - either use keepends=False and lineterm="\n", or keep keepends=True with lineterm="" but don't strip in the loop. The current approach may cause issues if the file contains \r\n line endings, as only \n is stripped.
| old_lines = old.splitlines(keepends=True) | |
| new_lines = new.splitlines(keepends=True) | |
| diff_iter = difflib.unified_diff(old_lines, new_lines, fromfile=filename, tofile=filename + " (updated)", lineterm="") | |
| for line in diff_iter: | |
| if line.startswith("---") or line.startswith("+++"): | |
| print(colorize(line.rstrip("\n"), "bold", color)) | |
| elif line.startswith("@@"): | |
| print(colorize(line.rstrip("\n"), "yellow", color)) | |
| elif line.startswith("+"): | |
| # avoid coloring the file header lines that also start with +++ | |
| print(colorize(line.rstrip("\n"), "green", color)) | |
| elif line.startswith("-"): | |
| print(colorize(line.rstrip("\n"), "red", color)) | |
| else: | |
| print(line.rstrip("\n")) | |
| old_lines = old.splitlines(keepends=False) | |
| new_lines = new.splitlines(keepends=False) | |
| diff_iter = difflib.unified_diff(old_lines, new_lines, fromfile=filename, tofile=filename + " (updated)", lineterm="\n") | |
| for line in diff_iter: | |
| if line.startswith("---") or line.startswith("+++"): | |
| print(colorize(line, "bold", color)) | |
| elif line.startswith("@@"): | |
| print(colorize(line, "yellow", color)) | |
| elif line.startswith("+"): | |
| # avoid coloring the file header lines that also start with +++ | |
| print(colorize(line, "green", color)) | |
| elif line.startswith("-"): | |
| print(colorize(line, "red", color)) | |
| else: | |
| print(line) |
Copilot
AI
Dec 12, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The backup timestamp format on line 135 uses %Y%m%dT%H%M%S which only includes precision to seconds. If the script is run multiple times within the same second (e.g., in automated tests or rapid iterations), subsequent backups would overwrite earlier ones. Consider adding microseconds (%f) or using a different conflict resolution strategy (e.g., checking if backup exists and adding a counter).
| ts = datetime.datetime.now().strftime("%Y%m%dT%H%M%S") | |
| ts = datetime.datetime.now().strftime("%Y%m%dT%H%M%S%f") |
Copilot
AI
Dec 12, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The error message on line 175 could be more helpful by distinguishing between the two cases: (1) manpage is already up-to-date with the correct opt-level, and (2) the -O entry pattern wasn't found in the manpage. Currently both scenarios produce the same message, making troubleshooting more difficult. Consider tracking whether any "Equivalent to" sentences with opt-level were found at all, regardless of whether they needed updating.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The regex pattern contains an unnecessary escape sequence
\\-in the character class. In the patternr'-O[^\n]*opt(?:\\-)?level\s*=\s*(\d+)', the(?:\\-)?part is attempting to match an optional hyphen in "opt-level". However, the backslash escape is only needed in the replacement context (for manpage formatting), not when parsing plain text output fromrustc --help. The pattern should use(?:-)?instead to match an optional literal hyphen.