search term:

sbt 2.x リモートキャッシュ

これは Scala Advent Calendar 2023 の 23日目の記事です。21日目は、さっちゃんのpath 依存型って何? 調べてみました!でした。

はじめに

リモートキャッシュは、ビルドの結果を共有することで劇的な性能の改善を可能とする。Mokhov 2018 ではクラウド・ビルド・システム (cloud build system) とも呼ばれている。これは、僕が Blaze (現在は Bazel としてオープンソース化されている) のことを聞いて以来関心を持ち続けてきた機能だ。2020年に、僕は sbt 1.x のコンパイルキャッシュを実装した。reibitto さんの報告によると「以前は全てをコンパイルするのに 7分かかっていたが、15秒で終わるようになった」らしい。他にも 2x ~ 5x 速くなったという報告を他の人も行っている。これらは期待の持てる内容であることに間違いないが、現行の機能は少し不器用で compile タスクにしか使えないという限界がある。2023年の3月に、RFC-1: sbt cache ideas として現状の課題と対策の設計のアウトラインを書き出してみた。以下に課題をまとめる:

  • 問題1: sbt 1.x は compile のリモートキャッシュ、およびその他いくつかのタスクに対してディスクキャッシュを実装するが、カスタムタスクが参加できるソリューションが望ましい。
  • 問題2: sbt 1.x はディスクキャッシュとリモートキャッシュで別の機構を持つが、ビルドユーザがローカルかリモートのキャッシュかを切り替えられる統一した機構が望ましい。
  • 問題3: sbt 1.x は Ivy resolver をキャッシュの抽象化に用いたが、よりオープンなリモートキャッシュ・バックエンドが望ましい

12月中は適当に自分でプロジェクトを選んで 毎日少しでもいいから作業して、それをブログに数行ずつ記録したり #decemberadventure というハッシュタグをつけて投稿するという独りアベントが Mastodon 界隈の一部で流行ってて、僕の december adventure 2023 として、sbt 2.x のリモートキャッシュに挑戦してみようと思った。実装の提案は GitHub #7464 で、本稿では、提案した変更点の解説を行う。注意: sbt の内部構造に関する予備知識はあんまり必要としないが、プルリクコメントの拡張版のようなものなので上級レベルの読者を想定している。あと、プルリク段階なので書いている先から詳細はどんどん変わっていくかもしれない。

Bazel + Scalafix を用いてリファクタリングを自動化する方法

Scalafix について コードベースが大型化するにつれ、自動リファクタリングを行うことができる言語ツールがあると便利だ。幸いなことに、2016年に Scala Center が Scalafix を作ってくれた。公開時のブログ記事の中で Ólafur Geirsson さんは: Scalafix は、簡単かもしれないが単調に繰り返されるコード変換を受け持つことで、あなたが意識を向ける価値のあることに集中することができます。大まかに説明すると、Scalafix はソースを読んで、非推奨機能の使用を新しい代替へと変換し、元のソースに書き込みます。 と解説していて、Scala 3 マイグレーションが動機になっていたことがうかがえる。 現在は、Scalafix は Brice Jaglin さんらによってメンテされていて、Scala 3 マイグレーション以外でも一般のリンティングやリファクタリングのツールとして使われている。例えば吉田さん (xuwei-k) なんかは数百の Scalafix を書いたらしく、その一部は xuwei-k/scalafix-rules にも公開されている。 Scalafix 独特の特徴として、syntactic (構文的) と semantic (意味論的) という2種類のルールがある。 syntactic rule は、コンパイルすることなくソースコードに対して直接実行することができる。シンプルだが、コード解析の力には制限がある。 semantic rule は、シンボルや型を用いてより高度なコード解析を行うことができるが、入力ソースを SemanticDB コンパイラ・プラグインと共にコンパイルしたものを事前に用意する必要がある。 syntactic rule は Scalafix CLI さえあれば良いので、Bazel との統合は特に必要無い。一方で、semantic rule は semanticdb などを渡して回るため、少し作業が必要となる。 Bazel 統合の先行研究 ianoc さんが作った ianoc/bazel-scalafix というリポがあるが、Bash 成分が多い。本稿では、もう少し Starlark を使った方法を解説するが、Bash も多少は必要となる。 Bazel を用いて何でもクロスビルドする方法も参照。 Bazel + Scalafix 手順の概要

ハイブリッド ScalaMatsuri の舞台裏

今週末、virtual/Tokyo ハイブリッドの ScalaMatsuri が開催された。まずは、登壇者の皆さん、スポンサー各社、参加者の皆さんにお礼します。

初のハイブリッドということで、至らない点も多々あったけども、成功したカンファレンスだったのではと思う。僕は、16名の ScalaMatsuri スタッフの一員、それからメインのセッションと飛びコンでの登壇者として参加させてもらった。

Bazel を用いて何でもクロスビルドする方法

一般論として Bazel は、モノバージョンニングといって、(JUnit や Pandas など) どのライブラリでもモノリポ内の全てのターゲットが同一のバージョンを使うという形態を好む。 モノバージョニングは、モノリポ内で発生しうるバージョン衝突を劇的に減らすため、よりコード再利用性を改善させるという効果がある。 しかし、実際に運用してみると全社が二人三脚状態になるという欠点も出てくる。 例えばサービス A、B、C、D の全てが Big Framework 1.1 を使っていると、デグレ (regression) があるかもしれないので全てを同時に Big Framework 1.2 に移植するのは人的負荷が高かったりする。 そんなこんなで数年が経ち、Big Framework 2.0 がリリースされて、やっぱりこれも採用はリスキーなのではということになる。 Scala エコシステムでは、sbt を用いてライブラリ作者がライブラリを複数の Scala 標準ライブラリやその他のフレームワークに対してビルドするというのは普通に行われている。 これはクロスビルドと呼ばれている。(x86 から aarch64 など CPU アーキテクチャをまたいだコンパイルをクロスコンパイルと言ったりするがそれとは別なことに注意) このクロスビルドという概念は、同ブランチ内で中長期に渡って色々な軸のマイグレーションを可能とすることから、Bazel においても有用なものじゃないかと思っている。 例えば現行のモノリポが Scala 2.12 だとして、徐々にマイグレーションを行ってほとんどのターゲットが Scala 2.12 と Scala 2.13 の両方でビルド可能な状態へ持っていく。 これは、一部のチームが全社に先行して新バージョンを試しつつ、コードベースとしては普通に進んでいくことができる。 local_repository ハック 先週、@ianocさんに Bazel でクロスビルドを可能とする機構を教えてもらった。 僕たちがやったのは Python の外部ライブラリの切り替えだが、本稿では Scala のクロスビルドを実装する。 (思い出すと去年、Long Cao さんがバーに入る行列で待っている間にこの説明を試みてくれた気がするが、当時はこのテクニックが非常に強力なものだと僕がイマイチ理解できなかった。) まず基本を先に言うと、ルートの WORKSPACE 内でサブディレクトリを参照する local_repository を宣言して、入れ子ワークスペースを作る。 実行時に --override_repository オプションを使って、これを別のワークスペースへとオーバーライドする。 このローカル・リポジトリは定数、マクロ、ファイルを含むターゲットなどを公開することができ、これを使うことで何でもオーバーライドできるはずだ。

Twitter での 2年

僕は Twitter社の Build/Bazel Migration チームでスタッフ・エンジニアとして勤務していた。信じられないような 2年の後、2022年11月17日をもって退職した (企業買収後のレイオフでも任意でもあんまり関係無いが、僕は任意退職希望のオファーを取った)。Twitter社は、切磋琢磨、多様性、そして Flock を構成する全ての人に対して溢れ出る優しさというかなり特別な文化を持った職場だった。これを間近で経験して、その一員となる機会を得たことに感謝している。(Flock は「鳥の群れ」の意で、社内での Twitter社の通称)

image1

以下は過去2年の簡単な振り返りだ。尚本稿での情報は、既に公開されているトークやデータに基づいている。買収後、うちのチームだけでも 10名以上のメンバーが Twitter社を抜けたので、在籍・元含め LinkedIn プロファイルへのリンクを本稿各所に貼った。

テストの粒度

sbt、Bazel、その他多くのビルドツールにおいて、「テスト」という用語が多様なレベルにまたがることが多いため、 それを曖昧無く定義しておくことは特に事前、事後処理、並列処理などを考えるときに役に立つのではないかと思う。 先に書いてしまうと、テストには以下の 4つのレベルがある: テスト・コマンド テスト・モジュール テスト・クラス テスト・メソッドまたはテスト式 コマンドライン・インターフェイスとしてのテスト 最上位のレベルはビルド・ツールがユーザに test コマンドととして提供するものだ。 ユーザが sbt シェルに test と打ち込むか、ターミナルから sbt --client test と打ち込むと、sbt のコマンド・エンジンは「test」を集約リストに列挙されたサブプロジェクト内でのタスク実行へと持ち上げる。 例えば root サブプロジェクトが core と util というサブプロジェクトを集約する場合、test は root/Test/test、core/Test/test、util/Test/test の並列実行だと解釈される。 僕はこの振る舞いをコマンド・ブロードキャストと呼んでいる。 Bazel ではこのブロードキャストはより明示的にユーザによって行われる。 例えば、ユーザが bazel testl example/... と打ち込むと、Bazel は example1/ ディレクトリ以下の全てのテスト・ターゲットを再帰的にクエリして、発見されたテスト・ターゲットを並列的にテストする。 モジュールとしてのテスト 共通しているのは「コマンドとしてのテスト」がテストモジュールを集約して、それらを並列実行することだ。 sbt は典型的にテスト・モジュールをサブプロジェクトと Test コンフィグレーションのペアとして表す。 Bazel はテスト・モジュールを scala_test(...) のような何らかのターゲットとして表す。 Bazel は rules_scala の scala_test_suite(...) のような名前付きのテスト集約も提供する。 Bazel に関して少し補足しておくと、テスト・モジュールの処理は非常に優秀だということだ。 デフォルトでテストの結果はキャッシュされ、キャッシングはリモート・キャッシュへと設定することができ、 実行環境をリモート・マシンへと設定することもできる。 そのため、ラップトップ上から環境を整えれば何百ものジョブを起動することができる。 また、ターゲットは従来のビルドツールよりも細かく作られ、理論上 .scala ファイルごとに scala_test(...) ターゲットを宣言して(別のマシンで)並列実行することができる。 クラスとしてのテスト JUnit、MUnit、ScalaTest、Specs2、Hedgehog、Verify などの JVM テストフレームワークは関連するテスト・メソッドをクラスやオブジェクトを用いてグループ化する。 Scala では、これらのテスト・クラスが FunSuite のように「suite」と名付けられることがあるが、JUnit における Suite は 複数のテスト・クラスを集約する特殊なテスト・クラスを指す。

GitHub Actions からの JDK 17

Ólaf さんの olafurpg/setup-scala を使ってプロジェクトを JDK 17 でテストする簡単な解説をしてみる。Setting up GitHub Actions with sbt でドキュメント化されている以下の設定をスタート地点とする。 name: CI on: pull_request: push: jobs: test: strategy: fail-fast: false matrix: include: - os: ubuntu-latest java: 11 jobtype: 1 - os: ubuntu-latest java: 11 jobtype: 2 - os: ubuntu-latest java: 11 jobtype: 3 runs-on: ${{ matrix.os }} steps: - name: Checkout uses: actions/checkout@v1 - name: Setup uses: olafurpg/setup-scala@v13 with: java-version: "adopt@1.${{ matrix.java }}" - name: Build and test run: | case ${{ matrix.

Scala 3 マクロ入門

はじめに マクロは楽しくかつ強力なツールだが、使いすぎは害もある。責任を持って適度にマクロを楽しんでほしい。 マクロとは何だろうか? よくある説明はマクロはコードを入力として受け取り、コードを出力するプログラムだとされる。それ自体は正しいが、map {...} のような高階関数や名前渡しパラメータのように一見コードのブロックを渡して回っている機能に親しんでいる Scala プログラマには「コードを入力として受け取る」の意味が一見分かりづらいかもしれない。 以下は、僕が Scala 3 にも移植した Expecty という assersion マクロの用例だ: scala> import com.eed3si9n.expecty.Expecty.assert import com.eed3si9n.expecty.Expecty.assert scala> assert(person.say(word1, word2) == "pong pong") java.lang.AssertionError: assertion failed assert(person.say(word1, word2) == "pong pong") | | | | | | | ping pong false | ping pong Person(Fred,42) at com.eed3si9n.expecty.Expecty$ExpectyListener.expressionRecorded(Expecty.scala:35) at com.eed3si9n.expecty.RecorderRuntime.recordExpression(RecorderRuntime.scala:39) ... 36 elided 例えば assert(...) で名前渡しの引数を使ったとしたら、その値を得るタイミングは制御できるが false しか得ることができない。一方マクロでは、person.say(word1, word2) == "pong pong" というソースコードの形そのものを受け取り、全ての式の評価値を含んだエラーメッセージを自動生成するということができる。頑張って書こうと思えば Predef.assert(...) を使っても手でこのようなエラーメッセージを書くことができるが、非常に退屈な作業となる。マクロの全貌はこれだけでは無い。 よくありがちな考え方としてコンパイラはソースコードをマシンコードへと翻訳するものだとものがある。確かにそういう側面もあるが、コンパイラは他にも多くの事を行っている。型検査 (type checking) はそのうちの一つだ。バイトコード (や JS) を最後に生成する他に、Scala コンパイラはライトウェイトな証明システムとして振る舞い、タイポや引数の型合わせなど様々なエラーを事前にキャッチする。Java の仮想機械は、Scala の型システムが何を行っているかをほとんど知らない。この情報のロスは、何か悪いことかのように型消去とも呼ばれるが、この型とランタイムという二元性によって Scala が JVM、JS、Native 上にゲスト・プログラミング言語として存在することができる。

酢鶏、パート1

実験的 sbt として、酢鶏 (sudori) という小さなプロジェクトを作っている。当面の予定はマクロ周りを Scala 3 に移植することだ。sbt のマクロを分解して、土台から作り直すという課題だ。これは Scala 2 と 3 でも上級者向けのトピックで、僕自身も試行錯誤しながらやっているので、覚え書きのようなものだと思ってほしい。 参考: Scala 3 Reference: Metaprogramming Convert 何にも依存していない基礎となる Convert というものを特定できた。 abstract class Convert { def apply[T: c.WeakTypeTag](c: blackbox.Context)(nme: String, in: c.Tree): Converted[c.type] .... } Tree を受け取って Converted という抽象データ型を返す部分関数の豪華版みたいなものに見える。Converted は、以下のように型パラメータとして [C <: blackbox.Context with Singleton] を取る: final case class Success[C <: blackbox.Context with Singleton]( tree: C#Tree, finalTransform: C#Tree => C#Tree ) extends Converted[C] { def isSuccess = true def transform(f: C#Tree => C#Tree): Converted[C] = Success(f(tree), finalTransform) } このように直接 Tree、つまり抽象構文木 (AST) を扱う古い Scala 2 マクロの実装の典型的な例だが、Scala 3 ではもっと綺麗に高度なレベルでメタプログラミングを行う仕掛けとして inline などがあるので、そこから始めるのを通常は推奨される。

sudori part 2

実験的 sbt として、酢鶏 (sudori) という小さなプロジェクトを作っている。当面の予定はマクロ周りを Scala 3 に移植することだ。sbt のマクロを分解して、土台から作り直すという課題だ。これは Scala 2 と 3 でも上級者向けのトピックで、僕自身も試行錯誤しながらやっているので、覚え書きのようなものだと思ってほしい。これはそのパート2だ。 参考: Scala 3 Reference: Metaprogramming 酢鶏、パート1 Instance build.sbt マクロと言われて思いつくのは .value を使った Applicative do マクロなんじゃないかと思う。呼び方としては、そうは呼ばない人もいるかもしれないが。この命令型から関数型への変換を担っているのは、ちょっと変わった名前を持つ Instance class のコンパニオンだ: /** * The separate hierarchy from Applicative/Monad is for two reasons. * * 1. The type constructor is represented as an abstract type because a TypeTag cannot represent a type constructor directly. * 2. The applicative interface is uncurried. */ trait Instance { type M[x] def app[K[L[x]], Z](in: K[M], f: K[Id] => Z)(implicit a: AList[K]): M[Z] def map[S, T](in: M[S], f: S => T): M[T] def pure[T](t: () => T): M[T] } trait MonadInstance extends Instance { def flatten[T](in: M[M[T]]): M[T] } Scaladoc でも言及されているが、sbt は内部に独自の Applicative[_] 型クラスを定義している。Mark が 2012年 (Scala 2.

Bintray から JFrog Artifactory へのマイグレーションと sbt 1.5.1

sbt 1.5.1 パッチリリースをアナウンスする。リリースノートの完全版はここにある - https://github.com/sbt/sbt/releases/tag/v1.5.1 。本稿では Bintray から JFrog Artifactory へのマイグレーションの報告もする。 Bintray から JFrog Artifactory へのマイグレーション まずは JFrog社に、sbt プロジェクトおよび Scala エコシステムへの継続的なサポートをしてもらっていることにお礼を言いたい。 sbt がコントリビューター数とプラグイン数において伸び盛りだった時期に Bintray の形をした問題があった。個人のコントリビューターに Ivy レイアウトのレポジトリを作って、sbt プラグインを公開して、しかし解決側では集約したいという問題だ。GitHub の sbt オーガニゼーションでプラグインのソースを複数人で流動的に管理することができるようになったが、バイナリファイルの配信は課題として残っていた。当時は sbt のバージョンもよく変わっていたというのがある。2014年に Bintray を採用して、成長期の配信メカニズムを担ってくれた。さらに僕たちは sbt の Debian と RPM インストーラーをホスティングするのに Bintray を使っていて、これは Lightbend 社が払ってくれている。 2021年2月、JFrog は Bintray サービスの終了をアナウンスした。その直後から、JFrog 社は向こうからコンタクトしてきて、何回もミーティングをスケジュールしてくれたり、open source sponsorship をグラントしてくれたり、マイグレーション用のツールキットをくれたりとお世話になっている。 今現在 Scala Center にライセンスされ、JFrogがスポンサーしてくれたクラウド・ホストな Artifactory のインスタンスが稼働している。「Artifactory のインスタンス」と何度も書くのが長いので、本稿では Artsy と呼ぶ。sbt 1.5.1 がリリースされたことで、マイグレーションは完了したと思う。 read 系 4月18日の時点で全ての sbt プラグインと sbt 0.

猫番: 19日目

猫番: 19日目。FunctionK という Rúnar さんによるランク2多相性のエンコーディング、そして高ランク多相が可能にすると予見された Resource データ型に関して。

統一スラッシュ構文のための syntactic Scalafix rule

sbt 1.1.0 で僕は統一スラッシュ構文を実装した。それから数年経った今日になって、古い sbt 0.13 でのシェル構文を廃止勧告するための pull request を送った。#6309 成り行きとして、build.sbt のための旧構文も廃止勧告にするという話題が出てきた。 will you also deprecate `scalacOptions in (Compile, console)` in *.sbt and *.scala files? I hope so — Seth Tisue (@SethTisue) February 16, 2021 「統一」スラッシュ構文がそう名付けられたのはシェル構文とビルド定義構文を統一するからだ。そのため、シェルの旧構文を廃止勧告するならば、skip in publish や scalacOptions in (Compile, console) というふうに in を使う旧 build.sbt 構文も同時に廃止勧告するというのは理にかなっている。 build.sbt を統一スラッシュ構文へと変換する syntactic Scalafix rule をちゃちゃっと作ったのでここで紹介する - https://gist.github.com/eed3si9n/57e83f5330592d968ce49f0d5030d4d5 用法 プロジェクトを git で管理するか、バックアップを取ること。 $ cs install scalafix $ export PATH="$PATH:$HOME/Library/Application Support/Coursier/bin" $ scalafix --rules=https://gist.githubusercontent.com/eed3si9n/57e83f5330592d968ce49f0d5030d4d5/raw/7f576f16a90e432baa49911c9a66204c354947bb/Sbt0_13BuildSyntax.scala *.

scala/scala の git bisect

git bisect はバグの入った場所を特定するのに有用なテクニックだ。 特に scala/scala の場合は、bisect.sh はビルド済みのコンパイラを Scala CI Artifactory から利用することで時間を節約できる。

sbt-strict-update を用いた Semantic Versioning の施行

Rob wrote: I want to tell sbt “this specific version breaks binary compatibility, so don’t resolve it via eviction, fail the build instead.” How do I do this? Complete answers only, I’m done trying to figure it out by following clues. sbt に「この特定のバージョンはバイナリ互換性を壊すからバージョンの解決をしないでビルドを失敗して」と言いたい。これはどうやるんだろうか? ヒントを追うのに疲れたので、完全な回答のみ募集。 これを行う小さな sbt プラグイン sbt-strict-update を書いた。 project/plugins.sbt に以下を追加: addSbtPlugin("com.eed3si9n" % "sbt-strict-update" % "0.1.0") そして build.sbt にこれを書く: ThisBuild / libraryDependencySchemes += "org.typelevel" %% "cats-effect" % "early-semver" それだけだ。 ThisBuild / scalaVersion := "2.

GitHub Actions からの sbt プラグインの自動公開

本稿は前に書いたTravis-CI からの sbt プラグインの自動公開の GitHub Actions 版だ。 Ólaf さんの olafurpg/sbt-ci-release を使って sbt プラグインのリリースを自動化してみる。sbt-ci-release の README は Sonatype OSS 向けの普通のライブラリのリリースを前提に書かれている。sbt プラグインのリリースに必要な差分以外の詳細は README を参照してほしい。 リリースを自動化することそのものがベスト・プラクティスだが、sbt プラグインのリリースに関連して特に嬉しいことがある。この方法を使うことで Bintray の sbt organization にユーザーを追加せずに、複数人で sbt プラグインのリリース権限を共有することが可能となる。これは仕事でメンテしているプラグインがあるときに便利だ。 step 1: sbt-ci-release sbt-release を使っている場合は削除する。sbt-ci-release を追加する。 addSbtPlugin("org.foundweekends" %% "sbt-bintray" % "0.6.1") addSbtPlugin("com.geirsson" % "sbt-ci-release" % "1.5.4") addSbtPlugin("com.jsuereth" % "sbt-pgp" % "2.1.1") // for gpg 2 version.sbt も削除する。 step 2: -SNAPSHOT version sbt-dynver を多少抑えて、タグの付いていないコミットで -SNAPSHOT バージョンを使えるようにする: ThisBuild / dynverSonatypeSnapshots := true ThisBuild / version := { val orig = (ThisBuild / version).

scopt 4

本稿は 2018年12月に 4.0.0-RC2 と共に初出した。2020年11月にリリースした 4.0.0 での変更を反映して更新してある。 最近 scopt 4.0 を書いている。せっかちな人は readme に飛んでほしい。 4.0.0 を試すには以下を build.sbt に書く: libraryDependencies += "com.github.scopt" %% "scopt" % "4.0.0" scopt 4.0.0 は以下のビルドマトリックスに対してクロスパブリッシュされている: Scala JVM JS (1.x) JS (0.6.x) Native (0.4.0-M2) Native (0.3.x) 3.0.0-M2 ✅ ✅ n/a n/a n/a 3.0.0-M1 ✅ ✅ n/a n/a n/a 2.13.x ✅ ✅ ✅ n/a n/a 2.12.x ✅ ✅ ✅ n/a n/a 2.11.x ✅ ✅ ✅ ✅ ✅ scopt はコマンドラインオプションをパースするための小さなライブラリだ。2008年に aaronharnly/scala-options として始まり、当初は Ruby の OptionParser を緩めにベースにしたものだった。scopt 2 で immutable parsing を導入して、scopt 3 では Read 型クラスを使ってメソッド数を大幅に減らすことができた。

Bintray を用いた sbt ビルドのリモートキャッシュ

sbt と Zinc 1.4.x 系列で僕が時間と力をかけたのはおそらくファイルの仮想化とタイムスタンプを抜き出すことだ。この組み合わせによりマシン特定性と時から Zinc の状態を解放することができ、Scala のための差分リモートキャッシュを構築するための礎となる。これに関してsbt でのコンパイルキャッシュを書いた。これはその続編となる。 sbt 1.4.x が出たので、この機能を実際に使ってみたいという気運が一部高まっている。 リモートキャッシュサーバー リモートキャッシュを運用するには、リモートキャッシュサーバーが必要となる。初期のロールアウトでは、追加でサーバーを用意せずに簡単に試せるように Maven リポジトリ (MavenCache("local-cache", file("/tmp/remote-cache")) を含む) と互換を持たせるようにした。次のステップはこのリモートキャッシュをマシン間で共有することだ。 とりあえず JFrog Bintray は Maven リポジトリとして振る舞うことができるという意味で良いフィットなんじゃないかと思う。Bintray に publish を行うには RESTful API を経由する必要があって、それは sbt-bintray がカプセル化している。 ちなみに Bazel は HTTP プロトコルか gRPC を用いたリモートキャッシュのサポートを提供し、これは Nginx、bazel-remote、Google Cloud Storage、その他 HTTP を話せるモノなら何でも実装できる。ライブラリ依存性と違って特に resolve する必要が無いので将来的にはそのような方向に移行するのが良いと思う。 sbt-bintray-remote-cache 今すぐリモートキャッシュを使ってみたいという人のために、sbt-bintray のスピンオフとして sbt-bintray-remote-cache というプラグインを作った。 使うには以下を project/plugins.sbt に追加する: addSbtPlugin("org.foundweekends" % "sbt-bintray-remote-cache" % "0.6.1") Bintray リポとパッケージ 次に、https://bintray.com/<your_bintray_user>/ に行って、新しい Generic なリポジトリを remote-cache という名前で作る。通常のアーティファクトとキャッシュが混ざらないようにするために、これは大切なステップだ。 それから、remote-cache リポジトリ内にパッケージを作る。基本的には 1つのビルドに対して 1つのパッケージを作る。

sbt 1.4.1

sbt 1.4.1 パッチリリースをアナウンスする。リリースノートの完全版はここにある - https://github.com/sbt/sbt/releases/tag/v1.4.1 アップグレード方法 公式 sbt ランチャーを SDKMAN か https://www.scala-sbt.org/download.html からダウンロードしてくる。このインストーラーには sbtn のバイナリが含まれている。 次に、使いたいビルドの project/build.properties ファイルに以下のように書く: sbt.version=1.4.1 この機構によって使いたいビルドにだけ sbt 1.4.1 が使えるようになっている。 主な変更点 @eatkins さんによる read line とか文字処理まわりの様々な変更。例えば、sbt new で文字がエコーされてこない問題など。 Scala.JS での Scala 2.13-3.0 サンドイッチの修正 #5984 by @xuwei-k shellPrompt とか release* キーなど build lint 時の警告の修正 #5983/#5991 by @xirc and @eed3si9n plugins コマンドの出力をサブプロジェクトで分けるようにした改善 #5932 by @aaabramov その他は https://github.com/sbt/sbt/releases/tag/v1.4.1 を参照 参加 sbt 1.4.1 は 9名のコントリビューターにより作られた。 Ethan Atkins, Eugene Yokota (eed3si9n), Adrien Piquerez, Kenji Yoshida (xuwei-k), Nader Ghanbari, Taichi Yamakawa, Andrii Abramov, Guillaume Martres, Regis Desgroppes。この場をお借りしてコントリビューターの皆さんにお礼を言いたい。また、これらのコントリのいくつかは ScalaMatsuri 2020 中のハッカソンにて行われた。

ScalaMatsuri 2020 におけるハッカソンの仮想化

本稿は ScalaMatsuri Day 2 アンカンファレンスで OSS ハッカソンを仮想化したことのレポートだ。誰かがアンカンファレンスのトピックとして提案したらしく、僕は朝会でファシリテーターとして申し出ただけなので事前準備は特に無し。元々は 4時間 (JST 正午 - 4pm、EDT 11pm - 3am) で枠をもらったが、うまく回ったのでコーヒーブレイクの後も数時間続いた。 アンカンファレンスをやるときにいつも強調してるのは「二本足の法則」で: いつでも自分にとってその場からの「学び」や自分から場への「貢献」が無いなと感じた場合: 自分の二本足を使って別の場へ移動すること オンラインのアンカンファレンスで、複数のセッションが行われているので別のトークを見るために抜けたり途中から参加することは自由であることを事前に伝えた。 使ったもの Zoom Meeting Discord Google Docs 主なコミュニケーションは ScalaMatsuri が用意していた Zoom Meeting を使った。これで異なる参加者が自分の画面を共有したり質問をしたりできる。潜在的な問題としては、全員が他の人全員を聞こえる状態になるので、複数のグループが同時にペアプログラムをしたいといった状況には向かない。 テキストベースのコミュニケーションとしては Discord を使った。Discord はリンクを共有したり、質問をしたりにも使う。僕たちはやらなかったが、Discord のボイスチャンネルを使って画面の共有も可能なのでプロジェクト毎にボイスチャンネル分かれるという使う方もできると思う。 プロジェクトと GitHub issue の列挙、どの作業をしたりのかのサインアップには Google Doc 一枚を使った。 流れ メンターをできるプロジェクトメンテナの人が参加してるかを聞く プロジェクトメンテナは他の人が手を付けやすい good first な GitHub issue を Google doc に書いて、Zoom でその簡単な説明をする。 参加者は issue の隣に自分の名前を書いてサインアップする (ペアで一つの issue に取り組むことも可) プロジェクトメンテナは単体テストと統合テスト (scala/scala だと partest、sbt/sbt だと scripted) の走らせ方を解説 自分も共同作業する場合はプロジェクトメンテナはもっとチャレンジングなタスクを提案してもいい 人の出入りがあるので、上記をリピート 基本的にはミュートしてハック ファシリテーターは、皆が作業するものがあるかどうかの確認を定期的に行う 誰かがタスクを完了したら成功でも失敗でも Zoom で軽く発表する。(参加者が多い場合はこれは1日の最後にやってもいい) scala/scala Scala のコンパイラや標準ライブラリが開発される scala/scala にコントリビュートに興味がある人が多かった。明らかなバグ修正じゃない場合は scala/scala へのプルリクは数ヶ月放置されたりする可能性もある旨を注意した。

並列クロスビルドサンドイッチ

sbt-projectmatrix は sbt のクロスビルドを改善するために、僕が実験として作っているプラグインで、本稿は第1回、第2回、第3回に続く第4弾だ。0.6.0 をリリースしたのでここで紹介する。 おさらい: 複数の Scala バージョンに対するビルド sbt-projectmatrix をビルドに追加後、以下のようにして 2つの Scala バージョンを使ったマトリックスをセットアップする。 ThisBuild / organization := "com.example" ThisBuild / scalaVersion := "2.12.10" ThisBuild / version := "0.1.0-SNAPSHOT" lazy val core = (projectMatrix in file("core")) .settings( name := "core" ) .jvmPlatform(scalaVersions = Seq("2.12.12", "2.13.3")) これはそれぞれの scalaVersion にサプブロジェクトを作る。 ++ スタイルのステートフルなクロスビルドと違って、これは並列にビルドする。これは変わっていない。 前回では % を使って依存性をスコープ付けできることを紹介した。 0.6.0 での新機能: よりシンプルなプロジェクトID JVM2_13 というサフィックスを追加する代わりに、sbt-projectmatrix 0.6.0 より JVM 軸と 2_13 軸はデフォルトとして、coreJVM2_13 でなはく普通に core とか util という名前のサブプロジェクトを生成することにした。 0.6.0 での新機能: 2.

Twitter に入社しました

本日付けで Twitter の Build Team に入社しました。世界中にいる Twitter 社のデベロッパーをサポートする次世代ビルド・システムの構築に関わることになります。 このチームはモノリポ・ビルドツールである Pants の開発に関わっていて、社内のシステムを Bazel へ移行させるのが当面の責務となると思います。デベロッパー・エクスペリエンスや開発効率ということに関して熱い思いを持っているチームと一緒に働けるという、僕が願っていた仕事なので、チームの人たちや新しい課題との出会いを楽しみにしています。 また、この場を借りて過渡期に DM などで、大丈夫にしてるかとか、社内で僕のことを推薦してくれたり、プロジェクトのオファーなど色々声をかけてくれた皆さんに感謝したいです。そんな声があったので元気にやってこれました。ありがとうございます。4月に義務サバティカルが始まってから、これまで時間が無くてできなかったビルドキャッシュとか Selective functor みたいな作業をしたり、Scala Center の素晴らしい方々とコラボすることができたので、そういう意味では色々良かったなと思います。 EE Build team は「San Francisco, Remote US」というロケーションからまだ募集中みたいなので、興味のある人は応募して一緒に作業しましょう。

Travis-CI からの sbt プラグインの自動公開

本稿では Ólafur さんの olafurpg/sbt-ci-release を使って sbt プラグインのリリースを自動化してみる。sbt-ci-release の README は Sonatype OSS 向けの普通のライブラリのリリースを前提に書かれている。sbt プラグインのリリースに必要な差分以外の詳細は README を参照してほしい。 リリースを自動化することそのものがベスト・プラクティスだが、sbt プラグインのリリースに関連して特に嬉しいことがある。この方法を使うことで Bintray の sbt organization にユーザーを追加せずに、複数人で sbt プラグインのリリース権限を共有することが可能となる。これは仕事でメンテしているプラグインがあるときに便利だ。 step 1: sbt-ci-release sbt-release を使っている場合は削除する。sbt-ci-release を追加する。 addSbtPlugin("org.foundweekends" %% "sbt-bintray" % "0.5.6") addSbtPlugin("com.geirsson" % "sbt-ci-release" % "1.5.3") version.sbt も削除する。 step 2: -SNAPSHOT version sbt-dynver を多少抑えて、タグの付いていないコミットで -SNAPSHOT バージョンを使えるようにする: ThisBuild / dynverSonatypeSnapshots := true ThisBuild / version := { val orig = (ThisBuild / version).value if (orig.endsWith("-SNAPSHOT")) "2.2.0-SNAPSHOT" else orig } step 3: sbt-bintray セッティングを復活させる プラグインは通常 sbt-bintray を使ってリリースするので、publishTo を bintray / publishTo に戻す。publishMavenStyle を false にする。

sbt 1.3.12

sbt 1.3.12 パッチリリースをアナウンスする。リリースノートの完全版はここにある - https://github.com/sbt/sbt/releases/tag/v1.3.12 。 特に Scala Center にお礼を言いたい。バグ報告、pull request レビュー、コントリビューションがちゃんと正しい所に行くかなどメンテ活動を行うにはある程度時間がかかるが、5月中の sbt のメンテ活動は Scala Center がスポンサーしてくれた。Daryja さん始め Scala Center のメンバーは皆気軽に共同作業しやすい人たちだ。 sbt 1.3.11 からの変更点 sbt 1.3.11 で launcher 統合周りにリグレッションがあり、repositories ファイルが無視されるという形のバグが出た。sbt 1.3.12 はそれを修正する。 #5583 アップグレード方法 通常は project/build.properties を sbt.version=1.3.12 と書き換えるだけで ok だ。しかし、リリースにスクリプトの修正が含まれている場合もあり、また全ての JAR ファイルが予め入った *.(zip|tgz|msi) を使ったほうが初回の依存性解決が速くなるためインストーラーを使ったインストールを推奨する。インストーラーは SDKMAN などに公開される。 sdk upgrade sbt Homebrew に関する注意 Homebrew のメンテナはもっと brew 依存性を使いたいという理由で JDK 13 への依存性を追加した brew#50649。そのため、PATH が通っている java が JDK 8 や 11 であっても sbt が JDK 13 で実行されるようになってしまう。

sbt 1.3.11

sbt 1.3.11 パッチリリースをアナウンスする。リリースノートの完全版はここにある - https://github.com/sbt/sbt/releases/tag/v1.3.11 。 特に Scala Center にお礼を言いたい。バグ報告、pull request レビュー、コントリビューションがちゃんと正しい所に行くかなどメンテ活動を行うにはある程度時間がかかるが、5月中の sbt のメンテ活動は Scala Center がスポンサーしてくれた。Daryja さん始め Scala Center のメンバーは皆気軽に共同作業しやすい人たちだ。 アップグレード方法 通常は project/build.properties を sbt.version=1.3.11 と書き換えるだけで ok だ。しかし、リリースにスクリプトの修正が含まれている場合もあり、また全ての JAR ファイルが予め入った *.(zip|tgz|msi) を使ったほうが初回の依存性解決が速くなるためインストーラーを使ったインストールを推奨する。インストーラーは SDKMAN などに公開される。 sdk upgrade sbt Homebrew に関する注意 Homebrew のメンテナはもっと brew 依存性を使いたいという理由で JDK 13 への依存性を追加した brew#50649。そのため、PATH が通っている java が JDK 8 や 11 であっても sbt が JDK 13 で実行されるようになってしまう。 sbt が JDK 13 で実行するのを回避するには jEnv をインストールするか、SDKMAN に乗り換える必要がある。 主な変更点 sbt 1.

Jar Jar Abrams

Jar Jar Abrams は、Java ライブラリをシェーディングするユーティリティである Jar Jar Links の実験的 Scala 拡張だ。 ライブラリ作者にとって他のライブラリは諸刃の剣だ。他のライブラリを使うことは作業の二重化を避け、他のライブラリを使いたくないというのはダブルスタンダードと言われかねない。しかし、その一方で、ライブラリを追加する度にそれはユーザ側にすると間接的依存性が追加されたことになり、衝突が起こる可能性も上がることになる。これは単一のプログラム内において 1つのバージョンのライブラリしか持てないことにもよる。 このような衝突はプログラムがランタイムやフレームワーク上で実行される場合によく起こる。sbt プラグインがその例だ。Spark もそう。1つの緩和策として間接的ライブラリを自分のパッケージの中にシェーディングするという方法がある。2004年に herbyderby (Chris Nokleberg) さんは Jar Jar Links というライブラリを再パッケージ化するツールを作った。 2015年に Wu Xiang さんが Jar Jar Links を使ったシェーディングのサポートを sbt-assembly に追加した。これは前向きな一歩だったが、課題も残っていた。問題の 1つは Scala コンパイラは ScalaSignature 情報を *.class ファイルに埋め込むが、Jar Jar がそのことを知らないことだ。2020年になって Simacan社の Jeroen ter Voorde さんが ScalaSignature の変換を sbt-assembly#393 にて実装した。sbt 以外でもこれは役に立つかもしれないので、独立したライブラリに抜き出した。これが Jar Jar Abrams だ。 core API コアには shadeDirectory 関数を実装する Shader オブジェクトがある。 package com.eed3si9n.jarjarabrams object Shader { def shadeDirectory( rules: Seq[ShadeRule], dir: Path, mappings: Seq[(Path, String)], verbose: Boolean ): Unit = .

sbt での Selective ファンクター

sbt コア・コンセプトのトークをするとき僕は sbt をカジュアルに関数型なビルド・ツールと言っている。関数型プログラミングの 2つの特徴としてデータを変化させるのではなく immutable (不変)なデータ構造を使うことと、いつ、どのようにして effect (作用) を取り扱うかに気を使っていることが挙げられる。 セッティングとタスク その観点から見ると、セッティング式とタスクはその 2点に合致していると考えることができる: セッティング列はビルドの不変グラフを形成する。 タスクは作用を表す。 匿名セッティングは Initialize[A] で表され、以下のようになっている: sealed trait Initialize[A] { def dependencies: Seq[ScopedKey[_]] def evaluate(build: BuildStructure): A // approx .... } 名前の付いたセッティングは Setting クラスで表される: sealed class Setting[A] private[Init] ( val key: ScopedKey[A], val init: Initialize[A], val pos: SourcePosition ) .... sbt.Task は副作用関数 () => A のラッパーだと便宜的に考えていい。ただし、僕たちが「compile はタスクだ」と言うとき、の文脈でのタスクは Initialize[Task[A]] で表される。つまり、これは Task[A] 型を返すセッティングだ。 これは Def.task の戻り型 Def.Initialize[Task[A]] を見ることで確認することができる。 Applicative 合成 Def.

sbt で約束 (promise) を守る

build.sbt は、自動的な並列処理を行うタスク・グラフを定義するための DSL だ。タスク同士のメッセージ・パッシングは something.value マクロで表され、これは Applicative 合成 (task1, task2) mapN { case (t1, t2) => .... } をエンコードする。 長く走っている task1 があるとき、途中で task2 と通信できる仕組みがあればいいと思っていた。 通常は task1 をサブタスクに分けることでこれを解決する。しかし、それを実装するのは一筋縄ではいかないこともある。例えば、Zinc に半分だけコンパイルして、残りは後で続けて下さい? もしくは Coursier に解決だけ行って実際のダウンロードは後でとどう言えばいいだろうか? たたき台として task1 が何らかの JSON ファイルを生成して、task2 はファイルが現れるまで待って、それを読み込むというやり方が考えられる。JSON ファイルの代わりに Promise[A] のような並行データ構造を使って改善することができる。しかし、待機という厄介なものが残っている。sbt は並列に実行するタスクの数を限っているので、待機のために枠を使うのはもったいない。Daniel さんの Thread Pools にこの辺りの事が良くまとまっている。今回あるのは差し当たり作業を一切行わないブロッキング IO ポーリングと考えることができると思う。 Def.promise scala.concurrent.Promise のラッパーを実装して Def.promise と呼んだ。具体例で説明する: val midpoint = taskKey[PromiseWrap[Int]]("") val longRunning = taskKey[Unit]("") val task2 = taskKey[Unit]("don't call this from shell") val joinTwo = taskKey[Unit]("") // Global / concurrentRestrictions := Seq(Tags.

sbt でのコンパイルキャッシュ

Google のビルドインフラ Blaze (現在は Bazel としてオープンソース化されている) のことを知ってから Scala のツールチェインにも似たような仕組みが欲しいとずっと思い続けてきた。これは特に独創的な発想という訳では無く、Peter Vlugter さんと Ben Dougherty さんの nailgun Zinc での機能 (Pants で使われていた?) や、Krzysztof Romanowski さんの Hoarder など先行研究もある。それらは、作業ディレクトリに合わせて Zinc Analsis ファイル内に格納されている絶対パスを変換するというアイディアから成り立っている。 僕の作業の詳細に入る前に、問題スペースをざっとデモしよう。 ビルドのマシン依存性 Akka の akka-actor/compile を sbt 1.3.10 でビルドするとこのようになる: cd ~/work/quicktest/ git clone git@github.com:akka/akka.git akka-0 cd akka-0 sbt akka > akka-actor/compile [info] Generating 'Tuples.scala' [info] Generating 'Functions.scala' [info] Updating [info] Resolved dependencies [info] Updating [info] Formatting 22 Java sources... [info] Reformatted 0 Java sources [info] Compiling 191 Scala sources and 28 Java sources to /Users/eed3si9n/work/quicktest/akka-0/akka-actor/target/scala-2.

Zinc 1.4.0-M1

Zinc 1.4.0-M1 をリリースした。これはベータ版であって将来の 1.4.x との互換性は保証されないことに注意してほしい。ただ、1.3.x と比較的近いコミットを選んだので実用性は高いはずだ。 Zinc を Scala 2.12 と 2.13 へとクロスビルドした zinc#754 by @eed3si9n ScalaPB を 0.9.3 へとアップグレードした zinc#713 by @slandelle ZipUtils 内での java.util.Date の使用を java.time 系へと置き換えた zinc#714 by @slandelle Zinc は Scala のための差分コンパイラだ。Zinc は Scala 2.10 ~ 2.13 と Dotty をコンパイルすることが可能だが、これまでの所 Zinc そのものは Scala 2.12 で実装されてきた。これは Scala 2.12 で実装されている sbt 1.x としては問題無いが、Zinc を 2.13 でもクロスビルドして欲しいという要望は前からあった。 どうやら Gatling は Zinc をライブラリとして使っているらしく、Gatling のコア開発者の Stephane Landelle さんはアップデートに必要なパッチを送ってくれた。最後に僕がする必要があった作業は入り組んだサブプロジェクトを解きほぐして再配線することだが、それには僕が昨日書いた sbt-projectmatrix を使った。 Li Haoyi さんも Mill を Scala 2.

並列クロスビルド、パート3

sbt-projectmatrix は sbt のクロスビルドを改善するために、僕が実験として作っているプラグインで、本稿は前々回、前回に続く第3弾だ。0.5.0 をリリースしたのでここで紹介する。 おさらい: 複数の Scala バージョンに対するビルド sbt-projectmatrix をビルドに追加後、以下のようにして 2つの Scala バージョンを使ったマトリックスをセットアップする。 ThisBuild / organization := "com.example" ThisBuild / scalaVersion := "2.12.10" ThisBuild / version := "0.1.0-SNAPSHOT" lazy val core = (projectMatrix in file("core")) .settings( name := "core" ) .jvmPlatform(scalaVersions = Seq("2.12.10", "2.11.12")) これは coreJVM2_11 と coreJVM2_12 というサブプロジェクトを作る。 ++ スタイルのステートフルなクロスビルドと違って、これは並列にビルドする。これは変わっていない。 前回では列で複数の次元を表現できる VirtualAxis を紹介した。 0.5.0 での新機能 0.4.0 は結構いい線いっていたが、実際に使ってみると不便な点があった。まずは % 構文が無いことだ。 サブプロジェクト間で Test コンフィギュレーションからだけ依存したり、Compile 同士、Test 同士で依存するというのは良くあることだ。0.5.0 は % を追加してこれを可能とする。 lazy val app = (projectMatrix in file("app")) .

Lightbend での6年

2014年3月に Lightbend社 (当時 Typesafe社) に入社した。信じられないような 6年の後、2020年4月7日をもって退職となった。Lightbend、パートナー各社、顧客、そしてカンファレンスなどで出会った色んな人とつながりを持ったり一緒に作業する機会をもらえたのは感謝している。振り返ると COVID-19前の時代でヨーロッパ、アジア、北米などを数ヶ月ごとに飛び回ってカンファレンスに出たり社内合宿を行っていたのが現実離れして感じる。 以下は過去6年の簡単な振り返りだ。 2014 Scala を趣味で始めたのは 2009年の終わり頃なので、2014年の時点では 4年ぐらいは書いていたのではないか。丁度「独習 Scalaz」が終わって、関連するネタで最初の nescala のトークを行った。10個ぐらいの sbt プラグインを作って、Stackoverflow でも良く活動してた。 3月に Lightbend社のツーリングチーム (当時は Typesafe社「Q課」) に入社した。当時のメンバーは Josh Sereth と Toni Cunei。Josh と sbt のメンテをするのは確かに仕事の分担だけども、仕事は戦略もしくは、難関というか、学びの多い顧客ドリブンなものが大半だった。入社した直後に顧客先に国内線で飛んで、Apache Ivy のコードを読んだりプロファイリングしたりしたのを覚えている。最初は面食らったが、すぐに sbt の中ではライブラリ依存性周りが最も慣れようになった。 2014年5月には sbt のバージョン番号を 0.13.2 から 0.13.5 と飛ばして sbt 1.x シリーズのテクノロジーレビューとした。必要な機能を実験的に導入していくことで sbt 1.x との差が大きくなり過ぎないようにするというアイディアだった。 sbt 0.13.6 になって、未解決の依存性のエラーを足りない依存性の木で表示したり、eviction warning、updateOptions での withLatestSnapshots など僕が追加したライブラリ依存性周りの機能が出てくるようになる。 2014年後半には Q課は Typesafe Reactive Platform v1 のためのインフラ作りを行った。これは Toni が実装した Dbuild を元にした商用配布パッケージだ。 2015 2015年3月、Josh と一緒に僕の最初の Scala Days のトーク ‘The road to sbt 1.

ユーザランドでの警告とエラー、パート2

先週は、Scala でユーザランドから警告を出す仕組みの提案である #8820 について書いた。例として ApiMayChange アノテーションを実装した。 package foo import scala.annotation.apiStatus, apiStatus._ @apiStatus( "should DSL is incubating, and future compatibility is not guaranteed", category = Category.ApiMayChange, since = "foo-lib 1.0", defaultAction = Action.Warning, ) implicit class ShouldDSL(s: String) { def should(o: String): Unit = () } これは始めとしては一応使えるけども、少し冗長だ。もしなんらかの API ステータスが頻繁に使われる場合、ライブラリ作者が独自のステータスアノテーションを定義できると嬉しいと思う。今日はその方法を考える。 その前に少し裏方の解説を必要とする。コンパイラがアノテーションを見る時この情報は AnnotationInfo として渡され、引数は構文木で表される。これによってコールサイトのソースコードはあるが、アノテーションのコードがコンストラクタで何かやったなどの事は分からない。一方、アノテーションクラスにタグ付けされたアノテーションのことは分かる。 ApiMayChange の実装再び アノテーションのに付けることを前提に作られたアノテーションはメタアノテーションと呼ばれ、これを使うことで apiStatus の継承を行うことができる: import scala.annotation.{ apiStatus, apiStatusCategory, apiStatusDefaultAction } import scala.annotation.meta._ @apiStatusCategory("api-may-change") @apiStatusDefaultAction(apiStatus.Action.Warning) @companionClass @companionMethod final class apiMayChange( message: String, since: String = "", ) extends apiStatus(message, since = since) category や defaultAction を extends apiStatus(.

Scala におけるユーザランドでのコンパイラ警告

一ライブラリ作者として、Scala でメソッドをタグ付けしてカスタムのコンパイラ警告やエラーを発動できるといいなと前から思っている。何故意図的にコンパイラエラーを出す必要があるのかと思うかもしれない。一つのユースケースとしては、API を廃止した後でマイグレーションのためのメッセージを表示させることだ。 Restligeist macro: n. A macro that fails immediately to display migration message after implementation has been removed from the API. — ∃ugene yokot∀ (@eed3si9n) August 30, 2016 僕はこれを Restligeist macro、つまり地縛霊マクロと呼んでいる。例えば、sbt 1.3.8 において <<= を使うと以下のエラーメッセージが起動時に表示される。 /tmp/hello/build.sbt:13: error: `<<=` operator is removed. Use `key := { x.value }` or `key ~= (old => { newValue })`. See http://www.scala-sbt.org/1.x/docs/Migrating-from-sbt-013x.html foo <<= test, ^ [error] sbt.compiler.EvalException: Type error in expression [error] Use 'last' for the full log.

Giter8 0.12.0

giter8.version

Giter8 0.12.0 に giter8-launcher という小さなアプリを追加した。このアプリの目的は Giter8 テンプレートの振る舞いを予測可能にすることにある。現状だと、テンプレート作者が Giter8 バージョン X を想定してテンプレートを作ったとしてもユーザー側は “sbt new” に同梱される別な Giter8 バージョン Y を使って実行されている。

sbt の良いアイディアの一つにユーザーがどのバージョンの sbt スクリプトをインストールしていてもコアの sbt バージョンはビルド作者が project/build.properties ファイルを使って指定できるというものがある。これによって「自分のマシンでしか動作しない」問題が大幅に改善される。giter8-launcher は sbt における sbt-launcher に同様のものだ。giter8-launcher はテンプレートのクローンして、project/build.properties ファイルを読み込んで、テンプレートのレンダリングに用いる実際の Giter8 バージョンを決定する。

テンプレート作者は project/build.properties ファイルを用いて以下のように Giter8 バージョンを指定できる:

giter8.version=0.12.0

VirtualAxis を用いた並列クロスビルド

sbt-projectmatrix は sbt のクロスビルドを改善するために、僕が実験として作っているプラグインで、本稿は前篇に続く第2弾だ。0.4.0 をリリースしたのでここで紹介する。 おさらい: 複数の Scala バージョンに対するビルド sbt-projectmatrix をビルドに追加後、以下のようにして 2つの Scala バージョンを使ったマトリックスをセットアップする。 ThisBuild / organization := "com.example" ThisBuild / scalaVersion := "2.12.10" ThisBuild / version := "0.1.0-SNAPSHOT" lazy val core = (projectMatrix in file("core")) .settings( name := "core" ) .jvmPlatform(scalaVersions = Seq("2.12.10", "2.11.12")) これは coreJVM2_11 と coreJVM2_12 というサブプロジェクトを作る。 ++ スタイルのステートフルなクロスビルドと違って、これは並列にビルドする。これは変わっていない。 前篇ではこの考え方をクロス・プラットフォームやクロス・ライブラリへと応用させることを考えた。 0.2.0 の問題 Support for mixed-style matrix dependencies #13 と Support for pure Java subprojects #14 という 2つの issue が立てられて、0.

Pamflet 0.8.2

Pamflet は短い文書、特にオープンソース・ソフトウェアの ユーザ・ドキュメントを公開するためのアプリだ。

Pamflet 0.8.2 はモノスペースのタイプフェイスを SFMono へと変更する。また、Blueprint から Bootstrap に移行したときに不意に導入されたピンクの文字色を元に戻す。

依存性解決のセマンティクス

依存性リゾルバー 依存性リゾルバー (dependency resolver)、もしくはパッケージマネージャーは、ユーザーによって与えられた制約の集合を元に矛盾しないモジュールの集合を決定するプログラムだ。通常この制約要件はモジュール名とそれらのバージョン番号を含む。JVM エコシステムにおける Maven モジュールは organization (group id) も指定に用いられる。その他の制約として、バージョン範囲、除外モジュール、バージョンオーバーライドなどもある。 パッケージングは大まかに OS パッケージ (Homebrew、Debian packages など)、特定のプログラミング言語のモジュール (CPAN、RubyGem、Maven など)、特定のアプリケーションのためのエクステンション (Eclipse プラグイン、IntelliJ プラグイン、VS Code extensions など) の 3つのカテゴリーがある。 依存性解決のセマンティクス 考え始めの近似としてモジュール依存性を DAG (有向非巡回グラフ) だと考えることができる。これは依存性グラフ、もしくは “deps graph” と呼ばれる。以下のような 2つのモジュール依存性があるとする: a:1.0。これはさらに c:1.0 に依存する。 b:1.0。これはさらに c:1.0 と d:1.0 に依存する。 +-----+ +-----+ |a:1.0| |b:1.0| +--+--+ +--+--+ | | +<-------+ | | v v +--+--+ +--+--+ |c:1.0| |d:1.0| +-----+ +-----+ a:1.0 と b:1.0 に依存すると、a:1.0、b:1.0、c:1.0、そして d:1.0 が得られる。これは木を歩いているだけだ。 間接依存性にバージョン範囲を含むと状況はもう少し複雑になる。

sbt 1.3.0

皆さんこんにちは。sbt プロジェクトを代表して sbt 1.3.0-RC1 をアナウンスします。これは sbt 1 のフィーチャーリリース第3弾で、バイナリ互換性は維持しつつ新機能にフォーカスを当てたリリースとなっている。sbt 1 は Semantic Versioning にもとづいてリリースされるので、プラグインは sbt 1.x シリーズ中機能することが期待されている。 2019年3月29日までに大きな問題が見つからなければ、1.3.0-RC1 は 1.3.0 final 版となる予定だ。 sbt 1.3 の主な新機能はデフォルトでの Coursier を使ったライブラリ管理、ClassLoader レイヤリング、IO の改善、そして super shell だ。これらの機能の組み合わせがビルドのユーザーエクスペリエンスを向上することを願っている。 互換性に影響のある変更点 Coursier を用いたライブラリ管理。詳細は後ほど。 ClassLoader レイヤリング。詳細は後ほど。 super shell。詳細は後ほど。 マルチコマンドの先頭にセミコロンが要らなくなった。clean;Test/compile; で動作するようになった。 #4456 by @eatkins sbt.internal.inc.ZincUtil 以下の関数で LM を使うものが ZincLmUtil に移動して、Zinc から LM に依存しないようになった。 zinc#655 by @dwijnand Coursier を用いたライブラリ管理 sbt 1.3.0 はライブラリ管理に Coursier を採用する。Coursier は、ライブラリ依存解決を行うもので Ivy に似ているが、より高速化を求めて Alexandre Archambault さん (@alexarchambault) により一から Scala でリライトされたものだ。

sbt-projectmatrix を用いた並列クロスビルド

去年 sbt のクロスビルドを改善するために、sbt-projectmatrix という実験的プラグインを書いた。0.2.0 をリリースしたのでここで紹介する。 複数の Scala バージョンに対するビルド sbt-projectmatrix をビルドに追加後、以下のようにして 2つの Scala バージョンを使ったマトリックスをセットアップする。 ThisBuild / organization := "com.example" ThisBuild / scalaVersion := "2.12.8" ThisBuild / version := "0.1.0-SNAPSHOT" lazy val core = (projectMatrix in file("core")) .settings( name := "core" ) .jvmPlatform(scalaVersions = Seq("2.12.8", "2.11.12")) これは coreJVM2_11 と coreJVM2_12 というサブプロジェクトを作る。 ++ スタイルのステートフルなクロスビルドと違って、これは並列にビルドする。 2つのマトリックス 1つ以上のマトリックスがあると面白くなる。 ThisBuild / organization := "com.example" ThisBuild / scalaVersion := "2.12.8" ThisBuild / version := "0.1.0-SNAPSHOT" // uncomment if you want root // lazy val root = (project in file(".

git リポジトリの分岐

サブディレクトリを新しいリポジトリへ分岐させる (シンプルな場合) git clone --no-hardlinks --branch master originalRepoURL childRepo cd childRepo git filter-branch --prune-empty --subdirectory-filter path/to/keep master git remote remove origin git prune git gc --aggressive originalRepoURL、master、path/to/keep などは適当な値に変える。全てのブランチを処理したい場合は -- --all を使う。 サブディレクトリを新しいリポジトリへ分岐させる (複雑な場合) 複数のパスをフィルターしたい場合は、--index-filter と brew install gnu-sed findutils によってインストールできる GNU xargs と GNU sed を使う必要がある。 git clone --no-hardlinks --branch master originalRepoURL childRepo cd childRepo git filter-branch --index-filter 'git rm --cached -qr --ignore-unmatch -- . && git reset -q $GIT_COMMIT -- path1/to/keep path2/to/keep' --prune-empty master git filter-branch --prune-empty --parent-filter 'gsed "s/-p //g" | gxargs git show-branch --independent | gsed "s/\</-p /g"' git remote remove origin git prune git gc --aggressive originalRepoURL、master、path1/to/keep、path2/to/keep などは適当な値に変える。全てのブランチを処理したい場合は -- --all を使う。

君達の JDK は全て SDKMAN! がいただいた

これは Travis CI に自分で JDK をインストールする解説の第2弾だ。以前は、jabba を紹介した。 今日は SDKMAN!, という、Marco Vermeulen (@marc0der) さんが作った元気な名前のツールを見ていく。これは、JDK の他にも Groovy、Spark、sbt など JVM 上の様々なツールを対象とする環境マネージャーだ。 AdoptOpenJDK 11 と 8 2020-09-23 更新: バージョン番号の正規表現を更新した。 2019-11-06 更新: SDKMAN の更新プロンプトが CI をブロックするのを回避するために sdkman_auto_selfupdate を追加した。また、sdk install の行に || true を追加した。 2019-07-08 更新: パッチバージョンを自動検知するように変更した。古い版は GitHub に置いてある。 以下は SDKMAN! を使って Travis CI 上で AdoptOpenJDK 8 と 11 を用いてクロスビルドする方法だ: dist: xenial language: scala scala: 2.12.10 matrix: include: - env: - ADOPTOPENJDK=11 - env: - ADOPTOPENJDK=8 before_install: # adding $HOME/.

Pamflet 0.8.0

年末の連休中に Pamflet の left TOC (目次) を実装して、Pamflet 0.8.0 としてリリースした。

Pamflet は短い文書、特にオープンソース・ソフトウェアの ユーザ・ドキュメントを公開するためのアプリだ。

scala.Seq のマスキング

現行の Scala 2.13.0-M5 のままで行くと、scala.Seq は scala.collection.Seq から scala.collection.immutable.Seq に変更される予定だ。Scala 2.13 collections rework に何故今まで不変じゃなかったのかの解説が少し書かれている。行間から推し量ると、scala.Seq がデフォルトで不変になることを喜ぶべきだと言っているんだと思う。 デフォルトで列が不変になることはアプリや新しく書かれるコードには良いことだと思う。ライブラリ作者にとってはもう少しこみいっているかもしれない。 あなたがクロスビルドされたライブラリを持っていて ライブラリのユーザも複数の Scala バージョンを使っていて ライブラリのユーザが Array(...) を使っていた場合 この不変 Seq への変更は、breaking change つまり非互換な API 変更となりうる。 失敗例としては scopt/scopt#218 がある。僕が scopt のクロスビルドを行ったが、args を渡せなくなったらしい。Scala 2.13.0-M5 においても args は Array[String] のままだ。 シンプルな修正は全てのソースにおいて scala.collection.Seq を import することだ。僕が欲しいのは Seq を使うとコンパイルが通らなくなることだ。 scala.Seq を unimport する まず最初にやってみたのは scala.Seq を unimport して、scala.collection.Seq か scala.collection.immutable.Seq のどちらかを import することを強制することだ。 import scala.{ Seq => _, _ } 最も外側にあるスコープ内でデフォルトの import scala.

カンファレンスを女性にとってよりセーフなスペースにするための方法

技術カンファレンスにおける女性の参加率 (やその他のバックグラウンドを持つ人の参加率) を改善するには、周辺のカルチャーを変えていく必要がある。そのためには以下の 2点に関してハッキリとしたシグナル化とコミュニケーションを必要とする

  1. カンファレンスで女性参加者をナンパするのはダメ
  2. 技術的な能力を前提として、女性参加者とプロフェッショナルかつ対等に接する これらは全てのカンファレンスにおいて基調講演の前と、社交タイムの前に繰り返し連絡されるべき事項だ。

sbt のための super shell

週末中に sbt のための super shell の実装がまとまってきたのでここに報告する。大まかな概要としては、ターミナル画面の下 n行を乗っ取って今走っているタスクを表示させる。 ログを現状報告に使うことの限界 ログは多くの場面で有用で、時としては何が起こっているかを知るための唯一の現実解であったりする。だけども、sbt のようなコンソールアプリにおいては、ログを使ってビルド・ユーザに現在なにが起こっているかを報告するのはうまくいかないことがある。 仮に sbt が一切ログを表示しなかったとすると、sbt が長時間走るタスクを実行して一見固まってしまったときに何が起きているか分からなくなる。そのため、update のようなタスクは “Updating blabla subproject” と “Done updating” といった開始、終了ログを表示する。update タスクはユーザやビルドによって非常に長い時間がかかってしまうことで有名だが、少ないライブラリ依存性を持つその他の多くのビルドは 1s 以内で完了する。そのような場合、ビルドの開始時に “Done updating” がズラーッと壁のように並ぶことになる。 つまり、ログ表示を現状報告に使うのはログが出すぎてうるさい状態と、情報が足りなくて不便な両極端の間を揺れることになる。 show your work (途中式を書くこと) 人生における多くの事と同様に、やったことの提示方法やユーザー・インターフェイスはその作業とかプロダクトそのものの必要不可欠な側面であり、特にその作業やプロダクトが自明で無いものほどそれが顕著になる。 僕は、sbt が単一のコマンド実行内においてタスクを並列処理することを当たり前のように考えてきた。しかし、最近になってその事を知らない人がいる場面に出くわすことが増えてきた。これは、実はもっともなことだ。なぜなら、ビルドの DSL もユーザインターフェイスも sbt がタスクの並列処理を行っていることを明らかにしていないからだ。 さらに、古参のユーザが sbt がタスクを並列実行していることを信じていたとしても、現在はどのタスクがパフォーマンスのボトルネックになっているのかを知るのが難しい。何らかのプラグインが不必要に update を呼び出したり、ソースが一切変わっていないのにプロセス外の Typescript コンパイラを呼び出したりしているかもしれない。 super shell 現在実行中のタスクを表示する “super shell” はこれらの問題を解決する。1s 以内に実行するタスクは画面には表示されず、長時間走っているタスクはカウントアップする時計が表示される。 初めて僕がこのような機能に気付いたのは Gradle の “rich console” だ。Buck もこれを実装していて、“super console” と呼ばれているらしいので、僕もその名前を借りることにした。 super shell の実装方法 一ヶ月ぐらい前に Scala で書くコンソール・ゲームを書いたが、実はそれはこの機能のための予備研究だった。 super shell は二部から構成される。第一にロガーを変更して、ログがターミナルの上方向へ移動するようにする。このテクニックは「コンソール・ゲーム」で既に解説したが、ScrollUp を使うことでターミナルで同じ位置を保ったままログを表示させ続けることができる。