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
72 changes: 65 additions & 7 deletions .github/workflows/validate-sdk-compliance.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ on:
type: string
default: sdk-compliance.yaml
language:
description: SDK language for public API check — must match a parse-<language> script (e.g. swift, javascript, dart)
description: SDK language for public API check — "swift", "javascript", or "dart"
type: string
required: true
sdk-ref:
Expand Down Expand Up @@ -45,7 +45,7 @@ jobs:

check:
name: Check public API against capability matrix
runs-on: ubuntu-latest
runs-on: ${{ inputs.language == 'swift' && 'macos-latest' || 'ubuntu-latest' }}
steps:
- name: Checkout PR branch
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
Expand All @@ -69,6 +69,17 @@ jobs:
with:
node-version: "22"

- name: Cache SPM dependencies
if: inputs.language == 'swift'
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
with:
path: |
~/.cache/org.swift.swiftpm
_sdk-pr/.build/repositories
_sdk-base/.build/repositories
key: spm-${{ runner.os }}-${{ hashFiles('_sdk-pr/Package.resolved') }}
restore-keys: spm-${{ runner.os }}-

- name: Install dependencies
run: npm ci
working-directory: _sdk-spec/scripts/capability-matrix
Expand All @@ -85,21 +96,68 @@ jobs:
run: dart pub get
working-directory: _sdk-spec/scripts/dart_symbol_extractor

- name: Extract symbol graphs — PR
if: inputs.language == 'swift'
run: |
# || true: test targets may fail to extract on CI runners; library targets land first
swift package dump-symbol-graph \
--minimum-access-level public \
--skip-synthesized-members || true
SGDIR=$(find .build -maxdepth 3 -type d -name "symbolgraph")
NFILES=$(ls "$SGDIR"/*.symbols.json 2>/dev/null | wc -l | tr -d ' ')
if [ "$NFILES" -eq 0 ]; then echo "::error::No symbol graphs emitted"; exit 1; fi
jq -s '[.[] | .symbols[]]' "$SGDIR"/*.symbols.json \
> "$GITHUB_WORKSPACE/pr-raw.json"
working-directory: _sdk-pr

- name: Extract symbol graphs — base
if: inputs.language == 'swift'
run: |
swift package dump-symbol-graph \
--minimum-access-level public \
--skip-synthesized-members || true
SGDIR=$(find .build -maxdepth 3 -type d -name "symbolgraph")
NFILES=$(ls "$SGDIR"/*.symbols.json 2>/dev/null | wc -l | tr -d ' ')
if [ "$NFILES" -eq 0 ]; then echo "::error::No symbol graphs emitted"; exit 1; fi
jq -s '[.[] | .symbols[]]' "$SGDIR"/*.symbols.json \
> "$GITHUB_WORKSPACE/base-raw.json"
working-directory: _sdk-base

- name: Normalize symbol graphs — PR
if: inputs.language == 'swift'
run: |
npm run --silent normalize-symbolgraph -- \
"$GITHUB_WORKSPACE/pr-raw.json" "$GITHUB_WORKSPACE/_sdk-pr" \
> "$GITHUB_WORKSPACE/pr-symbols.json"
working-directory: _sdk-spec/scripts/capability-matrix

- name: Normalize symbol graphs — base
if: inputs.language == 'swift'
run: |
npm run --silent normalize-symbolgraph -- \
"$GITHUB_WORKSPACE/base-raw.json" "$GITHUB_WORKSPACE/_sdk-base" \
> "$GITHUB_WORKSPACE/base-symbols.json"
working-directory: _sdk-spec/scripts/capability-matrix

- name: Resolve parse command
if: inputs.language != 'dart'
if: inputs.language != 'swift' && inputs.language != 'dart'
id: resolve
run: |
case "${{ inputs.language }}" in
swift) echo "cmd=parse-swift" >> "$GITHUB_OUTPUT" ;;
javascript) echo "cmd=parse-ts" >> "$GITHUB_OUTPUT" ;;
javascript) echo "cmd=parse-ts" >> "$GITHUB_OUTPUT" ;;
*) echo "::error::Unsupported language '${{ inputs.language }}'. Supported values: swift, javascript, dart"; exit 1 ;;
esac

- name: Parse PR and base branch
if: inputs.language != 'dart'
- name: Parse PR branch
if: inputs.language != 'swift' && inputs.language != 'dart'
run: |
npm run --silent ${{ steps.resolve.outputs.cmd }} -- "$GITHUB_WORKSPACE/_sdk-pr" \
> "$GITHUB_WORKSPACE/pr-symbols.json"
working-directory: _sdk-spec/scripts/capability-matrix

- name: Parse base branch
if: inputs.language != 'swift' && inputs.language != 'dart'
run: |
npm run --silent ${{ steps.resolve.outputs.cmd }} -- "$GITHUB_WORKSPACE/_sdk-base" \
> "$GITHUB_WORKSPACE/base-symbols.json"
working-directory: _sdk-spec/scripts/capability-matrix
Expand Down
2 changes: 1 addition & 1 deletion scripts/capability-matrix/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"validate:online": "tsx src/cli.ts validate --online",
"validate-compliance": "tsx src/compliance-cli.ts",
"parse-ts": "tsx src/parse-ts.ts",
"parse-swift": "tsx src/parse-swift.ts",
"normalize-symbolgraph": "tsx src/normalize-symbolgraph-cli.ts",
"check-api-symbols": "tsx src/check-api-symbols.ts",
"aggregate": "tsx src/aggregate.ts",
"report": "tsx src/cli.ts report",
Expand Down
18 changes: 18 additions & 0 deletions scripts/capability-matrix/src/normalize-symbolgraph-cli.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { readFileSync } from "node:fs";
import { resolve } from "node:path";
import { normalizeSymbolGraph, type SymbolGraphSymbol } from "./normalize-symbolgraph.js";

async function main(): Promise<void> {
const [,, rawPath, sdkRootArg] = process.argv;
if (!rawPath) {
console.error("Usage: normalize-symbolgraph <merged-symbols.json> [sdk-root]");
process.exit(1);
}

const sdkRoot = sdkRootArg ? resolve(sdkRootArg) : "";
const symbols = JSON.parse(readFileSync(rawPath, "utf8")) as SymbolGraphSymbol[];
const result = normalizeSymbolGraph(symbols, sdkRoot);
console.log(JSON.stringify(result, null, 2));
}

main().catch((e) => { console.error(e); process.exit(1); });
68 changes: 68 additions & 0 deletions scripts/capability-matrix/src/normalize-symbolgraph.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { relative } from "node:path";
import type { ParsedSymbol, ParseResult } from "./ts-parser.js";
export type { ParsedSymbol, ParseResult };

export interface SymbolGraphSymbol {
kind: { identifier: string };
pathComponents: string[];
location?: { uri: string };
}

// Kind identifiers that map to ParsedSymbol kinds.
// swift.deinit and all unrecognised kinds are skipped.
const KIND_MAP: Record<string, ParsedSymbol["kind"]> = {
"swift.class": "class",
"swift.struct": "class",
"swift.enum": "class",
"swift.protocol": "class",
"swift.actor": "class",
"swift.func": "function",
"swift.func.op": "function",
"swift.method": "method",
"swift.type.method": "method",
"swift.init": "method",
"swift.subscript": "method",
"swift.type.subscript": "method",
"swift.property": "property",
"swift.type.property": "property",
"swift.enum.case": "property",
"swift.typealias": "variable",
"swift.associatedtype": "variable",
"swift.var": "variable",
};

export function normalizeSymbolGraph(
symbols: SymbolGraphSymbol[],
sdkRoot: string,
): ParseResult {
const result: ParsedSymbol[] = [];

for (const sym of symbols) {
const kind = KIND_MAP[sym.kind.identifier];
if (kind === undefined) continue;

result.push({
name: qualifiedName(sym.pathComponents),
kind,
file: resolveFile(sym.location?.uri, sdkRoot),
});
}

return { symbols: result };
}

function qualifiedName(pathComponents: string[]): string {
if (pathComponents.length === 0) return "";
const parts = pathComponents.map((part, i) => {
if (i < pathComponents.length - 1) return part;
const parenIdx = part.indexOf("(");
return parenIdx >= 0 ? part.slice(0, parenIdx) : part;
});
return parts.join(".");
}

function resolveFile(uri: string | undefined, sdkRoot: string): string {
if (!uri) return "";
const path = uri.startsWith("file://") ? uri.slice(7) : uri;
return sdkRoot ? relative(sdkRoot, path) : path;
}
19 changes: 0 additions & 19 deletions scripts/capability-matrix/src/parse-swift.ts

This file was deleted.

171 changes: 0 additions & 171 deletions scripts/capability-matrix/src/swift-parser.ts

This file was deleted.

Loading