CVOYA graph Build System Guide

This document explains the CVOYA graph build configurations, version management, and development workflows.

πŸš€ Quick Start

Required Packages

To get started, install the Neo4j provider package (required):

dotnet add package Cvoya.Graph.Neo4j

Optionally, add the analyzers package for extra compile-time validation (recommended):

dotnet add package Cvoya.Graph.Analyzers

Essential Commands

# Development build (fastest, project references)
dotnet build --configuration Debug

# Test package references locally
dotnet msbuild eng/PackageValidation.proj -target:Validate

# Cut a release (--plan previews the tag without pushing)
./scripts/release.sh 1.2.3

# Build one production package (pack builds first by default)
dotnet pack src/Graph/Graph.csproj --configuration Release

# Run performance benchmarks
./scripts/run-benchmarks.sh

# Run local CodeQL analysis
./scripts/run-codeql.sh

πŸ“¦ Build Configurations

CVOYA graph uses four distinct build configurations optimized for different scenarios:

Configuration Project Refs Optimizations Packages VERSION Required Use Case
Debug βœ… Yes ❌ No ❌ No ❌ No Development & debugging
Benchmark βœ… Yes βœ… Yes ❌ No ❌ No Performance testing
LocalFeed βœ… Yes βœ… Yes βœ… Yes ❌ No Local package testing
Release βœ… Default βœ… Yes Via pack βœ… Yes Production package builds

Configuration Details

Debug Configuration

  • Purpose: Day-to-day development
  • Speed: Fastest builds (no optimizations)
  • Dependencies: Direct project references
  • Packages: None generated
  • Usage: dotnet build --configuration Debug

Benchmark Configuration

  • Purpose: Performance testing and benchmarking
  • Speed: Optimized builds with project references
  • Dependencies: Direct project references (fast rebuilds)
  • Packages: None generated
  • Usage: dotnet msbuild -target:BuildBenchmark

LocalFeed Configuration

  • Purpose: Building the local package set before package-reference validation
  • Speed: Optimized builds with fast project references
  • Dependencies: Direct project references (fast builds)
  • Packages: Generated in the configured package output directory
  • Usage: dotnet build --configuration LocalFeed

Release Configuration

  • Purpose: Production builds and publishing
  • Speed: Fully optimized
  • Dependencies: Project references by default; the package-validation orchestrator explicitly sets UsePackageReferences=true
  • Packages: Generated by dotnet pack; a normal Release build does not pack
  • Usage: dotnet pack src/Graph/Graph.csproj --configuration Release

🏷️ Version Management

CVOYA graph releases are tag-authoritative: the pushed tag is the version. Nothing in CI writes or stamps a version, which is what keeps package versions consistent and prevents accidental releases.

Version Scheme

MAJOR.MINOR.PATCH[-(alpha|beta|rc).YYYYMMDD[.N]]
# Stable release
1.2.3

# Date-anchored pre-releases (the .1 is a same-day counter)
1.2.3-alpha.20260716
1.2.3-rc.20260716.1

Creating Releases

scripts/release.sh computes the version, pushes the tag, and release.yml reads it back β€” nothing writes a version to a file, so nothing can be stamped twice.

# Preview the computed tag without pushing anything
./scripts/release.sh 1.2.3 --pre alpha --plan

# Cut a stable release  -> v1.2.3
./scripts/release.sh 1.2.3

# Cut a date-anchored pre-release  -> v1.2.3-alpha.20260716
./scripts/release.sh 1.2.3 --pre alpha

# ...and make it the current Latest on GitHub
./scripts/release.sh 1.2.3 --pre alpha --latest

The base version is positional and pre-release suffixes come from --pre β€” they are never typed by hand. See ./scripts/release.sh --help for the full option list, and release-process.md for the full process.

The VERSION file

VERSION is the development default for untagged local and CI builds, not the release source of truth. Editing it does not cut a release, and cutting a release does not edit it. release.yml passes the tag’s version to the pack job as GRAPH_RELEASE_VERSION, which Directory.Build.props prefers over the file.

Publishing a Release

Publishing is entirely tag-triggered β€” there is no manual publish step:

  1. Run ./scripts/release.sh X.Y.Z [--pre alpha|beta|rc] from a checkout whose HEAD is on origin/main. It computes the version, runs the pre-flight checks, and pushes the vX.Y.Z tag.
  2. .github/workflows/release.yml reads the version off the tag and rejects it if it doesn’t match the version scheme, runs the full test suite including the Neo4j and Apache AGE provider tests, packs every package, publishes to NuGet using Trusted Publishing (OIDC β€” no stored API key), attests build provenance, and creates the GitHub Release with generated notes.
  3. release.sh watches that run and then verifies every published package actually resolves on nuget.org before reporting success.

See docs/release-process.md for the full process, including the one-time NuGet Trusted Publishing portal setup.

πŸ”„ Development Workflows

Standard Development Workflow

# 1. Regular development (fastest)
dotnet build --configuration Debug

# 2. Run tests
dotnet test --configuration Debug

# 3. Performance testing when needed
dotnet build --configuration Benchmark
./scripts/run-benchmarks.sh

Local CodeQL Analysis

CVOYA graph’s GitHub workflow runs CodeQL for GitHub Actions, C#, and Ruby with the security-and-quality query suite. To catch those findings before pushing, install the CodeQL CLI and run:

./scripts/run-codeql.sh

The script writes one SARIF file per language under artifacts/codeql/results/. It downloads the Actions, C#, and Ruby query packs by default so local scans use the same query suite as .github/workflows/codeql.yml. The default CodeQL build mode is none, which is the required portable local gate. In that mode, the script analyzes a disposable source copy and temporary databases outside the checkout so CodeQL dependency probing cannot rewrite repository files.

To trace the same LocalFeed and Release builds used by the GitHub workflow, use manual build mode:

./scripts/run-codeql.sh --build-mode manual

Manual mode is optional because it relies on CodeQL compiler tracing support for the local platform and .NET toolchain. If manual mode completes the build but CodeQL exits with β€œcould not process any” C# source, rerun the default command without --build-mode manual. A successful default run satisfies the local CodeQL gate; capture the manual-mode CodeQL version and db/csharp/log path in PR notes only if the manual failure is relevant.

For a stricter local gate:

./scripts/run-codeql.sh --fail-on-alerts

To include CodeQL in the full build-system validation pass:

./scripts/validate-build.sh --codeql

Release Preparation Workflow

# 1. Verify the release build works
dotnet build --configuration Release

# 2. Run the full test suite
dotnet test --configuration Release

# 3. Preview the tag that would be cut
./scripts/release.sh 1.2.3 --plan

# 4. Cut it β€” release.sh pushes the tag, watches release.yml, and verifies
#    the published packages resolve on nuget.org
./scripts/release.sh 1.2.3

release.sh requires a clean checkout exactly at the refreshed origin/main commit; it refuses stale, divergent, or dirty state. Steps 1 and 2 are a local smoke test β€” release.yml reruns the full suite against the tagged commit regardless.

Package Testing Workflow

Package-reference validation has one repository-level owner:

dotnet msbuild eng/PackageValidation.proj -target:Validate

The orchestrator starts from clean repository-owned state, restores and builds the LocalFeed package set, explicitly packs the analyzer projects, verifies the exact nine-package inventory and every packaged assembly’s version metadata with scripts/verify-package-set.sh, and then restores/builds the solution with UsePackageReferences=true. eng/package-validation.NuGet.config maps Cvoya.* to the generated feed and everything else to NuGet.org.

All package, feed, global-packages, HTTP-cache, scratch, and plugin-cache paths live under artifacts/package-validation/. The workflow does not add or remove user-level NuGet sources and does not read, clear, or write the user’s global NuGet caches.

The inventory check requires bash and jq on PATH; this is the same toolchain used by the repository scripts on Linux, macOS, and Git-for-Windows/GitHub-hosted Windows environments. All path construction and project classification inside MSBuild use OS-native normalized paths.

The compatibility wrappers delegate to the same orchestrator:

./scripts/setup-local-feed-msbuild.sh
./scripts/cleanup-local-feed.sh

πŸ› οΈ Available MSBuild Targets

Version Management

# Cut a release (pushes the tag; --plan to preview)
./scripts/release.sh X.Y.Z [--pre alpha|beta|rc]

# Show the version the current tree would build as
dotnet msbuild -target:ShowVersion

Build Commands

# Build specific configurations
dotnet build --configuration Debug
dotnet build --configuration Benchmark
dotnet build --configuration Release

Local Package Testing

# Pack and verify the repository-scoped local feed
dotnet msbuild eng/PackageValidation.proj -target:PrepareLocalFeed

# Build the solution with locally produced package references
dotnet msbuild eng/PackageValidation.proj -target:BuildWithPackageReferences

# Run the complete required gate
dotnet msbuild eng/PackageValidation.proj -target:Validate

# Remove only repository-scoped validation state
dotnet msbuild eng/PackageValidation.proj -target:Clean

Cleanup Commands

# Clean build outputs
dotnet clean

πŸ“ Directory Structure

graphmodel/
β”œβ”€β”€ VERSION                    # Development default version for untagged builds
β”œβ”€β”€ Directory.Build.props      # MSBuild configuration
β”œβ”€β”€ eng/
β”‚   β”œβ”€β”€ PackageValidation.proj        # Local package-validation orchestrator
β”‚   β”œβ”€β”€ PackageVersionVerifier/        # Reads version metadata from packed DLLs
β”‚   └── package-validation.NuGet.config
β”œβ”€β”€ artifacts/package-validation/     # Repository-scoped feed, packages, and caches
└── scripts/
    β”œβ”€β”€ release.sh            # Release orchestration (tag, watch, verify)
    β”œβ”€β”€ run-benchmarks.sh     # Benchmark runner
    └── cleanup-local-feed.sh # Legacy cleanup script

🚨 Error Prevention

Common Errors and Solutions

β€œVERSION file is required for package generation”

# Supply a version for this package without editing VERSION
GRAPH_RELEASE_VERSION=1.2.3 dotnet pack src/Graph/Graph.csproj --configuration Release

β€œPackage reference could not be resolved”

# Recreate the isolated feed and package-reference restore
dotnet msbuild eng/PackageValidation.proj -target:Validate

A package whose contents don’t match its version

Use the normal pack command when building and packing in one step:

GRAPH_RELEASE_VERSION=1.2.3 dotnet pack --configuration Release -o ./artifacts src/Graph/Graph.csproj

--no-build is reserved for flows that already built with the same GRAPH_RELEASE_VERSION, such as release.yml. The complete local package gate also verifies the NuGet manifest and the assembly, file, and informational versions inside every package:

dotnet msbuild eng/PackageValidation.proj -target:Validate

Build cache issues

# Solution: Clean and rebuild
dotnet clean
dotnet build --configuration Release

πŸ”§ Advanced Configuration

Custom Build Properties

# Force package generation in any configuration
dotnet build -p:ForcePackageGeneration=true

# Override package output path
dotnet pack src/Graph/Graph.csproj --configuration Release -p:PackageOutputPath=/custom/path

CI/CD Integration

Releases are handled entirely by .github/workflows/release.yml, triggered by a validated vX.Y.Z[-prerelease] tag β€” there is no CI step that stamps a version. See docs/release-process.md.

πŸ“‹ Best Practices

Development

  • Use Debug configuration for daily development
  • Use Benchmark configuration for performance testing
  • Keep VERSION file in source control
  • Test with Release configuration before publishing

Release Management

  • Preview releases with ./scripts/release.sh X.Y.Z [--pre alpha|beta|rc] --plan
  • Let release.sh generate date-anchored prerelease versions; do not type tags by hand
  • Test Release configuration before publishing
  • Never move an existing release tag; rerun its workflow or choose a new version

Performance

  • Debug: Fastest for development
  • Benchmark: Fast + optimized for testing
  • Release: Optimized production builds and explicit packing

πŸ†˜ Troubleshooting

Build Issues

  1. Check if VERSION exists for Release/package-validation builds.
  2. Clean and rebuild: dotnet clean && dotnet build.
  3. For package-reference failures, run dotnet msbuild eng/PackageValidation.proj -target:Clean, then rerun -target:Validate.

Package Issues

  1. Remove repository package-validation state: dotnet msbuild eng/PackageValidation.proj -target:Clean.
  2. Rerun the complete package gate: dotnet msbuild eng/PackageValidation.proj -target:Validate.
  3. Inspect artifacts/package-validation/; do not clear user-level NuGet caches.

Version Issues

  1. Check the resolved version: dotnet msbuild -target:ShowVersion
  2. Override it for one build: GRAPH_RELEASE_VERSION=X.Y.Z dotnet build -c Release
  3. Verify VERSION file format (single line, no extra whitespace)