Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Regenerate the compilation database only if the content has changed #3735

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 23 additions & 73 deletions SCons/Tool/compilation_db.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,19 +45,6 @@
__COMPILATION_DB_ENTRIES = []


# We make no effort to avoid rebuilding the entries. Someday, perhaps we could and even
# integrate with the cache, but there doesn't seem to be much call for it.
class __CompilationDbNode(SCons.Node.Python.Value):
def __init__(self, value):
SCons.Node.Python.Value.__init__(self, value)
self.Decider(changed_since_last_build_node)


def changed_since_last_build_node(child, target, prev_ni, node):
""" Dummy decider to force always building"""
return True


def make_emit_compilation_DB_entry(comstr):
"""
Effectively this creates a lambda function to capture:
Expand All @@ -79,74 +66,40 @@ def emit_compilation_db_entry(target, source, env):
:return: target(s), source(s)
"""

dbtarget = __CompilationDbNode(source)

entry = env.__COMPILATIONDB_Entry(
target=dbtarget,
source=[],
__COMPILATIONDB_UOUTPUT=target,
__COMPILATIONDB_USOURCE=source,
__COMPILATIONDB_UACTION=user_action,
__COMPILATIONDB_ENV=env,
)

# TODO: Technically, these next two lines should not be required: it should be fine to
# cache the entries. However, they don't seem to update properly. Since they are quick
# to re-generate disable caching and sidestep this problem.
env.AlwaysBuild(entry)
env.NoCache(entry)

__COMPILATION_DB_ENTRIES.append(dbtarget)
entry = {
"directory": env.Dir("#").abspath,
"command": user_action.strfunction(
target=target, source=source, env=env
),
"file": {
"abspath": source[0].srcnode().abspath,
"path": source[0].srcnode().path
},
"output": {
"abspath": target[0].abspath,
"path": target[0].path
}
}

__COMPILATION_DB_ENTRIES.append(entry)

return target, source

return emit_compilation_db_entry


def compilation_db_entry_action(target, source, env, **kw):
"""
Create a dictionary with evaluated command line, target, source
and store that info as an attribute on the target
(Which has been stored in __COMPILATION_DB_ENTRIES array
:param target: target node(s)
:param source: source node(s)
:param env: Environment for use building this node
:param kw:
:return: None
"""

command = env["__COMPILATIONDB_UACTION"].strfunction(
target=env["__COMPILATIONDB_UOUTPUT"],
source=env["__COMPILATIONDB_USOURCE"],
env=env["__COMPILATIONDB_ENV"],
)

entry = {
"directory": env.Dir("#").abspath,
"command": command,
"file": env["__COMPILATIONDB_USOURCE"][0],
"output": env['__COMPILATIONDB_UOUTPUT'][0]
}

target[0].write(entry)


def write_compilation_db(target, source, env):
entries = []

use_abspath = env['COMPILATIONDB_USE_ABSPATH'] in [True, 1, 'True', 'true']

for s in __COMPILATION_DB_ENTRIES:
entry = s.read()
source_file = entry['file']
output_file = entry['output']

for entry in __COMPILATION_DB_ENTRIES:
if use_abspath:
source_file = source_file.srcnode().abspath
output_file = output_file.abspath
source_file = entry['file']['abspath']
output_file = entry['output']['abspath']
else:
source_file = source_file.srcnode().path
output_file = output_file.path
source_file = entry['file']['path']
output_file = entry['output']['path']

path_entry = {'directory': entry['directory'],
'command': entry['command'],
Expand All @@ -162,7 +115,8 @@ def write_compilation_db(target, source, env):


def scan_compilation_db(node, env, path):
return __COMPILATION_DB_ENTRIES
value = json.dumps(__COMPILATION_DB_ENTRIES, sort_keys=True)
return [SCons.Node.Python.Value(value)]


def compilation_db_emitter(target, source, env):
Expand Down Expand Up @@ -230,10 +184,6 @@ def generate(env, **kwargs):
[emitter, make_emit_compilation_DB_entry(command), ]
)

env["BUILDERS"]["__COMPILATIONDB_Entry"] = SCons.Builder.Builder(
action=SCons.Action.Action(compilation_db_entry_action, None),
)

env["BUILDERS"]["CompilationDatabase"] = SCons.Builder.Builder(
action=SCons.Action.Action(write_compilation_db, "$COMPILATIONDB_COMSTR"),
target_scanner=SCons.Scanner.Scanner(
Expand Down
67 changes: 47 additions & 20 deletions test/CompilationDatabase/basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,27 @@
import os.path
import TestSCons

def get_db(workdir, use_abspath, ccflags):
if use_abspath:
prefix = workdir
else:
prefix = ''
content = """[
{
"command": "%s mygcc.py cc -o test_main.o %s test_main.c",
"directory": "%s",
"file": "%s",
"output": "%s"
}
]""" % (sys.executable,
' '.join(['-c'] + ccflags),
workdir,
os.path.join(prefix, 'test_main.c'),
os.path.join(prefix, 'test_main.o'))
if sys.platform == 'win32':
content = content.replace('\\', '\\\\')
return content

test = TestSCons.TestSCons()

test.file_fixture('mylink.py')
Expand All @@ -56,35 +77,41 @@
'compile_commands_over_abs_1.json',
]

example_rel_file = """[
{
"command": "%s mygcc.py cc -o test_main.o -c test_main.c",
"directory": "%s",
"file": "test_main.c",
"output": "test_main.o"
}
]""" % (sys.executable, test.workdir)

if sys.platform == 'win32':
example_rel_file = example_rel_file.replace('\\', '\\\\')
stat_info = {}

example_rel_file = get_db(test.workdir, False, [])

for f in rel_files:
# print("Checking:%s" % f)
test.must_exist(f)
test.must_match(f, example_rel_file, mode='r')
stat_info[f] = os.stat(f)

example_abs_file = """[
{
"command": "%s mygcc.py cc -o test_main.o -c test_main.c",
"directory": "%s",
"file": "%s",
"output": "%s"
}
]""" % (sys.executable, test.workdir, os.path.join(test.workdir, 'test_main.c'), os.path.join(test.workdir, 'test_main.o'))

if sys.platform == 'win32':
example_abs_file = example_abs_file.replace('\\', '\\\\')
example_abs_file = get_db(test.workdir, True, [])

for f in abs_files:
test.must_exist(f)
test.must_match(f, example_abs_file, mode='r')
stat_info[f] = os.stat(f)


test.run()
for f in stat_info.keys():
test.fail_test(stat_info[f] != os.stat(f))


test.run(arguments='CCFLAGS=-g')

example_rel_file = get_db(test.workdir, False, ['-g'])

for f in rel_files:
test.must_exist(f)
test.must_match(f, example_rel_file, mode='r')


example_abs_file = get_db(test.workdir, True, ['-g'])

for f in abs_files:
test.must_exist(f)
Expand Down
1 change: 1 addition & 0 deletions test/CompilationDatabase/fixture/SConstruct
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ env = Environment(
LINKFLAGS=[],
CC='$PYTHON mygcc.py cc',
CXX='$PYTHON mygcc.py c++',
CCFLAGS=ARGUMENTS.get('CCFLAGS', ''),
tools=['gcc','g++','gnulink'],
)
env.Tool('compilation_db')
Expand Down
2 changes: 1 addition & 1 deletion test/fixture/mygcc.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

compiler = sys.argv[1]
clen = len(compiler) + 1
opts, args = getopt.getopt(sys.argv[2:], 'co:xf:K:')
opts, args = getopt.getopt(sys.argv[2:], 'co:xf:gK:')
for opt, arg in opts:
if opt == '-o':
out = arg
Expand Down