Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
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
18 changes: 17 additions & 1 deletion .ci/docker/conan-tests
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ ENV PY36=3.6.15 \
CLANGXX_14=/usr/bin/clang++-14 \
BAZEL_6=6.5.0 \
BAZEL_7=7.4.1 \
BAZEL_8=8.0.0
BAZEL_8=8.0.0 \
EMSDK=4.0.10

RUN apt-get update && \
apt-get install -y --no-install-recommends \
Expand Down Expand Up @@ -169,6 +170,21 @@ RUN cd /tmp && \
mv qbs-linux-x86_64-2.6.0 /usr/share/qbs && \
rm qbs-linux-x86_64-2.6.0.tar.gz

RUN cd /tmp && \
wget https://github.com/emscripten-core/emsdk/archive/refs/tags/${EMSDK}.tar.gz && \
tar xzf ${EMSDK}.tar.gz --directory /usr/share && \
cd /usr/share/emsdk-${EMSDK} && \
./emsdk update && \
./emsdk install latest && \
./emsdk activate latest --permanent && \
. /usr/share/emsdk-${EMSDK}/emsdk_env.sh && \
embuilder build MINIMAL && \
embuilder build MINIMAL --wasm64
# echo "EMSDK_QUIET=1 . /usr/share/emsdk-${EMSDK}/emsdk_env.sh" >> /etc/bash.bashrc
# Manually add the emsdk binaries to the PATH and set emcc cache directory to a writable location
ENV PATH="/usr/share/emsdk-$EMSDK:/usr/share/emsdk-$EMSDK/upstream/emscripten:/usr/share/emsdk-$EMSDK/node/22.16.0_64bit/bin:$PATH" \
EM_CACHE=$HOME/.emscripten_cache

RUN echo "export QT_NO_GLIB=1" >> /etc/profile.d/qt.sh

USER conan
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/osx-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ jobs:

- name: Install homebrew dependencies
run: |
brew install xcodegen make libtool zlib autoconf automake ninja
brew install xcodegen make libtool zlib autoconf automake ninja emscripten

- name: Cache CMake and Bazel installations
id: cache-tools
Expand Down Expand Up @@ -168,7 +168,7 @@ jobs:

- name: Install homebrew dependencies
run: |
brew install xcodegen make libtool zlib autoconf automake ninja
brew install xcodegen make libtool zlib autoconf automake ninja emscripten
export PATH=${HOME}/Applications/CMake/3.15.7/bin:$PATH:/usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin:/usr/sbin:/sbin
cmake --version
bazel --version
Expand Down
9 changes: 8 additions & 1 deletion conan/internal/default_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@
arch: [x86, x86_64, ppc32be, ppc32, ppc64le, ppc64,
armv4, armv4i, armv5el, armv5hf, armv6, armv7, armv7hf, armv7s, armv7k, armv8, armv8_32, armv8.3, arm64ec,
sparc, sparcv9,
mips, mips64, avr, s390, s390x, asm.js, wasm, sh4le,
mips, mips64, avr, s390, s390x, asm.js, wasm, wasm64, sh4le,
e2k-v2, e2k-v3, e2k-v4, e2k-v5, e2k-v6, e2k-v7,
riscv64, riscv32,
xtensalx6, xtensalx106, xtensalx7,
Expand Down Expand Up @@ -163,6 +163,13 @@
version: ["1.19", "1.20", "1.21", "1.22", "1.23", "1.24", "1.25"]
libcxx: [libstdc++, libstdc++11]
cppstd: [null, 98, gnu98, 11, gnu11, 14, gnu14, 17, gnu17, 20, gnu20, 23, gnu23]
emcc:
# From https://github.com/emscripten-core/emscripten/blob/main/ChangeLog.md
# There is no ABI compatibility guarantee between versions
version: [ANY]
libcxx: [null, libstdc++, libstdc++11, libc++]
cppstd: [null, 98, gnu98, 11, gnu11, 14, gnu14, 17, gnu17, 20, gnu20, 23, gnu23, 26, gnu26]
cstd: [null, 99, gnu99, 11, gnu11, 17, gnu17, 23, gnu23]

build_type: [null, Debug, Release, RelWithDebInfo, MinSizeRel]
"""
Expand Down
14 changes: 14 additions & 0 deletions conan/tools/build/cppstd.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ def supported_cppstd(conanfile, compiler=None, compiler_version=None):
"clang": _clang_supported_cppstd,
"mcst-lcc": _mcst_lcc_supported_cppstd,
"qcc": _qcc_supported_cppstd,
"emcc": _emcc_supported_cppstd,
}.get(compiler)
if func:
return func(Version(compiler_version))
Expand Down Expand Up @@ -267,3 +268,16 @@ def _qcc_supported_cppstd(version):
return ["98", "gnu98"]
else:
return ["98", "gnu98", "11", "gnu11", "14", "gnu14", "17", "gnu17"]

def _emcc_supported_cppstd(version):
"""
emcc is based on clang but follow different versioning scheme.
"""
if version <= "3.0.1":
return _clang_supported_cppstd(Version("14"))
if version <= "3.1.50":
return _clang_supported_cppstd(Version("18"))
if version <= "4.0.1":
return _clang_supported_cppstd(Version("20"))
# Since emcc 4.0.2 clang version is 21
return _clang_supported_cppstd(Version("21"))
16 changes: 16 additions & 0 deletions conan/tools/build/cstd.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ def supported_cstd(conanfile, compiler=None, compiler_version=None):
"gcc": _gcc_supported_cstd,
"msvc": _msvc_supported_cstd,
"clang": _clang_supported_cstd,
"emcc": _emcc_supported_cstd,
}.get(compiler)
if func:
return func(Version(compiler_version))
Expand Down Expand Up @@ -190,3 +191,18 @@ def _clang_supported_cstd(version):
if version < "18":
return ["99", "gnu99", "11", "gnu11", "17", "gnu17"]
return ["99", "gnu99", "11", "gnu11", "17", "gnu17", "23", "gnu23"]

def _emcc_supported_cstd(version):
"""
emcc is based on clang but follow different versioning scheme.
"""
if version <= "3.0.1":
return _clang_supported_cstd(Version("14"))
if version <= "3.1.50":
return _clang_supported_cstd(Version("18"))
if version <= "4.0.1":
return _clang_supported_cstd(Version("20"))
# Since emcc 4.0.2 clang version is 21
return _clang_supported_cstd(Version("21"))


11 changes: 9 additions & 2 deletions conan/tools/build/flags.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,13 @@ def architecture_flag(conanfile):
"e2k-v5": "-march=elbrus-v5",
"e2k-v6": "-march=elbrus-v6",
"e2k-v7": "-march=elbrus-v7"}.get(arch, "")
elif compiler == "emcc":
# Emscripten default output is WASM since 1.37.x (long time ago)
if arch == "wasm64":
return "-sMEMORY64=1"
# Deactivate WASM output forcing asm.js output instead
elif arch == "asm.js":
return "-sWASM=0"
return ""


Expand All @@ -78,7 +85,7 @@ def libcxx_flags(conanfile):
if compiler == "apple-clang":
# In apple-clang 2 only values atm are "libc++" and "libstdc++"
lib = f'-stdlib={libcxx}'
elif compiler == "clang" or compiler == "intel-cc":
elif compiler in ("clang", "intel-cc", "emcc"):
if libcxx == "libc++":
lib = "-stdlib=libc++"
elif libcxx == "libstdc++" or libcxx == "libstdc++11":
Expand All @@ -93,7 +100,7 @@ def libcxx_flags(conanfile):
elif compiler == "qcc":
lib = f'-Y _{libcxx}'

if compiler in ['clang', 'apple-clang', 'gcc']:
if compiler in ['clang', 'apple-clang', 'gcc', 'emcc']:
if libcxx == "libstdc++":
stdlib11 = "_GLIBCXX_USE_CXX11_ABI=0"
elif libcxx == "libstdc++11" and conanfile.conf.get("tools.gnu:define_libcxx11_abi",
Expand Down
1 change: 1 addition & 0 deletions conan/tools/gnu/get_gnu_triplet.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ def _get_gnu_arch(os_, arch):
"armv8.3": "aarch64",
"asm.js": "asmjs",
"wasm": "wasm32",
"wasm64": "wasm64",
}.get(arch, None)

if not machine:
Expand Down
1 change: 1 addition & 0 deletions conan/tools/meson/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
'sparc': ('sparc', 'sparc', 'big'),
'sparcv9': ('sparc64', 'sparc64', 'big'),
'wasm': ('wasm32', 'wasm32', 'little'),
'wasm64': ('wasm64', 'wasm64', 'little'),
'x86': ('x86', 'x86', 'little'),
'x86_64': ('x86_64', 'x86_64', 'little'),
'riscv32': ('riscv32', 'riscv32', 'little'),
Expand Down
3 changes: 2 additions & 1 deletion conan/tools/qbs/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
's390x': 's390x',
'asm.js': None,
'wasm': None,
'wasm64': None,
'sh4le': 'sh'
}

Expand Down Expand Up @@ -84,4 +85,4 @@
'MT': 'static',
'MDd': 'dynamic',
'MTd': 'static',
}
}
2 changes: 2 additions & 0 deletions test/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,8 @@
"path": {'Linux': '/usr/share/qbs/bin'}
}
},
"emcc": {},
"node": {},
# TODO: Intel oneAPI is not installed in CI yet. Uncomment this line whenever it's done.
# "intel_oneapi": {
# "default": "2021.3",
Expand Down
167 changes: 167 additions & 0 deletions test/functional/toolchains/emscripten/test_emcc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
# Test suite to check conan capabilities for cross compiling to web assembly and asmjs
import textwrap
import os
import platform
from shutil import rmtree
import pytest

from conan.test.utils.tools import TestClient

base_emscripten_profile = textwrap.dedent(
"""
[settings]
build_type=Release
compiler=emcc
compiler.cppstd=17
compiler.libcxx=libc++
compiler.version=4.0.10
os=Emscripten

[conf]
tools.build:exelinkflags=['-sALLOW_MEMORY_GROWTH=1']
tools.build:sharedlinkflags=['-sALLOW_MEMORY_GROWTH=1']

# Define the emcc executable paths
tools.build:compiler_executables={'c':'emcc', 'cpp':'em++'}

# Set Ninja as default generator as it is faster and will sove issues on Windows
tools.cmake.cmaketoolchain:generator=Ninja
# Verbosity to see emcc invocations
tools.compilation:verbosity=verbose
# Distinguish between architectures
tools.cmake.cmake_layout:build_folder_vars=['settings.build_type', 'settings.arch']

[buildenv]
AR=emar
NM=emnm
RANLIB=emranlib
STRIP=emstrip
"""
)

wasm32_profile = textwrap.dedent(
"""
include(base_emscripten_profile)
[settings]
arch=wasm

[conf]
tools.build:exelinkflags+=['-sMAXIMUM_MEMORY=4GB', '-sINITIAL_MEMORY=64MB']
tools.build:sharedlinkflags+=['-sMAXIMUM_MEMORY=4GB', '-sINITIAL_MEMORY=64MB']
"""
)

wasm_64_profile = textwrap.dedent(
"""
include(base_emscripten_profile)
[settings]
arch=wasm64
[conf]
tools.build:exelinkflags+=['-sMAXIMUM_MEMORY=16GB', '-sINITIAL_MEMORY=16GB']
tools.build:sharedlinkflags+=['-sMAXIMUM_MEMORY=16GB', '-sINITIAL_MEMORY=16GB']
"""
)


asmjs_profile = textwrap.dedent(
"""
include(base_emscripten_profile)
[settings]
arch=asm.js

[conf]
tools.build:exelinkflags+=['-sMAXIMUM_MEMORY=2GB', '-sINITIAL_MEMORY=64MB']
tools.build:sharedlinkflags+=['-sMAXIMUM_MEMORY=2GB', '-sINITIAL_MEMORY=64MB']
"""
)


@pytest.mark.tool("cmake")
@pytest.mark.tool("emcc")
@pytest.mark.tool("node")
@pytest.mark.skipif(platform.system() == "Windows", reason = "Emscripten not installed in Windows")
def test_cmake_emscripten():
client = TestClient()

client.run("new cmake_exe -d name=hello -d version=0.1")
client.save({"wasm32": wasm32_profile, "asmjs": asmjs_profile, "base_emscripten_profile": base_emscripten_profile,})

client.run("build . -pr:h=wasm32")
assert "Conan toolchain: Defining libcxx as C++ flags: -stdlib=libc++" in client.out
assert os.path.exists(os.path.join(client.current_folder, "build/release-wasm" , "hello.wasm"))

# Run JavaScript generated code which uses .wasm file
client.run_command("node ./build/release-wasm/hello")
Comment thread
perseoGI marked this conversation as resolved.
assert "Hello World Release!" in client.out

client.run("build . -pr:h=asmjs")
assert "WASM=0" in client.out
# No wasm should have been generated for asm.js architecture
assert not os.path.exists(os.path.join(client.current_folder, "build/release-asm.js" , "hello.wasm"))
client.run_command("node ./build/release-asm.js/hello")
assert "Hello World Release!" in client.out


@pytest.mark.tool("meson")
@pytest.mark.tool("emcc")
@pytest.mark.tool("node")
@pytest.mark.skipif(platform.system() == "Windows", reason = "Emscripten not installed in Windows")
def test_meson_emscripten():
client = TestClient()
client.run("new meson_exe -d name=hello -d version=0.1")

client.save({"wasm32": wasm32_profile, "wasm64": wasm_64_profile, "asmjs": asmjs_profile, "base_emscripten_profile": base_emscripten_profile,})
client.run("build . -pr:h=wasm64")
assert "C++ compiler for the host machine: em++" in client.out
assert "C++ linker for the host machine: em++ ld.wasm" in client.out
assert "Host machine cpu family: wasm64" in client.out
assert os.path.exists(os.path.join(client.current_folder, "build", "hello.wasm"))
# wasm64 only supported since node v23 so only run in MacOS where it is available
if platform.system() == "Darwin":
client.run_command("node ./build/hello")
assert "Hello World Release!" in client.out

rmtree(os.path.join(client.current_folder, "build"))
client.run("build . -pr:h=asmjs")
assert "C++ compiler for the host machine: em++" in client.out
assert "C++ linker for the host machine: em++ ld.wasm" in client.out
assert "Host machine cpu family: asm.js" in client.out
assert "WASM=0" in client.out

assert not os.path.exists(os.path.join(client.current_folder, "build", "hello.wasm"))
client.run_command("node ./build/hello")
assert "Hello World Release!" in client.out


@pytest.mark.tool("autotools")
@pytest.mark.tool("emcc")
@pytest.mark.tool("node")
@pytest.mark.skipif(platform.system() == "Windows", reason = "Emscripten not installed in Windows")
def test_autotools_emscripten():
client = TestClient(path_with_spaces=False)
client.run("new autotools_exe -d name=hello -d version=0.1")
client.save({"wasm32": wasm32_profile, "asmjs": asmjs_profile, "base_emscripten_profile": base_emscripten_profile,})
client.run("build . -pr:h=wasm32")
assert "checking for wasm32-local-emscripten-ranlib... emranlib" in client.out
assert "checking for wasm32-local-emscripten-gcc... emcc" in client.out
assert "checking for wasm32-local-emscripten-ar... emar" in client.out
assert "checking the archiver (emar) interface... ar" in client.out
assert "checking for wasm32-local-emscripten-strip... emstrip" in client.out

assert os.path.exists(os.path.join(client.current_folder, "build-release", "src", "hello.wasm"))
# Run JavaScript generated code which uses .wasm file
client.run_command("node ./build-release/src/hello")
assert "Hello World Release!" in client.out

rmtree(os.path.join(client.current_folder, "build-release"))
client.run("build . -pr:h=asmjs")
assert "WASM=0" in client.out
# No wasm should have been generated for asm.js architecture
assert not os.path.exists(os.path.join(client.current_folder, "build-release", "hello.wasm"))
client.run_command("node ./build-release/src/hello")
assert "Hello World Release!" in client.out


# TODO: test_bazel_emscripten(): need WIP new bazel toolchain
# TODO: test_msbuild_emscripten(): give support to msbuild
# TODO: test_premake_emscripten(): premake tests
1 change: 1 addition & 0 deletions test/unittests/tools/gnu/test_triplets.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
["tvOS", "x86_64", None, "x86_64-apple-tvos"],
["Emscripten", "asm.js", None, "asmjs-local-emscripten"],
["Emscripten", "wasm", None, "wasm32-local-emscripten"],
["Emscripten", "wasm64", None, "wasm64-local-emscripten"],
["AIX", "ppc32", None, "rs6000-ibm-aix"],
["AIX", "ppc64", None, "powerpc-ibm-aix"],
["Neutrino", "armv7", None, "arm-nto-qnx"],
Expand Down
Loading