Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
80 commits
Select commit Hold shift + click to select a range
9024b2f
feat: add CROSS_ORIGIN_STORAGE support for Wasm loading
tomayac Jun 6, 2026
403c59f
fix: correct COS implementation to match WICG explainer API
tomayac Jun 6, 2026
4657036
test: add test suite for CROSS_ORIGIN_STORAGE feature
tomayac Jun 6, 2026
44f94e5
docs: add Cross-Origin Storage guide and polish setting comment
tomayac Jun 6, 2026
7ea1d45
test: add cross_origin_storage browser example
tomayac Jun 6, 2026
5c65fe5
feat: add COS instrumentation callbacks and update example/docs/tests
tomayac Jun 6, 2026
e123413
fix: add link-time validation and changelog entry for CROSS_ORIGIN_ST…
tomayac Jun 6, 2026
e094e46
fix: remove incorrect 'base64' claim in SINGLE_FILE + COS descriptions
tomayac Jun 6, 2026
df886f9
feat: warn when CROSS_ORIGIN_STORAGE is used with SPLIT_MODULE or MAI…
tomayac Jun 6, 2026
894cedc
fix: warn when CROSS_ORIGIN_STORAGE is used with SIDE_MODULE
tomayac Jun 6, 2026
4c872ed
docs: clarify that COS only makes sense for widely-shared public Wasm…
tomayac Jun 6, 2026
343d0b9
feat: add CROSS_ORIGIN_STORAGE_ORIGINS setting
tomayac Jun 6, 2026
d87413a
fix: make '*' the implicit default for CROSS_ORIGIN_STORAGE_ORIGINS
tomayac Jun 6, 2026
186e8cb
fix: final polish — warnings, stale comments, doc consistency
tomayac Jun 6, 2026
461b275
test: add missing SIDE_MODULE warning test for CROSS_ORIGIN_STORAGE
tomayac Jun 6, 2026
1410b97
fix: correct COS test failures and add demo Makefile
tomayac Jun 8, 2026
9533aaf
docs: replace concrete project examples with generic descriptions
tomayac Jun 8, 2026
a32dad5
docs: add Chrome Web Store link for COS extension in See also
tomayac Jun 8, 2026
80d15c2
docs: replace CDN framing with 'popular' in COS guide
tomayac Jun 8, 2026
6635a14
docs: note multi-origin exception and origins flag in COS when-to-use
tomayac Jun 8, 2026
7522109
feat: pass hash as first arg to onCOSCacheMiss(hash, url)
tomayac Jun 8, 2026
485d6ab
docs: remove CDN mention and add visibility/security note in preamble.js
tomayac Jun 8, 2026
6e0f82b
docs: add timing-oracle security note to visibility upgrade callout i…
tomayac Jun 8, 2026
b38d5bc
docs: remove CDN mentions from CROSS_ORIGIN_STORAGE settings comments
tomayac Jun 8, 2026
4141f13
style: normalize double spaces after periods in COS settings comments
tomayac Jun 8, 2026
906d53d
style: use American spelling 'serialized' consistently
tomayac Jun 8, 2026
1c944e7
docs: describe progressive enhancement and fallback path in COS examp…
tomayac Jun 8, 2026
6656094
fix: ruff docstring formatting and regenerate settings_reference.rst
tomayac Jun 8, 2026
0ebc151
docs: warn about post-emcc wasm post-processing invalidating the embe…
tomayac Jun 8, 2026
d4b44f2
feat(cross-origin-storage): expose Module['wasmSHA256'] for custom in…
tomayac Jun 8, 2026
7a7efe7
fix(cross-origin-storage): also patch Module['wasmSHA256'] in post-bi…
tomayac Jun 8, 2026
c10b7ba
Merge branch 'main' into cross-origin-storage
tomayac Jun 9, 2026
05dafe6
docs: drop =1 from -sCROSS_ORIGIN_STORAGE flag references in COS guide
tomayac Jun 9, 2026
ed5611e
docs: use :ref: cross-references for settings in COS guide prose
tomayac Jun 9, 2026
db36d8b
docs: remove redundant link-time note from COS usage section
tomayac Jun 9, 2026
5dfb0c3
docs: use unquoted comma-separated syntax for CROSS_ORIGIN_STORAGE_OR…
tomayac Jun 9, 2026
f1ce667
docs: use comma-separated syntax for CROSS_ORIGIN_STORAGE_ORIGINS exa…
tomayac Jun 9, 2026
9c1056d
fix: make CROSS_ORIGIN_STORAGE + WASM_ASYNC_COMPILATION=0 a hard error
tomayac Jun 9, 2026
419d0bf
docs: update build-time constant example to match current cosHash obj…
tomayac Jun 9, 2026
37988bf
docs: use Module['wasmSHA256'] consistently and fix sed to patch both…
tomayac Jun 9, 2026
d459be3
refactor: rename wasmSHA256 to wasmHash and expose {algorithm,value} …
tomayac Jun 9, 2026
0efc119
refactor: address multiple PR review comments on CROSS_ORIGIN_STORAGE
tomayac Jun 9, 2026
ce79cdb
refactor: make COS instrumentation callbacks opt-in via INCOMING_MODU…
tomayac Jun 9, 2026
9596b82
Add browser tests
tomayac Jun 9, 2026
619dedf
fix: CI failures — ruff, build-docs, and browser-test experimental wa…
tomayac Jun 9, 2026
748585d
remove: dead WASM_SHA256 internal setting
tomayac Jun 9, 2026
765ed7a
style: add -O2 to COS example build and browser tests
tomayac Jun 9, 2026
e5b74d5
chore: untrack generated COS example build artifacts
tomayac Jun 9, 2026
3ad3cc8
test: log COS events to console in miss_then_hit browser test
tomayac Jun 9, 2026
4302b9c
Merge branch 'main' into cross-origin-storage
tomayac Jun 9, 2026
09ae30d
style: move [experimental] tag to end of CROSS_ORIGIN_STORAGE comment
tomayac Jun 10, 2026
e624ca2
fix: remove redundant cosHash?.value guard in COS preamble
tomayac Jun 10, 2026
a1e290c
fix: make CROSS_ORIGIN_STORAGE a hard error for non-web environments
tomayac Jun 10, 2026
46d948f
remove: Makefile from COS example directory
tomayac Jun 10, 2026
408118d
style: drop -o flag from COS error-only tests
tomayac Jun 10, 2026
ed92f2f
style: prefer hello_world.c over hello_world.cpp in COS tests
tomayac Jun 10, 2026
278eb7f
style: drop docstrings and comments from COS tests
tomayac Jun 10, 2026
f2c9029
refactor: extract setup_cross_origin_storage() from phase_linker_setup
tomayac Jun 10, 2026
8d399fa
remove: COS example build artifact entries from .gitignore
tomayac Jun 10, 2026
b09b640
refactor: consolidate 19 COS unit tests into 5
tomayac Jun 10, 2026
f8f5ddd
merge: resolve import conflict with upstream main
tomayac Jun 10, 2026
9465e49
style: shorten COS ChangeLog entry to 3 lines
tomayac Jun 10, 2026
f7ce0fd
remove: test_cross_origin_storage_callbacks from test_other.py
tomayac Jun 10, 2026
a77d2fb
fix: copyright year and remove redundant build comment from main.cpp
tomayac Jun 10, 2026
4a038e4
remove: test/cross_origin_storage/ example directory
tomayac Jun 10, 2026
7fe5ed9
Make feature detection rock-solid
tomayac Jun 10, 2026
2f21411
Make feature detection compact
tomayac Jun 10, 2026
bc048fb
fix: use Markdown syntax in ChangeLog, not RST :ref:
tomayac Jun 10, 2026
146cf39
refactor: move COS incompatibilities to INCOMPATIBLE_SETTINGS
tomayac Jun 10, 2026
d057132
style: clean up COS browser tests
tomayac Jun 10, 2026
d12b027
style: add PR number to COS ChangeLog entry
tomayac Jun 10, 2026
4834510
refactor: clean up COS preamble and hash injection
tomayac Jun 10, 2026
ed25ea6
Merge remote-tracking branch 'upstream/main' into cross-origin-storage
tomayac Jun 10, 2026
c9bc25f
test: simplify COS assertions — use tighter algorithm+value regex
tomayac Jun 10, 2026
33e2369
test: use simplified CROSS_ORIGIN_STORAGE_ORIGINS syntax in test
tomayac Jun 10, 2026
64eb609
test: drop redundant -sENVIRONMENT=web from COS error tests (web is d…
tomayac Jun 10, 2026
fb71285
Merge branch 'main' into cross-origin-storage
tomayac Jun 10, 2026
12c24a0
test: add TODO to remove COS extension once Chromium ships the featur…
tomayac Jun 10, 2026
1466998
Rebaseline codesize test to fix CI
tomayac Jun 11, 2026
5dafaae
Merge branch 'main' into cross-origin-storage
tomayac Jun 11, 2026
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
4 changes: 4 additions & 0 deletions ChangeLog.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ See docs/process.md for more on how version tagging works.

6.0.1 (in development)
----------------------
- New experimental `-sCROSS_ORIGIN_STORAGE` linker flag integrating the
proposed [Cross-Origin Storage browser API](https://github.com/WICG/cross-origin-storage)
as a progressive enhancement for Wasm loading on the web target. See
`docs/compiling/CrossOriginStorage.rst` for details. (#27066)
- The installed versions of the compiler-rt library now follow the upstream
naming convetion of `libclang_rt.<something>.a`. (#27089)
- Dynamic linking now explicitly requires asynchronous Wasm compilation. The
Expand Down
384 changes: 384 additions & 0 deletions site/source/docs/compiling/CrossOriginStorage.rst

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions site/source/docs/compiling/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ This section contains topics about building projects and running the output.
- :ref:`Deploying-Pages` covers topics related to hosting Emscripten compiled web pages on a CDN.
- :ref:`GitLab` explains how to build and test projects on GitLab.
- :ref:`Contrib-Ports` contains information about contrib ports.
- :ref:`CrossOriginStorage` explains how to enable the experimental Cross-Origin Storage integration for sharing Wasm modules across origins.


.. toctree::
Expand All @@ -26,3 +27,4 @@ This section contains topics about building projects and running the output.
Deploying-Pages
GitLab
Contrib-Ports
CrossOriginStorage
43 changes: 43 additions & 0 deletions site/source/docs/tools_reference/settings_reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3362,6 +3362,49 @@ indirectly using `importScripts`

Default value: false

.. _cross_origin_storage:

CROSS_ORIGIN_STORAGE
====================

Enables Cross-Origin Storage (COS) API support for Wasm
loading on the Web target. At link time Emscripten computes the SHA-256
hash of the final ``.wasm`` binary and embeds it in the generated JS.
At runtime the COS API is used as a progressive enhancement: the binary is
fetched from the shared cross-origin cache on a hit, or stored there after
a network fetch on a miss; when the API is absent or errors the runtime
falls through to the standard fetch path.

Requires the Web environment; using it without ``-sENVIRONMENT=web`` is a
hard link-time error. Incompatible with SINGLE_FILE and
WASM_ASYNC_COMPILATION=0 (both produce hard link-time errors).

See :ref:`CrossOriginStorage` for the full guide.

.. note:: This is an experimental setting

Default value: false

.. _cross_origin_storage_origins:

CROSS_ORIGIN_STORAGE_ORIGINS
============================

Controls which origins may read the Wasm binary from the COS cache. Only
meaningful when ``-sCROSS_ORIGIN_STORAGE`` is set. Applied only during the
write (cache-miss) path, not the read (cache-hit) path.

``['*']`` (default) — any origin can retrieve the file.
Explicit HTTPS origin list — restricted to those origins only::

-sCROSS_ORIGIN_STORAGE_ORIGINS=https://app.example.com,https://api.example.com

``[]`` — same-site only (omits the ``origins`` field entirely).

Mixing ``'*'`` with explicit origins is a link-time error.

Default value: ['*']

.. _fake_dylibs:

FAKE_DYLIBS
Expand Down
68 changes: 68 additions & 0 deletions src/preamble.js
Original file line number Diff line number Diff line change
Expand Up @@ -626,6 +626,68 @@ async function instantiateArrayBuffer(binaryFile, imports) {

async function instantiateAsync(binary, binaryFile, imports) {
#if !SINGLE_FILE
#if CROSS_ORIGIN_STORAGE
Comment thread
tomayac marked this conversation as resolved.
// Cross-Origin Storage (COS) progressive enhancement.
// https://github.com/WICG/cross-origin-storage
// Any error (NotAllowedError, network failure, …) falls through to the
Comment thread
tomayac marked this conversation as resolved.
// standard Emscripten streaming path so the page always loads.
if (globalThis.navigator?.crossOriginStorage) {
var cosHash = Module['wasmHash'];
try {
var cosHandles = await navigator.crossOriginStorage.requestFileHandles([cosHash]);
// Cache hit — read the Blob and instantiate from its ArrayBuffer.
var cosFile = await cosHandles[0].getFile();
var cosBytes = await cosFile.arrayBuffer();
#if expectToReceiveOnModule('onCOSCacheHit')
Module['onCOSCacheHit']?.(cosHash.value);
#endif
return WebAssembly.instantiate(cosBytes, imports);
} catch (cosErr) {
if (cosErr.name === 'NotFoundError') {
// Cache miss — fetch normally, then store in COS for future consumers.
try {
var networkResponse = await fetch(binaryFile, {{{ makeModuleReceiveExpr('fetchSettings', "{ credentials: 'same-origin' }") }}});
var wasmBytes = await networkResponse.arrayBuffer();
#if expectToReceiveOnModule('onCOSCacheMiss')
Module['onCOSCacheMiss']?.(cosHash.value, binaryFile);
#endif
// Fire-and-forget store; never block instantiation on the write.
(async () => {
try {
var writeHandles = await navigator.crossOriginStorage.requestFileHandles(
[cosHash],
#if CROSS_ORIGIN_STORAGE_ORIGINS.length === 1 && CROSS_ORIGIN_STORAGE_ORIGINS[0] === '*'
{ create: true, origins: '*' },
#elif CROSS_ORIGIN_STORAGE_ORIGINS.length
{ create: true, origins: {{{ JSON.stringify(CROSS_ORIGIN_STORAGE_ORIGINS) }}} },
#else
{ create: true },
#endif
);
var writable = await writeHandles[0].createWritable();
await writable.write(new Blob([wasmBytes], { type: 'application/wasm' }));
await writable.close();
#if expectToReceiveOnModule('onCOSStore')
Module['onCOSStore']?.(cosHash.value);
#endif
} catch (storeErr) {
err(`COS store failed: ${storeErr}`);
}
})();
return WebAssembly.instantiate(wasmBytes, imports);
} catch (fetchErr) {
// Network fetch failed; fall through to the standard path below.
err(`COS fallback fetch failed: ${fetchErr}`);
}
} else if (cosErr.name === 'NotAllowedError') {
err(`COS: permission denied.`);
} else {
err(`Cross-Origin Storage lookup failed: ${cosErr}`);
Comment thread
tomayac marked this conversation as resolved.
}
// Fall through to the standard streaming path below.
}
}
Comment thread
tomayac marked this conversation as resolved.
#endif // CROSS_ORIGIN_STORAGE
if (!binary
#if MIN_SAFARI_VERSION < 150000
// See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/instantiateStreaming
Expand Down Expand Up @@ -825,6 +887,12 @@ function getWasmImports() {

var info = getWasmImports();

#if CROSS_ORIGIN_STORAGE
// Expose the build-time hash so that custom Module['instantiateWasm']
// callbacks can implement their own COS-aware loading path.
Module['wasmHash'] = { algorithm: 'SHA-256', value: '<<< WASM_HASH_VALUE >>>' };
#endif

#if expectToReceiveOnModule('instantiateWasm')
// User shell pages can write their own Module.instantiateWasm = function(imports, successCallback) callback
// to manually instantiate the Wasm module themselves. This allows pages to
Expand Down
33 changes: 33 additions & 0 deletions src/settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -2206,6 +2206,39 @@ var GROWABLE_ARRAYBUFFERS = false;
// indirectly using `importScripts`
var CROSS_ORIGIN = false;

// Enables Cross-Origin Storage (COS) API support for Wasm
// loading on the Web target. At link time Emscripten computes the SHA-256
// hash of the final ``.wasm`` binary and embeds it in the generated JS.
// At runtime the COS API is used as a progressive enhancement: the binary is
// fetched from the shared cross-origin cache on a hit, or stored there after
// a network fetch on a miss; when the API is absent or errors the runtime
// falls through to the standard fetch path.
//
// Requires the Web environment; using it without ``-sENVIRONMENT=web`` is a
// hard link-time error. Incompatible with SINGLE_FILE and
// WASM_ASYNC_COMPILATION=0 (both produce hard link-time errors).
//
// See :ref:`CrossOriginStorage` for the full guide.
//
// [link]
// [experimental]
var CROSS_ORIGIN_STORAGE = false;

// Controls which origins may read the Wasm binary from the COS cache. Only
// meaningful when ``-sCROSS_ORIGIN_STORAGE`` is set. Applied only during the
// write (cache-miss) path, not the read (cache-hit) path.
//
// ``['*']`` (default) — any origin can retrieve the file.
// Explicit HTTPS origin list — restricted to those origins only::
//
// -sCROSS_ORIGIN_STORAGE_ORIGINS=https://app.example.com,https://api.example.com
//
// ``[]`` — same-site only (omits the ``origins`` field entirely).
//
// Mixing ``'*'`` with explicit origins is a link-time error.
// [link]
var CROSS_ORIGIN_STORAGE_ORIGINS = ['*'];

// This setting changes the behaviour of the ``-shared`` flag. When set to true
// you get the old emscripten behaviour where the ``-shared`` flag actually
// produces a normal object file (i.e. ``ld -r``). When set to true (the
Expand Down
12 changes: 12 additions & 0 deletions test/browser_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,16 @@
EMTEST_HEADLESS = None
EMTEST_CAPTURE_STDIO = int(os.getenv('EMTEST_CAPTURE_STDIO', '0'))

# Path to an unpacked Chrome extension implementing the Cross-Origin Storage
# polyfill. When set, the extension is loaded via --load-extension when
# launching a Chromium-based browser, enabling the COS browser test paths.
# Point this at a local clone of:
# https://github.com/web-ai-community/cross-origin-storage-extension
# (the directory that contains manifest.json).
Comment thread
tomayac marked this conversation as resolved.
Comment thread
tomayac marked this conversation as resolved.
# TODO: Remove this once Chromium ships COS natively (even behind a flag),
# and update the browser test to use that flag instead.
EMTEST_COS_EXTENSION_PATH = os.getenv('EMTEST_COS_EXTENSION_PATH', '')

# Triggers the browser to restart after every given number of tests.
# 0: Disabled (reuse the browser instance to run all tests. Default)
# 1: Restart a fresh browser instance for every browser test.
Expand Down Expand Up @@ -365,6 +375,8 @@ def configure_test_browser():
EMTEST_BROWSER += ' ' + ' '.join(config.default_flags)
if EMTEST_HEADLESS == 1:
EMTEST_BROWSER += f" {config.headless_flags}"
if EMTEST_COS_EXTENSION_PATH and is_chrome():
EMTEST_BROWSER += f' --load-extension="{EMTEST_COS_EXTENSION_PATH}"'


# Create a server and a web page. When a test runs, we tell the server about it,
Expand Down
172 changes: 172 additions & 0 deletions test/setup_cos_extension.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
#!/usr/bin/env python3
# Copyright 2026 The Emscripten Authors. All rights reserved.
# Emscripten is available under two separate licenses, the MIT license and the
# University of Illinois/NCSA Open Source License. Both these licenses can be
# found in the LICENSE file.

# SPDX-License-Identifier: Apache-2.0

r"""Download the Cross-Origin Storage Chrome extension for COS browser tests.

The COS extension polyfills navigator.crossOriginStorage in Chrome so that
automated browser tests can exercise the cache-miss and cache-hit paths without
requiring a native browser implementation of the API.

Usage
-----

Run once to download and unpack the extension::

python3 test/setup_cos_extension.py

Then pass the printed path as EMTEST_COS_EXTENSION_PATH when running the
browser tests::

EMTEST_COS_EXTENSION_PATH=$(python3 test/setup_cos_extension.py) \\
python3 test/runner.py browser.test_cross_origin_storage_miss_then_hit

Or, with --print-path suppressed so only the path is printed (suitable for
shell variable assignment), use --quiet::

ext=$(python3 test/setup_cos_extension.py --quiet)
EMTEST_COS_EXTENSION_PATH=$ext python3 test/runner.py \\
browser.test_cross_origin_storage_fallback \\
browser.test_cross_origin_storage_miss_then_hit

The extension is downloaded from its GitHub source repository and unpacked into
out/cos_extension/ (relative to the Emscripten root). Re-run with --force to
refresh an existing download.

Source: https://github.com/web-ai-community/cross-origin-storage-extension
"""

import argparse
import io
import os
import sys
import urllib.request
import zipfile

# Archive of the main branch of the COS extension source repository.
EXTENSION_ARCHIVE_URL = (
'https://github.com/web-ai-community/cross-origin-storage-extension'
'/archive/refs/heads/main.zip'
)

# Default destination relative to the Emscripten root (i.e. two levels up from
# this script, which lives in test/).
_SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__))
DEFAULT_DEST = os.path.join(_SCRIPT_DIR, '..', 'out', 'cos_extension')


def _find_manifest(root):
"""Return the directory containing manifest.json, searching up to 2 levels.

Returns None if root does not exist or no manifest.json is found.
"""
if not os.path.isdir(root):
return None
if os.path.exists(os.path.join(root, 'manifest.json')):
return root
for entry in os.scandir(root):
if entry.is_dir():
candidate = os.path.join(entry.path, 'manifest.json')
if os.path.exists(candidate):
return entry.path
return None


def download_and_unpack(dest_dir, quiet=False):
"""Download the extension archive from GitHub and unpack it into dest_dir.

Returns the path of the directory that contains manifest.json.
"""
if not quiet:
print('Downloading COS extension from GitHub...', file=sys.stderr)
req = urllib.request.Request(
EXTENSION_ARCHIVE_URL,
headers={'User-Agent': 'emscripten-test-setup'},
)
with urllib.request.urlopen(req) as response:
data = response.read()

if not quiet:
print(f'Unpacking to {dest_dir} ...', file=sys.stderr)
os.makedirs(dest_dir, exist_ok=True)

with zipfile.ZipFile(io.BytesIO(data)) as zf:
# GitHub archives wrap everything in a top-level directory, e.g.
# "cross-origin-storage-extension-main/". Strip that prefix.
names = zf.namelist()
prefix = names[0].split('/')[0] + '/' if names else ''
for member in names:
if member == prefix:
continue
rel = member[len(prefix):]
if not rel:
continue
target = os.path.join(dest_dir, rel)
if member.endswith('/'):
os.makedirs(target, exist_ok=True)
else:
os.makedirs(os.path.dirname(target), exist_ok=True)
with zf.open(member) as src, open(target, 'wb') as dst:
dst.write(src.read())

extension_dir = _find_manifest(dest_dir)
if extension_dir is None:
print(
f'ERROR: manifest.json not found anywhere under {dest_dir}.\n'
'The extension repository structure may have changed.\n'
'Please report this at https://github.com/emscripten-core/emscripten/issues',
file=sys.stderr,
)
sys.exit(1)

return extension_dir


def main():
parser = argparse.ArgumentParser(
description=__doc__,
formatter_class=argparse.RawDescriptionHelpFormatter,
)
parser.add_argument(
'--dest',
default=DEFAULT_DEST,
help='Directory to unpack the extension into (default: out/cos_extension)',
)
parser.add_argument(
'--force',
action='store_true',
help='Re-download even if the extension is already present',
)
parser.add_argument(
'--quiet',
action='store_true',
help='Suppress informational messages; print only the extension path',
)
args = parser.parse_args()

dest = os.path.realpath(args.dest)

extension_dir = _find_manifest(dest)
if extension_dir and not args.force:
if not args.quiet:
print(
f'Extension already present at {extension_dir} '
f'(use --force to re-download)',
file=sys.stderr,
)
else:
extension_dir = download_and_unpack(dest, quiet=args.quiet)
if not args.quiet:
print(f'Extension ready at {extension_dir}', file=sys.stderr)

# Always print the path as the last line so callers can capture it with
# $(...) or --quiet.
print(extension_dir)


if __name__ == '__main__':
main()
Loading
Loading