From bad2e1460a3b0a8abc63df2d2f36047477103d43 Mon Sep 17 00:00:00 2001 From: Joe Heck Date: Thu, 20 Aug 2026 14:18:38 -0700 Subject: [PATCH 1/5] switching all sources to build nightly main branch --- scripts/sources.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/sources.json b/scripts/sources.json index 48ca05e0..60c8db0c 100644 --- a/scripts/sources.json +++ b/scripts/sources.json @@ -38,14 +38,14 @@ "id": "swiftpm", "type": "git", "repo": "https://github.com/swiftlang/swift-package-manager.git", - "ref": "release/6.3", + "ref": "main", "targets": ["PackageManagerDocs", "PackageDescription", "PackagePlugin"] }, { "id": "swift-testing", "type": "git", "repo": "https://github.com/swiftlang/swift-testing.git", - "ref": "release/6.3", + "ref": "main", "targets": ["Testing"], "add_docc_plugin": true }, From b3629639dee1690bf77b5122d60182d6e157e301 Mon Sep 17 00:00:00 2001 From: Joe Heck Date: Thu, 20 Aug 2026 14:22:32 -0700 Subject: [PATCH 2/5] switching CI to use nightly builds for all combined docs generation --- .github/workflows/build_documentation.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/build_documentation.yml b/.github/workflows/build_documentation.yml index b6654336..fe8dc2ca 100644 --- a/.github/workflows/build_documentation.yml +++ b/.github/workflows/build_documentation.yml @@ -19,8 +19,7 @@ jobs: if: github.event_name != 'schedule' || github.repository == 'swiftlang/docs' runs-on: ubuntu-latest container: - # image: swiftlang/swift:nightly-main-jammy - image: swift:6.3 + image: swiftlang/swift:nightly-main-jammy steps: - uses: actions/checkout@v7 From 70d6ec2c39abc34487f3d016ed50949eb2a50ac7 Mon Sep 17 00:00:00 2001 From: Joe Heck Date: Thu, 20 Aug 2026 14:44:25 -0700 Subject: [PATCH 3/5] removing the caching mechanism for building for now --- .github/workflows/build_documentation.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build_documentation.yml b/.github/workflows/build_documentation.yml index fe8dc2ca..c332bcb7 100644 --- a/.github/workflows/build_documentation.yml +++ b/.github/workflows/build_documentation.yml @@ -23,12 +23,12 @@ jobs: steps: - uses: actions/checkout@v7 - - uses: actions/cache@v6 - with: - path: .workspace - key: docs-workspace-${{ hashFiles('scripts/sources.json') }} - restore-keys: | - docs-workspace- + # - uses: actions/cache@v6 + # with: + # path: .workspace + # key: docs-workspace-${{ hashFiles('scripts/sources.json') }} + # restore-keys: | + # docs-workspace- - name: Install Python run: apt-get update && apt-get install -y python3 From f288ec842b6cc315d5d62ddabe9c982144cab382 Mon Sep 17 00:00:00 2001 From: Joe Heck Date: Thu, 20 Aug 2026 15:00:27 -0700 Subject: [PATCH 4/5] disabling experimental link support, which is failing main builds of swift-testing --- scripts/build_docs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/build_docs.py b/scripts/build_docs.py index a8c225f2..b171e8f6 100755 --- a/scripts/build_docs.py +++ b/scripts/build_docs.py @@ -52,7 +52,7 @@ class ArchiveFetchError(Exception): DOCC_BUILD_FLAGS = [ "--experimental-enable-custom-templates", "--enable-mentioned-in", - "--enable-experimental-external-link-support", + # "--enable-experimental-external-link-support", # Emit a markdown copy of every rendered document (plus a manifest) under # data/documentation/. These sidecar files survive docc merge and # process-archive transform-for-static-hosting, so they reach the published From 9c750949013e6a6284fea472fc6ec5ca14282223 Mon Sep 17 00:00:00 2001 From: Joe Heck Date: Thu, 20 Aug 2026 15:31:56 -0700 Subject: [PATCH 5/5] fix the adding-docc-plugin process for swift-testing - awkward phase of docs - the plugin to be added needed to be added in the correct manifest section, and the prior process worked in Swift 6.3, but not with latest (main, for example) - this updates it to clone the content, THEN add the plugin, so that we can leverage generate-documentation. --- scripts/build_docs.py | 69 +++++++++++++- scripts/test_build_docs.py | 180 +++++++++++++++++++++++++++++++++++++ 2 files changed, 247 insertions(+), 2 deletions(-) diff --git a/scripts/build_docs.py b/scripts/build_docs.py index b171e8f6..dc6a94b4 100755 --- a/scripts/build_docs.py +++ b/scripts/build_docs.py @@ -458,12 +458,44 @@ def find_doccarchive(search_dir, target): return None +def _docc_plugin_resolves(source_dir, swift_cmd): + """Check whether swift-docc-plugin is in the resolved dependency graph. + + Uses `swift package describe --type json`, which evaluates Package.swift + under the real build environment — unlike a text search, this catches a + dependency that was added into an inactive branch of a conditional + expression (e.g. a manifest with two 'dependencies:' arrays gated by an + env-var flag). + """ + try: + result = subprocess.run( + swift_cmd + ["package", "describe", "--type", "json"], + cwd=str(source_dir), capture_output=True, text=True, check=True, + ) + pkg_info = json.loads(result.stdout) + except (subprocess.CalledProcessError, json.JSONDecodeError): + return False + return any( + "swift-docc-plugin" in dep.get("url", "") + for dep in pkg_info.get("dependencies", []) + ) + + def add_docc_plugin(source_dir, swift_cmd): - """Inject swift-docc-plugin dependency if not already present.""" + """Inject swift-docc-plugin dependency if not already present. + + Verifies the dependency actually resolves after `add-dependency` runs. + Some manifests have more than one 'dependencies:' array (e.g. a ternary + gated on a local-dependencies flag); `swift package add-dependency` can + add the entry to a branch that isn't active in this build, in which case + the entry is relocated into the first 'dependencies:' array instead. + """ package_swift = source_dir / "Package.swift" - if "swift-docc-plugin" in package_swift.read_text(): + before = package_swift.read_text() + if "swift-docc-plugin" in before: print("swift-docc-plugin dependency already present, skipping.") return + print("Adding swift-docc-plugin dependency...") subprocess.run( swift_cmd + [ @@ -475,6 +507,39 @@ def add_docc_plugin(source_dir, swift_cmd): check=True, ) + if _docc_plugin_resolves(source_dir, swift_cmd): + return + + print( + " swift-docc-plugin did not resolve after being added — the " + "manifest likely has more than one 'dependencies:' array; " + "relocating it into the first one..." + ) + after = package_swift.read_text() + added_lines = [ + line for line in after.splitlines(keepends=True) + if "swift-docc-plugin" in line + ] + if len(added_lines) != 1: + raise RuntimeError( + "could not isolate the swift-docc-plugin line added by " + "'swift package add-dependency' in Package.swift" + ) + plugin_line = added_lines[0] + + without_line = after.replace(plugin_line, "", 1) + marker = without_line.index("dependencies:") + bracket = without_line.index("[", marker) + 1 + fixed = without_line[:bracket] + "\n" + plugin_line.strip() + without_line[bracket:] + package_swift.write_text(fixed) + + if not _docc_plugin_resolves(source_dir, swift_cmd): + raise RuntimeError( + "swift-docc-plugin still does not resolve after relocating it " + "into the first 'dependencies:' array in Package.swift" + ) + print(" Relocated swift-docc-plugin into the active dependencies array.") + def _build_archive_source(source, workspace, temp_archive_dir): """Fetch an archive source, copy it into the staging dir, optionally strip. diff --git a/scripts/test_build_docs.py b/scripts/test_build_docs.py index 605d0caf..99d40df2 100644 --- a/scripts/test_build_docs.py +++ b/scripts/test_build_docs.py @@ -975,6 +975,186 @@ def fake_run(cmd, **kw): self.assertEqual(commit, "unknown") +class DoccPluginResolves(unittest.TestCase): + def test_true_when_dependency_present(self): + def fake_run(cmd, **kw): + payload = json.dumps({ + "dependencies": [ + {"url": "https://github.com/swiftlang/swift-docc-plugin"}, + ] + }) + return subprocess.CompletedProcess(cmd, 0, stdout=payload, stderr="") + + with mock.patch.object(build_docs.subprocess, "run", side_effect=fake_run): + self.assertTrue( + build_docs._docc_plugin_resolves(Path("/tmp/x"), ["swift"]) + ) + + def test_false_when_dependency_absent(self): + def fake_run(cmd, **kw): + payload = json.dumps({ + "dependencies": [ + {"url": "https://github.com/swiftlang/swift-syntax.git"}, + ] + }) + return subprocess.CompletedProcess(cmd, 0, stdout=payload, stderr="") + + with mock.patch.object(build_docs.subprocess, "run", side_effect=fake_run): + self.assertFalse( + build_docs._docc_plugin_resolves(Path("/tmp/x"), ["swift"]) + ) + + def test_false_when_describe_fails(self): + def fake_run(cmd, **kw): + raise subprocess.CalledProcessError(1, cmd) + + with mock.patch.object(build_docs.subprocess, "run", side_effect=fake_run): + self.assertFalse( + build_docs._docc_plugin_resolves(Path("/tmp/x"), ["swift"]) + ) + + def test_false_when_describe_output_is_not_json(self): + def fake_run(cmd, **kw): + return subprocess.CompletedProcess(cmd, 0, stdout="not json", stderr="") + + with mock.patch.object(build_docs.subprocess, "run", side_effect=fake_run): + self.assertFalse( + build_docs._docc_plugin_resolves(Path("/tmp/x"), ["swift"]) + ) + + +class AddDoccPlugin(unittest.TestCase): + def _describe_result(self, cmd, has_plugin): + deps = [{"url": "https://github.com/swiftlang/swift-syntax.git"}] + if has_plugin: + deps.append({"url": "https://github.com/swiftlang/swift-docc-plugin"}) + return subprocess.CompletedProcess( + cmd, 0, stdout=json.dumps({"dependencies": deps}), stderr="" + ) + + def test_skips_when_already_present(self): + with tempfile.TemporaryDirectory() as tmp: + source_dir = Path(tmp) + (source_dir / "Package.swift").write_text( + 'dependencies: [\n .package(url: "https://github.com/swiftlang/swift-docc-plugin", from: "1.1.0"),\n],\n' + ) + with mock.patch.object(build_docs.subprocess, "run") as run: + build_docs.add_docc_plugin(source_dir, ["swift"]) + run.assert_not_called() + + def test_no_relocation_needed_when_plugin_resolves(self): + with tempfile.TemporaryDirectory() as tmp: + source_dir = Path(tmp) + package_swift = source_dir / "Package.swift" + package_swift.write_text( + 'dependencies: [\n' + ' .package(url: "https://example.com/a.git", from: "1.0.0"),\n' + '],\n' + ) + + def fake_run(cmd, **kw): + if "add-dependency" in cmd: + text = package_swift.read_text() + text = text.replace( + '.package(url: "https://example.com/a.git", from: "1.0.0"),', + '.package(url: "https://example.com/a.git", from: "1.0.0"),\n' + ' .package(url: "https://github.com/swiftlang/swift-docc-plugin", from: "1.1.0"),', + ) + package_swift.write_text(text) + return subprocess.CompletedProcess(cmd, 0, stdout="", stderr="") + return self._describe_result(cmd, has_plugin=True) + + with mock.patch.object(build_docs.subprocess, "run", side_effect=fake_run): + build_docs.add_docc_plugin(source_dir, ["swift"]) + + final = package_swift.read_text() + self.assertEqual(final.count("swift-docc-plugin"), 1) + + def test_relocates_when_added_into_inactive_branch(self): + with tempfile.TemporaryDirectory() as tmp: + source_dir = Path(tmp) + package_swift = source_dir / "Package.swift" + package_swift.write_text( + 'dependencies: cond ? [\n' + ' .package(url: "https://example.com/active.git", from: "1.0.0"),\n' + '] : [\n' + ' .package(path: "../active"),\n' + '],\n' + ) + describe_results = iter([False, True]) + + def fake_run(cmd, **kw): + if "add-dependency" in cmd: + text = package_swift.read_text() + text = text.replace( + '.package(path: "../active"),', + '.package(path: "../active"),\n' + ' .package(url: "https://github.com/swiftlang/swift-docc-plugin", from: "1.1.0"),', + ) + package_swift.write_text(text) + return subprocess.CompletedProcess(cmd, 0, stdout="", stderr="") + return self._describe_result(cmd, has_plugin=next(describe_results)) + + with mock.patch.object(build_docs.subprocess, "run", side_effect=fake_run): + build_docs.add_docc_plugin(source_dir, ["swift"]) + + final = package_swift.read_text() + self.assertEqual(final.count("swift-docc-plugin"), 1) + self.assertLess( + final.index("swift-docc-plugin"), final.index("../active"), + "plugin dependency should have been relocated into the first array", + ) + + def test_raises_when_still_unresolved_after_relocation(self): + with tempfile.TemporaryDirectory() as tmp: + source_dir = Path(tmp) + package_swift = source_dir / "Package.swift" + package_swift.write_text( + 'dependencies: cond ? [\n' + ' .package(url: "https://example.com/active.git", from: "1.0.0"),\n' + '] : [\n' + ' .package(path: "../active"),\n' + '],\n' + ) + + def fake_run(cmd, **kw): + if "add-dependency" in cmd: + text = package_swift.read_text() + text = text.replace( + '.package(path: "../active"),', + '.package(path: "../active"),\n' + ' .package(url: "https://github.com/swiftlang/swift-docc-plugin", from: "1.1.0"),', + ) + package_swift.write_text(text) + return subprocess.CompletedProcess(cmd, 0, stdout="", stderr="") + return self._describe_result(cmd, has_plugin=False) + + with mock.patch.object(build_docs.subprocess, "run", side_effect=fake_run): + with self.assertRaises(RuntimeError): + build_docs.add_docc_plugin(source_dir, ["swift"]) + + def test_raises_when_added_line_not_isolatable(self): + with tempfile.TemporaryDirectory() as tmp: + source_dir = Path(tmp) + package_swift = source_dir / "Package.swift" + package_swift.write_text('dependencies: [\n],\n') + + def fake_run(cmd, **kw): + if "add-dependency" in cmd: + package_swift.write_text( + 'dependencies: [\n' + ' .package(url: "https://github.com/swiftlang/swift-docc-plugin", from: "1.1.0"),\n' + ' // swift-docc-plugin duplicate mention\n' + '],\n' + ) + return subprocess.CompletedProcess(cmd, 0, stdout="", stderr="") + return self._describe_result(cmd, has_plugin=False) + + with mock.patch.object(build_docs.subprocess, "run", side_effect=fake_run): + with self.assertRaises(RuntimeError): + build_docs.add_docc_plugin(source_dir, ["swift"]) + + class MergeArchives(unittest.TestCase): def _capture_merge_cmd(self, version): """Run merge_archives with a stubbed subprocess and return the cmd."""