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