use crate::build_info;
use crate::resolve::{FeatureSet, SelectionReason};
pub fn build_preamble(fs: &FeatureSet, command_line: &str) -> String {
let mut lines: Vec<String> = Vec::new();
let year = build_info::BUILD_YEAR;
lines.push(format!(
" * Generated by gloam {}. DO NOT EDIT.",
build_info::VERSION
));
lines.push(" *".to_string());
lines.push(format!(" * {command_line}"));
if !fs.extensions.is_empty()
|| !fs.excluded_explicit.is_empty()
|| !fs.excluded_baseline.is_empty()
{
lines.push(" *".to_string());
lines.push(format!(" * {}", extension_summary(fs)));
for (label, reason) in &[
("dependency", SelectionReason::Dependency),
("promoted", SelectionReason::Promoted),
("predecessor", SelectionReason::Predecessor),
] {
let mut names: Vec<&str> = fs
.extensions
.iter()
.filter(|e| e.reason == *reason)
.map(|e| e.name.as_str())
.collect();
names.sort_unstable();
if !names.is_empty() {
lines.push(format!(" * {}: {}", label, names.join(", ")));
}
}
if !fs.excluded_baseline.is_empty() {
lines.push(format!(
" * excluded by baseline: {} extensions",
fs.excluded_baseline.len()
));
}
if !fs.excluded_explicit.is_empty() {
let mut names = fs.excluded_explicit.clone();
names.sort_unstable();
lines.push(format!(" * excluded explicitly: {}", names.join(", ")));
}
}
lines.push(" *".to_string());
lines.push(format!(" * Copyright (c) {year} Steven Noonan"));
lines.push(" * SPDX-License-Identifier: MIT".to_string());
lines.push(" *".to_string());
lines.push(
" * Portions derived from Khronos Group XML API Registry specifications.".to_string(),
);
lines.push(format!(
" * Copyright (c) 2013-{year} The Khronos Group Inc."
));
lines.push(" * SPDX-License-Identifier: Apache-2.0".to_string());
if uses_angle_supplementals(fs) {
lines.push(" *".to_string());
lines.push(
" * Includes extensions from the ANGLE project (gl_angle_ext.xml, egl_angle_ext.xml)."
.to_string(),
);
lines.push(format!(" * Copyright (c) {year} Google Inc."));
lines.push(" * SPDX-License-Identifier: BSD-3-Clause".to_string());
}
format!("/*\n{}\n */", lines.join("\n"))
}
fn extension_summary(fs: &FeatureSet) -> String {
let total = fs.extensions.len();
let n_baseline_excluded = fs.excluded_baseline.len();
let n_explicit_excluded = fs.excluded_explicit.len();
let count = |reason: SelectionReason| -> usize {
fs.extensions.iter().filter(|e| e.reason == reason).count()
};
let n_all = count(SelectionReason::AllExtensions);
let n_explicit = count(SelectionReason::Explicit);
let n_mandatory = count(SelectionReason::Mandatory);
let n_dependency = count(SelectionReason::Dependency);
let n_promoted = count(SelectionReason::Promoted);
let n_predecessor = count(SelectionReason::Predecessor);
let mut parts: Vec<String> = Vec::new();
if n_all + n_mandatory == total {
parts.push("all".to_string());
} else {
if n_explicit > 0 {
parts.push(format!("{n_explicit} explicit"));
}
if n_mandatory > 0 {
parts.push(format!("{n_mandatory} mandatory"));
}
if n_dependency > 0 {
parts.push(format!("{n_dependency} dependency"));
}
if n_promoted > 0 {
parts.push(format!("{n_promoted} promoted"));
}
if n_predecessor > 0 {
parts.push(format!("{n_predecessor} predecessor"));
}
}
if n_baseline_excluded > 0 {
parts.push(format!("{n_baseline_excluded} excluded by baseline"));
}
if n_explicit_excluded > 0 {
parts.push(format!("{n_explicit_excluded} excluded explicitly"));
}
format!("Extensions: {} ({total} included)", parts.join(", "))
}
fn uses_angle_supplementals(fs: &FeatureSet) -> bool {
matches!(fs.spec_name.as_str(), "gl" | "egl")
}
#[cfg(test)]
mod tests {
use super::*;
use crate::resolve::Extension;
fn stub_fs(spec_name: &str) -> FeatureSet {
FeatureSet {
spec_name: spec_name.to_string(),
display_name: String::new(),
apis: vec![],
is_merged: false,
is_vulkan: false,
is_gl_family: false,
context_name: String::new(),
features: vec![],
extensions: vec![],
commands: vec![],
types: vec![],
flat_enums: vec![],
enum_groups: vec![],
feature_pfn_ranges: vec![],
ext_pfn_ranges: Default::default(),
ext_subset_indices: Default::default(),
alias_pairs: vec![],
required_headers: vec![],
excluded_explicit: vec![],
excluded_baseline: vec![],
include_type_groups: vec![],
type_groups: vec![],
ext_guard_groups: vec![],
cmd_pfn_groups: vec![],
flat_enum_groups: vec![],
}
}
fn stub_ext(name: &str, reason: SelectionReason) -> Extension {
Extension {
index: 0,
name: name.to_string(),
short_name: name.to_string(),
hash: String::new(),
protect: vec![],
reason,
}
}
#[test]
fn preamble_contains_version_and_command() {
let fs = stub_fs("vk");
let p = build_preamble(&fs, "gloam --api vk=1.3 c");
assert!(p.contains(build_info::VERSION));
assert!(p.contains("gloam --api vk=1.3 c"));
}
#[test]
fn preamble_contains_gloam_mit_license() {
let fs = stub_fs("vk");
let p = build_preamble(&fs, "gloam --api vk=1.3 c");
assert!(p.contains("Steven Noonan"));
assert!(p.contains("SPDX-License-Identifier: MIT"));
}
#[test]
fn preamble_contains_khronos_copyright() {
let fs = stub_fs("vk");
let p = build_preamble(&fs, "gloam --api vk=1.3 c");
assert!(p.contains("Khronos Group"));
assert!(p.contains("Apache-2.0"));
}
#[test]
fn preamble_includes_angle_for_gl() {
let fs = stub_fs("gl");
let p = build_preamble(&fs, "gloam --api gl:core=3.3 c");
assert!(p.contains("ANGLE"));
assert!(p.contains("BSD-3-Clause"));
}
#[test]
fn preamble_includes_angle_for_egl() {
let fs = stub_fs("egl");
let p = build_preamble(&fs, "gloam --api egl c");
assert!(p.contains("ANGLE"));
}
#[test]
fn preamble_excludes_angle_for_vulkan() {
let fs = stub_fs("vk");
let p = build_preamble(&fs, "gloam --api vk=1.3 c");
assert!(!p.contains("ANGLE"));
assert!(!p.contains("BSD-3-Clause"));
}
#[test]
fn preamble_excludes_angle_for_glx() {
let fs = stub_fs("glx");
let p = build_preamble(&fs, "gloam --api glx c");
assert!(!p.contains("ANGLE"));
}
#[test]
fn preamble_is_valid_c_comment() {
let fs = stub_fs("gl");
let p = build_preamble(&fs, "gloam --api gl:core=3.3 c");
assert!(p.starts_with("/*"));
assert!(p.ends_with(" */"));
}
#[test]
fn preamble_uses_build_year_not_hardcoded() {
let fs = stub_fs("vk");
let p = build_preamble(&fs, "gloam --api vk=1.3 c");
let year = build_info::BUILD_YEAR.to_string();
assert!(
p.contains(&format!("Copyright (c) {year} Steven Noonan")),
"gloam copyright should use BUILD_YEAR ({year})"
);
assert!(
p.contains(&format!("2013-{year} The Khronos Group")),
"Khronos copyright range should end with BUILD_YEAR ({year})"
);
}
#[test]
fn summary_all_extensions() {
let mut fs = stub_fs("vk");
fs.extensions = vec![
stub_ext("VK_KHR_swapchain", SelectionReason::AllExtensions),
stub_ext("VK_KHR_surface", SelectionReason::AllExtensions),
];
assert_eq!(extension_summary(&fs), "Extensions: all (2 included)");
}
#[test]
fn summary_all_plus_mandatory() {
let mut fs = stub_fs("wgl");
fs.extensions = vec![
stub_ext("WGL_ARB_extensions_string", SelectionReason::Mandatory),
stub_ext("WGL_ARB_pixel_format", SelectionReason::AllExtensions),
];
assert_eq!(extension_summary(&fs), "Extensions: all (2 included)");
}
#[test]
fn summary_explicit_only() {
let mut fs = stub_fs("vk");
fs.extensions = vec![
stub_ext("VK_KHR_swapchain", SelectionReason::Explicit),
stub_ext("VK_KHR_surface", SelectionReason::Explicit),
];
assert_eq!(
extension_summary(&fs),
"Extensions: 2 explicit (2 included)"
);
}
#[test]
fn summary_mixed_reasons() {
let mut fs = stub_fs("gl");
fs.extensions = vec![
stub_ext("GL_KHR_debug", SelectionReason::Explicit),
stub_ext("GL_ARB_copy_buffer", SelectionReason::Promoted),
stub_ext("GL_ARB_multitexture", SelectionReason::Promoted),
stub_ext(
"GL_ARB_parallel_shader_compile",
SelectionReason::Predecessor,
),
];
assert_eq!(
extension_summary(&fs),
"Extensions: 1 explicit, 2 promoted, 1 predecessor (4 included)"
);
}
#[test]
fn preamble_lists_promoted_extensions() {
let mut fs = stub_fs("gl");
fs.extensions = vec![
stub_ext("GL_KHR_debug", SelectionReason::Explicit),
stub_ext("GL_ARB_copy_buffer", SelectionReason::Promoted),
];
let p = build_preamble(&fs, "gloam --api gl:core=3.3 c");
assert!(p.contains("promoted: GL_ARB_copy_buffer"));
}
#[test]
fn preamble_lists_predecessor_extensions() {
let mut fs = stub_fs("gl");
fs.extensions = vec![
stub_ext("GL_KHR_parallel_shader_compile", SelectionReason::Explicit),
stub_ext(
"GL_ARB_parallel_shader_compile",
SelectionReason::Predecessor,
),
];
let p = build_preamble(&fs, "gloam --api gl:core=3.3 c");
assert!(p.contains("predecessor: GL_ARB_parallel_shader_compile"));
}
#[test]
fn preamble_lists_dependency_extensions() {
let mut fs = stub_fs("gl");
fs.extensions = vec![
stub_ext("GL_ARB_multi_draw_indirect", SelectionReason::Explicit),
stub_ext("GL_ARB_draw_indirect", SelectionReason::Dependency),
];
let p = build_preamble(&fs, "gloam --api gl:core=3.3 c");
assert!(p.contains("dependency: GL_ARB_draw_indirect"));
}
#[test]
fn summary_with_dependencies() {
let mut fs = stub_fs("gl");
fs.extensions = vec![
stub_ext("GL_ARB_multi_draw_indirect", SelectionReason::Explicit),
stub_ext("GL_ARB_draw_indirect", SelectionReason::Dependency),
];
assert_eq!(
extension_summary(&fs),
"Extensions: 1 explicit, 1 dependency (2 included)"
);
}
#[test]
fn preamble_no_extension_section_when_empty() {
let fs = stub_fs("vk");
let p = build_preamble(&fs, "gloam --api vk=1.3 c");
assert!(!p.contains("Extensions:"));
}
#[test]
fn summary_with_baseline_exclusions() {
let mut fs = stub_fs("gl");
fs.extensions = vec![stub_ext("GL_KHR_debug", SelectionReason::AllExtensions)];
fs.excluded_baseline = vec![
"GL_ARB_copy_buffer".to_string(),
"GL_ARB_multitexture".to_string(),
];
assert_eq!(
extension_summary(&fs),
"Extensions: all, 2 excluded by baseline (1 included)"
);
}
#[test]
fn summary_with_explicit_exclusions() {
let mut fs = stub_fs("gl");
fs.extensions = vec![stub_ext("GL_KHR_debug", SelectionReason::AllExtensions)];
fs.excluded_explicit = vec!["GL_EXT_direct_state_access".to_string()];
assert_eq!(
extension_summary(&fs),
"Extensions: all, 1 excluded explicitly (1 included)"
);
}
#[test]
fn summary_with_both_exclusion_types() {
let mut fs = stub_fs("gl");
fs.extensions = vec![stub_ext("GL_KHR_debug", SelectionReason::AllExtensions)];
fs.excluded_baseline = vec!["GL_ARB_copy_buffer".to_string()];
fs.excluded_explicit = vec!["GL_EXT_direct_state_access".to_string()];
assert_eq!(
extension_summary(&fs),
"Extensions: all, 1 excluded by baseline, 1 excluded explicitly (1 included)"
);
}
#[test]
fn preamble_shows_baseline_exclusion_count() {
let mut fs = stub_fs("gl");
fs.extensions = vec![stub_ext("GL_KHR_debug", SelectionReason::AllExtensions)];
fs.excluded_baseline = vec![
"GL_ARB_copy_buffer".to_string(),
"GL_ARB_multitexture".to_string(),
];
let p = build_preamble(&fs, "gloam --api gl:core c");
assert!(p.contains("excluded by baseline: 2 extensions"));
}
#[test]
fn preamble_shows_explicit_exclusion_names() {
let mut fs = stub_fs("gl");
fs.extensions = vec![stub_ext("GL_KHR_debug", SelectionReason::AllExtensions)];
fs.excluded_explicit = vec!["GL_EXT_direct_state_access".to_string()];
let p = build_preamble(&fs, "gloam --api gl:core c");
assert!(p.contains("excluded explicitly: GL_EXT_direct_state_access"));
}
}