diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 0000000000..281f731fa0 --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,235 @@ +FROM debian:stretch AS stage_build + +# ------------------------------------------------------------------------------ + +# Supports only 1.38.40+, accpets also '-upstream' variants +ARG EMSCRIPTEN_VERSION=1.38.43 +ARG EMSDK_CHANGESET=master + +# ------------------------------------------------------------------------------ + +# NOTE: Any change of following variables should be reflected in ./entrypoint file +ENV EMSDK /emsdk +ENV EM_DATA ${EMSDK}/.data +ENV EM_CONFIG ${EMSDK}/.emscripten +ENV EM_CACHE ${EM_DATA}/cache +ENV EM_PORTS ${EM_DATA}/ports + +# ------------------------------------------------------------------------------ + +RUN echo "## Start building" \ + \ +&& echo "## Update and install packages" \ + && apt-get -qq -y update \ + && apt-get -qq install -y --no-install-recommends \ + libxml2 \ + wget \ + git-core \ + ca-certificates \ + build-essential \ + file \ + python python-pip \ + python3 python3-pip \ + \ +&& echo "## Done" + +RUN echo "## Get EMSDK" \ + && git clone https://github.com/emscripten-core/emsdk.git ${EMSDK} \ + && cd ${EMSDK} && git reset --hard ${EMSDK_CHANGESET} \ + \ + && ./emsdk.py update-tags \ +&& echo "## Done" + +RUN echo "## Install Emscripten" \ + && cd ${EMSDK} \ + && ./emsdk install ${EMSCRIPTEN_VERSION} \ + \ +&& echo "## Done" + +# This generates configuration that contains all valid paths according to installed SDK +RUN cd ${EMSDK} \ + && echo "## Generate standard configuration" \ + \ + && ./emsdk activate ${EMSCRIPTEN_VERSION} --embedded \ + && ./emsdk construct_env > /dev/null \ + && cat ${EMSDK}/emsdk_set_env.sh \ + \ + # remove wrongly created entry with EM_CACHE, variable will be picked up from ENV + && sed -i -e "/EM_CACHE/d" ${EMSDK}/emsdk_set_env.sh \ + # add a link to tools like asm2wasm in a system path + # asm2wasm (and friends might be places either in ./upstream of ./fastcomp folder, hence detection is needed) + && printf "export PATH=$(dirname $(find . -name asm2wasm -exec readlink -f {} +)):\$PATH\n" >> ${EMSDK}/emsdk_set_env.sh \ + \ +&& echo "## Done" + +# Create a structure and make mutable folders accessible for r/w +RUN cd ${EMSDK} \ + && echo "## Create .data structure" \ + && for mutable_dir in ${EM_DATA} ${EM_PORTS} ${EM_CACHE} ${EMSDK}/zips ${EMSDK}/tmp; do \ + mkdir -p ${mutable_dir}; \ + chmod -R 777 ${mutable_dir}; \ + done \ + \ +&& echo "## Done" + +# Create an entrypoint that activates Emscripten SDK and helps running this image as non-root user +COPY entrypoint ${EMSDK}/ + + +# Create symbolic links for critical Emscripten Tools +# This is important for letting people using Emscripten in Dockerfiles without activation +# As each Emscripten release is placed to a different folder (i.e. /emsdk/emscripten/tag-1.38.31) +RUN echo "## Create symbolic links" \ + && . ${EMSDK}/emsdk_set_env.sh \ + \ + && mkdir -p ${EMSDK}/llvm ${EMSDK}/emscripten ${EMSDK}/binaryen \ + \ + && ln -s $(dirname $(which node))/.. ${EMSDK}/node/current \ + && ln -s $(dirname $(which clang))/.. ${EMSDK}/llvm/clang \ + && ln -s $(dirname $(which emcc)) ${EMSDK}/emscripten/sdk \ + \ + && ln -s $(dirname $(which asm2wasm)) ${EMSDK}/binaryen/bin \ + \ + && echo "## Done" + +# Clean up emscripten installation and strip some symbols +RUN echo "## Aggresive optimization: Remove debug symbols" \ +&& apt-get -qq -y update && apt-get -qq install -y --no-install-recommends \ + binutils \ + && . ${EMSDK}/emsdk_set_env.sh \ + # Remove debugging symbols from embedded node (extra 7MB) + && strip -s `which node` \ + # Tests consume ~80MB disc space + && rm -fr ${EMSDK}/llvm/clang/emscripten/tests \ + # strip out symbols from clang (~extra 50MB disc space) + && find ${EMSDK}/llvm/clang/bin -type f -exec strip -s {} + || true \ + && find ${EMSDK}/llvm/clang/fastcomp/bin -type f -exec strip -s {} + || true \ +&& echo "## Done" + +# Populate Emscripten SDK cache with libc++, to improve further compilation times. +RUN echo "## Pre-populate cache" \ + && . ${EMSDK}/emsdk_set_env.sh \ + \ + && embuilder.py build SYSTEM \ + \ + && mkdir -p /tmp/emscripten_test \ + && cd /tmp/emscripten_test \ + \ + && printf '#include \nint main(){std::cout << "HELLO FROM DOCKER C++"< test.cpp \ + && em++ --std=c++11 test.cpp -o test.js -s WASM=0 && node test.js \ + && em++ --std=c++11 -g3 test.cpp -o test.js -s WASM=0 && node test.js \ + && em++ --std=c++11 test.cpp -o test.js -s WASM=1 && node test.js \ + \ + && cd / \ + && rm -fr /tmp/emscripten_test \ + \ + # some files were created, and we need to make sure that those can be accessed by non-root people + && chmod -R 777 ${EM_DATA} \ + \ + # cleanup + && find ${EMSDK} -name "*.pyc" -exec rm {} \; \ + \ + && echo "## Done" + +# ------------------------------------------------------------------------------ +# -------------------------------- STAGE DEPLOY -------------------------------- +# ------------------------------------------------------------------------------ + +FROM debian:buster-slim AS stage_deploy + +COPY --from=stage_build /emsdk /emsdk + +# Fallback in case Emscripten isn't activated. +# This will let use tools offered by this image inside other Docker images (sub-stages) or with custom / no entrypoint +ENV EMSDK /emsdk +ENV EMSCRIPTEN=${EMSDK}/emscripten/sdk + +ENV EM_DATA ${EMSDK}/.data +ENV EM_CONFIG ${EMSDK}/.emscripten +ENV EM_CACHE ${EM_DATA}/cache +ENV EM_PORTS ${EM_DATA}/ports + +# Fallback in case Emscripten isn't activated +# Expose Major tools to system PATH, so that emcc, node, asm2wasm etc can be used without activation +ENV PATH="${EMSDK}:${EMSDK}/emscripten/sdk:${EMSDK}/llvm/clang/bin:${EMSDK}/node/current/bin:${EMSDK}/binaryen/bin:${PATH}" + +# Use entrypoint that's coming from emscripten-slim image. It sets all required system paths and variables +ENTRYPOINT ["/emsdk/entrypoint"] + + +# ------------------------------------------------------------------------------ +# Create a 'standard` 1000:1000 user +# Thanks to that this image can be executed as non-root user and created files will not require root access level on host machine +# Please note that this solution even if widely spread (i.e. Node.js uses it) is far from perfect as user 1000:1000 might not exist on +# host machine, and in this case running any docker image will cause other random problems (mostly due `$HOME` pointing to `/`) +# This extra user works nicely with entrypoint provided in `/emsdk/entrypoint` as it detects case explained before. +RUN echo "## Create emscripten user (1000:1000)" \ + && groupadd --gid 1000 emscripten \ + && useradd --uid 1000 --gid emscripten --shell /bin/bash --create-home emscripten \ + \ +&& echo "## Done" + +# ------------------------------------------------------------------------------ + +RUN echo "## Update and install packages" \ +# mitigate problem with create symlink to man for base debian image + && mkdir -p /usr/share/man/man1/ \ + \ + && apt-get -qq -y update && apt-get -qq install -y --no-install-recommends \ + libxml2 \ + ca-certificates \ + python \ + python3 \ + python-pip \ + python3-pip \ + wget \ + curl \ + zip \ + unzip \ + git \ + ssh-client \ + build-essential \ + make \ + ant \ + libidn11 \ + cmake \ + openjdk-11-jre-headless \ + \ + # Standard Cleanup on Debian images + && apt-get -y clean \ + && apt-get -y autoclean \ + && apt-get -y autoremove \ + && rm -rf /var/lib/apt/lists/* \ + && rm -rf /var/cache/debconf/*-old \ + && rm -rf /usr/share/doc/* \ + && rm -rf /usr/share/man/?? \ + && rm -rf /usr/share/man/??_* \ +&& echo "## Done" + +# ------------------------------------------------------------------------------ +# Internal test suite of tools that this image provides +COPY test_dockerimage.sh ${EMSDK}/ + +RUN echo "## Internal Testing of image (activated)" \ + && . ${EMSDK}/emsdk_set_env.sh \ + && ${EMSDK}/test_dockerimage.sh \ + \ +&& echo "## Done" + +RUN echo "## Internal Testing of image (not-activated)" \ + && ${EMSDK}/test_dockerimage.sh \ + \ +&& echo "## Done" + +# ------------------------------------------------------------------------------ +# Copy this Dockerimage into image, so that it will be possible to recreate it later +COPY Dockerfile /emsdk/dockerfiles/emscripten-core/emsdk/ + +LABEL maintainer="kontakt@trzeci.eu" \ + org.label-schema.name="emscripten" \ + org.label-schema.description="The official container with Emscripten SDK" \ + org.label-schema.url="https://emscripten.org" \ + org.label-schema.vcs-url="https://github.com/emscripten-core/emsdk" \ + org.label-schema.docker.dockerfile="/docker/Dockerfile" + +# ------------------------------------------------------------------------------ diff --git a/docker/Makefile b/docker/Makefile new file mode 100644 index 0000000000..a3b16b18c8 --- /dev/null +++ b/docker/Makefile @@ -0,0 +1,21 @@ +#!/usr/bin/env make + +# Emscripten version to build: Should match the version that has been already released. +# i.e.: 1.38.45, 1.38.45-upstream +version = +alias = + +.TEST: +ifndef version + $(error argument 'version' is not set. Please call `make version=SOME_VERSION ...`) +endif + +build: .TEST + docker build --build-arg=EMSCRIPTEN_VERSION=${version} --tag emscripten/emscripten:${version} . + +push: .TEST + docker push emscripten/emscripten:${version} +ifdef alias + docker tag emscripten/emscripten:${version} emscripten/emscripten:${alias} + docker push emscripten/emscripten:${alias} +endif diff --git a/docker/README.md b/docker/README.md new file mode 100644 index 0000000000..d006dc0449 --- /dev/null +++ b/docker/README.md @@ -0,0 +1,135 @@ +# Dockerfile for EMSDK + +This Dockerfile builds a self-contained version of emsdk that enables emscripten to be used without any +other installation on the host system. + +It is published at https://hub.docker.com/u/emscripten/emscripten + +### Usage + +Simple usage of this container to compile a hello-world +```bash +# create helloworld.cpp +cat << EOF > helloworld.cpp +#include +int main() { + std::cout << "Hello World!" << std::endl; + return 0; +} +EOF +``` + +```bash +# compile with docker image +docker run \ + --rm \ + -v $(pwd):$(pwd) \ + -u $(id -u):$(id -g) \ + emscripten/emscripten \ + emcc helloworld.cpp -o helloworld.js + +# execute on host machine +node helloworld.js +``` + +Teardown of compilation command: + +|part|description| +|---|---| +|`docker run`| A standard command to run a command in a container| +|`--rm`|remove a container after execution (optimization)| +|`-v $(pwd):$(pwd)`|Mounting current folder from the host system into mirrored path on the container
TIP: This helps to investigate possible problem as we preserve exactly the same paths like in host. In such case modern editors (like Sublime, Atom, VS Code) let us to CTRL+Click on a problematic file | +|`-u $(id -u):$(id -g)`| Run the container as a non-root user with the same UID and GID as local user. Hence all files produced by this are accessible to non-root users| +|`emscripten/emscripten`|Get the latest tag of this container| +|`emcc helloworld.cpp -o helloworld.js`|Execute `emcc` command with following arguments inside container, effectively compile our source code| + + + +### Building Dockerfile + +This image requires to specify following build arguments: + +| arg | description | +| --- | --- | +| `EMSCRIPTEN_VERSION` | One of released version of Emscripten. For example `1.38.45`
Can be used with `-upstream` variant like: `1.38.45-upstream`
Minimal supported version is **1.38.40**| + +**Building** + +This step will build Dockerfile as given tag on local machine +```bash +# using docker +docker build \ + --build-arg=EMSCRIPTEN_VERSION=1.38.43-upstream \ + --tag emscripten/emscripten:1.38.43-upstream \ + . +``` +```bash +# using predefined make target +make version=1.38.43-upstream build +``` + +**Tagging** + +In case of using `docker build` command directly, given `--tag` should match version of released Emscripten (you can see list of non-legacy versions by executing `emsdk list`). + +**Pushing** + +This step will take local image and push to default docker registry. You need to make sure that you logged in docker cli (`docker login`) and you have rights to push to that registry. + +```bash +# using docker +docker push emscripten/emscripten:1.38.43-upstream +``` +```bash +# using predefined make target +make version=1.38.43-upstream push +``` + +In case of pushing the most recent version, this version should be also tagged as `latest` or `latest-upstream` and pushed. +```bash +# using docker cli + +# in case of fastcomp variant (default backend) +docker tag emscripten/emscripten:1.38.43 emscripten/emscripten:latest +docker push emscripten/emscripten:latest + +# in case of upstream variant +docker tag emscripten/emscripten:1.38.43-upstream emscripten/emscripten:latest-upstream +docker push emscripten/emscripten:latest-upstream + +``` + +```bash +# using predefined make target + +make version=1.38.43-upstream alias=latest-upstream push + +``` + + +### Extending + +If your project uses packages that this image doesn't provide you might want to: +* Contribute to this repo: Maybe your dependency is either non-intrusive or could be useful for other people +* Create custom image that bases on this image + +1. create own Dockerfile that holds: + ```dockerfile + # Point at any base image that you find suitable to extend. + FROM emscripten/emscripten:1.38.25 + + # Install required tools that are useful for your project i.e. ninja-build + RUN apt update && apt install -y ninja-build + + ``` +2. build it + ```shell + docker build -t extended_emscripten . + ``` + +3. test + ```shell + docker run --rm extended_emscripten ninja --version + # Python 2.7.16 + ``` + diff --git a/docker/entrypoint b/docker/entrypoint new file mode 100755 index 0000000000..8e7c702845 --- /dev/null +++ b/docker/entrypoint @@ -0,0 +1,27 @@ +#!/bin/sh + +# In case when mapped user id by `docker run -u` is not created inside docker image +# The `$HOME` variable points to `/` - which prevents any tool to write to, as it requires root access +# In such case we set `$HOME` to `/tmp` as it should r/w for everyone + +if [ "$HOME" = "/" ] ; then + export HOME=/tmp +fi + +# In case of running as root, use `umask` to reduce problem of file permission on host +if [ "$(id -g)" = "0" ] && [ "$(id -u)" = "0" ] ; +then + umask 0000 +fi + +# Export this image specific Environment variables +# Those variables are important to use dedicated folder for all cache and predefined config file +export EM_CONFIG=/emsdk/.emscripten +export EM_CACHE=/emsdk/.data/cache +export EM_PORTS=/emsdk/.data/ports + +# Activate Emscripten SDK +. ${EMSDK}/emsdk_set_env.sh + +# Evaluate a command that's coming after `docker run` / `docker exec` +"$@" diff --git a/docker/test_dockerimage.sh b/docker/test_dockerimage.sh new file mode 100755 index 0000000000..7f5cfbea75 --- /dev/null +++ b/docker/test_dockerimage.sh @@ -0,0 +1,19 @@ +#!/bin/bash +set -ex + +which asm2wasm +which llvm-ar +which emsdk +node --version +npm --version +python3 --version +pip3 --version +python --version +pip --version +em++ --version +emcc --version +java -version +cmake --version + +# cleanup after test +find ${EMSDK} -name "*.pyc" -exec rm {} \;