rizsotto.mailinglist updated this revision to Diff 39703.
rizsotto.mailinglist added a comment.
findings from compare from older Perl implementation
http://reviews.llvm.org/D9600
Files:
tools/scan-build-py/README.md
tools/scan-build-py/libear/CMakeLists.txt
tools/scan-build-py/libear/__init__.py
tools/scan-build-py/libear/config.h.in
tools/scan-build-py/libear/ear.c
tools/scan-build-py/libscanbuild/clang.py
tools/scan-build-py/libscanbuild/command.py
tools/scan-build-py/libscanbuild/driver.py
tools/scan-build-py/libscanbuild/intercept.py
tools/scan-build-py/libscanbuild/interposition.py
tools/scan-build-py/libscanbuild/options.py
tools/scan-build-py/libscanbuild/report.py
tools/scan-build-py/libscanbuild/runner.py
tools/scan-build-py/libscanbuild/shell.py
tools/scan-build-py/setup.py
tools/scan-build-py/tests/functional/cases/__init__.py
tools/scan-build-py/tests/functional/cases/test_from_cdb.py
tools/scan-build-py/tests/functional/cases/test_from_cmd.py
tools/scan-build-py/tests/functional/exec/main.c
tools/scan-build-py/tests/functional/src/build/Makefile
tools/scan-build-py/tests/unit/__init__.py
tools/scan-build-py/tests/unit/test_clang.py
tools/scan-build-py/tests/unit/test_command.py
tools/scan-build-py/tests/unit/test_intercept.py
tools/scan-build-py/tests/unit/test_runner.py
tools/scan-build-py/tests/unit/test_shell.py
Index: tools/scan-build-py/tests/unit/test_shell.py
===================================================================
--- /dev/null
+++ tools/scan-build-py/tests/unit/test_shell.py
@@ -0,0 +1,42 @@
+# -*- coding: utf-8 -*-
+# The LLVM Compiler Infrastructure
+#
+# This file is distributed under the University of Illinois Open Source
+# License. See LICENSE.TXT for details.
+
+import libscanbuild.shell as sut
+import unittest
+
+
+class ShellTest(unittest.TestCase):
+
+ def test_encode_decode_are_same(self):
+ def test(value):
+ self.assertEqual(sut.encode(sut.decode(value)), value)
+
+ test("")
+ test("clang")
+ test("clang this and that")
+
+ def test_decode_encode_are_same(self):
+ def test(value):
+ self.assertEqual(sut.decode(sut.encode(value)), value)
+
+ test([])
+ test(['clang'])
+ test(['clang', 'this', 'and', 'that'])
+ test(['clang', 'this and', 'that'])
+ test(['clang', "it's me", 'again'])
+ test(['clang', 'some "words" are', 'quoted'])
+
+ def test_encode(self):
+ self.assertEqual(sut.encode(['clang', "it's me", 'again']),
+ 'clang "it\'s me" again')
+ self.assertEqual(sut.encode(['clang', "it(s me", 'again)']),
+ 'clang "it(s me" "again)"')
+ self.assertEqual(sut.encode(['clang', 'redirect > it']),
+ 'clang "redirect > it"')
+ self.assertEqual(sut.encode(['clang', '-DKEY="VALUE"']),
+ 'clang -DKEY=\\"VALUE\\"')
+ self.assertEqual(sut.encode(['clang', '-DKEY="value with spaces"']),
+ 'clang -DKEY=\\"value with spaces\\"')
Index: tools/scan-build-py/tests/unit/test_runner.py
===================================================================
--- tools/scan-build-py/tests/unit/test_runner.py
+++ tools/scan-build-py/tests/unit/test_runner.py
@@ -141,18 +141,19 @@
def test(expected, input):
spy = fixtures.Spy()
self.assertEqual(spy.success, sut.language_check(input, spy.call))
- self.assertEqual(expected, spy.arg)
+ self.assertEqual(expected, spy.arg['language'])
l = 'language'
f = 'file'
- i = 'cxx'
- test({f: 'file.c', l: 'c'}, {f: 'file.c', l: 'c'})
- test({f: 'file.c', l: 'c++'}, {f: 'file.c', l: 'c++'})
- test({f: 'file.c', l: 'c++', i: True}, {f: 'file.c', i: True})
- test({f: 'file.c', l: 'c'}, {f: 'file.c'})
- test({f: 'file.cxx', l: 'c++'}, {f: 'file.cxx'})
- test({f: 'file.i', l: 'c-cpp-output'}, {f: 'file.i'})
- test({f: 'f.i', l: 'c-cpp-output'}, {f: 'f.i', l: 'c-cpp-output'})
+ i = 'c++'
+ test('c', {f: 'file.c', l: 'c', i: False})
+ test('c++', {f: 'file.c', l: 'c++', i: False})
+ test('c++', {f: 'file.c', i: True})
+ test('c', {f: 'file.c', i: False})
+ test('c++', {f: 'file.cxx', i: False})
+ test('c-cpp-output', {f: 'file.i', i: False})
+ test('c++-cpp-output', {f: 'file.i', i: True})
+ test('c-cpp-output', {f: 'f.i', l: 'c-cpp-output', i: True})
def test_arch_loop(self):
def test(input):
@@ -163,16 +164,16 @@
input = {'key': 'value'}
self.assertEqual(input, test(input))
- input = {'archs_seen': ['-arch', 'i386']}
+ input = {'archs_seen': ['i386']}
self.assertEqual({'arch': 'i386'}, test(input))
- input = {'archs_seen': ['-arch', 'ppc']}
+ input = {'archs_seen': ['ppc']}
self.assertEqual(None, test(input))
- input = {'archs_seen': ['-arch', 'i386', '-arch', 'ppc']}
+ input = {'archs_seen': ['i386', 'ppc']}
self.assertEqual({'arch': 'i386'}, test(input))
- input = {'archs_seen': ['-arch', 'i386', '-arch', 'sparc']}
+ input = {'archs_seen': ['i386', 'sparc']}
result = test(input)
self.assertTrue(result == {'arch': 'i386'} or
result == {'arch': 'sparc'})
Index: tools/scan-build-py/tests/unit/test_intercept.py
===================================================================
--- tools/scan-build-py/tests/unit/test_intercept.py
+++ tools/scan-build-py/tests/unit/test_intercept.py
@@ -13,7 +13,7 @@
def test_compiler_call_filter(self):
def test(command):
- return sut.is_compiler_call({'command': [command]})
+ return sut.compiler_call({'command': [command]})
self.assertTrue(test('clang'))
self.assertTrue(test('clang-3.6'))
Index: tools/scan-build-py/tests/unit/test_command.py
===================================================================
--- tools/scan-build-py/tests/unit/test_command.py
+++ tools/scan-build-py/tests/unit/test_command.py
@@ -16,9 +16,6 @@
opts = sut.classify_parameters(cmd)
self.assertEqual(expected, opts['action'])
- Info = sut.Action.Info
- test(Info, ['clang', 'source.c', '-print-prog-name'])
-
Link = sut.Action.Link
test(Link, ['clang', 'source.c'])
@@ -26,7 +23,7 @@
test(Compile, ['clang', '-c', 'source.c'])
test(Compile, ['clang', '-c', 'source.c', '-MF', 'source.d'])
- Preprocess = sut.Action.Preprocess
+ Preprocess = sut.Action.Ignored
test(Preprocess, ['clang', '-E', 'source.c'])
test(Preprocess, ['clang', '-c', '-E', 'source.c'])
test(Preprocess, ['clang', '-c', '-M', 'source.c'])
@@ -37,9 +34,9 @@
opts = sut.classify_parameters(cmd)
return opts.get('compile_options', [])
- self.assertEqual(['-O1'], test(['clang', '-c', 'source.c', '-O']))
+ self.assertEqual(['-O'], test(['clang', '-c', 'source.c', '-O']))
self.assertEqual(['-O1'], test(['clang', '-c', 'source.c', '-O1']))
- self.assertEqual(['-O2'], test(['clang', '-c', 'source.c', '-Os']))
+ self.assertEqual(['-Os'], test(['clang', '-c', 'source.c', '-Os']))
self.assertEqual(['-O2'], test(['clang', '-c', 'source.c', '-O2']))
self.assertEqual(['-O3'], test(['clang', '-c', 'source.c', '-O3']))
@@ -52,6 +49,15 @@
self.assertEqual('c', test(['clang', '-c', 'source.c', '-x', 'c']))
self.assertEqual('cpp', test(['clang', '-c', 'source.c', '-x', 'cpp']))
+ def test_output(self):
+ def test(cmd):
+ opts = sut.classify_parameters(cmd)
+ return opts.get('output')
+
+ self.assertEqual(None, test(['clang', '-c', 'source.c']))
+ self.assertEqual('source.o',
+ test(['clang', '-c', '-o', 'source.o', 'source.c']))
+
def test_arch(self):
def test(cmd):
opts = sut.classify_parameters(cmd)
@@ -60,9 +66,9 @@
eq = self.assertEqual
eq([], test(['clang', '-c', 'source.c']))
- eq(['-arch', 'mips'],
+ eq(['mips'],
test(['clang', '-c', 'source.c', '-arch', 'mips']))
- eq(['-arch', 'mips', '-arch', 'i386'],
+ eq(['mips', 'i386'],
test(['clang', '-c', 'source.c', '-arch', 'mips', '-arch', 'i386']))
def test_input_file(self):
@@ -76,17 +82,6 @@
eq(['src.c'], test(['clang', '-c', 'src.c']))
eq(['s1.c', 's2.c'], test(['clang', '-c', 's1.c', 's2.c']))
- def test_output_file(self):
- def test(cmd):
- opts = sut.classify_parameters(cmd)
- return opts.get('output', None)
-
- eq = self.assertEqual
-
- eq(None, test(['clang', 'src.c']))
- eq('src.o', test(['clang', '-c', 'src.c', '-o', 'src.o']))
- eq('src.o', test(['clang', '-c', '-o', 'src.o', 'src.c']))
-
def test_include(self):
def test(cmd):
opts = sut.classify_parameters(cmd)
@@ -130,20 +125,16 @@
test(['clang', '-c', 'src.c', '-Dvar="val ues"']))
def test_ignored_flags(self):
- def test(cmd):
- salt = ['-I.', '-D_THIS']
- opts = sut.classify_parameters(cmd + salt)
- self.assertEqual(salt, opts.get('compile_options'))
- return opts.get('link_options', [])
+ def test(flags):
+ cmd = ['clang', 'src.o']
+ opts = sut.classify_parameters(cmd + flags)
+ self.assertEqual(['src.o'], opts.get('compile_options'))
- eq = self.assertEqual
-
- eq([],
- test(['clang', 'src.o']))
- eq([],
- test(['clang', 'src.o', '-lrt', '-L/opt/company/lib']))
- eq([],
- test(['clang', 'src.o', '-framework', 'foo']))
+ test([])
+ test(['-lrt', '-L/opt/company/lib'])
+ test(['-static'])
+ test(['-Wnoexcept', '-Wall'])
+ test(['-mtune=i386', '-mcpu=i386'])
def test_compile_only_flags(self):
def test(cmd):
@@ -152,17 +143,8 @@
eq = self.assertEqual
- eq([], test(['clang', '-c', 'src.c']))
- eq([],
- test(['clang', '-c', 'src.c', '-Wnoexcept']))
- eq([],
- test(['clang', '-c', 'src.c', '-Wall']))
- eq(['-Wno-cpp'],
- test(['clang', '-c', 'src.c', '-Wno-cpp']))
eq(['-std=C99'],
test(['clang', '-c', 'src.c', '-std=C99']))
- eq(['-mtune=i386', '-mcpu=i386'],
- test(['clang', '-c', 'src.c', '-mtune=i386', '-mcpu=i386']))
eq(['-nostdinc'],
test(['clang', '-c', 'src.c', '-nostdinc']))
eq(['-isystem', '/image/debian'],
@@ -181,8 +163,6 @@
eq = self.assertEqual
- eq([],
- test(['clang', '-c', 'src.c', '-fsyntax-only']))
eq(['-fsinged-char'],
test(['clang', '-c', 'src.c', '-fsinged-char']))
eq(['-fPIC'],
@@ -194,12 +174,14 @@
eq(['-isysroot', '/'],
test(['clang', '-c', 'src.c', '-isysroot', '/']))
eq([],
+ test(['clang', '-c', 'src.c', '-fsyntax-only']))
+ eq([],
test(['clang', '-c', 'src.c', '-sectorder', 'a', 'b', 'c']))
def test_detect_cxx_from_compiler_name(self):
def test(cmd):
opts = sut.classify_parameters(cmd)
- return opts.get('cxx')
+ return opts.get('c++')
eq = self.assertEqual
Index: tools/scan-build-py/tests/unit/test_clang.py
===================================================================
--- tools/scan-build-py/tests/unit/test_clang.py
+++ tools/scan-build-py/tests/unit/test_clang.py
@@ -18,8 +18,8 @@
handle.write('')
result = sut.get_arguments(
- tmpdir,
- ['clang', '-c', filename, '-DNDEBUG', '-Dvar="this is it"'])
+ ['clang', '-c', filename, '-DNDEBUG', '-Dvar="this is it"'],
+ tmpdir)
self.assertIn('NDEBUG', result)
self.assertIn('var="this is it"', result)
@@ -28,5 +28,5 @@
self.assertRaises(
Exception,
sut.get_arguments,
- '.',
- ['clang', '-###', '-fsyntax-only', '-x', 'c', 'notexist.c'])
+ ['clang', '-###', '-fsyntax-only', '-x', 'c', 'notexist.c'],
+ '.')
Index: tools/scan-build-py/tests/unit/__init__.py
===================================================================
--- tools/scan-build-py/tests/unit/__init__.py
+++ tools/scan-build-py/tests/unit/__init__.py
@@ -10,6 +10,7 @@
from . import test_report
from . import test_driver
from . import test_intercept
+from . import test_shell
def load_tests(loader, suite, pattern):
@@ -19,4 +20,5 @@
suite.addTests(loader.loadTestsFromModule(test_report))
suite.addTests(loader.loadTestsFromModule(test_driver))
suite.addTests(loader.loadTestsFromModule(test_intercept))
+ suite.addTests(loader.loadTestsFromModule(test_shell))
return suite
Index: tools/scan-build-py/tests/functional/src/build/Makefile
===================================================================
--- tools/scan-build-py/tests/functional/src/build/Makefile
+++ tools/scan-build-py/tests/functional/src/build/Makefile
@@ -31,3 +31,6 @@
build_all_in_one:
$(CC) -o $(PROGRAM) $(CFLAGS) $(LDFLAGS) $(CLEAN_SRCS:%.c=$(SRCDIR)/%.c)
+
+clean:
+ rm -f $(PROGRAM) $(OBJDIR)/*.o
Index: tools/scan-build-py/tests/functional/exec/main.c
===================================================================
--- tools/scan-build-py/tests/functional/exec/main.c
+++ tools/scan-build-py/tests/functional/exec/main.c
@@ -58,7 +58,7 @@
cwd = NULL;
}
-void expected_out(const char *cmd, const char *file) {
+void expected_out(const char *file) {
if (need_comma)
fprintf(fd, ",\n");
else
@@ -66,7 +66,7 @@
fprintf(fd, "{\n");
fprintf(fd, " \"directory\": \"%s\",\n", cwd);
- fprintf(fd, " \"command\": \"%s -c %s\",\n", cmd, file);
+ fprintf(fd, " \"command\": \"cc -c %s\",\n", file);
fprintf(fd, " \"file\": \"%s/%s\"\n", cwd, file);
fprintf(fd, "}\n");
}
@@ -116,7 +116,7 @@
char *const compiler = "/usr/bin/cc";
char *const argv[] = {"cc", "-c", file, 0};
- expected_out("cc", file);
+ expected_out(file);
create_source(file);
FORK(execv(compiler, argv);)
@@ -130,7 +130,7 @@
char *const argv[] = {compiler, "-c", file, 0};
char *const envp[] = {"THIS=THAT", 0};
- expected_out("/usr/bin/cc", file);
+ expected_out(file);
create_source(file);
FORK(execve(compiler, argv, envp);)
@@ -143,7 +143,7 @@
char *const compiler = "cc";
char *const argv[] = {compiler, "-c", file, 0};
- expected_out(compiler, file);
+ expected_out(file);
create_source(file);
FORK(execvp(compiler, argv);)
@@ -156,7 +156,7 @@
char *const compiler = "cc";
char *const argv[] = {compiler, "-c", file, 0};
- expected_out(compiler, file);
+ expected_out(file);
create_source(file);
FORK(execvP(compiler, _PATH_DEFPATH, argv);)
@@ -170,7 +170,7 @@
char *const argv[] = {"/usr/bin/cc", "-c", file, 0};
char *const envp[] = {"THIS=THAT", 0};
- expected_out("/usr/bin/cc", file);
+ expected_out(file);
create_source(file);
FORK(execvpe(compiler, argv, envp);)
@@ -182,7 +182,7 @@
char *const file = "execl.c";
char *const compiler = "/usr/bin/cc";
- expected_out("cc", file);
+ expected_out(file);
create_source(file);
FORK(execl(compiler, "cc", "-c", file, (char *)0);)
@@ -194,7 +194,7 @@
char *const file = "execlp.c";
char *const compiler = "cc";
- expected_out(compiler, file);
+ expected_out(file);
create_source(file);
FORK(execlp(compiler, compiler, "-c", file, (char *)0);)
@@ -207,7 +207,7 @@
char *const compiler = "/usr/bin/cc";
char *const envp[] = {"THIS=THAT", 0};
- expected_out(compiler, file);
+ expected_out(file);
create_source(file);
FORK(execle(compiler, compiler, "-c", file, (char *)0, envp);)
@@ -220,7 +220,7 @@
char *const compiler = "cc";
char *const argv[] = {compiler, "-c", file, 0};
- expected_out(compiler, file);
+ expected_out(file);
create_source(file);
pid_t child;
@@ -238,7 +238,7 @@
char *const compiler = "cc";
char *const argv[] = {compiler, "-c", file, 0};
- expected_out(compiler, file);
+ expected_out(file);
create_source(file);
pid_t child;
Index: tools/scan-build-py/tests/functional/cases/test_from_cmd.py
===================================================================
--- tools/scan-build-py/tests/functional/cases/test_from_cmd.py
+++ tools/scan-build-py/tests/functional/cases/test_from_cmd.py
@@ -5,7 +5,7 @@
# License. See LICENSE.TXT for details.
from ...unit import fixtures
-from . import make_args, silent_check_call
+from . import make_args, check_call_and_report
import unittest
import os.path
@@ -13,27 +13,25 @@
class OutputDirectoryTest(unittest.TestCase):
@staticmethod
- def run_sb(outdir, args):
- return silent_check_call(
- ['intercept-build', 'all', '-o', outdir] + args)
+ def run_sb(outdir, args, cmd):
+ return check_call_and_report(
+ ['intercept-build', 'all', '-o', outdir] + args,
+ cmd)
def test_regular_keeps_report_dir(self):
with fixtures.TempDir() as tmpdir:
- outdir = os.path.join(tmpdir, 'result')
make = make_args(tmpdir) + ['build_regular']
- self.run_sb(outdir, make)
+ outdir = self.run_sb(tmpdir, [], make)
self.assertTrue(os.path.isdir(outdir))
def test_clear_deletes_report_dir(self):
with fixtures.TempDir() as tmpdir:
- outdir = os.path.join(tmpdir, 'result')
make = make_args(tmpdir) + ['build_clean']
- self.run_sb(outdir, make)
+ outdir = self.run_sb(tmpdir, [], make)
self.assertFalse(os.path.isdir(outdir))
def test_clear_keeps_report_dir_when_asked(self):
with fixtures.TempDir() as tmpdir:
- outdir = os.path.join(tmpdir, 'result')
make = make_args(tmpdir) + ['build_clean']
- self.run_sb(outdir, ['--keep-empty'] + make)
+ outdir = self.run_sb(tmpdir, ['--keep-empty'], make)
self.assertTrue(os.path.isdir(outdir))
Index: tools/scan-build-py/tests/functional/cases/test_from_cdb.py
===================================================================
--- tools/scan-build-py/tests/functional/cases/test_from_cdb.py
+++ tools/scan-build-py/tests/functional/cases/test_from_cdb.py
@@ -5,6 +5,7 @@
# License. See LICENSE.TXT for details.
from ...unit import fixtures
+from . import call_and_report
import unittest
import os.path
@@ -31,82 +32,66 @@
def run_driver(directory, cdb, args):
cmd = ['intercept-build', 'analyze', '--cdb', cdb, '--output', directory] \
+ args
- child = subprocess.Popen(cmd,
- universal_newlines=True,
- stdout=subprocess.PIPE,
- stderr=subprocess.STDOUT)
- output = child.stdout.readlines()
- child.stdout.close()
- child.wait()
- return (child.returncode, output)
+ return call_and_report(cmd, [])
class OutputDirectoryTest(unittest.TestCase):
def test_regular_keeps_report_dir(self):
with fixtures.TempDir() as tmpdir:
cdb = prepare_cdb('regular', tmpdir)
- outdir = os.path.join(tmpdir, 'result')
- exit_code, output = run_driver(outdir, cdb, [])
- self.assertTrue(os.path.isdir(outdir))
+ exit_code, reportdir = run_driver(tmpdir, cdb, [])
+ self.assertTrue(os.path.isdir(reportdir))
def test_clear_deletes_report_dir(self):
with fixtures.TempDir() as tmpdir:
cdb = prepare_cdb('clean', tmpdir)
- outdir = os.path.join(tmpdir, 'result')
- exit_code, output = run_driver(outdir, cdb, [])
- self.assertFalse(os.path.isdir(outdir))
+ exit_code, reportdir = run_driver(tmpdir, cdb, [])
+ self.assertFalse(os.path.isdir(reportdir))
def test_clear_keeps_report_dir_when_asked(self):
with fixtures.TempDir() as tmpdir:
cdb = prepare_cdb('clean', tmpdir)
- outdir = os.path.join(tmpdir, 'result')
- exit_code, output = run_driver(outdir, cdb, ['--keep-empty'])
- self.assertTrue(os.path.isdir(outdir))
+ exit_code, reportdir = run_driver(tmpdir, cdb, ['--keep-empty'])
+ self.assertTrue(os.path.isdir(reportdir))
class ExitCodeTest(unittest.TestCase):
def test_regular_does_not_set_exit_code(self):
with fixtures.TempDir() as tmpdir:
cdb = prepare_cdb('regular', tmpdir)
- outdir = os.path.join(tmpdir, 'result')
- exit_code, output = run_driver(outdir, cdb, [])
+ exit_code, __ = run_driver(tmpdir, cdb, [])
self.assertFalse(exit_code)
def test_clear_does_not_set_exit_code(self):
with fixtures.TempDir() as tmpdir:
cdb = prepare_cdb('clean', tmpdir)
- outdir = os.path.join(tmpdir, 'result')
- exit_code, output = run_driver(outdir, cdb, [])
+ exit_code, __ = run_driver(tmpdir, cdb, [])
self.assertFalse(exit_code)
def test_regular_sets_exit_code_if_asked(self):
with fixtures.TempDir() as tmpdir:
cdb = prepare_cdb('regular', tmpdir)
- outdir = os.path.join(tmpdir, 'result')
- exit_code, output = run_driver(outdir, cdb, ['--status-bugs'])
+ exit_code, __ = run_driver(tmpdir, cdb, ['--status-bugs'])
self.assertTrue(exit_code)
def test_clear_does_not_set_exit_code_if_asked(self):
with fixtures.TempDir() as tmpdir:
cdb = prepare_cdb('clean', tmpdir)
- outdir = os.path.join(tmpdir, 'result')
- exit_code, output = run_driver(outdir, cdb, ['--status-bugs'])
+ exit_code, __ = run_driver(tmpdir, cdb, ['--status-bugs'])
self.assertFalse(exit_code)
def test_regular_sets_exit_code_if_asked_from_plist(self):
with fixtures.TempDir() as tmpdir:
cdb = prepare_cdb('regular', tmpdir)
- outdir = os.path.join(tmpdir, 'result')
- exit_code, output = run_driver(
- outdir, cdb, ['--status-bugs', '--plist'])
+ exit_code, __ = run_driver(
+ tmpdir, cdb, ['--status-bugs', '--plist'])
self.assertTrue(exit_code)
def test_clear_does_not_set_exit_code_if_asked_from_plist(self):
with fixtures.TempDir() as tmpdir:
cdb = prepare_cdb('clean', tmpdir)
- outdir = os.path.join(tmpdir, 'result')
- exit_code, output = run_driver(
- outdir, cdb, ['--status-bugs', '--plist'])
+ exit_code, __ = run_driver(
+ tmpdir, cdb, ['--status-bugs', '--plist'])
self.assertFalse(exit_code)
@@ -122,47 +107,46 @@
def test_default_creates_html_report(self):
with fixtures.TempDir() as tmpdir:
cdb = prepare_cdb('regular', tmpdir)
- outdir = os.path.join(tmpdir, 'result')
- exit_code, output = run_driver(outdir, cdb, [])
- self.assertTrue(os.path.exists(os.path.join(outdir, 'index.html')))
- self.assertEqual(self.get_html_count(outdir), 2)
- self.assertEqual(self.get_plist_count(outdir), 0)
+ exit_code, reportdir = run_driver(tmpdir, cdb, [])
+ self.assertTrue(
+ os.path.exists(os.path.join(reportdir, 'index.html')))
+ self.assertEqual(self.get_html_count(reportdir), 2)
+ self.assertEqual(self.get_plist_count(reportdir), 0)
def test_plist_and_html_creates_html_report(self):
with fixtures.TempDir() as tmpdir:
cdb = prepare_cdb('regular', tmpdir)
- outdir = os.path.join(tmpdir, 'result')
- exit_code, output = run_driver(outdir, cdb, ['--plist-html'])
- self.assertTrue(os.path.exists(os.path.join(outdir, 'index.html')))
- self.assertEqual(self.get_html_count(outdir), 2)
- self.assertEqual(self.get_plist_count(outdir), 5)
+ exit_code, reportdir = run_driver(tmpdir, cdb, ['--plist-html'])
+ self.assertTrue(
+ os.path.exists(os.path.join(reportdir, 'index.html')))
+ self.assertEqual(self.get_html_count(reportdir), 2)
+ self.assertEqual(self.get_plist_count(reportdir), 5)
def test_plist_does_not_creates_html_report(self):
with fixtures.TempDir() as tmpdir:
cdb = prepare_cdb('regular', tmpdir)
- outdir = os.path.join(tmpdir, 'result')
- exit_code, output = run_driver(outdir, cdb, ['--plist'])
+ exit_code, reportdir = run_driver(tmpdir, cdb, ['--plist'])
self.assertFalse(
- os.path.exists(os.path.join(outdir, 'index.html')))
- self.assertEqual(self.get_html_count(outdir), 0)
- self.assertEqual(self.get_plist_count(outdir), 5)
+ os.path.exists(os.path.join(reportdir, 'index.html')))
+ self.assertEqual(self.get_html_count(reportdir), 0)
+ self.assertEqual(self.get_plist_count(reportdir), 5)
class FailureReportTest(unittest.TestCase):
def test_broken_creates_failure_reports(self):
with fixtures.TempDir() as tmpdir:
cdb = prepare_cdb('broken', tmpdir)
- outdir = os.path.join(tmpdir, 'result')
- exit_code, output = run_driver(outdir, cdb, [])
- self.assertTrue(os.path.isdir(os.path.join(outdir, 'failures')))
+ exit_code, reportdir = run_driver(tmpdir, cdb, [])
+ self.assertTrue(
+ os.path.isdir(os.path.join(reportdir, 'failures')))
def test_broken_does_not_creates_failure_reports(self):
with fixtures.TempDir() as tmpdir:
cdb = prepare_cdb('broken', tmpdir)
- outdir = os.path.join(tmpdir, 'result')
- exit_code, output = run_driver(
- outdir, cdb, ['--no-failure-reports'])
- self.assertFalse(os.path.isdir(os.path.join(outdir, 'failures')))
+ exit_code, reportdir = run_driver(
+ tmpdir, cdb, ['--no-failure-reports'])
+ self.assertFalse(
+ os.path.isdir(os.path.join(reportdir, 'failures')))
class TitleTest(unittest.TestCase):
@@ -174,7 +158,7 @@
]
result = dict()
- index = os.path.join(directory, 'result', 'index.html')
+ index = os.path.join(directory, 'index.html')
with open(index, 'r') as handler:
for line in handler.readlines():
for regex in patterns:
@@ -188,14 +172,12 @@
def test_default_title_in_report(self):
with fixtures.TempDir() as tmpdir:
cdb = prepare_cdb('broken', tmpdir)
- outdir = os.path.join(tmpdir, 'result')
- exit_code, output = run_driver(outdir, cdb, [])
- self.assertTitleEqual(tmpdir, 'src - analyzer results')
+ exit_code, reportdir = run_driver(tmpdir, cdb, [])
+ self.assertTitleEqual(reportdir, 'src - analyzer results')
def test_given_title_in_report(self):
with fixtures.TempDir() as tmpdir:
cdb = prepare_cdb('broken', tmpdir)
- outdir = os.path.join(tmpdir, 'result')
- exit_code, output = run_driver(
- outdir, cdb, ['--html-title', 'this is the title'])
- self.assertTitleEqual(tmpdir, 'this is the title')
+ exit_code, reportdir = run_driver(
+ tmpdir, cdb, ['--html-title', 'this is the title'])
+ self.assertTitleEqual(reportdir, 'this is the title')
Index: tools/scan-build-py/tests/functional/cases/__init__.py
===================================================================
--- tools/scan-build-py/tests/functional/cases/__init__.py
+++ tools/scan-build-py/tests/functional/cases/__init__.py
@@ -4,6 +4,7 @@
# This file is distributed under the University of Illinois Open Source
# License. See LICENSE.TXT for details.
+import re
import os.path
import subprocess
@@ -37,3 +38,32 @@
return subprocess.check_call(cmd,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT)
+
+
+def call_and_report(analyzer_cmd, build_cmd):
+ child = subprocess.Popen(analyzer_cmd + ['-v'] + build_cmd,
+ universal_newlines=True,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.STDOUT)
+
+ pattern = re.compile('Report directory created: (.+)')
+ directory = None
+ for line in child.stdout.readlines():
+ match = pattern.search(line)
+ if match and match.lastindex == 1:
+ directory = match.group(1)
+ break
+ child.stdout.close()
+ child.wait()
+
+ return (child.returncode, directory)
+
+
+def check_call_and_report(analyzer_cmd, build_cmd):
+ exit_code, result = call_and_report(analyzer_cmd, build_cmd)
+ if exit_code != 0:
+ raise subprocess.CalledProcessError(
+ "Command '{0}' returned non-zero exit status {1}".format(
+ cmd, exit_code))
+ else:
+ return result
Index: tools/scan-build-py/setup.py
===================================================================
--- tools/scan-build-py/setup.py
+++ tools/scan-build-py/setup.py
@@ -2,44 +2,6 @@
# -*- coding: utf-8 -*-
from setuptools import setup
-from subprocess import check_call
-from distutils.dir_util import mkpath
-from distutils.command.build import build
-from distutils.command.install import install
-
-
-class BuildEAR(build):
-
- def run(self):
- import os
- import os.path
-
- mkpath(self.build_temp)
-
- source_dir = os.path.join(os.getcwd(), 'libear')
- dest_dir = os.path.abspath(self.build_lib)
-
- cmd = ['cmake', '-DCMAKE_INSTALL_PREFIX=' + dest_dir, source_dir]
- check_call(cmd, cwd=self.build_temp)
-
- cmd = ['make', 'install']
- check_call(cmd, cwd=self.build_temp)
-
-
-class Build(build):
-
- def run(self):
- self.run_command('buildear')
- build.run(self)
-
-
-class Install(install):
-
- def run(self):
- self.run_command('build')
- self.run_command('install_scripts')
- install.run(self)
-
setup(
name='beye',
@@ -55,9 +17,8 @@
scripts=['bin/scan-build',
'bin/intercept-build', 'bin/intercept-cc', 'bin/intercept-c++',
'bin/analyze-build', 'bin/analyze-cc', 'bin/analyze-c++'],
- packages=['libscanbuild'],
- package_data={'libscanbuild': ['resources/*']},
- cmdclass={'buildear': BuildEAR, 'install': Install, 'build': Build},
+ packages=['libscanbuild', 'libear'],
+ package_data={'libscanbuild': ['resources/*'], 'libear': ['config.h.in', 'ear.c']},
classifiers=[
"Development Status :: 4 - Beta",
"License :: OSI Approved :: University of Illinois/NCSA Open Source License",
Index: tools/scan-build-py/libscanbuild/shell.py
===================================================================
--- /dev/null
+++ tools/scan-build-py/libscanbuild/shell.py
@@ -0,0 +1,66 @@
+# -*- coding: utf-8 -*-
+# The LLVM Compiler Infrastructure
+#
+# This file is distributed under the University of Illinois Open Source
+# License. See LICENSE.TXT for details.
+""" This module implements basic shell escaping/unescaping methods. """
+
+import re
+import shlex
+
+__all__ = ['encode', 'decode']
+
+
+def encode(command):
+ """ Takes a command as list and returns a string. """
+
+ def needs_quote(arg):
+ """ Returns true if arguments needs to be protected by quotes.
+
+ Previous implementation was shlex.split method, but that's not good
+ for this job. Currently is running through the string with a basic
+ state checking. """
+
+ reserved = {' ', '$', '%', '&', '(', ')', '[', ']', '{', '}', '*', '|',
+ '<', '>', '@', '?', '!'}
+ state = 0
+ for c in arg:
+ if state == 0 and c in reserved:
+ return True
+ elif state == 0 and c == '\\':
+ state = 1
+ elif state == 1 and c in reserved | {'\\'}:
+ state = 0
+ elif state == 0 and c == '"':
+ state = 2
+ elif state == 2 and c == '"':
+ state = 0
+ elif state == 0 and c == "'":
+ state = 3
+ elif state == 3 and c == "'":
+ state = 0
+ return state != 0
+
+ def escape(arg):
+ """ Do protect argument if that's needed. """
+
+ table = {'\\': '\\\\', '"': '\\"'}
+ escaped = ''.join([table.get(c, c) for c in arg])
+
+ return '"' + escaped + '"' if needs_quote(arg) else escaped
+
+ return " ".join([escape(arg) for arg in command])
+
+
+def decode(string):
+ """ Takes a command string and returns as a list. """
+
+ def unescape(arg):
+ """ Gets rid of the escaping characters. """
+
+ if len(arg) >= 2 and arg[0] == arg[-1] and arg[0] == '"':
+ arg = arg[1:-1]
+ return re.sub(r'\\(["\\])', r'\1', arg)
+ return re.sub(r'\\([\\ $%&\(\)\[\]\{\}\*|<>@?!])', r'\1', arg)
+
+ return [unescape(arg) for arg in shlex.split(string)]
Index: tools/scan-build-py/libscanbuild/runner.py
===================================================================
--- tools/scan-build-py/libscanbuild/runner.py
+++ tools/scan-build-py/libscanbuild/runner.py
@@ -9,11 +9,11 @@
import logging
import os
import os.path
-import shlex
import tempfile
import functools
-from libscanbuild.command import classify_parameters, Action
+from libscanbuild.command import classify_parameters, Action, classify_source
from libscanbuild.clang import get_arguments, get_version
+from libscanbuild.shell import decode
__all__ = ['run']
@@ -29,7 +29,7 @@
try:
command = opts.pop('command')
logging.debug("Run analyzer against '%s'", command)
- opts.update(classify_parameters(shlex.split(command)))
+ opts.update(classify_parameters(decode(command)))
return action_check(opts)
except Exception:
@@ -87,7 +87,7 @@
dir=destination(opts))
os.close(handle)
cwd = opts['directory']
- cmd = get_arguments(cwd, [opts['clang']] + opts['report'] + ['-o', name])
+ cmd = get_arguments([opts['clang']] + opts['report'] + ['-o', name], cwd)
logging.debug('exec command in %s: %s', cwd, ' '.join(cmd))
subprocess.call(cmd, cwd=cwd)
@@ -116,7 +116,8 @@
requested, it calls the continuation to generate it. """
cwd = opts['directory']
- cmd = [opts['clang']] + opts['analyze'] + opts['output']
+ cmd = get_arguments([opts['clang']] + opts['analyze'] + opts['output'],
+ cwd)
logging.debug('exec command in %s: %s', cwd, ' '.join(cmd))
child = subprocess.Popen(cmd,
cwd=cwd,
@@ -170,7 +171,7 @@
common.extend(['-arch', opts.pop('arch')])
common.extend(opts.pop('compile_options', []))
common.extend(['-x', opts['language']])
- common.append(opts['file'])
+ common.append(os.path.relpath(opts['file'], opts['directory']))
opts.update({
'analyze': ['--analyze'] + opts['direct_args'] + common,
@@ -180,32 +181,11 @@
return continuation(opts)
-@require(['file'])
+@require(['file', 'c++'])
def language_check(opts, continuation=create_commands):
""" Find out the language from command line parameters or file name
extension. The decision also influenced by the compiler invocation. """
- def from_filename(name, cplusplus_compiler):
- """ Return the language from fille name extension. """
-
- mapping = {
- '.c': 'c++' if cplusplus_compiler else 'c',
- '.cp': 'c++',
- '.cpp': 'c++',
- '.cxx': 'c++',
- '.txx': 'c++',
- '.cc': 'c++',
- '.C': 'c++',
- '.ii': 'c++-cpp-output',
- '.i': 'c++-cpp-output' if cplusplus_compiler else 'c-cpp-output',
- '.m': 'objective-c',
- '.mi': 'objective-c-cpp-output',
- '.mm': 'objective-c++',
- '.mii': 'objective-c++-cpp-output'
- }
- (_, extension) = os.path.splitext(os.path.basename(name))
- return mapping.get(extension)
-
accepteds = {
'c', 'c++', 'objective-c', 'objective-c++', 'c-cpp-output',
'c++-cpp-output', 'objective-c-cpp-output'
@@ -213,7 +193,7 @@
key = 'language'
language = opts[key] if key in opts else \
- from_filename(opts['file'], opts.get('cxx', False))
+ classify_source(opts['file'], opts['c++'])
if language is None:
logging.debug('skip analysis, language not known')
@@ -236,7 +216,7 @@
key = 'archs_seen'
if key in opts:
# filter out disabled architectures and -arch switches
- archs = [a for a in opts[key] if '-arch' != a and a not in disableds]
+ archs = [a for a in opts[key] if a not in disableds]
if not archs:
logging.debug('skip analysis, found not supported arch')
Index: tools/scan-build-py/libscanbuild/report.py
===================================================================
--- tools/scan-build-py/libscanbuild/report.py
+++ tools/scan-build-py/libscanbuild/report.py
@@ -420,17 +420,9 @@
def copy_resource_files(output_dir):
""" Copy the javascript and css files to the report directory. """
- try:
- import pkg_resources
- package = 'libscanbuild'
- resources_dir = pkg_resources.resource_filename(package, 'resources')
- for resource in pkg_resources.resource_listdir(package, 'resources'):
- shutil.copy(os.path.join(resources_dir, resource), output_dir)
- except ImportError:
- resources_dir = os.path.join(
- os.path.dirname(os.path.realpath(__file__)), 'resources')
- for resource in os.listdir(resources_dir):
- shutil.copy(os.path.join(resources_dir, resource), output_dir)
+ this_dir = os.path.dirname(os.path.realpath(__file__))
+ for resource in os.listdir(os.path.join(this_dir, 'resources')):
+ shutil.copy(os.path.join(this_dir, 'resources', resource), output_dir)
def encode_value(container, key, encode):
Index: tools/scan-build-py/libscanbuild/options.py
===================================================================
--- tools/scan-build-py/libscanbuild/options.py
+++ tools/scan-build-py/libscanbuild/options.py
@@ -61,6 +61,11 @@
default=0,
help="""Enable verbose output from '%(prog)s'. A second and third
'-v' increases verbosity.""")
+ parser.add_argument(
+ '--override-compiler',
+ action='store_true',
+ help="""Always resort to the compiler wrapper even when better
+ interposition methods are available.""")
if add_cdb:
parser.add_argument('--cdb',
metavar='<file>',
@@ -153,14 +158,12 @@
'--maxloop', '-maxloop',
metavar='<loop count>',
type=int,
- default=4,
help="""Specifiy the number of times a block can be visited before
giving up. Increase for more comprehensive coverage at a cost
of speed.""")
advanced.add_argument('--store', '-store',
metavar='<model>',
dest='store_model',
- default='region',
choices=['region', 'basic'],
help="""Specify the store model used by the analyzer.
'region' specifies a field- sensitive store model.
@@ -171,7 +174,6 @@
'--constraints', '-constraints',
metavar='<model>',
dest='constraints_model',
- default='range',
choices=['range', 'basic'],
help="""Specify the contraint engine used by the analyzer. Specifying
'basic' uses a simpler, less powerful constraint model used by
@@ -241,11 +243,11 @@
help="""Loading external checkers using the clang plugin interface.""")
plugins.add_argument('--enable-checker', '-enable-checker',
metavar='<checker name>',
- action='append',
+ action=AppendCommaSeparated,
help="""Enable specific checker.""")
plugins.add_argument('--disable-checker', '-disable-checker',
metavar='<checker name>',
- action='append',
+ action=AppendCommaSeparated,
help="""Disable specific checker.""")
plugins.add_argument(
'--help-checkers',
@@ -258,3 +260,16 @@
'--help-checkers-verbose',
action='store_true',
help="""Print all available checkers and mark the enabled ones.""")
+
+
+class AppendCommaSeparated(argparse.Action):
+ """ argparse Action class to support multiple comma separated lists. """
+
+ def __call__(self, __parser, namespace, values, __option_string):
+ # getattr(obj, attr, default) does not really returns default but none
+ if getattr(namespace, self.dest, None) is None:
+ setattr(namespace, self.dest, [])
+ # once it's fixed we can use as expected
+ actual = getattr(namespace, self.dest)
+ actual.extend(values.split(','))
+ setattr(namespace, self.dest, actual)
Index: tools/scan-build-py/libscanbuild/interposition.py
===================================================================
--- tools/scan-build-py/libscanbuild/interposition.py
+++ tools/scan-build-py/libscanbuild/interposition.py
@@ -13,12 +13,11 @@
build_command)
from libscanbuild.driver import (initialize_logging, ReportDirectory,
analyzer_params, print_checkers,
- print_active_checkers)
+ print_active_checkers, need_analyzer)
from libscanbuild.report import document
from libscanbuild.clang import get_checkers
from libscanbuild.runner import action_check
-from libscanbuild.intercept import is_source_file
-from libscanbuild.command import classify_parameters
+from libscanbuild.command import classify_parameters, classify_source
__all__ = ['main', 'wrapper']
@@ -82,7 +81,7 @@
'CXX': os.path.join(wrapper_dir, 'analyze-cxx'),
'BUILD_ANALYZE_CC': args.cc,
'BUILD_ANALYZE_CXX': args.cxx,
- 'BUILD_ANALYZE_CLANG': args.clang,
+ 'BUILD_ANALYZE_CLANG': args.clang if need_analyzer(args.build) else '',
'BUILD_ANALYZE_VERBOSE': 'DEBUG' if args.verbose > 2 else 'WARNING',
'BUILD_ANALYZE_REPORT_DIR': destination,
'BUILD_ANALYZE_REPORT_FORMAT': args.output_format,
@@ -104,6 +103,10 @@
compilation = [compiler] + sys.argv[1:]
logging.info('execute compiler: %s', compilation)
result = subprocess.call(compilation)
+ # exit when it fails, ...
+ if result or not os.getenv('BUILD_ANALYZE_CLANG'):
+ return result
+ # ... and run the analyzer if all went well.
try:
# collect the needed parameters from environment, crash when missing
consts = {
@@ -118,7 +121,7 @@
# get relevant parameters from command line arguments
args = classify_parameters(sys.argv)
filenames = args.pop('files', [])
- for filename in (name for name in filenames if is_source_file(name)):
+ for filename in (name for name in filenames if classify_source(name)):
parameters = dict(args, file=filename, **consts)
logging.debug('analyzer parameters %s', parameters)
current = action_check(parameters)
@@ -128,5 +131,5 @@
logging.info(line.rstrip())
except Exception:
logging.exception("run analyzer inside compiler wrapper failed.")
- # return compiler exit code
- return result
+ finally:
+ return 0
Index: tools/scan-build-py/libscanbuild/intercept.py
===================================================================
--- tools/scan-build-py/libscanbuild/intercept.py
+++ tools/scan-build-py/libscanbuild/intercept.py
@@ -26,10 +26,12 @@
import os
import os.path
import re
-import shlex
+import glob
import itertools
+from libear import ear_library
from libscanbuild import duplicate_check, tempdir
from libscanbuild.command import Action, classify_parameters
+from libscanbuild.shell import encode, decode
__all__ = ['capture', 'wrapper']
@@ -48,7 +50,7 @@
current = itertools.chain.from_iterable(
# creates a sequence of entry generators from an exec,
# but filter out non compiler calls before.
- (format_entry(x) for x in commands if is_compiler_call(x)))
+ (format_entry(x) for x in commands if compiler_call(x)))
# read entries from previous run
if 'append' in args and args.append and os.path.exists(args.cdb):
with open(args.cdb) as handle:
@@ -57,7 +59,8 @@
previous = iter([])
# filter out duplicate entries from both
duplicate = duplicate_check(entry_hash)
- return (entry for entry in itertools.chain(previous, current)
+ return (entry
+ for entry in itertools.chain(previous, current)
if os.path.exists(entry['file']) and not duplicate(entry))
return commands
@@ -66,10 +69,11 @@
environment = setup_environment(args, tmpdir, wrappers_dir)
logging.debug('run build in environment: %s', environment)
exit_code = subprocess.call(args.build, env=environment)
- logging.debug('build finished with exit code: %d', exit_code)
+ logging.info('build finished with exit code: %d', exit_code)
# read the intercepted exec calls
- commands = (parse_exec_trace(os.path.join(tmpdir, filename))
- for filename in sorted(os.listdir(tmpdir)))
+ commands = (
+ parse_exec_trace(os.path.join(tmpdir, filename))
+ for filename in sorted(glob.iglob(os.path.join(tmpdir, '*.cmd'))))
# do post processing
entries = post_processing(itertools.chain.from_iterable(commands))
# dump the compilation database
@@ -85,10 +89,13 @@
The exec calls will be logged by the 'libear' preloaded library or by the
'wrapper' programs. """
+ compiler = args.cc if 'cc' in args.__dict__ else 'cc'
+ ear_library_path = ear_library(compiler, destination)
+
environment = dict(os.environ)
environment.update({'BUILD_INTERCEPT_TARGET_DIR': destination})
- if sys.platform in {'win32', 'cygwin'} or not ear_library_path(False):
+ if args.override_compiler or not ear_library_path:
environment.update({
'CC': os.path.join(wrappers_dir, 'intercept-cc'),
'CXX': os.path.join(wrappers_dir, 'intercept-cxx'),
@@ -98,11 +105,11 @@
})
elif 'darwin' == sys.platform:
environment.update({
- 'DYLD_INSERT_LIBRARIES': ear_library_path(True),
+ 'DYLD_INSERT_LIBRARIES': ear_library_path,
'DYLD_FORCE_FLAT_NAMESPACE': '1'
})
else:
- environment.update({'LD_PRELOAD': ear_library_path(False)})
+ environment.update({'LD_PRELOAD': ear_library_path})
return environment
@@ -152,6 +159,7 @@
generated by the interception library or wrapper command. A single
report file _might_ contain multiple process creation info. """
+ logging.debug('parse exec trace file: %s', filename)
with open(filename, 'r') as handler:
content = handler.read()
for group in filter(bool, content.split(GS)):
@@ -168,54 +176,34 @@
def format_entry(entry):
""" Generate the desired fields for compilation database entries. """
- def join_command(args):
- return ' '.join([shell_escape(arg) for arg in args])
-
def abspath(cwd, name):
""" Create normalized absolute path from input filename. """
fullname = name if os.path.isabs(name) else os.path.join(cwd, name)
return os.path.normpath(fullname)
+ logging.debug('format this command: %s', entry['command'])
atoms = classify_parameters(entry['command'])
- return ({
- 'directory': entry['directory'],
- 'command': join_command(entry['command']),
- 'file': abspath(entry['directory'], filename)
- } for filename in atoms.get('files', [])
- if is_source_file(filename) and atoms['action'] <= Action.Compile)
-
-
-def shell_escape(arg):
- """ Create a single string from list.
-
- The major challenge, to deal with white spaces. Which are used by
- the shell as separator. (Eg.: -D_KEY="Value with spaces") """
-
- def quote(arg):
- table = {'\\': '\\\\', '"': '\\"', "'": "\\'"}
- return '"' + ''.join([table.get(c, c) for c in arg]) + '"'
-
- return quote(arg) if len(shlex.split(arg)) > 1 else arg
-
-
-def is_source_file(filename):
- """ A predicate to decide the filename is a source file or not. """
-
- accepted = {
- '.c', '.C', '.cc', '.CC', '.cxx', '.cp', '.cpp', '.c++', '.m', '.mm',
- '.i', '.ii', '.mii'
- }
- _, ext = os.path.splitext(filename)
- return ext in accepted
+ if atoms['action'] <= Action.Compile:
+ for source in atoms['files']:
+ compiler = 'c++' if atoms['c++'] else 'cc'
+ output = ['-o', atoms['output']] if atoms.get('output') else []
+ command = [compiler, '-c'
+ ] + atoms['compile_options'] + output + [source]
+ logging.debug('formated as: %s', command)
+ yield {
+ 'directory': entry['directory'],
+ 'command': encode(command),
+ 'file': abspath(entry['directory'], source)
+ }
-def is_compiler_call(entry):
+def compiler_call(entry):
""" A predicate to decide the entry is a compiler call or not. """
patterns = [
re.compile(r'^([^/]*/)*intercept-c(c|\+\+)$'),
re.compile(r'^([^/]*/)*c(c|\+\+)$'),
- re.compile(r'^([^/]*/)*([^-]*-)*g(cc|\+\+)(-\d+(\.\d+){0,2})?$'),
+ re.compile(r'^([^/]*/)*([^-]*-)*[mg](cc|\+\+)(-\d+(\.\d+){0,2})?$'),
re.compile(r'^([^/]*/)*([^-]*-)*clang(\+\+)?(-\d+(\.\d+){0,2})?$'),
re.compile(r'^([^/]*/)*llvm-g(cc|\+\+)$'),
]
@@ -234,23 +222,11 @@
# 'clang' therefore both call would be logged. To avoid
# this the hash does not contain the first word of the
# command.
- command = ' '.join(shlex.split(entry['command'])[1:])
+ command = ' '.join(decode(entry['command'])[1:])
return '<>'.join([filename, directory, command])
-def ear_library_path(darwin):
- """ Returns the full path to the 'libear' library. """
-
- try:
- import pkg_resources
- lib_name = 'libear.dylib' if darwin else 'libear.so'
- lib_file = pkg_resources.resource_filename('libscanbuild', lib_name)
- return lib_file if os.path.exists(lib_file) else None
- except ImportError:
- return None
-
-
if sys.version_info.major >= 3 and sys.version_info.minor >= 2:
from tempfile import TemporaryDirectory
else:
Index: tools/scan-build-py/libscanbuild/driver.py
===================================================================
--- tools/scan-build-py/libscanbuild/driver.py
+++ tools/scan-build-py/libscanbuild/driver.py
@@ -20,7 +20,6 @@
import json
import tempfile
import multiprocessing
-from libscanbuild import tempdir
from libscanbuild.runner import run
from libscanbuild.intercept import capture
from libscanbuild.options import create_parser
@@ -50,14 +49,17 @@
# next step to run the analyzer against the captured commands
with ReportDirectory(args.output, args.keep_empty) as target_dir:
- run_analyzer(args, target_dir.name)
- # cover report generation and bug counting
- number_of_bugs = document(args, target_dir.name, True)
- # remove the compilation database when it was not requested
- if args.action == 'all' and os.path.exists(args.cdb):
- os.unlink(args.cdb)
- # set exit status as it was requested
- return number_of_bugs if args.status_bugs else exit_code
+ if args.action == 'analyze' or need_analyzer(args.build):
+ run_analyzer(args, target_dir.name)
+ # cover report generation and bug counting
+ number_of_bugs = document(args, target_dir.name, True)
+ # remove the compilation database when it was not requested
+ if args.action == 'all' and os.path.exists(args.cdb):
+ os.unlink(args.cdb)
+ # set exit status as it was requested
+ return number_of_bugs if args.status_bugs else exit_code
+ else:
+ return exit_code
except KeyboardInterrupt:
return 1
except Exception:
@@ -103,6 +105,12 @@
parser.error('missing build command')
+def need_analyzer(args):
+ """ Check the internt of the build command. """
+
+ return len(args) and not re.search('configure|autogen', args[0])
+
+
def run_analyzer(args, output_dir):
""" Runs the analyzer against the given compilation database. """
@@ -164,15 +172,16 @@
result.append('-analyzer-output={0}'.format(args.output_format))
if args.analyzer_config:
result.append(args.analyzer_config)
- if 2 <= args.verbose:
+ if 4 <= args.verbose:
result.append('-analyzer-display-progress')
if args.plugins:
result.extend(prefix_with('-load', args.plugins))
if args.enable_checker:
- result.extend(prefix_with('-analyzer-checker', args.enable_checker))
+ checkers = ','.join(args.enable_checker)
+ result.extend(['-analyzer-checker', checkers])
if args.disable_checker:
- result.extend(
- prefix_with('-analyzer-disable-checker', args.disable_checker))
+ checkers = ','.join(args.disable_checker)
+ result.extend(['-analyzer-disable-checker', checkers])
if os.getenv('UBIVIZ'):
result.append('-analyzer-viz-egraph-ubigraph')
@@ -215,6 +224,7 @@
def __init__(self, hint, keep):
self.name = ReportDirectory._create(hint)
self.keep = keep
+ logging.info('Report directory created: %s', self.name)
def __enter__(self):
return self
@@ -235,12 +245,5 @@
@staticmethod
def _create(hint):
- if tempdir() != hint:
- try:
- os.mkdir(hint)
- return hint
- except OSError:
- raise
- else:
- stamp = time.strftime('%Y-%m-%d-%H%M%S', time.localtime())
- return tempfile.mkdtemp(prefix='scan-build-{0}-'.format(stamp))
+ stamp = time.strftime('scan-build-%Y-%m-%d-%H%M%S-', time.localtime())
+ return tempfile.mkdtemp(prefix=stamp, dir=hint)
Index: tools/scan-build-py/libscanbuild/command.py
===================================================================
--- tools/scan-build-py/libscanbuild/command.py
+++ tools/scan-build-py/libscanbuild/command.py
@@ -6,264 +6,121 @@
""" This module is responsible for to parse a compiler invocation. """
import re
+import os
-__all__ = ['Action', 'classify_parameters']
+__all__ = ['Action', 'classify_parameters', 'classify_source']
class Action(object):
""" Enumeration class for compiler action. """
- Link, Compile, Preprocess, Info, Internal = range(5)
+ Link, Compile, Ignored = range(3)
def classify_parameters(command):
- """ Parses the command line arguments of the given invocation.
-
- To run analysis from a compilation command, first it disassembles the
- compilation command. Classifies the parameters into groups and throws
- away those which are not relevant. """
-
- def match(state, iterator):
- """ This method contains a list of pattern and action tuples.
- The matching start from the top if the list, when the first match
- happens the action is executed. """
-
- def regex(pattern, action):
- """ Matching expression for regex. """
-
- def evaluate(iterator):
- match = evaluate.regexp.match(iterator.current())
- if match:
- action(state, iterator, match)
- return True
-
- evaluate.regexp = re.compile(pattern)
- return evaluate
-
- def anyof(opts, action):
- """ Matching expression for string literals. """
-
- def evaluate(iterator):
- if iterator.current() in opts:
- action(state, iterator, None)
- return True
-
- return evaluate
-
- tasks = [
- # actions
- regex(r'^-(E|MM?)$', take_action(Action.Preprocess)),
- anyof({'-c'}, take_action(Action.Compile)),
- anyof({'-print-prog-name'}, take_action(Action.Info)),
- anyof({'-cc1'}, take_action(Action.Internal)),
- # architectures
- anyof({'-arch'}, take_two('archs_seen')),
- # module names
- anyof({'-filelist'}, take_from_file('files')),
- regex(r'^[^-].+', take_one('files')),
- # language
- anyof({'-x'}, take_second('language')),
- # output
- anyof({'-o'}, take_second('output')),
- # relevant compiler flags
- anyof({'-write-strings', '-v'}, take_one('compile_options')),
- anyof({'-ftrapv-handler', '--sysroot', '-target'},
- take_two('compile_options')),
- regex(r'^-isysroot', take_two('compile_options')),
- regex(r'^-m(32|64)$', take_one('compile_options')),
- regex(r'^-mios-simulator-version-min(.*)',
- take_joined('compile_options')),
- regex(r'^-stdlib(.*)', take_joined('compile_options')),
- regex(r'^-mmacosx-version-min(.*)',
- take_joined('compile_options')),
- regex(r'^-miphoneos-version-min(.*)',
- take_joined('compile_options')),
- regex(r'^-O[1-3]$', take_one('compile_options')),
- anyof({'-O'}, take_as('-O1', 'compile_options')),
- anyof({'-Os'}, take_as('-O2', 'compile_options')),
- regex(r'^-[DIU](.*)$', take_joined('compile_options')),
- regex(r'^-isystem(.*)$', take_joined('compile_options')),
- anyof({'-nostdinc'}, take_one('compile_options')),
- regex(r'^-std=', take_one('compile_options')),
- regex(r'^-include', take_two('compile_options')),
- anyof({
- '-idirafter', '-imacros', '-iprefix', '-iwithprefix',
- '-iwithprefixbefore'
- }, take_two('compile_options')),
- regex(r'^-m.*', take_one('compile_options')),
- regex(r'^-iquote(.*)', take_joined('compile_options')),
- regex(r'^-Wno-', take_one('compile_options')),
- # ignored flags
- regex(r'^-framework$', take_two()),
- regex(r'^-fobjc-link-runtime(.*)', take_joined()),
- regex(r'^-[lL]', take_one()),
- regex(r'^-M[TF]$', take_two()),
- regex(r'^-[eu]$', take_two()),
- anyof({'-fsyntax-only', '-save-temps'}, take_one()),
- anyof({
- '-install_name', '-exported_symbols_list', '-current_version',
- '-compatibility_version', '-init', '-seg1addr',
- '-bundle_loader', '-multiply_defined', '--param',
- '--serialize-diagnostics'
- }, take_two()),
- anyof({'-sectorder'}, take_four()),
- # relevant compiler flags
- regex(r'^-[fF](.+)$', take_one('compile_options'))
- ]
- for task in tasks:
- if task(iterator):
- return
-
- state = {'action': Action.Link, 'cxx': is_cplusplus_compiler(command[0])}
-
- arguments = Arguments(command)
- for _ in arguments:
- match(state, arguments)
- return state
-
-
-class Arguments(object):
- """ An iterator wraper around compiler arguments.
-
- Python iterators are only implement the 'next' method, but this one
- implements the 'current' query method as well. """
-
- def __init__(self, args):
- """ Takes full command line, but iterates on the parameters only. """
-
- self.__sequence = [arg for arg in args[1:] if arg != '']
- self.__size = len(self.__sequence)
- self.__current = -1
-
- def __iter__(self):
- """ Needed for python iterator. """
-
- return self
-
- def __next__(self):
- """ Needed for python iterator. (version 3.x) """
-
- return self.next()
-
- def next(self):
- """ Needed for python iterator. (version 2.x) """
-
- self.__current += 1
- return self.current()
-
- def current(self):
- """ Extra method to query the current element. """
-
- if self.__current >= self.__size:
- raise StopIteration
+ """ Parses the command line arguments of the given invocation. """
+
+ ignored = {
+ '-g': 0,
+ '-fsyntax-only': 0,
+ '-save-temps': 0,
+ '-install_name': 1,
+ '-exported_symbols_list': 1,
+ '-current_version': 1,
+ '-compatibility_version': 1,
+ '-init': 1,
+ '-e': 1,
+ '-seg1addr': 1,
+ '-bundle_loader': 1,
+ '-multiply_defined': 1,
+ '-sectorder': 3,
+ '--param': 1,
+ '--serialize-diagnostics': 1
+ }
+
+ state = {
+ 'action': Action.Link,
+ 'files': [],
+ 'output': None,
+ 'compile_options': [],
+ 'c++': cplusplus_compiler(command[0])
+ }
+
+ args = iter(command[1:])
+ for arg in args:
+ # compiler action parameters are the most important ones...
+ if arg in {'-E', '-S', '-cc1', '-M', '-MM', '-###'}:
+ state.update({'action': Action.Ignored})
+ elif arg == '-c':
+ state.update({'action': max(state['action'], Action.Compile)})
+ # arch flags are taken...
+ elif arg == '-arch':
+ archs = state.get('archs_seen', [])
+ state.update({'archs_seen': archs + [next(args)]})
+ # explicit language option taken...
+ elif arg == '-x':
+ state.update({'language': next(args)})
+ # output flag taken...
+ elif arg == '-o':
+ state.update({'output': next(args)})
+ # warning disable options are taken...
+ elif re.match(r'^-Wno-', arg):
+ state['compile_options'].append(arg)
+ # warning options are ignored...
+ elif re.match(r'^-[mW].+', arg):
+ pass
+ # some preprocessor parameters are ignored...
+ elif arg in {'-MD', '-MMD', '-MG', '-MP'}:
+ pass
+ elif arg in {'-MF', '-MT', '-MQ'}:
+ next(args)
+ # linker options are ignored...
+ elif arg in {'-static', '-shared', '-s', '-rdynamic'} or \
+ re.match(r'^-[lL].+', arg):
+ pass
+ elif arg in {'-l', '-L', '-u', '-z', '-T', '-Xlinker'}:
+ next(args)
+ # some other options are ignored...
+ elif arg in ignored.keys():
+ for _ in range(ignored[arg]):
+ next(args)
+ # parameters which looks source file are taken...
+ elif re.match(r'^[^-].+', arg) and classify_source(arg):
+ state['files'].append(arg)
+ # and consider everything else as compile option.
else:
- return self.__sequence[self.__current]
-
-
-def take_n(count=1, *keys):
- """ Take N number of arguments and append it to the refered values. """
-
- def take(values, iterator, _match):
- updates = []
- updates.append(iterator.current())
- for _ in range(count - 1):
- updates.append(iterator.next())
- for key in keys:
- current = values.get(key, [])
- values.update({key: current + updates})
-
- return take
-
-
-def take_one(*keys):
- """ Take one argument and append to the 'key' values. """
+ state['compile_options'].append(arg)
- return take_n(1, *keys)
-
-
-def take_two(*keys):
- """ Take two arguments and append to the 'key' values. """
-
- return take_n(2, *keys)
-
-
-def take_four(*keys):
- """ Take four arguments and append to the 'key' values. """
-
- return take_n(4, *keys)
-
-
-def take_joined(*keys):
- """ Take one or two arguments and append to the 'key' values.
-
- eg.: '-Isomething' shall take only one.
- '-I something' shall take two.
-
- This action should go with regex matcher only. """
-
- def take(values, iterator, match):
- updates = []
- updates.append(iterator.current())
- if not match.group(1):
- updates.append(iterator.next())
- for key in keys:
- current = values.get(key, [])
- values.update({key: current + updates})
-
- return take
-
-
-def take_from_file(*keys):
- """ Take values from the refered file and append to the 'key' values.
-
- The refered file is the second argument. (So it consume two args.) """
-
- def take(values, iterator, _match):
- with open(iterator.next()) as handle:
- current = [line.strip() for line in handle.readlines()]
- for key in keys:
- values[key] = current
-
- return take
-
-
-def take_as(value, *keys):
- """ Take one argument and append to the 'key' values.
-
- But instead of taking the argument, it takes the value as it was given. """
-
- def take(values, _iterator, _match):
- updates = [value]
- for key in keys:
- current = values.get(key, [])
- values.update({key: current + updates})
-
- return take
-
-
-def take_second(*keys):
- """ Take the second argument and append to the 'key' values. """
-
- def take(values, iterator, _match):
- current = iterator.next()
- for key in keys:
- values[key] = current
-
- return take
-
-
-def take_action(action):
- """ Take the action value and overwrite current value if that's bigger. """
-
- def take(values, _iterator, _match):
- key = 'action'
- current = values[key]
- values[key] = max(current, action)
-
- return take
+ return state
-def is_cplusplus_compiler(name):
+def classify_source(filename, cplusplus=False):
+ """ Return the language from fille name extension. """
+
+ mapping = {
+ '.c': 'c++' if cplusplus else 'c',
+ '.i': 'c++-cpp-output' if cplusplus else 'c-cpp-output',
+ '.ii': 'c++-cpp-output',
+ '.m': 'objective-c',
+ '.mi': 'objective-c-cpp-output',
+ '.mm': 'objective-c++',
+ '.mii': 'objective-c++-cpp-output',
+ '.C': 'c++',
+ '.cc': 'c++',
+ '.CC': 'c++',
+ '.cp': 'c++',
+ '.cpp': 'c++',
+ '.cxx': 'c++',
+ '.c++': 'c++',
+ '.C++': 'c++',
+ '.txx': 'c++'
+ }
+
+ __, extension = os.path.splitext(os.path.basename(filename))
+ return mapping.get(extension)
+
+
+def cplusplus_compiler(name):
""" Returns true when the compiler name refer to a C++ compiler. """
match = re.match(r'^([^/]*/)*(\w*-)*(\w+\+\+)(-(\d+(\.\d+){0,3}))?$', name)
Index: tools/scan-build-py/libscanbuild/clang.py
===================================================================
--- tools/scan-build-py/libscanbuild/clang.py
+++ tools/scan-build-py/libscanbuild/clang.py
@@ -11,7 +11,7 @@
import subprocess
import logging
import re
-import shlex
+from libscanbuild.shell import decode
__all__ = ['get_version', 'get_arguments', 'get_checkers']
@@ -23,7 +23,7 @@
return lines.decode('ascii').splitlines()[0]
-def get_arguments(cwd, command):
+def get_arguments(command, cwd):
""" Capture Clang invocation.
Clang can be executed directly (when you just ask specific action to
@@ -41,10 +41,6 @@
raise Exception("output not found")
return last
- def strip_quotes(quoted):
- match = re.match(r'^\"([^\"]*)\"$', quoted)
- return match.group(1) if match else quoted
-
cmd = command[:]
cmd.insert(1, '-###')
logging.debug('exec command in %s: %s', cwd, ' '.join(cmd))
@@ -57,9 +53,9 @@
child.stdout.close()
child.wait()
if 0 == child.returncode:
- if re.match(r'^clang: error:', line):
+ if re.search(r'clang(.*): error:', line):
raise Exception(line)
- return [strip_quotes(x) for x in shlex.split(line)]
+ return decode(line)
else:
raise Exception(line)
@@ -77,7 +73,7 @@
'-Xclang', plugin]]
cmd = [clang, '--analyze'] + load + ['-x', language, '-']
pattern = re.compile(r'^-analyzer-checker=(.*)$')
- return [pattern.match(arg).group(1) for arg in get_arguments('.', cmd)
+ return [pattern.match(arg).group(1) for arg in get_arguments(cmd, '.')
if pattern.match(arg)]
result = set()
Index: tools/scan-build-py/libear/ear.c
===================================================================
--- tools/scan-build-py/libear/ear.c
+++ tools/scan-build-py/libear/ear.c
@@ -28,6 +28,7 @@
#include <string.h>
#include <unistd.h>
#include <dlfcn.h>
+#include <pthread.h>
#if defined HAVE_POSIX_SPAWN || defined HAVE_POSIX_SPAWNP
#include <spawn.h>
@@ -40,6 +41,27 @@
extern char **environ;
#endif
+#define ENV_OUTPUT "BUILD_INTERCEPT_TARGET_DIR"
+#ifdef APPLE
+# define ENV_FLAT "DYLD_FORCE_FLAT_NAMESPACE"
+# define ENV_PRELOAD "DYLD_INSERT_LIBRARIES"
+# define ENV_SIZE 3
+#else
+# define ENV_PRELOAD "LD_PRELOAD"
+# define ENV_SIZE 2
+#endif
+
+#define DLSYM(TYPE_, VAR_, SYMBOL_) \
+ union { \
+ void *from; \
+ TYPE_ to; \
+ } cast; \
+ if (0 == (cast.from = dlsym(RTLD_NEXT, SYMBOL_))) { \
+ perror("bear: dlsym"); \
+ exit(EXIT_FAILURE); \
+ } \
+ TYPE_ const VAR_ = cast.to;
+
typedef char const * bear_env_t[ENV_SIZE];
@@ -72,23 +94,12 @@
};
static int initialized = 0;
+static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
static void on_load(void) __attribute__((constructor));
static void on_unload(void) __attribute__((destructor));
-#define DLSYM(TYPE_, VAR_, SYMBOL_) \
- union { \
- void *from; \
- TYPE_ to; \
- } cast; \
- if (0 == (cast.from = dlsym(RTLD_NEXT, SYMBOL_))) { \
- perror("bear: dlsym"); \
- exit(EXIT_FAILURE); \
- } \
- TYPE_ const VAR_ = cast.to;
-
-
#ifdef HAVE_EXECVE
static int call_execve(const char *path, char *const argv[],
char *const envp[]);
@@ -124,16 +135,20 @@
*/
static void on_load(void) {
+ pthread_mutex_lock(&mutex);
#ifdef HAVE_NSGETENVIRON
environ = *_NSGetEnviron();
#endif
if (!initialized)
initialized = bear_capture_env_t(&initial_env);
+ pthread_mutex_unlock(&mutex);
}
static void on_unload(void) {
+ pthread_mutex_lock(&mutex);
bear_release_env_t(&initial_env);
initialized = 0;
+ pthread_mutex_unlock(&mutex);
}
@@ -373,6 +388,7 @@
if (!initialized)
return;
+ pthread_mutex_lock(&mutex);
const char *cwd = getcwd(NULL, 0);
if (0 == cwd) {
perror("bear: getcwd");
@@ -404,6 +420,7 @@
exit(EXIT_FAILURE);
}
free((void *)cwd);
+ pthread_mutex_unlock(&mutex);
}
/* update environment assure that chilren processes will copy the desired
Index: tools/scan-build-py/libear/config.h.in
===================================================================
--- tools/scan-build-py/libear/config.h.in
+++ tools/scan-build-py/libear/config.h.in
@@ -20,14 +20,3 @@
#cmakedefine HAVE_NSGETENVIRON
#cmakedefine APPLE
-
-#define ENV_OUTPUT "BUILD_INTERCEPT_TARGET_DIR"
-
-#ifdef APPLE
-# define ENV_FLAT "DYLD_FORCE_FLAT_NAMESPACE"
-# define ENV_PRELOAD "DYLD_INSERT_LIBRARIES"
-# define ENV_SIZE 3
-#else
-# define ENV_PRELOAD "LD_PRELOAD"
-# define ENV_SIZE 2
-#endif
Index: tools/scan-build-py/libear/__init__.py
===================================================================
--- /dev/null
+++ tools/scan-build-py/libear/__init__.py
@@ -0,0 +1,284 @@
+# -*- coding: utf-8 -*-
+# The LLVM Compiler Infrastructure
+#
+# This file is distributed under the University of Illinois Open Source
+# License. See LICENSE.TXT for details.
+"""
+. """
+
+import sys
+import os
+import os.path
+import re
+import logging
+
+__all__ = ['ear_library']
+
+
+def ear_library(compiler, dst_dir):
+ """ Returns the full path to the 'libear' library. """
+
+ try:
+ src_dir = os.path.dirname(os.path.realpath(__file__))
+ with make_context(src_dir) as context:
+ context.set_compiler(compiler)
+ context.set_language_standard('c99')
+ context.add_definitions(['-D_GNU_SOURCE'])
+
+ with Configure(context) as configure:
+ configure.check_function_exists('execve', 'HAVE_EXECVE')
+ configure.check_function_exists('execv', 'HAVE_EXECV')
+ configure.check_function_exists('execvpe', 'HAVE_EXECVPE')
+ configure.check_function_exists('execvp', 'HAVE_EXECVP')
+ configure.check_function_exists('execvP', 'HAVE_EXECVP2')
+ configure.check_function_exists('execl', 'HAVE_EXECL')
+ configure.check_function_exists('execlp', 'HAVE_EXECLP')
+ configure.check_function_exists('execle', 'HAVE_EXECLE')
+ configure.check_function_exists('posix_spawn',
+ 'HAVE_POSIX_SPAWN')
+ configure.check_function_exists('posix_spawnp',
+ 'HAVE_POSIX_SPAWNP')
+ configure.check_symbol_exists('_NSGetEnviron', 'crt_externs.h',
+ 'HAVE_NSGETENVIRON')
+ configure.write_by_template(
+ os.path.join(src_dir, 'config.h.in'),
+ os.path.join(dst_dir, 'config.h'))
+ with SharedLibrary('ear', context) as target:
+ target.add_include(dst_dir)
+ target.add_sources('ear.c')
+ target.link_against(context.dl_libraries())
+ target.link_against(['pthread'])
+ target.build_release(dst_dir)
+ return os.path.join(dst_dir, target.name)
+
+ except Exception:
+ logging.info("Could not build interception library.", exc_info=True)
+ return None
+
+
+def execute(cmd, *args, **kwargs):
+ """ Make subprocess execution silent. """
+
+ import subprocess
+ kwargs.update({'stdout': subprocess.PIPE, 'stderr': subprocess.STDOUT})
+ return subprocess.check_call(cmd, *args, **kwargs)
+
+
+class TemporaryDirectory(object):
+ """ This function creates a temporary directory using mkdtemp() (the
+ supplied arguments are passed directly to the underlying function).
+ The resulting object can be used as a context manager. On completion
+ of the context or destruction of the temporary directory object the
+ newly created temporary directory and all its contents are removed
+ from the filesystem. """
+
+ def __init__(self, **kwargs):
+ from tempfile import mkdtemp
+ self.name = mkdtemp(**kwargs)
+
+ def __enter__(self):
+ return self.name
+
+ def __exit__(self, _type, _value, _traceback):
+ self.cleanup()
+
+ def cleanup(self):
+ from shutil import rmtree
+ if self.name is not None:
+ rmtree(self.name)
+
+
+class Context:
+ """ Abstract class to represent different toolset. """
+
+ def __init__(self, src_dir):
+ self.src_dir = src_dir
+ self.compiler = None
+ self.c_flags = []
+
+ def __enter__(self):
+ """ declared to work 'with'. """
+ return self
+
+ def __exit__(self, _type, _value, _traceback):
+ """ declared to work 'with'. """
+ pass
+
+ def set_compiler(self, compiler):
+ """ part of public interface """
+ self.compiler = compiler
+
+ def set_language_standard(self, standard):
+ """ part of public interface """
+ self.c_flags.append('-std=' + standard)
+
+ def add_definitions(self, defines):
+ """ part of public interface """
+ self.c_flags.extend(defines)
+
+ def dl_libraries(self):
+ pass
+
+ def shared_library_name(self, name):
+ pass
+
+ def shared_library_c_flags(self, release):
+ extra = ['-DNDEBUG', '-O3'] if release else []
+ return extra + ['-fPIC'] + self.c_flags
+
+ def shared_library_ld_flags(self, release, name):
+ pass
+
+
+class DarwinContext(Context):
+ def __init__(self, src_dir):
+ Context.__init__(self, src_dir)
+
+ def dl_libraries(self):
+ return []
+
+ def shared_library_name(self, name):
+ return 'lib' + name + '.dylib'
+
+ def shared_library_ld_flags(self, release, name):
+ extra = ['-dead_strip'] if release else []
+ return extra + ['-dynamiclib', '-install_name', '@rpath/' + name]
+
+
+class UnixContext(Context):
+ def __init__(self, src_dir):
+ Context.__init__(self, src_dir)
+
+ def dl_libraries(self):
+ return []
+
+ def shared_library_name(self, name):
+ return 'lib' + name + '.so'
+
+ def shared_library_ld_flags(self, release, name):
+ extra = [] if release else []
+ return extra + ['-shared', '-Wl,-soname,' + name]
+
+
+class LinuxContext(UnixContext):
+ def __init__(self, src_dir):
+ UnixContext.__init__(self, src_dir)
+
+ def dl_libraries(self):
+ return ['dl']
+
+
+def make_context(src_dir):
+ platform = sys.platform
+ if platform in {'win32', 'cygwin'}:
+ raise RuntimeError('not implemented on this platform')
+ elif platform == 'darwin':
+ return DarwinContext(src_dir)
+ elif platform in {'linux', 'linux2'}:
+ return LinuxContext(src_dir)
+ else:
+ return UnixContext(src_dir)
+
+
+class Configure:
+ def __init__(self, context):
+ self.ctx = context
+ self.results = {'APPLE': sys.platform == 'darwin'}
+
+ def __enter__(self):
+ return self
+
+ def __exit__(self, _type, _value, _traceback):
+ pass
+
+ def _try_to_compile_and_link(self, source):
+ try:
+ with TemporaryDirectory() as work_dir:
+ src_file = 'check.c'
+ with open(os.path.join(work_dir, src_file), 'w') as handle:
+ handle.write(source)
+
+ execute([self.ctx.compiler, src_file] + self.ctx.c_flags,
+ cwd=work_dir)
+ return True
+ except Exception:
+ return False
+
+ def check_function_exists(self, function, name):
+ template = "int FUNCTION(); int main() { return FUNCTION(); }"
+ source = template.replace("FUNCTION", function)
+
+ logging.debug('Checking function %s', function)
+ found = self._try_to_compile_and_link(source)
+ logging.debug('Checking function %s -- %s', function,
+ 'found' if found else 'not found')
+ self.results.update({name: found})
+
+ def check_symbol_exists(self, symbol, include, name):
+ template = """#include <INCLUDE>
+ int main() { return ((int*)(&SYMBOL))[0]; }"""
+ source = template.replace('INCLUDE', include).replace("SYMBOL", symbol)
+
+ logging.debug('Checking symbol %s', symbol)
+ found = self._try_to_compile_and_link(source)
+ logging.debug('Checking symbol %s -- %s', symbol,
+ 'found' if found else 'not found')
+ self.results.update({name: found})
+
+ def write_by_template(self, template, output):
+ def transform(line, definitions):
+
+ pattern = re.compile(r'^#cmakedefine\s+(\S+)')
+ m = pattern.match(line)
+ if m:
+ key = m.group(1)
+ if key not in definitions or not definitions[key]:
+ return '/* #undef {} */\n'.format(key)
+ else:
+ return '#define {}\n'.format(key)
+ return line
+
+ with open(template, 'r') as src_handle:
+ logging.debug('Writing config to %s', output)
+ with open(output, 'w') as dst_handle:
+ for line in src_handle:
+ dst_handle.write(transform(line, self.results))
+
+
+class SharedLibrary:
+ def __init__(self, name, context):
+ self.name = context.shared_library_name(name)
+ self.ctx = context
+ self.inc = []
+ self.src = []
+ self.lib = []
+
+ def __enter__(self):
+ return self
+
+ def __exit__(self, _type, _value, _traceback):
+ pass
+
+ def add_include(self, directory):
+ self.inc.extend(['-I', directory])
+
+ def add_sources(self, source):
+ self.src.append(source)
+
+ def link_against(self, libraries):
+ self.lib.extend(['-l' + lib for lib in libraries])
+
+ def build_release(self, directory):
+ for src in self.src:
+ logging.debug('Compiling %s', src)
+ execute(
+ [self.ctx.compiler, '-c', os.path.join(self.ctx.src_dir, src),
+ '-o', src + '.o'] + self.inc +
+ self.ctx.shared_library_c_flags(True),
+ cwd=directory)
+ logging.debug('Linking %s', self.name)
+ execute(
+ [self.ctx.compiler] + [src + '.o' for src in self.src] +
+ ['-o', self.name] + self.lib +
+ self.ctx.shared_library_ld_flags(True, self.name),
+ cwd=directory)
Index: tools/scan-build-py/libear/CMakeLists.txt
===================================================================
--- tools/scan-build-py/libear/CMakeLists.txt
+++ tools/scan-build-py/libear/CMakeLists.txt
@@ -29,10 +29,21 @@
check_function_exists(posix_spawnp HAVE_POSIX_SPAWNP)
check_symbol_exists(_NSGetEnviron crt_externs.h HAVE_NSGETENVIRON)
+find_package(Threads REQUIRED)
+
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/config.h.in ${CMAKE_CURRENT_BINARY_DIR}/config.h)
include_directories(${CMAKE_CURRENT_BINARY_DIR})
add_library(ear SHARED ear.c)
target_link_libraries(ear ${CMAKE_DL_LIBS})
+if(THREADS_HAVE_PTHREAD_ARG)
+ set_property(TARGET ear PROPERTY COMPILE_OPTIONS "-pthread")
+ set_property(TARGET ear PROPERTY INTERFACE_COMPILE_OPTIONS "-pthread")
+endif()
+if(CMAKE_THREAD_LIBS_INIT)
+ target_link_libraries(ear "${CMAKE_THREAD_LIBS_INIT}")
+endif()
+
+set_target_properties(ear PROPERTIES INSTALL_RPATH "@loader_path/${CMAKE_INSTALL_LIBDIR}")
install(TARGETS ear LIBRARY DESTINATION libscanbuild)
Index: tools/scan-build-py/README.md
===================================================================
--- tools/scan-build-py/README.md
+++ tools/scan-build-py/README.md
@@ -1,5 +1,4 @@
[](https://travis-ci.org/rizsotto/Beye)
-[](https://coveralls.io/r/rizsotto/Beye?branch=master)
Build EYE
=========
_______________________________________________
cfe-commits mailing list
[email protected]
http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits