From a636d23da43d0e9ae249cfdc4a0c8417e2a9dda1 Mon Sep 17 00:00:00 2001 From: lambda-0x <0xlambda@protonmail.com> Date: Mon, 10 Jun 2024 23:53:25 +0530 Subject: [PATCH] feat(sozo): add a subcommand to generate overlay files (#2025) * initial commit * add support for model overlays * clean up * add test for merge * separate out match statement * fix directory creation * fix: fix test typo + add comment on isolated variant test --------- Co-authored-by: glihm --- Cargo.lock | 1 + bin/sozo/src/commands/clean.rs | 2 +- bin/sozo/src/commands/dev.rs | 3 +- bin/sozo/src/commands/migrate.rs | 13 +- crates/benches/src/deployer.rs | 4 +- crates/dojo-lang/src/compiler.rs | 12 +- crates/dojo-test-utils/src/migration.rs | 3 +- crates/dojo-world/src/contracts/world_test.rs | 3 +- .../dojo-world/src/manifest/manifest_test.rs | 174 +++++++++++++++++- crates/dojo-world/src/manifest/mod.rs | 122 ++++++++++-- crates/dojo-world/src/manifest/types.rs | 3 +- crates/sozo/ops/Cargo.toml | 1 + crates/sozo/ops/src/events.rs | 6 +- crates/sozo/ops/src/migration/migrate.rs | 4 +- crates/sozo/ops/src/migration/mod.rs | 94 +++++++++- crates/sozo/ops/src/migration/utils.rs | 4 +- crates/sozo/ops/src/tests/migration.rs | 4 +- .../dojo_examples_actions_actions.toml | 6 +- .../dojo_examples_others_others.toml | 3 +- .../dev/overlays/dojo_base_base.toml | 1 + .../dev/overlays/dojo_world_world.toml | 1 + .../dojo_examples_actions_actions_moved.toml | 1 + .../dojo_examples_models_emote_message.toml | 1 + .../models/dojo_examples_models_moves.toml | 1 + .../dojo_examples_models_player_config.toml | 1 + .../models/dojo_examples_models_position.toml | 1 + ...es_others_others_contract_initialized.toml | 1 + 27 files changed, 408 insertions(+), 62 deletions(-) create mode 100644 examples/spawn-and-move/manifests/dev/overlays/dojo_base_base.toml create mode 100644 examples/spawn-and-move/manifests/dev/overlays/dojo_world_world.toml create mode 100644 examples/spawn-and-move/manifests/dev/overlays/models/dojo_examples_actions_actions_moved.toml create mode 100644 examples/spawn-and-move/manifests/dev/overlays/models/dojo_examples_models_emote_message.toml create mode 100644 examples/spawn-and-move/manifests/dev/overlays/models/dojo_examples_models_moves.toml create mode 100644 examples/spawn-and-move/manifests/dev/overlays/models/dojo_examples_models_player_config.toml create mode 100644 examples/spawn-and-move/manifests/dev/overlays/models/dojo_examples_models_position.toml create mode 100644 examples/spawn-and-move/manifests/dev/overlays/models/dojo_examples_others_others_contract_initialized.toml diff --git a/Cargo.lock b/Cargo.lock index 530db77742..d784dbbe5e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -12586,6 +12586,7 @@ dependencies = [ "starknet-crypto 0.6.2", "thiserror", "tokio", + "toml 0.8.13", "tracing", "tracing-log 0.1.4", "url", diff --git a/bin/sozo/src/commands/clean.rs b/bin/sozo/src/commands/clean.rs index 06fcd0a142..7d02a03834 100644 --- a/bin/sozo/src/commands/clean.rs +++ b/bin/sozo/src/commands/clean.rs @@ -3,7 +3,7 @@ use std::fs; use anyhow::Result; use camino::Utf8PathBuf; use clap::Args; -use dojo_lang::compiler::{ABIS_DIR, BASE_DIR, MANIFESTS_DIR}; +use dojo_world::manifest::{ABIS_DIR, BASE_DIR, MANIFESTS_DIR}; use scarb::core::Config; use tracing::trace; diff --git a/bin/sozo/src/commands/dev.rs b/bin/sozo/src/commands/dev.rs index ae6f7aa55b..2ff6eef64a 100644 --- a/bin/sozo/src/commands/dev.rs +++ b/bin/sozo/src/commands/dev.rs @@ -8,9 +8,8 @@ use cairo_lang_compiler::db::RootDatabase; use cairo_lang_filesystem::db::{AsFilesGroupMut, FilesGroupEx, PrivRawFileContentQuery}; use cairo_lang_filesystem::ids::FileId; use clap::Args; -use dojo_lang::compiler::{BASE_DIR, MANIFESTS_DIR}; use dojo_lang::scarb_internal::build_scarb_root_database; -use dojo_world::manifest::{BaseManifest, DeploymentManifest}; +use dojo_world::manifest::{BaseManifest, DeploymentManifest, BASE_DIR, MANIFESTS_DIR}; use dojo_world::metadata::dojo_metadata_from_workspace; use dojo_world::migration::world::WorldDiff; use dojo_world::migration::TxnConfig; diff --git a/bin/sozo/src/commands/migrate.rs b/bin/sozo/src/commands/migrate.rs index 9cbd421951..3acb7f25cb 100644 --- a/bin/sozo/src/commands/migrate.rs +++ b/bin/sozo/src/commands/migrate.rs @@ -1,6 +1,6 @@ use anyhow::{anyhow, Context, Result}; use clap::{Args, Subcommand}; -use dojo_lang::compiler::MANIFESTS_DIR; +use dojo_world::manifest::MANIFESTS_DIR; use dojo_world::metadata::{dojo_metadata_from_workspace, Environment}; use dojo_world::migration::TxnConfig; use katana_rpc_api::starknet::RPC_SPEC_VERSION; @@ -50,6 +50,8 @@ pub enum MigrateCommand { #[command(flatten)] transaction: TransactionOptions, }, + #[command(about = "Generate overlays file.")] + GenerateOverlays, } impl MigrateArgs { @@ -57,6 +59,13 @@ impl MigrateArgs { trace!(args = ?self); let ws = scarb::ops::read_workspace(config.manifest_path(), config)?; + // This variant is tested before the match on `self.command` to avoid + // having the need to spin up a Katana to generate the files. + if let MigrateCommand::GenerateOverlays = self.command { + trace!("Generating overlays."); + return migration::generate_overlays(&ws); + } + let env_metadata = if config.manifest_path().exists() { dojo_metadata_from_workspace(&ws).env().cloned() } else { @@ -81,6 +90,7 @@ impl MigrateArgs { match self.command { MigrateCommand::Plan => config.tokio_handle().block_on(async { + trace!(name, "Planning migration."); migration::migrate( &ws, world_address, @@ -99,6 +109,7 @@ impl MigrateArgs { migration::migrate(&ws, world_address, rpc_url, account, &name, false, txn_config) .await }), + _ => unreachable!("other case handled above."), } } } diff --git a/crates/benches/src/deployer.rs b/crates/benches/src/deployer.rs index abb0251c07..2a3aa0e08c 100644 --- a/crates/benches/src/deployer.rs +++ b/crates/benches/src/deployer.rs @@ -3,9 +3,9 @@ use std::path::PathBuf; use anyhow::{anyhow, bail, Context, Ok, Result}; use clap::Parser; -use dojo_lang::compiler::{DojoCompiler, DEPLOYMENTS_DIR, MANIFESTS_DIR}; +use dojo_lang::compiler::DojoCompiler; use dojo_lang::plugin::CairoPluginRepository; -use dojo_world::manifest::DeploymentManifest; +use dojo_world::manifest::{DeploymentManifest, DEPLOYMENTS_DIR, MANIFESTS_DIR}; use futures::executor::block_on; use katana_runner::KatanaRunner; use scarb::compiler::CompilerRepository; diff --git a/crates/dojo-lang/src/compiler.rs b/crates/dojo-lang/src/compiler.rs index 653aab250e..e21edfe2bb 100644 --- a/crates/dojo-lang/src/compiler.rs +++ b/crates/dojo-lang/src/compiler.rs @@ -21,7 +21,8 @@ use camino::{Utf8Path, Utf8PathBuf}; use convert_case::{Case, Casing}; use dojo_world::manifest::{ AbiFormat, Class, ComputedValueEntrypoint, DojoContract, DojoModel, Manifest, ManifestMethods, - BASE_CONTRACT_NAME, WORLD_CONTRACT_NAME, + ABIS_DIR, BASE_CONTRACT_NAME, BASE_DIR, CONTRACTS_DIR, MANIFESTS_DIR, MODELS_DIR, + WORLD_CONTRACT_NAME, }; use itertools::Itertools; use scarb::compiler::helpers::{build_compiler_config, collect_main_crate_ids}; @@ -40,15 +41,6 @@ use crate::semantics::utils::find_module_rw; const CAIRO_PATH_SEPARATOR: &str = "::"; -pub const MANIFESTS_DIR: &str = "manifests"; -pub const BASE_DIR: &str = "base"; -pub const OVERLAYS_DIR: &str = "overlays"; -pub const DEPLOYMENTS_DIR: &str = "deployments"; -pub const ABIS_DIR: &str = "abis"; - -pub const CONTRACTS_DIR: &str = "contracts"; -pub const MODELS_DIR: &str = "models"; - pub const SOURCES_DIR: &str = "src"; pub(crate) const LOG_TARGET: &str = "dojo_lang::compiler"; diff --git a/crates/dojo-test-utils/src/migration.rs b/crates/dojo-test-utils/src/migration.rs index 7ad6f25843..e8a576f341 100644 --- a/crates/dojo-test-utils/src/migration.rs +++ b/crates/dojo-test-utils/src/migration.rs @@ -1,7 +1,6 @@ use anyhow::Result; use camino::Utf8PathBuf; -use dojo_lang::compiler::{BASE_DIR, MANIFESTS_DIR, OVERLAYS_DIR}; -use dojo_world::manifest::{BaseManifest, OverlayManifest}; +use dojo_world::manifest::{BaseManifest, OverlayManifest, BASE_DIR, MANIFESTS_DIR, OVERLAYS_DIR}; use dojo_world::migration::strategy::{prepare_for_migration, MigrationStrategy}; use dojo_world::migration::world::WorldDiff; use katana_primitives::FieldElement; diff --git a/crates/dojo-world/src/contracts/world_test.rs b/crates/dojo-world/src/contracts/world_test.rs index 3b856c1f46..c011d573fb 100644 --- a/crates/dojo-world/src/contracts/world_test.rs +++ b/crates/dojo-world/src/contracts/world_test.rs @@ -1,14 +1,13 @@ use std::time::Duration; use camino::Utf8PathBuf; -use dojo_lang::compiler::{BASE_DIR, MANIFESTS_DIR, OVERLAYS_DIR}; use dojo_test_utils::compiler; use katana_runner::KatanaRunner; use starknet::accounts::{Account, ConnectedAccount}; use starknet::core::types::{BlockId, BlockTag, FieldElement}; use super::{WorldContract, WorldContractReader}; -use crate::manifest::{BaseManifest, OverlayManifest}; +use crate::manifest::{BaseManifest, OverlayManifest, BASE_DIR, MANIFESTS_DIR, OVERLAYS_DIR}; use crate::migration::strategy::prepare_for_migration; use crate::migration::world::WorldDiff; use crate::migration::{Declarable, Deployable, TxnConfig}; diff --git a/crates/dojo-world/src/manifest/manifest_test.rs b/crates/dojo-world/src/manifest/manifest_test.rs index bc92e1a4f8..a003fc0e23 100644 --- a/crates/dojo-world/src/manifest/manifest_test.rs +++ b/crates/dojo-world/src/manifest/manifest_test.rs @@ -2,7 +2,6 @@ use std::io::Write; use cainome::cairo_serde::{ByteArray, CairoSerde}; use camino::Utf8PathBuf; -use dojo_lang::compiler::{BASE_DIR, MANIFESTS_DIR, OVERLAYS_DIR}; use dojo_test_utils::compiler; use dojo_test_utils::rpc::MockJsonRpcTransport; use katana_runner::KatanaRunner; @@ -14,10 +13,14 @@ use starknet::macros::{felt, selector}; use starknet::providers::jsonrpc::{JsonRpcClient, JsonRpcMethod}; use super::{ - parse_contracts_events, AbiFormat, BaseManifest, DojoContract, DojoModel, OverlayManifest, + parse_contracts_events, AbiFormat, BaseManifest, DojoContract, DojoModel, OverlayDojoContract, + OverlayManifest, }; use crate::contracts::world::test::deploy_world; -use crate::manifest::{parse_models_events, AbstractManifestError, DeploymentManifest, Manifest}; +use crate::manifest::{ + parse_models_events, AbstractManifestError, DeploymentManifest, Manifest, OverlayClass, + OverlayDojoModel, BASE_DIR, MANIFESTS_DIR, OVERLAYS_DIR, +}; use crate::migration::world::WorldDiff; #[tokio::test] @@ -473,3 +476,168 @@ fn test_abi_format_load_abi_string() -> Result<(), Box> { Ok(()) } + +#[test] +fn overlay_merge_for_contract_and_model_work_as_expected() { + let other = OverlayManifest { + contracts: vec![ + OverlayDojoContract { name: "othercontract1".into(), ..Default::default() }, + OverlayDojoContract { name: "othercontract2".into(), ..Default::default() }, + OverlayDojoContract { name: "existingcontract".into(), ..Default::default() }, + ], + models: vec![ + OverlayDojoModel { name: "othermodel1".into(), ..Default::default() }, + OverlayDojoModel { name: "othermodel2".into(), ..Default::default() }, + OverlayDojoModel { name: "existingmodel".into(), ..Default::default() }, + ], + ..Default::default() + }; + + let mut current = OverlayManifest { + contracts: vec![ + OverlayDojoContract { name: "currentcontract1".into(), ..Default::default() }, + OverlayDojoContract { name: "currentcontract2".into(), ..Default::default() }, + OverlayDojoContract { name: "existingcontract".into(), ..Default::default() }, + ], + models: vec![ + OverlayDojoModel { name: "currentmodel1".into(), ..Default::default() }, + OverlayDojoModel { name: "currentmodel2".into(), ..Default::default() }, + OverlayDojoModel { name: "existingmodel".into(), ..Default::default() }, + ], + ..Default::default() + }; + + let expected = OverlayManifest { + contracts: vec![ + OverlayDojoContract { name: "currentcontract1".into(), ..Default::default() }, + OverlayDojoContract { name: "currentcontract2".into(), ..Default::default() }, + OverlayDojoContract { name: "existingcontract".into(), ..Default::default() }, + OverlayDojoContract { name: "othercontract1".into(), ..Default::default() }, + OverlayDojoContract { name: "othercontract2".into(), ..Default::default() }, + ], + models: vec![ + OverlayDojoModel { name: "currentmodel1".into(), ..Default::default() }, + OverlayDojoModel { name: "currentmodel2".into(), ..Default::default() }, + OverlayDojoModel { name: "existingmodel".into(), ..Default::default() }, + OverlayDojoModel { name: "othermodel1".into(), ..Default::default() }, + OverlayDojoModel { name: "othermodel2".into(), ..Default::default() }, + ], + ..Default::default() + }; + + current.merge(other); + + assert_eq!(current, expected); +} + +#[test] +fn overlay_merge_for_world_work_as_expected() { + // when other.world is none and current.world is some + let other = OverlayManifest { ..Default::default() }; + let mut current = OverlayManifest { + world: Some(OverlayClass { name: "world".into(), ..Default::default() }), + ..Default::default() + }; + let expected = OverlayManifest { + world: Some(OverlayClass { name: "world".into(), ..Default::default() }), + ..Default::default() + }; + current.merge(other); + + assert_eq!(current, expected); + + // when other.world is some and current.world is none + let other = OverlayManifest { + world: Some(OverlayClass { name: "world".into(), ..Default::default() }), + ..Default::default() + }; + let mut current = OverlayManifest { ..Default::default() }; + let expected = OverlayManifest { + world: Some(OverlayClass { name: "world".into(), ..Default::default() }), + ..Default::default() + }; + + current.merge(other); + assert_eq!(current, expected); + + // when other.world is some and current.world is some + let other = OverlayManifest { + world: Some(OverlayClass { name: "worldother".into(), ..Default::default() }), + ..Default::default() + }; + let mut current = OverlayManifest { + world: Some(OverlayClass { name: "worldcurrent".into(), ..Default::default() }), + ..Default::default() + }; + let expected = OverlayManifest { + world: Some(OverlayClass { name: "worldcurrent".into(), ..Default::default() }), + ..Default::default() + }; + + current.merge(other); + assert_eq!(current, expected); + + // when other.world is none and current.world is none + let other = OverlayManifest { ..Default::default() }; + let mut current = OverlayManifest { ..Default::default() }; + let expected = OverlayManifest { ..Default::default() }; + + current.merge(other); + assert_eq!(current, expected); +} + +#[test] +fn overlay_merge_for_base_work_as_expected() { + // when other.base is none and current.base is some + let other = OverlayManifest { ..Default::default() }; + let mut current = OverlayManifest { + base: Some(OverlayClass { name: "base".into(), ..Default::default() }), + ..Default::default() + }; + let expected = OverlayManifest { + base: Some(OverlayClass { name: "base".into(), ..Default::default() }), + ..Default::default() + }; + current.merge(other); + + assert_eq!(current, expected); + + // when other.base is some and current.base is none + let other = OverlayManifest { + base: Some(OverlayClass { name: "base".into(), ..Default::default() }), + ..Default::default() + }; + let mut current = OverlayManifest { ..Default::default() }; + let expected = OverlayManifest { + base: Some(OverlayClass { name: "base".into(), ..Default::default() }), + ..Default::default() + }; + + current.merge(other); + assert_eq!(current, expected); + + // when other.base is some and current.base is some + let other = OverlayManifest { + base: Some(OverlayClass { name: "baseother".into(), ..Default::default() }), + ..Default::default() + }; + let mut current = OverlayManifest { + base: Some(OverlayClass { name: "basecurrent".into(), ..Default::default() }), + ..Default::default() + }; + let expected = OverlayManifest { + base: Some(OverlayClass { name: "basecurrent".into(), ..Default::default() }), + ..Default::default() + }; + + current.merge(other); + assert_eq!(current, expected); + + // when other.base is none and current.base is none + let other = OverlayManifest { ..Default::default() }; + let mut current = OverlayManifest { ..Default::default() }; + let expected = OverlayManifest { ..Default::default() }; + + current.merge(other); + assert_eq!(current, expected); +} diff --git a/crates/dojo-world/src/manifest/mod.rs b/crates/dojo-world/src/manifest/mod.rs index d3aa32877a..5391761e49 100644 --- a/crates/dojo-world/src/manifest/mod.rs +++ b/crates/dojo-world/src/manifest/mod.rs @@ -39,6 +39,15 @@ pub const BASE_CONTRACT_NAME: &str = "dojo::base::base"; pub const RESOURCE_METADATA_CONTRACT_NAME: &str = "dojo::resource_metadata::resource_metadata"; pub const RESOURCE_METADATA_MODEL_NAME: &str = "0x5265736f757263654d65746164617461"; +pub const MANIFESTS_DIR: &str = "manifests"; +pub const BASE_DIR: &str = "base"; +pub const OVERLAYS_DIR: &str = "overlays"; +pub const DEPLOYMENTS_DIR: &str = "deployments"; +pub const ABIS_DIR: &str = "abis"; + +pub const CONTRACTS_DIR: &str = "contracts"; +pub const MODELS_DIR: &str = "models"; + #[derive(Error, Debug)] pub enum AbstractManifestError { #[error("Remote World not found.")] @@ -56,7 +65,9 @@ pub enum AbstractManifestError { #[error(transparent)] Model(#[from] ModelError), #[error(transparent)] - TOML(#[from] toml::de::Error), + TomlDe(#[from] toml::de::Error), + #[error(transparent)] + TomlSer(#[from] toml::ser::Error), #[error(transparent)] IO(#[from] io::Error), #[error("Abi couldn't be loaded from path: {0}")] @@ -93,8 +104,8 @@ impl From for DeploymentManifest { impl BaseManifest { /// Load the manifest from a file at the given path. pub fn load_from_path(path: &Utf8PathBuf) -> Result { - let contract_dir = path.join("contracts"); - let model_dir = path.join("models"); + let contract_dir = path.join(CONTRACTS_DIR); + let model_dir = path.join(MODELS_DIR); let world: Manifest = toml::from_str(&fs::read_to_string( path.join(WORLD_CONTRACT_NAME.replace("::", "_")).with_extension("toml"), @@ -136,6 +147,8 @@ impl BaseManifest { impl OverlayManifest { pub fn load_from_path(path: &Utf8PathBuf) -> Result { + fs::create_dir_all(path)?; + let mut world: Option = None; let world_path = path.join(WORLD_CONTRACT_NAME.replace("::", "_")).with_extension("toml"); @@ -151,15 +164,72 @@ impl OverlayManifest { base = Some(toml::from_str(&fs::read_to_string(base_path)?)?); } - let contract_dir = path.join("contracts"); - + let contract_dir = path.join(CONTRACTS_DIR); let contracts = if contract_dir.exists() { overlay_elements_from_path::(&contract_dir)? } else { vec![] }; - Ok(Self { world, base, contracts }) + let model_dir = path.join(MODELS_DIR); + let models = if model_dir.exists() { + overlay_elements_from_path::(&model_dir)? + } else { + vec![] + }; + + Ok(Self { world, base, contracts, models }) + } + + /// Writes `Self` to overlay manifests folder. + /// + /// - `world` and `base` manifest are written to root of the folder. + /// - `contracts` and `models` are written to their respective directories. + pub fn write_to_path_nested(&self, path: &Utf8PathBuf) -> Result<(), AbstractManifestError> { + fs::create_dir_all(path)?; + + if let Some(ref world) = self.world { + let world = toml::to_string(world)?; + let file_name = + path.join(WORLD_CONTRACT_NAME.replace("::", "_")).with_extension("toml"); + fs::write(file_name, world)?; + } + + if let Some(ref base) = self.base { + let base = toml::to_string(base)?; + let file_name = path.join(BASE_CONTRACT_NAME.replace("::", "_")).with_extension("toml"); + fs::write(file_name, base)?; + } + + overlay_dojo_contracts_to_path(&path.join(CONTRACTS_DIR), self.contracts.as_slice())?; + overlay_dojo_model_to_path(&path.join(MODELS_DIR), self.models.as_slice())?; + Ok(()) + } + + /// Add missing overlay items from `others` to `self`. + /// Note that this method don't override if certain item already exists in `self`. + pub fn merge(&mut self, other: OverlayManifest) { + if self.world.is_none() { + self.world = other.world; + } + + if self.base.is_none() { + self.base = other.base; + } + + for other_contract in other.contracts { + let found = self.contracts.iter().find(|c| c.name == other_contract.name); + if found.is_none() { + self.contracts.push(other_contract); + } + } + + for other_model in other.models { + let found = self.models.iter().find(|m| m.name == other_model.name); + if found.is_none() { + self.models.push(other_model); + } + } } } @@ -485,20 +555,6 @@ fn parse_models_events(events: Vec) -> Vec> { .collect() } -// fn elements_to_path(item_dir: &Utf8PathBuf, items: &Vec>) -> Result<()> -// where -// T: Serialize + ManifestMethods, -// { -// fs::create_dir_all(item_dir)?; -// for item in items { -// let item_toml = toml::to_string_pretty(&item)?; -// let item_name = item.name.split("::").last().unwrap(); -// fs::write(item_dir.join(item_name).with_extension("toml"), item_toml)?; -// } - -// Ok(()) -// } - fn elements_from_path(path: &Utf8PathBuf) -> Result>, AbstractManifestError> where T: DeserializeOwned + ManifestMethods, @@ -546,6 +602,32 @@ where Ok(elements) } +fn overlay_dojo_contracts_to_path( + path: &Utf8PathBuf, + elements: &[OverlayDojoContract], +) -> Result<(), AbstractManifestError> { + fs::create_dir_all(path)?; + + for element in elements { + let path = path.join(element.name.replace("::", "_")).with_extension("toml"); + fs::write(path, toml::to_string(&element)?)?; + } + Ok(()) +} + +fn overlay_dojo_model_to_path( + path: &Utf8PathBuf, + elements: &[OverlayDojoModel], +) -> Result<(), AbstractManifestError> { + fs::create_dir_all(path)?; + + for element in elements { + let path = path.join(element.name.replace("::", "_")).with_extension("toml"); + fs::write(path, toml::to_string(&element)?)?; + } + Ok(()) +} + impl ManifestMethods for DojoContract { type OverlayType = OverlayDojoContract; diff --git a/crates/dojo-world/src/manifest/types.rs b/crates/dojo-world/src/manifest/types.rs index dbbee2fd58..5eb3e67d0c 100644 --- a/crates/dojo-world/src/manifest/types.rs +++ b/crates/dojo-world/src/manifest/types.rs @@ -36,12 +36,13 @@ pub struct DeploymentManifest { pub models: Vec>, } -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(Default, Clone, Debug, Serialize, Deserialize)] #[cfg_attr(test, derive(PartialEq))] pub struct OverlayManifest { pub world: Option, pub base: Option, pub contracts: Vec, + pub models: Vec, } #[derive(Clone, Serialize, Deserialize, Debug)] diff --git a/crates/sozo/ops/Cargo.toml b/crates/sozo/ops/Cargo.toml index e4eded4705..f4144ae66b 100644 --- a/crates/sozo/ops/Cargo.toml +++ b/crates/sozo/ops/Cargo.toml @@ -50,6 +50,7 @@ tokio.workspace = true tracing-log = "0.1.3" tracing.workspace = true url.workspace = true +toml.workspace = true [dev-dependencies] assert_fs.workspace = true diff --git a/crates/sozo/ops/src/events.rs b/crates/sozo/ops/src/events.rs index c524fc2e96..73ae19d7fa 100644 --- a/crates/sozo/ops/src/events.rs +++ b/crates/sozo/ops/src/events.rs @@ -5,8 +5,7 @@ use anyhow::{anyhow, Result}; use cainome::parser::tokens::{CompositeInner, CompositeInnerKind, CoreBasic, Token}; use cainome::parser::AbiParser; use camino::Utf8PathBuf; -use dojo_lang::compiler::MANIFESTS_DIR; -use dojo_world::manifest::{AbiFormat, DeploymentManifest, ManifestMethods}; +use dojo_world::manifest::{AbiFormat, DeploymentManifest, ManifestMethods, MANIFESTS_DIR}; use starknet::core::types::{BlockId, EventFilter, FieldElement}; use starknet::core::utils::{parse_cairo_short_string, starknet_keccak}; use starknet::providers::jsonrpc::HttpTransport; @@ -249,8 +248,7 @@ fn process_inners( mod tests { use cainome::parser::tokens::{Array, Composite, CompositeInner, CompositeType}; use camino::Utf8Path; - use dojo_lang::compiler::{BASE_DIR, MANIFESTS_DIR}; - use dojo_world::manifest::BaseManifest; + use dojo_world::manifest::{BaseManifest, BASE_DIR}; use starknet::core::types::EmittedEvent; use super::*; diff --git a/crates/sozo/ops/src/migration/migrate.rs b/crates/sozo/ops/src/migration/migrate.rs index 831fd22c48..b6c194e5a4 100644 --- a/crates/sozo/ops/src/migration/migrate.rs +++ b/crates/sozo/ops/src/migration/migrate.rs @@ -2,12 +2,12 @@ use std::path::Path; use anyhow::{anyhow, bail, Context, Result}; use camino::Utf8PathBuf; -use dojo_lang::compiler::{ABIS_DIR, BASE_DIR, DEPLOYMENTS_DIR, MANIFESTS_DIR}; use dojo_world::contracts::abi::world; use dojo_world::contracts::{cairo_utils, WorldContract}; use dojo_world::manifest::{ AbiFormat, BaseManifest, DeploymentManifest, DojoContract, DojoModel, Manifest, - ManifestMethods, WorldContract as ManifestWorldContract, WorldMetadata, + ManifestMethods, WorldContract as ManifestWorldContract, WorldMetadata, ABIS_DIR, BASE_DIR, + DEPLOYMENTS_DIR, MANIFESTS_DIR, }; use dojo_world::metadata::{dojo_metadata_from_workspace, ResourceMetadata}; use dojo_world::migration::class::ClassMigration; diff --git a/crates/sozo/ops/src/migration/mod.rs b/crates/sozo/ops/src/migration/mod.rs index 85f58dfa44..d7d45d4c49 100644 --- a/crates/sozo/ops/src/migration/mod.rs +++ b/crates/sozo/ops/src/migration/mod.rs @@ -1,8 +1,14 @@ use std::sync::Arc; +use std::{fs, io}; -use anyhow::{anyhow, Result}; -use dojo_lang::compiler::MANIFESTS_DIR; +use anyhow::{anyhow, Context, Result}; +use camino::Utf8PathBuf; use dojo_world::contracts::WorldContract; +use dojo_world::manifest::{ + DojoContract, DojoModel, Manifest, OverlayClass, OverlayDojoContract, OverlayDojoModel, + OverlayManifest, BASE_CONTRACT_NAME, BASE_DIR, CONTRACTS_DIR, MANIFESTS_DIR, MODELS_DIR, + OVERLAYS_DIR, WORLD_CONTRACT_NAME, +}; use dojo_world::migration::world::WorldDiff; use dojo_world::migration::{DeployOutput, TxnConfig, UpgradeOutput}; use scarb::core::Workspace; @@ -174,3 +180,87 @@ enum ContractDeploymentOutput { enum ContractUpgradeOutput { Output(UpgradeOutput), } + +pub fn generate_overlays(ws: &Workspace<'_>) -> Result<()> { + let profile_name = + ws.current_profile().expect("Scarb profile expected to be defined.").to_string(); + + // its path to a file so `parent` should never return `None` + let manifest_dir = ws.manifest_path().parent().unwrap().to_path_buf(); + let profile_dir = manifest_dir.join(MANIFESTS_DIR).join(profile_name); + + let base_manifests = profile_dir.join(BASE_DIR); + + let world = OverlayClass { name: WORLD_CONTRACT_NAME.into(), original_class_hash: None }; + let base = OverlayClass { name: BASE_CONTRACT_NAME.into(), original_class_hash: None }; + + // generate default OverlayManifest from base manifests + let contracts = overlay_dojo_contracts_from_path(&base_manifests.join(CONTRACTS_DIR)) + .with_context(|| "Failed to build default DojoContract Overlays from path.")?; + let models = overlay_model_from_path(&base_manifests.join(MODELS_DIR)) + .with_context(|| "Failed to build default DojoModel Overlays from path.")?; + + let default_overlay = + OverlayManifest { world: Some(world), base: Some(base), contracts, models }; + + let overlay_path = profile_dir.join(OVERLAYS_DIR); + + // read existing OverlayManifest from path + let mut overlay_manifest = OverlayManifest::load_from_path(&overlay_path) + .with_context(|| "Failed to load OverlayManifest from path.")?; + + // merge them to get OverlayManifest which contains all the contracts and models from base + // manifests + overlay_manifest.merge(default_overlay); + + overlay_manifest + .write_to_path_nested(&overlay_path) + .with_context(|| "Failed to write OverlayManifest to path.")?; + + Ok(()) +} + +fn overlay_dojo_contracts_from_path(path: &Utf8PathBuf) -> Result> { + let mut elements = vec![]; + + let entries = path + .read_dir()? + .map(|entry| entry.map(|e| e.path())) + .collect::, io::Error>>()?; + + for path in entries { + if path.is_file() { + let manifest: Manifest = toml::from_str(&fs::read_to_string(path)?)?; + + let overlay_manifest = + OverlayDojoContract { name: manifest.name, ..Default::default() }; + elements.push(overlay_manifest); + } else { + continue; + } + } + + Ok(elements) +} + +fn overlay_model_from_path(path: &Utf8PathBuf) -> Result> { + let mut elements = vec![]; + + let entries = path + .read_dir()? + .map(|entry| entry.map(|e| e.path())) + .collect::, io::Error>>()?; + + for path in entries { + if path.is_file() { + let manifest: Manifest = toml::from_str(&fs::read_to_string(path)?)?; + + let overlay_manifest = OverlayDojoModel { name: manifest.name, ..Default::default() }; + elements.push(overlay_manifest); + } else { + continue; + } + } + + Ok(elements) +} diff --git a/crates/sozo/ops/src/migration/utils.rs b/crates/sozo/ops/src/migration/utils.rs index d21e7c4c29..d85ffb6df8 100644 --- a/crates/sozo/ops/src/migration/utils.rs +++ b/crates/sozo/ops/src/migration/utils.rs @@ -1,8 +1,8 @@ use anyhow::{anyhow, Result}; use camino::Utf8PathBuf; -use dojo_lang::compiler::{BASE_DIR, OVERLAYS_DIR}; use dojo_world::manifest::{ - AbstractManifestError, BaseManifest, DeploymentManifest, OverlayManifest, + AbstractManifestError, BaseManifest, DeploymentManifest, OverlayManifest, BASE_DIR, + OVERLAYS_DIR, }; use scarb_ui::Ui; use starknet::accounts::{ConnectedAccount, SingleOwnerAccount}; diff --git a/crates/sozo/ops/src/tests/migration.rs b/crates/sozo/ops/src/tests/migration.rs index 1f82982bf7..006c9a731e 100644 --- a/crates/sozo/ops/src/tests/migration.rs +++ b/crates/sozo/ops/src/tests/migration.rs @@ -2,11 +2,11 @@ use std::str; use cainome::cairo_serde::ContractAddress; use camino::Utf8Path; -use dojo_lang::compiler::{BASE_DIR, MANIFESTS_DIR, OVERLAYS_DIR}; use dojo_test_utils::migration::prepare_migration_with_world_and_seed; use dojo_world::contracts::{WorldContract, WorldContractReader}; use dojo_world::manifest::{ - BaseManifest, DeploymentManifest, OverlayManifest, WORLD_CONTRACT_NAME, + BaseManifest, DeploymentManifest, OverlayManifest, BASE_DIR, MANIFESTS_DIR, OVERLAYS_DIR, + WORLD_CONTRACT_NAME, }; use dojo_world::metadata::{ dojo_metadata_from_workspace, ArtifactMetadata, DojoMetadata, Uri, WorldMetadata, diff --git a/examples/spawn-and-move/manifests/dev/overlays/contracts/dojo_examples_actions_actions.toml b/examples/spawn-and-move/manifests/dev/overlays/contracts/dojo_examples_actions_actions.toml index a8e2fd4c2d..b21fc1adec 100644 --- a/examples/spawn-and-move/manifests/dev/overlays/contracts/dojo_examples_actions_actions.toml +++ b/examples/spawn-and-move/manifests/dev/overlays/contracts/dojo_examples_actions_actions.toml @@ -1,6 +1,4 @@ -computed = [ ] name = "dojo_examples::actions::actions" -reads = [ ] -writes = [ "Moves", "Position" ] +reads = [] +writes = ["Moves", "Position"] init_calldata = [] - diff --git a/examples/spawn-and-move/manifests/dev/overlays/contracts/dojo_examples_others_others.toml b/examples/spawn-and-move/manifests/dev/overlays/contracts/dojo_examples_others_others.toml index b74df0c8fe..129f942bd8 100644 --- a/examples/spawn-and-move/manifests/dev/overlays/contracts/dojo_examples_others_others.toml +++ b/examples/spawn-and-move/manifests/dev/overlays/contracts/dojo_examples_others_others.toml @@ -1,5 +1,4 @@ +name = "dojo_examples::others::others" reads = [] writes = [] -computed = [] init_calldata = ["$contract_address:dojo_examples::actions::actions", "$class_hash:dojo_examples::actions::actions", "10"] -name = "dojo_examples::others::others" diff --git a/examples/spawn-and-move/manifests/dev/overlays/dojo_base_base.toml b/examples/spawn-and-move/manifests/dev/overlays/dojo_base_base.toml new file mode 100644 index 0000000000..f706470d45 --- /dev/null +++ b/examples/spawn-and-move/manifests/dev/overlays/dojo_base_base.toml @@ -0,0 +1 @@ +name = "dojo::base::base" diff --git a/examples/spawn-and-move/manifests/dev/overlays/dojo_world_world.toml b/examples/spawn-and-move/manifests/dev/overlays/dojo_world_world.toml new file mode 100644 index 0000000000..a3e686e3ef --- /dev/null +++ b/examples/spawn-and-move/manifests/dev/overlays/dojo_world_world.toml @@ -0,0 +1 @@ +name = "dojo::world::world" diff --git a/examples/spawn-and-move/manifests/dev/overlays/models/dojo_examples_actions_actions_moved.toml b/examples/spawn-and-move/manifests/dev/overlays/models/dojo_examples_actions_actions_moved.toml new file mode 100644 index 0000000000..4958a7a15c --- /dev/null +++ b/examples/spawn-and-move/manifests/dev/overlays/models/dojo_examples_actions_actions_moved.toml @@ -0,0 +1 @@ +name = "dojo_examples::actions::actions::moved" diff --git a/examples/spawn-and-move/manifests/dev/overlays/models/dojo_examples_models_emote_message.toml b/examples/spawn-and-move/manifests/dev/overlays/models/dojo_examples_models_emote_message.toml new file mode 100644 index 0000000000..d60162cc72 --- /dev/null +++ b/examples/spawn-and-move/manifests/dev/overlays/models/dojo_examples_models_emote_message.toml @@ -0,0 +1 @@ +name = "dojo_examples::models::emote_message" diff --git a/examples/spawn-and-move/manifests/dev/overlays/models/dojo_examples_models_moves.toml b/examples/spawn-and-move/manifests/dev/overlays/models/dojo_examples_models_moves.toml new file mode 100644 index 0000000000..dc8784e746 --- /dev/null +++ b/examples/spawn-and-move/manifests/dev/overlays/models/dojo_examples_models_moves.toml @@ -0,0 +1 @@ +name = "dojo_examples::models::moves" diff --git a/examples/spawn-and-move/manifests/dev/overlays/models/dojo_examples_models_player_config.toml b/examples/spawn-and-move/manifests/dev/overlays/models/dojo_examples_models_player_config.toml new file mode 100644 index 0000000000..6af8240b36 --- /dev/null +++ b/examples/spawn-and-move/manifests/dev/overlays/models/dojo_examples_models_player_config.toml @@ -0,0 +1 @@ +name = "dojo_examples::models::player_config" diff --git a/examples/spawn-and-move/manifests/dev/overlays/models/dojo_examples_models_position.toml b/examples/spawn-and-move/manifests/dev/overlays/models/dojo_examples_models_position.toml new file mode 100644 index 0000000000..df38e71c32 --- /dev/null +++ b/examples/spawn-and-move/manifests/dev/overlays/models/dojo_examples_models_position.toml @@ -0,0 +1 @@ +name = "dojo_examples::models::position" diff --git a/examples/spawn-and-move/manifests/dev/overlays/models/dojo_examples_others_others_contract_initialized.toml b/examples/spawn-and-move/manifests/dev/overlays/models/dojo_examples_others_others_contract_initialized.toml new file mode 100644 index 0000000000..f8f3053fe5 --- /dev/null +++ b/examples/spawn-and-move/manifests/dev/overlays/models/dojo_examples_others_others_contract_initialized.toml @@ -0,0 +1 @@ +name = "dojo_examples::others::others::contract_initialized"