Chapter 24Lesson 01~140 minutes

Monorepos, Multi-Module Builds, Generated Code, and Complex Repository Layouts: Core Concepts and Mental Model

Model repository and build boundaries explicitly so SonarQube project ownership, scanner base directories, source/test scope, generated code, report paths, and concurrent analyses remain independently verifiable.

MonoreposModulesAnalysis scopeReport pathsGovernance

Learning objectives

  • Distinguish a repository, build module, deployable component, SonarQube project, application, and portfolio instead of assuming they are the same boundary.
  • Trace repository tree/build graph → chosen project boundary → sonar.projectBaseDir → source/test scope → report paths → scanner work directory → analysis task → project result.
  • Explain why Community Build can analyze multiple explicitly modeled projects from one repository even though native monorepo onboarding/binding is an Enterprise capability.
  • Classify generated, vendored, copied, test, and first-party source before choosing exclusions.
  • Explain why parallel scanners require distinct project identities, module workspaces, report artifacts, and scanner working directories.
  • Preserve revision, indexed-file evidence, report import evidence, ceTaskId, Compute Engine status, gate state, and ownership separately.

1. The practical problem: a repository tree is not an analysis model

Chapter 23 automated SonarQube policy safely. Chapter 24 applies that discipline to repositories where one checkout contains several independently built pieces, generated source, shared libraries, nested build files, and multiple report locations. The dangerous assumption is that SonarQube will infer the intended business boundary from directory names. It does not.

You must decide which files belong to which SonarQube project, from which directory each scanner resolves relative paths, which test and coverage reports belong to that project, and whether two analyses may run concurrently. A green dashboard is not trustworthy if another module was silently omitted or the same file was analyzed twice under unrelated project keys.

repository tree + build graph → explicit ownership map → project key + projectBaseDir → sources/tests/exclusions/reports → scanner workspace → report upload → ceTaskId → project measures/gate

2. Define the boundaries before configuring them

Boundary Meaning Common mistake
Repository One SCM checkout/history graph. Assuming one repository must equal one SonarQube project.
Build module A unit understood by Maven/Gradle/.NET/another build graph. Recreating build-native module metadata manually with fragile CLI paths.
Deployable/business component A unit with an owner/release lifecycle. Using directory depth as a proxy for ownership.
SonarQube project Independent analysis history, project key, permissions, New Code state and Quality Gate. Reusing one key for unrelated modules or revisions.
Application Commercial synthetic aggregation of projects that share a lifecycle. Using it to redefine file ownership instead of aggregating already-correct projects.
Portfolio Enterprise governance view across projects/applications. Assuming aggregation repairs missing or overlapping source.

3. Mental model: tree → ownership → base directory → evidence

Two-module repository causality
flowchart TD
  R[One Git revision] --> G[Build graph]
  G --> A[api module]
  G --> W[worker module]
  A --> PA[Project key: repo-api]
  W --> PW[Project key: repo-worker]
  PA --> BA[projectBaseDir: modules/api]
  PW --> BW[projectBaseDir: modules/worker]
  BA --> RA[src/tests/coverage.xml]
  BW --> RW[src/tests/coverage.xml]
  RA --> CA[Distinct scanner work/task]
  RW --> CW[Distinct scanner work/task]
  CA --> QA[API gate/history]
  CW --> QW[Worker gate/history]
  QA --> AG[Optional aggregation]
  QW --> AG

The arrow from module to SonarQube project is a governance decision. SonarQube does not discover it from a build graph unless the scanner/build integration explicitly supplies the structure. Even then, the project key and lifecycle boundary remain yours to define.

4. sonar.projectBaseDir: the coordinate system for analysis

For SonarScanner CLI, sonar.projectBaseDir changes the analysis directory. Relative source/test paths are interpreted from that base directory, and many report/import paths are also relative to the project root/base directory unless their parameter documentation says otherwise.

repo/
  modules/
    api/
      src/
      tests/
      coverage.xml
    worker/
      src/
      tests/
      coverage.xml

If the scanner starts at repo/ but receives -Dsonar.projectBaseDir=modules/worker, then sonar.sources=src correctly targets repo/modules/worker/src. But sonar.python.coverage.reportPaths=modules/worker/coverage.xml is now wrong: that relative value is interpreted from the worker base and effectively points one module path too deep.

Reason about paths as ordered pairs. Record both base directory and relative path. A path string alone is not reproducible evidence.

5. Source and test scope are ownership declarations

sonar.sources and sonar.tests take simple file/directory paths; wildcard patterns are not allowed for the initial roots. Inclusions/exclusions filter those roots afterward. Source and test populations must remain disjoint.

# From modules/api as projectBaseDir
sonar.sources=src
sonar.tests=tests
sonar.exclusions=generated/**,vendor/**

Do not start with a large exclusion catalog. First choose the correct base and initial roots, run the scanner with useful logs, and inspect what was indexed. Add exclusions only when a specific ownership reason exists.

6. Generated code is a policy category, not a synonym for “ignore”

Generated source can mean several things: disposable compiler output, checked-in API clients, ORM code, protobuf stubs, vendored third-party source, or templates that developers partly edit. Each has different ownership and risk.

Exclude from analysis

Use for reproducibly generated artifacts that are not maintained as source and would only duplicate upstream/template responsibility. Preserve the generator/version as evidence.

Analyze but exclude from coverage

Use when generated code ships and static defects still matter, but unit coverage is not an appropriate ownership metric. Prefer sonar.coverage.exclusions over removing the files entirely.

Analyze fully

Use when developers maintain or materially modify the generated output, or when it is the deployable artifact whose risk is owned by the team.

Vendor/third-party

Normally keep outside first-party project ownership and govern through dependency/SCA/SBOM controls rather than inflating source metrics.

7. Reports must match the same ownership coordinate system

Coverage/test/external reports are produced outside SonarQube and imported by the scanner. Most report-path parameters are relative to the project root/base directory unless documented otherwise. The paths recorded inside a report must also be resolvable to files the scanner has indexed for that project.

producer working directory → report file path → report-internal source path → scanner projectBaseDir → indexed file → imported measure

A report can exist on disk and still contribute nothing to the intended project if either coordinate system is wrong. Preserve the report header/path entries and scanner import log before changing anything.

8. Scanner working directories and concurrency

Current analysis-parameter documentation says sonar.working.directory must be unique for each project and warns that the scanner deletes the specified directory before each analysis. This matters immediately in parallel CI.

Good parallel shape:
job-api    → projectKey=repo-api    → base=modules/api    → work=.scannerwork-api
job-worker → projectKey=repo-worker → base=modules/worker → work=.scannerwork-worker

Bad parallel shape:
job-A ─┐
       ├→ same checkout + same .scannerwork + same report filename
job-B ─┘

Separate project keys are not enough if jobs write into the same scanner/report artifact path. Conversely, separate work directories do not make concurrent analyses of the same project/branch logically safe. Serialize same-project/revision policy unless you have a documented reason and can explain how resulting histories and tasks are ordered.

9. Community Build versus native monorepo and aggregation features

The mandatory learning path uses Community Build and explicit project keys/base directories. Current SonarQube Server documentation labels native monorepo project onboarding/binding as an Enterprise Edition feature. That feature helps bind multiple SonarQube projects to the same provider repository and distinguish PR gate reports; it does not magically discover your module ownership.

Commercial aggregation is separate again: Applications are available starting in Developer Edition and group projects that share a lifecycle; Portfolios start in Enterprise Edition and provide higher-level governance views. Neither replaces correct per-project scope, report mapping, or task evidence.

10. Read-only inspection before any redesign

git rev-parse --show-toplevel
git rev-parse HEAD
git status --short
find . -maxdepth 3 -type f \( -name 'pom.xml' -o -name 'build.gradle*' -o -name '*.sln' -o -name 'pyproject.toml' -o -name 'package.json' \) -print
find . -maxdepth 4 -type f \( -name 'coverage*.xml' -o -name 'lcov.info' -o -name '*.sarif' \) -print

# Record scanner/runtime separately.
sonar-scanner --version

Then write an ownership table: module, owner, release lifecycle, intended project key, project base directory, sources, tests, generated/vendor policy, report paths, working directory, token owner, and expected aggregation. Do not scan until every row is explainable.

Knowledge check

Does one Git repository imply one SonarQube project?

Why is modules/worker/coverage.xml wrong after setting sonar.projectBaseDir=modules/worker?

Should all generated code be excluded from analysis?

Why must parallel scanners not share sonar.working.directory?

Does Enterprise monorepo onboarding replace project/file ownership design?

Next lesson

Build and break the two-module workflow deliberately

Lesson 2 turns the ownership/base-directory model into a real Community Build two-module analysis and same-revision report-path repair.

Official references and version notes

Version and compatibility note

Rechecked 2026-09-08. Mandatory examples target Community Build 26.9.0.129388 and SonarScanner CLI 8.1.0.6389. For CLI-managed projects, sonar.projectBaseDir changes the analysis directory; sonar.sources/sonar.tests are simple paths relative to that base unless absolute. Most coverage/external report paths are project-root/base-relative unless their parameter docs state otherwise. sonar.working.directory must be unique for each project and is deleted before each analysis, so parallel jobs must isolate scanner/report artifacts. Current native monorepo onboarding/binding is an Enterprise Edition feature and still requires explicit projects; SonarQube does not infer monorepo projects automatically. Applications are a commercial aggregation starting in Developer Edition; Portfolios start in Enterprise Edition. Community Build can still model multiple independent projects from one repository manually with distinct project keys/base directories and an external governance-summary fallback. Recheck the exact scanner/build integration and report parameter semantics before production use.

Complex-layout evidence rule. Preserve repository root, exact revision, project key/owner, projectBaseDir, source/test roots, inclusions/exclusions, generated/vendor policy, report producer/path/internal filenames, unique scanner working directory, token owner/type (never value), scanner log, report-task.txt, ceTaskId, Compute Engine state, measures/gate, CI concurrency identity and aggregation edition/definition separately.

Keep the academy open

Support free, practical DevOps education.

Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.

Ethereum / ERC-20
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0Send only Ethereum/ERC-20 compatible assets to this address.