Source 2 Binary (S2B) is a software build utility with that aims to be easier to use and more flexible than the traditional 'make' environment.
Simply put, software build environments using 'make' and its supporting utilities are more complicated than they need to be.
The combination of makefiles, shell scripts, standard Unix utilities, m4 Autoconf macros, and Perl scripts currently used to provide portable software builds is an effective but complex solution. For larger projects, forcing the amount of required flexibility out of these tools is a difficult task. Attempting to support multiple platforms and widely varying system configurations in addition to user preferences can result in build environments that are closer to full-fledged programs than they are simple files used to drive software compilation..
S2B is an attempt to simplify the software build process. Whether or not it will succeed in this arena has yet to be determined. At this point, the project consists of a general idea and a proof-of-concept prototype. The current goal is to generate some discussion and see if enough people think this project is worth pursuing. So if you love it, hate it, or just have an opinion; let's hear about it in the Open Forum
At the fundamental level, S2B is similar to almost every other project in this vein. It consists of a library of Python modules that provide the basic functionality found in Autoconf and Make (dependency tracking, library availability checks, etc) and do the normal OO thing of platform/tool abstraction. These modules are then used by project-specific scripts to drive the build. Pretty standard. The interesting part comes from some tricks Python allows us to play with the scripts used to drive the build.
The idea behind these tricks (for the lack of a better term), is pretty simple. We instruct the Python interpreter to load the project-specific script and give us the resulting module object. We then do some inspection and processing of the module contents. This scheme allows us to break away from the normal programming style and present the user with a simplified interface. By declaring a few variables and adhering to a few conventions, the user can implicitly describe what they want to happen. In effect, we can use a normal Python module almost like customized S2B scripting language.
One of the reasons 'make' is so popular is the ability to quickly and concisely describe targets, dependencies, and operations. Attempting to do the same with a programming language is much more difficult and probably the reason 'make' wasn't replaced by Perl scripts a long time ago. The module processing scheme employed by S2B allows us to support an interface similar to that of traditional makefiles while still allowing full access to the underlying language. For general use, users will be able to use a simple makefile-like interface. When complicated situations arise that make this interface awkward to use, they can switch over to normal Python code.
As previously mentioned, the fundamental structure of S2B is pretty standard and uninteresting. Consequently, it will be largely ignored in this discussion. Since the simplified interface is what makes this project stand out, it will receive the most attention.
The Overview mentioned that the key to the simplified interface lies in inspecting and processing the contents of Python modules. For those unfamiliar with the capability Python provides in this area, the following list describes the key features related to this project.
We will use the following example to describe how everything fits together.
#!/usr/bin/python import s2b_lib from s2b_lib.targets import Target test = Target() test.sources = """ main source_file1 source_file2 """ s2b_lib.run()
The last line causes the s2b library to inspect the contents of this
module. The inspection will reveal a single object of type
Target with the member variable 'sources'. If the user is
building the test target, the sources variable will
be examined to determine its type. The type could be, as in this example, a
list of source files (the extensions will be automatically determined by
scanning the directory), a normal Python function that returns a list of
source files, a regular expression object like
regex.compile("server_*.c") to be applied to the directory
contents, or any number of things that could be used to gain a list of source
files.
Since the variable name used for the Target object is
test and no specific output type was specified in the
Target() constructor, the library will assume that the result is
to be an executable instead of an object file (for which the user could
either have named the variable test_o or constructed the object
with Test( targets.OBJ )). By default, the name of the variable
is what will be used for the name of the output. Knowing the list of sources,
the name of the output, and what the output type is; the library will check
the to see if each one of the source files is up to date (i.e. has a matching
.o file that is current), compile those that are not, and link them together
to result in the executable "test" binary.
To specify extra dependencies, we could add the line test.depends =
"aTargetName aSourceName aBooleanFunctionName" By examining the names
in the this string after the module has been imported, the s2b library would
be able to determine that the test target depends on another
Target object, a source file, and a regular Python function
(which, of course, would be called during the dependency check).
Library dependencies can be delt with in a similar manner. Consider the
the case in which our example included the line test.libraries = "X11
pthread QT3". Obviously, this would indicate that the files in this
target will need access to the header and '.so'/'.a' files associated with
these libraries during the compile/link phase.
By loading all of the project-specific modules and collecting all the
<target_name>.libraries = "..." information, we can create
a complete list of library dependencies for the project. This list can then
be used to accomplish the same sort of check done by the 'configure' scripts.
A side advantage of this scheme is that we do not need to place all of our
external dependencies in a single, top-level location.
Rather than using m4 to define the library checks, as is done with Autoconf, we can use a more flexible and consistent (if more verbose) method. Since the rest of S2B is written in Python, we might as well do this in Python as well. Library-specific issues are managed by library-specific modules in the s2b_lib.libs package. By following a few simple naming conventions, support for a new library can be added by simply dropping a new module into this package.
The following code is an example of a module for X11.
from s2b_lib.sys_conf import lib_info
from s2b_lib.tools import test_compile
include_dir = "/usr/X11R6/include"
libraries_dir = "/usr/X11R6/lib"
def detect_configuration():
if test_compile.compile_and_link("x11_test.c", include_dir, libraries_dir):
return lib_info.LibInfo(..., include_dir, libraries_dir, ...)
else:
return lib_info.library_unavailable()
Look pretty self-explanatory? Good. That's the what we're shooting for. One of the nice things about doing this check in a Python function is that we can put some extra intelligence in. We could, for example, look in a few common locations such as /opt or /usr/local if the initial check fails. We could look in different places depending on the underlying OS.
One thing about this code that might not be obvious, is that the name and
location of the include and library paths allow us to implement an easy
scheme for overriding the default paths via command-line switches. Should the
user add the switch "--lib_x11_include=<path>" the s2b
library would know to overwrite the include_dir variable in the
module s2b_lib.libs.x11.
Similar to "configure", we can cache the results of this detection. Our good ol' friend 'pickle' makes caching the LibInfo objects platform-independently trivial.
Another thing about S2B that should be mentioned is the method for
handling the source tree. By defining the variables SUB_DIRS = "src lib
etc" and EXCLUDE_DIRS = "test doc whatever" the
directories visited by the S2B build can be controlled. By default, S2B will
visit every directory in the subtree. These two module-level variables modify
that behavior.
The final thing to note about S2B in its current state is that the simplified interface we provide is not mandatory. The S2B library could be used like any other Python library for application development. If someone wanted to develop a cross-platform, distributed build system; S2B could be used to do it. It is likely that this capability would also prove beneficial to larger programs that need more logic than our simplified interface can easily provide. The majority would be probably be able to use the easier method but for those spots where it makes more sense to write some real code, you can go ahead and do it..