0
0

Macportsを任意の場所にインストールしたとき、潜在的に遭遇するトラブルを回避する。

Last updated at Posted at 2024-08-09
  • お忙しい方は、まとめをご覧ください。
  • (2024/8/13追記) メンテナンスの項(port selfupdateでバージョンアップしてしまった際の対応)

macportsのインストール先を変えた場合に遭遇する問題

macOSのソフトウェアディストリビューションのMacportsを使っているが、ディスクのシステム領域を汚さずユーザー領域にインストールして使っている。自分でコンパイルして使うソフトウェアは、~/Documents/workspace/opr/depot/以下に、ソフトウェアごとにディレクトリを置いていくことにしているので、公式サイトのSource Installationに従って、

macportsをインストールした時
% 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/binPATHを通しておけば、普通の使い方、

portの利用例
% 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は、

Portfileの場所の例
% ${prefix}/var/macports/sources/rsync.macports.org/macports/release/tarballs/ports/lang/llvm-18/Portfile

といった場所に置かれる。これをコンパイルするための作業ディレクトリとしては、

port install llvm-18の作業ディレクトリ
% 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}/etc/macports/macports.conf(うまくいかない例)
...
prefix                  /Users/{username}/macports
...

見かけ上、短いパスにすればいいのではないか、と思い試してみるが、実際にはうまくいかない。というのも、portのコードを見てみると、workpath

portの中身
% 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の中では、

macports.tclの中身
% 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の定義部分や周囲を見てみると、

macports.tclの中身
% 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::getportbuildpathmacports::getportlogpathPortfileのパスから作業サブディレクトリ名を生成しているようだ。

実装意図の推定

わざわざPortfileから作業ディレクトリ名を生成するのは、察するに、依存関係に従って同時にいくつものソフトウェアパッケージを展開する際にファイルが混じらないようにするためと思われる。また、単純にportnameとしないのは、${prefix}/etc/macports/macports.confのリストにある複数のレポジトリに同じportnameのパッケージがある可能性を想定しているのであろう。もしその目的であれば、port毎にユニークではない上位ディレクトリ構造${prefix}/var/macports/sources/までサブディレクトリ名に含める必要はなさそうである。

さらに、macportsのソースコードをざっくり見渡した感じだと、この文字列変換の逆を行なっているコードはなさそうであるし、そもそも不可逆な変換をしてしまっている。(${prefix}のディレクトリ名に_が含まれる場合があるので、単射な写像でない。)。 なので、この情報を落としてしまっても他への影響はないのではないかと踏んで、試しに作業ディレクトリ名からユニークではない部分を削って文字列長を短縮する変更をしてmacportsをインストールし直してみた。
(ついでにバージョンアップ)

コードの修正

変更点としては下記。(macportsの中では、なんだかいっぱいグローバル変数が使われているようなので、念の為に追加で定義したもの以外の変数の上書き修正は避けた)

MacPorts-2.10.0-truncate-workpath.patch
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]
     }

動作の確認

このパッチを使って下記手順で再インストールを試したところ、

macportsを修正して再インストール
% 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)

などとすればよいはず。

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。)

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0