Package CARLA on UE 5.8
Paths. scripts/… and references/… below are relative to the
directory holding this SKILL.md. Your working directory is the user's
project, not that directory, so prefix them with its absolute path or the
command is not found.
cmake --build Build/Release --target package # = package-shipping
That is the whole command. Everything hard about packaging here is in the two
things around it:
- The cook scope is a configure-time option, not a build argument.
-DCARLA_MAPS_TO_COOK= is baked into the CMake cache, so changing which maps
ship means re-configuring — and re-specifying every other option at the same
time.
BUILD SUCCESSFUL does not mean the package is usable. Four separate
defects produce a package that builds clean and then fails at runtime. All four
are detectable after the fact, which is what this skill's inspect mode is for.
Instructions
Progress:
- [ ] Step 1: Check prerequisites (bash scripts/check_env.sh)
- [ ] Step 2: Decide the cook scope and re-configure for it
- [ ] Step 3: Package (1-2 h)
- [ ] Step 4: Inspect the artifact — do NOT trust the exit code
- [ ] Step 5: Run it (run-carla-ue58-server)
Step 2: Cook scope
source scripts/env.sh
bash scripts/package.sh scope # what is configured right now
ROS2=1 MAPS="Town10HD_Opt,Town12,Town13,Town_C" \
bash ../build-carla-ue58/scripts/build.sh configure
Package paths, +-separated. Small maps are /Game/Carla/Maps/<Town>; large
World Partition maps are /Game/Carla/Maps/<Town>/<Town>.
Exclude Town15. Its one-file-per-actor packages each carry a serialized
reference to a MaterialInstanceDynamic that lives in the map package — MIDs are
transient and never saved, so the import can never resolve. 573 unique
LoadErrors, and the cook fails on the error count:
Failure - 573 error(s) → AutomationTool exiting with ExitCode=25. It has never
cooked on Linux or Windows. This is a content defect, not a configuration mistake.
Leaving CARLA_MAPS_TO_COOK empty does not cook everything — bCookAll=False,
so UAT falls back to DefaultGame.ini's five MapsToCook entries:
Town10HD_Opt, OpenDriveMap, TestMaps/EmptyMap, Mine_01, Town15/Town15.
Town15 is in that list, so the out-of-the-box package build fails. An explicit
scope is effectively mandatory. check_env.sh warns on both cases.
Keep OpenDriveMap in the scope unless you know you do not need it: it is the
host level for client.generate_opendrive_world(). Measured — with it excluded,
that call fails with a bare std::exception while the same call against -game
mode succeeds in 7.3 s.
Step 3: Package
bash scripts/package.sh build # shipping (default)
CONFIG=development bash scripts/package.sh build # package-development
or directly:
cmake --build Build/Release --target package # shipping
cmake --build Build/Release --target package-development
cmake --build Build/Release --target package-debug
cmake --build Build/Release --target package-test
cmake --build Build/Release --target package-debuggame
Under the hood this is UAT BuildCookRun with -pak -iostore, so the output is
an IoStore container (.ucas + .utoc) plus a .pak, not a loose file tree.
Expect 1–2 h and ~12 GB.
Delete Build/<preset>/Package/ first if you care what ends up inside. The
archive step copies over a previous archive without deleting
(Unreal/Package/RemoveUnrealPackageExtraFiles.cmake says so), so a package can
carry files from builds long past. Measured: a pre-existing package contained
Carla/Maps/Town12/OpenDrive/Town12.xodr that a clean build does not produce.
Stop any running packaged server first. It holds the pak files open; UAT then
fails to replace the .ucas, retries, and can still exit BUILD SUCCESSFUL with
a fresh .pak/.utoc beside a stale .ucas — internally inconsistent,
because the .utoc indexes the .ucas by byte offset. POSIX unlink semantics
make this less likely on Linux than on Windows, but the check costs nothing.
Step 4: Inspect — the part that matters
bash scripts/package.sh inspect
Checks, on the artifact rather than the log:
| Check | Failure it catches |
|---|
| pak/ucas/utoc timestamps within one build | the stale-.ucas inconsistency above |
Town1x/OpenDrive staged when that town is cooked | a large map with no road network |
Config/PostProcess staged | black screen: missing per-town exposure profile |
OpenDriveMap in the cook scope | generate_opendrive_world() failing with std::exception |
| shipping binary + launcher present and executable | a stage that dropped the binary |
VERSION git hashes | which carla / content / engine commits actually went in |
bundled wheel under PythonAPI/carla/dist/ | a package whose client cannot be installed |
What DirectoriesToAlwaysStageAsUFS actually delivers, measured on a clean 1-map
build with the engine's own UnrealPak -List (80 UFS entries, 20 .xodr):
| Path | Ships? |
|---|
Carla/Maps/OpenDrive/*.xodr | yes — all 19, regardless of cook scope |
Carla/Maps/TM/*.bin | yes |
Carla/Maps/Town15/{OpenDrive,Nav,TM} | yes (even though Town15 never cooks) |
Carla/Config/** incl. PostProcess | yes — Carla/Config IS recursive |
Carla/Maps/Town1x/TM/*.bin | yes |
Carla/Maps/Town1x/OpenDrive/*.xodr | NO |
So the gap is exactly two entries, not five: Carla/Maps/Town12/OpenDrive and
Carla/Maps/Town13/OpenDrive. A cleanly built package that cooks Town12 or Town13
ships those towns without OpenDRIVE. (Why the TM subdir stages and OpenDrive
does not is not explained by the ini.)
Step 5: Run it
cd ../run-carla-ue58-server && bash scripts/run_server.sh package
A packaged server works for clients — measured: get_world(), spawning and
navigation are all fine. What is broken is map handling:
get_available_maps() returns [], and load_world() resolves only
/Game/Carla/Maps/<Name>, so a cooked map loads by exact name while nested large
maps (Town12/Town12) and imported maps under /Game/<Pkg>/Maps/ do not. Use
-game mode when you need discovery or map switching. Detail and numbers in
[[run-carla-ue58-server]].
Examples
Example 1: "package CARLA with just Town10"
MAPS="Town10HD_Opt" re-configure, package.sh build, package.sh inspect. The
fastest useful package; skips the large-map staging concerns entirely.
Example 2: "my packaged Town_C renders black"
package.sh inspect. If Config/PostProcess is missing from the pak, that is the
cause — Default.json uses AEM_Manual exposure where the per-town profiles use
AEM_Histogram, so without the profile the scene draws correct geometry at the
wrong exposure. Add the staging entry and re-package.
Example 3: "the package built but has no maps"
Check scope first: an empty CARLA_MAPS_TO_COOK cooks everything and fails on
Town15, and a ;-separated or filesystem-path value cooks nothing while still
reporting success.
Example 4: "which commits are in this package?"
package.sh inspect prints the VERSION file: carla, content and engine git
hashes. Worth recording alongside any results you publish.
Troubleshooting
Problem: Failure - 573 error(s), 78 warning(s) / ExitCode=25 (Error_UnknownCookFailure)
Cause: Town15 in the cook scope.
Solution: exclude it. Nothing you configure can fix it; the content is wrong.
Problem: cook succeeds, package has no maps
Cause: CARLA_MAPS_TO_COOK in the wrong form — filesystem paths, or ; instead
of +.
Solution: package.sh scope shows the current value; the MAPS= knob on
build.sh configure builds a correct one.
Problem: BUILD SUCCESSFUL but the server crashes on start
Cause: possibly the stale-.ucas inconsistency from packaging over a running
server.
Solution: package.sh inspect compares timestamps; stop servers and re-package.
Problem: packaged large map loads empty / black screen
Cause: either the OFPA mount patch is missing (empty World Partition) or the
post-process profiles were not staged (wrong exposure). The two look similar; the
distinguishing symptom is that with the exposure problem the traffic lights and
signs still draw correctly.
Solution: check_env.sh reports the patch; package.sh inspect reports staging.
Problem: the cook logs dozens of Invalid duplicate copies of ExternalActor
Cause: expected side effect of the OFPA mount patch — every external package
becomes reachable under two names, and the AssetRegistry discards one
alphabetically. 74–78 warnings per cook.
Solution: none needed; it keeps the name the map expects. Worth removing properly
by scoping the mount, but it is not a failure.
Problem: Zen server does not survive cook → stage
Cause: known both platforms, different fixes. On Linux the route used is
[Zen.AutoLaunch] LimitProcessLifetime=False, which needs no manual server;
Windows uses -NoZenAutoLaunch and requires zenserver.exe --port 8558 already
running.
Solution: the Linux setting; do not copy the Windows recipe.
Problem: packaged map switching does not work / get_available_maps() is empty
Cause: two raw-filesystem checks that cannot work inside a .pak
(GetAllMapNames() uses FindFilesRecursive(..., "*.umap"), LoadNewEpisode()
gates on FPaths::FileExists()). Fixed on ue58-dev-windows via AssetRegistry
discovery; not merged into ue58-dev.
Solution: no fix on this branch. Use the editor for multi-map work, or cherry-pick
that change.
Outputs
Build/<preset>/Package/Carla-<version>-Linux-<config>/ containing
Linux/CarlaUnreal.sh + the shipping binary, Linux/CarlaUnreal/Content/Paks/
(pakchunk0-Linux.{pak,ucas,utoc} + global.{ucas,utoc}), a bundled PythonAPI/
with its wheel, and VERSION with the three git hashes — plus a .tar.gz of the
same. inspect is read-only.
Cook/stage mechanics, the staging list, and the full defect register are in
references/packaging.md.