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
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,9 @@ including paths, versioning rules and the selected `PROGRAMMING_LANGUAGE`,
regardless of the project's language or CI system. The selected plugin then
uses appropriate ecosystem tools: for example, GoReleaser for Go releases or
wheel and Twine for Python packages. Consult the
[plugin guides](./continuous_delivery_scripts/plugins) for language-specific
requirements; native build manifests and credentials may still be needed. See
[plugin guides](./guides/plugin-guides.md) for language-specific
requirements, including the [Go plugin guides](./guides/plugin-golang.md);
native build manifests and credentials may still be needed. See
the [project configuration guide](./DEVELOPMENT.md#project-configuration) for
the `pyproject.toml` fields required by each workflow.

Expand Down
294 changes: 273 additions & 21 deletions continuous_delivery_scripts/plugins/golang.py

Large diffs are not rendered by default.

18 changes: 14 additions & 4 deletions continuous_delivery_scripts/tag_and_release.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import logging
import sys
from pathlib import Path
from typing import TYPE_CHECKING, Optional, Tuple, Dict
from typing import TYPE_CHECKING, Optional, Tuple, Dict, List

from continuous_delivery_scripts.generate_docs import generate_documentation
from continuous_delivery_scripts.generate_news import version_project
Expand Down Expand Up @@ -57,6 +57,9 @@ def tag_and_release(mode: CommitType, current_branch: Optional[str] = None) -> N
raise ValueError("Undefined version.")
if mode == CommitType.DEVELOPMENT:
return
files_changed_by_release_preparation = get_language_specifics().prepare_release(
Path(str(configuration.get_value(ConfigurationVariable.PROJECT_ROOT))), version, is_new_version
)
# The documentation folder will be emptied when the documentation is updated
_update_documentation()
spdx_project: Optional["SpdxProject"] = None
Expand All @@ -67,7 +70,9 @@ def tag_and_release(mode: CommitType, current_branch: Optional[str] = None) -> N
# Add summaries after the documentation output has been cleared and regenerated.
spdx_project = _update_licensing_summary()
insert_licence_header(0)
_update_repository(mode, is_new_version, version, current_branch, version_elements)
_update_repository(
mode, is_new_version, version, current_branch, version_elements, files_changed_by_release_preparation
)
if is_new_version:
if get_language_specifics().should_clean_before_packaging():
_clean_repository()
Expand Down Expand Up @@ -118,14 +123,15 @@ def _update_repository(
version: str,
current_branch: Optional[str],
version_elements: Dict[str, str],
files_changed_by_release_preparation: List[Path],
) -> None:
"""Update repository with changes that happened."""
with ProjectTempClone(desired_branch_name=current_branch) as git:
git.configure_for_github()
time_str = datetime.datetime.utcnow().strftime("%Y-%m-%d %H:%M")
commit_message = f"🚀 releasing version {version} @ {time_str}" if is_new_version else "📰 Automatic changes ⚙"
if mode == CommitType.RELEASE:
_commit_release_changes(git, version, commit_message)
_commit_release_changes(git, version, commit_message, files_changed_by_release_preparation)
if is_new_version:
get_language_specifics().tag_release(
git,
Expand Down Expand Up @@ -165,8 +171,12 @@ def _add_version_changes(git: GitWrapper) -> None:
git.add(configuration.get_value(ConfigurationVariable.NEWS_DIR))


def _commit_release_changes(git: GitWrapper, version: str, commit_message: str) -> None:
def _commit_release_changes(
git: GitWrapper, version: str, commit_message: str, files_changed_by_release_preparation: List[Path]
) -> None:
logger.info(f"Committing release [{version}]...")
for path in files_changed_by_release_preparation:
git.add(path)
git.add(configuration.get_value(ConfigurationVariable.DOCUMENTATION_PRODUCTION_OUTPUT_PATH))
_add_version_changes(git)
_commit_changes(commit_message, git)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,10 @@ def should_clean_before_packaging(self) -> bool:
"""States whether the repository must be cleaned before packaging happens."""
return False

def prepare_release(self, root: Path, version: str, is_new_version: bool) -> List[Path]:
"""Prepare language-specific files for a new release; return paths to stage."""
return []

def tag_release(self, git: GitWrapper, version: str, shortcuts: Dict[str, bool]) -> None:
"""Tags release commit."""
logger.info(f"Tagging commit as release {version}")
Expand Down
4 changes: 3 additions & 1 deletion guides/automating-releases.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ news fragments used to calculate release versions.

Run from a configured Git checkout with installed dependencies, a writable
remote and the credentials required by the selected language plugin. The
[plugin documentation](https://github.com/ARMmbed/continuous-delivery-scripts/tree/main/continuous_delivery_scripts/plugins)
[plugin guide index](plugin-guides.md)
describes packaging, publication and credential requirements for each language.
Use a release type of `development`, `beta` or `release`:

Expand All @@ -33,6 +33,8 @@ does not publish a production release.
A production release updates release notes, documentation and tags, and the
plugin publishes its package or other configured output. The exact output
varies by plugin; see the [release workflow](https://github.com/ARMmbed/continuous-delivery-scripts/blob/main/.github/workflows/release.yml).
For Go repositories with several modules, see the
[Go plugin guides](plugin-golang.md).

## GitHub Actions example

Expand Down
220 changes: 220 additions & 0 deletions guides/go-multi-module.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
# Go multi-module projects with CDS

[Go plugin guides](plugin-golang.md) · [All plugin guides](plugin-guides.md)

## Overview

Continuous Delivery Scripts (CDS) releases a **project**, not individual modules.
One repository can contain several independently consumable Go modules; they all
share the version calculated by CDS and are released together. The model follows
[go-openapi/swag](https://github.com/go-openapi/swag): versioned internal
requirements describe published dependencies, while local replacements support
development before those versions exist. See also the
[go-openapi CI workflows](https://github.com/go-openapi/ci-workflows).

## Split a monolithic project

For a project currently using `module github.com/example/my-project`, extract
packages into their own module directories and give each its own `go.mod`:

```text
my-project/
├── go.mod # optional root compatibility module
├── go.work # optional workspace
├── core/go.mod
├── parser/go.mod
└── validator/go.mod
```

For example, run `go mod init github.com/example/my-project/core` from `core/`.
Repeat in each directory, using its actual published module path. A root module
can remain as a compatibility layer if it still provides the old packages; it
is not required. Update imports of moved packages yourself, or maintain
compatibility packages at the old paths. CDS does not rewrite source imports.
Check the [Go module reference](https://go.dev/ref/mod) and
[go.mod reference](https://go.dev/doc/modules/gomod-ref) when designing paths.

At the existing release `v1.2.0`, `core/go.mod` could contain:

```go
module github.com/example/my-project/core

go 1.24
```

`parser/go.mod` declares the published dependency and a local development path:

```go
module github.com/example/my-project/parser

go 1.24

require github.com/example/my-project/core v1.2.0

replace github.com/example/my-project/core => ../core
```

Go uses the local source from `../core` during module-mode development; the
`require` version describes what an external consumer will resolve. Replacements
in a dependency's `go.mod` are **not** inherited by that consumer. CDS preserves
both grouped and individual `replace` directives, and never adds or removes them.
For independent module-mode builds, provide local replacements for unpublished
internal versions. Ensure their directories exist and contain the expected
module's `go.mod`.

Alternatively, create `go.work` at the repository root:

```go
go 1.24

use (
.
./core
./parser
./validator
)
```

Omit `.` if there is no root module. An active workspace resolves its listed
modules locally, even without module-level replacements. Workspace-level
replacements take precedence over conflicting module-level replacements. CDS
reads and checks local replacements in `go.work` but does not modify it.
See the [Go workspace tutorial](https://go.dev/doc/tutorial/workspaces).
To test a module without workspace resolution, run `GOWORK=off go test ./...`
from that module's directory; module-level replacements remain active.

## Coordinated release: v1.2.0 → v1.3.0

Suppose `parser` depends on `core`, and `validator` depends on both. Before
release preparation, `validator/go.mod` contains:

```go
module github.com/example/my-project/validator

go 1.24

require (
github.com/example/my-project/core v1.2.0
github.com/example/my-project/parser v1.2.0 // indirect
github.com/external/library v1.4.0
)

replace (
github.com/example/my-project/core => ../core
github.com/example/my-project/parser => ../parser
)
```

CDS calculates the next **project** version `1.3.0` using its existing release
process.

### Module selection and eligibility

For a new version, the Go plugin uses the same module selection as
Go release tagging: the configured `SOURCE_DIR` and project root, plus modules
listed in `go.work` if present; otherwise, nested modules under `SOURCE_DIR`.
Set `SOURCE_DIR = "."` for the layout above, or list modules outside the source
directory in a root or source `go.work`. CDS does not scan other project-root
subdirectories for modules. It reads each selected `module` declaration using
Go tooling and matches `require` paths **exactly** against those declarations.
Modules need not be named after their directories or share a path prefix. A
duplicate declaration or malformed file among selected modules stops
preparation with an error.

CDS updates an internal requirement only when the requiring module has a
`replace` pointing to that exact module **inside this repository**, or the
requiring module is listed in `go.work use` and the dependency is either also
listed there or mapped to a repository-local module by a workspace `replace`.
A version-specific replacement must cover the upcoming version. Otherwise the
requirement stays unchanged, even if its module path is internal. Add local
resolution and rerun preparation before publication if that requirement must
reference the new release version.

### Result

After preparation, `parser/go.mod` requires `core v1.3.0`; the two internal
requirements in `validator/go.mod` become:

```go
require (
github.com/example/my-project/core v1.3.0
github.com/example/my-project/parser v1.3.0 // indirect
github.com/external/library v1.4.0
)
```

The local replacements, external dependency, annotations, module and Go
directives remain intact. Files without eligible outdated requirements are not
rewritten. CDS stages only changed `go.mod` files with its normal release
changes. The existing tagging, changelog, versioning and publication steps
continue without module-specific release scheduling. The project release tag
is `v1.3.0`; subdirectory modules can also receive path-prefixed tags such as
`core/v1.3.0`, `parser/v1.3.0` and `validator/v1.3.0`, all on the same commit.

Go's `go mod edit` changes `require` versions without downloading the new
module versions. Local replacements or the active workspace allow testing
before those versions are published. CDS does not run `go get` or `go mod tidy`
and does not fabricate `go.sum` entries. If release validation runs outside
workspace mode, ensure each unpublished dependency has a local replacement.

## Validate development and publication

From each relevant module, run `go test ./...` with the workspace or valid
local replacements enabled. Inspect requirements and replacements with
[`go mod edit -json`](https://go.dev/ref/mod#go-mod-edit); from the workspace
directory, inspect its configuration with
[`go work edit -json`](https://go.dev/ref/mod#go-work-edit).

After publication, test actual published resolution in a **separate** consumer
directory, outside the original workspace and without local replacements:

```bash
mkdir release-check
cd release-check
go mod init example.com/release-check
go get github.com/example/my-project/validator@v1.3.0
go mod verify
go list -m all
go test ./...
```

`go test ./...` is meaningful once the consumer contains a Go package that
imports the released module. This check catches issues hidden by local source
resolution.

## Major-version migration

Go generally requires a matching `/v2` (or higher) suffix on module paths
and imports for major version 2 and above. For example:

```go
module github.com/example/my-project/core/v2
```

CDS updates eligible, compatible internal dependencies, such as `v2.1.0` →
`v2.2.0` for `core/v2`. A transition from an unsuffixed `core` at `v1` to `v2.0.0`
requires manual migration: change module declarations, source imports,
internal requirement paths and relevant replacements before preparing the
release. For an eligible internal requirement, CDS warns when it detects a
major-version transition requiring this migration and refuses to write an
incompatible version to `go.mod`. It does not migrate source code or release
modules independently. See
[Developing a major version update](https://go.dev/doc/modules/major-version).

## Troubleshooting

- **A future version cannot be fetched:** Use a valid local `replace` or a
workspace listing the sibling module during preparation and testing.
- **A `go.mod` was not changed:** Check that CDS selects both module directories,
that the `require` path matches the sibling's declaration exactly, and that
its replacement or workspace resolution stays inside the repository.
- **A local replacement fails:** Confirm the target directory contains the
correct module's `go.mod`. Workspace `use` entries also need existing
module directories.
- **A consumer fails after publishing:** Local replacements do not propagate
to consumers. Test with the isolated consumer above and inspect the released
requirements and module tags.
- **A major-version release fails:** Update semantic import paths and imports
manually before running CDS again.

Additional reading: [Go module version numbering](https://go.dev/doc/modules/version-numbers).
8 changes: 6 additions & 2 deletions guides/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ the ecosystem tools used for project-specific steps. The tools run in a Git
checkout with a Python environment; they are not tied to one CI provider.
The [API reference](https://armmbed.github.io/continuous-delivery-scripts/api.html) covers
the implementation, while these pages focus on inputs, outputs and examples.
For language-specific packaging, credentials and metadata support, consult
the [plugin guides](https://github.com/ARMmbed/continuous-delivery-scripts/tree/main/continuous_delivery_scripts/plugins).
For language-specific packaging, credentials and metadata support, start with
the [plugin guide index](plugin-guides.md).

## Dependency licences and OpenChain workflows

Expand All @@ -28,6 +28,10 @@ the [plugin guides](https://github.com/ARMmbed/continuous-delivery-scripts/tree/
- [Generate a changelog](managing-changelogs.md): Build release notes from news fragments.
- [Automate releases](automating-releases.md): Tag and publish with a language plugin.

## Plugin guides

- [Browse plugin guides](plugin-guides.md): Select the landing page for your plugin and its topic guides.

## Other project checks

- [Generate code documentation](generating-code-documentation.md): Build the API reference.
Expand Down
8 changes: 8 additions & 0 deletions guides/plugin-basic.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Basic plugin guides

Select `PROGRAMMING_LANGUAGE = "basic"` for a project that needs CDS versioning,
changelog commits and Git tags without language-specific packaging.

- [Automate releases](automating-releases.md) — The shared release workflow.
- [Preview a version](previewing-versions.md) — Inspect the proposed project version.
- [Basic plugin reference](https://github.com/ARMmbed/continuous-delivery-scripts/blob/main/continuous_delivery_scripts/plugins/BASIC.MD) — Plugin behaviour.
7 changes: 7 additions & 0 deletions guides/plugin-ci.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# CI plugin guides

Select `PROGRAMMING_LANGUAGE = "ci"` for CI configuration projects. The plugin
uses the shared Git release steps and does not package software.

- [Automate releases](automating-releases.md) — The shared release workflow.
- [CI plugin reference](https://github.com/ARMmbed/continuous-delivery-scripts/blob/main/continuous_delivery_scripts/plugins/CI.MD) — Plugin behaviour.
8 changes: 8 additions & 0 deletions guides/plugin-docker.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Docker plugin guides

Select `PROGRAMMING_LANGUAGE = "docker"` for a Docker project. The current
plugin supports CDS release orchestration; Docker image build and publication
are not implemented by this plugin.

- [Automate releases](automating-releases.md) — The shared release workflow.
- [Docker plugin API](https://armmbed.github.io/continuous-delivery-scripts/plugins/docker.html) — Current plugin capabilities.
8 changes: 8 additions & 0 deletions guides/plugin-github-actions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# GitHub Actions plugin guides

Select `PROGRAMMING_LANGUAGE = "github_actions"` for GitHub Actions workflow
projects. This plugin extends the CI release steps with GitHub release actions
and requires the GitHub CLI.

- [Automate releases](automating-releases.md) — The shared release workflow.
- [GitHub Actions plugin reference](https://github.com/ARMmbed/continuous-delivery-scripts/blob/main/continuous_delivery_scripts/plugins/GITHUB_ACTIONS.MD) — Prerequisites and behaviour.
11 changes: 11 additions & 0 deletions guides/plugin-golang.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Go plugin guides

Select `PROGRAMMING_LANGUAGE = "golang"` for a Go project. The plugin uses
GoReleaser for packaging and supports Go module discovery, documentation and
dependency licence reporting.

- [Go multi-module projects](go-multi-module.md) — Split a project into modules and prepare coordinated internal dependency versions for one CDS release.
- [Automate releases](automating-releases.md) — The shared release workflow.
- [Generate code documentation](generating-code-documentation.md) — Generate Go API pages with doc2go.
- [Generate an SPDX SBOM](generating-an-spdx-sbom.md) — Report Go dependency metadata.
- [Go plugin reference](https://github.com/ARMmbed/continuous-delivery-scripts/blob/main/continuous_delivery_scripts/plugins/GOLAND.MD) — Go-specific prerequisites and configuration.
Loading
Loading