Product SiteDocumentation Site

Глава 14. Документирование пакета

14.1. Doxygen
При разработке продукта одной из ключевых составляющих является поддержка информационного пространства, позволяющего сообществу разработчиков и пользователей ориентироваться в нём и разбираться с доступным функционалом. Информационное пространство состоит как из описания внутренних элементов проекта, так и из информации о возможностях самого продукта и о правилах взаимодействия с ним. При поддержке продукта основную часть информационного пространства заполняет описание и структуризация всех проводящихся изменений проекта.
Написание пользовательской документации — отдельный фронт работ, он более или менее независим от процесса разработки — главное, чтобы она дошла хоть до какой-то тестовой эксплуатации.
А вот внутренняя документация, предназначенная для сообщества разработчиков и пользователей разрабатываемого программного инструментария, — это дело самих разработчиков, и тут необходима некоторая дисциплина.
Основная идея: внутренняя документация должна появляться одновременно с написанием соответствующих функций, при этом вносить как можно меньше «шума» в исходный текст программы и не мешать работе программиста (который должен её оформлять):
  • радикальный вариант: документация пишется до реализации функций, которые она документирует. Одним из ярких примеров является методика Literate Programming, предложенная Дональдом Кнутом;
  • если язык программирования поддерживает т. н. самодокументирование (как, например, docstrings в Python), большая часть технической документации уходит туда. В этом случае полезно разделять «документацию вообще», не привязанную к конкретному тексту функций, внутреннюю документацию в docstrings и «просто комментарии», которые теперь ориентированы на более узкую аудиторию — на разработчика, который собирается понять и изменить сложное место в программе;
  • техническое документирование — например, документирование API — обычно оперирует довольно обширным набором понятий (классы, типы их методов и полей, параметры функций и методов, содержимое библиотек и т. п.). Для ведения внутренней документации с давних пор существует целое семейство подсистем со своим синтаксисом и правилами привязки к исходным текстам. Таким подсистемам не обязательна поддержка самодокументирования — достаточно комментариев особого вида.
За пределами внутреннего документирования остаётся всё остальное информационное пространство:
  • offline-справочники, в первую очередь man и GNU Texinfo;
  • сайты, предназначенные для совместного создания сообществом (wiki или простые CMS), — как встроенные в Git-хостинги, так и в виде отдельных приложений;
  • т. н. «сайтогенераторы» — приложения, предназначенные для порождения статического HTML и последующей публикации. Это могут быть специализированные приложения, наподобие Pelican или Jekyll, но можно заставить работать в таком режиме и саму систему технического документирования;
  • специализированные сервисы документирования ( ReadTheDocs, Git Book).

14.1. Doxygen

Самым популярным на сегодня инструментом документирования является Sphinx; в нашем проекте косвенно задействован другой —  Perl Podlators — с его помощью форматируется man-страница. Внутреннее документирование мы организуем с помощью ещё одной классической системы —  Doxygen. Процесс создания документации с её помощью поддерживается Autotools, при этом доступно немалое количество выходных форматов документации — от HTML-страницы до печатного варианта:
Doxygen
Добавим в проект поддержку doxygen:
.
├── configure.ac
├── doc
│   ├── Makefile.am
│   └── syscall.pod
├── LICENSE
├── Makefile.am
├── src
│   ├── basic.c
│   ├── globals.c
│   ├── lssyscalls
│   ├── Makefile.am
│   ├── syscall.c
│   ├── syscall.h
│   └── utility.c
└── tests
   ├── include.ts
   ├── Makefile.am
   └── upstream.ts
Для описания параметров документирования используется базовый шаблонный файл Doxyfile.in, генерируемый командой:
@user
[user@VM syscall-master]$ doxygen -g Doxyfile.in
Файл представляет собой тщательно откомментированный перечень свойств будущей документации:
@user: syscall-master/Doxyfile.in
#---------------------------------------------------------------------------
# Project related configuration options
#---------------------------------------------------------------------------

# This tag specifies the encoding used for all characters in the configuration
# file that follow. The default is UTF-8 which is also the encoding used for all
# text before the first occurrence of this tag. Doxygen uses libiconv (or the
# iconv built into libc) for the transcoding. See
# https://www.gnu.org/software/libiconv/ for the list of possible encodings.
# The default value is: UTF-8.

DOXYFILE_ENCODING      = UTF-8

# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by
# double-quotes, unless you are using Doxywizard) that should identify the
# project for which the documentation is generated. This name is used in the
# title of most generated pages and in a few other places.
# The default value is: My Project.

PROJECT_NAME           = "My Project"

# The PROJECT_NUMBER tag can be used to enter a project or revision number. This
# could be handy for archiving the generated documentation or if some version
# control system is used.

PROJECT_NUMBER         =
Поменяем основные параметры:
@user: syscall-master/Doxyfile.in
diff --git a/Doxyfile.in b/Doxyfile.in
index a822940..29071c4 100644
--- a/Doxyfile.in
+++ b/Doxyfile.in
@@ -42,13 +42,13 @@ DOXYFILE_ENCODING      = UTF-8
# title of most generated pages and in a few other places.
# The default value is: My Project.

-PROJECT_NAME           = "My Project"
+PROJECT_NAME           = "@PACKAGE_TARNAME@"

# The PROJECT_NUMBER tag can be used to enter a project or revision number. This
# could be handy for archiving the generated documentation or if some version
# control system is used.

-PROJECT_NUMBER         =
+PROJECT_NUMBER         = @PACKAGE_VERSION@

# Using the PROJECT_BRIEF tag one can provide an optional one line description
# for a project that appears at the top of each page and should give viewer a
@@ -54,7 +54,7 @@ PROJECT_NUMBER         = @PACKAGE_VERSION@
# for a project that appears at the top of each page and should give viewer a
# quick idea about the purpose of the project. Keep the description short.

-PROJECT_BRIEF          =
+PROJECT_BRIEF          = "@PACKAGE_NAME@"

# With the PROJECT_LOGO tag one can specify a logo or an icon that is included
# in the documentation. The maximum height of the logo should not exceed 55
@@ -74,7 +74,7 @@ PROJECT_ICON           =
# entered, it will be relative to the location where doxygen was started. If
# left blank the current directory will be used.

-OUTPUT_DIRECTORY       =
+OUTPUT_DIRECTORY       = @DX_DOCDIR@

# If the CREATE_SUBDIRS tag is set to YES then doxygen will create up to 4096
# sub-directories (in 2 levels) under the output directory of each output format
@@ -215,7 +215,7 @@ SHORT_NAMES            = NO
# description.)
# The default value is: NO.

-JAVADOC_AUTOBRIEF      = NO
+JAVADOC_AUTOBRIEF      = YES

# If the JAVADOC_BANNER tag is set to YES then doxygen will interpret a line
# such as
@@ -297,7 +297,7 @@ ALIASES                =
# members will be omitted, etc.
# The default value is: NO.

-OPTIMIZE_OUTPUT_FOR_C  = NO
+OPTIMIZE_OUTPUT_FOR_C  = C

# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or
# Python sources only. Doxygen will then generate output that is more tailored
@@ -524,31 +524,31 @@ TIMESTAMP              = NO
# normally produced when WARNINGS is set to YES.
# The default value is: NO.

-EXTRACT_ALL            = NO
+EXTRACT_ALL            = YES

# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will
# be included in the documentation.
# The default value is: NO.
 -EXTRACT_PRIVATE        = NO
+EXTRACT_PRIVATE        = YES

# If the EXTRACT_PRIV_VIRTUAL tag is set to YES, documented private virtual
# methods of a class will be included in the documentation.
# The default value is: NO.

-EXTRACT_PRIV_VIRTUAL   = NO
+EXTRACT_PRIV_VIRTUAL   = YES

# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal
# scope will be included in the documentation.
# The default value is: NO.

-EXTRACT_PACKAGE        = NO
+EXTRACT_PACKAGE        = YES

# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be
# included in the documentation.
# The default value is: NO.

-EXTRACT_STATIC         = NO
+EXTRACT_STATIC         = YES

# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined
# locally in source files will be included in the documentation. If set to NO,
@@ -564,7 +564,7 @@ EXTRACT_LOCAL_CLASSES  = YES
# included.
# The default value is: NO.

-EXTRACT_LOCAL_METHODS  = NO
+EXTRACT_LOCAL_METHODS  = YES

# If this flag is set to YES, the members of anonymous namespaces will be
# extracted and appear in the documentation as a namespace called
@@ -949,7 +949,7 @@ WARN_LOGFILE           =
# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING
# Note: If this tag is empty the current directory is searched.

-INPUT                  =
+INPUT                  = @top_srcdir@/src

# This tag can be used to specify the character encoding of the source files
# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses
Самодокументирование представляет собой комментирование исходников, которое и уйдёт в документацию. Doxygen поддерживает как однострочные, так и многострочные комментарии, при чём вне зависимости от формата документирования в разных языках программирования. Ключевые слова, отмеченные символом @ позволяют описывать в документации основные компоненты программы, а также определять разделы (как, например, ключевое слово @mainpage для размещения текста на главной странице документации):
@user: syscall-master/src/syscall.c
/** @mainpage Syscall
* syscall - send system calls from your shell
*
* Execute a list of raw system calls. All the system calls listed in your system's unistd.h are
* supported, with up to 5 arguments. A maximum of 20 calls can be executed per invocation, each
* separated by a comma.
*
* Arguments starting by a "#" symbol are used to give a string length. For instance, "#hello"
* would be evaluated as 5.
*
* Arguments starting by a "$" followed by a number from 0 to 19 refer to a previous system call
* return code. For instance, $0 refers to to the return code of the first system call executed.
* To display those values, use the "echo" built-in command.
*
* The "echo" command can be used like any other system call to easily display "$" or "#" values,
* or any string or number.
*
* @param -<n> execute the given commands n times, where n is an integer between 0 and "INT_MAX"
* @param -h/--help return program usage
* @param -v/--version return version of program
*
* @return 0 if all syscalls were successful, 1 on error. Note that if any system returns -1,
* the program will exit immediately after printing the associated error message.
*/

#include "syscall.h"
/** Parse input shell-string and execute syscalls
*
* @return 0 if string parsing and syscall's execution were successfully completed, 1 if weren't
*/
int main(int argc, char **argv)
{
 int repeat = 1, skip = 1;

 if (argc <= 1) {
   usage();
   return 0;
 }

 /* arguments */
 if (argv[1][0] == '-') {
   /* help */
   if (streq(argv[1], "-h") || streq(argv[1], "--help")) {
     usage();
     return 0;
   }

   /* version */
   if (streq(argv[1], "-v") || streq(argv[1], "--version")) {
     puts("syscall version "VERSION);
     return 0;
   }

   /* Handle -n option */
   repeat = atoi(argv[1] + 1);
   if (repeat < 1) {
     errx(1, "option -<n> must be between 1 and %d", INT_MAX);
   }
   if (argc <= 3) {
     usage();
     return 1;
   }
   skip++;
 }

 memset(ret_values, -1, sizeof ret_values);

 while (repeat--) {
   split_cmdline(argc - skip, argv + skip);
 }

 return 0;
}
@user: syscall-master/src/utility.c
/** @page utility
* There are special functions for internal workings of the program
*/

#include "syscall.h"
/** Help-string of program usage
*/
void usage()
{
 puts("usage: syscall [-<n>] name [args...] [, name [args...]]...");
}

/** Syscall`s names comparator
*
* @param m1 first comparing syscall
* @param m2 second comparing syscall
*
* @return comparing result
*/
int scomp(const void *m1, const void *m2)
{
 Syscall *sys1 = (Syscall *)m1;
 Syscall *sys2 = (Syscall *)m2;
 return strcmp(sys1->name, sys2->name);
}

/** Binary search of executing syscall @p name
*
* @param name executing syscall
* @return system code of syscall
*/
long lookup(const char *name)
{
 Syscall key, *res;
 key.name = name;

 res = bsearch(&key, systab, systab_size, sizeof key, scomp);
 if (res == NULL) {
   errx(1, "unknown system call: %s", name);
 }

 return res->code;
}

/** Quick and dirty way to unescape \n at the end of strings
*/
void unescape_nl(char *str) {
 size_t end = strlen(str) - 1;

 while (str[end] == 'n' && str[end-1] == '\\') {
   str[end-1] = '\n';
   str[end] = '\0';
   end -= 2;
 }
}

/** Special debugging function
*/
void dump_ret_values(void) {
 for (int i = 0; i < CMD_MAX; i++) {
   printf("%0d  %d\n", i, ret_values[i]);
 }
}
В configure.ac опишем использование Doxygen, для этого необходимо лишь инициализировать его (при этом указываются имя проекта, путь к файлу настройки и путь к директории, где будет храниться документация) и указать в списке генератов:
@user: syscall-master/configure.ac
#                                               -*- Autoconf -*-
# Process this file with autoconf to produce a configure script.


AC_INIT([syscall in Shell], [1.0], [UsamG1t], [syscall])
AC_CONFIG_SRCDIR([src/syscall.c])

AM_INIT_AUTOMAKE([foreign subdir-objects])
LT_INIT([disable-static])
AC_CONFIG_HEADERS([config.h])

DX_INIT_DOXYGEN([syscall], [Doxyfile], [doxygen-doc])

# Checks for programs.
<...>
AC_CONFIG_FILES([Makefile src/Makefile doc/Makefile tests/Makefile Doxyfile])
AC_OUTPUT
В spec-файле все необходимые для документации пакеты должны быть занесены в BuildRequires:
  • непосредственно, doxygen;
  • graphviz для демонстрации графиков зависимостей файлов;
  • autoconf-archive для загрузки файлов с doxygen-макросами для их работы.
Для установки документации при установке пакета в директиву %files необходимо указать зависимость на данные в директории документации:
@user: syscall-master/.gear/syscall.spec
<...>
# Automatically added by buildreq on Fri Aug 08 2025
# optimized out: glibc-kernheaders-generic glibc-kernheaders-x86 gnu-config libgpg-error perl perl-Encode perl-Pod-Escapes perl-Pod-Simple perl-parent perl-podlators sh5
BuildRequires: perl-Pod-Usage check libcheck-devel doxygen graphviz autoconf-archive

<...>

%files
%_bindir/%name
%_libdir/*
%_docdir/*
%_man1dir/*

%changelog
* Tue Aug 12 2025 UsamG1t <usamg1t@altlinux.org> 1.3-alt1
- Add Doxygen

* Mon Aug 11 2025 UsamG1t <usamg1t@altlinux.org> 1.2-alt1
- Add gcov

* Fri Aug 08 2025 UsamG1t <usamg1t@altlinux.org> 1.1-alt1
- Add xUnit check

* Fri Aug 08 2025 UsamG1t <usamg1t@altlinux.org> 1.0-alt1
- Initial Build
Итоговый вид директорий проекта с документацией выглядит так:
.
├── configure.ac
├── doc
│   ├── Makefile.am
│   └── syscall.pod
├── Doxyfile.in
├── LICENSE
├── Makefile.am
├── src
│   ├── basic.c
│   ├── globals.c
│   ├── lssyscalls
│   ├── Makefile.am
│   ├── syscall.c
│   ├── syscall.h
│   └── utility.c
└── tests
   ├── include.ts
   ├── Makefile.am
   └── upstream.ts
@user
[user@VM syscall-master]$ gear-hsh --lazy
<...>
Wrote: /usr/src/in/srpm/syscall-1.3-alt1.src.rpm (w1.gzdio)
Installing syscall-1.3-alt1.src.rpm
<...>
SRCDIR='.' PROJECT='syscall' VERSION='1.0' PERL_PATH='/usr/bin/perl' HAVE_DOT='NO' GENERATE_MAN='NO' GENERATE_RTF='NO' GENERATE_XML='NO' GENERATE_HTMLHELP='NO' GENERATE_CHI='NO' GENERATE_HTML='YES' GENERATE_LAT
EX='NO' DOCDIR=doxygen-doc /usr/bin/doxygen Doxyfile
<...>
finished...
echo Timestamp >doxygen-doc/syscall.tag
<...>
Wrote: /usr/src/RPM/SRPMS/syscall-1.3-alt1.src.rpm (w2.lzdio)
Wrote: /usr/src/RPM/RPMS/x86_64/syscall-1.3-alt1.x86_64.rpm (w2.lzdio)
Wrote: /usr/src/RPM/RPMS/x86_64/syscall-debuginfo-1.3-alt1.x86_64.rpm (w2.lzdio)
@rooter
[user@VM syscall-master]$ hsh-shell --rooter
[root@localhost .in]# rpm -i syscall-1.3-alt1.x86_64.rpm
<13>Aug 12 11:39:55 rpm: syscall-1.3-alt1 1754998745 installed

[root@localhost .in]# ls /usr/share/doc/syscall/html/
annotated.html                             doc.svg             functions_vars.html   graph_legend.png  navtree.css                 syscall_8c.html            systab_8h.html            tab_sd.png
basic_8c.html                              docd.svg            globals.html          index.html        open.png                    syscall_8c__incl.map       systab_8h__dep__incl.map  tabs.css
basic_8c__incl.map                         doxygen.css         globals_8c.html       jquery.js         plus.svg                    syscall_8c__incl.md5       systab_8h__dep__incl.md5  utility_8c.html
basic_8c__incl.md5                         doxygen.svg         globals_8c__incl.map  menu.js           plusd.svg                   syscall_8c__incl.png       systab_8h__dep__incl.png  utility_8c__incl.map
basic_8c__incl.png                         doxygen_crawl.html  globals_8c__incl.md5  menudata.js       resize.js                   syscall_8h.html            systab_8h_source.html     utility_8c__incl.md5
bc_s.png                                   dynsections.js      globals_8c__incl.png  minus.svg         search                      syscall_8h__dep__incl.map  tab_a.png                 utility_8c__incl.png
bc_sd.png                                  files.html          globals_defs.html     minusd.svg        splitbar.png                syscall_8h__dep__incl.md5  tab_ad.png
classes.html                               folderclosed.svg    globals_func.html     nav_f.png         splitbard.png               syscall_8h__dep__incl.png  tab_b.png
clipboard.js                               folderclosedd.svg   globals_type.html     nav_fd.png        structsyscall-members.html  syscall_8h__incl.map       tab_bd.png
closed.png                                 folderopen.svg      globals_vars.html     nav_g.png         structsyscall.html          syscall_8h__incl.md5       tab_h.png
cookie.js                                  folderopend.svg     graph_legend.html     nav_h.png         sync_off.png                syscall_8h__incl.png       tab_hd.png
dir_68267d1309a1af8e8297ef4c3efbcdba.html  functions.html      graph_legend.md5      nav_hd.png        sync_on.png                 syscall_8h_source.html     tab_s.png

[root@localhost .in]#
Doxygen. Главная страница
С помощью Doxygen возможна генерация практически любого формата документации. Для создания дополнительных справочников, например, man, необходимо указать это в настройщике, а также добавить правила генерации в Makefile.am:
@user: syscall-master/Doxyfile.in
diff --git a/Doxyfile.in b/Doxyfile.in
index d097c79..aa9f764 100644
--- a/Doxyfile.in
+++ b/Doxyfile.in
@@ -2204,7 +2204,7 @@ RTF_EXTRA_FILES        =
# classes and files.
# The default value is: NO.

-GENERATE_MAN           = NO
+GENERATE_MAN           = YES

# The MAN_OUTPUT tag is used to specify where the man pages will be put. If a
# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
lines 1-13/13 (END)
Поскольку Autotools умеет генерировать рецепты для сборки man, необходимо лишь указать путь, где должна будет храниться страница с документацией:
@user: syscall-master/Makefile.am
SUBDIRS = src doc tests

@DX_RULES@

all-local:      doxygen-doc

doxygen-doc/man/man3/syscall.h.3: doxygen-doc

man3_MANS = doxygen-doc/man/man3/syscall.h.3

checklog:       check
       cat tests/*.log

gcov:   check
       $(MAKE) -C src gcov

http:   doxygen-doc
       python3 -m http.server --directory $</html
...<Building PKG>...
@rooter
[user@VM syscall-master]$ hsh-shell --rooter
[root@localhost .in]# rpm -i syscall-1.3-alt1.x86_64.rpm
<13>Aug 12 12:09:43 rpm: syscall-1.3-alt1 1754998745 installed

[root@localhost .in]# ls /usr/share/man/man1/syscall.1.xz
/usr/share/man/man1/syscall.1.xz
[root@localhost .in]# ls /usr/share/man/man3/syscall.h.3.xz
/usr/share/man/man3/syscall.h.3.xz
[root@localhost .in]#
/usr/share/man/man3/syscall.h.3.xz
src/syscall.h(3)                                        Library Functions Manual                                        src/syscall.h(3)

NAME
      src/syscall.h

SYNOPSIS
      #include <err.h>
      #include <errno.h>
      #include <limits.h>
      #include <stdio.h>
      #include <stdlib.h>
      #include <string.h>
      #include <unistd.h>

  Classes
      struct syscall

  Macros
      #define _DEFAULT_SOURCE
      #define VERSION   'unknown'
      #define streq(a,  b)   (strcmp(a,b) == 0)
      #define ARG(n)   parse_arg(syscall_name, cmd[n])
      #define CMD_MAX   20  /* maximum number of commands per invocation */

  Typedefs
      typedef struct syscall Syscall

  Functions
      void usage ()
          Help-string of program usage.
      int scomp (const void *m1, const void *m2)
          Syscall`s names comparator.
      long lookup (const char *name)
          Binary search of executing syscall name.
      void unescape_nl (char *str)
          Quick and dirty way to unescape
           at the end of strings. "
      void dump_ret_values (void)
          Special debugging function.
      void echo (int argc, char **argv)
          Info syscall for input data presentation.
      unsigned long parse_arg (const char *syscall_name, char *arg)
          Argument Parser.
      void parse_syscall (int cmd_no, char **cmd, int cmd_len)
          Search and Execute syscall with fix number of args.
      void split_cmdline (int argc, char **argv)
          Parse input to groups <<syscall + fix number of args>> and execute syscalls.

  Variables
      int ret_values [CMD_MAX]
      Syscall systab []
      int systab_size

Macro Definition Documentation
  #define _DEFAULT_SOURCE
  #define ARG( n)   parse_arg(syscall_name, cmd[n])
  #define CMD_MAX   20  /* maximum number of commands per invocation */
  #define streq( a,  b)   (strcmp(a,b) == 0)
  #define VERSION   'unknown'
Typedef Documentation
  typedef struct syscall Syscall
Function Documentation
  void dump_ret_values (void )
      Special debugging function.

  void echo (int argc, char ** argv)
      Info syscall for input data presentation.

      Returns
          input shell-string with parsed arguments

  long lookup (const char * name)
      Binary search of executing syscall name.

      Parameters
          name executing syscall

      Returns
          system code of syscall

  unsigned long parse_arg (const char * syscall_name, char * arg)
      Argument Parser.

      Parameters
          syscall_name syscall to which the argument belongs
          arg argument which need to be parsed:

          o #<word> for length of a string

          o $<number> for return values of a previous syscalls

          o <number> for a number

          o <word> for a string

      Returns
          parsing value depending on input

  void parse_syscall (int cmd_no, char ** cmd, int cmd_len)
      Search and Execute syscall with fix number of args.

      Parameters
          cmd_no number of syscall
          cmd syscall text pointer
          cmd_len length of arguments of syscall

  int scomp (const void * m1, const void * m2)
      Syscall`s names comparator.

      Parameters
          m1 first comparing syscall
          m2 second comparing syscall

      Returns
          comparing result

  void split_cmdline (int argc, char ** argv)
      Parse input to groups <<syscall + fix number of args>> and execute syscalls.

      Parameters
          argc length of shell-string (words)
          argv input shell-string

  void unescape_nl (char * str)
      Quick and dirty way to unescape
       at the end of strings.

  void usage ()
      Help-string of program usage.

Variable Documentation
  int ret_values[CMD_MAX] [extern]
  Syscall systab[] [extern]
  int systab_size [extern]
Author
      Generated automatically by Doxygen for syscall from the source code.

syscall                                        Version 1.0                                        src/syscall.h(3)