-
Notifications
You must be signed in to change notification settings - Fork 107
Expand file tree
/
Copy pathgenerate.mk
More file actions
990 lines (825 loc) · 44.7 KB
/
generate.mk
File metadata and controls
990 lines (825 loc) · 44.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
# Some helper funcions. See below for the configuration variables...
# Where the makefile is located.
# Not entirely sure why I need to adjust `\` to `/` on Windows, since non-mingw32 Make should already operate on Linux-style paths?
override makefile_dir := $(patsubst ./,.,$(subst \,/,$(dir $(firstword $(MAKEFILE_LIST)))))
# A string of all single-letter Make flags, without spaces.
override single_letter_makeflags := $(filter-out -%,$(firstword $(MAKEFLAGS)))
# Non-empty if this is a dry run with `-n`.
override dry_run := $(findstring n,$(single_letter_makeflags))
# Non-empty if `--trace` is present.
override tracing := $(filter --trace,$(MAKEFLAGS))
# A space.
override space := $(call) $(call)
# A comma.
override comma := ,
# A newline.
override define lf :=
$(call)
$(call)
endef
# This function encloses $1 in quotes. We also replace newlines with spaces.
override quote = '$(subst ','"'"',$(subst $(lf), ,$1))'
# Same as `$(shell ...)`, but triggers an error on failure.
# Currently this intentionally ignores `$(dry_run)`, since all our usages of it need to work even in dry runs.
override safe_shell = $(if $(tracing),$(warning Running command: $1))$(shell $1)$(if $(tracing),$(warning Command returned $(.SHELLSTATUS)))$(if $(filter 0,$(.SHELLSTATUS)),,$(error Command failed with exit code $(.SHELLSTATUS): `$1`))
# Same as `safe_shell`, but discards the output.
override safe_shell_exec = $(call,$(call safe_shell,$1))
# Loads the contents of file $1, replacing newlines with spaces.
override load_file = $(strip $(file <$1))
# Compare version numbers: A <= B
override version_leq = $(shell printf '%s\n' $1 $2 | sort -CV)$(filter 0,$(.SHELLSTATUS))
# Recursive wildcard function. $1 is a list of directories, $2 is a list of wildcards.
override rwildcard = $(foreach d,$(wildcard $(1:=/*)),$(call rwildcard,$d,$2) $(filter $(subst *,%,$2),$d))
# Assign to a variable safely, e.g. `$(call var,foo := 42)`.
override var = $(eval override $(subst $,$$$$,$1))
# Given a single number $1 = N, returns numbers from 0 to N-1 inclusive, space-separated.
# This is relatively expensive, so don't call this more than necessary.
override seq = $(call safe_shell,bash -c $(call quote,echo {0..$(call safe_shell,bash -c 'echo $$(($(strip $1)-1))')}))
# Enable double expansion of prerequisites. `$1_ObjectFiles` below needs this.
.SECONDEXPANSION:
# --- Configuration variables start here:
# What OS?
ifeq ($(OS),Windows_NT)
IS_WINDOWS := 1
IS_MACOS := 0
IS_LINUX := 0
else
UNAME := $(shell uname -s)
ifeq ($(UNAME),Darwin)
IS_WINDOWS := 0
IS_MACOS := 1
IS_LINUX := 0
else
IS_WINDOWS := 0
IS_MACOS := 0
IS_LINUX := 1
endif
endif
override IS_WINDOWS := $(filter-out 0,$(IS_WINDOWS))
override IS_MACOS := $(filter-out 0,$(IS_MACOS))
override IS_LINUX := $(filter-out 0,$(IS_LINUX))
# On Windows, check that we are in the VS prompt, or at least that `VCToolsInstallDir` is defined (which is what Clang needs).
# Otherwise Clang may or may not choose some weird system libraries.
ifneq ($(IS_WINDOWS),)
ifeq ($(origin VCToolsInstallDir),undefined)
$(error Must run this in Visual Studio developer command prompt, or at least copy the value of the `VCToolsInstallDir` env variable)
endif
endif
# What target language?
TARGET=python
#TARGET=c
override known_target_languages := python c csharp
override TARGET := $(strip $(TARGET))
$(if $(filter-out 1,$(words $(TARGET)))$(filter-out $(known_target_languages),$(TARGET)),$(error Wrong value of `TARGET`, must be one of: $(known_target_languages)))
override is_py := $(filter python,$(TARGET))
override is_c := $(filter c,$(TARGET))
override is_csharp := $(filter csharp,$(TARGET))
ifeq ($(TARGET),python)
# Set to 1 if you're planning to make a wheel from this module.
FOR_WHEEL := 0
override FOR_WHEEL := $(filter-out 0,$(FOR_WHEEL))
$(info Those modules are for a Python wheel? $(if $(FOR_WHEEL),YES,NO))
# Build `libpybind11nonlimitedapi_meshlib_X.Y.so` shims automatically, for all installed Python versions?
# You can always build them manually via `make shims -B`.
# The default is "yes" if we're either building for a Wheel, or if we're doing `make shims` (then we're enabling this variable to use the correct detection logic on Windows).
BUILD_SHIMS := $(if $(or $(FOR_WHEEL),$(filter shims,$(MAKECMDGOALS))),1,0)
override BUILD_SHIMS := $(filter-out 0,$(BUILD_SHIMS))
$(info Build shims? $(if $(BUILD_SHIMS),YES,NO))
endif # $(TARGET) == python
# Set to 1 if MeshLib was built in debug mode. Ignore this on Windows. By default we're trying to guess this based on the CMake cache.
# Currently this isn't needed for anything, hence commented out.
# MESHLIB_IS_DEBUG :=
# ifeq ($(IS_WINDOWS),)
# MESHLIB_IS_DEBUG := $(if $(filter Debug,$(shell cmake -L $(MESHLIB_SHLIB_DIR)/.. 2>/dev/null | grep -Po '(?<=CMAKE_BUILD_TYPE:STRING=).*')),1)
# $(info MeshLib built in debug mode? $(if $(filter-out 0,$(MESHLIB_IS_DEBUG)),YES,NO))
# endif
# override MESHLIB_IS_DEBUG := $(filter-out 0,$(MESHLIB_IS_DEBUG))
# ---- Windows-only vars: [
ifeq ($(TARGET),python)
# For Windows, set this to Debug or Release. This controls which MeshLib build we'll be using.
VS_MODE := Release
override valid_vs_modes := Debug Release
$(if $(filter-out $(valid_vs_modes),$(VS_MODE)),$(error Invalid `VS_MODE=$(VS_MODE)`, expected one of: $(valid_vs_modes)))
endif
# Vcpkg installation directory. We try to auto-detect it.
ifneq ($(IS_WINDOWS),)
VCPKG_DIR :=
ifeq ($(VCPKG_DIR),)
override vcpkg_marker_path := $(LOCALAPPDATA)\vcpkg\vcpkg.path.txt
VCPKG_DIR := $(call load_file,$(vcpkg_marker_path))
ifeq ($(VCPKG_DIR),)
$(error Can't find vcpkg! The path to it should be stored in `$(vcpkg_marker_path)`, but it's not there)
endif
$(info Using vcpkg at: $(VCPKG_DIR))
endif
# Try to read the triplet from the `VCPKG_DEFAULT_TRIPLET` env variable if it's set.
VCPKG_TRIPLET := $(if $(VCPKG_DEFAULT_TRIPLET),$(VCPKG_DEFAULT_TRIPLET),x64-windows-meshlib)
$(info Using vcpkg triplet: $(VCPKG_TRIPLET))
else
VCPKG_DIR = $(error We're only using vcpkg on Windows)
endif
# ] ----
# ---- MacOS-only vars: [
ifneq ($(IS_MACOS),)
# System-wide Brew installations use different paths on x86 Macs and Arm Macs: `/usr/local` and `/opt/homebrew` respectively.
# And per-user installations use yet another path, `~/.homebrew`.
HOMEBREW_DIR := $(call safe_shell,brew --prefix)
$(info Using homebrew at: $(HOMEBREW_DIR))
endif
# Min version. Not setting this seems to cause warnings when linking against MeshLib built with Apple Clang, which seems to have different defaults.
MACOS_MIN_VER :=
# ] ----
# The C++ compiler.
# When making C bindings, this is still necessary even though we don't compile anything with it.
# That's because we need to run `clang++ -print-resource-dir` and feed that resource directory to libclang, so it can find its internal headers.
ifneq ($(IS_WINDOWS),)
CXX_FOR_BINDINGS := clang++
else ifneq ($(IS_MACOS),)
CXX_FOR_BINDINGS := $(HOMEBREW_DIR)/opt/llvm@$(strip $(file <$(makefile_dir)clang_version.txt))/bin/clang++
else
# Only on Ubuntu we don't want the default Clang version, as it can be outdated. Use the suffixed one.
CXX_FOR_BINDINGS := clang++-$(strip $(file <$(makefile_dir)clang_version.txt))
endif
# Source directory of MRBind.
MRBIND_SOURCE := $(makefile_dir)../../thirdparty/mrbind
# MRBind executable .
MRBIND_EXE := $(MRBIND_SOURCE)/build/mrbind
MRBIND_GEN_C_EXE = $(MRBIND_EXE)_gen_c
MRBIND_GEN_CSHARP_EXE = $(MRBIND_EXE)_gen_csharp
# Look for MeshLib dependencies relative to this. On Linux should point to the project root, because that's where `./include` and `./lib` are.
ifneq ($(IS_WINDOWS),)
DEPS_BASE_DIR := $(VCPKG_DIR)/installed/$(VCPKG_TRIPLET)
DEPS_LIB_DIR := $(DEPS_BASE_DIR)/$(if $(filter Debug,$(VS_MODE)),debug/)lib
else
DEPS_BASE_DIR := .
DEPS_LIB_DIR := $(DEPS_BASE_DIR)/lib
endif
DEPS_INCLUDE_DIR := $(DEPS_BASE_DIR)/include
ifeq ($(TARGET),python)
# Where to find MeshLib.
ifneq ($(IS_WINDOWS),)
MESHLIB_SHLIB_DIR := source/x64/$(VS_MODE)
else
MESHLIB_SHLIB_DIR := build/Release/bin
endif
ifeq ($(wildcard $(MESHLIB_SHLIB_DIR)),)
$(warning MeshLib build directory `$(abspath $(MESHLIB_SHLIB_DIR))` doesn't exist! You either forgot to build MeshLib, or are running this script with the wrong current directory. Call this from your project's root)
endif
# Which C++ compiler we should try to match for ABI.
# Ignored on Windows.
CXX_FOR_ABI := $(if $(CXX),$(CXX),g++)
ABI_COMPAT_FLAG :=
# On Linux and MacOS, check if this compiler mangles C++20 constraints into function names. If not (old compilers), pass `-fclang-abi-compat=17` to prevent Clang 18 from mangling those.
ifeq ($(IS_WINDOWS),)# If not on Windows:
$(call safe_shell_exec,which $(CXX_FOR_ABI) >/dev/null 2>/dev/null)# Make sure this compiler exists.
ifneq ($(shell echo "template <typename T> void foo() requires true {} template void foo<int>();" | $(CXX_FOR_ABI) -xc++ - -std=c++20 -S -o - | grep -m1 '\b_Z3fooIiEvvQLb1E\b')$(filter 0,$(.SHELLSTATUS)),)
$(info ABI check: $(CXX_FOR_ABI) DOES mangle C++20 constraints into the function names.)
else
$(info ABI check: $(CXX_FOR_ABI) DOESN'T mangle C++20 constraints into the function names, enabling `-fclang-abi-compat=17`)
ABI_COMPAT_FLAG := -fclang-abi-compat=17
endif
endif
# Extra compiler and linker flags. `EXTRA_CFLAGS` also affect the parser.
EXTRA_CFLAGS :=
EXTRA_LDLAGS :=
# Flag presets.
MODE := release
ifeq ($(MODE),release)
override EXTRA_CFLAGS += -Oz -flto=thin -DNDEBUG
override EXTRA_LDFLAGS += -Oz -flto=thin $(if $(IS_MACOS),,-s)# No `-s` on macos. It seems to have no effect, and the linker warns about it.
else ifeq ($(MODE),debug)
override EXTRA_CFLAGS += -g
override EXTRA_LDFLAGS += -g
else ifeq ($(MODE),none)
# Nothing.
else
$(error Unknown MODE=$(MODE))
endif
$(info MODE: $(MODE))
ifeq ($(MODE),release)
CSHARP_MODE=Release
else
CSHARP_MODE=Debug
endif
# The list of Python versions, in the format `X.Y`.
# When setting this manually, both spaces and commas work as separators.
ifneq ($(IS_WINDOWS),)
override localappdata := $(subst \,/,$(LOCALAPPDATA))
ifneq ($(FOR_WHEEL),)
# On Windows wheel we use all versions we can find in appdata.
PYTHON_VERSIONS := $(patsubst $(localappdata)/Programs/Python/Python3%,3.%,$(filter $(localappdata)/Programs/Python/Python3%,$(wildcard $(localappdata)/Programs/Python/Python3*)))
else
# On Windows non-wheel we detect only one version by default, the one in pkg-config.
PYTHON_VERSIONS := $(patsubst python-%-embed,%,$(basename $(notdir $(lastword $(sort $(wildcard $(DEPS_BASE_DIR)/lib/pkgconfig/python-*-embed.pc))))))
endif
else
PYTHON_VERSIONS := $(patsubst python-%-embed,%,$(call safe_shell,pkg-config --list-all | grep -Po 'python-\d.\d+-embed' | sort))
endif
$(if $(PYTHON_VERSIONS),,$(error Unable to guess the Python versions))
# Adjust commas to spaces for easier manual setup.
override PYTHON_VERSIONS := $(subst $(comma), ,$(PYTHON_VERSIONS))
# Sort the versions.
override PYTHON_VERSIONS := $(call safe_shell,echo $(call quote,$(PYTHON_VERSIONS)) | tr ' ' '\n' | sort -V)
$(info Targeting Python versions: $(PYTHON_VERSIONS)$(if $(BUILD_SHIMS),, (doesn't matter because we're not building shims)))
# Pick a Python version to use the headers from. Normally it doesn't matter which one we use, since defining `Py_LIMITED_API` should make them almost equivalent.
# Here we use the smallest version from `PYTHON_VERSIONS`.
PYTHON_HEADER_VERSION := $(firstword $(PYTHON_VERSIONS))
$(info Using Python headers from version: $(PYTHON_HEADER_VERSION))
# The min Python version to support. We use this to compute the value of the `Py_LIMITED_API` macro.
# It's probably a good idea to have this match the value in `source/MRPython/CMakeLists.txt`.
PYTHON_MIN_VERSION := 3.8
override python_min_version_hex := 0x$(call safe_shell,printf "%02x%02x" $(subst ., ,$(PYTHON_MIN_VERSION)))00f0
$(info Python min version: $(PYTHON_MIN_VERSION) (Py_LIMITED_API=$(python_min_version_hex)))
# Python compilation flags.
# Normally we guess the flags using pkg-config, in an OS-dependent way.
# You can override this by setting `PYTHON_CFLAGS` and `PYTHON_LDFLAGS`,
# the values of which should contain `@X.Y@` which will be replaced with the `X.Y` Python version, or `@XY@` for the two numbers concatenated.
# Obtain the resulting flags by calling `$(call get_python_cflags,3.10)` (and similarly for ldflags).
PYTHON_CFLAGS :=
PYTHON_LDFLAGS :=
ifneq ($(and $(IS_WINDOWS),$(BUILD_SHIMS)),)
# On Windows wheel, hardcode the flags to point to appdata.
PYTHON_CFLAGS := -I$(localappdata)/Programs/Python/Python@XY@/Include
PYTHON_LDFLAGS := -L$(localappdata)/Programs/Python/Python@XY@/libs -lpython@XY@
endif
ifeq ($(PYTHON_CFLAGS)$(PYTHON_LDFLAGS),) # If no custom flags are specified...
ifneq ($(IS_WINDOWS),)
# Intentionally using non-debug Python even in Debug builds, to mimic what MeshLib does. Unsure why we do this.
override get_python_cflags = $(call safe_shell,PKG_CONFIG_PATH=$(call quote,$(DEPS_BASE_DIR)/lib/pkgconfig) PKG_CONFIG_LIBDIR=- pkg-config --cflags python-$1-embed)
override get_python_ldflags = $(call safe_shell,PKG_CONFIG_PATH=$(call quote,$(DEPS_BASE_DIR)/lib/pkgconfig) PKG_CONFIG_LIBDIR=- pkg-config --libs python-$1-embed) -L$(DEPS_BASE_DIR)/lib
else # Linux or MacOS:
override get_python_cflags = $(call safe_shell,pkg-config --cflags python-$1-embed)
ifneq ($(IS_MACOS),)
# On MacOS we don't link Python, instead we use `-Xlinker -undefined -Xlinker dynamic_lookup` to avoid the errors.
# This is important to avoid segfaults when importing the wheel.
override get_python_ldflags =
else # not MacOS:
override get_python_ldflags = $(call safe_shell,pkg-config --libs python-$1-embed)
endif # not MacOS
endif # Linux or MacOS
else # if using custom flags
override get_python_cflags = $(subst @XY@,$(subst .,,$1),$(subst @X.Y@,$1,$(PYTHON_CFLAGS)))
override get_python_ldflags = $(subst @XY@,$(subst .,,$1),$(subst @X.Y@,$1,$(PYTHON_LDFLAGS)))
endif # using custom flags
# Python module suffix.
ifneq ($(IS_WINDOWS),)
PYTHON_MODULE_SUFFIX := .pyd
else
PYTHON_MODULE_SUFFIX := .so
# # Python-config executable. Returns `python3-config`, or `python-3.XX-config`.
# PYTHON_CONFIG := $(subst -,,$(PYTHON_PKGCONF_NAME:-embed=))-config
# PYTHON_MODULE_SUFFIX := $(call safe_shell,$(PYTHON_CONFIG) --extension-suffix)
endif
$(info Using Python module suffix: $(PYTHON_MODULE_SUFFIX))
# Shared library naming pattern.
ifneq ($(IS_WINDOWS),)
SHIM_SHLIB_NAMING := %.dll
else
ifneq ($(IS_MACOS),)
# For consistency with the shims that CMake builds.
SHIM_SHLIB_NAMING := lib%.dylib
else
SHIM_SHLIB_NAMING := lib%.so
endif
endif
$(info Shared library name pattern: $(SHIM_SHLIB_NAMING))
# Enable PCH.
# Not all modules we build use PCHs even if this is true. But if this is false, PCHs are disabled for all modules.
ENABLE_PCH := 1
override ENABLE_PCH := $(filter-out 0,$(ENABLE_PCH))
# Those are passed when compiling the PCH. Can be empty.
# If this is non-empty and has any flags other than `-fpch-instantiate-templates`, we compile an additional `.o` for the PCH and link it to the result.
# (And building this `.o` even when it's not needed doesn't seem to cause any issues.)
# There are three flags that can be used in any combination here: `-fpch-codegen -fpch-debuginfo -fpch-instantiate-templates`.
# `-fpch-codegen` seems to be buggy, causing weird errors: undefined references to libfmt/gtest/some other functions when used with `-fpch-instantiate-templates`,
# or weird overload resolution errors during compilation without that.
PCH_CODEGEN_FLAGS := -fpch-debuginfo -fpch-instantiate-templates
# --- Guess the build settings for the optimal speed:
# Guess the amount of RAM we have (in gigabytes), to select an appropriate build profile.
# Also guess the number of CPU cores.
ifneq ($(IS_MACOS),)
ASSUME_RAM := $(shell bash -c 'echo $$(($(shell sysctl -n hw.memsize) / 1000000000))')
ASSUME_NPROC := $(call safe_shell,sysctl -n hw.ncpu)
else
# `--giga` uses 10^3 instead of 2^10, which is actually good for us, since it overreports a bit, which counters computers typically having slightly less RAM than 2^N gigs.
ASSUME_RAM := $(shell LANG= free --giga 2>/dev/null | awk 'NR==2{print $$2}')
ASSUME_NPROC := $(call safe_shell,nproc)
endif
# We clamp the nproc to this value, because when you have more cores, our heuristics fall apart (and you might run out of ram).
# The heuristics are not necessarily bad though, it's possible that less cores than jobs can be better in some cases?
MAX_NPROC := 16
CAPPED_NPROC := $(ASSUME_NPROC)
override nproc_string := $(ASSUME_NPROC) cores
ifeq ($(call safe_shell,echo $$(($(ASSUME_NPROC) >= $(MAX_NPROC)))),1)
CAPPED_NPROC := $(MAX_NPROC)
override nproc_string := >=$(MAX_NPROC) cores
endif
ifneq ($(ASSUME_RAM),)
ifeq ($(call safe_shell,echo $$(($(ASSUME_RAM) >= 64))),1)
override ram_string := >=64G RAM
# The default number of jobs. Override with `-jN` or `JOBS=N`, both work fine.
JOBS := $(CAPPED_NPROC)
# How many translation units to use for the bindings. Bigger value = less RAM usage, but usually slower build speed.
# When changing this, update the default value for `-j` above.
NUM_FRAGMENTS := $(CAPPED_NPROC)
else ifeq ($(call safe_shell,echo $$(($(ASSUME_RAM) >= 32))),1)
override ram_string := ~32G RAM
NUM_FRAGMENTS := $(call safe_shell,echo $$(($(CAPPED_NPROC) * 2)))# = CAPPED_NPROC * 2
JOBS := $(CAPPED_NPROC)
else ifeq ($(call safe_shell,echo $$(($(ASSUME_RAM) >= 16))),1)
# At this point we have so little RAM that we ignore nproc completely (or would need to clamp it to something like ~8, but who even has less cores than that?).
override ram_string := ~16G RAM
NUM_FRAGMENTS := 64
JOBS := 8
else
override ram_string := ~8G RAM (oof)
NUM_FRAGMENTS := 64
JOBS := 4
endif
else
override ram_string := unknown, assuming ~16G
NUM_FRAGMENTS := 64
JOBS := 8
endif
MAKEFLAGS += -j$(JOBS)
ifeq ($(filter-out file,$(origin NUM_FRAGMENTS) $(origin JOBS)),)
$(info Build machine: $(nproc_string), $(ram_string); defaulting to NUM_FRAGMENTS=$(NUM_FRAGMENTS) -j$(JOBS))
else
$(info Build machine: $(nproc_string), $(ram_string); NUM_FRAGMENTS=$(NUM_FRAGMENTS) -j$(JOBS))# This can print the wrong `-j` if you override it using `-j` instead of `JOBS=N`.
endif
# You can change this to something else to rename the module, to have it side-by-side with the legacy one.
PACKAGE_NAME := meshlib
endif # $(TARGET) == python
# How to translate C++ header directory names to the C directory names.
CPP_TO_C_DIR_NAME = $(patsubst MR%,MRC%,$1)
# --- The list of modules:
MODULES :=
MODULES += mrmesh
mrmesh_PyName := mrmeshpy
mrmesh_InputProjects := MRMesh MRIOExtras MRSymbolMesh MRVoxels
mrmesh_ExtraMrbindFlags := --allow MR
mrmesh_ExtraInputDirs := $(makefile_dir)extra_headers
mrmesh_PyEnablePch := 1
mrmesh_PyNumFragments := $(NUM_FRAGMENTS)
# Those files are parsed and baked into the final bindings.
mrmesh_PyExtraInputFiles := $(makefile_dir)helpers.cpp
# Those files are compiled as is and linked into the final bindings.
mrmesh_PyExtraSourceFiles := $(makefile_dir)aliases.cpp
# Enable Cuda? You can set this to 0 if you don't have Cuda installed.
# Even if this is false, we emit a dummy `isCudaAvailable()` that always returns false. That's what we use on Macs where there is no Cuda.
ENABLE_CUDA := $(if $(IS_MACOS),0,1)
override ENABLE_CUDA := $(filter-out 0,$(ENABLE_CUDA))
$(info Enable Cuda: $(if $(ENABLE_CUDA),YES,NO))
MODULES += mrcuda
ifneq ($(ENABLE_CUDA),)
mrcuda_InputProjects := MRCuda
else
mrcuda_ExtraInputDirs := $(makefile_dir)cuda_placeholder
mrcuda_CAddToCommonGeneratorFlags := \
--map-path $(call quote,$(makefile_dir)cuda_placeholder)/cuda_placeholder.h $(call CPP_TO_C_DIR_NAME,MRCuda)/MRCudaBasic \
--assume-include-dir $(call quote,$(makefile_dir)cuda_placeholder)
mrcuda_CImaginaryInputDirs := MRCuda
endif
mrcuda_ExtraMrbindFlags := --allow MR::Cuda
mrcuda_CSubLibraryMacroPrefix := MRC_CUDA_
mrcuda_CSubLibraryOutputProject := MeshLibC2Cuda
mrcuda_CSharpSubLibraryOutputProject := MRDotNet2Cuda
mrcuda_PyName := mrcudapy
# Which other Python modules to import at startup.
mrcuda_PyDependsOn := $(PACKAGE_NAME).mrmeshpy
# ----------- End of configuration variables.
.DELETE_ON_ERROR: # Delete output on command failure. Otherwise you'll get incomplete bindings.
ifeq ($(TARGET),python)
MODULE_OUTPUT_DIR := $(MESHLIB_SHLIB_DIR)/$(PACKAGE_NAME)
endif
ifeq ($(TARGET),c)
C_CODE_OUTPUT_DIR := $(makefile_dir)../../source/MeshLibC2
endif
ifeq ($(TARGET),csharp)
CSHARP_CODE_OUTPUT_DIR := $(makefile_dir)../../source/MRDotNet2
endif
INPUT_FILES_BLACKLIST := $(call load_file,$(makefile_dir)input_file_blacklist.txt)
INPUT_FILES_WHITELIST := %
ifneq ($(filter c csharp,$(TARGET)),)
TEMP_OUTPUT_DIR := $(makefile_dir)../../source/MeshLibC2/temp
else ifneq ($(IS_WINDOWS),)
TEMP_OUTPUT_DIR := source/TempOutput/Bindings_$(TARGET)/x64/$(VS_MODE)
else
TEMP_OUTPUT_DIR := build/binds
endif
ifneq ($(TARGET),csharp) # Stuff below is for all languages except C#. C# logic is at the bottom of this makefile.
INPUT_GLOBS := *.h
ifeq ($(TARGET),python)
# This is passed to `mrbind --format=...`.
MRBIND_PARSER_OUTPUT_FORMAT=macros
MRBIND_PARSER_OUTPUT_EXT=.cpp
else
MRBIND_PARSER_OUTPUT_FORMAT=json
MRBIND_PARSER_OUTPUT_EXT=.json
endif
# Note that we're ignoring `operator<=>` in `mrbind_flags.txt` because it causes errors on VS2022:
# `undefined symbol: void __cdecl std::_Literal_zero_is_expected(void)`,
# `referenced by source/TempOutput/PythonBindings/x64/Release/binding.0.o:(public: __cdecl std::_Literal_zero::_Literal_zero<int>(int))`.
MRBIND_FLAGS := --format=$(MRBIND_PARSER_OUTPUT_FORMAT) $(call load_file,$(makefile_dir)mrbind_flags.txt)
MRBIND_FLAGS_FOR_EXTRA_INPUTS := $(call load_file,$(makefile_dir)mrbind_flags_for_helpers.txt)
COMPILER_FLAGS := $(ABI_COMPAT_FLAG) $(EXTRA_CFLAGS) $(call load_file,$(makefile_dir)common_compiler_parser_flags.txt) -I. -I$(DEPS_INCLUDE_DIR) -I$(makefile_dir)../../source
# Add `-frelaxed-template-template-args` if Clang is old enough to support it. Newer versions have this behavior by default.
# Clang 18 and older need this flag. Clang 19 and 20 do the right thing by default, but still allow the flag with a deprecation warning. Clang 21 and newer consider this an unknown flag and error.
COMPILER_FLAGS += $(shell $(CXX_FOR_BINDINGS) --help | grep -o -- -frelaxed-template-template-args)
# Required for vcpkg environments
COMPILER_FLAGS += -I$(DEPS_INCLUDE_DIR)/eigen3
# TODO: use system Eigen
COMPILER_FLAGS += -isystem $(makefile_dir)../../thirdparty/eigen
COMPILER_FLAGS += -isystem $(makefile_dir)../../thirdparty/mrbind-pybind11/include
COMPILER_FLAGS_LIBCLANG := $(call load_file,$(makefile_dir)parser_only_flags.txt)
COMPILER := $(CXX_FOR_BINDINGS) $(subst $(lf), ,$(call load_file,$(makefile_dir)compiler_only_flags.txt)) -I$(makefile_dir)
# Need whitespace to handle `~` correctly.
COMPILER_FLAGS += -I $(MRBIND_SOURCE)/include
COMPILER_FLAGS_LIBCLANG += -DMR_PARSING_FOR_ANY_BINDINGS
COMPILER += -DMR_COMPILING_ANY_BINDINGS
ifeq ($(TARGET),python)
COMPILER_FLAGS_LIBCLANG += -DMR_PARSING_FOR_PB11_BINDINGS
COMPILER += -DMR_COMPILING_PB11_BINDINGS
else ifeq ($(TARGET),c)
COMPILER_FLAGS_LIBCLANG += -DMR_PARSING_FOR_C_BINDINGS
# This doesn't actually get used, since this makefile doesn't compile the C bindings, it only generates them
# We have to set those flags in CMake.
COMPILER += -DMR_COMPILING_C_BINDINGS
endif
LINKER := $(CXX_FOR_BINDINGS) -fuse-ld=lld
# Unsure if `-dynamiclib` vs `-shared` makes any difference on MacOS. I'm using the former because that's what CMake does.
# No $(PYTHON_LDFLAGS) here, that's only for our patched Pybind library.
LINKER_FLAGS := $(EXTRA_LDFLAGS) -L$(DEPS_LIB_DIR) -L$(DEPS_BASE_DIR)/lib -L$(MESHLIB_SHLIB_DIR) $(if $(is_py),-lMRPython) $(if $(IS_MACOS),-dynamiclib,-shared) $(call load_file,$(makefile_dir)linker_flags.txt)
# Set resource directory. Otherwise e.g. `offsetof` becomes non-constexpr,
# because the header override with it being constexpr is in this resource directory.
# We certainly need this on Windows and MacOS. It's not strictly necessary on Ubuntu, but is needed on Arch, so better make it unconditional.
COMPILER_FLAGS += -resource-dir=$(strip $(call safe_shell,$(CXX_FOR_BINDINGS) -print-resource-dir))
MRBIND_GEN_C_FLAGS := $(call load_file,$(makefile_dir)mrbind_gen_c_flags.txt)
# Adjusting canonical types to fixed-size typedefs.
ifeq ($(TARGET),c)
# Here you can choose between `--canonicalize-to-fixed-size-typedefs` and `--canonicalize-64-to-fixed-size-typedefs` to control
# whether the integerl types smaller than 64 bits are replaced with the typedefs or not. The 64-bit ones always need to be replaced.
MRBIND_FLAGS += --canonicalize-64-to-fixed-size-typedefs --canonicalize-size_t-to-uint64_t --implicit-enum-underlying-type-is-always-int
MRBIND_GEN_C_FLAGS += --reject-long-and-long-long --use-size_t-typedef-for-uint64_t
endif
# Inherit class members.
ifeq ($(TARGET),c)
MRBIND_FLAGS += --copy-inherited-members
endif
ifeq ($(TARGET),c)
# Output interop description JSON for C#.
MRBIND_GEN_C_FLAGS += --output-desc-json $(call quote,$(TEMP_OUTPUT_DIR)/interop_desc.json)
# Those are required when generating C, if we then want to produce C# from it.
# But even ignoring C#, they make C code look better.
MRBIND_GEN_C_FLAGS += --bind-shared-ptr-virally --force-emit-common-helpers
endif
ifneq ($(IS_WINDOWS),)
# "Cross"-compile to MSVC.
COMPILER_FLAGS += --target=x86_64-pc-windows-msvc
LINKER_FLAGS += --target=x86_64-pc-windows-msvc
# This seems to be undocumented?! MSYS2 CLANG64 needs it to successfully cross-compile, because the default `-rtlib=compiler-rt` causes it to choke.
# For some reason MIGNW64 and UCRT64 correctly guess the right default.
LINKER_FLAGS += -rtlib=platform
# Don't generate .lib files.
LINKER_FLAGS += -Wl,-noimplib
# Library paths:
COMPILER_FLAGS += -isystem $(makefile_dir)../../thirdparty/parallel-hashmap
COMPILER_FLAGS += -D_DLL -D_MT
# Only seems to matter on VS2022 and not on VS2019, for some reason.
COMPILER_FLAGS += -DNOMINMAX
COMPILER_FLAGS += -D_SILENCE_ALL_CXX23_DEPRECATION_WARNINGS
# Don't export Pybind exceptions. This works around Clang bug: https://github.com/llvm/llvm-project/issues/118276
# And I'm not sure if exporting them even did anything useful on Windows in the first place.
COMPILER_FLAGS += -DPYBIND11_EXPORT_EXCEPTION=
ifeq ($(VS_MODE),Debug)
COMPILER_FLAGS += -Xclang --dependent-lib=msvcrtd -D_DEBUG
# Override to match meshlib:
COMPILER_FLAGS += -D_ITERATOR_DEBUG_LEVEL=0
else # VS_MODE == Release
COMPILER_FLAGS += -Xclang --dependent-lib=msvcrt
endif
endif # Windows
# Linux.
ifneq ($(IS_LINUX),)
COMPILER_FLAGS += -I/usr/include/jsoncpp -isystem/usr/include/freetype2 -isystem/usr/include/gdcm-3.0
endif
# MacOS.
ifneq ($(IS_MACOS),)
# Our dependencies are here.
COMPILER_FLAGS += -I$(HOMEBREW_DIR)/include
# Boost.stacktrace complains otherwise.
COMPILER_FLAGS += -D_GNU_SOURCE
# The min version. We override it to avoid incompatibility warnings against Apple Clang when linking.
ifneq ($(MACOS_MIN_VER),)
COMPILER_FLAGS += -mmacosx-version-min=$(MACOS_MIN_VER)
LINKER_FLAGS += -mmacosx-version-min=$(MACOS_MIN_VER)
endif # have MACOS_MIN_VER
ifeq ($(TARGET),python)
LINKER_FLAGS += -L$(HOMEBREW_DIR)/lib
LINKER_FLAGS += -ltbb
# This fixes an error during wheel creation:
# /Library/Developer/CommandLineTools/usr/bin/install_name_tool: changing install names or rpaths can't be redone for: /private/var/folders/c2/_t7lgq_s3zb_r01vy_1qd6nh0000gs/T/tmpatczljnu/wheel/meshlib/mrmeshpy.so (for architecture arm64) because larger updated load commands do not fit (the program must be relinked, and you may need to use -headerpad or -headerpad_max_install_names)
# Apparently there's not enough space in the binary to fit longer library paths, and this pads it to have to up MAXPATHLEN space for each path.
LINKER_FLAGS += -Wl,-headerpad_max_install_names
# Those fix a segfault when importing the module, that only happens for wheels, not raw binaries.
# Pybind manual says you must use those.
# Also note that this is one long flag (`-undefined dynamic_lookup`), not two independent fones.
# Also not that you MUST NOT link the Python libs in addition to doing this, otherwise you'll still get segfaults.
LINKER_FLAGS += -Xlinker -undefined -Xlinker dynamic_lookup
endif # $(TARGET) == python
endif # MacOS
# Python-only.
ifeq ($(TARGET),python)
# Linux or MacOS.
ifneq ($(IS_LINUX)$(IS_MACOS),)
COMPILER += -fvisibility=hidden -fvisibility-inlines-hidden
COMPILER_FLAGS += -fPIC
# Override Pybind ABI identifiers to force compatibility with `mrviewerpy` (which is compiled with some other compiler, but is also made to define those).
COMPILER_FLAGS += -DPYBIND11_COMPILER_TYPE='"_meshlib"' -DPYBIND11_BUILD_ABI='"_meshlib"'
# MacOS rpath is quirky: 1. Must use `-rpath,` instead of `-rpath=`. 2. Must specify the flag several times, apparently can't use
# `:` or `;` as a separators inside of one big flag. 3. As you've noticed, it uses `@loader_path` instead of `$ORIGIN`.
rpath_origin := $(if $(IS_MACOS),@loader_path,$$$$ORIGIN)
LINKER_FLAGS += -Wl,-rpath,'$(rpath_origin)' -Wl,-rpath,'$(rpath_origin)/..' -Wl,-rpath,$(call quote,$(abspath $(MODULE_OUTPUT_DIR))) -Wl,-rpath,$(call quote,$(abspath $(MESHLIB_SHLIB_DIR))) -Wl,-rpath,$(call quote,$(abspath $(DEPS_LIB_DIR)))
endif # Linux or MacOS.
endif # Python-only.
# Directories:
# Temporary output.
$(TEMP_OUTPUT_DIR):
@mkdir -p $(call quote,$@)
ifeq ($(TARGET),python)
# Module output.
$(MODULE_OUTPUT_DIR):
@mkdir -p $(call quote,$@)
endif
override all_outputs :=
ifeq ($(TARGET),python)
# Things for our patched pybind: --- [
# Also setting `Py_LIMITED_API` is a part of this, but it's spread all over this makefile.
COMPILER += -DPYBIND11_NONLIMITEDAPI_LIB_SUFFIX_FOR_MODULE='"meshlib"'
# Pybind normally sets this to 5 in Python 3.12 and newer, and to 4 before that. But we need the same number everywhere for our modules to work on
# multiple different Python versions. We can't set it to 4 (since that's not compatible with the new Python, see https://github.com/pybind/pybind11/pull/4570),
# but we can set it to 5 unconditionally (Pybind doesn't do it by default only for ABI compatibility).
COMPILER += -DPYBIND11_INTERNALS_VERSION=5
# ] --- end things for our patched pybind
# Build the library files for different Python versions, for our patched Pybind.
PYBIND_LIBS_OUTPUT_DIR := $(MODULE_OUTPUT_DIR)
PYBIND_SOURCE_DIR := $(makefile_dir)../../thirdparty/mrbind-pybind11
PYBIND_NONLIMITEDAPI_CPP := $(PYBIND_SOURCE_DIR)/source/non_limited_api/non_limited_api.cpp
PYBIND_NONLIMITEDAPI_LIB_NAME_PREFIX := pybind11nonlimitedapi_meshlib_
override shim_outputs :=
$(foreach v,$(PYTHON_VERSIONS),\
$(call var,_obj := $(TEMP_OUTPUT_DIR)/$(PYBIND_NONLIMITEDAPI_LIB_NAME_PREFIX)$v.o)\
$(call var,_shlib := $(PYBIND_LIBS_OUTPUT_DIR)/$(patsubst %,$(SHIM_SHLIB_NAMING),$(PYBIND_NONLIMITEDAPI_LIB_NAME_PREFIX)$v))\
$(call var,shim_outputs += $(_shlib))\
$(eval $(_obj): $(PYBIND_NONLIMITEDAPI_CPP) | $(TEMP_OUTPUT_DIR) ; @echo $(call quote,[Compiling Pybind shim] $(_obj)) && $(COMPILER) $(call get_python_cflags,$v) $(COMPILER_FLAGS) $$< -c -o $$@)\
$(eval $(_shlib): $(_obj) ; @echo $(call quote,[Linking Pybind shim] $(_shlib)) && $(LINKER) $$^ -o $$@ $(call get_python_ldflags,$v) $(LINKER_FLAGS) -lpybind11nonlimitedapi_stubs)\
)
.PHONY: shims
shims: $(shim_outputs)
ifneq ($(BUILD_SHIMS),)
override all_outputs += $(shim_outputs)
endif
endif # $(TARGET) == python
# Those are used by `module_snippet` below.
.PHONY: only-generate
# This code segment is repeated for every module. $1 is the module name.
# This part sets some basic variables.
override define module_snippet_vars =
# Which directories we search for headers.
$(call var,$1__InputDirs := $(addprefix $(makefile_dir)../../source/,$($1_InputProjects)) $($1_ExtraInputDirs))
# Input headers.
$(call var,$1__InputFiles := $(filter-out $(INPUT_FILES_BLACKLIST),$(filter $(INPUT_FILES_WHITELIST),$(call rwildcard,$($1__InputDirs),$(INPUT_GLOBS)))))
endef
$(foreach x,$(MODULES),$(eval $(call module_snippet_vars,$x)))
# This part handles Python-specific variables.
ifeq ($(TARGET),python)
override define module_snippet_vars_py =
# Unset the PCH flag on the module if PCHs are disabled globally.
$(if $(ENABLE_PCH),,$(call var,$1_PyEnablePch :=))
# Set the default number of fragments, if not specified.
$(if $($1_PyNumFragments),,$(call var,$1_PyNumFragments := 1))
# Pick whatever Python version for get_python_cflags, it shouldn't matter.
$(call var,$1_CompilerFlagsPython := -DPy_LIMITED_API=$(python_min_version_hex) $(call get_python_cflags,$(PYTHON_HEADER_VERSION)))
# Compiler + compiler-only flags, adjusted per module. Don't use those for parsing.
$(call var,$1_CompilerFlagsFixed := $($1_CompilerFlagsPython) $(COMPILER_FLAGS) -DMB_PB11_MODULE_NAME=$($1_PyName) $(if $($1_PyDependsOn),-DMB_PB11_MODULE_DEPS=$(call quote,$(subst $(space),$(comma),$(patsubst %,"%",$($1_PyDependsOn))))))
endef
$(foreach x,$(MODULES),$(eval $(call module_snippet_vars_py,$x)))
endif # $(TARGET) == python
# This part runs the parser.
override define module_snippet_parse =
# Produce the one combined header including all our input headers.
# And if PCH is enabled, this also includes the headers to bake.
$(call var,$1__CombinedHeaderOutput := $(TEMP_OUTPUT_DIR)/$1.combined.hpp)
$($1__CombinedHeaderOutput): $($1__InputFiles) | $(TEMP_OUTPUT_DIR)
$$(file >$$@,#pragma once$$(lf))
$(call,### Special-case iostream, because a lot of our operators `<<` and `>>` are defined in headers with only `iosfwd` included, to avoid having `iostream` in our headers.)
$(call,### While we could conditionally include iostream in every header that does this, with an ifdef to only enable it for bindings, it's easier to do this globally here.)
$$(file >>$$@,#include <iostream>$$(lf))
$(call,### Write all our headers.)
$$(foreach f,$($1__InputFiles),$$(file >>$$@,#include "$$f"$$(lf)))
$(call,### Additional headers to bake into the PCH. The condition is to speed up parsing a bit.)
$(if $(is_py),\
$$(if $($1_PyEnablePch),$$(file >>$$@,#ifndef MR_PARSING_FOR_PB11_BINDINGS$$(lf)#include <pybind11/pybind11.h>$$(lf)#endif))\
$(call,### This alternative version bakes the whole our `core.h` [which includes `<pybind11/pybind11.h>], but for some reason my measurements show it to be a tiny bit slower. Weird.)\
$(call,### #ifndef MR_PARSING_FOR_PB11_BINDINGS$(lf)#define MB_PB11_STAGE -1$(lf)#include MRBIND_HEADER$(lf)#undef MB_PB11_STAGE$(lf)#endif$(lf))\
$(call,### Note temporarily setting `MB_PB11_STAGE=-1`, we don't want to bake any of the macros.)\
)
# Run the parser.
# Note, this DOESN'T use the PCH, because the macros are different (PCH enables `-DMR_COMPILING_PB11_BINDINGS`, but this needs `-DMR_PARSING_FOR_PB11_BINDINGS`).
$(call var,$1__ParserSourceOutput := $(TEMP_OUTPUT_DIR)/$1.generated$(MRBIND_PARSER_OUTPUT_EXT))
only-generate: $($1__ParserSourceOutput)
$($1__ParserSourceOutput): $($1__CombinedHeaderOutput) | $(TEMP_OUTPUT_DIR)
@echo $(call quote,[$1] [Parsing] $($1__ParserSourceOutput))
@$(MRBIND_EXE) $(subst $,$$$$,$(MRBIND_FLAGS)) $($1_ExtraMrbindFlags) $$(call quote,$$<) -o $$(call quote,$$@) -- $(COMPILER_FLAGS_LIBCLANG) $(COMPILER_FLAGS) $($1_CompilerFlagsPython)
endef
ifneq ($(is_py),)
# In Python, the parser runs once per module.
$(foreach x,$(MODULES),$(eval $(call module_snippet_parse,$x)))
else
# In other languages, the parser runs once, so we generate a fake combined module and apply the parser to that.
$(foreach x,$(MODULES),\
$(call var,meshlib_InputProjects += $($x_InputProjects))\
$(call var,meshlib_ExtraMrbindFlags += $($x_ExtraMrbindFlags))\
$(call var,meshlib__InputDirs += $($x__InputDirs))\
$(call var,meshlib__InputFiles += $($x__InputFiles))\
)
$(eval $(call module_snippet_parse,meshlib))
endif
# This part builds the Python modules.
ifeq ($(TARGET),python)
override define module_snippet_build_py =
# Compile the PCH.
$(call var,$1__BakedPch :=)
$(call var,$1__PchImportFlag :=)
$(call var,$1__PchObject :=)
$(if $($1_PyEnablePch),\
$(call var,$1__BakedPch := $(TEMP_OUTPUT_DIR)/$1.combined_pch.hpp.gch)\
$(call var,$1__PchImportFlag := -include$($1__BakedPch:.gch=))\
\
$($1__BakedPch): $($1__CombinedHeaderOutput) ; @echo $(call quote,[$1] [Compiling PCH] $($1__BakedPch)) && $(COMPILER) -o $$@ -xc++-header $$< $($1_CompilerFlagsFixed) $(PCH_CODEGEN_FLAGS)\
)
# PCH object file, if enabled.
# We strip the include directories from the flags here, because Clang warns that those are unused.
$(if $(and $($1_PyEnablePch),$(filter-out -fpch-instantiate-templates,$(PCH_CODEGEN_FLAGS))),\
$(call var,$1__PchObject := $(TEMP_OUTPUT_DIR)/$1.combined_pch.hpp.o)\
\
$($1__PchObject): $($1__BakedPch) ; @echo $(call quote,[$1] [Compiling PCH object] $($1__PchObject)) && $(filter-out -isystem% -I%,$(subst -isystem ,-isystem,$(subst -I ,-I,$(COMPILER) $($1_CompilerFlagsFixed)))) -c -o $$@ $($1__BakedPch)\
)
# Compile N object files (fragments) from the generated source.
$(TEMP_OUTPUT_DIR)/$1.fragment.%.o: $($1__ParserSourceOutput) $($1__BakedPch) | $(TEMP_OUTPUT_DIR)
@echo $$(call quote,[$1] [Compiling] $$< (fragment $$*))
@$(COMPILER) $$(call quote,$$<) -c -o $$(call quote,$$@) $($1_CompilerFlagsFixed) $($1__PchImportFlag) -DMB_NUM_FRAGMENTS=$(strip $($1_PyNumFragments)) -DMB_FRAGMENT=$$* $$(if $$(filter 0,$$*),-DMB_DEFINE_IMPLEMENTATION)
# A list of all object files.
# NOTE: This is amended later, so we must refer to it lazily.
$(call var,$1__ObjectFiles := $(patsubst %,$(TEMP_OUTPUT_DIR)/$1.fragment.%.o,$(call seq,$($1_PyNumFragments))))
# Link the module.
# Have to evaluate `$1__ObjectFiles` lazily to observe the later updates to it. This also relies on `.SECONDEXPANSION`.
$(call var,$1__LinkerOutput := $(MODULE_OUTPUT_DIR)/$($1_PyName)$(PYTHON_MODULE_SUFFIX))
$(call var,all_outputs += $($1__LinkerOutput))
$($1__LinkerOutput): $$$$($1__ObjectFiles) | $(MODULE_OUTPUT_DIR)
@echo $$(call quote,[$1] [Linking] $$@)
@$(LINKER) $$^ -o $$(call quote,$$@) $(LINKER_FLAGS) $(addprefix -l,$($1_InputProjects) pybind11nonlimitedapi_stubs)
# A pretty target.
.PHONY: $($1_PyName)
$($1_PyName): $($1__LinkerOutput)
endef
$(foreach x,$(MODULES),$(eval $(call module_snippet_build_py,$x)))
endif # $(TARGET) == python
# This part generates the C code.
ifeq ($(TARGET),c)
override define module_snippet_generate_c =
# Produce the one combined header including all our input headers.
# And if PCH is enabled, this also includes the headers to bake.
$(call var,$1__CCodeGenerationMarker := $(TEMP_OUTPUT_DIR)/$1.generation_marker)
$(call var,all_outputs += $($1__CCodeGenerationMarker))
# Currently I'm not adding a `/$1` suffix to the output directory because we only have one project enabled for C, and only run the parser once.
# Even if we support more projects (Cuda?), the parser should only be ran one anyway. So when we need multiple projects, this makefile will need to be reworked.
$(call var,$1__CCodeOutputDir := $(C_CODE_OUTPUT_DIR))
$($1__CCodeGenerationMarker): $($1__ParserSourceOutput) | $(TEMP_OUTPUT_DIR)
@echo $(call quote,[$1] [Generating C Code] $($1__CCodeOutputDir))
@$(MRBIND_GEN_C_EXE) \
--input $(call quote,$($1__ParserSourceOutput)) \
--output-header-dir $(call quote,$($1__CCodeOutputDir)/include) \
--output-source-dir $(call quote,$($1__CCodeOutputDir)/src) \
$(foreach x,$($1_InputProjects),--map-path $(call quote,$(makefile_dir)../../source/$x) $(call CPP_TO_C_DIR_NAME,$x)) \
--assume-include-dir $(call quote,$(makefile_dir)../../source) \
$(foreach m,$(MODULES),$(if $(and $($m_CSubLibraryMacroPrefix),$($m_InputProjects)),--split-library $($m_CSubLibraryMacroPrefix) '' $(call quote,$(subst $(space),:,$(call CPP_TO_C_DIR_NAME,$($m_InputProjects)))))) \
$(MRBIND_GEN_C_FLAGS) \
$(foreach m,$(MODULES),$($m_CAddToCommonGeneratorFlags))
$(call, ### Now copy the sub-libraries into their own directories.)
true $(strip $(foreach m,$(MODULES),\
$(if $(and $($m_CSubLibraryOutputProject),$($m_CSubLibraryOutputProject)),\
$(call var,_suffix := $(if $($m_COutputDirSuffix),/$($m_COutputDirSuffix)))\
$(foreach d,include src,\
$(call var,_target_dir := $(dir $(C_CODE_OUTPUT_DIR))$($m_CSubLibraryOutputProject)/$d)\
&& rm -rf $(_target_dir) \
&& mkdir -p $(_target_dir) \
$(foreach s,$(foreach p,$($m_InputProjects) $($m_CImaginaryInputDirs),$(C_CODE_OUTPUT_DIR)/$d/$(call CPP_TO_C_DIR_NAME,$p)),\
&& mv $s $(dir $(C_CODE_OUTPUT_DIR))$($m_CSubLibraryOutputProject)/$d$(_suffix) \
)\
)\
)\
))
endef
$(eval $(call module_snippet_generate_c,meshlib))
endif # $(TARGET) == c
# ---- End loop over modules.
ifeq ($(TARGET),python)
# This snippet parses and compiles an extra file.
# $1 is the module name, $2 is the input file name.
override define extra_file_snippet =
$(call var,_generated := $(TEMP_OUTPUT_DIR)/$1.extra.$(basename $(notdir $2)).cpp)
$(call var,_object := $(_generated:.cpp=.o))
$(call var,$1__ObjectFiles += $(_object))
only-generate: $(_generated)
$(_generated): $2 | $(TEMP_OUTPUT_DIR)
@echo $(call quote,[$1] [Generating] $(notdir $(_generated)))
@$(MRBIND_EXE) $(MRBIND_FLAGS_FOR_EXTRA_INPUTS) $(call quote,$2) -o $(call quote,$(_generated)) -- $(COMPILER_FLAGS_LIBCLANG) $(COMPILER_FLAGS) $($1_CompilerFlagsPython)
$(_object): $(_generated) | $(TEMP_OUTPUT_DIR)
@echo $(call quote,[$1] [Compiling] $(_generated))
@$(COMPILER) $(call quote,$(_generated)) -c -o $(call quote,$(_object)) $($1_CompilerFlagsFixed)
endef
$(foreach x,$(MODULES),$(foreach y,$($x_PyExtraInputFiles),$(eval $(call extra_file_snippet,$x,$y))))
# This snippet compiles an extra file as is, without passing it through the parser/generator.
# $1 is the module name, $2 is the input file name.
override define extra_pregen_file_snippet =
$(call var,_object := $(TEMP_OUTPUT_DIR)/$1.custom.$(notdir $(2:.cpp=.o)))
$(call var,$1__ObjectFiles += $(_object))
$(_object): $2 | $(TEMP_OUTPUT_DIR)
@echo $(call quote,[$1] [Compiling] $2)
@$(COMPILER) $(call quote,$2) -c -o $(call quote,$(_object)) $($1_CompilerFlagsFixed)
endef
$(foreach x,$(MODULES),$(foreach y,$($x_PyExtraSourceFiles),$(eval $(call extra_pregen_file_snippet,$x,$y))))
# The init script.
INIT_SCRIPT := $(MODULE_OUTPUT_DIR)/__init__.py
$(INIT_SCRIPT): $(makefile_dir)__init__.py
@cp $< $@
ifeq ($(IS_WINDOWS),) # If not on Windows, strip the windows-only part. Here and below we avoid `-i inplace` and use `awk` instead of `gawk` to not have to install `gawk` on MacOS, which can require building stuff from source on old MacOS.
@awk '/### windows-only: \[/{x=1} {if (!x) print} x && /### \]/{x=0}' $@ >$(call quote,$(MODULE_OUTPUT_DIR)/tmp.txt) && mv $(call quote,$(MODULE_OUTPUT_DIR)/tmp.txt) $@
endif
ifeq ($(FOR_WHEEL),) # If not on building a wheel, strip the wheel-only part.
@awk '/### wheel-only: \[/{x=1} {if (!x) print} x && /### \]/{x=0}' $@ >$(call quote,$(MODULE_OUTPUT_DIR)/tmp.txt) && mv $(call quote,$(MODULE_OUTPUT_DIR)/tmp.txt) $@
endif
override all_outputs += $(INIT_SCRIPT)
# Copying modules next to the exe on Windows.
# I don't think this is actually needed, since we erase them when creating the installer.
# That was originally done because it's hard to make VS build them directly in the correct directory,
# so some of our scripts look for them outside of `meshlib/`. Probably a good idea to fix that and not copy here at all.
ifneq ($(IS_WINDOWS),)
override all_outputs += $(MESHLIB_SHLIB_DIR)/__init__.py
$(MESHLIB_SHLIB_DIR)/__init__.py: $(INIT_SCRIPT)
@cp $< $@
override modules_copied_to_bin_dir := $(patsubst %,,$(MODULES))
$(foreach m,$(MODULES),\
$(call var,_in := $(MODULE_OUTPUT_DIR)/$($m_PyName)$(PYTHON_MODULE_SUFFIX))\
$(call var,_out := $(MESHLIB_SHLIB_DIR)/$($m_PyName)$(PYTHON_MODULE_SUFFIX))\
$(call var,all_outputs += $(_out))\
$(eval $(_out): $(_in) ; @cp $(_in) $(_out))\
)
endif
endif # $(TARGET) == python
# All modules.
.DEFAULT_GOAL := all
.PHONY: all
all: $(all_outputs)
else # If C#:
# C# needs almost none of the logic in this file, just one simple rule.
.PHONY: generate
generate:
$(strip $(MRBIND_GEN_CSHARP_EXE) \
--input-json $(call quote,$(TEMP_OUTPUT_DIR)/interop_desc.json) \
--output-dir $(call quote,$(CSHARP_CODE_OUTPUT_DIR)/src) \
--clean-output-dir \
--imported-lib-name MeshLibC2 \
--helpers-namespace MR::Misc \
--force-namespace MR \
--dotnet-version=std2.0 \
--begin-func-names-with-lowercase \
--wrap-doc-comments-in-summary-tag \
--fat-objects \
$(call, ### Handle sub-libraries) \
$(foreach m,$(MODULES),$(if $(and $($m_CSubLibraryMacroPrefix),$($m_CSubLibraryOutputProject)),--imported-split-lib-name $($m_CSubLibraryMacroPrefix) $($m_CSubLibraryOutputProject))) \
)
# # Can't compile sub-libraries separately yet, because we can't define the same C# partial class (which we use as namespaces) in different C# assemblies.
# $(call, ### Now copy over the generated sub-libraries)
# true $(strip $(foreach m,$(MODULES),\
# $(foreach p,$($m_InputProjects),\
# $(if $($m_CSharpSubLibraryOutputProject),\
# $(call var,_target_dir := $(CSHARP_CODE_OUTPUT_DIR)/../$($m_CSharpSubLibraryOutputProject)/src)\
# && rm -rf $(_target_dir)\
# && mkdir -p $(_target_dir)\
# && mv $(CSHARP_CODE_OUTPUT_DIR)/src/$(call CPP_TO_C_DIR_NAME,$p) $(_target_dir)\
# )\
# )\
# ))
.DEFAULT_GOAL := build
.PHONY: build
build: generate
dotnet build $(call quote,$(CSHARP_CODE_OUTPUT_DIR)) $(if $(CSHARP_MODE),-c $(CSHARP_MODE))
# # Can't compile sub-libraries separately yet, because we can't define the same C# partial class (which we use as namespaces) in different C# assemblies.
# $(foreach m,$(MODULES),\
# $(if $($m_CSharpSubLibraryOutputProject),\
# && dotnet build $(call quote,$(CSHARP_CODE_OUTPUT_DIR)/../$($m_CSharpSubLibraryOutputProject)) $(if $(CSHARP_MODE),-c $(CSHARP_MODE))\
# )\
# )\
endif