Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: instantiate tanssi container chains #285

Open
wants to merge 30 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
2ed4d7a
refactor: move extract_template_files into common crate
AlexD10S Aug 16, 2024
de2dcb9
fix: unnnecesary API query
AlexD10S Aug 16, 2024
92b1a07
feat: init tanssi templates
al3mart Aug 16, 2024
b88f23c
Update crates/pop-parachains/src/new_parachain.rs
al3mart Aug 16, 2024
c53ff45
Update crates/pop-parachains/src/new_parachain.rs
al3mart Aug 19, 2024
13f2210
ContainerChain Alias
al3mart Aug 19, 2024
4b3a2da
Tanssi template & runtime dirs
al3mart Aug 19, 2024
c4cb6c6
refactor: directories instead of folders (#286)
al3mart Aug 23, 2024
3636398
chore: merge main
AlexD10S Aug 26, 2024
df689f5
fix: target dir doesn't exist
al3mart Aug 26, 2024
9fbcf6d
feat: container zn template
al3mart Aug 26, 2024
7e35d02
resolve conflicts
al3mart Aug 26, 2024
6db37b6
fix: project manifest
al3mart Aug 28, 2024
2284850
fix: workspace manifest creation
al3mart Sep 13, 2024
e7c4639
refactor: cargo workspace generation
al3mart Sep 13, 2024
3f54786
chore: remove tanssi specifics from pop-common
al3mart Sep 14, 2024
51d562f
Merge pull request #311 from r0gue-io/al3mart/wip-improve-conatiner-c…
al3mart Sep 16, 2024
f72f427
merge main
al3mart Sep 16, 2024
a1871a1
fix: revert extractor changes
al3mart Sep 18, 2024
00594fb
style: tempalte typos
al3mart Sep 18, 2024
412a5d1
extractable .gitignore
al3mart Sep 18, 2024
c1a67c6
remove tanssi evm
al3mart Sep 18, 2024
69ab37c
parameter order
al3mart Sep 18, 2024
1db87cd
style: nightly fmt
al3mart Sep 18, 2024
fbc37a9
template naming
al3mart Sep 23, 2024
a5c360a
nightly fmt
al3mart Sep 23, 2024
e37e97f
refactor: don't use toml_edit
al3mart Sep 23, 2024
730f7ea
remove unwraps
al3mart Sep 24, 2024
509333b
remove unused imports
al3mart Sep 24, 2024
96c2a50
handle errors
al3mart Sep 24, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions crates/pop-common/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,6 @@ pub enum Error {
UnsupportedCommand(String),
#[error("Unsupported platform: {arch} {os}")]
UnsupportedPlatform { arch: &'static str, os: &'static str },
#[error("Toml error: {0}")]
TomlError(#[from] toml_edit::TomlError),
}
100 changes: 100 additions & 0 deletions crates/pop-common/src/manifest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,29 @@ use crate::Error;
use anyhow;
pub use cargo_toml::{Dependency, Manifest};
use std::{
collections::HashSet,
fs::{read_to_string, write},
path::{Path, PathBuf},
};
use toml_edit::{value, Array, DocumentMut, Item, Value};

/// Collects the dependencies of the given manifests in a HashMap.
///
/// # Arguments
/// * `manifests`: List of manifests to collect dependencies from.
fn collect_manifest_dependencies(manifests: &[&Manifest]) -> anyhow::Result<HashSet<String>> {
let mut dependencies = HashSet::new();
for m in manifests {
for d in &m.dependencies {
dependencies.insert(d.0.into());
}
for d in &m.build_dependencies {
dependencies.insert(d.0.into());
}
}
Ok(dependencies)
}

/// Parses the contents of a `Cargo.toml` manifest.
///
/// # Arguments
Expand Down Expand Up @@ -98,6 +116,88 @@ pub fn add_crate_to_workspace(workspace_toml: &Path, crate_path: &Path) -> anyho
Ok(())
}

/// Extends `target` cargo manifest with dependencies from `dependencies_from`
/// using versions from `source`.
///
/// It is useful when the list of dependencies is compiled from some external crates,
/// but there is interest on using a separate manifest as the source of truth for their versions.
///
/// # Arguments
/// * `source`: Manifest used as reference for the dependency versions.
/// * `target`: Manifest to be modified.
/// * `tag`: Version to use when transforming local dependencies from `source`.
/// * `dependencies_from`: List of `Manifest`s used to extend `target`.
/// * `local_exceptions`: List of dependencies that need to be reference a local path.
pub fn extend_dependencies_from_manifests_with_version_source(
source: Manifest,
al3mart marked this conversation as resolved.
Show resolved Hide resolved
target: &mut Manifest,
tag: Option<String>,
dependencies_from: &[&Manifest],
local_exceptions: Option<&[(String, String)]>,
) -> Result<(), Error> {
let mut target_manifest_workspace = target.workspace.clone().ok_or_else(|| {
Error::ManifestError(cargo_toml::Error::WorkspaceIntegrity(
"Could not parse manifest's workspace.".into(),
))
})?;

let source_manifest_workspace = source.workspace.clone().ok_or_else(|| {
Error::ManifestError(cargo_toml::Error::WorkspaceIntegrity(
"Could not parse manifest's workspace.".into(),
))
})?;

// Collect dependencies.
let dependencies = collect_manifest_dependencies(dependencies_from)?;

// Update dependencies.
let updated_dependencies: Vec<_> = dependencies
.into_iter()
.filter_map(|k| {
if let Some(dependency) = source_manifest_workspace.dependencies.get(&k) {
if let Some(d) = dependency.detail() {
let mut detail = d.clone();
if d.path.is_some() {
detail.path = None;
detail.git = source_manifest_workspace
.package
.clone()
.expect("Dependencies exist in source manifest.")
.repository;
if let Some(tag) = &tag {
match tag.as_str() {
"master" => detail.branch = Some("master".to_string()),
_ => detail.tag = Some(tag.to_string()),
}
};
}
Some((k, Dependency::Detailed(Box::from(detail))))
} else {
Some((k, dependency.clone()))
}
} else {
None
}
})
.collect();

target_manifest_workspace.dependencies.extend(updated_dependencies);

// Address local exceptions.
if let Some(local_exceptions) = local_exceptions {
for (dependency, path) in local_exceptions {
let d =
target_manifest_workspace.dependencies.get_mut(dependency).unwrap().detail_mut();
d.git = None;
d.tag = None;
d.branch = None;
d.path = Some(path.to_owned());
}
}
target.workspace = Some(target_manifest_workspace);
Ok(())
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
26 changes: 18 additions & 8 deletions crates/pop-common/src/templates/extractor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,24 @@ pub fn extract_template_files(
target_directory: &Path,
ignore_directories: Option<Vec<String>>,
) -> Result<()> {
let template_directory = repo_directory.join(template_name);
// Recursively copy all directories and files within. Ignores the specified ones.
copy_dir_all(
&template_directory,
target_directory,
&ignore_directories.unwrap_or_else(|| vec![]),
)?;
Ok(())
let template_directory = repo_directory.join(&template_name);
if template_directory.is_dir() {
// Recursively copy all directories and files within. Ignores the specified ones.
copy_dir_all(
&template_directory,
target_directory,
&ignore_directories.unwrap_or_else(|| vec![]),
)?;
return Ok(());
} else {
// If not a dir, just copy the file.
let dst = target_directory.join(&template_name);
// In case the first file being pulled is not a directory,
// Make sure the target directory exists.
fs::create_dir_all(&target_directory)?;
fs::copy(template_directory, &dst)?;
Ok(())
}
}

/// Recursively copy a directory and its files.
Expand Down
13 changes: 13 additions & 0 deletions crates/pop-parachains/src/generator/container.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// SPDX-License-Identifier: GPL-3.0

use askama::Template;

#[derive(Template)]
#[template(path = "container/network.templ", escape = "none")]
pub(crate) struct Network {
pub(crate) node: String,
}

#[derive(Template)]
#[template(path = "container/Cargo.templ", escape = "none")]
pub(crate) struct Cargo {}
1 change: 1 addition & 0 deletions crates/pop-parachains/src/generator/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// SPDX-License-Identifier: GPL-3.0

pub mod container;
pub mod pallet;
pub mod parachain;
167 changes: 157 additions & 10 deletions crates/pop-parachains/src/new_parachain.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,27 @@
// SPDX-License-Identifier: GPL-3.0

use crate::{
generator::parachain::{ChainSpec, Network},
utils::helpers::{sanitize, write_to_file},
Config, Parachain, Provider,
};
use std::{fs, path::Path};

use anyhow::Result;
use pop_common::{
git::Git,
templates::{Template, Type},
manifest::{extend_dependencies_from_manifests_with_version_source, from_path},
templates::{extractor::extract_template_files, Template, Type},
};
use std::{fs, path::Path};
use walkdir::WalkDir;

use crate::{
generator::{
container::{Cargo as ContainerCargo, Network as ContainerNetwork},
parachain::{ChainSpec, Network},
},
utils::helpers::{sanitize, write_to_file},
Config, Parachain, Provider,
};

// Type alias for tanssi specific parachain.
type ContainerChain = Parachain;

/// Create a new parachain.
///
/// # Arguments
Expand All @@ -32,6 +41,9 @@ pub fn instantiate_template_dir(
if Provider::Pop.provides(&template) {
return instantiate_standard_template(template, target, config, tag_version);
}
if Provider::Tanssi.provides(&template) {
return instantiate_tanssi_template(template, target, tag_version);
}
let tag = Git::clone_and_degit(template.repository_url()?, target, tag_version)?;
Ok(tag)
}
Expand Down Expand Up @@ -75,12 +87,125 @@ pub fn instantiate_standard_template(
Ok(tag)
}

/// Create a new Tanssi compatible container chain.
///
/// # Arguments
///
/// * `template` - template to generate the container-chain from.
/// * `target` - location where the parachain will be created.
/// * `tag_version` - version to use (`None` to use latest).
fn instantiate_tanssi_template(
template: &ContainerChain,
target: &Path,
tag_version: Option<String>,
) -> Result<Option<String>> {
let temp_dir = ::tempfile::TempDir::new_in(std::env::temp_dir())?;
let source = temp_dir.path();
let tag = Git::clone_and_degit(template.repository_url()?, &source, tag_version)?;

// Relevant files to extract.
let files = template.extractable_files().unwrap();
for file in files {
extract_template_files(file.to_string(), temp_dir.path(), target, None)?;
}

let owned_target = target.to_owned().to_path_buf();

// Templates are located in Tanssi's repo as follows:
// │
// ├┐ container-chains
// │├┬ nodes
// ││└ <template node directories>
// │└┬ runtime-templates
// │ └ <template runtime directories>

// Step 1: extract template node.
let template_path = format!(
"{}/{}",
template.node_directory().unwrap(),
template.template_name_without_provider()
);
extract_template_files(
template_path,
temp_dir.path(),
&owned_target.join("node").as_path(),
None,
)?;
// Step 2: extract template runtime.
let template_path = format!(
"{}/{}",
template.runtime_directory().unwrap(),
template.template_name_without_provider()
);
extract_template_files(
template_path,
temp_dir.path(),
&owned_target.join("runtime").as_path(),
None,
)?;

// Add network configuration.
use askama::Template;
let network = ContainerNetwork {
node: format!("container-chain-{}-node", template.template_name_without_provider()),
};
write_to_file(&target.join("network.toml"), network.render().expect("infallible").as_ref())?;
al3mart marked this conversation as resolved.
Show resolved Hide resolved

// Ready project manifest.
let target_manifest = ContainerCargo {};
write_to_file(
&target.join("Cargo.toml"),
target_manifest.render().expect("infallible").as_ref(),
)?;

// Dependencies that should remain with local references.
let local_dependencies = [(
format!("container-chain-template-{}-runtime", template.template_name_without_provider()),
"runtime".to_string(),
)];

let node_manifest = from_path(Some(
&source
.join(&format!("container-chains/nodes/{}", template.template_name_without_provider()))
.join("Cargo.toml"),
))?;
let runtime_manifest = from_path(Some(
&source
.join(&format!(
"container-chains/runtime-templates/{}",
template.template_name_without_provider()
))
.join("Cargo.toml"),
))?;
let dependencies_from = [&node_manifest, &runtime_manifest];

if dependencies_from.len() > 0 {
// Update versions to follow source.
let source_manifest = from_path(Some(source))?;
let mut target_manifest = from_path(Some(&target))?;
extend_dependencies_from_manifests_with_version_source(
source_manifest,
&mut target_manifest,
tag.clone(),
&dependencies_from,
Some(&local_dependencies),
)?;

let target_manifest = toml_edit::ser::to_string_pretty(&target_manifest)?;
fs::write(&target.join("Cargo.toml"), target_manifest)?;
}

Ok(tag)
}

#[cfg(test)]
mod tests {
use super::*;
use anyhow::Result;
use std::{env::current_dir, fs};

use anyhow::Result;

use super::*;

fn setup_template_and_instantiate() -> Result<tempfile::TempDir> {
let temp_dir = tempfile::tempdir().expect("Failed to create temp dir");
let config = Config {
Expand All @@ -92,8 +217,14 @@ mod tests {
Ok(temp_dir)
}

fn setup_tanssi_template() -> Result<tempfile::TempDir> {
let temp_dir = tempfile::tempdir().expect("Failed to create temp dir");
instantiate_tanssi_template(&Parachain::TanssiSimple, temp_dir.path(), None)?;
Ok(temp_dir)
}

#[test]
fn test_parachain_instantiate_standard_template() -> Result<()> {
fn parachain_instantiate_standard_template_works() -> Result<()> {
let temp_dir =
setup_template_and_instantiate().expect("Failed to setup template and instantiate");

Expand All @@ -120,4 +251,20 @@ mod tests {

Ok(())
}

#[test]
fn instantiate_tanssi_template_works() -> Result<()> {
let temp_dir = setup_tanssi_template().expect("Failed to instantiate container chain");
let node_manifest =
pop_common::manifest::from_path(Some(&temp_dir.path().join("node/Cargo.toml")))
.expect("Failed to read file");
assert_eq!("container-chain-simple-node", node_manifest.package().name());

let node_manifest =
pop_common::manifest::from_path(Some(&temp_dir.path().join("runtime/Cargo.toml")))
.expect("Failed to read file");
assert_eq!("container-chain-template-simple-runtime", node_manifest.package().name());

Ok(())
}
}
Loading