setup-patches.ps1 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113
  1. param()
  2. $ErrorActionPreference = "Stop"
  3. $found = Resolve-Path "$env:USERPROFILE\.cargo\registry\src\index.crates.io-*\hts-sys-2.2.0" `
  4. -ErrorAction SilentlyContinue
  5. if (-not $found) {
  6. Write-Error "hts-sys 2.2.0 not in cargo registry. Run 'cargo fetch' first."
  7. exit 1
  8. }
  9. $dest = Join-Path $PSScriptRoot "patches\hts-sys"
  10. if (Test-Path $dest) { Remove-Item $dest -Recurse -Force }
  11. Copy-Item $found.Path $dest -Recurse
  12. Write-Host "Copied hts-sys 2.2.0 -> patches\hts-sys"
  13. $buildRs = Join-Path $dest "build.rs"
  14. $text = [System.IO.File]::ReadAllText($buildRs) -replace "`r`n", "`n"
  15. # Fix 1: guard HAVE_DRAND48. drand48/srand48 are absent from MinGW libc.
  16. $old1 = " let mut config_lines = vec![`n `"/* Default config.h generated by build.rs */`",`n `"#define HAVE_DRAND48 1`",`n ];"
  17. $new1 = " let mut config_lines = vec![`n `"/* Default config.h generated by build.rs */`",`n ];`n if target_os != `"windows`" {`n config_lines.push(`"#define HAVE_DRAND48 1`");`n }"
  18. $result = $text.Replace($old1, $new1)
  19. if ($result -eq $text) { Write-Warning "Fix 1 pattern not matched - verify hts-sys version" }
  20. else { Write-Host "Fix 1 applied (HAVE_DRAND48 guard)"; $text = $result }
  21. # Fix 2: run version.sh through bash. Windows CreateProcess cannot execute .sh files.
  22. $old2 = " let version = std::process::Command::new(out.join(`"htslib`").join(`"version.sh`"))`n .output()`n .expect(`"failed to execute process`");`n let version_str = std::str::from_utf8(&version.stdout).unwrap().trim();"
  23. $new2 = " let version_str = std::process::Command::new(`"bash`")`n .arg(out.join(`"htslib`").join(`"version.sh`"))`n .output()`n .map(|o| std::str::from_utf8(&o.stdout).unwrap_or(`"1.19.1`").trim().to_string())`n .unwrap_or_else(|_| `"1.19.1`".to_string());"
  24. $result = $text.Replace($old2, $new2)
  25. if ($result -eq $text) { Write-Warning "Fix 2 pattern not matched - verify hts-sys version" }
  26. else { Write-Host "Fix 2 applied (version.sh via bash)"; $text = $result }
  27. # Fix 3: 64-bit file offsets on Windows MinGW.
  28. # Both the C compiler and bindgen must see the same define so types match.
  29. $old3 = " if want_static {`n cfg.warnings(false).static_flag(true).pic(true);`n } else {`n cfg.warnings(false).static_flag(false).pic(true);`n }"
  30. $new3 = " if want_static {`n cfg.warnings(false).static_flag(true).pic(true);`n } else {`n cfg.warnings(false).static_flag(false).pic(true);`n }`n`n // Fix 3: 64-bit file offsets on Windows MinGW.`n // Without this, off_t = i32 and seeks wrap at 2 GB.`n if target_os == `"windows`" {`n cfg.define(`"_FILE_OFFSET_BITS`", `"64`");`n }"
  31. $result = $text.Replace($old3, $new3)
  32. if ($result -eq $text) { Write-Warning "Fix 3a pattern not matched - verify hts-sys version" }
  33. else { Write-Host "Fix 3a applied (_FILE_OFFSET_BITS=64 cc define)"; $text = $result }
  34. $old4 = " bindgen::Builder::default()`n .header(`"wrapper.h`")`n .generate_comments(false)`n .blocklist_function(`"strtold`")`n .blocklist_type(`"max_align_t`")`n .generate()"
  35. $new4 = " let mut bindgen_builder = bindgen::Builder::default()`n .header(`"wrapper.h`")`n .generate_comments(false)`n .blocklist_function(`"strtold`")`n .blocklist_type(`"max_align_t`");`n if target_os == `"windows`" {`n bindgen_builder = bindgen_builder.clang_arg(`"-D_FILE_OFFSET_BITS=64`");`n }`n bindgen_builder`n .generate()"
  36. $result = $text.Replace($old4, $new4)
  37. if ($result -eq $text) { Write-Warning "Fix 3b pattern not matched - verify hts-sys version" }
  38. else { Write-Host "Fix 3b applied (_FILE_OFFSET_BITS=64 bindgen)"; $text = $result }
  39. # Fix 4: static-link MinGW regex libraries on Windows.
  40. # hts_expr.c needs POSIX regex; systre.c needs TRE. libregex depends on
  41. # libintl/gettext, which depends on libiconv.
  42. $old5 = " cfg.file(`"wrapper.c`");`n cfg.compile(`"hts`");"
  43. $new5 = " cfg.file(`"wrapper.c`");`n cfg.compile(`"hts`");`n`n // hts_expr.c uses POSIX regex (regcomp/regexec/regfree) -- provided by libregex (gnurx).`n // systre.c uses the TRE regex API (tre_regexec/tre_regerror) -- provided by libtre.`n // Both live in the MinGW lib dir; locate it via gcc -print-file-name.`n // Link statically so the exe has no runtime dependency on .dll files.`n if target_os == `"windows`" {`n let compiler = cfg.get_compiler();`n let mingw_lib = std::process::Command::new(compiler.path())`n .arg(`"-print-file-name=libtre.a`")`n .output()`n .ok()`n .and_then(|o| String::from_utf8(o.stdout).ok())`n .map(|s| s.trim().to_string())`n .filter(|s| s != `"libtre.a`")`n .and_then(|s| std::path::PathBuf::from(s).parent().map(|p| p.to_path_buf()));`n if let Some(dir) = mingw_lib {`n println!(`"cargo:rustc-link-search=native={}`", dir.display());`n }`n println!(`"cargo:rustc-link-lib=dylib:+verbatim=libcurl.dll.a`"); // hfile_libcurl.c: curl_easy/curl_multi APIs`n println!(`"cargo:rustc-link-lib=static=tre`"); // systre.c: tre_regexec/tre_regerror`n println!(`"cargo:rustc-link-lib=static=regex`"); // hts_expr.c: regcomp/regexec/regfree`n println!(`"cargo:rustc-link-lib=static=intl`"); // libregex dep: gettext`n println!(`"cargo:rustc-link-lib=static=iconv`"); // libintl dep: iconv`n }"
  44. $result = $text.Replace($old5, $new5)
  45. if ($result -eq $text) { Write-Warning "Fix 4 pattern not matched - verify hts-sys version" }
  46. else { Write-Host "Fix 4 applied (static-link regex/tre/intl/iconv on Windows)"; $text = $result }
  47. [System.IO.File]::WriteAllText($buildRs, $text, (New-Object System.Text.UTF8Encoding $false))
  48. $cargoToml = Join-Path $dest "Cargo.toml"
  49. $manifest = [System.IO.File]::ReadAllText($cargoToml) -replace "`r`n", "`n"
  50. $oldManifest = "[target.'cfg(all(unix, not(target_os = `"macos`")))'.dependencies.openssl-sys]`nversion = `"0.9.56`"`noptional = true"
  51. $newManifest = "[target.'cfg(any(windows, all(unix, not(target_os = `"macos`"))))'.dependencies.openssl-sys]`nversion = `"0.9.56`"`noptional = true"
  52. $result = $manifest.Replace($oldManifest, $newManifest)
  53. if ($result -eq $manifest) { Write-Warning "Manifest OpenSSL target patch not matched - verify hts-sys version" }
  54. else {
  55. Write-Host "Manifest patch applied (openssl-sys enabled on Windows)"
  56. [System.IO.File]::WriteAllText($cargoToml, $result, (New-Object System.Text.UTF8Encoding $false))
  57. }
  58. Write-Host "patches\hts-sys is ready."
  59. $rhl = Resolve-Path "$env:USERPROFILE\.cargo\registry\src\index.crates.io-*\rust-htslib-1.0.0" `
  60. -ErrorAction SilentlyContinue
  61. if (-not $rhl) {
  62. Write-Warning "rust-htslib 1.0.0 not in cargo registry. Run 'cargo fetch' first."
  63. } else {
  64. $rhlDest = Join-Path $PSScriptRoot "patches\rust-htslib"
  65. if (Test-Path $rhlDest) { Remove-Item $rhlDest -Recurse -Force }
  66. Copy-Item $rhl.Path $rhlDest -Recurse
  67. Write-Host "Copied rust-htslib 1.0.0 -> patches\rust-htslib"
  68. $bamMod = Join-Path $rhlDest "src\bam\mod.rs"
  69. $rhlText = [System.IO.File]::ReadAllText($bamMod) -replace "`r`n", "`n"
  70. $rhlText = $rhlText.Replace("offset as libc::off_t,", "offset as hts_sys::off_t,")
  71. if ($rhlText -match "offset as libc::off_t") {
  72. Write-Warning "rust-htslib fix A not applied - pattern not matched"
  73. } else {
  74. Write-Host "rust-htslib fix A applied (libc::off_t -> hts_sys::off_t)"
  75. }
  76. $oldIdxLoad = " let idx = unsafe { htslib::sam_index_load(htsfile, c_str.as_ptr()) };"
  77. $newIdxLoad = " // flags=0 omits HTS_IDX_SAVE_REMOTE so remote .bai files are not cached locally.`n let idx = unsafe { htslib::sam_index_load3(htsfile, c_str.as_ptr(), std::ptr::null(), 0) };"
  78. $rhlText = $rhlText.Replace($oldIdxLoad, $newIdxLoad)
  79. if ($rhlText -match "sam_index_load\(htsfile") {
  80. Write-Warning "rust-htslib fix B not applied - pattern not matched"
  81. } else {
  82. Write-Host "rust-htslib fix B applied (sam_index_load3 no-cache)"
  83. }
  84. $fromUrl = " pub fn from_url(url: &Url) -> Result<Self> {`n Self::new(url.as_str().as_bytes())`n }"
  85. $fromUrlAndIndex = " pub fn from_url(url: &Url) -> Result<Self> {`n Self::new(url.as_str().as_bytes())`n }`n`n /// Open a remote BAM via URL with a pre-downloaded local index file.`n /// Using a local index bypasses htslib's remote-index caching entirely.`n pub fn from_url_and_index<P: AsRef<Path>>(url: &Url, index_path: P) -> Result<Self> {`n Self::new_with_index_path(`n url.as_str().as_bytes(),`n &path_as_bytes(index_path, true)?,`n )`n }"
  86. $count = ([regex]::Matches($rhlText, [regex]::Escape($fromUrl))).Count
  87. if ($count -ge 2) {
  88. $idx = $rhlText.IndexOf($fromUrl, $rhlText.IndexOf($fromUrl) + 1)
  89. $rhlText = $rhlText.Substring(0, $idx) + $fromUrlAndIndex + $rhlText.Substring($idx + $fromUrl.Length)
  90. Write-Host "rust-htslib fix C applied (IndexedReader::from_url_and_index)"
  91. } else {
  92. Write-Warning "rust-htslib fix C not applied - could not locate second from_url in IndexedReader"
  93. }
  94. [System.IO.File]::WriteAllText($bamMod, $rhlText, (New-Object System.Text.UTF8Encoding $false))
  95. Write-Host "patches\rust-htslib is ready."
  96. }