From 1eb0162208b37f577d35ba775f007202b285900a Mon Sep 17 00:00:00 2001 From: David Steele Date: Mon, 9 Oct 2023 14:03:43 -0400 Subject: [PATCH] Build command and configuration reference in C. Migrate generation of these files from help.xml to the intermediate documentation format. This allows us to share a lot of code that is already in C and remove duplicated code in Perl. More duplicate code can be removed in Perl once man generation is migrated. Also update the unit test harness to allow testing of modules in the doc directory. --- doc/doc.pl | 18 + doc/lib/pgBackRestDoc/Common/DocConfig.pm | 395 ------------------ doc/lib/pgBackRestDoc/Common/DocRender.pm | 17 +- doc/manifest.xml | 6 +- doc/src/build/config/config.yaml | 97 +++++ doc/src/build/help/help.xml | 122 ++++++ doc/src/command/build/build.c | 36 ++ doc/src/command/build/build.h | 14 + doc/src/command/build/reference.c | 339 +++++++++++++++ doc/src/command/build/reference.h | 20 + doc/src/command/help/meson.build | 17 + doc/src/config/load.c | 123 ++++++ doc/src/config/load.h | 13 + doc/src/config/meson.build | 16 + doc/src/main.c | 99 +++++ doc/src/meson.build | 75 ++++ doc/xml/dtd/doc.dtd | 2 +- meson.build | 1 + src/build/help/help.xml | 4 +- test/ci.pl | 4 +- test/define.yaml | 12 + .../lib/pgBackRestTest/Common/CoverageTest.pm | 11 +- test/src/command/test/build.c | 5 +- test/src/module/doc/buildTest.c | 331 +++++++++++++++ test/src/module/test/testTest.c | 10 +- 25 files changed, 1366 insertions(+), 421 deletions(-) create mode 100644 doc/src/build/config/config.yaml create mode 100644 doc/src/build/help/help.xml create mode 100644 doc/src/command/build/build.c create mode 100644 doc/src/command/build/build.h create mode 100644 doc/src/command/build/reference.c create mode 100644 doc/src/command/build/reference.h create mode 100644 doc/src/command/help/meson.build create mode 100644 doc/src/config/load.c create mode 100644 doc/src/config/load.h create mode 100644 doc/src/config/meson.build create mode 100644 doc/src/main.c create mode 100644 doc/src/meson.build create mode 100644 test/src/module/doc/buildTest.c diff --git a/doc/doc.pl b/doc/doc.pl index 45b6a9edb..35e261771 100755 --- a/doc/doc.pl +++ b/doc/doc.pl @@ -220,6 +220,24 @@ eval $rhVariableOverride->{$strKey} = $rhKeyVariableOverride->{$strKey}; } + # Build C code + my $strBuildPath = "${strBasePath}/output/build"; + my $strRepoPath = dirname($strBasePath); + my $strBuildNinja = "${strBuildPath}/build.ninja"; + + &log(INFO, "build C helper"); + + if (!-e $strBuildNinja) + { + executeTest("meson setup -Dwerror=true -Dfatal-errors=true -Dbuildtype=debug ${strBuildPath} ${strRepoPath}"); + } + + executeTest("ninja -C ${strBuildPath} doc/src/doc-pgbackrest"); + executeTest( + "${strBuildPath}/doc/src/doc-pgbackrest --repo-path=${strRepoPath}" . + ($strLogLevel ne 'info' ? " --log-level=${strLogLevel}" : ''), + {bShowOutputAsync => true}); + # Load the manifest my $oManifest = new pgBackRestDoc::Common::DocManifest( $oStorageDoc, \@stryRequire, \@stryInclude, \@stryExclude, $rhKeyVariableOverride, $rhVariableOverride, diff --git a/doc/lib/pgBackRestDoc/Common/DocConfig.pm b/doc/lib/pgBackRestDoc/Common/DocConfig.pm index d2cb3bae7..480813136 100644 --- a/doc/lib/pgBackRestDoc/Common/DocConfig.pm +++ b/doc/lib/pgBackRestDoc/Common/DocConfig.pm @@ -19,18 +19,11 @@ use pgBackRestDoc::ProjectInfo; #################################################################################################################################### # Help types #################################################################################################################################### -use constant CONFIG_HELP_BETA => 'beta'; use constant CONFIG_HELP_COMMAND => 'command'; push @EXPORT, qw(CONFIG_HELP_COMMAND); -use constant CONFIG_HELP_CURRENT => 'current'; -use constant CONFIG_HELP_DEFAULT => 'default'; use constant CONFIG_HELP_DESCRIPTION => 'description'; push @EXPORT, qw(CONFIG_HELP_DESCRIPTION); -use constant CONFIG_HELP_EXAMPLE => 'example'; use constant CONFIG_HELP_INTERNAL => 'internal'; -use constant CONFIG_HELP_NAME => 'name'; -use constant CONFIG_HELP_NAME_ALT => 'name-alt'; - push @EXPORT, qw(CONFIG_HELP_NAME_ALT); use constant CONFIG_HELP_OPTION => 'option'; push @EXPORT, qw(CONFIG_HELP_OPTION); use constant CONFIG_HELP_SECTION => 'section'; @@ -97,54 +90,6 @@ sub docConfigOptionDefault push @EXPORT, qw(docConfigOptionDefault); -#################################################################################################################################### -# Get the allowed setting range for the option if it exists -#################################################################################################################################### -sub docConfigOptionRange -{ - my $strOption = shift; - my $strCommand = shift; - - # Get the command define - my $oCommandDefine = docConfigCommandDefine($strOption, $strCommand); - - # Check for default in command - if (defined($oCommandDefine) && defined($$oCommandDefine{&CFGDEF_ALLOW_RANGE})) - { - return $$oCommandDefine{&CFGDEF_ALLOW_RANGE}[0], $$oCommandDefine{&CFGDEF_ALLOW_RANGE}[1]; - } - - # If defined return, else try to grab the global default - return $rhConfigDefine->{$strOption}{&CFGDEF_ALLOW_RANGE}[0], $rhConfigDefine->{$strOption}{&CFGDEF_ALLOW_RANGE}[1]; -} - -push @EXPORT, qw(docConfigOptionRange); - -#################################################################################################################################### -# Get the option type -#################################################################################################################################### -sub docConfigOptionType -{ - my $strOption = shift; - - return $rhConfigDefine->{$strOption}{&CFGDEF_TYPE}; -} - -push @EXPORT, qw(docConfigOptionType); - -#################################################################################################################################### -# Test the option type -#################################################################################################################################### -sub docConfigOptionTypeTest -{ - my $strOption = shift; - my $strType = shift; - - return docConfigOptionType($strOption) eq $strType; -} - -push @EXPORT, qw(docConfigOptionTypeTest); - #################################################################################################################################### # CONSTRUCTOR #################################################################################################################################### @@ -317,10 +262,8 @@ sub process $$oCommandOption{&CONFIG_HELP_SUMMARY} = $oOptionDoc->nodeGet('summary')->textGet(); $$oCommandOption{&CONFIG_HELP_DESCRIPTION} = $oOptionDoc->textGet(); - $$oCommandOption{&CONFIG_HELP_EXAMPLE} = $oOptionDoc->fieldGet('example'); $oCommandOption->{&CONFIG_HELP_INTERNAL} = cfgDefineCommand()->{$strCommand}{&CFGDEF_INTERNAL} ? true : $oOptionDefine->{$strOption}{&CFGDEF_INTERNAL}; - $oCommandOption->{&CONFIG_HELP_BETA} = $oOptionDefine->{$strOption}{&CFGDEF_BETA}; # If internal is defined for the option/command it overrides everthing else if (defined($oOptionDefine->{$strOption}{&CFGDEF_COMMAND}{$strCommand}{&CFGDEF_INTERNAL})) @@ -329,31 +272,6 @@ sub process $oOptionDefine->{$strOption}{&CFGDEF_COMMAND}{$strCommand}{&CFGDEF_INTERNAL}; } - $$oCommandOption{&CONFIG_HELP_NAME} = $oOptionDoc->paramGet('name'); - - # Generate a list of alternate names - if (defined($rhConfigDefine->{$strOption}{&CFGDEF_DEPRECATE})) - { - my $rhNameAlt = {}; - - foreach my $strNameAlt (sort(keys(%{$rhConfigDefine->{$strOption}{&CFGDEF_DEPRECATE}}))) - { - $strNameAlt =~ s/\?//g; - - if ($strNameAlt ne $strOption) - { - $rhNameAlt->{$strNameAlt} = true; - } - } - - my @stryNameAlt = sort(keys(%{$rhNameAlt})); - - if (@stryNameAlt > 0) - { - $oCommandOption->{&CONFIG_HELP_NAME_ALT} = \@stryNameAlt; - } - } - # If the option did not come from the command also store in global option list. This prevents duplication of commonly # used options. if ($strOptionSource ne CONFIG_HELP_SOURCE_COMMAND) @@ -367,12 +285,8 @@ sub process $$oOption{&CONFIG_HELP_SECTION} = $strSection; } - $$oOption{&CONFIG_HELP_NAME} = $oOptionDoc->paramGet('name'); - $oOption->{&CONFIG_HELP_NAME_ALT} = $oCommandOption->{&CONFIG_HELP_NAME_ALT}; $$oOption{&CONFIG_HELP_DESCRIPTION} = $$oCommandOption{&CONFIG_HELP_DESCRIPTION}; - $$oOption{&CONFIG_HELP_EXAMPLE} = $oOptionDoc->fieldGet('example'); $oOption->{&CONFIG_HELP_INTERNAL} = $oOptionDefine->{$strOption}{&CFGDEF_INTERNAL}; - $oOption->{&CONFIG_HELP_BETA} = $oOptionDefine->{$strOption}{&CFGDEF_BETA}; } } } @@ -603,313 +517,4 @@ sub manGetFormatText return $strResult; } -#################################################################################################################################### -# helpConfigDocGet -# -# Get the xml for configuration help. -#################################################################################################################################### -sub helpConfigDocGet -{ - my $self = shift; - - # Assign function parameters, defaults, and log debug info - my $strOperation = logDebugParam(__PACKAGE__ . '->helpConfigDocGet'); - - # Build a hash of the sections - my $oConfigHash = $self->{oConfigHash}; - my $oConfigDoc = $self->{oDoc}->nodeGet('config'); - my $oSectionHash = {}; - - foreach my $strOption (sort(keys(%{$$oConfigHash{&CONFIG_HELP_OPTION}}))) - { - my $oOption = $$oConfigHash{&CONFIG_HELP_OPTION}{$strOption}; - - if (defined($$oOption{&CONFIG_HELP_SECTION})) - { - $$oSectionHash{$$oOption{&CONFIG_HELP_SECTION}}{$strOption} = true; - } - } - - my $oDoc = new pgBackRestDoc::Common::Doc(); - $oDoc->paramSet('title', $oConfigDoc->paramGet('title')); - - # set the description for use as a meta tag - $oDoc->fieldSet('description', $oConfigDoc->fieldGet('description')); - - # Output the introduction - my $oIntroSectionDoc = $oDoc->nodeAdd('section', undef, {id => 'introduction'}); - $oIntroSectionDoc->nodeAdd('title')->textSet('Introduction'); - $oIntroSectionDoc->textSet($oConfigDoc->textGet()); - - foreach my $strSection (sort(keys(%{$oSectionHash}))) - { - my $oSectionElement = $oDoc->nodeAdd('section', undef, {id => "section-${strSection}"}); - - my $oSectionDoc = $oConfigDoc->nodeGet('config-section-list')->nodeGetById('config-section', $strSection); - - # Set the summary text for the section - $oSectionElement->textSet($oSectionDoc->textGet()); - - $oSectionElement-> - nodeAdd('title')->textSet( - {name => 'text', - children=> [$oSectionDoc->paramGet('name') . ' Options (', {name => 'id', value => $strSection}, ')']}); - - foreach my $strOption (sort(keys(%{$$oSectionHash{$strSection}}))) - { - # Skip internal options - next if $oConfigHash->{&CONFIG_HELP_OPTION}{$strOption}{&CONFIG_HELP_INTERNAL}; - - $self->helpOptionGet(undef, $strOption, $oSectionElement, $$oConfigHash{&CONFIG_HELP_OPTION}{$strOption}); - } - } - - # Return from function and log return values if any - return logDebugReturn - ( - $strOperation, - {name => 'oDoc', value => $oDoc} - ); -} - -#################################################################################################################################### -# helpCommandDocGet -# -# Get the xml for command help. -#################################################################################################################################### -sub helpCommandDocGet -{ - my $self = shift; - - # Assign function parameters, defaults, and log debug info - my $strOperation = logDebugParam(__PACKAGE__ . '->helpCommandDocGet'); - - # Working variables - my $oConfigHash = $self->{oConfigHash}; - my $oOperationDoc = $self->{oDoc}->nodeGet('operation'); - my $oOptionDefine = cfgDefine(); - - my $oDoc = new pgBackRestDoc::Common::Doc(); - $oDoc->paramSet('title', $oOperationDoc->paramGet('title')); - - # set the description for use as a meta tag - $oDoc->fieldSet('description', $oOperationDoc->fieldGet('description')); - - # Output the introduction - my $oIntroSectionDoc = $oDoc->nodeAdd('section', undef, {id => 'introduction'}); - $oIntroSectionDoc->nodeAdd('title')->textSet('Introduction'); - $oIntroSectionDoc->textSet($oOperationDoc->textGet()); - - foreach my $strCommand (sort(keys(%{$$oConfigHash{&CONFIG_HELP_COMMAND}}))) - { - # Skip internal commands - next if $oConfigHash->{&CONFIG_HELP_COMMAND}{$strCommand}{&CONFIG_HELP_INTERNAL}; - - my $oCommandHash = $$oConfigHash{&CONFIG_HELP_COMMAND}{$strCommand}; - my $oSectionElement = $oDoc->nodeAdd('section', undef, {id => "command-${strCommand}"}); - - my $oCommandDoc = $oOperationDoc->nodeGet('command-list')->nodeGetById('command', $strCommand); - - $oSectionElement-> - nodeAdd('title')->textSet( - {name => 'text', - children=> [$oCommandDoc->paramGet('name') . ' Command (', {name => 'id', value => $strCommand}, ')']}); - - $oSectionElement->textSet($$oCommandHash{&CONFIG_HELP_DESCRIPTION}); - - # use Data::doc; - # confess Dumper($oDoc->{oDoc}); - - if (defined($$oCommandHash{&CONFIG_HELP_OPTION})) - { - my $oCategory = {}; - - foreach my $strOption (sort(keys(%{$$oCommandHash{&CONFIG_HELP_OPTION}}))) - { - # Skip internal options - next if $$oCommandHash{&CONFIG_HELP_OPTION}{$strOption}{&CONFIG_HELP_INTERNAL}; - - # Skip secure options that can't be defined on the command line - next if ($rhConfigDefine->{$strOption}{&CFGDEF_SECURE}); - - my ($oOption, $strCategory) = helpCommandDocGetOptionFind($oConfigHash, $oOptionDefine, $strCommand, $strOption); - - $$oCategory{$strCategory}{$strOption} = $oOption; - } - - # Iterate sections - foreach my $strCategory (sort(keys(%{$oCategory}))) - { - my $oOptionListElement = $oSectionElement->nodeAdd( - 'section', undef, {id => "category-${strCategory}", toc => 'n'}); - - $oOptionListElement-> - nodeAdd('title')->textSet(ucfirst($strCategory) . ' Options'); - - # Iterate options - foreach my $strOption (sort(keys(%{$$oCategory{$strCategory}}))) - { - $self->helpOptionGet($strCommand, $strOption, $oOptionListElement, - $$oCommandHash{&CONFIG_HELP_OPTION}{$strOption}); - } - } - } - } - - # Return from function and log return values if any - return logDebugReturn - ( - $strOperation, - {name => 'oDoc', value => $oDoc} - ); -} - -# Helper function for helpCommandDocGet() to find options. The option may be stored with the command or in the option list depending -# on whether it's generic or command-specific -sub helpCommandDocGetOptionFind -{ - my $oConfigHelpData = shift; - my $oOptionDefine = shift; - my $strCommand = shift; - my $strOption = shift; - - # Get section from the option - my $strSection = $oConfigHelpData->{&CONFIG_HELP_OPTION}{$strOption}{&CONFIG_HELP_SECTION}; - - # Get option from the command to start - my $oOption = $$oConfigHelpData{&CONFIG_HELP_COMMAND}{$strCommand}{&CONFIG_HELP_OPTION}{$strOption}; - - # If the option has a section (i.e. not command-line only) then it comes from the standard option reference - if ($$oOption{&CONFIG_HELP_SOURCE} eq CONFIG_HELP_SOURCE_SECTION) - { - $oOption = $$oConfigHelpData{&CONFIG_HELP_OPTION}{$strOption}; - } - - # Reduce the sections that are shown in the command help. This is the same logic as help.c. - if (!defined($strSection) || - ($strSection ne 'general' && $strSection ne 'log' && $strSection ne 'repository' && $strSection ne 'stanza')) - { - $strSection = 'command'; - } - - return $oOption, $strSection; -} - -#################################################################################################################################### -# helpOptionGet -# -# Get the xml for an option. -#################################################################################################################################### -sub helpOptionGet -{ - my $self = shift; - my $strCommand = shift; - my $strOption = shift; - my $oParentElement = shift; - my $oOptionHash = shift; - - # Create the option section - my $oOptionElement = $oParentElement->nodeAdd( - 'section', undef, {id => "option-${strOption}", toc => defined($strCommand) ? 'n' : 'y'}); - - # Set the option section title - $oOptionElement-> - nodeAdd('title')->textSet( - {name => 'text', - children=> [$$oOptionHash{&CONFIG_HELP_NAME} . ' Option (', {name => 'id', value => "--${strOption}"}, ')']}); - - # Add the option summary and description - $oOptionElement-> - nodeAdd('p')->textSet($$oOptionHash{&CONFIG_HELP_SUMMARY}); - - # Add beta warning - if ($$oOptionHash{&CONFIG_HELP_BETA}) - { - $oOptionElement->nodeAdd('p')->textSet({name => 'text', children => ['FOR BETA TESTING ONLY. DO NOT USE IN PRODUCTION.']}); - } - - $oOptionElement-> - nodeAdd('p')->textSet($$oOptionHash{&CONFIG_HELP_DESCRIPTION}); - - # Get the default value (or required=n if there is no default) - my $strCodeBlock; - - if (defined(docConfigOptionDefault($strOption, $strCommand))) - { - my $strDefault; - - if ($strOption eq CFGOPT_REPO_HOST_CMD || $strOption eq CFGOPT_PG_HOST_CMD) - { - $strDefault = '[INSTALL-PATH]/' . PROJECT_EXE; - } - else - { - if (docConfigOptionTypeTest($strOption, CFGDEF_TYPE_BOOLEAN)) - { - $strDefault = docConfigOptionDefault($strOption, $strCommand) ? 'y' : 'n'; - } - else - { - $strDefault = docConfigOptionDefault($strOption, $strCommand); - } - } - - $strCodeBlock = "default: ${strDefault}"; - } - # This won't work correctly until there is some notion of dependency - # elsif (optionRequired($strOption, $strCommand)) - # { - # $strCodeBlock = 'required: y'; - # } - - # Get the allowed range if it exists - my ($strRangeMin, $strRangeMax) = docConfigOptionRange($strOption, $strCommand); - - if (defined($strRangeMin)) - { - $strCodeBlock .= (defined($strCodeBlock) ? "\n" : '') . "allowed: ${strRangeMin}-${strRangeMax}"; - } - - # Get the example - my $strExample = ''; - - my $strOptionPrefix = $rhConfigDefine->{$strOption}{&CFGDEF_GROUP}; - my $strOptionIndex = defined($strOptionPrefix) ? - "${strOptionPrefix}1-" . substr($strOption, length($strOptionPrefix) + 1) : $strOption; - - if (defined($strCommand) && docConfigOptionTypeTest($strOption, CFGDEF_TYPE_BOOLEAN)) - { - if ($$oOptionHash{&CONFIG_HELP_EXAMPLE} ne 'n' && $$oOptionHash{&CONFIG_HELP_EXAMPLE} ne 'y') - { - confess &log(ERROR, "option ${strOption} example should be boolean but value is: " . - $$oOptionHash{&CONFIG_HELP_EXAMPLE}); - } - - $strExample = '--' . ($$oOptionHash{&CONFIG_HELP_EXAMPLE} eq 'n' ? 'no-' : '') . $strOptionIndex; - } - else - { - if (defined($strCommand)) - { - $strExample = '--'; - } - - $strExample .= "${strOptionIndex}=" . $$oOptionHash{&CONFIG_HELP_EXAMPLE}; - } - - $strCodeBlock .= (defined($strCodeBlock) ? "\n" : '') . "example: ${strExample}"; - - $oOptionElement-> - nodeAdd('code-block')->valueSet($strCodeBlock); - - # Output deprecated names - if (defined($oOptionHash->{&CONFIG_HELP_NAME_ALT})) - { - my $strCaption = 'Deprecated Name' . (@{$oOptionHash->{&CONFIG_HELP_NAME_ALT}} > 1 ? 's' : ''); - - $oOptionElement-> - nodeAdd('p')->textSet("${strCaption}: " . join(', ', @{$oOptionHash->{&CONFIG_HELP_NAME_ALT}})); - } -} - 1; diff --git a/doc/lib/pgBackRestDoc/Common/DocRender.pm b/doc/lib/pgBackRestDoc/Common/DocRender.pm index 1f928e265..6c0df2932 100644 --- a/doc/lib/pgBackRestDoc/Common/DocRender.pm +++ b/doc/lib/pgBackRestDoc/Common/DocRender.pm @@ -204,22 +204,7 @@ sub new new pgBackRestDoc::Common::DocConfig(${$self->{oManifest}->sourceGet('help')}{doc}, $self); } - if (defined($$oRenderOut{source}) && $$oRenderOut{source} eq 'help' && $self->{oManifest}->isBackRest()) - { - if ($self->{strRenderOutKey} eq 'configuration') - { - $self->{oDoc} = $self->{oReference}->helpConfigDocGet(); - } - elsif ($self->{strRenderOutKey} eq 'command') - { - $self->{oDoc} = $self->{oReference}->helpCommandDocGet(); - } - else - { - confess &log(ERROR, "cannot render $self->{strRenderOutKey} from source $$oRenderOut{source}"); - } - } - elsif (defined($$oRenderOut{source}) && $$oRenderOut{source} eq 'release' && $self->{oManifest}->isBackRest()) + if (defined($$oRenderOut{source}) && $$oRenderOut{source} eq 'release' && $self->{oManifest}->isBackRest()) { require pgBackRestDoc::Custom::DocCustomRelease; pgBackRestDoc::Custom::DocCustomRelease->import(); diff --git a/doc/manifest.xml b/doc/manifest.xml index 22886c3b6..659f32a5c 100644 --- a/doc/manifest.xml +++ b/doc/manifest.xml @@ -86,6 +86,8 @@ + + @@ -101,8 +103,8 @@ - - + + diff --git a/doc/src/build/config/config.yaml b/doc/src/build/config/config.yaml new file mode 100644 index 000000000..e029b594d --- /dev/null +++ b/doc/src/build/config/config.yaml @@ -0,0 +1,97 @@ +#################################################################################################################################### +# Configuration Definition +#################################################################################################################################### + +#################################################################################################################################### +# Commands +#################################################################################################################################### +command: + help: + log-level-default: DEBUG + parameter-allowed: true + + build: + parameter-allowed: true + + noop: + internal: true + + version: + log-level-default: DEBUG + +#################################################################################################################################### +# Option groups that are not used but must be present for modules to compile +#################################################################################################################################### +optionGroup: + pg: {} + repo: {} + +#################################################################################################################################### +# Options +#################################################################################################################################### +option: + # General options + #--------------------------------------------------------------------------------------------------------------------------------- + buffer-size: + type: size + internal: true + default: 64KiB + allow-list: + - 16KiB + - 32KiB + - 64KiB + - 128KiB + - 256KiB + - 512KiB + - 1MiB + + neutral-umask: + type: boolean + internal: true + default: true + + repo-path: + type: string + default: pgbackrest + command: + build: {} + + # Logging options + #--------------------------------------------------------------------------------------------------------------------------------- + log-level: + type: string-id + default: info + allow-list: + - off + - error + - warn + - info + - detail + - debug + - trace + + log-timestamp: + type: boolean + default: true + negate: true + command: log-level + + # Options that are not used but must be present for modules to compile. All must have a default or not be required. + #--------------------------------------------------------------------------------------------------------------------------------- + beta: {type: boolean, default: false, command: {noop: {}}} + compress-level-network: {type: string, required: false, command: {noop: {}}} + config: { + type: string, internal: true, default: CFGOPTDEF_CONFIG_PATH "/" PROJECT_CONFIG_FILE, default-literal: true, negate: true} + config-path: {type: string, required: false, command: {noop: {}}} + config-include-path: { + type: string, default: CFGOPTDEF_CONFIG_PATH "/" PROJECT_CONFIG_INCLUDE_PATH, default-literal: true, command: {noop: {}}} + job-retry: {type: string, required: false, deprecate: {job-retry-old: {}}, command: {noop: {}}} + job-retry-interval: {type: string, required: false, command: {noop: {}}} + log-level-file: {type: string, required: false, command: {noop: {}}} + log-level-stderr: {type: string, required: false, command: {noop: {}}} + pg: {type: string, required: false, command: {noop: {}}} + pg-path: {type: string, required: false, command: {noop: {}}} + repo-type: {type: string, required: false, command: {noop: {}}} + repo: {type: string, required: false, command: {noop: {}}} + spool-path: {type: string, required: false, command: {noop: {}}} + stanza: {type: string, required: false, command: {noop: {}}} diff --git a/doc/src/build/help/help.xml b/doc/src/build/help/help.xml new file mode 100644 index 000000000..7ff990d19 --- /dev/null +++ b/doc/src/build/help/help.xml @@ -0,0 +1,122 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Get help. + + +

Three levels of help are provided. If no command is specified then general help will be displayed. If a command is specified (e.g. pgbackrest help backup) then a full description of the command will be displayed along with a list of valid options. If an option is specified in addition to a command (e.g. pgbackrest help backup type) then a full description of the option as it applies to the command will be displayed.

+
+
+ + +

+ + + Build documentation. + + +

Build the specified documentation module.

+
+
+ + + Get version. + + +

Displays installed version.

+
+
+
+
+
diff --git a/doc/src/command/build/build.c b/doc/src/command/build/build.c new file mode 100644 index 000000000..b336ce4ec --- /dev/null +++ b/doc/src/command/build/build.c @@ -0,0 +1,36 @@ +/*********************************************************************************************************************************** +Build Command +***********************************************************************************************************************************/ +#include "build.auto.h" + +#include "command/build/reference.h" +#include "common/debug.h" +#include "common/log.h" +#include "storage/posix/storage.h" +#include "version.h" + +/**********************************************************************************************************************************/ +void +cmdBuild(const String *const pathRepo) +{ + FUNCTION_LOG_BEGIN(logLevelDebug); + FUNCTION_LOG_PARAM(STRING, pathRepo); + FUNCTION_LOG_END(); + + MEM_CONTEXT_TEMP_BEGIN() + { + Storage *const storageRepo = storagePosixNewP(pathRepo, .write = true); + const BldCfg bldCfg = bldCfgParse(storageRepo); + const BldHlp bldHlp = bldHlpParse(storageRepo, bldCfg, true); + + storagePutP( + storageNewWriteP(storageRepo, STRDEF("doc/output/xml/command.xml")), + xmlDocumentBuf(referenceCommandRender(&bldCfg, &bldHlp))); + storagePutP( + storageNewWriteP(storageRepo, STRDEF("doc/output/xml/configuration.xml")), + xmlDocumentBuf(referenceConfigurationRender(&bldCfg, &bldHlp))); + } + MEM_CONTEXT_TEMP_END(); + + FUNCTION_LOG_RETURN_VOID(); +} diff --git a/doc/src/command/build/build.h b/doc/src/command/build/build.h new file mode 100644 index 000000000..e027817e1 --- /dev/null +++ b/doc/src/command/build/build.h @@ -0,0 +1,14 @@ +/*********************************************************************************************************************************** +Build Documentation +***********************************************************************************************************************************/ +#ifndef DOC_COMMAND_BUILD_H +#define DOC_COMMAND_BUILD_H + +#include "common/type/string.h" + +/*********************************************************************************************************************************** +Functions +***********************************************************************************************************************************/ +void cmdBuild(const String *pathRepo); + +#endif diff --git a/doc/src/command/build/reference.c b/doc/src/command/build/reference.c new file mode 100644 index 000000000..d644a8aaa --- /dev/null +++ b/doc/src/command/build/reference.c @@ -0,0 +1,339 @@ +/*********************************************************************************************************************************** +Build Command +***********************************************************************************************************************************/ +#include "build.auto.h" + +#include "build/config/parse.h" +#include "build/help/parse.h" +#include "command/build/build.h" +#include "common/debug.h" +#include "common/log.h" +#include "storage/posix/storage.h" +#include "storage/storage.h" +#include "version.h" + +/*********************************************************************************************************************************** +Build option +***********************************************************************************************************************************/ +static void +referenceOptionRender( + XmlNode *const xmlSection, const BldCfgOptionCommand *const optCmdCfg, const BldCfgOption *const optCfg, + const BldHlpOption *const optHlp) +{ + FUNCTION_LOG_BEGIN(logLevelDebug); + FUNCTION_LOG_PARAM(XML_NODE, xmlSection); + FUNCTION_LOG_PARAM_P(VOID, optCmdCfg); + FUNCTION_LOG_PARAM_P(VOID, optCfg); + FUNCTION_LOG_PARAM_P(VOID, optHlp); + FUNCTION_LOG_END(); + + MEM_CONTEXT_TEMP_BEGIN() + { + XmlNode *const xmlOption = xmlNodeAdd(xmlSection, STRDEF("section")); + XmlNode *const xmlOptionTitle = xmlNodeAdd(xmlOption, STRDEF("title")); + + xmlNodeAttributeSet(xmlOption, STRDEF("id"), strNewFmt("option-%s", strZ(optHlp->name))); + xmlNodeContentSet(xmlOptionTitle, strNewFmt("%s Option (", strZ(optHlp->title))); + xmlNodeContentSet(xmlNodeAdd(xmlOptionTitle, STRDEF("id")), strNewFmt("--%s", strZ(optHlp->name))); + xmlNodeContentSet(xmlOptionTitle, STRDEF(")")); + xmlNodeChildAdd(xmlNodeAdd(xmlOption, STRDEF("p")), optHlp->summary); + + if (optCfg->beta) + xmlNodeContentSet(xmlNodeAdd(xmlOption, STRDEF("p")), STRDEF("FOR BETA TESTING ONLY. DO NOT USE IN PRODUCTION.")); + + xmlNodeChildAdd(xmlNodeAdd(xmlOption, STRDEF("p")), optHlp->description); + + // Add default value + StringList *const blockList = strLstNew(); + const String *const defaultValue = + optCmdCfg != NULL && optCmdCfg->defaultValue != NULL ? optCmdCfg->defaultValue : optCfg->defaultValue; + + if (defaultValue != NULL) + { + if (strEq(optCfg->type, OPT_TYPE_BOOLEAN_STR)) + strLstAddFmt(blockList, "default: %s", strEqZ(defaultValue, "true") ? "y" : "n"); + else + strLstAddFmt(blockList, "default: %s", strZ(defaultValue)); + } + + // Add allow range + if (optCfg->allowRangeMin != NULL) + { + ASSERT(optCfg->allowRangeMax != NULL); + strLstAddFmt(blockList, "allowed: %s-%s", strZ(optCfg->allowRangeMin), strZ(optCfg->allowRangeMax)); + } + + // Add examples + if (optHlp->exampleList != NULL) + { + String *output = strCatZ(strNew(), "example: "); + const String *const option = + optCfg->group != NULL ? + strNewFmt("%s1%s", strZ(optCfg->group), strZ(strSub(optCfg->name, strSize(optCfg->group)))) : optCfg->name; + + for (unsigned int exampleIdx = 0; exampleIdx < strLstSize(optHlp->exampleList); exampleIdx++) + { + const String *const example = strLstGet(optHlp->exampleList, exampleIdx); + + // If Command line example + if (optCmdCfg != NULL) + { + if (exampleIdx != 0) + strCatZ(output, " "); + + strCatZ(output, "--"); + + if (strEq(optCfg->type, OPT_TYPE_BOOLEAN_STR) && strEqZ(example, "n")) + strCatZ(output, "no-"); + } + // Else configuration file example + else if (exampleIdx != 0) + { + strLstAdd(blockList, output); + output = strCatZ(strNew(), "example: "); + } + + strCat(output, option); + + if (optCmdCfg == NULL || !strEq(optCfg->type, OPT_TYPE_BOOLEAN_STR)) + strCatFmt(output, "=%s", strZ(example)); + } + + strLstAdd(blockList, output); + } + + if (!strLstEmpty(blockList)) + xmlNodeContentSet(xmlNodeAdd(xmlOption, STRDEF("code-block")), strLstJoin(blockList, "\n")); + + // Add deprecated names + if (optCfg->deprecateList != NULL) + { + String *const deprecateStr = strNew(); + + for (unsigned int deprecateIdx = 0; deprecateIdx < lstSize(optCfg->deprecateList); deprecateIdx++) + { + const BldCfgOptionDeprecate *const deprecate = lstGet(optCfg->deprecateList, deprecateIdx); + + if (!strEq(deprecate->name, optCfg->name)) + strCatFmt(deprecateStr, "%s %s", !strEmpty(deprecateStr) ? "," : "", strZ(deprecate->name)); + } + + if (!strEmpty(deprecateStr)) + { + xmlNodeContentSet( + xmlNodeAdd(xmlOption, STRDEF("p")), + strNewFmt("Deprecated Name%s:%s", lstSize(optCfg->deprecateList) > 1 ? "s" : "", strZ(deprecateStr))); + } + } + } + MEM_CONTEXT_TEMP_END(); + + FUNCTION_LOG_RETURN_VOID(); +} + +/**********************************************************************************************************************************/ +XmlDocument * +referenceConfigurationRender(const BldCfg *const bldCfg, const BldHlp *const bldHlp) +{ + FUNCTION_LOG_BEGIN(logLevelDebug); + FUNCTION_LOG_PARAM_P(VOID, bldCfg); + FUNCTION_LOG_PARAM_P(VOID, bldHlp); + FUNCTION_LOG_END(); + + XmlDocument *const result = xmlDocumentNewP(STRDEF("doc"), .dtdName = STRDEF("doc"), .dtdFile = STRDEF("doc.dtd")); + + MEM_CONTEXT_TEMP_BEGIN() + { + XmlNode *const xmlRoot = xmlDocumentRoot(result); + + // Set attributes in root node + xmlNodeAttributeSet(xmlRoot, STRDEF("title"), STRDEF("{[project]}")); + xmlNodeAttributeSet(xmlRoot, STRDEF("subtitle"), bldHlp->optTitle); + xmlNodeAttributeSet(xmlRoot, STRDEF("toc"), STRDEF("y")); + + // Set description + xmlNodeContentSet(xmlNodeAdd(xmlRoot, STRDEF("description")), bldHlp->optDescription); + + // Set introduction + XmlNode *const xmlIntro = xmlNodeAdd(xmlRoot, STRDEF("section")); + + xmlNodeAttributeSet(xmlIntro, STRDEF("id"), STRDEF("introduction")); + xmlNodeContentSet(xmlNodeAdd(xmlIntro, STRDEF("title")), STRDEF("Introduction")); + xmlNodeChildAdd(xmlNodeAdd(xmlIntro, STRDEF("text")), bldHlp->optIntroduction); + + for (unsigned int sctIdx = 0; sctIdx < lstSize(bldHlp->sctList); sctIdx++) + { + const BldHlpSection *const section = lstGet(bldHlp->sctList, sctIdx); + XmlNode *const xmlSection = xmlNodeAdd(xmlRoot, STRDEF("section")); + XmlNode *const xmlSectionTitle = xmlNodeAdd(xmlSection, STRDEF("title")); + + xmlNodeAttributeSet(xmlSection, STRDEF("id"), strNewFmt("section-%s", strZ(section->id))); + xmlNodeContentSet(xmlSectionTitle, strNewFmt("%s Options (", strZ(section->name))); + xmlNodeContentSet(xmlNodeAdd(xmlSectionTitle, STRDEF("id")), section->id); + xmlNodeContentSet(xmlSectionTitle, STRDEF(")")); + xmlNodeChildAdd(xmlNodeAdd(xmlSection, STRDEF("text")), section->introduction); + + for (unsigned int optIdx = 0; optIdx < lstSize(bldHlp->optList); optIdx++) + { + const BldHlpOption *const optHlp = lstGet(bldHlp->optList, optIdx); + const BldCfgOption *const optCfg = lstFind(bldCfg->optList, &optHlp->name); + ASSERT(optCfg != NULL); + + if (!strEq(optHlp->section == NULL ? STRDEF("general") : optHlp->section, section->id)) + continue; + + // Skip if option is internal + if (optCfg->internal) + continue; + + referenceOptionRender(xmlSection, NULL, optCfg, optHlp); + } + } + } + MEM_CONTEXT_TEMP_END(); + + FUNCTION_LOG_RETURN(XML_DOCUMENT, result); +} + +/**********************************************************************************************************************************/ +// Helper to remap section names for the command reference +static String * +referenceCommandSection(const String *const section) +{ + FUNCTION_TEST_BEGIN(); + FUNCTION_TEST_PARAM(STRING, section); + FUNCTION_TEST_END(); + + if (section == NULL) + FUNCTION_TEST_RETURN(STRING, strNewZ("general")); + + if (!strEqZ(section, "general") && !strEqZ(section, "log") && !strEqZ(section, "repository") && !strEqZ(section, "stanza")) + FUNCTION_TEST_RETURN(STRING, strNewZ("command")); + + FUNCTION_TEST_RETURN(STRING, strDup(section)); +} + +XmlDocument * +referenceCommandRender(const BldCfg *const bldCfg, const BldHlp *const bldHlp) +{ + FUNCTION_LOG_BEGIN(logLevelDebug); + FUNCTION_LOG_PARAM_P(VOID, bldCfg); + FUNCTION_LOG_PARAM_P(VOID, bldHlp); + FUNCTION_LOG_END(); + + XmlDocument *const result = xmlDocumentNewP(STRDEF("doc"), .dtdName = STRDEF("doc"), .dtdFile = STRDEF("doc.dtd")); + + MEM_CONTEXT_TEMP_BEGIN() + { + XmlNode *const xmlRoot = xmlDocumentRoot(result); + + // Set attributes in root node + xmlNodeAttributeSet(xmlRoot, STRDEF("title"), STRDEF("{[project]}")); + xmlNodeAttributeSet(xmlRoot, STRDEF("subtitle"), bldHlp->cmdTitle); + xmlNodeAttributeSet(xmlRoot, STRDEF("toc"), STRDEF("y")); + + // Set description + xmlNodeContentSet(xmlNodeAdd(xmlRoot, STRDEF("description")), bldHlp->cmdDescription); + + // Set introduction + XmlNode *const xmlIntro = xmlNodeAdd(xmlRoot, STRDEF("section")); + + xmlNodeAttributeSet(xmlIntro, STRDEF("id"), STRDEF("introduction")); + xmlNodeContentSet(xmlNodeAdd(xmlIntro, STRDEF("title")), STRDEF("Introduction")); + xmlNodeChildAdd(xmlNodeAdd(xmlIntro, STRDEF("text")), bldHlp->cmdIntroduction); + + for (unsigned int cmdIdx = 0; cmdIdx < lstSize(bldHlp->cmdList); cmdIdx++) + { + const BldHlpCommand *const cmdHlp = lstGet(bldHlp->cmdList, cmdIdx); + const BldCfgCommand *const cmdCfg = lstFind(bldCfg->cmdList, &cmdHlp->name); + ASSERT(cmdCfg != NULL); + + // Skip internal commands + if (cmdCfg->internal) + continue; + + XmlNode *const xmlSection = xmlNodeAdd(xmlRoot, STRDEF("section")); + XmlNode *const xmlSectionTitle = xmlNodeAdd(xmlSection, STRDEF("title")); + + xmlNodeAttributeSet(xmlSection, STRDEF("id"), strNewFmt("command-%s", strZ(cmdHlp->name))); + xmlNodeContentSet(xmlSectionTitle, strNewFmt("%s Command (", strZ(cmdHlp->title))); + xmlNodeContentSet(xmlNodeAdd(xmlSectionTitle, STRDEF("id")), cmdHlp->name); + xmlNodeContentSet(xmlSectionTitle, STRDEF(")")); + xmlNodeChildAdd(xmlNodeAdd(xmlSection, STRDEF("text")), cmdHlp->description); + + // Build option list for command + StringList *const sctList = strLstNew(); + List *const optCfgList = lstNewP(sizeof(BldCfgOption)); + List *const optHlpList = lstNewP(sizeof(BldHlpOption)); + + for (unsigned int optIdx = 0; optIdx < lstSize(bldCfg->optList); optIdx++) + { + const BldCfgOption *const optCfg = lstGet(bldCfg->optList, optIdx); + + // Skip internal options + if (optCfg->internal || optCfg->secure) + continue; + + // Skip options that are not valid, internal, or do not have the main role for this command + const BldCfgOptionCommand *const optCmdCfg = lstFind(optCfg->cmdList, &cmdCfg->name); + + if (optCmdCfg == NULL || optCmdCfg->internal || !strLstExists(optCmdCfg->roleList, STRDEF("main"))) + continue; + + // Get help from command list or general list + BldHlpOption optHlp = {0}; + + if (cmdHlp->optList != NULL && lstFind(cmdHlp->optList, &optCfg->name) != NULL) + { + optHlp = *((const BldHlpOption *)lstFind(cmdHlp->optList, &optCfg->name)); + optHlp.section = strNewZ("command"); + } + else + { + ASSERT(lstFind(bldHlp->optList, &optCfg->name) != NULL); + optHlp = *((const BldHlpOption *)lstFind(bldHlp->optList, &optCfg->name)); + } + + // Remap section name + optHlp.section = referenceCommandSection(optHlp.section); + + // Add sections + strLstAddIfMissing(sctList, optHlp.section); + + // Add options + lstAdd(optCfgList, optCfg); + lstAdd(optHlpList, &optHlp); + } + + strLstSort(sctList, sortOrderAsc); + + for (unsigned int sctIdx = 0; sctIdx < strLstSize(sctList); sctIdx++) + { + const String *const section = strLstGet(sctList, sctIdx); + XmlNode *const xmlCommandCategory = xmlNodeAdd(xmlSection, STRDEF("section")); + XmlNode *const xmlCommandCategoryTitle = xmlNodeAdd(xmlCommandCategory, STRDEF("title")); + + xmlNodeAttributeSet(xmlCommandCategory, STRDEF("id"), strNewFmt("category-%s", strZ(section))); + xmlNodeAttributeSet(xmlCommandCategory, STRDEF("toc"), STRDEF("n")); + xmlNodeContentSet(xmlCommandCategoryTitle, strNewFmt("%s Options", strZ(strFirstUpper(strDup(section))))); + + for (unsigned int optIdx = 0; optIdx < lstSize(optHlpList); optIdx++) + { + const BldHlpOption *const optHlp = lstGet(optHlpList, optIdx); + const BldCfgOption *const optCfg = lstGet(optCfgList, optIdx); + ASSERT(optCfg != NULL); + const BldCfgOptionCommand *const optCmdCfg = lstFind(optCfg->cmdList, &cmdCfg->name); + ASSERT(optCmdCfg != NULL); + + if (!strEq(optHlp->section, section)) + continue; + + referenceOptionRender(xmlCommandCategory, optCmdCfg, optCfg, optHlp); + } + } + } + } + MEM_CONTEXT_TEMP_END(); + + FUNCTION_LOG_RETURN(XML_DOCUMENT, result); +} diff --git a/doc/src/command/build/reference.h b/doc/src/command/build/reference.h new file mode 100644 index 000000000..d1e5bb16e --- /dev/null +++ b/doc/src/command/build/reference.h @@ -0,0 +1,20 @@ +/*********************************************************************************************************************************** +Build Command and Configuration Reference +***********************************************************************************************************************************/ +#ifndef DOC_COMMAND_BUILD_REFERENCE_H +#define DOC_COMMAND_BUILD_REFERENCE_H + +#include "build/config/parse.h" +#include "build/help/parse.h" +#include "common/type/xml.h" + +/*********************************************************************************************************************************** +Functions +***********************************************************************************************************************************/ +// Build command reference +XmlDocument *referenceCommandRender(const BldCfg *bldCfg, const BldHlp *bldHlp); + +// Build configuration reference +XmlDocument *referenceConfigurationRender(const BldCfg *bldCfg, const BldHlp *bldHlp); + +#endif diff --git a/doc/src/command/help/meson.build b/doc/src/command/help/meson.build new file mode 100644 index 000000000..e38c2afea --- /dev/null +++ b/doc/src/command/help/meson.build @@ -0,0 +1,17 @@ +#################################################################################################################################### +# Generate help +#################################################################################################################################### +doc_help_auto_c_inc = custom_target( + 'doc_help.auto.c.inc', + output: 'help.auto.c.inc', + depend_files: [ + '../../build/config/config.yaml', + '../../build/help/help.xml', + ], + command: [ + build_code, + 'help', + '@CURRENT_SOURCE_DIR@/../../..', + '@BUILD_ROOT@/doc', + ], +) diff --git a/doc/src/config/load.c b/doc/src/config/load.c new file mode 100644 index 000000000..ee46bffda --- /dev/null +++ b/doc/src/config/load.c @@ -0,0 +1,123 @@ +/*********************************************************************************************************************************** +Configuration Load +***********************************************************************************************************************************/ +#include "build.auto.h" + +#include + +#include "command/command.h" +#include "common/debug.h" +#include "common/io/io.h" +#include "common/log.h" +#include "config/config.intern.h" +#include "storage/posix/storage.h" + +/*********************************************************************************************************************************** +Load log settings +***********************************************************************************************************************************/ +static void +cfgLoadLogSetting(void) +{ + FUNCTION_LOG_VOID(logLevelTrace); + + // Initialize logging + LogLevel logLevelConsole = logLevelOff; + bool logTimestamp = true; + + if (cfgOptionValid(cfgOptLogLevel)) + logLevelConsole = logLevelEnum(cfgOptionStrId(cfgOptLogLevel)); + + if (cfgOptionValid(cfgOptLogTimestamp)) + logTimestamp = cfgOptionBool(cfgOptLogTimestamp); + + logInit(logLevelConsole, logLevelOff, logLevelOff, logTimestamp, 0, 1, false); + + FUNCTION_LOG_RETURN_VOID(); +} + +/*********************************************************************************************************************************** +Update options that have complex rules +***********************************************************************************************************************************/ +static void +cfgLoadUpdateOption(void) +{ + FUNCTION_LOG_VOID(logLevelTrace); + + // Get current working dir + char currentWorkDir[1024]; + THROW_ON_SYS_ERROR(getcwd(currentWorkDir, sizeof(currentWorkDir)) == NULL, FormatError, "unable to get cwd"); + + // Invalidate config option so it does not show up in option list + cfgOptionInvalidate(cfgOptConfig); + + // If repo-path is relative then make it absolute + const String *const repoPath = cfgOptionStr(cfgOptRepoPath); + + if (!strBeginsWithZ(repoPath, "/")) + cfgOptionSet(cfgOptRepoPath, cfgOptionSource(cfgOptRepoPath), VARSTR(strNewFmt("%s/%s", currentWorkDir, strZ(repoPath)))); + + FUNCTION_LOG_RETURN_VOID(); +} + +/**********************************************************************************************************************************/ +void +cfgLoad(unsigned int argListSize, const char *argList[]) +{ + FUNCTION_LOG_BEGIN(logLevelDebug); + FUNCTION_LOG_PARAM(UINT, argListSize); + FUNCTION_LOG_PARAM(CHARPY, argList); + FUNCTION_LOG_END(); + + MEM_CONTEXT_TEMP_BEGIN() + { + // Make a copy of the arguments so they can be manipulated + StringList *const argListNew = strLstNew(); + + for (unsigned int argListIdx = 0; argListIdx < argListSize; argListIdx++) + strLstAddZ(argListNew, argList[argListIdx]); + + // Explicitly set --no-config so a stray config file will not be loaded + strLstAddZ(argListNew, "--no-" CFGOPT_CONFIG); + + // Parse config from command line + TRY_BEGIN() + { + cfgParseP(storagePosixNewP(FSLASH_STR), strLstSize(argListNew), strLstPtr(argListNew)); + } + CATCH(CommandRequiredError) + { + strLstAddZ(argListNew, CFGCMD_BUILD); + cfgParseP(storagePosixNewP(FSLASH_STR), strLstSize(argListNew), strLstPtr(argListNew)); + } + TRY_END(); + + // Error on noop command. This command is required to hold options that must be declared but are unused by test. + if (cfgCommand() == cfgCmdNoop) + THROW(CommandInvalidError, "invalid command '" CFGCMD_NOOP "'"); + + // If a command is set + if (cfgCommand() != cfgCmdNone && cfgCommand() != cfgCmdHelp && cfgCommand() != cfgCmdVersion) + { + // Load the log settings + if (!cfgCommandHelp()) + cfgLoadLogSetting(); + + // Neutralize the umask to make the repository file/path modes more consistent + if (cfgOptionValid(cfgOptNeutralUmask) && cfgOptionBool(cfgOptNeutralUmask)) + umask(0000); + + // Set IO buffer size + if (cfgOptionValid(cfgOptBufferSize)) + ioBufferSizeSet(cfgOptionUInt(cfgOptBufferSize)); + + // Update options that have complex rules + cfgLoadUpdateOption(); + + // Begin the command + cmdBegin(); + } + } + MEM_CONTEXT_TEMP_END(); + + FUNCTION_LOG_RETURN_VOID(); +} diff --git a/doc/src/config/load.h b/doc/src/config/load.h new file mode 100644 index 000000000..082fe1d86 --- /dev/null +++ b/doc/src/config/load.h @@ -0,0 +1,13 @@ +/*********************************************************************************************************************************** +Documentation Configuration Load +***********************************************************************************************************************************/ +#ifndef DOC_CONFIG_LOAD_H +#define DOC_CONFIG_LOAD_H + +/*********************************************************************************************************************************** +Functions +***********************************************************************************************************************************/ +// Load the configuration +void cfgLoad(unsigned int argListSize, const char *argList[]); + +#endif diff --git a/doc/src/config/meson.build b/doc/src/config/meson.build new file mode 100644 index 000000000..76d79d4bd --- /dev/null +++ b/doc/src/config/meson.build @@ -0,0 +1,16 @@ +#################################################################################################################################### +# Generate config +#################################################################################################################################### +doc_parse_auto_c_inc = custom_target( + 'doc_parse.auto.c.inc', + output : 'parse.auto.c.inc', + depend_files: [ + '../build/config/config.yaml', + ], + command : [ + build_code, + 'config', + '@CURRENT_SOURCE_DIR@/../..', + '@BUILD_ROOT@/doc', + ], +) diff --git a/doc/src/main.c b/doc/src/main.c new file mode 100644 index 000000000..0a6811c35 --- /dev/null +++ b/doc/src/main.c @@ -0,0 +1,99 @@ +/*********************************************************************************************************************************** +Main +***********************************************************************************************************************************/ +#include "build.auto.h" + +#include + +#include "command/build/build.h" +#include "command/command.h" +#include "command/exit.h" +#include "command/help/help.h" +#include "common/debug.h" +#include "common/log.h" +#include "common/macro.h" +#include "common/memContext.h" +#include "common/stat.h" +#include "config/load.h" +#include "config/parse.h" +#include "storage/posix/storage.h" +#include "version.h" + +/*********************************************************************************************************************************** +Include automatically generated help data +***********************************************************************************************************************************/ +#include "command/help/help.auto.c.inc" + +int +main(int argListSize, const char *argList[]) +{ + // Set stack trace and mem context error cleanup handlers + static const ErrorHandlerFunction errorHandlerList[] = {stackTraceClean, memContextClean}; + errorHandlerSet(errorHandlerList, LENGTH_OF(errorHandlerList)); + + FUNCTION_LOG_BEGIN(logLevelDebug); + FUNCTION_LOG_PARAM(INT, argListSize); + FUNCTION_LOG_PARAM(CHARPY, argList); + FUNCTION_LOG_END(); + + // Initialize command with the start time + cmdInit(); + + // Initialize statistics collector + statInit(); + + // Initialize exit handler + exitInit(); + + // Process commands + volatile int result = 0; + volatile bool error = false; + + TRY_BEGIN() + { + // Load the configuration + // ------------------------------------------------------------------------------------------------------------------------- + cfgLoad((unsigned int)argListSize, argList); + + // Display help + // ------------------------------------------------------------------------------------------------------------------------- + if (cfgCommandHelp()) + { + cmdHelp(BUF(helpData, sizeof(helpData))); + } + else + { + switch (cfgCommand()) + { + // Build + // ----------------------------------------------------------------------------------------------------------------- + case cfgCmdBuild: + cmdBuild(cfgOptionStr(cfgOptRepoPath)); + break; + + // Display version + // ----------------------------------------------------------------------------------------------------------------- + case cfgCmdVersion: + printf(PROJECT_NAME " Documentation " PROJECT_VERSION "\n"); + fflush(stdout); + break; + + // Error on commands that should have already been handled + // ----------------------------------------------------------------------------------------------------------------- + case cfgCmdHelp: + case cfgCmdNone: + case cfgCmdNoop: + THROW_FMT(AssertError, "'%s' command should have been handled", cfgCommandName()); + break; + } + } + } + CATCH_FATAL() + { + error = true; + result = exitSafe(result, true, 0); + } + TRY_END(); + + FUNCTION_LOG_RETURN(INT, error ? result : exitSafe(result, false, 0)); +} diff --git a/doc/src/meson.build b/doc/src/meson.build new file mode 100644 index 000000000..d65fc201c --- /dev/null +++ b/doc/src/meson.build @@ -0,0 +1,75 @@ +#################################################################################################################################### +# Error on release builds since we do not want anyone using meson for production yet +#################################################################################################################################### +if not get_option('force-release') and get_option('buildtype') != 'debug' and get_option('buildtype') != 'debugoptimized' + error('meson is currently not supported for release builds') +endif + +#################################################################################################################################### +# Write configuration +#################################################################################################################################### +configure_file(output: 'build.auto.h', configuration: configuration) + +#################################################################################################################################### +# Build config target +#################################################################################################################################### +# build parse.auto.c.inc +subdir('config') + +#################################################################################################################################### +# Build help target +#################################################################################################################################### +# build help.auto.c.inc +subdir('command/help') + +#################################################################################################################################### +# test target +#################################################################################################################################### +src_doc = [ + '../../src/build/common/render.c', + '../../src/build/common/string.c', + '../../src/build/common/xml.c', + '../../src/build/common/yaml.c', + '../../src/build/config/parse.c', + '../../src/build/help/parse.c', + '../../src/command/command.c', + '../../src/command/exit.c', + '../../src/command/help/help.c', + '../../src/common/compress/bz2/common.c', + '../../src/common/compress/bz2/decompress.c', + '../../src/common/ini.c', + '../../src/common/io/fd.c', + '../../src/common/io/fdRead.c', + '../../src/common/io/fdWrite.c', + '../../src/common/lock.c', + '../../src/common/stat.c', + '../../src/common/type/json.c', + '../../src/config/config.c', + '../../src/config/parse.c', + 'command/build/build.c', + 'command/build/reference.c', + 'config/load.c', + 'main.c', +] + +executable( + 'doc-pgbackrest', + src_common, + src_doc, + doc_help_auto_c_inc, + doc_parse_auto_c_inc, + include_directories: include_directories('.', '../../src'), + c_args: [ + # Use large buffer sizes to capture large error/log outputs + '-DERROR_MESSAGE_BUFFER_SIZE=262144', + '-DLOG_BUFFER_SIZE=262144', + arg_unity, + ], + dependencies: [ + lib_backtrace, + lib_bz2, + lib_xml, + lib_yaml, + ], + build_by_default: false, +) diff --git a/doc/xml/dtd/doc.dtd b/doc/xml/dtd/doc.dtd index 5b1b76650..4a17075ec 100644 --- a/doc/xml/dtd/doc.dtd +++ b/doc/xml/dtd/doc.dtd @@ -112,7 +112,7 @@ diff --git a/meson.build b/meson.build index 2e037c394..97bfd4262 100644 --- a/meson.build +++ b/meson.build @@ -213,4 +213,5 @@ configuration.set( # Include subdirs #################################################################################################################################### subdir('src') +subdir('doc/src') subdir('test/src') diff --git a/src/build/help/help.xml b/src/build/help/help.xml index f62bb7156..9550da641 100644 --- a/src/build/help/help.xml +++ b/src/build/help/help.xml @@ -1,7 +1,7 @@ - + The {[project]} Configuration Reference details all configuration options. @@ -1909,7 +1909,7 @@ - + The {[project]} Command Reference details all commands and options. diff --git a/test/ci.pl b/test/ci.pl index a5afe993a..555324300 100755 --- a/test/ci.pl +++ b/test/ci.pl @@ -131,7 +131,7 @@ eval processBegin('install common packages'); processExec('sudo apt-get -qq update', {bSuppressStdErr => true, bSuppressError => true}); processExec( - 'sudo DEBIAN_FRONTEND=noninteractive apt-get install -y libxml-checker-perl libyaml-perl', {bSuppressStdErr => true}); + 'sudo DEBIAN_FRONTEND=noninteractive apt-get install -y meson libxml-checker-perl libyaml-perl', {bSuppressStdErr => true}); processEnd(); if (!$bNoTempFs) @@ -182,7 +182,7 @@ eval { # Build list of packages that need to be installed my $strPackage = - "make gcc ccache meson python3-pip git rsync zlib1g-dev libssl-dev libxml2-dev libpq-dev libyaml-dev pkg-config" . + "make gcc ccache python3-pip git rsync zlib1g-dev libssl-dev libxml2-dev libpq-dev libyaml-dev pkg-config" . " uncrustify libssh2-1-dev"; # Add lcov when testing coverage diff --git a/test/define.yaml b/test/define.yaml index 1c7febc95..f672848a4 100644 --- a/test/define.yaml +++ b/test/define.yaml @@ -973,6 +973,18 @@ unit: - command/server/ping - command/server/server + # ******************************************************************************************************************************** + - name: doc + + test: + # ---------------------------------------------------------------------------------------------------------------------------- + - name: build + total: 1 + + coverage: + - doc/command/build/build + - doc/command/build/reference + # ********************************************************************************************************************************** # Integration tests # diff --git a/test/lib/pgBackRestTest/Common/CoverageTest.pm b/test/lib/pgBackRestTest/Common/CoverageTest.pm index 7429c7ae0..7d2474be6 100644 --- a/test/lib/pgBackRestTest/Common/CoverageTest.pm +++ b/test/lib/pgBackRestTest/Common/CoverageTest.pm @@ -129,6 +129,10 @@ sub coverageExtract { $strModuleName =~ s/^test/src/mg; } + elsif ($strModuleName =~ /^doc/mg) + { + $strModuleName =~ s/^doc/doc\/src/mg; + } my $strModuleOutName = $strModuleName; my $bTest = false; @@ -150,6 +154,10 @@ sub coverageExtract { $strModulePath .= 'test/src/module/' . substr(${strModuleOutName}, 5); } + elsif (${strModuleOutName} =~ /^doc\//) + { + $strModulePath .= "${strModuleOutName}"; + } else { $strModulePath .= "src/${strModuleOutName}"; @@ -161,7 +169,7 @@ sub coverageExtract my $strModuleSourceFile = $strModulePath . '.c' . ($bInc ? '.inc' : ''); executeTest( - "${strLCovExe}" . ($bTest ? ' --rc lcov_branch_coverage=0' : '') . " --extract=${strLCovOut} */${strModuleName}.c" . + "${strLCovExe}" . ($bTest ? ' --rc lcov_branch_coverage=0' : '') . " --extract=${strLCovOut} *${strModuleName}.c" . ($bInc ? '.inc' : '') . " --o=${strLCovOutTmp}"); # Combine with prior run if there was one @@ -315,6 +323,7 @@ sub coverageValidateAndGenerate { my $strCoverageFile = $strCodeModule; $strCoverageFile =~ s/^test/src/mg; + $strCoverageFile =~ s/^doc/doc\/src/mg; $strCoverageFile =~ s/^module/test/mg; $strCoverageFile = "${strTestResultCoveragePath}/raw/${strCoverageFile}.lcov"; diff --git a/test/src/command/test/build.c b/test/src/command/test/build.c index 6b92fd3b2..3e23524e7 100644 --- a/test/src/command/test/build.c +++ b/test/src/command/test/build.c @@ -214,6 +214,8 @@ cmdBldPathModule(const String *const moduleName) { if (strBeginsWithZ(moduleName, "test/")) strCatFmt(result, "test/src%s", strZ(strSub(moduleName, 4))); + else if (strBeginsWithZ(moduleName, "doc/")) + strCatFmt(result, "doc/src%s", strZ(strSub(moduleName, 3))); else strCatFmt(result, "src/%s", strZ(moduleName)); } @@ -534,10 +536,11 @@ testBldUnit(TestBuild *const this) " include_directories(\n" " '.',\n" " '%s/src',\n" + " '%s/doc/src',\n" " '%s/test/src',\n" " ),\n" " dependencies: [\n", - strZ(pathRepoRel), strZ(pathRepoRel)); + strZ(pathRepoRel), strZ(pathRepoRel), strZ(pathRepoRel)); if (testBldBackTrace(this)) { diff --git a/test/src/module/doc/buildTest.c b/test/src/module/doc/buildTest.c new file mode 100644 index 000000000..7d3bfd95f --- /dev/null +++ b/test/src/module/doc/buildTest.c @@ -0,0 +1,331 @@ +/*********************************************************************************************************************************** +Test Documentation Build +***********************************************************************************************************************************/ +#include "storage/posix/storage.h" + +#include "common/harnessStorage.h" + +/*********************************************************************************************************************************** +Test Run +***********************************************************************************************************************************/ +static void +testRun(void) +{ + FUNCTION_HARNESS_VOID(); + + // Create default storage object for testing + const Storage *const storageTest = storagePosixNewP(TEST_PATH_STR, .write = true); + + // ***************************************************************************************************************************** + if (testBegin("cmdBuild()")) + { + // ------------------------------------------------------------------------------------------------------------------------- + TEST_TITLE("referenceCommandSection()"); + + TEST_RESULT_STR_Z(referenceCommandSection(NULL), "general", "null section remap"); + + TEST_RESULT_STR_Z(referenceCommandSection(STRDEF("general")), "general", "general section no remap"); + TEST_RESULT_STR_Z(referenceCommandSection(STRDEF("log")), "log", "log section no remap"); + TEST_RESULT_STR_Z(referenceCommandSection(STRDEF("repository")), "repository", "repository section no remap"); + TEST_RESULT_STR_Z(referenceCommandSection(STRDEF("stanza")), "stanza", "stanza section no remap"); + + TEST_RESULT_STR_Z(referenceCommandSection(STRDEF("other")), "command", "other section remap"); + + // ------------------------------------------------------------------------------------------------------------------------- + TEST_TITLE("parse and render"); + + HRN_STORAGE_PUT_Z( + storageTest, "src/build/config/config.yaml", + "command:\n" + " backup: {}\n" + "\n" + " check: {}\n" + "\n" + " restore:\n" + " internal: true\n" + "\n" + "optionGroup:\n" + " pg: {}\n" + " repo: {}\n" + "\n" + "option:\n" + " config:\n" + " type: string\n" + " command:\n" + " backup:\n" + " internal: true\n" + " check: {}\n" + "\n" + " buffer-size:\n" + " section: global\n" + " beta: true\n" + " type: integer\n" + " default: 1024\n" + " allow-list: [512, 1024, 2048, 4096]\n" + "\n" + " internal:\n" + " section: global\n" + " internal: true\n" + " type: integer\n" + " default: 11\n" + "\n" + " secure:\n" + " section: global\n" + " type: boolean\n" + " secure: true\n" + " default: false\n" + "\n" + " repo-compress-level:\n" + " group: repo\n" + " type: integer\n" + " default: 9\n" + " allow-range: [0, 9]\n" + " command:\n" + " backup: {}\n" + " deprecate:\n" + " repo-compress-level: {}\n" + "\n" + " force:\n" + " type: boolean\n" + " default: true\n" + " command:\n" + " backup:\n" + " default: true\n" + " deprecate:\n" + " frc: {}\n" + "\n" + " stanza:\n" + " type: string\n" + " default: demo\n" + " required: false\n" + " deprecate:\n" + " stanza: {}\n" + " stanza1: {}\n" + " stanza2: {}\n" + " command:\n" + " backup:\n" + " command-role:\n" + " local: {}\n" + " remote: {}\n" + " check: {}\n" + "\n"); + + HRN_STORAGE_PUT_Z( + storageTest, "src/build/help/help.xml", + "\n" + " \n" + " config description" + "\n" + "

config text

" + "\n" + " \n" + " \n" + "

general section

" + "\n" + " \n" + " \n" + " Buffer size option summary.\n" + "

Buffer size option description.

\n" + " 128KiB\n" + " 256KiB\n" + "
\n" + "\n" + " \n" + " Internal option summary.\n" + "

Internal option description

\n" + "
\n" + "\n" + " \n" + " Secure option summary.\n" + "

Secure option description

\n" + "
\n" + "\n" + " \n" + " Stanza option summary.\n" + "

Stanza option description

\n" + "
\n" + "
\n" + "
\n" + "
\n" + "
\n" + "\n" + " \n" + " command description" + "

command text

" + "\n" + " \n" + " \n" + " \n" + " \n" + " \n" + "\n" + " \n" + " \n" + " backup command summary.\n" + "

Backup command description.

\n" + "\n" + " \n" + " \n" + "\n" + " \n" + " \n" + "
\n" + "\n" + " \n" + " Check command summary.\n" + "

Check command description.

\n" + "
\n" + "\n" + " \n" + " Restore command summary.\n" + "

Restore command description.

\n" + "
\n" + "
\n" + "
\n" + "
\n"); + + // ------------------------------------------------------------------------------------------------------------------------- + TEST_TITLE("build documentation"); + + TEST_RESULT_VOID(cmdBuild(TEST_PATH_STR), "write files"); + TEST_STORAGE_EXISTS(storageTest, "doc/output/xml/configuration.xml"); + TEST_STORAGE_EXISTS(storageTest, "doc/output/xml/command.xml"); + + // ------------------------------------------------------------------------------------------------------------------------- + TEST_TITLE("configuration.xml"); + + TEST_STORAGE_GET( + storageTest, + "doc/output/xml/configuration.xml", + "\n" + "\n" + "" + // {uncrustify_off - indentation} + "config description" + "
" + "Introduction" + "

config text

" + "
" + "
" + "General Options (<id>general</id>)" + "

general section

" + "
" + "Buffer Size Option (<id>--buffer-size</id>)" + "

Buffer size option summary.

" + "

FOR BETA TESTING ONLY. DO NOT USE IN PRODUCTION.

" + "

Buffer size option description.

" + "default: 1024\n" + "example: buffer-size=128KiB\n" + "example: buffer-size=256KiB" + "
" + "
" + "Config Option (<id>--config</id>)" + "

config option summary.

" + "

config option description.

" + "
" + "
" + "Secure Option (<id>--secure</id>)" + "

Secure option summary.

" + "

Secure option description

" + "default: n" + "
" + "
" + // {uncrustify_on} + "
\n"); + + // ------------------------------------------------------------------------------------------------------------------------- + TEST_TITLE("command.xml"); + + TEST_STORAGE_GET( + storageTest, + "doc/output/xml/command.xml", + "\n" + "\n" + "" + // {uncrustify_off - indentation} + "command description" + "
Introduction" + "

command text

" + "
" + "
" + "Backup Command (<id>backup</id>)" + "

Backup command description.

" + "
" + "Command Options" + "
" + "Force Backup Option (<id>--force</id>)" + "

Force option command backup summary.

" + "

Force option command backup description.

" + "default: y\n" + "example: --no-force --force" + "

Deprecated Name: frc

" + "
" + "
" + "Repo Compress Level Backup Option (<id>--repo-compress-level</id>)" + "

Repo compress level option command backup summary.

" + "

Repo compress level option command backup description.

" + "default: 9\n" + "allowed: 0-9\n" + "example: --repo1-compress-level=4" + "
" + "
" + "
" + "General Options" + "
" + "Buffer Size Option (<id>--buffer-size</id>)" + "

Buffer size option summary.

" + "

FOR BETA TESTING ONLY. DO NOT USE IN PRODUCTION.

" + "

Buffer size option description.

" + "default: 1024\n" + "example: --buffer-size=128KiB --buffer-size=256KiB" + "
" + "
" + "
" + "
" + "Check Command (<id>check</id>)" + "

Check command description.

" + "
" + "General Options" + "
" + "Buffer Size Option (<id>--buffer-size</id>)" + "

Buffer size option summary.

" + "

FOR BETA TESTING ONLY. DO NOT USE IN PRODUCTION.

" + "

Buffer size option description.

" + "default: 1024\n" + "example: --buffer-size=128KiB --buffer-size=256KiB" + "
" + "
" + "Config Option (<id>--config</id>)" + "

config option summary.

" + "

config option description.

" + "
" + "
" + "
" + "Stanza Options" + "
" + "Stanza Option (<id>--stanza</id>)" + "

Stanza option summary.

" + "

Stanza option description

" + "default: demo" + "

Deprecated Names: stanza1, stanza2

" + "
" + "
" + "
" + // {uncrustify_on} + "
\n"); + } + + FUNCTION_HARNESS_RETURN_VOID(); +} diff --git a/test/src/module/test/testTest.c b/test/src/module/test/testTest.c index a503c5cb4..23c1078c0 100644 --- a/test/src/module/test/testTest.c +++ b/test/src/module/test/testTest.c @@ -206,6 +206,7 @@ testRun(void) " include:\n" " - common/error/error\n" " - test/common/include\n" + " - doc/command/build/build\n" "\n" "integration:\n" " - name: mock\n" @@ -258,6 +259,8 @@ testRun(void) "test/src/module/common/stackTraceTest.c", "test/src/test.c"); + HRN_STORAGE_PUT_EMPTY(storageTest, "repo/doc/src/command/build/build.c"); + TEST_RESULT_VOID( cmdTest( STRDEF(TEST_PATH "/repo"), storagePathP(storageTest, STRDEF("test")), STRDEF("none"), 3, @@ -307,6 +310,7 @@ testRun(void) " include_directories(\n" " '.',\n" " '../../../repo/src',\n" + " '../../../repo/doc/src',\n" " '../../../repo/test/src',\n" " ),\n" " dependencies: [\n" @@ -423,6 +427,7 @@ testRun(void) " include_directories(\n" " '.',\n" " '../../../repo/src',\n" + " '../../../repo/doc/src',\n" " '../../../repo/test/src',\n" " ),\n" " dependencies: [\n" @@ -634,6 +639,7 @@ testRun(void) " include_directories(\n" " '.',\n" " '../../../repo/src',\n" + " '../../../repo/doc/src',\n" " '../../../repo/test/src',\n" " ),\n" " dependencies: [\n" @@ -688,7 +694,8 @@ testRun(void) STRDEF( "#include \"test/src/common/harnessShim.c\"\n" "#include \"test/src/common/harnessError.c\"\n" - "#include \"../../../repo/test/src/common/include.c\"")); + "#include \"../../../repo/test/src/common/include.c\"\n" + "#include \"../../../repo/doc/src/command/build/build.c\"")); strReplace( testCDup, STRDEF("{[C_TEST_INCLUDE]}"), STRDEF("#include \"../../../repo/test/src/module/test/shimTest.c\"")); strReplace( @@ -806,6 +813,7 @@ testRun(void) " include_directories(\n" " '.',\n" " '../../../repo/src',\n" + " '../../../repo/doc/src',\n" " '../../../repo/test/src',\n" " ),\n" " dependencies: [\n"