壁紙アプリの Android 版を更新しようとして、ビルドを走らせました。返ってきたのは「リソースが重複している」という、見覚えのないエラーです。
ソースは触っていません。それなのに、重複する名前のファイルがビルドの出力先にずらりと並んでいました。数えると、競合コピーが80個以上できていました。
結論から書きます。原因はコードではなく、同期ソフトが「毎回作り直されるファイル」を別の端末の変更とみなして複製したことでした。
競合コピーは、作り直されるファイルから生まれます
Dropbox は、同じファイルが複数の場所で変わったと判断すると、(◯◯'s conflicted copy 2026-10-21) のような名前の複製を残します。
人が編集する文書なら、それは親切な動きです。ところがビルドの生成物は事情が違います。クラッシュ解析のプラグインは、ビルドのたびにあるファイルを作り直します。その作り直しと同期の書き戻しが重なると、同じ場所に「別の版」が現れ、複製が増えていきます。
ビルドツールは出力先のファイルをすべて取り込みます。複製も例外ではありません。同じリソース名が2つ並んで、「重複」で止まります。
整理すると、次の表のようになります。
| 置き場所 | 誰が書くか | 競合コピーが出たとき |
|---|---|---|
| ソース(src など) | 自分 | 消さずに中身を見て判断する |
| build / .gradle | ビルドツール | 消してよい(次のビルドで作り直される) |
| Pods / xcuserdata | パッケージ管理・Xcode | 消してよい(再生成できる) |
この線引きが、あとの二つのスクリプトの骨組みになります。
まず数える:どこに何個あるかを知る
掃除の前に、現状を数えます。消す前に数えるのは、消してはいけない場所に競合コピーが混ざっていないかを知るためです。
#!/usr/bin/env python3
"""Dropbox の競合コピーを数える。生成物の下にあるものは別枠で集計する。"""
import os, re, sys, collections
CONFLICT = re.compile(
r"\((?:.+?)['’]s conflicted copy \d{4}-\d{2}-\d{2}(?: \d+)?\)|\(.*競合コピー.*\)"
)
GENERATED = {"build", ".gradle", "Pods", ".swiftpm", "xcuserdata", "node_modules", ".next"}
def scan(root):
total, scanned = 0, 0
by_dir = collections.Counter()
for dirpath, dirnames, filenames in os.walk(root):
dirnames[:] = [d for d in dirnames if d != ".git"] # .git は読まない
for name in filenames:
scanned += 1
if CONFLICT.search(name):
total += 1
parts = set(os.path.relpath(dirpath, root).split(os.sep))
hit = sorted(parts & GENERATED)
by_dir[hit[0] if hit else "(source)"] += 1
return scanned, total, by_dir
if __name__ == "__main__":
scanned, total, by_dir = scan(sys.argv[1])
print(f"scanned={scanned} conflicts={total}")
for k, v in by_dir.most_common():
print(f" {k}: {v}")
sys.exit(1 if total else 0)何を解決するかというと、「競合コピーがあるか」ではなく「どの種類の場所に、いくつあるか」を1回で出すことです。末尾の終了コードは、のちほどビルド前の守りや定期点検から使えます。
なぜこう書くか。名前の正規表現は英語版と日本語版の両方に合わせています。Dropbox の表記は環境の言語で変わるためです。
試しに、ビルドの出力先に似せた小さなフォルダで動かしました。
scanned=5 conflicts=4
build: 3
(source): 1私の作業フォルダ全体(9万7千ファイル余り)にも走らせてみました。競合コピーは0件で、所要は17秒ほどでした。ファイル数が多い場所では数十秒かかる前提で、ビルドのたびではなく「朝に1回」や「ビルド対象のフォルダだけ」に絞るほうが現実的です。