playdate_build/
layout.rs

1use std::borrow::Cow;
2use std::ffi::OsStr;
3use std::ffi::OsString;
4use std::fmt::Debug;
5use std::fmt::Display;
6use std::fmt::Write;
7use std::ops::Deref;
8use std::path::Path;
9use std::path::PathBuf;
10
11use crate::compile::PDX_BIN_NAME_ELF;
12use crate::compile::PDX_BIN_NAME_STEM;
13use crate::compile::PDX_PKG_EXT;
14use crate::compile::PDX_PKG_MANIFEST_FILENAME;
15use crate::compile::dylib_suffix_for_host;
16use crate::config::Env;
17
18
19pub trait Layout {
20	fn name(&self) -> &Name;
21
22
23	/// The root directory: `/path/to/target/profile/playdate`.
24	/// If per-target: `/path/to/target/$TRIPLE/profile/playdate`.
25	/// If build-script: `/path/to/target(/$TRIPLE)/profile/build/**/out` ($OUT_DIR or `export-dir`).
26	fn root(&self) -> &Path;
27
28	/// The intermediate artifact destination: `$root/$NAME/`
29	///
30	/// Crate can have multiple _targets_ (e.g. bins, lib, examples),
31	/// so we're have to specify the name,
32	/// so `dest` will be `$root/$NAME/`,
33	/// where `$NAME` is the name of the target.
34	fn dest(&self) -> Cow<Path> { self.root().join(self.name().as_ref()).into() }
35
36	/// Collected assets
37	fn assets(&self) -> Cow<Path> { self.build().clone() }
38	/// Hash of collected assets: `$dest/.assets.hash`
39	fn assets_hash(&self) -> Cow<Path> { self.dest().join(".assets.hash").into() }
40	fn assets_plan(&self) -> Cow<Path> { self.dest().join("plan.json").into() }
41
42	/// The directory for build package: `$dest/build`
43	///
44	/// Directory with all files prepared to build with pdc.
45	///
46	/// Contains:
47	/// - pdex.elf    : by cargo (+gcc) (link)
48	/// - pdex.dylib  : by cargo (link)
49	/// - pdxinfo     : manifest
50	/// - * files     : linked assets
51	fn build(&self) -> Cow<Path> { self.dest().join("build").into() }
52
53	/// Playdate package manifest: `$build/pdxinfo`
54	fn manifest(&self) -> Cow<Path> { self.build().join(PDX_PKG_MANIFEST_FILENAME).into() }
55
56	/// Playdate (hw) executable: `$build/pdex.elf`
57	fn binary(&self) -> Cow<Path> { self.build().join(PDX_BIN_NAME_ELF).into() }
58
59	/// Playdate (sim) library: `$build/pdex.(dylib|dll)`
60	///
61	/// Type of library depends on the current (HOST) target.
62	fn library(&self) -> Cow<Path> {
63		self.build()
64		    .join(PDX_BIN_NAME_STEM)
65		    .with_extension(dylib_suffix_for_host())
66		    .into()
67	}
68
69
70	/// The final package: `$root/$NAME.pdx`
71	fn artifact(&self) -> Cow<Path> {
72		self.root()
73		    .join(self.name().as_ref())
74		    .with_extension(PDX_PKG_EXT)
75		    .into()
76	}
77
78
79	/// Create all directories.
80	fn prepare(&mut self) -> std::io::Result<()> {
81		use std::fs::create_dir_all;
82		create_dir_all(self.root())?;
83		create_dir_all(self.dest())?;
84		create_dir_all(self.assets())?;
85		create_dir_all(self.build())?;
86		Ok(())
87	}
88}
89
90
91/// Default layout, usually for build-script.
92pub struct DefaultLayout {
93	name: Name,
94	root: PathBuf,
95	dest: PathBuf,
96	build: PathBuf,
97}
98
99
100impl DefaultLayout {
101	pub fn new(name: Name, root: PathBuf) -> Self {
102		let dest = root.join(name.as_path());
103		let build = dest.join("build");
104		Self { name,
105		       root,
106		       dest,
107		       build }
108	}
109}
110
111
112impl Layout for DefaultLayout {
113	fn name(&self) -> &Name { &self.name }
114	fn root(&self) -> &Path { self.root.as_path() }
115	fn dest(&self) -> Cow<Path> { self.dest.as_path().into() }
116	fn build(&self) -> Cow<Path> { self.build.as_path().into() }
117}
118
119
120#[derive(Clone)]
121pub struct Name(OsString);
122
123
124impl Name {
125	/// `crate_name` is a name of target, such as name of lib, bin or example.
126	pub fn with_names<S1, S2>(package_name: S1, crate_name: Option<S2>) -> Self
127		where S1: Into<OsString>,
128		      S1: PartialEq<S2>,
129		      S2: Into<OsString> {
130		let mut name: OsString = package_name.into();
131
132		if let Some(crate_name) = crate_name.map(Into::into) {
133			if name != crate_name {
134				name.write_fmt(format_args!("-{}", crate_name.to_string_lossy()))
135				    .unwrap();
136			}
137		}
138
139		Name(name)
140	}
141
142	/// `crate_name` is a name of target, such as name of lib, bin or example.
143	pub fn with_package<S>(package_name: S) -> Self
144		where S: Into<OsString> {
145		Name(package_name.into())
146	}
147
148
149	pub fn from_env(env: &Env) -> Self {
150		const UNKNOWN_CARGO_PKG_NAME: &str = "unknown";
151		let name = env.vars
152		              .get("CARGO_BIN_NAME")
153		              .or_else(|| env.vars.get("CARGO_CRATE_NAME"))
154		              .or_else(|| env.vars.get("CARGO_PKG_NAME"))
155		              .map(|s| s.as_str())
156		              .unwrap_or(UNKNOWN_CARGO_PKG_NAME);
157		Self(name.into())
158	}
159}
160
161
162impl Display for Name {
163	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
164		write!(f, "{}", self.0.to_string_lossy())
165	}
166}
167
168impl Debug for Name {
169	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{:?}", self.0) }
170}
171
172
173impl AsRef<OsStr> for Name {
174	fn as_ref(&self) -> &OsStr { self.0.as_ref() }
175}
176
177impl Name {
178	pub fn as_path(&self) -> &Path { Path::new(&self.0) }
179}
180
181impl Deref for Name {
182	type Target = Path;
183	fn deref(&self) -> &Self::Target { self.as_path() }
184}