Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,27 @@ jobs:
run: actionlint -shellcheck= -pyflakes=
- name: Lint Python scripts and tests
run: ruff check --select E9,F63,F7,F82 tests scripts
- name: Check generated CLI documentation
run: |
python3 scripts/gen_cli_docs.py --check
python3 -m unittest discover -s tests -p test_gen_cli_docs.py

docs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Install documentation tools
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends asciidoc xmlto docbook-xml docbook-xsl
- name: Build man pages and HTML from source
run: |
cmake -S . -B build-docs -DWITH_DOC_MAN=ON -DWITH_DOC_HTML=ON
cmake --build build-docs --target doc-man doc-html --parallel 2
test -s build-docs/man/ss-local.1
test -s build-docs/man/ss-nat.1
test -s build-docs/man/shadowsocks-c.8
test -s build-docs/html/ss-local.html

tests:
strategy:
Expand Down
32 changes: 32 additions & 0 deletions CONTRIBUTION.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,38 @@ unless a change has been discussed. For bundled dependency updates, follow
[the update procedure](third_party/README.md) and preserve upstream licenses and
notices.

## CLI and manual documentation

The SYNOPSIS and OPTIONS sections of the manual pages are generated from the
literal `getopt_long` declarations in `src/{local,server,tunnel,redir,manager}.c`
and the `getopts` declaration in `src/ss-nat`. Descriptions and argument names live
in `CLI_DOC` source comments: common C options in `src/utils.c`, program-specific
overrides in the corresponding C file, and shell options in `src/ss-nat`.
Each entry has an AsciiDoc term such as `--mtu <MTU>::` followed by its description.
The generator checks option coverage and argument arity across platform variants;
describe platform or feature restrictions in the comment. Cipher lists come from
the C cipher tables. Keep explanatory sections and examples in `doc/*.asciidoc`.

After changing a parser or its documentation comments, regenerate the checked-in
pages and run the generator tests:

```sh
python3 scripts/gen_cli_docs.py
python3 scripts/gen_cli_docs.py --check
python3 -m unittest discover -s tests -p test_gen_cli_docs.py
```

To render the manuals, install Python 3, AsciiDoc, and xmlto, then run:

```sh
cmake -S . -B build-docs -DWITH_DOC_MAN=ON -DWITH_DOC_HTML=ON
cmake --build build-docs --target doc-man doc-html --parallel
```

The build generates pages in the build directory without modifying source files
or executing target binaries, so it also works when cross-compiling. CI checks
that committed pages are current and renders both man and HTML output.

## Pull requests

Open your pull request against `master`. Explain the problem, what changes for
Expand Down
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ client mode and build options. Existing Snap packages still use the
The default build uses pinned sources included in this repository. It needs a
C11 compiler, CMake 3.20+, and Make or Ninja. No Git submodules, dependency
package installations, or network access are needed for configuration/build.
Python is used only by integration tests; documentation generation is optional.
Python is used by integration tests and optional documentation generation.

```sh
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
Expand All @@ -157,6 +157,10 @@ cmake --install build --prefix /your/install/prefix
Programs are in `build/bin/`. Bundled binaries link to platform runtime
libraries; they do not require separately installed third-party libraries.

Man pages and HTML documentation derive their CLI sections from the source
option parsers. See [the documentation workflow](CONTRIBUTION.md#cli-and-manual-documentation)
for regeneration and rendering commands.

For a smaller build, use `-DSS_MINIMAL=ON`. It excludes PCRE2 regex, plugin
subprocesses, the manager, and legacy stream ciphers. Minimal ACLs support
IPv4/IPv6 CIDRs, `full:example.com` for exact domains, and
Expand Down
58 changes: 49 additions & 9 deletions doc/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,53 @@ if(WITH_DOC_MAN AND NOT XMLTO_EXECUTABLE)
message(FATAL_ERROR "Manpage generation requires xmlto")
endif()

# NOTE For brew user, we have to setup this env var. see `brew info asciidoc'
# Homebrew catalogs are outside libxml's default search path. Respect an
# explicitly configured catalog and support both Apple Silicon and Intel Macs.
set(XMLTO_ENV)
set(XMLTO_CATALOG_DIR_MACOS /usr/local/etc/xml/catalog)
if (EXISTS ${XMLTO_CATALOG_DIR_MACOS})
set(XMLTO_ENV XML_CATALOG_FILES=${XMLTO_CATALOG_DIR_MACOS})
message(STATUS "Detect xmlto catalog dir ${XMLTO_CATALOG_DIR_MACOS}")
endif ()
if(APPLE AND "$ENV{XML_CATALOG_FILES}" STREQUAL "")
foreach(catalog /opt/homebrew/etc/xml/catalog /usr/local/etc/xml/catalog)
if(EXISTS ${catalog})
set(XMLTO_ENV XML_CATALOG_FILES=${catalog})
break()
endif()
endforeach()
endif()

set(CMAKE_MANPAGE_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/man)
set(CMAKE_HTML_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/html)

set(DOC_DIR ${PROJECT_SOURCE_DIR}/doc)
find_package(Python3 COMPONENTS Interpreter QUIET)
if((WITH_DOC_MAN OR WITH_DOC_HTML) AND NOT Python3_Interpreter_FOUND)
message(FATAL_ERROR "Documentation generation requires Python 3")
endif()

# Read source declarations, never execute target binaries (including cross builds).
# Keep checked-in pages for readers and for builds with documentation disabled.
set(CLI_DOC_DIR ${DOC_DIR})
set(CLI_DOC_FILES)
if(Python3_Interpreter_FOUND)
set(CLI_DOC_DIR ${CMAKE_CURRENT_BINARY_DIR}/generated)
set(CLI_DOC_INPUTS ${PROJECT_SOURCE_DIR}/src/utils.c
${PROJECT_SOURCE_DIR}/src/aead.c ${PROJECT_SOURCE_DIR}/src/stream.c
${PROJECT_SOURCE_DIR}/src/ss-nat)
foreach(module local server tunnel redir manager)
list(APPEND CLI_DOC_INPUTS ${PROJECT_SOURCE_DIR}/src/${module}.c)
endforeach()
foreach(name ss-local ss-server ss-tunnel ss-redir ss-manager ss-nat shadowsocks-c)
list(APPEND CLI_DOC_INPUTS ${DOC_DIR}/${name}.asciidoc)
list(APPEND CLI_DOC_FILES ${CLI_DOC_DIR}/${name}.asciidoc)
endforeach()
add_custom_command(OUTPUT ${CLI_DOC_FILES}
COMMAND ${Python3_EXECUTABLE} ${PROJECT_SOURCE_DIR}/scripts/gen_cli_docs.py
--output-dir ${CLI_DOC_DIR}
COMMAND ${CMAKE_COMMAND} -E touch ${CLI_DOC_FILES}
DEPENDS ${CLI_DOC_INPUTS} ${PROJECT_SOURCE_DIR}/scripts/gen_cli_docs.py
COMMENT "Generating CLI documentation from source"
VERBATIM)
add_custom_target(doc-cli DEPENDS ${CLI_DOC_FILES})
endif()

set(XMLTO_OPTS -m ${DOC_DIR}/manpage-normal.xsl -m ${DOC_DIR}/manpage-bold-literal.xsl man)
set(ASCIIDOC_XML_OPTS -b docbook -d manpage -f ${DOC_DIR}/asciidoc.conf -aversion=${PROJECT_VERSION})
set(ASCIIDOC_HTML_OPTS -b html4 -d article -f ${DOC_DIR}/asciidoc.conf -aversion=${PROJECT_VERSION})
Expand All @@ -38,22 +73,23 @@ foreach (manfile IN LISTS MAN_NAMES)

set(manfile ${CMAKE_MANPAGE_OUTPUT_DIRECTORY}/${manfile})
set(htmlfile ${CMAKE_HTML_OUTPUT_DIRECTORY}/${htmlfile})
set(docfile ${DOC_DIR}/${docfile})
set(docfile ${CLI_DOC_DIR}/${docfile})

add_custom_command(OUTPUT ${manfile}
COMMAND ${ASCIIDOC_EXECUTABLE} ${ASCIIDOC_XML_OPTS} -o ${xmlfile} ${docfile}
COMMAND ${CMAKE_COMMAND} -E env ${XMLTO_ENV} ${XMLTO_EXECUTABLE} ${XMLTO_OPTS} ${xmlfile}
# After we built the manpage, the xmlfile is nolongger needed
COMMAND ${CMAKE_COMMAND} -E remove ${xmlfile}
DEPENDS ${docfile}
DEPENDS ${docfile} ${DOC_DIR}/asciidoc.conf
${DOC_DIR}/manpage-normal.xsl ${DOC_DIR}/manpage-bold-literal.xsl
WORKING_DIRECTORY ${PROJECT_BINARY_DIR}/man
COMMENT "Building manpage ${manfile}"
VERBATIM)
list(APPEND MAN_FILES ${manfile})

add_custom_command(OUTPUT ${htmlfile}
COMMAND ${ASCIIDOC_EXECUTABLE} ${ASCIIDOC_HTML_OPTS} -o ${htmlfile} ${docfile}
DEPENDS ${docfile}
DEPENDS ${docfile} ${DOC_DIR}/asciidoc.conf
WORKING_DIRECTORY ${PROJECT_BINARY_DIR}/html
COMMENT "Building htmlfile ${htmlfile}"
VERBATIM)
Expand All @@ -62,6 +98,10 @@ endforeach ()

add_custom_target(doc-man ALL DEPENDS ${MAN_FILES})
add_custom_target(doc-html ALL DEPENDS ${HTML_FILES})
if(TARGET doc-cli)
add_dependencies(doc-man doc-cli)
add_dependencies(doc-html doc-cli)
endif()


if (NOT WITH_DOC_MAN)
Expand Down
4 changes: 2 additions & 2 deletions doc/asciidoc.conf
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ template::[header-declarations]
<refmeta>
<refentrytitle>{mantitle}</refentrytitle>
<manvolnum>{manvolnum}</manvolnum>
<refmiscinfo class="source">Shadowsocks-libev</refmiscinfo>
<refmiscinfo class="source">shadowsocks-c</refmiscinfo>
<refmiscinfo class="version">{version}</refmiscinfo>
<refmiscinfo class="manual">Shadowsocks-libev Manual</refmiscinfo>
<refmiscinfo class="manual">shadowsocks-c Manual</refmiscinfo>
</refmeta>
<refnamediv>
<refname>{manname}</refname>
Expand Down
Loading
Loading