Skip to main content

rch_common/
lib.rs

1//! Remote Compilation Helper - Common Library
2//!
3//! Shared types, patterns, and utilities used by rch, rchd, and rch-wkr.
4
5// Use deny instead of forbid to allow specific overrides for env var manipulation
6// in tests and profile defaults (env::set_var/remove_var are unsafe in Rust 2024)
7#![deny(unsafe_code)]
8
9use std::sync::OnceLock;
10
11pub mod api;
12pub mod artifact_verify;
13pub mod binary_hash;
14pub mod cargo_path_deps;
15pub mod config;
16pub mod dependency_closure_planner;
17pub mod discovery;
18pub mod e2e;
19pub mod errors;
20pub mod hooks;
21pub mod logging;
22pub mod mock;
23pub mod mock_worker;
24pub mod path_topology;
25pub mod patterns;
26#[cfg(test)]
27mod patterns_security_test;
28#[cfg(test)]
29mod proptest_tests;
30pub mod protocol;
31#[cfg(unix)]
32pub mod remote_compilation;
33#[cfg(unix)]
34pub mod remote_verification;
35pub mod repo_updater_contract;
36pub mod schema_versions;
37#[cfg(unix)]
38pub mod ssh;
39#[cfg(all(test, unix))]
40mod ssh_timeout_test;
41pub mod ssh_utils;
42pub mod test_change;
43pub mod testing;
44pub mod toolchain;
45pub mod types;
46pub mod ui;
47pub mod util;
48
49pub const BUILD_COMMIT_ENV_VARS: &[&str] = &[
50    "RCH_GIT_COMMIT",
51    "VERGEN_GIT_SHA",
52    "GIT_COMMIT",
53    "GITHUB_SHA",
54];
55
56pub fn build_commit() -> Option<&'static str> {
57    [
58        option_env!("RCH_GIT_COMMIT"),
59        option_env!("VERGEN_GIT_SHA"),
60        option_env!("GIT_COMMIT"),
61        option_env!("GITHUB_SHA"),
62    ]
63    .into_iter()
64    .flatten()
65    .map(str::trim)
66    .find(|value| !value.is_empty())
67}
68
69pub fn build_version_value() -> String {
70    build_version_value_with_commit(env!("CARGO_PKG_VERSION"), build_commit())
71}
72
73pub fn build_version_value_static() -> &'static str {
74    static VERSION_VALUE: OnceLock<String> = OnceLock::new();
75
76    VERSION_VALUE.get_or_init(build_version_value).as_str()
77}
78
79pub fn build_version_value_with_commit(package_version: &str, commit: Option<&str>) -> String {
80    let mut value = package_version.to_string();
81
82    if let Some(commit) = commit.map(str::trim).filter(|value| !value.is_empty()) {
83        value.push_str(" (commit ");
84        value.push_str(short_commit(commit));
85        value.push(')');
86    }
87
88    value
89}
90
91fn short_commit(commit: &str) -> &str {
92    commit
93        .char_indices()
94        .nth(12)
95        .map_or(commit, |(index, _)| &commit[..index])
96}
97
98pub use artifact_verify::{
99    ArtifactManifest, FileHash, VerificationFailure, VerificationResult, compute_file_hash,
100    create_manifest, verify_artifacts,
101};
102pub use binary_hash::{
103    BinaryHashResult, binaries_equivalent, binary_contains_marker, compute_binary_hash,
104};
105pub use cargo_path_deps::{
106    CargoPathDependencyEdge, CargoPathDependencyError, CargoPathDependencyErrorKind,
107    CargoPathDependencyGraph, CargoPathDependencyPackage, resolve_cargo_path_dependency_graph,
108    resolve_cargo_path_dependency_graph_with_policy,
109};
110pub use dependency_closure_planner::{
111    DependencyClosurePlan, DependencyClosurePlanState, DependencyPlanIssue, DependencyRiskClass,
112    DependencySyncAction, DependencySyncMetadata, DependencySyncReason,
113    build_dependency_closure_plan, build_dependency_closure_plan_with_policy,
114    plan_dependency_closure_from_graph,
115};
116pub use logging::{LogConfig, LogFormat, LoggingGuards, init_logging};
117pub use mock_worker::MockWorkerServer;
118pub use path_topology::{
119    DEFAULT_ALIAS_PROJECT_ROOT, DEFAULT_CANONICAL_PROJECT_ROOT, NormalizationDecision,
120    NormalizedProjectPath, PathNormalizationError, PathNormalizationErrorKind, PathTopologyPolicy,
121    normalize_project_path, normalize_project_path_with_policy,
122};
123pub use patterns::{
124    Classification, ClassificationDetails, ClassificationTier, CompilationKind, TierDecision,
125    classify_command, classify_command_detailed, split_shell_commands,
126};
127pub use protocol::{HookInput, HookOutput, ToolInput};
128pub use repo_updater_contract::{
129    MockRepoUpdaterAdapter, REPO_UPDATER_ALIAS_PROJECTS_ROOT, REPO_UPDATER_CANONICAL_PROJECTS_ROOT,
130    REPO_UPDATER_CONTRACT_SCHEMA_VERSION, REPO_UPDATER_DEFAULT_BINARY,
131    REPO_UPDATER_MIN_SUPPORTED_VERSION, RepoUpdaterAdapter, RepoUpdaterAdapterCommand,
132    RepoUpdaterAdapterContract, RepoUpdaterAdapterRequest, RepoUpdaterAdapterResponse,
133    RepoUpdaterFailure, RepoUpdaterFailureKind, RepoUpdaterFallbackMode,
134    RepoUpdaterIdempotencyGuarantee, RepoUpdaterJsonEnvelope, RepoUpdaterOutputFormat,
135    RepoUpdaterResponseStatus, RepoUpdaterVersionCompatibility, RepoUpdaterVersionPolicy,
136    build_invocation, classify_exit_code, evaluate_version_compatibility,
137    map_failure_kind_to_error_code, repo_updater_envelope_schema, repo_updater_request_schema,
138    repo_updater_response_schema,
139};
140// Platform-independent SSH utilities (available everywhere)
141pub use ssh_utils::{
142    CommandResult, EnvPrefix, build_env_prefix, is_retryable_transport_error,
143    is_retryable_transport_error_text, shell_escape_value,
144};
145// Unix-only SSH client (uses openssh crate)
146#[cfg(unix)]
147pub use ssh::{KnownHostsPolicy, SshClient, SshOptions, SshPool};
148pub use test_change::{TestChangeGuard, TestCodeChange};
149pub use toolchain::{ToolchainInfo, wrap_command_with_color, wrap_command_with_toolchain};
150pub use types::{
151    AffinityConfig, BuildCancellationMetadata, BuildCancellationWorkerHealth, BuildHeartbeatPhase,
152    BuildHeartbeatRequest, BuildLocation, BuildRecord, BuildStats, CircuitBreakerConfig,
153    CircuitState, CircuitStats, ColorMode, CommandPriority, CommandTimingBreakdown,
154    CompilationConfig, CompilationMetrics, CompilationTimer, CompilationTimingBreakdown,
155    EnvironmentConfig, ExecutionConfig, FairnessConfig, FleetConfig, GeneralConfig,
156    MetricsAggregator, OutputConfig, OutputVisibility, PathTopologyConfig, RchConfig,
157    ReleaseRequest, RequiredRuntime, RetryConfig, SavedTimeStats, SelectedWorker, SelectionConfig,
158    SelectionReason, SelectionRequest, SelectionResponse, SelectionStrategy, SelectionWeightConfig,
159    SelfHealingConfig, SelfHealingLogLevel, SelfTestConfig, SelfTestFailureAction, SelfTestWorkers,
160    TransferConfig, WorkerCapabilities, WorkerConfig, WorkerId, WorkerStatus, default_socket_path,
161    validate_remote_base,
162};
163
164// Testing module re-exports
165pub use testing::{TestLogEntry, TestLogger, TestPhase, TestResult};
166
167// Config module re-exports
168pub use config::{
169    ConfigSource, ConfigValueSource, ConfigWarning, EnvError, EnvParser, Profile, Severity,
170    Sourced, validate_config,
171};
172
173// Discovery module re-exports
174pub use discovery::{
175    DiscoveredHost, DiscoverySource, discover_all, parse_shell_aliases,
176    parse_shell_aliases_content, parse_ssh_config, parse_ssh_config_content,
177};
178
179// UI module re-exports
180pub use ui::{
181    ErrorPanel, ErrorSeverity, Icons, IntoErrorPanel, OutputContext, RchTheme, ResultExt,
182    anyhow_to_json, anyhow_to_panel, display_anyhow_error, display_error, display_error_with_code,
183    error_to_json, error_to_panel,
184};
185
186// Errors module re-exports
187pub use errors::{
188    CodeExplanation, CodeNamespace, ErrorCategory, ErrorCode, ErrorEntry, ReliabilityCategoryKind,
189    ReliabilityReasonCode,
190};
191
192// Schema-version registry re-exports
193pub use schema_versions::current_version as schema_version;
194pub use schema_versions::{ALL_COMPONENTS as SCHEMA_VERSION_COMPONENTS, SchemaComponent};
195
196// API module re-exports (unified API types for CLI and daemon)
197pub use api::{API_VERSION, ApiError, ApiResponse, ErrorContext, LegacyErrorCode};
198
199// Hooks module re-exports (daemon self-healing)
200pub use hooks::{HookResult, is_claude_code_installed, verify_and_install_claude_code_hook};
201
202#[cfg(test)]
203mod build_version_tests {
204    use super::{BUILD_COMMIT_ENV_VARS, build_version_value_with_commit};
205
206    #[test]
207    fn build_version_value_omits_missing_commit() {
208        assert_eq!(build_version_value_with_commit("1.0.24", None), "1.0.24");
209    }
210
211    #[test]
212    fn build_version_value_trims_and_shortens_commit() {
213        assert_eq!(
214            build_version_value_with_commit(
215                "1.0.24",
216                Some(" 2fa1249a18dfd05ae5e319e8d10bfd3c9ea1af55 "),
217            ),
218            "1.0.24 (commit 2fa1249a18df)"
219        );
220    }
221
222    #[test]
223    fn build_version_value_ignores_empty_commit() {
224        assert_eq!(
225            build_version_value_with_commit("1.0.24", Some("  ")),
226            "1.0.24"
227        );
228    }
229
230    #[test]
231    fn build_commit_env_var_order_is_documented() {
232        assert_eq!(
233            BUILD_COMMIT_ENV_VARS,
234            &[
235                "RCH_GIT_COMMIT",
236                "VERGEN_GIT_SHA",
237                "GIT_COMMIT",
238                "GITHUB_SHA"
239            ]
240        );
241    }
242}