Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ jobs:

# usage-example applies three things from mavenLocal: packpack (through the plugin), the
# toolchain plugin, and python-multiplatform's library and bindings plugin. Versions pinned in
# usage-example/gradle/libs.versions.toml (3.15.0-alpha01).
# usage-example/gradle/libs.versions.toml (3.15.0-alpha03).
- name: Publish packpack, the toolchain plugin and python-multiplatform to mavenLocal
run: |
(cd .ci/pypackpack && ./gradlew :packpack:publishToMavenLocal --console=plain)
Expand Down
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ plugins {
}

python {
compileSdk = "3.13"
compileSdk = "3.15.0" // the CPython python-multiplatform provides
localLibraryPath = "src/commonMain/python" // a pypackpack package: pyproject.toml + src/main/<pkg>
packaging {
fileName = "usage-example"
Expand Down Expand Up @@ -120,7 +120,8 @@ python {
Python only? Skip Gradle builds entirely:

```shell
./gradlew :toolchain:runTcl --args="install pythonx-compose"
uv tool install toolchain-lite
tcl install pythonx-compose
```

## 🧭 Architecture at a glance
Expand Down
2 changes: 1 addition & 1 deletion docs/guide/getting-started.html
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ <h2><span data-lang="en">3. Apply the plugin</span><span data-lang="ko">3. 플
}

python {
compileSdk = &quot;3.13&quot;
compileSdk = &quot;3.15.0&quot;
localLibraryPath = &quot;src/commonMain/python&quot;
packaging {
fileName = &quot;usage-example&quot;
Expand Down
27 changes: 27 additions & 0 deletions docs/guide/guide-staging.html
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,33 @@ <h2><span data-lang="en">iOS: the Xcode build phase</span><span data-lang="ko">i
<h2><span data-lang="en">After staging</span><span data-lang="ko">스테이징 이후</span></h2>
<p data-lang="en">Putting the staged <code>python/</code> on <code>sys.path</code> when the app starts is the runtime's job: <a href="ecosystem.html">python-multiplatform</a>'s side of the boundary, not this plugin's.</p><p data-lang="ko">앱이 시작할 때 스테이징된 <code>python/</code> 을 <code>sys.path</code> 에 올리는 것은 런타임의 일입니다: 이 플러그인이 아니라 <a href="ecosystem.html">python-multiplatform</a> 쪽 경계입니다.</p>
</section>

<section id="wasm-isolation">
<h2><span data-lang="en">WASM: cross-origin isolation and its limits</span><span data-lang="ko">WASM: 교차 출처 격리와 그 한계</span></h2>
<p data-lang="en">WASM runs free-threaded CPython 3.15t with pthreads. WebAssembly threads require <code>SharedArrayBuffer</code>, which browsers only expose when <code>self.crossOriginIsolated === true</code>: <code>Cross-Origin-Opener-Policy: same-origin</code> together with <code>Cross-Origin-Embedder-Policy: require-corp</code> (or <code>credentialless</code> via <code>-Ppython.wasm.coep=credentialless</code>), plus <code>Cross-Origin-Resource-Policy: same-origin</code> on the app's own assets. Cross-origin isolation is on by default for every WASM app this plugin builds:</p><p data-lang="ko">WASM 은 pthreads 기반 자유 스레드 CPython 3.15t 로 실행됩니다. WebAssembly 스레드는 <code>SharedArrayBuffer</code> 가 필요하며, 브라우저는 <code>self.crossOriginIsolated === true</code> 일 때만 이를 열어 줍니다: <code>Cross-Origin-Opener-Policy: same-origin</code> 과 <code>Cross-Origin-Embedder-Policy: require-corp</code>(또는 <code>-Ppython.wasm.coep=credentialless</code> 로 고르는 <code>credentialless</code>), 그리고 앱 자체 자산의 <code>Cross-Origin-Resource-Policy: same-origin</code> 입니다. 이 플러그인이 빌드하는 모든 WASM 앱에는 교차 출처 격리가 기본으로 켜집니다:</p>
<ul>
<li><span data-lang="en"><strong>Dev server headers:</strong> every <code>wasmJs*Webpack</code> task writes <code>webpack.config.d/python-cross-origin-isolation.js</code> so the local webpack dev server sends all three headers automatically.</span><span data-lang="ko"><strong>개발 서버 헤더:</strong> 모든 <code>wasmJs*Webpack</code> 태스크가 <code>webpack.config.d/python-cross-origin-isolation.js</code> 를 기록해 로컬 webpack 개발 서버가 세 헤더를 자동으로 보냅니다.</span></li>
<li><span data-lang="en"><strong>Static host headers (<code>_headers</code>):</strong> <code>stageWasmCrossOriginIsolation</code> emits a Cloudflare Pages and Netlify <code>_headers</code> file into the WASM browser distribution root.</span><span data-lang="ko"><strong>정적 호스트 헤더(<code>_headers</code>):</strong> <code>stageWasmCrossOriginIsolation</code> 이 Cloudflare Pages 와 Netlify 형식의 <code>_headers</code> 파일을 WASM 브라우저 배포 루트에 생성합니다.</span></li>
<li><span data-lang="en"><strong>Service-worker fallback (<code>coi-serviceworker.js</code>) and check page (<code>coi-check.html</code>):</strong> for hosts that cannot set HTTP headers (such as GitHub Pages), <code>coi-serviceworker.js</code> intercepts fetches and re-serves responses with COOP, COEP and CORP. Its cost is <strong>one reload on the first visit</strong>, and it has <strong>no effect when service workers are unavailable</strong> (non-HTTPS origins other than localhost, or webviews with service workers disabled). At startup the runtime checks <code>self.crossOriginIsolated</code> and fails immediately rather than failing later inside a thread; <code>coi-check.html</code> tests the same invariant on its own.</span><span data-lang="ko"><strong>서비스 워커 폴백(<code>coi-serviceworker.js</code>)과 점검 페이지(<code>coi-check.html</code>):</strong> GitHub Pages 처럼 HTTP 헤더를 설정할 수 없는 호스트를 위해, <code>coi-serviceworker.js</code> 가 요청을 가로채 COOP, COEP, CORP 헤더를 붙여 응답합니다. 그 비용은 <strong>첫 방문 시 1회 새로고침</strong>이며, <strong>서비스 워커를 쓸 수 없는 환경에서는 효과가 없습니다</strong>(localhost 이외의 비-HTTPS 출처나 서비스 워커가 꺼진 웹뷰). 시작 시 런타임이 <code>self.crossOriginIsolated</code> 를 확인해 스레드 안에서 뒤늦게 죽는 대신 즉시 실패하며, <code>coi-check.html</code> 로도 같은 조건을 바로 점검할 수 있습니다.</span></li>
</ul>
<h3><span data-lang="en">Browser limits of cross-origin isolation</span><span data-lang="ko">교차 출처 격리의 브라우저 제약</span></h3>
<div class="table-wrap"><table><thead><tr><th><span data-lang="en">Surface</span><span data-lang="ko">영역</span></th><th><span data-lang="en">What cross-origin isolation changes</span><span data-lang="ko">교차 출처 격리로 달라지는 점</span></th><th><span data-lang="en">What to do</span><span data-lang="ko">대응 방법</span></th></tr></thead><tbody><tr><td><span data-lang="en">Third-party <code>&lt;iframe&gt;</code></span><span data-lang="ko">제3자 <code>&lt;iframe&gt;</code></span></td><td><span data-lang="en">Embedded cross-origin documents must opt in with CORP/COEP headers, or the browser refuses to load them.</span><span data-lang="ko">내장된 교차 출처 문서가 CORP/COEP 헤더로 동의하지 않으면 브라우저가 로드를 거부합니다.</span></td><td><span data-lang="en">Serve the framed document with <code>Cross-Origin-Embedder-Policy</code> and <code>Cross-Origin-Resource-Policy: cross-origin</code>, or open it in a top-level navigation instead of an iframe.</span><span data-lang="ko">iframe 문서에 <code>Cross-Origin-Embedder-Policy</code> 와 <code>Cross-Origin-Resource-Policy: cross-origin</code> 을 설정하거나, iframe 대신 최상위 탐색으로 여세요.</span></td></tr><tr><td><span data-lang="en">Popup OAuth and payment flows</span><span data-lang="ko">팝업 기반 OAuth 및 결제 흐름</span></td><td><span data-lang="en">Under <code>Cross-Origin-Opener-Policy: same-origin</code>, cross-origin popups lose <code>window.opener</code> (it becomes <code>null</code>), so <code>postMessage</code> handshakes back to the opener break.</span><span data-lang="ko"><code>Cross-Origin-Opener-Policy: same-origin</code> 아래에서는 교차 출처 팝업의 <code>window.opener</code> 가 <code>null</code> 이 되어, 부모 창으로의 <code>postMessage</code> 핸드셰이크가 끊깁니다.</span></td><td><span data-lang="en">Use full-page redirect OAuth/payment flows, or configure the host with <code>same-origin-allow-popups</code> where the browser's isolation rules permit it.</span><span data-lang="ko">전체 페이지 리다이렉트 방식의 OAuth/결제 흐름을 쓰거나, 브라우저 격리 규칙이 허용하는 범위에서 호스트에 <code>same-origin-allow-popups</code> 를 설정하세요.</span></td></tr><tr><td><span data-lang="en">External images, scripts, fonts and media</span><span data-lang="ko">외부 이미지, 스크립트, 글꼴, 미디어</span></td><td><span data-lang="en">Under <code>Cross-Origin-Embedder-Policy: require-corp</code>, any cross-origin subresource without a <code>Cross-Origin-Resource-Policy</code> or CORS header is blocked.</span><span data-lang="ko"><code>Cross-Origin-Embedder-Policy: require-corp</code> 아래에서는 <code>Cross-Origin-Resource-Policy</code> 나 CORS 헤더가 없는 교차 출처 하위 리소스가 차단됩니다.</span></td><td><span data-lang="en">Add <code>crossorigin</code> attributes and serve CORS/CORP headers on the asset host, or build with <code>-Ppython.wasm.coep=credentialless</code> so no-cookie cross-origin requests load without CORP.</span><span data-lang="ko"><code>crossorigin</code> 속성을 붙이고 자산 호스트에서 CORS/CORP 헤더를 내려주거나, 쿠키 없는 교차 출처 요청을 CORP 없이 로드하도록 <code>-Ppython.wasm.coep=credentialless</code> 로 빌드하세요.</span></td></tr></tbody></table></div>
<h3><span data-lang="en">Header snippets for other hosts</span><span data-lang="ko">다른 호스트를 위한 헤더 설정 예시</span></h3>
<p data-lang="en">Cloudflare Pages and Netlify read the generated <code>_headers</code> file directly. For nginx, Apache, Firebase Hosting or Vercel, add the same headers in the host configuration:</p><p data-lang="ko">Cloudflare Pages 와 Netlify 는 생성된 <code>_headers</code> 파일을 바로 읽습니다. nginx, Apache, Firebase Hosting, Vercel 에서는 호스트 설정에 같은 헤더를 추가하세요:</p>
<pre><code># nginx
add_header Cross-Origin-Opener-Policy "same-origin" always;
add_header Cross-Origin-Embedder-Policy "require-corp" always;
add_header Cross-Origin-Resource-Policy "same-origin" always;

# Apache (.htaccess)
Header always set Cross-Origin-Opener-Policy "same-origin"
Header always set Cross-Origin-Embedder-Policy "require-corp"
Header always set Cross-Origin-Resource-Policy "same-origin"

# Firebase Hosting (firebase.json) / Vercel (vercel.json)
# Set headers on "/(.*)" -> Cross-Origin-Opener-Policy: same-origin,
# Cross-Origin-Embedder-Policy: require-corp, Cross-Origin-Resource-Policy: same-origin</code></pre>
</section>
</article></div>
</main>
<footer class="site-footer"><div class="wrap">
Expand Down
4 changes: 2 additions & 2 deletions docs/guide/guide-tcl.html
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,12 @@ <h1><span data-lang="en">tcl for Python users</span><span data-lang="ko">Python

<section>
<h2><span data-lang="en">Install a package</span><span data-lang="ko">패키지 설치</span></h2>
<pre><code class="language-shell">uv tool install toolchain-lite # once 0.1.0 is on PyPI
<pre><code class="language-shell">uv tool install toolchain-lite
tcl install pythonx-compose
tcl --version

./gradlew :toolchain:runTcl --args=&quot;install pythonx-compose&quot; # from a checkout</code></pre>
<p data-lang="en"><code>tcl install &lt;package&gt;</code> looks for a <code>pyproject.toml</code> in the current directory and its parents. If there is none it runs <code>uv init --bare</code> first; then it adds the package with <code>uv add</code>, through the same pypackpack backend the Gradle plugin uses.</p><p data-lang="ko"><code>tcl install &lt;package&gt;</code> 는 현재 디렉터리와 그 상위에서 <code>pyproject.toml</code> 을 찾습니다. 없으면 먼저 <code>uv init --bare</code> 를 실행하고, Gradle 플러그인과 같은 pypackpack 백엔드로 <code>uv add</code> 를 해서 패키지를 추가합니다.</p>
<p data-lang="en"><code>tcl install &lt;package&gt;</code> uses the <code>pyproject.toml</code> in the current directory. Inside a git repository it also looks in the parent directories, up to the repository root and never above it; outside a repository it does not look in parents at all, so an unrelated project above you is never edited. If there is none it runs <code>uv init --bare --no-workspace</code> in the current directory first; then it adds the package with <code>uv add</code>, through the same pypackpack backend the Gradle plugin uses.</p><p data-lang="ko"><code>tcl install &lt;package&gt;</code> 는 현재 디렉터리의 <code>pyproject.toml</code> 을 씁니다. git 저장소 안이면 상위 디렉터리도 저장소 루트까지만 찾고 그 위로는 가지 않습니다. 저장소 밖이면 상위 디렉터리를 아예 찾지 않으므로, 위에 있는 관계없는 프로젝트를 고치는 일이 없습니다. 없으면 먼저 현재 디렉터리에서 <code>uv init --bare --no-workspace</code> 를 실행하고, Gradle 플러그인과 같은 pypackpack 백엔드로 <code>uv add</code> 를 해서 패키지를 추가합니다.</p>
<div class="table-wrap"><table><thead><tr><th><span data-lang="en">Invocation</span><span data-lang="ko">호출</span></th><th><span data-lang="en">Result</span><span data-lang="ko">결과</span></th></tr></thead><tbody><tr><td><code>tcl install pkg</code></td><td><span data-lang="en">exit 0, <code>uv</code> output on stdout</span><span data-lang="ko">종료 코드 0, stdout 에 <code>uv</code> 출력</span></td></tr><tr><td><code>tcl</code></td><td><span data-lang="en">exit 1, <code>Usage: tcl install &lt;package&gt;</code></span><span data-lang="ko">종료 코드 1, <code>Usage: tcl install &lt;package&gt;</code></span></td></tr><tr><td><code>tcl install</code></td><td><span data-lang="en">exit 1, missing argument</span><span data-lang="ko">종료 코드 1, 인자 누락</span></td></tr><tr><td><code>tcl other</code></td><td><span data-lang="en">exit 1, unknown command</span><span data-lang="ko">종료 코드 1, 알 수 없는 명령</span></td></tr></tbody></table></div>
<div class="note"><p data-lang="en"><code>install</code> is the only command, matching issue #1. <code>tcl</code> needs <code>uv</code> on <code>PATH</code> and network access to PyPI.</p><p data-lang="ko">명령은 이슈 #1 그대로 <code>install</code> 하나뿐입니다. <code>tcl</code> 은 <code>PATH</code> 의 <code>uv</code> 와 PyPI 네트워크 접근이 필요합니다.</p></div>
</section>
Expand Down
Loading