Skip to content

Commit

Permalink
Add options support to contrib ports (emscripten-core#21276)
Browse files Browse the repository at this point in the history
  • Loading branch information
ypujante authored and mrolig5267319 committed Feb 23, 2024
1 parent b3535ea commit 8353c37
Show file tree
Hide file tree
Showing 8 changed files with 169 additions and 19 deletions.
6 changes: 4 additions & 2 deletions ChangeLog.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,11 @@ See docs/process.md for more on how version tagging works.
POINTER_SIZE }}}` and `{{{ makeGetValue(..) }}}` to be used in pre/post JS
files, just like they can be in JS library files. (#21227)
- Added concept of contrib ports which are ports contributed by the wider
community and supported on a "best effort" basis. A first contrib port is
community and supported on a "best effort" basis. See
`tools/ports/contrib/README.md` for details.A first contrib port is
available via `--use-port=contrib.glfw3`: an emscripten port of glfw written
in C++ with many features like support for multiple windows. (#21244)
in C++ with many features like support for multiple windows. (#21244 and
#21276)


3.1.53 - 01/29/24
Expand Down
27 changes: 24 additions & 3 deletions site/source/docs/compiling/Building-Projects.rst
Original file line number Diff line number Diff line change
Expand Up @@ -225,11 +225,30 @@ You should see some notifications about SDL2 being used, and built if it wasn't

To see a list of all available ports, run ``emcc --show-ports``.

.. note:: *SDL_image* has also been added to ports, use it with ``--use-port=sdl2_image``. For ``sdl2_image`` to be useful, you generally need to specify the image formats you are planning on using with e.g. ``-sSDL2_IMAGE_FORMATS='["bmp","png","xpm","jpg"]'``. This will also ensure that ``IMG_Init`` works properly when you specify those formats. Alternatively, you can use ``emcc --use-preload-plugins`` and ``--preload-file`` your images, so the browser codecs decode them (see :ref:`preloading-files`). A code path in the ``sdl2_image`` port will load through :c:func:`emscripten_get_preloaded_image_data`, but then your calls to ``IMG_Init`` with those image formats will fail (as while the images will work through preloading, IMG_Init reports no support for those formats, as it doesn't have support compiled in - in other words, IMG_Init does not report support for formats that only work through preloading).```
.. note:: *SDL_image* has also been added to ports, use it with
``--use-port=sdl2_image``. For ``sdl2_image`` to be useful, you generally
need to specify the image formats you are planning on using with e.g.
``--use-port=sdl2_image:formats=bmp,png,xpm,jpg``. This will also ensure that
``IMG_Init`` works properly when you specify those formats. Alternatively,
you can use ``emcc --use-preload-plugins`` and ``--preload-file`` your
images, so the browser codecs decode them (see :ref:`preloading-files`).
A code path in the ``sdl2_image`` port will load through
:c:func:`emscripten_get_preloaded_image_data`, but then your calls to
``IMG_Init`` with those image formats will fail (as while the images will
work through preloading, IMG_Init reports no support for those formats, as
it doesn't have support compiled in - in other words, ``IMG_Init`` does not
report support for formats that only work through preloading).

.. note:: *SDL_net* has also been added to ports, use it with ``--use-port=sdl2_net``.

.. note:: Emscripten also has support for older SDL1, which is built-in. If you do not specify SDL2 as in the command above, then SDL1 is linked in and the SDL1 include paths are used. SDL1 has support for *sdl-config*, which is present in `system/bin <https://github.com/emscripten-core/emscripten/blob/main/system/bin/sdl-config>`_. Using the native *sdl-config* may result in compilation or missing-symbol errors. You will need to modify the build system to look for files in **emscripten/system** or **emscripten/system/bin** in order to use the Emscripten *sdl-config*.
.. note:: Emscripten also has support for older SDL1, which is built-in.
If you do not specify SDL2 as in the command above, then SDL1 is linked in
and the SDL1 include paths are used. SDL1 has support for *sdl-config*,
which is present in `system/bin <https://github.com/emscripten-core/emscripten/blob/main/system/bin/sdl-config>`_.
Using the native *sdl-config* may result in compilation or missing-symbol errors.
You will need to modify the build system to look for files in
**emscripten/system** or **emscripten/system/bin** in order to use the
Emscripten *sdl-config*.

.. note:: You can also build a library from ports in a manual way if you prefer
that, but then you will need to also apply the python logic that ports does.
Expand All @@ -238,7 +257,9 @@ To see a list of all available ports, run ``emcc --show-ports``.
it's better to use the ports version as it is what is tested and known to
work.

.. note:: Since emscripten 3.1.54, ``--use-port`` is the preferred syntax to use a port in your project. The legacy syntax (for example ``-sUSE_SDL2``, ``-sUSE_SDL_IMAGE=2``) remains available.
.. note:: Since emscripten 3.1.54, ``--use-port`` is the preferred syntax to
use a port in your project. The legacy syntax (for example ``-sUSE_SDL2``,
``-sUSE_SDL_IMAGE=2``) remains available.


Contrib ports
Expand Down
2 changes: 1 addition & 1 deletion test/test_browser.py
Original file line number Diff line number Diff line change
Expand Up @@ -3024,7 +3024,7 @@ def test_sdl2_image_formats(self):
self.btest_exit('test_sdl2_image.c', 600, args=[
'--preload-file', 'screenshot.jpg',
'-DSCREENSHOT_DIRNAME="/"', '-DSCREENSHOT_BASENAME="screenshot.jpg"', '-DBITSPERPIXEL=24', '-DNO_PRELOADED',
'-sUSE_SDL=2', '-sUSE_SDL_IMAGE=2', '-sSDL2_IMAGE_FORMATS=jpg'
'--use-port=sdl2', '--use-port=sdl2_image:formats=jpg'
])

@no_wasm64('SDL2 + wasm64')
Expand Down
18 changes: 18 additions & 0 deletions test/test_other.py
Original file line number Diff line number Diff line change
Expand Up @@ -14515,3 +14515,21 @@ def test_js_preprocess_pre_post(self):
self.do_runf(test_file('hello_world.c'), 'assertions enabled\n4', emcc_args=['-sASSERTIONS=1'])
self.do_runf(test_file('hello_world.c'), 'assertions disabled\n4', emcc_args=['-sASSERTIONS=0'])
self.assertNotContained('#preprocess', read_file('hello_world.js'))

@with_both_compilers
def test_use_port_errors(self, compiler):
stderr = self.expect_fail([compiler, test_file('hello_world.c'), '--use-port=invalid', '-o', 'out.js'])
self.assertFalse(os.path.exists('out.js'))
self.assertContained('Error with --use-port=invalid | invalid port name: invalid', stderr)
stderr = self.expect_fail([compiler, test_file('hello_world.c'), '--use-port=sdl2:opt1=v1', '-o', 'out.js'])
self.assertFalse(os.path.exists('out.js'))
self.assertContained('Error with --use-port=sdl2:opt1=v1 | no options available for port sdl2', stderr)
stderr = self.expect_fail([compiler, test_file('hello_world.c'), '--use-port=sdl2_image:format=jpg', '-o', 'out.js'])
self.assertFalse(os.path.exists('out.js'))
self.assertContained('Error with --use-port=sdl2_image:format=jpg | format is not supported', stderr)
stderr = self.expect_fail([compiler, test_file('hello_world.c'), '--use-port=sdl2_image:formats', '-o', 'out.js'])
self.assertFalse(os.path.exists('out.js'))
self.assertContained('Error with --use-port=sdl2_image:formats | formats is missing a value', stderr)
stderr = self.expect_fail([compiler, test_file('hello_world.c'), '--use-port=sdl2_image:formats=jpg:formats=png', '-o', 'out.js'])
self.assertFalse(os.path.exists('out.js'))
self.assertContained('Error with --use-port=sdl2_image:formats=jpg:formats=png | duplicate option formats', stderr)
30 changes: 28 additions & 2 deletions tools/ports/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ def validate_port(port):
expected_attrs = ['get', 'clear', 'show']
if port.is_contrib:
expected_attrs += ['URL', 'DESCRIPTION', 'LICENSE']
if hasattr(port, 'handle_options'):
expected_attrs += ['OPTIONS']
for a in expected_attrs:
assert hasattr(port, a), 'port %s is missing %s' % (port, a)

Expand Down Expand Up @@ -393,10 +395,34 @@ def add_deps(node):
add_deps(port)


def handle_use_port_arg(settings, name):
def handle_use_port_error(arg, message):
utils.exit_with_error(f'Error with --use-port={arg} | {message}')


def handle_use_port_arg(settings, arg):
args = arg.split(':', 1)
name, options = args[0], None
if len(args) == 2:
options = args[1]
if name not in ports_by_name:
utils.exit_with_error(f'Invalid port name: {name} used with --use-port')
handle_use_port_error(arg, f'invalid port name: {name}')
ports_needed.add(name)
if options:
port = ports_by_name[name]
if not hasattr(port, 'handle_options'):
handle_use_port_error(arg, f'no options available for port {name}')
else:
options_dict = {}
for name_value in options.split(':'):
nv = name_value.split('=', 1)
if len(nv) != 2:
handle_use_port_error(arg, f'{name_value} is missing a value')
if nv[0] not in port.OPTIONS:
handle_use_port_error(arg, f'{nv[0]} is not supported; available options are {port.OPTIONS}')
if nv[0] in options_dict:
handle_use_port_error(arg, f'duplicate option {nv[0]}')
options_dict[nv[0]] = nv[1]
port.handle_options(options_dict)


def get_needed_ports(settings):
Expand Down
26 changes: 26 additions & 0 deletions tools/ports/contrib/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,31 @@ of information:
is about
* `LICENSE`: the license used by the project/port

A contrib port can have options using the syntax
`--use-port=name:opt1=v1:opt2=v2`.

If you want to support options, then your port needs to provide 2
additional components:

1. A handler function defined this way:
```python
def handle_options(options):
# options is of type Dict[str, str]
# in case of error, use utils.exit_with_error('error message')
```
2. A dictionary called `OPTIONS` (type `Dict[str, str]`) where each key is the
name of the option and the value is a short description of what it does

When emscripten detects that options have been provided, it parses them and
check that they are valid option names for this port (using `OPTIONS`). It then
calls the handler function with these (valid) options. If you detect an error
with a value, you should use `tools.utils.exit_with_error` to report the
failure.

> ### Note
> If the options influence the way the library produced by the port is built,
> you must ensure that the library name accounts for these options. Check
> `glfw3.py` for an example of ports with options.
After adding a contrib port, you should consider modifying the documentation
under `site/source/docs/compiling/Contrib-Ports.rst`.
45 changes: 41 additions & 4 deletions tools/ports/contrib/glfw3.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
# found in the LICENSE file.

import os
from tools import utils
from typing import Dict

TAG = '1.0.4'
HASH = 'c3c96718e5d2b37df434a46c4a93ddfd9a768330d33f0d6ce2d08c139752894c2421cdd0fefb800fe41fafc2bbe58c8f22b8aa2849dc4fc6dde686037215cfad'
Expand All @@ -13,14 +15,33 @@
DESCRIPTION = 'This project is an emscripten port of GLFW written in C++ for the web/webassembly platform'
LICENSE = 'Apache 2.0 license'

OPTIONS = {
'disableWarning': 'Boolean to disable warnings emitted by the library',
'disableJoystick': 'Boolean to disable support for joystick entirely',
'disableMultiWindow': 'Boolean to disable multi window support which makes the code smaller and faster'
}

# user options (from --use-port)
opts: Dict[str, bool] = {
'disableWarning': False,
'disableJoystick': False,
'disableMultiWindow': False
}


def get_lib_name(settings):
return 'lib_contrib.glfw3.a'
return ('lib_contrib.glfw3' +
('-nw' if opts['disableWarning'] else '') +
('-nj' if opts['disableJoystick'] else '') +
('-sw' if opts['disableMultiWindow'] else '') +
'.a')


def get(ports, settings, shared):
# get the port
ports.fetch_project('contrib.glfw3', f'https://github.com/pongasoft/emscripten-glfw/releases/download/v{TAG}/emscripten-glfw3-{TAG}.zip', sha512hash=HASH)
ports.fetch_project('contrib.glfw3',
f'https://github.com/pongasoft/emscripten-glfw/releases/download/v{TAG}/emscripten-glfw3-{TAG}.zip',
sha512hash=HASH)

def create(final):
root_path = os.path.join(ports.get_dir(), 'contrib.glfw3')
Expand All @@ -29,8 +50,16 @@ def create(final):
for source_include_path in source_include_paths:
ports.install_headers(source_include_path, target='GLFW')

# this should be an option but better to disable for now...
flags = ['-DEMSCRIPTEN_GLFW3_DISABLE_WARNING']
flags = []

if opts['disableWarning']:
flags += ['-DEMSCRIPTEN_GLFW3_DISABLE_WARNING']

if opts['disableJoystick']:
flags += ['-DEMSCRIPTEN_GLFW3_DISABLE_JOYSTICK']

if opts['disableMultiWindow']:
flags += ['-DEMSCRIPTEN_GLFW3_DISABLE_MULTI_WINDOW_SUPPORT']

ports.build_port(source_path, final, 'contrib.glfw3', includes=source_include_paths, flags=flags)

Expand All @@ -52,3 +81,11 @@ def linker_setup(ports, settings):
# includes
def process_args(ports):
return ['-isystem', ports.get_include_dir('contrib.glfw3')]


def handle_options(options):
for option, value in options.items():
if value.lower() in {'true', 'false'}:
opts[option] = value.lower() == 'true'
else:
utils.exit_with_error(f'{option} is expecting a boolean, got {value}')
34 changes: 27 additions & 7 deletions tools/ports/sdl2_image.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
# found in the LICENSE file.

import os
from typing import Dict, Set

TAG = 'release-2.6.0'
HASH = '2175d11a90211871f2289c8d57b31fe830e4b46af7361925c2c30cd521c1c677d2ee244feb682b6d3909cf085129255934751848fc81b480ea410952d990ffe0'
Expand All @@ -14,14 +15,26 @@
'sdl2_image_png': {'SDL2_IMAGE_FORMATS': ["png"]},
}

OPTIONS = {
'formats': 'A comma separated list of formats (ex: --use-port=sdl2_image:formats=png,jpg)'
}

# user options (from --use-port)
opts: Dict[str, Set] = {
'formats': set()
}


def needed(settings):
return settings.USE_SDL_IMAGE == 2


def get_formats(settings):
return set(settings.SDL2_IMAGE_FORMATS).union(opts['formats'])


def get_lib_name(settings):
settings.SDL2_IMAGE_FORMATS.sort()
formats = '-'.join(settings.SDL2_IMAGE_FORMATS)
formats = '-'.join(sorted(get_formats(settings)))

libname = 'libSDL2_image'
if formats != '':
Expand All @@ -44,13 +57,15 @@ def create(final):

defs = ['-O2', '-sUSE_SDL=2', '-Wno-format-security']

for fmt in settings.SDL2_IMAGE_FORMATS:
formats = get_formats(settings)

for fmt in formats:
defs.append('-DLOAD_' + fmt.upper())

if 'png' in settings.SDL2_IMAGE_FORMATS:
if 'png' in formats:
defs += ['-sUSE_LIBPNG']

if 'jpg' in settings.SDL2_IMAGE_FORMATS:
if 'jpg' in formats:
defs += ['-sUSE_LIBJPEG']

ports.build_port(src_dir, final, 'sdl2_image', flags=defs, srcs=srcs)
Expand All @@ -64,13 +79,18 @@ def clear(ports, settings, shared):

def process_dependencies(settings):
settings.USE_SDL = 2
if 'png' in settings.SDL2_IMAGE_FORMATS:
formats = get_formats(settings)
if 'png' in formats:
deps.append('libpng')
settings.USE_LIBPNG = 1
if 'jpg' in settings.SDL2_IMAGE_FORMATS:
if 'jpg' in formats:
deps.append('libjpeg')
settings.USE_LIBJPEG = 1


def handle_options(options):
opts['formats'].update({format.lower().strip() for format in options['formats'].split(',')})


def show():
return 'sdl2_image (-sUSE_SDL_IMAGE=2 or --use-port=sdl2_image; zlib license)'

0 comments on commit 8353c37

Please sign in to comment.