public static void GetAdditionalHelpText(IHelpWriter formatter, CommandLineApplication app, int firstColumnWidth)
        {
            formatter.WriteOnNewLine(null);
            formatter.WriteSectionTitle("OVERVIEW");
            formatter.WriteOnNewLine(@"With sakoe, you can 'build' your project. A build process is a succession of tasks that (typically) transform your source files into a deliverable format, usually called a release or package.

In sakoe, you describe a build process using a 'build configuration'. A build configuration holds 'properties' of the build (for instance, the path to the openedge installation directory $DLC). It also holds the list of 'tasks' that will be executed successively during the build process.

To illustrate this, here is a possible build process:
  - Task 1: compile all your .p files to a `procedures` directory.
  - Task 2: compile all your .w files into a pro-library `client.pl`.
  - Task 3: zip the `procedures` and `client.pl` together into an archive file `release.zip`.

In order to store these build configurations, sakoe uses project files: " + OeBuilderConstants.OeProjectExtension.PrettyQuote() + @".
You can create them with the command: " + typeof(ProjectInitCommand).GetFullCommandLine <MainCommand>().PrettyQuote() + @".");
            formatter.WriteOnNewLine(null);
            formatter.WriteOnNewLine(OeIncrementalBuildOptions.GetDefaultEnabledIncrementalBuild() ? "By default, a build is 'incremental'." : "A build can be 'incremental'.");
            formatter.WriteOnNewLine("An incremental build is the opposite of a full build. In incremental mode, only the files that were added/modified/deleted since the previous build are taken into account. Unchanged files are simply not rebuilt.");
            formatter.WriteOnNewLine(null);
            formatter.WriteOnNewLine("The chapters below contain more details about a project, build configuration, properties and tasks. ");

            // TODO: list all the node and their documentation, use a tree

            formatter.WriteOnNewLine(null);
            formatter.WriteSectionTitle("PROJECT");
            formatter.WriteOnNewLine(BuilderHelp.GetPropertyDocumentation(typeof(OeProject).GetXmlName()));

            formatter.WriteOnNewLine(null);
            formatter.WriteSectionTitle("BUILD CONFIGURATION");
            formatter.WriteOnNewLine(BuilderHelp.GetPropertyDocumentation(typeof(OeProject).GetXmlName(nameof(OeProject.BuildConfigurations))));

            formatter.WriteOnNewLine(null);
            formatter.WriteSectionTitle("BUILD CONFIGURATION VARIABLES");
            formatter.WriteOnNewLine(BuilderHelp.GetPropertyDocumentation(typeof(OeBuildConfiguration).GetXmlName(nameof(OeBuildConfiguration.Variables))));

            formatter.WriteOnNewLine(null);
            formatter.WriteSectionTitle("BUILD CONFIGURATION PROPERTIES");
            formatter.WriteOnNewLine(BuilderHelp.GetPropertyDocumentation(typeof(OeBuildConfiguration).GetXmlName(nameof(OeBuildConfiguration.Properties))));

            formatter.WriteOnNewLine(null);
            formatter.WriteSectionTitle("BUILD STEPS");
            formatter.WriteOnNewLine(BuilderHelp.GetPropertyDocumentation(typeof(OeBuildConfiguration).GetXmlName(nameof(OeBuildConfiguration.BuildSteps))));

            formatter.WriteOnNewLine(null);
        }
        /// <inheritdoc />
        protected override int ExecuteCommand(CommandLineApplication app, IConsole console)
        {
            // TODO: show task error and compilation error

            // show help.
            if (ShowBuildPropertyHelp)
            {
                HelpWriter.WriteOnNewLine(null);
                HelpWriter.WriteSectionTitle("BUILD PROPERTIES");

                foreach (var property in GetAvailableBuildProperties().OrderBy(p => p.Key))
                {
                    HelpWriter.WriteOnNewLine(property.Key);
                    HelpWriter.WriteOnNewLine(BuilderHelp.GetPropertyDocumentation(property.Key), padding: 20);
                    HelpWriter.WriteOnNewLine(null);
                }
                return(0);
            }

            if (string.IsNullOrEmpty(ProjectFile))
            {
                ProjectFile = GetCurrentProjectFilePath();
            }
            else
            {
                ProjectFile = GetProjectFilePath(ProjectFile);
            }
            Log?.Debug($"Base project file: {ProjectFile.PrettyQuote()}.");

            // get extra config
            var configQueue = new Queue <Tuple <string, string> >();

            configQueue.Enqueue(new Tuple <string, string>(ProjectFile, ConfigurationName));

            if (ExtraConfigurations != null)
            {
                foreach (var extraConfig in ExtraConfigurations)
                {
                    if (string.IsNullOrEmpty(extraConfig))
                    {
                        throw new CommandValidationException("The extra configuration option can't be empty.");
                    }

                    string file;
                    string name;
                    var    split = extraConfig.Split('=');
                    if (split.Length == 1)
                    {
                        file = ProjectFile;
                        name = split[0];
                    }
                    else if (split.Length == 2)
                    {
                        file = split[0];
                        name = split[1];
                    }
                    else
                    {
                        throw new CommandValidationException("There should be only one character '=' in the extra configuration.");
                    }

                    configQueue.Enqueue(new Tuple <string, string>(file, name));
                    Log?.Debug($"Added an extra configuration: {name.PrettyQuote()} in {file.PrettyQuote()}.");
                }
            }

            // check properties
            var availableBuildProperties = GetAvailableBuildProperties();
            var keyValueProperties       = new Dictionary <string, string>();

            if (BuildProperties != null)
            {
                foreach (var buildProperty in BuildProperties)
                {
                    var split = buildProperty.Split('=');
                    if (split.Length != 2)
                    {
                        throw new CommandValidationException($"There should be exactly one character '=' in the property {buildProperty.PrettyQuote()}.");
                    }
                    if (!availableBuildProperties.ContainsKey(split[0]))
                    {
                        throw new CommandValidationException($"The property {split[0].PrettyQuote()} does not exist, use the option {PropertyHelpLongName} to list the available properties.");
                    }
                    if (!keyValueProperties.ContainsKey(split[0]))
                    {
                        keyValueProperties.Add(split[0], split[1]);
                    }
                    else
                    {
                        Log?.Debug($"The property {split[0]} has been defined several times, we keep only the latest.");
                        keyValueProperties[split[0]] = split[1];
                    }
                }
            }

            // check variables
            List <OeVariable> addedVariables = null;

            if (BuildVariables != null)
            {
                addedVariables = new List <OeVariable>();
                foreach (var kpv in BuildVariables)
                {
                    var split = kpv.Split('=');
                    if (split.Length != 2)
                    {
                        throw new CommandValidationException($"There should be exactly one character '=' in the variable definition {kpv.PrettyQuote()}.");
                    }
                    addedVariables.Add(new OeVariable {
                        Name  = split[0],
                        Value = split[1]
                    });
                }
            }

            var config = OeProject.GetConfiguration(configQueue);

            using (var builder = new BuilderAuto(config)) {
                builder.BuildConfiguration.Properties.SetPropertiesFromKeyValuePairs(keyValueProperties);
                if (addedVariables != null)
                {
                    builder.BuildConfiguration.Variables.AddRange(addedVariables);
                }
                builder.BuildConfiguration.Properties.DlcDirectoryPath = GetDlcPath();
                builder.CancelToken = CancelToken;
                builder.Log         = GetLogger();
                builder.Build();


                // display compilation error.
                var compilationProblems = builder.CompilationProblems;
                if (compilationProblems.Count > 0)
                {
                    Out.WriteErrorOnNewLine(null);
                    Out.WriteErrorOnNewLine("COMPILATION ERRORS", ConsoleColor.Yellow, 1);
                    var groups = compilationProblems.GroupBy(cp => cp.FilePath).ToList();
                    var i      = 0;
                    foreach (var filePathGrouped in groups)
                    {
                        Out.WriteErrorOnNewLine($"{(groups.Count - 1 == i ? "└─ " : "├─ ")}In {filePathGrouped.Key}:", indentation: 1);
                        var j = 0;
                        foreach (var problem in filePathGrouped)
                        {
                            Out.WriteErrorOnNewLine($"{(groups.Count - 1 == i ? "   " : "│  ")}{(filePathGrouped.Count() - 1 == j ? "└─ " : "├─ ")}", indentation: 1);
                            Out.WriteError($"Line {problem.Line}, Column {problem.Column}, error {problem.ErrorNumber} : {(string.IsNullOrEmpty(problem.FilePath) ? problem.Message : problem.Message.Replace(problem.FilePath, Path.GetFileName(problem.FilePath)))}", problem is OeCompilationWarning ? ConsoleColor.Yellow : ConsoleColor.Red, 1);
                            j++;
                        }
                        i++;
                    }
                }

                // display exceptions.
                var taskExceptions = builder.TaskExecutionExceptions;
                if (taskExceptions.Count > 0)
                {
                    Out.WriteErrorOnNewLine(null);
                    Out.WriteErrorOnNewLine("TASK EXCEPTIONS", ConsoleColor.Yellow, 1);
                    var i = 0;
                    foreach (var exception in taskExceptions)
                    {
                        Out.WriteErrorOnNewLine(taskExceptions.Count - 1 == i ? "└─ " : "├─ ", indentation: 1);
                        Out.WriteError(exception.Message, exception.IsWarning ? ConsoleColor.Yellow : ConsoleColor.Red, 3);
                        i++;
                    }
                }
            }
            return(0);
        }