LoginSignup
10
3

More than 5 years have passed since last update.

pub global activateでインストールしたDartコマンドは起動が速いね、snapshot起動だからね

Last updated at Posted at 2018-12-16

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(デフォルト)、gitpathが指定できます。

さて本節の題名の通り、pub global activateを行うと、executableなDartパッケージはpre-compileされたsnapshotとしてインストールされます(なお後述しますが、path指定ではpre-compileしてくれません)。
ちょっと試してみましょう。

helloコマンドを作成する

bin/hello.dart
void main(List<String> args) {
  print('hello.');
}
pubspec.yaml
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.yamlexecutables:とコマンド名を書きます)。
で、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コマンドが起動できます!

~/.pub-cache/bin/hello
#!/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はいつ消えてもおかしくないので、信用できないという事ですかね?

global_packages.dart
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コマンドのアップデートって面倒(&忘れがち)ですよね。
問答無用で一気にアップデートするスクリプトを載せておきます。

pub_activate.sh
#!/usr/bin/env bash

pub global list | awk '{print $1}' | xargs -n1 pub global activate
10
3
2

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
10
3