Mac OS X LionでPython 2.7.2をビルドするとエラーになる
pythonbrewからpython 2.7.2を入れようとしたところ、エラーが出てインストールに失敗しました。pythonbrewが悪者みたいな言い方ですが、そんなことはありません。
pythonbrewから普通にpython 2.7.2をインストールすると、ビルドには成功するもののmake testの段階でエラーになります。--no-testオプションを使うとmake testをスキップできるのですが、気になるので調べてみました。調べたついでにパッチを作りました。
(ちなみに、homebrewからpythonをインストールしたときはエラーが起こらなかったなぁ、と思ったら、2012年1月時点のFormulaでは最初からmake testをスキップしてインストールしていました。)
失敗しているテスト
make test時に実行されるテストスイートのうち、以下のものが失敗していました。
5 tests failed: test_anydbm test_ctypes test_distutils test_platform test_whichdb
調べてみる
pythonのpの字も知らない初心者が無謀にも調査してみます。
ひとまずpythonbrewから--no-testオプションでインストールしておきます。
$ pythonbrew install --no-test 2.7.2
test_anydbm
まずはインストール時のログに出ていたtest_anydbmを調べてみます。とりあえずtest_anydbmで検索してみる。
$ cd ~/.pythonbrew $ find . -name "*test_anydbm*" ./build/Python-2.7.2/Lib/test/test_anydbm.py ./pythons/Python-2.7.2/lib/python2.7/test/test_anydbm.py ./pythons/Python-2.7.2/lib/python2.7/test/test_anydbm.pyc ./pythons/Python-2.7.2/lib/python2.7/test/test_anydbm.pyo
ほぅほぅ、Lib/testというところにテストスイートが格納されているみたいですね。~/.pythonbrew/pythons/ディレクトリはビルドしたpythonのデプロイ先らしいので~/.pythonbrew/build/Python-2.7.2/Lib/test/test_anydbm.pyを実行してみます。
$ ./pythons/Python-2.7.2/bin/python ./build/Python-2.7.2/Lib/test/test_anydbm.py
簡単に実行できました。以下のテストケースでエラーが出ました。
test_anydbm_creation (__main__.AnyDBMTestCase) ... ok test_anydbm_keys (__main__.AnyDBMTestCase) ... ERROR test_anydbm_modification (__main__.AnyDBMTestCase) ... ERROR test_anydbm_read (__main__.AnyDBMTestCase) ... ERROR
ERRORが出ている箇所は、いずれもanydbm.py:82でした。
ERROR: test_anydbm_keys (__main__.AnyDBMTestCase) ---------------------------------------------------------------------- Traceback (most recent call last): File "test_anydbm.py", line 61, in test_anydbm_keys f = anydbm.open(_fname, 'r') File "/Users/toggtc/.pythonbrew/pythons/Python-2.7.2/lib/python2.7/anydbm.py", line 82, in open raise error, "db type could not be determined" error: db type could not be determined
error: db type could not be determined... DBタイプが判別できない、、、だと?ソースを追っかけてみましょう。
[anydbm.py]
57 def open(file, flag='r', mode=0666): ... 69 # guess the type of an existing database 70 from whichdb import whichdb 71 result=whichdb(file) 72 if result is None: 73 # db doesn't exist 74 if 'c' in flag or 'n' in flag: 75 # file doesn't exist and the new 76 # flag was used so use default type 77 mod = _defaultmod 78 else: 79 raise error, "need 'c' or 'n' flag to open new db" 80 elif result == "": 81 # db type cannot be determined 82 raise error, "db type could not be determined"
71行目のwhichdb()の結果が空文字で例外を投げているようです。70行目でimport whichdbってやっているから、whichdb.pyがあるのかな。というかpythonって関数の中でimportできるんかい。
whichdbで検索。
$ find ./ -name "*whichdb*" ./test/test_whichdb.py ./test/test_whichdb.pyc ./test/test_whichdb.pyo ./whichdb.py ./whichdb.pyc ./whichdb.pyo
まさにwhichdb.pyというファイルがありました。
17 def whichdb(filename): ... 93 # Check for GNU dbm 94 if magic == 0x13579ace: 95 return "gdbm" ... 112 # Unknown 113 return ""
gdbmをインストールしているのですが、何故か94行目の判定に引っかからず、113行目に到達して空文字を返しているようですね。バグかな?
if magic == 0x13579ace:でググってみます。
・・・
ググりました、先生!→ http://bugs.python.org/issue13007
どうやら、gdbm 1.9以降で94行目のマジックナンバーが変わってしまったようです。今回はgdbm 1.10を使っているのでパッチを当てないといけないですね。上記ページのリンク先にパッチがあったので、当ててみます。
cd ~/.pythonbrew/build/Python-2.7.2 mkdir patches cd patches curl -kLo gdbm_1.9_magic.patch http://hg.python.org/cpython/raw-rev/14cafb8d1480 cd ../ patch -p1 < ./patches/gdbm_1.9_magic.patch
再度、test_anydbmを実行してみます。
$ cd ~/.pythonbrew/build/Python-2.7.2/Lib/test $ ../../../../pythons/Python-2.7.2/bin/python test_anydbm.py test_anydbm_creation (__main__.AnyDBMTestCase) ... ok test_anydbm_keys (__main__.AnyDBMTestCase) ... ok test_anydbm_modification (__main__.AnyDBMTestCase) ... ok test_anydbm_read (__main__.AnyDBMTestCase) ... ok ---------------------------------------------------------------------- Ran 4 tests in 0.019s OK
今度はテストに成功しました!
よしよし、この調子で次いってみましょ
test_ctypes
例のごとく、test_ctypesを実行。
$ ./pythons/Python-2.7.2/bin/python ./build/Python-2.7.2/Lib/test/test_ctypes.py ...(省略)... test_byte (ctypes.test.test_cfuncs.CFunctions) ... FAIL test_byte_plus (ctypes.test.test_cfuncs.CFunctions) ... FAIL test_short (ctypes.test.test_cfuncs.CFunctions) ... FAIL test_short_plus (ctypes.test.test_cfuncs.CFunctions) ... FAIL test_doubleresult (ctypes.test.test_functions.FunctionTestCase) ... FAIL test_floatresult (ctypes.test.test_functions.FunctionTestCase) ... FAIL test_intresult (ctypes.test.test_functions.FunctionTestCase) ... FAIL test_longdoubleresult (ctypes.test.test_functions.FunctionTestCase) ... FAIL ====================================================================== FAIL: test_byte (ctypes.test.test_cfuncs.CFunctions) ---------------------------------------------------------------------- Traceback (most recent call last): File "/Users/toggtc/.pythonbrew/pythons/Python-2.7.2/lib/python2.7/ctypes/test/test_cfuncs.py", line 21, in test_byte self.assertEqual(self.S(), -126) AssertionError: 130 != -126 ====================================================================== FAIL: test_byte_plus (ctypes.test.test_cfuncs.CFunctions) ---------------------------------------------------------------------- Traceback (most recent call last): File "/Users/toggtc/.pythonbrew/pythons/Python-2.7.2/lib/python2.7/ctypes/test/test_cfuncs.py", line 27, in test_byte_plus self.assertEqual(self.S(), -126) AssertionError: 130 != -126 ====================================================================== FAIL: test_short (ctypes.test.test_cfuncs.CFunctions) ---------------------------------------------------------------------- Traceback (most recent call last): File "/Users/toggtc/.pythonbrew/pythons/Python-2.7.2/lib/python2.7/ctypes/test/test_cfuncs.py", line 45, in test_short self.assertEqual(self.S(), -32766) AssertionError: 32770 != -32766 ====================================================================== FAIL: test_short_plus (ctypes.test.test_cfuncs.CFunctions) ---------------------------------------------------------------------- Traceback (most recent call last): File "/Users/toggtc/.pythonbrew/pythons/Python-2.7.2/lib/python2.7/ctypes/test/test_cfuncs.py", line 51, in test_short_plus self.assertEqual(self.S(), -32766) AssertionError: 32770 != -32766 ====================================================================== FAIL: test_doubleresult (ctypes.test.test_functions.FunctionTestCase) ---------------------------------------------------------------------- Traceback (most recent call last): File "/Users/toggtc/.pythonbrew/pythons/Python-2.7.2/lib/python2.7/ctypes/test/test_functions.py", line 143, in test_doubleresult self.assertEqual(result, -21) AssertionError: 65771.0 != -21 ====================================================================== FAIL: test_floatresult (ctypes.test.test_functions.FunctionTestCase) ---------------------------------------------------------------------- Traceback (most recent call last): File "/Users/toggtc/.pythonbrew/pythons/Python-2.7.2/lib/python2.7/ctypes/test/test_functions.py", line 131, in test_floatresult self.assertEqual(result, -21) AssertionError: 65771.0 != -21 ====================================================================== FAIL: test_intresult (ctypes.test.test_functions.FunctionTestCase) ---------------------------------------------------------------------- Traceback (most recent call last): File "/Users/toggtc/.pythonbrew/pythons/Python-2.7.2/lib/python2.7/ctypes/test/test_functions.py", line 105, in test_intresult self.assertEqual(result, -21) AssertionError: 65771 != -21 ====================================================================== FAIL: test_longdoubleresult (ctypes.test.test_functions.FunctionTestCase) ---------------------------------------------------------------------- Traceback (most recent call last): File "/Users/toggtc/.pythonbrew/pythons/Python-2.7.2/lib/python2.7/ctypes/test/test_functions.py", line 155, in test_longdoubleresult self.assertEqual(result, -21) AssertionError: 65771.0 != -21 ---------------------------------------------------------------------- Ran 337 tests in 0.458s FAILED (failures=8, skipped=1) Traceback (most recent call last): File "test_ctypes.py", line 15, in <module> test_main() File "test_ctypes.py", line 12, in test_main run_unittest(unittest.TestSuite(suites)) File "/Users/toggtc/.pythonbrew/pythons/Python-2.7.2/lib/python2.7/test/test_support.py", line 1089, in run_unittest _run_suite(suite) File "/Users/toggtc/.pythonbrew/pythons/Python-2.7.2/lib/python2.7/test/test_support.py", line 1072, in _run_suite raise TestFailed(err) test.test_support.TestFailed: multiple errors occurred
( ゚д゚) ...
CFunctions... ctypes... ってpythonとCを連携させる機能ですかね。イヤな予感がしてきました。
とりあえずソースを確認してみます!まずはtest_byteのスタックトレースに出ていたtest_cfuncs.py:21を見ます。
[test_cfuncs.py]
9 class CFunctions(unittest.TestCase): 10 _dll = CDLL(_ctypes_test.__file__) 11 12 def S(self): 13 return c_longlong.in_dll(self._dll, "last_tf_arg_s").value 14 def U(self): 15 return c_ulonglong.in_dll(self._dll, "last_tf_arg_u").value 16 17 def test_byte(self): 18 self._dll.tf_b.restype = c_byte 19 self._dll.tf_b.argtypes = (c_byte,) 20 self.assertEqual(self._dll.tf_b(-126), -42) 21 self.assertEqual(self.S(), -126)
テストエラーのログでは21行目で、self.S()が130になっていてassertに引っかかったようですね。long longのビット値を確認しているのかな。とぉーってもイヤな予感がします☆彡
macに標準で入っているpython 2.7.1で同じテストを実行してみましょう。
$ cd /usr/lib/python2.7/test $ python test_ctypes.py ... ---------------------------------------------------------------------- Ran 332 tests in 0.458s OK (skipped=1)
OK牧場!
テストコードに差異は無いか確認してみます。
$ diff /usr/lib/python2.7/ctypes/test/test_cfuncs.py ~/.pythonbrew/pythons/Python-2.7.2/lib/python2.7/ctypes/test/test_cfuncs.py
テストコードの差分は無いですね。ついでにctypesモジュールの差分をとってみます。
$ diff -r ~/.pythonbrew/pythons/Python-2.7.2/lib/python2.7/ctypes /usr/lib/python2.7/ctypes --exclude='*.pyc' --exclude='*.pyo' --exclude='*test*'
差分が、差分がありません><
イヤな予感しかしない。Appleビルドのpythonがうまく機能していることを考えると、おそらくllvm-gccでビルドしたせいでしょう。
llvm-gccではないgccで、もう一度python 2.7.2をビルドし直してみます。
※macにllvm-gccではないgccを導入する手順はこちら http://toggtc.hatenablog.com/entry/2012/01/28/224006
$ pythonbrew uninstall 2.7.2 $ pythonbrew --no-test --verbose --configure="CC=/usr/bin/gcc-4.2" 2.7.2
再度テストを実行します。
$ cd ~/.pythonbrew/build/Python-2.7.2/Lib/test/ $ ../../../../pythons/Python-2.7.2/bin/python test_ctypes.py ... ---------------------------------------------------------------------- Ran 337 tests in 0.378s OK (skipped=1)
gccだと成功しました!
llvm-gcc・・・(´・ω・`)
test_distutils
次はtest_distutilsです。テストスイートを実行したところ、エラーは1個だけでした。
$ ./pythons/Python-2.7.2/bin/python ./build/Python-2.7.2/Lib/test/test_distutils.py ... i686-apple-darwin11-gcc-4.2.1: /private/var/folders/jy/dhptnvj90b34s0135sb_g6w80000gn/T/tmpAfN6sj/foo.so: No such file or directory ====================================================================== ERROR: test_get_outputs (distutils.tests.test_build_ext.BuildExtTestCase) ---------------------------------------------------------------------- Traceback (most recent call last): File "/Users/toggtc/.pythonbrew/pythons/Python-2.7.2/lib/python2.7/distutils/tests/test_build_ext.py", line 291, in test_get_outputs cmd.run() File "/Users/toggtc/.pythonbrew/pythons/Python-2.7.2/lib/python2.7/distutils/command/build_ext.py", line 340, in run self.build_extensions() File "/Users/toggtc/.pythonbrew/pythons/Python-2.7.2/lib/python2.7/distutils/command/build_ext.py", line 449, in build_extensions self.build_extension(ext) File "/Users/toggtc/.pythonbrew/pythons/Python-2.7.2/lib/python2.7/distutils/command/build_ext.py", line 531, in build_extension target_lang=language) File "/Users/toggtc/.pythonbrew/pythons/Python-2.7.2/lib/python2.7/distutils/ccompiler.py", line 741, in link_shared_object extra_preargs, extra_postargs, build_temp, target_lang) File "/Users/toggtc/.pythonbrew/pythons/Python-2.7.2/lib/python2.7/distutils/unixccompiler.py", line 258, in link raise LinkError, msg LinkError: command 'gcc' failed with exit status 1
gccの実行でエラーが出ていますね。test_get_outputsというテストケース名とfoo.so: No such file or directoryというメッセージがあるので、どうやらfoo.soを作ろうとしたけど、実際には作られていないか、目的の場所に生成されていなくてエラーになっているようです。
not llvm-gccのgccに変えた後でもエラーが発生しているので、gccの問題では無さそうです。
っていう予想は付くのですが、一応トレースを上から追って行きましょう。
[distutils/command/build_ext.py]
266 def test_get_outputs(self): 267 tmp_dir = self.mkdtemp() 268 c_file = os.path.join(tmp_dir, 'foo.c') 269 self.write_file(c_file, 'void initfoo(void) {};\n') 270 ext = Extension('foo', [c_file]) 271 dist = Distribution({'name': 'xx', 272 'ext_modules': [ext]}) 273 cmd = build_ext(dist) 274 self._fixup_command(cmd) 275 cmd.ensure_finalized() 276 self.assertEqual(len(cmd.get_outputs()), 1) 277 278 if os.name == "nt": 279 cmd.debug = sys.executable.endswith("_d.exe") 280 281 cmd.build_lib = os.path.join(self.tmp_dir, 'build') 282 cmd.build_temp = os.path.join(self.tmp_dir, 'tempt') 283 284 # issue #5977 : distutils build_ext.get_outputs 285 # returns wrong result with --inplace 286 other_tmp_dir = os.path.realpath(self.mkdtemp()) 287 old_wd = os.getcwd() 288 os.chdir(other_tmp_dir) 289 try: 290 cmd.inplace = 1 291 cmd.run() 292 so_file = cmd.get_outputs()[0] 293 finally: 294 os.chdir(old_wd) 295 self.assertTrue(os.path.exists(so_file))
tmpディレクトリにfoo.cを作って、build_extという関数を実行したところでLinkError例外が出たようですね。build_extはコマンドパターンを実装しているようなので、run関数をみてみましょう。
[distutils/command/build_ext.py]
278 def run(self): ... 304 # Setup the CCompiler object that we'll use to do all the 305 # compiling and linking 306 self.compiler = new_compiler(compiler=self.compiler, 307 verbose=self.verbose, 308 dry_run=self.dry_run, 309 force=self.force) ... 311 # If we are cross-compiling, init the compiler now (if we are not 312 # cross-compiling, init would not hurt, but people may rely on 313 # late initialization of compiler even if they shouldn't...) 314 if os.name == 'nt' and self.plat_name != get_platform(): 315 self.compiler.initialize(self.plat_name) 316 317 # And make sure that any compile/link-related options (which might 318 # come from the command-line or from the setup script) are set in 319 # that CCompiler object -- that way, they automatically apply to 320 # all compiling and linking done here. 321 if self.include_dirs is not None: 322 self.compiler.set_include_dirs(self.include_dirs) 323 if self.define is not None: 324 # 'define' option is a list of (name,value) tuples 325 for (name, value) in self.define: 326 self.compiler.define_macro(name, value) 327 if self.undef is not None: 328 for macro in self.undef: 329 self.compiler.undefine_macro(macro) 330 if self.libraries is not None: 331 self.compiler.set_libraries(self.libraries) 332 if self.library_dirs is not None: 333 self.compiler.set_library_dirs(self.library_dirs) 334 if self.rpath is not None: 335 self.compiler.set_runtime_library_dirs(self.rpath) 336 if self.link_objects is not None: 337 self.compiler.set_link_objects(self.link_objects) ... 339 # Now actually compile and link everything. 340 self.build_extensions()
build_extコマンドオブジェクトに予めコンパイル時に必要なパラメータを設定しておく
↓
306行目でコンパイラインスタンスを生成
↓
317行目以降でbuild_extコマンドオブジェクト(self)が持っていたパラメータを、コンパイラオブジェクトに設定し直す
となっているようですね。
続いて、build_extensions()を辿っていくと...
[distutils/command/build_ext.py]
444 def build_extensions(self): 445 # First, sanity-check the 'extensions' list 446 self.check_extensions_list(self.extensions) 447 448 for ext in self.extensions: 449 self.build_extension(ext) 450 451 def build_extension(self, ext): ... 493 objects = self.compiler.compile(sources, 494 output_dir=self.build_temp, 495 macros=macros, 496 include_dirs=ext.include_dirs, 497 debug=self.debug, 498 extra_postargs=extra_args, 499 depends=ext.depends)
493-499行目で、さきほど設定したコンパイラオブジェクトからコンパイル処理を実行させて、
[distutils/command/build_ext.py]
522 self.compiler.link_shared_object( 523 objects, ext_path, 524 libraries=self.get_libraries(ext), 525 library_dirs=ext.library_dirs, 526 runtime_library_dirs=ext.runtime_library_dirs, 527 extra_postargs=extra_args, 528 export_symbols=self.get_export_symbols(ext), 529 debug=self.debug, 530 build_temp=self.build_temp, 531 target_lang=language) 532
522-531行目でリンクを実行しているようです。
スタックトレースでは531行目が出ていたので、493-499行目のコンパイル処理は終わって522-531行目のリンク処理で何らかのエラーが出たようですね。
スタックトレースの情報からself.compiler.link_shared_objectのcompilerオブジェクトはccompiler.pyだと分かるので、ccompilerのlink_shared_objectを見てみましょう。
[ccompiler.py]
732 def link_shared_object(self, objects, output_filename, output_dir=None, 733 libraries=None, library_dirs=None, 734 runtime_library_dirs=None, export_symbols=None, 735 debug=0, extra_preargs=None, extra_postargs=None, 736 build_temp=None, target_lang=None): 737 self.link(CCompiler.SHARED_OBJECT, objects, 738 output_filename, output_dir, 739 libraries, library_dirs, runtime_library_dirs, 740 export_symbols, debug, 741 extra_preargs, extra_postargs, build_temp, target_lang)
self.linkをみると
[ccompiler.py]
714 raise NotImplementedError
となっていたので、ccompilerは抽象クラスで、スタックトレースに出ていたunixccompilerが具象クラスという関係なのでしょう。
unixccompiler.pyをみてみます。スタックトレースの情報から、LinkError例外が出ているところを探ります。
[unixccompiler.py]
208 def link(self, target_desc, objects, ... 234 try: 235 if target_desc == CCompiler.EXECUTABLE: 236 linker = self.linker_exe[:] 237 else: 238 linker = self.linker_so[:] 239 if target_lang == "c++" and self.compiler_cxx: 240 # skip over environment variable settings if /usr/bin/env 241 # is used to set up the linker's environment. 242 # This is needed on OSX. Note: this assumes that the 243 # normal and C++ compiler have the same environment 244 # settings. 245 i = 0 246 if os.path.basename(linker[0]) == "env": 247 i = 1 248 while '=' in linker[i]: 249 i = i + 1 250 251 linker[i] = self.compiler_cxx[i] 252 253 if sys.platform == 'darwin': 254 linker = _darwin_compiler_fixup(linker, ld_args) 255 256 self.spawn(linker + ld_args) 257 except DistutilsExecError, msg: 258 raise LinkError, msg
tryスコープの中で、例外が出そうな箇所は、254行目か256行目ですね。さすがに246のbasename関数で例外を投げるってことはないでしょうから。
254行目にあった_darwin_compiler_fixupの中身を見てみます。
[unixccompiler.py]
44 def _darwin_compiler_fixup(compiler_so, cc_args): 53 stripArch = stripSysroot = 0 54 55 compiler_so = list(compiler_so) 56 kernel_version = os.uname()[2] # 8.4.3 57 major_version = int(kernel_version.split('.')[0]) 58 59 if major_version < 8: 60 # OSX before 10.4.0, these don't support -arch and -isysroot at 61 # all. 62 stripArch = stripSysroot = True 63 else: 64 stripArch = '-arch' in cc_args 65 stripSysroot = '-isysroot' in cc_args 66 67 if stripArch or 'ARCHFLAGS' in os.environ: 68 while 1: 69 try: 70 index = compiler_so.index('-arch') 71 # Strip this argument and the next one: 72 del compiler_so[index:index+2] 73 except ValueError: 74 break 75 76 if 'ARCHFLAGS' in os.environ and not stripArch: 77 # User specified different -arch flags in the environ, 78 # see also distutils.sysconfig 79 compiler_so = compiler_so + os.environ['ARCHFLAGS'].split() ...
ざっと見る限り、mac用にパラメータ調整しているだけでリンク処理はしていませんね。
次はspawnを見てみます。unixccompilerにspawnの実装が無かったので、ccompilerを見てみます。
[ccompiler.py]
14 from distutils.spawn import spawn 927 def spawn(self, cmd): 928 spawn(cmd, dry_run=self.dry_run)
おっと、実際にはspawn.pyに定義されていました。
[spawn.py]
17 def spawn(cmd, search_path=1, verbose=0, dry_run=0): 33 if os.name == 'posix': 34 _spawn_posix(cmd, search_path, dry_run=dry_run) 35 elif os.name == 'nt': 36 _spawn_nt(cmd, search_path, dry_run=dry_run) 37 elif os.name == 'os2': 38 _spawn_os2(cmd, search_path, dry_run=dry_run) 39 else: 40 raise DistutilsPlatformError, \
OSごとに実装が異なるようですね。macはPOSIX準拠なので_spawn_posixをみてみます。
[spawn.py]
100 def _spawn_posix(cmd, search_path=1, verbose=0, dry_run=0): 101 log.info(' '.join(cmd)) 102 if dry_run: 103 return 104 exec_fn = search_path and os.execvp or os.execv 105 pid = os.fork() 106 107 if pid == 0: # in the child 108 try: 109 exec_fn(cmd[0], cmd) 110 except OSError, e: 111 sys.stderr.write("unable to execute %s: %s\n" % 112 (cmd[0], e.strerror)) 113 os._exit(1) 114 115 sys.stderr.write("unable to execute %s for unknown reasons" % cmd[0]) 116 os._exit(1) 117 else: # in the parent 118 # Loop until the child either exits or is terminated by a signal 119 # (ie. keep waiting if it's merely stopped) 120 while 1: 121 try: 122 pid, status = os.waitpid(pid, 0) 123 except OSError, exc: 124 import errno 125 if exc.errno == errno.EINTR: 126 continue 127 raise DistutilsExecError, \ 128 "command '%s' failed: %s" % (cmd[0], exc[-1]) 129 if os.WIFSIGNALED(status): 130 raise DistutilsExecError, \ 131 "command '%s' terminated by signal %d" % \ 132 (cmd[0], os.WTERMSIG(status)) 133 134 elif os.WIFEXITED(status): 135 exit_status = os.WEXITSTATUS(status) 136 if exit_status == 0: 137 return # hey, it succeeded! 138 else: 139 raise DistutilsExecError, \ 140 "command '%s' failed with exit status %d" % \ 141 (cmd[0], exit_status)
おおっ!エラーログにあったメッセージを出力している箇所がありました!
140行目ですね。エラーログを抜粋します。
LinkError: command 'gcc' failed with exit status 1
完全に一致。
105行目でforkしたプロセスが109行目でgccコマンドを実行しているようです。
親プロセスは、122行目でwaitpidを呼び出してプロセスの状態変化を見て134行目でリンク処理をした子プロセスが終了したのを確認したけど、終了コードが異常でエラーを投げたんですね。
i686-apple-darwin11-gcc-4.2.1: /private/var/folders/jy/dhptnvj90b34s0135sb_g6w80000gn/T/tmpAfN6sj/foo.so: No such file or directoryっていうメッセージと合致しますね。
さて、処理の流れはわかったので、あとはgccに渡されたパラメータが何だったかを確認すれば原因が掴めそうですね。
パラメータの内容はログに出ていないので、デバッグログを仕込みましょう。
リンク時のパラメータを渡しているunixcompiler.py:256あたりをいじくります。
[unixcompiler.py]
256 print("[PY_DEBUG] unixcompiler.py) linker=", linker, "ld_args=", ld_args) 257 self.spawn(linker + ld_args)
print文を追加しました。
実行してみたところ、パラメータ値が取得できました。
('[PY_DEBUG] unixcompiler.py) linker=', ['/usr/bin/gcc-4.2', '-bundle', '-undefined', 'dynamic_lookup'], 'ld_args='['/var/folders/jy/dhptnvj90b34s0135sb_g6w80000gn/T/pythontest_aV8Rs2/tempt/var/folders/jy/dhptnvj90b34s0135sb_g6w80000gn/T/tmpQr8dDM/foo.o', '-L`pwd`', '-L', '-o', '/private/var/folders/jy/dhptnvj90b34s0135sb_g6w80000gn/T/tmp24JHaO/foo.so'])
見づらいので、コマンドに整形します。
$ /usr/bin/gcc-4.2 -bundle -undefined dynamic_lookup /var/folders/jy/dhptnvj90b34s0135sb_g6w80000gn/T/pythontest_aV8Rs2/tempt/var/folders/jy/dhptnvj90b34s0135sb_g6w80000gn/T/tmpQr8dDM/foo.o -L`pwd` -L -o /private/var/folders/jy/dhptnvj90b34s0135sb_g6w80000gn/T/tmp24JHaO/foo.so
お分かり頂けたであろうか?
クローズアップ
-L`pwd` -L -o
Apple GCCの場合、このパラメータ指定だと-oというディレクトリを探索してしまい、エラーになりますね。No such file or directoryというメッセージはそのせいでしょう。
念のため、mac標準のpython側にも同様のデバッグコードを仕込んで動かしてみます。
llvm-gcc-4.2 -Wl,-F. -bundle -undefined dynamic_lookup -Wl,-F. -arch i386 -arch x86_64 /var/folders/jy/dhptnvj90b34s0135sb_g6w80000gn/T/pythontest_tcsjJO/tempt/var/folders/jy/dhptnvj90b34s0135sb_g6w80000gn/T/tmpsN0zMd/foo.o -o /private/var/folders/jy/dhptnvj90b34s0135sb_g6w80000gn/T/tmpZBrIwM/foo.so
mac標準のpythonでは-Lオプションが出て来ませんね。
ひとまず、渡しているパラメータがわかったので、ソースをもう一度みてみましょう。
gccに渡すパラメータはbuild_extコマンドオブジェクトが保持しているのでしたね。build_extコマンドオブジェクトを生成したところをみてみましょう。
[test_build_ext.py]
266 def test_get_outputs(self): ... 273 cmd = build_ext(dist) 274 self._fixup_command(cmd) ... 291 cmd.run()
273行目でbuild_extコマンドオブジェクトのインスタンスを生成しています。291行目の_fixup_command関数の中でbuild_extコマンドオブジェクトを渡して何かしていますね。あやしぃ
[test_build_ext.py]
53 def _fixup_command(self, cmd): 54 # When Python was build with --enable-shared, -L. is not good enough 55 # to find the libpython<blah>.so. This is because regrtest runs it 56 # under a tempdir, not in the top level where the .so lives. By the 57 # time we've gotten here, Python's already been chdir'd to the 58 # tempdir. 59 # 60 # To further add to the fun, we can't just add library_dirs to the 61 # Extension() instance because that doesn't get plumbed through to the 62 # final compiler command. 63 if (sysconfig.get_config_var('Py_ENABLE_SHARED') and 64 not sys.platform.startswith('win')): 65 runshared = sysconfig.get_config_var('RUNSHARED') 66 if runshared is None: 67 cmd.library_dirs = ['.'] 68 else: 69 name, equals, value = runshared.partition('=') 70 cmd.library_dirs = value.split(os.pathsep)
何やらコメントが書いてありますね。
意訳すると、"--enable-sharedオプション付きでPythonをビルドした場合、Pythonはtmpディレクトリに移動するから-Lパラメータがうまく機能しないかもしれないよ。" ってことですかね。ふむ、このコメントで警告している内容は、今回の件とは直接関係なさそうですね。
63行目と65行目でconfigの値(Py_ENABLE_SHAREDとRUNSHARED)を取得しているので、実際にpythonを動かしてどんな値が取得されるかみてみましょう。
python 2.7.2の場合:
$ ~/.pythonbrew/pythons/Python-2.7.2/bin/python Python 2.7.2 (default, Jan 22 2012, 06:39:23) [GCC 4.2.1 (Apple Inc. build 5666) (dot 3)] on darwin Type "help", "copyright", "credits" or "license" for more information. >>> from distutils import sysconfig >>> sysconfig.get_config_var('Py_ENABLE_SHARED') 1 >>> sysconfig.get_config_var('RUNSHARED') 'DYLD_LIBRARY_PATH=`pwd`:'
Py_ENABLE_SHARED → 1
RUNSHARED → 'DYLD_LIBRARY_PATH=`pwd`:'
mac標準pythonの場合:
$ python Python 2.7.1 (r271:86832, Jul 31 2011, 19:30:53) [GCC 4.2.1 (Based on Apple Inc. build 5658) (LLVM build 2335.15.00)] on darwin Type "help", "copyright", "credits" or "license" for more information. >>> from distutils import sysconfig >>> sysconfig.get_config_var('Py_ENABLE_SHARED') 0 >>> sysconfig.get_config_var('RUNSHARED') 'DYLD_FRAMEWORK_PATH=/private/var/tmp/python/python-57~1/2.7/python:'
Py_ENABLE_SHARED → 0
RUNSHARED → 'DYLD_FRAMEWORK_PATH=/private/var/tmp/python/python-57~1/2.7/python:'
どうやらmac標準pythonはPy_ENABLE_SHAREDが0なので、--enable-shared付きでビルドされていないようです(けど、dylibはちゃんと作られています)。ということは、_fixup_command関数の63行目でfalse判定になるので何も処理されないようですね。
pythonbrewからインストールしたpythonは--enable-shared付きでビルドされているので、65行目以降の処理が実行されます。--enable-shared固有の問題でしたね。
さて、さきほど確認した結果だと、RUNSHAREDには'DYLD_LIBRARY_PATH=`pwd`:'が設定されていました。この値とソースを照らしあわせてみましょう。
65 runshared = sysconfig.get_config_var('RUNSHARED')
まず、ここでrunsharedにDYLD_LIBRARY_PATH=`pwd`: の値が設定されます。
66 if runshared is None: 67 cmd.library_dirs = ['.'] 68 else: 69 name, equals, value = runshared.partition('=') 70 cmd.library_dirs = value.split(os.pathsep)
ここで、runsharedはNoneではないので、69-70行目で、以下のように文字列分割しているようです。
DYLD_LIBRARY_PATH=`pwd`: ↓ DYLD_LIBRARY_PATH と `pwd`: ↓ `pwd`: ↓ `pwd` と (空白)
つまり、library_dirsには`pwd`と(空白)が設定されているので、gccのオプションで、
-L`pwd` -L (空白) -o /private/var/folders/jy/dhptnvj90b34s0135sb_g6w80000gn/T/tmp24JHaO/foo.so
となってしまい、gccの実行でエラーになったようです。
test_build_ext.pyを修正します。71-75行目を追加しました。
[test_build_ext.py]
53 def _fixup_command(self, cmd): ... 63 if (sysconfig.get_config_var('Py_ENABLE_SHARED') and 64 not sys.platform.startswith('win')): 65 runshared = sysconfig.get_config_var('RUNSHARED') 66 if runshared is None: 67 cmd.library_dirs = ['.'] 68 else: 69 name, equals, value = runshared.partition('=') 70 cmd.library_dirs = value.split(os.pathsep) 71 while(True): 72 try: 73 cmd.library_dirs.remove('') 74 except ValueError: 75 break
実行してみます。
$ ~/.pythonbrew/pythons/Python-2.7.2/bin/python test_distutils.py ... test_get_outputs (distutils.tests.test_build_ext.BuildExtTestCase) ... ok ... ---------------------------------------------------------------------- Ran 154 tests in 4.827s OK (skipped=6)
おお、成功しました!
もしかするとパッチが出ているかもしれませんが、見つからなかったので作りました。
3.2.2でも同じ事象が起こっていたので、ついでに作りました。
2.7.2版 https://raw.github.com/toggtc/python-patch/master/2.7.2/distutils_test_fixup_command_2.7.2.patch
3.2.2版 https://raw.github.com/toggtc/python-patch/master/3.2.2/distutils_test_fixup_command_3.2.2.patch
公式なパッチがあればご連絡をお願いします。
2012/2/3 追記:
→Ned氏にパッチを作成して頂きました。http://bugs.python.org/issue13901
ただし、↓のパッチは各ブランチの最新に対して当てたもので、現在リリースされている2.7.2には適用できません。2.7.2にパッチを当てたい場合は、↑のgithubにあげたパッチをお使いください。
New changeset 41cabdff2686 by Ned Deily in branch '2.7':
Issue #13901: Prevent test_distutils failures on OS X with --enable-shared.
http://hg.python.org/cpython/rev/41cabdff2686New changeset 6f6100a752ba by Ned Deily in branch '3.2':
Issue #13901: Prevent test_distutils failures on OS X with --enable-shared.
http://hg.python.org/cpython/rev/6f6100a752baNew changeset 84be86af9161 by Ned Deily in branch 'default':
Issue #13901: Prevent test_packaging failures on OS X with --enable-shared.
http://hg.python.org/cpython/rev/84be86af9161
ところで、DYLD_LIBRARY_PATH=`pwd`:となっていた理由ですが、DYLD_LIBRARY_PATH環境変数を設定した記憶が無かったので、調べてみたらconfigure.inファイルに設定がありました。
Darwin*) LDLIBRARY='libpython$(VERSION).dylib' BLDLIBRARY='-L. -lpython$(VERSION)' RUNSHARED='DYLD_LIBRARY_PATH=`pwd`:${DYLD_LIBRARY_PATH}'
環境変数にDYLD_LIBRARY_PATHを設定していないので、`pwd`:が設定されてしまっていたんですね。こっちも修正するべきか、、
test_platform
あと2つ!次はtest_platformです。
test_platform.pyを実行したところ、以下のエラーが出ました。
$ ./pythons/Python-2.7.2/bin/python ./build/Python-2.7.2/Lib/test/test_platform.py ====================================================================== FAIL: test_mac_ver (__main__.PlatformTest) ---------------------------------------------------------------------- Traceback (most recent call last): File "test_platform.py", line 195, in test_mac_ver self.assertEqual(res[2], 'i386') AssertionError: 'x86_64' != 'i386' ---------------------------------------------------------------------- Ran 20 tests in 0.325s FAILED (failures=1, skipped=1) Traceback (most recent call last): File "test_platform.py", line 255, in <module> test_main() File "test_platform.py", line 251, in test_main PlatformTest File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/test/test_support.py", line 1087, in run_unittest _run_suite(suite) File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/test/test_support.py", line 1070, in _run_suite raise TestFailed(err) test.test_support.TestFailed: Traceback (most recent call last): File "test_platform.py", line 194, in test_mac_ver self.assertEqual(res[2], 'i386') AssertionError: 'x86_64' != 'i386'
予想はつきますが、ソースを確認します。test_platform.py:195ですね。
159 def test_mac_ver(self): 160 res = platform.mac_ver() ... 193 if sys.byteorder == 'little': 194 self.assertEqual(res[2], 'i386') 195 else: 196 self.assertEqual(res[2], 'PowerPC')
platform.mac_ver()という関数が返す値がi386ではないので、assertに引っかかっています。
mac標準のpython 2.7.1でplatform.mac_ver()を実行すると、x86_64という文字列が取得されます。`uname -m`でも同様にx86_64が返ってきます。
$ python Python 2.7.1 (r271:86832, Jul 31 2011, 19:30:53) [GCC 4.2.1 (Based on Apple Inc. build 5658) (LLVM build 2335.15.00)] on darwin Type "help", "copyright", "credits" or "license" for more information. >>> import platform >>> platform.mac_ver() ('10.7.2', ('', '', ''), 'x86_64')
この件は、パッチが投稿されているみたいです。
http://mail.python.org/pipermail//python-checkins/2011-July/106590.html
というわけで、194行目を以下に置き換えてテストを再実行。
194 self.assertIn(res[2], ('i386', 'x86_64'))
---------------------------------------------------------------------- Ran 20 tests in 0.273s OK (skipped=1)
通りました!
test_whichdb
test_whichdbは、test_anydbmのときにwhichdb.pyにあてたパッチで解決していました。