- jai release.jai builds the release build, then stages release/mcap-VERSION-windows-x64/ with mcap.exe and its symbols, onnxruntime (CUDA provider), FFmpeg, the assets, the OCR, label and segmentation models (each checked against its models.config), NVIDIA's CUDA 13 runtime, all of cuDNN 9 and NVRTC, a README and third-party notices; zips it with Windows' tar; prints its SHA-256. - no_cuda leaves the CUDA runtime out; - no_build packages bin/ as it is. - cuDNN is copied whole: it loads sub-libraries by name (9.26's cudnn_engines_tensor_ir64_9.dll), and one missing fails every run on the GPU. - Checked by running the package with no CUDA or cuDNN on PATH: OCR, labels and segmentation on CUDA, video import through its FFmpeg. - release/ is ignored.
289 lines
12 KiB
Plaintext
289 lines
12 KiB
Plaintext
// Makes a release of mcap for Windows x64: one folder with everything it needs to run (the program,
|
|
// onnxruntime, FFmpeg, the OCR, label and segmentation models, and NVIDIA's CUDA 13 runtime and
|
|
// cuDNN 9, so the GPU works with nothing but the NVIDIA driver installed), zipped:
|
|
//
|
|
// jai release.jai release/mcap-VERSION-windows-x64/ and release/mcap-VERSION-windows-x64.zip
|
|
// jai release.jai - no_cuda without the CUDA runtime (1.3 GB less): the GPU then needs CUDA 13 and cuDNN 9 installed
|
|
// jai release.jai - no_build package bin/ as it is, without building it again first
|
|
//
|
|
// VERSION comes from first.jai. A tree with changes, or a HEAD that is not tagged vVERSION, is warned
|
|
// about, not refused. The models must be in bin/ already: OCR's and the labels' from their
|
|
// fetch_models.jai, segmentation's from tools/export_mask2former.py (see each module's README).
|
|
|
|
#run,stallable {
|
|
set_build_options_dc(.{do_output = false});
|
|
args := get_build_options().compile_time_command_line;
|
|
with_cuda := !array_find(args, "no_cuda");
|
|
build := !array_find(args, "no_build");
|
|
set_working_directory(#filepath);
|
|
|
|
version := read_version();
|
|
if !version fail("Cannot find VERSION in first.jai.");
|
|
name := tprint("mcap-%-windows-x64", version);
|
|
log("Releasing % (%).", name, ifx with_cuda then "with the CUDA runtime" else "without the CUDA runtime");
|
|
|
|
// Where the source is: warned about, not refused.
|
|
status, status_ok := run("git", "status", "--porcelain");
|
|
if status_ok && trim(status) log("Warning: the working tree has changes; the release is built from them.");
|
|
tag, tag_ok := run("git", "describe", "--exact-match", "--tags", "HEAD");
|
|
if !tag_ok || trim(tag) != tprint("v%", version) log("Warning: HEAD is not tagged v%.", version);
|
|
|
|
if build {
|
|
log("Building the release build...");
|
|
_, built := run("jai", "first.jai", "-", "release", print = true);
|
|
if !built fail("The build failed.");
|
|
}
|
|
|
|
// The folder, from nothing.
|
|
out := tprint("release/%", name);
|
|
if file_exists(out) && (!delete_directory(out) || file_exists(out)) fail(tprint("Cannot delete the last % (is a program or a shell in it?).", out));
|
|
make_directory_if_it_does_not_exist(out, recursive = true);
|
|
|
|
// The program and its libraries.
|
|
copy("bin/mcap.exe", out);
|
|
copy("bin/mcap.pdb", out); // symbols, for crash reports' stack traces
|
|
for file_list("bin") {
|
|
if ends_with_nocase(it, ".dll") copy(it, out);
|
|
}
|
|
for string.["data", "fonts", "images"] copy_folder(tprint("bin/%", it), tprint("%/%", out, it));
|
|
|
|
// The models: each folder as the build copied it, checked for the files its models.config names.
|
|
for string.["ocr-models", "label-models", "segment-models"] {
|
|
check_models(tprint("bin/%", it));
|
|
copy_folder(tprint("bin/%", it), tprint("%/%", out, it));
|
|
}
|
|
|
|
// NVIDIA's runtime, beside the program, where Windows looks for a DLL first.
|
|
if with_cuda {
|
|
missing: [..] string;
|
|
for CUDA_FILES {
|
|
path := find_cuda_file(it);
|
|
if !path { array_add(*missing, it); continue; }
|
|
copy(path, out);
|
|
}
|
|
// All of cuDNN, not a list of it: it loads sub-libraries by name as it needs them (9.26 added
|
|
// cudnn_engines_tensor_ir64_9.dll), and one missing fails every run on the GPU.
|
|
cudnn := find_cuda_file("cudnn64_9.dll");
|
|
if !cudnn array_add(*missing, "cudnn64_9.dll");
|
|
else for file_list(path_strip_filename(cudnn)) {
|
|
name := path_filename(it);
|
|
if begins_with_nocase(name, "cudnn") && ends_with_nocase(name, ".dll") copy(it, out);
|
|
}
|
|
if missing fail(tprint("Cannot find % (on PATH, or under Program Files\\NVIDIA). Install the CUDA 13 toolkit and cuDNN 9 for CUDA 13, or release with `- no_cuda`.", join(..missing, ", ")));
|
|
}
|
|
|
|
// print has no escape for %, which the README uses: the templates' blanks are filled by name.
|
|
readme := replace(RELEASE_README, "{VERSION}", version);
|
|
readme = replace(readme, "{GPU}", ifx with_cuda then CUDA_INCLUDED else CUDA_NOT_INCLUDED);
|
|
write_entire_file(tprint("%/README.txt", out), readme);
|
|
write_entire_file(tprint("%/THIRD-PARTY-NOTICES.txt", out), replace(NOTICES, "{CUDA}", ifx with_cuda then CUDA_NOTICE else ""));
|
|
if !copy_file("modules/ffmpeg/windows/LICENSE-BUILD.txt", tprint("%/FFMPEG-BUILD.txt", out)) fail("Cannot copy FFmpeg's build notes.");
|
|
|
|
// The zip, with Windows' own tar (bsdtar), which writes zips (and Zip64, past 4 GB).
|
|
zip := tprint("release/%.zip", name);
|
|
if file_exists(zip) file_delete(zip);
|
|
log("Zipping % ...", zip);
|
|
_, zipped := run("C:/Windows/System32/tar.exe", "-a", "-c", "-f", zip, "-C", "release", name);
|
|
if !zipped fail("Zipping failed.");
|
|
|
|
folder_bytes := folder_size(out);
|
|
zip_bytes := file_size(zip);
|
|
hash_text, hashed := run("certutil", "-hashfile", zip, "SHA256");
|
|
sha := "";
|
|
if hashed {
|
|
lines := split(hash_text, "\n");
|
|
if lines.count >= 2 sha = trim(lines[1]);
|
|
}
|
|
log("Done: % (% MB), from % (% MB).", zip, zip_bytes / (1024 * 1024), out, folder_bytes / (1024 * 1024));
|
|
if sha log("SHA-256 %", sha);
|
|
}
|
|
|
|
// The CUDA toolkit's files the CUDA provider loads (modules/onnxruntime/cuda.jai: CUDA_LIBRARIES, but
|
|
// the driver and cuDNN, which is copied whole), and NVRTC, which cuDNN's runtime-compiled engines load.
|
|
CUDA_FILES :: string.[
|
|
"cudart64_13.dll", "cublas64_13.dll", "cublasLt64_13.dll", "cufft64_12.dll",
|
|
"nvrtc64_130_0.dll", "nvrtc-builtins64_134.dll",
|
|
];
|
|
|
|
// A file of CUDA's or cuDNN's: on PATH (the toolkit's installer puts it there), else the newest cuDNN
|
|
// for CUDA 13 under Program Files\NVIDIA\CUDNN (its installer does not). "" when not found.
|
|
find_cuda_file :: (name: string) -> string {
|
|
for split(path_variable(), ";") {
|
|
if !it continue;
|
|
path := tprint("%/%", trim_right(it, "\\/"), name);
|
|
if file_exists(path) return path;
|
|
}
|
|
// cuDNN: Program Files/NVIDIA/CUDNN/v9.x/bin/13.y/x64/NAME, the newest (it sorts last).
|
|
best := "";
|
|
cudnn_root := "C:/Program Files/NVIDIA/CUDNN";
|
|
if file_exists(cudnn_root) {
|
|
for file_list(cudnn_root, recursive = true) {
|
|
normal := replace(it, "\\", "/");
|
|
if path_filename(normal) != name || !contains(normal, "/bin/13.") continue;
|
|
if compare(normal, best) > 0 best = normal;
|
|
}
|
|
}
|
|
return best;
|
|
}
|
|
|
|
// Every file a models folder's models.config names in quotes is there.
|
|
check_models :: (folder: string) {
|
|
config, ok := read_entire_file(tprint("%/models.config", folder), log_errors = false);
|
|
if !ok fail(tprint("% has no models.config: build the app first.", folder));
|
|
for raw: split(config, "\n") {
|
|
line := trim(raw);
|
|
if !line || line[0] == #char "#" || line[0] == #char "[" || line[0] == #char "@" continue;
|
|
colon := find_index_from_left(line, #char ":");
|
|
if colon < 0 continue;
|
|
key := trim(slice(line, 0, colon));
|
|
value := trim(advance(line, colon + 1));
|
|
if key == "title" || value.count < 2 || value[0] != #char "\"" continue;
|
|
file := slice(value, 1, find_index_from_left(advance(value, 1), #char "\""));
|
|
if !file_exists(tprint("%/%", folder, file)) fail(tprint("% names %, which is not there; fetch or make the models (see the module's README).", folder, file));
|
|
}
|
|
}
|
|
|
|
// This process's PATH.
|
|
path_variable :: () -> string {
|
|
kernel32 :: #system_library "kernel32";
|
|
GetEnvironmentVariableW :: (name: *u16, buffer: *u16, size: u32) -> u32 #foreign kernel32;
|
|
buffer: [32768] u16; // the longest an environment variable can be
|
|
count := GetEnvironmentVariableW(utf8_to_wide("PATH",, temp), buffer.data, buffer.count);
|
|
if !count || count >= buffer.count return "";
|
|
text, ok := wide_to_utf8(buffer.data, cast(s32) count);
|
|
return ifx ok then text else "";
|
|
}
|
|
|
|
read_version :: () -> string {
|
|
text, ok := read_entire_file("first.jai");
|
|
if !ok return "";
|
|
for raw: split(text, "\n") {
|
|
line := trim(raw);
|
|
if !begins_with(line, "VERSION ::") continue;
|
|
quote := find_index_from_left(line, #char "\"");
|
|
if quote < 0 return "";
|
|
rest := advance(line, quote + 1);
|
|
return slice(rest, 0, find_index_from_left(rest, #char "\""));
|
|
}
|
|
return "";
|
|
}
|
|
|
|
// Runs a command; its output, and whether it ran and succeeded.
|
|
run :: (args: .. string, print := false) -> output: string, ok: bool {
|
|
result, output, error := run_command(..args, capture_and_return_output = !print, print_captured_output = print);
|
|
return output, result.type == .EXITED && result.exit_code == 0;
|
|
}
|
|
|
|
copy :: (source: string, folder: string) {
|
|
dest := tprint("%/%", folder, path_filename(source));
|
|
if !copy_file(source, dest) fail(tprint("Cannot copy % to %.", source, dest));
|
|
}
|
|
|
|
copy_folder :: (source: string, dest: string) {
|
|
if !file_exists(source) fail(tprint("% is not there: build the app first.", source));
|
|
if !copy_directory(source, dest) fail(tprint("Cannot copy % to %.", source, dest));
|
|
}
|
|
|
|
file_size :: (path: string) -> s64 {
|
|
_, size, ok := file_modtime_and_size(path);
|
|
return ifx ok then size else 0;
|
|
}
|
|
|
|
folder_size :: (folder: string) -> s64 {
|
|
total: s64;
|
|
for file_list(folder, recursive = true) total += file_size(it);
|
|
return total;
|
|
}
|
|
|
|
fail :: (message: string) {
|
|
log_error("Release failed: %", message);
|
|
exit(1);
|
|
}
|
|
|
|
RELEASE_README :: #string END
|
|
mcap {VERSION}, for Windows 10 and 11 (x64)
|
|
|
|
A library of screenshots, photos and videos, each kept as one .mcap file: imported from any folder
|
|
(and Snagit's .snagx), read with OCR, labelled, segmented, and searched.
|
|
|
|
Running it
|
|
|
|
Unzip this folder anywhere and run mcap.exe. Everything it needs is in this folder.
|
|
|
|
The library: mcap shows the .mcap files in D:\McapIndex\mcap. To use another folder, start it with
|
|
-library, for example from a shortcut whose target is:
|
|
|
|
"C:\Programs\mcap\mcap.exe" -library "E:\My library"
|
|
|
|
Import (above the grid) brings in a folder of screenshots, photos, videos or Snagit captures; OCR,
|
|
Labels and Segments read them. See README.md in the source for searching and the command line.
|
|
|
|
The GPU
|
|
|
|
{GPU}
|
|
|
|
Without a usable NVIDIA GPU, OCR, labels and segmentation run on the CPU: slower, but the same.
|
|
The OCR, Labels and Segments windows say whether the GPU is in use, and if not, why.
|
|
|
|
Where it keeps things
|
|
|
|
The library's .mcap files hold everything read from them. The app's settings, label list and
|
|
segment colours are in %APPDATA%\mcap.
|
|
|
|
See THIRD-PARTY-NOTICES.txt for the libraries and models included.
|
|
END
|
|
|
|
CUDA_INCLUDED :: #string END
|
|
NVIDIA's CUDA 13 runtime and cuDNN 9 are included, so OCR, labels and segmentation run on an
|
|
NVIDIA GPU (Turing or newer: GeForce RTX 20 series and up) with only the NVIDIA driver installed:
|
|
a recent Game Ready or Studio driver that supports CUDA 13.
|
|
END
|
|
|
|
CUDA_NOT_INCLUDED :: #string END
|
|
This release does not include NVIDIA's CUDA runtime. To use an NVIDIA GPU, install the CUDA 13
|
|
toolkit and cuDNN 9 for CUDA 13 from NVIDIA; mcap finds them where their installers put them.
|
|
END
|
|
|
|
NOTICES :: #string END
|
|
Third-party software and models in this release
|
|
|
|
onnxruntime 1.30.0 (onnxruntime.dll, onnxruntime_providers_*.dll)
|
|
Microsoft, MIT License. https://github.com/microsoft/onnxruntime
|
|
|
|
FFmpeg 6.0 (avcodec, avformat, avutil, swscale, swresample DLLs)
|
|
The FFmpeg developers. This is gyan.dev's "full" shared build, licensed GPL v3 as a whole: see
|
|
FFMPEG-BUILD.txt. Source: https://github.com/FFmpeg/FFmpeg (commit ea3d24bbe3).
|
|
|
|
PP-OCRv5 text detection and recognition, and PP-LCNet text line orientation (ocr-models)
|
|
PaddlePaddle (Baidu), Apache License 2.0. https://github.com/PaddlePaddle/PaddleOCR
|
|
|
|
SigLIP 2 base, patch 16, 384 (label-models)
|
|
Google, Apache License 2.0, as converted to ONNX by onnx-community.
|
|
https://huggingface.co/google/siglip2-base-patch16-384
|
|
|
|
Mask2Former, Swin-B, COCO panoptic (segment-models)
|
|
Meta (Facebook AI Research), from facebook/mask2former-swin-base-coco-panoptic, exported to ONNX by
|
|
mcap's tools/export_mask2former.py. https://github.com/facebookresearch/Mask2Former
|
|
Its class names are COCO panoptic's (https://cocodataset.org).
|
|
{CUDA}
|
|
Dear ImGui (in mcap.exe), Omar Cornut, MIT License. stb_image, stb_image_write and
|
|
stb_image_resize (in mcap.exe), Sean Barrett, public domain or MIT.
|
|
END
|
|
|
|
CUDA_NOTICE :: #string END
|
|
|
|
NVIDIA CUDA 13 runtime (cudart, cublas, cublasLt, cufft, nvrtc DLLs) and cuDNN 9 (cudnn DLLs)
|
|
NVIDIA Corporation, redistributed under the CUDA Toolkit EULA (Attachment A) and the cuDNN
|
|
Software License Agreement. https://docs.nvidia.com/cuda/eula/
|
|
https://docs.nvidia.com/deeplearning/cudnn/latest/reference/eula.html
|
|
END
|
|
|
|
#import "Basic";
|
|
#import "String";
|
|
#import "File";
|
|
#import "File_Utilities";
|
|
#import "Process";
|
|
#import "System";
|
|
#import "Compiler";
|
|
#import "Windows_Utf8";
|