quoll-cli 0.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- quoll/__init__.py +0 -0
- quoll/cd/__init__.py +1 -0
- quoll/cd/config.py +75 -0
- quoll/ci/__init__.py +0 -0
- quoll/ci/config.py +213 -0
- quoll/ci/custom.py +32 -0
- quoll/ci/detect.py +139 -0
- quoll/ci/steps.py +85 -0
- quoll/ci/tools.py +104 -0
- quoll/commands/__init__.py +0 -0
- quoll/commands/_common.py +145 -0
- quoll/commands/_workspace.py +83 -0
- quoll/commands/add.py +104 -0
- quoll/commands/add_secret.py +75 -0
- quoll/commands/cd.py +214 -0
- quoll/commands/ci.py +521 -0
- quoll/commands/docker.py +244 -0
- quoll/commands/k8s.py +211 -0
- quoll/commands/new.py +337 -0
- quoll/commands/remove.py +79 -0
- quoll/config/__init__.py +0 -0
- quoll/config/quoll_toml.py +196 -0
- quoll/config/settings.py +22 -0
- quoll/config/store.py +39 -0
- quoll/console.py +3 -0
- quoll/docker/__init__.py +1 -0
- quoll/docker/config.py +64 -0
- quoll/docker/detect.py +41 -0
- quoll/docker/render.py +162 -0
- quoll/files.py +64 -0
- quoll/k8s/__init__.py +0 -0
- quoll/k8s/chart/templates/_helpers.tpl +38 -0
- quoll/k8s/chart/templates/deployment.yaml +93 -0
- quoll/k8s/chart/templates/hpa.yaml +24 -0
- quoll/k8s/chart/templates/ingress.yaml +35 -0
- quoll/k8s/chart/templates/pdb.yaml +16 -0
- quoll/k8s/chart/templates/service.yaml +18 -0
- quoll/k8s/chart/templates/serviceaccount.yaml +10 -0
- quoll/k8s/ci.py +39 -0
- quoll/k8s/config.py +97 -0
- quoll/k8s/deploy.py +22 -0
- quoll/k8s/detect.py +32 -0
- quoll/k8s/render.py +94 -0
- quoll/languages/__init__.py +121 -0
- quoll/languages/base.py +350 -0
- quoll/languages/cpp/Dockerfile.j2 +37 -0
- quoll/languages/cpp/__init__.py +17 -0
- quoll/languages/cpp/ci.py +192 -0
- quoll/languages/cpp/detect.py +84 -0
- quoll/languages/cpp/docker.py +47 -0
- quoll/languages/dotnet/Dockerfile.j2 +21 -0
- quoll/languages/dotnet/__init__.py +17 -0
- quoll/languages/dotnet/ci.py +88 -0
- quoll/languages/dotnet/detect.py +53 -0
- quoll/languages/dotnet/docker.py +47 -0
- quoll/languages/go/Dockerfile.j2 +25 -0
- quoll/languages/go/__init__.py +17 -0
- quoll/languages/go/ci.py +126 -0
- quoll/languages/go/detect.py +30 -0
- quoll/languages/go/docker.py +40 -0
- quoll/languages/java/Dockerfile.j2 +28 -0
- quoll/languages/java/__init__.py +20 -0
- quoll/languages/java/ci.py +163 -0
- quoll/languages/java/detect.py +77 -0
- quoll/languages/java/docker.py +45 -0
- quoll/languages/java/frameworks/__init__.py +1 -0
- quoll/languages/java/frameworks/spring_boot/Dockerfile.j2 +18 -0
- quoll/languages/java/frameworks/spring_boot/__init__.py +10 -0
- quoll/languages/java/frameworks/spring_boot/detect.py +9 -0
- quoll/languages/java/frameworks/spring_boot/docker.py +19 -0
- quoll/languages/java/frameworks/spring_boot/services.py +24 -0
- quoll/languages/node/Dockerfile.j2 +98 -0
- quoll/languages/node/__init__.py +29 -0
- quoll/languages/node/ci.py +200 -0
- quoll/languages/node/detect.py +164 -0
- quoll/languages/node/docker.py +141 -0
- quoll/languages/node/frameworks/__init__.py +1 -0
- quoll/languages/node/frameworks/angular/__init__.py +18 -0
- quoll/languages/node/frameworks/angular/ci.py +34 -0
- quoll/languages/node/frameworks/angular/detect.py +20 -0
- quoll/languages/node/frameworks/angular/docker.py +31 -0
- quoll/languages/node/frameworks/angular/starter.py +74 -0
- quoll/languages/node/frameworks/astro/__init__.py +10 -0
- quoll/languages/node/frameworks/astro/ci.py +17 -0
- quoll/languages/node/frameworks/astro/detect.py +14 -0
- quoll/languages/node/frameworks/astro/docker.py +17 -0
- quoll/languages/node/frameworks/nestjs/__init__.py +7 -0
- quoll/languages/node/frameworks/nestjs/detect.py +9 -0
- quoll/languages/node/frameworks/nestjs/docker.py +10 -0
- quoll/languages/node/frameworks/nextjs/Dockerfile.j2 +14 -0
- quoll/languages/node/frameworks/nextjs/__init__.py +8 -0
- quoll/languages/node/frameworks/nextjs/ci.py +29 -0
- quoll/languages/node/frameworks/nextjs/detect.py +13 -0
- quoll/languages/node/frameworks/nextjs/docker.py +41 -0
- quoll/languages/node/frameworks/nuxt/__init__.py +10 -0
- quoll/languages/node/frameworks/nuxt/ci.py +17 -0
- quoll/languages/node/frameworks/nuxt/detect.py +17 -0
- quoll/languages/node/frameworks/nuxt/docker.py +24 -0
- quoll/languages/node/frameworks/sveltekit/__init__.py +15 -0
- quoll/languages/node/frameworks/sveltekit/ci.py +15 -0
- quoll/languages/node/frameworks/sveltekit/detect.py +18 -0
- quoll/languages/node/frameworks/sveltekit/docker.py +18 -0
- quoll/languages/node/starter.py +42 -0
- quoll/languages/node/templates/express/README.md +11 -0
- quoll/languages/node/templates/express/env.example +2 -0
- quoll/languages/node/templates/express/eslint.config.js +15 -0
- quoll/languages/node/templates/express/gitignore +4 -0
- quoll/languages/node/templates/express/nvmrc +1 -0
- quoll/languages/node/templates/express/package.json +16 -0
- quoll/languages/node/templates/express/prettierignore +6 -0
- quoll/languages/node/templates/express/src/app.ts +27 -0
- quoll/languages/node/templates/express/src/server.ts +11 -0
- quoll/languages/node/templates/express/test/app.test.ts +29 -0
- quoll/languages/node/templates/express/tsconfig.build.json +8 -0
- quoll/languages/node/templates/express/tsconfig.json +14 -0
- quoll/languages/node/templates/express/vitest.config.ts +12 -0
- quoll/languages/php/Dockerfile.j2 +40 -0
- quoll/languages/php/__init__.py +19 -0
- quoll/languages/php/ci.py +147 -0
- quoll/languages/php/detect.py +75 -0
- quoll/languages/php/docker.py +59 -0
- quoll/languages/php/frameworks/__init__.py +1 -0
- quoll/languages/php/frameworks/laravel/__init__.py +17 -0
- quoll/languages/php/frameworks/laravel/ci.py +24 -0
- quoll/languages/php/frameworks/laravel/detect.py +13 -0
- quoll/languages/php/frameworks/laravel/docker.py +29 -0
- quoll/languages/php/frameworks/laravel/services.py +25 -0
- quoll/languages/php/frameworks/symfony/__init__.py +15 -0
- quoll/languages/php/frameworks/symfony/ci.py +22 -0
- quoll/languages/php/frameworks/symfony/detect.py +17 -0
- quoll/languages/php/frameworks/symfony/docker.py +25 -0
- quoll/languages/python/Dockerfile.j2 +61 -0
- quoll/languages/python/__init__.py +19 -0
- quoll/languages/python/ci.py +157 -0
- quoll/languages/python/detect.py +175 -0
- quoll/languages/python/docker.py +80 -0
- quoll/languages/python/frameworks/__init__.py +1 -0
- quoll/languages/python/frameworks/django/__init__.py +16 -0
- quoll/languages/python/frameworks/django/ci.py +43 -0
- quoll/languages/python/frameworks/django/detect.py +17 -0
- quoll/languages/python/frameworks/django/docker.py +20 -0
- quoll/languages/python/frameworks/fastapi/__init__.py +7 -0
- quoll/languages/python/frameworks/fastapi/detect.py +9 -0
- quoll/languages/python/frameworks/fastapi/docker.py +21 -0
- quoll/languages/python/frameworks/flask/__init__.py +7 -0
- quoll/languages/python/frameworks/flask/detect.py +9 -0
- quoll/languages/python/frameworks/flask/docker.py +22 -0
- quoll/languages/ruby/Dockerfile.j2 +45 -0
- quoll/languages/ruby/__init__.py +19 -0
- quoll/languages/ruby/ci.py +110 -0
- quoll/languages/ruby/detect.py +72 -0
- quoll/languages/ruby/docker.py +63 -0
- quoll/languages/ruby/frameworks/__init__.py +1 -0
- quoll/languages/ruby/frameworks/rails/Dockerfile.j2 +16 -0
- quoll/languages/ruby/frameworks/rails/__init__.py +18 -0
- quoll/languages/ruby/frameworks/rails/ci.py +26 -0
- quoll/languages/ruby/frameworks/rails/detect.py +15 -0
- quoll/languages/ruby/frameworks/rails/docker.py +62 -0
- quoll/languages/ruby/frameworks/rails/services.py +10 -0
- quoll/languages/rust/Dockerfile.j2 +23 -0
- quoll/languages/rust/__init__.py +17 -0
- quoll/languages/rust/ci.py +125 -0
- quoll/languages/rust/detect.py +46 -0
- quoll/languages/rust/docker.py +38 -0
- quoll/main.py +50 -0
- quoll/platforms/__init__.py +62 -0
- quoll/platforms/base.py +103 -0
- quoll/platforms/github/__init__.py +4 -0
- quoll/platforms/github/actions.py +78 -0
- quoll/platforms/github/api.py +124 -0
- quoll/platforms/github/cd.py +265 -0
- quoll/platforms/github/ci.py +497 -0
- quoll/platforms/github/dependabot.yml.j2 +49 -0
- quoll/platforms/github/platform.py +52 -0
- quoll/platforms/github/workflow.py +74 -0
- quoll/platforms/gitlab/__init__.py +4 -0
- quoll/platforms/gitlab/api.py +158 -0
- quoll/platforms/gitlab/cd.py +226 -0
- quoll/platforms/gitlab/ci.py +400 -0
- quoll/platforms/gitlab/pipeline.py +136 -0
- quoll/platforms/gitlab/platform.py +54 -0
- quoll/project.py +60 -0
- quoll/services.py +117 -0
- quoll/starters.py +117 -0
- quoll/workspace.py +232 -0
- quoll/yaml_dump.py +137 -0
- quoll_cli-0.2.0.dist-info/METADATA +641 -0
- quoll_cli-0.2.0.dist-info/RECORD +191 -0
- quoll_cli-0.2.0.dist-info/WHEEL +4 -0
- quoll_cli-0.2.0.dist-info/entry_points.txt +2 -0
- quoll_cli-0.2.0.dist-info/licenses/LICENSE +21 -0
quoll/languages/base.py
ADDED
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
"""The contract every language and framework plugin implements.
|
|
2
|
+
|
|
3
|
+
A language is a `Language` instance that bundles:
|
|
4
|
+
- what identifies it (`markers`) and what quoll.toml may contain for it (package managers, tools);
|
|
5
|
+
- `detect`, which inspects a project and returns a `Detection`;
|
|
6
|
+
- `ci`, a `CISteps` implementation that turns the validated [ci] config into platform-neutral steps
|
|
7
|
+
(quoll.ci.steps), which each CI platform renders;
|
|
8
|
+
- its `frameworks`, tried in order once the language is known.
|
|
9
|
+
|
|
10
|
+
A framework (`Framework`) refines the language: it adjusts the detection (test runner, tools...) and
|
|
11
|
+
can add its own lint tools, test runner, environment and steps through a `FrameworkCI`.
|
|
12
|
+
|
|
13
|
+
Both can ship a `DockerImage`; a framework's replaces its language's, and `Starter`s creating new projects
|
|
14
|
+
(`quoll new`).
|
|
15
|
+
"""
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import inspect
|
|
19
|
+
import json
|
|
20
|
+
import shlex
|
|
21
|
+
from abc import ABC, abstractmethod
|
|
22
|
+
from dataclasses import dataclass, field
|
|
23
|
+
from pathlib import Path
|
|
24
|
+
from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, Sequence, Tuple
|
|
25
|
+
|
|
26
|
+
from jinja2 import Environment, FileSystemLoader, PrefixLoader, StrictUndefined
|
|
27
|
+
|
|
28
|
+
from quoll.ci.steps import Cache, Item, Step
|
|
29
|
+
from quoll.platforms.base import Remote
|
|
30
|
+
from quoll.project import Project
|
|
31
|
+
from quoll.services import ServiceEnv, no_service_env
|
|
32
|
+
|
|
33
|
+
if TYPE_CHECKING:
|
|
34
|
+
from quoll.ci.config import CIConfig
|
|
35
|
+
from quoll.docker.config import DockerConfig
|
|
36
|
+
|
|
37
|
+
__all__ = ["Cache", "Item", "Step"]
|
|
38
|
+
|
|
39
|
+
# Languages with `scripts=True` also accept "script:<name>" as a lint tool or test runner.
|
|
40
|
+
SCRIPT_PREFIX = "script:"
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@dataclass
|
|
44
|
+
class Detection:
|
|
45
|
+
"""What quoll inferred about a project. Languages fill the toolchain fields; quoll fills the rest."""
|
|
46
|
+
|
|
47
|
+
stack: Optional[str] = None
|
|
48
|
+
framework: str = ""
|
|
49
|
+
package_manager: str = ""
|
|
50
|
+
package_manager_version: str = ""
|
|
51
|
+
lockfile: str = ""
|
|
52
|
+
install: str = ""
|
|
53
|
+
cache: bool = True
|
|
54
|
+
version: str = ""
|
|
55
|
+
version_file: str = ""
|
|
56
|
+
matrix_versions: List[str] = field(default_factory=list)
|
|
57
|
+
source_dir: str = "."
|
|
58
|
+
lint_tools: List[str] = field(default_factory=list)
|
|
59
|
+
test_runner: str = ""
|
|
60
|
+
coverage: bool = True
|
|
61
|
+
# The language can run its tests under sanitizers (C/C++).
|
|
62
|
+
sanitizers: bool = False
|
|
63
|
+
# A dependency audit exists for the project (e.g. a lock file the scanner reads).
|
|
64
|
+
audit: bool = True
|
|
65
|
+
build_command: str = ""
|
|
66
|
+
# Backing services the app uses (see quoll.services), from its client libraries.
|
|
67
|
+
services: List[str] = field(default_factory=list)
|
|
68
|
+
# Filled by quoll, not by languages.
|
|
69
|
+
has_dockerfile: bool = False
|
|
70
|
+
# Directory of the project's Helm chart ([k8s] in quoll.toml).
|
|
71
|
+
chart: str = ""
|
|
72
|
+
env_vars: Dict[str, str] = field(default_factory=dict)
|
|
73
|
+
default_branch: str = "main"
|
|
74
|
+
platform: str = "github"
|
|
75
|
+
remote: Optional[Remote] = None
|
|
76
|
+
other_stacks: List[str] = field(default_factory=list)
|
|
77
|
+
warnings: List[str] = field(default_factory=list)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class CISteps(ABC):
|
|
81
|
+
"""CI steps for one language. `cfg` is the validated [ci] section of quoll.toml.
|
|
82
|
+
|
|
83
|
+
The toolchain comes from `setup` on GitHub Actions (setup actions on a bare runner) and from `image` plus
|
|
84
|
+
`image_setup` on GitLab CI (jobs run in containers). Every other method returns steps for both: a step that
|
|
85
|
+
uses a GitHub Action must also give the equivalent `run` command.
|
|
86
|
+
"""
|
|
87
|
+
|
|
88
|
+
# Matrix variable, e.g. "python-version".
|
|
89
|
+
version_key: str = ""
|
|
90
|
+
# False when the audit action installs the toolchain itself (GitHub).
|
|
91
|
+
audit_needs_setup: bool = True
|
|
92
|
+
# False when the audit only reads the lockfile: the toolchain's dependency cache would have nothing to save.
|
|
93
|
+
audit_uses_cache: bool = True
|
|
94
|
+
# CodeQL language (https://codeql.github.com/docs/codeql-overview/supported-languages-and-frameworks/), empty
|
|
95
|
+
# when CodeQL doesn't support it; "autobuild" when the code must be compiled to be analyzed.
|
|
96
|
+
codeql_language: str = ""
|
|
97
|
+
codeql_build_mode: str = "none"
|
|
98
|
+
|
|
99
|
+
@abstractmethod
|
|
100
|
+
def setup(self, cfg: CIConfig, version: Optional[str]) -> List[Item]:
|
|
101
|
+
"""GitHub Actions steps installing the toolchain. `version` is None when read from `cfg.version_file`."""
|
|
102
|
+
|
|
103
|
+
@abstractmethod
|
|
104
|
+
def image(self, cfg: CIConfig, version: str) -> str:
|
|
105
|
+
"""Container image providing the toolchain at `version` (which may be a variable such as $NODE_VERSION)."""
|
|
106
|
+
|
|
107
|
+
def image_setup(self, cfg: CIConfig) -> List[Step]:
|
|
108
|
+
"""Commands completing `image` before the job's steps (package manager, components...)."""
|
|
109
|
+
return []
|
|
110
|
+
|
|
111
|
+
def cache(self, cfg: CIConfig) -> Optional[Cache]:
|
|
112
|
+
"""Dependency cache of container jobs; on GitHub, `setup` caches."""
|
|
113
|
+
return None
|
|
114
|
+
|
|
115
|
+
@abstractmethod
|
|
116
|
+
def install(self, cfg: CIConfig, packages: Sequence[str] = ()) -> List[Item]:
|
|
117
|
+
"""Install the project dependencies, plus extra CI-only `packages` where the language supports it."""
|
|
118
|
+
|
|
119
|
+
def exec(self, cfg: CIConfig, command: str) -> str:
|
|
120
|
+
"""`command` run with the project's installed tools (e.g. `uv run --no-sync`, `npx`)."""
|
|
121
|
+
return command
|
|
122
|
+
|
|
123
|
+
@abstractmethod
|
|
124
|
+
def lint(self, cfg: CIConfig, tools: List[str]) -> List[Item]:
|
|
125
|
+
"""Install dependencies and run `tools`, a subset of the language's lint tools."""
|
|
126
|
+
|
|
127
|
+
@abstractmethod
|
|
128
|
+
def test(self, cfg: CIConfig) -> List[Item]:
|
|
129
|
+
"""Install dependencies and run the tests, enforcing the coverage threshold when enabled."""
|
|
130
|
+
|
|
131
|
+
@abstractmethod
|
|
132
|
+
def coverage_file(self, cfg: CIConfig) -> str:
|
|
133
|
+
"""Coverage report uploaded to Codecov (a glob for some languages)."""
|
|
134
|
+
|
|
135
|
+
def reports(self, cfg: CIConfig) -> TestReports:
|
|
136
|
+
"""What the test steps leave for the platform to show, when `cfg.test.reports` is on."""
|
|
137
|
+
return TestReports()
|
|
138
|
+
|
|
139
|
+
@abstractmethod
|
|
140
|
+
def audit(self, cfg: CIConfig) -> List[Item]:
|
|
141
|
+
"""Scan dependencies for known vulnerabilities."""
|
|
142
|
+
|
|
143
|
+
@abstractmethod
|
|
144
|
+
def build(self, cfg: CIConfig) -> List[Item]:
|
|
145
|
+
"""Run `cfg.build.command`."""
|
|
146
|
+
|
|
147
|
+
def build_artifacts(self, det: Detection) -> List[str]:
|
|
148
|
+
"""What the detected build command produces, for the build job to keep (nothing if not a distributable)."""
|
|
149
|
+
return []
|
|
150
|
+
|
|
151
|
+
@abstractmethod
|
|
152
|
+
def dependabot_ecosystem(self, cfg: CIConfig) -> str:
|
|
153
|
+
"""Dependabot `package-ecosystem` for the project's dependencies."""
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
# Runtime images on Debian or Ubuntu: the security fixes released since the base image was built (the image scan
|
|
157
|
+
# fails on them otherwise, and rebuilding picks up the next ones).
|
|
158
|
+
OS_UPDATES = """# Security fixes released since the base image was built.
|
|
159
|
+
RUN apt-get update \\
|
|
160
|
+
&& DEBIAN_FRONTEND=noninteractive apt-get upgrade -y \\
|
|
161
|
+
&& rm -rf /var/lib/apt/lists/*"""
|
|
162
|
+
|
|
163
|
+
# The line printed by quoll's own coverage threshold checks: "Coverage 85.0% (threshold 80%)".
|
|
164
|
+
THRESHOLD_REGEX = r"/Coverage (\d+(?:\.\d+)?)% \(threshold/"
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
@dataclass(frozen=True)
|
|
168
|
+
class TestReports:
|
|
169
|
+
__test__ = False # not a pytest test class
|
|
170
|
+
|
|
171
|
+
# JUnit XML results (a glob).
|
|
172
|
+
junit: str = ""
|
|
173
|
+
# Cobertura XML coverage, shown on the diff of merge requests (GitLab).
|
|
174
|
+
cobertura: str = ""
|
|
175
|
+
# Regex extracting the total coverage from the job log (GitLab badge and MR widget).
|
|
176
|
+
coverage_regex: str = ""
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
class FrameworkCI:
|
|
180
|
+
"""Optional framework hooks. The defaults add nothing, so a framework only overrides what it changes."""
|
|
181
|
+
|
|
182
|
+
def env(self, cfg: CIConfig) -> Dict[str, str]:
|
|
183
|
+
"""Environment variables for the whole workflow."""
|
|
184
|
+
return {}
|
|
185
|
+
|
|
186
|
+
def lint(self, cfg: CIConfig, tool: str) -> List[Item]:
|
|
187
|
+
"""Steps for one of the framework's lint tools, run after the language's (dependencies installed)."""
|
|
188
|
+
raise NotImplementedError(f"{type(self).__name__} declares lint tool '{tool}' but does not implement it")
|
|
189
|
+
|
|
190
|
+
def test(self, cfg: CIConfig) -> List[Item]:
|
|
191
|
+
"""Full test steps when `cfg.test.runner` is one of the framework's test runners."""
|
|
192
|
+
raise NotImplementedError(f"{type(self).__name__} declares a test runner but does not implement it")
|
|
193
|
+
|
|
194
|
+
def before_build(self, cfg: CIConfig) -> List[Item]:
|
|
195
|
+
"""Steps before the build (e.g. a framework build cache)."""
|
|
196
|
+
return []
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
@dataclass
|
|
200
|
+
class DockerDefaults:
|
|
201
|
+
"""What an image infers from the project; becomes the [docker] section of quoll.toml."""
|
|
202
|
+
|
|
203
|
+
port: int
|
|
204
|
+
command: str = ""
|
|
205
|
+
build_command: str = ""
|
|
206
|
+
output_dir: str = ""
|
|
207
|
+
static: bool = False
|
|
208
|
+
warnings: List[str] = field(default_factory=list)
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
class DockerImage(ABC):
|
|
212
|
+
"""A Dockerfile, rendered from the `Dockerfile.j2` next to the class (or its closest parent's).
|
|
213
|
+
|
|
214
|
+
Images inherit like their classes. A framework image subclasses its language's: it overrides `defaults`
|
|
215
|
+
(port, command) or `context`, and when the image itself differs, ships a Dockerfile.j2 that extends the
|
|
216
|
+
language's and overrides only the stages (blocks) that change:
|
|
217
|
+
|
|
218
|
+
{% extends "node/Dockerfile.j2" %}
|
|
219
|
+
{% block app_files %}...{% endblock %}
|
|
220
|
+
|
|
221
|
+
Templates are addressed as "<directory>/Dockerfile.j2", e.g. "node/Dockerfile.j2".
|
|
222
|
+
"""
|
|
223
|
+
|
|
224
|
+
# False when the base image already runs a server (e.g. FrankenPHP).
|
|
225
|
+
requires_command: bool = True
|
|
226
|
+
|
|
227
|
+
@abstractmethod
|
|
228
|
+
def defaults(self, project: Project, det: Detection) -> DockerDefaults:
|
|
229
|
+
"""Port, start command and build settings inferred from the project."""
|
|
230
|
+
|
|
231
|
+
def context(self, project: Project, cfg: DockerConfig) -> Dict[str, Any]:
|
|
232
|
+
"""Template variables besides `cfg`."""
|
|
233
|
+
return {}
|
|
234
|
+
|
|
235
|
+
def dockerignore(self, cfg: DockerConfig) -> List[str]:
|
|
236
|
+
"""Patterns to keep out of the build context, on top of the common ones."""
|
|
237
|
+
return []
|
|
238
|
+
|
|
239
|
+
def writable_paths(self, cfg: DockerConfig) -> Optional[List[str]]:
|
|
240
|
+
"""Directories the app writes to: Kubernetes mounts them on a read-only root filesystem. None: the app
|
|
241
|
+
writes in its own tree, the root filesystem stays writable."""
|
|
242
|
+
return ["/tmp"]
|
|
243
|
+
|
|
244
|
+
def render(self, project: Project, cfg: DockerConfig) -> str:
|
|
245
|
+
dirs = self._template_dirs()
|
|
246
|
+
env = Environment(
|
|
247
|
+
loader=PrefixLoader({d.name: FileSystemLoader(str(d)) for d in dirs}),
|
|
248
|
+
keep_trailing_newline=True,
|
|
249
|
+
trim_blocks=True,
|
|
250
|
+
lstrip_blocks=True,
|
|
251
|
+
undefined=StrictUndefined,
|
|
252
|
+
)
|
|
253
|
+
env.filters["exec_form"] = exec_form
|
|
254
|
+
env.globals["os_updates"] = OS_UPDATES
|
|
255
|
+
return env.get_template(f"{dirs[0].name}/Dockerfile.j2").render(cfg=cfg, **self.context(project, cfg))
|
|
256
|
+
|
|
257
|
+
def _template_dirs(self) -> List[Path]:
|
|
258
|
+
"""Directories holding a Dockerfile.j2, from this class up to its oldest ancestor."""
|
|
259
|
+
dirs: List[Path] = []
|
|
260
|
+
for cls in type(self).__mro__:
|
|
261
|
+
if cls in (DockerImage, ABC, object):
|
|
262
|
+
continue
|
|
263
|
+
directory = Path(inspect.getfile(cls)).parent
|
|
264
|
+
if (directory / "Dockerfile.j2").exists() and directory not in dirs:
|
|
265
|
+
dirs.append(directory)
|
|
266
|
+
return dirs
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def exec_form(command: str) -> str:
|
|
270
|
+
"""JSON (exec) form of a command, so the process receives signals; through `sh -c` if it needs a shell."""
|
|
271
|
+
if any(c in command for c in "$&|;<>*`") or "\n" in command:
|
|
272
|
+
return json.dumps(["sh", "-c", command])
|
|
273
|
+
return json.dumps(shlex.split(command))
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
@dataclass(frozen=True)
|
|
277
|
+
class StarterOptions:
|
|
278
|
+
# Package name of the new project.
|
|
279
|
+
name: str
|
|
280
|
+
# Version of the framework, e.g. "18" for Angular 18 (empty: the latest).
|
|
281
|
+
version: str = ""
|
|
282
|
+
# Version of the language's runtime, e.g. "20" for Node.js 20 (empty: the starter's default).
|
|
283
|
+
runtime: str = ""
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
@dataclass(frozen=True)
|
|
287
|
+
class Starter:
|
|
288
|
+
"""Creates a new project in an empty directory (see quoll.starters for templates and generators)."""
|
|
289
|
+
|
|
290
|
+
# Key used on the command line: `quoll new --front angular` (`angular@18` for a version).
|
|
291
|
+
name: str
|
|
292
|
+
label: str
|
|
293
|
+
# Where it fits in a front-end + back-end repository: "front" or "back".
|
|
294
|
+
role: str
|
|
295
|
+
# (directory, options) -> warnings for the user (e.g. an unsupported version). The directory exists, empty.
|
|
296
|
+
create: Callable[[Path, StarterOptions], Optional[List[str]]]
|
|
297
|
+
# The runtime `runtime` options are for: `quoll new --node 20`.
|
|
298
|
+
runtime: str = ""
|
|
299
|
+
runtime_default: str = ""
|
|
300
|
+
# Front-ends: (directory, path, url) makes the dev server forward `path` (e.g. "/api") to the API at `url`, as
|
|
301
|
+
# nginx does in the image ([docker] proxy).
|
|
302
|
+
proxy: Optional[Callable[[Path, str, str], None]] = None
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
@dataclass(frozen=True)
|
|
306
|
+
class Framework:
|
|
307
|
+
name: str
|
|
308
|
+
# Name of the language it belongs to.
|
|
309
|
+
language: str
|
|
310
|
+
# Returns True when the framework is used, and may refine the detection (tools, test runner...).
|
|
311
|
+
detect: Callable[[Project, Detection], bool]
|
|
312
|
+
ci: FrameworkCI = field(default_factory=FrameworkCI)
|
|
313
|
+
lint_tools: Tuple[str, ...] = ()
|
|
314
|
+
test_runners: Tuple[str, ...] = ()
|
|
315
|
+
# Replaces the language's image when set.
|
|
316
|
+
docker: Optional[DockerImage] = None
|
|
317
|
+
# Extra variables pointing the app at a service, e.g. Laravel's DB_HOST.
|
|
318
|
+
service_env: ServiceEnv = no_service_env
|
|
319
|
+
starters: Tuple[Starter, ...] = ()
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
@dataclass(frozen=True)
|
|
323
|
+
class Language:
|
|
324
|
+
name: str
|
|
325
|
+
# Any of these files (or glob patterns) at the project root identifies the language.
|
|
326
|
+
markers: Tuple[str, ...]
|
|
327
|
+
package_managers: Tuple[str, ...]
|
|
328
|
+
lint_tools: Tuple[str, ...]
|
|
329
|
+
test_runners: Tuple[str, ...]
|
|
330
|
+
detect: Callable[[Project], Detection]
|
|
331
|
+
ci: CISteps
|
|
332
|
+
# Tried in order: the first whose detect() returns True is the project's framework.
|
|
333
|
+
frameworks: Tuple[Framework, ...] = ()
|
|
334
|
+
scripts: bool = False
|
|
335
|
+
docker: Optional[DockerImage] = None
|
|
336
|
+
# Extra variables pointing the app at a service, e.g. Spring's datasource URL.
|
|
337
|
+
service_env: ServiceEnv = no_service_env
|
|
338
|
+
starters: Tuple[Starter, ...] = ()
|
|
339
|
+
|
|
340
|
+
def matches(self, project: Project) -> bool:
|
|
341
|
+
return project.glob_any(*self.markers)
|
|
342
|
+
|
|
343
|
+
def is_lint_tool(self, value: str) -> bool:
|
|
344
|
+
return value in self.lint_tools or self._is_script(value)
|
|
345
|
+
|
|
346
|
+
def is_test_runner(self, value: str) -> bool:
|
|
347
|
+
return value in self.test_runners or self._is_script(value)
|
|
348
|
+
|
|
349
|
+
def _is_script(self, value: str) -> bool:
|
|
350
|
+
return self.scripts and value.startswith(SCRIPT_PREFIX) and len(value) > len(SCRIPT_PREFIX)
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{% block build %}
|
|
2
|
+
FROM gcc:{{ gcc_tag }} AS build
|
|
3
|
+
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
|
|
4
|
+
--mount=type=cache,target=/var/lib/apt,sharing=locked \
|
|
5
|
+
apt-get update \
|
|
6
|
+
&& apt-get install -y --no-install-recommends {{ packages }}
|
|
7
|
+
WORKDIR /src
|
|
8
|
+
COPY . .
|
|
9
|
+
{% if conan %}
|
|
10
|
+
# Conan resolves the dependencies; its toolchain file points CMake at them.
|
|
11
|
+
RUN --mount=type=cache,target=/root/.conan2 \
|
|
12
|
+
pipx run conan profile detect --exist-ok \
|
|
13
|
+
&& pipx run conan install . --output-folder=build --build=missing -s build_type=Release
|
|
14
|
+
ENV CMAKE_TOOLCHAIN_FILE=/src/build/conan_toolchain.cmake
|
|
15
|
+
{% endif %}
|
|
16
|
+
{% if vcpkg %}
|
|
17
|
+
RUN git clone --depth 1 https://github.com/microsoft/vcpkg /opt/vcpkg \
|
|
18
|
+
&& /opt/vcpkg/bootstrap-vcpkg.sh -disableMetrics
|
|
19
|
+
ENV VCPKG_ROOT=/opt/vcpkg \
|
|
20
|
+
CMAKE_TOOLCHAIN_FILE=/opt/vcpkg/scripts/buildsystems/vcpkg.cmake
|
|
21
|
+
{% endif %}
|
|
22
|
+
# libstdc++ and libgcc linked statically: the runtime image only needs glibc.
|
|
23
|
+
ENV LDFLAGS="-static-libstdc++ -static-libgcc"
|
|
24
|
+
RUN {% if vcpkg %}--mount=type=cache,target=/root/.cache/vcpkg {% endif %}{{ cfg.build_command }} \
|
|
25
|
+
&& mkdir -p /out \
|
|
26
|
+
&& cp "$(find build -type f -name '{{ target }}' -perm -u+x | head -n 1)" /out/app
|
|
27
|
+
{% endblock %}
|
|
28
|
+
|
|
29
|
+
{% block runtime %}
|
|
30
|
+
# distroless/cc: glibc only, no shell, no package manager, non-root.
|
|
31
|
+
FROM gcr.io/distroless/cc-debian13:nonroot
|
|
32
|
+
COPY --from=build /out/app /app
|
|
33
|
+
# distroless' nonroot, numeric: Kubernetes can then check it isn't root (runAsNonRoot).
|
|
34
|
+
USER 65532:65532
|
|
35
|
+
EXPOSE {{ cfg.port }}
|
|
36
|
+
CMD {{ cfg.command | exec_form }}
|
|
37
|
+
{% endblock %}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""C and C++: CMake (plain, Conan or vcpkg) and Meson."""
|
|
2
|
+
|
|
3
|
+
from quoll.languages.base import Language
|
|
4
|
+
from quoll.languages.cpp.ci import CppCI
|
|
5
|
+
from quoll.languages.cpp.detect import detect
|
|
6
|
+
from quoll.languages.cpp.docker import CppImage
|
|
7
|
+
|
|
8
|
+
language = Language(
|
|
9
|
+
name="cpp",
|
|
10
|
+
markers=("CMakeLists.txt", "meson.build"),
|
|
11
|
+
package_managers=("cmake", "conan", "vcpkg", "meson"),
|
|
12
|
+
lint_tools=("clang-format", "clang-tidy", "cppcheck"),
|
|
13
|
+
test_runners=("ctest", "meson"),
|
|
14
|
+
detect=detect,
|
|
15
|
+
ci=CppCI(),
|
|
16
|
+
docker=CppImage(),
|
|
17
|
+
)
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING, List, Optional, Sequence
|
|
4
|
+
|
|
5
|
+
from quoll.ci.steps import Item, Step, step
|
|
6
|
+
from quoll.ci.tools import install_osv_scanner
|
|
7
|
+
from quoll.languages.base import CISteps, TestReports
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from quoll.ci.config import CIConfig
|
|
11
|
+
|
|
12
|
+
# Ubuntu 24.04 on both platforms (GitHub's runner, GitLab's image): the toolchain comes from its packages.
|
|
13
|
+
IMAGE = "ubuntu:24.04"
|
|
14
|
+
SOURCES = "'*.c' '*.cc' '*.cpp' '*.cxx' '*.h' '*.hh' '*.hpp' '*.hxx'"
|
|
15
|
+
COVERAGE_REGEX = r"/^TOTAL\s+\d+\s+\d+\s+(\d+(?:\.\d+)?)%/"
|
|
16
|
+
|
|
17
|
+
# Installs "<gcc|clang>-<major>" and exports CC, CXX and GCOV (gcovr reads clang's coverage through llvm-cov),
|
|
18
|
+
# for the next steps too on GitHub.
|
|
19
|
+
_TOOLCHAIN = """compiler="{compiler}"
|
|
20
|
+
case "$compiler" in
|
|
21
|
+
gcc-*)
|
|
22
|
+
v="${{compiler#gcc-}}"; packages="gcc-$v g++-$v"; cc="gcc-$v"; cxx="g++-$v"; gcov="gcov-$v" ;;
|
|
23
|
+
clang-*)
|
|
24
|
+
v="${{compiler#clang-}}"; packages="clang-$v llvm-$v"; cc="clang-$v"; cxx="clang++-$v"; gcov="llvm-cov-$v gcov" ;;
|
|
25
|
+
*) echo "Unknown compiler '$compiler' (expected gcc-<major> or clang-<major>)"; exit 1 ;;
|
|
26
|
+
esac
|
|
27
|
+
sudo=""; [ "$(id -u)" -eq 0 ] || sudo=sudo
|
|
28
|
+
$sudo apt-get update -qq
|
|
29
|
+
# shellcheck disable=SC2086 # $packages lists several packages
|
|
30
|
+
$sudo env DEBIAN_FRONTEND=noninteractive apt-get install -y -qq --no-install-recommends \\
|
|
31
|
+
$packages {tools}
|
|
32
|
+
export CC="$cc" CXX="$cxx" GCOV="$gcov"
|
|
33
|
+
if [ -n "${{GITHUB_ENV:-}}" ]; then printf 'CC=%s\\nCXX=%s\\nGCOV=%s\\n' "$cc" "$cxx" "$gcov" >> "$GITHUB_ENV"; fi"""
|
|
34
|
+
|
|
35
|
+
# vcpkg is preinstalled on GitHub's runners; elsewhere it is bootstrapped once per job.
|
|
36
|
+
_VCPKG = """if [ -z "${VCPKG_ROOT:-}" ]; then
|
|
37
|
+
if [ -n "${VCPKG_INSTALLATION_ROOT:-}" ]; then
|
|
38
|
+
VCPKG_ROOT="$VCPKG_INSTALLATION_ROOT"
|
|
39
|
+
else
|
|
40
|
+
git clone --quiet --depth 1 https://github.com/microsoft/vcpkg "$HOME/vcpkg"
|
|
41
|
+
"$HOME/vcpkg/bootstrap-vcpkg.sh" -disableMetrics
|
|
42
|
+
VCPKG_ROOT="$HOME/vcpkg"
|
|
43
|
+
fi
|
|
44
|
+
fi"""
|
|
45
|
+
|
|
46
|
+
_CLANG_FORMAT = f"git ls-files -z {SOURCES} | xargs -0 -r clang-format --dry-run --Werror"
|
|
47
|
+
# Sources of the project only: FetchContent dependencies are built under build/.
|
|
48
|
+
_CLANG_TIDY = 'run-clang-tidy -p build -quiet "^$PWD/(?!build/)"'
|
|
49
|
+
_CPPCHECK = (
|
|
50
|
+
"cppcheck --project=build/compile_commands.json -ibuild --enable=warning,style,performance,portability "
|
|
51
|
+
"--inline-suppr --suppress=missingIncludeSystem --error-exitcode=1 --quiet"
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class CppCI(CISteps):
|
|
56
|
+
version_key = "compiler"
|
|
57
|
+
codeql_language = "c-cpp"
|
|
58
|
+
# CodeQL watches our own configure + build (dependencies included).
|
|
59
|
+
codeql_build_mode = "manual"
|
|
60
|
+
|
|
61
|
+
def setup(self, cfg: CIConfig, version: Optional[str]) -> List[Item]:
|
|
62
|
+
return [self._toolchain(cfg, version or cfg.versions[-1])]
|
|
63
|
+
|
|
64
|
+
def image(self, cfg: CIConfig, version: str) -> str:
|
|
65
|
+
return IMAGE
|
|
66
|
+
|
|
67
|
+
def image_setup(self, cfg: CIConfig) -> List[Step]:
|
|
68
|
+
# In a matrix job, COMPILER is the entry's.
|
|
69
|
+
return [self._toolchain(cfg, f"${{COMPILER:-{cfg.versions[-1]}}}")]
|
|
70
|
+
|
|
71
|
+
def install(self, cfg: CIConfig, packages: Sequence[str] = ()) -> List[Item]:
|
|
72
|
+
return [configure(cfg)]
|
|
73
|
+
|
|
74
|
+
def lint(self, cfg: CIConfig, tools: List[str]) -> List[Item]:
|
|
75
|
+
steps: List[Item] = []
|
|
76
|
+
if "clang-format" in tools:
|
|
77
|
+
steps.append(step("clang-format", run=_CLANG_FORMAT))
|
|
78
|
+
if "clang-tidy" in tools or "cppcheck" in tools:
|
|
79
|
+
# Both read the compilation database written by the configure step.
|
|
80
|
+
steps.append(configure(cfg))
|
|
81
|
+
if "clang-tidy" in tools:
|
|
82
|
+
steps.append(step("clang-tidy", run=_CLANG_TIDY))
|
|
83
|
+
if "cppcheck" in tools:
|
|
84
|
+
steps.append(step("cppcheck", run=_CPPCHECK))
|
|
85
|
+
return steps
|
|
86
|
+
|
|
87
|
+
def test(self, cfg: CIConfig) -> List[Item]:
|
|
88
|
+
t = cfg.test
|
|
89
|
+
meson = cfg.package_manager == "meson"
|
|
90
|
+
env = {}
|
|
91
|
+
if t.sanitizers:
|
|
92
|
+
env = {"ASAN_OPTIONS": "detect_leaks=1:abort_on_error=1", "UBSAN_OPTIONS": "print_stacktrace=1"}
|
|
93
|
+
if meson:
|
|
94
|
+
run = "meson compile -C build\nmeson test -C build --print-errorlogs"
|
|
95
|
+
else:
|
|
96
|
+
run = "cmake --build build\nctest --test-dir build --output-on-failure"
|
|
97
|
+
if t.reports:
|
|
98
|
+
run += " --output-junit junit.xml"
|
|
99
|
+
name = "Tests" + (" with sanitizers" if t.sanitizers else "")
|
|
100
|
+
steps: List[Item] = [configure(cfg, instrumented=True), step(name, run=run, env=env, secrets=t.env_secrets)]
|
|
101
|
+
if t.coverage:
|
|
102
|
+
command = (
|
|
103
|
+
'gcovr --root . --exclude build/ --exclude "(.+/)?tests?/" --gcov-executable "$GCOV" '
|
|
104
|
+
f"--txt --cobertura coverage.xml --fail-under-line {t.coverage_threshold}"
|
|
105
|
+
)
|
|
106
|
+
steps.append(step("Coverage threshold", run=command))
|
|
107
|
+
return steps
|
|
108
|
+
|
|
109
|
+
def coverage_file(self, cfg: CIConfig) -> str:
|
|
110
|
+
return "coverage.xml"
|
|
111
|
+
|
|
112
|
+
def reports(self, cfg: CIConfig) -> TestReports:
|
|
113
|
+
junit = "build/meson-logs/testlog.junit.xml" if cfg.package_manager == "meson" else "build/junit.xml"
|
|
114
|
+
coverage = cfg.test.coverage
|
|
115
|
+
return TestReports(
|
|
116
|
+
junit=junit,
|
|
117
|
+
cobertura="coverage.xml" if coverage else "",
|
|
118
|
+
coverage_regex=COVERAGE_REGEX if coverage else "",
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
def audit(self, cfg: CIConfig) -> List[Item]:
|
|
122
|
+
scan = step("Dependency vulnerability scan", run="osv-scanner scan source -L conan.lock")
|
|
123
|
+
return [install_osv_scanner(), scan]
|
|
124
|
+
|
|
125
|
+
def build(self, cfg: CIConfig) -> List[Item]:
|
|
126
|
+
default = "meson compile -C build" if cfg.package_manager == "meson" else "cmake --build build"
|
|
127
|
+
return [configure(cfg, release=True), step("Build", run=cfg.build.command or default)]
|
|
128
|
+
|
|
129
|
+
def dependabot_ecosystem(self, cfg: CIConfig) -> str:
|
|
130
|
+
# Dependabot has no ecosystem for CMake, Conan or Meson.
|
|
131
|
+
return ""
|
|
132
|
+
|
|
133
|
+
def _toolchain(self, cfg: CIConfig, compiler: str) -> Step:
|
|
134
|
+
tools = ["ca-certificates", "git", "ninja-build"]
|
|
135
|
+
tools.append("meson" if cfg.package_manager == "meson" else "cmake")
|
|
136
|
+
if cfg.package_manager == "conan":
|
|
137
|
+
tools.append("pipx")
|
|
138
|
+
if cfg.package_manager == "vcpkg":
|
|
139
|
+
tools += ["curl", "zip", "unzip", "tar", "pkg-config"]
|
|
140
|
+
if cfg.lint.enabled:
|
|
141
|
+
tools += [{"clang-format": "clang-format", "clang-tidy": "clang-tidy", "cppcheck": "cppcheck"}[t]
|
|
142
|
+
for t in cfg.lint.tools if t in ("clang-format", "clang-tidy", "cppcheck")]
|
|
143
|
+
if cfg.test.enabled and cfg.test.coverage:
|
|
144
|
+
tools.append("gcovr")
|
|
145
|
+
if cfg.security.audit:
|
|
146
|
+
tools.append("curl")
|
|
147
|
+
tools_list = " ".join(dict.fromkeys(tools))
|
|
148
|
+
if "${{" in compiler:
|
|
149
|
+
# An expression (the setup action's input): through the environment, never pasted into the script.
|
|
150
|
+
run = _TOOLCHAIN.format(compiler="$QUOLL_COMPILER", tools=tools_list)
|
|
151
|
+
return step("Set up the toolchain", run=run, env={"QUOLL_COMPILER": compiler})
|
|
152
|
+
return step("Set up the toolchain", run=_TOOLCHAIN.format(compiler=compiler, tools=tools_list))
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def configure(cfg: CIConfig, instrumented: bool = False, release: bool = False) -> Step:
|
|
156
|
+
"""Configure the build in build/ (compilation database included), resolving the dependencies.
|
|
157
|
+
|
|
158
|
+
`instrumented` adds coverage and sanitizers (tests); `release` builds optimized (build job).
|
|
159
|
+
"""
|
|
160
|
+
t = cfg.test
|
|
161
|
+
flags: List[str] = []
|
|
162
|
+
if instrumented and t.coverage:
|
|
163
|
+
flags.append("--coverage")
|
|
164
|
+
if instrumented and t.sanitizers:
|
|
165
|
+
flags.append("-fsanitize=address,undefined -fno-sanitize-recover=all -fno-omit-frame-pointer")
|
|
166
|
+
build_type = "Release" if release else "Debug"
|
|
167
|
+
|
|
168
|
+
if cfg.package_manager == "meson":
|
|
169
|
+
options = [f"--buildtype={build_type.lower()}"]
|
|
170
|
+
if instrumented and t.coverage:
|
|
171
|
+
options.append("-Db_coverage=true")
|
|
172
|
+
if instrumented and t.sanitizers:
|
|
173
|
+
options.append("-Db_sanitize=address,undefined -Dc_args=-fno-sanitize-recover=all "
|
|
174
|
+
"-Dcpp_args=-fno-sanitize-recover=all")
|
|
175
|
+
return step("Configure", run=f"meson setup build {' '.join(options)}")
|
|
176
|
+
|
|
177
|
+
lines: List[str] = []
|
|
178
|
+
args = [f"-DCMAKE_BUILD_TYPE={build_type}", "-DCMAKE_EXPORT_COMPILE_COMMANDS=ON"]
|
|
179
|
+
if flags:
|
|
180
|
+
joined = " ".join(flags)
|
|
181
|
+
args += [f'-DCMAKE_C_FLAGS="{joined}"', f'-DCMAKE_CXX_FLAGS="{joined}"']
|
|
182
|
+
if cfg.package_manager == "conan":
|
|
183
|
+
lines += [
|
|
184
|
+
"pipx run conan profile detect --exist-ok",
|
|
185
|
+
f"pipx run conan install . --output-folder=build --build=missing -s build_type={build_type}",
|
|
186
|
+
]
|
|
187
|
+
args.append("-DCMAKE_TOOLCHAIN_FILE=build/conan_toolchain.cmake")
|
|
188
|
+
elif cfg.package_manager == "vcpkg":
|
|
189
|
+
lines.append(_VCPKG)
|
|
190
|
+
args.append('-DCMAKE_TOOLCHAIN_FILE="$VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake"')
|
|
191
|
+
lines.append(f"cmake -S . -B build -G Ninja {' '.join(args)}")
|
|
192
|
+
return step("Configure", run="\n".join(lines))
|