SCons generates an incomplete dependency tree if I use a custom decider - scons

I'm trying to build a flex-bison parser with SCons, using a custom decider. (You can learn more about the decider in this question, although, the details probably aren't too relevant to the current problem.)
Files:
My SConstruct:
env = DefaultEnvironment()
deciderEnv = env.Clone()
def source_and_target_decider(dependency, target, prev_ni, repo_node=None):
src_old_csig = prev_ni.csig if hasattr(prev_ni, 'csig') else None
src_new_csig = dependency.get_csig()
tgt_stored_info = target.get_stored_info()
tgt_old_csig = tgt_stored_info.ninfo.csig if hasattr(tgt_stored_info.ninfo, 'csig') else None
tgt_new_csig = target.get_csig()
return src_new_csig != src_old_csig or tgt_new_csig != tgt_old_csig
deciderEnv.Decider(source_and_target_decider)
deciderEnv['YACCFLAGS'] = ['-d']
deciderEnv['YACCHXXFILESUFFIX'] = '.hh'
bison_source = deciderEnv.CXXFile('src/test.yy.cc', 'src/test.yy')[0]
deciderEnv['LEXFLAGS'] = [f'--header-file=${{SOURCE}}.hh']
flex_source = deciderEnv.CXXFile('src/test.ll.cc', 'src/test.ll')[0]
Program('test', [bison_source, flex_source])
src/test.yy:
%language "c++"
%code {
int yylex(int*);
}
%token X
%%
everything: X {}
and src/test.ll:
%{
#include "./test.yy.hh"
%}
%option c++
%%
. { return yy::parser::token::X; }
Result:
I run a command scons ./src/test.ll.o.
The first time you run it, it works correctly.
flex --header-file=src/test.ll.hh -t src/test.ll > src/test.ll.cc
bison -d -o src/test.yy.cc src/test.yy
g++ -o src/test.ll.o -c src/test.ll.cc
SCons seems to understand that it needs to generate src/test.yy.hh using bison before it compiles the src/test.ll.o because src/test.ll.cc #includes that header.
However, if I then remove .sconsign.dblite (leaving the rest of the files intact), SCons seems to lose that understanding for the duration of the next build.
Running scons ./src/test.ll.o yields:
flex --header-file=src/test.ll.hh -t src/test.ll > src/test.ll.cc
g++ -o src/test.ll.o -c src/test.ll.cc
and only after calling it a second time it decides to build the src/test.yy.hh:
bison -d -o src/test.yy.cc src/test.yy
g++ -o src/test.ll.o -c src/test.ll.cc
Dependency tree:
Normally the dependency tree (scons --tree=all src/test.ll.o) looks like this:
+-src/test.ll.o
+-src/test.ll.cc
| +-src/test.ll
| +-/bin/flex
+-src/test.yy.hh
| +-src/test.yy
| +-/bin/bison
+-/bin/g++
However, right after deleting the .sconsign.dblite it is incomplete:
+-src/test.ll.o
+-src/test.ll.cc
| +-src/test.ll
| +-/bin/flex
+-/bin/g++
Further observations:
The last line of the custom decider seems to be the culprit.
Specifically, the part or tgt_new_csig != tgt_old_csig. If you remove this part, the problem no longer occurs.
However, I have no clue why this particular piece of code breaks the build.
Any idea how to fix this?

The sconsign is where SCons stores info about previous builds.
If you remove it, then SCons will not have that info and will rebuild everything.
Because in your decider you are setting:
tgt_old_csig = tgt_stored_info.ninfo.csig if hasattr(tgt_stored_info.ninfo, 'csig') else None
and then comparing None to the current csig, which will not be equal and require a rebuild.
You'd likely need to change as follows to work when you're explicitly removing the source of the tgt_stored_info (the sconsign file).
def source_and_target_decider(dependency, target, prev_ni, repo_node=None):
src_old_csig = prev_ni.csig if hasattr(prev_ni, 'csig') else None
src_new_csig = dependency.get_csig()
tgt_stored_info = target.get_stored_info()
tgt_old_csig = tgt_stored_info.ninfo.csig if hasattr(tgt_stored_info.ninfo, 'csig') else None
tgt_new_csig = target.get_csig()
if tgt_old_csig:
return src_new_csig != src_old_csig or tgt_new_csig != tgt_old_csig
else:
return src_new_csig != src_old_csig
Why are you removing .sconsign.dblite?

Related

Internal Error: no cycle found in Scons Build

When I build my Sconstruct file, I am getting the below error.
scons: *** Found dependency cycle(s):
build/sselser/sselConfigArgs.h -> build/sselser/sselConfigArgs.h
Internal Error: no cycle found for node build/sselser/sselMain (<SCons.Node.FS.File instance at 0x9f61e8>) in state pending
Internal Error: no cycle found for node build/sselser/sselMain.o (<SCons.Node.FS.File instance at 0x9f2e68>) in state pending
File "/nfs/scons/scons-1.3.0/lib/scons-1.3.0/SCons/Taskmaster.py", line 1026, in cleanup
I guess this is due to dependency of sselMain in sselTransorm as the error occurs during the build of sselTransform directory.
Makefile in sselTransform:
UNIT_SUPPORT_FILES += ../sselser/sselMain intest ../../../make/Makenv
MDE_SUPPORT_FILES += ../sselser/sselMain intest ../../../make/Makenv
I need to add the same in Sconscript of sselTransform directory to resolve this issue.
How to resolve this issue?
Sconscript:
#Set CPPPATH, RPATH, DEFINES and CCFLAGS
env = Environment(CPPPATH =['.','../sselTransform','../sselSm','../sselSRC'],
RPATH = ['/l-n/app/colr/lib/infra/SunOS5.10/WS12.0'],CPPDEFINES = ['THREADSAFE','_RWSTD_SOLARIS_THREADS','_SVID_GETTO
D','DEBUG','sun5'],CCFLAGS = ['library=rwtools7_std','features=no%tmplife','-pta','-mt','-xdebugformat=stabs','-g0','-xildoff'])
env['CXX']=CXX
Src = Split('sselManager.C PromoNotifyMgr.C ')
env.StaticLibrary('libSselser-g0.a',Src)
Src1 = Split('sselMain.C sselManager.o PromoNotifyMgr.o ')
env.Program('sselMain',Src1)
configfile = 'sselConfigArgs.h'
CONFIG_PATH = '../../build/include/'
CONFIG=CONFIG_PATH+configfile
env.Command(CONFIG,configfile,
[Copy('$TARGET', '$SOURCE'),
Chmod('$TARGET', 0444)])
Sconstruct:
SConscript('src/ssel/sselser/SConscript',variant_dir='build/sselser',duplicate=0,exports='env')
Try this?
Notes:
I'm saving the build objects for your two source files and using those in both the program and static library.
I've added the target dir you're copying the header file to earlier in the CPPPATH.
You could have skipped the variables configfile, CONFIG_PATH, CONFIG and just used the strings in your Command.
You are using a VERY old version of SCons. If you're limited to python 2.7 please try using SCons 3.0.1? If you're not and can use Python 3.6, then try using SCons 4.3.0.
#Set CPPPATH, RPATH, DEFINES and CCFLAGS
env = Environment(
CPPPATH =['.','../include','../sselTransform','../sselSm','../sselSRC'],
RPATH = ['/l-n/app/colr/lib/infra/SunOS5.10/WS12.0'],
CPPDEFINES = ['THREADSAFE','_RWSTD_SOLARIS_THREADS','_SVID_GETTOD','DEBUG','sun5'],
CCFLAGS = ['library=rwtools7_std','features=no%tmplife','-pta','-mt','-xdebugformat=stabs','-g0','-xildoff'])
env['CXX']=CXX
Src = ['sselManager.C','PromoNotifyMgr.C']
objects = []
for s in Src:
objects.extend(env.StaticObject(s))
env.StaticLibrary('Sselser-g0',objects)
Src1 = ['sselMain.C'] + objects
env.Program('sselMain', Src1)
configfile = 'sselConfigArgs.h'
CONFIG_PATH = '../include/'
CONFIG=CONFIG_PATH+configfile
env.Command(CONFIG, configfile,
[Copy('$TARGET', '$SOURCE'),
Chmod('$TARGET', 0444)])

Scons -u option doesn't work?

Seems -u doesn't work on for me ( I am using scons-2.3.6).
To simplify the context, you can imagine my project structure like,
+root
+project
- bar.vcxproj (generated vs project)
-SConstruct
-bar.c
Inside SConstruct, I have put code like:
env_base = Environment()
...
env_base.StaticLibrary(target = 'bar', source = ['bar.c'])
...
If I execute command "scons" in root folder, everything works perfectly.
But If I execute command "scons -u" in project folder, scons can find my SConstruct up in root folder, but no file get compiled.
BTW : The reason for me to execute "scons -u" in project folder is because I want to put the generated vsproj in projet folder and use BuildCommandLine to compile the project.
I guess I didn't use "-u" correctly, what will be the elegant solution for my situation?
1st edit:
As bdbaddog asked, I have put the SConstruct here:
def BuildConfig(env, config):
env.Append(CCFLAGS = '/W 4')
env.Append(CCFLAGS = '/WX')
if config == "debug":
env.Append(CCFLAGS = '/DEBUG')
#env.Append(CCFLAGS = '-Zi /Fd${TARGET}.pdb')
env.Append(CCFLAGS = '/Z7')
elif config == "release":
pass
env_base = Environment()
lib = env_base.StaticLibrary(target = 'bar', source = ['bar.c'])
opts=Variables()
opts.Add('target', 'Compile Target (debug/release).', "debug")
# there is more in my project....
opts.Update(env_base) # update environment
# here I want to use my own command to build the project, so it can support different build option that is defined by me.
env_base['MSVSBUILDCOM'] = "scons -u target=$(Configuration)"
target = env_base["target"]
BuildConfig(env_base, env_base['target'])
env_base.MSVSProject(target = "project\\bar" + env_base['MSVSPROJECTSUFFIX'],
srcs = ["..\\bar.c"],
incs = [],
localincs = "",
resources = "",
misc = "",
buildtarget = lib,
variant = ['debug'],
auto_build_solution=0)
SCons only builds files under the current directory by default.
If you you wanted to only build files in a certain directory (for which there are rules that build the targets there), you can invoke SCons as follows:
scons the_target_directory_I_want_to_build
Though this may cause sources for targets in that directory to also be built.

vim youcompleteme can't find cstdint

I've encountered a problem trying to set up a C++ project on my Mac (Yosemite with Xcode 6) using CMake with Unix style Makefiles and vim with the youcompleteme plugin (I'm a Linux veteran and Mac newbie so I prefer this setup to Xcode). The code builds and runs but youcompleteme throws some bogus errors which I think boil down to it not being able to find the <cstdint> header.
I've just tried it on Linux too, and had the same problem.
I've configured .ycm_extra_conf.py to use a compile_commands.json generated by cake. The "command" lines in compile_commands.json use these flags:
"command": "/usr/bin/c++ -std=c++11 -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.10.sdk -F/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.10.sdk/System/Library/Frameworks -I/usr/local/include -I/Users/tony/Dev/cow/jni -I/Users/tony/Library/Frameworks/SDL2.framework/Headers -Wall -Wextra -Wunused -F/Users/tony/Library/Frameworks -o ...
There doesn't seem to be an explicit reference there to any directory containing stdint as a direct parent.
Is there a way I can get youcompleteme to do its stuff with libclang in such a way that it can find the directory implicitly, which seems to work when running c++ on the command line? Or what's the best way to get cmake to add an appropriate system header path without hardwiring it? I want my CMakeLists.txt to be portable and to be able to cope with toolchain upgrades.
My .ycm_extra_conf.py was pretty much a copy of the supplied example modified slightly to find compile_commands.json where I put it.
As #ladislas said, YCM needs to be explicitly pointed to all relevant include directories as libclang won't use the same implicit location a normal compiler driver invocation (i.e. clang++ from the command line) would use.
What I usually do, on OSX, is to let YCM know about Xcode's libc++ headers with something like (in .ycm_extra_conf.py):
import os
import ycm_core
import subprocess
xcode_cpp11headers = subprocess.Popen("xcode-select -p", stdout = subprocess.PIPE, shell=True).communicate()[0].rstrip('\n') + '/Toolchains/XcodeDefault.xctoolchain/usr/include/c++/v1'
.
.
flags = [
.
.
'-isystem',
xcode_cpp11headers,
.
.
]
The "xcode_cpp11headers" variable is populated with the proper path depending on the location of your current Xcode's installation, and you can change it accordingly if you want to use say the commandline-tools version of libc++ (i.e. the includes are in /Library/Developer/CommandLineTools/usr/include/c++/v1) or a source distribution of libc++ if you have compiled your own.
Of course this is platform dependant and either you provide a platform specific .ycm_extra_conf.py alongside your project, or you can populate that variable differently with some extra python code accordingly to the current platform.
You must add all the paths where YCM needs to look for your sources, libraries, and so on.
It does not work recursively, so it's a little bit cumbersome at first but is should not change once set for your project.
As an example, here is mine for an Arduino project :
https://github.com/ladislas/Bare-Arduino-Project/blob/master/.ycm_extra_conf.py#L21
Hope this helps!
EDIT - 2015/01/08
The solution of #abigagli is very elegant! I too use something like this to parse my lib directory and look for .h files to add their path to flags.
Here it is if it can be useful :) http://git.io/IiR1JA
As I found out from the above answers, YCM needs to be told the compiler's system include paths which are usually implicit in other ways of using the compiler. I added a function GetSystemIncludePaths() to .ycm_extra_conf.py to discover and cache these paths portably. Here's the full file with comments and irrelevant content of flags list snipped. The original is Copyright (C) 2014 Google Inc with a GPL2+ licence:
import subprocess, os
import ycm_core
flags = []
def DirectoryOfThisScript():
return os.path.dirname( os.path.abspath( __file__ ) )
compilation_database_folder = os.path.abspath(
os.path.join(DirectoryOfThisScript(), 'build-make'))
if os.path.exists( compilation_database_folder ):
database = ycm_core.CompilationDatabase( compilation_database_folder )
else:
database = None
SOURCE_EXTENSIONS = [ '.cpp', '.cxx', '.cc', '.c', '.m', '.mm' ]
def MakeRelativePathsInFlagsAbsolute( flags, working_directory ):
if not working_directory:
return list( flags )
new_flags = []
make_next_absolute = False
path_flags = [ '-isystem', '-I', '-iquote', '--sysroot=' ]
for flag in flags:
new_flag = flag
if make_next_absolute:
make_next_absolute = False
if not flag.startswith( '/' ):
new_flag = os.path.join( working_directory, flag )
for path_flag in path_flags:
if flag == path_flag:
make_next_absolute = True
break
if flag.startswith( path_flag ):
path = flag[ len( path_flag ): ]
new_flag = path_flag + os.path.join( working_directory, path )
break
if new_flag:
new_flags.append( new_flag )
return new_flags
def IsHeaderFile( filename ):
extension = os.path.splitext( filename )[ 1 ]
return extension in [ '.h', '.hxx', '.hpp', '.hh' ]
def GetCompilationInfoForFile( filename ):
if IsHeaderFile( filename ):
basename = os.path.splitext( filename )[ 0 ]
for extension in SOURCE_EXTENSIONS:
replacement_file = basename + extension
if os.path.exists( replacement_file ):
compilation_info = database.GetCompilationInfoForFile(
replacement_file )
if compilation_info.compiler_flags_:
return compilation_info
return None
return database.GetCompilationInfoForFile( filename )
def GetSystemIncludePaths():
cache = os.path.join(DirectoryOfThisScript(), ".ycm_sys_incs")
if os.path.exists(cache):
fp = open(cache, 'r')
flags = fp.readlines()
fp.close()
flags = [s.strip() for s in flags]
else:
devnull = open(os.devnull, 'r')
child = subprocess.Popen(["/usr/bin/cpp", "-xc++", "-v"],
stdin = devnull, stderr = subprocess.PIPE)
output = child.communicate()[1].split('\n')
devnull.close()
flags = []
status = 0
for l in output:
l = l.strip()
if l == '#include "..." search starts here:':
status = 1
elif l == '#include <...> search starts here:':
status = 2
elif status:
if l == 'End of search list.':
break
elif l.endswith('(framework directory)'):
continue
elif status == 1:
flags.append('-I')
elif status == 2:
flags.append('-isystem')
flags.append(os.path.normpath(l))
fp = open(cache, 'w')
fp.write('\n'.join(flags))
fp.close()
return flags
def FlagsForFile( filename, **kwargs ):
if database:
compilation_info = GetCompilationInfoForFile( filename )
if not compilation_info:
return None
final_flags = MakeRelativePathsInFlagsAbsolute(
compilation_info.compiler_flags_,
compilation_info.compiler_working_dir_ )
sys_incs = GetSystemIncludePaths()
if sys_incs:
final_flags += sys_incs
else:
relative_to = DirectoryOfThisScript()
final_flags = MakeRelativePathsInFlagsAbsolute( flags, relative_to )
return {
'flags': final_flags,
'do_cache': True
}

SCONS: How do I carry on an action on a target in place

Let's say I want to strip all the debug symbols in the shared libraries that I build whiling keeping the original file name.
I tried to add an command in the method:
def mySharedLibrary(self, *args, **kwargs):
# do some common work for every shared library like add a soname or append some lib files to LIBS parameter
target = SharedLibary(*args, **kwargs)
target = env.Command(target,target, "objcopy --strip-debug ${SOURCE}")
return target
I get this error: two different method was given to the same target,
I guess it's because the two targets returned by env.Command and SharedLibrary are exactly the same name.
Any ideas to do this?
Thanks in advance!
I had the same problem and got the same error. What I had to do was to create an intermediate target/library. The intermediate and final targets each had their own library name, so SCons doesnt get confused.
You could probably do something like the following:
env.SharedLibrary(target = 'namePreStrip', source = 'yourSource')
env.Command(target = 'name', source = 'namePreStrip', 'objcopy...')
I used objcopy to build a library out of several libraries. Here's the actual source code I implemented:
#
# Build an object file out of several other source files, objects, and libraries
# Optionally execute objcopy on the resulting library, depending if objcopyFlags
# has been populated
#
# env - SCons Environment used to build, Mandatory arg
# target - resulting library name, without LIBPREFIX and LIBSUFFIX, ej 'nsp2p',
# Mandatory arg
# sourceFiles - list of '.cc' files that will be compiled and included in the
# resulting lib, Optional arg
# objects - list of already compiled object files to be included in resulting lib,
# Optional arg
# libraries - list of libraries to be included in resulting lib, Optional arg
# objcopyFlags - list of flags to pass to objcopy command. objcopy will only
# be executed if this list is populated, Optional arg
#
# One of [sourceFiles, objects, or libraries] must be specified, else nothing
# will be performed
#
# Not using a custom builder because I dont like the way SCons prints the
# entire command each time its called, even if its not going to actually
# build anything AND I need more method args than provided by custom builders
#
def buildWholeArchive(self, env, target, sourceFiles, objects, libraries, objcopyFlags):
if len(sourceFiles) == 0 and len(objects) == 0 and len(libraries) == 0:
print "Incorrect use of buildWholeArchive, at least one of [sourceFiles | objects | librarires] must be specified, no build action will be performed"
return None
# Compile each source file
objNodes = []
if len(sourceFiles) > 0:
objNodes = env.Object(source = sourceFiles)
cmdList = []
cmdList.append(env['CXX'])
cmdList.append('-nostdlib -r -o $TARGET -Wl,--whole-archive')
for obj in objNodes:
cmdList.append(env.File(obj).abspath)
for obj in objects:
cmdList.append(env.File(obj).abspath)
for lib in libraries:
cmdList.append(lib)
cmdList.append('-Wl,--no-whole-archive')
cmd = ' '.join(cmdList)
libTarget = '%s%s%s' % (env['LIBPREFIX'], target, env['LIBSUFFIX'])
if len(objcopyFlags) > 0:
# First create the library, then run objcopy on it
objTarget = '%s%s_preObjcopy%s' % (env['LIBPREFIX'], target, env['LIBSUFFIX'])
preObjcopyTarget = env.Command(target = objTarget, source = [], action = cmd)
env.Depends(preObjcopyTarget, [objNodes, sourceFiles, objects, libraries])
objCmdList = [env['OBJCOPY']]
objCmdList.extend(objcopyFlags)
objCmdList.append('$SOURCE $TARGET')
objcopyCmd = ' '.join(objCmdList)
archiveTarget = env.Command(target = libTarget, source = preObjcopyTarget, action = objcopyCmd)
else:
# Just create the library
archiveTarget = env.Command(target = libTarget, source = [], action = cmd)
env.Depends(archiveTarget, [objNodes, sourceFiles, objects, libraries])
return archiveTarget
And here is how I called it:
sourceFiles = ['file1.cc', 'file2.cc']
libSource = []
if 'OcteonArchitecture' in env:
libSource.append(lib1)
libSource.append(lib2)
libSource.append(lib3)
objcopy = []
if 'OcteonArchitecture' in env:
objcopy.extend([
'--redefine-sym calloc=ns_calloc',
'--redefine-sym free=ns_free',
'--redefine-sym malloc=ns_malloc',
'--redefine-sym realloc=ns_realloc'])
archiveTarget = clonedEnv.buildWholeArchive(target = libName,
sourceFiles = sourceFiles,
objects = [],
libraries = libSource,
objcopyFlags = objcopy)
env.Alias('libMyLib', archiveTarget)

Using scons with Keil compiler/linker

I'm trying to use the Keil C51 compiler with scons as a build system.
The final problem I have is how to get $SOURCES to be output comma-separated to the linker. The default uses space as separator.
The relevant parts of the SConstruct is
path = ['C:\Keil\C51\BIN']
env = Environment(ENV = {'PATH' : path})
#Compiler settings
env['CC'] = 'c51.exe'
env['CCCOM'] = '$CC $SOURCES $_CPPINCFLAGS $CFLAGS $_CCCOMCOM ' #-o $TARGET
env['INCPREFIX'] = 'INCDIR('
env['INCSUFFIX'] = ')'
# Linker settings
env.Replace(LINK='BL51.exe')
env['LINKCOM'] = '$LINK $SOURCES TO $TARGET $LINKFLAGS $__RPATH $_LIBDIRFLAGS $_LIBFLAGS'
With this I get:
BL51.exe driver.obj flash.obj initialization.obj power.obj TO Outfile.omf
What I want is:
BL51.exe driver.obj,flash.obj,initialization.obj,power.obj TO Outfile.omf
$SOURCES is a construction variable and I cant find how to change how it is printed to the command-line.
Anyone?
I solved this by using:
env['LINKCOM'] = '$LINK ",".join( $SOURCES ) TO $TARGET $LINKFLAGS $__RPATH $_LIBDIRFLAGS $_LIBFLAGS'

Resources