AngularDartで開発していた際、ふとwebdev
コマンドが(*.dartのソースコードではなく)pre-compileされたsnapshot
としてインストールされている事に気付きました。
ちょうど、「Dart2になってから(DartVM用の)コマンド起動ちょっと遅くなったし、大きいパッケージをimportすると更に遅いな・・」と思っていたところだったので、調べてみました。
なおDartのsnapshotの話は、@takyamさんの以下の翻訳記事が参考になります。
https://qiita.com/takyam/items/99ee4500f01fa7cd1d27
pubやgit(hub)からActivateしたDartコマンドはsnapshotにpre-compileされる
例えばですが、前述のwebdev
コマンドはpub global activate webdev
を実行することでActivate(インストール/アップデート)します。
Activateの際には、パッケージの取得元を-s
(--source
)で指定することが出来ます。
~$ pub global activate -h
Make a package's executables globally available.
Usage: pub global activate <package...>
-h, --help Print this usage information.
-s, --source The source used to find the package.
[git, hosted (default), path] ★ ココ
(以下略)
詳細は https://www.dartlang.org/tools/pub/cmd/pub-global#activating-a-package を参照下さい。
取得元として、hosted
(デフォルト)、git
、path
が指定できます。
さて本節の題名の通り、pub global activate
を行うと、executableなDartパッケージはpre-compileされたsnapshotとしてインストールされます(なお後述しますが、path指定ではpre-compileしてくれません)。
ちょっと試してみましょう。
helloコマンドを作成する
void main(List<String> args) {
print('hello.');
}
name: hello_dart
description: A sample command-line application.
version: 0.0.1
environment:
sdk: '>=2.1.0 <3.0.0'
executables: # ★ ココが重要
hello:
上記の様な、簡単なDartVM用コマンドを作成します(pubspec.yaml
にexecutables:
とコマンド名を書きます)。
で、GitHubの「hello_dart」リポジトリとしてアップします。
helloコマンドをGitHubからActivteする
次に、sourceをgit
指定でActivateします。
~$ pub global activate -s git git@github.com:takutaro/hello_dart.git
Resolving dependencies... (2.4s)
+ hello_dart 0.0.1 from git git@github.com:takutaro/hello_dart.git at 7f70b3
Precompiling executables...
Precompiled hello_dart:hello. ★ pub global activateのタイミングで事前コンパイルされる!
Installed executable hello.
Activated hello_dart 0.0.1 from Git repository "git@github.com:takutaro/hello_dart.git".
~$ tree -a .pub-cache/bin/
.pub-cache/bin/
└── hello
~/.pub-cache/bin
配下にhelloコマンドがインストールされました(予め~/.pub-cache/bin
にはパスが通っている前提です)。
Precompiled hello_dart:hello
の出力も見えます。
ではhello
コマンドの中身を覗いてみます。
コマンド(スクリプト)の中身
中身は以下の様になっています。
snapshotが存在する場合はそれを利用していますね。これでほぼノータイムでDartコマンドが起動できます!
#!/usr/bin/env sh
# This file was created by pub v2.2.0-dev.0.0.
# Package: hello_dart
# Version: 0.0.1
# Executable: hello
# Script: hello
# ★ snapshotが存在するので、snapshotで起動。速い!
dart "/home/taku/.pub-cache/global_packages/hello_dart/bin/hello.dart.snapshot.dart2" "$@"
# The VM exits with code 253 if the snapshot version is out-of-date.
# If it is, we need to delete it and run "pub global" manually.
exit_code=$?
if [ $exit_code != 253 ]; then
exit $exit_code
fi
# ★ snapshotが古かったりした場合は、それを使わずに起動
pub global run hello_dart:hello "$@"
ちなみにディレクトリ/ファイル構成は以下のようになっていますね。
.pub-cache/global_packages/hello_dart/
├── .packages
├── bin
│ └── hello.dart.snapshot.dart2
└── pubspec.lock
.pub-cache/git/hello_dart-7f70b3b25b4895dbef84910ba1781be5907a236f/bin
└── hello.dart
なぜpath指定ではsnapshotにpre-compileしてくれないのか?
分かりません。ローカルにあるsourceはいつ消えてもおかしくないので、信用できないという事ですかね?
class GlobalPackages {
final SystemCache cache;
String get _directory => p.join(cache.rootDir, "global_packages");
String get _binStubDir => p.join(cache.rootDir, "bin");
GlobalPackages(this.cache);
Future activateGit(String repo, List<String> executables,
{Map<String, FeatureDependency> features, bool overwriteBinStubs}) async {
var name = await cache.git.getPackageNameFromRepo(repo);
_describeActive(name);
await _installInCache( // ★ snapshotに事前コンパイルされる
cache.git.source
.refFor(name, repo)
.withConstraint(VersionConstraint.any)
.withFeatures(features ?? const {}),
executables,
overwriteBinStubs: overwriteBinStubs);
}
Future activateHosted(
String name, VersionConstraint constraint, List<String> executables,
{Map<String, FeatureDependency> features, bool overwriteBinStubs}) async {
_describeActive(name);
await _installInCache( // ★ 〃
cache.hosted.source
.refFor(name)
.withConstraint(constraint)
.withFeatures(features ?? const {}),
executables,
overwriteBinStubs: overwriteBinStubs);
}
Future activatePath(String path, List<String> executables,
{bool overwriteBinStubs}) async {
// ★ ローカルパス指定でのactivateではsnapshotを作ってくれない
var entrypoint = Entrypoint(path, cache);
await entrypoint.acquireDependencies(SolveType.GET);
var name = entrypoint.root.name;
_describeActive(name);
var fullPath = canonicalize(entrypoint.root.dir);
var id = cache.path.source.idFor(name, entrypoint.root.version, fullPath);
_writeLockFile(name, LockFile([id]));
var binDir = p.join(_directory, name, 'bin');
if (dirExists(binDir)) deleteEntry(binDir);
_updateBinStubs(entrypoint.root, executables,
overwriteBinStubs: overwriteBinStubs);
log.message('Activated ${_formatPackage(id)}.');
}
おまけ
ActivateでインストールしたDartコマンドのアップデートって面倒(&忘れがち)ですよね。
問答無用で一気にアップデートするスクリプトを載せておきます。
#!/usr/bin/env bash
pub global list | awk '{print $1}' | xargs -n1 pub global activate