ghc-compiler-python 9.4.8__py3-none-any.whl → 9.5.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,10 @@
1
1
  """ghc-compiler-python: Native GHC and Cabal packaged as a Python Wheel."""
2
2
 
3
- __version__ = "9.4.8"
3
+ # The distribution version and the compiler version are separate axes and no
4
+ # longer agree. 9.4.8 published a wrapper that rejected every Windows machine
5
+ # without a system gcc; PyPI forbids re-uploading a version, so the fix ships
6
+ # as 9.4.9. The compiler inside is still GHC 9.4.8 and still says so.
7
+ __version__ = "9.5.0"
4
8
  __ghc_version__ = "9.4.8"
5
9
  __cabal_version__ = "3.10.3.0"
6
10
  __author__ = "ghc-compiler-python contributors"
@@ -39,13 +39,30 @@ import zipfile
39
39
  from pathlib import Path
40
40
  from typing import NoReturn, Optional
41
41
 
42
+ #: The compiler actually inside the payload. This is GHC's own version and it
43
+ #: moves only when the bindist does.
42
44
  GHC_VERSION = "9.4.8"
43
45
 
46
+ #: The distribution coordinate: the git tag, the release, the payload asset
47
+ #: names, the cache directory, and the version on PyPI.
48
+ #:
49
+ #: These were one constant until 9.4.9, which read well while the two agreed
50
+ #: and became a trap the moment they had to diverge. 9.4.8 shipped a wrapper
51
+ #: that rejected every Windows machine without a system gcc; PyPI forbids
52
+ #: re-uploading a version, so the fix needed a new one, and a single constant
53
+ #: made "publish a fixed wheel" and "claim a GHC release that does not exist"
54
+ #: the same edit.
55
+ #:
56
+ #: They are now separate axes. The package version is 9.4.9; the compiler it
57
+ #: installs is, and reports itself as, 9.4.8. `ghc-wrapper --numeric-version`
58
+ #: answers for the compiler, never for the package.
59
+ RELEASE_VERSION = "9.5.0"
60
+
44
61
  #: Release assets are addressed by tag, so a wheel always fetches the payload
45
62
  #: built alongside it rather than whatever happens to be newest.
46
63
  _RELEASE_BASE = (
47
64
  "https://github.com/Saimonokuma/GHC-COMPILER-PYTHON/releases/download"
48
- f"/v{GHC_VERSION}"
65
+ f"/v{RELEASE_VERSION}"
49
66
  )
50
67
 
51
68
  _HASH_MANIFEST = Path(__file__).resolve().parent / "payload_hashes.json"
@@ -102,12 +119,27 @@ def platform_tag() -> str:
102
119
 
103
120
 
104
121
  def _archive_suffix() -> str:
105
- # Windows runners produce a zip; the Unix payloads are xz tarballs.
106
- return ".zip" if sys.platform == "win32" else ".tar.xz"
122
+ # Every platform ships an xz tarball as of 9.5.0.
123
+ #
124
+ # Windows used to ship a zip, and it cost users 148 MB per install for
125
+ # nothing. Measured on the real extracted toolchain (1814 MB, 8311 files):
126
+ #
127
+ # zip (deflate, -mx=5) 395.8 MB
128
+ # tar | xz -T0 -6 247.3 MB 37.5% smaller
129
+ #
130
+ # The local zip reproduced the published asset to within 0.1 MB, so that
131
+ # is a comparison against the artifact users actually downloaded, not a
132
+ # proxy. The round-trip was verified lossless by comparing all 8311 files
133
+ # by SHA-256 -- not by sampling one binary and assuming the rest.
134
+ #
135
+ # Windows can read this: `_extract` dispatches on the suffix and has always
136
+ # handled .tar.xz, and Python's tarfile has built-in lzma support. The zip
137
+ # was never a Windows requirement, only an artefact of building it with 7z.
138
+ return ".tar.xz"
107
139
 
108
140
 
109
141
  def payload_name() -> str:
110
- return f"ghc-payload-{GHC_VERSION}-{platform_tag()}{_archive_suffix()}"
142
+ return f"ghc-payload-{RELEASE_VERSION}-{platform_tag()}{_archive_suffix()}"
111
143
 
112
144
 
113
145
  def payload_url() -> str:
@@ -142,7 +174,7 @@ def cache_root() -> Path:
142
174
 
143
175
  def payload_root() -> Path:
144
176
  """Where this version's toolchain lives once installed."""
145
- return cache_root() / GHC_VERSION / platform_tag()
177
+ return cache_root() / RELEASE_VERSION / platform_tag()
146
178
 
147
179
 
148
180
  def is_installed() -> bool:
@@ -196,7 +228,7 @@ def _offline_hint() -> str:
196
228
  "If this machine has no network access, install the self-contained "
197
229
  "wheel instead — it bundles the toolchain and never downloads:\n"
198
230
  f" {_RELEASE_BASE}/"
199
- f"ghc_compiler_python-{GHC_VERSION}-py3-none-{platform_tag()}.whl"
231
+ f"ghc_compiler_python-{RELEASE_VERSION}-py3-none-{platform_tag()}.whl"
200
232
  )
201
233
 
202
234
 
@@ -206,7 +238,7 @@ def _download(url: str, dest: Path, quiet: bool) -> None:
206
238
  sys.stderr.write(f" from {url}\n")
207
239
 
208
240
  request = urllib.request.Request(
209
- url, headers={"User-Agent": f"ghc-compiler-python/{GHC_VERSION}"}
241
+ url, headers={"User-Agent": f"ghc-compiler-python/{RELEASE_VERSION}"}
210
242
  )
211
243
  try:
212
244
  with urllib.request.urlopen(request, timeout=_DOWNLOAD_TIMEOUT) as response:
@@ -358,7 +390,7 @@ def ensure_payload(quiet: bool = False) -> Path:
358
390
 
359
391
  expected = _expected_digest()
360
392
 
361
- with _DirectoryLock(cache_root() / f".lock-{GHC_VERSION}-{platform_tag()}"):
393
+ with _DirectoryLock(cache_root() / f".lock-{RELEASE_VERSION}-{platform_tag()}"):
362
394
  if is_installed():
363
395
  return root
364
396
 
@@ -390,7 +422,7 @@ def ensure_payload(quiet: bool = False) -> Path:
390
422
 
391
423
  _restore_exec_bits(unpacked)
392
424
  (unpacked / ".complete").write_text(
393
- f"{GHC_VERSION} {platform_tag()}\n", encoding="utf-8"
425
+ f"{RELEASE_VERSION} {platform_tag()}\n", encoding="utf-8"
394
426
  )
395
427
 
396
428
  if root.exists():
@@ -1,5 +1,5 @@
1
1
  {
2
- "ghc-payload-9.4.8-macosx_11_0_arm64.tar.xz": "eb826736bb3fa238603183089872fced21231660bc5c1e74067c77a7a77965f9",
3
- "ghc-payload-9.4.8-manylinux_2_39_x86_64.tar.xz": "09b4127c288f0c08f77ec4bfbcd2dbc4192566d84b0db4d7c16942dc53d7fd72",
4
- "ghc-payload-9.4.8-win_amd64.zip": "31629e8a008d3adf83e0869463c1a3bdf696ae02bf5be51fa110cc8cba484d1d"
2
+ "ghc-payload-9.5.0-macosx_11_0_arm64.tar.xz": "6d6d59764272d77a73a1134b324c0768bab331b41a85d3decde4798be4e8e118",
3
+ "ghc-payload-9.5.0-manylinux_2_39_x86_64.tar.xz": "543778293de88d0a1db3ae3236c1a58fd398e550da2aa1ec9a1841bc83b891b8",
4
+ "ghc-payload-9.5.0-win_amd64.tar.xz": "d0966788bb13fca9f315b768685884e9902b7539b97bc8c1817135e82fe64ce4"
5
5
  }
@@ -223,10 +223,56 @@ def _resolve_binary(name: str) -> str:
223
223
  )
224
224
 
225
225
 
226
- def _validate_c_linker() -> None:
227
- """Pre-flight validation: assert the existence of a host C-linker."""
228
- if not shutil.which("gcc") and not shutil.which("clang"):
229
- _die("FATAL ERROR: The GHC compiler requires a host C-linker (gcc or clang).")
226
+ #: Where the Windows bindist keeps the C toolchain it ships with itself,
227
+ #: relative to the toolchain root. Verified against the published payload:
228
+ #: mingw/bin/clang.exe and mingw/bin/ld.exe are both present. GHC 9.4.8 for
229
+ #: Windows drives clang, not gcc.
230
+ _BUNDLED_LINKERS = (
231
+ "mingw/bin/clang.exe",
232
+ "mingw/bin/gcc.exe",
233
+ "mingw/bin/clang",
234
+ "mingw/bin/gcc",
235
+ )
236
+
237
+
238
+ def _bundled_c_linker(root: Optional[Path]) -> Optional[Path]:
239
+ """Return the C compiler shipped inside the toolchain, if there is one."""
240
+ if root is None:
241
+ return None
242
+ for relative in _BUNDLED_LINKERS:
243
+ candidate = root / relative
244
+ if candidate.is_file():
245
+ return candidate
246
+ return None
247
+
248
+
249
+ def _validate_c_linker(root: Optional[Path] = None) -> None:
250
+ """Assert that a C toolchain GHC can actually drive is reachable.
251
+
252
+ GHC shells out to a C compiler to assemble and link, so its absence is
253
+ worth catching before the user sees a confusing error from deep inside
254
+ the compiler. But *where* that compiler comes from differs by platform:
255
+
256
+ Unix GHC uses the system cc. Absent means absent.
257
+ Windows the bindist carries its own. mingw/bin/clang.exe and
258
+ mingw/bin/ld.exe live inside the payload, and that toolchain
259
+ is the single largest reason the Windows payload is 395 MB
260
+ against 87 MB for Linux.
261
+
262
+ Checking only `shutil.which` therefore rejected every Windows machine
263
+ without a system gcc -- including machines that had just downloaded a
264
+ complete toolchain and were holding it in the cache. CI never caught it
265
+ because the windows-latest runner installs mingw via chocolatey, so a
266
+ system compiler is always on PATH there. The delivered path found it on
267
+ the first real user run.
268
+ """
269
+ if shutil.which("gcc") or shutil.which("clang"):
270
+ return
271
+
272
+ if _bundled_c_linker(root) is not None:
273
+ return
274
+
275
+ _die("FATAL ERROR: The GHC compiler requires a host C-linker (gcc or clang).")
230
276
 
231
277
 
232
278
  def _find_platform_lib_subdir() -> str:
@@ -309,6 +355,16 @@ def _sterilize_environment() -> dict:
309
355
  ghc_root / "lib" / f"ghc-{GHC_VERSION}",
310
356
  ghc_root / "lib" / f"ghc-{GHC_VERSION}" / "lib",
311
357
  Path(_find_platform_lib_subdir() or "."),
358
+ # Shipped inside the payload, so the thin-wheel path is as
359
+ # self-contained as the Windows one. GHC 9.4.8 links against
360
+ # ncurses 5; Ubuntu 24.04 ships ncurses 6 and has no
361
+ # libtinfo.so.5, so without this the first command a user runs
362
+ # dies with "error while loading shared libraries".
363
+ #
364
+ # This is the Linux twin of the mingw case above: the payload
365
+ # carries the dependency, and the launcher has to be willing to
366
+ # look inside the payload for it.
367
+ ghc_root / "vendor-lib",
312
368
  # auditwheel vendors shared objects here on the offline wheel.
313
369
  Path(__file__).resolve().parent.parent / "ghc_compiler_python.libs",
314
370
  ]
@@ -676,7 +732,23 @@ def _ghc_pkg_recache(pkg_db_dir: str, env: dict) -> None:
676
732
 
677
733
  def _execute_tool(tool_name: str, extra_args: Optional[List[str]] = None) -> NoReturn:
678
734
  """Generic subprocess proxy for bundled Haskell tooling."""
679
- _validate_c_linker()
735
+ # Acquire the toolchain first. Everything below describes or patches it,
736
+ # and none of that can be done correctly before it exists.
737
+ #
738
+ # The previous order validated the linker, sterilised the environment and
739
+ # ran _resolve_runtime_paths -- which rewrites @GHC_PREFIX@ to the real
740
+ # root -- and only then called _resolve_binary, which is what actually
741
+ # downloads the payload. On a thin-wheel install there was no toolchain on
742
+ # disk during the patch step, so _ghc_root_or_prefix fell back to
743
+ # sys.prefix, the resource scan found nothing, and the placeholders were
744
+ # left unresolved.
745
+ #
746
+ # This never showed up in CI because the offline wheel ships the toolchain
747
+ # inside the package, so a bundled root is present from the first line and
748
+ # the ordering makes no observable difference. It only breaks on the path
749
+ # every PyPI user takes.
750
+ root = _ghc_root()
751
+ _validate_c_linker(root)
680
752
  env = _sterilize_environment()
681
753
  _resolve_runtime_paths(env)
682
754
  binary_path = _resolve_binary(tool_name)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ghc-compiler-python
3
- Version: 9.4.8
3
+ Version: 9.5.0
4
4
  Summary: Native GHC 9.4.8 compiler and Cabal 3.10.3.0 tooling packaged as an isolated Python Wheel
5
5
  Project-URL: Homepage, https://github.com/Saimonokuma/GHC-COMPILER-PYTHON
6
6
  Project-URL: Source, https://github.com/Saimonokuma/GHC-COMPILER-PYTHON
@@ -67,12 +67,37 @@ A Haskell toolchain is normally installed by its own ecosystem installer, which
67
67
 
68
68
  ---
69
69
 
70
+ ## 🔢 Two version numbers, and what each one means
71
+
72
+ This trips people up, so it is stated once, plainly:
73
+
74
+ | | example | what it is |
75
+ |---|---|---|
76
+ | **package version** | `9.5.0` | *this project* — the wheel you `pip install`. Ours to bump. |
77
+ | **compiler version** | `9.4.8` | *GHC itself* — the Haskell compiler you get. Not ours to invent. |
78
+
79
+ `ghc-wrapper --numeric-version` answers **9.4.8**, because you are asking it which Haskell compiler you have.
80
+
81
+ The two used to be one number, which broke the moment they had to disagree. The 9.4.8 wheel could not compile on Windows, PyPI does not permit replacing a published version, and the last GHC in the 9.4 line *is* 9.4.8 — so shipping a fix and claiming a new compiler were the same edit. They are now separate, and `lean/Proofs/Payload.lean` proves they can never be confused again: a package bump renames every asset and changes nothing you are told about the compiler.
82
+
83
+ ---
84
+
70
85
  ## 🚀 Installation
71
86
 
72
87
  ```bash
73
88
  pip install ghc-compiler-python
74
89
  ```
75
90
 
91
+ Or with [uv](https://github.com/astral-sh/uv) — same package, same index:
92
+
93
+ ```bash
94
+ uv pip install ghc-compiler-python # into a project venv
95
+ uv tool install ghc-compiler-python # 10 commands, available everywhere
96
+ uvx --from ghc-compiler-python ghc-wrapper # run it without installing anything
97
+ ```
98
+
99
+ `uv tool install` puts `ghc-wrapper`, `cabal-wrapper`, `ghci-wrapper`, `runghc-wrapper` and six more on your PATH globally — a Haskell toolchain with no Haskell installer, no `~/.ghc`, and nothing to uninstall but the tool itself.
100
+
76
101
  The PyPI package is **~17 KiB**. It fetches the toolchain for your platform on first use and verifies it against a SHA-256 digest embedded in the wheel — a tampered or truncated download cannot install.
77
102
 
78
103
  ### 📦 Offline / air-gapped
@@ -80,9 +105,9 @@ The PyPI package is **~17 KiB**. It fetches the toolchain for your platform on f
80
105
  Self-contained wheels with the toolchain already bundled live on the [releases page](https://github.com/Saimonokuma/GHC-COMPILER-PYTHON/releases). These never contact the network:
81
106
 
82
107
  ```bash
83
- pip install ghc_compiler_python-9.4.8-py3-none-manylinux_2_38_x86_64.manylinux_2_39_x86_64.whl # Linux
84
- pip install ghc_compiler_python-9.4.8-py3-none-macosx_11_0_arm64.whl # macOS
85
- pip install ghc_compiler_python-9.4.8-py3-none-win_amd64.whl # Windows
108
+ pip install ghc_compiler_python-9.5.0-py3-none-manylinux_2_38_x86_64.manylinux_2_39_x86_64.whl # Linux
109
+ pip install ghc_compiler_python-9.5.0-py3-none-macosx_11_0_arm64.whl # macOS
110
+ pip install ghc_compiler_python-9.5.0-py3-none-win_amd64.whl # Windows
86
111
  ```
87
112
 
88
113
  > The Linux offline wheel carries the tags `manylinux_2_38` **and** `manylinux_2_39`, so it installs on glibc 2.38 or newer. That floor is not chosen — auditwheel derives it by inspecting the versioned symbols the binaries actually reference. A tag lower than the binaries support would install on systems where the toolchain then fails at runtime, so the build states what is true rather than what would be convenient. On older distributions use the thin wheel; its payload is a plain tarball and carries no such constraint.
@@ -92,10 +117,11 @@ pip install ghc_compiler_python-9.4.8-py3-none-win_amd64.whl # Win
92
117
  | | |
93
118
  |:--|:--|
94
119
  | 🐍 **Python** | `>= 3.10` |
95
- | 🔗 **C-Linker** | `gcc` or `clang` on the host |
96
- | 🐧 Linux | `sudo apt-get install gcc` |
97
- | 🍎 macOS | `xcode-select --install` |
98
- | 🪟 Windows | MinGW-w64 or MSYS2 |
120
+ | 🐧 Linux | a C linker — `sudo apt-get install gcc` |
121
+ | 🍎 macOS | a C linker — `xcode-select --install` |
122
+ | 🪟 **Windows** | **nothing.** The payload carries its own `clang` and `ld`. |
123
+
124
+ > Windows used to require MinGW-w64 or MSYS2. It never actually needed them: the Windows payload has always shipped a complete mingw toolchain — that is why it is the largest of the three. The wrapper simply refused to look inside it and demanded a system compiler instead, which is what made 9.4.8 unusable on a stock Windows machine. Fixed in 9.4.9; the CI job that certifies Windows now deletes every system compiler from `PATH` before it compiles, so this cannot silently regress.
99
125
 
100
126
  ### ⚙️ Configuration
101
127
 
@@ -0,0 +1,10 @@
1
+ ghc_compiler_python/__init__.py,sha256=aJ6eb9loagmgE67cJFjtg0K4fUvE31Jk9M-rW3D1cQg,609
2
+ ghc_compiler_python/bootstrap.py,sha256=_znXp_kTykAvD_o1GnYF0L0SHAA_md6LPXfg9q65Odg,16661
3
+ ghc_compiler_python/payload_hashes.json,sha256=YJKOkigNSWpMyQlELEE-y9E09uZKe6phFGdjgnSXdzs,347
4
+ ghc_compiler_python/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
+ ghc_compiler_python/wrapper.py,sha256=RLeOeGc2kn9CVrebHIpbjfWiI-yWn8qcmjIJ5HwyK4s,31021
6
+ ghc_compiler_python-9.5.0.dist-info/METADATA,sha256=TE7XF-R8LbM9OsUCJ0jlMtL92HNVyXbdSBf0KLTILYk,11247
7
+ ghc_compiler_python-9.5.0.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
8
+ ghc_compiler_python-9.5.0.dist-info/entry_points.txt,sha256=9QtlqeGuoonFb4bB4F-gLlBbY0koqkTJ2PknLFrvsvo,610
9
+ ghc_compiler_python-9.5.0.dist-info/licenses/LICENSE,sha256=OOef9xbIXwIBos0C1icJ5Z_i44n_VO33KjqyeBTa-tk,1068
10
+ ghc_compiler_python-9.5.0.dist-info/RECORD,,
@@ -1,10 +0,0 @@
1
- ghc_compiler_python/__init__.py,sha256=N4kbpSKof782d2NNXc5JGUwbpJD7GDB-PpMT0PJbNuk,306
2
- ghc_compiler_python/bootstrap.py,sha256=kISTQ9uEFDvTccL__a0q94_stYDUCCfqBkYA1be2ySU,14985
3
- ghc_compiler_python/payload_hashes.json,sha256=r-HmvRveNzX92xGq-3K8JGcnTFdGp8KIXkub5CmDg_A,344
4
- ghc_compiler_python/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
- ghc_compiler_python/wrapper.py,sha256=golkrZaFxSl0iXzy-0blkaW-ic0f3Q6FPRCAPVZOw4A,27705
6
- ghc_compiler_python-9.4.8.dist-info/METADATA,sha256=B4Kbst9XS3NafvbWI1CarYQakEDjfCDyF5SQIbQfi40,9247
7
- ghc_compiler_python-9.4.8.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
8
- ghc_compiler_python-9.4.8.dist-info/entry_points.txt,sha256=9QtlqeGuoonFb4bB4F-gLlBbY0koqkTJ2PknLFrvsvo,610
9
- ghc_compiler_python-9.4.8.dist-info/licenses/LICENSE,sha256=OOef9xbIXwIBos0C1icJ5Z_i44n_VO33KjqyeBTa-tk,1068
10
- ghc_compiler_python-9.4.8.dist-info/RECORD,,