rl_lang/tooling/
package.rs1use std::collections::HashSet;
19use std::io::Write;
20use std::path::{Path, PathBuf};
21
22const MAGIC: &[u8] = b"\x00RL_PACKAGE_V1\x00";
23const MAGIC_LEN: usize = 15;
24const FOOTER_LEN: usize = MAGIC_LEN + 8;
25
26pub fn find_embedded() -> Option<String> {
31 let path = std::env::current_exe().ok()?;
32 let bytes = std::fs::read(path).ok()?;
33
34 if bytes.len() < FOOTER_LEN {
35 return None;
36 }
37
38 let len_start = bytes.len() - 8;
40 let len = u64::from_le_bytes(bytes[len_start..].try_into().ok()?) as usize;
41
42 let magic_start = len_start.checked_sub(MAGIC_LEN)?;
44 if &bytes[magic_start..len_start] != MAGIC {
45 return None;
46 }
47
48 let source_start = magic_start.checked_sub(len)?;
50 let source_end = magic_start;
51
52 String::from_utf8(bytes[source_start..source_end].to_vec()).ok()
53}
54
55pub fn package(source_path: &str, output_path: &str) {
62 if let Err(e) = try_package(source_path, output_path) {
63 eprintln!("error: packaging failed: {}", e);
64 std::process::exit(1);
65 }
66 println!("packaged '{}' -> '{}'", source_path, output_path);
67}
68
69fn try_package(source_path: &str, output_path: &str) -> std::io::Result<()> {
70 let source = bundle(source_path)?;
71 let self_bytes = std::fs::read(std::env::current_exe()?)?;
72
73 let mut out = std::fs::File::create(output_path)?;
74 out.write_all(&self_bytes)?;
75 out.write_all(source.as_bytes())?;
76 out.write_all(MAGIC)?;
77 out.write_all(&(source.len() as u64).to_le_bytes())?;
78
79 #[cfg(unix)]
80 {
81 use std::os::unix::fs::PermissionsExt;
82 let mut perms = std::fs::metadata(output_path)?.permissions();
83 perms.set_mode(0o755);
84 std::fs::set_permissions(output_path, perms)?;
85 }
86
87 Ok(())
88}
89
90fn bundle(entry_path: &str) -> std::io::Result<String> {
91 let mut visited = HashSet::new();
92 bundle_inner(Path::new(entry_path), &mut visited)
93}
94
95fn bundle_inner(path: &Path, visited: &mut HashSet<PathBuf>) -> std::io::Result<String> {
96 let canonical = path.canonicalize()?;
97 if visited.contains(&canonical) {
98 return Ok(String::new());
99 }
100 visited.insert(canonical.clone());
101
102 let source = std::fs::read_to_string(path)?;
103 let base_dir = path.parent().unwrap_or(Path::new("."));
104 let mut output = String::new();
105
106 for line in source.lines() {
107 let trimmed = line.trim();
108
109 if let Some(rest) = trimmed.strip_prefix("get ") {
110 let file_part = if let Some(from_idx) = rest.find(" from ") {
114 rest[from_idx + 6..].trim()
115 } else {
116 rest.trim()
117 };
118
119 if !file_part.contains("::") {
121 let rel: PathBuf = file_part.split('/').collect();
122 let file_path = base_dir.join(rel).with_extension("rl");
123 match bundle_inner(&file_path, visited) {
124 Ok(inlined) => {
125 output.push_str(&inlined);
126 output.push('\n');
127 continue;
128 }
129 Err(e) => {
130 eprintln!("warning: could not bundle '{}': {}", file_path.display(), e);
131 }
132 }
133 }
134 }
135
136 output.push_str(line);
137 output.push('\n');
138 }
139
140 Ok(output)
141}