🫥

Github Actions上とローカルMac上のmakeタスクの挙動が異なっていてプチハマりした件

2023/02/17に公開

タスクランナーとしてmakeタスクをよく利用しています。
その際、ローカルのMacで問題なく動くのに、GitHub Actions上で異なる動作になっていてプチハマりしてたのでメモします。
なお、GitHub Actionsで利用しているDocker Imageは ubuntu-latest です。

makeタスクの内容

docker composeのサービス起動待ちのために以下のようなワンライナーのタスクを作ることがあります。 [1]
以下は localhost:8080の疎通ができるまで1秒スリープしながら 600回繰り返す(おおよそ最大10分待つ) という何のことはないタスクです。

waiting-for-service:
	@echo "waiting for service ..."
	@for i in {0..600}; do if [ `curl -fs http://localhost:8080/` ]; then echo 'complete!'; break; fi; sleep 1; done;

ローカルPC環境で動かすと想定通り、サービスの起動を待つ動作になります。
ところがGitHub Actions上ではうんともすんとも言わずスルッと抜けてしまいます。 なんで?
というわけで、一つ一つ検証しながら原因を探ってみたいと思います。


検証その1:判定の仕方を変えてみる

あまり問題はあるとは思えないのですが、 curlの判定を終了ステータスで確認するようにしてみました。

waiting-for-service:
	@echo "waiting for service ..."
	@for i in {0..600}; do _=`curl -fs http://localhost:8080/`; if [ "$$?" = "0" ]; then echo 'complete!'; break; fi; sleep 1; done;

しかし、特に改善は見られませんでした。

検証その2:makeバージョンを疑ってみる

ローカルのmakeバージョンとGitHub Actions上のmakeバージョンを表示してみて、差分を確認しました。

ローカルPC: makeバージョン

% make --version
GNU Make 3.81
Copyright (C) 2006  Free Software Foundation, Inc.
This is free software; see the source for copying conditions.
There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE.

This program built for i386-apple-darwin11.3.0

GitHub Actions: makeバージョン

% make --version
GNU Make 4.3
Built for x86_64-pc-linux-gnu
Copyright (C) 1988-2020 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

お、差分があるようです。これかな?と思いローカルPCのmakeを 4.x系にしてみることにしました。
brewでインストールし、PATH変数に追加すればOKです。

% brew install make
Running `brew update --auto-update`...
(中略)

GNU "make" has been installed as "gmake".
If you need to use it as "make", you can add a "gnubin" directory
to your PATH from your bashrc like:

    PATH="/opt/homebrew/opt/make/libexec/gnubin:$PATH"
==> Summary
🍺  /opt/homebrew/Cellar/make/4.4: 16 files, 1.2MB
==> Running `brew cleanup make`...
Disable this behaviour by setting HOMEBREW_NO_INSTALL_CLEANUP.
Hide these hints with HOMEBREW_NO_ENV_HINTS (see `man brew`).
Warning: Use postgresql@14 instead of deprecated postgresql

表示内容に則ってPATHを指定し、makeを実行してみると問題なくupgradeされたようです。

% export PATH="/opt/homebrew/opt/make/libexec/gnubin:$PATH"     
% make -v                                           
GNU Make 4.4
Built for aarch64-apple-darwin21.6.0
Copyright (C) 1988-2022 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

これが原因であればローカル実行すると再現するはずですが、このmakeバージョンで実行しても特に再現せず問題なく wait出来てしまいました😢


検証その3:ループ内でデバッグしてみる

もっと早くやれよというお話もありますが、forループがそもそもきちんと動いているかを確認してみます。
最初のスクリプトにechoを仕込んでみます。また、curlのsilentも外して詳細に表示してみます。

waiting-for-service:
	@echo "waiting for service ..."
	@for i in {0..600}; do echo "i=$$i"; _=`curl -f http://localhost:8080/`; if [ "$$?" = "0" ]; then echo 'complete!'; break; fi; sleep 1; done;

ローカル動作結果

ローカルでは以下のような表示になりました。
きちんと、何度かwaitしたのちにbreakしています。

% make waiting-for-service
waiting for service ....

i=0
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0
curl: (52) Empty reply from server

i=1
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0
curl: (52) Empty reply from server
(中略)
complete!

GitHub Actions 動作結果

GitHub Actions上では以下のような表示になりました。
おや、iの値がおかしいですね…!?
またcompleteも表示されずにすぐに終わっています。なるほど、きちんとループ変数が展開されていないことが原因のようです。

% make waiting-for-service
waiting for service ....

i={0..600}
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0


検証その4:SHELLを指定してみる

ループ変数に使われている以下の記述が問題のようです。
for i in {0..600};
ここまでくると、確信はないですがおそらくshとbashの違いかなーという想像がついたのでmakefile内で指定するようにしてみました。
試す方針としては

  • 「ローカルではsh指定
  • 「GitHubActionsではbash指定

するようにしてみます。

ローカル動作結果

以下を追加した結果です。

SHELL=/bin/sh

以下実行結果です。あれ、sh指定でも問題なく動作してしまった😓
若干雲行きが…

% make waiting-for-service
waiting for service ....

i=0
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0
curl: (52) Empty reply from server

i=1
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0
curl: (52) Empty reply from server
(中略)
complete!

GitHub Actions 動作結果

以下を追加した結果です。

SHELL=/bin/bash

以下実行結果です。おお、想定通りの正しい動作になったようです! 🎉
やはり、原因としては実行シェルの違いだったようですね。

% make waiting-for-service
waiting for service ....

i=0
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0
curl: (52) Empty reply from server

i=1
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0
curl: (52) Empty reply from server
(中略)
complete!

最終的なスクリプト

あまり書く意味もないと思いますが、最終的には以下のようなタスクになっています

SHELL=/bin/bash

waiting-for-service:
	@echo "waiting for service ..."
	@for i in {0..600}; do if [ `curl -fs http://localhost:8080/` ]; then echo 'complete!'; break; fi; sleep 1; done;


まとめ

わかってみればなんのことはない原因でした。
SHELL変数は知っていたのですが、場合によって設定したり設定しなかったりしていました。
今後は確実にSHELL指定してmakeタスクを書いていこうと思います!

脚注
  1. make内では改行するの若干面倒なのでワンライナーで書きたくなりますよね ↩︎

Discussion