402 lines
12 KiB
Dart
402 lines
12 KiB
Dart
import 'dart:io';
|
|
import 'package:archive/archive.dart';
|
|
import 'package:code_assets/code_assets.dart';
|
|
import 'package:hooks/hooks.dart';
|
|
import 'package:logging/logging.dart';
|
|
|
|
import 'package:native_toolchain_c/native_toolchain_c.dart';
|
|
import 'package:path/path.dart' as path;
|
|
|
|
void main(List<String> args) async {
|
|
await build(args, (BuildInput input, BuildOutputBuilder output) async {
|
|
final packageRoot = input.packageRoot;
|
|
var pkgRootFilePath = packageRoot.toFilePath(windows: Platform.isWindows);
|
|
|
|
final config = input.config;
|
|
|
|
// Most users will only need release builds of Filament.
|
|
// Debug builds are probably only relevant if you're a package developer debugging an internal Filament issue.
|
|
// Also note that there are known driver issues with Android debug builds, e.g.:
|
|
// https://github.com/google/filament/issues/7162
|
|
// (these aren't present in Filament release builds).
|
|
// However, if you know what you're doing, you can change "release" to "debug" .
|
|
final buildMode = BuildMode.release;
|
|
|
|
final dryRun = false;
|
|
final packageName = input.packageName;
|
|
final outputDirectory = input.outputDirectory;
|
|
final targetOS = config.code.targetOS;
|
|
|
|
final targetArchitecture = config.code.targetArchitecture;
|
|
var logPath = path.join(
|
|
pkgRootFilePath, ".dart_tool", "thermion_dart", "log", "build.log");
|
|
var logFile = File(logPath);
|
|
if (!logFile.parent.existsSync()) {
|
|
logFile.parent.createSync(recursive: true);
|
|
}
|
|
|
|
final logger = Logger("")
|
|
..level = Level.ALL
|
|
..onRecord.listen((record) => logFile.writeAsStringSync(
|
|
record.message + "\n",
|
|
mode: FileMode.append,
|
|
flush: true));
|
|
|
|
var platform = targetOS.toString().toLowerCase();
|
|
|
|
if (!dryRun) {
|
|
logger
|
|
.info("Building Thermion for ${targetOS} in mode ${buildMode.name}");
|
|
}
|
|
|
|
// We don't support Linux (yet), so the native/Filament libraries won't be
|
|
// compiled/available. However, we still want to be able to run the Dart
|
|
// package itself on a Linux host (e.g. for a wasm/dartdev backend compiler).
|
|
// TODO
|
|
if (platform == "linux") {
|
|
throw Exception("TODO");
|
|
}
|
|
|
|
var libDir = (await getLibDir(
|
|
packageRoot, targetOS, targetArchitecture, logger, buildMode))
|
|
.path;
|
|
|
|
var sources = Directory(path.join(pkgRootFilePath, "native", "src"))
|
|
.listSync(recursive: true)
|
|
.whereType<File>()
|
|
.map((f) => f.path)
|
|
.where((f) => !(f.contains("CMakeLists") || f.contains("main.cpp")))
|
|
.toList();
|
|
|
|
if (targetOS != OS.windows) {
|
|
sources = sources.where((p) => !p.contains("windows")).toList();
|
|
}
|
|
|
|
sources.addAll([
|
|
path.join(pkgRootFilePath, "native", "include", "material",
|
|
"unlit_fixed_size.c"),
|
|
path.join(pkgRootFilePath, "native", "include", "material", "image.c"),
|
|
path.join(pkgRootFilePath, "native", "include", "material", "grid.c"),
|
|
path.join(pkgRootFilePath, "native", "include", "resources",
|
|
"translation_gizmo_glb.c"),
|
|
path.join(pkgRootFilePath, "native", "include", "resources",
|
|
"rotation_gizmo_glb.c"),
|
|
]);
|
|
|
|
var libs = [
|
|
"filament",
|
|
"backend",
|
|
"filameshio",
|
|
"viewer",
|
|
if(targetOS != OS.iOS)
|
|
"filamat",
|
|
"meshoptimizer",
|
|
"mikktspace",
|
|
"geometry",
|
|
"utils",
|
|
"filabridge",
|
|
"gltfio_core",
|
|
if(targetOS != OS.android && targetOS != OS.iOS)
|
|
"gltfio",
|
|
"filament-iblprefilter",
|
|
"image",
|
|
"imageio",
|
|
"tinyexr",
|
|
"filaflat",
|
|
"dracodec",
|
|
"ibl",
|
|
"ktxreader",
|
|
"png",
|
|
"z",
|
|
"stb",
|
|
"uberzlib",
|
|
"smol-v",
|
|
"uberarchive",
|
|
"zstd",
|
|
"basis_transcoder",
|
|
if (targetOS == OS.macOS) ...["matdbg", "fgviewer"]
|
|
];
|
|
|
|
if (platform == "windows") {
|
|
// we just need the libDir and don't need to explicitly link the actual libs
|
|
// (these are linked via ThermionWin32.h)
|
|
libDir =
|
|
Directory(libDir).uri.toFilePath(windows: targetOS == OS.windows);
|
|
} else {
|
|
libs.add("stdc++");
|
|
}
|
|
|
|
final flags = []; //"-fsanitize=address"];
|
|
|
|
final defines = <String, String?>{
|
|
// uncomment this to enable (very verbose) trace logging
|
|
// "ENABLE_TRACING": "1"
|
|
};
|
|
|
|
var frameworks = [];
|
|
if (platform != "windows") {
|
|
flags.addAll(['-std=c++17']);
|
|
} else if (!dryRun) {
|
|
defines["WIN32"] = "1";
|
|
defines["_DLL"] = "1";
|
|
if (buildMode == BuildMode.debug) {
|
|
defines["_DEBUG"] = "1";
|
|
} else {
|
|
defines["RELEASE"] = "1";
|
|
defines["NDEBUG"] = "1";
|
|
}
|
|
flags.addAll([
|
|
"/std:c++20",
|
|
if (buildMode == BuildMode.debug) ...["/MDd", "/Zi"],
|
|
if (buildMode == BuildMode.release) "/MD",
|
|
"/VERBOSE",
|
|
...defines.keys.map((k) => "/D$k=${defines[k]}").toList()
|
|
]);
|
|
}
|
|
|
|
if (platform == "ios") {
|
|
frameworks.addAll([
|
|
'Foundation',
|
|
'CoreGraphics',
|
|
'QuartzCore',
|
|
'GLKit',
|
|
"Metal",
|
|
'CoreVideo',
|
|
'OpenGLES'
|
|
]);
|
|
} else if (platform == "macos") {
|
|
frameworks.addAll([
|
|
'Foundation',
|
|
'CoreVideo',
|
|
'Cocoa',
|
|
"Metal",
|
|
]);
|
|
|
|
if (!dryRun && buildMode == BuildMode.debug) {
|
|
flags.addAll([
|
|
"-g",
|
|
"-O0",
|
|
]);
|
|
}
|
|
|
|
libs.addAll(["bluegl", "bluevk"]);
|
|
} else if (platform == "android") {
|
|
libs.addAll(["GLESv3", "EGL", "bluevk", "dl", "android"]);
|
|
}
|
|
|
|
frameworks = frameworks.expand((f) => ["-framework", f]).toList();
|
|
|
|
var srcs = File(Directory.systemTemp.path +
|
|
Platform.pathSeparator +
|
|
"thermion_sources.rsp");
|
|
srcs.writeAsStringSync(sources.join("\n"));
|
|
|
|
final cbuilder = CBuilder.library(
|
|
name: packageName,
|
|
language: Language.cpp,
|
|
assetName: 'thermion_dart.dart',
|
|
sources: platform == "windows" ? [] : sources,
|
|
includes: platform == "windows"
|
|
? []
|
|
: ['native/include', 'native/include/filament'],
|
|
defines: platform == "windows" ? {} : defines,
|
|
flags: [
|
|
if (platform == "macos") '-mmacosx-version-min=13.0',
|
|
if (platform == "ios") '-mios-version-min=13.0',
|
|
...flags,
|
|
...frameworks,
|
|
if (platform != "windows") ...[
|
|
...libs.map((lib) => "-l$lib"),
|
|
"-L$libDir"
|
|
],
|
|
if (platform == "windows") ...[
|
|
"/I${path.join(pkgRootFilePath, "native", "include")}",
|
|
"/I${path.join(pkgRootFilePath, "native", "include", "filament")}",
|
|
"/I${path.join(pkgRootFilePath, "native", "include", "windows", "vulkan")}",
|
|
"@${srcs.uri.toFilePath(windows: true)}",
|
|
// ...sources,
|
|
'/link',
|
|
"/LIBPATH:$libDir",
|
|
'/DLL',
|
|
]
|
|
],
|
|
dartBuildFiles: ['hook/build.dart'],
|
|
);
|
|
|
|
await cbuilder.run(
|
|
input: input,
|
|
output: output,
|
|
logger: logger,
|
|
);
|
|
if (targetOS == OS.android) {
|
|
if (!dryRun) {
|
|
final archExtension = switch (targetArchitecture) {
|
|
Architecture.arm => "arm-linux-androideabi",
|
|
Architecture.arm64 => "aarch64-linux-android",
|
|
Architecture.x64 => "x86_64-linux-android",
|
|
Architecture.ia32 => "i686-linux-android",
|
|
_ => throw FormatException('Invalid')
|
|
};
|
|
|
|
var compilerPath = config.code.cCompiler!.compiler.path;
|
|
|
|
if (Platform.isWindows && compilerPath.startsWith("/")) {
|
|
compilerPath = compilerPath.substring(1);
|
|
}
|
|
|
|
var ndkRoot = File(compilerPath)
|
|
.parent
|
|
.parent
|
|
.uri
|
|
.toFilePath(windows: Platform.isWindows);
|
|
|
|
var stlPath = File([
|
|
ndkRoot,
|
|
"sysroot",
|
|
"usr",
|
|
"lib",
|
|
archExtension,
|
|
"libc++_shared.so"
|
|
].join(Platform.pathSeparator));
|
|
final libcpp = CodeAsset(
|
|
package: "thermion_dart",
|
|
name: "libc++_shared.so",
|
|
linkMode: DynamicLoadingBundled(),
|
|
file: stlPath.uri,
|
|
);
|
|
|
|
output.assets.addEncodedAsset(libcpp.encode());
|
|
}
|
|
}
|
|
|
|
if (targetOS == OS.windows) {
|
|
var importLib = File(path.join(
|
|
outputDirectory.path.substring(1).replaceAll("/", "\\"),
|
|
"thermion_dart.lib"));
|
|
final libthermion = CodeAsset(
|
|
package: packageName,
|
|
name: "thermion_dart.lib",
|
|
linkMode: DynamicLoadingBundled(),
|
|
file: importLib.uri,
|
|
);
|
|
output.assets.addEncodedAsset(libthermion.encode());
|
|
|
|
for (final dir in ["windows/vulkan"]) {
|
|
// , "filament/bluevk", "filament/vulkan"
|
|
final targetSubdir =
|
|
path.join(outputDirectory.path, "include", dir).substring(1);
|
|
if (!Directory(targetSubdir).existsSync()) {
|
|
Directory(targetSubdir).createSync(recursive: true);
|
|
}
|
|
|
|
for (var file
|
|
in Directory(path.join(pkgRootFilePath, "native", "include", dir))
|
|
.listSync()) {
|
|
if (file is File) {
|
|
final targetPath =
|
|
path.join(targetSubdir, path.basename(file.path));
|
|
file.copySync(targetPath);
|
|
final include = CodeAsset(
|
|
package: packageName,
|
|
name: "include/$dir/${path.basename(file.path)}",
|
|
linkMode: DynamicLoadingBundled(),
|
|
file: file.uri,
|
|
);
|
|
output.assets.addEncodedAsset(include.encode());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
String _FILAMENT_VERSION = "v1.58.0";
|
|
String _getLibraryUrl(String platform, String mode) {
|
|
return "https://pub-c8b6266320924116aaddce03b5313c0a.r2.dev/filament-${_FILAMENT_VERSION}-${platform}-${mode}.zip";
|
|
}
|
|
|
|
//
|
|
// Download precompiled Filament libraries for the target platform from Cloudflare.
|
|
//
|
|
Future<Directory> getLibDir(Uri packageRoot, OS targetOS,
|
|
Architecture targetArchitecture, Logger logger, BuildMode buildMode) async {
|
|
var platform = targetOS.toString().toLowerCase();
|
|
|
|
var mode = buildMode == BuildMode.debug ? "debug" : "release";
|
|
|
|
var libDir = Directory(path.join(
|
|
packageRoot.toFilePath(windows: Platform.isWindows),
|
|
".dart_tool",
|
|
"thermion_dart",
|
|
"lib",
|
|
_FILAMENT_VERSION,
|
|
platform,
|
|
mode));
|
|
|
|
if (platform == "android") {
|
|
final archExtension = switch (targetArchitecture) {
|
|
Architecture.arm => "armeabi-v7a",
|
|
Architecture.arm64 => "arm64-v8a",
|
|
Architecture.x64 => "x86_64",
|
|
Architecture.ia32 => "x86",
|
|
_ => throw FormatException('Invalid')
|
|
};
|
|
libDir = Directory(path.join(libDir.path, archExtension));
|
|
} else if (platform == "windows") {
|
|
if (targetArchitecture != Architecture.x64) {
|
|
throw Exception("Unsupported architecture : ${targetArchitecture}");
|
|
}
|
|
}
|
|
|
|
logger.info("Searching for Filament libraries under ${libDir.path}");
|
|
|
|
var url = _getLibraryUrl(platform, mode);
|
|
|
|
if (targetOS == OS.windows) {
|
|
url = url.replaceAll(".zip", "-vulkan.zip");
|
|
}
|
|
|
|
final filename = url.split("/").last;
|
|
|
|
// We will write an empty file called success to the unzip directory after successfully downloading/extracting the prebuilt libraries.
|
|
// If this file already exists, we assume everything has been successfully extracted and skip
|
|
final unzipDir = platform == "android" ? libDir.parent.path : libDir.path;
|
|
final successToken = File(path.join(
|
|
unzipDir, targetOS == OS.windows ? "success-vulkan" : "success"));
|
|
final libraryZip = File(path.join(unzipDir, filename));
|
|
|
|
if (!successToken.existsSync()) {
|
|
if (libraryZip.existsSync()) {
|
|
libraryZip.deleteSync();
|
|
}
|
|
|
|
if (!libraryZip.parent.existsSync()) {
|
|
libraryZip.parent.createSync(recursive: true);
|
|
}
|
|
|
|
logger.info(
|
|
"Downloading prebuilt libraries for $platform/$mode from $url to ${libraryZip}, files will be unzipped to ${unzipDir}");
|
|
final request = await HttpClient().getUrl(Uri.parse(url));
|
|
final response = await request.close();
|
|
|
|
await response.pipe(libraryZip.openWrite());
|
|
|
|
final archive = ZipDecoder().decodeBytes(await libraryZip.readAsBytes());
|
|
|
|
for (final file in archive) {
|
|
final filename = file.name;
|
|
if (file.isFile) {
|
|
final data = file.content as List<int>;
|
|
final f = File('${unzipDir}/$filename');
|
|
await f.create(recursive: true);
|
|
await f.writeAsBytes(data);
|
|
} else {
|
|
final d = Directory('${unzipDir}/$filename');
|
|
await d.create(recursive: true);
|
|
}
|
|
}
|
|
successToken.writeAsStringSync("SUCCESS");
|
|
}
|
|
return libDir;
|
|
}
|