2026-04-28 追記
42Tokyo入学後、最初の課題であるlibftをクリアしたことで、この記事で推測していた内容の一部を実際に確認できた。
2026年4月のカリキュラム改訂後のlibftを前提に、わかったことと42-c-templateの見直し内容を更新版の記事にまとめている。
42Tokyoの課題管理をlibft後に見直してみた
最初の課題libftをクリアしてわかったこととGitHubでの課題管理方法の更新
きっかけ
NorminetteのREADMEにGitHub Actionsのワークフロー設定例が記載されていた。
GitHub - 42school/norminette: Official 42 norminette
Official 42 norminette. Contribute to 42school/norminette development by creating an account on GitHub.
課題で指定されたもの以外のファイルが存在すると機械採点で弾かれるものだと思い込んでいたが、公式が「こんな設定できるよ」と例示しているなら.github/以下のワークフロー設定ファイルが存在しても問題ないのだろうと仮定し、GitHubを利用した課題の管理方法について考えてみた。
機械採点 (Moulinette) に関する考察
機械採点の内容はブラックボックスであるため、各課題のsubject.pdfや採点結果 (Deepthought) などから予測するしかない。
Piscineでの経験を元に考えると、機械採点の基本的な流れは以下のとおりかと思う。
①提出物をクローン
↓
②Norminette
↓
③コンパイル
↓
④テスト
↓
⑤結果集計
これにプラスして、Makefileが必要な課題であればmake clean, make fclean, make reのチェックも行われる。
したがって、指定のファイルが指定のディレクトリに置いてあればコンパイルが通るため機械採点的にはそれで良く、コンパイルに関係ない余分なファイルがあったとしても無視されるのではないかというのが現時点での私の考えだ。
もちろんこれは考察に過ぎないので、入学して課題を進める中で色々試してみるつもり。
GitHubでの管理
運用方針
- 一つの課題ごとにリポジトリを用意
- GitHubリポジトリを
originとし、intraから払い出されたvogsphereのURLをリモートに追加する - 基本的にコードは
origin/mainで管理し、提出時だけvogsphereにpushする - テストは自分で用意せず、42の偉大な先輩たちが作成してくれたテストツールを活用する
GitHub - usatie/libft-tester-tokyo: Tester for the libft project of 42 school
Tester for the libft project of 42 school. Contribute to usatie/libft-tester-tokyo development by creating an account on GitHub.
構成
以下の構成をテンプレートリポジトリとして用意し、課題ごとにここからリポジトリを作成することにした。 Makefileは課題に合わせて書き換える必要あり。
srcディレクトリやincディレクトリをいちいち作るのもめんどくさいので、.gitkeepを配置してあらかじめ用意しておく。
ただし、課題によっては最上位にex00などの指定された名称のディレクトリを用意しなければいけない可能性もあるため、要注意。
.
├── Makefile
├── src/ # ソースファイル
├── inc/ # ヘッダーファイル
└── .github/
└── workflows/
├── format-c.yml
├── norminette.yml
└── notify-discord.yml
GitHub - bassaaaa/42-c-template: 42 subject template for C
42 subject template for C. Contribute to bassaaaa/42-c-template development by creating an account on GitHub.
リモートの設定と提出
GitHubリポジトリをoriginとして作成した後、intraで払い出されたvogsphereのURLをリモートに追加する。リモート名は連番(42_v1, 42_v2, ...)にすることで、リトライ時にも対応できるようにする。
git remote add 42_v1 <vogsphereのURL>
git push 42_v1 mainリトライ時は新しいURLが払い出されるので、番号を増やして追加する。
git remote add 42_v2 <リトライ時のURL>
git push 42_v2 maingit remote -vでどのリモート名がどのURLに対応しているか一目で分かる。
origin git@github.com:yourname/repo.git (fetch)
origin git@github.com:yourname/repo.git (push)
42_v1 git@vogsphere.42tokyo.jp:intra-uuid-xxx/... (fetch)
42_v1 git@vogsphere.42tokyo.jp:intra-uuid-xxx/... (push)
42_v2 git@vogsphere.42tokyo.jp:intra-uuid-yyy/... (fetch)
42_v2 git@vogsphere.42tokyo.jp:intra-uuid-yyy/... (push)GitHub ActionsでCIを設定
Piscine中はc-formatter-42を利用していたため、提出後のNormエラーという事態にはならなかったが、自動化できるものはしておきたいということで、ワークフローを組んだ。
フローの流れ
mainブランチへのpushまたはPRをトリガーに、以下の順でジョブが実行される。
push / PR → main
↓
① c_formatter_42でフォーマット(変更があれば自動コミット)
↓
② NorminetteでNormチェック
↓
③ 結果をDiscordに通知
各ジョブは独立したワークフローファイルに分割されており、norminette.ymlがエントリーポイントとなってformat-c.ymlとnotify-discord.ymlをworkflow_callで呼び出す構成にした。
c_formatter_42の自動適用
Norminetteの前段として、c_formatter_42によるフォーマットを自動実行する。変更があればformat: apply c_formatter_42というコミットメッセージで自動コミット・プッシュされる。
自動コミット・プッシュにはリポジトリへの書き込み権限が必要なため、format-c.ymlを呼び出すジョブにpermissions: contents: writeを付与している。
jobs:
format:
permissions:
contents: write
uses: ./.github/workflows/format-c.yml- name: Format code
run: |
find . \( -name "*.c" -o -name "*.h" \) | grep -v "/.git/" | xargs -I {} c_formatter_42 {}
- name: Commit formatted code
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git diff --quiet && exit 0
git add -u
git commit -m "format: apply c_formatter_42"
git pushNorminetteの自動実行
フォーマット実行後、Norminetteが実行される。
- name: Run norminette
id: norminette
run: |
set +e
OUTPUT=$(norminette 2>&1)
EXIT_CODE=$?
echo "$OUTPUT"
{
echo "output<<EOF"
echo "$OUTPUT"
echo "EOF"
} >> $GITHUB_OUTPUT
exit $EXIT_CODEDiscord通知
Norminetteの実行結果をDiscordに通知する。 通知には以下の情報を含めている。
- 成功 / エラーをタイトルと色で視覚的に区別
- リポジトリ名・ブランチ名
- コミットメッセージ(全文)
- 実行者
- GitHubのコミットURLリンク
- Norminetteの実行結果(コードブロック)
- name: Notify Discord
if: always() && env.DISCORD_WEBHOOK_URL != ''
env:
DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }}
COMMIT_MSG: ${{ github.event.head_commit.message }}
COMMIT_URL: ${{ github.server_url }}/${{ github.repository }}/commit/${{ github.sha }}
REPO: ${{ github.repository }}
BRANCH: ${{ github.ref_name }}
ACTOR: ${{ github.actor }}
JOB_STATUS: ${{ job.status }}
NORM_OUTPUT: ${{ steps.norminette.outputs.output }}
run: |
TRUNCATED=$(printf '```\n%s\n```' "$(echo "$NORM_OUTPUT" | head -c 990)")
if [ "$JOB_STATUS" = "success" ]; then
COLOR=3066993
TITLE="✅ Norminette Passed"
else
COLOR=15158332
TITLE="❌ Norminette Error"
fi
PAYLOAD=$(jq -n \
--arg title "$TITLE" \
--argjson color "$COLOR" \
--arg repo "$REPO" \
--arg branch "$BRANCH" \
--arg commit "$COMMIT_MSG" \
--arg actor "$ACTOR" \
--arg url "$COMMIT_URL" \
--arg output "$TRUNCATED" \
'{
embeds: [{
title: $title,
color: $color,
fields: [
{ name: "Repo", value: $repo, inline: true },
{ name: "Branch", value: $branch, inline: true },
{ name: "Commit", value: $commit, inline: false },
{ name: "By", value: $actor, inline: true },
{ name: "Result", value: $output, inline: false },
{ name: "\u200b", value: ("[GitHubで確認](" + $url + ")"), inline: false }
]
}]
}')
curl -s -X POST "$DISCORD_WEBHOOK_URL" \
-H "Content-Type: application/json" \
-d "$PAYLOAD"Webhook URLはリポジトリのSecretsに設定する。
- Discordのチャンネル設定 → 連携サービス → ウェブフック からWebhook URLを取得
- GitHubリポジトリの Settings → Secrets and variables → Actions → New repository secret
DISCORD_WEBHOOK_URLという名前でURLを登録
ワークフロー内では ${{ secrets.DISCORD_WEBHOOK_URL }} で参照し、値が未設定の場合は通知ステップをスキップするようにした。
まとめ
今回構築したのは、pushするだけでフォーマット・Normチェック・Discord通知までが自動で走る環境だ。個人課題では過剰に見えるかもしれないが、「提出後にNormエラーに気づく」という最悪のパターンを防ぐには十分な投資だと思っている。
入学後のチーム課題はPiscineの時と比べて規模も期間も比べ物にならないようなので、スタイルの統一やNormチェックを自動化しておく意義はさらに大きい。
ただし、.github/以下のファイルがMoulinetteの採点に影響するかは未検証だ。「指定外のファイルは無視される」という前提で構築しているが、あくまで推測に過ぎない。入学後、実際に課題を提出しながら問題がないか確認していく。もし何か影響があった場合は新たに記事を書くつもり。
今後の展望
やりたいことはまだまだある。
| やること | 概要 |
|---|---|
| リポジトリ作成の自動化 | 42APIのslugを使い、テンプレートからのリポジトリ作成〜vogsphereリモート追加までをワンコマンドで |
| CIの強化 | makeのコンパイルチェック、valgrindによるメモリリークの検出をCIに組み込む |
| 進捗ページ | 42APIから課題一覧と評価結果を取得し、このブログに進捗ダッシュボードを作る |