ujimushi(@旧sradjp(15364))の日記

旧スラドの日記の引越先です

How to check what other packages an installed package uses?[勝手に回答]

まだまだ続くよどこまでも。勝手に続く勝手に回答シリーズ。今回もJuliaLang Discourseから How to check what other packages an installed package uses?という質問です。

実は私が過去に書いた日記にujimushiの日記: julia言語でインストールしているパッケージの依存関係をなんちゃってツリー表示する というのがあってそれを使えばいいかも,とか思います。

ソースを再掲。

using Pkg
 
"""
    print_pkg_dependencies(pkgname, limit=2)
 
print a tree of `pkgname`'s dependencies to `limit` depth.
`limit=2`(default)
"""
function print_pkg_dependencies(pkgname::AbstractString, limit::Int=2)
    function printdep_uuid(uuid, nth, pre_str, depth)
        pkginf = Pkg.dependencies()[uuid];
        N = length(pkginf.dependencies);
        if N > 0
            m = 1
            for (k, v) in pkginf.dependencies
                next_str, keisen = begin
                    m == N ? (" ", "┗") : ("┃", "┣");
                end
                version = Pkg.dependencies()[v].version;
                if isnothing(version)
                    version = ""
                end
                println("$pre_str$keisen$k $version")
                if nth < depth
                    printdep_uuid(v, nth + 1,
                                  pre_str * next_str,
                                  depth);
                end
                m = m + 1;
            end
        end
    end
    for (k, v) in Pkg.dependencies()
        if v.name == pkgname
            println("[$pkgname] $(v.version)");
            printdep_uuid(k, 1, "", limit);
        end
    end
end

次は実行結果

julia> print_pkg_dependencies("CSV")
[CSV] 0.10.14
┣WorkerUtilities 1.6.1
┣InlineStrings 1.4.0
┃┗Parsers 2.8.1
┣PooledArrays 1.4.3
┃┣DataAPI 1.16.0
┃┗Future 
┣PrecompileTools 1.2.1
┃┗Preferences 1.4.3
┣WeakRefStrings 1.4.2
┃┣DataAPI 1.16.0
┃┣InlineStrings 1.4.0
┃┗Parsers 2.8.1
┣CodecZlib 0.7.4
┃┣Zlib_jll 1.2.13+1
┃┗TranscodingStreams 0.10.7
┣Tables 1.11.1
┃┣DataAPI 1.16.0
┃┣OrderedCollections 1.6.3
┃┣LinearAlgebra 
┃┣IteratorInterfaceExtensions 1.0.0
┃┣DataValueInterfaces 1.0.0
┃┗TableTraits 1.0.1
┣Mmap 
┣Unicode 
┣Dates 
┃┗Printf 
┣FilePathsBase 0.9.21
┃┣Dates 
┃┣Compat 4.14.0
┃┣Test 
┃┣Mmap 
┃┣UUIDs 
┃┗Printf 
┣Parsers 2.8.1
┃┣Dates 
┃┣PrecompileTools 1.2.1
┃┗UUIDs 
┗SentinelArrays 1.4.1
 ┣Dates 
 ┗Random 

以前の日記の実行結果と比較すると,各パッケージのバージョンが上がっていて面白いですね。

というか上のソースも2年以上も前に書いたものなのか。質問の「以前はこんな感じで依存関係が表示できたはず」って何年前のことなんだろう?

まぁJuliaらしからぬ再帰を使ったソースなので実行速度は遅いですが,まぁ確認できればいいでしょ程度の代物。

なお,例で示しているCSVが依存するパッケージの全部のネストを辿るには第2引数の深さ制限を8ぐらいにしないと全て表示できないようです。

Plots.jlでオプション指定を使い回す方法

Julia言語のことでも基本的に自分のために書いている「日記」でしかないこのblog。 たまには初心者的な方にも役に立つ事を書いてみるといいながら,よく忘れるので書いておく備忘録。

Plots.jlのGRバックエンドはサイズを大きくしてもフォントのサイズが自動的に大きくなるということが無かったりして, グラフのサイズが大きい時用のフォントサイズ指定や, タイトルや凡例だけは日本語フォントにしたい等,使い回ししたい時はよくある。

そういう時には,名前付きタプルを使うと便利である。

# using Pkg
# path_env = "/home/ujimushi/test"
# Pkg.activate(path_env)

using Plots
import GR
gr()

my_options = (titlefontfamily="ipag", legendfontfamily="ipag", 
              titlefontsize=30, legendfontsize=20,
              guidefontsize=20, tickfontsize=20, size=(1600, 1200))

plot(sin; title="タイトル", label="凡例", my_options...)
savefig(joinpath(path_env, "test_options.png"))

上記の例だと,サイズを(1600, 1200)と大きくしているので,それに対してフォントサイズを 変えるオプションをまとめて指定している。

こういう オプションのセット をいくつか用意しておくと,いちいち入力する手間が 省けて便利である… と忘れっぽい私自身に向けて言っておく。

一度利用したソースのx要素やy要素,plotを消すと名前付きタプルになるので, 再利用しやすいと思われる。

Plots.jl Ver.2 (GR)用フォントパッチ(Linux用)

いまだに正式リリースされていないPlots.jl Ver.2用に 先日紹介したPlots.jlのGRバックエンド用フォントパッチ(Linux用)を移植した。

誰の役にも立たない情報であるが,いつかバージョン2が出た時の自分のために記録しておく。 といっても,バージョン1よりもGR用のラッパーの位置が分かりづらくなっているだけで, 基本的にはあまり変わらない。

# plots_v2_gr_font_patch_for_linux.jl

# using Pkg
# plotsv2_env = "/home/ujimushi/test"
# Pkg.activate(plotsv2_env)

using Plots
import GR

MyGR = PlotsBase.get_backend_module(:GR)[1]

@eval MyGR begin
    function gr_custom_fontlist_linux()
        コマンド出力 = pipeline(`fc-list`, `egrep "[.](ttf|ttc|otf)"`,`cut -d : -f 1`)
        読込(x) = read(x, String)
        終端改行削除(x) = chomp(x)
        行分割(x) = split(x, "\n")
        ファイル名抽出(x) = basename(x)
        fnames = コマンド出力 |> 読込 |> 終端改行削除 |> 行分割 .|> ファイル名抽出
        Dict([(chopsuffix(f, r"[.](ttf|ttc|otf)") , f) for f in fnames])
    end
    
    """
        パッチで指定可能なフォント名 => ファイル名の辞書型

    `"フォント名" in keys(PlotsBase.get_backend_module(:GR)[1].gr_custom_fontlist)`
    でフォントが選択可能か分かる
    """
    const gr_custom_fontlist = gr_custom_fontlist_linux()
    
    function gr_set_font(
        f::Font, s; halign = f.halign, valign = f.valign, color = f.color,
        rotation = f.rotation,)
        _MyGR = Plots.PlotsBase.get_backend_module(:GR)[1]
        family = lowercase(f.family)
        GR.setcharheight(_MyGR.gr_point_mult(s) * f.pointsize)
        GR.setcharup(sincosd(-rotation)...)
        if !haskey(_MyGR.gr_font_family, family)
            # この辺を変更
            _MyGR.gr_font_family[family] = GR.loadfont(gr_custom_fontlist[f.family])
        end
        haskey(_MyGR.gr_font_family, family) && GR.settextfontprec(
            _MyGR.gr_font_family[family],
            _MyGR.gr_font_family[family]  200 ? 3 : GR.TEXT_PRECISION_STRING,
        )
        _MyGR.gr_set_textcolor(plot_color(color))
        GR.settextalign(gr_haligns[halign], gr_valigns[valign])
        nothing
    end
end

使う時は上記のファイルをインクルードして必ずgr()する必要がある。 前回も記述したが存在しないフォント名を指定するとエラーで終了するので注意。

Plots.jl Ver.2を試用できる環境を構築する

Plots.jl 開発状況(Road to plots 2.0) で紹介した通り,Plots.jlのバージョン2の開発が進んでいる。

そこで,そのバージョン 2を試用できる環境を構築するスクリプトを作ってみた。 juliaのLibGit2がよく分かっていないので,gitコマンドで構築する。

using Pkg

function my_setup_plots_v2(env_path)
    wd_old = pwd()
    devplotpath = joinpath(env_path, "Plots.jl")
    cd(env_path)
    run(`git clone https://github.com/JuliaPlots/Plots.jl`)
    cd(devplotpath)
    run(`git checkout v2`)
    Pkg.activate(env_path)
    Pkg.develop(path=joinpath(devplotpath, "RecipesPipeline"))
    Pkg.develop(path=joinpath(devplotpath, "RecipesBase"))
    Pkg.develop(path=joinpath(devplotpath, "PlotsBase"))
    Pkg.develop(path=devplotpath)
    Pkg.precompile()
    cd(wd_old)
    nothing
end

env_pathに環境を構築するフォルダを指定すると,その直下にPlots.jlをcloneして 必要なモジュールをdevelopする関数。

include("上記のソース.jl")
my_setup_plots_v2("構築/する/環境の/フォルダ")

とかすると構築できる。

なお,上記の環境には別途GRモジュールを手動で追加されたし。

で,使用例。

using Pkg
path_env = "/home/ujimushi/test"
Pkg.activate(path_env)
using Plots
import GR
gr()

plot(sin; title="Plots.jl Version 2のプレビュー環境", titlefontfamily="ipag")
savefig(joinpath(path_env, "plots_v2_1st.png"))

デフォルトではバックエンドが指定されていないので,import GRしてからgr()等が 必須となる。

それ以降の使い勝手は1.xとそれほど大きく変わらないのでは?と現状では推測している。

Using Luxor.Table to visualize Julia 2D matrix results in transposed view [勝手に回答]

調子に乗って続けて書いてみる「勝手に回答」シリーズ。今回は Using Luxor.Table to visualize Julia 2D matrix results in transposed view 。前回に引き続きJulia Discourseの質問です。

Luxor.TableとJuliaの二次元行列を一次元で参照していく時の順番がそれぞれ行,列で方向が 違うけど,CartesinanIndices()permutedims(m)は使いたくないとのことです。

っていうか,こういう時は転置行列を作るtranspose()を使うのがお約束。

質問のソースリストの

for n in eachindex(table)
    Luxor.text(string(m[n]), table[n], halign=:center, valign=:middle)
end

の部分を

for (n, k) in zip(eachindex(table), transpose(m))
    Luxor.text("$k", table[n], halign=:center, valign=:middle)
end

に変更するだけでOKです。一応次が結果画像。

確かにpermutedims(m)は実際にメモリを確保して転置するのでサイズが大きいと遅くて メモリをたくさん消費します。

transpose()と比較してみます。

julia> m_big = rand(1000, 10000);

julia> @time permutedims(m_big);
  0.140805 seconds (2 allocations: 76.294 MiB, 59.17% gc time)

julia> @time transpose(m_big);
  0.000003 seconds (1 allocation: 16 bytes)
  
julia> permutedims(m_big) == transpose(m_big)
true

permutedims()が76MB使っているのに対し,transpose()は16byte, これは,m_bigを転置してるぜという情報と元行列の参照だけを 確保しているだけで,実際には計算していないためだと思います。

このtransposeが使えるのは,全て数値の行列の時だけで, How to transpose Matrix{Any} that contains strings?のように文字列とかが入っていると transpose()は使えないということになります。

Julia Error: InitError when trying to use Plots in Python via JuliaCall [勝手に回答]

少し間が開いた「勝手に回答」シリーズ。今回はJulia Error: InitError when trying to use Plots in Python via JuliaCallJulia Discourseからの質問です。

うまくいかない原因は,おそらくJulia言語は1.10.2であるにもかかわらず, python用には1.10.0がインストールされ,どちらも$HOME/.juliaの環境を共用しているから よく分からない状況になっているのでしょう。

これは,juliaの1.10.[12]ではPythonCallがjulia言語側のバグでうまく動かないため, juliacallインストール時に用意されるJulia環境が1.10.0に強制されるためです。

そこで,pythonのvenv用にインストールされるJuliaの環境を独立させる設定をしてみます。

まず,$HOME/venv-3.11pythonのvenv環境を構築しているものとします。

$HOME/venv-3.11/bin/activateJULIA_DEPOT_PATH関係の設定を追加します。 deactivate内にJULIA_DEPOT_PATHをunsetするようにして, activateを実行すると,JULIA_DEPOT_PATH$HOME/venv-3.11/juliaを設定するようにします。

# This file must be used with "source bin/activate" *from bash*
# you cannot run it directly

deactivate () {
    # reset old environment variables
    if [ -n "${_OLD_VIRTUAL_PATH:-}" ] ; then
        PATH="${_OLD_VIRTUAL_PATH:-}"
        export PATH
        unset _OLD_VIRTUAL_PATH
    fi
    if [ -n "${_OLD_VIRTUAL_PYTHONHOME:-}" ] ; then
        PYTHONHOME="${_OLD_VIRTUAL_PYTHONHOME:-}"
        export PYTHONHOME
        unset _OLD_VIRTUAL_PYTHONHOME
    fi

    # This should detect bash and zsh, which have a hash command that must
    # be called to get it to forget past commands.  Without forgetting
    # past commands the $PATH changes we made may not be respected
    if [ -n "${BASH:-}" -o -n "${ZSH_VERSION:-}" ] ; then
        hash -r 2> /dev/null
    fi

    if [ -n "${_OLD_VIRTUAL_PS1:-}" ] ; then
        PS1="${_OLD_VIRTUAL_PS1:-}"
        export PS1
        unset _OLD_VIRTUAL_PS1
    fi

    unset VIRTUAL_ENV
    unset VIRTUAL_ENV_PROMPT
    # Juliaの環境をリセット ------------------------------------
    unset JULIA_DEPOT_PATH
    
    if [ ! "${1:-}" = "nondestructive" ] ; then
    # Self destruct!
        unset -f deactivate
    fi
}

# unset irrelevant variables
deactivate nondestructive

VIRTUAL_ENV="/home/ujimushi/venv-3.11"
export VIRTUAL_ENV

_OLD_VIRTUAL_PATH="$PATH"
PATH="$VIRTUAL_ENV/bin:$PATH"
export PATH

# python venv用の julia 環境を設定 ---------------------------------
JULIA_DEPOT_PATH=$VIRTUAL_ENV/julia
export JULIA_DEPOT_PATH

# unset PYTHONHOME if set
# this will fail if PYTHONHOME is set to the empty string (which is bad anyway)
# could use `if (set -u; : $PYTHONHOME) ;` in bash
if [ -n "${PYTHONHOME:-}" ] ; then
    _OLD_VIRTUAL_PYTHONHOME="${PYTHONHOME:-}"
    unset PYTHONHOME
fi

if [ -z "${VIRTUAL_ENV_DISABLE_PROMPT:-}" ] ; then
    _OLD_VIRTUAL_PS1="${PS1:-}"
    PS1="(venv-3.11) ${PS1:-}"
    export PS1
    VIRTUAL_ENV_PROMPT="(venv-3.11) "
    export VIRTUAL_ENV_PROMPT
fi

# This should detect bash and zsh, which have a hash command that must
# be called to get it to forget past commands.  Without forgetting
# past commands the $PATH changes we made may not be respected
if [ -n "${BASH:-}" -o -n "${ZSH_VERSION:-}" ] ; then
    hash -r 2> /dev/null
fi

変更した後は

$ source ~/venv-3.11/bin/activate

で環境を有効にして,

(venv-3.11)$ pip install juliacall

でjuliacallのインストールをします。

(venv-3.11)$ python

pythonのReplを有効にしてOSにインストールしているQtのバージョンと合わせる。 手元のubuntu 23.10のQt6のバージョンは6.4.2なので

from juliacall import Main as jl
jl.seval("using Pkg")
jl.seval('Pkg.add(name="Qt6Base_jll", version=v"6.4.2+3")')
jl.seval('Pkg.pin(name="Qt6Base_jll", version=v"6.4.2+3")')
jl.Pkg.add('Qt6Wayland_jll')  # これは必要ではないかも…
jl.Pkg.add('Plots')

のような感じで実行します。 QtのバージョンはOS側に何がインストールされているかで変える必要があると思います。 後はOS側aptでlibqt6*のライブラリをインストールしておきます。

そして,

jl.seval("using Plots")
jl.plot(jl.sin, gui=True)

とかで手元の環境ではタイトルバー一表示されない変な感じでグラフが表示されました。 まだ警告はまだいくつか出ますが…

pythonは詳しくないので本当にこれでいいのかは分かりませんがsavefigとかもできるのでこれでいいでしょう。

ただ,内容を読み返してみて単に python環境のJuliaでPlots.jlのパッケージをaddしてないだけ のような感じもするのだが, 本当のところは質問者も分からないといった感じかもしれない今日この頃。

追記 ubuntu 22.04 LTSの別のPCでの例

  • Qt6Base_jllのバージョンはv"6.3.0+1"
  • Qt6Wayland_jllはインストールしない

でうまく動きました。むしろ,23.10よりも安定している。23.10の例でもQt6Base_jllのバージョンを下げた方が いいかもしれない。

xz-utils(v 5.6.0, v5.6.1)にバックドアの脆弱性

Julia Discourseの最新記事をずら~っと眺めていて気付いたのだが, xz-utilsのバージョン5.6.0, 5.6.1のtarballに含まれるバイナリにバックドアがしかけられていたそうだ。

PSA: backdoor in xz-utils and relevance for the Julia ecosystemがその内容。

手元でartifactsの中にも該当バージョンが入っていて,早速フォルダごと削除。 また,XZ_jll5.6.1は削除。 ただ依存しているのは,tomlファイルでgrepをかけてもChainRulesCore.jldocs/Manifest.tomlぐらいで, 5.6.1を消しても通常の動作ではあまり関係無さそう。

ただ,何故5.6.1が入っているのかは謎。

Ubuntuの方は5.4.1で,特に警戒が必要なssh系には影響が無さそうで一安心。

[追記 2024/4/2 12時過ぎ] Julia言語用アーティファクトの中のxz-utilsのバージョン確認のための何か

一応xz-utilsのバージョンを確認できるものを作ってみました。

using Pkg

function print_xz_versions()
    a_path = joinpath(Pkg.depots1(), "artifacts")
    for d in readdir(a_path)
        pc_path = joinpath(a_path, d, "lib", "pkgconfig", "liblzma.pc")
        if isfile(pc_path)
            for l in readlines(pc_path)
                m = match(r"^Version: ([^ \n]+)", l)
                if !isnothing(m)
                    is_danger = !isnothing(match(r"5\.6\.[01]", m[1]))
                    ms_str = is_danger ? "Danger !!" : ""
                    println("Version: $(m[1])  $ms_str")
                    println("dir: $(joinpath(a_path, d))\n")
                end
            end
        end
    end
    nothing
end

何かファイルに保存して,includeしてprint_xz_versions()で保存されているxz_utilsのバージョンと保存場所を表示します。

確認方法としては,pkg-config用のファイルを見つけてそのファイルに書かれているバージョンをチェックしています。

[追記 2024/4/2 22時過ぎ] Juliaの公式パッケージの対応

ということで,おそらく今後pkg updateしても5.6.[01]のバージョンがダウンロードされることはないと思います。

基本的にはsshのサーバーとコラボしない限り問題は少ないはずですが,所定の位置に脆弱性のあるバージョンがあることが分かると, ハックしてsshdとリンクさせることは,難しいとは言えど技術的には可能なので5.6.[01]のバージョンを見つけたら削除しておく方がいいでしょう。

Julia言語のartifactsは一度作成されるとなかなか削除されないので,自分の手で確実に削除されるのをおすすめします。

なお,私の会社PCでは,BInaryBuilder.jl→大元はBinaryBuilderBase.jlに依存しているパッケージがXZ_jllを利用しているようでした。