Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 2 additions & 1 deletion .ci/docker/conan-tests
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,8 @@ RUN apt-get update && \
g++-9-multilib \
gcc-11-multilib \
g++-11-multilib \
scons && \
scons \
emscripten && \
# fix: asm/errno.h: No such file or directory
ln -s /usr/include/asm-generic/ /usr/include/asm && \
add-apt-repository -y ppa:ubuntu-toolchain-r/test && \
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',
}
}
1 change: 1 addition & 0 deletions test/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,7 @@
"path": {'Linux': '/usr/share/qbs/bin'}
}
},
"emcc": {}
# TODO: Intel oneAPI is not installed in CI yet. Uncomment this line whenever it's done.
# "intel_oneapi": {
# "default": "2021.3",
Expand Down
173 changes: 173 additions & 0 deletions test/functional/toolchains/emscripten/test_emcc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
# 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

[platform_tool_requires]
emsdk/4.0.10

[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")
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 "em++ -stdlib=libc++ -O3 -DNDEBUG -std=c++17" in client.out
build_folder = "build/release-wasm" if platform.system() != "Windows" else "build"
assert os.path.exists(os.path.join(client.current_folder, build_folder, "hello.wasm"))

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

client.run("build . -pr:h=asmjs")
assert "WASM=0" in client.out
build_folder = "build/release-asm.js" if platform.system() != "Windows" else "build"
# No wasm should be generated for asm.js architecture
assert not os.path.exists(os.path.join(client.current_folder, build_folder, "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")
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"))
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")
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, "wasm64": wasm_64_profile, "asmjs": asmjs_profile, "base_emscripten_profile": base_emscripten_profile,})
client.run("build . -pr:h=wasm32")
assert "em++ -stdlib=libc++ -sALLOW_MEMORY_GROWTH=1 -sMAXIMUM_MEMORY=4GB -sINITIAL_MEMORY=64MB -sALLOW_MEMORY_GROWTH=1 -sMAXIMUM_MEMORY=4GB -sINITIAL_MEMORY=64MB" in client.out
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 be 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


# def test_bazel_emscripten():
# client = TestClient(path_with_spaces=False)
# client.run("new bazel7_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=wasm32")

# def test_msbuild_emscripten():
# client = TestClient(path_with_spaces=False)
# client.run("new msbuild_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=wasm32")
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