diff --git a/ChangeLog.md b/ChangeLog.md index d271798abe748..caa1b73c1446c 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -30,6 +30,11 @@ See docs/process.md for more on how version tagging works. the top of the JS file. This is useful as it allows things like `{{{ 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 + available via `--use-port=contrib.glfw3`: an emscripten port of glfw written + in C++ with many features like support for multiple windows. (#21244) + 3.1.53 - 01/29/24 ----------------- diff --git a/embuilder.py b/embuilder.py index 7179bb92ae75c..917a0361c123d 100755 --- a/embuilder.py +++ b/embuilder.py @@ -126,7 +126,8 @@ def get_help(): return ''' Available targets: - build / clear %s + build / clear + %s Issuing 'embuilder build ALL' causes each task to be built. ''' % '\n '.join(all_tasks) diff --git a/site/source/docs/compiling/Building-Projects.rst b/site/source/docs/compiling/Building-Projects.rst index fd8b798353a70..46a306f14d613 100644 --- a/site/source/docs/compiling/Building-Projects.rst +++ b/site/source/docs/compiling/Building-Projects.rst @@ -241,15 +241,21 @@ To see a list of all available ports, run ``emcc --show-ports``. .. 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 +------------- + +Contrib ports are contributed by the wider community and supported on a +"best effort" basis. Since they are not run as part of emscripten CI they are +not always guaranteed to build or function. +See :ref:`Contrib Ports ` for more information. + Adding more ports ----------------- -Adding more ports is fairly easy. Basically, the steps are +The simplest way to add a new port is to put it under the ``contrib`` directory. Basically, the steps are: * Make sure the port is open source and has a suitable license. - * Add it to emscripten-ports on github. The ports maintainers can create the repo and add the relevant developers to a team for that repo, so they have write access. - * Add a script to handle it under ``tools/ports/`` (see existing code for examples) and use it in ``tools/ports/__init__.py``. - * Add testing in the test suite. + * Read the ``README.md`` file under ``tools/ports/contrib`` which contains more information. Build system issues diff --git a/site/source/docs/compiling/Contrib-Ports.rst b/site/source/docs/compiling/Contrib-Ports.rst new file mode 100644 index 0000000000000..00b649761c9c0 --- /dev/null +++ b/site/source/docs/compiling/Contrib-Ports.rst @@ -0,0 +1,24 @@ +.. _Contrib-Ports: + +======================== +Emscripten Contrib Ports +======================== + +Contrib ports are contributed by the wider community and +supported on a "best effort" basis. Since they are not run as part +of emscripten CI they are not always guaranteed to build or function. + +The following is the complete list of the contrib ports that are +available in emscripten. In order to use a contrib port you use the +``--use-port=`` option (:ref:`emcc `). + +.. _contrib.glfw3: + +contrib.glfw3 +============= + +This project is an emscripten port of glfw written in C++ for the web/webassembly platform + +`Project information `_ + +License: Apache 2.0 license \ No newline at end of file diff --git a/site/source/docs/compiling/index.rst b/site/source/docs/compiling/index.rst index 129e1ec0d9ca1..5ab6dc48fce45 100644 --- a/site/source/docs/compiling/index.rst +++ b/site/source/docs/compiling/index.rst @@ -11,6 +11,7 @@ This section contains topics about building projects and running the output. - :ref:`Running-html-files-with-emrun` explains how to use *emrun* to run generated HTML pages in a locally launched web server. - :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. .. toctree:: @@ -22,3 +23,4 @@ This section contains topics about building projects and running the output. Running-html-files-with-emrun Deploying-Pages GitLab + Contrib-Ports diff --git a/test/other/test_contrib_ports.cpp b/test/other/test_contrib_ports.cpp new file mode 100644 index 0000000000000..946f9b492e149 --- /dev/null +++ b/test/other/test_contrib_ports.cpp @@ -0,0 +1,25 @@ +/* + * Copyright 2024 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. + */ + +#include +#include +#include + +// cpp otherwise it fails to link +int main() { + + assert(glfwInit() == GLFW_TRUE); + + GLFWwindow* window = glfwCreateWindow(320, 200, "test_glfw3_port", 0, 0); + assert(window != 0); + // this call ensures that it uses the right port + assert(emscripten_glfw_is_window_fullscreen(window) == EM_FALSE); + glfwTerminate(); + + + return 0; +} diff --git a/test/test_other.py b/test/test_other.py index 7bef169281e13..aba5e0e557c4a 100644 --- a/test/test_other.py +++ b/test/test_other.py @@ -2369,6 +2369,11 @@ def test_sdl2_ttf(self): self.emcc(test_file('browser/test_sdl2_ttf.c'), args=['-sUSE_SDL=2', '-sUSE_SDL_TTF=2'], output_filename='a.out.js') self.emcc(test_file('browser/test_sdl2_ttf.c'), args=['--use-port=sdl2', '--use-port=sdl2_ttf'], output_filename='a.out.js') + def test_contrib_ports(self): + # Verify that contrib ports can be used (using the only contrib port available ATM, but can be replaced + # with a different contrib port when there is another one + self.emcc(test_file('other/test_contrib_ports.cpp'), ['--use-port=contrib.glfw3']) + def test_link_memcpy(self): # memcpy can show up *after* optimizations, so after our opportunity to link in libc, so it must be special-cased create_file('main.c', r''' diff --git a/test/test_sanity.py b/test/test_sanity.py index 7ae6f8df3b1e7..0375e28237a9d 100644 --- a/test/test_sanity.py +++ b/test/test_sanity.py @@ -529,7 +529,7 @@ def test_emcc_ports(self): # listing ports out = self.do([EMCC, '--show-ports']) - self.assertContained('Available ports:', out) + self.assertContained('Available official ports:', out) self.assertContained('sdl2', out) self.assertContained('sdl2_image', out) self.assertContained('sdl2_net', out) diff --git a/tools/link.py b/tools/link.py index e0dc389d4ae77..3040ba6bff6a0 100644 --- a/tools/link.py +++ b/tools/link.py @@ -2941,6 +2941,8 @@ def run(linker_inputs, options, state, newargs): logger.debug('stopping after linking to object file') return 0 + phase_calculate_system_libraries(state, linker_arguments, newargs) + js_syms = {} if (not settings.SIDE_MODULE or settings.ASYNCIFY) and not shared.SKIP_SUBPROCS: js_info = get_js_sym_info() @@ -2963,8 +2965,6 @@ def add_js_deps(sym): settings.ASYNCIFY_IMPORTS_EXCEPT_JS_LIBS = settings.ASYNCIFY_IMPORTS[:] settings.ASYNCIFY_IMPORTS += ['*.' + x for x in js_info['asyncFuncs']] - phase_calculate_system_libraries(state, linker_arguments, newargs) - phase_link(linker_arguments, wasm_target, js_syms) # Special handling for when the user passed '-Wl,--version'. In this case the linker diff --git a/tools/ports/__init__.py b/tools/ports/__init__.py index 71df3d0c2c6c4..9ceb3b63e37e6 100644 --- a/tools/ports/__init__.py +++ b/tools/ports/__init__.py @@ -34,13 +34,11 @@ def load_port(name): - expected_attrs = ['get', 'clear', 'show'] - port = __import__(name, globals(), level=1) + port = __import__(name, globals(), level=1, fromlist=[None]) ports.append(port) + port.is_contrib = name.startswith('contrib.') port.name = name ports_by_name[port.name] = port - for a in expected_attrs: - assert hasattr(port, a), 'port %s is missing %s' % (port, a) if not hasattr(port, 'needed'): port.needed = lambda s: name in ports_needed else: @@ -57,6 +55,8 @@ def load_port(name): if not hasattr(port, 'variants'): # port variants (default: no variants) port.variants = {} + if not hasattr(port, 'show'): + port.show = lambda: f'{port.name} (--use-port={port.name}; {port.LICENSE})' for variant, extra_settings in port.variants.items(): if variant in port_variants: @@ -64,6 +64,22 @@ def load_port(name): port_variants[variant] = (port.name, extra_settings) +def validate_port(port): + expected_attrs = ['get', 'clear', 'show'] + if port.is_contrib: + expected_attrs += ['URL', 'DESCRIPTION', 'LICENSE'] + for a in expected_attrs: + assert hasattr(port, a), 'port %s is missing %s' % (port, a) + + +def validate_ports(): + for port in ports: + validate_port(port) + for dep in port.deps: + if dep not in ports_by_name: + utils.exit_with_error('unknown dependency in port: %s' % dep) + + @ToolchainProfiler.profile() def read_ports(): for filename in os.listdir(ports_dir): @@ -72,10 +88,14 @@ def read_ports(): filename = os.path.splitext(filename)[0] load_port(filename) - for port in ports: - for dep in port.deps: - if dep not in ports_by_name: - utils.exit_with_error('unknown dependency in port: %s' % dep) + contrib_dir = os.path.join(ports_dir, 'contrib') + for filename in os.listdir(contrib_dir): + if not filename.endswith('.py') or filename == '__init__.py': + continue + filename = os.path.splitext(filename)[0] + load_port('contrib.' + filename) + + validate_ports() def get_all_files_under(dirname): @@ -441,9 +461,15 @@ def add_cflags(args, settings): # noqa: U100 def show_ports(): - print('Available ports:') - for port in sorted(ports, key=lambda p: p.name): - print(' ', port.show()) + sorted_ports = sorted(ports, key=lambda p: p.name) + print('Available official ports:') + for port in sorted_ports: + if not port.is_contrib: + print(' ', port.show()) + print('Available contrib ports:') + for port in sorted_ports: + if port.is_contrib: + print(' ', port.show()) read_ports() diff --git a/tools/ports/contrib/README.md b/tools/ports/contrib/README.md new file mode 100644 index 0000000000000..f5b3b40d3a2a3 --- /dev/null +++ b/tools/ports/contrib/README.md @@ -0,0 +1,19 @@ +Emscripten "Contrib" Ports +========================== + +Ports in this directory are contributed by the wider community and are +supported on a "best effort" basis. Since they are not run as part of +emscripten CI they are not always guaranteed to build or function. + +If you want to add a contrib port, please use another contrib port as +an example. In particular, each contrib port must provide 3 extra piece +of information: + +* `URL`: the url where the user can find more information about + the project/port +* `DESCRIPTION`: a (short) description of what the project/port + is about +* `LICENSE`: the license used by the project/port + +After adding a contrib port, you should consider modifying the documentation +under `site/source/docs/compiling/Contrib-Ports.rst`. \ No newline at end of file diff --git a/tools/ports/contrib/__init__.py b/tools/ports/contrib/__init__.py new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/tools/ports/contrib/glfw3.py b/tools/ports/contrib/glfw3.py new file mode 100644 index 0000000000000..b15cf6c38c944 --- /dev/null +++ b/tools/ports/contrib/glfw3.py @@ -0,0 +1,54 @@ +# Copyright 2024 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. + +import os + +TAG = '1.0.4' +HASH = 'c3c96718e5d2b37df434a46c4a93ddfd9a768330d33f0d6ce2d08c139752894c2421cdd0fefb800fe41fafc2bbe58c8f22b8aa2849dc4fc6dde686037215cfad' + +# contrib port information (required) +URL = 'https://github.com/pongasoft/emscripten-glfw' +DESCRIPTION = 'This project is an emscripten port of glfw written in C++ for the web/webassembly platform' +LICENSE = 'Apache 2.0 license' + + +def get_lib_name(settings): + return 'lib_contrib.glfw3.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) + + def create(final): + root_path = os.path.join(ports.get_dir(), 'contrib.glfw3') + source_path = os.path.join(root_path, 'src', 'cpp') + source_include_paths = [os.path.join(root_path, 'external', 'GLFW'), os.path.join(root_path, 'include', 'GLFW')] + 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'] + + ports.build_port(source_path, final, 'contrib.glfw3', includes=source_include_paths, flags=flags) + + return [shared.cache.get_lib(get_lib_name(settings), create, what='port')] + + +def clear(ports, settings, shared): + shared.cache.erase_lib(get_lib_name(settings)) + + +def linker_setup(ports, settings): + root_path = os.path.join(ports.get_dir(), 'contrib.glfw3') + source_js_path = os.path.join(root_path, 'src', 'js', 'lib_emscripten_glfw3.js') + settings.JS_LIBRARIES += [source_js_path] + + +# Using contrib.glfw3 to avoid installing headers into top level include path +# so that we don't conflict with the builtin GLFW headers that emscripten +# includes +def process_args(ports): + return ['-isystem', ports.get_include_dir('contrib.glfw3')]