build.rs 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321
  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. build_idl_with_cargo_args(program_path, resolution, skip_lint, no_docs, &[])
  48. }
  49. /// Generate IDL via compilation with passing cargo arguments.
  50. pub fn build_idl_with_cargo_args(
  51. program_path: impl AsRef<Path>,
  52. resolution: bool,
  53. skip_lint: bool,
  54. no_docs: bool,
  55. cargo_args: &[String],
  56. ) -> Result<Idl> {
  57. let idl = build(
  58. program_path.as_ref(),
  59. resolution,
  60. skip_lint,
  61. no_docs,
  62. cargo_args,
  63. )?;
  64. let idl = convert_module_paths(idl);
  65. let idl = sort(idl);
  66. verify(&idl)?;
  67. Ok(idl)
  68. }
  69. /// Build IDL.
  70. fn build(
  71. program_path: &Path,
  72. resolution: bool,
  73. skip_lint: bool,
  74. no_docs: bool,
  75. cargo_args: &[String],
  76. ) -> Result<Idl> {
  77. // `nightly` toolchain is currently required for building the IDL.
  78. let toolchain = std::env::var("RUSTUP_TOOLCHAIN")
  79. .map(|toolchain| format!("+{}", toolchain))
  80. .unwrap_or_else(|_| "+nightly".to_string());
  81. install_toolchain_if_needed(&toolchain)?;
  82. let output = Command::new("cargo")
  83. .args([
  84. &toolchain,
  85. "test",
  86. "__anchor_private_print_idl",
  87. "--features",
  88. "idl-build",
  89. ])
  90. .args(cargo_args)
  91. .args(["--", "--show-output", "--quiet"])
  92. .env(
  93. "ANCHOR_IDL_BUILD_NO_DOCS",
  94. if no_docs { "TRUE" } else { "FALSE" },
  95. )
  96. .env(
  97. "ANCHOR_IDL_BUILD_RESOLUTION",
  98. if resolution { "TRUE" } else { "FALSE" },
  99. )
  100. .env(
  101. "ANCHOR_IDL_BUILD_SKIP_LINT",
  102. if skip_lint { "TRUE" } else { "FALSE" },
  103. )
  104. .env("ANCHOR_IDL_BUILD_PROGRAM_PATH", program_path)
  105. .env("RUSTFLAGS", "--cfg procmacro2_semver_exempt")
  106. .current_dir(program_path)
  107. .stderr(Stdio::inherit())
  108. .output()?;
  109. if !output.status.success() {
  110. return Err(anyhow!("Building IDL failed"));
  111. }
  112. enum State {
  113. Pass,
  114. Address,
  115. Constants(Vec<String>),
  116. Events(Vec<String>),
  117. Errors(Vec<String>),
  118. Program(Vec<String>),
  119. }
  120. let mut address = String::new();
  121. let mut events = vec![];
  122. let mut error_codes = vec![];
  123. let mut constants = vec![];
  124. let mut types = BTreeMap::new();
  125. let mut idl: Option<Idl> = None;
  126. let output = String::from_utf8_lossy(&output.stdout);
  127. if env::var("ANCHOR_LOG").is_ok() {
  128. println!("{}", output);
  129. }
  130. let mut state = State::Pass;
  131. for line in output.lines() {
  132. match &mut state {
  133. State::Pass => match line {
  134. "--- IDL begin address ---" => state = State::Address,
  135. "--- IDL begin const ---" => state = State::Constants(vec![]),
  136. "--- IDL begin event ---" => state = State::Events(vec![]),
  137. "--- IDL begin errors ---" => state = State::Errors(vec![]),
  138. "--- IDL begin program ---" => state = State::Program(vec![]),
  139. _ => {
  140. if line.starts_with("test result: ok")
  141. && !line.starts_with("test result: ok. 0 passed; 0 failed; 0")
  142. {
  143. if let Some(idl) = idl.as_mut() {
  144. idl.address = mem::take(&mut address);
  145. idl.constants = mem::take(&mut constants);
  146. idl.events = mem::take(&mut events);
  147. idl.errors = mem::take(&mut error_codes);
  148. idl.types = {
  149. let prog_ty = mem::take(&mut idl.types);
  150. let mut types = mem::take(&mut types);
  151. types.extend(prog_ty.into_iter().map(|ty| (ty.name.clone(), ty)));
  152. types.into_values().collect()
  153. };
  154. }
  155. }
  156. }
  157. },
  158. State::Address => {
  159. address = line.replace(|c: char| !c.is_alphanumeric(), "");
  160. state = State::Pass;
  161. continue;
  162. }
  163. State::Constants(lines) => {
  164. if line == "--- IDL end const ---" {
  165. let constant = serde_json::from_str(&lines.join("\n"))?;
  166. constants.push(constant);
  167. state = State::Pass;
  168. continue;
  169. }
  170. lines.push(line.to_owned());
  171. }
  172. State::Events(lines) => {
  173. if line == "--- IDL end event ---" {
  174. #[derive(Deserialize)]
  175. struct IdlBuildEventPrint {
  176. event: IdlEvent,
  177. types: Vec<IdlTypeDef>,
  178. }
  179. let event = serde_json::from_str::<IdlBuildEventPrint>(&lines.join("\n"))?;
  180. events.push(event.event);
  181. types.extend(event.types.into_iter().map(|ty| (ty.name.clone(), ty)));
  182. state = State::Pass;
  183. continue;
  184. }
  185. lines.push(line.to_owned());
  186. }
  187. State::Errors(lines) => {
  188. if line == "--- IDL end errors ---" {
  189. error_codes = serde_json::from_str(&lines.join("\n"))?;
  190. state = State::Pass;
  191. continue;
  192. }
  193. lines.push(line.to_owned());
  194. }
  195. State::Program(lines) => {
  196. if line == "--- IDL end program ---" {
  197. idl = Some(serde_json::from_str(&lines.join("\n"))?);
  198. state = State::Pass;
  199. continue;
  200. }
  201. lines.push(line.to_owned());
  202. }
  203. }
  204. }
  205. idl.ok_or_else(|| anyhow!("IDL doesn't exist"))
  206. }
  207. /// Install the given toolchain if it's not already installed.
  208. fn install_toolchain_if_needed(toolchain: &str) -> Result<()> {
  209. let is_installed = Command::new("cargo")
  210. .arg(toolchain)
  211. .output()?
  212. .status
  213. .success();
  214. if !is_installed {
  215. Command::new("rustup")
  216. .args(["toolchain", "install", toolchain.trim_start_matches('+')])
  217. .spawn()?
  218. .wait()?;
  219. }
  220. Ok(())
  221. }
  222. /// Convert paths to name if there are no conflicts.
  223. fn convert_module_paths(idl: Idl) -> Idl {
  224. let idl = serde_json::to_string(&idl).unwrap();
  225. let idl = Regex::new(r#""((\w+::)+)(\w+)""#)
  226. .unwrap()
  227. .captures_iter(&idl.clone())
  228. .fold(idl, |acc, cur| {
  229. let path = cur.get(0).unwrap().as_str();
  230. let name = cur.get(3).unwrap().as_str();
  231. // Replace path with name
  232. let replaced_idl = acc.replace(path, &format!(r#""{name}""#));
  233. // Check whether there is a conflict
  234. let has_conflict = replaced_idl.contains(&format!(r#"::{name}""#));
  235. if has_conflict {
  236. acc
  237. } else {
  238. replaced_idl
  239. }
  240. });
  241. serde_json::from_str(&idl).expect("Invalid IDL")
  242. }
  243. /// Alphabetically sort fields for consistency.
  244. fn sort(mut idl: Idl) -> Idl {
  245. idl.accounts.sort_by(|a, b| a.name.cmp(&b.name));
  246. idl.constants.sort_by(|a, b| a.name.cmp(&b.name));
  247. idl.events.sort_by(|a, b| a.name.cmp(&b.name));
  248. idl.instructions.sort_by(|a, b| a.name.cmp(&b.name));
  249. idl.types.sort_by(|a, b| a.name.cmp(&b.name));
  250. idl
  251. }
  252. /// Verify IDL is valid.
  253. fn verify(idl: &Idl) -> Result<()> {
  254. // Check full path accounts
  255. if let Some(account) = idl
  256. .accounts
  257. .iter()
  258. .find(|account| account.name.contains("::"))
  259. {
  260. return Err(anyhow!(
  261. "Conflicting accounts names are not allowed.\nProgram: `{}`\nAccount: `{}`",
  262. idl.metadata.name,
  263. account.name
  264. ));
  265. }
  266. // Check potential discriminator collisions
  267. macro_rules! check_discriminator_collision {
  268. ($field:ident) => {
  269. if let Some((outer, inner)) = idl.$field.iter().find_map(|outer| {
  270. idl.$field
  271. .iter()
  272. .filter(|inner| inner.name != outer.name)
  273. .find(|inner| outer.discriminator.starts_with(&inner.discriminator))
  274. .map(|inner| (outer, inner))
  275. }) {
  276. return Err(anyhow!(
  277. "Ambiguous discriminators for {} `{}` and `{}`",
  278. stringify!($field),
  279. outer.name,
  280. inner.name
  281. ));
  282. }
  283. };
  284. }
  285. check_discriminator_collision!(accounts);
  286. check_discriminator_collision!(events);
  287. check_discriminator_collision!(instructions);
  288. Ok(())
  289. }