@@ -175,6 +175,15 @@ def prepend_path(env: dict[str, str], directory: Path) -> None:
175175 env [path_key ] = str (directory ) + os .pathsep + current
176176
177177
178+ def configure_windows_temp_env (env : dict [str , str ]) -> None :
179+ if not is_windows ():
180+ return
181+ temp_dir = ROOT / ".tmp" / "subprocess-temp"
182+ temp_dir .mkdir (parents = True , exist_ok = True )
183+ env ["TEMP" ] = str (temp_dir )
184+ env ["TMP" ] = str (temp_dir )
185+
186+
178187def configure_windows_rust_env (env : dict [str , str ], cargo : Sequence [str ]) -> bool :
179188 rustup_fallback = (
180189 is_windows ()
@@ -192,6 +201,100 @@ def configure_windows_rust_env(env: dict[str, str], cargo: Sequence[str]) -> boo
192201 return True
193202
194203
204+ def env_get (env : dict [str , str ], name : str ) -> str :
205+ value = env .get (name )
206+ if value is not None :
207+ return value
208+ name_lower = name .lower ()
209+ for key , current in env .items ():
210+ if key .lower () == name_lower :
211+ return current
212+ return ""
213+
214+
215+ def cargo_target_linker_env_var (target : str ) -> str :
216+ normalized = target .upper ().replace ("-" , "_" )
217+ return f"CARGO_TARGET_{ normalized } _LINKER"
218+
219+
220+ def msvc_target_arch_dir (target : str ) -> str :
221+ if target .startswith ("x86_64-" ):
222+ return "x64"
223+ if target .startswith ("aarch64-" ):
224+ return "arm64"
225+ if target .startswith ("i686-" ):
226+ return "x86"
227+ raise SystemExit (f"unsupported MSVC target for linker lookup: { target } " )
228+
229+
230+ def msvc_host_arch_dir () -> str :
231+ if host_arch () == "arm64" :
232+ return "Hostarm64"
233+ return "Hostx64"
234+
235+
236+ def msvc_linker_candidates (env : dict [str , str ], target : str ) -> list [Path ]:
237+ target_arch = msvc_target_arch_dir (target )
238+ host_arch_dir = msvc_host_arch_dir ()
239+ candidates : list [Path ] = []
240+
241+ tools_dir = env_get (env , "VCToolsInstallDir" )
242+ if tools_dir :
243+ candidates .append (
244+ Path (tools_dir ) / "bin" / host_arch_dir / target_arch / "link.exe"
245+ )
246+
247+ roots = [
248+ env_get (env , "VCINSTALLDIR" ),
249+ (
250+ str (Path (env_get (env , "VSINSTALLDIR" )) / "VC" )
251+ if env_get (env , "VSINSTALLDIR" )
252+ else ""
253+ ),
254+ ]
255+ for root in roots :
256+ if not root :
257+ continue
258+ tools_root = Path (root ) / "Tools" / "MSVC"
259+ if not tools_root .is_dir ():
260+ continue
261+ for version in sorted (tools_root .iterdir (), reverse = True ):
262+ candidates .append (
263+ version / "bin" / host_arch_dir / target_arch / "link.exe"
264+ )
265+
266+ return candidates
267+
268+
269+ def git_for_windows_linker (path : str | None ) -> bool :
270+ if not path :
271+ return False
272+ normalized = str (path ).replace ("\\ " , "/" ).lower ()
273+ return "/git/usr/bin/link.exe" in normalized
274+
275+
276+ def configure_windows_msvc_linker_env (env : dict [str , str ]) -> None :
277+ if not is_windows ():
278+ return
279+ target = windows_target_triple ()
280+ if not target .endswith ("-pc-windows-msvc" ):
281+ return
282+ linker_var = cargo_target_linker_env_var (target )
283+ if env_get (env , linker_var ):
284+ return
285+ for candidate in msvc_linker_candidates (env , target ):
286+ if candidate .is_file ():
287+ env [linker_var ] = str (candidate )
288+ return
289+ env_path = env_get (env , "PATH" )
290+ if git_for_windows_linker (shutil .which ("link" , path = env_path or None )):
291+ raise SystemExit (
292+ "MSVC link.exe was not found, and Git for Windows link.exe is first "
293+ "in PATH. Run from a Visual Studio developer shell or configure "
294+ "ilammy/msvc-dev-cmd before Windows MSVC cargo recipes."
295+ )
296+
297+
195298def ensure_windows_target_installed (command_prefix : Sequence [str ] = ()) -> None :
196299 if not is_windows ():
197300 return
@@ -327,10 +430,10 @@ def generate_windows_import_library(dll: Path, implib: Path) -> None:
327430def ensure_windows_runtime_import_library (runtime : Path | None = None ) -> Path :
328431 library = runtime or runtime_library_path ()
329432 implib = runtime_import_library_path (library )
330- if implib .is_file ():
331- return implib
332433 if not library .is_file ():
333434 raise SystemExit (f"Windows runtime library not found at { library } " )
435+ if implib .is_file () and implib .stat ().st_mtime_ns >= library .stat ().st_mtime_ns :
436+ return implib
334437 generate_windows_import_library (library , implib )
335438 return implib
336439
@@ -576,6 +679,8 @@ def command_compile_c_readme_examples(_: argparse.Namespace) -> None:
576679
577680def command_build_cpp (args : argparse .Namespace ) -> None :
578681 mode = build_mode (args .mode )
682+ env = os .environ .copy ()
683+ configure_windows_temp_env (env )
579684 lib = runtime_library_path (mode )
580685 cmake_args = [
581686 f"-DOPENPIT_RUNTIME_LIBRARY={ lib } " ,
@@ -589,8 +694,8 @@ def command_build_cpp(args: argparse.Namespace) -> None:
589694 build = cpp_build_dir (args .kind , mode )
590695 platform_args = cmake_platform_args ()
591696 prepare_cmake_build_dir (build , platform_args )
592- run (["cmake" , "-S" , source , "-B" , build , * platform_args , * cmake_args ])
593- run (["cmake" , "--build" , build , "--config" , cmake_build_type (mode )])
697+ run (["cmake" , "-S" , source , "-B" , build , * platform_args , * cmake_args ], env = env )
698+ run (["cmake" , "--build" , build , "--config" , cmake_build_type (mode )], env = env )
594699
595700
596701def command_ctest (args : argparse .Namespace ) -> None :
@@ -646,6 +751,8 @@ def command_cpp_format(args: argparse.Namespace) -> None:
646751
647752
648753def command_lint_cpp (_ : argparse .Namespace ) -> None :
754+ env = os .environ .copy ()
755+ configure_windows_temp_env (env )
649756 repo = str (ROOT ).replace ("\\ " , "/" )
650757 binding_build = cpp_build_dir ("binding" , "debug" )
651758 examples_build = cpp_build_dir ("examples" , "debug" )
@@ -666,7 +773,8 @@ def command_lint_cpp(_: argparse.Namespace) -> None:
666773 * platform_args ,
667774 "-DCMAKE_BUILD_TYPE=Debug" ,
668775 "-DCMAKE_EXPORT_COMPILE_COMMANDS=ON" ,
669- ]
776+ ],
777+ env = env ,
670778 )
671779 if is_windows () and not (ROOT / binding_build / "compile_commands.json" ).is_file ():
672780 print (
@@ -687,7 +795,7 @@ def command_lint_cpp(_: argparse.Namespace) -> None:
687795 ]
688796 command .extend (["-p" , binding_build ])
689797 command .extend (str (path ) for path in group )
690- run (command )
798+ run (command , env = env )
691799
692800 prepare_cmake_build_dir (examples_build , platform_args )
693801 run (
@@ -700,7 +808,8 @@ def command_lint_cpp(_: argparse.Namespace) -> None:
700808 * platform_args ,
701809 "-DCMAKE_BUILD_TYPE=Debug" ,
702810 "-DCMAKE_EXPORT_COMPILE_COMMANDS=ON" ,
703- ]
811+ ],
812+ env = env ,
704813 )
705814 examples = [
706815 path
@@ -717,7 +826,7 @@ def command_lint_cpp(_: argparse.Namespace) -> None:
717826 ]
718827 command .extend (["-p" , examples_build ])
719828 command .extend (str (path ) for path in group )
720- run (command )
829+ run (command , env = env )
721830
722831
723832def command_gen_docs_cpp (_ : argparse .Namespace ) -> None :
@@ -735,9 +844,25 @@ def command_gen_docs_cpp(_: argparse.Namespace) -> None:
735844 return
736845 shutil .rmtree (ROOT / "docs" / "cpp-api" , ignore_errors = True )
737846 run (["doxygen" , "bindings/cpp/Doxyfile" ])
847+ normalize_doxygen_mainpage_anchor ()
738848 run ([sys .executable , "scripts/_generate_api_c_sitemap.py" ])
739849
740850
851+ def normalize_doxygen_mainpage_anchor () -> None :
852+ index = ROOT / "docs" / "cpp-api" / "index.html"
853+ if not index .is_file ():
854+ return
855+ text = index .read_text (encoding = "utf-8" )
856+ normalized = re .sub (
857+ r'id="md_[^"]*DoxygenMainPage"' ,
858+ 'id="openpit-cpp-sdk-mainpage"' ,
859+ text ,
860+ count = 1 ,
861+ )
862+ if normalized != text :
863+ index .write_text (normalized , encoding = "utf-8" , newline = "\n " )
864+
865+
741866def command_build_ffi (args : argparse .Namespace ) -> None :
742867 mode = build_mode (args .mode )
743868 env = os .environ .copy ()
@@ -755,6 +880,7 @@ def command_build_ffi(args: argparse.Namespace) -> None:
755880 if is_windows ():
756881 command_prefix = cargo [:- 1 ] if cargo [- 1 ] == "cargo" else []
757882 ensure_windows_target_installed (command_prefix )
883+ configure_windows_msvc_linker_env (env )
758884 rustflags = env .get ("RUSTFLAGS" , "" )
759885 env ["RUSTFLAGS" ] = f"{ rustflags } -C target-feature=+crt-static" .strip ()
760886 command .extend (["--target" , windows_target_triple ()])
@@ -785,6 +911,7 @@ def command_python_develop(args: argparse.Namespace) -> None:
785911 configure_windows_rust_env (env , cargo )
786912 command_prefix = cargo [:- 1 ] if cargo [- 1 ] == "cargo" else []
787913 ensure_windows_target_installed (command_prefix )
914+ configure_windows_msvc_linker_env (env )
788915 command .extend (["--target" , windows_target_triple ()])
789916 run (command , env = env )
790917
0 commit comments