-
Notifications
You must be signed in to change notification settings - Fork 1
Introduction
Project Init is our tool to quickly initialize new software projects from scratch. The idea is to automate many boring and tedious things that are part of setting up a new project. For example, pretty much every real project will use some sort of build system or some tool chain to create a deployable artifact out of the project's source code. No matter what tool chain or programming language is used, usually one must write some configuration files that contain the necessary information to tell the build system how it should build the project. Often the basic structure and content of such files for a particular build system is quite similar. One must also take care of choosing an appropriate license for the project and placing the legal boilerplate text in the right location. A high-quality software project should also contain other useful information besides the actual source code, like Readme files etc. Even though the concrete content of all these files are in the end of course different between projects, the basic structure is often similar enough to be able to automate the initial setup. This is exactly what Project Init does for you: it helps you initialize a new software project so you can get going as quickly as possible.
The aim of this tool is to automate the very first initial step when creating the source tree of a new software project. It is not, however, a code generator. You will still have to write the code yourself. It merely provides some example code in order to be able to directly build the initialized project from source. But that source code is not intended to be useful for anything really. It's just there to have something you can build and run out-of-the-box, so to speak. It's also a great way to learn how to structure and build projects in programming languages you might not be familiar with.
Project Init supports the core programming languages on our tech stack. The way Project Init is published here on GitHub is also how we use it internally when creating new projects. We made it open source because we believe it might be useful for other people too. When you use Project Init, by default, the created projects bear the imprint of Raven Computing, e.g. in source code copyright headers and build system files. But we don't actually claim any copyright for the files that the tool itself creates. The source code and base content of Project Init is licensed under the Apache License Version 2. You're free to license the project files that the tool creates in any way you want. You can also change the copyright notices of files generated by the Project Init tool to whatever you want and we hereby give you explicit permission to do so. To make your life easier, you can configure Project Init to automatically use the author's name and copyright holder of your choice.
The Project Init system is also extensible and configurable by an add-on mechanism. The system is designed to be reusable so that anyone can adjust the behaviour and content without having to change the core code of the system. You can create addons to add more programming languages and project types according to your needs.
Project Init can be used on all Debian-based Linux distributions. You only require a Bash interpreter minimum version 4 and some basic system utilities which should be available on all Linux distributions anyway. In principle, you should be able to run Project Init on all major Linux distributions, but we officially only support Debian-based systems. See the Compatibility section in the project's main Readme for more details.
For how to use Project Init take a look at the Getting Started section in the project's main Readme.
For more information on how to install Project Init take a look at the Bootstrap Readme.
This section will go into the details on how Project Init works. There are essentially two parts that this system is concerned with. There is code that it executes and content that it uses as the basis to create new projects. The code uses an API to prompt questions to the user and collect all necessary information for the new project to be initialized. This kind of code is often referred to as init code and the script that it is located in is called an init script. When all available code is executed and has reached a certain point, the system will take the content of a source directory and copy it to the target directory where the new software project should be initialized in according to the user's input. This content is referred to as a project source template. It is a template because usually it does not represent a ready-to-be-used project source, but rather is a template which is used and processed by the Project Init system to set up a real new project. If you were to manually copy the files into a separate directory and use them as your basis for a new project it would not work. You would have to manually replace or delete all substitution variables and perhaps rearrange some files. Project Init does all of that for you automatically. We will describe in the following how that happens.
Project Init works hierarchically. That is, execution starts at the root and incrementally descends into so called init levels. An init level is represented as a directory containing an init script. Each init level is responsible for acquiring the information from the user that is relevant in its context. Because this is arranged hierarchically, it goes from broad to more concrete the lower you go with init levels. For example, at the beginning the program starts at init level 0 (zero). The code for this init level is provided by the system itself. It contains the form questions that are shown right at the beginning when the user starts the program, like the prompt for the project name and project description. These are common things that every project has: a title and short description. At some point the user is then asked what programming language to use for the new project. The code and content for programming languages is separated into different directories because they represent different technologies. So when the user chooses a particular programming language, he effectively tells the system to descend into the corresponding directory and run the init script it contains. The system is then said to run in a lower init level. The code of that init level is then responsible to set everything up that is common to all projects of that particular programming language.
An init script is a shell script (Bash) that contains the code for the init level that the script is located in. Every init level must have a corresponding init script. The responsibility of code in an init script is, on the one hand, call appropriate API functions to show form questions to the user and collect the relevant information in this way, and on the other hand, use the provided information of the user to set variables that are going to be used when the new project is initialized.
Additionally, an init script has the responsibility to decide how the Project Init system should continue after it has collected all the relevant information. This happens at the very end in the last few lines of code in an init script. There are essentially two possible options. On the one hand, if there are more (lower) init levels available for processing, the init script can instruct the Project Init system to descend into one of the available init levels (i.e. subdirectories) and resume execution there. This is accomplished by calling the proceed_next_level() function. On the other hand, if the underlying init script is located in a leaf init level (i.e. there are no more init levels to possibly descend into), it can instruct the Project Init system to actually set up and initialize the project with all the acquired information so far. This is done by calling the corresponding API functions project_init_copy(), project_init_license() and project_init_process(), respectively. The functions have to be called in this particular order. When the program has reached a leaf init script, usually the only available subdirectory at that init level is the source directory which contains the project source template for that particular project type chosen by the user.
Since Project Init allows initializing different kinds of projects, it wouldn't make sense to put all template files together into one big project source template directory. To keep things more manageable, we differentiate between various kinds of projects, so called project types. A project type is simply a categorization of software projects. For example, in all base project types we generally differentiate between executable programs and libraries, among other things. This is because when a user wants to create a new project, he most likely knows beforehand whether he wants to create a program that is executable directly or he only wants to build a library that should be used by other programs. The user is going to be asked different questions depending on the selected project type in each case. The idea is that all questions and parameters that are common to, let's say an executable program and library in a particular programming language, are handled and processed in a higher init level above the init levels for the executable program and library, respectively.
The prompt which lets the user select a project type for a chosen programming language is displayed in init level 1. The code to accomplish that is usually placed at the end of the corresponding init script. The API provides the select_project_type() function to automate this process.
All available project types have a corresponding project source template, located in a source directory of a leaf init level. This is the scaffold out of which a concrete project is initialized from. A project source template essentially contains most of the files, including some example source code files, which are used as the basis for a new software project of that type. For example, the project source template for Java executable programs contains among other things a POM, application build script and some small example source code file with a main() method. It also defines the core project file and directory layout that should be used by this project type and language. These files and subdirectories are first copied and then subsequently processed by the Project Init system. This is done as follows.
Once the program execution flow has reached the end of a leaf init script, that init script calls the provided API functions to set up the selected project type. The initialization happens in three steps, which correspond to the three API functions that are called in this particular order:
The project_init_copy() function copies all files from the project source template to the project target directory where the new project should be initialized in and sets an internal data structure to keep track of the copied files.
The project_init_license() function sets up the license files based on the software license selected by the user. It also takes care of creating the copyright headers in source code files.
Lastly, the project_init_process() function further processes the copied project source template files in the target project directory. Most importantly, it searches through all copied template files and replaces all encountered substitution variables (see next section) with the value set during the walk through the various init levels. It might also rearrange source files in the target directory to, for example, match a namespace layout according to the namespace set by the user. In principle, it can do whatever is needed to create a functioning project in the target directory. One important aspect to realise is, that in order to keep the code which conceptually belongs together also closely together in one file, the project_init_process() function will call a callback function in each init script. This is a function named process_files_lvl_X() where the X is the integer number of the init level that the function is responsible for, i.e. the init level of the init script that the function is defined in. For example, an init script at init level 3 can define a callback function named process_files_lvl_3() which will be called by the Project Init system at the correct time. That function should then do the processing of template files in the project target directory based on the set variables from the form questions prompted to the user in the very same init script.
One essential property of project source template files is that they can contains so called substitution variables. A substitution variable is a text pattern of the form ${{VAR_?}}
where the '?' is the identifying name of the corresponding substitution variable. For example, the substitution variable ${{VAR_COPYRIGHT_HEADER}}
is usually placed in source code template files and represents the copyright header for that source code file. It is replaced by the Project Init system, when a new project is initialized, with the actual text for the concrete copyright header applicable to the underlying source code file. Substitution variables can be placed anywhere in a project source template file. They are processed after the Project Init system has copied the template files from the corresponding init level source directory to the project target directory. All substitution variables are processed by calls to the replace_var() function.
Since v1.4.0
The Quickstart functionality lets a user create and process files using Project Init functions without having to go through the entire main form of the program. It is not intended to create an entire new standalone project structure but rather generate individual files or directories to be used in an existing project or to otherwise automate certain file content creation steps in your workflow.
A Quickstart function is defined as a function in a Bash script. The name of the function specifies the name by which a user can use that Quickstart functionality. When invoking the project-init
command, specify the Quickstart name preceded by an '@' character. This will instruct the Project Init tool to run the corresponding Quickstart function instead of going through the main project initialization form like when project-init is invoked without any arguments.
For example, the following command will create a new file 'main.c' containing a main() function in your current working directory:
project-init @main.c
Addons can define their own Quickstart functions and the setup for that is extremely easy. Form questions can still be shown in order to gather input from the user to make the output of a Quickstart function parameterizable. Most of the functionality provided by the main Project Init utilities can also be used in Quickstart functions. See the Addons section for more information.