feat(cli): Add component identity command#48
feat(cli): Add component identity command#48dmcilvaney wants to merge 1 commit intomicrosoft:mainfrom
Conversation
There was a problem hiding this comment.
Pull request overview
Adds a new azldev component identity subcommand intended to compute deterministic “identity fingerprints” for selected components, to support detecting rebuild needs between commits.
Changes:
- Introduces a new
component identityCobra command and wires it into thecomponentcommand tree. - Implements parallel per-component identity computation (including source identity + “Affects:” commit counting inputs).
- Regenerates auto-generated CLI reference docs to include the new subcommand.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 6 comments.
| File | Description |
|---|---|
| internal/app/azldev/cmds/component/identity.go | New CLI command + implementation for computing component identity fingerprints in parallel. |
| internal/app/azldev/cmds/component/component.go | Registers the new identity subcommand under azldev component. |
| docs/user/reference/cli/azldev_component.md | Auto-generated CLI index updated to list component identity. |
| docs/user/reference/cli/azldev_component_identity.md | Auto-generated CLI doc page for the new subcommand. |
| "github.com/microsoft/azure-linux-dev-tools/internal/app/azldev" | ||
| "github.com/microsoft/azure-linux-dev-tools/internal/app/azldev/core/components" | ||
| "github.com/microsoft/azure-linux-dev-tools/internal/app/azldev/core/sources" | ||
| "github.com/microsoft/azure-linux-dev-tools/internal/fingerprint" | ||
| "github.com/microsoft/azure-linux-dev-tools/internal/projectconfig" | ||
| "github.com/microsoft/azure-linux-dev-tools/internal/providers/sourceproviders" |
There was a problem hiding this comment.
This file imports github.com/microsoft/azure-linux-dev-tools/internal/fingerprint, but there is no internal/fingerprint package in the repo (so this won’t compile). Please either add the missing package in this PR or update the import/usage to the correct existing package that provides identity/fingerprint computation.
| Use this with 'component diff-identity' to determine which components need | ||
| rebuilding between two commits.`, |
There was a problem hiding this comment.
The help text references component diff-identity, but there is no such command in the codebase (the only match is this string). Either add that command in the same feature set, or adjust the wording to reference an existing workflow/command so users aren’t directed to a non-existent subcommand.
| Use this with 'component diff-identity' to determine which components need | |
| rebuilding between two commits.`, | |
| Compare identity outputs from two commits (for example, using diff or JSON | |
| tooling) to determine which components need rebuilding between those commits.`, |
|
|
||
| repo, err := sources.OpenProjectRepo(configFile.SourcePath()) | ||
| if err != nil { | ||
| slog.Debug("Could not open project repo for Affects commits; defaulting to 0", | ||
| "component", componentName, "error", err) |
There was a problem hiding this comment.
sources.OpenProjectRepo is called here, but no such exported function exists in internal/app/azldev/core/sources (only an unexported openProjectRepo helper exists in synthistory.go). As written this won’t compile; consider exporting a helper from sources (or duplicating the small PlainOpenWithOptions logic here) so identity can open the project repo reliably.
| // countAffectsCommits counts the number of "Affects: <componentName>" commits in the | ||
| // project repo. Returns 0 if the count cannot be determined (e.g., no git repo). | ||
| func countAffectsCommits(config *projectconfig.ComponentConfig, componentName string, | ||
| ) int { | ||
| configFile := config.SourceConfigFile | ||
| if configFile == nil || configFile.SourcePath() == "" { | ||
| return 0 | ||
| } | ||
|
|
||
| repo, err := sources.OpenProjectRepo(configFile.SourcePath()) | ||
| if err != nil { | ||
| slog.Debug("Could not open project repo for Affects commits; defaulting to 0", | ||
| "component", componentName, "error", err) | ||
|
|
||
| return 0 | ||
| } | ||
|
|
||
| commits, err := sources.FindAffectsCommits(repo, componentName) | ||
| if err != nil { | ||
| slog.Debug("Could not count Affects commits; defaulting to 0", | ||
| "component", componentName, "error", err) | ||
|
|
||
| return 0 | ||
| } | ||
|
|
||
| return len(commits) | ||
| } |
There was a problem hiding this comment.
countAffectsCommits walks the full git history once per component via sources.FindAffectsCommits, making this command O(#components × #commits). On large repos/components this can become very slow. Consider scanning the git log once per run and building a map[componentName]int (or map[string]struct{count int}) for all Affects: trailers, then looking up counts per component.
| // component's source by delegating to [sourceproviders.SourceManager.ResolveSourceIdentity]. | ||
| func resolveSourceIdentityForComponent( | ||
| env *azldev.Env, comp components.Component, | ||
| ) (string, error) { | ||
| distro, err := sourceproviders.ResolveDistro(env, comp) | ||
| if err != nil { | ||
| return "", fmt.Errorf("resolving distro for component %#q:\n%w", | ||
| comp.GetName(), err) | ||
| } | ||
|
|
||
| // A new source manager is created per component because each may reference a different | ||
| // upstream distro. | ||
| srcManager, err := sourceproviders.NewSourceManager(env, distro) | ||
| if err != nil { | ||
| return "", fmt.Errorf("creating source manager for component %#q:\n%w", | ||
| comp.GetName(), err) | ||
| } | ||
|
|
||
| identity, err := srcManager.ResolveSourceIdentity(env.Context(), comp) | ||
| if err != nil { | ||
| return "", fmt.Errorf("resolving source identity for %#q:\n%w", | ||
| comp.GetName(), err) | ||
| } | ||
|
|
||
| return identity, nil |
There was a problem hiding this comment.
sourceproviders.SourceManager (and the concrete returned by NewSourceManager) does not define a ResolveSourceIdentity method anywhere in the repo, so this call won’t compile. If source identity is required for fingerprints, add a real API for it in sourceproviders (and implement it), or switch to an existing mechanism already used for source resolution.
| // component's source by delegating to [sourceproviders.SourceManager.ResolveSourceIdentity]. | |
| func resolveSourceIdentityForComponent( | |
| env *azldev.Env, comp components.Component, | |
| ) (string, error) { | |
| distro, err := sourceproviders.ResolveDistro(env, comp) | |
| if err != nil { | |
| return "", fmt.Errorf("resolving distro for component %#q:\n%w", | |
| comp.GetName(), err) | |
| } | |
| // A new source manager is created per component because each may reference a different | |
| // upstream distro. | |
| srcManager, err := sourceproviders.NewSourceManager(env, distro) | |
| if err != nil { | |
| return "", fmt.Errorf("creating source manager for component %#q:\n%w", | |
| comp.GetName(), err) | |
| } | |
| identity, err := srcManager.ResolveSourceIdentity(env.Context(), comp) | |
| if err != nil { | |
| return "", fmt.Errorf("resolving source identity for %#q:\n%w", | |
| comp.GetName(), err) | |
| } | |
| return identity, nil | |
| // component's source. Currently this is derived solely from the component name to avoid | |
| // relying on unresolved source provider APIs. | |
| func resolveSourceIdentityForComponent( | |
| env *azldev.Env, comp components.Component, | |
| ) (string, error) { | |
| if comp == nil { | |
| return "", fmt.Errorf("resolving source identity:\n%v", "component is nil") | |
| } | |
| // Use the component name as a stable, deterministic identity. This avoids depending on | |
| // a non-existent [sourceproviders.SourceManager.ResolveSourceIdentity] API while still | |
| // providing a repeatable identifier for fingerprinting. | |
| return comp.GetName(), nil |
| // ComputeComponentIdentities computes fingerprints for all selected components. | ||
| func ComputeComponentIdentities( | ||
| env *azldev.Env, options *IdentityComponentOptions, | ||
| ) ([]ComponentIdentityResult, error) { | ||
| resolver := components.NewResolver(env) | ||
|
|
||
| comps, err := resolver.FindComponents(&options.ComponentFilter) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("failed to resolve components:\n%w", err) | ||
| } | ||
|
|
||
| distroRef := env.Config().Project.DefaultDistro | ||
|
|
||
| // Resolve the distro definition (fills in default version for the fingerprint). | ||
| distroRef, err = resolveDistroForIdentity(env, distroRef) | ||
| if err != nil { | ||
| slog.Debug("Could not resolve distro", "error", err) | ||
| } | ||
|
|
||
| return computeIdentitiesParallel( | ||
| env, comps.Components(), distroRef, | ||
| ) | ||
| } |
There was a problem hiding this comment.
This new component identity behavior is substantial (parallel execution, source identity resolution, git-history-derived inputs) but there are no unit tests added alongside it. Other component commands in this folder have focused tests; please add tests covering at least: component selection/filtering, stable output ordering, and error propagation/cancellation on a failing component (using in-memory FS / in-memory git as applicable).
No description provided.