diff --git a/.github/scripts/.ci_base_profile b/.github/scripts/.ci_base_profile new file mode 100644 index 0000000..e9d1d47 --- /dev/null +++ b/.github/scripts/.ci_base_profile @@ -0,0 +1,6 @@ +include(default) + +[conf] +# Verbosity +tools.build:verbosity=verbose +tools.compilation:verbosity=verbose diff --git a/.github/scripts/build_recipes.py b/.github/scripts/build_recipes.py new file mode 100644 index 0000000..7ad10f4 --- /dev/null +++ b/.github/scripts/build_recipes.py @@ -0,0 +1,76 @@ +#!/usr/bin/env python3 +import os +import json +from pathlib import Path +import subprocess +import yaml +from collections import defaultdict + +BASE_PATH = Path(__file__).parent + + +def get_updated_recipes(): + updated_paths = os.getenv("BUILD_RECIPES", "").split(" ") + recipe_files = defaultdict(set) + for path in updated_paths: + parts = Path(path).parts + if len(parts) >= 2 and parts[0] == "recipes": + recipe_name = parts[1] + file = "/".join(parts[2:]) + recipe_files[recipe_name].add(file) + return set(recipe_files.keys()) + + +def load_versions(recipe_dir: Path) -> dict: + cfg = recipe_dir / "config.yml" + if not cfg.is_file(): + return {} + return yaml.safe_load(cfg.read_text() or "").get("versions", {}) + + +for recipe_name in get_updated_recipes(): + recipe_dir = Path("recipes") / recipe_name + versions = load_versions(recipe_dir) + + for version, info in versions.items(): + recipe_path = recipe_dir / info.get("folder", ".") + + inspect_json = subprocess.check_output( + ["conan", "inspect", str(recipe_path / "conanfile.py"), "--format=json"], + text=True, + ) + recipe_name = json.loads(inspect_json)["name"] + + cmd = [ + "conan", + "create", + str(recipe_path), + "--version", + version, + "--build", + "missing", + "-pr:a", + str(BASE_PATH / ".ci_base_profile"), + ] + + with open(BASE_PATH / "profiles.json", "r") as f: + profile_list = json.load(f).get(recipe_name) + + print(f"::group::{recipe_name}/{version}", flush=True) + + try: + print("executing:", " ".join(cmd), flush=True) + subprocess.run(cmd, check=True) + for profile in profile_list.get("profiles", []): + example_cmd = [ + "conan", + "test", + str(recipe_path / "test_example"), + f"{recipe_name}/{version}", + "--profile", + profile, + ] + print("executing:", " ".join(example_cmd), flush=True) + subprocess.run(example_cmd, check=True) + finally: + print("::endgroup::", flush=True) diff --git a/.github/scripts/profiles.json b/.github/scripts/profiles.json new file mode 100644 index 0000000..09498fd --- /dev/null +++ b/.github/scripts/profiles.json @@ -0,0 +1,5 @@ +{ + "emsdk": { + "profiles": ["emsdk/wasm32"] + } +} diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..f4219ee --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,51 @@ +name: Test all recipes + +on: + push: + pull_request: + branches: + - main + workflow_dispatch: + +jobs: + create: + name: Create and test Conan recipes + runs-on: ubuntu-latest + steps: + - name: Checkout sources + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.11" + + - name: Install requirements + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + + - name: Cache local Conan repo + uses: actions/cache@v3 + with: + path: ~/.conan2/p + key: ${{ runner.os }}-conan2-${{ hashFiles('recipes/**') }} + restore-keys: | + ${{ runner.os }}-conan2- + + - name: Get changed recipes + id: changed-recipes + uses: tj-actions/changed-files@ed68ef82c095e0d48ec87eccea555d944a631a4c # v46 + with: + files: | + recipes/** + files_ignore: recipes/*.{md,txt} + + - name: Build and test recipes + if: steps.changed-recipes.outputs.any_changed == 'true' + env: + BUILD_RECIPES: ${{ steps.changed-recipes.outputs.all_changed_files }} + run: | + conan profile detect + conan config install conan_config + python .github/scripts/build_recipes.py diff --git a/.github/workflows/profiles.yml b/.github/workflows/profiles.yml new file mode 100644 index 0000000..4821eb5 --- /dev/null +++ b/.github/workflows/profiles.yml @@ -0,0 +1,41 @@ +name: Manually publish profile releases + +on: + workflow_dispatch: + inputs: + recipe: + description: 'Recipe to publish profiles for' + required: true + version: + description: 'Version of the packaged profiles' + required: true + +jobs: + create: + name: Create a profile release + runs-on: ubuntu-latest + steps: + - name: Checkout sources + uses: actions/checkout@v4 + + - name: Set variables + id: vars + run: | + echo "RECIPE=${{ github.event.inputs.recipe }}" >> $GITHUB_ENV + echo "VERSION=${{ github.event.inputs.version }}" >> $GITHUB_ENV + echo "ZIP_NAME=${{ github.event.inputs.recipe }}-${{ github.event.inputs.version }}-profiles.zip" >> $GITHUB_ENV + + - name: Zip profile folder + run: | + mkdir -p dist + cd conan_config + zip -r "../dist/$ZIP_NAME" "profiles/$RECIPE" + + - name: Create GitHub release + uses: softprops/action-gh-release@v2 + with: + tag_name: ${{ env.ZIP_NAME }} + name: ${{ env.ZIP_NAME }} + files: dist/${{ env.ZIP_NAME }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/README.md b/README.md index 39b463e..8b5f4ce 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,6 @@ utilities, for example the Emscripten SDK or other compilers. These recipes are maintained independently of [Conan Center Index](https://github.com/conan-io/conan-center-index) with flexible maintenance scopes. ---- ## 🌟 Why a separate repo? @@ -18,10 +17,25 @@ tailoring recipes as needed. It also supports bleeding-edge or niche toolchains that don't belong in the main index, offering maximum flexibility and control. ---- +## 🧰 Reference Profiles + +This repository includes several **pre-configured Conan profiles** designed to make toolchain setup effortless: + +To install the respective profiles: + +```sh +$ conan config install https://github.com/conan-io/conan-toolchains.git -sf conan_config +``` + +Profiles could be later listed: +```sh +$ conan profile list +``` +Toolchain profiles will be located under `/`. +Profiles starting with dot (`.`) are considered *base* which are used for other profiles and should not be used directly. -## πŸš€ Getting started +## πŸš€ Getting started ### Setup `conan-toolchains` as a [local recipe index](https://docs.conan.io/2/devops/devops_local_recipes_index.html#devops-local-recipes-index) repository @@ -38,21 +52,14 @@ This repository is still under active development, and no Conan remote with pre- ## Contributing +We welcome and appreciate contributions to **conan-toolchains**! -If you wish to contribute to **conan-toolchains**, follow these steps to clone the repository -and install the required development dependencies. +We recommend using [pre-commit](https://pre-commit.com/) to enforce code style and formatting. To +activate the pre-commit hooks contributors could optionally run the following commands: ``` -git clone git@github.com:conan-io/conan-toolchains.git -cd conan-toolchains -# Recommended: Setup your virtual environment before installing dependencies +# Setup your virtual environment before installing dependencies pip install pre-commit -``` - -We use [pre-commit](https://pre-commit.com/) to enforce code style and formatting. To -activate the pre-commit hooks: - -``` pre-commit install ``` diff --git a/conan_config/profiles/emsdk/.base b/conan_config/profiles/emsdk/.base new file mode 100644 index 0000000..946eeee --- /dev/null +++ b/conan_config/profiles/emsdk/.base @@ -0,0 +1,23 @@ +[settings] +build_type=Release +compiler=emcc +compiler.cppstd=17 +compiler.libcxx=libc++ +# Choose between both types of multithreading support (or none) +# compiler.threads= +compiler.version=4.0.10 +os=Emscripten + +[tool_requires] +ninja/[*] +emsdk/4.0.10 + +[conf] +tools.build:exelinkflags+=['-sALLOW_MEMORY_GROWTH=1'] +tools.build:sharedlinkflags+=['-sALLOW_MEMORY_GROWTH=1'] + +# Set Ninja as default generator as it will avoid Windows issues +tools.cmake.cmaketoolchain:generator=Ninja + +# Distinguish between architectures +tools.cmake.cmake_layout:build_folder_vars=['settings.build_type', 'settings.arch'] diff --git a/conan_config/profiles/emsdk/README.md b/conan_config/profiles/emsdk/README.md new file mode 100644 index 0000000..517da6d --- /dev/null +++ b/conan_config/profiles/emsdk/README.md @@ -0,0 +1,126 @@ +# EMSDK - Emscripten Compiler Profiles for Conan + +This repository provides a collection of pre-built Conan profiles that can be +used as a reference for **cross-compiling projects to WebAssembly (WASM)**. + +πŸ“˜ For detailed information and usage examples, refer to the official [Conan +documentation](https://docs.conan.io/2/examples/cross_build/emscripten.html#setting-up-conan-profile-for-webassembly-wasm). + +πŸ’‘ Issues and suggestions are welcome via the `conan-toolchains` repository. +Contributions are encouraged! + + +## 🧭 Introduction + +As of **Conan 2.18**, the `emcc` compiler (from +[Emscripten](https://emscripten.org/docs/)) is natively supported. This allows +accurate modeling of the compiler’s built-in features. + +The current `emcc` compiler model includes the following settings: + +``` +emcc: + version: [ANY] + libcxx: [null, libstdc++, libstdc++11, libc++] + threads: [null, posix, wasm_workers] + 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] +``` + +> ℹ️ Note: `emcc` is a front-end for the `clang` compiler, so it shares many of the same settings and options. + +However, there are a few **important caveats** to consider when using Emscripten with Conan. + + +## ⚠️ Caveats + +### πŸ”„ ABI Incompatibility + +There is **no ABI compatibility guarantee** between different `emcc` versions. +Refer to the [Emscripten Changelog](https://github.com/emscripten-core/emscripten/blob/main/ChangeLog.md) for details. + +In Conan v2, the `compiler.version` setting mostly acts as **syntactic sugar**. While it doesn't influence compilation directly, it: + +- Enables Conan to apply version-specific flags, compile and link flags. +- Ensures distinct **package IDs** for different compiler versions. + +To prevent ABI issues, make sure your **Conan profile's `compiler.version`** +matches the **actual `emsdk` version** used. This ensures consistent builds and +automatic recompilation when updating `emsdk`. + + + +### 🧡 Multithreading + +The `emcc` compiler can produce incompatible binaries depending on whether threading is enabled. + +Refer to the Emscripten documentation on [pthreads](https://emscripten.org/docs/porting/pthreads.html#compiling-with-pthreads-enabled) +and [wasm workers](https://emscripten.org/docs/api_reference/wasm_workers.html). + +Emscripten supports two threading models: + +- **POSIX Threads (Pthreads)** +- **Wasm Workers** + +These are **mutually incompatible**, and this distinction must be reflected in the Conan profile to avoid mixing binaries. + +Enable threading in your profile by setting: + +```ini +compiler.threads=posix # For Pthreads +compiler.threads=wasm_workers # For Wasm Workers +``` + +Conan will automatically inject the necessary linker flags. + +## πŸ“‚ Profile Overview + +This repository includes the following profiles: +- `wasm32`: WebAssembly 32-bit target (default). +- `wasm64`: Experimental 64-bit WebAssembly target (for projects needing >4β€―GB dynamic memory). +- `local`: Use your locally installed emsdk instead of the Conan-managed one. + + +⚠️ WASM64 caveats: + +The latest node version `emsdk/4.0.10` installs is the `node/22.16.0`, which can not run directly wasm64 binaries. +Also, it is not valid to compile wasm64 with `-sMIN_NODE_VERSION=221600`, see following error: +``` +em++: warning: MIN_NODE_VERSION=221600 is not compatible with MEMORY64 (230000 or above required) [-Wcompatibility] +``` +Thus, to be able to run an `wasm64` binary you will need to download at least `node/23` manually in your system. + +πŸ› οΈ For the local profile: +- Ensure `emcc`, `em++`, `emar`, etc. are available in your `PATH`. +- Check the `[buildenv]` section in the profile. +- Optionally, provide your own `Emscripten.cmake` via the `user_toolchains` setting. +- Define the arch setting: `wasm`, `wasm64`, or the mostly deprecated `asm.js`. + + +## ▢️ Usage + +After installing the profiles (TBD), you can build your project like this: + +``` +$ conan build -pr emsdk/wasm32 +``` + +### 🧠 Dynamic Memory Allocation + +By default, WebAssembly does not allow dynamic memory growth. To enable it, you must set the following linker flag: + +`-s ALLOW_MEMORY_GROWTH=1` + +Our base profiles enable this flag by default to simplify usage. + +If you want to disable memory growth, simply remove or comment out the relevant line in the profile. + +πŸ”Ž The Conan docs explain the dynamic memory limits for each architecture. +These are also preconfigured in the `wasm32` and `wasm64` profiles and can be +customized as needed, including the `INITIAL_MEMORY` setting. + + +### πŸ™Œ Contribute + +Feel free to open issues or pull requests in the `conan-toolchains` repository. +Contributions to extend or improve these profiles are welcome! diff --git a/conan_config/profiles/emsdk/local b/conan_config/profiles/emsdk/local new file mode 100644 index 0000000..99b6580 --- /dev/null +++ b/conan_config/profiles/emsdk/local @@ -0,0 +1,17 @@ +include(./.base) + +[platform_tool_requires] +emsdk/4.0.10 + +[conf] +tools.build:compiler_executables={'c':'emcc', 'cpp':'em++'} +# Add local Emscripten toolchain +# tools.cmake.cmaketoolchain:user_toolchain=["/path/to/emsdk/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake"] + +[buildenv] +CC=emcc +CXX=em++ +AR=emar +NM=emnm +RANLIB=emranlib +STRIP=emstrip diff --git a/conan_config/profiles/emsdk/wasm32 b/conan_config/profiles/emsdk/wasm32 new file mode 100644 index 0000000..be675ee --- /dev/null +++ b/conan_config/profiles/emsdk/wasm32 @@ -0,0 +1,8 @@ +include(./.base) + +[settings] +arch=wasm + +[conf] +tools.build:exelinkflags+=['-sMAXIMUM_MEMORY=4GB', '-sINITIAL_MEMORY=64MB'] +tools.build:sharedlinkflags+=['-sMAXIMUM_MEMORY=4GB', '-sINITIAL_MEMORY=64MB'] diff --git a/conan_config/profiles/emsdk/wasm64 b/conan_config/profiles/emsdk/wasm64 new file mode 100644 index 0000000..20f2909 --- /dev/null +++ b/conan_config/profiles/emsdk/wasm64 @@ -0,0 +1,12 @@ +include(./.base) + +[settings] +arch=wasm64 + +[conf] +# In this early stage of wasm64, ALLOW_MEMORY_GROWTH is not having effect. Also it may not be the most efficient solution. +# wasm64 for now needs to declare INITIAL_MEMORY as the maximum memory +tools.build:exelinkflags+=['-sMAXIMUM_MEMORY=16GB', '-sINITIAL_MEMORY=16GB', '-sASSERTIONS'] +tools.build:sharedlinkflags+=['-sMAXIMUM_MEMORY=16GB', '-sINITIAL_MEMORY=16GB', '-sASSERTIONS'] + +# Node version from emsdk/4.0.10 can not run a wasm64 binary. See more details in README diff --git a/pyproject.toml b/pyproject.toml index 1f4380e..1c05400 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.ruff] output-format = "full" -src = ["recipes"] +src = ["recipes", ".github/scripts"] fix = true show-fixes = true line-length = 120 diff --git a/recipes/emsdk/all/conandata.yml b/recipes/emsdk/all/conandata.yml new file mode 100644 index 0000000..6b61e80 --- /dev/null +++ b/recipes/emsdk/all/conandata.yml @@ -0,0 +1,4 @@ +sources: + "4.0.10": + url: "https://github.com/emscripten-core/emsdk/archive/4.0.10.tar.gz" + sha256: "2497b55ddbba9bf9be2d18cfca3e973d40a0cfaa8d18f6caacb882a65b2faf1c" diff --git a/recipes/emsdk/all/conanfile.py b/recipes/emsdk/all/conanfile.py new file mode 100644 index 0000000..d5f5119 --- /dev/null +++ b/recipes/emsdk/all/conanfile.py @@ -0,0 +1,135 @@ +import os +from pathlib import Path + +from conan import ConanFile +from conan.tools.build import cross_building +from conan.tools.env import Environment, VirtualBuildEnv +from conan.tools.files import chdir, copy, get +from conan.tools.layout import basic_layout + +required_conan_version = ">=2.1" + + +class EmSDKConan(ConanFile): + name = "emsdk" + description = "Emscripten SDK. Emscripten is an Open Source LLVM to JavaScript compiler" + url = "https://github.com/conan-io/conan-center-index" + homepage = "https://github.com/kripken/emscripten" + topics = ("emsdk", "emscripten", "sdk", "emcc", "em++", "nodejs") + license = "MIT" + package_type = "application" + settings = "os", "arch" + upload_policy = "skip" + build_policy = "missing" + + def layout(self): + basic_layout(self, src_folder="src") + + def source(self): + get(self, **self.conan_data["sources"][self.version], destination=self.source_folder, strip_root=True) + + @property + def _relative_paths(self): + return ["bin", os.path.join("bin", "upstream", "emscripten")] + + @property + def _paths(self): + return [os.path.join(self.package_folder, path) for path in self._relative_paths] + + @property + def _emsdk(self): + return os.path.join(self.package_folder, "bin") + + @property + def _emscripten(self): + return os.path.join(self.package_folder, "bin", "upstream", "emscripten") + + @property + def _em_config(self): + return os.path.join(self.package_folder, "bin", ".emscripten") + + @property + def _em_cache(self): + return os.path.join(self.package_folder, "bin", ".emscripten_cache") + + @property + def _node_path(self): + subfolders = [path for path in (Path(self.package_folder) / "bin" / "node").iterdir() if path.is_dir()] + if len(subfolders) != 1: + return None + return os.path.join("bin", "node", subfolders[0].name, "bin") + + def generate(self): + env = Environment() + env.prepend_path("PATH", self._paths) + env.define_path("EMSDK", self._emsdk) + env.define_path("EMSCRIPTEN", self._emscripten) + env.define_path("EM_CONFIG", self._em_config) + env.define_path("EM_CACHE", self._em_cache) + env.vars(self, scope="emsdk").save_script("emsdk_env_file") + + # To avoid issues when cross-compiling or with not common arch in profiles we need to set EMSDK_ARCH + # This is important for the emsdk install command + env = VirtualBuildEnv(self) + # Special consideration for armv8 as emsdk expects "arm64" + arch = "arm64" if str(self.settings.arch) == "armv8" else str(self.settings.arch) + env.environment().define("EMSDK_ARCH", arch) + env.generate() + + def build(self): + with chdir(self, self.source_folder): + emsdk = "emsdk.bat" if self.settings_build.os == "Windows" else "./emsdk" + self.run(f"{emsdk} install latest") + self.run(f"{emsdk} activate latest") + + def package(self): + copy(self, "LICENSE", src=self.source_folder, dst=os.path.join(self.package_folder, "licenses")) + copy(self, "*", src=self.source_folder, dst=os.path.join(self.package_folder, "bin")) + if not cross_building(self): + self.run("embuilder build MINIMAL", env=["conanemsdk", "conanrun"]) # force cache population + # Avoid cache failures in case this package is uploaded as paths in sanity.txt are absolute + os.remove(os.path.join(self._em_cache, "sanity.txt")) + + def _define_tool_var(self, value): + suffix = ".bat" if self.settings.os == "Windows" else "" + path = os.path.join(self._emscripten, f"{value}{suffix}") + return path + + def package_info(self): + self.cpp_info.bindirs = self._relative_paths + [self._node_path] + self.cpp_info.includedirs = [] + self.cpp_info.libdirs = [] + self.cpp_info.resdirs = [] + + # If we are not building for Emscripten, probably we don't want to inject following environment variables, + # but it might be legit use cases... until we find them, let's be conservative. + if not hasattr(self, "settings_target") or self.settings_target is None: + return + + if self.settings_target.os != "Emscripten": + self.output.warning( + f"You've added {self.name}/{self.version} as a build requirement, while os={self.settings_target.os} != Emscripten" + ) + return + + toolchain = os.path.join( + self.package_folder, "bin", "upstream", "emscripten", "cmake", "Modules", "Platform", "Emscripten.cmake" + ) + self.conf_info.prepend("tools.cmake.cmaketoolchain:user_toolchain", toolchain) + + self.buildenv_info.define_path("EMSDK", self._emsdk) + self.buildenv_info.define_path("EMSCRIPTEN", self._emscripten) + self.buildenv_info.define_path("EM_CONFIG", self._em_config) + self.buildenv_info.define_path("EM_CACHE", self._em_cache) + + compiler_executables = { + "c": self._define_tool_var("emcc"), + "cpp": self._define_tool_var("em++"), + } + self.conf_info.update("tools.build:compiler_executables", compiler_executables) + self.buildenv_info.define_path("CC", compiler_executables["c"]) + self.buildenv_info.define_path("CXX", compiler_executables["cpp"]) + self.buildenv_info.define_path("AR", self._define_tool_var("emar")) + self.buildenv_info.define_path("NM", self._define_tool_var("emnm")) + self.buildenv_info.define_path("RANLIB", self._define_tool_var("emranlib")) + self.buildenv_info.define_path("STRIP", self._define_tool_var("emstrip")) diff --git a/recipes/emsdk/all/test_example/CMakeLists.txt b/recipes/emsdk/all/test_example/CMakeLists.txt new file mode 100644 index 0000000..adff224 --- /dev/null +++ b/recipes/emsdk/all/test_example/CMakeLists.txt @@ -0,0 +1,4 @@ +cmake_minimum_required(VERSION 3.15) +project(test_example CXX) + +add_executable(test_example main.cpp) diff --git a/recipes/emsdk/all/test_example/conanfile.py b/recipes/emsdk/all/test_example/conanfile.py new file mode 100644 index 0000000..c00605e --- /dev/null +++ b/recipes/emsdk/all/test_example/conanfile.py @@ -0,0 +1,28 @@ +import os +from conan import ConanFile +from conan.tools.cmake import CMakeToolchain, CMake, cmake_layout, CMakeDeps + + +class EmsdkExampleRecipe(ConanFile): + name = "emsdk-example" + version = "1.0" + package_type = "application" + settings = "os", "compiler", "build_type", "arch" + + def layout(self): + cmake_layout(self) + + def generate(self): + deps = CMakeDeps(self) + deps.generate() + tc = CMakeToolchain(self) + tc.generate() + + def build(self): + cmake = CMake(self) + cmake.configure() + cmake.build() + + def test(self): + bin_path = os.path.join(self.cpp.build.bindir, "test_example") + self.run(f"node {bin_path}", env="conanbuild") diff --git a/recipes/emsdk/all/test_example/main.cpp b/recipes/emsdk/all/test_example/main.cpp new file mode 100644 index 0000000..4576fd8 --- /dev/null +++ b/recipes/emsdk/all/test_example/main.cpp @@ -0,0 +1,6 @@ +#include + +int main() { + std::cout << "Hello from emscripten!\n"; + return 0; +} diff --git a/recipes/emsdk/all/test_package/conanfile.py b/recipes/emsdk/all/test_package/conanfile.py new file mode 100644 index 0000000..a0948be --- /dev/null +++ b/recipes/emsdk/all/test_package/conanfile.py @@ -0,0 +1,16 @@ +from conan import ConanFile +from conan.tools.build import can_run + + +class TestPackageConan(ConanFile): + settings = "os", "arch", "compiler", "build_type" + + def requirements(self): + self.requires(self.tested_reference_str) + + def test(self): + # Check the package provides working binaries + if can_run(self): + self.run("emcc -v", env="conanrun") + self.run("em++ -v", env="conanrun") + self.run("node -v", env="conanrun") diff --git a/recipes/emsdk/config.yml b/recipes/emsdk/config.yml new file mode 100644 index 0000000..b53578f --- /dev/null +++ b/recipes/emsdk/config.yml @@ -0,0 +1,3 @@ +versions: + "4.0.10": + folder: all diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..e8138e9 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +conan>=2.18 +PyYAML