Fix package pattern in json schema configuration registry (#1429)

This commit is contained in:
WangWeiLin-MV 2024-07-26 04:40:53 +08:00 коммит произвёл GitHub
Родитель 46805d559e
Коммит 998c893164
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
12 изменённых файлов: 277 добавлений и 34 удалений

24
.github/workflows/pr.yaml поставляемый
Просмотреть файл

@ -70,3 +70,27 @@ jobs:
with: with:
name: format.patch name: format.patch
path: out/format.patch path: out/format.patch
json-schema:
runs-on: windows-2022
timeout-minutes: 10
steps:
- uses: actions/checkout@v3
- name: Get microsoft/vcpkg pinned sha into VCPKG_SHA
id: vcpkg_sha
shell: pwsh
run: |
"VCPKG_SHA="+(Get-Content vcpkg-init/vcpkg-scripts-sha.txt -Raw).Trim() >> $env:GITHUB_OUTPUT
- name: Checkout microsoft/vcpkg for end-to-end tests
uses: actions/checkout@v3
with:
fetch-depth: 0
path: ${{ github.workspace }}/vcpkg-root
repository: microsoft/vcpkg
ref: ${{ steps.vcpkg_sha.outputs.VCPKG_SHA }}
- name: Run vcpkg json-schema end-to-end tests
shell: pwsh
run: |
${{ github.workspace }}/azure-pipelines/json-schema-tests.ps1
env:
VCPKG_ROOT: ${{ github.workspace }}/vcpkg-root

Просмотреть файл

@ -0,0 +1,117 @@
function GenPackageJson {
param(
[Parameter(Mandatory)][bool]$Expected,
[Parameter(Mandatory)][AllowEmptyString()][string[]]$PackageArray,
[string]$Baseline = '0' * 40
)
return @(
$Expected,
@{
registries = @(
[pscustomobject]@{
kind = 'git'
repository = ''
baseline = $Baseline
packages = $PackageArray
}
)
}
)
}
# See src/vcpkg-test/registries.cpp "check valid package patterns"
@{
$VcpkgJsonSchema.Configuration =
@{
'packages: ["a"]' = GenPackageJson $true @('a')
'packages: empty' = GenPackageJson $false [string[]]@('')
'packages: blank' = GenPackageJson $false @(' ')
'packages: baseline 39' = GenPackageJson $false @('a') ('0' * 39)
'packages: hashtag ["*"]' = GenPackageJson $true @('*')
'packages: hashtag ["a*"]' = GenPackageJson $true @('a*')
'packages: hashtag ["*a"]' = GenPackageJson $false @('*a')
'packages: hashtag ["b-*"]' = GenPackageJson $true @('b-*')
'packages: hashtag ["c-d-*"]' = GenPackageJson $true @('c-d-*')
'packages: hashtag dup ["a**"]' = GenPackageJson $false @('a**')
'packages: hashtag dup ["b-**"]' = GenPackageJson $false @('b-**')
'packages: hashtag dup ["c--*"]' = GenPackageJson $false @('c--*')
'packages: hashtag dup ["d-*-*"]' = GenPackageJson $false @('d-*-*')
'packages: hashtag mid ["a*b"]' = GenPackageJson $false @('a*b')
'packages: mix array ["a*","b"]' = GenPackageJson $true @('a*', 'b')
'packages: symbols ["a+"]' = GenPackageJson $false @('a+')
'packages: symbols ["a?"]' = GenPackageJson $false @('a?')
}
$VcpkgJsonSchema.Port =
@{
# test identifiers
'port-name: "co"' = $true, @{name = 'co' }
'port-name: "rapidjson"' = $true, @{name = 'rapidjson' }
'port-name: "boost-tuple"' = $true, @{name = 'boost-tuple' }
'port-name: "vcpkg-boost-helper"' = $true, @{name = 'vcpkg-boost-helper' }
'port-name: "lpt"' = $true, @{name = 'lpt' }
'port-name: "com"' = $true, @{name = 'com' }
# reject invalid characters
'port-name: ""' = $false, @{name = '' }
'port-name: " "' = $false, @{name = ' ' }
'port-name: "boost_tuple"' = $false, @{name = 'boost_tuple' }
'port-name: "boost.' = $false, @{name = 'boost.' }
'port-name: "boost.tuple"' = $false, @{name = 'boost.tuple' }
'port-name: "boost@1"' = $false, @{name = 'boost@1' }
'port-name: "boost#1"' = $false, @{name = 'boost#1' }
'port-name: "boost:x64-windows"' = $false, @{name = 'boost:x64-windows' }
# accept legacy
'port-name: "all_modules"' = $false, @{name = 'all_modules' } # removed in json-schema
# reject reserved keywords
'port-name: "prn"' = $false, @{name = 'prn' }
'port-name: "aux"' = $false, @{name = 'aux' }
'port-name: "nul"' = $false, @{name = 'nul' }
'port-name: "con"' = $false, @{name = 'con' }
'port-name: "core"' = $false, @{name = 'core' }
'port-name: "default"' = $false, @{name = 'default' }
'port-name: "lpt0"' = $false, @{name = 'lpt0' }
'port-name: "lpt9"' = $false, @{name = 'lpt9' }
'port-name: "com0"' = $false, @{name = 'com0' }
'port-name: "com9"' = $false, @{name = 'com9' }
# reject incomplete segments
'port-name: "-a"' = $false, @{name = '-a' }
'port-name: "a-"' = $false, @{name = 'a-' }
'port-name: "a--"' = $false, @{name = 'a--' }
'port-name: "---"' = $false, @{name = '---' }
}
}.GetEnumerator()
| ForEach-Object {
@{
SchemaName = $_.Key
JsonCases = $_.Value.GetEnumerator() | ForEach-Object {
@{
Title = $_.Key
Expected = $_.Value[0]
Json = ConvertTo-Json -InputObject $_.Value[1] -Depth 5 -Compress
}
}
}
}
| ForEach-Object {
$_SchemaName = $_.SchemaName
$_SchemaPath = Join-Path $WorkingRoot $_SchemaName
$_.JsonCases | ForEach-Object {
$_Title = $_.Title
$_Expected = $_.Expected
$_Actual = Test-Json -ea:0 -Json $_.Json -SchemaFile $_SchemaPath
$_Result = $_.Expected -eq $_Actual ? 'Pass':'Fail'
if ($_Result -eq 'Fail') {
throw "$_SchemaName validate fail with $_Title, expected $_Expected"
}
[pscustomobject]@{
SchemaName = $_SchemaName
Title = $_Title
Expected = $_Expected
Actual = $_Actual
Result = $_Result
}
}
}
| Sort-Object SchemaName, Title
| Format-Table

Просмотреть файл

@ -0,0 +1,19 @@
$VcpkgPortSchemaPath = Join-Path $WorkingRoot $VcpkgJsonSchema.Port
Get-ChildItem -Directory -Path (Join-Path $VcpkgRoot 'ports')
| ForEach-Object -Parallel {
$PortName = $_.Name
$PortDir = $_.FullName
$PortJsonPath = Join-Path $PortDir 'vcpkg.json'
$Schema = $using:VcpkgPortSchemaPath
$_Actual = Test-Json -ea:0 -LiteralPath $PortJsonPath -SchemaFile $Schema
[pscustomobject]@{
PortName = $PortName
Actual = $_Actual
}
}
| ForEach-Object { Write-Host $_; $_ }
| Where-Object Actual -EQ $false
| ForEach-Object { Write-Error $_; $_ }

Просмотреть файл

@ -0,0 +1,52 @@
[CmdletBinding()]
Param(
[ValidateNotNullOrEmpty()]
[string]$WorkingRoot = 'work',
[Parameter(Mandatory = $false)]
[string]$VcpkgRoot
)
$ErrorActionPreference = 'Stop'
if ($PSVersionTable.PSVersion.Major -lt 7) {
Write-Error "json-schema e2e tests must use pwsh"
}
$VcpkgSrcDir = $PWD
$WorkingRoot = (New-Item -Path $WorkingRoot -ItemType Directory -Force).FullName
$VcpkgRoot = & {
if (-not [string]::IsNullOrWhitespace($VcpkgRoot)) {
return $VcpkgRoot
}
if ([string]::IsNullOrWhitespace($env:VCPKG_ROOT)) {
throw "Could not determine VCPKG_ROOT"
}
return $env:VCPKG_ROOT
} | Get-Item | Select-Object -ExpandProperty FullName
$VcpkgJsonSchema = @{
Artifact = 'artifact.schema.json'
Configuration = 'vcpkg-configuration.schema.json'
Definitions = 'vcpkg-schema-definitions.schema.json'
Port = 'vcpkg.schema.json'
}
# remove `$id` in schema for error 'Test-Json: Cannot parse the JSON schema.'
$VcpkgJsonSchema.Values
| ForEach-Object {
Copy-Item -Path (Join-path $VcpkgSrcDir 'docs' $_) -Destination $WorkingRoot
Join-Path $WorkingRoot $_
}
| ForEach-Object {
(Get-Content -Raw -Path $_) -replace '(?s)\n "\$id".+?\n', "`n"
| Set-Content -NoNewline -Path $_
}
| Out-Null
Get-ChildItem $PSScriptRoot/json-schema-tests-dir/*.test.ps1
| ForEach-Object {
Write-Host "Running test $_"
& $_.FullName
}

Просмотреть файл

@ -1,4 +1,5 @@
{ {
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "https://raw.githubusercontent.com/microsoft/vcpkg-tool/main/docs/artifact.schema.json", "$id": "https://raw.githubusercontent.com/microsoft/vcpkg-tool/main/docs/artifact.schema.json",
"type": "object", "type": "object",
"properties": { "properties": {

Просмотреть файл

@ -1,4 +1,5 @@
{ {
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "https://raw.githubusercontent.com/microsoft/vcpkg-tool/main/docs/vcpkg-configuration.schema.json", "$id": "https://raw.githubusercontent.com/microsoft/vcpkg-tool/main/docs/vcpkg-configuration.schema.json",
"type": "object", "type": "object",
"properties": { "properties": {

Просмотреть файл

@ -1,5 +1,5 @@
{ {
"$schema": "http://json-schema.org/draft-07/schema", "$schema": "http://json-schema.org/draft-07/schema#",
"$id": "https://raw.githubusercontent.com/microsoft/vcpkg-tool/main/docs/vcpkg-schema-definitions.schema.json", "$id": "https://raw.githubusercontent.com/microsoft/vcpkg-tool/main/docs/vcpkg-schema-definitions.schema.json",
"definitions": { "definitions": {
"artifact-references": { "artifact-references": {
@ -194,7 +194,7 @@
"description": "A port dependency fetchable by vcpkg.", "description": "A port dependency fetchable by vcpkg.",
"oneOf": [ "oneOf": [
{ {
"$ref": "#/definitions/port-name" "$ref": "#/definitions/identifier"
}, },
{ {
"$ref": "#/definitions/dependency-object" "$ref": "#/definitions/dependency-object"
@ -206,7 +206,7 @@
"type": "object", "type": "object",
"properties": { "properties": {
"name": { "name": {
"$ref": "#/definitions/port-name" "$ref": "#/definitions/identifier"
}, },
"features": { "features": {
"type": "array", "type": "array",
@ -731,8 +731,7 @@
}, },
{ {
"not": { "not": {
"description": "Identifiers must not be a Windows filesystem or vcpkg reserved name.", "$ref": "#/definitions/reserved-name"
"pattern": "^(prn|aux|nul|con|lpt[1-9]|com[1-9]|core|default)$"
} }
} }
] ]
@ -759,9 +758,9 @@
"type": "object", "type": "object",
"$comment": "The regexes below have (#\\d+)? added to the end as overrides allow putting the port-version inline", "$comment": "The regexes below have (#\\d+)? added to the end as overrides allow putting the port-version inline",
"properties": { "properties": {
"name": { "name": {
"$ref": "#/definitions/identifier" "$ref": "#/definitions/identifier"
}, },
"version-string": { "version-string": {
"description": "Deprecated in favor of 'version' in overrides. Text used to identify an arbitrary version", "description": "Deprecated in favor of 'version' in overrides. Text used to identify an arbitrary version",
"type": "string", "type": "string",
@ -800,18 +799,18 @@
} }
] ]
}, },
"port-name": { "package-pattern": {
"type": "string", "type": "string",
"description": "Name of a package.", "description": "A pattern to match package name.",
"allOf": [ "allOf": [
{ {
"description": "Package name must be a dot-separated list of valid identifiers", "$comment": "https://learn.microsoft.com/en-us/vcpkg/reference/vcpkg-configuration-json#registry-packages",
"pattern": "^[a-z0-9]+(-[a-z0-9]+)*(\\.[a-z0-9]+(-[a-z0-9]+)*)*$" "description": "Package pattern may contain only lowercase letters, digits, and -, with an optional trailing *",
"pattern": "^([a-z0-9]+-)*([a-z0-9]+[*]?|[*])$"
}, },
{ {
"not": { "not": {
"description": "Identifiers must not be a Windows filesystem or vcpkg reserved name.", "$ref": "#/definitions/reserved-name"
"pattern": "(^|\\.)(prn|aux|nul|con|lpt[1-9]|com[1-9]|core|default)(\\.|$)"
} }
} }
] ]
@ -860,7 +859,7 @@
"packages": { "packages": {
"type": "array", "type": "array",
"items": { "items": {
"$ref": "#/definitions/port-name" "$ref": "#/definitions/package-pattern"
} }
} }
}, },
@ -887,6 +886,21 @@
} }
] ]
}, },
"reserved-name": {
"description": "Vcpkg reserved identifier names for lowercase.",
"type": "string",
"anyOf": [
{
"description": "Vcpkg reserved names.",
"pattern": "^(core|default)$"
},
{
"$comment": "https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file#naming-conventions",
"description": "A relaxed pattern of Win32 filesystem reserved names.",
"pattern": "^(con|prn|aux|nul|com[0-9]|lpt[0-9])(\\.[^.]+)*$"
}
]
},
"semantic-version": { "semantic-version": {
"description": "A semantic version string. See https://semver.org/", "description": "A semantic version string. See https://semver.org/",
"type": "string", "type": "string",
@ -915,7 +929,7 @@
{ {
"not": { "not": {
"description": "Identifiers and parts delimited by slashes must not be a Windows filesystem or vcpkg reserved name.", "description": "Identifiers and parts delimited by slashes must not be a Windows filesystem or vcpkg reserved name.",
"pattern": "(^|/)(prn|aux|nul|con|lpt[1-9]|com[1-9]|core|default)(/|$)" "pattern": "(^|/)(prn|aux|nul|con|lpt[0-9]|com[0-9]|core|default)(/|$)"
} }
} }
] ]

Просмотреть файл

@ -1,5 +1,5 @@
{ {
"$schema": "http://json-schema.org/draft-07/schema", "$schema": "http://json-schema.org/draft-07/schema#",
"$id": "https://raw.githubusercontent.com/microsoft/vcpkg-tool/main/docs/vcpkg.schema.json", "$id": "https://raw.githubusercontent.com/microsoft/vcpkg-tool/main/docs/vcpkg.schema.json",
"type": "object", "type": "object",
"allOf": [ "allOf": [
@ -7,7 +7,7 @@
"properties": { "properties": {
"name": { "name": {
"description": "The name of the top-level package", "description": "The name of the top-level package",
"$ref": "vcpkg-schema-definitions.schema.json#/definitions/port-name" "$ref": "vcpkg-schema-definitions.schema.json#/definitions/identifier"
}, },
"version-string": { "version-string": {
"description": "Text used to identify an arbitrary version", "description": "Text used to identify an arbitrary version",

Просмотреть файл

@ -335,7 +335,7 @@ namespace vcpkg::Json
struct IdentifierDeserializer final : Json::IDeserializer<std::string> struct IdentifierDeserializer final : Json::IDeserializer<std::string>
{ {
virtual LocalizedString type_name() const override; virtual LocalizedString type_name() const override;
// [a-z0-9]+(-[a-z0-9]+)*, plus not any of {prn, aux, nul, con, lpt[1-9], com[1-9], core, default} // [a-z0-9]+(-[a-z0-9]+)*, plus not any of {prn, aux, nul, con, lpt[0-9], com[0-9], core, default}
static bool is_ident(StringView sv); static bool is_ident(StringView sv);
virtual Optional<std::string> visit_string(Json::Reader&, StringView sv) const override; virtual Optional<std::string> visit_string(Json::Reader&, StringView sv) const override;
static const IdentifierDeserializer instance; static const IdentifierDeserializer instance;

Просмотреть файл

@ -121,11 +121,15 @@ TEST_CASE ("check valid package patterns", "[registries]")
CHECK(ID::is_ident("rapidjson")); CHECK(ID::is_ident("rapidjson"));
CHECK(ID::is_ident("boost-tuple")); CHECK(ID::is_ident("boost-tuple"));
CHECK(ID::is_ident("vcpkg-boost-helper")); CHECK(ID::is_ident("vcpkg-boost-helper"));
CHECK(ID::is_ident("lpt"));
CHECK(ID::is_ident("com"));
// reject invalid characters // reject invalid characters
CHECK(!ID::is_ident("")); CHECK(!ID::is_ident(""));
CHECK(!ID::is_ident(" "));
CHECK(!ID::is_ident("boost_tuple")); CHECK(!ID::is_ident("boost_tuple"));
CHECK(!ID::is_ident("boost.tuple")); CHECK(!ID::is_ident("boost.tuple"));
CHECK(!ID::is_ident("boost."));
CHECK(!ID::is_ident("boost@1")); CHECK(!ID::is_ident("boost@1"));
CHECK(!ID::is_ident("boost#1")); CHECK(!ID::is_ident("boost#1"));
CHECK(!ID::is_ident("boost:x64-windows")); CHECK(!ID::is_ident("boost:x64-windows"));
@ -140,8 +144,10 @@ TEST_CASE ("check valid package patterns", "[registries]")
CHECK(!ID::is_ident("con")); CHECK(!ID::is_ident("con"));
CHECK(!ID::is_ident("core")); CHECK(!ID::is_ident("core"));
CHECK(!ID::is_ident("default")); CHECK(!ID::is_ident("default"));
CHECK(!ID::is_ident("lpt1")); CHECK(!ID::is_ident("lpt0"));
CHECK(!ID::is_ident("com1")); CHECK(!ID::is_ident("lpt9"));
CHECK(!ID::is_ident("com0"));
CHECK(!ID::is_ident("com9"));
// reject incomplete segments // reject incomplete segments
CHECK(!ID::is_ident("-a")); CHECK(!ID::is_ident("-a"));
@ -154,11 +160,17 @@ TEST_CASE ("check valid package patterns", "[registries]")
CHECK(is_package_pattern("b*")); CHECK(is_package_pattern("b*"));
CHECK(is_package_pattern("boost*")); CHECK(is_package_pattern("boost*"));
CHECK(is_package_pattern("boost-*")); CHECK(is_package_pattern("boost-*"));
CHECK(is_package_pattern("boost-multi-*"));
// reject invalid patterns // reject invalid patterns
CHECK(!is_package_pattern(""));
CHECK(!is_package_pattern(" "));
CHECK(!is_package_pattern("*a")); CHECK(!is_package_pattern("*a"));
CHECK(!is_package_pattern("a*a")); CHECK(!is_package_pattern("a*a"));
CHECK(!is_package_pattern("a**")); CHECK(!is_package_pattern("a**"));
CHECK(!is_package_pattern("a-**"));
CHECK(!is_package_pattern("a--*"));
CHECK(!is_package_pattern("a-*-*"));
CHECK(!is_package_pattern("a+")); CHECK(!is_package_pattern("a+"));
CHECK(!is_package_pattern("a?")); CHECK(!is_package_pattern("a?"));
} }

Просмотреть файл

@ -1080,12 +1080,13 @@ namespace vcpkg::Json
if (sv.size() < 5) if (sv.size() < 5)
{ {
// see https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file#naming-conventions
if (sv == "prn" || sv == "aux" || sv == "nul" || sv == "con" || sv == FeatureNameCore) if (sv == "prn" || sv == "aux" || sv == "nul" || sv == "con" || sv == FeatureNameCore)
{ {
return false; // we're a reserved identifier return false; // we're a reserved identifier
} }
if (sv.size() == 4 && (Strings::starts_with(sv, "lpt") || Strings::starts_with(sv, "com")) && if (sv.size() == 4 && (Strings::starts_with(sv, "lpt") || Strings::starts_with(sv, "com")) &&
sv[3] >= '1' && sv[3] <= '9') sv[3] >= '0' && sv[3] <= '9')
{ {
return false; // we're a reserved identifier return false; // we're a reserved identifier
} }

Просмотреть файл

@ -1022,32 +1022,34 @@ namespace vcpkg
return true; return true;
} }
/*if (sv == "*") // ([a-z0-9]+-)*([a-z0-9]+[*]?|[*])
{
return true;
}*/
// ([a-z0-9]+(-[a-z0-9]+)*)(\*?)
auto cur = sv.begin(); auto cur = sv.begin();
const auto last = sv.end(); const auto last = sv.end();
// each iteration of this loop matches either
// ([a-z0-9]+-)
// or the last
// ([a-z0-9]+[*]?|[*])
for (;;) for (;;)
{ {
// [a-z0-9]+
if (cur == last) if (cur == last)
{ {
return false; return false;
} }
// this if checks for the first matched character of [a-z0-9]+
if (!ParserBase::is_lower_digit(*cur)) if (!ParserBase::is_lower_digit(*cur))
{ {
if (*cur != '*') // [a-z0-9]+ didn't match anything, so we must be matching
// the last [*]
if (*cur == '*')
{ {
return false; return ++cur == last;
} }
return ++cur == last; return false;
} }
// match the rest of the [a-z0-9]+
do do
{ {
++cur; ++cur;
@ -1060,11 +1062,11 @@ namespace vcpkg
switch (*cur) switch (*cur)
{ {
case '-': case '-':
// repeat outer [a-z0-9]+ again to match -[a-z0-9]+ // this loop iteration matched [a-z0-9]+- once
++cur; ++cur;
continue; continue;
case '*': case '*':
// match last optional * // this loop matched [a-z0-9]+[*]? (and [*] was present)
++cur; ++cur;
return cur == last; return cur == last;
default: return false; default: return false;