This is an automated email from the ASF dual-hosted git repository.

lidavidm pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow-adbc.git


The following commit(s) were added to refs/heads/main by this push:
     new 97765976 docs: enable linking to Javadoc via Intersphinx (#1381)
97765976 is described below

commit 97765976b65830159b9a9b52c92b160cb3b7987b
Author: David Li <[email protected]>
AuthorDate: Wed Dec 20 12:50:12 2023 -0500

    docs: enable linking to Javadoc via Intersphinx (#1381)
    
    - Generate a Sphinx Intersphinx inventory by scraping generated
    Javadocs.
    - Inject the location of this inventory in CI.
    - Inject a fake URL to use for the location of the Javadocs since we
    don't know this at documentation build time.
    - At website build time, replace the fake URL with the real URL.
    
    The approach here is extensible to other languages.
    
    Fixes #1357.
---
 .github/workflows/native-unix.yml    |   7 ++
 ci/scripts/docs_build.sh             |  23 +++--
 ci/scripts/website_build.sh          |   7 ++
 docs/source/conf.py                  |  25 +++++
 docs/source/ext/adbc_java_domain.py  |  47 ++++++++++
 docs/source/ext/javadoc_inventory.py | 173 +++++++++++++++++++++++++++++++++++
 docs/source/format/specification.rst |  10 +-
 7 files changed, 282 insertions(+), 10 deletions(-)

diff --git a/.github/workflows/native-unix.yml 
b/.github/workflows/native-unix.yml
index 4d6c50c5..d8084da7 100644
--- a/.github/workflows/native-unix.yml
+++ b/.github/workflows/native-unix.yml
@@ -571,6 +571,13 @@ jobs:
         shell: bash -l {0}
         run: |
           ./ci/scripts/docs_build.sh "$(pwd)"
+      - name: Archive docs
+        uses: actions/upload-artifact@v3
+        with:
+          name: docs
+          retention-days: 2
+          path: |
+            docs/build/html
       - name: Test Recipes (C++)
         shell: bash -l {0}
         run: |
diff --git a/ci/scripts/docs_build.sh b/ci/scripts/docs_build.sh
index 7294e6d6..271dec45 100755
--- a/ci/scripts/docs_build.sh
+++ b/ci/scripts/docs_build.sh
@@ -25,15 +25,26 @@ main() {
     doxygen
     popd
 
-    pushd "$source_dir/docs"
-    make html
-    make doctest
-    popd
-
     pushd "$source_dir/java"
     mvn site
+    popd
+
+    pushd "$source_dir/docs"
+    # The project name/version don't really matter here.
+    python "$source_dir/docs/source/ext/javadoc_inventory.py" \
+           "ADBC" \
+           "version" \
+           "$source_dir/java/target/site/apidocs" \
+           "java/api"
+
+    # We need to determine the base URL without knowing it...
+    # Inject a dummy URL here, and fix it up in website_build.sh
+    export 
ADBC_INTERSPHINX_MAPPING_java_adbc="http://javadocs.home.arpa/;$source_dir/java/target/site/apidocs/objects.inv";
+
+    make html
     rm -rf "$source_dir/docs/build/html/java/api"
-    cp -r target/site/apidocs "$source_dir/docs/build/html/java/api"
+    cp -r "$source_dir/java/target/site/apidocs" 
"$source_dir/docs/build/html/java/api"
+    make doctest
     popd
 
     for desc_file in $(find "${source_dir}/r" -name DESCRIPTION); do
diff --git a/ci/scripts/website_build.sh b/ci/scripts/website_build.sh
index 480c57c9..31db174a 100755
--- a/ci/scripts/website_build.sh
+++ b/ci/scripts/website_build.sh
@@ -47,16 +47,23 @@ main() {
     fi
 
     local -r regex='^([0-9]+\.[0-9]+\.[0-9]+)$'
+    local directory="main"
     if [[ "${new_version}" =~ $regex ]]; then
         cp -r "${docs}" "${site}/${new_version}"
         git -C "${site}" add --force "${new_version}"
+        directory="${new_version}"
     else
         # Assume this is dev docs
         rm -rf "${site}/main"
         cp -r "${docs}" "${site}/main"
         git -C "${site}" add --force "main"
+        directory="main"
     fi
 
+    # Fix up lazy Intersphinx links (see docs_build.sh)
+    # Assumes GNU sed
+    sed -i 
"s|http://javadocs.home.arpa/|https://arrow.apache.org/adbc/${directory}/|g" 
$(grep -Rl javadocs.home.arpa "${site}/${directory}/")
+
     # Copy the version script and regenerate the version list
     # The versions get embedded into the JavaScript file to save a roundtrip
     rm -f "${site}/version.js"
diff --git a/docs/source/conf.py b/docs/source/conf.py
index 6e4858a1..128dfc73 100644
--- a/docs/source/conf.py
+++ b/docs/source/conf.py
@@ -15,6 +15,7 @@
 # specific language governing permissions and limitations
 # under the License.
 
+import os
 import sys
 from pathlib import Path
 
@@ -35,7 +36,10 @@ version = release
 
 exclude_patterns = []
 extensions = [
+    # recipe directive
     "adbc_cookbook",
+    # generic directives to enable intersphinx for java
+    "adbc_java_domain",
     "breathe",
     "numpydoc",
     "sphinx.ext.autodoc",
@@ -111,6 +115,27 @@ intersphinx_mapping = {
     "arrow": ("https://arrow.apache.org/docs/";, None),
 }
 
+# Add env vars like ADBC_INTERSPHINX_MAPPING_adbc_java = url;path
+# to inject more mappings
+
+
+def _find_intersphinx_mappings():
+    prefix = "ADBC_INTERSPHINX_MAPPING_"
+    for key, val in os.environ.items():
+        if key.startswith(prefix):
+            name = key[len(prefix) :]
+            url, _, path = val.partition(";")
+            print("[ADBC] Found Intersphinx mapping", name)
+            intersphinx_mapping[name] = (url, path)
+        #         "adbc_java": (
+        #     "http://localhost:8000/";,
+        #     
"/home/lidavidm/Code/arrow-adbc/java/target/site/apidocs/objects.inv",
+        # ),
+
+
+_find_intersphinx_mappings()
+
+
 # -- Options for numpydoc ----------------------------------------------------
 
 numpydoc_class_members_toctree = False
diff --git a/docs/source/ext/adbc_java_domain.py 
b/docs/source/ext/adbc_java_domain.py
new file mode 100644
index 00000000..186141fc
--- /dev/null
+++ b/docs/source/ext/adbc_java_domain.py
@@ -0,0 +1,47 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+"""A basic Java domain for Sphinx."""
+
+import typing
+
+from sphinx.application import Sphinx
+
+
+def setup(app: Sphinx) -> dict[str, typing.Any]:
+    # XXX: despite documentation, this is added to 'std' domain not 'rst'
+    # domain (look at the source)
+    app.add_object_type(
+        "javatype",
+        "jtype",
+        objname="Java Type",
+    )
+    app.add_object_type(
+        "javamember",
+        "jmember",
+        objname="Java Member",
+    )
+    app.add_object_type(
+        "javapackage",
+        "jpackage",
+        objname="Java Package",
+    )
+    return {
+        "version": "0.1",
+        "parallel_read_safe": True,
+        "parallel_write_safe": True,
+    }
diff --git a/docs/source/ext/javadoc_inventory.py 
b/docs/source/ext/javadoc_inventory.py
new file mode 100644
index 00000000..7c2fbcf2
--- /dev/null
+++ b/docs/source/ext/javadoc_inventory.py
@@ -0,0 +1,173 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+"""Generate a Sphinx inventory for a Javadoc site."""
+
+from __future__ import annotations
+
+import argparse
+import json
+import typing
+import urllib.parse
+from pathlib import Path
+
+import sphinx.util.inventory
+
+# XXX: we're taking advantage of duck typing to do stupid things here.
+
+
+class FakeEnv(typing.NamedTuple):
+    project: str
+    version: str
+
+
+class FakeObject(typing.NamedTuple):
+    # Looks like this
+    # name domainname:typ prio uri dispname
+    name: str
+    # written as '-' if equal to name
+    dispname: str
+    # member, doc, etc
+    typ: str
+    # passed through builder.get_target_uri
+    docname: str
+    # not including the #
+    anchor: str
+    # written, but never used
+    prio: str
+
+
+class FakeDomain(typing.NamedTuple):
+    objects: list[FakeObject]
+
+    def get_objects(self):
+        return self.objects
+
+
+class FakeBuildEnvironment(typing.NamedTuple):
+    config: FakeEnv
+    domains: dict[str, FakeDomain]
+
+
+class FakeBuilder:
+    def get_target_uri(self, docname: str) -> str:
+        return docname
+
+
+def extract_index(data: str, prelude: str) -> list:
+    epilogue = ";updateSearchResults();"
+    if not data.startswith(prelude):
+        raise ValueError(
+            f"Cannot parse search index; expected {prelude!r} but found 
{data[:50]!r}"
+        )
+    if data.endswith(epilogue):
+        data = data[len(prelude) : -len(epilogue)]
+    else:
+        # Some JDK versions appear to generate without the epilogue
+        data = data[len(prelude) :]
+    return json.loads(data)
+
+
+def make_fake_domains(root: Path, base_url: str) -> dict[str, FakeDomain]:
+    if not base_url.endswith("/"):
+        base_url += "/"
+
+    # Scrape the search index generated by Javadoc
+    # 
https://github.com/openjdk/jdk17/blob/4afbcaf55383ec2f5da53282a1547bac3d099e9d/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/util/IndexItem.java#L515
+    # "p" is containing package
+    # "m" is containing module
+    # "c" is containing class
+    # "l" is label
+    # "u" is the URL anchor
+
+    with open(root / "type-search-index.js") as f:
+        data = extract_index(f.read(), "typeSearchIndex = ")
+    with open(root / "member-search-index.js") as f:
+        data.extend(extract_index(f.read(), "memberSearchIndex = "))
+    with open(root / "package-search-index.js") as f:
+        data.extend(extract_index(f.read(), "packageSearchIndex = "))
+
+    domains = {
+        "std": FakeDomain(objects=[]),
+    }
+
+    for item in data:
+        if "p" not in item:
+            # Non-code item (package, or index)
+            if "All " in item["l"]:
+                # Ignore indices ("All Packages")
+                continue
+            # This is a package
+            name = item["l"]
+            url = f"{item['l'].replace('.', '/')}/package-summary.html"
+            anchor = ""
+            typ = "javapackage"
+            domain = "std"
+        elif "c" in item:
+            # This is a class member
+            name = f"{item['p']}.{item['c']}#{item['l']}"
+            url = f"{item['p'].replace('.', '/')}/{item['c']}.html"
+            anchor = item["u"] if "u" in item else item["l"]
+            typ = "javamember"
+            domain = "std"
+        else:
+            # This is a class/interface
+            name = f"{item['p']}.{item['l']}"
+            url = f"{item['p'].replace('.', '/')}/{item['l']}.html"
+            anchor = ""
+            typ = "javatype"
+            domain = "std"
+
+        url = urllib.parse.urljoin(base_url, url)
+        domains[domain].objects.append(
+            FakeObject(
+                name=name,
+                dispname=name,
+                typ=typ,
+                docname=url,
+                anchor=anchor,
+                prio=1,
+            )
+        )
+
+    return domains
+
+
+def main():
+    parser = argparse.ArgumentParser()
+    parser.add_argument("project", help="Project name.")
+    parser.add_argument("version", help="Project version.")
+    parser.add_argument("path", type=Path, help="Path to the generated 
Javadocs.")
+    parser.add_argument("url", help="Eventual base URL of the Javadocs.")
+
+    args = parser.parse_args()
+
+    domains = make_fake_domains(args.path, args.url)
+    config = FakeEnv(project=args.project, version=args.version)
+    env = FakeBuildEnvironment(config=config, domains=domains)
+
+    output = args.path / "objects.inv"
+    sphinx.util.inventory.InventoryFile.dump(
+        str(output),
+        env,
+        FakeBuilder(),
+    )
+    print("Wrote", output)
+
+
+if __name__ == "__main__":
+    main()
diff --git a/docs/source/format/specification.rst 
b/docs/source/format/specification.rst
index 19b73010..d1c91ea1 100644
--- a/docs/source/format/specification.rst
+++ b/docs/source/format/specification.rst
@@ -23,7 +23,9 @@ This document summarizes the general featureset.
 
 - For C/C++ details, see :doc:`adbc.h <../../cpp/api/adbc>`.
 - For Go details, see the `source 
<https://github.com/apache/arrow-adbc/blob/main/go/adbc/adbc.go>`__.
-- For Java details, see the `source 
<https://github.com/apache/arrow-adbc/tree/main/java/core>`__.
+- For Java details, see the `source
+  <https://github.com/apache/arrow-adbc/tree/main/java/core>`__, particularly
+  the package :jpackage:`org.apache.arrow.adbc.core`.
 
 Databases
 =========
@@ -34,7 +36,7 @@ provides a place to hold ownership of the in-memory database.
 
 - C/C++: :cpp:class:`AdbcDatabase`
 - Go: ``Driver``
-- Java: ``org.apache.arrow.adbc.core.AdbcDatabase``
+- Java: :jtype:`org.apache.arrow.adbc.core.AdbcDatabase`
 
 Connections
 ===========
@@ -43,7 +45,7 @@ A connection is a single, logical connection to a database.
 
 - C/C++: :cpp:class:`AdbcConnection`
 - Go: ``Connection``
-- Java: ``org.apache.arrow.adbc.core.AdbcConnection``
+- Java: :jtype:`org.apache.arrow.adbc.core.AdbcConnection`
 
 Autocommit
 ----------
@@ -55,7 +57,7 @@ implementations will support this.
 
 - C/C++: :c:macro:`ADBC_CONNECTION_OPTION_AUTOCOMMIT`
 - Go: ``OptionKeyAutoCommit``
-- Java: ``org.apache.arrow.adbc.core.AdbcConnection#setAutoCommit(boolean)``
+- Java: 
:jmember:`org.apache.arrow.adbc.core.AdbcConnection#setAutoCommit(boolean)`
 
 Metadata
 --------

Reply via email to