5
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Elixirでバージョン文字列を解析して分岐処理

Last updated at Posted at 2025-11-28

はじめに

ある日、AtomVM で動かすファームウェアを友人と開発している中で、「同じ基板だけど、LCD と SD の CS ピンだけ配線が違う」という 2 種類のバージョンをサポートする必要が出てきました。

  • ファームウェア自体は共通
  • ただし、あるバージョンまでは「旧配線」、それ以降は「新配線」

そこで、

  • PIYOPIYO_BOARD という環境変数に基板のバージョン番号を渡す
  • config/config.exs の中でバージョン文字列を解析する
  • バージョンに応じて CS ピンの組み合わせを切り替える

という方針をとりました。

その際に使ったのが、Elixir 標準の Version モジュール です。

piyopiyo-board-2025-12.png

やりたいこと

今回の要件をもう少し整理すると、やりたいことは次の 3 点です。

  • 環境変数 PIYOPIYO_BOARD にバージョン文字列(例: v1.5, 1.6)を渡す
  • config/config.exs でその文字列を 正規化して Version に変換する
  • 「1.5 以下なら旧配線」「1.6 以上なら新配線」のようにバージョンで分岐する

ここで活躍するのが Version.parse/1, Version.parse!/1, Version.compare/2 です。

Version モジュール

Elixir にはバージョン文字列を扱うための Version モジュールが用意されています。

  • Version.parse!/1 でバージョン文字列を構造体に変換
  • Version.compare/2 で 2 つのバージョンを比較
  • Version.match?/2 で「バージョン要件」を満たしているかどうかを判定 (nerves_motd の例)

Version モジュールは「セマンティックバージョニング」に沿った形式を前提にしており、バージョン文字列の形式にけっこう厳格です。

iex> Version.parse("1.6.0")
{:ok, %Version{major: 1, minor: 6, patch: 0}}

iex> Version.parse("1.6")
:error

"1.6" のような文字列は、そのままだと弾かれてしまいます。

一方で、今回想定している設定値はこんな感じです。

  • "v1.5"
  • "v1.6"
  • "1.5"
  • "1.6.1"

今回のように「環境変数に自由な文字列が入ってくる」ケースでは、そのまま Version に渡すのではなく、いったん前処理して正規化する必要があります。

そこで、以下のような前処理を入れてから、Version.parse!/1 に渡すことにしました。

  1. 先頭の "v" を取り除く
  2. "." で分割する
  3. 足りない部分を "0" で補って "major.minor.patch" にそろえる

環境変数からバージョンを正規化する

まずは「バージョン文字列を正規化して Version 構造体に変換する」部分だけを抜き出した例です。

board = System.get_env("PIYOPIYO_BOARD") || "v1.6"

raise_invalid_board = fn parts ->
  raise """
  Unsupported PIYOPIYO_BOARD=#{inspect(board)} (parsed parts: #{inspect(parts)}).

  Expected a version-like value such as:

    * "v1.5"
    * "v1.6"
    * "1.5"
    * "1.6.1"
  """
end

version_segments =
  board
  |> String.trim_leading("v")
  |> String.split(".")
  |> case do
    [maj] -> [maj, "0", "0"]
    [maj, min] -> [maj, min, "0"]
    [maj, min, patch] -> [maj, min, patch]
    parts -> raise_invalid_board.(parts)
  end

unless Enum.all?(version_segments, &String.match?(&1, ~r/^\d+$/)) do
  raise_invalid_board.(version_segments)
end

version =
  version_segments
  |> Enum.join(".")
  |> Version.parse!()

ここまでで、

  • PIYOPIYO_BOARD"v1.5" でも "1.5" でも "1.5.0" でも
  • 最終的には Version.parse!("1.5.0") と同じ %Version{} に正規化

できるようになります。

バージョンで配線を切り替える

あとは Version.compare/2 を使って、基板のバージョンに応じて CS ピンの組み合わせを切り替えます。

今回のルールは次の通りです。

  • v1.5 以下 → 旧配線(LCD CS: 43, SD CS: 4)
  • v1.6 以上 → 新配線(LCD CS: 4, SD CS: 43)

実際の config/config.exs はこんな感じになりました。

import Config

board = System.get_env("PIYOPIYO_BOARD") || "v1.6"

raise_invalid_board = fn parts ->
  raise """
  Unsupported PIYOPIYO_BOARD=#{inspect(board)} (parsed parts: #{inspect(parts)}).

  Expected a version-like value such as:

    * "v1.5"
    * "v1.6"
    * "1.5"
    * "1.6.1"
  """
end

version_segments =
  board
  |> String.trim_leading("v")
  |> String.split(".")
  |> case do
    [maj] -> [maj, "0", "0"]
    [maj, min] -> [maj, min, "0"]
    [maj, min, patch] -> [maj, min, patch]
    parts -> raise_invalid_board.(parts)
  end

unless Enum.all?(version_segments, &String.match?(&1, ~r/^\d+$/)) do
  raise_invalid_board.(version_segments)
end

version =
  version_segments
  |> Enum.join(".")
  |> Version.parse!()

{lcd_cs_pin, sd_cs_pin} =
  case Version.compare(version, Version.parse!("1.6.0")) do
    # v1.5 or lower
    :lt -> {43, 4}
    # v1.6 or higher
    _ -> {4, 43}
  end

spi_config = [
  bus_config: [sclk: 7, miso: 8, mosi: 9],
  device_config: [
    spi_dev_lcd: [
      cs: lcd_cs_pin,
      mode: 0,
      clock_speed_hz: 20_000_000,
      command_len_bits: 0,
      address_len_bits: 0
    ],
    spi_dev_touch: [
      cs: 44,
      mode: 0,
      clock_speed_hz: 1_000_000,
      command_len_bits: 0,
      address_len_bits: 0
    ]
  ]
]

config :sample_app,
  board: board,
  spi_config: spi_config,
  sd_cs_pin: sd_cs_pin

これで、ビルド時に次のように環境変数を変えるだけで配線を切り替えられます。

# 旧配線 (v1.5 相当)
export PIYOPIYO_BOARD=v1.5

# 新配線 (v1.6 以降)
export PIYOPIYO_BOARD=v1.6

おわりに

Elixir の Version モジュールは、ライブラリのバージョン管理だけでなく、今回のように「それっぽいバージョン文字列を受け取って分岐する」小さな用途でも便利に使えます。

  • 文字列のまま大小比較するよりもロジックが明快になる
  • 正規化の過程で、不正な値は早めに検出できる
  • 「v1.5 以下」「v1.6 以上」といった表現がコード上でもそのまま表現できる

環境変数で設定を切り替える場面は多いので、ちょっとした分岐でも Version を思い出すと、後から読みやすい設定になってくれそうだと感じました。

5
0
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?