Skip to content

Latest commit

Β 

History

304 Commits

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

πŸ”§ Build Metadata Action

License Go Version

Universal GitHub Action to capture and display comprehensive metadata related to software builds across 15+ languages and build systems.

Overview

The build-metadata-action is a unified solution for extracting, processing, and reporting build metadata for projects written in Python, Java, JavaScript/TypeScript, Go, .NET, Rust, Ruby, and other languages. It consolidates functionality from language-specific metadata actions while providing standardized outputs and rich CI/CD integration.

Key Features

  • 🌐 Multi-Language Support: Python, Java (Maven/Gradle), Node.js, Go, .NET, Rust, Ruby, and more
  • πŸ“Š Rich Reporting: Generates beautiful GitHub Step Summary outputs with project and build information
  • πŸ” Version Detection: Integrates with version-extract-action for comprehensive version extraction
  • πŸ› οΈ Environment Capture: Reports CI environment, tool versions, and runtime configuration
  • πŸ“¦ Standardized Outputs: Consistent, namespaced outputs for downstream build actions
  • 🎯 Dynamic Versioning: Detects and handles dynamic versioning strategies
  • πŸ”— Monorepo Support: Handles multi-language and multi-project repositories

Supported Languages & Build Systems

Language Build Systems Version Files
Python setuptools, poetry, flit, hatch pyproject.toml, setup.py, setup.cfg
JavaScript/TypeScript npm, yarn, pnpm package.json, tsconfig.json
Java Maven, Gradle (Groovy/Kotlin) pom.xml, build.gradle, build.gradle.kts
.NET/C# MSBuild, dotnet CLI *.csproj, *.sln, *.props
Go Go modules go.mod
Rust Cargo Cargo.toml
Ruby Bundler, RubyGems *.gemspec, Gemfile
PHP Composer composer.json
Swift Swift Package Manager Package.swift
Dart/Flutter pub pubspec.yaml
Terraform/OpenTofu Terraform, OpenTofu *.tf, versions.tf
C/C++ CMake, Autoconf, Meson CMakeLists.txt, configure.ac
Scala SBT build.sbt
Elixir Mix mix.exs
Haskell Cabal *.cabal
Julia Pkg Project.toml

Usage

Basic Example

- name: Extract Build Metadata
  id: metadata
  uses: lfreleng-actions/build-metadata-action@v1
  with:
    path_prefix: .

Full Example

name: Build and Deploy

on: [push, pull_request]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: '3.13'

      - name: Extract Build Metadata
        id: metadata
        uses: lfreleng-actions/build-metadata-action@v1
        with:
          path_prefix: .
          output_format: summary
          include_environment: true
          use_version_extract: true
          verbose: false
          artifact_upload: true
          artifact_formats: json

      - name: Use Metadata in Build
        run: |
          echo "Building ${{ steps.metadata.outputs.project_name }} \
            v${{ steps.metadata.outputs.project_version }}"
          echo "Project Type: ${{ steps.metadata.outputs.project_type }}"

Multi-Language Monorepo Example

- name: Extract Python Metadata
  id: python-metadata
  uses: lfreleng-actions/build-metadata-action@v1
  with:
    path_prefix: ./python-service

- name: Extract Node.js Metadata
  id: node-metadata
  uses: lfreleng-actions/build-metadata-action@v1
  with:
    path_prefix: ./web-frontend

- name: Build Services
  run: |
    echo "Python: ${{ steps.python-metadata.outputs.project_version }}"
    echo "Node.js: ${{ steps.node-metadata.outputs.project_version }}"

Export Environment Variables Example

Use export_env_vars: true to make metadata available as environment variables in later steps:

- name: Extract Build Metadata
  id: metadata
  uses: lfreleng-actions/build-metadata-action@v1
  with:
    path_prefix: .
    export_env_vars: true

- name: Use Environment Variables
  run: |
    echo "Project: $PROJECT_NAME"
    echo "Version: $PROJECT_VERSION"
    echo "Type: $PROJECT_TYPE"
    # All outputs become uppercase environment variables
    # e.g., project_name -> PROJECT_NAME
    #       python_build_version -> PYTHON_BUILD_VERSION

Output Formats Example

Generate output in one or more formats simultaneously (comma, space, or newline-separated):

- name: Extract Build Metadata
  id: metadata
  uses: lfreleng-actions/build-metadata-action@v1
  with:
    path_prefix: .
    # You can specify one or more formats
    output_format: summary,json,markdown
    # Or with spaces: "summary json markdown"
    # Or with newlines:
    # output_format: |
    #   summary
    #   json
    #   markdown

- name: Artifact Formats
  uses: lfreleng-actions/build-metadata-action@v1
  with:
    artifact_upload: true
    artifact_formats: json,yaml
    # Uploads both JSON and YAML artifacts

Inputs

Name Required Default Description
path_prefix No . Path to the project root
project_type No "" Declare the project type instead of detecting it (e.g. java-maven, java-gradle). See Declaring the project type.
output_format No summary Output format(s): summary, json, markdown, yaml. Accepts comma-separated, space-separated, or newline-separated values. Set to empty string to disable output.
include_environment No true Include environment metadata
use_version_extract No true Use version-extract-action for version detection
verbose No false Enable verbose output
artifact_upload No true Upload gathered metadata as workflow artifacts
artifact_name_prefix No build-metadata Custom prefix for artifact names
artifact_formats No json Formats to upload as artifacts. Can be comma-separated, space-separated, or newline-separated (e.g., json, yaml, or json,yaml).
validate_output No true Check JSON/YAML output before uploading
strict_validation No true Use strict validation mode (round-trip testing)
export_env_vars No false Export all outputs as environment variables (uppercase with underscores) for use in later steps

Declaring the project type

Detection resolves the first rule that matches in priority order, and those priorities are global rather than per language. A repository carrying more than one marker file resolves to whichever ranks highest, which is not always the thing the workflow builds.

The common case is a Maven project that also has a package.json, the shape frontend-maven-plugin produces. javascript-npm outranks java-maven, so:

Output Plain Maven Maven plus package.json
project_type java-maven javascript-npm
build_tool maven npm
java_version 17 (empty)

A consumer reading java_version gets nothing and falls back to its own default, choosing a JDK the project never asked for.

Where the caller already knows, say so:

- uses: lfreleng-actions/build-metadata-action@<sha>
  with:
    project_type: java-maven

A reusable workflow dedicated to one build tool always has better information than a detector, because the caller chose that workflow.

The action reports an unrecognised value and discards it, running detection instead: detection still produces a real answer, whereas a type matching no extractor produces none at all.

Outputs

Common Outputs

All project types provide these standardized outputs:

Output Description Example
project_type Resolved project type, detected or supplied via the project_type input python-modern
build_tool Build tool the project type implies; empty when not identified maven
project_name Project/package name myproject
project_version Current version 1.2.3
project_path Absolute project path /workspace/myproject
version_source Source of version info pyproject.toml
versioning_type Versioning type: static or dynamic static
version_properties_version Version from version.properties (LF/ONAP convention); empty when absent 1.1.0
version_properties_match Whether version.properties matches project_version (empty when not comparable) true
snapshot_version Synthesized interim/development version (X.Y.Z-SNAPSHOT convention) 1.1.0-SNAPSHOT
release_files Comma-separated release request files under releases/ (global-jjb/LF convention); empty when none releases/3.8.2.yaml
release_file_count Number of release request files found under releases/ 1
is_release_ready True when at least one release request file is present under releases/ true
release_version Version parsed from a lone release file; empty when more than one exists 3.8.2
release_ref Git ref parsed from a lone release file; empty when more than one exists abc123...
build_timestamp ISO 8601 build timestamp 2025-11-03T12:00:00Z
git_sha Current git commit SHA abc123...
git_branch Current git branch main
git_tag Current git tag v1.2.3
ci_platform CI platform github
ci_run_id CI run identifier 12345678
ci_run_url URL to CI run https://github.com/...
runner_os Runner OS Linux
runner_arch Runner architecture X64
metadata_json Complete metadata as JSON {...}
success Extraction success indicator true

Language-Specific Outputs

Python

Output Description
python_version Python interpreter version
python_package_name Distribution package name
python_requires_python Required Python version range
python_build_backend Build backend (setuptools, poetry, etc.)
python_metadata_source Source file (pyproject.toml, etc.)
python_matrix_json CI matrix configuration as JSON
python_dependencies Runtime dependencies

Java (Maven)

Output Description
java_version JDK version
java_version_source JDK version source
java_group_id Maven groupId
java_artifact_id Maven artifactId
java_packaging Packaging type (jar, war, etc.)
java_has_parent Whether the POM declares a parent
java_is_multi_module Multi-module (reactor) project
java_module_count Number of reactor modules
java_frameworks Detected frameworks
java_source_dirs Main source directories
java_test_source_dirs Test source directories
java_coverage_tool Coverage tool, when configured
java_coverage_report_paths Coverage report locations

The layout outputs describe where a scanner should look. They are module-relative, so a consumer applies them per module across a reactor, and they fall back to Maven's conventions (src/main/java, src/test/java) when <build> is silent, because that is what the build itself uses.

java_coverage_tool and java_coverage_report_paths appear when the build configures coverage, and are absent otherwise. Absence is a deliberate answer: pointing a scanner at a report that is never produced is worse than telling it there is none. The action finds JaCoCo in <build><plugins>, in <build><pluginManagement>, and in declared modules β€” a reactor root often configures nothing itself and delegates to a parent module, as ONAP cps does with cps-parent/pom.xml.

The action resolves the Java level (java_version) in Maven's own precedence: the POM's maven.compiler.release, then maven.compiler.source/target, then java.version, then the maven-compiler-plugin <configuration>. When the scanned POM declares no level, the action inherits it from on-disk parent POMs (via relativePath) and, for aggregator roots, from a reactor module β€” so an ONAP-style root whose level lives in a shared *-parent module still resolves. The java_version_source output reports where the value came from (e.g. maven.compiler.release, maven-compiler-plugin/release, module:cps-parent).

Java (Gradle)

Output Description
java_version JDK version
java_version_source JDK version source
java_group_id Project group
java_artifact_id Project name
java_build_dsl Build DSL (groovy or kotlin)
java_is_multi_project Multi-project build
java_frameworks Detected frameworks
java_gradle_version Gradle version the wrapper declares
java_gradle_version_source Source of that version

For Gradle the action reads the level from the build file toolchain (JavaLanguageVersion.of(N)), then source/targetCompatibility (JavaVersion.VERSION_N or a bare/quoted literal), then gradle.properties; java_version_source reports the form detected.

java_gradle_version comes from the wrapper's distributionUrl. This is the version the project asks to build with, which is a different fact from the version a CI step provisioned: gradle/actions/setup-gradle reports what it set up itself, and sets up nothing when a build defers to the wrapper, so that output is empty for wrapper-driven projects.

The output stays empty when the project has no wrapper, or when distributionUrl names no recognisable version. A consumer comparing it against a tool's floor needs to tell "too old" from "unknown", so the action reports nothing rather than guessing.

Node.js/JavaScript

Output Description
node_version Node.js version
npm_version npm version
node_package_manager Detected package manager (npm, yarn, pnpm)
node_engines Required node/npm versions
node_workspaces Workspace packages (monorepo)

.NET/C#

Output Description
dotnet_version .NET SDK version
dotnet_framework Target framework(s)
dotnet_assembly_name Assembly name
dotnet_package_id NuGet package ID

Go

Output Description
go_base_name Friendly name from the module path (/vN suffix stripped)
go_module_path Go module path declared in go.mod
go_go_version Go version from the go directive in go.mod
go_metadata_source Source of Go metadata (go.mod)
go_toolchain Toolchain directive from go.mod (when present)
go_dependencies Direct dependencies as module@version
go_indirect_dependencies Indirect dependencies as module@version
go_dependency_count Number of direct dependencies
go_total_dependency_count Total dependencies (direct plus indirect)
go_dependency_map JSON object mapping modules to versions
go_replace_directives Replace directives as JSON array of {old, new}
go_replace_count Number of replace directives
go_exclude_directives Exclude directives (comma-separated)
go_exclude_count Number of exclude directives
go_retract_directives Retract directives (comma-separated)
go_retract_count Number of retract directives
go_frameworks Detected Go frameworks/tools (comma-separated)
go_go_version_matrix Supported (non-EOL) Go versions for testing
go_matrix_json Go version test matrix as JSON

The action derives the Go version matrix from live endoflife.date data: it selects the supported (non-EOL) Go releases at or above the version declared in go.mod. When the API is unreachable, a static fallback list of the supported releases applies instead.

Rust

Output Description
rust_version Rust compiler version
cargo_version Cargo version
rust_edition Rust edition
rust_workspace_members Workspace members

Example Output

When used in a GitHub Actions workflow, the action generates a rich step summary:

# πŸ”§ Build Metadata

## Project Information

| Key                | Value                |
| ------------------ | -------------------- |
| Project Type       | Python (Modern)      |
| Project Name       | dependamerge         |
| Project Version    | 1.2.3                |
| Version Source     | pyproject.toml       |
| Dynamic Versioning | No                   |
| Build Timestamp    | 2025-11-03T12:00:00Z |
| Git SHA            | `abc1234`            |
| Git Branch         | `main`               |

## CI Environment

| Component   | Value          |
| ----------- | -------------- |
| Platform    | github         |
| Runner OS   | Linux          |
| Runner Arch | X64            |
| Workflow    | Build and Test |
| Run Number  | 42             |

## Tool Versions

| Tool       | Version |
| ---------- | ------- |
| python     | 3.13.0  |
| pip        | 24.0    |
| setuptools | 75.0.0  |

## Language-Specific Metadata

### Python Project Details

| Key             | Value          |
| --------------- | -------------- |
| Package Name    | `dependamerge` |
| Requires Python | >=3.10         |
| Build Backend   | setuptools     |
| Metadata Source | pyproject.toml |

### Build Matrix

```json
{
  "python-version": ["3.10", "3.11", "3.12", "3.13", "3.14"]
}

βœ… Metadata extraction successful

Integration with Other Actions

With Version Extract Action

- name: Extract Metadata
  uses: lfreleng-actions/build-metadata-action@v1
  with:
    use_version_extract: true
  env:
    VERSION_EXTRACT_ACTION_PATH: /path/to/version-extract-action

With Build Actions

- name: Extract Metadata
  id: metadata
  uses: lfreleng-actions/build-metadata-action@v1

- name: Build Python Package
  uses: lfreleng-actions/python-build-action@v1
  with:
    version: ${{ steps.metadata.outputs.project_version }}
    python_version: ${{ steps.metadata.outputs.python_version }}

Advanced Features

Dynamic Versioning Support

The action detects and reports when projects use dynamic versioning:

  • Python: setuptools_scm, versioneer, PEP 621 dynamic versions
  • Node.js: semantic-release, version 0.0.0-development
  • Java: Maven properties, Gradle project version
  • Rust: 0.0.0, 0.1.0-dev versions

Monorepo Support

Automatically detects and handles monorepo structures:

  • Node.js workspaces
  • Python multi-package projects
  • Rust workspaces
  • Maven multi-module projects
  • Gradle multi-project builds

Implementation Details

Built with Go using design patterns from version-extract-action:

  • Strategy Pattern: Language-specific extractors
  • Chain of Responsibility: Sequential project type detection
  • Factory Pattern: Dynamic extractor selection
  • Configuration-Driven: YAML-based pattern definitions
  • Dynamic Version Fetching: Automatically updates version matrices from upstream sources with static fallbacks

Dynamic Version Management

To keep pace with fast-evolving language ecosystems, the action uses a dynamic + fallback strategy for version matrices:

Rust Version Detection

  • Primary: Fetches current stable version from rust-lang.org
    • Generates ~6 recent versions (9 months of releases)
    • Adapts to Rust's 6-week release cycle automatically
    • 5-second timeout prevents workflow delays
  • Fallback: Static version list (updated monthly)
    • Ensures CI/CD reliability during network issues or API downtime
    • Prevents build failures from temporary connectivity problems
    • Provides reasonable version coverage even offline

Why This Approach?

Languages like Rust, Swift, and PHP release frequently (every 6-8 weeks). Static version lists become outdated within weeks, leading to:

  • Missing security updates and new features in CI tests
  • Manual maintenance burden to keep lists current
  • Stale testing that doesn't catch real-world compatibility issues

Dynamic fetching solves this while the fallback ensures reliability.

See IMPLEMENTATION_PLAN.md for detailed architecture and design decisions.

Development

Prerequisites

  • Go 1.24 or higher
  • Git
  • (Optional) Language toolchains for testing

Building

make build

Testing

The project includes a comprehensive test suite that validates metadata extraction across all supported languages and project types.

Quick Start:

make test

Comprehensive Testing:

The GitHub Actions workflow tests the action against:

  • Real-world projects: 12+ actual open-source repositories
  • Synthetic projects: 15+ minimal generated project structures
  • All major languages: Python, JavaScript, Go, Rust, Java, PHP, Ruby, C#, Swift, Dart, Docker, Helm, Terraform, and more

Tests run in parallel using GitHub Actions matrix strategy for speed.

πŸ“š See Testing Guide for detailed information about:

  • Test architecture and strategy
  • How to add new test cases
  • Coverage across 50+ project types
  • Performance and troubleshooting

Running Locally

./build-metadata --path /path/to/project --output-format summary

Contributing

Contributions are welcome! Please see our contributing guidelines and code of conduct.

License

Apache License 2.0 - see LICENSE for details.

Related Projects

Support

For questions, issues, or feature requests, please open an issue on GitHub.

About

Universal action to capture and display metadata related to project builds

Resources

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages