######################################################################### ################## LogiLogi.org Make LakeExtend Lib ##################### ######################################################################### ######## Copyright (C) 2004 Wybo Wiersma ######## ######################################################################### # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ######################################################################### ### How lakeExtend makes extending lake easy... ### The lakeExtend library allows the replacement of lake functionality (like build, or the library-header installation routine) without having to change the functions used in lakefiles and without having to modify other extension libraries. Also it makes adding functionality easier because there are base-classes available both for (new) tasks and for (new) replacable function classes. These base-classes upon creation do what is needed for lake to use them, like adding themselves to a list resp. a map. These classes are explained more in depth in the section below. Besides these classes lakeExtend offers a general structure for extending lake. The basic user-library lakeUsr has been set up according to it. In total there are five steps needed to extend lake. Firstly an userfriendly user-function has to be created for it. For lakeUsr these are found in the .lake/lakeUsrFun directory (these functions are so- called class-functions, more about that later). Then a task-function has to be made. This is the function that is called from one or multiple userfriendly functions. This task-function creates an instantiation of a class inheriting from the Task-class. This is the fourth step. Both task-functions and task-classes are found in the .lake/lakeUsrTask dir. To create a Task-class only the Task constructor and it's virtual function "run()" have to be overloaded. This overloaded run()-funcion calls the function-object that does the real work. This function-object inherits from RunFun. In the case of lakeUsr the RunFun's mostly make use of the functionality provided by the lakeCore library (lakelibsrc/lakeCore). You only need to read the following if you're interested in extending lake or if you're just curious how lake(Extend) works. ### The Task and RunFun classes ### Both the Task- and RunFun-class add themselves to a global list/map upon creation. The idea is that names for them are provided in their constructors, names by which Tasks and RunFuns can be found and coupled through these lists. When a task like lakeBuild(dirTree,exeName) is called via the task-function the first thing that happens is that an Instantiation of a child of the Task-class for this task is created. The declaration of this class is given below: class TaskBuild: public LibUsingTask { public: TaskBuild() : LibUsingTask("build") {} void run(); }; The implementation of the run()-function looks like this: void TaskBuild::run() { (*dynamic_cast(d_pRunFun))(d_map["sourceDir"], d_map["buildDir"],d_map["exeName"], d_pLibUsageStore,d_map["extraCompilePreArgs"], d_map["extraCompilePostArgs"], d_map["extraLinkPreArgs"], d_map["extraLinkPostArgs"], d_pExtendConfigStore); } All the run()-function does is cast and call the RunFun that is stored as its member (d_pRunFun). The RunFun is assigned to this member by the constructor of the Task-base-class at the time it is created. So this all is taken care of (and the same task can use different RunFun's within the same lakefile) behind the scenes. Another thing the Task-base-class constructor does at creation time is adding itself to the global list of Tasks that are to be executed. The tasks are sorted and executed at the time the processTasks function is called (normally this happens from the ./lake/partsrc/lake.tail.3.run.cc source). The processTasks function sorts the tasks based on the values set in their ExtendConfigStore's e_Rank variable. Their run()- functions are called in order. These run-functions (as already stated) call the RunFun datamember to do the real work. Below is the declaration of the RunFun function-object for lakeUsr's build-task: class RunFunBuild: public RunFun { public: RunFunBuild() : RunFun("build") {} virtual void operator()(std::string const & sourceDir, std::string const & buildDir, std::string const & exeName, LibUsageStore * pLibUsageStore, std::string const & extraCompilePreArgs, std::string const & extraCompilePostArgs, std::string const & extraLinkPreArgs, std::string const & extraLinkPostArgs, lf::ExtendConfigStore const * pExtendConfigStore); }; As you can see the operator()(...) is overloaded (which makes it a function object) and it is virtual. It is virtual to allow others to overload it if they want to create a different implementation of the function. If this overloaded class is then created (with new) after the basic one, the basic one is replaced within the map at creation and it is from then on assigned to newly created Tasks instead of the old one. The last important thing the Task's constructor does is cloning and storing a copy of the global config-class (the ExtendConfigStore) in a datamember (so different configurations can be used in succession) ### The five steps ### Two of the five steps have already largely been described: The overloading of the Task- and RunFun-classes (3rd and 4th step). The first step is the creation of an userfriendly function that can be called from the lakefile. These userfriendly functions are not function-objects but function-classes. This means that their constructor is used for passing the arguments: As in the following declaration (lakeBuild): // Easy build function with default builddir class lakeBuild: private ll::shareLakeExtendConfig { public: lakeBuild(std::string const & sourcesToBuild, std::string const & intoExecutable); }; This function-class can just be called as lakeBuild(dirTree,exeName). Function-classes are used because this allows using global variables or initializations in a neat way. You can see that lakeBuild inherits from shareLakeExtendConfig. This class contains a protected member called s_pExtendConfigStore: the global configstore. This configuration store doesn't need to be passed as an argument and is neither globally accessable in this way (so a neat solution allowing the user of the function-class just to focus on the two arguments that are important for him). The second step is the creation of the taskFunction. This is the function that creates and fills the Task-class: void taskBuild(string const & sourcesToBuild, string const & buildDir, string const & intoExecutable, string const & extraCompilePreArgs, string const & extraCompilePostArgs, string const & extraLinkPreArgs, string const & extraLinkPostArgs) { TaskBuild * pTb = new TaskBuild(); (*pTb)["sourceDir"] = sourcesToBuild; (*pTb)["buildDir"] = buildDir; (*pTb)["exeName"] = intoExecutable; (*pTb)["extraCompilePreArgs"] = extraCompilePreArgs; (*pTb)["extraCompilePostArgs"] = extraCompilePostArgs; (*pTb)["extraLinkPreArgs"] = extraLinkPreArgs; (*pTb)["extraLinkPostArgs"] = extraLinkPostArgs; // Task not deleted because it adds itself to the taskmap in // shareLakeExtend } The TaskBuild class declared in the second part of this Readme is created and filled in the code above. Note that the newly created class is not deleted at the end of the taskFunction, as it adds itself to the list of tasks to process (it is deleted at the end of it's usefull life by the processTasks function already mentioned). The third and fourth step were already described (Task and RunFun overloading). Now the final step consists of making sure the newly created/overloaded RunFun is initialized at startup. For the lakeUsr library this is done in the initRunFuns function-class found in ./lake/libsrc/lakeUsrShareInit/. This class is parent of initLakeUsr and so initialized in that way. For custom RunFun's a good way to initialize them is by adding a new initMyExtension function-class in which they are created. The code for lakeUsr is included below: initRunFuns::initRunFuns() { if (!s_didInitRunFuns) { // Creates the RunFuns, they add themselves to the shareLakeExtend list new ll::RunFunBuild(); new ll::RunFunBuildSLib(); new ll::RunFunExtClean(); new ll::RunFunInstallSLibHdrs(); new ll::RunFunInstallSLibLib(); new ll::RunFunPurgeDir(); new ll::RunFunRunCommand(); new ll::RunFunUninstallFile(); } } That's it! Not too hard, is it ? (just copy and modify some sources from lakeUsr to see it work...)