Skip to content

Commit 7411abd

Browse files
committed
feat!(stackable-versioned): Add conversion tracking
1 parent 9583068 commit 7411abd

File tree

16 files changed

+1756
-384
lines changed

16 files changed

+1756
-384
lines changed

crates/stackable-versioned-macros/src/attrs/container.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ pub struct ContainerAttributes {
1313

1414
impl ContainerAttributes {
1515
fn validate(self) -> Result<Self> {
16-
if self.crd_arguments.is_some()
16+
if self.crd_arguments.is_none()
1717
&& (self.skip.object_from.is_present()
1818
|| self.skip.merged_crd.is_present()
1919
|| self.skip.try_convert.is_present())

crates/stackable-versioned-macros/src/attrs/item/field.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use darling::{FromField, Result};
1+
use darling::{FromField, Result, util::Flag};
22
use syn::{Attribute, Ident};
33

44
use crate::{attrs::item::CommonItemAttributes, codegen::VersionDefinition, utils::FieldIdent};
@@ -36,6 +36,11 @@ pub struct FieldAttributes {
3636
// FromMeta.
3737
/// The original attributes for the field.
3838
pub attrs: Vec<Attribute>,
39+
40+
/// Indicates that this field's type is a nested sub struct. The indicator
41+
/// is needed to let the macro know to generate conversion code with support
42+
/// for tracking across struct boundaries.
43+
pub nested: Flag,
3944
}
4045

4146
impl FieldAttributes {

crates/stackable-versioned-macros/src/attrs/item/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use quote::format_ident;
77
use syn::{Attribute, Path, Type, spanned::Spanned};
88

99
use crate::{
10-
codegen::{ItemStatus, VersionDefinition},
10+
codegen::{VersionDefinition, item::ItemStatus},
1111
utils::ItemIdentExt,
1212
};
1313

crates/stackable-versioned-macros/src/codegen/changes.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use std::{collections::BTreeMap, ops::Bound};
33
use k8s_version::Version;
44
use syn::Type;
55

6-
use crate::codegen::{ItemStatus, VersionDefinition};
6+
use crate::codegen::{VersionDefinition, item::ItemStatus};
77

88
pub trait Neighbors<K, V>
99
where

crates/stackable-versioned-macros/src/codegen/container/enum.rs

Lines changed: 47 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,15 @@ use quote::quote;
66
use syn::{Generics, ItemEnum};
77

88
use crate::{
9-
attrs::container::NestedContainerAttributes,
9+
attrs::container::ContainerAttributes,
1010
codegen::{
11-
ItemStatus, StandaloneContainerAttributes, VersionDefinition,
11+
Direction, VersionContext, VersionDefinition,
1212
changes::Neighbors,
13-
container::{CommonContainerData, Container, ContainerIdents, ContainerOptions, Direction},
14-
item::VersionedVariant,
13+
container::{
14+
CommonContainerData, Container, ContainerIdents, ContainerOptions, ContainerTokens,
15+
ExtendContainerTokens, ModuleGenerationContext,
16+
},
17+
item::{ItemStatus, VersionedVariant},
1518
},
1619
};
1720

@@ -27,11 +30,13 @@ impl Container {
2730
}
2831

2932
let options = ContainerOptions {
30-
kubernetes_arguments: None,
31-
skip_from: attributes.options.skip.is_some_and(|s| s.from.is_present()),
33+
skip_from: attributes.skip.from.is_present(),
34+
skip_object_from: attributes.skip.object_from.is_present(),
35+
skip_merged_crd: attributes.skip.merged_crd.is_present(),
36+
skip_try_convert: attributes.skip.try_convert.is_present(),
3237
};
3338

34-
let idents = ContainerIdents::from(item_enum.ident, None);
39+
let idents = ContainerIdents::from(item_enum.ident);
3540

3641
let common = CommonContainerData {
3742
original_attributes: item_enum.attrs,
@@ -62,18 +67,43 @@ pub struct Enum {
6267

6368
// Common token generation
6469
impl Enum {
70+
pub fn generate_tokens<'a>(
71+
&'a self,
72+
versions: &'a [VersionDefinition],
73+
gen_ctx: ModuleGenerationContext<'a>,
74+
) -> ContainerTokens<'a> {
75+
let mut versions = versions.iter().peekable();
76+
let mut container_tokens = ContainerTokens::default();
77+
78+
while let Some(version) = versions.next() {
79+
let next_version = versions.peek().copied();
80+
let ver_ctx = VersionContext::new(version, next_version);
81+
82+
let enum_definition = self.generate_definition(ver_ctx);
83+
let upgrade_from = self.generate_from_impl(Direction::Upgrade, ver_ctx, gen_ctx);
84+
let downgrade_from = self.generate_from_impl(Direction::Downgrade, ver_ctx, gen_ctx);
85+
86+
container_tokens
87+
.extend_inner(&version.inner, enum_definition)
88+
.extend_between(&version.inner, upgrade_from)
89+
.extend_between(&version.inner, downgrade_from);
90+
}
91+
92+
container_tokens
93+
}
94+
6595
/// Generates code for the enum definition.
66-
pub fn generate_definition(&self, version: &VersionDefinition) -> TokenStream {
96+
pub fn generate_definition(&self, ver_ctx: VersionContext<'_>) -> TokenStream {
6797
let where_clause = self.generics.where_clause.as_ref();
6898
let type_generics = &self.generics;
6999

70100
let original_attributes = &self.common.original_attributes;
71101
let ident = &self.common.idents.original;
72-
let version_docs = &version.docs;
102+
let version_docs = &ver_ctx.version.docs;
73103

74104
let mut variants = TokenStream::new();
75105
for variant in &self.variants {
76-
variants.extend(variant.generate_for_container(version));
106+
variants.extend(variant.generate_for_container(ver_ctx.version));
77107
}
78108

79109
quote! {
@@ -89,15 +119,16 @@ impl Enum {
89119
pub fn generate_from_impl(
90120
&self,
91121
direction: Direction,
92-
version: &VersionDefinition,
93-
next_version: Option<&VersionDefinition>,
94-
add_attributes: bool,
122+
ver_ctx: VersionContext<'_>,
123+
gen_ctx: ModuleGenerationContext<'_>,
95124
) -> Option<TokenStream> {
96-
if version.skip_from || self.common.options.skip_from {
125+
if ver_ctx.version.skip_from || self.common.options.skip_from {
97126
return None;
98127
}
99128

100-
next_version.map(|next_version| {
129+
let version = ver_ctx.version;
130+
131+
ver_ctx.next_version.map(|next_version| {
101132
// TODO (@Techassi): Support generic types which have been removed in newer versions,
102133
// but need to exist for older versions How do we represent that? Because the
103134
// defined struct always represents the latest version. I guess we could generally
@@ -118,7 +149,7 @@ impl Enum {
118149

119150
// Only add the #[automatically_derived] attribute only if this impl is used
120151
// outside of a module (in standalone mode).
121-
let automatically_derived = add_attributes
152+
let automatically_derived = gen_ctx.add_attributes
122153
.not()
123154
.then(|| quote! {#[automatically_derived]});
124155

crates/stackable-versioned-macros/src/codegen/container/mod.rs

Lines changed: 117 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
1-
use darling::{Result, util::IdentString};
2-
use proc_macro2::{Span, TokenStream};
3-
use quote::{format_ident, quote};
4-
use syn::{Attribute, Ident, ItemEnum, ItemStruct, Visibility};
1+
use std::collections::HashMap;
2+
3+
use darling::util::IdentString;
4+
use k8s_version::Version;
5+
use proc_macro2::{Span, TokenStream, TokenTree};
6+
use quote::format_ident;
7+
use syn::{Attribute, Ident};
58

69
use crate::{
7-
attrs::container::{StandaloneContainerAttributes, k8s::KubernetesArguments},
10+
attrs::container::StructCrdArguments,
811
codegen::{
9-
KubernetesTokens, VersionDefinition,
12+
VersionDefinition,
1013
container::{r#enum::Enum, r#struct::Struct},
14+
module::ModuleGenerationContext,
1115
},
1216
utils::ContainerIdentExt,
1317
};
@@ -37,57 +41,117 @@ pub enum Container {
3741
Enum(Enum),
3842
}
3943

40-
impl Container {
41-
/// Generates the container definition for the specified `version`.
42-
pub fn generate_definition(&self, version: &VersionDefinition) -> TokenStream {
43-
match self {
44-
Container::Struct(s) => s.generate_definition(version),
45-
Container::Enum(e) => e.generate_definition(version),
46-
}
44+
#[derive(Debug, Default)]
45+
pub struct ContainerTokens<'a> {
46+
pub versioned: HashMap<&'a Version, VersionedContainerTokens>,
47+
pub outer: TokenStream,
48+
}
49+
50+
#[derive(Debug, Default)]
51+
/// A collection of generated tokens for a container per version.
52+
pub struct VersionedContainerTokens {
53+
/// The inner tokens are placed inside the version module. These tokens mostly only include the
54+
/// container definition with attributes, doc comments, etc.
55+
pub inner: TokenStream,
56+
57+
/// These tokens are placed between version modules. These could technically be grouped together
58+
/// with the outer tokens, but it makes sense to keep them separate to achieve a more structured
59+
/// code generation. These tokens mostly only include `From` impls to convert between two versions
60+
pub between: TokenStream,
61+
}
62+
63+
pub trait ExtendContainerTokens<'a, T> {
64+
fn extend_inner<I: IntoIterator<Item = T>>(
65+
&mut self,
66+
version: &'a Version,
67+
streams: I,
68+
) -> &mut Self;
69+
fn extend_between<I: IntoIterator<Item = T>>(
70+
&mut self,
71+
version: &'a Version,
72+
streams: I,
73+
) -> &mut Self;
74+
fn extend_outer<I: IntoIterator<Item = T>>(&mut self, streams: I) -> &mut Self;
75+
}
76+
77+
impl<'a> ExtendContainerTokens<'a, TokenStream> for ContainerTokens<'a> {
78+
fn extend_inner<I: IntoIterator<Item = TokenStream>>(
79+
&mut self,
80+
version: &'a Version,
81+
streams: I,
82+
) -> &mut Self {
83+
self.versioned
84+
.entry(version)
85+
.or_default()
86+
.inner
87+
.extend(streams);
88+
self
4789
}
4890

49-
pub fn generate_from_impl(
50-
&self,
51-
direction: Direction,
52-
version: &VersionDefinition,
53-
next_version: Option<&VersionDefinition>,
54-
add_attributes: bool,
55-
) -> Option<TokenStream> {
56-
match self {
57-
Container::Struct(s) => {
58-
// TODO (@Techassi): Decide here (based on K8s args) what we want to generate
59-
s.generate_from_impl(direction, version, next_version, add_attributes)
60-
}
61-
Container::Enum(e) => {
62-
e.generate_from_impl(direction, version, next_version, add_attributes)
63-
}
64-
}
91+
fn extend_between<I: IntoIterator<Item = TokenStream>>(
92+
&mut self,
93+
version: &'a Version,
94+
streams: I,
95+
) -> &mut Self {
96+
self.versioned
97+
.entry(version)
98+
.or_default()
99+
.between
100+
.extend(streams);
101+
self
65102
}
66103

67-
/// Generates Kubernetes specific code for the container.
68-
///
69-
/// This includes CRD merging, CRD conversion, and the conversion tracking status struct.
70-
pub fn generate_kubernetes_code(
71-
&self,
72-
versions: &[VersionDefinition],
73-
tokens: &KubernetesTokens,
74-
vis: &Visibility,
75-
is_nested: bool,
76-
) -> Option<TokenStream> {
77-
match self {
78-
Container::Struct(s) => s.generate_kubernetes_code(versions, tokens, vis, is_nested),
79-
Container::Enum(_) => None,
80-
}
104+
fn extend_outer<I: IntoIterator<Item = TokenStream>>(&mut self, streams: I) -> &mut Self {
105+
self.outer.extend(streams);
106+
self
107+
}
108+
}
109+
110+
impl<'a> ExtendContainerTokens<'a, TokenTree> for ContainerTokens<'a> {
111+
fn extend_inner<I: IntoIterator<Item = TokenTree>>(
112+
&mut self,
113+
version: &'a Version,
114+
streams: I,
115+
) -> &mut Self {
116+
self.versioned
117+
.entry(version)
118+
.or_default()
119+
.inner
120+
.extend(streams);
121+
self
122+
}
123+
124+
fn extend_between<I: IntoIterator<Item = TokenTree>>(
125+
&mut self,
126+
version: &'a Version,
127+
streams: I,
128+
) -> &mut Self {
129+
self.versioned
130+
.entry(version)
131+
.or_default()
132+
.between
133+
.extend(streams);
134+
self
81135
}
82136

83-
/// Generates KUbernetes specific code for individual versions.
84-
pub fn generate_kubernetes_version_items(
85-
&self,
86-
version: &VersionDefinition,
87-
) -> Option<(TokenStream, IdentString, TokenStream, String)> {
137+
fn extend_outer<I: IntoIterator<Item = TokenTree>>(&mut self, streams: I) -> &mut Self {
138+
self.outer.extend(streams);
139+
self
140+
}
141+
}
142+
143+
impl Container {
144+
// TODO (@Techassi): Only have a single function here. It should return and store all generated
145+
// tokens. It should also have access to a single GenerationContext, which provides all external
146+
// parameters which influence code generation.
147+
pub fn generate_tokens<'a>(
148+
&'a self,
149+
versions: &'a [VersionDefinition],
150+
ctx: ModuleGenerationContext<'a>,
151+
) -> ContainerTokens<'a> {
88152
match self {
89-
Container::Struct(s) => s.generate_kubernetes_version_items(version),
90-
Container::Enum(_) => None,
153+
Container::Struct(s) => s.generate_tokens(versions, ctx),
154+
Container::Enum(e) => e.generate_tokens(versions, ctx),
91155
}
92156
}
93157

@@ -162,12 +226,8 @@ impl KubernetesIdents {
162226

163227
#[derive(Debug)]
164228
pub struct ContainerOptions {
165-
pub kubernetes_arguments: Option<KubernetesArguments>,
166229
pub skip_from: bool,
167-
}
168-
169-
#[derive(Copy, Clone, Debug)]
170-
pub enum Direction {
171-
Upgrade,
172-
Downgrade,
230+
pub skip_object_from: bool,
231+
pub skip_merged_crd: bool,
232+
pub skip_try_convert: bool,
173233
}

0 commit comments

Comments
 (0)