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
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,9 @@ Source archives are downloaded at the tag's full commit SHA and removed after
conversion. No repository checkout is copied into the build cache.

A DocC catalog entry may select `traits` from its tagged package, including
`default` when it needs the default APIs. Both compilation and symbol extraction
use that selection. Omitted traits keep the package defaults. Symbol extraction
includes extensions to external types so those APIs remain linkable in DocC.
`default` when it needs the default APIs. The compiler emits public symbol graphs
during that selected build. Omitted traits keep the package defaults. Symbol
graphs include extensions to external types so those APIs remain linkable in DocC.
Generated breadcrumbs omit external-type containers for which DocC emits no
page, while keeping the package's actual extension members. The link checker
distinguishes tutorial chapter labels from navigable tutorial pages; explicit
Expand Down
20 changes: 10 additions & 10 deletions Scripts/build-site
Original file line number Diff line number Diff line change
Expand Up @@ -103,16 +103,16 @@ def build_documentation(config, project, tag, cache, output, toolchain, jobs, co
swift_options += documentation_traits(manifest, project["documentation"].get("traits"))
if jobs is not None:
swift_options += ["--jobs", str(jobs)]
# SwiftPM's symbol extraction includes generated test-runner modules.
command(["swift", "build", *swift_options, "--build-tests"], cwd=source)
command(["swift", "package", *swift_options, "dump-symbol-graph",
"--minimum-access-level", "public", "--emit-extension-block-symbols"], cwd=source)
graph_directories = [path for path in scratch.glob("*/symbolgraph") if path.is_dir()]
if not graph_directories:
graph_directories = [path for path in scratch.glob("*/*/symbolgraph") if path.is_dir()]
if len(graph_directories) != 1:
raise SiteError(f"{repo}: expected one symbol graph directory, got {len(graph_directories)}")
all_graphs = list(graph_directories[0].glob("*.symbols.json"))
# Compiler emission preserves the selected traits; SwiftPM's separate
# symbol-graph command enables every trait when it creates its build plan.
graph_directory = work / "symbol-graphs"
graph_directory.mkdir(parents=True, exist_ok=True)
command(["swift", "build", *swift_options,
"-Xswiftc", "-emit-symbol-graph",
"-Xswiftc", "-emit-symbol-graph-dir", "-Xswiftc", str(graph_directory),
"-Xswiftc", "-symbol-graph-minimum-access-level", "-Xswiftc", "public",
"-Xswiftc", "-emit-extension-block-symbols"], cwd=source)
all_graphs = list(graph_directory.glob("*.symbols.json"))
module_graphs = {}
for path in all_graphs:
module = json.loads(path.read_text())["module"]["name"]
Expand Down
66 changes: 66 additions & 0 deletions Tests/test_pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@

import importlib.machinery
import importlib.util
from contextlib import nullcontext
import json
from pathlib import Path
import sys
import tempfile
import unittest
from unittest.mock import patch

SCRIPTS = Path(__file__).resolve().parent.parent / "Scripts"
sys.path.insert(0, str(SCRIPTS))
Expand Down Expand Up @@ -184,6 +186,70 @@ def test_documentation_targets_follow_the_tagged_public_products(self):
with self.assertRaises(build_site.SiteError):
build_site.library_targets(manifest, ["CLI"])

def test_selected_build_supplies_public_and_extension_graphs(self):
fixtures = SCRIPTS.parent / ".build/test-fixtures"
fixtures.mkdir(parents=True, exist_ok=True)
with tempfile.TemporaryDirectory(dir=fixtures) as directory:
root = Path(directory)
source = root / "source"
source.mkdir()
(source / "Package.swift").write_text("// fixture manifest\n")
manifest = {
"traits": [{"name": "Preview"}],
"products": [{"type": {"library": ["automatic"]}, "targets": ["Core"]}],
"targets": [{"name": "Core"}],
}
project = {"repository": "example", "documentation": {
"targets": "library-products", "traits": ["Preview", "default"],
}}
builds = []
converted = []

def command(arguments, cwd=None, capture=False):
if arguments[:3] == ["swift", "package", "dump-package"]:
return json.dumps(manifest)
if arguments[:2] == ["swift", "build"]:
builds.append(arguments)
self.assertEqual(arguments[arguments.index("--traits") + 1], "Preview,default")
self.assertIn("-emit-symbol-graph", arguments)
self.assertIn("-emit-extension-block-symbols", arguments)
level = arguments.index("-symbol-graph-minimum-access-level")
self.assertEqual(arguments[level + 2], "public")
destination = Path(arguments[arguments.index("-emit-symbol-graph-dir") + 2])
for name, module in [("Core", "Core"), ("Core@Foundation", "Core"),
("Dependency", "Dependency")]:
(destination / (name + ".symbols.json")).write_text(
json.dumps({"module": {"name": module}}))
return ""
if arguments[:3] == ["xcrun", "docc", "convert"]:
symbols = Path(arguments[arguments.index("--additional-symbol-graph-dir") + 1])
converted.extend(path.name for path in symbols.glob("*.symbols.json"))
Path(arguments[arguments.index("--output-path") + 1]).mkdir(parents=True)
return ""
if arguments[:4] == ["xcrun", "docc", "process-archive", "transform-for-static-hosting"]:
destination = Path(arguments[arguments.index("--output-path") + 1])
landing = destination / "documentation/core/index.html"
landing.parent.mkdir(parents=True)
landing.write_text("Core documentation")
return ""
self.fail(f"Unexpected external command: {arguments}")

output = root / "output"
output.mkdir()
with patch.object(build_site, "archive_source", return_value=nullcontext(source)), \
patch.object(build_site, "command", side_effect=command), \
patch.object(build_site, "add_identity"), \
patch.object(build_site, "normalize_extension_hierarchy"), \
patch.object(build_site, "identity_records", return_value=[]):
result = build_site.build_documentation(
{"organization": "sample"}, project,
{"name": "v1.0.0", "commit": {"sha": "a" * 40}},
root / "cache", output, "fixture toolchain", 2, "blue", b"icon")
self.assertEqual(len(builds), 1)
self.assertEqual(set(converted), {"Core.symbols.json", "[email protected]"})
self.assertEqual(result["modules"], ["Core"])
self.assertTrue((output / "example/documentation/core/index.html").is_file())


if __name__ == "__main__":
unittest.main()
Loading