Skip to content

feat: expose selected version to formulas - #178

Open
MeteorsLiu wants to merge 1 commit into
xgo-dev:mainfrom
MeteorsLiu:feat/target-version
Open

feat: expose selected version to formulas#178
MeteorsLiu wants to merge 1 commit into
xgo-dev:mainfrom
MeteorsLiu:feat/target-version

Conversation

@MeteorsLiu

Copy link
Copy Markdown
Collaborator

Summary

  • expose LLARs selected raw version/ref as target.version
  • inject the version into each Formula clone alongside the target matrix
  • preserve tags, slash-containing refs, and commit hashes without normalization
  • regenerate the Formula ixgo export with qexp from ixgo v1.1.6

Testing

  • go test -ldflags="-checklinkname=0" ./formula -count=1
  • go test -ldflags="-checklinkname=0" ./internal/ixgo -count=1
  • go test -ldflags="-checklinkname=0" ./internal/formula -count=1
  • go test -ldflags="-checklinkname=0" ./internal/modules -count=1

@codecov

codecov Bot commented Aug 26, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@fennoai fennoai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: target.version auto-property

The change is well-structured and well-tested. The matrixTargetformulaTarget rename is clean (no stale references remain), the new Version() accessor is documented, Target() keeps its defensive copy, and both cache paths in at() inject identically. The new TestFormulaModule_AtInjectsTargetVersion gives solid end-to-end coverage of target.version flowing into filter, onRequire, and onBuild. Build and package tests pass.

No blocking issues. The findings below are minor/optional cleanups.

A note on the hot path (pre-existing, not introduced here): at() re-walks and re-parses every formula file via findMaxFromVer on each lookup, and formula.Clone re-runs the interpreted Main() under the module mutex. This PR only makes that path slightly hotter by adding one reflection write. Worth keeping in mind if formula resolution ever becomes a bottleneck, but out of scope for this change.

Additional findings

  • internal/modules/target.go:46: [P2] Duplicate setValue helper risks silent divergence: This setValue is byte-for-byte identical to the one in internal/formula/utils.go. These are the trickiest helpers in the codebase (unsafe unexported-field access, nil→zero semantics), and two copies can silently drift — if one later changes how nil or the unexported field is handled, injectTarget would no longer match the loader's semantics. Consider exporting SetValue/ValueOf from internal/formula (or a shared internal package) and reusing it here.
  • internal/modules/target.go:28: [P3] Options seeded from DefaultOptions is subtle; add a comment: effective.Options is initialized from maps.Clone(target.DefaultOptions), discarding whatever target.Options already held, then target.DefaultOptions is cloned again on the next line. Today Defaults() is the only Options mutator and keeps both fields in lockstep, so the behavior is correct — but the intent is non-obvious (target.Options is what the DSL reads back, DefaultOptions is a separate axis), and the double maps.Clone of DefaultOptions is redundant when it is non-empty. A one-line comment ("seed active options from defaults, then overlay the build matrix") plus collapsing the double clone would help. If a future DSL setter ever writes Options independently, this line would silently drop it.

Comment thread formula/classfile.go
Comment on lines +139 to +147
func (p *ModuleF) Target() formulaTarget {
return formulaTarget{
version: p.targetVersion,
m: Matrix{
Require: maps.Clone(p.target.Require),
Options: maps.Clone(p.target.Options),
},
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P3] Target() lacks a doc comment on its defensive-copy contract

Unlike Version()/Require()/Options(), Target() has no doc comment. It returns a defensive copy (map-level maps.Clone of Require/Options, dropping DefaultOptions), which TestModuleF_TargetReturnsMatrixCopy relies on. Note the clone is shallow: the inner []string slices are shared, so in-place element mutation (Target().Options()["k"][0] = ...) would still reach internal state — the test only covers whole-slice reassignment and new keys. A short doc note clarifying the copy contract (and its shallowness) would prevent misreads.

for key, values := range matrix.Options {
effective.Options[key] = slices.Clone(values)
}
setValue(structElem, "targetVersion", version)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P3] target.version re-exposes an untrusted upstream string to the DSL

version is written into the formula struct with no validation. It crosses a trust boundary: it can originate from git tags/refs discovered on a remote repository (attacker-controlled) or MVS-resolved dependency versions. Once exposed via target.version, formula authors commonly forward it into sinks such as deps.require "owner/mod", target.version and ctx.setMetadata target.version, which reach VCS refs and filesystem paths (the MkdirTemp path already escapes /). The module path is validated elsewhere, but the version is not at this injection point. This is defense-in-depth — the value already flowed through the same channels before this PR — but it is worth documenting that target.version is not trusted input so downstream sinks validate/escape it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant