conan-io / cmake-conan

CMake wrapper for conan C and C++ package manager
MIT License
830 stars 252 forks source link

Any plans of adding support for emscripten? #72

Open trivigy opened 6 years ago

trivigy commented 6 years ago

I am getting while using version 0.8.

CMake Error at build/Debug/conan.cmake:61 (message):
  cmake system Emscripten is not supported by conan.  Use one of
  Windows;Linux;Macos;Android;iOS;FreeBSD

Here is how my CMakeList.txt looks like:

cmake_minimum_required(VERSION 3.9.3)

set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_CXX_FLAGS "-s WASM=1 --bind")

list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake/")
if (NOT EXISTS "${CMAKE_BINARY_DIR}/conan.cmake")
    message(STATUS "Downloading conan.cmake from https://github.com/conan-io/cmake-conan")
    file(DOWNLOAD
        "https://raw.githubusercontent.com/conan-io/cmake-conan/v0.8/conan.cmake"
        "${CMAKE_BINARY_DIR}/conan.cmake"
        EXPECTED_MD5 58bc519cadc890b5c33235defadc1176)
endif ()
include(${CMAKE_BINARY_DIR}/conan.cmake)

project(aide VERSION 0.0.1)
conan_cmake_run(
    CONANFILE conanfile.txt
    BASIC_SETUP CMAKE_TARGETS
    BUILD missing
)

set(CMAKE_EXECUTABLE_SUFFIX ".html")
file(GLOB_RECURSE SOURCE_FILES src/*.cpp)

add_executable(index ${SOURCE_FILES})

When I call cmake I am actually passing the emscripten toolchain.cmake link:

cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_TOOLCHAIN_FILE=/path/to/emsdk/emscripten/1.37.28/cmake/Modules/Platform/Emscripten.cmake -G "Unix Makefiles" /path/to/project/aide
trivigy commented 6 years ago

p.s. I am aware of this: https://github.com/conan-io/conan/pull/1565

This is more of a question with regards to the conan-cmake extension.

memsharded commented 6 years ago

Hi!

I think it doesn't make sense to provide something in cmake-conan for emscripten if conan itself doesn't provide so minimun tooling & docs for emscripten. As we are not experienced in emscripten, we were waiting for emscripten users contributions and feedback (cc @mikea). What do you think? could you provide some help so conan provides some emscripten support? Of course we can adapt cmake-conan in parallel to use that. Thanks!

trivigy commented 6 years ago

@memsharded I actually came up with a work around that turned out to really work well for me. Basically the trick is that I let cmake load conan regularly first and execute any cmake dependencies. The dependencies themselves have to be obviously rewritten a bit to rely on the emsdk (which is also made into a conanfile.py) Once that is done, switch the toolchain configs to the emsdk (which was just downloaded and compiled by conan) and let everything else be done with the new toolchain from there.

Sorry for spamming all of the file content here. But here is my example:

CmakeLists.txt

cmake_minimum_required(VERSION 3.9.3)

if (NOT DEFINED CMAKE_TOOLCHAIN_FILE)
    set(CMAKE_TOOLCHAIN_FILE ${CMAKE_SOURCE_DIR}/cmake/toolchain.cmake)
    include(${CMAKE_SOURCE_DIR}/cmake/toolchain.cmake)
endif ()

list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake/")
if (NOT EXISTS "${CMAKE_BINARY_DIR}/conan.cmake")
    message(STATUS "Downloading conan.cmake from https://github.com/conan-io/cmake-conan")
    file(DOWNLOAD
        "https://raw.githubusercontent.com/conan-io/cmake-conan/v0.8/conan.cmake"
        "${CMAKE_BINARY_DIR}/conan.cmake"
        EXPECTED_MD5 58bc519cadc890b5c33235defadc1176)
endif ()
include(${CMAKE_BINARY_DIR}/conan.cmake)

project(agent VERSION 0.0.2)
conan_cmake_run(
    CONANFILE conanfile.txt
    BASIC_SETUP CMAKE_TARGETS
    BUILD missing
)

set(CMAKE_TOOLCHAIN_FILE ${CONAN_USER_EMSDK_emscripten_root}/cmake/Modules/Platform/Emscripten.cmake)
set(CMAKE_C_COMPILER "")
set(CMAKE_CXX_COMPILER "")
set(CMAKE_AR "")
set(CMAKE_RANLIB "")

include(${CONAN_USER_EMSDK_emscripten_root}/cmake/Modules/Platform/Emscripten.cmake)

set(CMAKE_CXX_FLAGS "-s WASM=1 --bind --shell-file ${CMAKE_CURRENT_SOURCE_DIR}/src/shell.html")
set(CMAKE_CXX_FLAGS_DEBUG "-s DEMANGLE_SUPPORT=1")
set(CMAKE_CXX_FLAGS_RELEASE "-O2")

set(CMAKE_EXECUTABLE_SUFFIX ".html")
file(GLOB_RECURSE SOURCE_FILES src/*.cpp)

... {more stuff comes later}

Most important is to notice a few things. The first one is that I am originally loading a toolchain.cmake that is designed for standard gcc compiler. But later, after emsdk conanfile.py was actually compiled and configured by conan, I am resetting the toolchain to the one provided by the emscripten tool. It will reset all of the variables to the right stuff. Important I am using here version 1.37.35 of emscripten. I had issues with the latest one 1.37.36 because I think something was messed up with their toolchain file. The second important piece is that I am resetting executable_suffix to html. Without this, emsdk freaks out and cannot compile properly at the end.

/cmake/toolchain.cmake

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)

conanfile.py for emsdk

from conans import ConanFile
from conans.tools import download, untargz, replace_in_file, os_info, SystemPackageTool
from pathlib import Path
import os

class EmsdkConan(ConanFile):
    name = "emsdk"
    version = "1.37.35"
    folder = "emsdk-portable"
    url = "https://github.com/kripken/emscripten"
    description = "Emscripten SDK"
    license = "https://github.com/kripken/emscripten/blob/incoming/LICENSE"
    settings = "os", "compiler", "build_type", "arch"
    generators = "cmake"

    def source(self):
        zip_name = "emsdk-portable.tar.gz"
        download("https://s3.amazonaws.com/mozilla-games/emscripten/releases/emsdk-portable.tar.gz", zip_name)
        untargz(zip_name)
        os.unlink(zip_name)

    def build(self):
        version = "latest"
        if self.version != "latest":
            version = "sdk-{}-64bit".format(self.version)

        self.run("rm -rf {}".format(str(Path.home() / ".emscripten")))
        self.run("./emsdk install {}".format(version), cwd=self.folder)
        self.run("./emsdk activate {}".format(version), cwd=self.folder)
        self.run("./emsdk construct_env", cwd=self.folder)

        replace_in_file(
            "{}/{}/emscripten/{}/cmake/Modules/Platform/Emscripten.cmake".format(self.build_folder, self.folder, self.version),
            'set(CMAKE_AR "${EMSCRIPTEN_ROOT_PATH}/emar${EMCC_SUFFIX}" CACHE FILEPATH "Emscripten ar")',
            'set(CMAKE_AR "${EMSCRIPTEN_ROOT_PATH}/emar${EMCC_SUFFIX}")'
        )

        replace_in_file(
            "{}/{}/emscripten/{}/cmake/Modules/Platform/Emscripten.cmake".format(self.build_folder, self.folder, self.version),
            'set(CMAKE_RANLIB "${EMSCRIPTEN_ROOT_PATH}/emranlib${EMCC_SUFFIX}" CACHE FILEPATH "Emscripten ranlib")',
            'set(CMAKE_RANLIB "${EMSCRIPTEN_ROOT_PATH}/emranlib${EMCC_SUFFIX}")'
        )

    def package(self):
        pass

    def system_requirements(self):
        if os_info.is_linux:
            installer = SystemPackageTool()
            for pkg in ["build-essential", "cmake", "python2.7", "nodejs", "default-jre"]:
                installer.install(pkg)

    def package_info(self):
        cwd = Path(self.package_folder)
        root = Path(*cwd.parts[:-2]) / "build" / cwd.parts[-1]
        folder = "{}/{}".format(root, self.folder)
        emscripten_folder = "{}/emscripten/{}".format(folder, self.version)

        self.user_info.emscripten_root = emscripten_folder
        self.env_info.path.append("{}".format(folder))
        self.env_info.path.append("{}/clang/e{}_64bit".format(folder, self.version))
        self.env_info.path.append("{}/node/8.9.1_64bit/bin".format(folder))
        self.env_info.path.append(emscripten_folder)
        self.env_info.emsdk = "{}".format(folder)
        self.env_info.em_config = str(Path.home() / ".emscripten")
        self.env_info.binaryen_root = "{}/clang/e{}_64bit/binarye".format(folder, self.version)
        self.env_info.emscripten = emscripten_folder

I am not really sure if I did a great job explaining how I got it to work but if someone stumbles on this because they were trying to do this, feel free to tag me and I'll give more details.

memsharded commented 6 years ago

Hi @trivigy

Thanks very much for providing this info. It might be useful for other people.

Let me point out some possible things to consider by users reading this:

What I'd like to learn myself is how to use emscripten with conan. So a simple "hello world" library conan package that is compiled with ems, then uploaded, then re-used in a different conan package project and how the final application is run.

trivigy commented 6 years ago

@memsharded package() is empty because emsdk comes with already compiled stuff and in effect nothing needs to be moved out of the build directory. The reason it is build and not source is because there are some minor manipulations to config files that needs to be done so by that point everything is copied to the build directory. Once it is there, some emsdk commands are ran ./emsdk {command} those actually configure variables inside of the emsdk itself so if you then move things into the package directory, those configured variables will fail. After all, they were linked to the build directory. So that leaves us with only exporting the environment variables through package_info().

No the generator cmake is not actually being used. I just forgot that stuff there. For now I am leaving this stuff here as a reference but if I find some free time on my hands I will gladly make a little "hello world". Alternatively if someone else would I am happy to answer any questions thrown my way.

trivigy commented 6 years ago

Here is an example of how the emsdk is later being used by a dependent package. I am compiling here protobuf to emscripten. One weird thing to note is that I have an additional conanfile.py for the actual standard protobuf. That is because of the need for the executable. Unfortunately in em-Protobuf I remove the compilation of the executable since this will compile stuff into web-assembly and cannot run on localhost. Basically I am just grabbing protoc from the standard Protobuf conanfile.

conanfile.py

from conans import ConanFile, tools, AutoToolsBuildEnvironment
from conans.tools import os_info, SystemPackageTool

class ProtobufConan(ConanFile):
    name = "em-Protobuf"
    version = "3.5.2"
    folder = "protobuf"
    url = "https://github.com/google/protobuf"
    description = "Protocol Buffers - Google's data interchange format"
    license = "https://github.com/google/protobuf/blob/master/LICENSE"
    requires = "Protobuf/3.5.2@syncaide/stable", "emsdk/1.37.35@syncaide/stable"
    settings = "os", "compiler", "build_type", "arch"
    exports_sources = ["emscripten.patch"]
    generators = "cmake"
    packages = [
        "autoconf",
        "automake",
        "libtool",
        "curl",
        "make",
        "g++",
        "unzip"
    ]

    def source(self):
        self.run("git clone https://github.com/google/protobuf.git")
        self.run("git checkout tags/v{}".format(self.version), cwd=self.folder)
        self.run("git apply ../emscripten.patch", cwd=self.folder)

    def configure(self):
        self.options["Protobuf"].bin_only = True

    def system_requirements(self):
        if os_info.is_linux:
            installer = SystemPackageTool()
            for pkg in self.packages:
                installer.install(pkg)

    def build(self):
        protoc = self.deps_cpp_info["Protobuf"].rootpath + '/bin/protoc'

        env = AutoToolsBuildEnvironment(self)
        with tools.environment_append(env.vars):
            self.run("./autogen.sh", cwd=self.folder)

            args = ['--disable-shared', '--with-protoc={}'.format(protoc)]
            self.run("emconfigure ./configure {}".format(' '.join(args)), cwd=self.folder)
            self.run("emmake make -j {}".format(tools.cpu_count()), cwd=self.folder)

    def package(self):
        self.copy("*.h", "include", "{}/src".format(self.folder))
        self.copy("*.proto", "include", "{}/src".format(self.folder))
        self.copy("*.a", "lib", "{}/src/.libs".format(self.folder), keep_path=False)

    def package_info(self):
        self.cpp_info.libs = ["protobuf", "protobuf-lite"]
Trass3r commented 5 years ago

I think it doesn't make sense to provide something in cmake-conan for emscripten if conan itself doesn't provide so minimun tooling & docs for emscripten.

Looks like it's supported by conan now.

jfaust-fy commented 3 years ago

Emscripten is supported, and mostly works with with cmake-conan if you add Emscripten to the list of supported platforms in cmake.conan.

However, the main issue I'm running into is that cmake needs the toolchain specified on the command line, so we can't use the emscripten toolchain as part of [build_requires] as recommended by https://docs.conan.io/en/latest/integrations/cross_platform/emscripten.html.

Any ideas here? I guess I can use an external emscripten and setup all the env variables in conan_cmake_run...

jfaust-fy commented 3 years ago

This seems to work...

PROFILE "${CMAKE_CURRENT_SOURCE_DIR}/conan/emscripten.profile"
        ENV
            EMSDK="${EMSCRIPTEN_ROOT_PATH}/../../"
            EMSCRIPTEN="${EMSCRIPTEN_ROOT_PATH}"
            CONAN_CMAKE_TOOLCHAIN_FILE="${EMSCRIPTEN_ROOT_PATH}/cmake/Modules/Platform/Emscripten.cmake"
            PATH="$ENV{PATH}:${EMSCRIPTEN_ROOT_PATH}:${EMSCRIPTEN_ROOT_PATH}"
            CC="${EMSCRIPTEN_ROOT_PATH}/emcc"
            CXX="${EMSCRIPTEN_ROOT_PATH}/em++"
            RANLIB="${EMSCRIPTEN_ROOT_PATH}/ranlib"
            AR="${EMSCRIPTEN_ROOT_PATH}/ar"

when configuring with:

cmake -DCMAKE_TOOLCHAIN_FILE=$EMSCRIPTEN_ROOT_PATH/cmake/Modules/Platform/Emscripten.cmake