littlefs2_pack/lib.rs
1//! # littlefs2-pack
2//!
3//! A Rust crate for building a file system into a LittleFS
4//! binary file to be flashed to an embedded device.
5//!
6//! This crate wraps the [LittleFS C library](https://github.com/littlefs-project/littlefs)
7//! using the [`littlefs2-sys`](https://crates.io/crates/littlefs2-sys) crate. The [`littlefs2`](https://crates.io/crates/littlefs2) crate
8//! might have been an easier starting point but it doesn't
9//! currently allow setting the image configuration dynamically
10//! at runtime, such as the block size and count.
11//!
12//! `littlefs2-pack` is tested for compatibility with the C++
13//! [`mklittlefs` project](https://github.com/earlephilhower/mklittlefs). This is ensured with the `cross-compat.rs`
14//! test that packs with one tool then unpack with the other,
15//! in both directions. These tests are ran against the version of
16//! `mklittlefs` in the submodule and requires that tool to be built
17//! prior to running the tests.
18//!
19//! ## Example
20//!
21//! ```rust,no_run
22//! use littlefs2_pack::littlefs::LfsImage;
23//! use littlefs2_pack::config::ImageConfig;
24//!
25//! let config = ImageConfig {
26//! name: String::from("filesystem"),
27//! block_size: 4096,
28//! block_count: 128,
29//! read_size: 256,
30//! write_size: 256,
31//! block_cycles: -1,
32//! cache_size: 256,
33//! lookahead_size: 8,
34//! };
35//!
36//! let mut image = LfsImage::new(config).unwrap();
37//! image.format().unwrap();
38//!
39//! image.mount_and_then(|fs| {
40//! fs.create_dir("/data")?;
41//! fs.write_file("/data/hello.txt", b"Hello from LittleFS!")?;
42//! Ok(())
43//! }).unwrap();
44//!
45//! let binary = image.into_data();
46//! std::fs::write("filesystem.bin", &binary).unwrap();
47//! ```
48
49use std::path::Path;
50
51use crate::{config::Config, littlefs::LfsImage, partition_table::get_partition};
52
53pub mod config;
54pub mod littlefs;
55pub mod partition_table;
56pub mod walk;
57
58/// Generate a LittleFS image and Rust configuration module from a
59/// `littlefs.toml` file.
60///
61/// Reads the TOML configuration at `littlefs_config`, packs the
62/// directory tree it references into a LittleFS binary image, and
63/// writes two files into `$OUT_DIR`:
64///
65/// - **`filesystem.bin`** — the raw LittleFS image ready to be
66/// flashed to the device.
67/// - **`littlefs_config.rs`** — Rust constants for the image geometry
68/// (`BLOCK_SIZE`, `BLOCK_COUNT`, `TOTAL_SIZE`, etc.), typenum
69/// aliases, an `IMAGE` static that embeds the binary via
70/// `include_bytes!`, and an optional `paths` module mirroring the
71/// packed directory layout.
72///
73/// Also copies the built filesystem image up to the target/<profile> directory.
74/// The `$OUT_DIR` is difficult to access during flash or runtime since it's
75/// a hash encoded build directory. This step makes the image much easier to
76/// find at flash time.
77///
78/// # Usage in `build.rs`
79///
80/// ```rust,no_run
81/// littlefs2_pack::pack_and_generate_config(
82/// std::path::Path::new("littlefs.toml"),
83/// );
84/// ```
85///
86/// Then in your firmware crate:
87///
88/// ```rust,ignore
89/// mod littlefs {
90/// include!(concat!(env!("OUT_DIR"), "/littlefs_config.rs"));
91/// }
92/// ```
93///
94/// # Panics
95///
96/// Panics if the `OUT_DIR` environment variable is not set (i.e. this
97/// function is called outside of a Cargo build script), or if any step
98/// of config parsing, image creation, packing, or file I/O fails. This
99/// panic behavior is because a build should not proceed if this step
100/// doesn't succeed.
101pub fn pack_and_generate_config(littlefs_config: &Path) {
102 // Load the config from the file
103 let config = Config::from_file(littlefs_config).unwrap();
104
105 let image_name = config.image.name.clone();
106 let out_dir = std::env::var("OUT_DIR").unwrap();
107
108 // OUT_DIR is like target/<triple>/<profile>/build/<crate>-<hash>/out
109 // The issue with this directory is that it's only easily accessible
110 // at compile time, it's hard to discern at run time. This script
111 // copies the image up to the target profile directory after build
112 // so the image can be found at flash time
113 let profile_dir = Path::new(&out_dir)
114 .ancestors()
115 .nth(3) // out/ -> <crate>-<hash>/ -> build/ -> <profile>/
116 .unwrap();
117 let img_file_path = format!("{}/{}.bin", out_dir, image_name);
118 let rust_file_path = format!("{}/{}.rs", out_dir, image_name);
119
120 // Create, format, and pack the image
121 let mut image = LfsImage::new(config.image).unwrap();
122 image.format().unwrap();
123 image.pack_from_config(config.directory).unwrap();
124
125 // Generate the Rust config module
126 std::fs::write(&rust_file_path, &image.emit_rust().unwrap()).unwrap();
127
128 // Generate the image binary
129 let binary = image.into_data();
130 std::fs::write(&img_file_path, &binary).unwrap();
131
132 // Copy the binary up to the profile directory
133 std::fs::copy(
134 &img_file_path,
135 profile_dir.join(format!("{}.bin", image_name)),
136 )
137 .unwrap();
138}
139
140/// Generate a Rust module with partition offset and size constants
141/// from an ESP-IDF partition table CSV.
142///
143/// Reads the CSV at `partition_csv`, looks up the partition named
144/// `partition_name`, and writes a `partition_config.rs` file into
145/// `$OUT_DIR` containing:
146///
147/// ```text
148/// pub const PARTITION_NAME: &str = "littlefs";
149/// pub const PARTITION_OFFSET: u32 = 0x200000;
150/// pub const PARTITION_SIZE: u32 = 0xE00000;
151/// ```
152///
153/// # Usage in `build.rs`
154///
155/// ```rust,no_run
156/// littlefs2_pack::generate_esp_partitions_config(
157/// std::path::Path::new("partitions.csv"),
158/// "littlefs",
159/// );
160/// ```
161///
162/// Then in your firmware crate:
163///
164/// ```rust,ignore
165/// mod partition {
166/// include!(concat!(env!("OUT_DIR"), "/partition_config.rs"));
167/// }
168/// ```
169pub fn generate_esp_partitions_config(partition_csv: &Path, partition_name: &str) {
170 let out_dir = std::env::var("OUT_DIR").unwrap();
171
172 let partition = get_partition(partition_csv, partition_name).unwrap();
173 partition.emit_rust(Path::new(&out_dir)).unwrap();
174}