toge's diary

コンピュータ関連の趣味をつらつらと。

Conanで依存関係の衝突にあって対処した話

直近困ったのでメモ。

TL;DR

  • Conanでパッケージ衝突がおきたら、パッケージ依存グラフ機能を使おう
  • 自分でレシピを書くことがあったら、依存パッケージのバージョンはできるだけ範囲指定してほしい(願望)

OpenCVとTesseract OCRを使ってみたかった

相変わらずC++で何かをする時にはパッケージマネージャとしてConanを使っています。 OpenCVとTesseract OCRを同時に利用したい場合が出てきたんで、こんなconanfile.txtを書くことになりました。

[requires]
opencv/4.5.1
tesseract/4.1.1

[generators]
cmake

で、このconanfile.txtがあるディレクトリでconan install .すると、こんなエラーが出てしまいます。

% conan install .
Configuration:
[settings]
arch=x86_64
arch_build=x86_64
build_type=Release
compiler=gcc
compiler.libcxx=libstdc++11
compiler.version=10.2
os=Linux
os_build=Linux
[options]
[build_requires]
[env]

WARN: libtiff/4.1.0: requirement libwebp/1.1.0 overridden by leptonica/1.79.0 to libwebp/1.0.3 
ERROR: libtiff/4.1.0: Incompatible requirements obtained in different evaluations of 'requirements'
    Previous requirements: [zlib/1.2.11, xz_utils/5.2.5, libjpeg/9d, jbig/20160605, zstd/1.4.5, libwebp/1.1.0]
    New requirements: [zlib/1.2.11, xz_utils/5.2.5, libjpeg/9d, jbig/20160605, zstd/1.4.5, libwebp/1.0.3]

うーん、よく分からないですね。 OpenCVとtesseractなのに、libtiffがlibwebpのこと文句言ってるし…。

パッケージ依存グラフで原因を調べてみる

こういうのは大抵パッケージの依存パッケージの衝突が原因です。 Conanのパッケージ依存グラフ機能(experimental)を使うと図で表示されて便利だったりします。 docs.conan.io ※Graphizのインストールが必要になります。

OpenCVの依存グラフ

最初にOpenCVの依存グラフ見てみましょう。

% conan info opencv/4.5.1@ --graph=opencv.dot   
% dot -Tpng opencv.dot > opencv.png   

f:id:toge:20210116024447p:plain
OpenCVの依存グラフ

OpenCV -> libtiff -> libwebpの依存が見えますね。

Tesseract OCRの依存グラフ

次はTesseract OCRの依存グラフです。

% conan info tesseract/4.1.1@ --graph=tesseract.dot    
% dot -Tpng tesseract.dot > tesseract.png   

f:id:toge:20210116024733p:plain
Tesseract OCRの依存グラフ

webpについて2つの依存経路がありますね。

  • Tesseract OCR -> leptonica -> libtiff -> libwebp
  • Tessearct OCR -> leptonica -> libwebp

そしてwebpのバージョンが1.0.3に変化しています。

なぜこんなことが起きるかというと、leptonicaのConanレシピがwebpのバージョンを1.0.3に決め打ちしているためです。

https://conan.io/center/leptonica?version=1.79.0&tab=recipe

        if self.options.with_webp:
            self.requires("libwebp/1.0.3")

そうするとleptonicaが依存するlibtiffも空気を読んでwebpのバージョンを1.0.3に変更してしまっているんです。 一方でOpenCV側で依存しているlibtiffは特に制約がないのでレシピの通り1.1.0を使います。

https://conan.io/center/libtiff?version=4.1.0&tab=recipe

        if self.options.get_safe("webp"):
            self.requires("libwebp/1.1.0")

これで見事に「libtiffが依存するlibwebpのバージョンが衝突する」という事態が起きてしまいました。

回避策

回避策は何個かあると思います。 私が考えついたのは以下の2つ。

  • libtiffレシピのoptionでwebpを無効にする
  • leptonicaレシピのoptionでwebpを無効にする

他のプロジェクトでの汎用性を考えると、libtiffを使う場合に毎回webpを無効にするオプションをつけるのは面倒くさいので、(そうしないとlbtiffのバイナリが2バージョンできてしまう)今回はleptonicaレシピのoptionでwebpを無効にすることにします。 どうせ画像の読み書きはOpenCVでやるのでleptonicaの機能が減っても困りませんし。

% conan info tesseract/4.1.1@ -o leptonica:with_webp=False --graph=tesseract_wo_webp.dot
% dot -Tpng tesseract_wo_webp.dot > tesseract_wo_webp.png   

f:id:toge:20210116030821p:plain
webpを無効化したTesseract OCRの依存グラフ

ちゃんとlibwebpのバージョンが1.1.0になりました。 これならOpenCVと衝突しなくなります。

conanfile.txtに結果を反映

次のようなconanfile.txtにすることで、衝突なくinstallができるようになりました。

[requires]
opencv/4.5.1
tesseract/4.1.1

[generators]
cmake

[options]
leptonica:with_webp=False

補足: 本来はどうあるべきか?

汚い逃げ方をしてしまいましたが、本来はレシピがお行儀良く依存関係を書いてくれていればいいのになぁと思います。 Conanでは依存パッケージのバージョン番号を範囲で指定できるのでleptonicaもlibtiffも範囲指定してくれていれば良かったんですよね。

        if self.options.with_webp:
            self.requires("libwebp/[>=1.0.3]")

もちろんバージョンによってAPIや内部動作が変わるので、細かいバージョンの組み合わせをいちいちチェックしてられないのだろうとは思います。

そう考えると今回のようなテクニックを使う場面はまだまだありそうですね。

xcodeをバージョンアップしてもapple clangがアップデートされるとは限らない

xcodeを12.3にすることができて一安心していたのですが、 apple clangが11.0のままだったことに気がつきました。 どうやらxcodeがアップデートしても、apple clangがアップデートされるわけではないみたいですね。

こちらの内容を元に以下のコマンドでアップデートしました。 python5.com

sudo rm -rf /Library/Developer/CommandLineTools
xcode-select --install

めでたくapple clangが12.0になったら、今度はConanのboostパッケージがビルドできない・・・。 どうやらレシピのバグらしくて、レシピのアップデート待ちです。 github.com

xcodeはバージョンアップするといつも何か起きてしまうなぁ。

conanfile.txtで条件分岐したい場合はconanfile.pyを使う

TL;DR

conanパッケージに関して環境依存で細かい条件分岐をしたい場合はconanfile.txtではなくconanfile.pyを使いましょう。 Pythonコードでやりたい放題です。

導入というかtermuxの話

termux面白いですね。 普段持ち歩くAndroidでほぼ完璧なLinux環境が作れてしまうワクワク感がたまりません。

最近termuxで開発環境を作るのが楽しいです。 そこで困るのがtermuxの特殊なコンパイル環境です。

最近になってclang version 11が入ってほぼ最新のC++環境が手に入るのですが、微妙にx86_64とは異なる勝手があって困ることがあります。 今日困ったけれどなんとか解決できたものがあったのでメモっておきます。

足りないヘッダファイルがあってboostがコンパイルできない

私はC++のパッケージマネージャーの一つであるConanを利用しています。 conan.io Termuxに限らず、色々な環境でビルドする時にライブラリのバージョンを揃えたり、マイナーなライブラリを利用する際にとても便利です。

TermuxでもConanを使ってboostライブラリの最新版を使ってみようと思ったのですが、見事にハマりました。 strfmon() を定義している monetary.h というヘッダがないのでboost localeとboost logがビルドできないのです。

libs/locale/src/posix/numeric.cpp:26:10: fatal error: 'monetary.h' file not found
#include <monetary.h>
         ^~~~~~~~~~~~
1 error generated.

conanでパッケージオプションを指定する方法

boostCMakelists.txt側で認識してくれていないのが謎ですが monetary.h が無い環境なんてかなりマイナーなのでしょう。 私としてはとにかく今利用できないのが困ります。

幸いboost locale, boost logを使っていないプロジェクトが多いので、そういうプロジェクトだけはTermuxでビルドできるようにしてみたいと思いました。

boost locale, boost logだけをビルドの対象から外すのはconanコマンドが読み込むconanfile.txtにパッケージオプションを指定するだけです。

[requires]
boost/1.75.0

[generators]
cmake

[options]
boost.without_locale = True
boost.without_log = True

monetary.hがない時だけパッケージオプションを指定したい

上の方法でとりあえずコンパイルできるのですが monetary.hがある環境ではboost locale, boost logもビルドしてほしいのです。 そうしないと~/.conan/data/boost//1.75.0/_/_/packageの下に「全ビルド版」と「locale, log除外版」の複数のバイナリが生成されてしまってboostの性質上やたらディスク使用量が大きいのが嫌だなぁと。(貧乏性です)

こういう環境などによる条件分岐にconanfile.txtは対応していません。 幸いconangithubに同じような質問をしている人がいました。 github.com

なるほどconanfile.txtと同じようにインストールするパッケージ方法としてconanfile.pyがあるんですね。 知らなんだ。 docs.conan.io

conanfile.pyの中では普通にPythonが書けてしまうのでmonetary.hの存在チェックも可能になります。 ひとまず以下のようなconanfile.pyを書いてやりたいことを実現できるようになりました。

from conans import ConanFile
import subprocess

class ProjectConan(ConanFile):
   settings = "os", "arch", "compiler"
   generators = "cmake"

   def requirements(self):
       self.requires("boost/1.75.0")

   def config_options(self):
       compiler = str(self.settings.compiler)
       # check monetary header existance
       if self.settings.os == "Linux" and (compiler == "gcc" or compiler == "clang"):
           monetary_check = subprocess.run([compiler, "-E", "-x", "c", "-"], input="#include<monetary.h>", text=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
           if monetary_check.returncode != 0:
               self.options["boost"].without_locale = True
               self.options["boost"].without_log = True

まとめ

  • ConanでのC++パッケージ管理便利です。
  • conanfile.txtでパッケージ定義が基本ですが、条件分岐したければconanfile.pyPythonコードを書けます。

termuxのclangで最適化

termuxでコンパイルする場合、 「とにかくホスト上で最適化して動けばいいんだよ」 といつも思っています。 というかC++コンパイルする時は大抵そう思ってる、貧乏性です。 「必要になったらそのCPU用にコンパイルすればいいでしょ?」という適当な考え方です。

「termuxのclangで何を指定すればいいんだろうか?」という疑問に関するメモです。

無指定

echo | clang++ -E -v -の結果を整形

-cc1 
-triple aarch64-unknown-linux-android24 
-disable-free 
-disable-llvm-verifier 
-discard-value-names 
-main-file-name - 
-mrelocation-model pic 
-pic-level 2 
-pic-is-pie 
-mframe-pointer=non-leaf 
-fno-rounding-math 
-mconstructor-aliases 
-target-cpu generic 
-target-feature +neon 
-target-abi aapcs 
-mllvm 
-aarch64-fix-cortex-a53-835769=1 
-fallow-half-arguments-and-returns 
-fno-split-dwarf-inlining 
-debugger-tuning=gdb 
-ferror-limit 19 
-fno-signed-char 
-fgnuc-version=4.2.1
-fcolor-diagnostics 

-taret-feature +neon が指定されているけれど、これは使われているのかなぁ。 neon勉強しないと分からない。

-O3

いつものやつ。

echo | clang++ -E -v -O3 -を整形。

-cc1 
-triple aarch64-unknown-linux-android24
-disable-free 
-disable-llvm-verifier 
-discard-value-names 
-main-file-name - 
-mrelocation-model pic
-pic-level 2 
-pic-is-pie 
-mframe-pointer=non-leaf 
-fno-rounding-math 
-mconstructor-aliases 
-target-cpu generic 
-target-feature +neon 
-target-abi aapcs 
-mllvm 
-aarch64-fix-cortex-a53-835769=1 
-fallow-half-arguments-and-returns 
-fno-split-dwarf-inlining 
-debugger-tuning=gdb 
-O3 
-ferror-limit 19 
-fno-signed-char 
-fgnuc-version=4.2.1 
-fcolor-diagnostics 
-vectorize-loops 
-vectorize-slp

うん、いつものO3な気がする。

-O3 -march=natvie

> echo | clang++ -E -v -O3 -march=native -
clang version 11.0.0 (https://github.com/termux/termux-packages 39dec01e591687a324c84205de6c9713165c4802)
Target: aarch64-unknown-linux-android24
Thread model: posix
InstalledDir: /data/data/com.termux/files/usr/bin
clang-11: error: the clang compiler does not support '-march=native'

x86_64と違って-march=natvieという指定に対応していない・・・。

-O3 -mtune=native

echo | clang++ -E -v -O3 -mtune=native - を整形。

-cc1 
-triple aarch64-unknown-linux-android24 
-disable-free 
-disable-llvm-verifier 
-discard-value-names 
-main-file-name - 
-mrelocation-model pic 
-pic-level 2 
-pic-is-pie 
-mframe-pointer=non-leaf 
-fno-rounding-math 
-mconstructor-aliases 
-target-cpu generic 
-target-feature +neon 
-target-abi aapcs 
-mllvm 
-aarch64-fix-cortex-a53-835769=1 
-fallow-half-arguments-and-returns 
-fno-split-dwarf-inlining 
-debugger-tuning=gdb 
-O3 
-ferror-limit 19 
-fno-signed-char 
-fgnuc-version=4.2.1 
-fcolor-diagnostics 
-vectorize-loops 
-vectorize-slp 

まだmtuneが生きている。だけど-O3と変わらない?

-O3 -mcpu=native

echo | clang++ -E -v -O3 -mtune=native - を整形。

-cc1 
-triple aarch64-unknown-linux-android24 
-disable-free 
-disable-llvm-verifier 
-discard-value-names 
-main-file-name - 
-mrelocation-model pic 
-pic-level 2 
-pic-is-pie 
-mframe-pointer=non-leaf 
-fno-rounding-math 
-mconstructor-aliases 
-target-cpu cortex-a73 
-target-feature +fp-armv8 
-target-feature +neon 
-target-feature +crc 
-target-feature +crypto 
-target-feature +sha2 
-target-feature +aes 
-target-abi aapcs 
-mllvm 
-aarch64-fix-cortex-a53-835769=1 
-fallow-half-arguments-and-returns 
-fno-split-dwarf-inlining 
-debugger-tuning=gdb 
-O3 
-ferror-limit 19 
-fno-signed-char 
-fgnuc-version=4.2.1 
-fcolor-diagnostics 
-vectorize-loops 
-vectorize-slp 

target-cpuがcortex-a73になり、target-featureに色々ついてますね。

この記事がまだ有効みたいで、x86_64ではもう使われない-mcpuがarmでは有効と。 stackoverflow.com

結論?

aarch64でclang使う時は-mcpu=nativeつけておけば良さそう。

まだこのブログ残ってたんだ

もう4年以上放置されているブログなのに、はてなさんまだ保管してくれてたんですね。
ありがたや。

また少し書こうかなぁと。

まずは機能が大分変わってる気がするからそこらへん追いつかないとなぁ。

既存のPDFをPoDoFoで変換

ふとしたことでそこらへんに転がっているPDFを加工したいことがあります。
PDFを生成するライブラリではなくて、PDFのパーザが必要になるのですがC++でやるには何がいいんですかね〜。

今回は、ざっと探して最初に見つかったPoDoFoを使おうと思ってます。
PoDoFo

ライセンスがLGPLv2なのがちょっと気に入りませんが、ちょっとしたツールを作るだけなので問題ありません。
2014年で開発止まっているようですが、今のところ問題なく使えます。
cmakeを使っているのも好印象。
命名規則がかなりごちゃごちゃなのがちょっと気になりますが。
(GetPageCount()だったり、GetNumAnnots()だったりするんです。)

残念ながらサンプルはPDFの生成しか無いし、ドキュメントはDoxygenでのクラスリファレンスなのでぱっと見使い方が分かりません。
PoDoFo ToolsとしていろいろPoDoFoライブラリを使ったツールが公開されているので、これを触りながら使い方を理解してます。
http://podofo.sourceforge.net/tools.html#tools

どうやら、PDFの編集をする一番楽な方法は全部メモリに読み込んで、編集して、書き出す方法のようなので、これに習おうと思います。
ちゃんと理解すればStream処理もできるのかもしれませんが、とりあえず後回し。

奇数頁だけ削除して書き出すプログラムを書いてみました。

#include <iostream>
#include <podofo/podofo.h>

int main(int argc, char* argv[]) {
  if (argc < 3) {
    std::cout << "usage : "
              << argv[0]
              << " <input file> <output file>"
              << std::endl;
    return 1;
  }

  PoDoFo::PdfMemDocument pdfdoc(argv[1]);
  int page_number = pdfdoc.GetPageCount();
  for (int index = page_number - 1; index >= 0; --index) {
    if (index % 2 == 0) {
      continue;
    }
    pdfdoc.DeletePages(index, 1);
  }
  pdfdoc.Write(argv[2]);

  return 0;
}

PdfMemDocumentというクラスを使うのがキモですね。
ファイル名を渡せばPDFの構造としてメモリの読み込んでくれます。
DeletePagesでメモリ上の情報から指定ページだけ決して、最後にWriteでファイル書き出し。
案外簡単。


PDFによっては次のようなメッセージがでちゃいます。
消したページを参照している処が問題起こしてるのかな。
これもおいおい対応できるようにしたいですね。

WARNING: Treating object 110 0 R as a free object.

まあ、この程度ならプログラム書かなくてもできちゃうので、もう少し中身を弄れるようになりたいところです。

最近のC++に追い付こうと思います

C++で書きなぐるがの楽しかったのですが、そろそろ限界を感じてきたので、C++14とかをちゃんと勉強しようと思ってます。
私はC++98の知識しかないので、ここ2、3年のドラスティックな変化に全然対応できないと痛感してます。

ひとまず以下をサクっと読んで文法的には理解したので、色々調べながら追いつくしかないですね。
www.apress.com

シンプルな構成のC++のHTTPクライアントライブラリは無いものか

「libcurl使えば一発じゃん」と言われればそれまでなのですが、
C++界隈の一部ではheader only libraryが大人気なわけです。

Boostやfollyみたいなガチなものから、もっとホビーなものまで沢山あるのに、
案外無いのがHTTPクライアント。
C++バリバリな方々はそもそもクライアントサイドは作らないのかな。

もう10年近く前の記事で取り上げたHappyHTTPを未だに使ってます。
toge.hatenablog.com

あの当時はまだ開発続けてくれると思ってたんだがなぁ。
全く進化なし。
取り敢えずcallbackの仕組みとかが古臭いのでlambda使ったり、enum class使ったりちょいちょい弄ってますが、
C++11/14の機能を巧みに使った素敵なライブラリを誰か作ってはいないものか。

C++ Requestsとか相当いい線行ってるんですけどね、libcurl依存なのよね。
github.com

Linuxでrarを解凍

忘れた頃に必要になるrarファイル。
ちょっと変なフォーマットだと、Gnomeの機能では解凍できないのよね。

昔は重宝したunrarなんてコマンドは最近のLinux(Fedora 23)にはなかったので、
野良ビルドすることに。

UnRar-5.3.9

ここらへんにあるとおり実行して、
できたunrarを適当に配置すればあっさり使えます。

$ tar xvf unrarsrc-5.3.8.tar.gz 
$ cd unrar
$ make
$ cp unrar /usr/local/bin

最新のフォーマットにも対応しているし、にほんご入りファイル名を処理できました。
めでたしめでたし。

Dockerで簡単にElasticsearch+Kibana環境

自分用のメモ。

ちゃんとやるにはpluginとか色々必要ですし、ネットワークも適切な設定が必要ですが、
まあとりあえず開発で最新版を使いたい時用手順です。

環境はFedora23 workstationです。

$ docker run -d -p 9200:9200 -p 9300:9300 -v "$PWD/esdata":/usr/share/elasticsearch/data elasticsearch:2
$ curl -XGET http://localhost:9200/
{
  "name" : "Runner",
  "cluster_name" : "elasticsearch",
  "version" : {
    "number" : "2.1.1",
    "build_hash" : "40e2c53a6b6c2972b3d13846e450e66f4375bd71",
    "build_timestamp" : "2015-12-15T13:05:55Z",
    "build_snapshot" : false,
    "lucene_version" : "5.3.1"
  },
  "tagline" : "You Know, for Search"
}

なんか立ち上がったっぽいですね。

$ tree $PWD/esdata
esdata
└── elasticsearch
    └── nodes
        └── 0
            ├── _state
            │   └── global-2.st
            ├── indices
            └── node.lock

5 directories, 2 files

カレントディレクトリの下にesdataディレクトリができて、結構デカイデータが入るので注意。

$ docker ps
0c4e128e4145        elasticsearch:2     "/docker-entrypoint.s"   19 minutes ago      Up 19 minutes       0.0.0.0:9200->9200/tcp, 0.0.0.0:9300->9300/tcp   stoic_panini

とりあえず立ち上がってるね。よしよし。
次はKibanaだ。

$ docker run -e ELASTICSEARCH_URL=http://192.168.1.101:9200 -p 5601:5601 -d kibana

"192.168.1.101"はホストのIPアドレスを指定。
localhostだとKibanaのコンテナ自信のポートになっちゃうから注意。

$ curl -X GET http://localhost:5601/
<script>var hashRoute = '/app/kibana';
var defaultRoute = '/app/kibana';

var hash = window.location.hash;
if (hash.length) {
  window.location = hashRoute + hash;
} else {
  window.location = defaultRoute;

とりあえず動いてるっぽいですね。
後でブラウザから確認しましたが4.3.1が入ってました。(2016/1/12時点)

$ docker ps 
c41219f376f4        kibana              "/docker-entrypoint.s"   5 minutes ago       Up 5 minutes        0.0.0.0:5601->5601/tcp                           kibana-test02
0c4e128e4145        elasticsearch:2     "/docker-entrypoint.s"   19 minutes ago      Up 19 minutes       0.0.0.0:9200->9200/tcp, 0.0.0.0:9300->9300/tcp   stoic_panini

ポートもばっちり。

http://localhost:5601/にアクセスするとちゃんとKibanaの画面がでてきて、満足。

volumioはcronが無効になってる

Raspberry Pi 2のディストリビューションをRaspbianからVolumioにしたんだけど、
cronがことごとく動いていなかった。

ポリシーで無効になってるのか。
www.raspyfi.com

chkconfig --add cron
service cron start

でちゃんと動くようになりました。
ぐぬぬ、1週間ぐらいデータが欠損しちゃったよ。

C++でテキスト処理 導入編

C++好きとしては、sed/awkで書いてしまうちょっとしたテキスト処理もC++11/Boostで華麗にかけないかとロマンを抱いてしまいます。
何もせずにファイル読み込んで表示するだけだとこんな感じか。

#include "iostream"
#include "fstream"
#include "string"

int main() {
  std::ifstream input("example.txt");
  if (input.fail()) {
    std::cerr << "File does not exists." << std::endl;
    return 1;
  }

  std::string str;
  while (std::getline(input, str)) {
    std::cout << str << std::endl;
  }

  return 0;
}

明らかにつまらん。
せっかくC++11なんてあるんだし、もちっと心踊る書き方はできないものかね。

全然新しいC++勉強してないので少し情報漁ってみようと思います。

Raspberry Piで動くSDカード一覧

http://elinux.org/RPi_SD_cards

ちゃんとこんなページがあるのか。調べてから買えばよかった。<br>今のところ買ったけどRaspberry Piでは動かないSDHC 32GBは用途が思いつかず。

あ、一応動かなかったのは次のやつ。→ http://www.amazon.co.jp/gp/product/B0093IE9S4/
ちなみにWiFi USBアダプタはあっさり動いた。→ http://www.amazon.co.jp/gp/product/B003EIKJ56

 

ちゃんと動作確認リストは使おうね、、という話でした。

 

Raspberry PiでBoostをビルドしようとして大変

RaspbianのBoostが古いのに我慢できず、Boost 1.53(現時点で最新版)のビルドしようとしてみた。

あー、やっぱりビルド遅いね。「忍耐力が鍛えられる」と聴いたけど、まさかこれほどとは。Class 4というのもあるんだろうが。

やっぱり母艦のUbuntu 12.10にクロスコンパイル環境作るか。色々大変そうだが。

 

ちなみに、Boost 1.53はOverclock(High), SD Card Class4、gcc 4.6の環境で4.5時間かかって終わりましたとさ。

...failed updating 56 targets...
...skipped 6 targets...
...updated 897 targets...

real    264m38.742s
user    257m11.050s
sys     5m0.010s

と思ったら、何個かfailしている・・。調べなきゃ。