diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 90980e2..5ed04d3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,6 +17,7 @@ jobs: feature: [ "browsers", "build-essential", + "cypress-deps", "docker-out", "eclipse-deps", "git-lfs", diff --git a/README.md b/README.md index 96aaef1..a39f8fd 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,7 @@ Below is a list with included features, click on the link for more details. | --- | --- | | [browsers](./features/src/browsers/README.md) | A package which installs various browsers. | | [build-essential](./features/src/build-essential/README.md) | Installs essential build tools (e.g., gcc, g++, make, libc-dev) for compiling C/C++ and | +| [cypress-deps](./features/src/cypress-deps/README.md) | Installs all system dependencies required for running Cypress tests in a dev container. | | [docker-out](./features/src/docker-out/README.md) | A feature which installs the Docker client and re-uses the host socket. | | [eclipse-deps](./features/src/eclipse-deps/README.md) | Installs all system dependencies required for running Eclipse IDE in a dev container. | | [git-lfs](./features/src/git-lfs/README.md) | A feature which installs Git LFS. | diff --git a/build/build.go b/build/build.go index 73909ab..6947a6b 100644 --- a/build/build.go +++ b/build/build.go @@ -22,6 +22,7 @@ import ( var featureList = []string{ "browsers", "build-essential", + "cypress-deps", "docker-out", "eclipse-deps", "git-lfs", @@ -82,6 +83,17 @@ func init() { return publishFeature("build-essential") }) + ////////// cypress-deps + gotaskr.Task("Feature:cypress-deps:Package", func() error { + return packageFeature("cypress-deps") + }) + gotaskr.Task("Feature:cypress-deps:Test", func() error { + return testFeature("cypress-deps") + }) + gotaskr.Task("Feature:cypress-deps:Publish", func() error { + return publishFeature("cypress-deps") + }) + ////////// docker-out gotaskr.Task("Feature:docker-out:Package", func() error { return packageFeature("docker-out") diff --git a/features/src/cypress-deps/NOTES.md b/features/src/cypress-deps/NOTES.md new file mode 100644 index 0000000..3243c7b --- /dev/null +++ b/features/src/cypress-deps/NOTES.md @@ -0,0 +1,32 @@ +## Notes + +### Cypress Binary Cache + +By default, Cypress installs binaries somewhere in the user home. This directory is not a volume and therefore is cleared when the container is rebuild which means, Cypress needs to be reinstalled in each new container. + +To prevent this, the Cypress cache folder can be set to a folder from the workspace which is a volume (basically the cloned repository) and with that, it does not need to be reinstalled each time the container is rebuilt. + +To do this, add the following environment variable to your container: +``` +"containerEnv": { + "CYPRESS_CACHE_FOLDER": "${containerWorkspaceFolder}/.cypress_cache" +} +``` + +Also don't forget to add `.cypress_cache` to your `.gitignore` file. + +### x11 Socket + +The Cypress UI in the dev-container is displayed via x11. If the corresponding socket is not correcly forwarded into the container, this can lead to heavy performance loss due to higher CPU usage. + +To correctly forward the x11 socket into the container, make sure to add the following variables to the `runArgs`: +``` +"runArgs": [ + "-e", "DISPLAY=${localEnv:DISPLAY}", + "-v", "/tmp/.X11-unix:/tmp/.X11-unix" +] +``` + +### System Compatibility + +Debian, Ubuntu diff --git a/features/src/cypress-deps/README.md b/features/src/cypress-deps/README.md new file mode 100755 index 0000000..05642c5 --- /dev/null +++ b/features/src/cypress-deps/README.md @@ -0,0 +1,45 @@ +# Cypress Dependencies (cypress-deps) + +Installs all system dependencies required for running Cypress tests in a dev container. + +## Example Usage + +```json +"features": { + "ghcr.io/postfinance/devcontainer-features/cypress-deps:0.1.0": { + } +} +``` + +## Notes + +### Cypress Binary Cache + +By default, Cypress installs binaries somewhere in the user home. This directory is not a volume and therefore is cleared when the container is rebuild which means, Cypress needs to be reinstalled in each new container. + +To prevent this, the Cypress cache folder can be set to a folder from the workspace which is a volume (basically the cloned repository) and with that, it does not need to be reinstalled each time the container is rebuilt. + +To do this, add the following environment variable to your container: +``` +"containerEnv": { + "CYPRESS_CACHE_FOLDER": "${containerWorkspaceFolder}/.cypress_cache" +} +``` + +Also don't forget to add `.cypress_cache` to your `.gitignore` file. + +### x11 Socket + +The Cypress UI in the dev-container is displayed via x11. If the corresponding socket is not correcly forwarded into the container, this can lead to heavy performance loss due to higher CPU usage. + +To correctly forward the x11 socket into the container, make sure to add the following variables to the `runArgs`: +``` +"runArgs": [ + "-e", "DISPLAY=${localEnv:DISPLAY}", + "-v", "/tmp/.X11-unix:/tmp/.X11-unix" +] +``` + +### System Compatibility + +Debian, Ubuntu diff --git a/features/src/cypress-deps/devcontainer-feature.json b/features/src/cypress-deps/devcontainer-feature.json new file mode 100644 index 0000000..f3631c4 --- /dev/null +++ b/features/src/cypress-deps/devcontainer-feature.json @@ -0,0 +1,6 @@ +{ + "id": "cypress-deps", + "version": "0.1.0", + "name": "Cypress Dependencies", + "description": "Installs all system dependencies required for running Cypress tests in a dev container." +} \ No newline at end of file diff --git a/features/src/cypress-deps/install.sh b/features/src/cypress-deps/install.sh new file mode 100644 index 0000000..a898c50 --- /dev/null +++ b/features/src/cypress-deps/install.sh @@ -0,0 +1,3 @@ +. ./functions.sh + +"./installer_$(detect_arch)" diff --git a/features/src/cypress-deps/installer.go b/features/src/cypress-deps/installer.go new file mode 100644 index 0000000..ebbac85 --- /dev/null +++ b/features/src/cypress-deps/installer.go @@ -0,0 +1,63 @@ +package main + +import ( + "builder/installer" + "fmt" + "os" +) + +func main() { + if err := runMain(); err != nil { + fmt.Printf("Error: %v\n", err) + os.Exit(1) + } +} + +func runMain() error { + fmt.Println("Installing Cypress Dependencies") + osInfo, err := installer.Tools.System.GetOsInfo() + if err != nil { + return fmt.Errorf("failed to get OS info: %w", err) + } + + // Common dependencies for all supported distros + commonDeps := []string{ + "libgbm-dev", + "libnotify-dev", + "libnss3", + "libxss1", + "libxtst6", + "xauth", + "xvfb", + } + + var deps []string + + debianTrixieOrNewer := osInfo.Vendor == "debian" && func() bool { + var major int + fmt.Sscanf(osInfo.VersionId, "%d", &major) + return major >= 13 + }() + + ubuntuNobleOrNewer := osInfo.Vendor == "ubuntu" && func() bool { + var major int + fmt.Sscanf(osInfo.VersionId, "%d", &major) + return major >= 24 + }() + + if debianTrixieOrNewer || ubuntuNobleOrNewer { + deps = append([]string{ + "libgtk2.0-0t64", + "libgtk-3-0t64", + "libasound2t64", + }, commonDeps...) + } else { + deps = append([]string{ + "libgtk2.0-0", + "libgtk-3-0", + "libasound2", + }, commonDeps...) + } + + return installer.Tools.Apt.InstallDependencies(deps...) +} diff --git a/features/test/cypress-deps/packages_installed.sh b/features/test/cypress-deps/packages_installed.sh new file mode 100644 index 0000000..a2f1545 --- /dev/null +++ b/features/test/cypress-deps/packages_installed.sh @@ -0,0 +1,26 @@ +#!/bin/bash +set -e + +[[ -f "$(dirname "$0")/../functions.sh" ]] && source "$(dirname "$0")/../functions.sh" +[[ -f "$(dirname "$0")/functions.sh" ]] && source "$(dirname "$0")/functions.sh" + +# Detect distro and codename +. /etc/os-release + +if { [[ "$ID" = "debian" && "$VERSION_CODENAME" = "trixie" ]] || [[ "$ID" = "ubuntu" && "$VERSION_CODENAME" = "noble" ]]; }; then + check_package_installed "libgtk2.0-0t64" + check_package_installed "libgtk-3-0t64" + check_package_installed "libasound2t64" +else + check_package_installed "libgtk2.0-0" + check_package_installed "libgtk-3-0" + check_package_installed "libasound2" +fi + +check_package_installed "libgbm-dev" +check_package_installed "libnotify-dev" +check_package_installed "libnss3" +check_package_installed "libxss1" +check_package_installed "libxtst6" +check_package_installed "xauth" +check_package_installed "xvfb" diff --git a/features/test/cypress-deps/scenarios.json b/features/test/cypress-deps/scenarios.json new file mode 100644 index 0000000..a934d70 --- /dev/null +++ b/features/test/cypress-deps/scenarios.json @@ -0,0 +1,13 @@ +{ + "packages_installed": { + "build": { + "dockerfile": "Dockerfile", + "options": [ + "--add-host=host.docker.internal:host-gateway" + ] + }, + "features": { + "./cypress-deps": {} + } + } +} \ No newline at end of file diff --git a/features/test/cypress-deps/test-images.json b/features/test/cypress-deps/test-images.json new file mode 100644 index 0000000..9e621d6 --- /dev/null +++ b/features/test/cypress-deps/test-images.json @@ -0,0 +1,6 @@ +[ + "mcr.microsoft.com/devcontainers/base:debian-12", + "mcr.microsoft.com/devcontainers/base:debian-13", + "mcr.microsoft.com/devcontainers/base:ubuntu-22.04", + "mcr.microsoft.com/devcontainers/base:ubuntu-24.04" +] \ No newline at end of file diff --git a/installer/system.go b/installer/system.go index a81e60d..49131a3 100644 --- a/installer/system.go +++ b/installer/system.go @@ -24,8 +24,9 @@ func (s *system) MapArchitecture(mapping map[string]string) (string, error) { } type OsInfo struct { - Vendor string - Codename string + Vendor string + Codename string + VersionId string } func (s *system) GetOsInfo() (*OsInfo, error) { @@ -38,10 +39,13 @@ func (s *system) GetOsInfo() (*OsInfo, error) { infoMap := map[string]string{} for scanner.Scan() { parts := strings.SplitN(scanner.Text(), "=", 2) - infoMap[parts[0]] = parts[1] + // Remove surrounding quotes if present + val := strings.Trim(parts[1], `"`) + infoMap[parts[0]] = val } return &OsInfo{ - Vendor: infoMap["ID"], - Codename: infoMap["VERSION_CODENAME"], + Vendor: infoMap["ID"], + Codename: infoMap["VERSION_CODENAME"], + VersionId: infoMap["VERSION_ID"], }, nil }