本記事は Deno Advent Calendar 2025 の12月13日向けに投稿した記事です。
注意事項
本記事の主題はC++言語の開発支援に関する内容です。Denoは、C++/CMakeプロジェクトに対する様々なサポートを行うためのツールとして使用しています。Deno自体の詳細な機能解説や、Deno/TypeScriptアプリケーションの開発手法を期待されている方は、ご注意ください。
C++開発者がDenoを活用することで、クロスプラットフォームなビルドスクリプトを型安全に記述できる、という事例紹介となります。
サンプルコード
本記事で紹介したサンプルプロジェクトのコードは、以下のGitHubリポジトリで公開しています。ぜひクローンして、実際に動作を確認してみてください。
GitHubリポジトリ: COx2/deno-advent-calender-2025
はじめに
C++のクロスプラットフォーム開発では、CMakeでビルド設定を抽象化できても、その前後の処理(ファイル操作、パッケージング、配布物の作成など)はシェルスクリプトに頼ることが多く、以下のような課題がありました。
- macOSでは
zsh、WindowsではPowerShellと、プラットフォームごとにスクリプトを書き分ける必要がある - 外部ツール(Python、Node.js、Ruby等)をインストールする必要がある
- スクリプト言語とアプリケーション開発言語の特性の差異によるコンテキストスイッチの負荷がある
この記事では、Denoとdaxを使ってこれらの課題を解決し、ポータブルなC++/CMakeプロジェクト向けビルド支援ツールを構築する方法を紹介します。
対象読者
- C++開発者でビルドスクリプトの保守に課題を感じている方
- クロスプラットフォーム開発でシェルスクリプトの差異に悩んでいる方
背景:なぜこの技術スタックなのか
ビルドスクリプトの実装には、従来からいくつかの選択肢があります。Ruby + RakeやPython + Invokeなどは成熟したツールですが、筆者は導入に際して以下の課題を感じていました。
- 実行環境のセットアップが必要(Ruby実行環境、Python実行環境)
- 依存関係管理ツールが別途必要
- C++プロジェクトの参加者が必ずしもスクリプト言語の特性に慣れているとは限らない
本記事では、Deno + daxという組み合わせを提案します。
Deno + daxを使用するメリット
- Denoはシングルバイナリであり、コマンドラインから環境構築が可能
- TypeScriptによる型システムの恩恵が受けられる
- Webフロントエンド開発者なら即座に参加可能
- Node.jsの知識があればそのまま応用できる
特に、C++言語等の静的型付言語に慣れている方であれば、プロジェクトに参加した際、TypeScriptであれば「あ、これなら読めます」となるケースが期待されます。これにより、アプリケーション開発者がビルドスクリプトを改修することの障壁も下がることが期待されます。
Deno: シングルバイナリのランタイム
Denoは、Node.jsの作者であるRyan Dahl氏が作成した、JavaScriptとTypeScriptのためのモダンなランタイム環境です。
単一の実行ファイル(約100MB)で完結したTypeScript/JavaScript実行環境であることが大きな特徴です。また、標準ライブラリが充実している、必要な追加ライブラリはURL importで実行時に取り込むことができるなど、スクリプトを開発する環境と実行する環境が異なる場合でも動作を維持できる仕組みが整っています。加えて、明示的な権限管理を促すセキュリティファーストな設計もスクリプトを共有する際に安全が担保されています。
Denoのインストールは非常に簡単です。(以下は、本記事執筆時点でのコマンドライン呼び出しであることにご注意ください。)
■ Windowsの場合
irm https://deno.land/install.ps1 | iex
■ macOS, Linuxの場合
curl -fsSL https://deno.land/install.sh | sh
dax: TypeScriptで書けるシェルスクリプト
dax は、David Sherret氏が開発した TypeScript 向けのシェルスクリプトライブラリです。rm や cp 等の典型的なコマンドが TypeScript で実装されており、dax がそれらの命令をパースしてDenoランタイム上で実行をしてくれます。
■ daxの使用例:ディレクトリを削除する処理をクロスプラットフォームに記述する
async function clean(): Promise<void> {
console.log("🧹 Cleaning build directory...");
await $`rm -rf build dist`;
}
daxを使用するメリット
- Windows/macOS/Linuxで同じコードが動く
- TypeScriptの変数の値を展開してコマンドに渡せる
- OSのシェルと同じようにOS上のコマンドも呼べる
- TypeScriptで記述した一連のタスクの中で実行することができる
- エラーハンドリングが容易
特に、本記事では dax から CMake コマンドを適宜呼び出すことで、一連のビルドタスクと連携することを期待します。具体的には、以下のように、OSのシェルスクリプトで記述するCMakeコマンドライン実行に関する記述を、TypeScriptコードの中に記述することで、TypeScriptコード内の変数をCMakeコマンドラインに展開することができ、TypeScriptコードとCMakeプロジェクト設定との間の橋渡しの役割を担います。
■ daxからCMakeコマンドラインを呼び出す具体例
// CMake設定を実行
await $`cmake -B build -DCMAKE_BUILD_TYPE=${args.config} -G ${args.generator}`;
// Windowsとそれ以外でコマンドを分ける
if (Deno.build.os === "windows") {
await $`cmake --build build --config ${args.config}`;
} else {
await $`cmake --build build --config ${args.config} --parallel`;
}
CMake: C++プロジェクトの抽象化
CMakeとは、Windows/macOS/Linuxを含め、様々なプラットフォームで一貫したビルドプロセスを生成するためのクロスプラットフォームなビルドシステムジェネレーターです。開発者は CMakeLists.txt という設定ファイルにビルドシステムへの指示を記述することで、Visual Studio/Xcode/Makefileなどの特定のビルドシステム用のビルドファイルを生成します。
本記事の技術スタックでは、CMakeを「純粋なC++ビルド設定」に専念させることを期待します。Deno + daxがビルドの前後処理を担当することで、CMakeはC++ビルドシステムとしての役割に集中できます。
サンプルプロジェクト: マルチライブラリC++アプリケーション
本記事では、サンプルプロジェクトで事例を示した後に、当プロジェクト内部で用いられている技術の詳細を説明します。本記事のサンプルプロジェクトとして、以下の構成のC++17プロジェクトを構築しました。
GitHubリポジトリ: COx2/deno-advent-calender-2025
myapp/
├── src/
│ ├── main.cpp # メインアプリケーション
│ ├── core/ # 静的ライブラリ
│ │ ├── core.h
│ │ └── core.cpp
│ └── utils/ # 動的ライブラリ
│ ├── utils.h
│ └── utils.cpp
├── CMakeLists.txt # CMake設定
├── build.ts # メインビルドスクリプト
├── build.config.ts # ビルド設定
├── cmake-types.ts # CMake File API型定義
├── cmake-file-api.ts # File APIハンドリング
├── deno.json # Denoタスク定義
├── .gitignore # Git設定
└── README.md # プロジェクトドキュメント
プロジェクト概要
このサンプルプロジェクトは、実際のC++プロジェクトでよくある、「実行ファイル + 静的ライブラリ + 動的ライブラリ」という実践的なパターン構成を再現しています
-
myapp_core (静的ライブラリ): 計算ロジックを含むコアライブラリ
-
Calculatorクラスを提供 - 加算、乗算、バージョン情報の取得機能
-
-
myapp_utils (動的ライブラリ): ユーティリティ関数群
- 文字列の結合機能(
join) - バナー表示機能(
printBanner) - Windows DLLエクスポートマクロを含む
- 文字列の結合機能(
-
myapp (実行ファイル): 両ライブラリを使用するアプリケーション
- 計算結果を表示
- ユーティリティを使って整形出力
TypeScriptビルドスクリプトの実装
ビルドスクリプトは以下の4つのファイルで構成されています。
ファイル構成と役割
1. build.config.ts - 型安全な設定ファイル
プロジェクトの基本情報を管理します。この設定により、プロジェクト名やバージョンを一元管理でき、TypeScriptの型チェックにより設定ミスを防ぎます。
export interface BuildConfig {
projectName: string;
version: string;
author: string;
buildTypes: string[];
}
2. cmake-types.ts - CMake File APIの型定義
CMake File API(CMake File APIの解説については後述)のJSONレスポンスに対応する型定義を提供します。これらの型定義により、TypeScriptコードにおいてIDE上で補完が効き、構造の誤りをスクリプティング時に検出することができます。
-
FileAPIIndex: CMakeのインデックスファイル構造 -
CodeModelV2: ビルド設定とターゲット情報 -
TargetInfo: 個別ターゲットの詳細情報 -
BuildArtifact: ビルド成果物の情報
3. cmake-file-api.ts - File APIのハンドリング
CMake File APIとの連携を担当します。
主な機能
-
setupFileAPI(): CMakeへのクエリファイル作成命令を実行 -
parseFileAPI(): CMakeのレスポンスをパースして成果物を抽出 -
printArtifacts(): ビルド成果物の整形表示
クロスプラットフォーム対応:
- Denoの
walkAPIを使用してファイル検索(Windows/macOS/Linux共通) -
joinとnormalizeを使用したパス正規化により、プラットフォーム間のパス区切り文字の違いを吸収する
4. build.ts - メインビルドスクリプト
ビルド処理の中心となるスクリプトです。
主な処理フロー
- コマンドライン引数のパース(Deno標準ライブラリを使用)
-
clean(): ビルドディレクトリのクリーンアップ -
configure(): CMakeの設定実行とFile APIクエリの作成 -
build(): CMakeビルドの実行と成果物の自動検出 -
runTests(): ビルドした実行ファイルの実行
プラットフォーム毎にコンパイラを選択する処理を自動化
- Windows: Visual Studio 2022をデフォルトジェネレーターに設定
- macOS/Linux: Unix Makefilesをデフォルトジェネレーターに設定
実行方法
前提条件
以下のツールがインストールされている必要があります
- Deno 1.x以降
- CMake 3.15以降
- C++17対応コンパイラ(GCC、Clang、MSVC)
基本的なビルド実行
# Denoタスクを使用(推奨)
deno task build
# または直接実行
deno run --allow-all build.ts
利用可能なコマンド一覧
ビルドコマンド
deno task build # リリースビルド
deno task build:debug # デバッグビルド
deno task build:release # リリースビルド(明示的)
deno task clean # ビルドディレクトリをクリーン
deno task rebuild # クリーン + リリースビルド
deno task test # ビルド + 実行テスト
開発コマンド
deno task format # TypeScriptコードのフォーマット
deno task format:check # フォーマットチェック
deno task lint # TypeScriptのリント
ビルドスクリプトのオプション
直接スクリプトを実行する場合、以下のオプションが使用できます
# 通常ビルド
deno run --allow-all build.ts
# デバッグビルド
deno run --allow-all build.ts --config Debug
# ビルド + テスト実行
deno run --allow-all build.ts --test
# クリーン
deno run --allow-all build.ts --clean
# ジェネレーターを指定(Ninjaがインストールされている場合)
deno run --allow-all build.ts --generator Ninja
Windowsユーザーへの注意事項: Windowsで実行する場合、デフォルトのジェネレーターは「Visual Studio 17 2022」を選択するように記述しています。Ninjaをインストールしている場合は、--generator Ninjaオプションでより高速なビルドが可能です。
コンソール出力例
ビルド実行時には以下のような情報が表示されます
🚀 Building MyApp v1.0.0
Configuration: Release
Platform: windows
✅ CMake File API query created
⚙️ Configuring CMake...
-- ...
🔨 Building project...
-- ...
📄 Reading CMake File API: {path/to/myapp}/build/.cmake/api/v1/reply/index-*.json
📦 Build Artifacts:
────────────────────────────────────────────────────────────────────────────────
STATIC_LIBRARY:
• myapp_core
{path/to/myapp}/...
SHARED_LIBRARY:
• myapp_utils
{path/to/myapp}/...
EXECUTABLE:
• myapp
{path/to/myapp}/...
────────────────────────────────────────────────────────────────────────────────
✅ Build completed successfully!
このように、CMake File APIによってビルド成果物が自動的に検出され、パスが表示されます。
アプリケーションの実行
ビルドが完了したら、以下のコマンドでアプリケーションを実行できます
# Windows
.\build\bin\Release\myapp.exe
# macOS/Linux
./build/bin/myapp
実行結果の例:
====================
| MyApp Calculator |
====================
Version: 1.0.0
10 + 5 = 15
10 × 5 = 50
開発ワークフローについての解説
デバッグビルドとテスト
開発時には、デバッグモードでビルドしてテストを実行することが多いです。以下が典型的なワークフローです。
オプション1: ビルドとテストを分離
# デバッグモードでビルド
deno task build:debug
# 実行ファイルを直接実行
./build/bin/myapp # macOS/Linux
.\build\bin\Release\myapp.exe # Windows
オプション2: ビルドとテストを一度に実行
# ビルド(リリースモード)+ テスト実行
deno task test
# ビルド(デバッグモード)+ テスト実行
deno task test --config Debug
注意: deno task testコマンドは、まずプロジェクトをビルドし、その後実行ファイルを自動で実行します。追加の引数を渡すことでビルドをカスタマイズできます。
# デバッグ設定でビルド + テスト
deno task test --config Debug
# Ninjaジェネレーターを使用してテスト
deno task test --generator Ninja
内部詳細:ビルド-テスト実行において行われていること
deno task testを実行すると、以下の順序で処理が行われます
- CMakeがプロジェクトを設定(必要に応じて)
- すべてのターゲット(実行ファイルとライブラリ)をビルド
- CMake File APIを使用してビルドした実行ファイルの場所を特定
- 実行ファイルを自動的に実行
これにより、常に最新のビルド結果をテストすることが保証されます。
技術説明:CMake File APIの活用
CMake File APIとは
CMake 3.14以降に導入されたCMake File APIは、CMakeの内部情報をJSON形式で外部に提供する仕組みです。
取得できる情報:
- ビルドターゲット(実行ファイル、ライブラリ)の一覧
- 各ターゲットの出力パス
- 依存関係グラフ
- コンパイルオプション、リンクオプション
- ソースファイルの一覧
TypeScriptとの相性
Deno以外のスクリプト実行環境でもJSONをパース可能ではありますが、TypeScriptを使うことで、型システムによる利点が得られます。
- IDEで補完が効く
- 型定義により構造が明確
- リファクタリングが安全
// 型定義により構造が明確
const index: FileAPIIndex = JSON.parse(
await Deno.readTextFile(indexPath)
);
// IDE補完が効き、タイポを防げる
const codemodelRef = index.reply["codemodel-v2"];
CMake File APIを使用する利点
ビルド成果物のファイルパスなどの情報を、CMake自身が出力してくれることにより、C++ビルドシステム毎の実装の差異に起因するディレクトリ構造の変更に対して、ビルドスクリプトを修正する必要がなくなります。
従来の方法(ハードコーディング)
if(Deno.build.os === "windows"){
const execPath = "build/bin/Release/myapp.exe";
const libPath = "build/lib/Release/myapp_utils.dll";
} else {
...
}
CMake File APIを使用して動的にファイルパスを解決する
const artifacts = await parseFileAPI("build");
const exec = artifacts.find(a => a.type === "EXECUTABLE");
const lib = artifacts.find(a =>
a.type === "SHARED_LIBRARY" && a.name === "myapp_utils"
);
CMake File APIの動作フロー
本プロジェクトでのCMake File APIの活用フローは以下の通りです
1. CMake File APIのクエリを作成する
setupFileAPI()関数が、CMakeに対してcodemodel-v2をリクエストするクエリファイルを作成します:
build/.cmake/api/v1/query/codemodel-v2
2. CMake設定を実行する
cmake -B buildを実行すると、CMakeがクエリを検出し、以下の場所にJSONレスポンスを生成します:
build/.cmake/api/v1/reply/
├── index-*.json # インデックスファイル
├── codemodel-v2-*.json # コードモデル情報
└── target-*.json # 各ターゲットの詳細情報
3. CMake File APIのレスポンスをTypeScriptでパースする
parseFileAPI()関数が以下の処理を実行します
- Denoの
walkAPIでインデックスファイルを検索 - 最新のインデックスファイルを読み込み
- TypeScriptの型定義を使って安全にパース
- 各ターゲットの情報を収集
- プラットフォーム固有のパス処理を実行
4. CMakeビルド成果物の情報を抽出する
パースした情報から、以下のCMakeビルド成果物の情報を抽出します。具体的には、ビルド成果物のファイルパスを得ることができます。
- 実行ファイル(EXECUTABLE)
- 静的ライブラリ(STATIC_LIBRARY)
- 動的ライブラリ(SHARED_LIBRARY)
5. 発展的:後続処理への活用
抽出した成果物情報は、以下のような用途に活用できます
- テスト実行時の実行ファイルパスの特定
- コード署名対象ファイルの自動検出
- パッケージング対象の特定
- インストール処理の自動化
技術説明:Deno Taskによるコマンド簡略化
Denoにはタスクランナーが組み込まれており、deno.jsonでタスクを定義することで、複雑なコマンドを短く簡潔に実行できます。ビルド/テスト/クリーンの実行等、アプリケーション開発の工程において繰り返し実行する処理をコマンドラインから短いタイプ数で呼び出すことができます。
deno.jsonの設定
本記事のサンプルプロジェクトでは、myappディレクトリのルートに配置したdeno.jsonにおいて、以下のタスクを定義しています
{
"tasks": {
"build": "deno run --allow-all build.ts",
"build:debug": "deno run --allow-all build.ts --config Debug",
"build:release": "deno run --allow-all build.ts --config Release",
"clean": "deno run --allow-all build.ts --clean",
"rebuild": "deno run --allow-all build.ts --clean && deno run --allow-all build.ts --config Release",
"test": "deno run --allow-all build.ts --test",
"format": "deno fmt",
"format:check": "deno fmt --check",
"lint": "deno lint"
}
}
タスク一覧の表示
deno taskとだけ入力すると、利用可能なタスクの一覧が表示されます
$ deno task
Available tasks:
- build
deno run --allow-all build.ts
- build:debug
deno run --allow-all build.ts --config Debug
- build:release
deno run --allow-all build.ts --config Release
- clean
deno run --allow-all build.ts --clean
- rebuild
deno run --allow-all build.ts --clean && deno run --allow-all build.ts --config Release
- test
deno run --allow-all build.ts --test
- format
deno fmt
- format:check
deno fmt --check
- lint
deno lint
まとめ
筆者は、Deno + dax + CMakeの組み合わせにより、C++/CMakeプロジェクトにおいて以下のメリットが得られると考えています。ご参考になれば幸いです。
- クロスプラットフォーム性: Windows/macOS/Linuxで同じスクリプトが動作
- 型安全性: TypeScriptによる設定管理とスクリプティング時のエラー検出
- 環境構築の簡略化: Denoのシングルバイナリで依存関係を最小化
- CMake統合: CMake File APIによるビルド情報の自動取得
- 開発者体験の向上: Deno Taskによるコマンド簡略化と自己文書化
- コラボレーション改善: JavaScript/TypeScriptの高い普及率を活用