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にあてたパッチで解決していました。
Mac OS X Lion + Xcode 4.2.1環境にLLVM-GCCではないGCC 4.2.1 (Apple build 5666.3) をインストールする
Xcode 4.2からgccがバンドルされなくなりました。ここで言っているgccはllvm-gccではない、legacyでrealでnativeなgccです。
以前のバージョンのXcodeからgccを引っこ抜いてくるかとも思ったのですが、Appleのオープンソースサイトを漁っているとgccが置いてあるではありませんか!
http://opensource.apple.com/release/developer-tools-41/
→gcc-5666.3
環境
Mac OS X 10.7.2
Xcode 4.2.1
インストール
誰かインストールした人はいないかとgcc-5666.3でググると、以下のページが引っかかりました。
http://caiustheory.com/install-gcc-421-apple-build-56663-with-xcode-42
# osx-gcc-installerというのもあるようですが、Xcodeのアンインストールを要求してくるので使わないでおきました。
ビルド
早速ビルド&インストールしてみます。
cd /private/tmp curl -kLO http://opensource.apple.com/tarballs/gcc/gcc-5666.3.tar.gz tar zxvf gcc-5666.3.tar.gz cd gcc-5666.3 mkdir -p build/obj build/dst build/sym sudo gnumake install RC_OS=macos RC_ARCHS='i386 x86_64' TARGETS='i386 x86_64' SRCROOT=`pwd` OBJROOT=`pwd`/build/obj DSTROOT=`pwd`/build/dst SYMROOT=`pwd`/build/sym sudo ditto build/dst /
作業ディレクトリを/private/tmpにしていますが、このディレクトリ内にあるものは次回再起動後に自動で削除されるので、ソースを残しておきたい場合はtar.gzファイルを別のディレクトリに展開するといいでしょう。
gnumakeコマンドを実行している箇所はsudoで実行しておかないと、インストールスクリプトの最後でroot/wheelへの権限変更ができなくなり、手動で権限変更する羽目になります。
DSTROOTはいきなり / (ルート)を指定しても良いのですが、何が配置されるか分からなかったので、一旦build/dstに生成してからルートにコピーするようにしています。
生成されるファイル
↓生成されるファイルです。
./Developer/Documentation/DocSets/com.apple.ADC_Reference_Library.DeveloperTools.docset/Contents/Resources/Documents/documentation/DeveloperTools/gcc-4.2.1/cpp/Argument-Prescan.html (中略) ./Developer/Documentation/DocSets/com.apple.ADC_Reference_Library.DeveloperTools.docset/Contents/Resources/Documents/documentation/DeveloperTools/gcc-4.2.1/gccint/Working-with-declarations.html ./usr/bin/c++-4.2 ./usr/bin/cpp-4.2 ./usr/bin/g++-4.2 ./usr/bin/gcc-4.2 ./usr/bin/gcov-4.2 ./usr/bin/i686-apple-darwin11-cpp-4.2.1 ./usr/bin/i686-apple-darwin11-g++-4.2.1 ./usr/bin/i686-apple-darwin11-gcc-4.2.1 ./usr/include/gcc/darwin/4.2/float.h ./usr/include/gcc/darwin/4.2/ppc_intrinsics.h ./usr/include/gcc/darwin/4.2/stdarg.h ./usr/include/gcc/darwin/4.2/varargs.h ./usr/lib/gcc/i686-apple-darwin11/4.2.1/crt3.o ./usr/lib/gcc/i686-apple-darwin11/4.2.1/include/ammintrin.h ./usr/lib/gcc/i686-apple-darwin11/4.2.1/include/decfloat.h ./usr/lib/gcc/i686-apple-darwin11/4.2.1/include/emmintrin.h ./usr/lib/gcc/i686-apple-darwin11/4.2.1/include/fenv.h ./usr/lib/gcc/i686-apple-darwin11/4.2.1/include/float.h ./usr/lib/gcc/i686-apple-darwin11/4.2.1/include/iso646.h ./usr/lib/gcc/i686-apple-darwin11/4.2.1/include/limits.h ./usr/lib/gcc/i686-apple-darwin11/4.2.1/include/mm3dnow.h ./usr/lib/gcc/i686-apple-darwin11/4.2.1/include/mm_malloc.h ./usr/lib/gcc/i686-apple-darwin11/4.2.1/include/mmintrin.h ./usr/lib/gcc/i686-apple-darwin11/4.2.1/include/nmmintrin.h ./usr/lib/gcc/i686-apple-darwin11/4.2.1/include/omp.h ./usr/lib/gcc/i686-apple-darwin11/4.2.1/include/pmmintrin.h ./usr/lib/gcc/i686-apple-darwin11/4.2.1/include/README ./usr/lib/gcc/i686-apple-darwin11/4.2.1/include/smmintrin.h ./usr/lib/gcc/i686-apple-darwin11/4.2.1/include/stdarg.h ./usr/lib/gcc/i686-apple-darwin11/4.2.1/include/stdbool.h ./usr/lib/gcc/i686-apple-darwin11/4.2.1/include/stddef.h ./usr/lib/gcc/i686-apple-darwin11/4.2.1/include/syslimits.h ./usr/lib/gcc/i686-apple-darwin11/4.2.1/include/tgmath.h ./usr/lib/gcc/i686-apple-darwin11/4.2.1/include/tmmintrin.h ./usr/lib/gcc/i686-apple-darwin11/4.2.1/include/unwind.h ./usr/lib/gcc/i686-apple-darwin11/4.2.1/include/varargs.h ./usr/lib/gcc/i686-apple-darwin11/4.2.1/include/xmmintrin.h ./usr/lib/gcc/i686-apple-darwin11/4.2.1/install-tools/gsyslimits.h ./usr/lib/gcc/i686-apple-darwin11/4.2.1/install-tools/include/ammintrin.h ./usr/lib/gcc/i686-apple-darwin11/4.2.1/install-tools/include/decfloat.h ./usr/lib/gcc/i686-apple-darwin11/4.2.1/install-tools/include/emmintrin.h ./usr/lib/gcc/i686-apple-darwin11/4.2.1/install-tools/include/float.h ./usr/lib/gcc/i686-apple-darwin11/4.2.1/install-tools/include/iso646.h ./usr/lib/gcc/i686-apple-darwin11/4.2.1/install-tools/include/limits.h ./usr/lib/gcc/i686-apple-darwin11/4.2.1/install-tools/include/mm3dnow.h ./usr/lib/gcc/i686-apple-darwin11/4.2.1/install-tools/include/mm_malloc.h ./usr/lib/gcc/i686-apple-darwin11/4.2.1/install-tools/include/mmintrin.h ./usr/lib/gcc/i686-apple-darwin11/4.2.1/install-tools/include/nmmintrin.h ./usr/lib/gcc/i686-apple-darwin11/4.2.1/install-tools/include/pmmintrin.h ./usr/lib/gcc/i686-apple-darwin11/4.2.1/install-tools/include/README ./usr/lib/gcc/i686-apple-darwin11/4.2.1/install-tools/include/smmintrin.h ./usr/lib/gcc/i686-apple-darwin11/4.2.1/install-tools/include/stdarg.h ./usr/lib/gcc/i686-apple-darwin11/4.2.1/install-tools/include/stdbool.h ./usr/lib/gcc/i686-apple-darwin11/4.2.1/install-tools/include/stddef.h ./usr/lib/gcc/i686-apple-darwin11/4.2.1/install-tools/include/tgmath.h ./usr/lib/gcc/i686-apple-darwin11/4.2.1/install-tools/include/tmmintrin.h ./usr/lib/gcc/i686-apple-darwin11/4.2.1/install-tools/include/unwind.h ./usr/lib/gcc/i686-apple-darwin11/4.2.1/install-tools/include/varargs.h ./usr/lib/gcc/i686-apple-darwin11/4.2.1/install-tools/include/xmmintrin.h ./usr/lib/gcc/i686-apple-darwin11/4.2.1/install-tools/macro_list ./usr/lib/gcc/i686-apple-darwin11/4.2.1/install-tools/mkheaders.conf ./usr/lib/gcc/i686-apple-darwin11/4.2.1/libcc_kext.a ./usr/lib/gcc/i686-apple-darwin11/4.2.1/libgcc.a ./usr/lib/gcc/i686-apple-darwin11/4.2.1/libgcc_eh.a ./usr/lib/gcc/i686-apple-darwin11/4.2.1/libgcc_static.a ./usr/lib/gcc/i686-apple-darwin11/4.2.1/libgcov.a ./usr/lib/gcc/i686-apple-darwin11/4.2.1/libgomp.a ./usr/lib/gcc/i686-apple-darwin11/4.2.1/libgomp.spec ./usr/lib/gcc/i686-apple-darwin11/4.2.1/libstdc++.dylib ./usr/lib/gcc/i686-apple-darwin11/4.2.1/x86_64/crt3.o ./usr/lib/gcc/i686-apple-darwin11/4.2.1/x86_64/libgcc.a ./usr/lib/gcc/i686-apple-darwin11/4.2.1/x86_64/libgcc_eh.a ./usr/lib/gcc/i686-apple-darwin11/4.2.1/x86_64/libgcov.a ./usr/lib/gcc/i686-apple-darwin11/4.2.1/x86_64/libgomp.a ./usr/lib/gcc/i686-apple-darwin11/4.2.1/x86_64/libgomp.spec ./usr/libexec/gcc/i686-apple-darwin11/4.2.1/cc1 ./usr/libexec/gcc/i686-apple-darwin11/4.2.1/cc1obj ./usr/libexec/gcc/i686-apple-darwin11/4.2.1/cc1objplus ./usr/libexec/gcc/i686-apple-darwin11/4.2.1/cc1plus ./usr/libexec/gcc/i686-apple-darwin11/4.2.1/collect2 ./usr/libexec/gcc/i686-apple-darwin11/4.2.1/install-tools/fixinc.sh ./usr/libexec/gcc/i686-apple-darwin11/4.2.1/install-tools/fixincl ./usr/libexec/gcc/i686-apple-darwin11/4.2.1/install-tools/mkheaders ./usr/share/man/man1/c++-4.2.1 ./usr/share/man/man1/cpp-4.2.1 ./usr/share/man/man1/g++-4.2.1 ./usr/share/man/man1/gcc-4.2.1 ./usr/share/man/man1/gcov-4.2.1 ./usr/share/man/man1/i686-apple-darwin11-cpp-4.2.1.1 ./usr/share/man/man1/i686-apple-darwin11-g++-4.2.1.1 ./usr/share/man/man1/i686-apple-darwin11-gcc-4.2.1.1
Formulaを作ってhomebrewで管理しようとも思ったのですが、Appleビルドなので/usr直下にインストールされても気にしない(・ε・)
確認
$ /usr/bin/gcc-4.2 --version i686-apple-darwin11-gcc-4.2.1 (GCC) 4.2.1 (Apple Inc. build 5666) (dot 3) Copyright (C) 2007 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
llvmじゃないので安心して眠れます。
はてなブログ (新ブログ) にGoogle Analyticsを設定する 設定は一度でOK
※2012/4/6追記 本記事の設定方法は非推奨です。
2012/4/5から、はてなブログの管理ページにてGoogle Analyticsの設定が行えるようになりました。
「管理ページ」→「設定」→「詳細設定」の箇所に、Google AnalyticsのトラックIDを設定する項目があります。
※2012/02/01 本記事の画像にサイトURLの末尾に/が含まれていたため、修正しました。既にスラッシュ付きで適用されてしまった方は、記事下部にある「注意: 登録するウェブサイトのURL末尾に/(スラッシュ)を付けてはいけない」をご参照のうえ、修正してください。設定は一度でOKとか言っておいて、なんだかとっても恐縮です。
新ブログ(以下、はてなブログ)を使い始めました。このはてなブログにはアクセス解析の機能が最初から備わっています。
Google Analyticsとは違い、使い勝手がとてもシンプルです。
しかし、シンプルゆえにアクセス頻度の高いユーザ環境がMacなのかMacなのか、それともMacなのかが分かりません。残念でなりません。
そこで、はてなブログにGoogle Analyticsを設定してみます。
やり方は、トラッキング用のJavaScript(トラッキングコード)を編集して、サイドバーに埋め込むだけ、です。サイドバーに埋め込むので、一度設定すればOKです。
注意点がいくつかあります。
- サイドバーに埋め込むので<head></head>のときとは処理されるタイミングが異なります。ある閲覧者がページを見に来たけど、ページ全体が処理される前に離脱してしまうとトラッキングされない可能性があります。
- サイドバーに埋めこむので、スマートフォン用の表示ページでサイドバーが表示されない場合、トラッキングされないかもしれません。
- はてなブログの仕様変更等により、将来的に今回の方法は使えなくなる可能性があります。(2012年1月現在有効)
headタグ内にトラッキングコードが設定できればすべて解決なのですが、その方法は、きっと、たぶん、もしかすると、はてなさんが提供してくれます。_setAccountと_trackPageviewのコマンドを実行する2行のコードを生成するだけなんだからっ
1. Google Analyticsのアカウント・トラッキングコードを設定する
※事前にGoogleアカウントを取得しておく必要があります。
1.1 Google Analytics アカウントを作成する
(1) 以下のページにアクセスします。
(2-A) Google Analyticsをはじめて使う場合:
Google Analyticsをはじめて使う場合は、最初のページが新規アカウントの作成ページになっていると思います。
お申し込みボタンを押します。
(2-B) 既にGoogle Analyticsに既存のアカウント(プロファイル)がある場合:
新しいデザインのGoogle Analyticsだと操作が分かりづらいのですが、右上の設定ボタン(ネジマーク)をクリックして辿っていくと、新規プロファイルの画面に辿り着きます。
(3) アカウントの作成
アカウントの作成画面が出たら、下図のように必要事項を入力します。
項目 | 設定内容 |
---|---|
アカウント名 | 任意の名前 |
ウェブサイトのURL | はてなブログのURLで、「http://」を除いた部分を入力します。末尾に/は付けません。(※1) |
タイムゾーン | お住まいの国・地域を選択 |
データ共有 | Googleとデータを共有する場合は「共有する」を選択します。(※2) |
ユーザ契約 | お住まいの国・地域を選択 |
(※1: 末尾に/をつけると、サイトコンテンツに表示されたショートカットがうまく機能しません。)
(※2: 詳しくはここを参照:http://support.google.com/googleanalytics/bin/answer.py?hl=ja&answer=87515)
「同意する」ボタンを押せば、アカウントが作成されます。
1.2 トラッキングコードの設定
アカウントを作成したら、トラッキングコードの設定をします。
トラッキングコードの設定画面で、「標準」タブではなく「カスタム」タブを選択してください。
(※トラッキングIDを隠してもしょうがないのですが、伏せておきますね)
上図のように、
- _setAccountと_trackPageviewの前に「t2.」を追加する
- 枠で囲った部分を削除する
と、以下のようなコードになるはずです。UA-********-*の部分は、自動的に払い出されるIDになっているので変更しないでください。
<script type="text/javascript">
var _gaq = _gaq || [];
_gaq.push(['t2._setAccount', 'UA-********-*']);
_gaq.push(['t2._trackPageview']);
</script>
「t2」という部分は任意の文字で構いません。単に2つ目のトラッキングという意味です。
2. はてなブログにトラッキングコードを設置する
Google Analyticsの設定が終わったら、次は、はてなブログ側の設定を行います。
2.1 トラッキングコードの埋め込み
さて、はてなダイアリーではトラッキングIDを設定すればGoogle Analyticsと連携できましたが、はてなブログには連携機能がありません。
そこで1.2 のトラッキングコードをブログページに埋め込むことになります。
(1) まず、はてなブログの「管理」画面を開きます。
(2) 左側に表示されている管理メニューから「デザイン」を選択し、デザイン管理画面を開きます。
(3) カスタマイズタブを選択し、「モジュールの追加」をクリックします。
(4) さきほどのトラッキングコードをペーストして、保存します。
(5)「デザインを変更する」ボタンを押せば、変更が反映されます。
2.2トラッキングコードが埋め込まれたかの確認
トラッキングコードを埋め込んだ、はてなブログのページを開いて、ブラウザからページのソースを表示します。
さきほどのトラッキングコードが埋め込まれていれば成功です。
トラッキングのデータ反映には時間が掛かります。すぐに確認したい場合は、Google Analyticsにリアルタイム機能が追加されているので、そちらで確認してみてください。
参考:http://analytics-ja.blogspot.com/2011/09/blog-post.html
1日経っても反映されていないようであれば、設定が間違っていないか確認しましょう。特にトラッキングコードをコピペするときに変な文字を挿入してしまったとか、別なトラッキングコードを挿入してしまったとか、トラッキングID(UA-********-*)を編集してしまった、なんていうことが無いかチェックしてみてください。
なお、この方法は2011年1月現在で有効な方法です。
はてなブログの仕様が変わると使えなくなる可能性があります。
注意: 登録するウェブサイトのURL末尾に/(スラッシュ)を付けてはいけない
(2012/02/01追記しました)
1.1 (3)のアカウント設定で少し触れましたが、URLの末尾にスラッシュを付けると、サイトコンテンツのレポートを表示するときのリンクがおかしくなります。
登録するURLの末尾にスラッシュを付けるとどうなるか
スラッシュを末尾に付けてしまったときの悲劇をお見せいたします。
サイトコンテンツは、プロファイルのトップ画面から「標準レポート」をクリックし、
左側のサイドバーから「コンテンツ」→「サイトコンテンツ」→「ページ」と辿って行きます。
表示された画面の下部にコンテンツのURLがあり、その右側にリンクのアイコンがあります。
このアイコンをクリックすると、リンク先のページが開くはずなのですが、、、
ご覧のとおり、URLがおかしくなってしまい、正しいページが表示されません。
hatenablog.com/ のあとに、 /entry/2012/01/29/014540 をそのままくっつけているので、hatenablog.com//entry/2012/01/29/014540となって、余計なスラッシュが入った状態になっている、ということです。
既にスラッシュ付きで登録してしまった場合の修正方法
プロファイル編集からURLを変更すればOKです。なお、スラッシュ付きのままでも解析は行われます。また、末尾のスラッシュを削除するだけなら、解析結果に影響はありませんでした。(以前の解析結果はそのまま保持される)
手順です。
まず、ネジマークをクリック。
「プロファイル」→「プロファイル設定」と辿り、URLの末尾から/を削除して適用します。
おまけ
はてなブログでは、アクセス解析の機能が最初から備わっていました。この機能はどうやらGoogle Analyticsを使って実現しているようです。現に、はてなブログのページのHTMLコードを見ると、</head>の前に以下のGoogle Analyticsが提供するデフォルトのトラッキングコードが仕込まれています。
<script type="text/javascript"> var _gaq = _gaq || []; _gaq.push(['_setAccount', 'UA-441387-54']); _gaq.push(['_setDomainName', 'hatena.ne.jp']); _gaq.push(['_trackPageview', '/b/toggtc.hatenablog.com/']); (function() { var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true; ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js'; var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s); })(); </script>
上記のコード中にある下記の部分で、ga.jsをロードするscriptタグを追加しています。
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true; ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
はてな側で埋め込んだデフォルトのトラッキングコードと、自分自身で埋め込んだデフォルトのトラッキングコードが同一ページ上で存在すると、ga.jsをロードするコードが重複することになります。
ga.jsが二重インクルードを防止するようになっていれば話は別ですが、そうでない場合、うまく動作しなくなる可能性があります。
そこで今回は、function() { .. } の部分をごっそり削除しました。もし、ga.jsが二重インクルードに対応していれば、削除する必要はないでしょう。
はてな側で生成しているHTMLにてga.jsが既にロードされているので、後は自分用のトラッキングビーコンを送信するコードを埋め込むだけです。Google Analyticsにアクセスするために、_gaqというグローバルオブジェクトを使います。
var _gaq = _gaq || []; _gaq.push(['<span style="color: #cc0000">t2</span>._setAccount', 'UA-********-*']); _gaq.push(['<span style="color: #cc0000">t2</span>._trackPageview']);
ということでした。