Flutterは単一のコードベースからiOS, Android, Web, Windowsなど多彩なプラットフォーム向けにアプリを作れるのが魅力です。
しかし、「Windowsアプリ(.exe)をビルドするにはWindows機が必要」 という制約があります。
普段MacやLinuxで開発していると、ビルドのためだけにPCを移動したり、Git経由で同期してWindows側でコマンドを叩くのは非常に手間ですよね。GitHub ActionsなどのCIサービスも便利ですが、ちょっとした動作確認のためにpushして待つのは時間がかかります。
そこで今回は、余っているWindows PCを「ビルド専用サーバー」にし、Macからワンクリックでビルドを依頼し、完成した .exe を受け取るシステム をPythonで自作しました。
作るもの:システム構成
仕組みはシンプルです。HTTP通信(REST API)を使ってソースコードと成果物をやり取りします。
- Client (Mac/Linux): 現在のコードをZip圧縮し、サーバーへ送信。
- Server (Windows): コードを受け取り、
flutter build windowsを実行。 - Server: 生成された
.exe等をZipにまとめて返却。 - Client: 成果物を受け取り、日時付きファイル名で保存してデスクトップ通知。
これを Visual Studio Code や Android Studio のタスクとして登録し、ショートカット一発で実行できるようにします。
1. サーバー側の構築 (Windows機)
まずはビルドを行うWindowsマシンの設定です。
| 項目 | 要件・推奨バージョン | 備考 |
| OS | Windows 11 | Home/Proどちらでも可 |
| Flutter SDK | 3.x 以上推奨 | C:\src\flutter に配置 |
| Visual Studio | Visual Studio 2026 | 「C++によるデスクトップ開発」をインストール済であること |
| Python | 3.10 以上 | APIサーバーの実行に使用 |
| PowerShell | 5.0 以上 | ファイアウォール設定等で使用(標準でOK) |
管理しやすくするため、今回はすべての関連ファイル(SDK含む)を C:¥src 配下に集約する構成にします。
1-1. 前提環境の準備
Windows 11のマシンを用意し、以下のフォルダ構成を作ります。
(※フォルダ構成は例です)
C:¥
└─ src¥
├─ flutter¥ (Flutter SDK 本体)
├─ build_server.py (今回作成するスクリプト)
└─ server_workspace¥ (※スクリプトが自動生成します)
- Flutter SDK: 公式サイトからダウンロードし、
C:¥src¥flutterに配置してください。環境変数のPathに「C:¥src¥flutter¥bin」を通してください。flutter doctorでVisual Studio (C++) の要件が満たされていることを確認します。 - Python 3.x: 公式サイトからインストールしてください。
※今回はWindowsアプリのビルドのみを目的としているため、Android Studioのインストールは不要です。
1-2. 必要なPythonライブラリのインストール
高速なAPIフレームワークである、FastAPI を使用します。コマンドプロンプトで以下を実行してください。
pip install fastapi uvicorn python-multipart
1-3. ファイアウォールの設定(重要)
LAN内のMac/Linuxから接続できるように、ポート 8000 を開放します。
「管理者権限」でPowerShell を開き、以下のコマンドを実行します。
New-NetFirewallRule -DisplayName "FlutterBuildServer" -Direction Inbound -LocalPort 8000 -Protocol TCP -Action Allow
※ セキュリティソフトのファイアウォール設定も合わせて調整してください。
1-4. サーバー用スクリプトの作成
C:¥src¥build_server.py というファイルを作成し、以下のコードを保存します。
# C:¥src¥build_server.py
import os
import shutil
import subprocess
import zipfile
import uuid
from fastapi import FastAPI, UploadFile, File, HTTPException
from fastapi.responses import FileResponse
import uvicorn
app = FastAPI()
# 設定: C:¥src 配下を作業ディレクトリとする
BASE_DIR = r"C:¥src"
WORK_DIR = os.path.join(BASE_DIR, "server_workspace")
os.makedirs(WORK_DIR, exist_ok=True)
@app.post("/build")
async def build_flutter_app(file: UploadFile = File(...)):
build_id = str(uuid.uuid4())
project_dir = os.path.join(WORK_DIR, build_id)
os.makedirs(project_dir, exist_ok=True)
zip_path = os.path.join(project_dir, "source.zip")
try:
# 1. ソースコード受信・解凍
with open(zip_path, "wb") as buffer:
shutil.copyfileobj(file.file, buffer)
with zipfile.ZipFile(zip_path, 'r') as zip_ref:
zip_ref.extractall(project_dir)
# 2. ビルド実行
print(f"[{build_id}] Build started...")
subprocess.run(["flutter", "pub", "get"], cwd=project_dir, check=True, shell=True)
subprocess.run(["flutter", "build", "windows"], cwd=project_dir, check=True, shell=True)
# 3. 成果物のパス特定 (Flutterバージョンによるパスの揺れを吸収)
base_build = os.path.join(project_dir, "build", "windows")
target_dir = os.path.join(base_build, "x64", "runner", "Release")
if not os.path.exists(target_dir):
target_dir = os.path.join(base_build, "runner", "Release")
# 4. 圧縮して返却
artifact_zip = os.path.join(WORK_DIR, f"{build_id}_artifact")
shutil.make_archive(artifact_zip, 'zip', target_dir)
print(f"[{build_id}] Build success.")
return FileResponse(path=f"{artifact_zip}.zip", media_type='application/zip')
except Exception as e:
print(f"[{build_id}] Error: {e}")
raise HTTPException(status_code=500, detail=str(e))
if __name__ == "__main__":
print(f"Server started at {BASE_DIR}")
uvicorn.run(app, host="0.0.0.0", port=8000)
サーバーの起動:
コマンドプロンプトで以下を実行し、サーバーを待機状態にします。
cd C:¥src
python build_server.py
※ ここでWindows機のIPアドレス(例: 192.168.1.10)を確認しておいてください。
2. クライアント側の構築 (Mac / Linux / Windows)
次に、普段開発を行っているマシン側の設定です。OSは問いません。
2-1. 必要なライブラリ
通信用とデスクトップ通知用のライブラリを入れます。
# Mac / Linux
pip3 install requests plyer
# Windows
pip install requests plyer
2-2. クライアントスクリプトの配置
Flutterプロジェクトのルートディレクトリ(pubspec.yamlがある場所)に client_builder.py を作成します。
重要: SERVER_URL の部分は、先ほどのWindows機のIPアドレスに書き換えてください。
# client_builder.py
import requests
import os
import zipfile
from datetime import datetime # 追加: 日時取得用
# ==========================================
# 【設定】 WindowsサーバーのIPアドレスに書き換えてください
SERVER_URL = "http://192.168.0.130:8000/build"
# ==========================================
OUTPUT_DIR = "dist_server"
def zip_project(source_dir, output_zip):
with zipfile.ZipFile(output_zip, 'w', zipfile.ZIP_DEFLATED) as zipf:
for root, dirs, files in os.walk(source_dir):
dirs[:] = [d for d in dirs if d not in ['.git', 'build', '.dart_tool', 'dist_server', '.idea', '.vscode']]
for file in files:
if file == '.DS_Store': continue
file_path = os.path.join(root, file)
arcname = os.path.relpath(file_path, source_dir)
zipf.write(file_path, arcname)
def main():
print(f"--- Remote Build Start ---")
zip_filename = "temp_source.zip"
try:
print("Compressing project...")
zip_project(os.getcwd(), zip_filename)
print(f"Uploading to {SERVER_URL} ...")
with open(zip_filename, 'rb') as f:
# タイムアウトを10分(600秒)に設定
response = requests.post(SERVER_URL, files={'file': f}, stream=True, timeout=600)
if response.status_code == 200:
os.makedirs(OUTPUT_DIR, exist_ok=True)
# --- 変更箇所: 日時付きファイル名の生成 ---
# 例: 20250123141503 (YYYYMMDDhhmmss形式)
timestamp = datetime.now().strftime('%Y%m%d%H%M%S')
filename = f"app_release_{timestamp}.zip"
artifact_path = os.path.join(OUTPUT_DIR, filename)
# -------------------------------------
with open(artifact_path, 'wb') as f:
for chunk in response.iter_content(chunk_size=8192):
f.write(chunk)
print(f"Build Success! Saved to: {artifact_path}")
else:
print(f"Build Failed: {response.status_code}\n{response.text}")
except Exception as e:
print(f"Error: {e}")
finally:
if os.path.exists(zip_filename):
os.remove(zip_filename)
if __name__ == "__main__":
main()
3. IDEへの統合
スクリプトを毎回ターミナルから叩くのは面倒です。IDEからワンタッチで実行できるようにします。
3-1. Visual Studio Code の場合
プロジェクト内の .vscode/tasks.json に以下を記述します。
Mac/Linuxの python3 と Windowsの python コマンドの違いを吸収する設定にしています。
{
"version": "2.0.0",
"tasks": [
{
"label": "Remote Build (Windows)",
"type": "shell",
"command": "${config:python.pythonPath}",
"args": [
"${workspaceFolder}/client_builder.py"
],
"options": {
"env": {
"PYTHONIOENCODING": "utf-8"
}
},
"windows": {
"command": "python"
},
"osx": {
"command": "python"
},
"linux": {
"command": "python"
},
"group": "build",
"presentation": {
"reveal": "always",
"panel": "new"
}
}
]
}
- 使い方:
Ctrl(Cmd) + Shift + Bを押し、「Remote Build (Windows)」を選択します。
3-2. Android Studio の場合
「External Tools」機能を使用します。
- メニューから Preferences (WindowsはSettings) > Tools > External Tools を開きます。
- 「+」ボタンで新規追加し、以下のように設定します。
| 項目 | 設定値 |
| Name | Remote Build Windows |
| Program | (Mac/Linux) /usr/bin/python3 (または which python3 の結果)(Windows) python |
| Arguments | client_builder.py |
| Working directory | $ProjectFileDir$ |
- 使い方: メニューの Tools > External Tools > Remote Build Windows から実行。
- 推奨: Keymap設定でショートカットキー(例:
Cmd + Shift + W)を割り当てると非常に便利です。
実際に使ってみる
- Windowsサーバーで
build_server.pyを起動しておきます。 - MacのVS CodeやAndroid Studioで開発します。
- ビルドが必要になったら、IDEからタスクを実行します。
- ソースコードが転送され、サーバー側でビルドが走ります(初回は依存関係の解決などで少し時間がかかります)。
- 完了するとMac側に通知が届き、プロジェクト内の
dist_serverフォルダにapp_release_YYYYMMDDhhmmss.zipが保存されます。
まとめ
これで、MacBook一台でカフェにいながら(VPN等を介して)、自宅のパワフルなWindowsマシンにビルドを投げ、Windowsアプリを生成する環境が整いました。
簡易的なCI/CDですが、個人開発や少人数のチーム開発であれば、これだけで十分な効率化になります。ぜひ試してみてください!








