-
Notifications
You must be signed in to change notification settings - Fork 209
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
macOS向け自動ビルドの際に.dylibファイルの問題を検出・修正するスクリプトを追加 #191
Changes from all commits
9af1746
19c7640
7af9194
1755502
f814e18
c10ff0f
7a8215a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
""" | ||
macOSにおいて共有ライブラリを操作するためのツールをまとめたモジュール | ||
""" | ||
|
||
import subprocess | ||
from pathlib import Path | ||
from typing import List | ||
|
||
|
||
def get_dylib_paths(base_path: Path) -> List[Path]: | ||
"""base_path以下の全てのサブディレクトリにあるdylibファイルのリストを返す""" | ||
return list(base_path.glob("**/*.dylib")) | ||
|
||
|
||
def get_rpaths(shared_lib_path: Path) -> List[Path]: | ||
"""引数で指定された共有ライブラリのrpathのリストを返す""" | ||
proc = subprocess.run(["otool", "-L", str(shared_lib_path)], stdout=subprocess.PIPE) | ||
output = proc.stdout.decode("utf-8") | ||
paths = [ | ||
Path(line.lstrip().split(" ", maxsplit=1)[0]) | ||
for line in output.splitlines()[1:] | ||
] | ||
# 得られたパスのリストのうち、共有ライブラリ自体とライブラリ名が同じものは | ||
# rpath ではなく install ID というものなので除外 | ||
return [ | ||
path | ||
for path in paths | ||
if path.name.split(".")[0] != shared_lib_path.name.split(".")[0] | ||
] | ||
|
||
|
||
def is_distributable_rpath(rpath: Path) -> bool: | ||
"""開発環境にインストールされたパッケージに依存しないrpathかどうか""" | ||
# 以下のプレフィックスで始まるrpathは配布に際して問題がない | ||
# - プレースホルダ。実行時に自動で解決される | ||
# - @executable_path/ | ||
# - @loader_path/ | ||
# - @rpath/ | ||
# - システム標準のライブラリがあるディレクトリ | ||
# - /usr/lib/ | ||
# - /System/Library/Frameworks/ | ||
# - /System/Library/PrivateFrameworks/ | ||
DISTRIBUTABLE_PREFIXES = [ | ||
"@executable_path/", | ||
"@loader_path/", | ||
"@rpath/", | ||
"/usr/lib/", | ||
"/System/Library/Frameworks/", | ||
"/System/Library/PrivateFrameworks/", | ||
] | ||
result = False | ||
|
||
for prefix in DISTRIBUTABLE_PREFIXES: | ||
if str(rpath).startswith(prefix): | ||
result = True | ||
break | ||
else: | ||
continue | ||
|
||
return result | ||
|
||
|
||
def change_rpath(old_rpath: Path, new_rpath: Path, dylib_path: Path, base_path: Path): | ||
"""dylib_pathで指定されたdylibのrpathを、old_rpathから、new_rpath(base_pathからの相対パスに変換したもの)に変更する""" | ||
relative_new_rpath = new_rpath.relative_to(base_path) | ||
subprocess.run( | ||
[ | ||
"install_name_tool", | ||
"-change", | ||
old_rpath, | ||
"@rpath/" + str(relative_new_rpath), | ||
dylib_path, | ||
] | ||
) | ||
|
||
|
||
class SharedLib: | ||
"""共有ライブラリの情報""" | ||
|
||
__path: Path | ||
__rpaths: List[Path] | ||
|
||
def __init__(self, shared_lib_path: Path): | ||
self.__path = shared_lib_path | ||
self.__rpaths = get_rpaths(shared_lib_path) | ||
|
||
@property | ||
def path(self) -> Path: | ||
return self.__path | ||
|
||
def get_non_distributable_rpaths(self) -> List[Path]: | ||
"""rpathのうち、開発環境に依存しているもののリスト""" | ||
return [rpath for rpath in self.__rpaths if not is_distributable_rpath(rpath)] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
""" | ||
配布物内の.dylibファイルの不足を解消するためのスクリプト | ||
|
||
引数で指定したbase_directory以下にある.dylibファイルのrpathをチェックし、 | ||
rpathの指す.dylibファイルがbase_directory以下に存在しなかった場合、 | ||
rpathの指している場所からその.dylibファイルをbase_directory直下へとコピーする。 | ||
""" | ||
|
||
import argparse | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ここにあるコードはおそらくずっと変わらないと思います。 |
||
import shutil | ||
import sys | ||
from pathlib import Path | ||
from typing import List, Set | ||
|
||
from build_util_macos.shlib_tools import SharedLib, get_dylib_paths | ||
|
||
parser = argparse.ArgumentParser() | ||
parser.add_argument( | ||
"base_directory", help="copy the missing dylibs under base_directory", type=str | ||
) | ||
args = parser.parse_args() | ||
base_dir_path = Path(args.base_directory) | ||
|
||
if not (base_dir_path.exists() and base_dir_path.is_dir()): | ||
print("could not find the directory:", str(base_dir_path), file=sys.stderr) | ||
exit(1) | ||
|
||
# base_dir_path以下の全てのサブディレクトリを探索して得たdylibのリスト | ||
dylib_paths: List[Path] = get_dylib_paths(base_dir_path) | ||
# 全てのdylibのファイル名のリスト | ||
dylib_names: List[str] = [path.name for path in dylib_paths] | ||
|
||
# 開発環境に依存したrpathを持つdylibのリスト | ||
non_distributable_dylibs: List[SharedLib] = [] | ||
for dylib_path in dylib_paths: | ||
lib = SharedLib(dylib_path) | ||
if lib.get_non_distributable_rpaths(): | ||
non_distributable_dylibs.append(lib) | ||
|
||
# 開発環境に依存したrpathの集合 | ||
non_distributable_rpaths: Set[Path] = set() | ||
for dylib in non_distributable_dylibs: | ||
rpaths: Set[Path] = set([rpath for rpath in dylib.get_non_distributable_rpaths()]) | ||
non_distributable_rpaths = non_distributable_rpaths.union(rpaths) | ||
|
||
# rpathが指しているdylibのうち、base_dir_path以下に存在しないもののリスト | ||
external_dylib_paths: List[Path] = [] | ||
for rpath in non_distributable_rpaths: | ||
if not (rpath.name in dylib_names): | ||
external_dylib_paths.append(rpath) | ||
|
||
# 不足しているdylibをbase_dir_path直下にコピー | ||
for dylib_path in external_dylib_paths: | ||
shutil.copy(dylib_path, base_dir_path, follow_symlinks=True) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
""" | ||
配布物内の.dylibファイルのrpathをどのようなユーザー環境においても有効になるように修正するスクリプト | ||
|
||
引数で指定したbase_directory以下にある.dylibファイルのrpathをチェックし、 | ||
開発環境に依存した(配布先の環境に存在することが保証されていない)rpathであった場合、 | ||
base_directory以下の.dylibファイルを相対パスで指すように変更する。 | ||
(base_directory以下の.dylibファイルに不足がないことを前提とする。) | ||
""" | ||
|
||
import argparse | ||
import sys | ||
from pathlib import Path | ||
from typing import List, Set | ||
|
||
from build_util_macos.shlib_tools import SharedLib, change_rpath, get_dylib_paths | ||
|
||
parser = argparse.ArgumentParser() | ||
parser.add_argument( | ||
"base_directory", help="fix the rpaths of the dylibs under base_directory", type=str | ||
) | ||
args = parser.parse_args() | ||
base_dir_path = Path(args.base_directory) | ||
|
||
if not (base_dir_path.exists() and base_dir_path.is_dir()): | ||
print("could not find the directory:", str(base_dir_path), file=sys.stderr) | ||
exit(1) | ||
|
||
# base_dir_path以下の全てのサブディレクトリを探索して得たdylibのリスト | ||
internal_dylib_paths: List[Path] = get_dylib_paths(base_dir_path) | ||
# 全てのdylibのファイル名のリスト | ||
internal_dylib_names: List[str] = [path.name for path in internal_dylib_paths] | ||
|
||
# 開発環境に依存したrpathを持つdylibのリスト | ||
non_distributable_dylibs: List[SharedLib] = [] | ||
for internal_dylib_path in internal_dylib_paths: | ||
lib = SharedLib(internal_dylib_path) | ||
if lib.get_non_distributable_rpaths(): | ||
non_distributable_dylibs.append(lib) | ||
|
||
# 開発環境に依存したrpathの集合 | ||
non_distributable_rpaths: Set[Path] = set() | ||
for dylib in non_distributable_dylibs: | ||
rpaths: Set[Path] = set([rpath for rpath in dylib.get_non_distributable_rpaths()]) | ||
non_distributable_rpaths = non_distributable_rpaths.union(rpaths) | ||
|
||
# rpathが指しているdylibのうち、base_dir_path以下に存在しないもののリスト | ||
external_dylib_paths: List[Path] = [] | ||
for rpath in non_distributable_rpaths: | ||
if not (rpath.name in internal_dylib_names): | ||
external_dylib_paths.append(rpath) | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ここから上の処理は結構似ているので、1つのファイルにしても良いかもと思いました。 |
||
# base_dir_path以下でdylibが不足している場合は、不足しているdylibを表示して終了 | ||
if external_dylib_paths: | ||
print( | ||
f"following dylibs not found under base_dir_path ({str(base_dir_path)}):", | ||
file=sys.stderr, | ||
) | ||
for path in external_dylib_paths: | ||
print(f"\t{path.name}", file=sys.stderr) | ||
exit(1) | ||
|
||
# 開発環境に依存したrpathを、base_dir_path以下のdylibを指すように変更 | ||
for dylib in non_distributable_dylibs: | ||
for rpath in dylib.get_non_distributable_rpaths(): | ||
for internal_dylib_path in internal_dylib_paths: | ||
if internal_dylib_path.name == rpath.name: | ||
change_rpath(rpath, internal_dylib_path, dylib.path, base_dir_path) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
capture_output=Trueでもできるかもです。
(修正はしてもしなくてもいいと思います)