iOS(Swift)プロジェクトにSonarCloudを導入する

今回は、静的解析ツールのSonarQubeのクラウド版、 SonarCloudを導入します。

Travis, fastlane導入までは、前回の記事をご参照ください。 scior.hatenablog.com


環境

Ruby関連は、

  • ruby 2.5.1p57
  • bundler 1.16.2
  • fastlane 2.104.0

SonarQube

SonarQubeは、静的解析によって、脆弱性やバグ、コードの重複などを診断するサービスです。
他の機能で測定した、Coverage Reportも表示してくれます。
サポートしている言語も多いので、今回Swiftにも導入してみました。

Continuous Inspection | SonarQube

見た目はこのようになっています。
(ほぼ空のプロジェクトなので、クリーンな状態です。)

f:id:scior:20180917223853p:plain

ローカルや自前のサーバーで使うこともできますが、
今回はホスティング版のSonarCloudを利用します。

こちらは、publicリポジトリに対しては無料です。

自前で環境を用意したい方はこちらです。

Downloads | SonarQube

Continuous Inspection

アカウント登録

SonarCloud https://sonarcloud.io/

こちらから、各種ソースホスティングサービス(GitHubなど)と連携して、アカウントを作成します。
登録の際にリポジトリを指定して読み込めますが、後から追加で読み込む際は、
SonarCloudからでは出来ないので、GitHub側から指定します。

GitHubのアカウント設定のApplicationsの画面を開き、

f:id:scior:20180917221523p:plain

Configureからリポジトリを追加します。

f:id:scior:20180917221529p:plain

Select repositoriesで追加したいリポジトリを選びます。

SonarCloudの設定

リポジトリの追加後、この画面に遷移します。

f:id:scior:20180917221535p:plain

適当な名前をつけて、tokenを生成します。

f:id:scior:20180917221538p:plain

上の画像のようにOther, macOSを選択して、sonar-scannerをダウンロードしてPATHを通すと、
一番下のコマンドで、ローカルのソースコードを直接SonarCloudにかけることができます。
(ただし、Swiftの場合、次のsonar-project.propertiesで、
sonar.language=swiftを設定してからでないと、うまく動きません。)

sonar-project.properties

プロジェクトルートにsonar-project.propertiesを作ります。
今回は、MinimumWKWebViewというプロジェクトを例にします。

github.com

# SonarQubeで割り振られるプロジェクトごとのキー
sonar.projectKey=Scior_MinimumWKWebView
sonar.projectName=MinimumWKWebView
sonar.projectVersion=1.0
# 今回はSonarCloudを用いるのでそちらに向ける
sonar.host.url=https://sonarcloud.io

sonar.language=swift
sonar.sources=MinimumWKWebView
# 静的解析の対象を指定する
sonar.inclusions=MinimumWKWebView/**
# カバレッジレポートの場所を指定
sonar.coverageReportPaths=sonarqube-generic-coverage.xml

sonar.projectKeyはSonarCloudに記載されているので、そちらを指定します。

sonar.inclusionsで解析の対象を指定します。
こちらを記述しない場合、fastlaneの.swiftファイルなども解析されてしまい、
ひどい結果になります。

Travis CIとの連携

Using SonarCloud with Travis CI - Travis CI

こちらのTravisのドキュメントを参考に進めていきます。
TravisCUIツールを用いて、先ほど取得したtokenを暗号化します。
(インストール: gem install travis)

travis encrypt [token]

出力された暗号化されたtokenを.travis.ymlに追記します。
organizationはSonarCloudのページに記載されているキーです。

dist: trusty

addons:
  sonarcloud:
    organization: "scior-github"
    token:
      secure: ********

scriptの適切な位置に、sonar-scannerを追加します。

script:
  # -XオプションでDebug出力される
  - sonar-scanner -X

ここまでで、静的解析の結果がSonarCloudに反映されます。

Coverage Reportの反映 (Xcode 9.3+)

xccovの利用

Xcode 9.3からxccovを使って、Coverage測定ができるようになりました。
xcodebuildで生成される.xccovarchivedファイルを、
SonarCloudが解釈できる形にパースして、Travisから渡します。

SonarQubeの解説: Swift Coverage Results Import - Plugins - Doc SonarQube

パースするシェルスクリプトは、下のものを利用します。

github.com

こちらのxccov-to-sonarqube-generic.shを用いて、XMLに変換します。
このシェルスクリプトを、プロジェクトの適当な場所にコピーします。
(今回は./utils/に置きました。)

  • .travis.yml
script:
  - fastlane coverage # 前回のfastlaneの時のスクリプト
  - bash utils/xccov-to-sonarqube-generic.sh Build/Logs/Test/*.xccovarchive > sonarqube-generic-coverage.xml
  - sonar-scanner -X

fastlaneのビルド結果は、デフォルトではDerivedData云々の、
探しづらい場所に出力されてしまうので、出力先を./Buildに変更します。

  • Fastfile
default_platform(:ios)

platform :ios do
  desc "Test"
  lane :test do
    scan(
      derived_data_path: "Build"
    )
  end
end

slatherなど他のコマンドを記述している場合は、そちらにも追記します。

試していないこと

fastlaneにもsonarコマンドがあるのですが、カバレッジの反映まで出来るかどうか、
encryptされたtokenが使えるかどうかなど、不明点が多いので未検証です。

その他参考

SonarQube Integration for Swift – SayTech

Express向けのSwaggerミドルウェア

Express向けのSwaggerパッケージを色々探しましたが、
依存パッケージが少なく、npm auditで引っかからなかった、
こちらを紹介します。

Swagger UI Express

www.npmjs.com

github.com

使い方

ExpresssのRouterと合わせて使うと、このような感じです。

const express = require('express');
// eslint-disable-next-line new-cap
const router = express.Router();
const swaggerUi = require('swagger-ui-express');
const swaggerDocument = require('./swagger.json');

router.use('/', swaggerUi.serve);
router.get('/', swaggerUi.setup(swaggerDocument));

const apiBaseUrl = '/api/v1/';

router.get(apiBaseUrl + 'about', (req, res) => {
  res.send('about');
});

module.exports = router;

yamljsを使うとyamlからも読み込めるので、Swagger Editorでも編集しやすいです。

その他

swagger-node

https://github.com/swagger-api/swagger-node

最近(2018.09現在)更新されていないようで、依存パッケージが古いみたいです。
脆弱性だらけなので使えなそう。

generator-express-no-stress

https://github.com/cdimascio/generator-express-no-stress

Swagger以外にも、ESlintやPinoやBabelを含めた雛形を作ってくれますが、
今回はSwaggerだけ欲しいということで外れました。

memo: CentOS7系のfirewalld

firewalld

CentOS6系までのiptablesの代わりに、7系からではfirewalldを用います。
中身はiptablesのままらしいです。

設定の操作はfirewall-cmdを用います。

バージョン

CentOS Linux release 7.5.1804 (Core)

共通操作

現在の設定の確認

firewall-cmd --list-all

こんな感じで出ます。

public (active)
  target: default
  icmp-block-inversion: no
  interfaces: eth0
  sources:
  services: ssh dhcpv6-client
  ports:
  protocols:
  forward-ports:
  source-ports:
  icmp-blocks:

設定再読み込み

一時的な設定を破棄して、設定を再読み込みをするときは、

firewall-cmd --reload

systemctlでもいけます。

systemctl restart firewalld

各種設定

サービス追加

httphttpsなどの元からデフォルト値があるものは、 add-serviceで追加すると、対応するポート(80,443...)を開けてくれます。

firewall-cmd --add-service=http

ポート追加

それ以外の物は、ポート番号を指定して開ける事ができます。

firewall-cmd --add-port=1234/tcp

永続設定

firewalldが再起動した場合にも残る設定をしたいときは、--permanentをつけます。

firewall-cmd --add-port=1234/tcp --permanent

Zone

設定値をまとめたプロファイルのようななものです。
デフォルトではpublicになっていますが、 zoneごとに設定を変えて、異なるネットワークインターフェースに適用したりできます。

デフォルトゾーンの変更

# externalの初期値ではsshのみ許可されます
firewall-cmd --set-default-zone=external

設定値変更

firewall-cmd --zone=external --add-service=http --permanent

Springのcontrollerにおけるvalidation

Spring Frameworkにおけるvalidation

http://terasolunaorg.github.io/guideline/5.4.1.RELEASE/ja/ArchitectureInDetail/WebApplicationDetail/Validation.html

詳しくは上記リンクも参照したほうが良いですが、 カジュアルに使うように残します。

Hibernate Validator

実装はHibernate Validatorなので、javax.validation.constraintsアノテーションを使います。

主なアノテーション

@NotNull // nullでないことをチェック

/**
* Stringに対して使えるアノテーション
*/
@NotEmpty // 空文字列でないことをチェック
@Size(min = 1, max = 10) // 文字列の長さが1以上10以下であることをチェック
@Pattern(regexp = "[0-9A-Z]*") // "[0-9A-Z]*"の正規表現にマッチしているかチェック
@Email // Emailアドレスとしてvalidなものかチェック

/**
* Integerなど整数型に対して使えるアノテーション
*/
@Min(3) // 3以上であることをチェック
@Max(100) // 以下であることをチェック

Formに対するvalidation

制約を記したFormクラスを用意して、コントローラで受け取るのが基本です。

Formクラス

ビューの入力フォームに対応するFormクラスを書きます。

@Data
public final class SearchForm {
    @NotNull
    @Size(min = 1, max = 100)
    @Pattern(regexp = "[0-9a-zA-z]*")
    private String message;
}

Controllerクラス

Formのバリデーションに失敗するとBindingResultにエラーが入ります。

@Controller
@RequestMapping("")
public class SearchController {
    @PostMapping("serach")
    public ModelAndView search(
        @Validated @ModelAttribute final SearchForm searchForm,
        final BindingResult bindingResult
    ) {
        final ModelAndView modelAndView = new ModelAndView();
        modelAndView.setViewName("hoge");
        // Formのバリデーションに失敗するとBindingResultにエラーが入る
        if (bindingResult.hasErrors()) {
            // エラー時の処理
        }

        // ...

        return modelAndView;
    }
}

エラー時の実際の処理は、エラーページに飛ばしたり、 modelに何かを付け加えたりといったものになります。

エラーメッセージは、デフォルトで親切なものが出ます。
Constraintごとにmessage属性を付与して、カスタムすることができますが、
セキュリティ上の懸念がある場合は、modelに付与して、一括で同じメッセージを出しても良いと思います。

Form以外に対するvalidation

Form以外の普通の引数に対するvalidationでも、不正なリクエストを弾く場合などで、Hibernate Validatorは有用です。

@Controller
@RequestMapping("")
public class HogeController {
    @PostMapping("add")
    public ModelAndView add(
        @RequestParam("quantity")
        @NotNull
        @Min(1)
        @Max(100)
        @Valid final Integer quantity) {

        // ...

    }
}

(ここの場合Springの@Validatedではなく、Hibernateの方の@ValidでもOKです)

ここでinvalidだった場合、中に入る前に、javax.validation.ConstraintViolationExceptionが投げられます。

Springの場合はControllerで投げられた例外は@ExceptionHandlerで拾うことができるので、
@ControllerAdviceの中に書いておけば、全ての@Controllerから投げられたConstraintViolationExceptionを拾うことができます。

@ControllerAdvice
public class MyExceptionHandler {
    // 400 Bad Requestを返してエラーページに飛ばす
    @ExceptionHandler(ConstraintViolationException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public String handleConstraintViolationException() {
        // ログ落としたり...
        
        return "error";
    }
}

書いてないこと

自前のconstraint annotationを作ることもできます。
長くなったので、また今度ということで… (忘れる😩)

Lombokはいいぞ

Lombokとは

いい感じにAnnotaitionで、Javaのよく出てくる処理を書いてくれるやつ。
使うと世界が変わる。

網羅的にざっと書きます。

共通テク

うまくいかなかったらIDEのDelombokでバラして考えてみる。
ドキュメントも充実している。

よく使う

@Getter, @Setter

https://projectlombok.org/features/GetterSetter

名前の通りGetter,Setterを自動で作ってくれる。
クラスにもフィールドにも使用可能で、final修飾されたフィールドのSetterはもちろん作られない。

@Getter @Setter
public class Hoge {
    private int val;

    // ここにgetVal(), setVal(int val)みたいなのが作られるイメージ
}

フィールドだと、

public class Hoge {
    @Getter @Setter
    private int val;
}

残念ながらsynchronized相当のものは作れない。

@ToString

https://projectlombok.org/features/ToString

クラスの先頭につけると、toStringのオーバーライドメソッドがいい感じに生成される。
迷ったらつけとけばいいと思う。

@ToStirng
public class Hoge {
    private int val;
    private String hoge;

    // @Override public String toString() が生成される
}

循環参照でハマったりしてtoStringで出したくないフィールドがあるときは、

@ToString
// あるいはここで、@ToString(exclude = "secret")
public class Hoge {
    private int val;
    @ToString.Exclude private String secret;
}

こんな感じで指定する。

@EqualsAndHashCode

https://projectlombok.org/features/EqualsAndHashCode

equalsを用意するときにはhashCodeも用意しようということで、いっぺんにやってくれるやつ。
迷ったらつける。

@Data

https://projectlombok.org/features/Data

ここまでやった奴を全部つけてくれるアノテーション
@Setter@Getter@ToString@EqualsAndHashCode@RequiredArgsConstructorをつけてくれる。

@Data
public class Hoge {
    private int var;
    private String hoge;

    // get, set, toString, equals, hasCode, canEqualsができる
    // この場合Hoge()もできる。詳細は後述
}

めんどくさいときは、これだけつけておけばOK。
@RequiredArgsConstructorについては後述。

@xxxArgsConstructor

https://projectlombok.org/features/constructor

  • @NoArgsConstructor
  • @AllArgsConstructor
  • @RequiredArgsConstructor

この3種類がある。 クラスの先頭につけると、 @NoArgsConstructorは引数なしのコンストラクタ、 @AllArgsConstructorは全てのフィールドと対応する引数を持つコンストラクタを生成する。

@RequiredArgsConstructorfinalフィールドに対してのみ初期化を行うコンストラクタを生成する。

@RequiredArgsConstructor
public class Hoge {
    private final int val;
    private String hoge;

    /*
    このようなコンストラクタができる
    public Hoge(int val) {
        this.val = val;
    }
    */
}

@NonNull

https://projectlombok.org/features/NonNull

nullチェックを生成する。引数にnullが入ってきた時点でNullPointerExceptionを吐いて落とす。
引数が多くなってくると、nullチェックも多くなってくるのでアノテーションでできると便利。

// nullが入ってきたら中に入る前に落としてくれる
pubilc void hoge(@NonNull final String message) {
    message.hashCode();
}

知らなくても困らないやつ

@Builder

https://projectlombok.org/features/Builder

Builderパターンを自動生成する。

@Builder
public class Hoge {
    private int val;
    private String name;

    // ごちゃごちゃBuilderパターンができる
}

使うときは、

Hoge hogehoge = Hoge.builder()
    .val(1)
    .name("a")
    .build();

TPOに合わせて使うと便利かも。

@SneakyThrows

https://projectlombok.org/features/SneakyThrows

問題児

チェック例外を非チェック例外をとして、ある意味握りつぶす。
使いどころは、コード上チェック例外が起こり得ないけど、Javaの仕様的にtry catchを書くのを強いられて、めんどくさい時(ということだと思う)。

rethrowみたいなの自作する時とかにも使えるかもしれない…。

普通に実装するのも、そんなに大変ではなさそう。

@SuppressWarnings("unchecked")
public static <E extends Throwable> void sneakyThrow(Throwable e) throws E {
    throw (E) e;
}

@Slf4j

つけるとSlf4jのloggerを作ってくれる。

@Slf4j
public class Hoge {
    // これができる
    // private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(Hoge.class);
}

ありがたみが薄い。

まとめ

Lombok使うとJavaが楽しくなります。

SwiftLint, fastlane, Travis CI導入まで (Xcode 9.4)

iOS開発(Swift)でCIまでを動かす際の手順です。
今回は、Xcode 9.4, Swift 4(3でも可)です。

サンプルリポジトリ:
github.com

導入するツール

  • SwiftLint(0.26.0)
    コーディングスタイルの静的解析をしてくれます。
    空行2連続など、軽微な間違いは自動修正してくれます。

  • fastlane(2.99.1)
    ビルド、テスト、デプロイなどなんでもやってくれますが、
    今回はテストにのみ使います。

  • Travis CI
    GitHubのプルリクエスト、プッシュにトリガーして、
    fastlaneを自動で動かします。

あらかじめ、XCTest、XCUITestを含むプロジェクトを用意します。

Swiftlint

https://github.com/realm/SwiftLint
基本的にGitHubのREADMEの通りに進めます。

HomebrewでSwiftLintをインストールします。

brew install swiftlint

プロジェクトのBuild Phases > Run Scriptに下記スクリプトを追加します。
これでビルドごとにlintが動きます。

if which swiftlint >/dev/null; then
    swiftlint autocorrect
    swiftlint
else
    echo "warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint"
fi

今回は、一度自動修正をかけてから、lintを動かします。

そして、xcodeprojと同じディレクトリに.swiftlint.ymlを置きます。
設定はお好みですが、trailing_whitespaceは最初に自動生成されるコードに対しても警告を出しまくるので、 一旦止めました。

disabled_rules:
- trailing_whitespace

opt_in_rules:
- attributes
- closure_end_indentation
- closure_spacing
- conditional_returns_on_newline
- empty_count
- explicit_init
- fatal_error_message
- first_where
- force_unwrapping
- implicitly_unwrapped_optional
- number_separator
- object_literal
- operator_usage_whitespace
- overridden_super_call
- private_outlet
- prohibited_super_call
- redundant_nil_coalescing
- switch_case_on_newline
- valid_docs

excluded:
- Pods/
- Carthage/
- vendor/bundle

line_length: 300

後述しますが、CocoaPodsCarthageのパスと合わせて、 vendor/bundleもlintの対象から外して置きます。

Fastlane

https://docs.fastlane.tools/getting-started/ios/setup/

公式ではgem installを使った方法も載っていますが、 ここではbrew caskでインストールします。

brew cask install fastlane

xcodeprojと同じディレクトリにGemfileを作成します。

source "https://rubygems.org"

gem "fastlane"

今回は、bundlerfastlanevendor/bundleに置きます。
bundler自体のインストール手順は割愛します。
インストールが終わったら、fastlane initで、 テンプレートファイルを生成します。

# with bundler
bundle install --path vendor/bundle
bundle exec fastlane init

この際に、vendor/bundleにlintがかかるとビルドが通らなくなるので、 excludeして置きます。

次に、fastfile/Fastfileを編集して、laneを定義します。
今回はテストを実行するだけなので、run_testsのみを用います。

# Sample
default_platform(:ios)

platform :ios do
  desc "Test"
  lane :test do
    run_tests(devices: ["iPhone 8 Plus"])
  end
end

今回作ったlaneは次のように実行できます。

bundle exec fastlane test

テスト結果は、コンソールとfastlane以下のHTMLファイルに書き出されます。

Travis CI

https://travis-ci.org/

まずは上記リンクから流れに任せて登録します。
GitHubアカウントの連携を許可するだけで、登録できます。

xcodeprojと同じディレクトリに.travis.ymlを作成します。

language: objective-c
osx_image: xcode9.4

script: fastlane test

今回は、fastlaneを用いるので、scriptでコマンドを指定します。

プロフィールのところから、リポジトリの一覧が出ると思うので、 そこから今回使うリポジトリの設定をオンにします。
f:id:scior:20180716110659p:plain 

masterブランチにのみTravis CIを通したいときは、
以下の特定ブランチのみにトリガーする設定を.travis.ymlに追加します。

branches:
  only:
    - master

ここまで設定できると、Travisのログはこのような感じになります。

f:id:scior:20180716111804p:plain

あとはGitHub側で、CIをpassしないと、プルリクがマージできない設定などを適宜追加します。

今後やること

ここまでで最小限の設定なので、もう少しカスタムしたり、
deployまで行える設定をしたいと思います。

Perl6で開基から開集合系を生成する

10年ぶりくらいにPerl触ってます。RubyPythonより好きです。

コード

((1).Set, (2, 3).Set, (2).Set).combinations.map({.reduce(&infix:<∪>)}).Set.say;
> set(set() set(1 2 3) set(1 2) set(1) set(2 3) set(2))

解説

((1).Set, (2, 3).Set, (2).Set)

開基です。Xとします。

.combinations

冪集合2^{X} (の部分集合)を求めるメソッドです。ちなみに後ろに数値を指定すると、その位数の集合だけ取り出せます。 この場合、

()
(set(1))
(set(2 3))
(set(2))
(set(1) set(2 3))
(set(1) set(2))
(set(2 3) set(2))
(set(1) set(2 3) set(2))

となります。

.map({.reduce(&infix:<∪>)})

2^{X}の各元に.reduce(&infix:<∪>)を適用します。
reduceは配列の要素に対してbinary operatorを順番に適用し、その結果を返します。
&infix:<∪>は記号の通り、集合のunionをとるので、各B \in 2^{X}に対して、\bigcup_{A \in B}Aを返します。

結果はリストなので、.Setで重複を取り除きます。

ひとこと

集合の演算をそのまま数学記号でかけるので分かりやすいですが、打ちにくいです。
今回はinfixの形で利用しましたが、普通の演算子と同じように使うこともできます。

参照

https://docs.perl6.org/language/setbagmix
https://docs.perl6.org/type/Array