16 # Work out tar/Python bug.
17 # See http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=532570
18 # and http://bugs.python.org/issue1652
19 signal
.signal(signal
.SIGPIPE
, signal
.SIG_DFL
)
23 "python": "http://www.python.org/ftp/python/2.6.1/Python-2.6.1.tar.bz2",
27 ("http://nativeclient.googlecode.com/svn/trunk/src/third_party@1108",
29 ("http://nativeclient.googlecode.com/svn/trunk/src/native_client/src/third_party/npapi@1424",
30 "native_client/src/third_party/npapi"),
34 def stamp_file(filename
):
35 fh
= open(filename
, "w")
39 def is_elf_file(filename
):
43 return header
== "\x7fELF"
46 def assert_equals(x
, y
):
48 if type(x
) == str and type(y
) == str:
49 raise AssertionError("ACTUAL:\n%s\nEXPECTED:\n%s" % (x
, y
))
50 raise AssertionError("%r != %r" % (x
, y
))
53 # This is an alternative to subprocess.PIPE, which can cause deadlocks
54 # if the pipe buffer gets filled up.
56 # Make read/write file handle pair. This is like creating a pipe
57 # FD pair, but without a pipe buffer limit.
58 fd
, filename
= tempfile
.mkstemp(prefix
="nacl_test_")
60 write_fh
= os
.fdopen(fd
, "w", 0)
61 read_fh
= open(filename
, "r")
64 return write_fh
, read_fh
67 # Workaround for Python's variable binding semantics.
68 def make_action(func
, *args
):
69 return lambda log
: func(*args
)
72 class NaClBuild(object):
74 def __init__(self
, env
, dir_path
):
76 self
._dir
_path
= dir_path
77 self
._glibc
_src
= "glibc"
78 # This is the directory where scons looks for the toolchain by
80 self
._sdkloc
= os
.path
.join(
81 self
._dir
_path
, "native_client/src/third_party/nacl_sdk/linux/sdk")
83 def add_path(subdirs
):
85 [os
.path
.join(self
._dir
_path
, subdir
) for subdir
in subdirs
] +
87 return ["env", "PATH=%s" % path
]
89 subdirs
= ["native_client/scons-out/dbg-linux-x86-32/staging",
90 os
.path
.join(self
._sdkloc
, "nacl-sdk/bin"),
92 self
._path
_env
= cmd_env
.PrefixCmdEnv(add_path(subdirs
), env
)
93 self
._gcc
_override
_env
= cmd_env
.PrefixCmdEnv(
94 add_path(subdirs
+ ["override-bin"]), env
)
95 self
.path_env
= self
._path
_env
97 def checkout_thirdparty(self
, log
):
98 for url
, dest
in svn_repos
:
99 self
._env
.cmd(["svn", "checkout", url
, dest
])
101 def svn_checkout(self
, log
):
102 self
._env
.cmd(["python", "mini_gclient.py"])
104 def _git_fetch_repo(self
, dir_path
, url
):
105 # This is like "git clone --no-checkout", but it is more incremental.
106 git_dir
= os
.path
.join(dir_path
, ".git")
107 if not os
.path
.exists(git_dir
):
108 self
._env
.cmd(["mkdir", "-p", dir_path
])
109 self
._env
.cmd(cmd_env
.in_dir(dir_path
) +
111 self
._env
.cmd(cmd_env
.in_dir(dir_path
) +
112 ["git", "remote", "add", "origin", url
])
113 refspec
= "+refs/heads/*:refs/remotes/origin/*"
114 self
._env
.cmd(cmd_env
.in_dir(dir_path
) +
115 ["git", "fetch", url
, refspec
])
117 def _git_checkout_repo(self
, dir_path
, commit
):
118 # This will leave HEAD set to a commit ID, not a branch.
119 # Be careful. One could lose commits that are not saved
121 self
._env
.cmd(cmd_env
.in_dir(dir_path
) +
122 ["git", "checkout", commit
])
124 @action_tree.action_node
126 for mod
in deps_update
.get_modules():
127 yield mod
["name"], make_action(
128 self
._git
_fetch
_repo
, mod
["path"], mod
["url"])
130 @action_tree.action_node
131 def git_checkout(self
):
132 for mod
in deps_update
.get_modules():
133 yield mod
["name"], make_action(
134 self
._git
_checkout
_repo
, mod
["path"], mod
["commit"])
136 def make_stamps(self
, log
):
137 # Stops tools/Makefile deleting our source and Git repos.
138 assert os
.path
.exists("native_client/tools/BUILD/binutils-2.20")
139 stamp_file("native_client/tools/BUILD/stamp-binutils-2.20")
141 def fetch_sqlite(self
, log
):
142 self
._env
.cmd(["mkdir", "-p", "debs"])
143 url
= "http://archive.ubuntu.com/ubuntu/pool/main/s/sqlite3/sqlite3_3.6.10-1.dsc"
144 # Disable signature checking because it doesn't work under
145 # karmic where the public key is apparently not available.
146 # Our SVN downloads are not secure so this is not a big deal.
147 # We'd really want to include a hash here anyway, not a public
149 # TODO: do a secure download.
150 self
._env
.cmd(cmd_env
.in_dir("debs") +
151 ["dget", "--allow-unauthenticated", "-x", url
])
153 def fetch_python(self
, log
):
154 self
._env
.cmd(["wget", "-c", urls
["python"],
155 "-O", posixpath
.basename(urls
["python"])])
157 def unpack_python(self
, log
):
158 self
._env
.cmd(["tar", "-xjf", posixpath
.basename(urls
["python"])])
160 @action_tree.action_node
162 return [self
.checkout_thirdparty
,
171 def build_toolchain(self
, log
):
172 self
._env
.cmd(cmd_env
.in_dir("native_client/tools")
173 + ["make", "SDKLOC=%s" % self
._sdkloc
])
175 def build_nacl(self
, log
):
176 self
._env
.cmd(cmd_env
.in_dir("native_client")
177 + ["./scons", "--mode=dbg-linux"])
179 def test_nacl(self
, log
):
180 # TODO: use --mode=dbg-linux,nacl
182 cmd_env
.in_dir("native_client")
183 + ["./scons", "--mode=dbg-linux", "run_all_tests"])
185 def configure_glibc(self
, log
):
186 self
._path
_env
.cmd(cmd_env
.in_dir(self
._glibc
_src
)
189 def build_glibc(self
, log
):
190 self
._env
.cmd(cmd_env
.in_dir("glibc/build")
193 def test_glibc_static(self
, log
):
194 self
._path
_env
.cmd(cmd_env
.in_dir(self
._glibc
_src
)
195 + ["./make-example.sh"])
197 ["ncval_stubout", "glibc/hellow"])
198 write_fh
, read_fh
= make_fh_pair()
199 proc
= self
._path
_env
.cmd(["sel_ldr", "-d", "glibc/hellow"],
201 assert_equals(read_fh
.read(),
202 "Hello (via write())\n"
203 "Hello world (via printf())\n")
205 def _run_dynamic(self
, args
, **kwargs
):
206 return self
._path
_env
.cmd(
208 "-d", "install-stubout/lib/ld-linux.so.2",
209 "--", "--library-path", "install-stubout/lib"]
212 def test_glibc_dynamic(self
, log
):
213 self
._path
_env
.cmd(cmd_env
.in_dir(self
._glibc
_src
)
214 + ["./make-example-dynamic.sh"])
215 write_fh
, read_fh
= make_fh_pair()
216 proc
= self
._run
_dynamic
(["glibc/hellow-dyn"], stdout
=write_fh
)
217 assert_equals(read_fh
.read(),
218 "Hello (via write())\n"
219 "Hello world (via printf())\n")
221 def test_running_ldso(self
, log
):
222 # TODO: expect a non-zero exit code.
223 write_fh
, read_fh
= make_fh_pair()
225 self
._run
_dynamic
([], stderr
=write_fh
)
226 except cmd_env
.CommandFailedError
, exn
:
227 assert_equals(exn
.rc
, 127)
229 raise AssertionError("Expected non-zero exit code")
230 output
= read_fh
.read()
232 # Check for ld.so's help message.
233 assert ("You have invoked `ld.so', the helper program "
234 "for shared library executables." in output
)
236 def install_glibc(self
, log
):
237 self
._env
.cmd(cmd_env
.in_dir("glibc/build")
238 + ["make", "install"])
240 def stub_out(self
, log
):
241 self
._env
.cmd(["rm", "-rf",
243 self
._env
.cmd(["cp", "-a",
248 "ncval_stubout install-stubout/lib/*.so.*"])
250 def build_sqlite(self
, log
):
251 self
._gcc
_override
_env
.cmd(
252 cmd_env
.in_dir("debs/sqlite3-3.6.10")
253 + ["dpkg-buildpackage", "-b", "-us", "-uc", "-rfakeroot"])
255 def configure_python(self
, log
):
256 self
._gcc
_override
_env
.cmd(cmd_env
.in_dir("Python-2.6.1")
257 + ["i386", "./configure"])
259 def build_python(self
, log
):
260 self
._gcc
_override
_env
.cmd(
261 cmd_env
.in_dir("Python-2.6.1") + ["make"])
263 def _get_install_dir(self
, subdir_name
):
264 install_dir
= os
.path
.join(self
._dir
_path
, "install-trees", subdir_name
)
265 self
._env
.cmd(["rm", "-rf", install_dir
])
266 self
._env
.cmd(["mkdir", "-p", install_dir
])
269 def _rewrite_for_nacl(self
, install_dir
):
270 for dir_path
, dirnames
, filenames
in os
.walk(install_dir
):
271 for filename
in filenames
:
272 pathname
= os
.path
.join(dir_path
, filename
)
273 if is_elf_file(pathname
):
274 self
._path
_env
.cmd(["ncrewrite", pathname
])
276 def install_python(self
, log
):
277 install_dir
= self
._get
_install
_dir
("python")
278 self
._gcc
_override
_env
.cmd(
279 cmd_env
.in_dir("Python-2.6.1")
280 + ["make", "install", "DESTDIR=%s" % install_dir
])
281 self
._rewrite
_for
_nacl
(install_dir
)
283 def install_sqlite(self
, log
):
284 install_dir
= self
._get
_install
_dir
("sqlite")
285 self
._env
.cmd(["dpkg-deb", "-x", "debs/libsqlite3-0_3.6.10-1_i386.deb",
287 self
._rewrite
_for
_nacl
(install_dir
)
289 def build_python_extension(self
, log
):
291 ["rm", "-rf", "googleclient/native_client/python_extension/build"])
292 self
._gcc
_override
_env
.cmd(
293 cmd_env
.in_dir("googleclient/native_client/python_extension") +
294 ["python", "setup.py", "install", "--install-lib=../imcplugin"])
296 ["ncrewrite", "googleclient/native_client/imcplugin/nacl.so"])
298 def test_libraries(self
, log
):
299 # Minimal library test. Tests that we can load more libraries
300 # than just libc.so, and that libraries can call via the PLT.
301 gcc
= "nacl-glibc-gcc"
302 self
._path
_env
.cmd([gcc
, "-shared", "-fPIC", "tests/libhello.c",
303 "-o", "tests/libhello.so"])
304 self
._path
_env
.cmd([gcc
, "tests/hello.c", "tests/libhello.so",
305 "-o", "tests/hello"])
306 # Check that the executable runs natively as well as under sel_ldr.
307 subprocess
.check_call(["./tests/hello"])
308 self
._path
_env
.cmd(["ncrewrite", "tests/libhello.so", "tests/hello"])
309 write_fh
, read_fh
= make_fh_pair()
310 self
._run
_dynamic
(["./tests/hello"], stdout
=write_fh
)
311 assert_equals(read_fh
.read(), "Hello world, in libhello\n")
313 def test_dlopen(self
, log
):
314 self
._path
_env
.cmd(["nacl-glibc-gcc", "tests/dlopen.c", "-ldl",
315 "-o", "tests/dlopen"])
316 self
._path
_env
.cmd(["ncrewrite", "tests/dlopen"])
317 write_fh
, read_fh
= make_fh_pair()
318 self
._run
_dynamic
(["./tests/dlopen"], stdout
=write_fh
)
319 assert_equals(read_fh
.read(),
320 "Trying dlopen\nHello world, in libhello\nDone\n")
322 def test_static_dlopen(self
, log
):
324 "nacl-glibc-gcc", "-static",
325 "-Wl,-T,glibc/elf_i386.x",
326 "tests/dlopen.c", "-ldl", "-o", "tests/dlopen-static"])
327 self
._path
_env
.cmd(["ncrewrite", "tests/dlopen-static"])
328 self
._path
_env
.cmd(["ncval_stubout", "tests/dlopen-static"])
329 write_fh
, read_fh
= make_fh_pair()
330 self
._path
_env
.cmd(["sel_ldr", "-d",
331 "-E", "LD_LIBRARY_PATH=install-stubout/lib",
332 "./tests/dlopen-static"], stdout
=write_fh
)
333 # The output comes out in a different order from before
334 # because the executable and library use different copies of
335 # stdio, and thus different buffers for stdout.
336 assert_equals(read_fh
.read(),
337 "Hello world, in libhello\nTrying dlopen\nDone\n")
339 def test_toolchain(self
, log
):
340 self
._path
_env
.cmd(["python", "toolchain_test.py"])
342 def test_gcc_wrapper(self
, log
):
343 self
._path
_env
.cmd(["python", "gccwrapper_test.py"])
345 def test_python(self
, log
):
346 self
._path
_env
.cmd(["ncrewrite", "Python-2.6.1/python"])
347 write_fh
, read_fh
= make_fh_pair()
348 proc
= self
._run
_dynamic
(
349 ["Python-2.6.1/python", "-c", 'print "Hello world!"'],
351 assert_equals(read_fh
.read(), "Hello world!\n")
353 def test_debugger(self
, log
):
354 self
._path
_env
.cmd(["nacl-glibc-gcc", "-g", "tests/faulty.c",
355 "-o", "tests/faulty"])
356 self
._path
_env
.cmd(["ncrewrite", "tests/faulty"])
357 write_fh
, read_fh
= make_fh_pair()
358 self
._path
_env
.cmd(["python", "debugger.py", "tests/faulty"],
360 stdout
= read_fh
.read()
361 expected
= ("Caught signal\n",
362 "tests/faulty.c:5\n",
364 "Signal handler done, exiting\n")
366 for line
in expected
:
367 if line
not in stdout
:
368 raise AssertionError("Text %r not found in output" % line
)
369 print "** Debug output OK"
371 @action_tree.action_node
373 return [self
.build_toolchain
,
375 self
.configure_glibc
,
380 self
.configure_python
,
384 # self.build_python_extension, # TODO: re-enable
387 @action_tree.action_node
389 return [self
.test_gcc_wrapper
,
392 self
.test_glibc_static
,
393 self
.test_running_ldso
,
394 self
.test_glibc_dynamic
,
397 self
.test_static_dlopen
,
401 @action_tree.action_node
403 return [self
.checkout
, self
.build
, self
.test
]
406 class DryRunEnv(object):
408 def cmd(self
, args
, **kwargs
):
413 parser
= optparse
.OptionParser()
414 parser
.add_option("--dry-run", action
="store_true", dest
="dry_run",
415 help="Print commands instead of running them")
416 options
, args
= parser
.parse_args(args
)
420 env
= cmd_env
.BasicEnv()
421 tree
= NaClBuild(env
, os
.getcwd())
422 action_tree
.action_main(tree
.all
, args
)
425 if __name__
== "__main__":