OpenSSLはどこにいる

この記事は、はてなエンジニア Advent Calendarの15日目です。

qiita.com

OpenSSLに依存しているモジュールをインストールしようと思ったときにライブラリが見つからなくて困ることがあります。

例えばmacOS上でhomebrewを使ってOpenSSLをインストールした場合は /usr/local/opt/openssl以下に配置されるため、モジュールのインストール時にインクルードパスとライブラリパスを指定しないといけません。 cpanmであれば--configure-args経由でビルドする際の引数を渡すことができますが、コンパイラにどのように渡されるかはMakefile.PLを読む必要があります。大変ですね。

そこで作ったのが、いいかんじにOpenSSLのパスを取ってきてくれるモジュールCrypt::OpenSSL::Guessです。

metacpan.org

これはNet::SSLeayで使われているOpenSSLのパス解決部分をモジュール化したものです。このモジュールは現在Crypt::OpenSSL::RSACrypt::OpenSSL::Randomで使われています。

使い方はMakefile.PL内に以下のように書くだけです。

use ExtUtils::MakerMaker;
use Crypt::OpenSSL::Guess;
 
WriteMakefile(
    # ...
    LIBS => ['-lssl -lcrypto ' . openssl_lib_paths()],
    INC  => openssl_inc_paths(),
);

実装は素直で、パスを順番に探して見つかったものを返すというものです。 この中に含まれていない場合でも簡単にパスを指定できるように、環境変数OPENSSL_PREFIXに設定できるようにしています。

sub find_openssl_prefix {
    my ($dir) = @_;
 
    if (defined $ENV{OPENSSL_PREFIX}) {
        return $ENV{OPENSSL_PREFIX};
    }
 
    my @guesses = (
        '/home/linuxbrew/.linuxbrew/opt/openssl/bin/openssl' => '/home/linuxbrew/.linuxbrew/opt/openssl', # LinuxBrew openssl
        '/usr/local/opt/openssl/bin/openssl' => '/usr/local/opt/openssl', # OSX homebrew openssl
        '/usr/local/bin/openssl'         => '/usr/local', # OSX homebrew openssl
        '/opt/local/bin/openssl'         => '/opt/local', # Macports openssl
        '/usr/bin/openssl'               => '/usr',
        '/usr/sbin/openssl'              => '/usr',
        '/opt/ssl/bin/openssl'           => '/opt/ssl',
        '/opt/ssl/sbin/openssl'          => '/opt/ssl',
        '/usr/local/ssl/bin/openssl'     => '/usr/local/ssl',
        '/usr/local/openssl/bin/openssl' => '/usr/local/openssl',
        '/apps/openssl/std/bin/openssl'  => '/apps/openssl/std',
        '/usr/sfw/bin/openssl'           => '/usr/sfw', # Open Solaris
        'C:\OpenSSL\bin\openssl.exe'     => 'C:\OpenSSL',
        'C:\OpenSSL-Win32\bin\openssl.exe'        => 'C:\OpenSSL-Win32',
        $Config{prefix} . '\bin\openssl.exe'      => $Config{prefix},           # strawberry perl
        $Config{prefix} . '\..\c\bin\openssl.exe' => $Config{prefix} . '\..\c', # strawberry perl
        '/sslexe/openssl.exe'            => '/sslroot'# VMS, openssl.org
        '/ssl$exe/openssl.exe'           => '/ssl$root', # VMS, HP install
    );
 
    while (my $k = shift @guesses
           and my $v = shift @guesses) {
        if ( -x $k ) {
            return $v;
        }
    }
    (undef, $dir) = check_no_path()
       and return $dir;
 
    return;
}

PerlにはThere's more than one way to do it(やり方はいくつもある)というモットーがありますが、それに続くbut sometimes consistency is not a bad thing either(ときには共通のやり方があっても悪くはない)という言葉があるように、各モジュールが行っていたOpenSSLのパスの解決の仕組みを共通のやり方で出来るようにモジュール化したという話でした。