build.rs 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274
  1. use std::{
  2. collections::BTreeMap,
  3. env, mem,
  4. path::Path,
  5. process::{Command, Stdio},
  6. };
  7. use anyhow::{anyhow, Result};
  8. use regex::Regex;
  9. use serde::Deserialize;
  10. use crate::types::{Idl, IdlEvent, IdlTypeDef};
  11. /// A trait that types must implement in order to include the type in the IDL definition.
  12. ///
  13. /// This trait is automatically implemented for Anchor all types that use the `AnchorSerialize`
  14. /// proc macro. Note that manually implementing the `AnchorSerialize` trait does **NOT** have the
  15. /// same effect.
  16. ///
  17. /// Types that don't implement this trait will cause a compile error during the IDL generation.
  18. ///
  19. /// The default implementation of the trait allows the program to compile but the type does **NOT**
  20. /// get included in the IDL.
  21. pub trait IdlBuild {
  22. /// Create an IDL type definition for the type.
  23. ///
  24. /// The type is only included in the IDL if this method returns `Some`.
  25. fn create_type() -> Option<IdlTypeDef> {
  26. None
  27. }
  28. /// Insert all types that are included in the current type definition to the given map.
  29. fn insert_types(_types: &mut BTreeMap<String, IdlTypeDef>) {}
  30. /// Get the full module path of the type.
  31. ///
  32. /// The full path will be used in the case of a conflicting type definition, e.g. when there
  33. /// are multiple structs with the same name.
  34. ///
  35. /// The default implementation covers most cases.
  36. fn get_full_path() -> String {
  37. std::any::type_name::<Self>().into()
  38. }
  39. }
  40. /// Generate IDL via compilation.
  41. pub fn build_idl(
  42. program_path: impl AsRef<Path>,
  43. resolution: bool,
  44. skip_lint: bool,
  45. no_docs: bool,
  46. ) -> Result<Idl> {
  47. let idl = build(program_path.as_ref(), resolution, skip_lint, no_docs)?;
  48. let idl = convert_module_paths(idl);
  49. let idl = sort(idl);
  50. verify(&idl)?;
  51. Ok(idl)
  52. }
  53. /// Build IDL.
  54. fn build(program_path: &Path, resolution: bool, skip_lint: bool, no_docs: bool) -> Result<Idl> {
  55. // `nightly` toolchain is currently required for building the IDL.
  56. let toolchain = std::env::var("RUSTUP_TOOLCHAIN")
  57. .map(|toolchain| format!("+{}", toolchain))
  58. .unwrap_or_else(|_| "+nightly".to_string());
  59. install_toolchain_if_needed(&toolchain)?;
  60. let output = Command::new("cargo")
  61. .args([
  62. &toolchain,
  63. "test",
  64. "__anchor_private_print_idl",
  65. "--features",
  66. "idl-build",
  67. "--",
  68. "--show-output",
  69. "--quiet",
  70. ])
  71. .env(
  72. "ANCHOR_IDL_BUILD_NO_DOCS",
  73. if no_docs { "TRUE" } else { "FALSE" },
  74. )
  75. .env(
  76. "ANCHOR_IDL_BUILD_RESOLUTION",
  77. if resolution { "TRUE" } else { "FALSE" },
  78. )
  79. .env(
  80. "ANCHOR_IDL_BUILD_SKIP_LINT",
  81. if skip_lint { "TRUE" } else { "FALSE" },
  82. )
  83. .env("ANCHOR_IDL_BUILD_PROGRAM_PATH", program_path)
  84. .env("RUSTFLAGS", "--cfg procmacro2_semver_exempt")
  85. .current_dir(program_path)
  86. .stderr(Stdio::inherit())
  87. .output()?;
  88. if !output.status.success() {
  89. return Err(anyhow!("Building IDL failed"));
  90. }
  91. enum State {
  92. Pass,
  93. Address,
  94. Constants(Vec<String>),
  95. Events(Vec<String>),
  96. Errors(Vec<String>),
  97. Program(Vec<String>),
  98. }
  99. let mut address = String::new();
  100. let mut events = vec![];
  101. let mut error_codes = vec![];
  102. let mut constants = vec![];
  103. let mut types = BTreeMap::new();
  104. let mut idl: Option<Idl> = None;
  105. let output = String::from_utf8_lossy(&output.stdout);
  106. if env::var("ANCHOR_LOG").is_ok() {
  107. println!("{}", output);
  108. }
  109. let mut state = State::Pass;
  110. for line in output.lines() {
  111. match &mut state {
  112. State::Pass => match line {
  113. "--- IDL begin address ---" => state = State::Address,
  114. "--- IDL begin const ---" => state = State::Constants(vec![]),
  115. "--- IDL begin event ---" => state = State::Events(vec![]),
  116. "--- IDL begin errors ---" => state = State::Errors(vec![]),
  117. "--- IDL begin program ---" => state = State::Program(vec![]),
  118. _ => {
  119. if line.starts_with("test result: ok") {
  120. if let Some(idl) = idl.as_mut() {
  121. idl.address = mem::take(&mut address);
  122. idl.constants = mem::take(&mut constants);
  123. idl.events = mem::take(&mut events);
  124. idl.errors = mem::take(&mut error_codes);
  125. idl.types = {
  126. let prog_ty = mem::take(&mut idl.types);
  127. let mut types = mem::take(&mut types);
  128. types.extend(prog_ty.into_iter().map(|ty| (ty.name.clone(), ty)));
  129. types.into_values().collect()
  130. };
  131. }
  132. }
  133. }
  134. },
  135. State::Address => {
  136. address = line.replace(|c: char| !c.is_alphanumeric(), "");
  137. state = State::Pass;
  138. continue;
  139. }
  140. State::Constants(lines) => {
  141. if line == "--- IDL end const ---" {
  142. let constant = serde_json::from_str(&lines.join("\n"))?;
  143. constants.push(constant);
  144. state = State::Pass;
  145. continue;
  146. }
  147. lines.push(line.to_owned());
  148. }
  149. State::Events(lines) => {
  150. if line == "--- IDL end event ---" {
  151. #[derive(Deserialize)]
  152. struct IdlBuildEventPrint {
  153. event: IdlEvent,
  154. types: Vec<IdlTypeDef>,
  155. }
  156. let event = serde_json::from_str::<IdlBuildEventPrint>(&lines.join("\n"))?;
  157. events.push(event.event);
  158. types.extend(event.types.into_iter().map(|ty| (ty.name.clone(), ty)));
  159. state = State::Pass;
  160. continue;
  161. }
  162. lines.push(line.to_owned());
  163. }
  164. State::Errors(lines) => {
  165. if line == "--- IDL end errors ---" {
  166. error_codes = serde_json::from_str(&lines.join("\n"))?;
  167. state = State::Pass;
  168. continue;
  169. }
  170. lines.push(line.to_owned());
  171. }
  172. State::Program(lines) => {
  173. if line == "--- IDL end program ---" {
  174. idl = Some(serde_json::from_str(&lines.join("\n"))?);
  175. state = State::Pass;
  176. continue;
  177. }
  178. lines.push(line.to_owned());
  179. }
  180. }
  181. }
  182. idl.ok_or_else(|| anyhow!("IDL doesn't exist"))
  183. }
  184. /// Install the given toolchain if it's not already installed.
  185. fn install_toolchain_if_needed(toolchain: &str) -> Result<()> {
  186. let is_installed = Command::new("cargo")
  187. .arg(toolchain)
  188. .output()?
  189. .status
  190. .success();
  191. if !is_installed {
  192. Command::new("rustup")
  193. .args(["toolchain", "install", toolchain.trim_start_matches('+')])
  194. .spawn()?
  195. .wait()?;
  196. }
  197. Ok(())
  198. }
  199. /// Convert paths to name if there are no conflicts.
  200. fn convert_module_paths(idl: Idl) -> Idl {
  201. let idl = serde_json::to_string(&idl).unwrap();
  202. let idl = Regex::new(r#""((\w+::)+)(\w+)""#)
  203. .unwrap()
  204. .captures_iter(&idl.clone())
  205. .fold(idl, |acc, cur| {
  206. let path = cur.get(0).unwrap().as_str();
  207. let name = cur.get(3).unwrap().as_str();
  208. // Replace path with name
  209. let replaced_idl = acc.replace(path, &format!(r#""{name}""#));
  210. // Check whether there is a conflict
  211. let has_conflict = replaced_idl.contains(&format!(r#"::{name}""#));
  212. if has_conflict {
  213. acc
  214. } else {
  215. replaced_idl
  216. }
  217. });
  218. serde_json::from_str(&idl).expect("Invalid IDL")
  219. }
  220. /// Alphabetically sort fields for consistency.
  221. fn sort(mut idl: Idl) -> Idl {
  222. idl.accounts.sort_by(|a, b| a.name.cmp(&b.name));
  223. idl.constants.sort_by(|a, b| a.name.cmp(&b.name));
  224. idl.events.sort_by(|a, b| a.name.cmp(&b.name));
  225. idl.instructions.sort_by(|a, b| a.name.cmp(&b.name));
  226. idl.types.sort_by(|a, b| a.name.cmp(&b.name));
  227. idl
  228. }
  229. /// Verify IDL is valid.
  230. fn verify(idl: &Idl) -> Result<()> {
  231. // Check full path accounts
  232. if let Some(account) = idl
  233. .accounts
  234. .iter()
  235. .find(|account| account.name.contains("::"))
  236. {
  237. return Err(anyhow!(
  238. "Conflicting accounts names are not allowed.\nProgram: `{}`\nAccount: `{}`",
  239. idl.metadata.name,
  240. account.name
  241. ));
  242. }
  243. Ok(())
  244. }