Gradle, ProGuard, intellij-plugin周りの備忘録
intellij plugin開発時に難読化したbuildを生成しようとした時の備忘録。
流れ
- intellij marketplaceドキュメントを見て、ProGuardがいいかなと適当に判断
- ざっとググったらandroid関係の記事ばかりでよくわからなかったのでProGuardドキュメントへ
- そもそもgradleの書き方わからないし、javaのcompileからbuild生成の流れもわからないと厳しそうだったので遠回り。
- gradleのドキュメント でgradleの基本とjava pluginについての記事を読む
- java pluginのタスクを順に実行してみてcompile - buildの流れを把握
- proguardのタスクをどう組み込めばいいかを gradle-intellij-pluginのコードを見ながら検討
- 一旦proguardタスクを単体で実行できるかtry
- lib/rt.jarがない周りの問題にハマり、
dontwarn
でいいんじゃないかと判断 - (proguard設定のサンプル集)https://github.com/Guardsquare/proguard/tree/master/examples/gradle を見ながらkeep周りの設定
- lib/rt.jarがない周りの問題にハマり、
- gradleのドキュメントをなんども見返しながら、なんとかproguard taskをbuildに組み込み
理解したことmemo
- defaul scopeがproject
- DSL Referene Home のCoreTypeのProjectを見れば大体わかる
- projectがビルドのベース。multi projectというのもあるらしい。
- java pluginがprojectを拡張している
sourceCompatibility
とかはjava pluginが拡張したもの
manualの見方
- DSL Referene Home に主要なclassの説明がある
- 落ち着いて時間をかけて読んで大体わかるようになった
configurations
- implements fileList
- content is dependency files(because configurations is dependency configuration)
- Railsっぽい魔法感がある
- ビルドツールとしてのeasyさを追求している感じ
- 最初は訳がからないけど少しわかってきたら便利
- implements fileList
configuration fase and execution fase
file path convention
life cycle task
- Base Plugin が提供するtaskに色々紐付ける形になっている
build.build
とintellij.buildPlugin
どっちでも同じようにbuildできるのが不思議だった
task間のやりとり
- dependeOnで指定したtaskから色々情報が得られる
- taskの取得はdocumentのTaskContainerを見れば大体わかる
- AbstractTaskのpropertyを使う
- 必要であればsubTypeのcastして固有のpropertyを使う
configurations.compile.collect {}
のように、configurationsのdependencyからとるようなケースもある
- dependeOnで指定したtaskから色々情報が得られる
java compile flow outline
- what is fat jar
- compile java to class files
- jdk
- library jars
- jar
- class files
- resources
- build
- jar
- runtime dependency jars
proguard
- ${javaHome}/libs/rt.jar
- javaのcoreが入ったjar
- libraryjarsにこれを指定するのがデファクトスタンダードぽい
- javaのversionや配布元によってなかったりする
- shrink, optimazeするため、dependenciesのjarを指定する必要があって、適切に指定しないとwarnが出る
- obfuscateだけしたいなら
dontwarn, dontshrink, dontoptimize
を指定すれば、 jarを指定しなくてもok- depedency jarを省くとdependency内のclassをoverrideしたメソッド名も変更されてしまって壊れる(dependecy jarを指定して試してないからもしかして関係なく壊れる?)
- keepを指定することで避けられる。例えば下記のような
- depedency jarを省くとdependency内のclassをoverrideしたメソッド名も変更されてしまって壊れる(dependecy jarを指定して試してないからもしかして関係なく壊れる?)
- ${javaHome}/libs/rt.jar
keepclassmembers '!public class * { \ public protected <methods>; \ }'
振り返り
- Gradle魔法感があってとっつきにくそうと思ったけど、ドキュメントが丁寧に書かれていたので、ちゃんと読めばなんとか理解できた
- 既存のbuildの流れにちょっとしたタスクを組み込みたいだけでも色々理解しなければならないことが多くて大変だったけど、結局理解していく過程は充実感があるから、これもプログラマの醍醐味?
- 難読化も魔法じゃない
gomのトレース
パッケージマネージメントツール プロジェクトの依存するpackage(revisionを含む)を管理できる。 go標準のパッケージマネージメントツールがない時からあって、結構な利用者がいそう。
概要
データとしては、プロジェクト毎の依存パッケージ情報を記載するGomfileと、依存パッケージデータを格納するvendorディレクトリがある。
buildやtestなどのgoコマンドをラップしているgomコマンドが使えるようになり、gom経由で実行すると各プロジェクトのvendorディレクトリも認識されるようになる。
技術的要素
Gomfileの生成
- プロジェクトをscanして、 build.Import で依存packageのパスを取得できる
build.Package.Goroot(bool)
とbuild.IsLocalImport
でvendor packageかどうかを判別できる- 上記のパスを元に、GOPATH以下のpackage dirに移動して、
git rev-parse HEAD
でrevisionを取得できる
Gomfileをparseするときの正規表現
install
- GOPATHに
./vendor
設定した上で、go get package
することで、./vendor
にpackageを持ってきている
- GOPATHに
memo
- goの例外処理でひたらすerrorを返していくやり方は解り易くて良さそう
- 各種変数や関数の定義が色々なファイルに散らばってしまっている感があるけど、これぐらいの規模なら問題ない?(普通にIDEを使えば実際読み解きずらいとは感じなかった)
- コンソール出力の色付け
github.com/daviddengcn/go-colortext
便利そう
main.go
- flagArgsをsubArgsに切り出したり、flagArgs(0) あたりの処理、最初の引数がコマンドで、それ以降がコマンドごとの引数だということが解りやすい
- コマンドのswitch文が読みやすい
- genコマンドのswitchの入れ子、2階層ぐらいなら全然問題ない
exec_test.go
- run経由で go envを実行して取得したgopathの中に、tmp pkgのvendor dirが含まれているかをチェック。
- gomはgo ** 系のコマンドをラップしていて、PATHやGOPATHをvendorが読み込まれるようにしている。
- それがちゃんと指定されているかをテストしている。
- defer func(){...}() という書き方
- ioutil.Temp**系の関数を使うと面倒そうなpkg dirや標準出力のparse系のテストが割と簡単に書ける?
- cmdに渡すstdoutを、直接os.Stdoutを使うのではなく、変数のstdoutを経由させることでテストが可能になっている。
genGomfile
gopwtのトレース
概要
power assertのgolang版のgopwtのトレース
どうやったか
使い方と全体ディレクトリ構造とコードをざっくり見た後に、インクリメンタルに動作させつつコードを書き写していった。
gopwtの使い方
main_test.go
func TestMain(m *testing.M) { flag.Parse() gopwt.Empower() os.Exit(m.Run()) } func TestHoge(t *testing.T) { assert.OK(t, 2 == inc(2), "yeah!") } func inc(a int) int { return a + 1 }
上記を実行すると
--- FAIL: TestHoge (0.00s) assert.go:85: FAIL trace_gopwt_test.go:18 assert.OK(t, 2 == inc(2), "yeah!") | | | 3 false Assertion messages: - yeah! --- [int] inc(2) +++ [int] 2 @@ -1,1 +1,1@@ -3 +2
のような形で出力される。
empowerをしないと下記のようになる
--- FAIL: TestHoge (0.00s) --- FAIL: TestHoge (0.00s) assert.go:37: [FAIL Assertion] assert.OK(t, 2 == inc(2), "yeah!") Please call gopwt.Empower() in your TestMain(t * testing.M).It give you power. If you need more information, see http://github.com/ToQoz/gopwt AssertionMessage: - yeah!
どのように実現させているか?
empower内でやっていること
tmp dirにpackageを複製、調整し、 tmp dir上で exec.Command("go", "test")
を実行している。
(package複製時に empower()
は取り除いているので無限ループしない(取り除かないと無限ループするようなやり方で実現させているということ))
package複製時にやっていること
assert.OK
を powered表示用のメソッド translatedassert.OK
に書き換える。
その時に、translatedassertで使う色々な細かい情報を作っている。
translatedassert.OK
のメソッドの定義
func OK(t *testing.T, e bool, messages []string, header, filename string, line int, origexpr string, termw int, expectedPos, gotPos int, pvPairs ...posValuePair)
実際に書き換えられたコード
translatedassert.OK( t, 2 == translatedassert.FRVInterface(translatedassert.MFCall("{path to tmp dir}}/trace_gopwt_test.go", 24, 912, translatedassert.RVOf(inc), translatedassert.RVOf(2))), []string{"yeah!"}, `FAIL`, `trace_gopwt_test.go`, 24, `assert.OK(t, 2 == inc(2), "yeah!")`, 236, 19, 14, translatedassert.NewPosValuePair(14, 2, false, `2`), translatedassert.NewPosValuePair(16, 2 == translatedassert.FRVInterface(translatedassert.MFCall("{path to tmp dir}}/trace_gopwt_test.go", 24, 912, translatedassert.RVOf(inc), translatedassert.RVOf(2))), true, `2 == inc(2)`), translatedassert.NewPosValuePair(19, translatedassert.FRVInterface(translatedassert.MFCall("{path to tmp dir}}/trace_gopwt_test.go", 24, 912, translatedassert.RVOf(inc), translatedassert.RVOf(2))), true, `inc(2)`), translatedassert.NewPosValuePair(23, 2, false, `2`) )
下記の4つがテスト用の式を分割した形で表示させるための大事な情報。 表示調整用のpositionのint値や、元の式のstring、式を評価した値などが作られている。
設計メモ
プロジェクトを分割すると、
- テストをassert.OKの形式で書けるようにするところ
/assert
- power assert形式で、FAILした情報を見やすく表示してくれるところ
/translatedassert
- 元のテストファイルを解析し、必要な情報とともに
assert.OK
をtranslatedassert.OK
にpackageを書き換えるところ/translator
やってみてメモ
- テストの出力部分をごにょごにょ調整しているだけだと思ったらとんでもなかった
goでgoのプログラムをメタ的に扱う機能が充実してそう
- go fileをparseして一部書き換えるとかが割とカジュアルにできそう
ast nodeの再帰的な処理(子ノードや、ノードの種類に適応させた処理)が結構あって参考になりそう
- typeのassertionやswitchでの処理がかなりあってgo的な書き方の参考になりそう
translatedassert向けのpkgはかなり大仕事でコード量が多いけど、 translator/internal 内に閉じ込めているので、プロジェクト全体の見通しが良い
- /main.goからは translator.Translate()を呼び出して、作成したpkg上でexec.Command("go", "test") を実行するだけ
pkgを複製する処理の絡みでpathを色々調整する処理が結構複雑で理解が進みずらいところがあった
- PackageContextにわかりやすい名前でメソッドを定義したら解りやすくなりそう?
PackageContextでのpkgを扱う処理を CopyPackage, ReadPackage, TypecheckPackage, RewritePackage に分割していて解りやすかった
- まとめてやろうとしたらかなりカオスになってしまいそう
- 複雑な大きな処理をどういった視点で分割するかの判断は難しそう
- dependency pkgのcache周りの処理は理解しきれていない
fuelphpのvalidationについて
filedsetに対して定義できるものと、modelに対して定義できるものがある。
Fieldsetのvalidation
Fieldsetと1対1で紐付いたvalidationインスタンスのrunメソッド(return bool)を呼び出す。
$fieldset->validation()->run()
ruleの設定
各fieldに対して個別にadd_ruleで設定可能。
デフォルトで用意されているルールは下記サイトの下部を参照
http://fuelphp.jp/docs/1.9/classes/validation/methods.html
メッセージの設定
下記のいずれかの方法で可能
app/lang/ja/validation.php
に書く- Validationインスタンスに対して設定する
$fieldse->validation()->set_message(‘ルール名’, ‘メッセージ’)
- 各fieldに設定する
$fieldset->field(‘field名’)->set_error_message(‘ルール名’, ‘メッセージ’)
カスタムルール
validationインスタンスは _find_rule
メソッド内で、 $thi->callablesに設定されているオブジェクトの prefixが _validationのメソッドを探しいく。
デフォルトではvalidationインスタンス自身が設定されているので、Fuel\Core\Validation
内のprefixが_validationのメソッドが適用できるようになってい
る。
callablesはvalidationインスタンスのadd_callableで追加できるので、_validation_***
が定義されているクラスであれば何でもok(Fieldset自身とか、専用の Validation_Callable_Hoge のようなクラスを作ったりとか)
またadd_callableで設定しておかなくても、validationインスタンスのrunメソッドの第三引数に$temp_callableを渡せる。
modelのvalidationでカスタムルールをモデル内に定義できるのもこれで実現させてる。
https://github.com/fuel/orm/blob/1.9/develop/classes/observer/validation.php#L232
modelのvalidation
ミニマムのサンプル
<?php class Model_Post extends \Orm\Model { protected static $_properties = array( 'id', 'title' => [ 'validation' => [ 'required', 'max_length' => [1], 'contains_hoge' ] ], ); protected static $_observers = array( 'Orm\Observer_Validation' => ['events' => ['before_save']] ); protected static $_table_name = 'posts'; public function _validation_contains_hoge($val) { return (bool)preg_match('/hoge/', $val); } }
$_properties
のvalidation
に設定できる_validation_***
という命名規則でmodelにカスタムバリデーションを定義できる- 上記の指定だと
before_save
時にバリデーションが実行される - fail時は
Orm/ValidationFailed
がthrowされる - errorメッセージは
app/lang/validation.php
でruleごとに定義できる(model内では設定できないぽい) - 全体の表示の用のメッセージは $e->message で取得(フォーマットはConfigのvalidationで設定可能)
- エラーの個別の取得は $e->get_fieldset() でmodelから生成したFieldsetが取得できるので、Fieldsetのインターフェース経由で行える。
$e->get_fieldset()→error('title')
仕組み
modelのpropertiesの設定から動的にFieldsetを生成し、Fieldsetのvalidationの機能を利用して実現させてる。
bootstrapのモーダルを複数開いたときの問題と対処法
bootstrapのモーダルは複数重ねて開いたときにいくつか細かい問題が発生する。 versionは3系。
2つめのモーダルの背景(.modal-backdrop
)より1つめのモーダルが上に表示されてしまう
標準の挙動
背景(.modal-backdrop
)はmodalごとに、bodyにappendされる(z-indexは.modalのz-index設定値 - 10)
z-indexのスタック文脈について詳しい記事
z-index再入門 - z-indexの仕組み | CodeGrid
問題点
モーダルの数、重なりに関係なく
.modal
は.modal-backgroup
より上に表示される。
対処法
.modal
と.modal-backdrop
のz-indexをstackされている要素を考慮するようにhackする
$(document).on('show.bs.modal', '.modal', function () { var zIndex = 1040 + (10 * $('.modal:visible').length); $(this).css('z-index', zIndex); setTimeout(function() { $('.modal-backdrop').not('.modal-stack').css('z-index', zIndex - 1).addClass('modal-stack'); }, 0); });
bodyスクロールの調整
標準の挙動
body.modal-open
に overflow:hidden
を指定することで、モーダルが開いているときの、body要素のscrollを無効化している
問題点
.modal-open
はbodyに対して設定しているので、モーダルが複数ある場合は考慮されていない。
なので、2つめのモーダルを閉じたときに body.modal-open
が外れてしまい、まだ開いているモーダルがあるのにbodyのスクロールが有効になってしまう。
対処法
モーダルを閉じた後のeventで、まだ開かれているモーダルがあるときは、再度 body.modal-open
をつける
$(document).on('hidden.bs.modal', '.modal', function () { $('.modal:visible').length && $(document.body).addClass('modal-open'); });
right paddingの調整
標準の挙動
モーダルを開く際に、bodyに縦スクロールが発生している場合、.modal
(.modal-dialog
ではない)に対してスクロールバーの幅分paddingRightを指定する。
結果モーダルはスクロールバーの幅分を除いた表示領域内でセンタリングされる。
問題点
2つめのモーダルを開く際はbodyに overflow: hidden
が指定されているため(body.moda-open
によって)スクロールバーが発生していない判定になり、paddingRightが設定されない。
結果、2つめのモーダルが若干右にずれる形になる。
また、1つめのモーダルで縦スクロールが発生している場合、1つめのモーダルはスクロールバー幅分を除いた領域でセンタリングされるが、2つめのモーダルはスクロール幅分を考慮しない領域でセンタリングされるので、2つめが若干右にずれる。
対処法
うーむ。。
参考
デザインパターン入門【マルチスレッド編】備忘録
android開発を少しすることになってマルチスレッド周りの知識が残念な感じだったので、良い本だと噂の
を読んだので備忘録。
メモ
- synchronizedがあったら何か守るべきものがあると考える
- InterruptedException
- throws InterruptedExceptionがついているメソッドは以下のように認識すると良い
- 時間がかかる
- キャンセルできる
- Thread.interruptメソッドは直接InterruptedExceptionを投げるわけではなく、Threadをinterrupt状態にするだけ
- Threadがwait,sleep,joinなどの待ち状態のときにinterrupt状態を確認していて、interrupt状態の場合にInterruptedExceptionを自ら投げる
- 通常の処理を行っているときに急にInterruptedExceptionが発生することはない
- throws InterruptedExceptionがついているメソッドは以下のように認識すると良い
パターン図
いくつかパターンをピックアップして、PlauntUMLの練習がてら図で表してみる。
続きを読むvuexでのデータの扱いについて少し困ったので整理
目的
vuexを使っているある程度複雑なvueコンポーネント上で、定数的なものをどう扱っていいか解らなかったので、一旦考えを整理しておきたい。
現状の整理
なぜvuex?
コンポーネントの階層が4つまであって、propsだけで実現しようとすると、rootコンポーネントのpropsを最下層まで渡していくのが面倒だし、挙動や設計を少し変えようとした場合の変更箇所も増えるので大変。
vuexを使うと、storeを共有できるので、最下層のコンポーネントからも直接storeからstateを参照、更新(mutationの実行)ができるので楽。
vuexのデメリット?
コンポーネントが依存する先が増えるので、挙動を理解するのが少し大変になるのと、再利用性は低くなる。
props, data, storeそれぞれの役割を理解して使わないとカオスになる(なった。経験値が必要) 勿論ちゃんと整理して使えばかなり便利。
props, data, store
何に困ったか?
今回作っていたのはがっつりSPAという感じではなく、phpで動いているwebサービスのある一画面をSPA的に実装する形。
なので、
- php側からjsに各値を渡す
- 渡された値をstoreのデフォルト値として設定
という感じ。
php → js に渡したいデータとして、state的なもの以外にphp側では定数として定義されているデータも渡す必要があって、割と大事なデータで色々なコンポーネントから参照される。
window.constants
のように定義すればいけるけど、グローバル変数に依存するのは何か違くない?と思い及び腰に。
storeに突っ込んでおけば、 mapStateでどのコンポーネントからでも必要があれば簡単に参照できるし、依存先もstoreだけになるかあらまあいいか。。と思いそうしたけどなんか違うよな。。と。(じっくり考える余裕がなかった反省)
考察
storeに突っ込むのは何がいけない?
storeはアプリ全体でリアクティブなフローを実現するための仕組みなので、state的なデータを保持するためのものなのでそもそも使い方が間違っている。
storeにconstants的なデータも入れてしまうと、stateとconstantsが入り乱れてしまうので、何が動的に変わる想定のデータで、何がimmutableな定数的なデータとして想定されているものか解らなくなってしまい、コードを読み解くのが困難になってしまう。 (読み解くの難しいコードはどんな状況であれ、避けるべきで良くないもの)
どうすべきだったか?
- constants用のclassを定義する
- 名前をちゃんと付けることで、windowに直接入れ込むより用途が明確になる
- 各プロパティのgetterを定義することで、他にどういったデータがあるか解りやすくなる
- php側から渡されるjsonをloadする関数
loadJson
をexportして、loadしたらfreezeされる- 「phpから渡されるデータをload後にimmutableになる」という設計の意図が解りやすくなる
class HogeConstants { constructor(json) { // json parse .. } get a() { return this.a } get b() { return this.b } } let instance export function loadJson(json) { instance = new HogeConstants(json) Object.freeze(instance) // singleton pattern https://www.sitepoint.com/javascript-design-patterns-singleton/ } export function getHogeConstants() { return instance }
phpとの連携部分のjs部分で、 loadJson
を呼び出しあげて、
あとはこのデータを使いたいコンポーネント側でimportすれば参照できるようになる。
アプリの規模によってはwindowの直接設定しても問題にならないこともある気がする。
あんまりvuexどうたらは関係なく、jsもある程度の規模になったらある程度設計を考えないと、という話?