XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

XFESystem

【Rust】XFE系统,基于Rust和汇编

公开
关注 0 Fork 0 Star 0
UTF-8
"""Collect/verify notices for the independent application graph, never legacy Cosmos.

Uses restored packages and existing pinned source/tool notices. Existing output
must remain byte-identical. No downloads, package edits, or binary copies occur.
"""
import argparse
import hashlib
import json
from pathlib import Path
import re
import xml.etree.ElementTree as ET

ROOT = Path(__file__).resolve().parents[2]
OUTPUT = ROOT / "Tools/IndependentKernel/Licenses"
OLD = ROOT / "Tools/CSharpBoot/Licenses/Collected"
APP_LOCK = ROOT / "Source/XFESystem.NativeRuntime/Application/packages.lock.json"
RUNTIME_LOCK = ROOT / "Tools/IndependentKernel/Runtime/NativeToolchain.json"
TLS_IDENTITY = ROOT / "Source/XFESystem.Tls/DependencyManifest.json"
TLS_LOCK = ROOT / "Source/XFESystem.Tls/Packages.lock.json"
SECURITY_ROOT = ROOT / "Source/XFESystem.NativeRuntime/Application/Security"
UPSTREAM = {"Acornima": "Acornima", "AngleSharp": "AngleSharp", "AngleSharp.Css": "AngleSharpCss",
            "HtmlSanitizer": "HtmlSanitizer", "Jint": "Jint", "Markdig": "Markdig", "Mono.Cecil": "MonoCecil"}


def digest(data):
    return hashlib.sha256(data).hexdigest()


def read_json(path):
    return json.loads(path.read_text(encoding="utf-8-sig"))


def save(relative, data):
    target = (OUTPUT / relative).resolve()
    if not target.is_relative_to(OUTPUT.resolve()):
        raise ValueError("Notice escaped the independent directory")
    target.parent.mkdir(parents=True, exist_ok=True)
    if target.exists():
        assert target.read_bytes() == data, f"Existing notice changed: {relative}"
    else:
        target.write_bytes(data)
    return {"Path": relative, "Sha256": digest(data), "Bytes": len(data)}


def main():
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("--NuGetCache", type=Path, default=Path.home() / ".nuget/packages")
    parser.add_argument("--ToolRoot", type=Path, default=ROOT / ".tools")
    args = parser.parse_args()
    files, packages, components = [], [], []
    source_manifest = read_json(OUTPUT / "LicenseSources.json")
    package_licenses = {item["Id"]: item for item in source_manifest.get("PackageLicenseFiles", [])}
    upstream_notices = {item["Id"]: item for item in source_manifest.get("ExternalPackageNotices", [])}
    tls_identity = read_json(TLS_IDENTITY)
    source_specs = read_json(ROOT / "Tools/CSharpBoot/LicenseSources.json")["OfficialSources"]
    official = {item["Name"]: item for item in source_specs}

    def copy(relative, source, scope, url=None, expected=None):
        data = source.read_bytes()
        if expected:
            assert digest(data) == expected, f"Source differs: {source}"
        record = save(relative, data)
        record.update(Scope=scope)
        try:
            record["SourcePath"] = str(source.relative_to(ROOT)).replace("\\", "/")
        except ValueError:
            record["SourcePath"] = "Restored NuGet cache/" + source.name
        if url:
            record["SourceUrl"] = url
        if not any(item["Path"] == relative for item in files):
            files.append(record)
        return relative

    def collect_package(identifier, specification, scope):
        version = specification["resolved"]
        directory = args.NuGetCache / identifier.lower() / version
        metadata = read_json(directory / ".nupkg.metadata")
        assert metadata["contentHash"] == specification["contentHash"], identifier + " differs from lock"
        bundle = directory / (identifier.lower() + "." + version + ".nupkg")
        nuspec = directory / (identifier.lower() + ".nuspec")
        document = ET.fromstring(nuspec.read_bytes())
        ns = {"n": document.tag[1:].split("}")[0]}
        declaration = document.find("n:metadata/n:license", ns)
        repository = document.find("n:metadata/n:repository", ns)
        prefix = "Packages/" + identifier + "-" + version
        record = {"Id": identifier, "Version": version, "Scope": scope,
                  "Classification": "SameAuthorSeparateProject" if identifier.startswith("XFEExtension") else "External",
                  "LockedContentHash": specification["contentHash"], "NuGetContentHash": metadata["contentHash"],
                  "NupkgSha256": digest(bundle.read_bytes()),
                  "Repository": dict(repository.attrib) if repository is not None else {},
                  "LicenseDeclaration": {"Type": declaration.attrib.get("type"), "Value": declaration.text} if declaration is not None else {},
                  "LicenseFiles": []}
        copy(prefix + "/Metadata.nuspec", nuspec, scope)
        if identifier in package_licenses:
            spec = package_licenses[identifier]
            assert version == spec["Version"], "Pinned license package version differs"
            assert identifier == tls_identity["Package"] and version == tls_identity["Version"], "TLS dependency version differs"
            assert specification["contentHash"] == tls_identity["NuGetContentSha512"], "TLS content lock differs"
            assert record["NupkgSha256"] == tls_identity["PackageSha256"], "TLS package archive differs"
            assert record["Repository"].get("commit") == tls_identity["SourceCommitFromNuGetMetadata"], "TLS repository commit differs"
            assert record["LicenseDeclaration"] == {"Type": "expression", "Value": tls_identity["License"]}, "TLS license declaration differs"
            assert read_json(TLS_LOCK)["dependencies"]["net10.0"][identifier]["contentHash"] == specification["contentHash"], "TLS project lock differs"
            record["LicenseFiles"].append(copy(prefix + "/" + spec["File"], directory / spec["File"], scope,
                                                 tls_identity["PackageUrl"], spec["Sha256"]))
            record["DependencyManifestSha256"] = digest(TLS_IDENTITY.read_bytes())
        elif identifier in upstream_notices:
            spec=upstream_notices[identifier]
            assert version==spec['Version'] and record['Repository'].get('commit')==spec['RepositoryCommit'], 'Storage package identity differs'
            assert record['LicenseDeclaration']=={'Type':'expression','Value':spec['License']}, 'Storage package license differs'
            for item in spec['Notices']:
                record['LicenseFiles'].append(copy(item['Path'],OUTPUT/item['Path'],scope,item['SourceUrl'],item['Sha256']))
            if spec.get('NoticeProvenance'):record['NoticeProvenance']=spec['NoticeProvenance']
        elif declaration is not None and declaration.attrib.get("type") == "file":
            record["LicenseFiles"].append(copy(prefix + "/" + declaration.text, directory / declaration.text, scope))
        elif identifier in UPSTREAM:
            key = UPSTREAM[identifier]
            spec = official[key]
            record["LicenseFiles"].append(copy("Upstream/" + key + "/License.txt", OLD / "Upstream" / key / "License.txt",
                                               scope, spec["Url"], spec["Sha256"]))
        else:
            raise ValueError("No complete license for " + identifier)
        packages.append(record)

    app = read_json(APP_LOCK)
    assert list(app["dependencies"]) == ["net10.0"], "Unexpected application target"
    for identifier, specification in app["dependencies"]["net10.0"].items():
        if specification["type"] == "Project":
            continue
        assert not identifier.lower().startswith("cosmos"), "Cosmos entered the independent app"
        collect_package(identifier, specification, "IndependentApplicationGraph")
    patch_lock = ROOT / "Tools/JintCleanupPatch/Packages.lock.json"
    cecil = read_json(patch_lock)["dependencies"]["net10.0"]["Mono.Cecil"]
    entropy_patch_lock = ROOT / "Tools/TlsEntropyPatch/Packages.lock.json"
    entropy_cecil = read_json(entropy_patch_lock)["dependencies"]["net10.0"]["Mono.Cecil"]
    assert entropy_cecil == cecil, "Private patch tools must share the pinned Mono.Cecil identity"
    collect_package("Mono.Cecil", cecil, "HostJintAndTlsEntropyPrivateOutputPatchTools")

    runtime = read_json(RUNTIME_LOCK)
    for identifier, identity in runtime["Packages"].items():
        directory = args.NuGetCache / identifier / runtime["Version"]
        bundle = directory / (identifier + "." + runtime["Version"] + ".nupkg")
        assert digest(bundle.read_bytes()) == identity["NupkgSha256"], identifier + " archive differs"
        prefix = "Packages/" + identifier + "-" + runtime["Version"]
        scope = "GuestNativeAotRuntimeAndBcl" if "nativeaot" in identifier else "HostOfficialNativeAotCompiler"
        notices = []
        for name in ("LICENSE.TXT", "THIRD-PARTY-NOTICES.TXT", identifier + ".nuspec"):
            if (directory / name).exists():
                expected = identity["FilesSha256"].get(name)
                assert expected, name + " not bound to runtime lock"
                notices.append(copy(prefix + "/" + name, directory / name, scope, expected=expected))
        assert any(item.endswith("THIRD-PARTY-NOTICES.TXT") for item in notices)
        if not any(item.endswith("LICENSE.TXT") for item in notices):
            # The official compiler package declares MIT by expression and has
            # no license file; provide the original official runtime MIT text.
            notices.append(copy(prefix + "/LICENSE.TXT", args.NuGetCache / "microsoft.netcore.app.runtime.nativeaot.linux-x64" /
                                runtime["Version"] / "LICENSE.TXT", scope,
                                expected=runtime["Packages"]["microsoft.netcore.app.runtime.nativeaot.linux-x64"]["FilesSha256"]["LICENSE.TXT"]))
        packages.append({"Id": identifier, "Version": runtime["Version"], "Scope": scope,
                         "Classification": "External", "NupkgSha256": identity["NupkgSha256"],
                         "LicenseFiles": notices})

    roots_manifest_path = SECURITY_ROOT / "Manifest.json"
    roots = read_json(roots_manifest_path)
    root_bytes = (SECURITY_ROOT / "MozillaRoots.pem").read_bytes()
    assert roots["Kind"] == "ExplicitMozillaRootSnapshot" and roots["License"] == "MPL-2.0", "Unexpected root source/license"
    assert roots["Sha256"] == digest(root_bytes) and roots["Bytes"] == len(root_bytes), "Trusted root snapshot differs"
    assert root_bytes.count(b"-----BEGIN CERTIFICATE-----") == roots["RootCount"] == root_bytes.count(b"-----END CERTIFICATE-----"), "Trusted root certificate count differs"
    assert roots["HostCertificateStoreUsed"] is False, "Host roots entered the guest snapshot"
    entries = source_manifest["Files"]
    for item in entries:
        source = (args.ToolRoot / item["ToolPath"]) if "ToolPath" in item else ROOT / item["SourcePath"]
        path = copy(item["Destination"], source, item["Scope"], item.get("SourceUrl"), item.get("Sha256"))
        components.append({"Name": item["Name"], "Version": item.get("Version"), "Scope": item["Scope"],
                           "Classification": item.get("Classification", "External"), "LicenseFile": path,
                           "SourceIdentity": item.get("SourceIdentity")})
    # Preserve the complete protocol notice from the actual Limine binary's
    # third-party notice, without reconstructing or abbreviating license text.
    limine = (args.ToolRoot / "limine/3RDPARTY.md").read_text(encoding="utf-8")
    protocol = re.search(r"### Limine Boot Protocol\s+.*?```\s*\n(.*?)\n```", limine, re.S)
    assert protocol, "Actual Limine archive lacks its protocol license"
    protocol_file = "Boot/LimineProtocol-0BSD.txt"
    files.append(save(protocol_file, (protocol.group(1) + "\n").encode("utf-8")))
    files[-1].update(Scope="GuestBootProtocolDeclarations", SourcePath=".tools/limine/3RDPARTY.md", Extraction="Exact full Limine Boot Protocol license block")
    components.append({"Name": "Limine Boot Protocol", "Version": "base revision 6", "Scope": "GuestBootProtocolDeclarations", "Classification": "External", "LicenseFile": protocol_file})
    manifest = {"Schema": "XFESystem.IndependentLicenses.V1", "ApplicationLockSha256": digest(APP_LOCK.read_bytes()),
                "RuntimeToolchainLockSha256": digest(RUNTIME_LOCK.read_bytes()),
                "JintPatchToolLockSha256": digest(patch_lock.read_bytes()),
                "TlsEntropyPatchToolLockSha256": digest(entropy_patch_lock.read_bytes()),
                "TlsDependencyManifestSha256": digest(TLS_IDENTITY.read_bytes()),
                "TlsProjectLockSha256": digest(TLS_LOCK.read_bytes()),
                "TrustedRootManifestSha256": digest(roots_manifest_path.read_bytes()),
                "TrustedRootSnapshotSha256": digest(root_bytes), "TrustedRootCount": roots["RootCount"],
                "SourcesSha256": digest((OUTPUT / "LicenseSources.json").read_bytes()),
                "Scope": "Independent NativeApplication graph, native runtime, actual boot image components and explicitly separated host tools",
                "ContainsCosmos": False, "ExcludesLegacyCollectedGraph": True, "GuestAcceptancePassed": False,
                "NativeTlsAcceptancePassed": False, "FullReleaseQualified": False,
                "GuestAcceptanceNote": "License completeness and hash checks do not prove guest execution; use an image-bound acceptance receipt.",
                "PackageCount": len(packages), "Packages": packages, "Components": components, "Files": sorted(files, key=lambda item: item["Path"])}
    content = (json.dumps(manifest, ensure_ascii=False, indent=2) + "\n").encode("utf-8")
    target = OUTPUT / "Manifest.json"
    # This generated inventory may update as the lock graph changes. License
    # bodies themselves can only be added or verified byte for byte.
    target.write_bytes(content)
    print(json.dumps({"PackageCount": len(packages), "NoticeFiles": len(files), "ContainsCosmos": False,
                      "Manifest": str(target), "Passed": True}))


if __name__ == "__main__":
    main()