Compiling both C and C++ sources in Cython - c++

I'm trying to compile both C and C++ sources at the same time in Cython. This is my current setup:
-setup.py
from distutils.core import setup
from Cython.Build import cythonize
from distutils.extension import Extension
import os
language = "c++"
extra_compile_flags = ["-std=c++17"]
os.environ["CC"] = "clang++"
ext_modules = [
Extension(
name="Dummy",
sources=["mydummy.pyx", "source1.cpp","source2.c"],
language=language,
extra_compile_args=extra_compile_flags,
)
]
ext_modules = cythonize(ext_modules)
setup(
name="myapp",
ext_modules=ext_modules,
)
-compile command:
python3 setup.py build_ext --inplace --verbose
In the log I get the following message:
clang++ -DNDEBUG -g -fwrapv -O2 -Wall -g -fstack-protector-strong -Wformat -Werror=format-security -Wdate-time -D_FORTIFY_SOURCE=2 -fPIC -I. -I/usr/include/python3.6m -I/usr/include/python3.6m -c /path/source2.c -o build/temp.linux-x86_64-3.6/./path/source2.o -std=c++17 -O3
clang: warning: treating 'c' input as 'c++' when in C++ mode, this behavior is deprecated [-Wdeprecated]
The compilation goes trough, but the warning looks pretty nasty. How can I get rid of it?
The naive solution
os.environ["CC"] = "clang"
os.environ["CXX"] = "clang++"
is failing with error: invalid argument '-std=c++17' not allowed with 'C'

gcc (or clang) is just a frontend and it calls the appropriate compiler (c- or c++-compiler) given the file-extension of the compiled file (.c means it should be compiled with c-compiler and .cpp means it should be compiled with c++-compiler), see for example this similar SO-issue.
Thus there is no need to set the compiler to "clang++", as it will happen automatically.
However, cython needs to know that it has to produce a cpp-file (along with accepting c++-syntax) and not a c-file. Also the linker has to know, that it has to link against cpp-libaries (it is done automatically if g++/clang++ is used for linking). Thus we need to add language = "c++" to the Extension's definition, as suggested in DavidW's answer - this will take care of the last two problems.
The remaining problem is, that we would to like use different compiler options (-std=c++17 only for cpp-files) for different source-files. This can be achieved using a less known command build_clib:
#setup.py:
from distutils.core import setup
from Cython.Build import cythonize
from distutils.extension import Extension
ext_modules = [
Extension(
name="mydummy",
sources=["mydummy.pyx", "source1.cpp"]
language="c++",
extra_compile_args=["-std=c++17"],
)
]
ext_modules = cythonize(ext_modules)
myclib = ('myclib', {'sources': ["source2.c"]})
setup(
name="mydummy",
libraries=[myclib],
ext_modules=ext_modules,
)
And now building either with
python setup.py build --verbose
or
python setup.py build_clib --verbose build_ext -i --verbose
will first build a simple static library consisting of C-code and link it to the resulting extension consisting of c++-code.
The above code uses the default compilation flags when building the static library, which should be enough in your case.
distutils doesn't offer the possibility to specify additinal flags, so if it is necessary we have either to switch to setuptools which offers this functionality, or to patch the build_clib command.
For the second alternative we have to add the following to the above setup.py-file:
#setup.py
...
# adding cflags to build_clib
from distutils import log
from distutils.command.build_clib import build_clib
# use original implementation but with tweaked build_libraries!
class build_clib_with_cflags(build_clib):
def build_libraries(self, libraries):
for (lib_name, build_info) in libraries:
sources = build_info.get('sources')
if sources is None or not isinstance(sources, (list, tuple)):
raise DistutilsSetupError(
"in 'libraries' option (library '%s'), "
"'sources' must be present and must be "
"a list of source filenames" % lib_name)
sources = list(sources)
log.info("building '%s' library", lib_name)
macros = build_info.get('macros')
include_dirs = build_info.get('include_dirs')
cflags = build_info.get('cflags') # HERE we add cflags
objects = self.compiler.compile(sources,
output_dir=self.build_temp,
macros=macros,
include_dirs=include_dirs,
extra_postargs=cflags, # HERE we use cflags
debug=self.debug)
self.compiler.create_static_lib(objects, lib_name,
output_dir=self.build_clib,
debug=self.debug)
...
setup(
...
cmdclass={'build_clib': build_clib_with_cflags}, # use our class instead of built-in!
)
and now we can add additional compile flags to the library-definitions (the previous step can be skipped if setuptools is used):
...
myclib = ('myclib', {'sources': ["source2.c"], 'cflags' : ["-O3"]})
...

Related

Using CMake and Clang++ with C++20 Modules support [duplicate]

Clang and MSVC already supports Modules TS from unfinished C++20 standard.
Can I build my modules based project with CMake or other build system and how?
I tried build2, it supports modules and it works very well, but i have a question about it's dependency management (UPD: question is closed).
CMake currently does not support C++20 modules.
See also the relevant issue in the CMake issue tracker. Note that supporting modules requires far more support from the build system than inserting a new compiler option. It fundamentally changes how dependencies between source files have to be handled during the build: In a pre-modules world all cpp source files can be built independently in any order. With modules that is no longer true, which has implications not only for CMake itself, but also for the downstream build system.
Take a look at the CMake Fortran modules paper for the gory details. From a build system's point of view, Fortran's modules behave very similar to the C++20 modules.
Update: CMake 3.20 introduces experimental support for Modules with the Ninja Generator (and only for Ninja). Details can be found in the respective pull request. At this stage, this feature is still highly experimental and not intended for production use. If you intend to play around with this anyway, you really should be reading both the Fortran modules paper and the dependency format paper to understand what you're getting into.
This works on Linux Manjaro (same as Arch), but should work on any Unix OS. Of course, you need to build with new clang (tested with clang-10).
helloworld.cpp:
export module helloworld;
import <cstdio>;
export void hello() { puts("Hello world!"); }
main.cpp:
import helloworld; // import declaration
int main() {
hello();
}
CMakeLists.txt:
cmake_minimum_required(VERSION 3.16)
project(main)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
set(PREBUILT_MODULE_PATH ${CMAKE_BINARY_DIR}/modules)
function(add_module name)
file(MAKE_DIRECTORY ${PREBUILT_MODULE_PATH})
add_custom_target(${name}.pcm
COMMAND
${CMAKE_CXX_COMPILER}
-std=c++20
-stdlib=libc++
-fmodules
-c
${CMAKE_CURRENT_SOURCE_DIR}/${ARGN}
-Xclang -emit-module-interface
-o ${PREBUILT_MODULE_PATH}/${name}.pcm
)
endfunction()
add_compile_options(-fmodules)
add_compile_options(-stdlib=libc++)
add_compile_options(-fbuiltin-module-map)
add_compile_options(-fimplicit-module-maps)
add_compile_options(-fprebuilt-module-path=${PREBUILT_MODULE_PATH})
add_module(helloworld helloworld.cpp)
add_executable(main
main.cpp
helloworld.cpp
)
add_dependencies(main helloworld.pcm)
Assuming that you're using gcc 11 with a Makefile generator, the following code should work even without CMake support for C++20:
cmake_minimum_required(VERSION 3.19) # Lower versions should also be supported
project(cpp20-modules)
# Add target to build iostream module
add_custom_target(std_modules ALL
COMMAND ${CMAKE_COMMAND} -E echo "Building standard library modules"
COMMAND g++ -fmodules-ts -std=c++20 -c -x c++-system-header iostream
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
)
# Function to set up modules in GCC
function (prepare_for_module TGT)
target_compile_options(${TGT} PUBLIC -fmodules-ts)
set_property(TARGET ${TGT} PROPERTY CXX_STANDARD 20)
set_property(TARGET ${TGT} PROPERTY CXX_EXTENSIONS OFF)
add_dependencies(${TGT} std_modules)
endfunction()
# Program name and sources
set (TARGET prog)
set (SOURCES main.cpp)
set (MODULES mymod.cpp)
# Setup program modules object library
set (MODULE_TARGET prog-modules)
add_library(${MODULE_TARGET} OBJECT ${MODULES})
prepare_for_module(${MODULE_TARGET})
# Setup executable
add_executable(${TARGET} ${SOURCES})
prepare_for_module(${TARGET})
# Add modules to application using object library
target_link_libraries(${TARGET} PRIVATE ${MODULE_TARGET})
Some explanation:
A custom target is added to build the standard library modules, in case you want to include standard library header units (search for "Standard Library Header Units" here). For simplicity, I just added iostream here.
Next, a function is added to conveniently enable C++20 and Modules TS for targets
We first create an object library to build the user modules
Finally, we create our executable and link it to the object library created in the previous step.
Not consider the following main.cpp:
import mymod;
int main() {
helloModule();
}
and mymod.cpp:
module;
export module mymod;
import <iostream>;
export void helloModule() {
std::cout << "Hello module!\n";
}
Using the above CMakeLists.txt, your example should compile fine (successfully tested in Ubuntu WSL with gcc 1.11.0).
Update:
Sometimes when changing the CMakeLists.txt and recompiling, you may encounter an error
error: import "/usr/include/c++/11/iostream" has CRC mismatch
Probably the reason is that every new module will attempt to build the standard library modules, but I'm not sure. Unfortunately I didn't find a proper solution to this (avoiding rebuild if the gcm.cache directory already exists is bad if you want to add new standard modules, and doing it per-module is a maintenance nightmare). My Q&D solution is to delete ${CMAKE_BINARY_DIR}/gcm.cache and rebuild the modules. I'm happy for better suggestions though.
CMake ships with experimental support for C++20 modules:
https://gitlab.kitware.com/cmake/cmake/-/blob/master/Help/dev/experimental.rst
This is tracked in this issue:
https://gitlab.kitware.com/cmake/cmake/-/issues/18355
There is also a CMakeCXXModules repository that adds support for modules to CMake.
https://github.com/NTSFka/CMakeCxxModules
While waiting for proper C++20 modules support in CMake, I've found that if using MSVC Windows, for right now you can make-believe it's there by hacking around the build instead of around CMakeLists.txt: continously generate with latest VS generator, and open/build the .sln with VS2020. The IFC dependency chain gets taken care of automatically (import <iostream>; just works). Haven't tried Windows clang or cross-compiling. It's not ideal but for now at least another decently workable alternative today, so far.
Important afterthought: use .cppm and .ixx extensions.
CMake does not currently support C++20 modules like the others have stated. However, module support for Fortran is very similar, and perhaps this could be easily changed to support modules in C++20.
http://fortranwiki.org/fortran/show/Build+tools
Now, perhaps there i an easy way to modify this to support C++20 directly. Not sure. It is worth exploring and doing a pull request should you resolve it.
Add MSVC version (revised from #warchantua 's answer):
cmake_minimum_required(VERSION 3.16)
project(Cpp20)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
set(PREBUILT_MODULE_DIR ${CMAKE_BINARY_DIR}/modules)
set(STD_MODULES_DIR "D:/MSVC/VC/Tools/MSVC/14.29.30133/ifc/x64") # macro "$(VC_IFCPath)" in MSVC
function(add_module name)
file(MAKE_DIRECTORY ${PREBUILT_MODULE_DIR})
add_custom_target(${name}.ifc
COMMAND
${CMAKE_CXX_COMPILER}
/std:c++latest
/stdIfcDir ${STD_MODULES_DIR}
/experimental:module
/c
/EHsc
/MD
${CMAKE_CURRENT_SOURCE_DIR}/${ARGN}
/module:export
/ifcOutput
${PREBUILT_MODULE_DIR}/${name}.ifc
/Fo${PREBUILT_MODULE_DIR}/${name}.obj
)
endfunction()
set(CUSTOM_MODULES_DIR ${CMAKE_CURRENT_SOURCE_DIR}/modules)
add_module(my_module ${CUSTOM_MODULES_DIR}/my_module.ixx)
add_executable(test
test.cpp
)
target_compile_options(test
BEFORE
PRIVATE
/std:c++latest
/experimental:module
/stdIfcDir ${STD_MODULES_DIR}
/ifcSearchDir ${PREBUILT_MODULE_DIR}
/reference my_module=${PREBUILT_MODULE_DIR}/my_module.ifc
/EHsc
/MD
)
target_link_libraries(test ${PREBUILT_MODULE_DIR}/my_module.obj)
add_dependencies(test my_module.ifc)
With C++20 Modules the file compilation order matters, which is totally new. That's why the implementation is complicated and still experimental in 2023. Please read the authors blogpost
I was not able to find Cmake support for modules. Here is an example how to use modules using clang. I am using Mac and this example works ok on my system. It took me quite a while to figure this out so unsure how general this is across linux or Windows.
Source code in file driver.cxx
import hello;
int main() { say_hello("Modules"); }
Source code in file hello.cxx
#include <iostream>
module hello;
void say_hello(const char *n) {
std::cout << "Hello, " << n << "!" << std::endl;
}
Source code in file hello.mxx
export module hello;
export void say_hello (const char* name);
And to compile the code with above source files, here are command lines on terminal
clang++ \
-std=c++2a \
-fmodules-ts \
--precompile \
-x c++-module \
-Xclang -fmodules-embed-all-files \
-Xclang -fmodules-codegen \
-Xclang -fmodules-debuginfo \
-o hello.pcm hello.mxx
clang++ -std=c++2a -fmodules-ts -o hello.pcm.o -c hello.pcm
clang++ -std=c++2a -fmodules-ts -x c++ -o hello.o \
-fmodule-file=hello.pcm -c hello.cxx
clang++ -std=c++2a -fmodules-ts -x c++ -o driver.o \
-fmodule-file=hello=hello.pcm -c driver.cxx
clang++ -o hello hello.pcm.o driver.o hello.o
and to get clean start on next compile
rm -f *.o
rm -f hello
rm -f hello.pcm
expected output
./hello
Hello, Modules!
Hope this helps, all the best.

Linking with NS3 module with circular dependency to other library

I am trying to build custom NS3 module which depends on some static library. This static library depends on NS3 module.
Platform: Ubuntu 16.04 x64
Toolchain: GCC 5.4.0
I will refer to my custom NS3 module as mymodule
I will refer to the library which mymodule depends on as mylib
I will refer to the program which links with mymodule and mylib as myprog
wscript for mymodule:
def build(bld):
module = bld.create_ns3_module('mymodule', ['network'])
module.features = 'c cxx cxxstlib ns3module'
module.source = [
'model/mymodule.cc' ]
# Make a dependency to some other static lib:
bld.env.INCLUDES_MYLIB = [ "some/include/path" ]
bld.env.LIB_MYLIB = ['mylib']
bld.env.LIBPATH_MYLIB = [ "some/path" ]
module.use.append('MYLIB')
# Create a program which uses mymodule
p = bld.create_ns3_program('myprog', ['core', 'mymodule'])
p.source = 'prog.cpp'
headers = bld(features='ns3header')
headers.module = 'mymodule'
headers.source = ['model/mymodule.h']
When I do ./waf build it fails: LD cannot link myprog because mylib has unresolved symbols. This failure is actually expected because mylib and mymodule are codependent and should be linked in non-standard way.
Workarounds:
If I build myprog by hand and use -Wl,--start-group
-lns3.26-mymodule-debug -lmylib -Wl,--end-group it links perfectly fine and works as expected.
If I combine two static libs by hand (using ar -M script) and then run ./waf build it also works fine.
The question: How can I integrate one of the workarounds above into wscript?
it looks like a known problem with order of static libs inclusion. The behavior has changed in waf 1.9, due to this problem.
One workaround might be to use the linkflags attribute of program. You should prefer the use of STLIB_MYLIB and STLIBPATH_MYLIB as mylib is static. In waf 1.9 with he correct order of libs it might suffice.
Anyway, use -v to see the command line generated by waf, it might help !

Cython, C++ and gsl

So I have set up a c++ class with say class.cpp, class.h. class.cpp uses some functions from gsl (it has #include <gsl/gsl_blas.h>)
I have no problem linking this to another c++ file main.cpp and I can compile it with
g++ -o main main.o class.o -I/home/gsl/include -lm -L/home/gsl/lib -lgsl -lgslcblas
Also, without including the gsl libary in class.cpp, I have managed to create a cython file that uses my class in class.cpp, and it works.
However, when I try to combine these two (ie using a c++ class in cython, where the c++ class uses gsl functions), I do not know what to do. I guess I have to include the
I/home/gsl/include -lm -L/home/gsl/lib -lgsl -lgslcblas
somewhere in the setup file, but I dont know where or how. My setup.py looks like
from distutils.core import setup
from Cython.Build import cythonize
import os
os.environ["CC"] = "g++"
os.environ["CXX"] = "g++"
setup(
name = "cy",
ext_modules = cythonize('cy.pyx'),
)
and I have
# distutils: language = c++
# distutils: sources = class.cpp
in the beginning of my .pyx file.
Thanks for any help!
I suggest you to use the extra_compile_args option in your extension.
I already wrote some answer that luckily uses GSL dependency here.
Customize following your needs, but it should work this way.
Hope this can help...
You can specify the required external libraries using the libraries, include_dirs and library_dirs parameters of the Extension class. For example:
from distutils.extension import Extension
from distutils.core import setup
from Cython.Build import cythonize
import numpy
myext = Extension("myext",
sources=['myext.pyx', 'myext.cpp'],
include_dirs=[numpy.get_include(), '/path/to/gsl/include'],
library_dirs=['/path/to/gsl/lib'],
libraries=['m', 'gsl', 'gslcblas'],
language='c++',
extra_compile_args=["-std=c++11"],
extra_link_args=["-std=c++11"])
setup(name='myproject',
ext_modules=cythonize([myext]))
Here, the C++ class is defined in myext.cpp and the cython interface in myext.pyx. See also: Cython Documentation

C++ Access Autoconf Variable Datadir

I am creating a program called spellcheck, and I'm using autoconf and automake to create a build system for it. The program relies on the dictionary 'english.dict', which is in the data directory (based on whatever prefix the user selected). I want the data directory path accessible by spellcheck, so I created a custom variable that contained its value:
# -*- Autoconf -*-
# Process this file with autoconf to produce a configure script.
AC_PREREQ([2.69])
AC_INIT(libspellcheck, 1.25, corinthianmonthly#hotmail.com)
AC_OUTPUT(Makefile libspellcheck/Makefile spellcheck/Makefile man/Makefile)
AC_CONFIG_SRCDIR([])
AC_CONFIG_HEADERS([config.h])
AC_DEFINE_UNQUOTED([DATA_PATH], ["$pkgdatadir"],"DData Directory Path")
AM_INIT_AUTOMAKE
# Checks for programs.
AC_PROG_CXX
AC_PROG_CC
AC_PROG_CXX
AC_PROG_RANLIB
# Checks for libraries.
# Checks for header files.
AC_CHECK_HEADERS([stdlib.h,iostream,fstream,string,stdio.h,sstream,cctype,algorithm,boost/algorithm/string.hpp])
# Checks for typedefs, structures, and compiler characteristics.
AC_CHECK_HEADER_STDBOOL
AC_TYPE_SIZE_T
# Checks for library functions.
AC_OUTPUT
However, in the config.h file, this value is blank:
/* config.h. Generated from config.h.in by configure. */
/* config.h.in. Generated from configure.ac by autoheader. */
/* "Description" */
#define DATA_PATH ""
...
I tried changing $pkgdatadir to $datadir, but I got the same result. What am I doing wrong, or is what I am trying to achieve impossible?
EDIT: I redefined the variable in my Makefile.am for spellcheck:
AM_CFLAGS = -DDATA_PATH=\"$(pkgdatadir)\" -m32 -Wall
bin_PROGRAMS = spellcheck
pkgdata_DATA = english.dict
spellcheck_SOURCES = spellcheck.cpp meta.cpp
spellcheck_LDADD = ../libspellcheck/libspellcheck.a
But now it complains about DATA_PATH being nonexistant:
spellcheck.cpp:4:22: error: 'DATA_PATH' was not declared in this scope
#define DEFAULT_DICT DATA_PATH "english.dict"
Because now it seems to be ignoring all CFLAGS:
g++ -DHAVE_CONFIG_H -I. -g -O2 -MT spellcheck.o -MD -MP -MF .deps/spellcheck.Tpo -c -o spellcheck.o spellcheck.cpp
It turns out that I needed to use AM_CPPFLAGS rather than CFLAGS.

Why the success of SCons build depends on variant_dir name?

I am bored to death with such behavior. So in SConstruct file we have the last string like this one:
import compilers, os
env = Environment(ENV = os.environ, TOOLS = ['default'])
def set_compiler(compiler_name):
env.Replace(FORTRAN = compiler_name)
env.Replace(F77 = compiler_name)
env.Replace(F90 = compiler_name)
env.Replace(F95 = compiler_name)
def set_flags(flags):
env.Replace(FORTRANFLAGS = flags)
env.Replace(F77FLAGS = flags)
env.Replace(F90FLAGS = flags)
env.Replace(F95FLAGS = flags)
mod_dir_prefix = {
"gfortran": "-J ",
"ifort": "-???",
"pgfortran": "-module "
}
flags = {
("gfortran", "debug"): "-O0 -g -Wall -Wextra -pedantic -fimplicit-none -fbounds-check -fbacktrace",
("gfortran", "release"): "-O3",
("pgfortran", "debug"): "-O0 -g -C -traceback",
("pgfortran", "release"): "-O4"
}
if not GetOption('clean'):
print "\nAvailable Fortran compilers:\n"
for k, v in compilers.compilers_dict().iteritems():
print "%10s : %s" % (k, v)
compiler = raw_input("\nChoose compiler: ")
set_compiler(compiler)
debug_or_release = raw_input("\nDebug or release: ")
set_flags(flags[(compiler, debug_or_release)])
env.Replace(FORTRANMODDIRPREFIX = mod_dir_prefix[compiler])
env.Replace(LINK = compiler)
env.Replace(LINKCOM = "$LINK -o $TARGET $LINKFLAGS $SOURCES $_LIBDIRFLAGS $_LIBFLAGS $_FRAMEWORKPATH $_FRAMEWORKS $FRAMEWORKSFLAGS")
env.Replace(LINKFLAGS = "")
env.Replace(FORTRANMODDIR = '#Mod')
Export('env')
SConscript('Sources/SConscript', variant_dir='Build', duplicate=0)
compilers.py is my own module to find some Fortran compilers which are available.
In Sources folder we have a couple of Fortran source files.
Sources\SConscript
Import('env')
env.Program('app', Glob('*.f90'))
Scons supports Fortran and everything works fine.
gfortran -o Temp\kinds.o -c -O3 -JMod Sources\kinds.f90
gfortran -o Temp\math.o -c -O3 -JMod Sources\math.f90
gfortran -o Temp\sorts.o -c -O3 -JMod Sources\sorts.f90
gfortran -o Temp\utils.o -c -O3 -JMod Sources\utils.f90
gfortran -o Temp\main.o -c -O3 -JMod Sources\main.f90
gfortran -o Temp\app.exe Temp\kinds.o Temp\main.o Temp\math.o Temp\sorts.o Temp\utils.o
scons: done building targets.
After renaming variant_dir name to let say #Bin or #Build we get error message:
gfortran -o Bin\kinds.o -c -O3 -JMod Sources\kinds.f90
gfortran -o Bin\main.o -c -O3 -JMod Sources\main.f90
Sources\main.f90:3.11:
USE sorts
1
Fatal Error: Can't open module file 'sorts.mod' for reading at (1): No such file or directory
Of course the order of compilation matters. But why it depends on variant_dir name? Seems like a bug, but maybe I'm doing something wrong.
P.S. This behavior doesn't depend on duplicate variable value.
P.P.S. Tested with SCons 2.0.1 on Windows with Python 2.7 and Mac OS X with Python 2.5.1.
This is a reply to an old thread, but I had virtually the same problem and needed to dig around for a solution.
Firstly, your build order is probably off because the dependency scanner for Fortran does not work properly. Try running
scons [your_arguments] -n --tree=all | less
which won't actually compile anything but show you the commands and in the end will print the dependency tree as Scons sees it.
A possible solution:
Try adding the line (I added your source for context):
env.Replace(FORTRANMODDIR = '#Mod')
env.Replace(FORTRANPATH = '.' ]
Export('env')
As far as I understand, paths are relative to the "virtual" location of the SConscript file (i.e. the src directory or the variant build directory), this should add the directory containing the source files to the scanner's search path.
In my version of scons (2.3.0), I cannot use the duplicate=0 argument, since it automatically inserts the original source directory into the module path, causing the command line to look like -module build/ -module src/ (ifort) and essentially overriding my preference not to clutter the source directory. This might be a bug, though.