みつきんのメモ

組み込みエンジニアです。Interface誌で「My オリジナルLinuxの作り方」連載中

PlatformIO mbedのビルド時間を短縮するスクリプトを作った

はじめに

2020/3/6 追記

PlatformIOでframeworkをmbedにすると必要のない機能のソースコードコンパイルするため、 ビルド時間がかなり長くなってしまう。

| Mbed for STM32 compiles toooooo long | mbed + PlatformIO = too long compilation

これは度々話題に挙げられていて.mbedignoreで不要なソースをフィルタできると答えが出るのだが、

Does .mbedignore still work?

.mbedignoreは適切な場所に配置しないと効力を発揮しないことと、 適切な配置場所の具体的な例が乏しいため、実際のところ使いづらい。

いろいろ試した結果、.mbedignoreでビルド時間の短縮に成功した。しかし、毎回手でこれを行うが面倒なので PlatformIOのextra_scriptsの機能を使用して、プロジェクト毎に簡単に使用できるようにしてみた。

.mbedignoreファイル

まず、.mbedignoreファイルについて説明する。

mbed osのソースツリーに配置することで、使用しない機能のソースファイルをビルドから除外することができる。

ただしソースツリーのルート直下に配置された.mbedignoreは無視されてしまう。

.mbedignoreの内容を正しく適用するにはその一段下のディレクトリにそれぞれ配置する必要がある。

.
├── TESTS
├── TEST_APPS
├── UNITTESTS
├── cmsis
├── components
├── docs
├── drivers
├── events
├── features
├── hal
├── platform
├── platformio
├── rtos
├── targets
└── tools

例えば、features/cellular/*をビルド対象から外したい場合は、featuresディレクトリに次の内容で.mbedignoreを作成する必要がある。

cellular/*

extra_scripts

PlatformIOではmbedフレームワークのソースツリーは${HOME}/.platformio/packages/framework-mbedに格納される。

framework-mbedディレクトリは、そのユーザーが使用するすべてのPlatformIOのプロジェクトから参照されるので、プロジェクト毎に毎回調整する必要が出てくる。

少なくとも使用するボードによってビルド対象から外したい機能やドライバは変わってくるはずなので、これを毎回設定するのは面倒くさい。

そこで、pio-mbedignoreというリポジトリに、ヘルパスクリプトを作成した。

使い方

ヘルパスクリプトの使い方の流れは次のようになる。

  1. リポジトリから適当な場所にダウンロードする
  2. mbedignore.pyをPlatformIOの自分のプロジェクトのルートに配置する
  3. mbedignoreディレクトリを作成し、その下にfeaturesdriversなどの名前でファイルを作成する
  4. 作成したファイルに.mbedignoreの記述ルールに従って、無視したいファイルのパスを指定する
  5. platformio.iniを修正し、extra_scriptに設定する

具体的には次のようになる。

$ git clone https://github.com/mickey-happygolucky/pio-mbedignore.git
$ cp pio-mbedignore/mbedignore.py /path/to/pio_project
$ cd /path/to/pio_project
$ mkdir mbedignore

先程の例のようにcellularを除外するにはmbedignore/featuresを次の内容で作成する。

cellular/*

ビルド毎にmbedignore.pyが実行されるようにするために、platformio.iniに次の内容を追加する。

2020/3/6 追記1

extra_scripts =
  pre:mbedignore.py
  post:mbedignore.py

extra_scripts =
  post:mbedignore.py

これで、pio run毎に、必要に応じて、framework-mbed以下に.mbedignoreが配置され、指定したファイルをビルド対象から外すことができるようになる。

extra_scriptsのpost:指定にも指定しているのはゴミが残らないようにするためだが、これについては後述する。

pre:post:の仕様を勘違いしていた。platformio.iniでのpre/post指定は、extra_scriptsの実行タイミングをPlatformIOのビルドシステムのメインスクリプトを実行する前にするか、後ろにするかという指定。 env.AddPreAction()/env.AddPostAction()を使用するため、ターゲットシステムの情報が確定されてから実行したいのでpost:のみの設定が正解。

2020/3/6 追記1 終了

仕組み

PlatformIOではplatformio.iniextra_scriptspythonスクリプトのファイルを指定すると、platformioコマンド実行時に既存の処理をカスタムしたり、任意の処理を追加したりすることができる。

今回作成したスクリプトはそれを利用して、pio run毎に必要に応じてframework-mbedディレクトリに.mbedignoreを配置するようにしている。 実際には、実ファイルを作成するわけではなく、シンボリックリンクを作成している。

mbedignore.py

extra_scriptsではImport(env)を使用することで、platformio実行時の環境変数にアクセスしたり、 任意のタイミングで実行されるアクションを登録できるようになっている。

Import("env")
import os
import glob

srcs = glob.glob(os.getcwd() + '/mbedignore/*')
print(srcs)
dst_basedir = env.Split(env['PROJECT_PACKAGES_DIR'])[0] + '/framework-mbed'

def clean_mbedignore(source, target, env):
    print('Clean .mbedignore')

    for src in srcs:
        dst_dir = dst_basedir + '/' + os.path.basename(src)
        dst = dst_dir +'/.mbedignore'

        if os.path.exists(dst) == True:
            os.remove(dst)
            print('symlink deleted : ' + dst)


def mbedignore():
    for src in srcs:
        dst_dir = dst_basedir + '/' + os.path.basename(src)
        dst = dst_dir +'/.mbedignore'

        if os.path.exists(dst) == True:
            os.remove(dst)
            print('symlink deleted : ' + dst)

        if os.path.exists(dst_dir) == True:
            os.symlink(src, dst)
            print('symlink created : src = ' + src + '->' + dst)


mbedignore()
env.AddPostAction("checkprogsize", clean_mbedignore)

PlatformIOが知っているディレクトリやファイルなどは環境変数のようにアクセスできるので、 必要な情報はそこから取り出すようにしている。

2020/3/6 追記2

env.AddPreAction("buildprog", clean_mbedignore)の行で、このスクリプトによって作成されたシンボリックリンクを削除する処理を、 bulidprogターゲットの後に実行されるように登録している。

なぜかplatformio.inipre:指定した場合はここで設定した処理が実行されないので、このスクリプトpost:でも登録している。

ただし、このスクリプトの実行時にすでに.mbedignoreが存在している場合は、削除してから作り直すので post:の指定を忘れても実行に影響は無い。

修正前はenv.AddPreAction("buildprog", clean_mbedignore)としていたが、ビルド済みだった場合実行されないので、 env.AddPostAction("checkprogsize", clean_mbedignore)とした。修正後はcheckprogsizeターゲットの後に、このスクリプトによって作成されたシンボリックリンクを削除している。

2020/3/6 追記2 終了

実行時間比較

リポジトリのテスト用のmbedignoreファイルを使用して、 bluepill環境でpio runの実行時間を比較した。

どちらも実行前にcleanしてある。

スクリプト未使用時

real  2m23.069s
user    8m41.679s
sys 1m38.485s

スクリプト使用時

real  0m23.076s
user    1m56.642s
sys 0m16.316s

体感でも違いがわかるくらいには差が出ている。

まとめ

PlatforIOでmbedを使用するとビルド時間が長い。

.mbedignoreを使えば不要なコンパイルを避けビルド時間を短縮できるが、 具体的な使い方がの情報が少ないことと、意外と不親切。

今回はmbedignore.pyを作成し、簡単に.mbedignoreの恩恵を受けられるようにした。