1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
use std::{
    fs::{self, File},
    io::Read,
    path::{Path, PathBuf},
};

use anyhow::{anyhow, Context, Error};
use cargo::{core::Workspace, ops, util::interning::InternedString};
use path_absolutize::Absolutize;

use crate::{compilation::*, ext::*, hashing::*, optimization::*};

pub mod compilation;
pub mod ext;
pub mod hashing;
pub mod optimization;
pub mod self_updater;

const CONTRACTS: &str = "contracts";
const LIBRARY: &str = "library";
const ARTIFACTS: &str = "artifacts";

/// Runs cw-optimizoor against the workspace path.
pub async fn run<P: AsRef<Path> + TakeExt<PathBuf>>(
    workspace_path: P,
) -> anyhow::Result<(), Error> {
    let manifest_path = find_manifest(&workspace_path)?;
    let cfg = config()?;
    let ws = Workspace::new(manifest_path.as_path(), &cfg).expect("couldn't create workspace");
    let output_dir = create_artifacts_dir(&ws)?;

    // all ws members that are contracts
    let all_contracts = ws
        .members()
        .filter(|&p| p.manifest_path().starts_with(ws.root().join(CONTRACTS)))
        .collect::<Vec<_>>();

    if all_contracts.is_empty() {
        return Err(anyhow!("No CW contracts found. Exiting."));
    }

    // collect ws members with deps with feature = library to be compiled individually
    let individual_contracts = all_contracts
        .iter()
        .filter(|p| {
            p.dependencies()
                .iter()
                .any(|d| d.features().contains(&InternedString::from(LIBRARY)))
        })
        .map(|&p| p.clone())
        .collect::<Vec<_>>();

    // package names of contracts to be compiled individually
    let individual_names = individual_contracts
        .iter()
        .map(|p| p.package_id().name().to_string())
        .collect::<Vec<_>>();

    // package names of contracts to be compiled together
    let common_names = all_contracts
        .iter()
        .map(|p| p.package_id().name().to_string())
        .filter(|name| !individual_names.contains(name))
        .collect::<Vec<_>>();

    println!("🧐️  Compiling .../{}", &manifest_path.rtake(2).display());
    let mut intermediate_wasm_paths = compile(&cfg, &ws, ops::Packages::Packages(common_names))?;
    let mut special_intermediate_wasm_paths = compile_ephemerally(&cfg, individual_contracts)?;
    intermediate_wasm_paths.append(&mut special_intermediate_wasm_paths);

    println!("🤓  Intermediate checksums:");
    let mut prev_intermediate_checksums = String::new();

    let checksums_intermediate_path = output_dir.join("checksums_intermediate.txt");
    File::options()
        .read(true)
        .write(true)
        .create(true)
        .open(&checksums_intermediate_path)
        .and_then(|mut file| file.read_to_string(&mut prev_intermediate_checksums))
        .context(format!(
            "Failed read from {path}",
            path = checksums_intermediate_path.display()
        ))?;
    write_checksums(&intermediate_wasm_paths, &checksums_intermediate_path).context(format!(
        "Failed write into {path}",
        path = checksums_intermediate_path.display()
    ))?;

    println!("🥸  Ahh I'm optimiziing");
    let final_wasm_paths = incremental_optimizations(
        &output_dir,
        intermediate_wasm_paths,
        prev_intermediate_checksums,
    )?;

    println!("🤓  Final checksums:");
    let checksums_path = output_dir.join("checksums.txt");
    write_checksums(&final_wasm_paths, &checksums_path).context(format!(
        "Failed write into {path}",
        path = checksums_path.display()
    ))?;

    println!(
        "🫡  Done. Saved optimized artifacts to:\n   {}",
        ws.root().join(ARTIFACTS).display()
    );

    Ok(())
}

/// Find the Cargo.toml if a directory path is passed in
pub fn find_manifest<P: AsRef<Path>>(workspace_path: P) -> anyhow::Result<PathBuf> {
    let manifest_path = match workspace_path.as_ref().absolutize()?.to_path_buf() {
        absolute_path if absolute_path.ends_with("Cargo.toml") => absolute_path,
        absolute_path if absolute_path.is_dir() => absolute_path.join("Cargo.toml"),
        absolute_path => {
            return Err(anyhow!(
                "Invalid workspace path: {}",
                absolute_path.display()
            ))
        }
    };

    if !manifest_path.exists() {
        return Err(anyhow!(
            "Couldn't locate manifest {}",
            manifest_path.display()
        ));
    }

    Ok(manifest_path)
}

/// Creates the artifacts dir if it doesn't exist.
fn create_artifacts_dir(ws: &Workspace) -> anyhow::Result<PathBuf> {
    let output_dir = ws.root().absolutize()?.to_path_buf().join(ARTIFACTS);
    fs::create_dir_all(&output_dir)?;

    Ok(output_dir)
}