codingame_tools.language.toolchain.fragment¶
fragment
¶
Composable Dockerfile fragments: the pieces a multi-language toolchain image is built from.
One image has to serve several languages at once--that is what makes a single dev container work,
and what lets cg run more than one language without a container per language. But the languages
don't get along by default. CodinGame runs Oracle JDK 21.0.4 for Java and JVM 1.8 for Clojure,
Groovy and Scala, so a single image must carry both, and neither can own the global JAVA_HOME.
Elsewhere the opposite is true and sharing is essential: C# / F# / VB.NET all run one .NET
runtime, JavaScript and TypeScript one Node, C and C++ one gcc.
Both facts point at the same shape: an image is composed from fragments with dependencies, like a makefile. A fragment is either
- a subsystem--
gcc11,jvm8,jdk21,node20--which installs a toolchain, or - a language, which usually installs nothing and merely depends on a subsystem plus supplies
its own activation script.
CandC++are exactly this: both depend ongcc11, both emit zero Dockerfile lines, and they differ only in whether their script exportsCG_CCorCG_CXX.
A fragment emitting no Dockerfile lines at all is therefore normal, not degenerate.
Why activation scripts rather than a global environment¶
Each fragment may ship /opt/cg/env.d/<slug>.sh, which exports what its toolchain needs and
sources its dependencies' scripts first, so activation is transitive. Every command cg runs in
the container is prefixed with one line:
. /opt/cg/env.d/cpp.sh
"$CG_CXX" $CG_CXXFLAGS -x c++ -o "$out" "$src"
The two alternatives were rejected deliberately:
- A global
PATH/JAVA_HOMEcannot represent two JDKs at once. The conflict above is simply unrepresentable, so this fails at the first multi-JVM image. - Wrapper executables (
/opt/cg/bin/cpp-compile) would move compile flags into the image, versioning them with the image rather than with cg--so changing a warning flag would need an image rebuild. Activation scripts keep the split where it belongs: the image knows where its toolchain is, cg knows how to invoke it.
Why ordering is deterministic¶
render_dockerfile sorts topologically and breaks ties by slug, so a subset's fragment order is
a prefix of a superset's. That makes a subset image's layers a prefix of the superset's, so adding
a language rebuilds only from that point on, and a larger published image reuses layers already
pulled. It is the reason to sort at all: correctness alone would accept any topological order.
ENV_DIR
module-attribute
¶
ENV_DIR = '/opt/cg/env.d'
Where activation scripts live inside the image--see the module docstring.
Under /opt rather than /etc/profile.d deliberately: these are not meant to apply to every
shell. Sourcing them all would defeat the point, since two of them may set JAVA_HOME
incompatibly. They are opt-in, one at a time, by the command that needs one.
CgToolchainError
¶
Bases: Exception
Raised for an unresolvable fragment set--an unknown slug, or a dependency cycle.
CgToolchainFragment
dataclass
¶
CgToolchainFragment(slug, version, depends_on=(), dockerfile='', env_script='')
One composable piece of a toolchain image.
slug
instance-attribute
¶
slug
Stable identifier, used in dependency edges, in the generated header, and as the activation script's filename. Lowercase, no spaces--it appears in shell and Dockerfile contexts.
version
instance-attribute
¶
version
Bumped whenever dockerfile or env_script changes, so an unmodified generated Dockerfile
can be detected as stale and regenerated. Deliberately per fragment rather than one global
template version: changing the Rust fragment shouldn't invalidate a C++-only image.
depends_on
class-attribute
instance-attribute
¶
depends_on = ()
Slugs that must be installed before this one. The mechanism that lets several languages share
a toolchain (C and C++ -> gcc11) and lets conflicting ones coexist (java -> jdk21
while scala -> jvm8).
dockerfile
class-attribute
instance-attribute
¶
dockerfile = ''
Statements inserted verbatim. Legitimately empty: a language whose toolchain is entirely supplied by a subsystem contributes only its dependency edge and its activation script, and emits nothing here. An empty fragment produces no Dockerfile section and so no extra layer.
env_script
class-attribute
instance-attribute
¶
env_script = ''
Body of /opt/cg/env.d/<slug>.sh, if this fragment needs one. The composer prepends the
. <dep>.sh lines itself, so a fragment only writes its own exports.
resolve_fragments
¶
resolve_fragments(requested, registry)
Every fragment needed for requested, dependencies first, in deterministic order.
Ties are broken by slug so the result is stable across runs and, more importantly, so a subset's order is a prefix of a superset's--see the module docstring on layer sharing.
Parameters:
-
requested(Iterable[str]) –Slugs asked for, in any order. Duplicates are harmless.
-
registry(Mapping[str, CgToolchainFragment]) –Every known fragment, keyed by slug.
Returns:
-
list[CgToolchainFragment]–Fragments in install order.
Raises:
-
CgToolchainError–on an unknown slug or a dependency cycle.
Source code in codingame_tools/language/toolchain/fragment.py
103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 | |
render_dockerfile
¶
render_dockerfile(fragments, *, base_image, preamble='')
The full cg-owned Dockerfile for fragments, already in install order.
Renders cg's own base.dockerfile content. Distinct from
codingame_tools.language._docker.compose_dockerfile, which composes that file on disk with
the user's custom.dockerfile -- generation versus merging.
The header is machine-readable in the same spirit as the single-language one it replaces: it records every fragment and its version, so a generated file can be recognized as cg's, checked for staleness, and told apart from one the user has edited.
Parameters:
-
fragments(list[CgToolchainFragment]) –In install order, as returned by
resolve_fragments. -
base_image(str) –Value for the
CG_BASE_IMAGEbuild arg--one pinned neutral base that every fragment installs onto, rather than a per-language base image. -
preamble(str, default:'') –Statements common to every image, inserted before any fragment.
Source code in codingame_tools/language/toolchain/fragment.py
179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 | |