diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..d26a5bc --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,25 @@ +{ + "image": "mcr.microsoft.com/devcontainers/javascript-node:1-18-bullseye", + "customizations": { + "vscode": { + "settings": { + "json.schemas": [ + { + "fileMatch": [ + "*/devcontainer-feature.json" + ], + "url": "https://raw.githubusercontent.com/devcontainers/spec/main/schemas/devContainerFeature.schema.json" + } + ] + }, + "extensions": [ + "mads-hartmann.bash-ide-vscode" + ] + } + }, + "features": { + "ghcr.io/devcontainers/features/docker-in-docker:2": {} + }, + "remoteUser": "node", + "updateContentCommand": "npm install -g @devcontainers/cli" +} diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml new file mode 100644 index 0000000..9ba1e62 --- /dev/null +++ b/.github/workflows/release.yaml @@ -0,0 +1,47 @@ +name: "Release dev container features & Generate Documentation" +on: + workflow_dispatch: + +jobs: + deploy: + if: ${{ github.ref == 'refs/heads/main' }} + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + packages: write + steps: + - uses: actions/checkout@v3 + + - name: "Publish Features" + uses: devcontainers/action@v1 + with: + publish-features: "true" + base-path-to-features: "./src" + generate-docs: "true" + + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Create PR for Documentation + id: push_image_info + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + set -e + echo "Start." + # Configure git and Push updates + git config --global user.email github-actions[bot]@users.noreply.github.com + git config --global user.name github-actions[bot] + git config pull.rebase false + branch=automated-documentation-update-$GITHUB_RUN_ID + git checkout -b $branch + message='Automated documentation update' + # Add / update and commit + git add */**/README.md + git commit -m 'Automated documentation update [skip ci]' || export NO_UPDATES=true + # Push + if [ "$NO_UPDATES" != "true" ] ; then + git push origin "$branch" + gh pr create --title "$message" --body "$message" + fi diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml new file mode 100644 index 0000000..59339d1 --- /dev/null +++ b/.github/workflows/test.yaml @@ -0,0 +1,56 @@ +name: "CI - Test Features" +on: + push: + branches: + - main + pull_request: + workflow_dispatch: + +jobs: + test-autogenerated: + runs-on: ubuntu-latest + continue-on-error: true + strategy: + matrix: + features: + - matlab + baseImage: + - debian:latest + - ubuntu:latest + - mcr.microsoft.com/devcontainers/base:ubuntu + steps: + - uses: actions/checkout@v3 + + - name: "Install latest devcontainer CLI" + run: npm install -g @devcontainers/cli + + - name: "Generating tests for '${{ matrix.features }}' against '${{ matrix.baseImage }}'" + run: devcontainer features test --skip-scenarios -f ${{ matrix.features }} -i ${{ matrix.baseImage }} . + + test-scenarios: + runs-on: ubuntu-latest + continue-on-error: true + strategy: + matrix: + features: + - matlab + steps: + - uses: actions/checkout@v3 + + - name: "Install latest devcontainer CLI" + run: npm install -g @devcontainers/cli + + - name: "Generating tests for '${{ matrix.features }}' scenarios" + run: devcontainer features test -f ${{ matrix.features }} --skip-autogenerated --skip-duplicated . + + test-global: + runs-on: ubuntu-latest + continue-on-error: true + steps: + - uses: actions/checkout@v3 + + - name: "Install latest devcontainer CLI" + run: npm install -g @devcontainers/cli + + - name: "Testing global scenarios" + run: devcontainer features test --global-scenarios-only . diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml new file mode 100644 index 0000000..5dcc21b --- /dev/null +++ b/.github/workflows/validate.yml @@ -0,0 +1,16 @@ +name: "Validate devcontainer-feature.json files" +on: + workflow_dispatch: + pull_request: + +jobs: + validate: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: "Validate devcontainer-feature.json files" + uses: devcontainers/action@v1 + with: + validate-only: "true" + base-path-to-features: "./src" diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..5cdec65 --- /dev/null +++ b/LICENSE @@ -0,0 +1,16 @@ +MATHWORKS CLOUD REFERENCE ARCHITECTURE LICENSE + +The files in this GitHub repository refer to commercial software products and services, virtual machine images, and related materials of The MathWorks, Inc. (“MathWorks Programs”). MathWorks Programs are separately licensed under the MathWorks Software License Agreement, available in the desktop installation of the MathWorks Programs or in the virtual machine image. The files in this GitHub repository may also refer to third-party software licensed under separate terms provided by such third parties. + +The following license terms apply only to the files in this GitHub repository, including files in this folder and its subfolders, and do not apply to MathWorks Programs. References to “software” and “code” in the following license terms refer to the files in this GitHub repository. + +Copyright (c) 2024, The MathWorks, Inc. + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. +In all cases, the software is, and all modifications and derivatives of the software shall be, licensed to you solely for use in conjunction with MathWorks products and service offerings. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..631e9c5 --- /dev/null +++ b/README.md @@ -0,0 +1,64 @@ +# MATLAB Feature for Development Containers + + +Use the [MATLAB Feature](./src/matlab/README.md) in this repository to add MATLAB®, Simulink®, and other MathWorks™ products to your development containers. + +For more information about running MATLAB in dev containers, see +[Run MATLAB in GitHub™ Codespaces](https://github.com/mathworks-ref-arch/matlab-codespaces). + + +## Get Started + +A development container [Feature (GitHub)](https://github.com/devcontainers/features/) is self-contained code you can use to add functionality to your development container. You can add a feature to your development container by modifying `devcontainer.json`, the configuration file of the container. For instructions on creating a development container and adding a feature, see [Create a Dev Container (VS Code Docs)](https://code.visualstudio.com/docs/devcontainers/create-dev-container). + + +Use the [MATLAB Feature](./src/matlab/README.md) to: + +* Install the [system dependencies](https://github.com/mathworks-ref-arch/container-images/tree/main/matlab-deps) required to run MATLAB and other MathWorks products. +* Install MATLAB and other MathWorks products using [MATLAB Package Manager](https://github.com/mathworks-ref-arch/matlab-dockerfile/blob/main/MPM.md). +* Add MATLAB to the system PATH. +* Install Python packages such as the [MATLAB Engine for Python](https://github.com/mathworks/matlab-engine-for-python), [MATLAB Proxy](https://github.com/mathworks/matlab-proxy), and [MATLAB Integration for Jupyter](https://github.com/mathworks/jupyter-matlab-proxy). + +### Usage + +To use the MATLAB Feature, include it in your dev container by specifying the `devcontainer.json` configuration file with your desired MATLAB Feature [Options](./src/matlab/README.md#options). + +For example, to install MATLAB `R2024a` with Symbolic Math Toolbox in a `ubuntu` base image, use this `devcontainer.json` configuration: + +```json +{ + "image": "ubuntu:latest", + "features": { + "ghcr.io/mathworks/devcontainer-features/matlab:0": { + "release": "r2024a", + "products": "MATLAB Symbolic_Math_Toolbox" + } + } +} +``` +This configuration installs MATLAB R2024a in your dev container and adds the `matlab` executable to your PATH. + + +## Related Links + +Codespaces: +* [Run MATLAB in GitHub Codespaces (GitHub)](https://github.com/mathworks-ref-arch/matlab-codespaces) +* [Overview of Codespaces (GitHub)](https://docs.github.com/en/codespaces/overview) + +Dev Containers: +* [Create a Dev Container (VS Code Docs)](https://code.visualstudio.com/docs/devcontainers/create-dev-container) +* [Dev Container Metadata Reference](https://containers.dev/implementors/json_reference/) + +Dev Container Features: +* [Dev Container Features (GitHub)](https://github.com/devcontainers/features/) +* [Dev Container Features Specification](https://containers.dev/implementors/features/) +* [Dev Container Feature JSON Properties](https://containers.dev/implementors/features/#devcontainer-json-properties) + + + + +---- + +Copyright 2024 The MathWorks, Inc. + +---- \ No newline at end of file diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..986ee9e --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,4 @@ +# Reporting Security Vulnerabilities + +If you believe you have discovered a security vulnerability, please report it to +[security@mathworks.com](mailto:security@mathworks.com). For more information, see [MathWorks Vulnerability Disclosure Policy for Security Researchers](https://www.mathworks.com/company/aboutus/policies_statements/vulnerability-disclosure-policy.html). \ No newline at end of file diff --git a/src/matlab/README.md b/src/matlab/README.md new file mode 100644 index 0000000..56c10bb --- /dev/null +++ b/src/matlab/README.md @@ -0,0 +1,40 @@ + +# MATLAB (matlab) + +Installs MATLAB, supporting packages and tools. + +## Example Usage + +```json +"features": { + "ghcr.io/mathworks/devcontainer-features/matlab:0": {} +} +``` + +## Options + +| Options Id | Description | Type | Default Value | +|-----|-----|-----|-----| +| release | MATLAB Release to install. | string | r2024a | +| products | Products to install, specified as a list of product names separated by spaces.
See [MPM.md](https://github.com/mathworks-ref-arch/matlab-dockerfile/blob/main/MPM.md#product-installation-options) for more information on product specification and availability. | string | MATLAB | +| doc | Flag to install documentation and examples. (R2022b and earlier releases) | boolean | false | +| installGpu | Skips installation of GPU libraries when you install Parallel Computing Toolbox. (R2023a and later releases) | boolean | false | +| destination | Full path to the installation destination folder. | string | /opt/matlab/$RELEASE | +| installMatlabProxy | Installs matlab-proxy and its dependencies. (R2020b and later releases) | boolean | false | +| installJupyterMatlabProxy | Installs jupyter-matlab-proxy and its dependencies. (R2020b and later releases) | boolean | false | +| installMatlabEngineForPython | Installs the MATLAB Engine for Python if the destination option is set correctly. | boolean | false | +| startInDesktop | Starts matlab-proxy on container start. | string | false | +| networkLicenseManager | MATLAB will use the specified Network License Manager. | string | - | +| skipMATLABInstall | Set to true if you dont want to install MATLAB. Useful if you only want to install the proxy products. | boolean | false | + +## Customizations + +### VS Code Extensions + +- `MathWorks.language-matlab` + + + +--- + +_Note: This file was auto-generated from the [devcontainer-feature.json](https://github.com/mathworks/devcontainer-features/blob/main/src/matlab/devcontainer-feature.json). Add additional notes to a `NOTES.md`._ diff --git a/src/matlab/devcontainer-feature.json b/src/matlab/devcontainer-feature.json new file mode 100644 index 0000000..f3911e5 --- /dev/null +++ b/src/matlab/devcontainer-feature.json @@ -0,0 +1,107 @@ +{ + "name": "MATLAB", + "id": "matlab", + "version": "0.0.7", + "description": "Installs MATLAB with supporting packages and tools.", + "documentationURL": "https://github.com/mathworks/devcontainer-features", + "options": { + "release": { + "type": "string", + "proposals": [ + "r2024a", + "r2023b", + "r2023a", + "r2022b", + "r2022a", + "r2021b", + "r2021a", + "r2020b", + "r2020a", + "r2019b", + "r2019a" + ], + "default": "r2024a", + "description": "MATLAB Release to install." + }, + "products": { + "type": "string", + "proposals": [ + "MATLAB", + "MATLAB Simulink Signal_Processing_Toolbox" + ], + "default": "MATLAB", + "description": "Products to install, specified as a list of space-separated product names.
For details of products, see [MATLAB Package Manager](https://github.com/mathworks-ref-arch/matlab-dockerfile/blob/main/MPM.md#product-installation-options)." + }, + "doc": { + "type": "boolean", + "default": false, + "description": "Flag to install documentation and examples (R2022b and earlier releases)." + }, + "installGpu": { + "type": "boolean", + "default": false, + "description": "Skips installation of GPU libraries when you install Parallel Computing Toolbox (R2023a and later releases)." + }, + "destination": { + "type": "string", + "default": "/opt/matlab/$RELEASE", + "description": "Full path to the installation destination folder." + }, + "installMatlabProxy": { + "default": false, + "description": "Installs matlab-proxy and its dependencies (R2020b and later releases).", + "type": "boolean" + }, + "installJupyterMatlabProxy": { + "default": false, + "description": "Installs jupyter-matlab-proxy and its dependencies (R2020b and later releases).", + "type": "boolean" + }, + "installMatlabEngineForPython": { + "default": false, + "description": "Installs the MATLAB Engine for Python if the destination option is set correctly.", + "type": "boolean" + }, + "startInDesktop": { + "default": "false", + "description": "Starts matlab-proxy when container starts.", + "type": "string", + "enum": [ + "true", + "false", + "test" + ] + }, + "networkLicenseManager": { + "type": "string", + "proposals": [ + "port@hostname" + ], + "default": "", + "description": "MATLAB will use the specified Network License Manager." + }, + "skipMATLABInstall": { + "type": "boolean", + "default": false, + "description": "Set to true if you do not want to install MATLAB, for example if you only want to install `matlab-proxy` or `jupyter-matlab-proxy`." + } + }, + "postStartCommand": "( ls ~/.startmatlabdesktop >> /dev/null 2>&1 && env MWI_APP_PORT=8888 matlab-proxy-app 2>/dev/null ) || echo 'Will not start matlab-proxy-app...'", + "customizations": { + "vscode": { + "extensions": [ + "MathWorks.language-matlab" + ], + "settings": { + "MATLAB.indexWorkspace": true, + "MATLAB.installPath": "/usr/local/bin/matlab", + "MATLAB.matlabConnectionTiming": "never", + "MATLAB.telemetry": true + } + } + }, + "installsAfter": [ + "ghcr.io/devcontainers/features/common-utils", + "ghcr.io/devcontainers/features/python" + ] +} \ No newline at end of file diff --git a/src/matlab/install-helper-functions.sh b/src/matlab/install-helper-functions.sh new file mode 100755 index 0000000..b37dd22 --- /dev/null +++ b/src/matlab/install-helper-functions.sh @@ -0,0 +1,312 @@ +#!/usr/bin/env bash +#------------------------------------------------------------------------------------------------------------- +# Copyright 2024 The MathWorks, Inc. +#------------------------------------------------------------------------------------------------------------- +# Helpers functions to encapsulate OS specific installation + +set -eu -o pipefail + +# Global variable to store the last error message. +LAST_ERR="" + +function _ihf_print_installation_status_on_exit() { + if [ $? -eq 0 ]; then + printf "Done!\n" + else + printf "$LAST_ERR \nFailed to install for MATLAB ${MATLAB_RELEASE:-undefined} on ${PRETTY_NAME:-undefined}.\nTo debug, call script with SHELLOPTS=xtrace \n" + fi +} + +trap _ihf_print_installation_status_on_exit EXIT + +function ihf_print_and_exit() { + LAST_ERR="$1" + printf "$LAST_ERR, exiting...\n" + exit 1 +} + +function ihf_get_matlab_deps_os() { + local LINUX_DISTRO=$(ihf_is_debian_or_rhel) + local MATLAB_DEPS_OS_VERSION="undefined" + # get os-release variables + . /etc/os-release + + case ${LINUX_DISTRO} in + debian) + if [[ "${ID}" == "ubuntu" ]]; then + local SUPPORTED_VERSION_CODENAME="focal jammy" + MATLAB_DEPS_OS_VERSION=${ID}${VERSION_ID} + elif [[ "${ID}" == "debian" ]]; then + local SUPPORTED_VERSION_CODENAME="bullseye bookworm" + local UBUNTU_VERSION_ID=${VERSION_ID/11/20.04} + UBUNTU_VERSION_ID=${UBUNTU_VERSION_ID/12/22.04} + MATLAB_DEPS_OS_VERSION=ubuntu${UBUNTU_VERSION_ID} + fi + ;; + rhel) + if [[ "${ID}" == "rhel" ]]; then + # This link lists the Fedora base image that a particular RHEL version depends on: + # https://docs.fedoraproject.org/en-US/quick-docs/fedora-and-red-hat-enterprise-linux/ + # UBI 9 -> Fedora 34 + # UBI 8 -> Fedora 28 + # The End of life for these distributions can be found here: + # https://endoflife.date/rhel + # https://endoflife.date/fedora + local SUPPORTED_MAJOR_VERSION="8 9" + local MATLAB_DEPS_OS_VERSION="ubi" + local MAJOR_VERSION_ID=$(echo $VERSION_ID | cut -d '.' -f 1) + local MAJOR_VERSION_ID_REGEX="\<${MAJOR_VERSION_ID}\>" + if [[ ${SUPPORTED_MAJOR_VERSION[@]} =~ ${MAJOR_VERSION_ID_REGEX} ]]; then + MATLAB_DEPS_OS_VERSION=${MATLAB_DEPS_OS_VERSION}${MAJOR_VERSION_ID} + else + ihf_print_and_exit "Un-supported version ${MAJOR_VERSION_ID}" + fi + + elif [[ "${ID}" == "fedora" ]]; then + # Assuming UBI 9 as fedora 28 was at end of life in 2018. + MATLAB_DEPS_OS_VERSION="ubi9" + else + ihf_print_and_exit "Unsupported OS ${ID}" + fi + ;; + esac + + #return value + echo $MATLAB_DEPS_OS_VERSION + +} + +function _ihf_get_additional_repos (){ + . /etc/os-release + + local _DISTRO=$(ihf_is_debian_or_rhel) + # To find some devel packages, some rhel need to enable specific extra repos, but not on RedHat ubi images... + local INSTALL_CMD_ADDL_REPOS="" + if [ ${_DISTRO} = "rhel" ] && [ ${ID} != "rhel" ]; then + local MAJOR_VERSION_ID=$(echo $VERSION_ID | cut -d '.' -f 1) + if [ ${MAJOR_VERSION_ID} = "8" ]; then + INSTALL_CMD_ADDL_REPOS="--enablerepo powertools" + elif [ ${MAJOR_VERSION_ID} = "9" ]; then + INSTALL_CMD_ADDL_REPOS="--enablerepo crb" + fi + fi + + echo $INSTALL_CMD_ADDL_REPOS +} + + +function ihf_get_pkg_mgr_cmd() { + local PKG_MGR_CMD="" + if type apt-get >/dev/null 2>&1; then + PKG_MGR_CMD=apt-get + elif type microdnf >/dev/null 2>&1; then + PKG_MGR_CMD=microdnf + elif type dnf >/dev/null 2>&1; then + PKG_MGR_CMD=dnf + else + PKG_MGR_CMD=yum + fi + + echo $PKG_MGR_CMD +} + +function ihf_get_install_cmd() { + local PKG_MGR_CMD=$(ihf_get_pkg_mgr_cmd) + local INSTALL_CMD="" + if type apt-get >/dev/null 2>&1; then + INSTALL_CMD="${PKG_MGR_CMD} -y install --no-install-recommends" + elif type microdnf >/dev/null 2>&1; then + INSTALL_CMD="${PKG_MGR_CMD} $(_ihf_get_additional_repos) -y install --refresh --best --nodocs --noplugins --setopt=install_weak_deps=0" + elif type dnf >/dev/null 2>&1; then + INSTALL_CMD="${PKG_MGR_CMD} $(_ihf_get_additional_repos) -y install --refresh --best --nodocs --noplugins --setopt=install_weak_deps=0" + else + INSTALL_CMD="${PKG_MGR_CMD} $(_ihf_get_additional_repos) -y install --disableplugin=subscription-manager --noplugins --setopt=install_weak_deps=0" + fi + + echo $INSTALL_CMD +} + +function ihf_get_remove_cmd() { + local PKG_MGR_CMD=$(ihf_get_pkg_mgr_cmd) + local REMOVE_CMD="${PKG_MGR_CMD} -y remove" + + echo $REMOVE_CMD +} + +# returns "true/false" string if MATLAB_RELEASE is valid +function ihf_is_valid_matlab_release() { + # List of supported MATLAB_RELEASE values + local _SUPPORTED_MATLAB_RELEASES=("r2024a" "r2023b" "r2023a" "r2022b" "r2022a" "r2021b" "r2021a" "r2020b" "r2020a" "r2019b" "r2019a") + + ## Validate MATLAB_RELEASE + if [ -z "$MATLAB_RELEASE" ]; then + echo "false" + else + # checking if valid matlab release configuration + local _RELEASE_REGEX="\<${MATLAB_RELEASE}\>" + if [[ ${_SUPPORTED_MATLAB_RELEASES[@]} =~ ${_RELEASE_REGEX} ]]; then + echo "true" + else + echo "false" + fi + fi +} + +function ihf_is_debian_or_rhel() { + . /etc/os-release + + if [ "${ID}" = "debian" ] || [ "${ID_LIKE}" = "debian" ]; then + echo "debian" + elif [[ "${ID}" = "rhel" || "${ID}" = "fedora" || "${ID}" = "mariner" || "${ID_LIKE}" = *"rhel"* || "${ID_LIKE}" = *"fedora"* || "${ID_LIKE}" = *"mariner"* ]]; then + echo "rhel" + else + ihf_print_and_exit "Linux distro ${ID} not supported." + fi +} + +function ihf_clean_up() { + local LINUX_DISTRO=$(ihf_is_debian_or_rhel) + case ${LINUX_DISTRO} in + debian) + rm -rf /var/lib/apt/lists/* + apt-get clean && apt-get autoremove + ;; + rhel) + rm -rf /var/cache/dnf/* /var/cache/yum/* + rm -rf /tmp/yum.log + yum --disableplugin=subscription-manager clean all -y + ;; + esac +} + +ihf_updaterc() { + local _bashrc + local _zshrc + if [ "${UPDATE_RC}" = "true" ]; then + case $ADJUSTED_ID in + debian) + echo "Updating /etc/bash.bashrc and /etc/zsh/zshrc..." + _bashrc=/etc/bash.bashrc + _zshrc=/etc/zsh/zshrc + ;; + rhel) + echo "Updating /etc/bashrc and /etc/zshrc..." + _bashrc=/etc/bashrc + _zshrc=/etc/zshrc + ;; + esac + if [[ "$(cat ${_bashrc})" != *"$1"* ]]; then + echo -e "$1" >>${_bashrc} + fi + if [ -f "${_zshrc}" ] && [[ "$(cat ${_zshrc})" != *"$1"* ]]; then + echo -e "$1" >>${_zshrc} + fi + fi +} + +function ihf_pkg_mgr_update() { + local LINUX_DISTRO=$(ihf_is_debian_or_rhel) + local PKG_MGR_CMD=$(ihf_get_pkg_mgr_cmd) + echo "UPDATING using $PKG_MGR_CMD" + case $LINUX_DISTRO in + debian) + echo "Running apt-get update..." + ${PKG_MGR_CMD} update -y + ;; + rhel) + if [ ${PKG_MGR_CMD} = "microdnf" ]; then + if [ "$(ls /var/cache/yum/* 2>/dev/null | wc -l)" = 0 ]; then + echo "Running ${PKG_MGR_CMD} makecache ..." + ${PKG_MGR_CMD} makecache + fi + else + if [ "$(ls /var/cache/${PKG_MGR_CMD}/* 2>/dev/null | wc -l)" = 0 ]; then + echo "Running ${PKG_MGR_CMD} check-update ..." + set +e + ${PKG_MGR_CMD} check-update + rc=$? + if [ $rc != 0 ] && [ $rc != 100 ]; then + exit 1 + fi + set -e + fi + fi + ;; + esac +} + +# Checks if packages are installed and installs them if not +function ihf_install_packages() { + + local LINUX_DISTRO=$(ihf_is_debian_or_rhel) + local INSTALL_CMD=$(ihf_get_install_cmd) + echo "install command: $INSTALL_CMD" + echo "INSTALLING $@" + case ${LINUX_DISTRO} in + debian) + if ! dpkg -s "$@" >/dev/null 2>&1; then + ihf_pkg_mgr_update && \ + ${INSTALL_CMD} $@ + fi + ;; + rhel) + if ! rpm -q "$@" >/dev/null 2>&1; then + ihf_pkg_mgr_update + ${INSTALL_CMD} $@ + fi + ;; + esac +} + +function ihf_remove_packages() { + + local LINUX_DISTRO=$(ihf_is_debian_or_rhel) + local REMOVE_CMD=$(ihf_get_remove_cmd) + echo "remove command: $REMOVE_CMD" + echo "REMOVING: $@" + case ${LINUX_DISTRO} in + debian) + if dpkg -s "$@" >/dev/null 2>&1; then + ${REMOVE_CMD} $@ + fi + ;; + rhel) + if rpm -q "$@" >/dev/null 2>&1; then + ${REMOVE_CMD} $@ + fi + ;; + esac +} + +function test_script() { + # Only run tests if this script is called directly + # These will not run if this script is sourced from another file + if [ $(basename "$0") == "install-helper-functions.sh" ]; then + echo "=============Starting test" + echo "me=$(basename "$0")" + + echo "testing update" + ihf_pkg_mgr_update + + + MATLAB_RELEASE=r2024a + is_release_valid=$(ihf_is_valid_matlab_release) + echo "Is $MATLAB_RELEASE valid? Ans: $is_release_valid" + + MATLAB_RELEASE=r2023bd + echo "Is $MATLAB_RELEASE valid? Ans: $(ihf_is_valid_matlab_release)" + + LINUX_DISTRO=$(ihf_is_debian_or_rhel) + echo "LINUX_DISTRO: $LINUX_DISTRO" + + matlab_deps_os=$(ihf_get_matlab_deps_os) + echo "The MATLAB deps to install is: $matlab_deps_os" + + install_cmd=$(ihf_get_install_cmd) + echo "install_cmd: $install_cmd" + + echo "Finished test==============" + fi +} + +test_script diff --git a/src/matlab/install-matlab-deps.sh b/src/matlab/install-matlab-deps.sh new file mode 100755 index 0000000..8bac277 --- /dev/null +++ b/src/matlab/install-matlab-deps.sh @@ -0,0 +1,71 @@ +#!/usr/bin/env bash +# This script install the OS dependencies required by MATLAB for the release specified in the +# environment variable MATLAB_RELEASE on any linux OS that is dervied from Ubuntu or RHEL +#------------------------------------------------------------------------------------------------------------- +# Copyright 2024 The MathWorks, Inc. +#------------------------------------------------------------------------------------------------------------- + +set -eu -o pipefail +# exits on an error (-e, equivalent to -o errexit); +# exits on an undefined variable (-u, equivalent to -o nounset); +# exits on an error in piped-together commands (-o pipefail) + +# Uncomment to debug: +# set -x +# Or, set environment variable "SHELLOPTS=xtrace" before starting script + +if [ $(basename "$0") != "install-matlab-deps.sh" ]; then + _SCRIPT_LOCATION="$1" + source ${_SCRIPT_LOCATION}/install-helper-functions.sh +else + source $(dirname "$0")/install-helper-functions.sh +fi + + +# Verify if valid MATLAB_RELEASE +if [ "$(ihf_is_valid_matlab_release)" == "false" ]; then + ihf_print_and_exit "Invalid or Unsupported MATLAB_RELEASE: $MATLAB_RELEASE " +fi + +function print_os_info(){ + . /etc/os-release + echo "Running install-matlab-deps script on: $PRETTY_NAME" + echo "ID=$ID , VERSION_ID=$VERSION_ID, for MATLAB_RELEASE=$MATLAB_RELEASE" +} + +function get_prerequisite_pkgs() { + # Returns the list of pre-requisite packages required to install matlab-deps + echo "wget unzip ca-certificates" +} + +function get_base_dependencies_list() { + local MATLAB_DEPS_OS_VERSION=$(ihf_get_matlab_deps_os) + local BASE_DEPS_URL=https://raw.githubusercontent.com/mathworks-ref-arch/container-images/main/matlab-deps/${MATLAB_RELEASE}/${MATLAB_DEPS_OS_VERSION}/base-dependencies.txt + # Get matlab_deps - if this fails, then we aren't on a supported os + local PKGS=$(wget -qO- ${BASE_DEPS_URL}) + if [ -z "$PKGS" ]; then + ihf_print_and_exit "${MATLAB_DEPS_OS_VERSION} is not a supported OS for MATLAB ${MATLAB_RELEASE} ." + fi + echo $PKGS +} + +function install_matlab_deps() { + local MATLAB_DEPS_OS_VERSION=$(ihf_get_matlab_deps_os) + # local linux_distro=$(ihf_is_debian_or_rhel) + + print_os_info + + local PREREQ_PACKAGES=$(get_prerequisite_pkgs) + + ihf_install_packages "$PREREQ_PACKAGES" + + echo "Get list of dependencies from ${MATLAB_RELEASE}/${MATLAB_DEPS_OS_VERSION}/base-dependencies.txt" + local BASE_DEPS_PKGS=$(get_base_dependencies_list) + ihf_install_packages "$BASE_DEPS_PKGS" + + ihf_clean_up + # Return 0 to indicate success! + return 0 +} +install_matlab_deps + diff --git a/src/matlab/install.sh b/src/matlab/install.sh new file mode 100755 index 0000000..0690793 --- /dev/null +++ b/src/matlab/install.sh @@ -0,0 +1,289 @@ +#!/usr/bin/env bash +#------------------------------------------------------------------------------------------------------------- +# Copyright 2024 The MathWorks, Inc. +#------------------------------------------------------------------------------------------------------------- +# NOTE: The 'install.sh' entrypoint script is always executed as the root user. + +set -eu -o pipefail +# exits on an error (-e, equivalent to -o errexit); +# exits on an undefined variable (-u, equivalent to -o nounset); +# exits on an error in piped-together commands (-o pipefail) + +# Uncomment to debug: +# set -x +# Or, set environment variable "SHELLOPTS=xtrace" before starting script + +### Variable Declaration Begin ### + +## Set defaults to all the options in the feature. + +# r2024a is the latest available release. +RELEASE="${RELEASE:-"r2024a"}" +PRODUCTS="${PRODUCTS:-"MATLAB"}" +DOC="${DOC:-"false"}" +INSTALLGPU="${INSTALLGPU:-"false"}" +DESTINATION="${DESTINATION:-"/opt/matlab/${RELEASE}"}" +INSTALLMATLABPROXY="${INSTALLMATLABPROXY:-"false"}" +INSTALLJUPYTERMATLABPROXY="${INSTALLJUPYTERMATLABPROXY:-"false"}" +INSTALLMATLABENGINEFORPYTHON="${INSTALLMATLABENGINEFORPYTHON:-"false"}" +STARTINDESKTOP="${STARTINDESKTOP:-"false"}" +NETWORKLICENSEMANAGER="${NETWORKLICENSEMANAGER:-" "}" +SKIPMATLABINSTALL="${SKIPMATLABINSTALL:-"false"}" + +MATLAB_RELEASE="${RELEASE}" +MATLAB_PRODUCT_LIST="${PRODUCTS}" +MATLAB_INSTALL_LOCATION="${DESTINATION}" + + +_CONTAINER_USER_HOME="${_CONTAINER_USER_HOME:-"undefined"}" +_CONTAINER_USER="${_CONTAINER_USER:-"undefined"}" + +_SCRIPT_LOCATION=$(dirname $(readlink -f "$0")) + +### Variable Declaration End ### +### Helper Functions Begin ### +export DEBIAN_FRONTEND=noninteractive + +function updaterc() { + echo "Updating /etc/bash.bashrc and /etc/zsh/zshrc..." + if [[ "$(cat /etc/bash.bashrc)" != *"$1"* ]]; then + echo -e "$1" >>/etc/bash.bashrc + fi + if [ -f "/etc/zsh/zshrc" ] && [[ "$(cat /etc/zsh/zshrc)" != *"$1"* ]]; then + echo -e "$1" >>/etc/zsh/zshrc + fi +} + +function install_python_and_pip() { + if [ "$(ihf_is_debian_or_rhel)" == "rhel" ]; then + # uninstalling python3-requests package as it cannot be updated by subsequent install command. + ihf_remove_packages "python3-requests" + fi + ihf_install_packages "python3 python3-pip" && \ + python3 -m pip install --upgrade pip + +} + +function install_xvfb() { + if [ "$(ihf_is_debian_or_rhel)" == "debian" ]; then + # Xvfb is unavailable in RHEL systems + ihf_install_packages "xvfb" + fi +} + +function install_matlab_proxy() { + install_python_and_pip && + install_xvfb && + python3 -m pip install --upgrade matlab-proxy +} + +function install_jupyter_matlab_proxy() { + install_python_and_pip && + install_xvfb && + python3 -m pip install --upgrade jupyter-matlab-proxy matlab-proxy jupyterlab jupyterlab-git +} + +function install_matlab_engine_for_python() { + # Installing the engine is tricky + # The installation can fail if the python version does not match the supported release + declare -A matlabengine_map + matlabengine_map['r2024a']="24.1" + matlabengine_map['r2023b']="23.2" + matlabengine_map['r2023a']="9.14" + matlabengine_map['r2022b']="9.13" + matlabengine_map['r2022a']="9.12" + matlabengine_map['r2021b']="9.11" + matlabengine_map['r2021a']="9.10" + matlabengine_map['r2020b']="9.9" + + install_python_and_pip && + env LD_LIBRARY_PATH=${MATLAB_INSTALL_LOCATION}/bin/glnxa64 \ + python3 -m pip install matlabengine==${matlabengine_map[$MATLAB_RELEASE]}.* +} + +# Create a home folder for non-root, undefined CONTAINER_USER, if not already available +function create_home_folder_for_container_user() { + if [ ! -z "$_CONTAINER_USER" -a "$_CONTAINER_USER" != "undefined" ] && [ "$_CONTAINER_USER" != "root" ]; then + if [ "$_CONTAINER_USER_HOME" == "undefined" ] || [ -z "$_CONTAINER_USER_HOME" ]; then + echo "Creating home directory for CONTAINER_USER" + + ORIG_UID=$(id -u $_CONTAINER_USER) + ORIG_GID=$(id -g $_CONTAINER_USER) + + # adduser is the preferred high level function to create a user. + # The "-m" flag is used to indicate the creation of the home folder. + # The "-M" flag is used to avoid the creation of the home folder. + ## We are recreating the CONTAINER_USER after deleting to avoid the "user already exists" error + adduser tempuser -M -u $(($ORIG_UID + 1)) -g $ORIG_GID && \ + userdel $_CONTAINER_USER && \ + adduser $_CONTAINER_USER -m -u $ORIG_UID -g $ORIG_GID && \ + userdel tempuser + + # Set value to newly created home + _CONTAINER_USER_HOME=$(cat /etc/passwd | grep $_CONTAINER_USER | cut -d ':' -f 6) + + # Manual approach without using "adduser" + # mkdir /home/$_CONTAINER_USER && \ + # cp -rT /etc/skel /home/ $_CONTAINER_USER && \ + # chown -R ${_CONTAINER_USER}:${_CONTAINER_USER} /home/$_CONTAINER_USER + # _CONTAINER_USER_HOME="/home/$_CONTAINER_USER" + echo "New home directory: ${_CONTAINER_USER_HOME}" + fi + fi +} + +# Include the helper functions: ihf_* +source ${_SCRIPT_LOCATION}/install-helper-functions.sh + +### Helper Functions End ### +### Script Section Begin ### + +## Handle all other options first before attempting to install MATLAB. + +# Verify if valid MATLAB_RELEASE +if [ "$(ihf_is_valid_matlab_release)" == "false" ]; then + ihf_print_and_exit "Invalid or Unsupported MATLAB_RELEASE: $MATLAB_RELEASE " +fi + +MATLAB_FEATURE_INSTALL_TMPDIR=/tmp/matlab-feature-install +mkdir -p $MATLAB_FEATURE_INSTALL_TMPDIR && pushd $MATLAB_FEATURE_INSTALL_TMPDIR + +# Install matlab-proxy if requested +if [ "${INSTALLMATLABPROXY}" == "true" ]; then + echo "Installing matlab-proxy" + install_matlab_proxy +fi + +# Install jupyter-matlab-proxy if requested +if [ "${INSTALLJUPYTERMATLABPROXY}" == "true" ]; then + echo "Installing jupyter-matlab-proxy" + install_jupyter_matlab_proxy +fi + +if [ "${STARTINDESKTOP}" == "true" ] || [ "${STARTINDESKTOP}" == "test" ]; then + echo "User wants to start in MATLAB Desktop." + # Leave a marker file that can be checked by the postStartCommand + # matlab-proxy will be started by the postStartCommand. + # Current postStartCommand: + # "( ls ~/.startmatlabdesktop >> /dev/null 2>&1 && env MWI_APP_PORT=8888 matlab-proxy-app 2>/dev/null ) || echo 'Will not start matlab-proxy-app...'", + # the /tmp directory is not available on codespaces, using _CONTAINER_USER_HOME instead. + + create_home_folder_for_container_user + + if [ ! -z "${_CONTAINER_USER_HOME}" -a "${_CONTAINER_USER_HOME}" != "undefined" ]; then + # This feature is only available when _CONTAINER_USER_HOME is known. + if [ "${STARTINDESKTOP}" == "true" ]; then + install_matlab_proxy && + touch ${_CONTAINER_USER_HOME}/.startmatlabdesktop && + chmod a+rw ${_CONTAINER_USER_HOME}/.startmatlabdesktop && + rm -f ${_CONTAINER_USER_HOME}/.teststartmatlabdesktop + else + # This file is used during testing and does not actually effect the postStartCommand that is looking for + # the startmatlabdesktop file! + # Without this, Tests would hang indefinitely waiting for the postStartCommand + install_matlab_proxy && + touch ${_CONTAINER_USER_HOME}/.teststartmatlabdesktop && + chmod a+rw ${_CONTAINER_USER_HOME}/.teststartmatlabdesktop && + rm -f ${_CONTAINER_USER_HOME}/.startmatlabdesktop + fi + else + echo "Cannot start in desktop as the _CONTAINER_USER_HOME is undefined or empty. Value:'${_CONTAINER_USER_HOME}'" + fi +fi + +# Update RC files with the provided license manager info +if [ ! -z "${NETWORKLICENSEMANAGER}" -a "${NETWORKLICENSEMANAGER}" != " " ]; then + updaterc "export MLM_LICENSE_FILE=${NETWORKLICENSEMANAGER}" +fi + +if [ "$SKIPMATLABINSTALL" != 'true' ]; then + + ### MATLAB Installation Steps: + ## 1. Install OS Dependencies required by MATLAB + ## 2. Setup MPM flags based on options + ## 3. Install MATLAB using MPM + + ## 1. Install OS Dependencies required by MATLAB + source ${_SCRIPT_LOCATION}/install-matlab-deps.sh ${_SCRIPT_LOCATION} + + ## 2. Setup MPM flags based on options + ADDITIONAL_MPM_FLAGS=" " + + # Handle DOC installation + if [ "${DOC}" == "true" ]; then + ADDITIONAL_MPM_FLAGS="${ADDITIONAL_MPM_FLAGS} --doc " + fi + + # Handle GPU installation + if [ "${INSTALLGPU}" == "false" ]; then + RELEASES_THAT_SUPPORT_NOGPU=("r2024a" "r2023b" "r2023a") + # The value variable is assigned a regex that matches the exact value + value="\<${MATLAB_RELEASE}\>" + if [[ ${RELEASES_THAT_SUPPORT_NOGPU[@]} =~ $value ]]; then + echo "'$MATLAB_RELEASE' supports NOGPU flag..." + ADDITIONAL_MPM_FLAGS="${ADDITIONAL_MPM_FLAGS} --no-gpu " + else + echo "'$MATLAB_RELEASE' does not support NOGPU flag, skipping..." + fi + fi + + echo "Container user is defined as : '$_CONTAINER_USER'" + echo "Container user's effective home dir: '$_CONTAINER_USER_HOME'" + echo "Remote user is defined as : '${_REMOTE_USER:-"undefined"}'" + echo "Remote user's effective home dir: '${_REMOTE_USER_HOME:-"undefined"}'" + + ## 3. Install MATLAB using MPM + if [ ! -z "$_CONTAINER_USER" -a "$_CONTAINER_USER" != "undefined" ] && [ "$_CONTAINER_USER" != "root" ]; then + # Use the containerUser option to set this value. The "vscode" user works well with tests. + # The "codespaces" user returns an empty _CONTAINER_USER_HOME + echo "Container user is defined as : '$_CONTAINER_USER'" + echo "Container user's effective home dir: '$_CONTAINER_USER_HOME'" + + create_home_folder_for_container_user + + echo "Proceeding to install matlab as '$_CONTAINER_USER'..." + + # Switching to container user + su $_CONTAINER_USER + pushd $_CONTAINER_USER_HOME + + # Installing MATLAB as containerUser allows for support packages to be installed at the correct location. + wget -q https://www.mathworks.com/mpm/glnxa64/mpm && + chmod +x mpm && + sudo HOME=${_CONTAINER_USER_HOME} ./mpm install \ + --release=${MATLAB_RELEASE} \ + --destination=${MATLAB_INSTALL_LOCATION} \ + --products ${MATLAB_PRODUCT_LIST} ${ADDITIONAL_MPM_FLAGS} && + sudo rm -f mpm /tmp/mathworks_root.log && + sudo ln -fs ${MATLAB_INSTALL_LOCATION}/bin/matlab /usr/local/bin/matlab + + ## Resetting to original context + # exit will reset the user to root and call popd + # exit + popd + sudo su + else + echo "Proceeding to install matlab as root user..." + # Installs as root, because feature scripts run as root user. + # Any support package installed here will not be accessible to non-root users of the system. + wget https://www.mathworks.com/mpm/glnxa64/mpm && + chmod +x mpm && + ./mpm install \ + --release=${MATLAB_RELEASE} \ + --destination=${MATLAB_INSTALL_LOCATION} \ + --products ${MATLAB_PRODUCT_LIST} ${ADDITIONAL_MPM_FLAGS} && + rm -f mpm /tmp/mathworks_root.log && + ln -fs ${MATLAB_INSTALL_LOCATION}/bin/matlab /usr/local/bin/matlab + fi +fi + +# MATLAB Engine for Python can only be installed if MATLAB is on the PATH +if [ "${INSTALLMATLABENGINEFORPYTHON}" == "true" ]; then + echo "Installing matlabengine" + install_matlab_engine_for_python +fi + +popd +### Script Section End ### +echo "MATLAB feature installation is complete." +exit 0 diff --git a/src/matlab/tests.sh b/src/matlab/tests.sh new file mode 100755 index 0000000..6b5322f --- /dev/null +++ b/src/matlab/tests.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +# Calls conatins to test the install scripts + +# Check default installation for r2024a +RELEASE=r2024a +RUN_INSTALL_SCRIPT="env RELEASE=${RELEASE} /mounted/src/matlab/install.sh " +# TEST="python3 -m pip list | grep matlabengine && echo PASSED! || echo FAILED!" +docker run -it --rm --entrypoint /usr/bin/sh -v `pwd`:/mounted ubuntu:20.04 -c "$RUN_INSTALL_SCRIPT && echo PASSED! || echo FAILED!" + +RELEASE=r2022b +RUN_INSTALL_SCRIPT="env RELEASE=${RELEASE} /mounted/src/matlab/install.sh " +# TEST="python3 -m pip list | grep matlabengine && echo PASSED! || echo FAILED!" +docker run -it --rm --entrypoint /usr/bin/sh -v `pwd`:/mounted registry.access.redhat.com/ubi9/ubi:latest -c "$RUN_INSTALL_SCRIPT && echo PASSED! || echo FAILED!" + +RELEASE=r2024a +RUN_INSTALL_SCRIPT="env RELEASE=${RELEASE} /mounted/src/matlab/install.sh " +# TEST="python3 -m pip list | grep matlabengine && echo PASSED! || echo FAILED!" +docker run -it --rm --entrypoint /usr/bin/bash -v `pwd`:/mounted registry.access.redhat.com/ubi9/ubi:latest + +# RELEASE=r2024a +# RUN_INSTALL_SCRIPT="sudo env INSTALLMATLABENGINEFORPYTHON=true SKIPMATLABINSTALL=true _CONTAINER_USER=matlab \ +# _CONTAINER_USER_HOME=/home/matlab DESTINATION=/opt/matlab/${RELEASE^} RELEASE=${RELEASE} \ +# ~/install/install.sh " +# TEST_IF_MATLABENGINE_INSTALLED="python3 -m pip list | grep matlabengine && echo PASSED! || echo FAILED!" +# docker run -it --rm --entrypoint /bin/sh -v `pwd`/src/matlab/:/home/matlab/install mathworks/matlab:${RELEASE} -c "$RUN_INSTALL_SCRIPT && $TEST_IF_MATLABENGINE_INSTALLED" \ No newline at end of file diff --git a/test/matlab/check_ubi9.sh b/test/matlab/check_ubi9.sh new file mode 100644 index 0000000..3865c2d --- /dev/null +++ b/test/matlab/check_ubi9.sh @@ -0,0 +1,71 @@ +#!/bin/bash +#------------------------------------------------------------------------------------------------------------- +# Copyright 2024 The MathWorks, Inc. +#------------------------------------------------------------------------------------------------------------- +# +# This test file will be executed against one of the scenarios devcontainer.json test that +# includes the 'matlab' feature with the r2024a release, and a support package installed. +# Support package installation is special, because these packages need to be installed into +# the end users HOME folder and not into the root users folders. Installing into root will +# result in users being unable to access the Support Packages. +# +# "check_ubi9": { +# "image": "registry.access.redhat.com/ubi9/ubi:latest", +# "features": { +# "ghcr.io/devcontainers/features/common-utils:2": { +# "installZsh": false, +# "installOhMyZshConfig": false, +# "username": "vscode", +# "userUid": "1000", +# "userGid": "1000", +# "upgradePackages": "true" +# }, +# "matlab": { +# "release": "r2024a", +# "products": "MATLAB MATLAB_Support_Package_for_Android_Sensors", +# "startInDesktop": "test" +# } +# }, +# "containerUser": "vscode" +# } + +# This test can be run with the following command: +# +# devcontainer features test \ +# --features matlab \ +# --remote-user root \ +# --base-image mcr.microsoft.com/devcontainers/base:ubuntu \ +# `pwd` +# OR: +# devcontainer features test -p `pwd` -f matlab --filter check_ubi9 --log-level debug +set -e + +# Optional: Import test library bundled with the devcontainer CLI +source dev-container-features-test-lib + +# Feature-specific tests +# The 'check' command comes from the dev-container-features-test-lib. +# check