# -*- mode:python;indent-tabs-mode:nil;show-trailing-whitespace:t; -*- # # SCons build script for tests. # (c) Copyright Rosetta Commons Member Institutions. # (c) This file is part of the Rosetta software suite and is made available under license. # (c) The Rosetta software is developed by the contributing members of the Rosetta Commons. # (c) For more information, see http://www.rosettacommons.org. Questions about this can be # (c) addressed to University of Washington UW TechTransfer, email: license@u.washington.edu. # SCons imports Import("build", "project") # Python imports from tools.build.settings import Settings, ProjectSettings # Load the project settings. requested = Settings.load(build.toplevel + "/test/" + project + ".test.settings") requested = ProjectSettings(project, requested) # Add the default settings for source builds. actual = ProjectSettings(project) # Subprojects are used to determine dependencies on other projects actual.subprojects = requested.subprojects # Sources. Currently we assume they are all C++. If this ever changes # we will need to differentiate language types in the .src.settings files. for package, modules in requested.sources.items(): for module in modules: source = module + ".cxxtest.hh" if package: actual.sources += [ "%s/%s/%s" % (project, package, source) ] else: actual.sources += [ "%s/%s" % (project, source) ] # Include paths. Gives priority to user defined paths. actual.include_path = \ requested.include_path + \ [ "#test" ] + \ [ "#src" ] + \ [ "#external/include" ] + \ [ "#src/platform/" + "/".join(build.platform_includes) ] # Crawl up the platform paths looking for headers # This makes more specific platforms take priority. for last in range(1, len(build.platform_includes)): platform_include_path = "#src/platform/" + \ "/".join(build.platform_includes[:-last]) actual.include_path += [ platform_include_path ] # Library paths. Gives priority to user defined paths. actual.library_path = \ requested.library_path + \ [ "#external/lib" ] + \ [ "#build/src/" + build.platform ] + \ [ "#build/external/" + build.platform ] # Libraries. Gives priority to user defined libraries. # Doesn't currently distinguish between source and test libraries # (because we don't have test libraries) actual.libraries = \ requested.libraries + \ requested.subprojects # The Boost library extensions vary by OS which is a huge nuisance. # XXX: This is a temporary solution: the right answer is a Boost # XXX: build script which does the work of selecting the extension. #boost_ext = { # "linux" : "-gcc", # # "macos" : "", # # "windows" : "", # }.get(build.options.os, "") #if "mingw" in build.options.extras and build.options.os == "windows": # boost_ext = "-mgw" #pre_boostified = actual.libraries #actual.libraries = [] #for lib in pre_boostified: # if lib.startswith("boost_"): # lib += boost_ext # actual.libraries += [ lib ] # Transform the modified settings into SCons Environment variables. # Gives priority to project settings over global settings. env = build.environment.Clone() env.Prepend(**actual.symbols()) # Need to generate the test case files using the Python script generator cxxtestgen.py # We don't want to recreate the test case files unless the underlying header file # containing the actual tests is changed. So rather than iterating through the list of # .hh files and always calling env.Execute() on each of them, let's add a Builder # to our current "construction environment" that will handle running the cxxtestgen.py # script on the source files. cxxbuilder = Builder( action="external/cxxtest/cxxtestgen.py --have-std --part -o $TARGET $SOURCE", suffix = '.cxxtest.cpp', src_suffix = '.cxxtest.hh' ) # Since there's no way to call a builder on a *list* of targets (which have a *list* of # corresponding source files), we have to iterate through the files and call the builder # on each one individually. The return of the actual call to the Builder is a Node object # I believe, which can be saved and used for subsequent Builder calls. In other words, the # "Nodes" added to the list 'cxx' can then be passed as the main Program Builder source # argument. This makes the CxxTestGen Builder execute before Program executes when building # does finally start. cxx = [] build.environment.Append( BUILDERS = {'CxxTestGen': cxxbuilder} ) for file in actual.sources: cxx.append( build.environment.CxxTestGen(file) ) # When compiling tests for CxxTest using the --part argument to the Python script, no main() # method is added to the generated .cpp files. But there needs to be one file that has a main. # Let's assume inside each "project" there will be a project.cxxtest.hh which has no tests or anything # in it. This file will get processed with the --root switch which adds a main() method to the # generated .cpp file. Rather than creating a custom Builder for this file, we can get away with # a simple Command() function. The build_dir setting in the build settings will put the file in # the appropriate build directory. infile = "%s/%s.cxxtest.hh" % (project, project) outfile = "%s/%s.cxxtest.cpp" % (project, project) main = env.Command(outfile, infile, "external/cxxtest/cxxtestgen.py --error-printer --root -o $TARGET $SOURCE") # Build the executable. We may want to produce tests as libraries # at some point but for now we are only generating a runnable program. target = project + ".test" # The ".test" part of the filename apparently confuses SCons on Windows # so explicitly add the extension for an executable. if build.options.os in ("windows"): target += ".exe" # # This option will add rpath setting inside executable # if "static" not in build.options.extras: env["RPATH"] = [ build.toplevel + "/build/src/" + build.platform, build.toplevel + "/build/external/" + build.platform ] tests = env.Program(target, [cxx, main]) # Lacking this, doing a build of '' won't build the library, # because the library is created above the '' directory. Alias(project, tests) # Some of the unit tests read in text files. Hardcoding the location of the text files or taking the text in the # file and placing it into a string variable in the code are both less than ideal solutions. Having smaller versions # of the files along with the test code seems to be the best solution. However, the test executables get built in # the build directory. When running them there, the text files are still in the test/ subdirectories. We need to # have a separate command to copy text files used by test cases from test/ to the right build subdirectory. # # To get Scons to copy the file appropriately, we make an explicit dependancy between the main test executable and # the copied file location. We then use a Copy builder to set up the file copy. Doing it this way means that only # the files which have gotten changed will be copied, and they'll get copied any time they do change. # The trick is to use the "#" prefixing on the scons source/target notation to get Scons to realize that the paths # we're giving it are relative to the root directory. # The one drawback of how it is currently structured is that if any of the the text file inputs change, you'll relink # the test executable, even though you don't have to. for file in requested.testinputfiles: input = "test/%s/%s" % (project, file) dest = "build/test/" + build.platform + "/" + project + "/" + file env.Depends(target, "#"+dest) env.Command("#"+dest, "#"+input, Copy(dest,input) ) # Setup useful aliases for building subdirectories of project. # This allows a target to be project + package and have SCons build only # that package. # TODO: Allow building of specific source units. #print "BUILD_TARGETS: %s" % (BUILD_TARGETS) #for package in BUILD_TARGETS: # print "package: %s" % (package) # if package.startswith(project): # Alias(package, "#build/test/" + build.platform + "/" + package)