Tuesday, June 10, 2008

Plake: Morph a File Based on Targets

Plake
This blog was always intended as a means to talk about projects I'm working on, as well as a way to voice my opinions to the world. So far, it's been largely skewed to the latter.

So, I'd like to talk about a little build tool I've written called Plake. In a nutshell:
Plake is a tool that allows you to maintain sections within a single file (usually, variations of the same code/markup/content) and then assemble variations of that file according to which target you call. It was inspired by Make, can be used in conjunction with Make, and is written in Perl, hence the name "Plake".
Make is a nearly ubiquitous build tool. It's used in countless software projects and is even the basis of the CPAN installer that's part of any Perl distribution --
perl -MCPAN -e shell
Make does a really simple, powerful thing. It sets up rules (aka targets) that execute commands or invokes other targets, which is known as dependency chaining. From these rather simple concepts, you are able to orient a project for different variations, nicely denoted by a single target name.

For example, you might type

make linux_build

to build a Linux platform binary, which may consist of X number of steps that must execute in a certain order. Or, you might say

make apache_modperl

to include files from your web application specifically for an Apache/mod_perl web environment, along with the more general non-platform specific files.

What Make can't do, however, is snag bits of code (or markup) from individual files for a given build. If you've ever looked at cross-platfrom C/C++ code, you've probably noticed the #ifdef directives in the header files. These are used because sometimes there are small portions of code that need to be excluded when compiling for a certain platform or target, and keeping totally separate files to accommodate this is excessive.

Plake allows you to define sections within a single file, and then "assemble" only the sections you want at build time. Here's an example.
Let's say you have a C++ source file that gets built for the Windows platform and also for Linux. Keep the differences as sections in a single Plake file. Then when you assemble the .cpp file for the given platform, it only contains that platform's code.

The following commands both produce "myfile.cpp" (but possibly at different folder locations) with only the code that each platform needs:

plake file=myfile.plk target=windows_build

plake file=myfile.plk target=linux_build
Because Make is generally made up of shell commands, you would put the above commands under the appropriate Make target, and when you type make target, Plake assembles the file with only the parts you need prior to compiling it. The advantage you get, in the scenario above, is that reviewing code is easier, since after a specific target is assembled, only the code you need to see is there.

There are some other uses for Plake, which I've discussed over at Perlmonks, here and here. This is the short list:
  • Setting variations for builds. A convenience for me since I have yet to implement a more complex (i.e. overrides) configuration system, but still have to make subtle changes (usually, by hand-editing) for various implementations at various stages of development.
  • Assemble C/C++ files for specific platforms, in the stead of #ifdef, etc. The resulting .c/.cpp/.h file would be assembled dynamically when the project was make'd for a given platform, just prior to compilation. The code generated for that platform would be a bit simpler to review, since it only includes code that a person cares about in that build.
  • Remove experimental features, stubs, or extra debugging from code prior to generating distros, i.e. "Cleanup".
  • Branching, like what source control does. You could keep some client or "branch" specific features out of a specific build, but still maintain it in a single file.
  • Template variations, like letter writing. Instead of a single boiler plate template, you have targets like "standard_greeting", "enthusiastic_greeting", "familiar_greeting", etc.
  • Target-based programming for Perl. Sort of a side-effect, and one I don't see all the ramifications of, but you could use Plake to assemble code targets wholly or partially independent of each other by storing Perl code in a Plake file and doing an eval against the assembled content for a given target. (Just think -- you could keep your entire project of hundreds of modules and code files all in one single, massive text file! I can see everyone lining up now...)
The last item above, target-based programming, is particularly interesting, I think, so I'll cover it briefly before finishing up. Plake was written in Perl, and uses the eval() function to execute code on the fly. With a minimal change in the code, you could take the content you return from the plk file and eval() it, effectively creating a target-based interpreter. (I include a sample that does this in the download. See plakeval.pl.)

So, if you have a Plake file like
!plake:

target('helowrld', "helowrld", '');
target('oneplus', "oneplus", '');
target('both', "helowrld oneplus", '');

!plake helowrld
print "helowrld\n";

!plake oneplus:
# Add value to one
print 1+3.14, "\n";
and you called it with plakeval.pl, you would get the following:
perl t\plakeval.pl file="t/plakeval.plk" target="helowrld"
helowrld


perl t\plakeval.pl file="t/plakeval.plk" target="oneplus"
4.14


perl t\plakeval.pl file="t/plakeval.plk" target="both"
helowrld
4.14
When the target both is called, you can see that we are printing helowrld and also adding 3.14+1.

What this means is that you can stick things together in a file that perhaps make sense in a certain context, but wouldn't otherwise. Like I said, target-based programming is sort of a side-effect, and while I haven't really explored its value, I have a sense that some exists. At any rate, I find it interesting.

But really, Plake was designed to let you keep variations of a file in a single actual file on the hard drive, and then omit or include parts of it based on a target. And it does that really well. I use it in my own projects and it saves me a considerable amount of error-prone work.

No comments:

Post a Comment