This tutorial expands the series of tutorial on cappuccino tools. This time, we will learn how to use bake. bake is the building system for your own cappuccino application. It enables to automate the use of tools like git, svn or rsync to get the source of your application (I mean cappuccino web framework itself and your application code). Then you will be able to optionaly "press" your application (this will remove dependencies on unused part of cappuccino framework and enable your application to load faster). The last part of the bake will enable to upload your application to your web server (with scp and ssh).
Well since the best way to discover how tu use a tool is by reading it's code, you can read the file named "main.j" in the folder "/path/to/cappuccino/Tools/bake". Let's start with the main() function. We can see that the command line should be like the following (with optionals parameters noted in [])
$ bake [--skip-update] [--deploy] [--press [--png]] [bakefile]
Note : currently the following optionals flags are NOT used : [--base base] [--archive] [--clean] [--host host] [--path path] [--message message]
So if you type just "bake", the tool is expecting a file named "bakefile" in the current directory.
We also discover that the command line options are merged with the one present in the "bakefile". The target directory to generate all temporary files will be in working directory and will be named based on the name of the bakefile (the extension added/modified is ".oven")
You will have subdirectories in your "bakefile.oven" : "Checkouts" is where you fetch the sources, "Build" is used to build cappuccino framework, "Products" will contain your versionned application (one folder for each call to bake named with a timestamp and the corresponding tar.gz file).
The tool logic is pretty simple : --skip-update will remove the update of the sources. If you dont type --deploy, your server will not be modified (this was frightening me a lot ;-)). The default behaviour is to update the source but not to deploy the application (which is just fine for development).
var configOpts = readConfig(bakefile);
for (var i in configOpts) options[i] = configOpts[i];
for (var i in commandOpts) options[i] = commandOpts[i];
targetPath = pwd() + "/" + bakefile.match(/^[^\.]+/)[0] + ".oven";
mkdirs(targetPath);
checkoutsPath = targetPath + "/Checkouts"
mkdirs(checkoutsPath);
buildsPath = targetPath + "/Build"
mkdirs(buildsPath);
productsPath = targetPath + "/Products"
mkdirs(productsPath);
if (!options.skipUpdate)
{
CPLog.info("Updating");
update();
}
CPLog.info("Building");
build();
if (options.deploy)
{
CPLog.info("Deploying");
deploy();
}
If you read the source code of the readConfig() function, you will see that CPJSObjectCreateWithJSON() is used. Bingo ! You know that the "bakefile" is just a JSON string. For beginners like me, just remember that [] is an array and {} is a dictionary.
function readConfig(configFile)
{
var fileData = String(readFile(configFile));
if (!fileData)
throw new Error("Couldn't read file: " + configFile);
var configs = CPJSObjectCreateWithJSON(fileData);
return configs;
}
You can see a good starting point example of a bakefile in the "/path/to/cappuccino/Tools/bake" directory : the file is named "example.bakefile". You can see that the main dictionary contains three keys : "sources" for the sources of your application (you have 3 supported types : "git", "rsync" and "svn"), "deployments" contains your host and the path where to deploy on the host and "templateVars" which contains some variables for the "index.html" template.
{
"sources" : [
{
"type" : "git",
"path" : "git://github.com/280north/cappuccino.git",
"parts" : [
{
"src" : "Objective-J",
"dst" : "Frameworks/Objective-J",
"build" : "ant -DBuild=BUILD_PATH",
"copyFrom" : "Release/Objective-J"
},
{
"src" : "Foundation",
"dst" : "Frameworks/Foundation",
"build" : "steam build -c Release -b BUILD_PATH",
"copyFrom" : "Release/Foundation"
},
{
"src" : "AppKit",
"dst" : "Frameworks/AppKit",
"build" : "steam build -c Release -b BUILD_PATH",
"copyFrom" : "Release/AppKit"
}
]
},
{
"type" : "rsync",
"path" : "/Users/username/projects/NewApplication",
"parts" : [
{
"src" : "",
"dst" : "Blah"
}
]
},
{
"type" : "svn",
"path" : "https://svn.youserver.com/Project/trunk",
"parts" : [
{
"src" : "subdirectory",
"dst" : "Something"
}
]
}
],
"deployments" : [
{ "host" : "deploy@myserver.com", "path" : "/var/www/mysite/public" }
],
"templateVars" : {
"APPLICATION_NAME" : "My Application",
"BACKGROUND_COLOR" : "black",
"TEXT_COLOR" : "black"
}
}
During the update phase, bake will use external tools : "git pull" or "git clone", "svn up" or "svn co", "rsync -avz".
build phase will use the "press" tool if you used the [--press [--png]] flags. Internally the tool will be used like this :
press [--png] versionedPath versionedPath-Stripped ; rm -rf versionedPath; mv versionedPath-Stripped versionedPath"
This will help you understand the bake log : I was surprised by the absence of the directory "versionedPath-Stripped" (but it was just a normal behaviour).
if (options.press)
{
var tempPath = versionedPath+"-Stripped";
var pressCommand = ["press", versionedPath, tempPath];
if (options.png)
pressCommand.push("--png");
var result = exec(pressCommand);
if (result.code)
throw new Error("press failed");
exec(["rm", "-rf", versionedPath]);
exec(["mv", tempPath, versionedPath]);
}
To inform your end user of the cappuccino framework loading, bake determine the number of file to load (all *.sj and *.j files).
var results = exec(["bash", "-c", "find "+versionedPath+" \\( -name \"*.sj\" -or -name \"*.j\" \\) -exec cat {} \\; | wc -c | tr -d \" \""]);
Finally, the template "bake_template.html" is modified with the variables in your bakefile and the current version and total files number. The "index.html" is generated. This file is used to display a loading progression and a redirection to your application when all objective-J code is loaded.
var substitutions = options.templateVars;
substitutions.VERSION = version;
substitutions.FILES_TOTAL = filesTotal;
var template = readFile(options.templatePath);
if (!template)
throw new Error("Couldn't get template");
for (var variable in substitutions)
template = template.replace(new RegExp("\\$" + variable, "g"), substitutions[variable]);
templateBytes = new java.lang.String(template).getBytes();
templateOutput = new FileOutputStream(productsPath + "/" + version + "/index.html");
templateOutput.write(templateBytes, 0, templateBytes.length);
templateOutput.close();
The tar.gz file is generated. This enables you to deploy the application manually or let the "deploy" phase do it for you.
exec(["tar", "czf", version+".tar.gz", version], null, new File(productsPath));
You can deploy your application to more than just one server. The deployment phase will use the "scp" command to upload the tar.gz file. "ssh" is used to execute remote commands. The tar.gz file is used to populate the "path/version" directory. The "index.html" file is located in the "path" directory. A link "Current" is created to access the application.
for (var i = 0; i < options.deployments.length; i++)
{
var dep = options.deployments[i];
exec(["scp", productsPath + "/" + version + ".tar.gz", dep.host + ":~/" + version + ".tar.gz"]);
exec(["ssh", dep.host,
"tar xzf " + version + ".tar.gz; " +
"mkdir -p " + dep.path + "; " +
"mv " + version + "/" + version + "/ " + dep.path + "/" + version + "; " +
"mv " + version + "/index.html " + dep.path + "/index.html; " +
"rm " + version + ".tar.gz; " +
"rmdir " + version + "; " +
"cd " + dep.path + "; " +
"ln -nsf " + version + " Current"]);
}
If you have a cappuccino application named "example", the bakefile named "example.bakefile" will generate a directory "example.oven". You will noticed that I suppressed all "build" from the "sources" key. I have choosen to do it since I am using cappuccino 0.7 beta which use "rake" to build and not the old "ant" and "steam build" command (which were used in cappuccino 0.6). Before analysing the bake source code, I wanted to be sure that no deployment will occur, so a removed the entry in the "deployments" key. You don't have to do it : just refrain to use the --deploy flag ;-)
{
"sources" : [
{
"type" : "git",
"path" : "git://github.com/280north/cappuccino.git",
"parts" : [
{
"src" : "Objective-J",
"dst" : "Frameworks/Objective-J",
"copyFrom" : "Release/Objective-J"
},
{
"src" : "Foundation",
"dst" : "Frameworks/Foundation",
"copyFrom" : "Release/Foundation"
},
{
"src" : "AppKit",
"dst" : "Frameworks/AppKit",
"copyFrom" : "Release/AppKit"
}
]
},
{
"type" : "rsync",
"path" : "/Users/philippe/Desktop/example",
"parts" : [
{
"src" : "",
"dst" : ""
}
]
}
],
"deployments" : [
],
"templateVars" : {
"APPLICATION_NAME" : "My Application",
"BACKGROUND_COLOR" : "black",
"TEXT_COLOR" : "black"
}
}
You can type the command:
$ bake --press example.bakefile
You will see that by using "press" you will modify some of your files. The "Info.plist" will not contain the files that you are not using in your application.
2009-03-18 09:43:41.799 Cappuccino [info]: exec: 2009-03-18 09:43:41.796 Cappuccino [error]: PHASE 3.5: fix bundle plists
2009-03-18 09:43:41.843 Cappuccino [info]: exec: 2009-03-18 09:43:41.797 Cappuccino [info]: Modifying .sj: /Users/philippe/Desktop/example.oven/Products/1240040594/1240040594/Info.plist
2009-03-18 09:43:41.848 Cappuccino [info]: exec: 2009-03-18 09:43:41.834 Cappuccino [info]: Modifying .sj: /Users/philippe/Desktop/example.oven/Products/1240040594/1240040594/Frameworks/Foundation/Info.plist
2009-03-18 09:43:41.851 Cappuccino [info]: exec: 2009-03-18 09:43:41.836 Cappuccino [info]: Removing: CPArray+KVO.j
2009-03-18 09:43:41.869 Cappuccino [info]: exec: 2009-03-18 09:43:41.858 Cappuccino [info]: Modifying .sj: /Users/philippe/Desktop/example.oven/Products/1240040594/1240040594/Frameworks/AppKit/Info.plist
2009-03-18 09:43:41.871 Cappuccino [info]: exec: 2009-03-18 09:43:41.860 Cappuccino [info]: Removing: _CPCibConnector.j
2009-03-18 09:43:41.940 Cappuccino [info]: exec: 2009-03-18 09:43:41.925 Cappuccino [error]: PHASE 4: copy to output
2009-03-18 09:43:42.727 Cappuccino [info]: exec: 2009-03-18 09:43:42.726 Cappuccino [info]: Writing out /Users/philippe/Desktop/example.oven/Products/1240040594/1240040594-Stripped/main.j
2009-03-18 09:43:42.735 Cappuccino [info]: exec: 2009-03-18 09:43:42.734 Cappuccino [info]: Writing out /Users/philippe/Desktop/example.oven/Products/1240040594/1240040594-Stripped/Frameworks/Foundation/Foundation.sj
2009-03-18 09:43:42.747 Cappuccino [info]: exec: 2009-03-18 09:43:42.745 Cappuccino [info]: Writing out /Users/philippe/Desktop/example.oven/Products/1240040594/1240040594-Stripped/Frameworks/AppKit/AppKit.sj
2009-03-18 09:43:42.849 Cappuccino [info]: exec: 2009-03-18 09:43:42.848 Cappuccino [info]: Writing out /Users/philippe/Desktop/example.oven/Products/1240040594/1240040594-Stripped/AppController.j
2009-03-18 09:43:42.851 Cappuccino [info]: exec: 2009-03-18 09:43:42.849 Cappuccino [info]: Writing out /Users/philippe/Desktop/example.oven/Products/1240040594/1240040594-Stripped/Info.plist
2009-03-18 09:43:42.855 Cappuccino [info]: exec: 2009-03-18 09:43:42.851 Cappuccino [info]: Writing out /Users/philippe/Desktop/example.oven/Products/1240040594/1240040594-Stripped/Frameworks/Foundation/Info.plist
2009-03-18 09:43:42.856 Cappuccino [info]: exec: 2009-03-18 09:43:42.855 Cappuccino [info]: Writing out /Users/philippe/Desktop/example.oven/Products/1240040594/1240040594-Stripped/Frameworks/AppKit/Info.plist
2009-03-18 09:43:44.274 Cappuccino [info]: exec: 11599790
2009-03-18 09:43:44.278 Cappuccino [debug]: FILES_TOTAL=[11599790]