- お忙しい方は、まとめをご覧ください。
- (2024/8/13追記) メンテナンスの項(
port selfupdate
でバージョンアップしてしまった際の対応)
macportsのインストール先を変えた場合に遭遇する問題
macOSのソフトウェアディストリビューションのMacportsを使っているが、ディスクのシステム領域を汚さずユーザー領域にインストールして使っている。自分でコンパイルして使うソフトウェアは、~/Documents/workspace/opr/depot/
以下に、ソフトウェアごとにディレクトリを置いていくことにしているので、公式サイトのSource Installationに従って、
% tar xvf MacPorts-2.9.3.tar.bz2
% cd MacPorts-2.9.3
% ./configure --prefix=${HOME}/Documents/workspace/opr/depot/macports-2.9.3.arm64 \
--with-no-root-privileges --enable-readline \
--enable-shared --without-startupitems \
--with-applications-dir=${HOME}/Documents/workspace/opr/depot/macports-2.9.3.arm64/Applications \
--with-frameworks-dir=${HOME}/Documents/workspace/opr/depot/macports-2.9.3.arm64/Library/Frameworks
% make && make install
とすれば、問題なく~/Documents/workspace/opr/depot/macports-2.9.3.arm64/bin/port
が作られ、~/Documents/workspace/opr/depot/macports-2.9.3.arm64/bin
にPATH
を通しておけば、普通の使い方、
% port selfupdate ; port install coreutils
で、問題ないソフトは問題なく入れられる。
ところがソフトウエアパッケージによっては、公式サイトでは動作確認が取れているのにインストール(コンパイル)に失敗することがある。しかもビルドログを見ても、単にFile not found!
もしくは、Can not create file!
的なエラーメッセージだけで何が問題なのかよくわからない、という場面に遭遇した。
また、全部のport
がエラーを出すわけではないので、第1感として「Macportsのフレームワークの問題ではなく、個々のport
の問題」と思ってしまう ので、なかなか原因特定/解法考案ができなかった。
原因
ビルドログを見ていて、Can not create file: ほにゃらら
というエラーメッセージが出ている。そのため、試しに同じファイル名のファイルを作ってみようとすると、
% touch ほにゃらら
touch: ほにゃらら: File name too long
と表示され、ファイル名の長さば許される最大長を超えてしまっていることが原因だと判明した。
macportsは、各ソフトウェアパッケージをコンパイルする際、元のPortfile
が置かれているディレクトリパス名を使って作業ディレクトリを生成する。
macportsをソースからインストールしたときに./configure
に与えた--prefix
オプションの場所(もしくは、設定ファイル/etc/macports/macports.conf
の中の変数prefix
)の場所が$prefixだとすると、Portfile
は、
% ${prefix}/var/macports/sources/rsync.macports.org/macports/release/tarballs/ports/lang/llvm-18/Portfile
といった場所に置かれる。これをコンパイルするための作業ディレクトリとしては、
% ls $macports_root/var/macports/build/
_Users_{username}_Documents_workspace_opr_depot_macports-2.9.3.arm64_var_macports_sources_rsync.macports.org_macports_release_tarballs_ports_lang_llvm-18
というように、 Portfile
が置かれたディレクトリのフルパスの区切り文字(スラッシュ)をアンダースコアに変えて繋げた非常に長い名前のディレクトリが作られる。 実際のコンパイル作業は、さらにその下にソースコードが展開されて、コンパイルなどが行われる。 これが、Filename too long
が起きる主因と思われる。
macportsに触らずに対処できるか?
この問題は意外と根が深い。まず、作業ディレクトリをどこにするか、というのはmacportsの設定(${prefix}/etc/macports/macports.conf
)にオプションはない。Portfile
を作成するためのマニュアルの変数の説明に、workpath
,portbuildpath
といった変数が登場するが、これらの値は変更不能 (All of these variables except prefix are read-only!
)と明示されている。
macportsを深い階層のディレクトリにおいていることが作業ディレクトリ名を長くしている原因のようなので、たとえば、シンボリックリンクを張って
% (cd ~/ ; ln -s ${prefix} macports)
...
prefix /Users/{username}/macports
...
見かけ上、短いパスにすればいいのではないか、と思い試してみるが、実際にはうまくいかない。というのも、port
のコードを見てみると、workpath
は
% nl -ba ${prefix}/bin/port
...
4079 # output the path to the port's work directory
4080 set workpath [macports::getportworkpath_from_portdir $portdir $portname]
4081 if {[file exists $workpath]} {
...
のように定義されているが、生成している関数macports::getportworkpath_from_portdir
の中では、
% nl -ba $prefix/libexec/macports/lib/macports1.0/macports.tcl
...
2753 proc macports::getportworkpath_from_buildpath {portbuildpath} {
2754 return [file normalize [file join $portbuildpath work]]
2755 }
...
となっていて、ご丁寧にもシンボリックリンクを実体パスに変換してくれている。([file normalize ...]
)
どうやらmacportsのコードを触らずに変更するのは難しそうである。
解法案
該当コードの特定
上記のworkpath
の定義部分や周囲を見てみると、
% nl -ba $prefix/libexec/macports/lib/macports1.0/macports.tcl
...
3030
3031 proc macports::getportbuildpath {id {portname {}}} {
3032 variable portdbpath
3033 regsub {://} $id {.} port_path
3034 regsub -all {/} $port_path {_} port_path
3035 return [file join $portdbpath build $port_path $portname]
3036 }
3037
3038 proc macports::getportlogpath {id {portname {}}} {
3039 variable portdbpath
3040 regsub {://} $id {.} port_path
3041 regsub -all {/} $port_path {_} port_path
3042 return [file join $portdbpath logs $port_path $portname]
3043 }
3044
.....
3048
3049 proc macports::getportworkpath_from_portdir {portpath {portname {}}} {
3050 return [macports::getportworkpath_from_buildpath [macports::getportbuildpath $portpath $portname]]
3051 }
3052
この辺りが関係していそうである。関数macports::getportbuildpath
やmacports::getportlogpath
でPortfile
のパスから作業サブディレクトリ名を生成しているようだ。
実装意図の推定
わざわざPortfile
から作業ディレクトリ名を生成するのは、察するに、依存関係に従って同時にいくつものソフトウェアパッケージを展開する際にファイルが混じらないようにするためと思われる。また、単純にportname
としないのは、${prefix}/etc/macports/macports.conf
のリストにある複数のレポジトリに同じportname
のパッケージがある可能性を想定しているのであろう。もしその目的であれば、port
毎にユニークではない上位ディレクトリ構造${prefix}/var/macports/sources/
までサブディレクトリ名に含める必要はなさそうである。
さらに、macportsのソースコードをざっくり見渡した感じだと、この文字列変換の逆を行なっているコードはなさそうであるし、そもそも不可逆な変換をしてしまっている。(${prefix}
のディレクトリ名に_
が含まれる場合があるので、単射な写像でない。)。 なので、この情報を落としてしまっても他への影響はないのではないかと踏んで、試しに作業ディレクトリ名からユニークではない部分を削って文字列長を短縮する変更をしてmacportsをインストールし直してみた。
(ついでにバージョンアップ)
コードの修正
変更点としては下記。(macports
の中では、なんだかいっぱいグローバル変数が使われているようなので、念の為に追加で定義したもの以外の変数の上書き修正は避けた)
diff -rupN MacPorts-2.10.0.orig/src/macports1.0/macports.tcl MacPorts-2.10.0/src/macports1.0/macports.tcl
--- MacPorts-2.10.0.orig/src/macports1.0/macports.tcl 2024-08-07 23:17:43
+++ MacPorts-2.10.0/src/macports1.0/macports.tcl 2024-08-13 20:25:36
@@ -3028,16 +3028,29 @@ proc _source_is_obsolete_svn_repo {source_dir} {
return 0
}
+proc macports::trim_prefix {fullpath prefix} {
+ set fullpath [file normalize $fullpath]
+ set prefix [file normalize $prefix]
+ if {[string first $prefix $fullpath] != 0} {
+ return $fullpath
+ }
+ set relative_path [string range $fullpath [string length $prefix] end]
+ set relative_path [string trimleft $relative_path "/\\"]
+ return $relative_path
+}
+
proc macports::getportbuildpath {id {portname {}}} {
variable portdbpath
- regsub {://} $id {.} port_path
+ set s_id [macports::trim_prefix $id [file join $portdbpath sources]]
+ regsub {://} $s_id {.} port_path
regsub -all {/} $port_path {_} port_path
return [file join $portdbpath build $port_path $portname]
}
proc macports::getportlogpath {id {portname {}}} {
variable portdbpath
- regsub {://} $id {.} port_path
+ set s_id [macports::trim_prefix $id [file join $portdbpath sources]]
+ regsub {://} $s_id {.} port_path
regsub -all {/} $port_path {_} port_path
return [file join $portdbpath logs $port_path $portname]
}
diff -rupN MacPorts-2.10.0.orig/src/port1.0/tests/portutil.test MacPorts-2.10.0/src/port1.0/tests/portutil.test
--- MacPorts-2.10.0.orig/src/port1.0/tests/portutil.test 2024-08-07 23:17:43
+++ MacPorts-2.10.0/src/port1.0/tests/portutil.test 2024-08-13 20:25:56
@@ -52,9 +52,21 @@ proc init_eval_targets {} {
set mport [mportopen file://.]
+ proc trim_prefix {fullpath prefix} {
+ set fullpath [file normalize $fullpath]
+ set prefix [file normalize $prefix]
+ if {[string first $prefix $fullpath] != 0} {
+ return $fullpath
+ }
+ set relative_path [string range $fullpath [string length $prefix] end]
+ set relative_path [string trimleft $relative_path "/\\"]
+ return $relative_path
+ }
+
proc getportbuildpath {id {portname ""}} {
global portdbpath
- regsub {://} $id {.} port_path
+ set s_id [trim_prefix $id [file join $portdbpath sources]]
+ regsub {://} $s_id {.} port_patha
regsub -all {/} $port_path {_} port_path
return [file join $portdbpath build $port_path $portname]
}
動作の確認
このパッチを使って下記手順で再インストールを試したところ、
% tar xvf MacPorts-2.10.0.tar.bz2
% cd MacPorts-2.10.0
% patch -p1 < ${somewhere}/MacPorts-truncate-workpath.patch
% ./configure --prefix=${HOME}/Documents/workspace/opr/depot/macports-2.10.0.arm64 \
--with-no-root-privileges --enable-readline \
--enable-shared --without-startupitems \
--with-applications-dir=${HOME}/Documents/workspace/opr/depot/macports-2.10.0.arm64/Applications \
--with-frameworks-dir=${HOME}/Documents/workspace/opr/depot/macports-2.10.0.arm64/Library/Frameworks
% make && make install
とくに問題なくインストールされた。PATH
を通して試しに
% port -v selfupdate
% port -v install coreutils
...
とか、実行してみると、coreutils
は、作業ディレクトリ
${prefix}/var/macports/build/rsync.macports.org_macports_release_tarballs_ports_sysutils_coreutils/
以下に展開され、サブディレクトリ名は上位ディレクトリを含まない名前に短縮できた。
ソースコード全体を確認してない場当たり対処ですが、いまのところ、とくに副作用はなさそうである。(まだ顕在化してない。)
メンテナンス
(追記: 2024/8/13)
もし、port selfupdate
で、macportsのバージョンアップがあると、上記のパッチがあたったものは上書きされてしまうので、その場合には再度書き換える必要がある。
% port -v selfupdate
% (cd ${macports_root}/libexec/macports/lib/macports1.0 ; patch -p3 < {someware}/macports-1.0-buildpath-truncate.patch)
などとすればよいはず。
diff -rc MacPorts-2.10.0.orig/src/macports1.0/macports.tcl MacPorts-2.10.0/src/macports1.0/macports.tcl
*** MacPorts-2.10.0.orig/src/macports1.0/macports.tcl Wed Aug 7 23:17:43 2024
--- MacPorts-2.10.0/src/macports1.0/macports.tcl Tue Aug 13 20:25:36 2024
***************
*** 3028,3043 ****
return 0
}
proc macports::getportbuildpath {id {portname {}}} {
variable portdbpath
! regsub {://} $id {.} port_path
regsub -all {/} $port_path {_} port_path
return [file join $portdbpath build $port_path $portname]
}
proc macports::getportlogpath {id {portname {}}} {
variable portdbpath
! regsub {://} $id {.} port_path
regsub -all {/} $port_path {_} port_path
return [file join $portdbpath logs $port_path $portname]
}
--- 3028,3056 ----
return 0
}
+ proc macports::trim_prefix {fullpath prefix} {
+ set fullpath [file normalize $fullpath]
+ set prefix [file normalize $prefix]
+ if {[string first $prefix $fullpath] != 0} {
+ return $fullpath
+ }
+ set relative_path [string range $fullpath [string length $prefix] end]
+ set relative_path [string trimleft $relative_path "/\\"]
+ return $relative_path
+ }
+
proc macports::getportbuildpath {id {portname {}}} {
variable portdbpath
! set s_id [macports::trim_prefix $id [file join $portdbpath sources]]
! regsub {://} $s_id {.} port_path
regsub -all {/} $port_path {_} port_path
return [file join $portdbpath build $port_path $portname]
}
proc macports::getportlogpath {id {portname {}}} {
variable portdbpath
! set s_id [macports::trim_prefix $id [file join $portdbpath sources]]
! regsub {://} $s_id {.} port_path
regsub -all {/} $port_path {_} port_path
return [file join $portdbpath logs $port_path $portname]
}
まとめ
-
Macports
をディレクトリ階層のちょっと深いところにおくと、 ソフトウェアのビルドの際に非常に長い名前の作業ディレクトリを作成し、その下で作業する。そのため OSが許すファイル名長さの上限を超えてしまうことによるエラーを起こしやすい潜在的な問題がある.
(しかも、macportsのインストール先のパスの長さの違いにより起きたり起きなかったりするので、エラーの原因が特定しづらい。) - 設定ファイルや、シンボリックリンク活用での回避はできない模様。
- そのためソース(Tclのスクリプト)を書き換えることで対処してみた。
パッチファイルはここに記述。 - 場当たり的対処なので、当然ながら動作未保証。いまのところ顕在化した副作用はない模様。
感想
これはMacports
の問題というよりは、macOSのFILENAME_MAX
,PATH_MAX
が1024と小さいことが問題なきがします。(比較として、手元にあるLinux OSだと4096。)