たにしきんぐダム

プログラミングやったりゲームしてます

Bazel 入門 (for Scala developers)

Scala Advent Calendar 2022 14日目の記事です。今日は Bazel について書きます。

(ちょっと自動翻訳っぽい日本語ですが、実際そうで、よそで英語で(自分で)書いた文章を日本語に適当に訳して投稿しています)


プロジェクトのビルド時間は、チームの開発効率に大きな影響を与えます。

しかし、コードベースが大きくなればなるほど、ビルドにかかる時間は長くなり、ビルド時間が長くなればなるほど、開発者のエクスペリエンスは悪化していきます。 Scalaデファクトスタンダードなビルドシステムであるsbtは優れたビルドツールですが、非常に大規模なプロジェクト(100万行~とか)では長いビルド時間をいい感じにすることは難しい。

この記事では、シンプルなScalaアプリケーションのビルドを通して、Google-scale のリポジトリでも高速なビルドを実現するためのビルドシステム「Bazel」を紹介します。(Bazel の発音は ベィゼル)

What is Bazel

Bazelは{Fast, Correct} choose two を謳うビルドシステムで、Artifact-Based Build Systemと呼ばれるものです。

Artifact-Based Build System とは?

AntやMavenなどの従来のビルドシステムは、Task-Based Build System と呼ばれています。 Task-Based Build System のビルド設定では、do taskA, then do taskB, and then do taskC のように procedural なタスクの実行順序を記述する。一方、Buck、Pants、Bazelなどの成果物ベースビルドシステムでは、ビルドする成果物、依存関係のリスト、などを declarative に記述する

Bazelの基本的な考え方は、ビルドは純粋な関数であり、ソースと依存関係は入力、アーティファクトは出力。そこに副作用はない(よなぁ!?)というものです。

アーティファクトベースのビルドシステムのコンセプトの詳細については、 Chapter 18 of Software Engineering at Google が詳しい。

{Fast, Correct} Choose Two

この主張をよりよく理解するためには、Bazelの Hermeticity という性質を理解するのと良い。

Bazel は 密閉ビルドシステム (hermetic build system) と呼ばれ、同じ入力ソースと構成が与えられると、同じ出力を返す。Hermeticity のおかげで、Bazelは再現性のあるビルドを提供します。つまり、同じ入力が与えられれば、誰のコンピュータでも常に同じ出力を返す。 (OS などの platform 情報がビルドに対して暗黙的な依存として含まれるので、誰のコンピュータでもというのは少し語弊があるが...)** (これを correct build という)

また、 correct build のおかげで、Bazelはチーム内でビルドキャッシュを共有するための リモートキャッシュ機能を提供することができます。リモートキャッシュを使えば、チームメンバー間で共有されたビルドキャッシュを利用して、大規模なプロジェクトを高速にビルドすることができる。

Bazel Basics

ということで、この記事の残りでは、Bazel を使って簡単な Scala アプリケーションをビルドしつつ、Bazelの重要なコンセプトを紹介していきます。

github.com

ディレクトリ構成はこんな感じ

|-- WORKSPACE
`-- src
    `-- main
        `-- scala
            |-- cmd
            |   |-- BUILD
            |   `-- Runner.scala
            `-- lib
                |-- BUILD
                `-- Greeting.scala

Bazel の設定ファイルには WORKSPACEBUILD の2つ。

  • WORKSPACE ファイルは、外部からの情報(3rd party dependenciesなど)をBazelプロジェクトに取り込むための設定を記述するファイル
  • BUILD ファイルは、Bazel がソースコードをどうビルドするかを記述するファイル

WORKSPACE ファイル

WORKSPACEファイルには、外部の依存関係(Bazel と JVM の両方)などが記述される。例えば、Scala をコンパイルするための Bazel の拡張(build rule)である [rules_scala](https://github.com/bazelbuild/rules_scala) を ダウンロードするときはWORKSPACE` に以下のような感じで書く。

load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
 ...
http_archive(
    name = "io_bazel_rules_scala",
    sha256 = "77a3b9308a8780fff3f10cdbbe36d55164b85a48123033f5e970fdae262e8eb2",
    strip_prefix = "rules_scala-20220201",
    type = "zip",
    url = "https://github.com/bazelbuild/rules_scala/releases/download/20220201/rules_scala-20220201.zip",
)

(より詳しいインストラクションは README 見てね)

BUILD file

Bazel でソースコードをどうビルドするかを定義するためには、BUILD ファイルを書いていく。。 まずはビルドする Scala ファイルをシュッと見てみましょう。このプロジェクトでは、2つの単純なScalaファイルが異なるパッケージで入っているだけです。

// src/main/scala/lib/Greeting.scala
package lib
object Greeting {
  def sayHi = println("Hi!")
}
// src/main/scala/cmd/Runner.scala
package cmd
import lib.Greeting
object Runner {
  def main(args: Array[String]) = {
    Greeting.sayHi
  }
}

このように、 lib.GreetingsayHi メソッドを提供する小さいライブラリモジュールであり、 cmd.Runnerlib.Greeting に依存。

それでは、これらのScalaソースをビルドするための BUILD ファイルの書き方を見てみましょう。

scala_library

この例では lib.Greeting をビルドするために、BUILD ファイルを Greeting.scala の隣に置き (必ずしも隣に置く必要はないよ、詳しいことは このブログとかを読んでね)、以下のような設定を書いてみます。

# src/main/scala/lib/BUILD
load("@io_bazel_rules_scala//scala:scala.bzl", "scala_library")
scala_library(
    name = "greeting",
    srcs = ["Greeting.scala"],
)

ここでは rules_scala が提供する scala_library というビルドルールを使って、BUILD ファイルを記述しています。

Bazel におけるruleとは、コードをビルドしたりテストしたりするための指示のセットを宣言したもの。 例えば、(Bazel がネイティブでサポートしている)Java プログラムをビルドするためのルールがあったり、rules_scalaScala プログラムをビルドするためのルール群を提供する。例えばscala_library は与えられた Scala のソースをコンパイルして、JAR ファイルを生成する。

BUILD ファイルの中身を一行ずつ説明していくと

  • load 文は BUILD ファイルに scala_library ルールをインポートする。
  • scala_library は Bazel のビルドルール。必須属性は namesrcs
    • name は この target の一意な識別子 (ruleインスタンスtarget と呼ぶ)。
    • srcs` はビルドする Scala ファイルのリスト。

bazel build

ではビルドファイルもかけたことなので、早速ビルドしましょう。ビルドには bazel build コマンドを使う。

$ bazel build //src/main/scala/lib:greeting
...
INFO: Found 1 target...
Target //src/main/scala/lib:greeting up-to-date:
  bazel-bin/src/main/scala/lib/greeting.jar
INFO: Elapsed time: 0.152s, Critical Path: 0.00s
INFO: 1 process: 1 internal.
INFO: Build completed successfully, 1 total action

やったー jar ファイルが生成されました。しかし //src/main/scala/lib:greeting って何だ???

Label

src/main/scala/lib:greeting は Bazel ではLabel と呼ばれるもので、 src/main/scala/lib/BUILD に定義されている greeting という target を指している。Bazelでは、ビルドターゲットを一意に識別するためにLabelを使用します。

Labelは3つの要素から構成される。例えば @myrepo//my/app/main:app_binaryでは、以下のようになります。

  • @myrepo//リポジトリ名を指定します。@myrepo を省略することも可能で、その場合は // が同じ作業リポジトリを参照することになる。
  • my/app/main はパッケージ (BUILD ファイルへのプロジェクトルートからの相対パス)を表す。
  • :app_binary はターゲット名。

つまり、 /src/main/scala/lib:greeting は、同じワークスペースにあるターゲットを指しており、 src/main/scala/lib にある BUILD ファイルで定義されていて、そのターゲット名は greeting なビルドターゲットのビルドを実行する。

Dependency

次に、lib.Greetingに依存するcmd.Runnerをビルドしてみましょう。今回は cmd.Runnerlib.Greeting に依存しているため、 deps 属性を使用してターゲット間の依存関係を導入します。

# src/main/scala/cmd/BUILD
load("@io_bazel_rules_scala//scala:scala.bzl", "scala_binary")
scala_binary(
    name = "runner",
    main_class = "cmd.Runner",
    srcs = ["Runner.scala"],
    deps = ["//src/main/scala/lib:greeting"],
)

前の例と違う点は

  • scala_library の代わりに scala_binary を使っている。
    • scala_binaryexecutable rules と呼ばれるルール。executable rule は、ソースから executable をどのようにビルドするかを定義します。
    • この処理には、依存関係のリンクや、依存関係のクラスパスのリストアップが含まれたりする (一方 scala_library は thin jar を作るだけ)
  • 例えば、scala_binaryルールはソースと依存関係から実行可能なスクリプトをビルドする。
    • 実行ファイルがビルドされたら、bazel run コマンドを用いて実行することができる。
  • 依存関係をリストアップするために、deps 属性を追加している。
    • この例では、cmd.Runnerlib.Greeting に依存しているので、 //src/main/scala/lib:greeting というラベルを追加しています。

これでビルドできるはず...

$ bazel build //src/main/scala/cmd:runner

ERROR: .../01_scala_tutorial/src/main/scala/cmd/BUILD:3:13:
in scala_binary rule //src/main/scala/cmd:runner:
target '//src/main/scala/lib:greeting' is not visible from
target '//src/main/scala/cmd:runner'.

だめでした。target '//src/main/scala/lib:greeting' is not visible from target '//src/main/scala/cmd:runner'. らしいです。

visibility

Bazelには一般的なプログラミングに見られる visibility の概念があります。デフォルトでは、すべてのターゲットの可視性は private で、同じパッケージ内のターゲットのみがお互いにアクセスできるようになっています。

cmd から lib:greeting を見えるようにするには、greetingvisibility 属性を追加します。

 scala_library(
     name = "greeting",
     srcs = ["Greeting.scala"],
+    visibility = ["//src/main/scala/cmd:__pkg__"],
 )

//src/main/scala/cmd:__pkg__ はパッケージ //src/main/scala/cmd へのアクセスを許可する Visibility Specification

これで...

$ bazel build //src/main/scala/cmd:runner
...
INFO: Found 1 target...
Target //src/main/scala/cmd:runner up-to-date:
  bazel-bin/src/main/scala/cmd/runner.jar
  bazel-bin/src/main/scala/cmd/runner
INFO: Elapsed time: 0.146s, Critical Path: 0.01s
INFO: 1 process: 1 internal.
INFO: Build completed successfully, 1 total action

ビルドできました!

見ての通り、scala_binaryルールは runner.jar に加えて runner も生成します。これは runner.jar のラッパースクリプトで、これを使って簡単にJARを実行することができる。

$ ./bazel-bin/src/main/scala/cmd/runner
# もしくは
$ bazel run bazel build //src/main/scala/cmd:runner
Hi!

Tips

上記の例では、ターゲットのラベルを指定して1つのターゲットをビルドしていますが、面倒くさい。複数のビルドターゲットをまとめてビルドすることはできるのでしょうか?

できる!ワイルドカードを使って、複数のターゲットを選択することができます。例えば、$ bazel build //... とすれば全てのターゲットをビルドすることができます。便利

ここまででScalaで簡単なアプリケーションを作ることを通じて、Bazelの基本を学びましたが、Bazelで3rd-partyのライブラリを使うにはどうしたらいいのだろう?

External JVM dependencies

次は外部ライブラリを使ってちょっとだけ難しいアプリを作ってみましょう。

scalameta を使って Scala プログラムを解析し、pprint を使って AST をきれいに印刷する簡単なアプリケーションを作ってみます。これを通じてMaven からサードパーティーライブラリを使う方法を学んでみましょう。

github.com

この例では、外部のJVM依存性を管理するための標準的なルール・セットの一つである rules_jvm_external を使用します。

(ちなみに: rules_jvm_external を使わなくても maven_jarを使用して、Mavenリポジトリからjarをダウンロードすることができますが、rules_jvm_external は transitive deps の resolve や pinning などいろんな便利機能 を備えたデファクトスタンダードなので、今回は rules_jvm_external を使うことにします)。

今回の Scala プログラムは1ファイルだけ

// src/main/scala/example/App.scala
package example
import scala.meta._
object App {
  def main(args: Array[String]) = {
    pprint.pprintln(parse(args.head))
  }
  private def parse(arg: String) = {
    arg.parse[Source].get
  }
}

argument で受け取った Scala プログラムを解析して pprint で出力するだけ

Maven リポジトリから scalametapprint をダウンロードするために、 rules_jvm_external を使用します。まずは rules_jvm_external をダウンロードしましょう。

rules_jvm_external をダウンロードするには、リリースページ にある setup statements をコピーして、以下のように WORKSPACE ファイルにコピペする。

http_archive(
    name = "rules_jvm_external",
    strip_prefix = "rules_jvm_external-4.5",
    sha256 = "b17d7388feb9bfa7f2fa09031b32707df529f26c91ab9e5d909eb1676badd9a6",
    url = "https://github.com/bazelbuild/rules_jvm_external/archive/refs/tags/4.5.zip",
)
...

そして、利用するライブラリ一覧を同じく WORKSPACE に書いていく。

load("@rules_jvm_external//:defs.bzl", "maven_install")
maven_install(
    artifacts = [
        "org.scalameta:scalameta_2.13:4.5.13",
        "com.lihaoyi:pprint_2.13:0.7.3",
    ],
    repositories = [
        "https://repo1.maven.org/maven2",
    ],
)

こういう感じで依存するライブラリをダウンロードはできるのですが、どうやって使えばいいのだろう?

ダウンロードした依存関係を使用するには、scala_library などの deps 属性に依存関係を追加する必要があります。rules_jvm_external@maven リポジトリ以下のライブラリのターゲットを以下のフォーマットで自動生成します。

The default label syntax for an artifact foo.bar:baz-qux:1.2.3 is @maven//:foo_bar_baz_qux https://github.com/bazelbuild/rules_jvm_external#usage

したがって、com.lihaoyi:pprint_2.13:0.7.3@maven//:com_lihaoyi_pprint_2_13 というラベルで参照できるようになります。そこで、以下の BUILD ファイルを App.scala の隣に書いていく

# src/main/scala/example/BUILD
scala_binary(
    name = "app",
    main_class = "example.App",
    srcs = ["App.scala"],
    deps = [
        "@maven//:com_lihaoyi_pprint_2_13",
        "@maven//:org_scalameta_scalameta_2_13",
    ],
)

そしてビルド、実行してみましょう

$ bazel build //src/main/scala/example:app
...
INFO: Found 1 target...
Target //src/main/scala/example:app up-to-date:
  bazel-bin/src/main/scala/example/app.jar
  bazel-bin/src/main/scala/example/app
INFO: Elapsed time: 0.165s, Critical Path: 0.00s
INFO: 1 process: 1 internal.
INFO: Build completed successfully, 1 total action
...

$ bazel-bin/src/main/scala/example/app "object main { println(1) }"
Source(
  stats = List(
    Defn.Object(
      ...
    )
  )
)

良かったですね。

まとめ

今回は、Bazel が大規模リポジトリでの高速ビルドを可能にすることを紹介し、簡単な Scala アプリケーションのビルドを通して、Bazel の基本概念と使用方法を紹介しました。この記事がBazelを使い始める最初の一歩になれば嬉しいです。

Bazelは、sbtやMavenなどの他のビルドツールに比べて、多くのビルド設定を管理する必要があることが、今回の小さな例でも見て取れたかなと思います。しかしこれは再現可能なビルドやリモートキャッシュといったBazelの長所によるスケーラブルなビルド速度のためのトレードオフです。

Bazelについてもっと知りたいなら、まずは公式のガイドに目を通して、実際にいくつか小さなアプリケーションをビルドしてみたりすることをおすすめします。

Scripting with Scala CLI

Scala Advent Calendar 8日目の記事です。

Scalaスクリプト書いてますか?僕は書いてません、でした、最近までは。

隣のクラスのPythonやGoはインタープリタコンパイラをインストールしたらすぐに適当なスクリプト書き始められるというのに、Scalaときたらちょっとしたスクリプト書くのにもsbtなりのビルドシステムをセットアップして〜みたいなことしないといけない。

そんなあなたにはこれ!

scala-cli.virtuslab.org

Scala CLIScala をサクッと書くための CLI ツールで、これさえインストールすればsbtなどのビルドツールを使わなくてもすぐに一通りのプログラムを書くことができるようになります。

インストール方法とかはドキュメントを見てもらって、Scala CLI 使ってなにか書いてみましょう。

scala-cli.virtuslab.org

ここ最近の為替レートのチャートを表示するスクリプト書いてみる

最近は円安すごくて為替レート気になりますよね。ということでCLIからサクッと為替レート見れるスクリプトを書いてみます。(とりあえずEUR->JPYで)

為替レートの取得には Exchange Rates API を使って、ライブラリは以下のものを使います。

できました。(secret に取得したAPIトークンを入れてね)

#!/usr/bin/env -S scala-cli shebang --quiet

//> using lib "com.softwaremill.sttp.client3::core:3.8.3"
//> using lib "com.mitchtalmadge:ascii-data:1.4.0"
//> using lib "com.lihaoyi::ujson:2.0.0"
import ujson._
import sttp.client3._
import com.mitchtalmadge.asciidata.graph.ASCIIGraph

import java.time._

val secret = "xxx"

val endDate = if (args.length > 1) LocalDate.parse(args(1)) else LocalDate.now()
val startDate = endDate.minus(Period.ofDays(90))
val client = SimpleHttpClient()
val response = client
  .send(
    basicRequest
      .header("apikey", secret)
      .get(
        uri"https://api.apilayer.com/exchangerates_data/timeseries?start_date=${startDate}&end_date=${endDate}&base=EUR&symbols=JPY"
      )
  )
val responseBody =
  response.body.getOrElse(throw new Exception("Service not responding"))
val data = ujson.read(responseBody)
val values =
  data("rates").obj.toSeq.sortBy(_._1).map((_, value) => value("JPY").num)
println(ASCIIGraph.fromSeries(values.toArray).plot())

これを script.sc として保存して以下のコマンドで実行。

$ scala-cli script.sc
# もしくは
$ chmod +x script.sc
$ ./script.sc

実行すると直近90日のEUR->JPYの為替レートが以下のような感じのascii chartで表示されます。

実行結果(うまくコピペできなかった)

解説

using lib なんちゃらって何?

//> using lib "com.softwaremill.sttp.client3::core:3.8.3"

using directive っていうやつ。//> using lib "org::name:version" でそのスクリプトからライブラリをダウンロードして使えるようにしてくれる。(裏側ではcoursierを使ってダウンロードされてivyキャッシュに保存されるので、実行するたびにダウンロードされることはないよ)。

scala-cli.virtuslab.org

どのバージョンのScalaが使われてるの?

デフォルトだと最新(今だと3.2.0)。

$ scala-cli -S 2.13.10 script.sc みたいな感じで Scala のバージョンを指定したり

//> using scala "2.13.10"sc ファイルの頭につけて、using directive でバージョン指定もできる。

IDE サポートは?

あります! Metals の場合は .sc ファイルを開くと、scファイルが見つかりました、ScalaCLI script としてインポートしますか? みたいなのが出るのでハイを押すと普通にIDE機能を使った状態でスクリプトを書くことができるようになる。

もしくは IDE Setup | Scala CLI

ammonite とは何が違うの?

裏側では ammonite 使ってます(それにいろいろ便利機能をラップした感じ)。なので ammonite でできることはだいたいできるはず。

ammonite でできること。

blog.3qe.us

なんで ./script.sc で実行できるの?

shebang#!/usr/bin/env -S scala-cli shebang って書いてるから。

https://scala-cli.virtuslab.org/docs/guides/scripts#self-executable-scala-script


これで Scala でもシュシュッとスクリプト書けるぞ!!!

良いブログ

alexn.org

ちなみに

(ちなみに今後 scala コマンドをこの scala-cli で置き換えるという SIP が提案され、最近 accept されました)。

contributors.scala-lang.org

contributors.scala-lang.org

効率の良い学習方法を身につけるために『使える脳の鍛え方 成功する学習の科学 』を読んだ

2-3年ほど前に大学院受験の勉強を始める際に読んだ。中高生のときに多分こうすると良いんだろうなという勉強方法(というか記憶術)を裏付けてくれる良い本だったのを覚えている。

最近人に何度かおすすめすることがあり、パラパラっと読み直したけどやっぱり良い本だった。

この本はここ百数十年くらいの効率の良い学習方法(この本でいう「学習」とは主に学んだことの長期記憶への定着を指す)に関する研究結果をまとめて、認知心理学や教育科学の門外漢に対しても分かりやすく噛み砕いて書かれたもの。

目から鱗の真実、のようなことは書かれていなくて、まあそうだよねっていうような学習方法が効率の良い/悪い学習方法だということが、過去の心理学実験の結果をベースに書かれている。

書かれている話はだいたいこういう感じ。

  • 多くの学生は大事な部分をマーカーで印をつける、似たような本を何度も多読する、短い期間に集中して勉強するなどの学習方法を好むが、それらの学習方法はさほど効率的ではない
  • 定期的に学んだことを思い出そうとすることで長期記憶の定着が促進される
  • 一種類の問題をマスターして次、と学習を進めるのではなく、多様な問題を交互に学習するほうが効果的
  • すぐに答えを見るのではなく、どうすれば良いのか考えると良い
  • 新しい知識を、すでに自分の知っている知識や体験、異なるコンテキストに結びつける

この分野のサーベイ論文を門外漢向けに大衆化しましたみたいな内容で読み物として面白かった。

本読むのが面倒な人は productivity 大好き Youtuber の Ali Abdaal さんがほとんど全部説明してくれてるので以下の動画を見るのがおすすめ(というかこの人の動画からこの本を知ったのであった)

youtu.be

sbt-npm-package でシュッと scala.js のビルド成果物を npm にアップロードする

Scala Advent Calendar 2022 - Qiita 1日目の記事です

scala.js でビルドした成果物を npm にアップロードすることありますか? 私はある。

普通に scala.js でビルドした js を npm にアップロードしようとするとこういう感じの流れになります。

  • sbt fullOptJS とかで js にビルド
  • ビルド成果物(.../target/scala-2.13/foo-opt.js とか) を適当なディレクトリにコピー
  • そのディレクトリに package.json と、npmにアップロードする README.md を設置
  • npm login して npm publish

まあ、これでも別にいいっちゃいいのですが、いくつか気に入らないところがあり

  • build.sbt の設定と、package.json の設定を別々に管理することになる (version とか)
  • sbt によるビルド成果物のパスをリリーススクリプト側にハードコードしないといけない。
    • 別にいいけど、なんとなくビルド成果物の居場所はsbtが知ってる状態であってほしい。

なんか sbt 側で npm publish をラップしたいな〜と思って適当に github 眺めてると良さげな sbt plugin を見つけました。

github.com

全然ドキュメントない!けどコード読んだらなんか良さそうだったので使ってみた

sbt-npm-package ざっくり

sbt-npm-package がやってくれるのは

  • npm-package ディレクトリを target 以下に作る (target/scala-2.13/npm-package とか)、ここにjsとかpackage.jsonとかを集めてくれる。
  • npmPackageNpmrcnpm-package/.npmrc の生成
    • 中身はデフォルトで //registry.npmjs.org/:_authToken=${NPM_TOKEN}
  • npmPackagePublish で js 成果物を npm にアップロード
    • npm-package/package.json を build.sbt での設定を元に生成
    • ビルド成果物のJSを npm-package/main.js にコピー
    • (デフォルトではプロジェクトルートの) README.mdnpm-package にコピー
    • npm-package ディレクトリ上で npm publish を実行

なので基本的にはこの記事の最初に述べたリリース手順をsbt側に寄せて自動化してくれるというシンプルなもの。

設定しそうな settingKey

sbt-npm-package/NpmPackagePlugin.scala at f72e90581a63841039685371dbbe46e20e6ae6c7 · davenverse/sbt-npm-package · GitHub

settingKey 説明 デフォルト
npmPackageName package.jsonname sbt の project name からいい感じに作られる
npmPackageDescription description
npmPackageVersion version sbtversion が利用される
npmPackageRepository repository git ls-remote --get-url origin
npmPackageREADME アップロードする README.md へのパス README.md
npmPackageStage fullOptJS とか fastOptJS とか指定する FastOptJS

設定にない項目は npmPackageAdditionalNpmConfig で書き加えられる。

npmPackageAdditionalNpmConfig := Map(
   "homepage" -> _root_.io.circe.Json.fromString("https://scalameta.org/")

使用例

例えば

npmPackageName := "scalameta-parsers",
npmPackageDescription := "Library to parse Scala programs",
npmPackageRepository := Some("https://github.com/scalameta/scalameta"),
npmPackageAuthor := "scalameta",
npmPackageLicense := Some("BSD-3-Clause"),
npmPackageKeywords := Seq("scala", "parser"),
npmPackageVersion := "4.6.0",
npmPackageAdditionalNpmConfig := Map(
  "homepage" -> _root_.io.circe.Json.fromString("https://scalameta.org/")
),

こんな感じの設定で npmPackagePublish すると以下のような pakcage.json が作られます。

{
  "name" : "scalameta-parsers",
  "description" : "Library to parse Scala programs",
  "version" : "4.6.0",
  "type" : "commonjs",
  "repository" : {
    "type" : "git",
    "url" : "https://github.com/scalameta/scalameta"
  },
  "author" : "scalameta",
  "license" : "BSD-3-Clause",
  "main" : "main.js",
  "dependencies" : {},
  "devDependencies" : {},
  "keywords" : ["scala", "parser"],
  "homepage" : "https://scalameta.org/"
}

良さそうですね。

あとは repository secret に npm からとってきた access token を NPM_TOKEN として登録して、以下のように npm と sbt のある環境で npmPackageNpmrc -> npmPackagePublish すれば出来上がり

name: Release JS
on:
  push:
    tags: ["*"]
jobs:
  publish:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/setup-node@v3
        with:
          node-version: "18"
      - uses: actions/checkout@v3
      - uses: olafurpg/setup-scala@v13
      - run: git fetch --unshallow
      - name: Publish
        run: sbt "parsersJS/npmPackageNpmrc; parsersJS/npmPackagePublish"
        env:
          NPM_TOKEN: ${{ secrets.NPM_TOKEN }}

詳しくはこちら

github.com

ブログに使い方書かないで sbt-npm-package にPRでもすれば良かった気もしてきた。

一人旅が下手

自分はもっと一人でいろいろ楽しめるタイプの人間だと思っていた。しかし、これまで何度か海外に一人で旅行に行ってきて、また2022年9月にもひとりでポーランドに出張/旅行に行ってきて確信したのだが、自分は思ったより一人旅が下手なようだ。

  • キレイな景色とか美味しい料理を一人で食べても、その感動をシェアできる人がいないと感動を噛み締められない。
    • すぐ興味が別のものに行くので、キレイだなぁと思っても10秒くらいで飽きてしまう。
  • あまり計画立てたりアクティブにどこか行くタイプでもないので、海外に滞在しても周囲のスーパーとかカフェとか手近なところをめぐるだけ
    • 現地の文化や空気を味わうのが好きなのでこれも楽しい
  • なんでもないカフェやバーとかで突然知らない人と会話を楽しめるような社交性はない。

逆に自分はどういう旅行に楽しみを見出していたかというと、人との出会いに一番の楽しみを感じていたなと思う。

例えば

  • ポーランド旅行記 - たにしきんぐダム
    • ScalaDays 2019 に参加するためにスイスのLausanneに旅行に行った際はは OSS つながりで Ólafur Páll Geirsson や現職の同僚である Tomasz Godzik と話せたし
    • ソーシャルイベントのおかげで全く接点のなかった人と仲良くなったり、カンファレンスで会った日本人の方からのつながりでドイツの会社のエンジニアの人と知り合うことができた。
  • コロナ禍2022年9月海外渡航メモ - たにしきんぐダム
    • 今年の2022年9月にポーランドのKrakowに旅行に行った際は、そもそも会社の同僚とのソーシャライズが主目的だったのでいろんな人とのコミュニケーションが取れてよかった
    • また sphere.it というオフラインカンファレンスでも、友人が何人か参加していたのでそのつてでネットワークを広げることができた。
  • シドニー旅行記 - たにしきんぐダム
    • 一人旅ではないが、8年くらい前にシドニーに語学留学したのが人生で一番楽しい海外滞在だったと思う。
    • 思い出補正や、シドニー自体が魅力的な街であること、初めての海外というのもあるが、やはりクラスメイトと一緒に留学したことや、現地の大学生や別の日本の留学中の学生と仲良くなれたのが一番大きかったんじゃないかなと思う。
      • 今度またシドニーに行って検証したい気もする。
  • ローザンヌ旅行記 - たにしきんぐダム
    • 一方3年前にKrakowに旅行に行った際は、知り合いも全然おらず、街歩きは楽しいけどすぐ飽きてしまい、滞在中数日間はAirbnbの宿にこもってゼルダで遊んでいた(最悪)

ということで

  • (知り合いの参加している/自分が発表する)技術カンファレンス駆動旅行は、知らない人とコミュニケーションを取る機会があるので良い
  • (少人数の)ソーシャライズイベントが用意されていると最高
  • 知り合いのいない場所に一人で(海外)旅行してもどうせ楽しめないのでやめておく
    • 国内旅行も同じではあるけど、国内旅行はスパンが短いので飽きる前に帰ってこれているので良いのかもしれない。
  • 複数人で旅行だと特にイベントなどのない旅行も楽しめるかもしれない
    • けど友達との旅行なら別に海外じゃなくてもどこ行っても楽しいんじゃないか?

思ったより自分は社交的な人間なのかもしれないね。