fix: 支持 Linux 本机构建环境

This commit is contained in:
2026-08-05 17:11:05 +08:00
parent c5956365fa
commit c8ca009d13
12 changed files with 106 additions and 54 deletions

View File

@@ -4,6 +4,7 @@ const fs = require("fs");
const path = require("path");
const { spawnSync } = require("child_process");
const { readAreaConfig } = require("./lib/area-config");
const { blenderExecutable: resolveBlenderExecutable } = require("./lib/tool-paths");
const {
SCENE_LAYERS,
SCENE_FILE,
@@ -314,6 +315,7 @@ function exportCesium(area) {
const startedAt = new Date(started).toISOString();
runCommand(blenderExecutable(area), [
"--background",
"--factory-startup",
"--python",
path.join(repoRoot, "blender", "export_cesium.py"),
"--",
@@ -440,7 +442,7 @@ function compressCesiumGlb(area) {
}
function blenderExecutable(area) {
return path.join(area.blenderApp, "Contents", "MacOS", "Blender");
return resolveBlenderExecutable(area.blenderApp);
}
function ensureFile(file, label) {

View File

@@ -5,6 +5,7 @@ const path = require("path");
const os = require("os");
const { execFileSync } = require("child_process");
const { JsStreetNetwork } = require("osm2streets-js-node");
const { qgisPaths } = require("./lib/tool-paths");
const { buildCustomTurnLaneArrows } = require("./lib/turn-lane-arrows");
const {
SCENE_LAYERS,
@@ -21,9 +22,9 @@ const args = parseArgs(process.argv.slice(2));
const configPath = path.resolve(args.config || path.join(repoRoot, "config", "default.json"));
const config = loadConfig(configPath, args);
const qgisApp = config.qgisApp;
const qgisMacOS = path.join(qgisApp, "Contents", "MacOS");
const qgisPython = path.join(qgisMacOS, "python3.12");
const ogr2ogr = path.join(qgisMacOS, "ogr2ogr");
const qgis = qgisPaths(qgisApp);
const qgisPython = qgis.python;
const ogr2ogr = qgis.ogr2ogr;
const normalizeLaneArrowsScript = path.join(repoRoot, "scripts", "normalize-lane-arrows.py");
const inputPath = path.resolve(config.input);
const outDir = path.resolve(config.outDir);
@@ -128,7 +129,7 @@ fs.writeFileSync(
if (fs.existsSync(gpkgPath)) {
fs.unlinkSync(gpkgPath);
}
const ogrEnv = qgisEnv();
const ogrEnv = qgis.env;
// First layer creates the GeoPackage; the rest append into it.
SCENE_LAYERS.forEach((layer, index) => {
importLayer(gpkgPath, path.join(outDir, layerFile(layer)), layer.id, index > 0, ogrEnv);
@@ -141,7 +142,7 @@ const defaultPreviewExtent = extentString(expandBounds(
previewPad,
));
fs.writeFileSync(qgisScript, makeQgisScript({
qgisMacOS,
qgisPrefix: qgis.prefix,
gpkgPath,
projectPath,
previewPath,
@@ -154,17 +155,9 @@ execFileSync(qgisPython, [qgisScript], {
stdio: "inherit",
env: {
...process.env,
...qgisEnv(),
...qgis.env,
QT_QPA_PLATFORM: "offscreen",
PYTHONHOME: path.join(qgisApp, "Contents", "Frameworks"),
PYTHONPATH: [
path.join(qgisApp, "Contents", "Resources", "python"),
path.join(qgisApp, "Contents", "Resources", "python3.11", "site-packages"),
].join(":"),
DYLD_LIBRARY_PATH: [
qgisMacOS,
path.join(qgisApp, "Contents", "Frameworks"),
].join(":"),
...qgis.pythonEnv,
},
});
fixCanvas(projectPath, config.canvasExtent || extentString(expandBounds(bbox, canvasPad)));
@@ -252,13 +245,6 @@ function deepMerge(base, overrides) {
return out;
}
function qgisEnv() {
return {
PROJ_LIB: path.join(qgisApp, "Contents", "Resources", "qgis", "proj"),
GDAL_DATA: path.join(qgisApp, "Contents", "Resources", "qgis", "gdal"),
};
}
function getOsmBounds(xmlText) {
// OSM exports may contain distant relation members (for example subway
// nodes) that are not part of the requested map extent. Prefer the explicit
@@ -1418,13 +1404,9 @@ function normalizeLaneArrows(geojsonPath, outlineSimplifyMeters) {
stdio: "inherit",
env: {
...process.env,
...qgisEnv(),
...qgis.env,
QT_QPA_PLATFORM: "offscreen",
PYTHONHOME: path.join(qgisApp, "Contents", "Frameworks"),
PYTHONPATH: [
path.join(qgisApp, "Contents", "Resources", "python"),
path.join(qgisApp, "Contents", "Resources", "python", "plugins"),
].join(path.delimiter),
...qgis.pythonEnv,
},
});
}
@@ -1464,7 +1446,7 @@ from qgis.core import (
QgsVectorLayer,
)
QGIS_PREFIX = ${JSON.stringify(options.qgisMacOS)}
QGIS_PREFIX = ${JSON.stringify(options.qgisPrefix)}
GPKG = ${JSON.stringify(options.gpkgPath)}
PROJECT_PATH = ${JSON.stringify(options.projectPath)}
PREVIEW_PATH = ${JSON.stringify(options.previewPath)}
@@ -1472,6 +1454,11 @@ PREVIEW_EXTENT = [${options.previewExtent.split(",").map(Number).join(", ")}]
LAYER_PREFIX = ${JSON.stringify(options.layerPrefix || "osm2streets")}
LAYER_SPECS = ${JSON.stringify(qgisLayerSpecs(), null, 4)}
try:
IMAGE_FORMAT = QImage.Format.Format_ARGB32_Premultiplied
except AttributeError:
IMAGE_FORMAT = QImage.Format_ARGB32_Premultiplied
def fill_symbol(color, outline="0,0,0,0", outline_width="0"):
return QgsFillSymbol.createSimple({
"color": color,
@@ -1523,7 +1510,7 @@ settings.setDestinationCrs(QgsCoordinateReferenceSystem("EPSG:4326"))
settings.setExtent(QgsRectangle(*PREVIEW_EXTENT))
settings.setOutputSize(QSize(1600, 1100))
settings.setBackgroundColor(QColor(245, 245, 240))
image = QImage(settings.outputSize(), QImage.Format_ARGB32_Premultiplied)
image = QImage(settings.outputSize(), IMAGE_FORMAT)
image.fill(settings.backgroundColor().rgba())
painter = QPainter(image)
job = QgsMapRendererCustomPainterJob(settings, painter)

View File

@@ -60,8 +60,8 @@ function normalizeAreaConfig(raw, options = {}) {
id,
input,
outputRoot,
qgisApp: raw.qgisApp || "/Applications/QGIS.app",
blenderApp: raw.blenderApp || "/Applications/Blender.app",
qgisApp: raw.qgisApp || (process.platform === "darwin" ? "/Applications/QGIS.app" : "/usr"),
blenderApp: raw.blenderApp || (process.platform === "darwin" ? "/Applications/Blender.app" : "/usr/bin/blender"),
stages: {
intermediates: raw.stages?.intermediates ?? raw.stages?.qgis ?? true,
blender: raw.stages?.blender ?? true,

58
scripts/lib/tool-paths.js Normal file
View File

@@ -0,0 +1,58 @@
"use strict";
const path = require("path");
function qgisPaths(qgisApp) {
if (process.platform === "darwin") {
const macosDir = path.join(qgisApp, "Contents", "MacOS");
const resourcesDir = path.join(qgisApp, "Contents", "Resources");
return {
python: path.join(macosDir, "python3.12"),
ogr2ogr: path.join(macosDir, "ogr2ogr"),
ogrinfo: path.join(macosDir, "ogrinfo"),
prefix: macosDir,
env: {
PROJ_LIB: path.join(resourcesDir, "qgis", "proj"),
GDAL_DATA: path.join(resourcesDir, "qgis", "gdal"),
},
pythonEnv: {
PYTHONHOME: path.join(qgisApp, "Contents", "Frameworks"),
PYTHONPATH: [
path.join(resourcesDir, "python"),
path.join(resourcesDir, "python3.11", "site-packages"),
].join(path.delimiter),
DYLD_LIBRARY_PATH: [macosDir, path.join(qgisApp, "Contents", "Frameworks")].join(path.delimiter),
},
};
}
const binDir = path.join(qgisApp, "bin");
const shareDir = path.join(qgisApp, "share");
return {
python: path.join(binDir, "python3"),
ogr2ogr: path.join(binDir, "ogr2ogr"),
ogrinfo: path.join(binDir, "ogrinfo"),
prefix: qgisApp,
env: {
PROJ_LIB: path.join(shareDir, "proj"),
GDAL_DATA: path.join(shareDir, "gdal"),
},
pythonEnv: {
PYTHONPATH: [
path.join(shareDir, "qgis", "python"),
path.join(shareDir, "qgis", "python", "plugins"),
].join(path.delimiter),
},
};
}
function blenderExecutable(blenderApp) {
return process.platform === "darwin"
? path.join(blenderApp, "Contents", "MacOS", "Blender")
: blenderApp;
}
module.exports = {
blenderExecutable,
qgisPaths,
};

View File

@@ -18,6 +18,7 @@ const fs = require("fs");
const path = require("path");
const crypto = require("crypto");
const { spawnSync } = require("child_process");
const { blenderExecutable } = require("./lib/tool-paths");
const repoRoot = path.resolve(__dirname, "..");
const baselineRoot = path.join(repoRoot, "outputs", "_refactor-baseline");
@@ -169,8 +170,8 @@ function fileStat(file) {
}
function blendDigest(config, blend, outFile) {
const blenderApp = config.blenderApp || "/Applications/Blender.app";
const blender = path.join(blenderApp, "Contents", "MacOS", "Blender");
const blenderApp = config.blenderApp || (process.platform === "darwin" ? "/Applications/Blender.app" : "/usr/bin/blender");
const blender = blenderExecutable(blenderApp);
const run = spawnSync(blender, [
"--background", "--factory-startup",
"--python", path.join(repoRoot, "blender", "tools", "scene_digest.py"),

View File

@@ -16,6 +16,7 @@ const fs = require("fs");
const path = require("path");
const os = require("os");
const { execFileSync } = require("child_process");
const { qgisPaths } = require("./lib/tool-paths");
const {
SCENE_LAYERS,
SCENE_FILE,
@@ -27,10 +28,10 @@ const {
const args = parseArgs(process.argv.slice(2));
const config = loadConfig(args);
const qgisApp = config.qgisApp || "/Applications/QGIS.app";
const qgisMacOS = path.join(qgisApp, "Contents", "MacOS");
const ogr2ogr = path.join(qgisMacOS, "ogr2ogr");
const ogrinfo = path.join(qgisMacOS, "ogrinfo");
const qgisApp = config.qgisApp || (process.platform === "darwin" ? "/Applications/QGIS.app" : "/usr");
const qgis = qgisPaths(qgisApp);
const ogr2ogr = qgis.ogr2ogr;
const ogrinfo = qgis.ogrinfo;
const outDir = path.resolve(requireText(config.outDir, "outDir"));
const gpkgPath = path.resolve(requireText(config.gpkg, "gpkg"));
@@ -130,10 +131,7 @@ function requireText(value, key) {
}
function gdalEnv() {
return {
PROJ_LIB: path.join(qgisApp, "Contents", "Resources", "qgis", "proj"),
GDAL_DATA: path.join(qgisApp, "Contents", "Resources", "qgis", "gdal"),
};
return qgis.env;
}
function gpkgLayers() {