ソースコードからビルドしたMicrosoft Programming Experience Toolkit (PXT)を使って、ローカル環境でMicrosoft MakeCode for micro:bitを起動する

# なぜ、ソースコードからPXTをビルドするのか？

micro:bitには、`radio-broadcast`という拡張機能がありますが、この拡張機能のブロックにおいて、エディタ上での表示に不具合があります。
その原因となる箇所が`pxt`にあり、これを改修し、動作確認を行いたい為です。

## 不具合：ユーザー定義の列挙型

その不具合は、次のように再現できます（**2023/08/25現在 - PXT version:9.1.10**）。

micro:bitのプロジェクトを新規に作成し、拡張機能から`radio-broadcast`を選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252891/ef7a6f37-bd5e-771d-6b5c-53f26c5653aa.png)

選択した拡張機能から `radio send` ブロックを配置します。すると、ブロックの表示上は、 `message1` にもかかわらず、ドロップダウンリストを開いてみると、チェックマークがどこにも付いていません。
項目が選択されていないようにみえます（**期待する動作になっていない**）。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252891/5463ec4a-6479-f8e1-ab2c-e43e2dad6314.png)

次に、このブロックでは、ドロップダウンリストの選択肢を追加することができますので、`add1`、`add2`を追加します。すると、追加した項目が表示され、ドロップダウンリストでも、表示されている項目にチェックマークが付きます。
この場合、項目が選択されていることがわかります(**期待する動作**)。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252891/0bb7557e-ea14-23f3-f734-cdfe1d60bab1.png)

さらに、エディタ画面をBlocksからJavaScriptへと切り替えます。
これまで見ていた項目は、実は列挙型(enum)であり、ブロックベースのエディタ上でもそのメンバーを追加することが可能です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252891/01858ac9-e8f7-d45f-ab58-dd637d9b8379.png)

ここで、`add2`となっていた引数の値を`add1`に変更し、JavaScriptからBlocksへと切り替えます。
すると、ブロック上には、 `message1` が表示され、ドロップダウンリストを開いても、チェックマークがありません。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252891/8397ab1d-5570-587e-f1e6-b45116f58b3d.png)

Webブラウザー(Chrome)のコンソール出力を見ると警告メッセージが表示されていました。
"add1"は、ドロップダウンリストの選択肢にはないので、選択されないようです。
>
> Cannot set the dropdown's value to an unavailable option.
> Block type: radioMessageCode, Field name: MEMBER, Value: add1
>
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252891/72787968-4281-2f33-86ac-7cd066426d16.png)

なぜ、 `add1` が選択肢にないのでしょうか。
`pxt`のソースコードを読んでいくと、[FieldUserEnumクラスのdoClassValidation_()関数](https://github.com/microsoft/pxt/blob/master/pxtblocks/fields/field_userenum.ts#L23)を改修する必要がありそうです。

https://github.com/microsoft/pxt/blob/master/pxtblocks/fields/field_userenum.ts#L23

# ビルド環境の構築

ソースコードからビルドし、エディタ（**Microsoft MakeCode for micro:bit**）を起動するまでのビルド環境を構築します。

- Winodws10
- [Docker Desktop](https://www.docker.com/products/docker-desktop/)
- [Visual Studio Code](https://code.visualstudio.com/download)
- [Dev Containers extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers)

github.comも使えるようにします。

# ソースコード

## pxt

[Microsoft Programming Experience Toolkit (PXT)](https://github.com/microsoft/pxt)のソースコードは、githubで公開されています。

https://github.com/microsoft/pxt

今回、この `pxt` を改修しますので、まずは、forkします。
forkしたリポジトリで、改修作業用のブランチを作成し、それをデフォルトブランチにしました。

## pxt-microbit

Microsoft MakeCode for micro:bit のソースコードは、 `pxt-microbit` として、githubで公開されています。

https://github.com/microsoft/pxt-microbit

今回の不具合は、これを使用していて気付いたものですが、問題はここにはありません。ソースコードを参照するのみですので、forkする必要はありません。

## 作業用のプロジェクト

作業用のプロジェクトとして、次のテンプレートを使用します。

https://github.com/jp-rad/pxt-ubit-extension

このテンプレートを元に、新しいリポジトリを作成し、クローンします。

# 開発環境の構築

## 作業用プロジェクトの開始

1. クローンした作業用のプロジェクトをVSCodeで開く
1. `Reopen in Container`で Docker にリモート接続する

## ソースコードの取得と準備

次のフォルダ構成でソースコードを取得し、準備します。

```
[workspace]
    ┗ [makecode]
        ┣ [pxt]
        ┣ [pxt-common-packages]
        ┗ [pxt-microbit]
```

**参考：micro:bit target for PXT - Developer Setup**

https://github.com/microsoft/pxt-microbit#developer-setup


### 作業フォルダ - makecode

VSCodeで新しいターミナルを開き、作業フォルダを作成します。
```
mkdir makecode
```

### pxt

`pxt` リポジトリをクローンします
```
cd makecode
git clone https://github.com/jp-96/pxt
```

依存モジュールのインストールとビルドを行います。
```
cd pxt
npm install
npm run build
cd ../..
```

### pxt-common-packages

`pxt-common-packages` リポジトリをクローンし、依存関係モジュールをインストールします。
```
cd makecode
git clone https://github.com/microsoft/pxt-common-packages
cd pxt-common-packages
npm install
```

`pxt-common-packages` で、ローカルの `pxt` フォルダをリンクします。 `pxt-common-packages` は、 `pxt-core` に依存していますが、その `pxt-core` の代わりに、ローカル環境でビルドした `pxt` フォルダを参照するようにしています。
```
rm -rf node_modules/pxt-core
pxt link ../pxt
cd ../..
```

### pxt-microbit
`pxt-microbit` リポジトリをクローンします。
```
cd makecode
git clone https://github.com/microsoft/pxt-microbit
```

依存モジュールをインストールします。
```
cd pxt-microbit
npm install
```

`pxt-microbit` でも、ローカルの `pxt` フォルダとローカルの `pxt-common-packages` フォルダとをリンクします。
```
rm -rf node_modules/pxt-core
pxt link ../pxt
rm -rf node_modules/pxt-common-packages
pxt link ../pxt-common-packages
cd ../..
```

## 起動

`pxt-microbit` で、サーバーを起動します。
**※ サーバー停止：** `Ctrl+C`で停止
```
cd makecode
cd pxt-microbit
pxt serve
```

コンソールに表示されたURLへWebブラウザーでアクセスし、**Microsoft MakeCode for micro:bit**を開きます。

# PXTの改修

`console.log()` で、Webブラウザーのコンソールに実行順序や変数の値を出力してデバッグできます。

今回の場合、デバッグを進めると、[FieldUserEnumクラスのdoClassValidation_()関数](https://github.com/microsoft/pxt/blob/master/pxtblocks/fields/field_userenum.ts#L23)を改修する必要があることがわかりました。
ドロップダウンリストの選択肢には、表示上の値と選択肢の値との2つの値が保持されているのですが、この選択肢の値が、列挙型のメンバーの値とメンバーの名称を結合した `value+name` の形式で管理されているようです。
そのことが、 `createMenuGenerator()` 関数のコメントでも記してあります。

>// The format of the name is 10mem where "10" is the value and "mem" is the enum member

```javascript

    function createMenuGenerator(opts: pxtc.EnumInfo): () => string[][] {
        return function () {
            const res: string[][] = [];

            const that = this as FieldUserEnum;
            if (that.sourceBlock_ && that.sourceBlock_.workspace) {
                const options = that.sourceBlock_.workspace.getVariablesOfType(opts.name);
                options.forEach(model => {
                    // The format of the name is 10mem where "10" is the value and "mem" is the enum member
                    const withoutValue = model.name.replace(/^\d+/, "")
                    res.push([withoutValue, model.name]);
                });
            } else {
                // Can't create variables from within the flyout, so we just have to fake it
                opts.initialMembers.forEach((e) => res.push([e, e]) );
            }


            res.push([lf("Add a new {0}...", opts.memberName), "CREATE"]);

            return res;
        }
    }

```

その為、 `message1` や `add1` 、 `add2` というメンバーの名称だけでは、ドロップダウンリストの選択肢には存在しないと判断されてしまっているようです。

そこで、 `doClassValidation_()` 関数を次のように改修しました。

```javascript
        doClassValidation_(value: any) {
+           console.log("doClassValidation_1", value)   // DEBUG:LOG
+           // The format of the name is 10mem where "10" is the value and "mem" is the enum member
+           if (this.sourceBlock_ && this.sourceBlock_.workspace) {
+               const options = this.sourceBlock_.workspace.getVariablesOfType(this.opts.name);
+               options.some(model => {
+                   const [name, num] = parseName(model);
+                   if ((name === value) || (num === value)) {
+                       value = model.name
+                       return true
+                   }
+                   return false
+               });
+           }
+           console.log("doClassValidation_2", value)   // DEBUG:LOG
            // update cached option list when adding a new kind
            if (this.opts?.initialMembers && !this.opts.initialMembers.find(el => el == value)) this.getOptions();
            return super.doClassValidation_(value);
        }
```



## PXTのビルド

VSCodeで新しいターミナルを開き、 `pxt` をビルドします。

```
cd makecode
cd pxt
```

```
npm run build
```

## 再起動

`pxt-microbit` 内で、サーバーを再起動します。
**※ サーバー停止：** `Ctrl+C`で停止
```
cd makecode
cd pxt-microbit
pxt serve
```

## 動作確認

初回、配置した時もチェックされているし、JavaScriptで編集してからBlocksへ切り替えても、チェックされています。

**配置時にmessage1がチェックされている**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252891/95413521-489a-1fe0-c400-16e00c25b99c.png)

**JavaScriptでmessage1をadd2に編集**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252891/243e7358-1d34-9826-c8c7-10ea65890428.png)

**Blocksに切り替えるとadd2がチェックされている**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252891/ebdf8c53-099b-1448-324d-a0434f8fd731.png)

# プルリクエスト(Pull Request)

せっかくですので、改修内容を[microsoft/pxt](https://github.com/microsoft/pxt)の本家リポジトリにプルリクエスト(Pull Request)しました。

https://github.com/microsoft/pxt/pull/9654

開発チームからいくつか指摘（アドバイス）をいただきました。

1. 最近、**?**(safe navigation operator)を使うことが流行っています
1. someの副作用に依存した書き方のよりも、もう少し簡潔に書けます

**アドバイスされたソースコード**
```javascript
        doClassValidation_(value: any) {
+           // The format of the name is 10mem where "10" is the value and "mem" is the enum member
+           if (this.sourceBlock_?.workspace) {
+               const options = this.sourceBlock_.workspace.getVariablesOfType(this.opts.name);
+               const matchingOption = options.find(model => {
+                   const [name, ] = parseName(model);
+                   return name === value;
+               });
+               if (matchingOption) {
+                   value = matchingOption.name;
+               }
+           }
            // update cached option list when adding a new kind
            if (this.opts?.initialMembers && !this.opts.initialMembers.find(el => el == value)) this.getOptions();
            return super.doClassValidation_(value);
        }
```

:::note info
不具合を報告するリポジトリ
今回の不具合の原因は、 `pxt` にありましたが、不具合を報告する際は、 `pxt-arcade` や `pxt-microbit` に報告した方が好ましいようです。
:::

# まとめ

1. `radio send` ブロックのドロップダウンリストで不具合がありました
1. `pxt` のソースコードを取得し、ビルドしました
1. `pxt-common-packages`、`pxt-microbit`のソースコードを取得し、ビルドした`pxt`を参照するようにしました
1. `pxt` を改修し、ローカル環境で**Microsoft MakeCode for micro:bit**を起動し、動作確認しました
1. 本家リポジトリに改修内容をプルリクエストし、開発チームから指摘（アドバイス）をいただきました

**おまけ：気になる不具合**

拡張機能を開発する為に、 [MakeCode Blocks Playground](https://makecode.com/playground)という開発者向けエディタのサンプルを試していますが、その中で今回の不具合に気づきました([Sample:Creating enums](https://makecode.com/playground#language-create-enums))。

- 拡張機能の開発 - https://makecode.com/defining-blocks
- Playground - https://makecode.com/playground
- Sample:Creating enums - https://makecode.com/playground#language-create-enums

他にも、次の不具合が報告されていて、気になります。

https://github.com/microsoft/pxt/issues/9192

- Sample:Dropdowns - https://makecode.com/playground#field-editors-dropdowns