Mooncakesは、Moonbit言語のパッケージマネージャーです。本記事では、Mooncakesが依存ライブラリをどのように管理・取得するのかについて解説します。
管理
Moonbitは、プロジェクトごとにmoon.mod.jsonというファイルを使用して依存ライブラリを管理します。以下はmoon.mod.jsonの例です。
{
"name": "username/projectname",
"version": "0.1.0",
"deps": {
"TheWaWaR/clap": "0.2.5"
},
"readme": "README.mbt.md",
"repository": "",
"license": "Apache-2.0",
"keywords": [],
"description": ""
}
よく見るパッケージマネージャーの設定ファイルという感じがします。しかし、たいへん困ったことに、ロックファイルが生成されません。
取得
依存ライブラリの取得は3段階で行われます。まず、パッケージレジストリである mooncakes.io に登録されているライブラリをすべて取得します。ライブラリの一覧はgitリポジトリとして mooncakes.io/git/index で公開されているので、moonコマンドがMOON_HOME下のローカルリポジトリを更新します。
// https://github.com/moonbitlang/moon
// crates/moonutil/src/mooncakes.rs L578-603
impl RegistryConfig {
pub fn new() -> Self {
if let Ok(v) = std::env::var("MOONCAKES_REGISTRY") {
RegistryConfig {
index: format!("{v}/git/index"),
registry: v,
}
} else {
RegistryConfig {
registry: "https://mooncakes.io".into(),
index: "https://mooncakes.io/git/index".into(),
}
}
}
pub fn load() -> Self {
let config_path = crate::moon_dir::config_json();
if !config_path.exists() {
return Self::new();
}
let file = File::open(config_path).unwrap();
let reader = BufReader::new(file);
let config: RegistryConfig = serde_json_lenient::from_reader(reader).unwrap();
config
}
}
// https://github.com/moonbitlang/moon
// crates/mooncake/src/update.rs L174-222
pub fn update(target_dir: &Path, registry_config: &RegistryConfig) -> anyhow::Result<i32> {
if target_dir.exists() {
let url = get_remote_url(target_dir).map_err(|e| UpdateError {
source: UpdateErrorKind::GetRemoteUrlError(e),
})?;
if url == registry_config.index {
let result = pull_latest_registry_index(registry_config, target_dir);
match result {
// 中略
}
} else {
// 中略
std::fs::remove_dir_all(target_dir).map_err(|e| UpdateError {
source: UpdateErrorKind::IO(e),
})?;
clone_registry_index(registry_config, target_dir).map_err(|e| UpdateError {
source: UpdateErrorKind::CloneRegistryIndexError(e),
})?;
// 中略
}
} else {
clone_registry_index(registry_config, target_dir).map_err(|e| UpdateError {
source: UpdateErrorKind::CloneRegistryIndexError(e),
})?;
// 中略
}
}
次に、moon.mod.jsonに記述に基づいて依存ライブラリをリスト化します。前の段階で取得したライブラリ一覧は改行区切りのJSON列として保存されており、再帰的に依存ライブラリをたどります。下記のindexファイルの例では、0.1.6は外部への依存を持っています。
// https://mooncakes.io/git/index
// user/bob/toml.index
{"name": "bob/toml", "version": "0.1.0", "readme": "README.md", "repository": "", "license": "Apache-2.0", "keywords": ["toml", "parser", "config"], "description": "A TOML parser implementation in MoonBit", "source": "src", "checksum": "ce07600864db30ca176120d712d952bd621094ef1220513dfe428c19460d73b6", "created_at": "2025-08-01T09:19:09.691871+00:00"}
{"name": "bob/toml", "version": "0.1.1", "readme": "README.md", "repository": "", "license": "Apache-2.0", "keywords": ["toml", "parser", "config"], "description": "A TOML parser implementation in MoonBit", "source": "src", "checksum": "c98523014bda43ab0fdc88aa08897834a40ffa06261d0ccc9185a493f5fa9478", "created_at": "2025-08-03T13:26:30.441280+00:00"}
// 中略
{"name": "bob/toml", "version": "0.1.6", "deps": {"bob/lexer": "0.1.0"}, "readme": "README.md", "repository": "https://github.com/moonbit-community/toml-parser", "license": "Apache-2.0", "keywords": ["toml", "parser", "config"], "description": "A TOML parser implementation in MoonBit", "checksum": "f81c8e81343f39c02a286931121b49570fa23c2908715aaa095adf329a2c7fdd", "created_at": "2025-08-20T02:40:00.995997+00:00"}
最後に、ソースコードのzipをS3からダウンロードします。このとき、一度ダウンロードされたライブラリはMOON_HOME下にキャッシュされ、次回以降はキャッシュが使用されます。このタイミングで、indexファイルに記載されているハッシュを用いてチェックサムがされます。
// https://github.com/moonbitlang/moon
// crates/mooncake/src/registry/online.rs L39-58
impl OnlineRegistry {
pub fn mooncakes_io() -> Self {
OnlineRegistry {
index: moonutil::moon_dir::index(),
url_base: "https://moonbitlang-mooncakes.s3.us-west-2.amazonaws.com/user".to_string(),
cache: RefCell::new(HashMap::new()),
}
}
// 中略
}
// https://github.com/moonbitlang/moon
// crates/mooncake/src/registry/online.rs L166-203
fn download_or_using_cache(
&self,
name: &ModuleName,
version: &Version,
quiet: bool,
) -> anyhow::Result<bytes::Bytes> {
let pkg_index = self.index_file_of(name);
if !pkg_index.exists() {
anyhow::bail!("Module {}@{} not found", name, version);
}
let cache_file = cache_of(name, version);
let mut checksum_ok = false;
if cache_file.exists() {
let checksum = self.read_checksum_from_index_file(name, version)?;
let current_checksum = calc_sha2(&cache_file);
if current_checksum.is_ok() && current_checksum.unwrap() == checksum {
checksum_ok = true;
}
}
if checksum_ok {
if !quiet {
println!("Using cached {name}@{version}");
}
let data = std::fs::read(cache_file)?;
return Ok(bytes::Bytes::from(data));
}
if !quiet {
println!("Downloading {name}");
}
let filepath = form_urlencoded::Serializer::new(String::new())
.append_key_only(&format!("{}/{}/{}", name.username, name.unqual, version))
.finish();
let url = format!("{}/{}.zip", self.url_base, filepath);
let data = reqwest::blocking::get(url)?.error_for_status()?.bytes()?;
std::fs::create_dir_all(cache_file.parent().unwrap())?;
std::fs::write(cache_file, &data)?;
Ok(data)
}
ライセンス情報
本記事で引用したソースコードは、 https://github.com/moonbitlang/moon にてAGPL-3.0ライセンスのもとで公開されています。