Skip to main content

pop_common/
lib.rs

1// SPDX-License-Identifier: GPL-3.0
2
3#![doc = include_str!("../README.md")]
4
5pub use account_id::{parse_account, parse_h160_account};
6#[cfg(feature = "integration-tests")]
7#[allow(deprecated)]
8use assert_cmd::cargo::cargo_bin;
9pub use build::Profile;
10pub use docker::Docker;
11pub use errors::Error;
12pub use git::{Git, GitHub, Release};
13pub use helpers::{
14	get_project_name_from_path, get_relative_or_absolute_path, is_root, replace_in_file,
15};
16pub use metadata::format_type;
17pub use signer::create_signer;
18pub use sourcing::set_executable_permission;
19use std::{cmp::Ordering, net::TcpListener, ops::Deref};
20#[cfg(feature = "integration-tests")]
21use std::{ffi::OsStr, path::Path};
22pub use subxt::{Config, PolkadotConfig as DefaultConfig};
23pub use subxt_signer::sr25519::Keypair;
24pub use templates::{
25	extractor::extract_template_files,
26	frontend::{FrontendTemplate, FrontendType},
27};
28pub use test::test_project;
29
30/// Module for parsing and handling account IDs.
31pub mod account_id;
32/// Provides functionality for accessing rate-limited APIs.
33pub(crate) mod api;
34/// Provides build profiles for usage when building Rust projects.
35pub mod build;
36/// Test utilities for mocking commands.
37#[cfg(test)]
38pub mod command_mock;
39/// Provides utils to work with docker
40pub mod docker;
41/// Represents the various errors that can occur in the crate.
42pub mod errors;
43/// Provides functionality for interacting with Git, GitHub, repositories and releases.
44pub mod git;
45/// Provides general purpose file and path helpers.
46pub mod helpers;
47/// Provides functionality for resolving and managing Cargo manifests.
48pub mod manifest;
49/// Provides functionality for formatting and resolving metadata types.
50pub mod metadata;
51/// Provides parsers for determining Polkadot SDK versions.
52pub mod polkadot_sdk;
53/// Provides functionality for creating a signer from a secret URI.
54pub mod signer;
55/// Provides functionality for sourcing binaries from a variety of different sources.
56pub mod sourcing;
57/// Provides traits and functions used for templates and template extraction.
58pub mod templates;
59/// Module for testing utilities and functionality.
60pub mod test;
61/// Contains utilities for setting up a local test environment.
62pub mod test_env;
63
64static APP_USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"));
65
66/// Trait for observing status updates.
67pub trait Status {
68	/// Update the observer with the provided `status`.
69	fn update(&self, status: &str);
70}
71
72impl Status for () {
73	// no-op: status updates are ignored
74	fn update(&self, _: &str) {}
75}
76
77/// Determines the target triple based on the current platform.
78pub fn target() -> Result<&'static str, Error> {
79	use std::env::consts::*;
80
81	if OS == "windows" {
82		return Err(Error::UnsupportedPlatform { arch: ARCH, os: OS });
83	}
84
85	match ARCH {
86		"aarch64" => {
87			return match OS {
88				"macos" => Ok("aarch64-apple-darwin"),
89				_ => Ok("aarch64-unknown-linux-gnu"),
90			};
91		},
92		"x86_64" | "x86" => {
93			return match OS {
94				"macos" => Ok("x86_64-apple-darwin"),
95				_ => Ok("x86_64-unknown-linux-gnu"),
96			};
97		},
98		&_ => {},
99	}
100	Err(Error::UnsupportedPlatform { arch: ARCH, os: OS })
101}
102
103/// Creates a new Command instance for running the `pop` binary in integration tests.
104///
105/// # Arguments
106///
107/// * `dir` - The working directory where the command will be executed.
108/// * `args` - An iterator of arguments to pass to the command.
109///
110/// # Returns
111///
112/// A new Command instance configured to run the pop binary with the specified arguments
113#[cfg(feature = "integration-tests")]
114pub fn pop(
115	dir: &Path,
116	args: impl IntoIterator<Item = impl AsRef<OsStr>>,
117) -> tokio::process::Command {
118	#[allow(deprecated)]
119	let mut command = tokio::process::Command::new(cargo_bin("pop"));
120	command.current_dir(dir).args(args);
121	println!("{command:?}");
122	command
123}
124
125/// Checks if the preferred port is available; otherwise returns a random available port.
126///
127/// Note: There is a small window between checking port availability and the caller
128/// binding to it where another process could claim the port. Callers should handle
129/// bind failures gracefully.
130pub fn resolve_port(preferred_port: Option<u16>) -> u16 {
131	// Try to bind to the preferred port if provided.
132	if let Some(port) = preferred_port &&
133		TcpListener::bind(format!("127.0.0.1:{}", port)).is_ok()
134	{
135		return port;
136	}
137
138	// Else, fallback to a random available port.
139	TcpListener::bind("127.0.0.1:0")
140		.expect("Failed to bind to an available port")
141		.local_addr()
142		.expect("Failed to retrieve local address. This should never occur.")
143		.port()
144}
145
146/// A slice of `T` items which have been sorted.
147pub struct SortedSlice<'a, T>(&'a mut [T]);
148impl<'a, T> SortedSlice<'a, T> {
149	/// Sorts a slice with a comparison function, preserving the initial order of equal elements.
150	///
151	/// # Arguments
152	/// * `slice`: A mutable slice of `T` items.
153	/// * `f`: A comparison function which returns an [Ordering].
154	pub fn by(slice: &'a mut [T], f: impl FnMut(&T, &T) -> Ordering) -> Self {
155		slice.sort_by(f);
156		Self(slice)
157	}
158
159	/// Sorts a slice with a key extraction function, preserving the initial order of equal
160	/// elements.
161	///
162	/// # Arguments
163	/// * `slice`: A mutable slice of `T` items.
164	/// * `f`: A comparison function which returns a key.
165	pub fn by_key<K: Ord>(slice: &'a mut [T], f: impl FnMut(&T) -> K) -> Self {
166		slice.sort_by_key(f);
167		Self(slice)
168	}
169}
170
171impl<T> Deref for SortedSlice<'_, T> {
172	type Target = [T];
173
174	fn deref(&self) -> &Self::Target {
175		&self.0[..]
176	}
177}
178
179/// Provides functionality for making calls to parachains or smart contracts.
180pub mod call {
181	// Note: cargo contract logic is used for parsing events after calling a chain. This could be
182	// refactored in the future so that we don't have to use cargo contract code in
183	// `pop-chains`.
184	pub use contract_build::Verbosity;
185	pub use contract_extrinsics::{DisplayEvents, TokenMetadata};
186	pub use ink_env::DefaultEnvironment;
187}
188
189#[cfg(test)]
190mod tests {
191	use super::*;
192	use anyhow::Result;
193
194	#[test]
195	fn target_works() -> Result<()> {
196		crate::command_mock::CommandMock::default().execute_sync(|| {
197			use std::{process::Command, str};
198			let output = Command::new("rustc").arg("-vV").output()?;
199			let output = str::from_utf8(&output.stdout)?;
200			let target_expected = output
201				.lines()
202				.find(|l| l.starts_with("host: "))
203				.map(|l| &l[6..])
204				.unwrap()
205				.to_string();
206			assert_eq!(target()?, target_expected);
207			Ok(())
208		})
209	}
210
211	#[test]
212	fn resolve_port_works() -> Result<()> {
213		let port = resolve_port(None);
214		let listener = TcpListener::bind(format!("127.0.0.1:{}", port));
215		assert!(listener.is_ok());
216		Ok(())
217	}
218
219	#[test]
220	fn resolve_port_skips_busy_preferred_port() -> Result<()> {
221		let listener = TcpListener::bind("127.0.0.1:0")?;
222		let busy_port = listener.local_addr()?.port();
223		let port = resolve_port(Some(busy_port));
224		assert_ne!(port, busy_port);
225		let listener = TcpListener::bind(format!("127.0.0.1:{}", port));
226		assert!(listener.is_ok());
227		Ok(())
228	}
229
230	#[test]
231	fn sorted_slice_sorts_by_function() {
232		let mut values = ["one", "two", "three"];
233		let sorted = SortedSlice::by(values.as_mut_slice(), |a, b| a.cmp(b));
234		assert_eq!(*sorted, ["one", "three", "two"]);
235	}
236
237	#[test]
238	fn sorted_slice_sorts_by_key() {
239		let mut values = ['c', 'b', 'a'];
240		let sorted = SortedSlice::by_key(values.as_mut_slice(), |v| *v as u8);
241		assert_eq!(*sorted, ['a', 'b', 'c']);
242	}
243}