しばたテックブログ

気分で書いている技術ブログです。

PowerShell Core 6.0をデバッグ実行する - Visual Studio Code編

PowerShell Advent Calendar 2017、11日目です。

qiita.com

PowerShell 6.0のGAリリースも間近となりビルド・デバッグ手順も固まってきたと思えたので本エントリを書きます。

はじめに

PowerShellがオープンソース化した時点でPowerShellのビルドはVisual Studio 2015で行われており、ソリューションの構成で、

  • .NET Frameworkで動作するWindows PowerShell
  • NET Coreで動作するPowerShell Core

をそれぞれ個別にビルドできる様になっていました。

その後PowerShell 6.0がPowerShell Coreのみをリリースする方針となってからは、Windows PowerShell専用の処理を削除しつつ、Visual Studio 2017とVisual Studio Codeでビルド・デバッグができる様に変更されています。
(ただ、Visual Studio 2017でのビルドについては現在も目下修正中の様で手元の環境ではうまくいきませんでした。追記しました)

細かい話はGitHub上の、

PowerShell/docs/building at master · PowerShell/PowerShell · GitHub

PowerShell/docs/debugging at master · PowerShell/PowerShell · GitHub

のドキュメントに記載されていますので興味があればご覧ください。

PowerShellをデバッグ実行する

本エントリではWindows環境、64bit Windows 10(1709)でVisual Studio Codeを使用した場合の手順を紹介します。
他のOSについては先述のGihHubのドキュメントに手順がありますのでそちらをご覧ください。

1. Gitのインストール

ソースコードをCloneするのにGitクライアントが必要です。
適当にインストールしてgitコマンドが使える様にしておいてください。

git-for-windows.github.io

2. Visual Studio Codeのインストール

Visual Studio Codeを適当にインストールしてください。

code.visualstudio.com

今回は64bit版のVer.1.18.1をインストールします。
また、以下の拡張が必要になるのでインストールしておいてください。

  • C#拡張 (現時点のバージョンは1.13.1)
  • PowerShell拡張 (現時点のバージョンは1.5.1)

f:id:stknohg:20171206162748p:plain

f:id:stknohg:20171206162759p:plain

3. ソースコードのClone

PowerShellのリポジトリよりソースコードをCloneします。

ソースコードを修正したい、Pull Requestを送りたいといった場合であればForkしてからのCloneとなるでしょうが、今回はとりあえずデバッグ実行できれば良いのでリポジトリを直接Cloneします。

任意のディレクトリで以下の様に--recursiveオプションを付けてgit cloneしてください。

git clone --recursive https://github.com/PowerShell/PowerShell.git

実行後PowerShellディレクトリが作成されソースコードがCloneされているはずです。

4. その他必要ツールのインストール

PowerShellをビルド・デバッグ実行するには上記の他に

が必要となります。

これらのツールを個別にインストールしても構わないのですが、PowerShellのソースコードにはビルトのための専用モジュールがあり、このモジュールの機能を利用することで必要なツールをまとめてインストールすることができます。

ソースコードのルートディレクトリにbuild.psm1というモジュールがあるので、これをインポートしStart-PSBootstrap関数を実行すると必要なツールをまとめてインストールしてくれます。

# cd [PowerShellのルートディレクトリ]
cd .\PowerShell\
Import-Module .\build.psm1
Start-PSBootstrap

f:id:stknohg:20171209195521p:plain

このStart-PSBootstrapでは$env:LOCALAPPDATA\Microsoft\配下のフォルダに必要なツールをインストールします。
現時点では、

  • .NET Core SDK : Ver.2.0.2
  • PowerShell 6.0 : Ver.6.0.0-rc
  • RCEdit : Ver.0.2.0

がインストールされます。
(今後インストールされるバージョンは上がっていくでしょう)

ここで、仕様なのかバグなのか不明なのですが、.NET Core SDKをインストールした後にdonetコマンドのパス($env:LOCALAPPDATA\Microsoft\dotnet)がPATH環境変数に永続して登録されませんでした。
このため、手動でPATHに登録するかビルド・デバッグ実行の都度PATHに追加しておく必要があります。

5. ビルドとデバッグ実行

最後にVisual Studio Codeを起動し、PowerShellをデバッグ実行してみます。

# dotnetコマンドのPATHを通してからVisual Studio Codeを実行
$env:PATH += ";$env:LOCALAPPDATA\Microsoft\dotnet"
code

PowerShellにはC++のソースも一部含まれているため、初回起動時に以下の様なダイアログが出ますが放置して大丈夫です。

f:id:stknohg:20171206162852p:plain

ここでデバッグの構成をみると、下図の様に、

  • .NET Core Launch
  • .NET Core Attach

が登録されており、.NET Core Launchを実行すればPowerShellのビルトとデバッグ実行が可能です。
F5キー実行でも構いません。

f:id:stknohg:20171206162911p:plain

最初のビルドにはしばらく時間がかかりますので焦らず待ちます。
ビルドエラーが出る様であれば適宜対処してください。

f:id:stknohg:20171206162944p:plain

最終的には下図の様にPowerShellをデバッグ実行することができます。

f:id:stknohg:20171206163006p:plain

【補足】PowerShellをビルドする

PowerShellをビルドするだけであればVisual Studio Codeが無くても先述のbuild.psm1モジュールを使い、Start-PSBuildコマンドで可能です。

# Start-PSBuildのパラメーターはこの例の他にもいろいろあります
Import-Module .\build.psm1
Start-PSBuild -Configuration Release

最後に

とりあえずこんな感じです。
PowerShellをデバッグ実行することでソースの修正はもちろんですが、怪しいと思った挙動の詳細を細かく追うことも可能になります。

みなさんも是非トライしてみてください。


【2018/04/12追記】

Visual Studio 2017を使った手順を書きました。

blog.shibata.tech

Redmineをアップグレードしてみた

結構前にやった作業記録なんですが、個人的な備忘録としてブログに残しておきます。

blog.shibata.tech

で構築したRedmineですが新しいバージョンがリリースされていたのでアップグレードしてみました。
3.4.2 → 3.4.3へのアップグレードをしています。

1. 公式な手順

Redmineのアップグレードについて、公式な手順はこちらにあります。

アップグレード - Redmine Guide 日本語訳

日本語訳がしっかり用意されているのは私みたいな利用者からすると非常にありがたいです。

環境によって細かい手順は変わりますが、基本的には、

  • 新バージョンのRedmineの展開
  • データベースのバックアップと(必要があれば)移行
  • 添付ファイルのバックアップと移行
  • Redmineのアップグレード
  • データベースのアップグレード
  • クリーンアップ

という手順を踏むことになります。

1. 新バージョンのRedmineの展開

最初に新しいバージョンのRedmineを展開します。

現時点ではC:\redmine-3.4.2にRedmineをインストールしていますので、C:\redmine-3.4.3に新しいバージョンのRedmineを展開してアップグレードすることにします。

手順としてはもう少し後でも構わないのですが、最初に現行のRedmineのサービスを止めておきます。

# サービス停止
$REDMINE_SERVICE_NAME = "Redmine"
$REDMINE_INSTALL_PORT = 3000
Stop-Service $REDMINE_SERVICE_NAME

サービスを止めたら新しいRedmineを展開します。
この手順についてはバージョンが違うだけで前にインストールしたときと同様になります。

# 新しいRedmineの展開
$REDMINE_VER = '3.4.3'
$REDMINE_INSTALL_ROOT = 'C:\'
$REDMINE_INSTALL_PATH = Join-Path $REDMINE_INSTALL_ROOT "redmine-$REDMINE_VER"
# Zipのダウンロード
Invoke-WebRequest -Uri "http://www.redmine.org/releases/redmine-$REDMINE_VER.zip" -OutFile "$(Get-Location -PSProvider Filesystem)\redmine-$REDMINE_VER.zip"
# PowerShell 4.0だとZipを扱えないのでRubyで頑張って解凍する
# ※いったんカレントディレクトリに解凍してから移動している
gem install rubyzip --no-rdoc --no-ri
ruby -e "require 'zip'; Zip::File.open('.\redmine-$REDMINE_VER.zip') do |zip| zip.each do |entry| puts \""entry #{entry.name}\""; zip.extract(entry, entry.name) { true } end; end"
Move-Item ".\redmine-$REDMINE_VER" $REDMINE_INSTALL_ROOT -Force

2. データベースのバックアップ(とコピー)

今回の環境ではデータベースにSQLiteを使用しています。
Redmine 3.4.2のデータベースファイルを、先ほど展開したディレクトリにコピーして移行し、コピー元のファイルをバックアップとすることにします。

これは単純にファイル(config\database.ymldb\redmine.sqlite3")をコピーするだけです。

# データベースのコピー : SQLite
$OLD_REDMINE_PATH = "C:\redmine-3.4.2"
Copy-Item -LiteralPath (Join-Path $OLD_REDMINE_PATH "config\database.yml") -Destination (Join-Path $REDMINE_INSTALL_PATH "config\database.yml")
Copy-Item -LiteralPath (Join-Path $OLD_REDMINE_PATH "db\redmine.sqlite3") -Destination (Join-Path $REDMINE_INSTALL_PATH "db\redmine.sqlite3")

3. 添付ファイルのバックアップ(とコピー)

データベースと同様に添付ファイル(filesフォルダ)もコピーすることで新しいバージョンへの移行とバックアップを行います。

# filesフォルダの移行
Copy-Item -LiteralPath (Join-Path $OLD_REDMINE_PATH "files") -Destination (Join-Path $REDMINE_INSTALL_PATH "files") -Recurse

4. Redmineのアップグレード

ここからRedmineのアップグレードをしていきます。
公式な手順ではconfig/configuration.ymlを移行すると記載されていますが、今回の環境では設定していないため何もしません。
ただし、ThinをインストールするためにGemfile.localファイルを追加しているので、このファイルをコピーして移行しておきます。

# config/configuration.yml は今回の例では設定していないのでスキップ
# ただし、他にカスタマイズしている設定があればコピーしておくこと

# 今回は Gemfile.local(Thin Service用)をコピー
Copy-Item -LiteralPath (Join-Path $OLD_REDMINE_PATH "Gemfile.local") -Destination (Join-Path $REDMINE_INSTALL_PATH "Gemfile.local")

ここからは前にインストールした時と同様にbundle installを行い新しいバージョンのRedmine環境を構築します。

# DevKitへのPATHが通ってない場合は通しておく
$RUBY_DEVKIT_INSTALL_PATH = 'C:\MinGW'
$env:PATH = "$(Join-Path $RUBY_DEVKIT_INSTALL_PATH "bin");$(Join-Path $RUBY_DEVKIT_INSTALL_PATH "mingw\bin");" + $env:PATH

# bundle install
cd $REDMINE_INSTALL_PATH
bundle install --without development test rmagick --path vendor/bundle
# セッション改ざん防止用秘密鍵の作成
bundle exec rake generate_secret_token

5. データベースのアップグレード

# データベースのマイグレーション
bundle exec rake db:migrate RAILS_ENV="production"

6. クリーンアップ

# キャッシュとセッションファイルのクリア
bundle exec rake tmp:cache:clear tmp:sessions:clear RAILS_ENV="production"

7. サービス設定の変更

ここまでの手順で新しいバージョンのRedmine環境の構築が完了しているので、Thinで起動するRedmineを新しいバージョンのものに変更することで環境の切り替えを行います。

# Thinの再登録
thin_service remove -N $REDMINE_SERVICE_NAME
thin_service install -N $REDMINE_SERVICE_NAME -c "$REDMINE_INSTALL_PATH" -p $REDMINE_INSTALL_PORT -e production
# サービス起動
Start-Service Redmine

動作確認

以上でアップグレードは完了です。
ブラウザからRedmineにログインしてバージョンが新しくなっている事、正しくデータ移行できていることが確認できればOKです。

f:id:stknohg:20171209222019p:plain

PowerShellにおける"配列リテラル"について

PowerShell Advent Calendar 2017、9日目です。

qiita.com

久しぶりのPowerShellの基本がわかっていなかったシリーズです。

以前のオープンソースカンファレンス2017 Hokkaidoでの登壇などで、ふつうに

PowerShellには配列リテラルがあります。

と説明していたのですが思いっきり間違っていました。
誤った説明をしてきたことを謝罪するとともに、あらためてPowerShellにおける"配列リテラル"について説明したいと思います。

PowerShellの仕様としての"配列リテラル"

中の人であるBruce Payetteさんが書いたWindows PowerShell in ActionPowerShell Blogの記事によれば、


  • Windows PowerShell in Action : 2.4 Collections: arrays and sequencesより一部引用

Here’s how array literals are defined in PowerShell: They’re not. There’s no array literal notation in PowerShell.


  • PowerShell Blog : Array Literals In PowerShell より一部引用

The first thing to understand is that there are no array literals in PowerShell Arrays are built using operators or casts.


と、PowerShellに"配列リテラル"は存在しないと説明されています。

例えば、

$a = 1,2

$a = (1,2)

といった例における配列は両者ともリテラルではなくカンマ演算子によって生成されるものであるとされています。

また、PowerShellの言語仕様書(本エントリではV3を例示)においても、2.3.5.5 Array literalsにおいて、

2.3.5.5 Array literals

PowerShell allows expressions of array type (§9) to be written using the unary comma operator (§7.2.1), array-expression (§7.1.7), the binary comma operator (§7.3), and the range operator (§7.4).

との説明がなされ、仕様として"配列リテラル"は存在しないことがきちんと説明されていました。

PowerShellの構文から見た"配列リテラル"

私が間違った説明をした原因とも言えるのですが、PowerShellの構文上、ASTにおいては"配列リテラル"に類するものが存在しています。

先述の配列の例をShowPSAstモジュールを使いASTを可視化してみると以下の様になります。

Import-Module ShowPSAst
{ $a = (1,2) } | Show-Ast

f:id:stknohg:20171207181148p:plain

1,2の部分についてはASTとしてはArrayLiteralAstとなっておりあたかも配列リテラルを取り扱うかの様になっています。

そして、言語仕様書ではこれはarray-literal-expressionとして以下の様に定義されています。

f:id:stknohg:20171207181206p:plain

図の上部を見ればわかりますが、これはarray-literal-expressionといいつつカンマ演算子専用の構文です。

この仕様と実装のちぐはぐさが私の誤解の根本的な原因となりました。

ちなみに、仕様の説明では"二項"カンマ演算子と記載されていますが、単項や3項以上の場合でもこのArrayLiteralAstが使われます。

# 単項の場合
{ $a = ,1 } | Show-Ast

f:id:stknohg:20171207181228p:plain

# 3項以上の場合
{ $a = 1,2,3,4 } | Show-Ast

f:id:stknohg:20171207181245p:plain

【補足】配列部分式演算子について

補足としてもう一つややこしい話をします。

PowerShellにおいて配列を生成するにはカンマ演算子のほかに@()による配列部分式演算子を使うことができます。

ここでカンマ演算子と配列部分式演算子の違いについて触れておきます。

配列部分式演算子は構文としてはarray-expressionとして以下の様に定義されています。

f:id:stknohg:20171207181259p:plain

この演算子最大の特徴は()の中がstatement-listと複数の文を受け入れことができる点になります。

この説明だけみればカンマ演算子と配列部分式演算子が全然別物だと認識できる*1かと思いますが、ちょっとややこしい例を挙げてみます。

#
# カンマ演算子による配列生成
#
# 定義可能
$array01 = (
    1,
    2,
    3
)
# エラーになる
$error01 = (
    1
   ,2
   ,3
)
# エラーになる
$error02 = (
    1
    2
    3
)

#
# 配列部分式演算子による配列生成
#
# 定義可能
$array10 = @(
    1,
    2,
    3
)
# 定義可能
$array11 = @(
    1
   ,2
   ,3
)
# 定義可能
$array12 = @(
    1
    2
    3
)

この6つの配列定義についてそれぞれ定義可・不可となる理由がわかるでしょうか?
これがきちんと説明できればPowerShellの配列をマスターしたも同然です。

カンマ演算子による配列生成の解説

カンマ演算子による配列生成で$array01以外がエラーとなるのはarray-literal-expressionの定義にある通り、カンマ演算子を使い複数行にわたる配列定義を行う際のカンマは後置のみ許可されているためです。

こちらは割と直感的に理解できるのではないかと思います。

配列部分式演算子による配列生成の解説

そして、ちょっと厄介なのが$array10$array12で、これらはすべて定義可能であり同じ結果を返します。

まず、$array10

$array01 = (
    1,
    2,
    3
)

$array10 = @($array01)

と同等のイメージになります。これはわかりやすいかと思います。

つぎに$array11$array12については以下の様に;を入れるとわかりやすくなります。
本項の最初に説明した()の中に複数の文が来るケースに該当します。

# 定義可能
$array11 = @(
    1;
   ,2;
   ,3;
)
# 定義可能
$array12 = @(
    1;
    2;
    3;
)

それぞれに注釈を入れると

# 定義可能
$array11 = @(
    1;    # 文1 : 数値リテラル -> パイプライン
   ,2;    # 文2 : 単項カンマ演算子 + 数値リテラル -> パイプライン
   ,3;    # 文3 : 単項カンマ演算子 + 数値リテラル -> パイプライン
)
# 定義可能
$array12 = @(
    1;    # 文1 : 数値リテラル -> パイプライン
    2;    # 文2 : 数値リテラル -> パイプライン
    3;    # 文3 : 数値リテラル -> パイプライン
)

と、$array11は単項カンマ演算子を使った3つの文、$array12は数値リテラルを使った3つの文から構成されており、3つの文を評価した結果が配列化されることになります。

本エントリではこれ以上触れませんがそれぞれのケースに対してASTを見てみるとより分かりやすいでしょう。

最後に

とりあえずこんな感じです。

個人的にPowerShellの配列は自由度の高さとその代償としての厄介さを抱えているのかなと思っています。

追記

ぎたぱそ先生の配列生成のパフォーマンスについての記事で本エントリを取り上げてもらいました。

tech.guitarrapc.com

とても有用な内容ですので是非こちらもご覧ください。

*1:実際別物です