ソフトウェアエンジニアの雑記

日々思ったことをまとめます

AirdropのOSS版「LocalSend」でMac・Linux・Windowsも簡単ファイル共有をする

個人の開発環境が、MacBookPro M4 ProとデスクトップLinux(Linux Mint)を使用しており、ファイルの共有でいいものがないかなーと探したら、LocalSendというすごい便利なものがあったので、ご紹介です。

LocalSend: Share files to nearby devices

LocalSendとは

LocalSendは、完全無料のオープンソースクロスプラットフォームファイル共有アプリです。AppleAirdropのような体験を、すべてのOSで実現します。

主な特徴:

LinuxMacでのファイル共有

これまで異なるOS間でのファイル共有は面倒でした。

従来の方法の問題点:

  • メール添付(容量制限)
  • クラウドサービス経由(アップロード→ダウンロード)
  • USBメモリ(物理的な移動が必要)

LocalSendなら:

  • アプリを開いて送信先を選ぶだけ
  • 最大転送速度40MB/s以上
  • ファイルサイズ制限なし(999ファイルまで一度に送信可能)

Airdropとの違い

機能 Airdrop LocalSend
対応OS Apple製品のみ 全OS対応
料金 無料(Apple製品購入が前提) 完全無料
オープンソース
インターネット接続 不要 不要
設定の簡単さ 自動検出 アプリを両端で起動
セキュリティ 高い 同等(HTTPS暗号化)

LocalSendの魅力: - クロスプラットフォーム対応 - オープンソースで透明性が高い - 永続的に無料 - シンプルで直感的なUI

こんな人におすすめ: - MacWindowsLinuxの複数OS環境で作業する人 - プライバシーを重視するユーザー - 無料でシンプルなファイル共有ツールを探している人

まとめ

LocalSendは「すべてのデバイスで使えるAirdrop」として、異なるOS間のファイル共有がかなり便利なものになります。両デバイスでアプリを開いておくだけで、同じWi-Fiネットワーク内で瞬時にファイル共有できます。Android, iOSなどでも使えるようなのでぜひ使ってみてください。

開発者目線だと、UIはDart、通信の暗号化まわりはRustで作られているところはポイント高いなーと思いました。通信は、REST API + HTTPSで行っているようで、実装部分も時間があったら読んでみようかと思います。

公式サイト

https://localsend.org

SpringBoot x ShedLockで簡単に同時実行制御を行う

SpringBootでバッチ処理を書くときに、SpringBatchが選択肢に上がると思いますが、設定だったりクラスを継承したりと面倒です。確かに中断後から処理を開始してくれたりと便利な部分もあるのですが、SpringBatchの仕様理解とかが必要になってきます。もっとカジュアルに同時実行制御を行いたい場合に、ShedLock が選択肢に入ってくると思います。今回は、簡単な紹介になります。

コードはここにおいておきます。GitHub - ko-sasaki/springboot-shedlock-example

build.gradle

依存関係は下記のようになります。(mavenの方は読み替えてください)

plugins {  
    id 'java'  
    id 'org.springframework.boot' version '3.4.8'  
    id 'io.spring.dependency-management' version '1.1.7'  
}  
  
group = 'net.ksasaki.sample'  
version = '0.0.1-SNAPSHOT'  
  
java {  
    toolchain {  
       languageVersion = JavaLanguageVersion.of(21)  
    }  
}  
  
configurations {  
    compileOnly {  
       extendsFrom annotationProcessor  
    }  
}  
  
repositories {  
    mavenCentral()  
}  
  
dependencies {  
    implementation 'org.springframework.boot:spring-boot-starter-web'  
    compileOnly 'org.projectlombok:lombok'  
    developmentOnly 'org.springframework.boot:spring-boot-docker-compose' // (1)
    developmentOnly 'org.springframework.boot:spring-boot-devtools'  
    runtimeOnly 'org.postgresql:postgresql'  
    annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'  
    annotationProcessor 'org.projectlombok:lombok'  
    testImplementation 'org.springframework.boot:spring-boot-starter-test'  
    testRuntimeOnly 'org.junit.platform:junit-platform-launcher'  

    
    implementation 'net.javacrumbs.shedlock:shedlock-spring:6.9.2'  // 追加
    implementation 'net.javacrumbs.shedlock:shedlock-provider-jdbc-template:6.9.2'    // 追加


}  
  
tasks.named('test') {  
    useJUnitPlatform()  
}

追加が必要なのは、この2行になります。

環境まわり

今回DBはPostgreSQLを使用します。SpringBootは、開発モードでもアプリケーション起動時にdocker composeファイルを指定すると立ち上げてくれる便利な機能があります。今回はそれを使用します。( 設定ファイルの(1)の依存関係の追加が必要になります。)

compose.ymlは下記のように設定します。

name: shed-lock  
services:  
  db:  
      image: "postgres:latest"  
      container_name: "shed-lock-db"  
      environment:  
          POSTGRES_USER: "shedlock"  
          POSTGRES_PASSWORD: "shedlock"  
          POSTGRES_DB: "shedlock"  
      ports:  
      - "5432:5432"  
      volumes:  
        - database:/var/lib/postgresql/data  
        - ./postgres/init:/docker-entrypoint-initdb.d // init.sqlは後述
      networks:  
        - shedlock-network  
  
volumes:  
  database:  
networks:  
  shedlock-network:

基本的なdocker composeの設定になります。

application.ymlは下記のように設定します。

spring:
  application:
    name: schedule
  datasource:
    driver-class-name: org.postgresql.Driver
    url: jdbc:postgresql://localhost:5432/shedlock
    username: shedlock
    password: shedlock

  docker:
    compose:
      file: compose.yml
  task:
    scheduling:
      pool:
        size: 10    // scheduleはデフォルトだと1スレッドしか立ち上がらない
  main:
    keep-alive: true  // virtual threadのみが動作していてもJVMが終了しない設定
  threads:
    virtual:
      enabled: true

注意点は、スレッド数とkeep-aliveになります。SpringがSchedulingとして使用するスレッドはデフォルトだと1なので、スケジュールを複数設置すると同時実行は1つのみですので、実際に使う場合は設定を変更しておくと良いと思います。spring.main.keep-alive は、バーチャルスレッドを有効にしている場合に、バーチャルスレッドは動作しているが、メインスレッドの処理が止まるとJVMが終了してしまうため、それを回避するための設定です。有効にしておくと不慮のJVM終了にはならないと思います。

テーブル定義

init.sqlは下記のようにします。プロダクション環境ではFlyway等のマイグレーションツールで設定してください。

CREATE TABLE shedlock  
(  
    name       VARCHAR(64)  NOT NULL,  
    lock_until TIMESTAMP    NOT NULL,  
    locked_at  TIMESTAMP    NOT NULL,  
    locked_by  VARCHAR(255) NOT NULL,  
    PRIMARY KEY (name)  
);

アプリケーションコード

package net.ksasaki.sample.schedule;  
  
import lombok.extern.slf4j.Slf4j;  
import net.javacrumbs.shedlock.core.LockProvider;  
import net.javacrumbs.shedlock.provider.jdbctemplate.JdbcTemplateLockProvider;  
import net.javacrumbs.shedlock.spring.annotation.EnableSchedulerLock;  
import net.javacrumbs.shedlock.spring.annotation.SchedulerLock;  
import org.springframework.boot.SpringApplication;  
import org.springframework.boot.autoconfigure.SpringBootApplication;  
import org.springframework.context.annotation.Bean;  
import org.springframework.context.annotation.Configuration;  
import org.springframework.jdbc.core.JdbcTemplate;  
import org.springframework.scheduling.annotation.EnableScheduling;  
import org.springframework.scheduling.annotation.Scheduled;  
import org.springframework.stereotype.Component;  
  
import javax.sql.DataSource;  
import java.time.LocalDateTime;  
  
@SpringBootApplication  
public class ScheduleApplication {  
  
    public static void main(String[] args) {  
       SpringApplication.run(ScheduleApplication.class, args);  
    }  
  
}  
  
  
@EnableScheduling  
@EnableSchedulerLock(defaultLockAtMostFor = "10m")  
@Configuration
class ScheduleConfig {   // ①
    @Bean  
    public LockProvider lockProvider(DataSource dataSource) {  
       return new JdbcTemplateLockProvider(  
             JdbcTemplateLockProvider.Configuration.builder()  
                   .withJdbcTemplate(new JdbcTemplate(dataSource))  
                   .withLockedByValue( "schedule")  
                   .usingDbTime()  
                   .build()  
       );  
    }  
}  
  
@Component  
@Slf4j  
class ScheduleComponent {  // ②
  
    @Scheduled(fixedRate = 1000, initialDelay = 1000)  // ③
    @SchedulerLock(name = "scheduleComponentA", lockAtLeastFor = "3s")  // ④
    public void runA() throws InterruptedException {  
       log.info("Scheduled task is running... A {}", LocalDateTime.now());  
    }  
  
    @Scheduled(fixedRate = 1000, initialDelay = 1000)  
    @SchedulerLock(name = "scheduleComponentB" , lockAtLeastFor = "4s")  
    public void runB() {  
       log.info("Scheduled task is running... B {}", LocalDateTime.now());  
    }  
  
    @Scheduled(fixedRate = 500, initialDelay = 1000)  
    @SchedulerLock(name = "scheduleComponentC" , lockAtLeastFor = "5s")  
    public void runC() {  
       log.info("Scheduled task is running... C {}", LocalDateTime.now());  
    }  
  
    @Scheduled(fixedRate = 1000, initialDelay = 1000)  
    @SchedulerLock(name = "scheduleComponentD", lockAtLeastFor = "2s")  
    public void run0() throws InterruptedException {  
       log.info("Scheduled task is running... D {}", LocalDateTime.now());  
    }  
}

1ファイルにしたので、長いですけど順を追って解説します。

ShedLockの設定

// ①の部分になりますが、LockProviderの設定が必要になります。基本的にはSpringのDataSourceオブジェクトを渡すくらいで処理が完了します。

@EnableScheduling // ①
@EnableSchedulerLock(defaultLockAtMostFor = "10m") // ②
@Configuration
class ScheduleConfig {
    @Bean  
    public LockProvider lockProvider(DataSource dataSource) {  
       return new JdbcTemplateLockProvider(  
             JdbcTemplateLockProvider.Configuration.builder()  
                   .withJdbcTemplate(new JdbcTemplate(dataSource))  
                   .withLockedByValue( "schedule")  // locked_byカラムのデータ
                   .usingDbTime()  
                   .build()  
       );  
    }  
}  

① スケジュール機能を有効にする

@EnableSchedulingでスケジュール機能を有効にします。

② ShedLock機能を有効にする

@EnableSchedulerLock(defaultLockAtMostFor = "10m")ShedLockを有効にします。defaultLockAtMostForは、デフォルトの最長ロック時間を指定します。

スケジュール設定部分

SpringBootを起動する部分は省いてスケジュール設定をする部分のみ説明します。

@Component
@Slf4j  
class ScheduleComponent {  // ③
  
    @Scheduled(fixedRate = 1000, initialDelay = 1000)  // ④
    @SchedulerLock(name = "scheduleComponentA", lockAtLeastFor = "3s")  // ⑤
    public void runA() throws InterruptedException {  
       log.info("Scheduled task is running... A {}", LocalDateTime.now());  
    }  
  
    @Scheduled(fixedRate = 1000, initialDelay = 1000)  
    @SchedulerLock(name = "scheduleComponentB" , lockAtLeastFor = "4s")  
    public void runB() {  
       log.info("Scheduled task is running... B {}", LocalDateTime.now());  
    }  
  
    @Scheduled(fixedRate = 500, initialDelay = 1000)  
    @SchedulerLock(name = "scheduleComponentC" , lockAtLeastFor = "5s")  
    public void runC() {  
       log.info("Scheduled task is running... C {}", LocalDateTime.now());  
    }  
  
    @Scheduled(fixedRate = 1000, initialDelay = 1000)  
    @SchedulerLock(name = "scheduleComponentD", lockAtLeastFor = "2s")  
    public void run0() throws InterruptedException {  
       log.info("Scheduled task is running... D {}", LocalDateTime.now());  
    }  
}

③ スケジュールクラスのDI設定

スケジュール設定をするには、DIに登録する必要がありますので、classに@Component、``を付与します。

④ スケジュールの設定

Springのスケジューリングの設定はアノテーションで設定を行う。fixedRate完了後、次の実行時までの時間をms単位で設定します。initialDelayは、SpringBoot起動後、初回の開始時間までの時間をms単位で設定します。

@Scheduled(fixedRate = 1000, initialDelay = 1000)  // ③

⑤ ShedLockの設定

ShedLock の設定を行います。

@SchedulerLock(name = "scheduleComponentA", lockAtLeastFor = "3s")  // ④
name: ロックする名前
lockAtLeastFor: 最小時間
lockAtMostFor: 最大時間

以上となります。nameはこのあと紹介するDBのデータでPKとなっています。lockAtLeastForは、タスクが短時間で完了してもロックし続ける時間になります。lockAtMostForは、ロックしつづける最大時間になります。

起動後

ログ

ログは下記のようになります。わかりにくいですが、ロックされている時間は動作せずにスキップされています。複数台で実行すると、片方のインスタンスのみで実行されているのがわかります。

テーブルの中身

テーブルの中身は下記のようになっています。ロックされて、それぞれ同時実行できないようになっています。@Scheduledの実行タイミングで、チェックにしてロックされていたらスキップするような挙動になっています。

まとめ

バッチ処理は複数台で同時実行を制御するのが一番面倒かなとおもいます。ShedLockはその部分のみを担ってくれます。SpringBatchにあるような分散処理系で便利な、クラッシュ時のリスタート、チェックポイント、スキップやチャンク指向処理など機能はありません。バッチ処理をSpringBatchでオンラインアプリケーションコードとバッチ処理のコードの乖離が進む傾向にあると思います。なるべくビジネスロジックは、オンラインアプリケーションコードに寄せた形でのバッチ処理を書くときに、ShedLockを使う選択肢はありだと思います。ぜひ試してみてください。

(参加レポート) 設計ナイト2025【オフライン】

吉祥寺開催の設計ナイトに初めて参加してきました。結構高度で刺激的な内容でした。間違っているところもあると思いますが、

kichijojipm.connpass.com

誰のための設計?

https://www.docswell.com/s/tyonekubo/58MQP2-2025-07-25-sekkeinight#p1

人間中心設計の話から入り、デザインの原理、そしてプログラム設計の話にはいっていきます。手続き型コード(ifとかforとか)を多用するコードは、高い認知負荷バグの温床低い可読性密結合による拡張性の欠如に繋がるとしています。印象に残ったのは、解決方法にはDSLがよいみたいな話があり、AI時代はDSLの構築コストが下がっているから導入してもいいんじゃないか?というような提起でした。個人的にはDSLの構築コストが下がったとしても読み解くコストは増える、または読み解くのにAIなしだとキツイみたいな感じになるなら、無理にDSLにせずに型設計をちゃんとして解決していった方が難易度は上がらずに継続的な感じになるんじゃないかと思いました。

なぜAI時代に 「イベント」を中心に考えるのか?

https://speakerdeck.com/ytake/why-focus-on-events-in-the-age-of-ai

データが大事、時間軸のデータが大事という話でAI時代はイベントソーシングのようなデータがより重要になるという話でした。これには同意で、AI時代に限らずデータはとても大切でライフサイクルも長いです。ここ十数年テーブル設計、データ設計に注力してある程度の解は自分の中で確立しています。AI時代だから・・・っていうのは違うと思いますけど、データが大事というのはとても重要です。

ecspressoの設計思想に至る道 / sekkeinight2025

https://speakerdeck.com/fujiwara3/sekkeinight2025

ecspressoの設計思想に関する話でした。結論としては作成開始当初は設計思想というものはなかったけど、気付いたらそういう設計思想だったみたいな話です。これはFUJIWARAさんの頭の中にある経験や設計思想がプロダクトにモロに表れてて、私もそれに強く共感できたので楽しめました。簡単に言うと設計するにあたり境界が大事ということでした。如何に依存を断ち切れるかは、設計思想では大事なことだとおもいます。

可変性を制する設計: 構造と振る舞いから考える概念モデリングとその実装

自分には少しむずかしくて、うまくまとめられませんでした。すみません。

ADR運用におけるデータ利活用の考え方

ADRをデータ -> 情報 -> 知識 -> 知恵にしていこうという話でした。データだけ集めて情報にして、知識さらには知恵にしていくのが大事という話です。現時点ではアーキテクチャの意思決定時にドキュメントも含め残していますが、これは実践してみないとですので、今後やっていこうとおもいます。

AIエージェント開発を支える設計

AIエージェントを作っていくにあたり、

どうかと思う設計選手権LT

https://docs.google.com/presentation/d/138lZji5oJ-W7zM-I8N3gYB6rzucwFONw7IXbmTSCBkA/edit

ずっと大きく頷いていた話でした。かなり大雑把にいうと、オーバーエンジニアリングは良くないという話です。SOLID過剰適用もベストプラクティス偏重、モダン至上主義、関数型プログラミングの型の使いすぎ、エフェクト過剰みたいなものは、最終的に認知負荷が増大し、崩壊につながるかもみたいなものは多くみてきましたので、オーバーエンジニアリングなのかどうかを常々チェックできるようにこれからも精進していきたいと思います。

書籍紹介

ソフトウェア設計のトレードオフと誤り ―プログラミングの際により良い選択をするには

ソフトウェア設計のトレードオフと誤り ―プログラミングの際により良い選択をするには

この書籍は、設計によるトレードオフと誤りをまとめてくれている本です。なんでもそうですけど、銀の弾丸はないのでどんなに良い技術でも絶対に、やりすぎ、偏重、オーバーエンジニアリングをすると運用コストがあがりや変更容易性が下がるといった影響がでてきます。技術でも設計でも必ずトレードオフがあることを教えてくれる良い書籍だと思います。

まとめ

はじめて設計ナイトに参加させていただきたいが、登壇している皆様の試行錯誤の話が聞けてよかったです。プロダクトやメンバーの状況により採用する設計は異なると思いますので、自分も精進していければとおもいます。また、次回参加させていただきたいとおもいます。

設計ナイトの宣伝(おまけ)

近々のイベントの紹介があったのでわかる範囲ないで貼っておきます。

大吉祥寺.pm https://kichijojipm.connpass.com/event/361631/

キチピー リジェクトコン【非公式】 https://connpass.com/event/363351/

第十二回技術書同人誌博覧会 https://gishohaku.connpass.com/event/352601/

ShizuokaTECH#1 https://shizuoka-tech.connpass.com/event/350174/

OCIJP https://fullenergy-oci.connpass.com/event/362399/

レビュー「小さなチーム、大きな仕事 働き方の新スタンダード」

概要

著者は、アメリカのソフトウェア会社「ベースキャンプ (旧 37signals)」のエンジニアで、この会社はRubyOnRailsを開発しているので有名だと思います。この本が出版される頃でも社員は16人と小規模で、それを可能にするために 「常識」や「王道」の考え方を根本から見直し、事業から開発の仕方、働き方まで別の角度で異議を唱えている意欲的な書籍です。大きな組織に辟易している方には読んでいただきたい一冊です。

Amazon.co.jp: 小さなチーム、大きな仕事 働き方の新しいスタンダード (ハヤカワ文庫NF) eBook : ジェイソン フリード, デイヴィッド ハイネマイヤー ハンソン, 黒沢 健二, 松永 肇一, 美谷 広海, 祐佳 ヤング: Kindleストア

感想

章ごとに数行の感想を書いていきます。

現実の世界なんて無視しよう

「現実」の世界は、悲観と絶望と言い訳に満ちているから、引きずり込まれないように無視しようという話。「現実」にはステレオタイプな考え方がはびこっていて、規模や失敗確率の誤解、計画の害悪など私達が信じまくっている内容を1章から覆していく。

先に進む

自分たち(ベースキャンプ社)で必要な「自分が使いたいものを作る」や「時間がないは言い訳」、「信念を持つ」、「身軽でいること」など前進するために、後回しにしないこと、不要なものは作らないことなどが語られている。前進・行動することの大切さが語られている。この中では、「身軽でいること」というのは、大変共感していて、通常業務でもいつのまにか色々な制約や契約に縛られて、遅くなっていることが多いがビジネスはスピードが命なので、この部分はかなりの神経を使って注意を払うことがいいと思います。

進展

「制約」から知恵が生まれ、少ないことをうまくやり、必須機能から取り組み、決断して前進するという話です。また、世の中に目を向けると変化しているものと変化していないものがあり、それをどうキャッチしていくかなどが紹介されています。前章の「先に進む」に近いのですが、より具体的な内容になっています。

生産性

書類は幻想で、やることに対して意味をもたせ、集中できる環境づくり・時間づくりが大事で、会議はなるべく少なく、完璧を目指さないということが大事だということが書かれています。完璧を目指さず、小さい目標を設定し、どんどん仕事をするというのは本当に大事で、日本人はこの真逆をよくやっているとおもいます。立ち上げフェーズだとスピードがやはり大事なので、どんどん細かくリリースすることが大切です。

競合相手

すでに競合相手がいる場合は、表層を真似るだけではダメだし、競合相手より上をいかないと勝てないし、しかし競合相手が何をしているかなんて気にする必要がないと書かれている。言ってることは矛盾しているような気もしますけど、気にしすぎると判断を誤るから、自分のプロダクトに集中することを促している。

進化

「基本的にノーと言おう」、「熱意を優先順位と混同するな」、「舞台裏を見せる」などについて書かれているが、前半は進化するために、ついついやってしまいがちな『良い人』のようなものは、プロダクトやサービスを全く成長はさせないと書かれていrました。そして、後半の「舞台裏を見せる」は、ノウハウみたいなものは公開しても実際に大切なのはノウハウを作り出す過程だったり作り出す人なので、公開してもいいという話でした。ノウハウは常にブラッシュアップしていくもので、現時点のスナップショットを公開してもすぐに風化いくとのことでした。確かにそうだとは思いますけど、不変的な事項のフルオープンは躊躇しそうだなとおもいました。

人を雇う

「限界で人を雇う」、「履歴書はばかばかしい」、「全員が働く」、「文章力のある人を雇う」で、ここの章がこの本の言いたいことの大きな1つだと思います。如何にチームを小さくするか、どう採用の基準をどうみるか、そしてどう効率的に働くかが書かれています。個人的にはすべて同意なので、時間がない人はこの章だけでも読むと良いと思います。

ダメージコントロール

「対応の速度はすべてを変える」、「謝り方を知る」、「全員を最前線に」とあり、スピードを落とさないために、どうするかを考えるのはすごい大事だと思います。設計も、コードも、採用もスピードが落ちるなら一旦考え直した方がいいと個人的にはおもっています。「文句は放っておく」で大事なのは、自信があるなら突き進むということを書いてあります。人は変化に敏感で、反応するけどすぐに慣れるというものでした。

文化

この章は、「文化はつくるものではない」、「従業員ではガキではない」、「大げさに反応しない」、「なるたけ早くは毒」などに言及されている。文化は作るものではなく、自然発生的に醸成されていくものだし、従業員のために細かくルールを作らなくてもいいし、「なるたけ早く」は、本当の緊急時のみに使用することとしています。特に「なるたけ早く」は依頼側が勝手に期日を設定するわけで、そんなときほど仕様はボロボロなのでホントにやめてほしい。

最後に

この書籍は、海外での会社の中のことを書いていると思いますが、日本の事業会社の実態とそんなに変わらないなと感じています。僕がこの10年くらいで開発組織やチームをうまく蘇生するチェックポイントだったり教訓だったりが、そのまま書籍になっていました。

自分自身、大きな会社も小さな会社も経験をしましたが、むやみに人を増やすとコミュニケーションにかかる時間が増えていき、生産活動時間が減っていくことを目の当たりにしました。適切な文化醸成であったり、情報共有ルールなどの整備を行うことで、人を増やしても1人あたりの生産量が減らない、もしくは減りにくい状況にはなるかなと思います。

この書籍から学べることはあると思いますので、ぜひ読んでみてください。当たり前のことのように思いますけど、理由まで説明されているので、頭に残りやすいと思います。

Amazon.co.jp: 小さなチーム、大きな仕事 働き方の新しいスタンダード (ハヤカワ文庫NF) eBook : ジェイソン フリード, デイヴィッド ハイネマイヤー ハンソン, 黒沢 健二, 松永 肇一, 美谷 広海, 祐佳 ヤング: Kindleストア

タスクランナーTaskでモノレポでも快適な開発環境を構築する

Taskを使うとプロジェクトで使うタスクランナーをMakefileから脱却できます。モジュラーモノリス構成のモノレポでも各サブプロジェクトごとにTaskfile.ymlを設置でき便利です。簡単な使い方をご紹介します。

インストール

brew install go-task
scoop bucket add extras
scoop install task

基本的な使い方

go-taskのタスクはTaskfile.ymlに定義します。以下は簡単な例:

version: '3'

tasks:
  hello:
    cmds:
      - echo 'Hello, World!'
    silent: true

このファイルをプロジェクトのルートに置き、task helloを実行するとHello, World!が出力されます。silent: trueはコマンドの実行ログを非表示にし、結果のみを表示します

環境変数の利用

環境変数が使用できます。環境変数は、 {{ .環境変数名 }} で使用できます。 下記は、使用例になります。

version: '3'

env:
  ENV: task env

tasks:
  stdout:
    cmds:
      - echo "Hello,  {{ .ENV }}"
      - echo "Hello, {{ .SHELL_ENV }}"

実行してみます。

export SHELL_ENV="shell env"
task  stdout

Hello, task env
Hello, shell env

Taskfile.ymlからTaskfile.ymlを読み込む

Taskでは、Taskfile.ymlのネストが可能です。 include という機能になります。今回は、commonという共通用のサブプロジェクトがいるという想定にします。

project/
├── Taskfile.yml
├── common/
│   └── Taskfile.yml

common/Taskfile.ymlの内容

version: '3'

tasks:
  common-echo:
    cmds:
      - echo "Hello, Common Taskfile!"

上記をルートのTaskfile.ymlでcommonサブプロジェクトのTaskfile.ymlを読み込む

Taskfile.ymlの内容

version: '3'

includes:
  common:
    basedir: ./common
    desc: Common echo task
    taskfile: ./common/Taskfile.yml

tasks:
  echo:
    desc: Run the dev task
    deps:
      - common:common-echo
    cmds:
      - echo "Hello, Root Taskfile"

タスク一覧をみてみると、下記のようにincludeが成功しています。

task -l

* echo:                     Run the dev task
* common:common-echo:       Common echo task

これでタスクランナーに階層を持たせることができました。

サブプロジェクトからほかのサブプロジェクトのTaskfile.ymlを読み込む

下記のようなディレクトリ構成になっているとします。serviceAサブプロジェクトからcommonサブプロジェクトのタスクを使用したい場合の方法になります。

├── Taskfile.yml
├── common/
│   └── Taskfile.yml
├── serviceA/
│   └── Taskfile.yml

serviceA/Taskfile.ymlは下記のようになります。

version: '3'
includes:
  common:
    description: Common tasks
    basedir: ../common
    taskfile: ../common/Taskfile.yml

tasks:
  serviceA-echo:
    desc: Service A echo task
    deps:
      - common:common-echo
    cmds:
      - echo "Hello, ServiceA Taskfile!"

includesで、common/Taskfile.ymlを読み込めばOKです。こうすることで、プロジェクトで共通のタスクランナーの管理が簡単になります。

ホットリロード

Taskにはホットリロード機能も備わっています。これはかなり便利で、npmなどより軽いので、移行できるならしてもいいかなと思います。

Taskfile.ymlは下記のように書きます。

version: '3'

tasks:
  test:
    cmd: |
       ./gradlew test  // 実行するコマンドを指定
    ignore_error: true   // エラーが発生しても無視 
    sources:   // 対象のファイル群を指定
      - src/main/java/**/*.java
      - src/main/resources/**/*.*
    method: timestamp   // 更新要素を指定する(他にchecksumも使える)

このようにすると、sourcesのファイル群が変更された際にテストが別タスクで実行される

コマンドは、watch用のコマンドがあるので、それを実行する。

task test -w

これで、ファイル変更ごとに./gradlew testが実行される。

まとめ

MakefileだとShellで自前で実装しないといけないものが、Taskだと大体備わっています。GithubActionsでもすぐ使えるので、大変便利かなと。開発環境をよりよくするお供にぜひ使ってみてください。

Javaをスクリプト言語っぽくするJBangその5(CLIツールを作成する、テンプレート機能)

Javaスクリプト言語っぽくするJBangその5です。CLIツールの作成とテンプレート機能を説明します。CLIとかでJavaを使う選択肢はあんまりないかなと思いますが、結構簡単につくれてしまうのでJavaが動く環境であれば選択肢に入れていいかと思います。

k-sasaki.net

k-sasaki.net

k-sasaki.net

k-sasaki.net

テンプレートからCLIを作成

JBangではテンプレートからCLIがすぐに制作できるようにプリセットされています。下記のコマンド投入で

jbang init --template=cli cli/CliSample.java

下記のJavaコードが出力されます。

///usr/bin/env jbang "$0" "$@" ; exit $?
//DEPS info.picocli:picocli:4.6.3


import picocli.CommandLine;
import picocli.CommandLine.Command;
import picocli.CommandLine.Parameters;

import java.util.concurrent.Callable;

@Command(name = "CliSample", mixinStandardHelpOptions = true, version = "CliSample 0.1",
        description = "CliSample made with jbang")
class CliSample implements Callable<Integer> {

    @Parameters(index = "0", description = "The greeting to print", defaultValue = "World!")
    private String greeting;

    public static void main(String... args) {
        int exitCode = new CommandLine(new CliSample()).execute(args);
        System.exit(exitCode);
    }

    @Override
    public Integer call() throws Exception { // your business logic goes here...
        System.out.println("Hello " + greeting);
        return 0;
    }
}

中身を見てみると、//DEPSPicoCLIが設定されています。実行してみましょう。

$ jbang cli/CliSample.java CLI
Hello CLI

プロジェクト内で必要なコマンドとかは簡単に作れそうですね。

templateコマンド

JBangにはtemplateコマンドがあります。プリセットされているものも含めて一覧にするコマンドがあるので、実行してみます。

$ jbang template list
agent
   Agent template
cli
   CLI template
gpt
   Template using ChatGPT (requires --preview and OPENAI_API_KEY)
gpt.groovy
   Template using ChatGPT for groovy (requires --preview and OPENAI_API_KEY)
gpt.kt
   Template using ChatGPT for kotlin (requires --preview and OPENAI_API_KEY)
hello
   Basic Hello World template
hello.groovy
   Basic groovy Hello World template
hello.kt
   Basic kotlin Hello World template
jbang-catalog
   Template for creating a new JBang catalog hosted on github with automatic renovate updates
qcli
   Quarkus CLI template
qmetrics
   Quarkus Metrics template
qrest
   Quarkus REST template
readme.md
   Basic markdown readme template

プリセットされているものだけでも、魅力的なものがたくさんあります。QuarkusのRESTAPIやCLIだったり、ChatGPTのAPIクライアントを生成できるようです。

JBangのtemplateに追加する

その3で作成したJBangアプリケーションをテンプレート化してみましょう。

$ jbang template add --name SpringBootTemplate --description SpringBootSampleApp api/SpringBootSample.java api/application.yml

[jbang] No explicit target pattern was set, using first file: {basename}.java=api/SpringBootSample.java
[jbang] Template 'SpringBootTemplate' added to '/Users/kohei.sasaki/.jbang/jbang-catalog.json'

テンプレートに名前をつけて、テンプレート化したいファイルをスペース区切りで入力します。説明文もつけてみました。作成した情報は、jbang-catalog.jsonの中に入ります。

{
  ...
  ,"templates": {
    "SpringBootTemplate": {
      "file-refs": {
        "{basename}.java": "../git/jbang-sample/api/SpringBootSample.java",
        "application.yml": "../git/jbang-sample/api/application.yml"
      },
      "description": "SpringBootSampleApp",
      "properties": {}
    }
}

登録されていますね。テンプレート一覧をみて、確認してみます。

$ jbang template list
SpringBootTemplate
   SpringBootSampleApp

追加されていました。テンプレートに追加したものでJBang initをしてみます。

jbang init --tempalte SpringBootTemplate template/SpringBootSample.java

tree template
template
├── SpringBootSample.java
└── application.yml

無事作成されています。これで、チーム内ルールがコード化されているので、ちょっとしたツールとかもカジュアルに生産性高く作れそうです。

まとめ

JBang templateの使用方法を説明してみました。生成されるコードはJavaコードがほとんどですが、コマンド、実行などスクリプト言語っぽく使え、チーム開発でも活用できそうです。ぜひ使ってみてください。

Javaをスクリプト言語っぽくするJBang その4(JBangアプリをインストールする、カタログにする)

Javaスクリプト言語っぽくするJBangその4です。今回は、JBangで作成したアプリのインストールとエイリアスとカタログについてです。

k-sasaki.net

k-sasaki.net

k-sasaki.net

JBangアプリのインストール

JBangで作成したアプリは、jbang app コマンドでインストールが可能です。

前回作成したSpringBootSample.javaをインストールしてみましょう。

$ jbang app install api/SpringBootSample.java

これでインストールが可能です。jbang app listでインストールアプリ一覧が表示されます。

$ jbang app list
SpringBootSample

となります。

$ SpringBootSample

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/

 :: Spring Boot ::                (v3.5.0)

2025-06-15T07:27:45.723+09:00  INFO 1388 --- [           main] api.SpringBootSample

無事起動できています。

仕組みは簡単で、$HOME/.jbang/bin/配下に呼び出し用のshellscriptが作成されているだけになります。

JBangのエイリアス

JBangにはエイリアスがあります。これはjbang appとは異なり、jbangのショートカットみたいなものになります。下記のコマンドで追加が可能です。

$ jbang alias add api/SpringBootSample.java
[jbang] Alias 'SpringBootSample' added to '/Users/kohei.sasaki/.jbang/jbang-catalog.json'

登録ができているかを確認します。

$ jbang alias list

SpringBootSample
   /Users/kohei.sasaki/.jbang/../git/jbang-sample/api/SpringBootSample.java

登録できているようです。では、実行してみましょう。

jbang SpringBootSample

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/

 :: Spring Boot ::                (v3.5.0)

2025-06-15T07:27:45.723+09:00  INFO 1388 --- [           main] api.SpringBootSample

無事実行できます。

さらにJavaコードに説明文をを書くことが可能です。

//DESCRIPTION SpringBoot sample application.

説明文は追加時のみ読み取られるので、更新をかけます。

$ jbang alias add --force api/SpringBootSample.java
[jbang] Alias 'SpringBootSample' added to '/Users/kohei.sasaki/.jbang/jbang-catalog.json'

その後、説明文が追加されたかみてみましょう。

$ jbang alias list

SpringBootSample
   SpringBoot sample application.
   /Users/kohei.sasaki/.jbang/../git/jbang-sample/api/SpringBootSample.java

説明文が追加されています。

カタログ

簡単に誤解を怖れずにいうと、前述のエイリアスをひとまとめにしたものになります。これを用いることで、チーム内のスクリプトの共有やテストツールの共有などが簡単にできてしまいます。

JBangのドキュメント内にあるカタログをみてみましょう。 github.com

下記は抜粋ですが、jbang alias がひとまとめになっています。

{
  "catalogs": {},
  "aliases": {
    "hello": {
      "script-ref": "hello.java",
      "description": "Script that says hello back for each argument"
    },
    ....
    ....
    ....
  }
}

こちらのカタログを追加してみましょう。

$ jbang catalog add https://github.com/jbangdev/jbang-catalog/blob/HEAD/jbang-catalog.json
[jbang] Catalog 'jbang-catalog' added to '/Users/kohei.sasaki/.jbang/jbang-catalog.json'

標準出力されたjbang-catalog.jsonファイルをみてみましょう。

{
  "catalogs": {
    "jbang-catalog": {
      "catalog-ref": "https://github.com/jbangdev/jbang-catalog/blob/HEAD/jbang-catalog.json"
    },
    ...
    ...
    ...
}

追加されていました。カタログのリストをみてみます。JBangではデフォルトで様々なカタログが標準で入っています。

$ jbang catalog list

alibaba
   https://github.com/alibaba/jbang-catalog/blob/HEAD/jbang-catalog.json
apache/camel
   Run Apache Camel routes easily
   https://github.com/apache/camel/blob/HEAD/jbang-catalog.json
...
...

jbangdev
   JBang's own catalog of small utilities
   https://github.com/jbangdev/jbang-catalog/blob/HEAD/jbang-catalog.json
...
...


`jbangdev`で追加されています。カタログの中に何が登録されているかを見てみます。


$ jbang catalog list jbangdev

...
hello@jbangdev
   Script that says hello back for each argument
   https://github.com/jbangdev/jbang-catalog/blob/HEAD/hello.java
...

先ほど登録したカタログには約20くらいのエイリアスが登録されています。helloを実行してみます。カタログ内のjbangを実行する際は下記のように@の後ろにカタログ名をつけます。

$ jbang hello@jbangdev jbang

Hello jbang

実行できました。これで自分で作ったJBangアプリが共有できるのは簡単ですね。

まとめ

JBangのアプリのインストール、エイリアスエイリアスをまとめたカタログをご紹介させていただきました。GistなどやGithubリポジトリ内にカタログファイルをおいておくと、簡単にツールなどを共有できますし、プロジェクト固有のツールなどの共有もスムーズにいくとおもいます。実際のプロジェクトですぐ使えるものばかりなので、ぜひ使っていただければと思います。