Immersive Stream For XRを継続開発するときのmac版スクリプトの準備



# はじめに

Google Cloud（以下、GCP）が提供する Immersive Stream For XR（以下、is4xr）をみなさん活用していますでしょうか。is4xrの概要や仕組みについては、[こちら](https://qiita.com/1kjwk1/items/ed4f2d9683924cf2e375)の記事をご覧ください。

is4xrのクイックスタートでは作業を行うローカルPCとしてはwindowsが推奨されていますが、macでも問題ないことは以前の記事でも紹介しました。しかしながら継続的な開発を行う場合、プロジェクトを同期するために必要なスクリプトがwindows向けのPowerShellしか用意されていないため、mac向けのシェルスクリプトにします。

https://cloud.google.com/immersive-stream/xr/docs/quickstart

:::note info
is4xrはそもそもプロジェクトビルドをクラウド上で行うため、ローカルPCのプラットフォームは関係ないです。
※実際には関係あるケースがありますがここでは割愛します。
:::

# is4xrでの継続的な開発

クイックスタート内で継続的な開発については以下のように図示されています。

![quickstart-dev-cycle.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3489259/a6af5226-eb80-a148-a1f9-104d0e2aa0e7.png)

右半分の`Content itetation cycle`が継続的な開発部分になりますが、もう少し細かく記載すると以下となります。

1. ローカルPCでUnrealEngine（以下、UE）プロジェクトを開発する
1. `SyncContent`スクリプトを使ってGoogle Cloud Storage（以下、GCS）のバケットへプロジェクトを同期する
1. is4xrでビルドを実行し、バージョンを作成する
1. is4xrでバージョンを指定してインスタンスを作成する
1. WEBアクセスでクラウドレンダリングで確認する

上記 2. について、Googleでis4xr向けに用意されたテンプレートプロジェクトにはwindows向けのPowerShellスクリプトが含まれています。内容的にはGoogle Cloud SDKのgsutilのrsyncコマンドを使ってビルドに必要なファイル、フォルダをGCSバケットに同期しています。

前回までの記事で説明簡略化のためにまるごとバケットに直接アップロードしていましたが、通常はこの方法でプロジェクトを更新したらスクリプトで同期してビルドしてデプロイという継続的な開発を行うことになります。

PowerShellをmacで利用するには手間がかかるのでシェルスクリプトに書き直そうと思ったのですが、テンプレートプロジェクトのissueとして既にシェルスクリプトを含めて書いてくれている方がいました。

https://github.com/GoogleCloudPlatform/immersive-stream-for-xr-templates/issues/16

ハートもついてますが処理されず謎の状態のまま１年放置されてますね、、、

今回やりたかったことはほぼこちらの内容になりますが、現在はgsutilよりgcloud storageコマンドを使う方が一般的になっていますので修正を加えたシェルスクリプトを紹介します。

## `gsutil` と `gcloud storage` コマンド

GCSバケット操作に関しては、gsutilが昔からあるコマンドですが、現在は全体的にgcloudコマンドに整理されていっているようですので、基本的にgcloud storageを使う方がよいと思います。

gcloud storageのGA直後はサブコマンドが不足していて、rsyncもなかったようですが現在は存在しています。また、gsutilでは並列処理などをオプションとして指定する必要がありましたがデフォルトとなっていて指定なども不要となっています。

https://cloud.google.com/sdk/gcloud/reference/storage/rsync

gsutilよりめっちゃ速いよというGoogle公式の記事もありますので、基本的にこちらを使う方がよさそうです。（UEプロジェクトでは数十GBのサイズを扱うことなどもあります）

https://cloud.google.com/blog/products/storage-data-transfer/new-gcloud-storage-cli-for-your-data-transfers?hl=en



## 同期が必要なファイル

is4xrでビルドするために必要なファイルは以下となります。

|種別|必要なファイル|
|---|---|
|is4xrとして必要|CHANGELOG.md|
|UEとして必要|*.uproject, Config, Cloud, ToBuild, Source, Plugins, ToCustomize|

CHANGELOG.mdだけUEには関係がなくis4xrとして必要なファイルになります。
このファイルの内容でビルダーとのバージョン整合性チェック等が行われているようです。

また、現在のテンプレートプロジェクトのmainブランチのスクリプトを見ると、同期しているファイル、フォルダの内容が異なりますが、最新の内容が過去のPRでロールバックされてしまっているのでこちらが正しいです。不要なファイルを対象外にしているだけで実際はどちらでも問題は発生しないのでスルーされそうですが一応issueはあげています。

https://github.com/GoogleCloudPlatform/immersive-stream-for-xr-templates/issues/41

# 最終的なシェルスクリプト

issueで上げていただいた方のシェルスクリプトでgcloud storageコマンドに変更したものがこちらです。

``` sh
#!/bin/bash

gcs_bucket=$1
if [ -z $gcs_bucket ]; then
	echo "Please input Bucket name"
	exit 1
fi

ProjectPath=$(dirname "$0")
cd $ProjectPath/..
ProjectPath=$(pwd)
ProjectFile=$(find *.uproject | head -1); echo $A;

SyncFiles=("$ProjectFile" "CHANGELOG.md")

FoldersList=("Content" "Config" "Cloud" "ToBuild" "Source" "Plugins" "ToCustomize")
SyncFolders=()
for folder in ${FoldersList[@]}
do
	if [ -d "$ProjectPath/$folder" ]; then
		SyncFolders+=($folder)
	fi
done


echo "Sync the following files and folders to gs://$gcs_bucket:"
printf -v SyncFileString '%s, ' "${SyncFiles[@]}"
echo "Files: ${SyncFileString:0:${#SyncFileString}-2}"
printf -v SyncFolderString '%s, ' "${SyncFolders[@]}"
echo "Folders: ${SyncFolderString:0:${#SyncFolderString}-2}"

read -r -p "Proceed? (Y/n) " response
case "$response" in
    [yY][eE][sS]|[yY]) 
        ;;
    *)
        exit 1
        ;;
esac

echo "Deleting all *.uproject files..."
gsutil rm gs://${gcs_bucket}/*.uproject


for file in ${SyncFiles[@]}
do
	echo "Copying file ""$SyncFile""..."
  	gsutil cp "$ProjectPath/$file" gs://$gcs_bucket/$file
done

for folder in ${SyncFolders[@]}
do
	echo "Synching folder ""$folder""..."
	gcloud storage rsync "$ProjectPath/$folder" gs://$gcs_bucket/$folder --recursive --delete-unmatched-destination-objects
done

echo "All files uploaded."
```

# 動作確認

実行にはGoogle Cloud SDKが必要になるため導入して実行します。

## Google Cloud SDKの導入

brewが導入されていればそのままbrewで入れるのが手軽です。

https://formulae.brew.sh/cask/google-cloud-sdk

``` sh
$ brew install --cask google-cloud-sdk
```
brewの説明ページの通り利用しているシェルにあわせて追加します。私はzshなので`.zshrc`に以下を追加します。

``` sh
source "$(brew --prefix)/share/google-cloud-sdk/path.zsh.inc"
source "$(brew --prefix)/share/google-cloud-sdk/completion.zsh.inc"
```


公式の場合はこちらを参考にしてください。

https://cloud.google.com/sdk/docs/install?hl=ja


is4xrを利用しているgoogleアカウントで初期設定をします。

``` sh
$ gcloud init
```

## シェルスクリプトの実行

テンプレートプロジェクトの`./XR_Actions`配下に作成したシェルスクリプトを配置して実行します。こんな感じになればOKです。

``` sh
❯ ./XR_Actions/SyncContent.sh is4xr-test

Sync the following files and folders to gs://is4xr-test:
Files: XR_Template.uproject, CHANGELOG.md
Folders: Content, Config, ToBuild
Proceed? (Y/n) Y
Deleting all *.uproject files...
Removing gs://is4xr-test/XR_Template.uproject...
/ [1 objects]
Operation completed over 1 objects.
Copying file ...
Copying file:///Users/xxxx/Downloads/immersive-stream-for-xr-templates-main/Unreal_Template_Project/XR_Template.uproject [Content-Type=application/octet-stream]...
/ [1 files][  513.0 B/  513.0 B]
Operation completed over 1 objects/513.0 B.
Copying file ...
Copying file:///Users/xxxx/Downloads/immersive-stream-for-xr-templates-main/Unreal_Template_Project/CHANGELOG.md [Content-Type=text/markdown]...
- [1 files][  1.5 KiB/  1.5 KiB]
Operation completed over 1 objects/1.5 KiB.
Synching folder Content...
At file:///Users/xxxx/Downloads/immersive-stream-for-xr-templates-main/Unreal_Template_Project/Content/**, worker process 20398 thread 8367471296 listed 385...
At gs://is4xr-test/Content/**, worker process 20398 thread 8367471296 listed 385...
  Completed files 0 | 0B
Synching folder Config...
At file:///Users/xxxx/Downloads/immersive-stream-for-xr-templates-main/Unreal_Template_Project/Config/**, worker process 20740 thread 8367471296 listed 5...
At gs://is4xr-test/Config/**, worker process 20740 thread 8367471296 listed 5...
  Completed files 0 | 0B
Synching folder ToBuild...
At file:///Users/xxxx/Downloads/immersive-stream-for-xr-templates-main/Unreal_Template_Project/ToBuild/**, worker process 20996 thread 8367471296 listed 2...
At gs://is4xr-test/ToBuild/**, worker process 20996 thread 8367471296 listed 2...
  Completed files 0 | 0B
All files uploaded.

```


# おまけ


もしgsutilコマンドのままで実行する場合、私の環境だと以下２点の警告がでましたので参考で対応方法を記載します。

1. crcmodのインストールが必要
1. マルチプロセッシングに問題がある場合はオプションを追加

## crcmodのインストールが必要

以下のような警告が発生します。

``` sh
WARNING: gsutil rsync uses hashes when modification time is not available at
both the source and destination. Your crcmod installation isn't using the
module's C extension, so checksumming will run very slowly. If this is your
first rsync since updating gsutil, this rsync can take significantly longer than
usual. For help installing the extension, please see "gsutil help crcmod".
```

以下Google Cloud SDKのissueで説明があります。

https://github.com/GoogleCloudPlatform/gsutil/issues/1073

```sh
❯ gsutil version -l
~~省略~~
multiprocessing available: True
compiled crcmod: False
~~省略~~
```

`compiled crcmod: False`になっているので、issueの通りインストールしてTrueにします。

```sh
# Get the python path that Cloud SDK is using
python_path=$(gcloud info | grep "Python Location" | sed 's/.*\[\(.*\)\]/\1/g' )
# Install crcmod for that python binary
sudo $python_path -m pip install -U crcmod
```

## マルチプロセッシングに問題がある場合はオプションを追加

以下のような内容が表示されます。

```
If you experience problems with multiprocessing on MacOS, they might be related to https://bugs.python.org/issue33725. You can disable multiprocessing by editing your .boto config or by adding the following flag to your command: `-o "GSUtil:parallel_process_count=1"`. Note that multithreading is still available even if you disable multiprocessing.
```

マルチプロセッシング自体はTrueになっているのですが、うまく動かないため記載通りオプションを追加した内容に修正します。

```sh
gsutil -m -o "GSUtil:parallel_process_count=1" rsync -r -d "$ProjectPath/$folder" gs://$gcs_bucket/$folder
```

# おわりに

is4xrを継続的に開発するために必要となるスクリプトについて、macで利用するためのシェルスクリプトを用意しました。