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
2 changes: 1 addition & 1 deletion ci/platform_boundary_research.tsv
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ crates/fbuild-core/src/platform/windows/usb_pnp.rs 35 native_path windows_sys::
crates/fbuild-core/src/platform/windows/usb_pnp.rs 39 native_path windows_sys:: process host_mechanic
crates/fbuild-core/src/platform/windows/usb_pnp.rs 40 native_path windows_sys:: process host_mechanic
crates/fbuild-core/tests/platformio_package_resolution.rs 5 attr_cfg #[cfg(windows)] host host_mechanic
crates/fbuild-core/tests/platformio_package_resolution.rs 76 attr_cfg #[cfg(windows)] host host_mechanic
crates/fbuild-core/tests/platformio_package_resolution.rs 106 attr_cfg #[cfg(windows)] host host_mechanic
crates/fbuild-daemon/Cargo.toml 68 native_dependency libc process host_mechanic
crates/fbuild-daemon/src/handlers/emulator/tests_npm_cache.rs 146 attr_cfg #[cfg(windows)] host_executable host_artifact_policy
crates/fbuild-daemon/src/handlers/emulator/tests_process.rs 9 attr_cfg #[cfg(windows)] host_executable host_artifact_policy
Expand Down
51 changes: 48 additions & 3 deletions crates/fbuild-core/src/platformio_package.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,10 @@ pub struct RegistrySpec {
pub owner: Option<String>,
pub name: String,
pub requirement: Option<String>,
/// Explicit PlatformIO registry category from an `owner/type/name` or
/// `type/owner/name` path. Framework/toolchain aliases both use `tool`.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub registry_type: Option<String>,
}

/// A source accepted by PlatformIO's package-spec grammar. An archive URL or
Expand Down Expand Up @@ -286,9 +290,22 @@ pub fn parse_package_spec(raw: &str) -> Result<PackageSpec> {
}

fn parse_registry(name: &str, requirement: Option<&str>) -> Result<RegistrySpec> {
let (owner, name) = match name.split_once('/') {
Some((owner, name)) => (Some(owner), name),
None => (None, name),
let parts = name.split('/').collect::<Vec<_>>();
let (owner, name, registry_type) = match parts.as_slice() {
[name] => (None, *name, None),
[owner, name] => (Some(*owner), *name, None),
[first, middle, name] => {
let first_type = canonical_registry_type(first);
let middle_type = canonical_registry_type(middle);
match (first_type, middle_type) {
(Some(kind), None) => (Some(*middle), *name, Some(kind)),
// Owner-first is canonical when both segments look like types:
// registry owner names are not reserved words.
(_, Some(kind)) => (Some(*first), *name, Some(kind)),
_ => return Err(ResolutionError::InvalidSpec(name.to_string())),
}
}
_ => return Err(ResolutionError::InvalidSpec(name.to_string())),
};
if !valid_segment(name) || owner.is_some_and(|o| !valid_segment(o)) {
return Err(ResolutionError::InvalidSpec(name.into()));
Expand All @@ -300,9 +317,19 @@ fn parse_registry(name: &str, requirement: Option<&str>) -> Result<RegistrySpec>
owner: owner.map(str::to_string),
name: name.into(),
requirement: requirement.map(str::to_string),
registry_type: registry_type.map(str::to_string),
})
}

fn canonical_registry_type(raw: &str) -> Option<&'static str> {
match raw {
"platform" => Some("platform"),
"tool" | "toolchain" | "framework" | "uploader" | "debugger" => Some("tool"),
"library" => Some("library"),
_ => None,
}
}

fn valid_segment(value: &str) -> bool {
!matches!(value, "" | "." | "..")
&& value
Expand Down Expand Up @@ -439,6 +466,7 @@ fn parse_direct_source(value: &str) -> Result<PackageSource> {

/// Construct the metadata endpoint for an owner-qualified registry name.
pub fn registry_api_url(kind: PackageKind, spec: &RegistrySpec) -> Result<String> {
validate_registry_type(kind, spec)?;
let owner = spec
.owner
.as_deref()
Expand All @@ -456,6 +484,22 @@ pub fn registry_api_url(kind: PackageKind, spec: &RegistrySpec) -> Result<String
))
}

fn validate_registry_type(kind: PackageKind, spec: &RegistrySpec) -> Result<()> {
if spec
.registry_type
.as_deref()
.is_some_and(|declared| declared != kind.registry_type())
{
return Err(ResolutionError::InvalidSpec(format!(
"{} is a {} package, not a {} package",
spec.name,
spec.registry_type.as_deref().unwrap_or_default(),
kind.registry_type()
)));
}
Ok(())
}

#[derive(Deserialize)]
struct RegistryResponse {
name: String,
Expand Down Expand Up @@ -534,6 +578,7 @@ pub fn resolve_registry_json(
system: &str,
metadata_json: &str,
) -> Result<ResolvedPayload> {
validate_registry_type(kind, spec)?;
if let Some(requirement) = &spec.requirement {
validate_requirement(requirement)?;
}
Expand Down
30 changes: 30 additions & 0 deletions crates/fbuild-core/tests/platformio_package_resolution.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,36 @@ fn parses_generic_registry_aliases_without_platform_dispatch() {
);
}

#[test]
fn explicit_registry_type_paths_normalize_without_native_dispatch() {
let owner_first =
parse_package_spec("platformio/tool/toolchain-gccarmnoneeabi@1.90201.191206").unwrap();
let type_first =
parse_package_spec("tool/platformio/toolchain-gccarmnoneeabi@1.90201.191206").unwrap();
let ordinary =
parse_package_spec("platformio/toolchain-gccarmnoneeabi@1.90201.191206").unwrap();
assert_eq!(owner_first.registry(), type_first.registry());
assert_eq!(
owner_first.registry().unwrap().name,
ordinary.registry().unwrap().name
);
assert_eq!(
registry_api_url(PackageKind::Tool, owner_first.registry().unwrap()).unwrap(),
"https://api.registry.platformio.org/v3/packages/platformio/tool/toolchain-gccarmnoneeabi"
);
assert!(registry_api_url(PackageKind::Platform, owner_first.registry().unwrap()).is_err());
assert!(parse_package_spec("platformio/unknown/toolchain-gccarmnoneeabi@1.0.0").is_err());
let type_named_owner = parse_package_spec("tool/library/example@1.0.0").unwrap();
assert_eq!(
type_named_owner.registry().unwrap().owner.as_deref(),
Some("tool")
);
assert_eq!(
registry_api_url(PackageKind::Library, type_named_owner.registry().unwrap()).unwrap(),
"https://api.registry.platformio.org/v3/packages/tool/library/example"
);
}

#[test]
fn classifies_archive_repository_and_local_payload_paths() {
let archive = parse_package_spec("https://example.test/platform.zip").unwrap();
Expand Down
Loading