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
16 changes: 10 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,17 @@ it. That has happened before (a per-key map merge strategy), which is why the ch
### Keeping regeneration reproducible

`./regen.sh` stamps `src/metamodel/GENERATED_FROM` with the generator revision it used and
with any generator commits that are not in `linkml/main` yet. While that list is non-empty
the crate cannot be reproduced from upstream linkml alone, so a generator fix belongs
upstream as a linkml PR *before* the regenerated crate is committed here.
with every generator change it needed that is not in `linkml/main`, each resolved to the pull
request carrying it. That list is the reproduction recipe: apply those PRs on top of
`linkml/main` and re-run the script.

Each open linkml generator PR this crate is waiting on has its own tracking issue, so they close
as they merge; [#109](https://github.com/Kapernikov/rust-linkml-core/issues/109) is the
regeneration itself and links to them.
So a generator fix does **not** have to be merged upstream before the regenerated crate lands
here — it has to be *identifiable*. Push it and open the PR, so the stamp can name something a
reader can fetch instead of a sha that only exists on your disk. (`LINKML_PR_REPO` overrides
which repo is searched for the PR; it defaults to `linkml/linkml`.)

Each such PR gets a tracking issue here, so they can be closed off one by one as they merge
upstream and drop out of the stamp.

## Development on the Python bindings

Expand Down
49 changes: 39 additions & 10 deletions regen.sh
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,39 @@ if [[ ! -d $LINKML_DIR ]]; then
fi
LINKML_DIR=$(cd "$LINKML_DIR" && pwd)

# The revision that produced this output, and — more useful when the output
# does not match — which generator commits are not upstream yet. Anything
# listed here has to be merged and this file re-stamped before another machine
# can reproduce the crate.
# The revision that produced this output, plus the generator changes it needs
# that are not in linkml/main. Naming those is what makes the crate
# reproducible: someone else applies the listed PRs and runs this script. They
# do not have to be merged first.
revision=$(git -C "$LINKML_DIR" rev-parse HEAD)
described=$(git -C "$LINKML_DIR" describe --always --dirty)
unmerged=$(git -C "$LINKML_DIR" log --oneline linkml/main..HEAD 2>/dev/null || true)
# A dirty generator checkout means the revision does not determine the output,
# which is the one thing the stamp is supposed to promise. Worth saying out loud;
# not worth refusing over, since the diff may well be unrelated to the generator.
if [[ -n $(git -C "$LINKML_DIR" status --porcelain --untracked-files=no) ]]; then
echo "WARNING: $LINKML_DIR has uncommitted changes; the recorded revision" >&2
echo " alone will not reproduce this output." >&2
fi
extra_commits=$(git -C "$LINKML_DIR" log --format='%h %s' linkml/main..HEAD 2>/dev/null || true)

# Resolve each extra commit to the pull request carrying it, so the stamp names
# something a reader can fetch rather than a bare sha. Best effort: needs `gh`
# and network, and the commit has to be pushed to $LINKML_PR_REPO.
PR_REPO=${LINKML_PR_REPO:-linkml/linkml}
describe_commit() {
local sha=$1 subject=$2 prs=
if command -v gh >/dev/null 2>&1; then
# Every PR whose head contains the commit, not just one: a stacked branch
# puts the same commit in several, and any of them reproduces it.
prs=$(gh api "repos/$PR_REPO/commits/$sha/pulls" \
--jq '[.[].number] | map("#" + tostring) | join(", ")' 2>/dev/null || true)
fi
if [[ -n $prs && $prs != "null" ]]; then
echo "$sha $subject ($PR_REPO$prs)"
else
echo "$sha $subject (no PR found on $PR_REPO)"
fi
}

out_dir=$CRATE_DIR
tmp_dir=
Expand Down Expand Up @@ -95,12 +121,15 @@ fi
echo "generator_describe = $described"
echo "meta_yaml = src/schemaview/tests/data/meta.yaml"
echo
if [[ -n $unmerged ]]; then
echo "# Generator commits NOT in linkml/main. Until these are merged, this"
echo "# crate cannot be reproduced from upstream linkml alone."
while IFS= read -r line; do echo "# $line"; done <<<"$unmerged"
if [[ -n $extra_commits ]]; then
echo "# To reproduce: apply these generator changes on top of linkml/main,"
echo "# then run ./regen.sh. They need not be merged; naming them is enough."
echo "# Newest first:"
while IFS= read -r line; do
echo "# $(describe_commit "${line%% *}" "${line#* }")"
done <<<"$extra_commits"
else
echo "# Every generator commit is in linkml/main; reproducible from upstream."
echo "# Every generator commit is in linkml/main; plain checkout reproduces."
fi
} >"$PROVENANCE"

Expand Down
13 changes: 13 additions & 0 deletions src/metamodel/GENERATED_FROM
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Provenance of the generated linkml_meta crate. Written by regen.sh.
# Do not edit by hand.
generator_repo = git@github.com:Kapernikov/linkml.git
generator_revision = e7015ec5cfe365db14e4aea92b646f58759ef254
generator_describe = v1.11.1-288-ge7015ec5c-dirty
meta_yaml = src/schemaview/tests/data/meta.yaml

# To reproduce: apply these generator changes on top of linkml/main,
# then run ./regen.sh. They need not be merged; naming them is enough.
# Newest first:
# e7015ec5c fix(rustgen): preserve LinkML slot name on the wire for escaped fields (linkml/linkml#3919)
# 0e8b0cb3a fix(rustgen): include concrete base class in its *OrSubtype enum (linkml/linkml#3919)
# a9c00ca0a fix(rustgen): emit serde tag for type designators on subtype enums (linkml/linkml#3919)
38 changes: 36 additions & 2 deletions src/metamodel/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -794,20 +794,29 @@ impl serde_utils::InlinedPair for Extension {
#[cfg_attr(feature = "serde", serde(untagged))]
pub enum ExtensionOrSubtype {
Annotation(Annotation),
Extension(Extension),
}

impl From<Annotation> for ExtensionOrSubtype {
fn from(x: Annotation) -> Self {
Self::Annotation(x)
}
}
impl From<Extension> for ExtensionOrSubtype {
fn from(x: Extension) -> Self {
Self::Extension(x)
}
}

#[cfg(feature = "pyo3")]
impl<'py> FromPyObject<'py> for ExtensionOrSubtype {
fn extract_bound(ob: &pyo3::Bound<'py, pyo3::types::PyAny>) -> pyo3::PyResult<Self> {
if let Ok(val) = ob.extract::<Annotation>() {
return Ok(ExtensionOrSubtype::Annotation(val));
}
if let Ok(val) = ob.extract::<Extension>() {
return Ok(ExtensionOrSubtype::Extension(val));
}
Err(PyErr::new::<pyo3::exceptions::PyTypeError, _>(
"invalid ExtensionOrSubtype",
))
Expand All @@ -823,6 +832,7 @@ impl<'py> IntoPyObject<'py> for ExtensionOrSubtype {
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
match self {
ExtensionOrSubtype::Annotation(val) => val.into_pyobject(py).map(move |b| b.into_any()),
ExtensionOrSubtype::Extension(val) => val.into_pyobject(py).map(move |b| b.into_any()),
}
}
}
Expand Down Expand Up @@ -859,25 +869,32 @@ impl serde_utils::InlinedPair for ExtensionOrSubtype {
if let Ok(x) = Annotation::from_pair_mapping(k.clone(), v.clone()) {
return Ok(ExtensionOrSubtype::Annotation(x));
}
if let Ok(x) = Extension::from_pair_mapping(k.clone(), v.clone()) {
return Ok(ExtensionOrSubtype::Extension(x));
}
Err("none of the variants matched the mapping form".into())
}

fn from_pair_simple(k: Self::Key, v: Self::Value) -> Result<Self, Self::Error> {
if let Ok(x) = Annotation::from_pair_simple(k.clone(), v.clone()) {
return Ok(ExtensionOrSubtype::Annotation(x));
}
if let Ok(x) = Extension::from_pair_simple(k.clone(), v.clone()) {
return Ok(ExtensionOrSubtype::Extension(x));
}
Err("none of the variants support the primitive form".into())
}

fn extract_key(&self) -> &Self::Key {
match self {
ExtensionOrSubtype::Annotation(inner) => inner.extract_key(),
ExtensionOrSubtype::Extension(inner) => inner.extract_key(),
}
}
}

#[cfg(feature = "stubgen")]
::pyo3_stub_gen::impl_stub_type!(ExtensionOrSubtype = Annotation);
::pyo3_stub_gen::impl_stub_type!(ExtensionOrSubtype = Annotation | Extension);

#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
Expand Down Expand Up @@ -4245,6 +4262,7 @@ impl<'py> FromPyObject<'py> for Box<AnonymousTypeExpression> {
#[cfg_attr(feature = "pyo3", pyclass(subclass, get_all, set_all))]
pub struct TypeDefinition {
#[cfg_attr(feature = "serde", serde(default))]
#[cfg_attr(feature = "serde", serde(rename = "typeof"))]
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub typeof_: Option<String>,
#[cfg_attr(feature = "serde", serde(default))]
Expand Down Expand Up @@ -5503,6 +5521,7 @@ pub struct Definition {
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub is_a: Option<String>,
#[cfg_attr(feature = "serde", serde(default))]
#[cfg_attr(feature = "serde", serde(rename = "abstract"))]
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub abstract_: Option<bool>,
#[cfg_attr(feature = "serde", serde(default))]
Expand Down Expand Up @@ -6306,6 +6325,7 @@ pub struct EnumDefinition {
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub is_a: Option<String>,
#[cfg_attr(feature = "serde", serde(default))]
#[cfg_attr(feature = "serde", serde(rename = "abstract"))]
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub abstract_: Option<bool>,
#[cfg_attr(feature = "serde", serde(default))]
Expand Down Expand Up @@ -8250,6 +8270,7 @@ impl<'py> FromPyObject<'py> for Box<EnumExpression> {
pub enum EnumExpressionOrSubtype {
AnonymousEnumExpression(AnonymousEnumExpression),
EnumDefinition(EnumDefinition),
EnumExpression(EnumExpression),
}

impl From<AnonymousEnumExpression> for EnumExpressionOrSubtype {
Expand All @@ -8262,6 +8283,11 @@ impl From<EnumDefinition> for EnumExpressionOrSubtype {
Self::EnumDefinition(x)
}
}
impl From<EnumExpression> for EnumExpressionOrSubtype {
fn from(x: EnumExpression) -> Self {
Self::EnumExpression(x)
}
}

#[cfg(feature = "pyo3")]
impl<'py> FromPyObject<'py> for EnumExpressionOrSubtype {
Expand All @@ -8272,6 +8298,9 @@ impl<'py> FromPyObject<'py> for EnumExpressionOrSubtype {
if let Ok(val) = ob.extract::<EnumDefinition>() {
return Ok(EnumExpressionOrSubtype::EnumDefinition(val));
}
if let Ok(val) = ob.extract::<EnumExpression>() {
return Ok(EnumExpressionOrSubtype::EnumExpression(val));
}
Err(PyErr::new::<pyo3::exceptions::PyTypeError, _>(
"invalid EnumExpressionOrSubtype",
))
Expand All @@ -8292,6 +8321,9 @@ impl<'py> IntoPyObject<'py> for EnumExpressionOrSubtype {
EnumExpressionOrSubtype::EnumDefinition(val) => {
val.into_pyobject(py).map(move |b| b.into_any())
}
EnumExpressionOrSubtype::EnumExpression(val) => {
val.into_pyobject(py).map(move |b| b.into_any())
}
}
}
}
Expand Down Expand Up @@ -8320,7 +8352,7 @@ impl<'py> FromPyObject<'py> for Box<EnumExpressionOrSubtype> {

#[cfg(feature = "stubgen")]
::pyo3_stub_gen::impl_stub_type!(
EnumExpressionOrSubtype = AnonymousEnumExpression | EnumDefinition
EnumExpressionOrSubtype = AnonymousEnumExpression | EnumDefinition | EnumExpression
);

#[derive(Debug, Clone, PartialEq)]
Expand Down Expand Up @@ -10224,6 +10256,7 @@ pub struct SlotDefinition {
pub is_a: Option<String>,
#[merge(strategy = overwrite_except_none)]
#[cfg_attr(feature = "serde", serde(default))]
#[cfg_attr(feature = "serde", serde(rename = "abstract"))]
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub abstract_: Option<bool>,
#[merge(strategy = overwrite_except_none)]
Expand Down Expand Up @@ -11568,6 +11601,7 @@ pub struct ClassDefinition {
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub is_a: Option<String>,
#[cfg_attr(feature = "serde", serde(default))]
#[cfg_attr(feature = "serde", serde(rename = "abstract"))]
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub abstract_: Option<bool>,
#[cfg_attr(feature = "serde", serde(default))]
Expand Down
16 changes: 16 additions & 0 deletions src/metamodel/src/poly.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,18 +54,21 @@ impl Extension for crate::ExtensionOrSubtype {
fn extension_tag<'a>(&'a self) -> &'a crate::uriorcurie {
match self {
ExtensionOrSubtype::Annotation(val) => val.extension_tag(),
ExtensionOrSubtype::Extension(val) => val.extension_tag(),
}
}
fn extension_value<'a>(&'a self) -> &'a crate::AnyValue {
match self {
ExtensionOrSubtype::Annotation(val) => val.extension_value(),
ExtensionOrSubtype::Extension(val) => val.extension_value(),
}
}
fn extensions<'a>(
&'a self,
) -> Option<impl poly_containers::MapRef<'a, String, ExtensionOrSubtype>> {
match self {
ExtensionOrSubtype::Annotation(val) => val.extensions().map(|x| x.to_any()),
ExtensionOrSubtype::Extension(val) => val.extensions().map(|x| x.to_any()),
}
}
}
Expand Down Expand Up @@ -6984,24 +6987,28 @@ impl EnumExpression for crate::EnumExpressionOrSubtype {
match self {
EnumExpressionOrSubtype::AnonymousEnumExpression(val) => val.code_set(),
EnumExpressionOrSubtype::EnumDefinition(val) => val.code_set(),
EnumExpressionOrSubtype::EnumExpression(val) => val.code_set(),
}
}
fn code_set_tag<'a>(&'a self) -> Option<&'a str> {
match self {
EnumExpressionOrSubtype::AnonymousEnumExpression(val) => val.code_set_tag(),
EnumExpressionOrSubtype::EnumDefinition(val) => val.code_set_tag(),
EnumExpressionOrSubtype::EnumExpression(val) => val.code_set_tag(),
}
}
fn code_set_version<'a>(&'a self) -> Option<&'a str> {
match self {
EnumExpressionOrSubtype::AnonymousEnumExpression(val) => val.code_set_version(),
EnumExpressionOrSubtype::EnumDefinition(val) => val.code_set_version(),
EnumExpressionOrSubtype::EnumExpression(val) => val.code_set_version(),
}
}
fn pv_formula<'a>(&'a self) -> Option<&'a crate::PvFormulaOptions> {
match self {
EnumExpressionOrSubtype::AnonymousEnumExpression(val) => val.pv_formula(),
EnumExpressionOrSubtype::EnumDefinition(val) => val.pv_formula(),
EnumExpressionOrSubtype::EnumExpression(val) => val.pv_formula(),
}
}
fn permissible_values<'a>(
Expand All @@ -7014,6 +7021,9 @@ impl EnumExpression for crate::EnumExpressionOrSubtype {
EnumExpressionOrSubtype::EnumDefinition(val) => {
val.permissible_values().map(|x| x.to_any())
}
EnumExpressionOrSubtype::EnumExpression(val) => {
val.permissible_values().map(|x| x.to_any())
}
}
}
fn include<'a>(
Expand All @@ -7024,6 +7034,7 @@ impl EnumExpression for crate::EnumExpressionOrSubtype {
val.include().map(|x| x.to_any())
}
EnumExpressionOrSubtype::EnumDefinition(val) => val.include().map(|x| x.to_any()),
EnumExpressionOrSubtype::EnumExpression(val) => val.include().map(|x| x.to_any()),
}
}
fn minus<'a>(
Expand All @@ -7034,6 +7045,7 @@ impl EnumExpression for crate::EnumExpressionOrSubtype {
val.minus().map(|x| x.to_any())
}
EnumExpressionOrSubtype::EnumDefinition(val) => val.minus().map(|x| x.to_any()),
EnumExpressionOrSubtype::EnumExpression(val) => val.minus().map(|x| x.to_any()),
}
}
fn inherits<'a>(&'a self) -> Option<impl poly_containers::SeqRef<'a, String>> {
Expand All @@ -7042,18 +7054,21 @@ impl EnumExpression for crate::EnumExpressionOrSubtype {
val.inherits().map(|x| x.to_any())
}
EnumExpressionOrSubtype::EnumDefinition(val) => val.inherits().map(|x| x.to_any()),
EnumExpressionOrSubtype::EnumExpression(val) => val.inherits().map(|x| x.to_any()),
}
}
fn reachable_from<'a>(&'a self) -> Option<&'a crate::ReachabilityQuery> {
match self {
EnumExpressionOrSubtype::AnonymousEnumExpression(val) => val.reachable_from(),
EnumExpressionOrSubtype::EnumDefinition(val) => val.reachable_from(),
EnumExpressionOrSubtype::EnumExpression(val) => val.reachable_from(),
}
}
fn matches<'a>(&'a self) -> Option<&'a crate::MatchQuery> {
match self {
EnumExpressionOrSubtype::AnonymousEnumExpression(val) => val.matches(),
EnumExpressionOrSubtype::EnumDefinition(val) => val.matches(),
EnumExpressionOrSubtype::EnumExpression(val) => val.matches(),
}
}
fn concepts<'a>(&'a self) -> Option<impl poly_containers::SeqRef<'a, crate::uriorcurie>> {
Expand All @@ -7062,6 +7077,7 @@ impl EnumExpression for crate::EnumExpressionOrSubtype {
val.concepts().map(|x| x.to_any())
}
EnumExpressionOrSubtype::EnumDefinition(val) => val.concepts().map(|x| x.to_any()),
EnumExpressionOrSubtype::EnumExpression(val) => val.concepts().map(|x| x.to_any()),
}
}
}
Expand Down
Loading
Loading