Add a MLT=0 build for a tippecanoe without MapLibre Tile support

MapLibre Tile support pulls in the maplibre-tile-spec submodule and needs
cmake to build it, which is a lot to ask of anyone who only wants to work
with Mapbox Vector Tiles.

`make MLT=0` compiles with -DNO_MLT, skips the submodule and its cmake
build entirely, and drops the MLT tests from `make test`. The result needs
no dependencies beyond the ones MVT already needed, and can be built from
a checkout with no submodules at all.

Everything that touches the MLT library is behind the #ifdef, which is
just the two files that were written for it. The option parsing and the
tile format helpers stay compiled either way, so nothing else needs to
know: --output-format=mlt reports that the build has no MLT support rather
than being an unrecognized value, and a tile that is recognized as MLT
reports the same instead of being misparsed as a protobuf, since the
format sniffing itself doesn't need the library.

CI builds and tests this configuration from a checkout without submodules,
so it can't quietly stop working.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LxhELiLtpUFwrPSWwTYdHG
This commit is contained in:
Claude
2026-08-06 18:05:42 +00:00
parent 13f1c3b6e4
commit c70d98afe8
6 changed files with 94 additions and 19 deletions
+9
View File
@@ -21,3 +21,12 @@ jobs:
run: brew install sqlite3
- run: uname -a; BUILDTYPE=${{ matrix.version }} make
- run: make test
test-without-mlt:
runs-on: ubuntu-latest
steps:
# No submodules: the MLT=0 build must not need them
- uses: actions/checkout@v3
- run: sudo apt-get install libsqlite3-dev
- run: uname -a; make MLT=0
- run: make MLT=0 test
+30 -8
View File
@@ -4,12 +4,36 @@ BUILDTYPE ?= Release
BUILD_INFO ?=
SHELL = /bin/sh
# MapLibre Tile support requires the maplibre-tile-spec submodule and cmake
# to build it. Use `make MLT=0` for a build with no dependencies beyond the
# ones that Mapbox Vector Tiles need; it can neither read nor write MLT.
MLT ?= 1
MLT_BUILD_STAMP = mlt-build/.built
ifeq ($(MLT),0)
MLT_FLAGS = -DNO_MLT
MLT_INCLUDES =
MLT_STD =
MLT_DEPS =
MLT_DECODE_LIBS =
MLT_LIBS =
MLT_TESTS =
else
MLT_FLAGS =
MLT_INCLUDES = -isystem maplibre-tile-spec/cpp/include -isystem maplibre-tile-spec/cpp/vendor/fsst
MLT_STD = -std=c++20
MLT_DEPS = $(MLT_BUILD_STAMP)
MLT_DECODE_LIBS = mlt-build/libmlt-cpp.a mlt-build/libfsst-lib.a mlt-build/fastpfor/libFastPFOR.a
MLT_LIBS = mlt-build/libmlt-cpp-encoder.a $(MLT_DECODE_LIBS)
MLT_TESTS = mlt-test mlt-decode-test mlt-output-test
endif
# inherit from env if set
CC := $(CC)
CXX := $(CXX)
CFLAGS := $(CFLAGS) -fPIE -DBUILD_INFO=$(BUILD_INFO)
CXXFLAGS := $(CXXFLAGS) -std=c++17 -fPIE -DBUILD_INFO=$(BUILD_INFO)
CXXFLAGS := $(CXXFLAGS) -std=c++17 -fPIE -DBUILD_INFO=$(BUILD_INFO) $(MLT_FLAGS)
LDFLAGS := $(LDFLAGS)
WARNING_FLAGS := -Wall -Wshadow -Wsign-compare -Wextra -Wunreachable-code -Wuninitialized -Wshadow
RELEASE_FLAGS := -O3 -DNDEBUG
@@ -57,10 +81,6 @@ H = $(wildcard *.h) $(wildcard *.hpp)
C = $(wildcard *.c) $(wildcard *.cpp)
INCLUDES = -I/usr/local/include -I. -Iclipper2/include
MLT_INCLUDES = -isystem maplibre-tile-spec/cpp/include -isystem maplibre-tile-spec/cpp/vendor/fsst
MLT_DECODE_LIBS = mlt-build/libmlt-cpp.a mlt-build/libfsst-lib.a mlt-build/fastpfor/libFastPFOR.a
MLT_LIBS = mlt-build/libmlt-cpp-encoder.a $(MLT_DECODE_LIBS)
MLT_BUILD_STAMP = mlt-build/.built
LIBS = -L/usr/local/lib
$(MLT_BUILD_STAMP): maplibre-tile-spec/cpp/CMakeLists.txt
@@ -76,7 +96,9 @@ $(MLT_BUILD_STAMP): maplibre-tile-spec/cpp/CMakeLists.txt
cmake --build mlt-build --target mlt-cpp mlt-cpp-encoder $(if $(VERBOSE),,-- -s) > /dev/null
touch $@
ifneq ($(MLT),0)
$(MLT_LIBS): $(MLT_BUILD_STAMP)
endif
tippecanoe: geojson.o jsonpull/jsonpull.o tile.o pool.o mbtiles.o geometry.o projection.o memfile.o mvt.o mlt.o mlt_decode.o serial.o main.o platform.o text.o dirtiles.o pmtiles_file.o plugin.o read_json.o write_json.o geobuf.o flatgeobuf.o evaluator.o geocsv.o csv.o geojson-loop.o json_logger.o visvalingam.o compression.o clip.o sort.o attribute.o thread.o shared_borders.o clipper2/src/clipper.engine.o $(MLT_LIBS)
$(CXX) $(PG) $(LIBS) $(FINAL_FLAGS) $(CXXFLAGS) -o $@ $^ $(LDFLAGS) -lm -lz -lsqlite3 -lpthread
@@ -104,8 +126,8 @@ tippecanoe-overzoom: overzoom.o mvt.o mlt.o mlt_decode.o clip.o evaluator.o json
%.o: %.c
$(CC) -MMD $(PG) $(INCLUDES) $(FINAL_FLAGS) $(CFLAGS) -c -o $@ $<
mlt.o mlt_decode.o: %.o: %.cpp $(MLT_BUILD_STAMP)
$(CXX) -MMD $(PG) $(INCLUDES) $(MLT_INCLUDES) $(FINAL_FLAGS) $(CXXFLAGS) -std=c++20 -c -o $@ $<
mlt.o mlt_decode.o: %.o: %.cpp $(MLT_DEPS)
$(CXX) -MMD $(PG) $(INCLUDES) $(MLT_INCLUDES) $(FINAL_FLAGS) $(CXXFLAGS) $(MLT_STD) -c -o $@ $<
%.o: %.cpp
$(CXX) -MMD $(PG) $(INCLUDES) $(FINAL_FLAGS) $(CXXFLAGS) -c -o $@ $<
@@ -119,7 +141,7 @@ indent:
TESTS = $(wildcard tests/*/out/*.json)
SPACE = $(NULL) $(NULL)
test: tippecanoe tippecanoe-decode $(addsuffix .check,$(TESTS)) raw-tiles-test parallel-test pbf-test join-test enumerate-test decode-test join-filter-test unit json-tool-test allow-existing-test csv-test layer-json-test pmtiles-test decode-pmtiles-test overzoom-test flatgeobuf-test mlt-test mlt-decode-test mlt-output-test
test: tippecanoe tippecanoe-decode $(addsuffix .check,$(TESTS)) raw-tiles-test parallel-test pbf-test join-test enumerate-test decode-test join-filter-test unit json-tool-test allow-existing-test csv-test layer-json-test pmtiles-test decode-pmtiles-test overzoom-test flatgeobuf-test $(MLT_TESTS)
./unit
suffixes = json json.gz
+6
View File
@@ -50,6 +50,12 @@ $ make -j
$ make install
```
The submodule is the [MapLibre Tile](https://github.com/maplibre/maplibre-tile-spec)
implementation, which is built with `cmake`. If you don't need MapLibre Tile support,
build with `make MLT=0` instead, which needs neither the submodule nor `cmake`. Such a
build can't read or write MLT, and rejects `--output-format=mlt`, but is otherwise the same.
Run `make clean` first if you are switching an existing build directory between the two.
See [Development](#development) below for how to upgrade your
C++ compiler or install prerequisite packages if you get
compiler errors.
+19 -4
View File
@@ -1,8 +1,6 @@
#include "mlt.hpp"
#include "errors.hpp"
#include <mlt/encoder.hpp>
#include <algorithm>
#include <cstdint>
#include <cstdio>
@@ -12,7 +10,11 @@
#include <string>
#include <vector>
#ifndef NO_MLT
#include <mlt/encoder.hpp>
using Vertex = mlt::Encoder::Vertex;
#endif
int output_format = OUTPUT_MVT;
bool mlt_sort_features = true;
@@ -22,7 +24,12 @@ void set_output_format(char **argv, const char *format) {
if (strcmp(format, "mvt") == 0 || strcmp(format, "pbf") == 0) {
output_format = OUTPUT_MVT;
} else if (strcmp(format, "mlt") == 0) {
#ifdef NO_MLT
fprintf(stderr, "%s: this build was compiled without MapLibre Tile support\n", argv[0]);
exit(EXIT_ARGS);
#else
output_format = OUTPUT_MLT;
#endif
} else {
fprintf(stderr, "%s: --output-format must be 'mvt' or 'mlt'\n", argv[0]);
exit(EXIT_ARGS);
@@ -38,13 +45,19 @@ const char *tile_format_extension(int format) {
}
std::string encode_tile(mvt_tile &tile, int format) {
#ifndef NO_MLT
if (format == OUTPUT_MLT) {
return encode_as_mlt(tile, mlt_sort_features, mlt_pretessellate);
} else {
return tile.encode();
}
#else
(void) format;
#endif
return tile.encode();
}
#ifndef NO_MLT
// An MLT property column has a single type for the whole layer, while MVT
// values each carry their own type, so a type that can hold every value of
// an attribute has to be chosen before the layer can be converted.
@@ -327,3 +340,5 @@ std::string encode_as_mlt(const mvt_tile &tile, bool sort_features, bool pretess
auto bytes = encoder.encode(layers, config);
return std::string(reinterpret_cast<const char *>(bytes.data()), bytes.size());
}
#endif
+4 -1
View File
@@ -24,14 +24,17 @@ const char *tile_format_extension(int format);
// options were selected on the command line
std::string encode_tile(mvt_tile &tile, int format);
#ifndef NO_MLT
std::string encode_as_mlt(const mvt_tile &tile, bool sort_features, bool pretessellate);
#endif
// Does this (already decompressed) tile look like MapLibre Tile rather than
// Mapbox Vector Tile data?
bool is_mlt(const std::string &message);
// Decode a MapLibre Tile into the equivalent vector tile, returning false
// (and complaining to stderr) if it can't be parsed.
// (and complaining to stderr) if it can't be parsed, or if this build
// doesn't have MLT support compiled in.
bool decode_mlt(const std::string &message, mvt_tile &out);
#endif
+26 -6
View File
@@ -1,11 +1,5 @@
#include "mlt.hpp"
#include <mlt/decoder.hpp>
#include <mlt/geometry.hpp>
#include <mlt/layer.hpp>
#include <mlt/properties.hpp>
#include <mlt/tile.hpp>
#include <algorithm>
#include <cmath>
#include <cstdint>
@@ -18,6 +12,14 @@
#include <variant>
#include <vector>
#ifndef NO_MLT
#include <mlt/decoder.hpp>
#include <mlt/geometry.hpp>
#include <mlt/layer.hpp>
#include <mlt/properties.hpp>
#include <mlt/tile.hpp>
#endif
namespace {
bool read_varint(const char *&p, const char *end, unsigned long long &out) {
@@ -39,6 +41,8 @@ bool read_varint(const char *&p, const char *end, unsigned long long &out) {
return false;
}
#ifndef NO_MLT
long long round_coord(float v) {
return (long long) std::llround(v);
}
@@ -237,6 +241,8 @@ void convert_layer(const mlt::Layer &in, mvt_layer &out, const std::shared_ptr<s
}
}
#endif
} // namespace
bool is_mlt(const std::string &message) {
@@ -267,6 +273,18 @@ bool is_mlt(const std::string &message) {
return layer_tag == 1;
}
#ifdef NO_MLT
bool decode_mlt(const std::string &message, mvt_tile &out) {
(void) message;
(void) out;
fprintf(stderr, "This build was compiled without MapLibre Tile support\n");
return false;
}
#else
bool decode_mlt(const std::string &message, mvt_tile &out) {
try {
mlt::Decoder decoder(true);
@@ -289,3 +307,5 @@ bool decode_mlt(const std::string &message, mvt_tile &out) {
return false;
}
}
#endif