setup-patches.ps1 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126
  1. param()
  2. $ErrorActionPreference = "Stop"
  3. $cargoDir = Join-Path $PSScriptRoot ".cargo"
  4. if (-not (Test-Path $cargoDir)) {
  5. New-Item -ItemType Directory -Path $cargoDir | Out-Null
  6. }
  7. $cargoConfig = Join-Path $cargoDir "config.toml"
  8. $cargoConfigText = @"
  9. [patch.crates-io]
  10. hts-sys = { path = "patches/hts-sys" }
  11. rust-htslib = { path = "patches/rust-htslib" }
  12. "@
  13. [System.IO.File]::WriteAllText($cargoConfig, $cargoConfigText, (New-Object System.Text.UTF8Encoding $false))
  14. Write-Host "Wrote local Windows Cargo patch config -> .cargo\config.toml"
  15. $found = Resolve-Path "$env:USERPROFILE\.cargo\registry\src\index.crates.io-*\hts-sys-2.2.0" `
  16. -ErrorAction SilentlyContinue
  17. if (-not $found) {
  18. Write-Error "hts-sys 2.2.0 not in cargo registry. Run 'cargo fetch' first."
  19. exit 1
  20. }
  21. $dest = Join-Path $PSScriptRoot "patches\hts-sys"
  22. if (Test-Path $dest) { Remove-Item $dest -Recurse -Force }
  23. Copy-Item $found.Path $dest -Recurse
  24. Write-Host "Copied hts-sys 2.2.0 -> patches\hts-sys"
  25. $buildRs = Join-Path $dest "build.rs"
  26. $text = [System.IO.File]::ReadAllText($buildRs) -replace "`r`n", "`n"
  27. # Fix 1: guard HAVE_DRAND48. drand48/srand48 are absent from MinGW libc.
  28. $old1 = " let mut config_lines = vec![`n `"/* Default config.h generated by build.rs */`",`n `"#define HAVE_DRAND48 1`",`n ];"
  29. $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 }"
  30. $result = $text.Replace($old1, $new1)
  31. if ($result -eq $text) { Write-Warning "Fix 1 pattern not matched - verify hts-sys version" }
  32. else { Write-Host "Fix 1 applied (HAVE_DRAND48 guard)"; $text = $result }
  33. # Fix 2: run version.sh through bash. Windows CreateProcess cannot execute .sh files.
  34. $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();"
  35. $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());"
  36. $result = $text.Replace($old2, $new2)
  37. if ($result -eq $text) { Write-Warning "Fix 2 pattern not matched - verify hts-sys version" }
  38. else { Write-Host "Fix 2 applied (version.sh via bash)"; $text = $result }
  39. # Fix 3: 64-bit file offsets on Windows MinGW.
  40. # Both the C compiler and bindgen must see the same define so types match.
  41. $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 }"
  42. $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 }"
  43. $result = $text.Replace($old3, $new3)
  44. if ($result -eq $text) { Write-Warning "Fix 3a pattern not matched - verify hts-sys version" }
  45. else { Write-Host "Fix 3a applied (_FILE_OFFSET_BITS=64 cc define)"; $text = $result }
  46. $old4 = " bindgen::Builder::default()`n .header(`"wrapper.h`")`n .generate_comments(false)`n .blocklist_function(`"strtold`")`n .blocklist_type(`"max_align_t`")`n .generate()"
  47. $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()"
  48. $result = $text.Replace($old4, $new4)
  49. if ($result -eq $text) { Write-Warning "Fix 3b pattern not matched - verify hts-sys version" }
  50. else { Write-Host "Fix 3b applied (_FILE_OFFSET_BITS=64 bindgen)"; $text = $result }
  51. # Fix 4: static-link MinGW regex libraries on Windows.
  52. # hts_expr.c needs POSIX regex; systre.c needs TRE. libregex depends on
  53. # libintl/gettext, which depends on libiconv.
  54. $old5 = " cfg.file(`"wrapper.c`");`n cfg.compile(`"hts`");"
  55. $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 }"
  56. $result = $text.Replace($old5, $new5)
  57. if ($result -eq $text) { Write-Warning "Fix 4 pattern not matched - verify hts-sys version" }
  58. else { Write-Host "Fix 4 applied (static-link regex/tre/intl/iconv on Windows)"; $text = $result }
  59. [System.IO.File]::WriteAllText($buildRs, $text, (New-Object System.Text.UTF8Encoding $false))
  60. $cargoToml = Join-Path $dest "Cargo.toml"
  61. $manifest = [System.IO.File]::ReadAllText($cargoToml) -replace "`r`n", "`n"
  62. $oldManifest = "[target.'cfg(all(unix, not(target_os = `"macos`")))'.dependencies.openssl-sys]`nversion = `"0.9.56`"`noptional = true"
  63. $newManifest = "[target.'cfg(any(windows, all(unix, not(target_os = `"macos`"))))'.dependencies.openssl-sys]`nversion = `"0.9.56`"`noptional = true"
  64. $result = $manifest.Replace($oldManifest, $newManifest)
  65. if ($result -eq $manifest) { Write-Warning "Manifest OpenSSL target patch not matched - verify hts-sys version" }
  66. else {
  67. Write-Host "Manifest patch applied (openssl-sys enabled on Windows)"
  68. [System.IO.File]::WriteAllText($cargoToml, $result, (New-Object System.Text.UTF8Encoding $false))
  69. }
  70. Write-Host "patches\hts-sys is ready."
  71. $rhl = Resolve-Path "$env:USERPROFILE\.cargo\registry\src\index.crates.io-*\rust-htslib-1.0.0" `
  72. -ErrorAction SilentlyContinue
  73. if (-not $rhl) {
  74. Write-Warning "rust-htslib 1.0.0 not in cargo registry. Run 'cargo fetch' first."
  75. } else {
  76. $rhlDest = Join-Path $PSScriptRoot "patches\rust-htslib"
  77. if (Test-Path $rhlDest) { Remove-Item $rhlDest -Recurse -Force }
  78. Copy-Item $rhl.Path $rhlDest -Recurse
  79. Write-Host "Copied rust-htslib 1.0.0 -> patches\rust-htslib"
  80. $bamMod = Join-Path $rhlDest "src\bam\mod.rs"
  81. $rhlText = [System.IO.File]::ReadAllText($bamMod) -replace "`r`n", "`n"
  82. $rhlText = $rhlText.Replace("offset as libc::off_t,", "offset as hts_sys::off_t,")
  83. if ($rhlText -match "offset as libc::off_t") {
  84. Write-Warning "rust-htslib fix A not applied - pattern not matched"
  85. } else {
  86. Write-Host "rust-htslib fix A applied (libc::off_t -> hts_sys::off_t)"
  87. }
  88. $oldIdxLoad = " let idx = unsafe { htslib::sam_index_load(htsfile, c_str.as_ptr()) };"
  89. $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) };"
  90. $rhlText = $rhlText.Replace($oldIdxLoad, $newIdxLoad)
  91. if ($rhlText -match "sam_index_load\(htsfile") {
  92. Write-Warning "rust-htslib fix B not applied - pattern not matched"
  93. } else {
  94. Write-Host "rust-htslib fix B applied (sam_index_load3 no-cache)"
  95. }
  96. $fromUrl = " pub fn from_url(url: &Url) -> Result<Self> {`n Self::new(url.as_str().as_bytes())`n }"
  97. $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 }"
  98. $count = ([regex]::Matches($rhlText, [regex]::Escape($fromUrl))).Count
  99. if ($count -ge 2) {
  100. $idx = $rhlText.IndexOf($fromUrl, $rhlText.IndexOf($fromUrl) + 1)
  101. $rhlText = $rhlText.Substring(0, $idx) + $fromUrlAndIndex + $rhlText.Substring($idx + $fromUrl.Length)
  102. Write-Host "rust-htslib fix C applied (IndexedReader::from_url_and_index)"
  103. } else {
  104. Write-Warning "rust-htslib fix C not applied - could not locate second from_url in IndexedReader"
  105. }
  106. [System.IO.File]::WriteAllText($bamMod, $rhlText, (New-Object System.Text.UTF8Encoding $false))
  107. Write-Host "patches\rust-htslib is ready."
  108. }