lunka_src/
lib.rs

1use ::cc::Build as CcBuild;
2use ::std::{
3	fs::read_dir,
4	io::Error as IoError,
5	path::Path,
6};
7
8pub use ::cc::Error as CcError;
9
10mod lua_conf;
11pub use lua_conf::*;
12pub mod platforms;
13
14use platforms::{
15	Platform, from_current_triple, CURRENT_TRIPLE,
16};
17
18/// Builder for a compilation of Lua 5.4.
19#[repr(transparent)]
20pub struct Build {
21	cc: CcBuild,
22}
23
24impl Build {
25	/// Create a new builder based on the [`Platform`] returned by [`from_current_triple`],
26	/// panicking if determining the platform or setting up failed.
27	pub fn for_current() -> Self {
28		let Some(platform) = from_current_triple() else {
29			panic!("couldn't determine platform for current target triple {CURRENT_TRIPLE:?}");
30		};
31		Self::new(platform)
32	}
33}
34
35impl Build {
36	/// Create a new builder based on a [`Platform`],
37	/// panicking if setting up failed.
38	/// 
39	/// See also [`Build::try_new`] for the non-panicking version.
40	pub fn new<P: Platform>(p: P) -> Self {
41		match Self::try_new(p) {
42			Ok(b) => b,
43			Err(e) => panic!("{e}"),
44		}
45	}
46
47	/// Create a new builder based on a [`Platform`].
48	pub fn try_new<P: Platform>(p: P) -> Result<Self, CcError> {
49		let mut cc = CcBuild::new();
50
51		{
52			let tool = cc.try_get_compiler()?;
53
54			let stds = p.standards();
55			let mut set_std = |std: Option<&str>| if let Some(std) = std { cc.std(std); };
56			if tool.is_like_gnu() {
57				set_std(stds.gnu)
58			} else if tool.is_like_clang() {
59				set_std(stds.clang)
60			} else if tool.is_like_msvc() {
61				set_std(stds.msvc)
62			} else if tool.is_like_clang_cl() {
63				set_std(stds.clang_cl)
64			}
65		}
66	
67		cc.warnings(true).extra_warnings(true);
68		for define in p.defines() {
69			cc.define(define, None);
70		}
71	
72		Ok(Self {
73			cc,
74		})
75	}
76
77	/// Run the compiler, generating the file `output`,
78	/// and panicking if compilation fails.
79	/// 
80	/// See also [`Build::try_compile`] for the non-panicking version.
81	pub fn compile(&self, output: &str) {
82		if let Err(e) = self.try_compile(output) {
83			panic!("{e}");
84		}
85	}
86
87	/// Run the compiler, generating the file `output`.
88	pub fn try_compile(&self, output: &str) -> Result<(), CcError> {
89		self.cc.try_compile(output)
90	}
91
92	/// Set the host assumed by this configuration.
93	pub fn host(&mut self, host: &str) -> &mut Self {
94		self.cc.host(host);
95		self
96	}
97
98	/// Set the output directory where all object files and static libraries will be located.
99	pub fn out_dir<P: AsRef<Path>>(&mut self, path: P) -> &mut Self {
100		self.cc.out_dir(path);
101		self
102	}
103
104	fn define_flag(&mut self, flag: &str) -> &mut Self {
105		self.cc.define(flag, None);
106		self
107	}
108
109	fn define_lit(&mut self, ident: &str, data: &str) -> &mut Self {
110		self.cc.define(ident, Some(data));
111		self
112	}
113
114	fn define_str(&mut self, ident: &str, data: &str) -> &mut Self {
115		let data = format!("\"{}\"", data.replace('"', "\\\"").replace('\\', "\\\\"));
116		self.define_lit(ident, &data)
117	}
118
119	/// Add all Lua 5.4.8 source files bundled with this crate,
120	/// which allows for [`LuaConf`] to be used,
121	/// panicking if an error occurs while reading the directory contents.
122	pub fn add_lunka_src(&mut self) -> &mut Self {
123		match self.try_add_lunka_src() {
124			Ok(s) => s,
125			Err(e) => panic!("{e}"),
126		}
127	}
128
129	/// Add all Lua 5.4.8 source files bundled with this crate,
130	/// which allows for [`LuaConf`] to be used.
131	pub fn try_add_lunka_src(&mut self) -> Result<&mut Self, IoError> {
132		let root = Path::new(env!("CARGO_MANIFEST_DIR")).join("lua-5.4.8");
133		self.include(root.join("include"));
134		let src = {
135			let mut b = root;
136			b.push("src");
137			b
138		};
139		for result in read_dir(src)? {
140			let item = result?;
141			if !item.file_type()?.is_file() {
142				continue
143			}
144			self.cc.file(item.path());
145		}
146		Ok(self)
147	}
148
149	/// Add all Lua source files found in the specified `root`,
150	/// panicking if an error occurs while reading the directory contents.
151	/// 
152	/// See also [`Build::try_add_lua_src`] for the non-panicking version.
153	/// 
154	/// `root` must be a directory containing both source files (`*.c`) and headers (`*.h`).
155	/// 
156	/// If [`LuaConf`] is used,
157	/// then the path cannot point to a normal Lua source distribution.
158	/// See the documentation for [`LuaConf`] for more details.
159	pub fn add_lua_src<P: AsRef<Path>>(&mut self, root: P) -> &mut Self {
160		match self.try_add_lua_src(root) {
161			Ok(s) => s,
162			Err(e) => panic!("{e}"),
163		}
164	}
165
166	/// Add all Lua source files found in the specified `root`.
167	/// 
168	/// `root` must be a directory containing both source files (`*.c`) and headers (`*.h`).
169	/// 
170	/// If [`LuaConf`] is used,
171	/// then the path cannot point to a normal Lua source distribution.
172	/// See the documentation for [`LuaConf`] for more details.
173	pub fn try_add_lua_src<P: AsRef<Path>>(&mut self, root: P) -> Result<&mut Self, IoError> {
174		const BINARIES: [&str; 2] = ["lua.c", "luac.c"];
175		for result in read_dir(root)? {
176			let item = result?;
177			if !item.file_type()?.is_file() {
178				continue
179			}
180
181			let file_name = item.file_name();
182			let Some(file_name) = file_name.to_str() else {
183				continue
184			};
185			if !file_name.ends_with(".c") || BINARIES.contains(&file_name) {
186				continue
187			}
188
189			self.cc.file(item.path());
190		}
191		Ok(self)
192	}
193
194	/// Add an include directory.
195	pub fn include<P: AsRef<Path>>(&mut self, path: P) -> &mut Self {
196		self.cc.include(path);
197		self
198	}
199
200	/// Adds multiple include directories.
201	pub fn includes<P>(&mut self, paths: P) -> &mut Self
202	where
203		P: IntoIterator,
204		P::Item: AsRef<Path>,
205	{
206		self.cc.includes(paths);
207		self
208	}
209
210	/// Set whether debug information should be emitted for this build.
211	pub fn debug_info(&mut self, emit_debug_info: bool) -> &mut Self {
212		self.cc.debug(emit_debug_info);
213		self
214	}
215
216	/// Set the semi-arbitrary optimization level for the generated object files.
217	pub fn opt_level(&mut self, opt_level: u32) -> &mut Self {
218		self.cc.opt_level(opt_level);
219		self
220	}
221
222	/// Enable compatibility with Lua 5.3.
223	pub fn compat_lua_5_3(&mut self) -> &mut Self {
224		self.define_flag("LUA_COMPAT_5_3")
225	}
226
227	/// Include several deprecated functions in the `math` library.
228	pub fn compat_math_lib(&mut self) -> &mut Self {
229		self.define_flag("LUA_COMPAT_MATH_LIB")
230	}
231
232	/// Emulate the `__le` metamethod using `__lt`.
233	pub fn compat_lt_le(&mut self) -> &mut Self {
234		self.define_flag("LUA_COMPAT_LT_LE")
235	}
236
237	/// Enable several consistency checks in the API.
238	pub fn api_checks(&mut self) -> &mut Self {
239		self.define_flag("LUA_USE_APICHECK")
240	}
241
242	/// Set the default path that Lua uses to look for Lua libraries.
243	pub fn lua_lib_path(&mut self, path: &str) -> &mut Self {
244		self.define_str("LUA_PATH_DEFAULT", path)
245	}
246
247	/// Set the default path that Lua uses to look for C libraries.
248	pub fn lua_c_lib_path(&mut self, path: &str) -> &mut Self {
249		self.define_str("LUA_CPATH_DEFAULT", path)
250	}
251
252	/// Set the directory separator for `require` submodules.
253	pub fn dir_separator(&mut self, sep: &str) -> &mut Self {
254		self.define_str("LUA_DIRSEP", sep)
255	}
256
257	/// Enable Unicode Identifiers.
258	/// 
259	/// This is a define that isn't explicitly mentioned in the configuration header,
260	/// but is checked in `lctype.c` to build the identifier character table.
261	pub fn unicode_identifiers(&mut self) -> &mut Self {
262		self.define_flag("LUA_UCID")
263	}
264
265	/// Use additional configuration provided by a [`LuaConf`] in this build.
266	pub fn lua_conf<S: AsRef<str>>(&mut self, lua_conf: &LuaConf<S>) -> &mut Self {
267		if lua_conf.no_number_to_string {
268			self.define_flag("LUNKA_NOCVTN2S");
269		}
270		if lua_conf.no_string_to_number {
271			self.define_flag("LUNKA_NOCVTS2N");
272		}
273		if let Some(extra_space) = lua_conf.extra_space.as_ref().map(move |s| s.as_ref()) {
274			self.define_lit("LUNKA_EXTRASPACE", extra_space);
275		}
276		if let Some(id_size) = lua_conf.id_size.as_ref().map(move |s| s.as_ref()) {
277			self.define_lit("LUNKA_IDSIZE", id_size);
278		}
279		self
280	}
281
282	/// Use 32-bit integers and floats despite what the platform is.
283	pub fn use_32_bits(&mut self) -> &mut Self {
284		self.define_flag("LUNKA_32BITS")
285	}
286}