Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for contrib ports #21191

Closed
wants to merge 14 commits into from
34 changes: 34 additions & 0 deletions site/source/docs/tools_reference/settings_reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1700,6 +1700,40 @@ Specify the GLFW version that is being linked against. Only relevant, if you
are linking against the GLFW library. Valid options are 2 for GLFW2 and 3
for GLFW3.

.. _use_contrib_port:

USE_CONTRIB_PORT
================

Specify which contrib ports to use. If there is only one contrib port to use,
it can be specified this way -sUSE_CONTRIB_PORT=port. If multiple contrib
ports are need, you specify it that way: -sUSE_CONTRIB_PORT=[port1,port2].

.. note:: 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.

Available contrib ports:

.. _use_contrib_port=contrib_example:

USE_CONTRIB_PORT=contrib_example
--------------------------------

Port Contrib Example

`Project information <https://github.com/emscripten-core/emscripten>`_
License: MIT license

.. _use_contrib_port=glfw3:

USE_CONTRIB_PORT=glfw3
----------------------

This project is an emscripten port of glfw written in C++ for the web/webassembly platform

`Project information <https://github.com/pongasoft/emscripten-glfw>`_
License: Apache 2.0 license


.. _wasm:

WASM
Expand Down
6 changes: 6 additions & 0 deletions src/settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -1339,6 +1339,12 @@ var EMSCRIPTEN_TRACING = false;
// [link]
var USE_GLFW = 0;

// Specify which contrib ports to use. If there is only one contrib port to use,
// it can be specified this way -sUSE_CONTRIB_PORT=port. If multiple contrib
// ports are need, you specify it that way: -sUSE_CONTRIB_PORT=[port1,port2].
// [link]
var USE_CONTRIB_PORT = [];

// Whether to use compile code to WebAssembly. Set this to 0 to compile to JS
// instead of wasm.
//
Expand Down
28 changes: 28 additions & 0 deletions test/browser/test_contrib_ports.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* 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 <GLFW/glfw3.h>
#include <GLFW/emscripten_glfw3.h>
#include <contrib_example.h>
#include <assert.h>

int main() {
// from one contrib port
assert(contrib_example() == 12);

// from another contrib port
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;
}
4 changes: 4 additions & 0 deletions test/test_browser.py
Original file line number Diff line number Diff line change
Expand Up @@ -2998,6 +2998,10 @@ def test_glfw_events(self):
def test_glfw3_hi_dpi_aware(self):
self.btest_exit('test_glfw3_hi_dpi_aware.c', args=['-sUSE_GLFW=3', '-lGL'])

@requires_graphics_hardware
def test_contrib_ports(self):
self.btest_exit('test_contrib_ports.c', args=['-sUSE_CONTRIB_PORT=[contrib_example,glfw3]', '-lGL'])

@requires_graphics_hardware
@no_wasm64('SDL2 + wasm64')
@parameterized({
Expand Down
4 changes: 2 additions & 2 deletions tools/link.py
Original file line number Diff line number Diff line change
Expand Up @@ -2931,6 +2931,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()
Expand All @@ -2953,8 +2955,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
Expand Down
19 changes: 17 additions & 2 deletions tools/maint/update_settings_docs.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
sys.path.append(root_dir)

from tools.utils import path_from_root, read_file, safe_ensure_dirs
from tools.ports import ports

header = '''\
.. _settings-reference:
Expand Down Expand Up @@ -56,17 +57,29 @@
output_file = path_from_root('site/source/docs/tools_reference/settings_reference.rst')


def write_setting(f, setting_name, comment, tags):
def write_setting(f, setting_name, comment, tags, level='='):
# Convert markdown backticks to rst double backticks
f.write('\n.. _' + setting_name.lower() + ':\n')
f.write('\n' + setting_name + '\n')
f.write('=' * len(setting_name) + '\n\n')
f.write(level * len(setting_name) + '\n\n')
f.write(comment + '\n')
for tag in tags:
for t in tag.split():
if all_tags[t]:
f.write('\n.. note:: ' + all_tags[t] + '\n')

def write_contrib_ports(f):
f.write('\n.. note:: 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.')
f.write('\n\nAvailable contrib ports:\n')
for port in ports:
if port.is_contrib:
comment = port.project_description()
comment += f'\n\n`Project information <{port.project_url()}>`_'
comment += f'\nLicense: {port.project_license()}'
write_setting(f, f'USE_CONTRIB_PORT={port.name}', comment, [], '-')
f.write('\n')

def write_file(f):
f.write(header)
Expand Down Expand Up @@ -95,6 +108,8 @@ def write_file(f):
setting_name = line.split()[1]
comment = '\n'.join(current_comment).strip()
write_setting(f, setting_name, comment, current_tags)
if setting_name == 'USE_CONTRIB_PORT':
write_contrib_ports(f)
current_comment = []
current_tags = []

Expand Down
74 changes: 49 additions & 25 deletions tools/ports/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,38 +29,57 @@
logger = logging.getLogger('ports')


def load_port(port, expected_attrs):
ports.append(port)
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, 'process_dependencies'):
port.process_dependencies = lambda x: 0
if not hasattr(port, 'linker_setup'):
port.linker_setup = lambda x, y: 0
if not hasattr(port, 'deps'):
port.deps = []
if not hasattr(port, 'process_args'):
port.process_args = lambda x: []
if not hasattr(port, 'variants'):
# port variants (default: no variants)
port.variants = {}

for variant, extra_settings in port.variants.items():
if variant in port_variants:
utils.exit_with_error('duplicate port variant: %s' % variant)
port_variants[variant] = (port.name, extra_settings)


def read_ports():
expected_attrs = ['get', 'clear', 'show', 'needed']
for filename in os.listdir(ports_dir):
if not filename.endswith('.py') or filename == '__init__.py':
continue
filename = os.path.splitext(filename)[0]
port = __import__(filename, globals(), level=1)
ports.append(port)
port.is_contrib = False
port.name = filename
load_port(port, expected_attrs)

expected_attrs = ['get', 'clear', 'show', 'needed', 'project_url', 'project_description', 'project_license']
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]
port = __import__('contrib.' + filename, globals(), level=1, fromlist=[None])
port.is_contrib = True
port.name = filename
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, 'process_dependencies'):
port.process_dependencies = lambda x: 0
if not hasattr(port, 'linker_setup'):
port.linker_setup = lambda x, y: 0
if not hasattr(port, 'deps'):
port.deps = []
if not hasattr(port, 'process_args'):
port.process_args = lambda x: []
if not hasattr(port, 'variants'):
# port variants (default: no variants)
port.variants = {}

for variant, extra_settings in port.variants.items():
if variant in port_variants:
utils.exit_with_error('duplicate port variant: %s' % variant)
port_variants[variant] = (port.name, extra_settings)

for dep in port.deps:
if dep not in ports_by_name:
utils.exit_with_error('unknown dependency in port: %s' % dep)
port.needed = lambda settings, name = port.name: name in settings.USE_CONTRIB_PORT
port.show = lambda name = port.name, license = port.project_license(): f'{name} (-sUSE_CONTRIB_PORT={name}; {license})'
load_port(port, expected_attrs)

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)


def get_all_files_under(dirname):
Expand Down Expand Up @@ -422,7 +441,12 @@ def add_cflags(args, settings): # noqa: U100
def show_ports():
print('Available ports:')
for port in ports:
print(' ', port.show())
if not port.is_contrib:
print(' ', port.show())
print('Available Contrib ports:')
for port in ports:
if port.is_contrib:
print(' ', port.show())


read_ports()
20 changes: 20 additions & 0 deletions tools/ports/contrib/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
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 (provided as functions in the port file):

* `project_url`: the url where the user can find more information about
the project/port
* `project_description`: a (short) description of what the project/port
is about
* `project_license`: the license used by the project/port

After adding (resp. modifying) a contrib port, you must run the
`./tools/maint/update_settings_docs.py` command to add (resp. update)
the new port to the documentation.
Empty file added tools/ports/contrib/__init__.py
Empty file.
58 changes: 58 additions & 0 deletions tools/ports/contrib/contrib_example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# 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.


# The purpose of this contrib port is to have 2 contrib ports in order to have a test with
# 2 ports since the syntax is different. Once there are 2 contrib ports available, this
# example can be removed and the test changed to use the 2 available ports...
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about using the libarchive example I provided in my PR?



import os


def get_lib_name(settings):
return 'libcontrib_example.a'


def get(ports, settings, shared):

def create(final):
source_path = ports.get_dir()
ports.write_file(os.path.join(source_path, 'contrib_example.h'), example_h)
ports.write_file(os.path.join(source_path, 'contrib_example.c'), example_c)
ports.install_headers(source_path)
ports.build_port(source_path, final, 'contrib_example')

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 process_args(ports):
return ['-isystem', ports.get_include_dir('contrib_example')]


def project_url():
return 'https://github.com/emscripten-core/emscripten'


def project_description():
return 'Port Contrib Example'


def project_license():
return 'MIT license'


example_h = 'int contrib_example();'


example_c = r'''
int contrib_example() {
return 12;
}
'''
Loading
Loading