From 766bc7143cd08c3d0b5a15fc3631766a39e43e2f Mon Sep 17 00:00:00 2001 From: Daniel McIlvaney Date: Thu, 25 Jun 2026 18:00:55 -0700 Subject: [PATCH 1/3] chore(release): add release tools to mage --- .github/dependabot.yml | 14 ++ CHANGELOG.md | 48 +++++++ README.md | 12 +- cliff.toml | 81 ++++++++++++ cmd/azldev/azldev.go | 12 ++ docs/developer/README.md | 1 + docs/developer/how-to/releasing.md | 143 +++++++++++++++++++++ magefiles/magefile.go | 2 + magefiles/magerelease/magerelease.go | 183 +++++++++++++++++++++++++++ pkg/app/azldev_cli/azldev.go | 10 +- tools/README.md | 2 + tools/git-cliff/Cargo.toml | 14 ++ tools/git-cliff/src/lib.rs | 3 + 13 files changed, 523 insertions(+), 2 deletions(-) create mode 100644 CHANGELOG.md create mode 100644 cliff.toml create mode 100644 docs/developer/how-to/releasing.md create mode 100644 magefiles/magerelease/magerelease.go create mode 100644 tools/git-cliff/Cargo.toml create mode 100644 tools/git-cliff/src/lib.rs diff --git a/.github/dependabot.yml b/.github/dependabot.yml index e5704ab8..c716b17d 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -65,3 +65,17 @@ updates: update-types: - "minor" - "patch" + + # git-cliff CLI (used by 'mage changelog'), pinned via a Cargo manifest so the + # version is visible to Dependabot and security scanners. + - package-ecosystem: "cargo" + directory: "/tools/git-cliff" + schedule: + interval: "weekly" + cooldown: + default-days: 7 + groups: + dependabot-cargo-tools: + applies-to: version-updates + patterns: + - "*" diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..e223bd99 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,48 @@ +# Changelog + +All notable changes to `azldev` are documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [0.1.0] - 2026-03-18 + +First tagged preview release of `azldev`, the developer CLI for the +[Azure Linux](https://github.com/microsoft/azurelinux) distro. + +### Added + +- **Project and metadata management.** Scaffold a project with `azldev project + init` or `project new`, then parse, resolve, and query the TOML metadata + (`azldev.toml`) that defines Azure Linux. Configuration merges built-in + defaults with project and user-level (XDG) files, is fully validated, and is + published as a JSON Schema via `azldev config generate-schema`. +- **Component inspection and locking.** List and inspect components with `azldev + component list` and `component query`, and import new ones with `component + add`. Deterministic component fingerprints and per-component lock files keep + builds reproducible; `component update` refreshes them with `--check-only`, + `--bump`, freshness-based skipping, a progress bar, and upstream-staleness + detection. `component changed` and `component diff-sources` report what moved. +- **Source preparation and spec rendering.** `component prepare-sources` and + `component render` produce build-ready sources and specs through a + `mock`-based batch pipeline, synthesizing the git history that `rpmautospec` + needs and constructing dist-git from lock-file history. A rich overlay system + (spec search/replace, prepend/append lines, remove section or subpackage, file + and source replacement, per-file overlay files, and inline metadata) + customizes specs, with explicit release-calculation modes (`autorelease`, + `static`, and automatic). Source archives are fetched from lookaside caches. +- **Local package and image builds.** Build individual packages with `mock` + using `component build`, emitting RPMs and SRPMs into structured, + publish-channel-aware output directories. `azldev image` builds, customizes, + injects files into, boots, and runs LISA tests against Azure Linux images on a + local QEMU VM. +- **Package and repository queries.** Inspect binary package configuration with + `azldev package list` (including `--rpm-file`, debug-package synthesis, and + separate package/component group columns), and inspect or manage RPM + repositories with `azldev repo query`, backed by repo resources and repo-set + templates. +- **Command-line experience.** Shell completions for bash, zsh, fish, and + PowerShell; actionable hints on errors; global `--quiet`, `--verbose`, and + `--dry-run` flags with `table`, `json`, `csv`, and `markdown` output formats; + an embedded MCP server (`azldev advanced mcp`); and auto-generated CLI + reference documentation. diff --git a/README.md b/README.md index 588f4ed8..9227dc4d 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,8 @@ # Azure Linux Dev Tools +[![Go Reference](https://pkg.go.dev/badge/github.com/microsoft/azure-linux-dev-tools.svg)](https://pkg.go.dev/github.com/microsoft/azure-linux-dev-tools) +[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](./LICENSE) + Azure Linux Dev Tools is a collection of utilities useful for development of the Azure Linux distro. @@ -28,9 +31,12 @@ It supports: 1. Install `azldev`: ```console - go install github.com/microsoft/azure-linux-dev-tools/cmd/azldev@main + go install github.com/microsoft/azure-linux-dev-tools/cmd/azldev@latest ``` + To pin a specific release instead of tracking the latest, replace `@latest` + with a version tag, e.g. `@v0.1.0`. + 1. To ensure you can build using `mock` you must be a member of the `mock` group, e.g.: ```console @@ -62,6 +68,10 @@ Please see our [Contribution Guidelines](./CONTRIBUTING.md) for our project. For development setup and workflow, please consult our [Developer Guide](./docs/developer). +## Changelog + +Notable changes for each release are recorded in the [changelog](./CHANGELOG.md). + ## Getting Help Have questions, found a bug, or need a new feature? Open an issue in our [GitHub repository](https://github.com/microsoft/azure-linux-dev-tools/issues/new/choose). For guidance on how to file an issue, see [how to report issues](https://aka.ms/azurelinux-reportissues). diff --git a/cliff.toml b/cliff.toml new file mode 100644 index 00000000..67ab8256 --- /dev/null +++ b/cliff.toml @@ -0,0 +1,81 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# +# git-cliff configuration for generating azldev's CHANGELOG.md from Conventional +# Commits. Run it via `mage changelog`; see docs/developer/how-to/releasing.md. +# Reference: https://git-cliff.org +# +# The generated entries are a *draft*: a human (or Copilot) prunes and rewords +# them into user-facing notes before a release. Internal commit types (docs, +# test, chore, build, ci, style, refactor, and dependency bumps) are skipped so +# the draft starts close to user-facing. + +[changelog] +# The markdownlint-disable-file comment scopes two relaxations to CHANGELOG.md +# only (no repo-wide markdownlint config): MD013 because auto-generated entries +# are one line per commit and can exceed the line length, and MD024 because the +# Keep a Changelog format repeats '### Added'/'### Fixed' under every version. +header = """ +# Changelog + + + +All notable changes to `azldev` are documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +""" +# Per-version section in Keep a Changelog style. `version` is unset when +# rendering unreleased commits, which yields an `## [Unreleased]` heading. The +# leading blank line keeps a blank line above each version heading (markdownlint +# MD022) when the section is prepended below the file header or another version. +body = """ + +{% if version %}\ +## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }} +{% else %}\ +## [Unreleased] +{% endif %}\ +{% for group, commits in commits | group_by(attribute="group") %} +### {{ group | striptags | trim | upper_first }} +{% for commit in commits %} +- {{ commit.message | split(pat="\n") | first | upper_first | trim }}\ +{% endfor %} +{% endfor %}\ +""" +# Collapse any run of 3+ newlines left by the template into a single blank line +# so the rendered changelog has no double blank lines (markdownlint MD012). +postprocessors = [{ pattern = '\n{3,}', replace = "\n\n" }] +trim = true +footer = "" + +[git] +conventional_commits = true +# Keep non-conventional commits (e.g. early "README.md committed" bootstrap +# commits) instead of treating them as parse errors. They fall through to the +# catch-all `.*` skip parser below, so they're dropped quietly rather than +# emitting a "commit(s) skipped due to parse error(s)" warning during the +# full-history version bump. +filter_unconventional = false +# Drop commits that don't match a kept parser below (cuts internal noise). +filter_commits = true +# ...but never drop a breaking change, even if its type is skipped above. +protect_breaking_commits = true +# Only consider release tags; ignore local scratch tags like `bak/*`, `wip-*`. +tag_pattern = "v[0-9]*" +sort_commits = "oldest" +commit_parsers = [ + { message = "^feat", group = "Added" }, + { message = "^fix", group = "Fixed" }, + { message = "^perf", group = "Changed" }, + { message = "^revert", group = "Removed" }, + { message = "^refactor", skip = true }, + { message = "^docs", skip = true }, + { message = "^test", skip = true }, + { message = "^chore", skip = true }, + { message = "^build", skip = true }, + { message = "^ci", skip = true }, + { message = "^style", skip = true }, + # Skip anything that didn't match a kept type above. + { message = ".*", skip = true }, +] diff --git a/cmd/azldev/azldev.go b/cmd/azldev/azldev.go index 171c0819..11aaf2e3 100644 --- a/cmd/azldev/azldev.go +++ b/cmd/azldev/azldev.go @@ -1,6 +1,18 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +// Command azldev is a developer tool for working on the Azure Linux distro. +// +// It parses, resolves, and queries the TOML-based metadata that defines Azure +// Linux, prepares component sources for building with mock, fetches source +// archives from lookaside caches, and offers convenience utilities for +// locally building individual packages and images. +// +// Install the latest release with: +// +// go install github.com/microsoft/azure-linux-dev-tools/cmd/azldev@latest +// +// Run "azldev --help" for usage information, or see the user guide under docs/user. package main import ( diff --git a/docs/developer/README.md b/docs/developer/README.md index 1cb73514..a628ce09 100644 --- a/docs/developer/README.md +++ b/docs/developer/README.md @@ -10,6 +10,7 @@ To get started developing `azldev`, review the [Getting Started guide](./how-to/ * [Submitting Pull Requests](./how-to/pull-requests.md) * [Configuration management](./how-to/config-management.md) * [Add go tools](./how-to/add-go-tool.md) +* [Cut a release](./how-to/releasing.md) ## Reference diff --git a/docs/developer/how-to/releasing.md b/docs/developer/how-to/releasing.md new file mode 100644 index 00000000..2a753763 --- /dev/null +++ b/docs/developer/how-to/releasing.md @@ -0,0 +1,143 @@ +# How to: cut a release + +This guide covers releasing the `azldev` Go module so that +`go install ...@version` and the [pkg.go.dev][pkgsite] reference page work. + +## TL;DR + +```console +# One-time: install the changelog generator (git-cliff) +cargo binstall git-cliff # or: cargo install git-cliff --locked, or: brew install git-cliff + +# 1. Draft the changelog, curate it into user-facing notes, then PR + merge to main +mage changelog # prepends a draft ## [X.Y.Z] section to CHANGELOG.md + +# 2. Tag the release from the changelog version, then publish by pushing the tag +mage release # creates annotated tag vX.Y.Z on HEAD (does not push) + +# Undo a local tag created by mistake (before pushing) +git tag -d vX.Y.Z + +git push origin vX.Y.Z # pushing the tag is what publishes the release +``` + +Each step is explained in full under [Cut a release](#cut-a-release) below. + +## Versioning policy + +We follow [Semantic Versioning][semver] with a `v` prefix on tags +(`vMAJOR.MINOR.PATCH`). + +* **`v0.x.y` (current).** The pre-1.0 phase: the CLI and any exported Go API may + change in breaking ways on a *minor* bump. Use this while the surface is still + settling. +* **`v1.0.0` and later.** Commits to SemVer stability: breaking changes require a + major bump. +* **Major versions `>= 2`** require a `/vN` suffix on the module path + (e.g. `github.com/microsoft/azure-linux-dev-tools/v2`). Staying on `v0`/`v1` + avoids that. Don't cut a `v2.0.0` tag without first updating the module path. + +## How publishing actually works + +There is no separate "upload" step. The public [Go module proxy][proxy] and +pkg.go.dev fetch directly from this repository's Git tags: + +* `go install .../cmd/azldev@vX.Y.Z` resolves the tag through the proxy. +* `azldev version` reports the right version automatically for proxy installs — + Go embeds the module version into the binary's build info. +* pkg.go.dev renders the package doc comments plus this repo's `README.md`. Our + `LICENSE` (MIT) marks the module redistributable so docs are shown. Only the + public `cmd/` and `pkg/` packages appear; `internal/` is hidden by design. + +## Cut a release + +1. Make sure `main` is green and up to date locally. + +2. Generate and curate the changelog. Run `mage changelog` to prepend a draft + section for the next version to [`CHANGELOG.md`](../../../CHANGELOG.md), then + edit it down into user-facing notes. See [Changelog](#changelog) below. + +3. Tag the release. Once the changelog change is on `main`, run `mage release`: + it reads the version from the top `## [X.Y.Z]` heading in + [`CHANGELOG.md`](../../../CHANGELOG.md) and creates a matching annotated tag + (`vX.Y.Z`), so the tag and the changelog can't disagree. Then push the tag: + + ```console + mage release + git push origin vX.Y.Z + ``` + + `mage release` creates the tag locally but never pushes — pushing the tag is + what publishes the release. The version lives only in the git tag (there is no + version file); `azldev version` reads it from the build. To sign the tag, + create it manually with `git tag -s` instead. + + `mage release` is idempotent: if that version is already tagged it does + nothing, so the same command is safe to automate on every merge to `main`. + +4. Warm the proxy and pkg.go.dev so the new version is discoverable promptly. + This is harmless and only triggers indexing of an already-public tag: + + ```console + GOPROXY=https://proxy.golang.org go list \ + -m github.com/microsoft/azure-linux-dev-tools@v0.1.1 + ``` + + Then visit + `https://pkg.go.dev/github.com/microsoft/azure-linux-dev-tools@v0.1.1` once to + prompt the docs build. + +5. (Optional, recommended) Create a GitHub Release for the tag and paste the new + `CHANGELOG.md` section as the release notes. A GitHub Release is separate from + the Git tag, so you can add notes even to a tag that already exists. + +## Changelog + +[`CHANGELOG.md`](../../../CHANGELOG.md) at the repo root follows the +[Keep a Changelog](https://keepachangelog.com/en/1.1.0/) format. New sections are +drafted from the Conventional Commit history with +[git-cliff](https://git-cliff.org) and then curated by hand. Generate a draft +with: + +```console +mage changelog +``` + +This prepends a `## [X.Y.Z]` section (the version is inferred from the commits) +above the previous release, skipping internal commit types — docs, test, chore, +build, ci, style, refactor, and dependency bumps — per +[`cliff.toml`](../../../cliff.toml). The result is a **draft**: git-cliff emits +commit subjects, not release prose, so prune and reword the entries into +user-facing notes before committing. + +`mage changelog` is the single changelog flow — it runs identically locally and +in CI (CI just installs git-cliff first), so there is nothing to keep in sync +between the two. It needs git-cliff on your `PATH`; the version is pinned in +[`tools/git-cliff/Cargo.toml`](../../../tools/git-cliff/Cargo.toml) so Dependabot +and security scanners track it. Install it once: + +```console +cargo binstall git-cliff # or: cargo install git-cliff --locked, or: brew install git-cliff +``` + +> Tip: the generated draft is a natural place to let Copilot help rewrite commit +> subjects into concise, user-facing notes before you commit. + +## Fixing a bad release + +Proxy versions are immutable — you cannot delete or move a published version. +To withdraw one, [retract][retract] it: add a `retract` directive to `go.mod` +describing the bad version(s) and release a new patch. `go get` will then skip +the retracted versions. + +```go +// in go.mod +retract ( + v0.1.1 // contains a build-breaking bug; use v0.1.2. +) +``` + +[pkgsite]: https://pkg.go.dev/github.com/microsoft/azure-linux-dev-tools +[semver]: https://semver.org/ +[proxy]: https://proxy.golang.org/ +[retract]: https://go.dev/ref/mod#go-mod-file-retract diff --git a/magefiles/magefile.go b/magefiles/magefile.go index da8330ef..486eef79 100644 --- a/magefiles/magefile.go +++ b/magefiles/magefile.go @@ -16,6 +16,8 @@ import ( //mage:import "github.com/microsoft/azure-linux-dev-tools/magefiles/magecheckfix" //mage:import + _ "github.com/microsoft/azure-linux-dev-tools/magefiles/magerelease" + //mage:import _ "github.com/microsoft/azure-linux-dev-tools/magefiles/magemutation" //mage:import "github.com/microsoft/azure-linux-dev-tools/magefiles/magescenario" diff --git a/magefiles/magerelease/magerelease.go b/magefiles/magerelease/magerelease.go new file mode 100644 index 00000000..f81c78d1 --- /dev/null +++ b/magefiles/magerelease/magerelease.go @@ -0,0 +1,183 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +package magerelease + +import ( + "errors" + "fmt" + "os" + "os/exec" + "path/filepath" + "regexp" + "strings" + + "github.com/magefile/mage/sh" + "github.com/microsoft/azure-linux-dev-tools/magefiles/mageutil" +) + +var ( + ErrChangelog = errors.New("changelog generation failed") + ErrRelease = errors.New("release tagging failed") +) + +// Changelog generates a draft changelog section for the next release from the Conventional Commit +// history and prepends it to 'CHANGELOG.md', using git-cliff and the repo's 'cliff.toml'. +// +// The output is a *draft*: review and curate it into user-facing notes before releasing. See +// docs/developer/how-to/releasing.md. +// +// git-cliff must be installed and on PATH; this target does not install it. The same target runs +// locally and in CI (CI just installs git-cliff first), so there is a single changelog flow. +func Changelog() error { + mageutil.MagePrintln(mageutil.MsgStart, "Generating changelog draft with git-cliff...") + + cliff, err := exec.LookPath("git-cliff") + if err != nil { + return mageutil.PrintAndReturnError(gitCliffInstallHint(), ErrChangelog, err) + } + + projectDir := mageutil.AzldevProjectDir() + configPath := filepath.Join(projectDir, "cliff.toml") + changelogPath := filepath.Join(projectDir, "CHANGELOG.md") + + // Guard against double-prepending: if CHANGELOG.md already starts with the version git-cliff + // would bump to, the draft is already present. Re-running would add a duplicate section. + bumped := bumpedVersion(cliff, configPath) + + top, topErr := latestChangelogVersion() + if bumped != "" && topErr == nil && top == bumped { + mageutil.MagePrintf(mageutil.MsgInfo, + "CHANGELOG.md already has a draft for v%s; nothing to do "+ + "(run 'git restore CHANGELOG.md' to regenerate).\n", bumped) + + return nil + } + + // --bump computes the next version from the commits; --unreleased limits to commits since the + // last release tag; --prepend inserts the new section at the top of the existing changelog. + err = sh.Run(cliff, "--config", configPath, "--bump", "--unreleased", "--prepend", changelogPath) + if err != nil { + return mageutil.PrintAndReturnError("git-cliff failed to generate the changelog.", ErrChangelog, err) + } + + mageutil.MagePrintf(mageutil.MsgSuccess, + "Updated %#q. Review and curate the new section before releasing.\n", changelogPath) + + return nil +} + +// Release creates the release tag from CHANGELOG.md. It reads the version from the top +// `## [X.Y.Z]` heading and creates a matching annotated git tag (vX.Y.Z) on HEAD, so the tag and +// the changelog can never disagree. It does not push: pushing the tag is the point of no return +// for a release, so that stays an explicit step (manual locally, or a dedicated CI step). +// +// It is idempotent: if the changelog version is already tagged it does nothing, so it is safe to +// trigger on every merge to 'main' and only tags when the changelog carries a new version. Run it +// after the changelog PR has merged (see docs/developer/how-to/releasing.md). +func Release() error { + mageutil.MagePrintln(mageutil.MsgStart, "Preparing release tag from CHANGELOG.md...") + + version, err := latestChangelogVersion() + if err != nil { + return mageutil.PrintAndReturnError("Could not read the release version from CHANGELOG.md.", ErrRelease, err) + } + + tag := "v" + version + + exists, err := tagExists(tag) + if err != nil { + return mageutil.PrintAndReturnError("Could not check existing tags.", ErrRelease, err) + } + + if exists { + mageutil.MagePrintf(mageutil.MsgInfo, "Tag %#q already exists; nothing to release.\n", tag) + + return nil + } + + err = sh.Run("git", "tag", "-a", tag, "-m", tag) + if err != nil { + return mageutil.PrintAndReturnError(fmt.Sprintf("Failed to create tag %#q.", tag), ErrRelease, err) + } + + mageutil.MagePrintf(mageutil.MsgSuccess, "Created annotated tag %#q (matching CHANGELOG.md).\n", tag) + mageutil.MagePrintf(mageutil.MsgInfo, "Push it to publish the release: git push origin %s\n", tag) + mageutil.MagePrintf(mageutil.MsgInfo, "The tag is local only; to remove it before pushing: git tag -d %s\n", tag) + + return nil +} + +// latestChangelogVersion returns the version from the first `## [X.Y.Z]` heading in CHANGELOG.md, +// which is the release being prepared. +func latestChangelogVersion() (string, error) { + changelogPath := filepath.Join(mageutil.AzldevProjectDir(), "CHANGELOG.md") + + content, err := os.ReadFile(changelogPath) + if err != nil { + return "", fmt.Errorf("failed to read %#q:\n%w", changelogPath, err) + } + + // Match a Keep a Changelog version heading, e.g. "## [0.2.0] - 2026-06-25". + headingRe := regexp.MustCompile(`(?m)^##\s+\[(\d+\.\d+\.\d+)\]`) + + match := headingRe.FindStringSubmatch(string(content)) + if match == nil { + return "", fmt.Errorf("no '## [X.Y.Z]' version heading found in %#q", changelogPath) + } + + return match[1], nil +} + +// tagExists reports whether the given git tag already exists locally. +func tagExists(tag string) (bool, error) { + out, err := sh.Output("git", "tag", "--list", tag) + if err != nil { + return false, fmt.Errorf("failed to list git tags:\n%w", err) + } + + return strings.TrimSpace(out) != "", nil +} + +// gitCliffInstallHint builds the "git-cliff is missing" message, naming the version pinned in +// tools/git-cliff/Cargo.toml when it can be read. +func gitCliffInstallHint() string { + const base = "git-cliff not found on PATH. The pin lives in tools/git-cliff/Cargo.toml." + + if version := pinnedGitCliffVersion(); version != "" { + return fmt.Sprintf("%s Install it (e.g. 'cargo binstall git-cliff@%s' or 'brew install git-cliff'), "+ + "then re-run.", base, version) + } + + return base + " Install it (e.g. 'cargo binstall git-cliff' or 'brew install git-cliff'), then re-run." +} + +// bumpedVersion asks git-cliff for the version it would bump to next (without the leading "v"), +// or "" if it can't be determined (e.g. no eligible commits, or git-cliff fails). +func bumpedVersion(cliff, configPath string) string { + out, err := sh.Output(cliff, "--config", configPath, "--bumped-version") + if err != nil { + return "" + } + + return strings.TrimPrefix(strings.TrimSpace(out), "v") +} + +// pinnedGitCliffVersion returns the git-cliff version pinned in tools/git-cliff/Cargo.toml, or "" +// if it can't be determined. The pin lives there (not in Go) so Dependabot and security scanners +// can track and update it. +func pinnedGitCliffVersion() string { + cargoPath := filepath.Join(mageutil.AzldevProjectDir(), "tools", "git-cliff", "Cargo.toml") + + content, err := os.ReadFile(cargoPath) + if err != nil { + return "" + } + + match := regexp.MustCompile(`(?m)^\s*git-cliff\s*=\s*"=?(\d+\.\d+\.\d+)"`).FindStringSubmatch(string(content)) + if match == nil { + return "" + } + + return match[1] +} diff --git a/pkg/app/azldev_cli/azldev.go b/pkg/app/azldev_cli/azldev.go index 54872aa9..1f8782de 100644 --- a/pkg/app/azldev_cli/azldev.go +++ b/pkg/app/azldev_cli/azldev.go @@ -1,6 +1,11 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +// Package azldev_cli wires together and runs the azldev command-line application. +// +// It is the entry point used by the azldev command (see +// github.com/microsoft/azure-linux-dev-tools/cmd/azldev); end users should +// install and run that command rather than importing this package directly. package azldev_cli import ( @@ -18,6 +23,8 @@ import ( "github.com/microsoft/azure-linux-dev-tools/internal/app/azldev/cmds/version" ) +// Main constructs the azldev CLI application, runs it with the process +// arguments, and exits the process with the resulting status code. func Main() { // Instantiate the main CLI app instance. app := InstantiateApp() @@ -28,7 +35,8 @@ func Main() { os.Exit(ret) } -// Constructs a new instance of the main CLI application, with all subcommands registered. +// InstantiateApp constructs a new instance of the azldev CLI application with +// all subcommands registered. func InstantiateApp() *azldev.App { // Instantiate the main CLI application. app := azldev.NewApp(azldev.DefaultFileSystemFactory(), azldev.DefaultOSEnvFactory()) diff --git a/tools/README.md b/tools/README.md index b7a3fb17..b588f633 100644 --- a/tools/README.md +++ b/tools/README.md @@ -4,6 +4,8 @@ This directory contains a subdirectory for each golang-based build tool that thi We use separate modules (one per tool) to manage and isolate tools' dependencies. +> **Note:** Not every tool here is Go. [`git-cliff`](./git-cliff/) (used by `mage changelog`) is a Rust CLI, pinned via a `Cargo.toml` instead of a `go.mod` so Dependabot (cargo ecosystem) and security scanners can track and bump it. It is not installed via `go tool`; install it with `cargo`/`brew` (or in CI). + ## MCP Server (magemcp) The `magemcp` subdirectory contains an MCP (Model Context Protocol) server that exposes Mage build targets as tools for AI coding assistants. This allows AI agents to build, test, and check code quality in this repository. diff --git a/tools/git-cliff/Cargo.toml b/tools/git-cliff/Cargo.toml new file mode 100644 index 00000000..cb8a2b10 --- /dev/null +++ b/tools/git-cliff/Cargo.toml @@ -0,0 +1,14 @@ +# Pin for the git-cliff CLI used by `mage changelog` (and installed by CI). +# +# This crate is never compiled into azldev. It exists only so the git-cliff +# version is pinned in a manifest that Dependabot (cargo ecosystem) and security +# scanners can read and bump -- unlike a version hardcoded in Go. The `mage +# changelog` target reads this version for its install hint, and CI installs it. +[package] +name = "azldev-git-cliff-pin" +version = "0.0.0" +edition = "2021" +publish = false + +[dependencies] +git-cliff = "=2.13.1" diff --git a/tools/git-cliff/src/lib.rs b/tools/git-cliff/src/lib.rs new file mode 100644 index 00000000..8ad62a25 --- /dev/null +++ b/tools/git-cliff/src/lib.rs @@ -0,0 +1,3 @@ +//! Intentionally empty. This crate exists only to pin the git-cliff CLI version +//! in Cargo.toml so Dependabot and security scanners can track and bump it (see +//! Cargo.toml). Nothing here is built into azldev. From aeaa3f7871ab362953888d2eed6e345eea4ef8e5 Mon Sep 17 00:00:00 2001 From: Daniel McIlvaney Date: Fri, 26 Jun 2026 16:43:19 -0700 Subject: [PATCH 2/3] fixup! chore(release): add release tools to mage --- CHANGELOG.md | 2 ++ cliff.toml | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e223bd99..be9497af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ # Changelog + + All notable changes to `azldev` are documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), diff --git a/cliff.toml b/cliff.toml index 67ab8256..e0cb1092 100644 --- a/cliff.toml +++ b/cliff.toml @@ -61,8 +61,8 @@ filter_unconventional = false filter_commits = true # ...but never drop a breaking change, even if its type is skipped above. protect_breaking_commits = true -# Only consider release tags; ignore local scratch tags like `bak/*`, `wip-*`. -tag_pattern = "v[0-9]*" +# Only consider release tags of the form `vX.Y.Z`. +tag_pattern = '^v[0-9]+\.[0-9]+\.[0-9]+$' sort_commits = "oldest" commit_parsers = [ { message = "^feat", group = "Added" }, From 04d8febb1bbf5ef4de254dc2f835875f1eeee678 Mon Sep 17 00:00:00 2001 From: Daniel McIlvaney Date: Fri, 26 Jun 2026 16:55:21 -0700 Subject: [PATCH 3/3] fixup! chore(release): add release tools to mage --- magefiles/magerelease/magerelease.go | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/magefiles/magerelease/magerelease.go b/magefiles/magerelease/magerelease.go index f81c78d1..bfa0c8d6 100644 --- a/magefiles/magerelease/magerelease.go +++ b/magefiles/magerelease/magerelease.go @@ -32,9 +32,16 @@ var ( func Changelog() error { mageutil.MagePrintln(mageutil.MsgStart, "Generating changelog draft with git-cliff...") - cliff, err := exec.LookPath("git-cliff") - if err != nil { - return mageutil.PrintAndReturnError(gitCliffInstallHint(), ErrChangelog, err) + // git-cliff is an external (non-Go) tool installed separately, so invoke it by name and let + // PATH resolution happen at execution time (the repo forbids exec.LookPath for tool checks). + // Probe it once up front so a missing binary surfaces an actionable install hint. + const cliff = "git-cliff" + if _, err := sh.Output(cliff, "--version"); err != nil { + if errors.Is(err, exec.ErrNotFound) { + return mageutil.PrintAndReturnError(gitCliffInstallHint(), ErrChangelog, err) + } + + return mageutil.PrintAndReturnError("git-cliff is installed but failed to run.", ErrChangelog, err) } projectDir := mageutil.AzldevProjectDir() @@ -56,7 +63,7 @@ func Changelog() error { // --bump computes the next version from the commits; --unreleased limits to commits since the // last release tag; --prepend inserts the new section at the top of the existing changelog. - err = sh.Run(cliff, "--config", configPath, "--bump", "--unreleased", "--prepend", changelogPath) + err := sh.Run(cliff, "--config", configPath, "--bump", "--unreleased", "--prepend", changelogPath) if err != nil { return mageutil.PrintAndReturnError("git-cliff failed to generate the changelog.", ErrChangelog, err) }