0
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?

[mise] 複数の依存関係があるタスクで Indicatif が表示されない問題の解決方法

Posted at

概要

Rust には CUI 上で可視性の良いプログレスバーを表示できる Indicatif というライブラリがある。mise で Indicatif を使用するプログラムを実行するとき、タスクの依存がない、または単一の依存の場合はプログレスバーが表示されるが、複数に依存する場合はプログレスバーが表示されないという挙動を見つけた。

結果的に、単一タスクか複数(並列)タスクかで mise が自動で挙動を変えているため、複数タスク時に TTY 端末が割り当てられないという動きが原因だった。-o interleave で内部的なパイプをしないようにすれば正しく表示されるようになる。

バグではないが、mise の挙動を知らないとハマりそうなので、検索で引っかかるように Qiita に記録しておく。Rust かどうかにかかわらず、カラー出力 (colored, termcolor, ansi_term など) を伴うプログレスバー (pbr, progress-bar など)、スピナー、死活表示、シンタックスハイライト、ログフォーマット、また端末制御 (crossterm や termion など) を行うカーソル位置制御、画面クリア機能など、TTY を検出して CUI を制御しているプログラムは同じような影響を受けるだろう。

挙動

以下のようなプログラムがある。

use indicatif::{ProgressBar, ProgressStyle};
use std::thread;
use std::time::Duration;

fn main() {
  let pb = ProgressBar::new(100);
  pb.set_style(
    ProgressStyle::default_bar()
      .template("Preparing: {spinner:.green} [{elapsed_precise}] [{bar:40.cyan/blue}] {pos}/{len} ({eta})")
      .unwrap()
      .progress_chars("#>-"),
  );
  if pb.is_hidden() {
    eprintln!("### progress bar is hidden");
  }

  for i in 0..=100 {
    pb.set_position(i);
    thread::sleep(Duration::from_millis(50));
  }
  pb.finish_with_message("finished");

  let is_tty = console::user_attended_stderr();
  eprintln!("TTY detection: {}", is_tty);
  eprintln!("Is stderr a TTY?: {}", atty::is(atty::Stream::Stderr));
}
[package]
name = "mise_indicatif"
version = "0.1.0"
edition = "2024"

[dependencies]
indicatif = "0.18.0"
console = "0.16"
atty = "0.2"

Indicatif は画面制御のために大量のエスケープシーケンスを出力する。出力先が TTY 端末であればそれで良いが、CI 環境のようにパイプやリダイレクトされている場合はゴミ情報となるためプログレスバーを表示しないようになっている。

以下では、通常の起動ではプロセスに TTY が割り当てられているためプログレスバーが表示される。しかし、パイプやリダイレクトのような TTY が検出できない実行では何も表示しない。

$ cargo run
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.15s
     Running `target/debug/mise_indicatif`
Preparing:   [00:00:05] [########################################] 100/100 (0s) ✅
TTY detection: true ✅
Is stderr a TTY?: true ✅

$ cargo run 2>&1 | more
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.17s
     Running `target/debug/mise_indicatif`
### progress bar is hidden ✅
TTY detection: false ✅
Is stderr a TTY?: false ✅

さて、mise で次のような タスクを作成したとする。

[tools]
rust = "1.89"

[tasks.setup]
run = "echo setup"

[tasks.format]
run = "cargo fmt"

[tasks.single1]
depends = ["setup"]
run = "cargo run"

[tasks.single2]
depends = ["format"]
run = "cargo run"

[tasks.multiple]
depends = ["setup", "format"]
run = "cargo run"

2 つの単一実行タスクはそれぞれプログレスバーが表示されるが、それらを組み合わせた複数タスクのケースでは TTY が検出できなくなってプログレスバーが表示されない。

$ mise run single1
[setup] $ echo setup
setup
[single1] $ cargo run
Preparing:   [00:00:05] [########################################] 100/100 (0s) ✅
TTY detection: true ✅
Is stderr a TTY?: true ✅
Finished in 5.33s

$ mise run single2
[format] $ cargo fmt
[single2] $ cargo run
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.17s
     Running `target/debug/mise_indicatif`
Preparing:   [00:00:05] [########################################] 100/100 (0s) ✅
TTY detection: true ✅
Is stderr a TTY?: true ✅
Finished in 5.46s

$ mise run multiple
[format] $ cargo fmt
[setup] $ echo setup
[setup] setup
[setup] Finished in 2.6ms
[format] Finished in 127.9ms
[multiple] $ cargo run
[multiple] ### progress bar is hidden ❌
[multiple] TTY detection: false ❌
[multiple] Is stderr a TTY?: false ❌
[multiple] Finished in 5.28s
Finished in 5.41s

解決策

mise の出力オプション -o または --outputinterleave を指定する。

$ mise run multiple -o interleave
[setup] $ echo setup
[format] $ cargo fmt
setup
[multiple] $ cargo run
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.18s
     Running `target/debug/mise_indicatif`
Preparing:   [00:00:05] [########################################] 100/100 (0s) ✅
TTY detection: true ✅
Is stderr is TTY?: true ✅
Finished in 5.46s

または mise.toml に以下のような出力設定を追加しておくか、環境変数 MISE_TASK_OUTPUT も利用できる。

[settings]  
task_output = "interleave"

問題の根本原因は mise の出力モード選択ロジックにある。単一タスクの場合は stdout/stderr へ直接出力する interleave モードで動作するので起動プロセスから TTY が検出できる。複数タスクの場合は並行する出力がどのタスクのものかを識別できるように prefix モードで動作する。この prefix モードは起動プロセスの出力をパイプしてバッファリングし、行単位で [setup] などのプレフィクスを付けて stdout/stderr へ出力する。このため prefix モードで動作するとプログラムからは TTY が検出できず、結果的に Indicatif は出力がパイプやリダイレクトされていると見なしてプログレスバーを非表示にする。

0
0
0

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
0
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?