[clang-tools-extra] [clang-doc] Allow setting a base directory for hosted pages (PR #132482)

Paul Kirth via cfe-commits cfe-commits at lists.llvm.org
Fri Mar 21 15:53:37 PDT 2025


https://github.com/ilovepi updated https://github.com/llvm/llvm-project/pull/132482

>From 1a9d8768e61e4e69560cff228eacee81e61d9764 Mon Sep 17 00:00:00 2001
From: Paul Kirth <paulkirth at google.com>
Date: Fri, 21 Mar 2025 22:29:14 +0000
Subject: [PATCH] [clang-doc] Allow setting a base directory for hosted pages

Currently, when we set URLs from JS, we set them only using the protocol
and host locations. This works fine when docs are served from the base
directory of the site, but if you want to nest it under another
directory, our JS fails to set the correct path, leading to broken
links.

This patch adds a --base option to specify the path prefix to use, which
is set in the generated index_json.js file. index.json can then fill in
the prefix appropriately when generating links in a browser. This flag
has no effect for non HTML output.

Given an index hosted at: www.docs.com/base_directory/index.html
we used to generate the following link:
    www.docs.com/file.html
Using --base base_directory we now generate:
    www.docs.com/base_directory/file.html

This allows such links to work when hosting pages without using a custom
index.js.
---
 clang-tools-extra/clang-doc/HTMLGenerator.cpp       |  5 +++++
 clang-tools-extra/clang-doc/Representation.cpp      |  4 ++--
 clang-tools-extra/clang-doc/Representation.h        |  3 ++-
 clang-tools-extra/clang-doc/assets/index.js         |  7 ++++---
 clang-tools-extra/clang-doc/tool/ClangDocMain.cpp   | 13 +++++++++----
 clang-tools-extra/test/clang-doc/assets.cpp         |  4 ++--
 clang-tools-extra/test/clang-doc/test-path-abs.cpp  |  3 ++-
 .../unittests/clang-doc/HTMLGeneratorTest.cpp       |  6 +++---
 8 files changed, 29 insertions(+), 16 deletions(-)

diff --git a/clang-tools-extra/clang-doc/HTMLGenerator.cpp b/clang-tools-extra/clang-doc/HTMLGenerator.cpp
index a8404479569f9..9b95d082fdfe7 100644
--- a/clang-tools-extra/clang-doc/HTMLGenerator.cpp
+++ b/clang-tools-extra/clang-doc/HTMLGenerator.cpp
@@ -1078,6 +1078,11 @@ static llvm::Error serializeIndex(ClangDocContext &CDCtx) {
   std::replace(RootPathEscaped.begin(), RootPathEscaped.end(), '\\', '/');
   OS << "var RootPath = \"" << RootPathEscaped << "\";\n";
 
+  llvm::SmallString<128> Base(CDCtx.Base);
+  std::string BaseEscaped = Base.str().str();
+  std::replace(BaseEscaped.begin(), BaseEscaped.end(), '\\', '/');
+  OS << "var Base = \"" << BaseEscaped << "\";\n";
+
   CDCtx.Idx.sort();
   llvm::json::OStream J(OS, 2);
   std::function<void(Index)> IndexToJSON = [&](const Index &I) {
diff --git a/clang-tools-extra/clang-doc/Representation.cpp b/clang-tools-extra/clang-doc/Representation.cpp
index 4da93b24c131f..fd206fb6a18cc 100644
--- a/clang-tools-extra/clang-doc/Representation.cpp
+++ b/clang-tools-extra/clang-doc/Representation.cpp
@@ -367,10 +367,10 @@ void Index::sort() {
 ClangDocContext::ClangDocContext(tooling::ExecutionContext *ECtx,
                                  StringRef ProjectName, bool PublicOnly,
                                  StringRef OutDirectory, StringRef SourceRoot,
-                                 StringRef RepositoryUrl,
+                                 StringRef RepositoryUrl, StringRef Base,
                                  std::vector<std::string> UserStylesheets)
     : ECtx(ECtx), ProjectName(ProjectName), PublicOnly(PublicOnly),
-      OutDirectory(OutDirectory), UserStylesheets(UserStylesheets) {
+      OutDirectory(OutDirectory), UserStylesheets(UserStylesheets), Base(Base) {
   llvm::SmallString<128> SourceRootDir(SourceRoot);
   if (SourceRoot.empty())
     // If no SourceRoot was provided the current path is used as the default
diff --git a/clang-tools-extra/clang-doc/Representation.h b/clang-tools-extra/clang-doc/Representation.h
index bb0c534af7b74..b614ce45f5aac 100644
--- a/clang-tools-extra/clang-doc/Representation.h
+++ b/clang-tools-extra/clang-doc/Representation.h
@@ -507,7 +507,7 @@ struct ClangDocContext {
   ClangDocContext() = default;
   ClangDocContext(tooling::ExecutionContext *ECtx, StringRef ProjectName,
                   bool PublicOnly, StringRef OutDirectory, StringRef SourceRoot,
-                  StringRef RepositoryUrl,
+                  StringRef RepositoryUrl, StringRef Base,
                   std::vector<std::string> UserStylesheets);
   tooling::ExecutionContext *ECtx;
   std::string ProjectName; // Name of project clang-doc is documenting.
@@ -523,6 +523,7 @@ struct ClangDocContext {
   std::vector<std::string> UserStylesheets;
   // JavaScript files that will be imported in allHTML file.
   std::vector<std::string> JsScripts;
+  StringRef Base;
   Index Idx;
 };
 
diff --git a/clang-tools-extra/clang-doc/assets/index.js b/clang-tools-extra/clang-doc/assets/index.js
index 58a92f049f19f..8a83f072526f9 100644
--- a/clang-tools-extra/clang-doc/assets/index.js
+++ b/clang-tools-extra/clang-doc/assets/index.js
@@ -1,9 +1,10 @@
 function genLink(Ref) {
   // we treat the file paths different depending on if we're
   // serving via a http server or viewing from a local
-  var Path = window.location.protocol.startsWith("file") ?
-      `${window.location.protocol}//${RootPath}/${Ref.Path}` :
-      `${window.location.protocol}//${window.location.host}/${Ref.Path}`;
+  var Path = window.location.protocol.startsWith("file")
+                 ? `${window.location.protocol}//${RootPath}/${Ref.Path}`
+                 : `${window.location.protocol}//${window.location.host}/${
+                       Base}/${Ref.Path}`;
   if (Ref.RefType === "namespace") {
     Path = `${Path}/index.html`
   } else if (Ref.Path === "") {
diff --git a/clang-tools-extra/clang-doc/tool/ClangDocMain.cpp b/clang-tools-extra/clang-doc/tool/ClangDocMain.cpp
index 2ce707feb3d5e..a754f38a95447 100644
--- a/clang-tools-extra/clang-doc/tool/ClangDocMain.cpp
+++ b/clang-tools-extra/clang-doc/tool/ClangDocMain.cpp
@@ -67,6 +67,12 @@ static llvm::cl::opt<std::string>
                  llvm::cl::desc("Directory for outputting generated files."),
                  llvm::cl::init("docs"), llvm::cl::cat(ClangDocCategory));
 
+static llvm::cl::opt<std::string>
+    BaseDirectory("base",
+                  llvm::cl::desc(R"(Base Directory for generated documentation.
+URLs will be rooted at this directory for HTML links.)"),
+                  llvm::cl::init(""), llvm::cl::cat(ClangDocCategory));
+
 static llvm::cl::opt<bool>
     PublicOnly("public", llvm::cl::desc("Document only public declarations."),
                llvm::cl::init(false), llvm::cl::cat(ClangDocCategory));
@@ -141,8 +147,7 @@ llvm::Error getAssetFiles(clang::doc::ClangDocContext &CDCtx) {
   using DirIt = llvm::sys::fs::directory_iterator;
   std::error_code FileErr;
   llvm::SmallString<128> FilePath(UserAssetPath);
-  for (DirIt DirStart = DirIt(UserAssetPath, FileErr),
-                   DirEnd;
+  for (DirIt DirStart = DirIt(UserAssetPath, FileErr), DirEnd;
        !FileErr && DirStart != DirEnd; DirStart.increment(FileErr)) {
     FilePath = DirStart->path();
     if (llvm::sys::fs::is_regular_file(FilePath)) {
@@ -268,8 +273,8 @@ Example usage for a project using a compile commands database:
       OutDirectory,
       SourceRoot,
       RepositoryUrl,
-      {UserStylesheets.begin(), UserStylesheets.end()}
-  };
+      BaseDirectory,
+      {UserStylesheets.begin(), UserStylesheets.end()}};
 
   if (Format == "html") {
     if (auto Err = getHtmlAssetFiles(argv[0], CDCtx)) {
diff --git a/clang-tools-extra/test/clang-doc/assets.cpp b/clang-tools-extra/test/clang-doc/assets.cpp
index d5a2d20e92240..c5933e504f6b9 100644
--- a/clang-tools-extra/test/clang-doc/assets.cpp
+++ b/clang-tools-extra/test/clang-doc/assets.cpp
@@ -1,5 +1,5 @@
 // RUN: rm -rf %t && mkdir %t
-// RUN: clang-doc --format=html --output=%t --asset=%S/Inputs/test-assets --executor=standalone %s
+// RUN: clang-doc --format=html --output=%t --asset=%S/Inputs/test-assets --executor=standalone %s --base base_dir
 // RUN: FileCheck %s -input-file=%t/index.html -check-prefix=INDEX
 // RUN: FileCheck %s -input-file=%t/test.css -check-prefix=CSS
 // RUN: FileCheck %s -input-file=%t/test.js -check-prefix=JS
@@ -19,4 +19,4 @@
 // CSS-NEXT:     padding: 0;
 // CSS-NEXT: }
 
-// JS: console.log("Hello, world!");
\ No newline at end of file
+// JS: console.log("Hello, world!");
diff --git a/clang-tools-extra/test/clang-doc/test-path-abs.cpp b/clang-tools-extra/test/clang-doc/test-path-abs.cpp
index 292a2a3b5474d..8875a3a73ab7e 100644
--- a/clang-tools-extra/test/clang-doc/test-path-abs.cpp
+++ b/clang-tools-extra/test/clang-doc/test-path-abs.cpp
@@ -1,6 +1,7 @@
 // RUN: rm -rf %t && mkdir -p %t
-// RUN: clang-doc --format=html --executor=standalone %s --output=%t
+// RUN: clang-doc --format=html --executor=standalone %s --output=%t --base base_dir
 // RUN: FileCheck %s -input-file=%t/index_json.js  -check-prefix=JSON-INDEX
 
 // JSON-INDEX: var RootPath = "{{.*}}test-path-abs.cpp.tmp";
+// JSON-INDEX-NEXT: var Base = "base_dir";
 
diff --git a/clang-tools-extra/unittests/clang-doc/HTMLGeneratorTest.cpp b/clang-tools-extra/unittests/clang-doc/HTMLGeneratorTest.cpp
index 97afa12cab6d3..66cf77d7b0f37 100644
--- a/clang-tools-extra/unittests/clang-doc/HTMLGeneratorTest.cpp
+++ b/clang-tools-extra/unittests/clang-doc/HTMLGeneratorTest.cpp
@@ -28,9 +28,9 @@ std::unique_ptr<Generator> getHTMLGenerator() {
 
 ClangDocContext
 getClangDocContext(std::vector<std::string> UserStylesheets = {},
-                   StringRef RepositoryUrl = "") {
-  ClangDocContext CDCtx{
-      {}, "test-project", {}, {}, {}, RepositoryUrl, UserStylesheets};
+                   StringRef RepositoryUrl = "", StringRef Base = "") {
+  ClangDocContext CDCtx{{}, "test-project", {},   {},
+                        {}, RepositoryUrl,  Base, UserStylesheets};
   CDCtx.UserStylesheets.insert(
       CDCtx.UserStylesheets.begin(),
       "../share/clang/clang-doc-default-stylesheet.css");



More information about the cfe-commits mailing list