Repository navigation
C++20 modules: support gnu emulation modes and extended floating-point types #315
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
4ab83b7
2856df7
d58d9f0
c23faa6
8289ec9
3d96ddb
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 |
|---|---|---|
|
|
@@ -41,30 +41,36 @@ | |
| import subprocess | ||
| import sys | ||
| import tempfile | ||
| from pathlib import Path | ||
|
|
||
| import edgutil | ||
| # Git invokes this hook with the ambient environment, which does not | ||
| # necessarily have PYTHONPATH configured (e.g., on Windows). Locate | ||
| # "dev_tools/pylibs" relative to this script so "edgutil" can be imported. | ||
| sys.path.insert( | ||
| 0, str(Path(__file__).resolve().parents[1] / 'dev_tools' / 'pylibs') | ||
| ) | ||
|
|
||
| from pathlib import Path | ||
| import edgutil | ||
|
|
||
| if platform.system() == 'Windows': | ||
| # On Windows we need to use the kernel API to detect if this script is | ||
| # running in a standalone console window (if so, there will be only one | ||
| # processes at this point). | ||
| import ctypes | ||
|
|
||
| kernel32 = ctypes.WinDLL('kernel32', use_last_error = True) | ||
| process_array = (ctypes.c_uint * 2)() | ||
| num_processes = kernel32.GetConsoleProcessList(process_array, 2) | ||
| if num_processes != 1: | ||
| # When not running in a standalone console window, the script relaunches | ||
| # itself to make sure the user has an interactive shell. Then it forwards | ||
| # the exit code from the delegate process. | ||
| # The standard input of this hook carries the push ref list supplied by | ||
| # git, so it cannot be used to prompt the user. When a console is | ||
| # attached, open the console input device directly ("CONIN$" is the | ||
| # Windows counterpart of "/dev/tty") so that all prompts and output stay | ||
| # in the user's current terminal. | ||
| try: | ||
| sys.stdin = open('CONIN$', 'r') | ||
| except OSError: | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Confession: I wrote the original logic here, I'm not a "Windows guy" but it seemed to work and be reasonably correct; I'll defer to others that know Windows better as to if this is an improvement.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for the context! The Windows rationale: There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. cmd and PowerShell are shells, and Windows Terminal is a terminal GUI. From the Windows perspective, none of them are consoles; only conhost.exe and openconsole.exe are. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For performance, NtQueryVolumeInformationFile should be used, but since this is a Python script, the current approach is acceptable. |
||
| # No console is attached (e.g., a GUI git client or a pseudo terminal | ||
| # such as mintty). Relaunch in a standalone console window so that the | ||
| # user can still respond to interactive prompts. | ||
| delegate_proc = subprocess.run( | ||
| [ | ||
| sys.executable, | ||
| __file__ | ||
| ], | ||
| creationflags = subprocess.CREATE_NEW_CONSOLE | ||
| creationflags = subprocess.CREATE_NEW_CONSOLE, | ||
| env = {**os.environ, 'EDG_PREPUSH_DELEGATE': '1'} | ||
| ) | ||
| sys.exit(delegate_proc.returncode) | ||
| else: | ||
|
|
@@ -137,6 +143,7 @@ class PrePushChecker: | |
|
|
||
| def __init__(self): | ||
| self.errors = 0 | ||
| self.abort = False | ||
|
|
||
| def confirm_okay(self): | ||
| '''Ask the user whether a policy warning is acceptable. | ||
|
|
@@ -159,7 +166,8 @@ def confirm_okay(self): | |
| return | ||
| if response == 'q': | ||
| self.errors = 1 | ||
| sys.exit(1) | ||
| self.abort = True | ||
| return | ||
|
|
||
| print( | ||
| "Please answer 'y' (to accept), 'n' (to continue check) or " | ||
|
|
@@ -168,15 +176,18 @@ def confirm_okay(self): | |
| tries += 1 | ||
| if tries >= 10: | ||
| print('Too many attempts.') | ||
| sys.exit(1) | ||
| self.errors = 1 | ||
| self.abort = True | ||
| return | ||
|
|
||
| def check_commits(self): | ||
| '''Walk commits until one already on a remote branch is found. | ||
|
|
||
| As commits are walked, check to make sure they match formatting guidance. | ||
|
|
||
| Returns the oldest new commit hash, or None if there are no new commits. | ||
| Exits immediately if a commit subject fails the PR-number policy. | ||
| Returns the oldest new commit hash, or None if there are no new commits | ||
| (or if a commit subject fails the PR-number policy, in which case an | ||
| error is recorded and the check sequence is aborted). | ||
| ''' | ||
| first_new_commit = None | ||
|
|
||
|
|
@@ -200,7 +211,9 @@ def check_commits(self): | |
| print( | ||
| " [POLICY] Commit message doesn't contain valid PR number (or [])." | ||
| ) | ||
| sys.exit(1) | ||
| self.errors = 1 | ||
| self.abort = True | ||
| return None | ||
|
|
||
| first_new_commit = commit | ||
|
|
||
|
|
@@ -285,6 +298,8 @@ def check_changed_files(self, first_new_commit): | |
| results = json.load(file_handle) | ||
|
|
||
| for file_path_str, file_results in results.items(): | ||
| if self.abort: | ||
| break | ||
| print(f" Checking file: {file_path_str}") | ||
|
|
||
| misspellings = file_results['misspellings'] | ||
|
|
@@ -336,18 +351,32 @@ def check_changed_files(self, first_new_commit): | |
| print('=' * 79) | ||
| self.confirm_okay() | ||
|
|
||
| def run(self) -> bool: | ||
| '''Execute the full pre-push check sequence.''' | ||
| def run(self) -> int: | ||
| '''Execute the full pre-push check sequence. Return the exit status.''' | ||
| first_new_commit = self.check_commits() | ||
|
|
||
| # Skip per file checks if we didn't actually detect a new commit. | ||
| if first_new_commit is not None: | ||
| if not self.abort and first_new_commit is not None: | ||
| self.check_changed_files(first_new_commit) | ||
|
|
||
| return self.errors == 0 | ||
| return 0 if (self.errors == 0 and not self.abort) else 1 | ||
|
|
||
| def pause_delegate_window() -> None: | ||
| '''When running as a relaunch delegate, wait for the user before the | ||
| standalone console window closes so that failure output can be read.''' | ||
| if os.environ.get('EDG_PREPUSH_DELEGATE') != '1': | ||
| return | ||
|
|
||
| try: | ||
| input('Push checks failed; press Enter to close this window. ') | ||
| except EOFError: | ||
| pass | ||
|
|
||
| def main(): | ||
| checker = PrePushChecker() | ||
| sys.exit(0 if checker.run() else 1) | ||
| returncode = checker.run() | ||
| if returncode != 0: | ||
| pause_delegate_window() | ||
| sys.exit(returncode) | ||
|
|
||
| main() | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| # Part of the EDG Compiler Project, under the Apache License v2.0 with LLVM | ||
| # Exceptions. | ||
| # See https://edgcpp.org/LICENSE.txt for license information. | ||
| # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | ||
|
|
||
| # Windows counterpart of the ".envrc" file used with direnv on POSIX systems: | ||
| # from a PowerShell prompt, dot source this script to make the EDG | ||
| # development tools and their supporting Python libraries available in the | ||
| # current session: | ||
| # | ||
| # . .\dev-env.ps1 | ||
| # | ||
| # This prepends "dev_tools/win-bin" and "dev_tools/bin" to the PATH and | ||
| # "dev_tools/pylibs" to the PYTHONPATH. The script may be dot sourced from | ||
| # any directory and repeated dot sourcing is harmless. | ||
|
|
||
| $edgRepoRoot = $PSScriptRoot | ||
|
|
||
| $edgToolDirs = @( | ||
| (Join-Path $edgRepoRoot 'dev_tools\win-bin'), | ||
| (Join-Path $edgRepoRoot 'dev_tools\bin') | ||
| ) | ||
|
|
||
| if (-not (@($env:Path -split ';' | | ||
| Where-Object { $edgToolDirs -icontains $_ }))) { | ||
| $env:Path = "$($edgToolDirs -join ';');$env:Path" | ||
| } | ||
|
|
||
| $edgPyLibs = Join-Path $edgRepoRoot 'dev_tools\pylibs' | ||
| if (-not (@($env:PYTHONPATH -split ';' | | ||
| Where-Object { $_ -ieq $edgPyLibs }))) { | ||
| if ($env:PYTHONPATH) { | ||
| $env:PYTHONPATH = "$edgPyLibs;$env:PYTHONPATH" | ||
| } else { | ||
| $env:PYTHONPATH = $edgPyLibs | ||
| } | ||
| } | ||
|
|
||
| Remove-Variable -Name edgRepoRoot, edgToolDirs, edgPyLibs ` | ||
| -ErrorAction SilentlyContinue | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Neat!
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks! :) |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -241,7 +241,13 @@ def _check_parents(mapped_dirs: List[Path], workspace_dir: Path) -> None: | |
| This can be valid (from a Docker perspective), but it's an advanced use case | ||
| and can be a dangerous mistake (so we don't permit it). | ||
| ''' | ||
| workspace_drive = os.path.splitdrive(str(workspace_dir))[0] | ||
| for mapped_dir in mapped_dirs: | ||
| if os.path.splitdrive(str(mapped_dir))[0] != workspace_drive: | ||
| # The two paths are on different drives (which can only happen on | ||
| # Windows), so neither path can contain the other and the check below | ||
| # does not apply. | ||
| continue | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We probably want to do a bit more interrogation as this is a sort of safety check(?)
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good point. Replaced the blanket |
||
| common_dir = Path(os.path.commonpath([mapped_dir, workspace_dir])) | ||
| if common_dir == workspace_dir: | ||
| print('Current workspace is the parent of a mapped dir:') | ||
|
|
@@ -390,7 +396,7 @@ def exec_docker_cmd(image_name: str, workspace_dir: Path, | |
| f"EDG_USER_NAME={render_user_name()}" | ||
| ] | ||
|
|
||
| if os.isatty(sys.stdin.fileno()): | ||
| if edgutil.is_interactive_terminal(): | ||
| # Make the container interactive. | ||
| docker_args.append('-it') | ||
| else: | ||
|
|
@@ -473,13 +479,15 @@ def exec_docker_cmd(image_name: str, workspace_dir: Path, | |
|
|
||
| if platform.system() == 'Windows': | ||
| # Windows does not properly handle the execvp. Instead, keep this process | ||
| # around, and simply forward on stdin, stdout, and stderr. | ||
| subprocess.run( | ||
| # around, and simply forward on stdin, stdout, and stderr. Forward the | ||
| # exit status of the container so that callers observe failures. | ||
| completed_proc = subprocess.run( | ||
| docker_args, | ||
| stdin = sys.stdin, | ||
| stdout = sys.stdout, | ||
| stderr = sys.stderr | ||
| ) | ||
| sys.exit(completed_proc.returncode) | ||
| else: | ||
| # Execute the docker command, replacing the current process. | ||
| os.execvp('docker', docker_args) | ||
|
|
||
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.
I don't love this, I'd prefer people actual configure the environment (which
dev-init.pyshould help with ... which also installs the script?). However, if we're only doing this for thepre-push.pyscript (as a sort of "balancing act" to handle weird edge cases), I think that's fine.Uh oh!
There was an error while loading. Please reload this page.
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.
Understood: it is deliberately only in
pre-push.py, as the balancing act you describe. Every other tool is invoked from a configured environment, but git runs this hook with the ambient environment, and nothing surfaced that distinction to the user today (the failure was anImportErrorinside a closing console window). And yes,dev-init.pyinstalls the hook (it writes the.git/hooks/pre-pushshim that executes.git-hooks/pre-push.py), sodev-initusers get the self-locating version automatically.