railmux 0.4.0.dev6__tar.gz → 0.4.0.dev7__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {railmux-0.4.0.dev6/src/railmux.egg-info → railmux-0.4.0.dev7}/PKG-INFO +1 -1
- railmux-0.4.0.dev7/src/railmux/__init__.py +1 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/src/railmux/windows_bootstrap.py +30 -7
- railmux-0.4.0.dev7/src/railmux/windows_install_log.py +189 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/src/railmux/windows_msys2.py +191 -91
- railmux-0.4.0.dev7/src/railmux/windows_pacman.py +259 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7/src/railmux.egg-info}/PKG-INFO +1 -1
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/src/railmux.egg-info/SOURCES.txt +4 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/tests/test_windows_bootstrap.py +52 -0
- railmux-0.4.0.dev7/tests/test_windows_install_log.py +160 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/tests/test_windows_msys2.py +15 -9
- railmux-0.4.0.dev7/tests/test_windows_pacman.py +223 -0
- railmux-0.4.0.dev6/src/railmux/__init__.py +0 -1
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/LICENSE +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/MANIFEST.in +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/README.md +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/pyproject.toml +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/setup.cfg +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/src/railmux/__main__.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/src/railmux/atomic_file.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/src/railmux/background_index.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/src/railmux/cli.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/src/railmux/codex_index.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/src/railmux/config.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/src/railmux/config_cli.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/src/railmux/diagnostics.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/src/railmux/discovery.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/src/railmux/display_transport.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/src/railmux/entrypoint.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/src/railmux/fast_display_client.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/src/railmux/fast_display_history.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/src/railmux/fast_display_input.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/src/railmux/fast_display_protocol.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/src/railmux/fast_display_server.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/src/railmux/favorites.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/src/railmux/fuzzy.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/src/railmux/help_workspace.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/src/railmux/launcher.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/src/railmux/legacy_sessions.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/src/railmux/local_clipboard.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/src/railmux/local_open.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/src/railmux/models.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/src/railmux/modes.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/src/railmux/mouse_manager.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/src/railmux/orphan_marker.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/src/railmux/pane_surface.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/src/railmux/path_codec.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/src/railmux/provider_paths.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/src/railmux/remote_config.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/src/railmux/renames.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/src/railmux/restart_state.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/src/railmux/runtime_config.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/src/railmux/scroll_agent.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/src/railmux/scroll_manager.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/src/railmux/selection_isolation.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/src/railmux/self_update.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/src/railmux/session_cache.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/src/railmux/session_index.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/src/railmux/setting_contracts.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/src/railmux/settings.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/src/railmux/ssh_args.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/src/railmux/ssh_compat.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/src/railmux/ssh_display_diagnostics.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/src/railmux/ssh_doctor.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/src/railmux/system_deps.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/src/railmux/terminal_status.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/src/railmux/tmux_binding_manager.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/src/railmux/tmux_ctl.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/src/railmux/tmux_health.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/src/railmux/tmux_server.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/src/railmux/tool_panes.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/src/railmux/transcript.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/src/railmux/ui/__init__.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/src/railmux/ui/_widgets.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/src/railmux/ui/app.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/src/railmux/ui/keymap.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/src/railmux/ui/modals.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/src/railmux/ui/projects_pane.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/src/railmux/ui/running_pane.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/src/railmux/ui/sessions_pane.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/src/railmux/ui/sidebar.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/src/railmux/ui/statusbar.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/src/railmux/ui/workspace.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/src/railmux.egg-info/dependency_links.txt +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/src/railmux.egg-info/entry_points.txt +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/src/railmux.egg-info/requires.txt +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/src/railmux.egg-info/top_level.txt +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/tests/test_agent_workspace.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/tests/test_atomic_file.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/tests/test_background_index.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/tests/test_cli.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/tests/test_codex_delete.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/tests/test_codex_index.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/tests/test_codex_lineage.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/tests/test_codex_transcript.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/tests/test_config.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/tests/test_config_cli.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/tests/test_denested_transport_benchmark.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/tests/test_diagnostics.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/tests/test_discovery.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/tests/test_display_transport.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/tests/test_fast_full_window.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/tests/test_focus_highlight.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/tests/test_fullscreen_binding.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/tests/test_fuzzy.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/tests/test_help_agent.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/tests/test_help_workspace.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/tests/test_history_cleanup.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/tests/test_keymap.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/tests/test_launcher.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/tests/test_layout_profile.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/tests/test_legacy_sessions.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/tests/test_local_clipboard.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/tests/test_local_open.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/tests/test_modals.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/tests/test_mode_selection.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/tests/test_modes.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/tests/test_mouse_manager.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/tests/test_new_project.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/tests/test_options.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/tests/test_orphan_recovery.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/tests/test_pane_surface.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/tests/test_path_codec.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/tests/test_projects_pane.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/tests/test_provider_paths.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/tests/test_release_notes.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/tests/test_release_workflow.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/tests/test_remote_config.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/tests/test_renames.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/tests/test_restart_state.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/tests/test_running_pane.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/tests/test_running_sort.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/tests/test_runtime_config.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/tests/test_scroll_agent.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/tests/test_scroll_manager.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/tests/test_selection_isolation.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/tests/test_self_update.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/tests/test_session_cache.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/tests/test_session_index.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/tests/test_sessions_pane.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/tests/test_setting_contracts.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/tests/test_settings.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/tests/test_sidebar.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/tests/test_soft_quit.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/tests/test_ssh_args.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/tests/test_ssh_compat.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/tests/test_ssh_display_benchmark.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/tests/test_ssh_display_diagnostics.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/tests/test_ssh_doctor.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/tests/test_status.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/tests/test_statusbar.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/tests/test_system_deps.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/tests/test_terminal_size.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/tests/test_terminal_status.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/tests/test_termux_local_touch.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/tests/test_tmux_binding_manager.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/tests/test_tmux_ctl.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/tests/test_tmux_health.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/tests/test_tmux_integration.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/tests/test_tmux_server.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/tests/test_tmux_status.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/tests/test_tool_panes.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/tests/test_transcript.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/tests/test_utf8_state.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/tests/test_web_demo_recorder.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/tests/test_wheel_smoke.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/tests/test_widgets.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/tests/test_windows_wrapper_contract.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/tests/test_yolo_prompt.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/tools/denested_transport_benchmark.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/tools/release_notes.py +0 -0
- {railmux-0.4.0.dev6 → railmux-0.4.0.dev7}/tools/wheel_smoke.py +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.4.0.dev7"
|
|
@@ -26,7 +26,7 @@ def _print_help() -> None:
|
|
|
26
26
|
" railmux ssh HOST [OPTIONS]\n"
|
|
27
27
|
" railmux config [--remote HOST] [OPTIONS]\n"
|
|
28
28
|
" railmux doctor [--remote HOST] [OPTIONS]\n"
|
|
29
|
-
" railmux runtime {status,install} [--yes]\n\n"
|
|
29
|
+
" railmux runtime {status,install} [--yes] [--verbose]\n\n"
|
|
30
30
|
"Windows preview: Railmux runs in a private managed MSYS2/tmux "
|
|
31
31
|
"runtime while Codex and Claude Code remain Windows-native and use "
|
|
32
32
|
"the existing Windows user session directories."
|
|
@@ -56,7 +56,8 @@ def _confirm_install(*, input_fn: Callable[[str], str] = input) -> bool:
|
|
|
56
56
|
try:
|
|
57
57
|
answer = input_fn(
|
|
58
58
|
"Install the private MSYS2/tmux runtime now? "
|
|
59
|
-
"This downloads
|
|
59
|
+
"This downloads a 50 MB base plus required updates and packages, "
|
|
60
|
+
"and uses about 300 MB or more of private disk space [y/N] "
|
|
60
61
|
)
|
|
61
62
|
except (EOFError, KeyboardInterrupt):
|
|
62
63
|
print()
|
|
@@ -68,13 +69,18 @@ def _install(
|
|
|
68
69
|
*,
|
|
69
70
|
environ: Mapping[str, str],
|
|
70
71
|
assume_yes: bool,
|
|
72
|
+
verbose: bool = False,
|
|
71
73
|
input_fn: Callable[[str], str] = input,
|
|
72
74
|
) -> Msys2Runtime | None:
|
|
73
75
|
if not assume_yes and not _confirm_install(input_fn=input_fn):
|
|
74
76
|
print("MSYS2 runtime installation cancelled.", file=sys.stderr)
|
|
75
77
|
return None
|
|
76
78
|
try:
|
|
77
|
-
runtime = install_managed_runtime(
|
|
79
|
+
runtime = install_managed_runtime(
|
|
80
|
+
version=__version__,
|
|
81
|
+
environ=environ,
|
|
82
|
+
verbose=verbose,
|
|
83
|
+
)
|
|
78
84
|
except RuntimeInstallError as exc:
|
|
79
85
|
print(f"error: {exc}", file=sys.stderr)
|
|
80
86
|
return None
|
|
@@ -135,12 +141,24 @@ def main(
|
|
|
135
141
|
if arguments[:2] == ["runtime", "status"] and len(arguments) == 2:
|
|
136
142
|
return _runtime_status(runtime, environ=environ)
|
|
137
143
|
if arguments and arguments[0] == "runtime":
|
|
138
|
-
|
|
139
|
-
|
|
144
|
+
install_arguments = arguments[1:]
|
|
145
|
+
install_flags = install_arguments[1:] if install_arguments[:1] == ["install"] else []
|
|
146
|
+
if (
|
|
147
|
+
not install_arguments
|
|
148
|
+
or install_arguments[0] != "install"
|
|
149
|
+
or len(install_flags) != len(set(install_flags))
|
|
150
|
+
or any(flag not in {"--yes", "--verbose"} for flag in install_flags)
|
|
151
|
+
):
|
|
152
|
+
print(
|
|
153
|
+
"error: usage: railmux runtime {status,install} "
|
|
154
|
+
"[--yes] [--verbose]",
|
|
155
|
+
file=sys.stderr,
|
|
156
|
+
)
|
|
140
157
|
return 2
|
|
141
158
|
runtime = runtime or _install(
|
|
142
159
|
environ=environ,
|
|
143
|
-
assume_yes=
|
|
160
|
+
assume_yes="--yes" in install_flags,
|
|
161
|
+
verbose="--verbose" in install_flags,
|
|
144
162
|
input_fn=input_fn,
|
|
145
163
|
)
|
|
146
164
|
return 0 if runtime is not None else 2
|
|
@@ -162,7 +180,12 @@ def main(
|
|
|
162
180
|
file=sys.stderr,
|
|
163
181
|
)
|
|
164
182
|
return 2
|
|
165
|
-
runtime = _install(
|
|
183
|
+
runtime = _install(
|
|
184
|
+
environ=environ,
|
|
185
|
+
assume_yes=False,
|
|
186
|
+
verbose=False,
|
|
187
|
+
input_fn=input_fn,
|
|
188
|
+
)
|
|
166
189
|
if runtime is None:
|
|
167
190
|
return 2
|
|
168
191
|
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
"""Bounded user output and complete UTF-8 logs for Windows runtime setup."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import os
|
|
5
|
+
import re
|
|
6
|
+
import sys
|
|
7
|
+
import time
|
|
8
|
+
from collections import deque
|
|
9
|
+
from collections.abc import Callable, Mapping
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import BinaryIO, TextIO
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
_LOG_KEEP_COUNT = 5
|
|
15
|
+
_FAILURE_LINE_LIMIT = 500
|
|
16
|
+
_LOG_NAME_RE = re.compile(
|
|
17
|
+
r"install-[0-9A-Za-z.+-]+-[0-9]{8}T[0-9]{6}Z-[0-9]+\.log\Z"
|
|
18
|
+
)
|
|
19
|
+
_URL_CREDENTIAL_RE = re.compile(r"(https?://)[^/@\s]+@", re.IGNORECASE)
|
|
20
|
+
_SECRET_QUERY_RE = re.compile(
|
|
21
|
+
r"(?i)([?&](?:access_token|password|token)=)[^&\s]+"
|
|
22
|
+
)
|
|
23
|
+
_MIRROR_WARNING_MARKERS = (
|
|
24
|
+
"too many errors from ",
|
|
25
|
+
"failed retrieving file ",
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _redact_line(line: str) -> str:
|
|
30
|
+
line = _URL_CREDENTIAL_RE.sub(r"\1<redacted>@", line)
|
|
31
|
+
return _SECRET_QUERY_RE.sub(r"\1<redacted>", line)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def install_log_path(
|
|
35
|
+
environ: Mapping[str, str],
|
|
36
|
+
*,
|
|
37
|
+
version: str,
|
|
38
|
+
clock: Callable[[], float] = time.time,
|
|
39
|
+
) -> Path:
|
|
40
|
+
local_app_data = environ.get("LOCALAPPDATA", "").strip()
|
|
41
|
+
if not local_app_data:
|
|
42
|
+
raise OSError("LOCALAPPDATA is unavailable")
|
|
43
|
+
stamp = time.strftime("%Y%m%dT%H%M%SZ", time.gmtime(clock()))
|
|
44
|
+
return (
|
|
45
|
+
Path(local_app_data)
|
|
46
|
+
/ "Railmux"
|
|
47
|
+
/ "logs"
|
|
48
|
+
/ f"install-{version}-{stamp}-{os.getpid()}.log"
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _prune_install_logs(directory: Path, *, keep: int = _LOG_KEEP_COUNT) -> None:
|
|
53
|
+
candidates: list[Path] = []
|
|
54
|
+
try:
|
|
55
|
+
entries = list(directory.iterdir())
|
|
56
|
+
except OSError:
|
|
57
|
+
return
|
|
58
|
+
for entry in entries:
|
|
59
|
+
if entry.is_file() and _LOG_NAME_RE.fullmatch(entry.name):
|
|
60
|
+
candidates.append(entry)
|
|
61
|
+
try:
|
|
62
|
+
candidates.sort(
|
|
63
|
+
key=lambda path: (path.stat().st_mtime_ns, path.name),
|
|
64
|
+
reverse=True,
|
|
65
|
+
)
|
|
66
|
+
except OSError:
|
|
67
|
+
return
|
|
68
|
+
for stale in candidates[max(keep, 1) :]:
|
|
69
|
+
try:
|
|
70
|
+
stale.unlink()
|
|
71
|
+
except OSError:
|
|
72
|
+
continue
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class InstallReporter:
|
|
76
|
+
"""Show stable phases while retaining complete command output."""
|
|
77
|
+
|
|
78
|
+
def __init__(
|
|
79
|
+
self,
|
|
80
|
+
path: Path,
|
|
81
|
+
*,
|
|
82
|
+
verbose: bool,
|
|
83
|
+
stream: TextIO | None = None,
|
|
84
|
+
) -> None:
|
|
85
|
+
self.path = path
|
|
86
|
+
self.verbose = verbose
|
|
87
|
+
self.stream = sys.stdout if stream is None else stream
|
|
88
|
+
self._log: TextIO | None = None
|
|
89
|
+
self._tail: deque[str] = deque(maxlen=20)
|
|
90
|
+
self._reported_mirror_warning = False
|
|
91
|
+
|
|
92
|
+
def __enter__(self) -> InstallReporter:
|
|
93
|
+
self.path.parent.mkdir(parents=True, exist_ok=True)
|
|
94
|
+
self._log = self.path.open("x", encoding="utf-8", newline="\n")
|
|
95
|
+
self._write_log("Railmux Windows runtime installation\n")
|
|
96
|
+
_prune_install_logs(self.path.parent)
|
|
97
|
+
return self
|
|
98
|
+
|
|
99
|
+
def __exit__(self, _exc_type, _exc, _traceback) -> None:
|
|
100
|
+
if self._log is not None:
|
|
101
|
+
self._log.close()
|
|
102
|
+
self._log = None
|
|
103
|
+
|
|
104
|
+
def _write_log(self, text: str) -> None:
|
|
105
|
+
if self._log is None:
|
|
106
|
+
return
|
|
107
|
+
self._log.write(text)
|
|
108
|
+
self._log.flush()
|
|
109
|
+
|
|
110
|
+
def _console(self, text: str, *, flush: bool = True) -> None:
|
|
111
|
+
try:
|
|
112
|
+
print(text, file=self.stream, flush=flush)
|
|
113
|
+
except UnicodeEncodeError:
|
|
114
|
+
encoding = getattr(self.stream, "encoding", None) or "utf-8"
|
|
115
|
+
safe = text.encode(encoding, errors="replace").decode(encoding)
|
|
116
|
+
print(safe, file=self.stream, flush=flush)
|
|
117
|
+
|
|
118
|
+
def phase(self, number: int, total: int, message: str) -> None:
|
|
119
|
+
rendered = f"[{number}/{total}] {message}"
|
|
120
|
+
self._console(rendered)
|
|
121
|
+
self._write_log(f"\n{rendered}\n")
|
|
122
|
+
|
|
123
|
+
def done(self, detail: str | None = None) -> None:
|
|
124
|
+
rendered = " done" + (f" · {detail}" if detail else "")
|
|
125
|
+
self._console(rendered)
|
|
126
|
+
self._write_log(f"{rendered}\n")
|
|
127
|
+
|
|
128
|
+
def note(self, message: str) -> None:
|
|
129
|
+
rendered = f" {message}"
|
|
130
|
+
self._console(rendered)
|
|
131
|
+
self._write_log(f"{rendered}\n")
|
|
132
|
+
|
|
133
|
+
def command_started(self, label: str) -> None:
|
|
134
|
+
self._tail.clear()
|
|
135
|
+
self._reported_mirror_warning = False
|
|
136
|
+
self._write_log(f"\n--- {label} ---\n")
|
|
137
|
+
|
|
138
|
+
def command_output(self, output: bytes | str | None) -> None:
|
|
139
|
+
if not output:
|
|
140
|
+
return
|
|
141
|
+
if isinstance(output, bytes):
|
|
142
|
+
text = output.decode("utf-8", errors="replace")
|
|
143
|
+
else:
|
|
144
|
+
text = output
|
|
145
|
+
normalized = text.replace("\r\n", "\n").replace("\r", "\n")
|
|
146
|
+
for raw_line in normalized.splitlines():
|
|
147
|
+
line = _redact_line(raw_line)
|
|
148
|
+
self._write_log(f"{line}\n")
|
|
149
|
+
if line.strip():
|
|
150
|
+
self._tail.append(line)
|
|
151
|
+
if self.verbose:
|
|
152
|
+
self._console(line)
|
|
153
|
+
elif (
|
|
154
|
+
not self._reported_mirror_warning
|
|
155
|
+
and any(marker in line.lower() for marker in _MIRROR_WARNING_MARKERS)
|
|
156
|
+
):
|
|
157
|
+
self.note(
|
|
158
|
+
"A package mirror failed; pacman is trying the next "
|
|
159
|
+
"approved source…"
|
|
160
|
+
)
|
|
161
|
+
self._reported_mirror_warning = True
|
|
162
|
+
|
|
163
|
+
def command_failed(self, label: str, returncode: int) -> None:
|
|
164
|
+
self.note(f"{label} failed with exit code {returncode}.")
|
|
165
|
+
if self._tail:
|
|
166
|
+
self.note("Last output:")
|
|
167
|
+
for line in list(self._tail)[-8:]:
|
|
168
|
+
if len(line) > _FAILURE_LINE_LIMIT:
|
|
169
|
+
line = f"{line[:_FAILURE_LINE_LIMIT]}… [truncated; see log]"
|
|
170
|
+
self._console(f" {line}")
|
|
171
|
+
self.note(f"Full UTF-8 log: {self.path}")
|
|
172
|
+
|
|
173
|
+
def finish(self) -> None:
|
|
174
|
+
self._write_log("\nInstallation completed successfully.\n")
|
|
175
|
+
self._console(f"Installation log: {self.path}")
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def stream_process_output(
|
|
179
|
+
process: object,
|
|
180
|
+
reporter: InstallReporter,
|
|
181
|
+
) -> int:
|
|
182
|
+
stdout: BinaryIO | None = getattr(process, "stdout", None)
|
|
183
|
+
if stdout is not None:
|
|
184
|
+
while True:
|
|
185
|
+
chunk = stdout.readline()
|
|
186
|
+
if not chunk:
|
|
187
|
+
break
|
|
188
|
+
reporter.command_output(chunk)
|
|
189
|
+
return process.wait()
|
|
@@ -24,6 +24,17 @@ from contextlib import contextmanager
|
|
|
24
24
|
from dataclasses import dataclass
|
|
25
25
|
from pathlib import Path
|
|
26
26
|
|
|
27
|
+
from railmux.windows_install_log import (
|
|
28
|
+
InstallReporter,
|
|
29
|
+
install_log_path,
|
|
30
|
+
stream_process_output,
|
|
31
|
+
)
|
|
32
|
+
from railmux.windows_pacman import (
|
|
33
|
+
PacmanMirrorDecision,
|
|
34
|
+
PacmanMirrorError,
|
|
35
|
+
optimize_pacman_mirror,
|
|
36
|
+
)
|
|
37
|
+
|
|
27
38
|
|
|
28
39
|
MSYS2_RELEASE = "2026-03-22"
|
|
29
40
|
MSYS2_ARCHIVE_NAME = f"msys2-base-x86_64-{MSYS2_RELEASE.replace('-', '')}.sfx.exe"
|
|
@@ -98,6 +109,7 @@ class RuntimeInstallError(RuntimeErrorBase):
|
|
|
98
109
|
Probe = Callable[..., subprocess.CompletedProcess[bytes]]
|
|
99
110
|
Runner = Callable[..., subprocess.CompletedProcess]
|
|
100
111
|
Downloader = Callable[[str, Path, str], None]
|
|
112
|
+
MirrorOptimizer = Callable[[Path], PacmanMirrorDecision]
|
|
101
113
|
|
|
102
114
|
|
|
103
115
|
@dataclass(frozen=True)
|
|
@@ -817,15 +829,32 @@ def _run_checked(
|
|
|
817
829
|
argv: Sequence[str],
|
|
818
830
|
*,
|
|
819
831
|
env: Mapping[str, str],
|
|
820
|
-
|
|
832
|
+
reporter: InstallReporter,
|
|
821
833
|
label: str,
|
|
834
|
+
runner: Runner | None = None,
|
|
822
835
|
) -> None:
|
|
836
|
+
reporter.command_started(label)
|
|
823
837
|
try:
|
|
824
|
-
|
|
838
|
+
if runner is None:
|
|
839
|
+
process = subprocess.Popen(
|
|
840
|
+
list(argv),
|
|
841
|
+
env=dict(env),
|
|
842
|
+
stdin=subprocess.DEVNULL,
|
|
843
|
+
stdout=subprocess.PIPE,
|
|
844
|
+
stderr=subprocess.STDOUT,
|
|
845
|
+
bufsize=-1,
|
|
846
|
+
)
|
|
847
|
+
returncode = stream_process_output(process, reporter)
|
|
848
|
+
else:
|
|
849
|
+
result = runner(list(argv), env=dict(env), check=False)
|
|
850
|
+
reporter.command_output(getattr(result, "stdout", None))
|
|
851
|
+
reporter.command_output(getattr(result, "stderr", None))
|
|
852
|
+
returncode = result.returncode
|
|
825
853
|
except OSError as exc:
|
|
826
854
|
raise RuntimeInstallError(f"could not start {label}") from exc
|
|
827
|
-
if
|
|
828
|
-
|
|
855
|
+
if returncode:
|
|
856
|
+
reporter.command_failed(label, returncode)
|
|
857
|
+
raise RuntimeInstallError(f"{label} failed with exit code {returncode}")
|
|
829
858
|
|
|
830
859
|
|
|
831
860
|
def _bash_command(root: Path, command: str, *arguments: str) -> list[str]:
|
|
@@ -891,9 +920,11 @@ def install_managed_runtime(
|
|
|
891
920
|
version: str,
|
|
892
921
|
environ: Mapping[str, str],
|
|
893
922
|
downloader: Downloader | None = None,
|
|
894
|
-
runner: Runner =
|
|
923
|
+
runner: Runner | None = None,
|
|
895
924
|
probe: Probe = _probe,
|
|
896
925
|
lock_factory: Callable[[Path], object] = install_lock,
|
|
926
|
+
mirror_optimizer: MirrorOptimizer = optimize_pacman_mirror,
|
|
927
|
+
verbose: bool = False,
|
|
897
928
|
) -> Msys2Runtime:
|
|
898
929
|
"""Install a fresh private runtime and activate it only after verification."""
|
|
899
930
|
if not _VERSION_RE.fullmatch(version):
|
|
@@ -906,94 +937,163 @@ def install_managed_runtime(
|
|
|
906
937
|
base = _managed_base(environ)
|
|
907
938
|
if base is None:
|
|
908
939
|
raise RuntimeInstallError("LOCALAPPDATA is unavailable")
|
|
909
|
-
|
|
940
|
+
try:
|
|
941
|
+
base.mkdir(parents=True, exist_ok=True)
|
|
942
|
+
log_path = install_log_path(environ, version=version)
|
|
943
|
+
except OSError as exc:
|
|
944
|
+
raise RuntimeInstallError("could not create the private runtime log") from exc
|
|
910
945
|
final_root = managed_root(environ, version=version)
|
|
911
946
|
assert final_root is not None
|
|
912
947
|
final_root.parent.mkdir(parents=True, exist_ok=True)
|
|
913
948
|
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
print(f"Downloading verified MSYS2 {MSYS2_RELEASE} runtime…")
|
|
933
|
-
download_from_sources(
|
|
934
|
-
archive,
|
|
935
|
-
MSYS2_ARCHIVE_SHA256,
|
|
936
|
-
downloader=downloader,
|
|
937
|
-
)
|
|
949
|
+
try:
|
|
950
|
+
with InstallReporter(log_path, verbose=verbose) as reporter:
|
|
951
|
+
with lock_factory(base):
|
|
952
|
+
existing = find_runtime(version=version, environ=environ, probe=probe)
|
|
953
|
+
if existing is not None:
|
|
954
|
+
reporter.note("The exact managed runtime is already ready.")
|
|
955
|
+
reporter.finish()
|
|
956
|
+
return existing
|
|
957
|
+
if final_root.exists():
|
|
958
|
+
if _marker_matches(final_root, version=version):
|
|
959
|
+
raise RuntimeInstallError(
|
|
960
|
+
"the managed MSYS2 runtime is present but could not be "
|
|
961
|
+
"verified; retry after checking antivirus or disk load"
|
|
962
|
+
)
|
|
963
|
+
raise RuntimeInstallError(
|
|
964
|
+
"the managed MSYS2 directory exists but is incomplete; "
|
|
965
|
+
"it was left untouched"
|
|
966
|
+
)
|
|
938
967
|
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
968
|
+
with tempfile.TemporaryDirectory(
|
|
969
|
+
prefix=".install-", dir=base
|
|
970
|
+
) as raw_stage:
|
|
971
|
+
stage = Path(raw_stage)
|
|
972
|
+
archive = stage / MSYS2_ARCHIVE_NAME
|
|
973
|
+
reporter.phase(1, 7, f"Downloading MSYS2 {MSYS2_RELEASE} base")
|
|
974
|
+
source = download_from_sources(
|
|
975
|
+
archive,
|
|
976
|
+
MSYS2_ARCHIVE_SHA256,
|
|
977
|
+
downloader=downloader,
|
|
978
|
+
)
|
|
979
|
+
reporter.done(source)
|
|
980
|
+
|
|
981
|
+
reporter.phase(2, 7, "Verifying and extracting the private base")
|
|
982
|
+
_run_checked(
|
|
983
|
+
[str(archive), "-y", f"-o{stage}"],
|
|
984
|
+
env=environ,
|
|
985
|
+
reporter=reporter,
|
|
986
|
+
runner=runner,
|
|
987
|
+
label="MSYS2 extraction",
|
|
988
|
+
)
|
|
989
|
+
root = stage / "msys64"
|
|
990
|
+
if not (root / "usr" / "bin" / "bash.exe").is_file():
|
|
991
|
+
raise RuntimeInstallError(
|
|
992
|
+
"the verified MSYS2 archive was incomplete"
|
|
993
|
+
)
|
|
994
|
+
reporter.done()
|
|
995
|
+
|
|
996
|
+
reporter.phase(3, 7, "Selecting an MSYS2 package mirror")
|
|
997
|
+
try:
|
|
998
|
+
decision = mirror_optimizer(root)
|
|
999
|
+
except PacmanMirrorError as exc:
|
|
1000
|
+
reporter.note(
|
|
1001
|
+
f"Mirror measurement unavailable ({exc}); using the "
|
|
1002
|
+
"official order."
|
|
1003
|
+
)
|
|
1004
|
+
reporter.done("official order")
|
|
1005
|
+
else:
|
|
1006
|
+
for mirror_probe in decision.probes:
|
|
1007
|
+
reporter.command_output(
|
|
1008
|
+
f"mirror probe {mirror_probe.label}: "
|
|
1009
|
+
f"{_format_download_rate(mirror_probe.rate)}\n"
|
|
1010
|
+
)
|
|
1011
|
+
for label, reason in decision.failures:
|
|
1012
|
+
reporter.command_output(
|
|
1013
|
+
f"mirror probe {label}: unavailable ({reason})\n"
|
|
1014
|
+
)
|
|
1015
|
+
if decision.selected is None:
|
|
1016
|
+
reporter.done("official order")
|
|
1017
|
+
else:
|
|
1018
|
+
reporter.done(
|
|
1019
|
+
f"{decision.selected.label} · "
|
|
1020
|
+
f"{_format_download_rate(decision.selected.rate)}"
|
|
1021
|
+
)
|
|
1022
|
+
|
|
1023
|
+
runtime = Msys2Runtime(root, managed=False)
|
|
1024
|
+
child_env = runtime.environment(environ)
|
|
1025
|
+
reporter.phase(4, 7, "Updating the private MSYS2 base")
|
|
1026
|
+
reporter.note(
|
|
1027
|
+
"pacman is noninteractive; displayed [Y/n] prompts do "
|
|
1028
|
+
"not require input."
|
|
1029
|
+
)
|
|
1030
|
+
_run_checked(
|
|
1031
|
+
_bash_command(root, _INITIAL_UPDATE_COMMAND),
|
|
1032
|
+
env=child_env,
|
|
1033
|
+
reporter=reporter,
|
|
1034
|
+
runner=runner,
|
|
1035
|
+
label="MSYS2 base update",
|
|
1036
|
+
)
|
|
1037
|
+
reporter.done()
|
|
1038
|
+
|
|
1039
|
+
# A new process is required after msys2-runtime/bash updates.
|
|
1040
|
+
child_env = runtime.environment(environ)
|
|
1041
|
+
reporter.phase(5, 7, "Installing tmux and private Python")
|
|
1042
|
+
_run_checked(
|
|
1043
|
+
_bash_command(root, _PACKAGE_INSTALL_COMMAND),
|
|
1044
|
+
env=child_env,
|
|
1045
|
+
reporter=reporter,
|
|
1046
|
+
runner=runner,
|
|
1047
|
+
label="MSYS2 package installation",
|
|
1048
|
+
)
|
|
1049
|
+
reporter.done()
|
|
1050
|
+
|
|
1051
|
+
reporter.phase(6, 7, f"Installing Railmux {version}")
|
|
1052
|
+
_run_checked(
|
|
1053
|
+
_bash_command(root, _VENV_COMMAND),
|
|
1054
|
+
env=child_env,
|
|
1055
|
+
reporter=reporter,
|
|
1056
|
+
runner=runner,
|
|
1057
|
+
label="Railmux virtual environment creation",
|
|
1058
|
+
)
|
|
1059
|
+
_run_checked(
|
|
1060
|
+
_bash_command(root, _PACKAGE_COMMAND, version),
|
|
1061
|
+
env=child_env,
|
|
1062
|
+
reporter=reporter,
|
|
1063
|
+
runner=runner,
|
|
1064
|
+
label="Railmux runtime package installation",
|
|
1065
|
+
)
|
|
1066
|
+
reporter.done()
|
|
1067
|
+
|
|
1068
|
+
reporter.phase(7, 7, "Validating and activating the runtime")
|
|
1069
|
+
_write_marker(root, version=version)
|
|
1070
|
+
staged_runtime = Msys2Runtime(root, managed=True)
|
|
1071
|
+
if not probe_runtime(
|
|
1072
|
+
staged_runtime,
|
|
1073
|
+
version=version,
|
|
1074
|
+
environ=environ,
|
|
1075
|
+
probe=probe,
|
|
1076
|
+
):
|
|
1077
|
+
raise RuntimeInstallError(
|
|
1078
|
+
"the staged Railmux runtime failed validation"
|
|
1079
|
+
)
|
|
1080
|
+
os.replace(root, final_root)
|
|
1081
|
+
|
|
1082
|
+
installed = Msys2Runtime(final_root, managed=True)
|
|
1083
|
+
if not probe_runtime(
|
|
1084
|
+
installed,
|
|
1085
|
+
version=version,
|
|
1086
|
+
environ=environ,
|
|
1087
|
+
probe=probe,
|
|
1088
|
+
):
|
|
1089
|
+
raise RuntimeInstallError(
|
|
1090
|
+
"the activated Railmux runtime failed validation"
|
|
1091
|
+
)
|
|
1092
|
+
reporter.done("ready")
|
|
1093
|
+
reporter.finish()
|
|
1094
|
+
return installed
|
|
1095
|
+
except RuntimeInstallError as exc:
|
|
1096
|
+
raise RuntimeInstallError(f"{exc}; full log: {log_path}") from exc
|
|
1097
|
+
except OSError as exc:
|
|
1098
|
+
detail = f"; full log: {log_path}" if log_path.is_file() else ""
|
|
1099
|
+
raise RuntimeInstallError(f"runtime installation failed{detail}") from exc
|