# Design of `rust-version-info-file`
This document details the design of the `rust-version-info-file` library, explaining how the implementation fulfills the acceptance criteria derived from the requirements.
## 1. High-Level Design
The library is designed as a simple utility to be called from a `build.rs` script. Its architecture consists of a single public function, `rust_version_info_file`, which acts as a facade, orchestrating the gathering of information and file I/O operations.
The overall process flow is as follows:
1. Read the contents of the target destination file, if it exists.
2. Execute shell commands to get the current Rust compiler version and the project's dependency tree.
3. Format the collected information into a single string.
4. Compare the newly generated string with the content read from the destination file.
5. If the content is different, overwrite the destination file with the new content.
This design ensures that the file is only modified when the underlying version information changes, preventing unnecessary file system writes and respecting build system caching.
## 2. Detailed Design
The implementation is broken down into one public function and three private helper functions.
### 2.1. `rust_version_info_file<T: AsRef<Path>>(dst: T, cargo_toml_file: &str)`
This is the public entry point of the library, fulfilling **AC1**.
- **Orchestration**: It coordinates the entire process. It calls `read_file` to get the old content, then calls `rustc_version_info` and `tree_version_info` to generate the new content (**AC2**, **AC3**).
- **Formatting**: It uses `format!` to combine the results of the helper calls into a single, newline-separated string, fulfilling **AC4**.
- **Conditional Write Logic**: It implements the core logic for file modification. By comparing the old and new strings, it decides whether to write to the file. This comparison and the subsequent file operations satisfy **AC7**, **AC8**, and **AC9**.
- If the file needs to be written, it is opened with `create(true)`, `write(true)`, and `truncate(true)`, which handles both creating a new file and overwriting an existing one.
### 2.2. `rustc_version_info() -> String`
This function is responsible for retrieving the Rust compiler version.
- **Command Execution**: It executes the `rustc --version` command using `std::process::Command`.
- **Environment Variable**: It respects the `RUSTC` environment variable by using `std::env::var_os("RUSTC")`. If the variable is not set, it defaults to `"rustc"`. This design directly implements **AC6**.
- **Output Handling**: It captures the `stdout` of the command, trims any trailing newline, and returns the result as a `String`. It uses `.unwrap()`, assuming that `rustc` is always present and the command will not fail in a valid Rust build environment.
### 2.3. `tree_version_info(cargo_toml_file: &str) -> String`
This function retrieves and processes the dependency tree.
- **Command Execution**: It executes `cargo tree` with the `--color never` flag to ensure plain text output and the `--manifest-path` argument to target the correct `Cargo.toml`.
- **Output Processing**: It captures the `stdout` from the command. To satisfy **AC5**, it performs a string manipulation to remove the file path from the first line. It finds the first occurrence of `" ("` and the subsequent `")"` and removes the text between them, effectively cleaning up the root crate's line in the tree.
- **Error Handling**: Like `rustc_version_info`, this function uses `.unwrap()` and will panic if the `cargo tree` command fails.
### 2.4. `read_file(path: &Path) -> String`
This is a small utility function for reading the destination file's current content.
- **File Reading**: It attempts to open and read the specified file path.
- **Error Handling**: If the file does not exist or cannot be read, it returns an empty `String`. This is critical for the comparison logic in the main function, as a non-existent file is treated as having empty content, triggering a write operation (**AC7**).
## 3. Data Flow
1. The `dst` path is passed to `read_file`, which returns the file's content as `old_s`.
2. The `RUSTC` environment variable (or its default) is used by `rustc_version_info` to produce the `rustc` version string.
3. The `cargo_toml_file` path is passed to `tree_version_info` to produce the processed `cargo tree` string.
4. The `rustc` and `cargo` strings are combined into `curr_s`.
5. `old_s` and `curr_s` are compared.
6. If they differ, `curr_s` is written to the `dst` path.