GitHub Package Registry に GitHub Actions から Docker イメージを push

GitHub Actions に続き GitHub Package Registry も僕のアカウントで使えるようになりました。

github.com

blog.kondoumh.com

この時は、コンテナをビルドして終わりでしたが Registry が使えるようになったので コンテナイメージを格納する処理を追加してみます。

事前にアクセストークンを取得して、リポジトリの Settings で Secret を追加しておきます。

f:id:kondoumh:20190928101638p:plain

ワークフロー定義。イメージビルド後に Package Registry (docker.pkg.github.com) にログイン、イメージを push する処理を追加。

jobs:
  package:

    runs-on: ubuntu-latest

    steps:
          :
  build-image:

    needs: package
    runs-on: ubuntu-latest
 
    steps:
    - uses: actions/checkout@v1
    - uses: actions/download-artifact@master
      with:
        name: sb-sample-service.jar
        path: ./target

    - name: Build and push Docker image
      run: |
        docker build . --file Dockerfile --tag docker.pkg.github.com/kondoumh/sb-sample-service/app:latest
        docker login docker.pkg.github.com -u kondoumh -p ${{ secrets.DOCKER_REGISTRY_TOKEN }}
        docker push docker.pkg.github.com/kondoumh/sb-sample-service/app:latest

実行すると push 成功 しました。

f:id:kondoumh:20190928101821p:plain

格納されたコンテナを pull してみます。

$ docker login docker.pkg.github.com -u kondoumh
Password: 
  :
Login Succeeded

$ docker pull docker.pkg.github.com/kondoumh/sb-sample-service/app:latest
1.0: Pulling from kondoumh/sb-sample-service/app
e7c96db7181b: Pull complete 
f910a506b6cb: Pull complete 
c2274a1a0e27: Pull complete 
87e0b7342010: Pull complete 
Digest: sha256:97f2da5372e9f659dbe976e245835a4cb082960b2b6aac96ca6e84cdc7305d5b
Status: Downloaded newer image for docker.pkg.github.com/kondoumh/sb-sample-service/app:latest


$ docker image ls
REPOSITORY                                             TAG                 IMAGE ID            CREATED             SIZE
docker.pkg.github.com/kondoumh/sb-sample-service/app   latest                 a9ed328094d5        31 minutes ago      138MB

大丈夫みたいです。

さて、上記ワークフローだと、Git のブランチ名やタグ名をイメージのタグ名に付与するような処理を書く必要があり、やや面倒です。探したらコンテナイメージのビルドとタグ付け、push までをやってくれる Action がありました。

github.com

Master ブランチでビルドした場合は latest タグをつけてくれるようです。これを使ってワークフローを書き直してみます。

   :
  build-image:

    needs: package
    runs-on: ubuntu-latest
 
    steps:
    - uses: actions/checkout@v1
    - uses: actions/download-artifact@master
      with:
        name: sb-sample-service.jar
        path: ./target

    - name: Build image and Publish to Registry
      uses: elgohr/Publish-Docker-Github-Action@master
      with:
        name: docker.pkg.github.com/kondoumh/sb-sample-service/app
        username: kondoumh
        password: ${{ secrets.DOCKER_REGISTRY_TOKEN }}
        registry: docker.pkg.github.com

run でコマンドを書くよりも行増えてますが、より宣言的なスタイルになっています。デフォルトだと docker.io に push しようとするので、registry パラメータで Package Registry の URL を指定する必要があります。

ログを見るとちゃんと latest タグをつけてくれています。

f:id:kondoumh:20190928102313p:plain

CI とレジストリを完全装備した GitHub、ソースコードだけでなく、ビルド成果物の生成と管理ができるようになってしまいました。

VS Code の Remote Containers で Java 12 開発環境を構築

以前 VS Code の Remote Development を試した時は VirtualBox の仮想マシンをターゲットに環境を作りました。

blog.kondoumh.com

Remote Development では Docker コンテナもターゲットマシンに使えます (Remote - Containers) 。

marketplace.visualstudio.com

特定の言語・フレームワークをインストールしたコンテナイメージを用意しておけば、面倒な環境構築をスキップして開発を開始できます。

前から気になっていた VS Code の Java 拡張の使い勝手も知りたいと思い、Java 12 環境を Docker - Containers で作ってみました。Docker Desktop for macOS を使用しています。

Java 環境用のフォルダを作成し、VS Code で開きます。

コマンドパレットで、Remote-Containers: Add Development Container Configuration Files を選択

f:id:kondoumh:20190921173515p:plain

選択メニューから Java 12 openjdk:12-jdk を選択

f:id:kondoumh:20190921173539p:plain

.devcontainer というフォルダが作成され、設定の JSON ファイルや Dockerfile が生成されます。ポップアップの Reopen in Container をクリック。

f:id:kondoumh:20190921173607p:plain

コンテナのビルドが始まります。

f:id:kondoumh:20190921173647p:plain

コンテナが起動されると、接続状態になります。

f:id:kondoumh:20190921173831p:plain

この状態で docker ps するとコンテナが起動していました。

$ docker ps
CONTAINER ID        IMAGE                                       COMMAND                  CREATED              STATUS              PORTS               NAMES
347c4633b941        vsc-java-a10e83bc1108453baed86ae69c6ee569   "/bin/sh -c 'echo Co…"   About a minute ago   Up About a minute                       ad

.devcontainer が配置されるフォルダはホストマシンにボリュームマウントされていますので、コンテナが破棄された後もファイルは残ります。

VS Code のターミナルを開いて、Java のバージョン情報や OS の情報を見てみます。

f:id:kondoumh:20190921180541p:plain

ちゃんと openjdk 12.0.2 がインストールされていて、OS は Oracle Linux Server 7.6 であることがわかります*1

コンテナ側にインストールされた Extension を確認すると、Java Extension Pack と Maven for Java はインストール済みになっていました。

f:id:kondoumh:20190921211358p:plain

フォルダを追加してコマンドパレットから Java: Create Java Project を実行します。

f:id:kondoumh:20190922001327p:plain

追加したフォルダを選択して OK を押します。

f:id:kondoumh:20190922001522p:plain

プロジェクト名を入れて Enter を打つと別ウィンドウが起動して、Java プロジェクトを開きます。このウィンドウもコンテナにアタッチされています。コンソールアプリなので、main 関数の上に表示されている Run をクリックすると実行されます。

f:id:kondoumh:20190922002139p:plain

ブレークポイントを追加して、Debug をクリックするとデバッグモードで実行しデバッグが可能です。

f:id:kondoumh:20190922002434p:plain

コード書いてみると補間はばっちり。import も自動追加されたりします。

f:id:kondoumh:20190922002737p:plain

ひとまず、Java 12 環境を作ってコードを書き始めることができました。

次は Spring Boot のプロジェクトを作ってみます。

コンテナには Maven CLI はインストールされていないようです。Dockerfile を見ると Maven や Gradle をインストールするスクリプトがコメントアウトされています。有効化してイメージをリビルドします。

f:id:kondoumh:20190921205804p:plain

コンテナが再起動され使えるようになりました。

f:id:kondoumh:20190921210550p:plain

コマンドパレットから Maven: Create Maven Project を実行します。

f:id:kondoumh:20190922004326p:plain

maven-archetype-quickstart を選択して、バージョンを選択

f:id:kondoumh:20190922005744p:plain

先ほどのフォルダを選択します。

f:id:kondoumh:20190922010105p:plain

archetype の処理が流れて groupId, artifactId などのプロンプトが出るので適当に入力していくとプロジェクトが作成されます。

f:id:kondoumh:20190922010153p:plain

生成されたプロジェクトの pom.xml を編集して、Spring Boot 用の設定をします。

  :
  <name>hello-springboot</name>

  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.1.8.RELEASE</version>
  </parent>
  :
  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    :

src/main/java/hello/App.java を編集してエンドポイントを追加、Boot アプリのエントリーポイントを追加します。

package hello;

import java.net.http.HttpRequest;

import org.springframework.boot.*;
import org.springframework.boot.autoconfigure.*;
import org.springframework.web.bind.annotation.*;

@RestController
@EnableAutoConfiguration
public class App 
{
    @RequestMapping(value = "/{name}", method = RequestMethod.GET)
    String hello(@PathVariable("name") String name) {
        return "Hello World! " + name + "\n";
    }

    public static void main(String[] args) {
        SpringApplication.run(App.class, args);
    }
}

ターミナルを分割し、片方で mvn spring-boot:run でアプリを起動、片方で curl でエントリーポイントにリクエストを送ってみます。

f:id:kondoumh:20190922012852p:plain

無事にレスポンスが取得できました。

実行しているコンテナは、VS Code のウィドウを閉じると破棄されます。

コンテナには vscode という non-root ユーザーも登録されていて、devcontainer.json の runArgs のコメントを解除すれば、vscode ユーザーで利用することも可能です。

{
    "name": "Java 12",
    "dockerFile": "Dockerfile",
          :
    "runArgs": [ "-u", "vscode" ],
          :
}

コンテナの設定については、公式ドキュメントが充実しています。

code.visualstudio.com

以上、Remote Containers の Java 開発環境、使い勝手としては IntelliJ IDEA や Eclipse に劣らない感じで、軽快な動作が素晴らしいです。Java 専用 IDE で便利プラグインを多用していると物足りないかもしれませんが。

コンテナを使うと開発準備も楽ですし、Dockerfile を配布すればメンバーの環境も統一できます。複数バージョンの JDK 混在させて切替えたりしなくてもよくなりますね。VS Code がコンテナの管理をかなり賢くやってくれるのが素敵です。

先日 .NET Core を試した時は、ローカル環境に .NET Core SDK をインストールしていました。

blog.kondoumh.com

これも C# (.NET Core) の Dev Container Configuration が提供されているのでこちらを使った方が手軽でした。

*1:RHEL 7.6 ベースです

Electron アプリのパッケージツールを electron-builder に移行

GitHub Actions で 野良 Scrapbox アプリの CI を 作った話の続きです。

blog.kondoumh.com

ほぼ同時ぐらいに azu さんが、同じテーマのブログを書かれてました。

efcl.info

僕のはビルドするところまででしたが、リリースまで実現されていて非常に参考になりました。特にパッケージツールとして使われている electron-builder が便利そうだったので早速移行することにしました。

www.npmjs.com

これまで electron-packager を使っていて electron-builder の存在は知ってたものの、試したことはありませんでした。各プラットフォームに対応した(完全に動作する)インストーラまで作ってくれます。

f:id:kondoumh:20190919214959p:plain

はなぢ出ました。

electron-packager の場合、インストーラを作成するには他の NPM パッケージとの併用が必要なので手をつけてませんでした。これでユーザーのインストール作業も簡単になります。

package.json にプラットフォーム毎の設定が書けるので npm script はプラットフォーム共通にできます。

  "build": {
    "productName": "sbe",
    "mac": {
      "icon": "icons/mac/sbe.icns"
    },
    "win": {
      "icon": "icons/win/sbe.ico"
    }
  },
  "scripts": {
    "start": "electron .",
    "pack": "electron-builder --dir",
    "dist": "electron-builder",
  },

インストーラ形式で生成されるため、各 OS のコマンドで成果物を zip 圧縮する必要もなくなりました。

おかげで GitHub Actions workflow の matrix.os による if expression も撲滅することができ、step も減らせて非常にスッキリしました。

GitHub Actions の Matrix build で各 OS 向けの Electron アプリをビルドする

Scrapbox の野良 Electron アプリ sbe をリリースするときは、macOS / Windows のバイナリをそれぞれ MacBook Pro や Windows ラップトップでビルドし GitHub の Release Draft にアップロードして公開しています。ビルドが1箇所でできないのは結構めんどくさいものです。

GitHub Actions の Matrix build を使うと複数の OS・複数のランタイム環境 (複数バージョンの Node.js 環境など) の組合せのビルドやテストを1つの Workflow で一気に実行できます。これを使って面倒な各 OS 向けのビルドを CI 化しようと思いました。

GitHub Actionsのワークフロー構文 - GitHub ヘルプ

Matrix build の Matrix パラメータには Ubuntu / Windows / macOS などを仮想環境として指定可能です。

LinuxおよびWindowsのGitHub Actions仮想環境は、GitHub Actions runnerがインストールされたMicrosoft AzureのStandard_DS2_v2仮想マシン上にホストされています。 GitHub Actions runner は、Azure Pipelines Agent のフォークです。 Standard_DS2_v2マシンリソースに関する詳しい情報については、Microsoft Azureドキュメンテーションの「DSv2-series」を参照してください。 GitHubは、macOS仮想環境のホストにMacStadiumを使用しています。

GitHub Actionsの仮想環境 - GitHub ヘルプ

GitHub が Microsoft に買収されたことで Azure の Windows Server 環境を Action の実行環境として使えるみたいですね。MacStadium は iOS / macOS アプリの CI/CD サービスのようです。

www.macstadium.com

今回は Matrix build により macOS と Windows 環境で下記ステップを実行します。

  • リポジトリへの tag push をトリガーとして開始する
  • Node.js 環境を構築する (最新の v12 系のみ)
  • OS に応じた npm script (electron-packager) を実行する
  • 生成されたバイナリーを zip 圧縮する
  • 圧縮されたバイナリーを成果物として保存する

実際の Workflow 定義です。

name: Build sbe binaries

on:
  push:
    branches:
      - "!*"
    tags:
      - "v*"

jobs:
  build:

    runs-on: ${{ matrix.os }}

    strategy:
      matrix:
        os: [windows-latest, macos-latest]

    steps:
    - uses: actions/checkout@v1
    - name: setup nodejs
      uses: actions/setup-node@v1
      with:
        node-version: 12.x
    - name: install dependencies
      run: npm install

    - name: package for macOS
      if: matrix.os == 'macos-latest'
      run: |
        echo ${GITHUB_REF}
        npm run package-macos
        zip -ry sbe-darwin-x64/sbe-macos.zip sbe-darwin-x64/sbe.app
    - name: archive package for macOS
      if: matrix.os == 'macos-latest'
      uses: actions/upload-artifact@master
      with:
        name: sbe-v-macos
        path: ./sbe-darwin-x64/sbe-macos.zip

    - name: show ref tags
      if: matrix.os == 'windows-latest'
      run: echo %GITHUB_REF%
    - name: package for Windows
      if: matrix.os == 'windows-latest'
      run: npm run package-win32
    - name: zip package for Windows
      if: matrix.os == 'windows-latest'
      run: powershell Compress-Archive -Path sbe-win32-x64 -DestinationPath sbe-windows.zip
    - name: archive package for Windows
      if: matrix.os == 'windows-latest'
      uses: actions/upload-artifact@master
      with:
        name: sbe-v-windows
        path: sbe-windows.zip

v で始まる名前の tag が push された場合に起動するようにトリガーを設定しています。

Job の Matrix パラメータ に windows-latestmacos-latest を指定して Matrix build を実行しています。

step 毎に if expression を使って Matrix パラメータ (os) を判定し実行するコマンドを使い分けています。macos では bash を、Windows では cmd.exe ( と PowerShell) で動作するコマンドを記述しています。

GitHub Actions (beta) の Windows 仮想環境では、run: で複数コマンドを実行できないようでしたので step を分割し1コマンドずつ実行するようにしました。

push された tag の名前はビルトインの環境変数 GITHUB_REF に格納されます。artifact のファイル名に tag 名を使いたいのですが、GITHUB_REF に設定される値は refs/tags/v1.0.2 のような形式なのでそのままファイル名に使用することができません。GitHub Actions の関数には文字列の join はあるのに split がないという状況です。

GitHub Actions のコンテキストおよび式の構文 - GitHub ヘルプ

ということで tag 名を upload-artifat の path に指定するのは断念し、echo で出力するのみとしています。

Workflow を実行すると OS 毎にジョブがパラレル実行され、ビルド済みのバイナリーが Artifact として保存されます。

f:id:kondoumh:20190915211234p:plain

GitHub Actions で各 OS 向けのバイナリのビルドを 1つの Workflow で実現できました。今後は、GitHub API で Release Draft にアップロードする Job や UI テストを実行する Job を追加していきたいと思っています。

追記) electron-packager -> electron-builder に移行することで OS 固有の処理がなくなり if expression はなくすことができました。

blog.kondoumh.com

参考記事:

www.kaizenprogrammer.com

GitHub Actions で Hugo のサイト生成と公開を自動化する

Hugo を導入してホームページ更新は楽になりました。

blog.kondoumh.com

ただ、「ビルド時の環境変数設定を忘れて Google Analytics のトラッキング ID が生成されてなかった」など凡ミスがあったり、生成したファイルをアップロードするのが面倒だったりと、まだ自動化の余地があります。

Netlify が使えれば超楽ですが、レンタルサーバーに配置してるので、ページの Markdown ファイルを編集して GitHub に push したら生成・公開してくれるとよいかなと。

自分のアカウントではすでに GitHub Actions が有効になっています。

blog.kondoumh.com

ということで、以下のステップからなるワークフローを追加してみました。

  • Hugo をインストール
  • サイトのコードを theme 用の submodule を含めて clone
  • hugo コマンドを実行して generate
  • 成果物を upload-artifact で保存
  • レンタルサーバーにアップロード

実際のワークフロー定義です。

name: Generate and publish site

on: [push]

jobs:
  build:

    runs-on: ubuntu-latest
    
    steps:
    - name: install hugo
      run: |
        curl -L https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_${HUGO_VERSION}_linux-64bit.tar.gz | tar -xz
        sudo mv hugo /usr/local/bin/hugo
      env:
        HUGO_VERSION: 0.57.2

    - name: checkout
      uses: actions/checkout@v1
      with:
        submodules: true

    - name: generate
      run: HUGO_ENV=production hugo

    - name: archive
      uses: actions/upload-artifact@master
      with:
        name: public
        path: ./public

    - name: publish
      run: |
        pushd ./public
        find . -type f -exec curl --ftp-create-dirs -T {} ftp://${{ secrets.FTP_USER }}:${{ secrets.FTP_PASSWD }}@${{ secrets.FTP_HOST}}/${{ secrets.ROOT_PATH}}/{} \;
        popd

Hugo のインストールは、curl でバイナリをダウンロードして、/usr/local/bin に配置するだけ。超シンプルです。sudo が必要だったので Action は non-root ユーザーで実行されるようですね。

インストールする Hugo のバージョンは 環境変数 HUGO_VERSION に設定してます。現状、環境変数は外部からは与えられないようです。

checkout action で submodules を true にすれば、clone 時に submodules も更新してくれます。

レンタルサーバーにアップロードするための秘密情報は、リポジトリに設定された secrets に設定してます。

f:id:kondoumh:20190914223552p:plain

${{ secrets.NAME_OF_ENV }} の形式で取得可能です。secrets を環境変数のように使えなくもないですが、一度設定してしまうと値を閲覧することすらできないのでやはり秘密情報専用に使う方がよいでしょう。

レンタルサーバが FTP しか受け付けないので、生成したファイルを curl で FTP アップロードするワンライナーを実行しています。

実行結果です。

f:id:kondoumh:20190914224518p:plain

うまくいきました。

これで、ホームページ更新のワークフローが自動化され GitHub リポジトリで完結するようになりました。

Android 10 on Pixel 3 XL

Pixel 3 XL に Android 10 がインストール可能になったので早速入れてみました。

グループアイコンのフチがくっきりしたり、スクリーンロックアイコンがアニメーションするようになりました。

ダークテーマにしたら、標準アプリはだいたいダークになりました。

Gmail アプリは未対応のようですが、他には Slack も対応してて自動で切り替わってました。

ダークモード、消費電力が少なそうで、目にキツくない感じしますね。

Android 10 からの機能ではないと思いますが、ライブ壁紙?が追加されたので設定してみました。iOS の壁紙のように見る角度によって変わったり、昼と夜でも変わったり。

共有画面で連絡先とアプリが同時に出るようになったので、タップミスしなくなりました。これまでは、アプリが出た後に時間差で連絡先が出て座標がズレるため、あらぬ場所を押してしまう現象があったのでした。

期待のデスクトップモードは正式リリースではなく開発者オプションで有効化する必要があります。Pixel 3 の場合、USB-C からのディスプレイ出力には対応しておらず、なんらかのアダプターが必要な感じがします。ショボーン

.NET Core と VS Code によるクロスプラットフォーム Web 開発環境

.NET Core が成熟してきているというのをヒシヒシと感じていて、元祖 .NET Framework はメンテナンスモードだし、.NET Core ファミリーは来年には .NET 5 として大統一されるみたいだし、ちょっと触っておきたいという気持ちになりました。

devblogs.microsoft.com

Visual Studio 2019 Community の最新版を入れていても、.NET Core SDK の最新版は入っていないっぽいので、ダウンロード・インストールします。

dotnet.microsoft.com

Windows 版

f:id:kondoumh:20190828085947p:plain

C:\> dotnet --info
.NET Core SDK (global.json を反映):
 Version:   2.2.401
 Commit:    729b316c13

ランタイム環境:
 OS Name:     Windows
 OS Version:  10.0.17134
 OS Platform: Windows
 RID:         win10-x64
 Base Path:   C:\Program Files\dotnet\sdk\2.2.401\

Host (useful for support):
  Version: 2.2.6
  Commit:  7dac9b1b51

.NET Core SDKs installed:
  2.1.503 [C:\Program Files\dotnet\sdk]
  2.1.602 [C:\Program Files\dotnet\sdk]
  2.1.801 [C:\Program Files\dotnet\sdk]
  2.2.401 [C:\Program Files\dotnet\sdk]

.NET Core runtimes installed:
  Microsoft.AspNetCore.All 2.1.7 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All]
  Microsoft.AspNetCore.All 2.1.9 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All]
  Microsoft.AspNetCore.All 2.1.12 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All]
  Microsoft.AspNetCore.All 2.2.6 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All]
  Microsoft.AspNetCore.App 2.1.7 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
  Microsoft.AspNetCore.App 2.1.9 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
  Microsoft.AspNetCore.App 2.1.12 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
  Microsoft.AspNetCore.App 2.2.6 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
  Microsoft.NETCore.App 2.1.7 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.NETCore.App 2.1.9 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.NETCore.App 2.1.12 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.NETCore.App 2.2.6 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]

To install additional .NET Core runtimes or SDKs:
  https://aka.ms/dotnet-download

macOS 版

f:id:kondoumh:20190828225609p:plain

$  dotnet --info
.NET Core SDK (global.json を反映):
 Version:   2.2.401
 Commit:    729b316c13

ランタイム環境:
 OS Name:     Mac OS X
 OS Version:  10.14
 OS Platform: Darwin
 RID:         osx.10.14-x64
 Base Path:   /usr/local/share/dotnet/sdk/2.2.401/

Host (useful for support):
  Version: 2.2.6
  Commit:  7dac9b1b51

.NET Core SDKs installed:
  1.0.4 [/usr/local/share/dotnet/sdk]
  2.0.0 [/usr/local/share/dotnet/sdk]
  2.1.505 [/usr/local/share/dotnet/sdk]
  2.1.701 [/usr/local/share/dotnet/sdk]
  2.2.401 [/usr/local/share/dotnet/sdk]

.NET Core runtimes installed:
  Microsoft.AspNetCore.All 2.1.9 [/usr/local/share/dotnet/shared/Microsoft.AspNetCore.All]
  Microsoft.AspNetCore.All 2.1.12 [/usr/local/share/dotnet/shared/Microsoft.AspNetCore.All]
  Microsoft.AspNetCore.All 2.2.6 [/usr/local/share/dotnet/shared/Microsoft.AspNetCore.All]
  Microsoft.AspNetCore.App 2.1.9 [/usr/local/share/dotnet/shared/Microsoft.AspNetCore.App]
  Microsoft.AspNetCore.App 2.1.12 [/usr/local/share/dotnet/shared/Microsoft.AspNetCore.App]
  Microsoft.AspNetCore.App 2.2.6 [/usr/local/share/dotnet/shared/Microsoft.AspNetCore.App]
  Microsoft.NETCore.App 1.0.5 [/usr/local/share/dotnet/shared/Microsoft.NETCore.App]
  Microsoft.NETCore.App 1.1.2 [/usr/local/share/dotnet/shared/Microsoft.NETCore.App]
  Microsoft.NETCore.App 2.0.0 [/usr/local/share/dotnet/shared/Microsoft.NETCore.App]
  Microsoft.NETCore.App 2.1.9 [/usr/local/share/dotnet/shared/Microsoft.NETCore.App]
  Microsoft.NETCore.App 2.1.12 [/usr/local/share/dotnet/shared/Microsoft.NETCore.App]
  Microsoft.NETCore.App 2.2.6 [/usr/local/share/dotnet/shared/Microsoft.NETCore.App]

To install additional .NET Core runtimes or SDKs:
  https://aka.ms/dotnet-download

バージョンや runtime 同じですね。

docs.microsoft.com

チュートリアルを Visual Studio ではなく、dotnet コマンドと VS Code で進めていきます(チュートリアルには両方の手順が書かれています)。

dotnet コマンドリファレンスは↓にあります。

docs.microsoft.com

空のソリューションを作成します。

$ mkdir WebApiSample
$ cd WebApiSample
$ dotnet new sln
テンプレート "Solution File" が正常に作成されました。
$ ls
WebApiSample.sln

WebApi のプロジェクトを作成します。

$ dotnet new webapi -o TodoApi
テンプレート "ASP.NET Core Web API" が正常に作成されました。

作成後のアクションを処理しています...
'dotnet restore' を TodoApi/TodoApi.csproj で実行しています...
  TodoApi.csproj の復元が 1.51 sec で完了しました。

正常に復元されました。

$ ls
TodoApi          WebApiSample.sln

ソリューションに TodoApi プロジェクトを追加してビルドします。

$ dotnet sln WebApiSample.sln add TodoApi/TodoApi.csproj
プロジェクト `TodoApi/TodoApi.csproj` をソリューションに追加しました。

$ dotnet build
.NET Core 向け Microsoft (R) Build Engine バージョン 16.2.32702+c4012a063
Copyright (C) Microsoft Corporation.All rights reserved.

  WebApiSample/TodoApi/TodoApi.csproj の復元が 48.07 ms で完了しました。
  TodoApi -> WebApiSample/TodoApi/bin/Debug/netcoreapp2.2/TodoApi.dll

ビルドに成功しました。
    0 個の警告
    0 エラー

経過時間 00:00:03.44

以上、.NET Core SDK に含まれる dotnet コマンドだけで、ソリューション作成、プロジェクトの作成と追加、ビルドまでできてしまいました。

ここで VS Code でプロジェクトフォルダを開くと拡張のインストールを促されます。

marketplace.visualstudio.com

これは OmniSharp が開発する Language Server Protocol 準拠の拡張で、本家 Visual Studio レベルのコード補完、エラー解析機能を提供します。その他必要なソフトウェアが一式インストールされます。

チュートリアルに従ってコードを追加していきます。

データクラス

namespace TodoApi.Models
{
    public class TodoItem
    {
        public long Id { get; set; }
        public string Name { get; set; }
        public bool IsComplete { get; set; }
    }
}

Entity Framework の DbContext

using Microsoft.EntityFrameworkCore;

namespace TodoApi.Models
{
  public class TodoContext : DbContext
  {
    public TodoContext(DbContextOptions<TodoContext> options) : base(options)
    {
    }
    public DbSet<TodoItem> TodoItems { get; set; }
  }
}

これを Startup クラスに DbContext としてインジェクションします。UseInMemoryDatabase なんての使えるので開発が楽ですね。

using Microsoft.EntityFrameworkCore;
 :
using TodoApi.Models;

namespace TodoApi
{
    public class Startup
        :
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddDbContext<TodoContext>(opt =>
            opt.UseInMemoryDatabase("TodoList"));
            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
        }
       :
    }
}

あとは、Controller クラスに HTTP メソッドに対応するメソッドを追加していきます。

using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using TodoApi.Models;

namespace TodoApi.Controllers
{
  [Route("api/[controller]")]
  [ApiController]
  public class TodoController : ControllerBase
  {
    private readonly TodoContext _context;

    public TodoController(TodoContext context)
    {
      _context = context;

      if (_context.TodoItems.Count() == 0)
      {
        _context.TodoItems.Add(new TodoItem { Name = "Item1"});
        _context.SaveChanges();
      }
    }

    [HttpGet]
    public async Task<ActionResult<IEnumerable<TodoItem>>> GetTodoItems()
    {
      return await _context.TodoItems.ToListAsync();
    }
  
    [HttpGet("{id}")]
    public async Task<ActionResult<TodoItem>> GetTodoItem(long id)
    {
      var todoItem = await _context.TodoItems.FindAsync(id);
      if (todoItem == null)
      {
        return NotFound();
      }
      return todoItem;
    }

    [HttpPost]
    public async Task<ActionResult<TodoItem>> PostTodoItem(TodoItem item)
    {
      _context.TodoItems.Add(item);
      await _context.SaveChangesAsync();
      return CreatedAtAction(nameof(GetTodoItem), new TodoItem{ Id = item.Id }, item);
    }
  }
}

VS Code によるコード補完の様子。

f:id:kondoumh:20190829201524p:plain

デバッグの様子です。

f:id:kondoumh:20190829200536p:plain

Windows でも macOS でも全く同じ DX です。Node.js + TypeScript で Express 開発の時も相当いいと思いましたが、もはやほぼ Visual Studio (≠ Code) です。C# のような静的型付け言語には専用の IDE が要ると思っていたのですが・・。サーバーサイドのコードだけならもう Visual Studio なくてもできます。

blog.kondoumh.com

.NET Core SDK と VS Code があれば、快適な ASP.NET Core 開発環境が構築できることが分かりました。

さらに Remote Development を使えばリモートマシンやコンテナイメージに .NET Core SDK を導入しておき、手元は VS Code だけで開発ってのもできるでしょう。

blog.kondoumh.com

OmniSharp に対応した拡張を使えば、ターミナル の Emacs や Vim でもいけそうです。