diff --git a/build/config.json b/build/config.json index 20f24d5455..19912f2266 100644 --- a/build/config.json +++ b/build/config.json @@ -183,7 +183,7 @@ "downloadUrl": "https://github.com/cli/cli" }, "yarn": { - "versionCommand": "yarn --version", + "versionCommand": "yarn --version 2>/dev/null | grep -oE '[0-9]+\\.[0-9]+\\.[0-9]+'", "downloadUrl": "https://yarnpkg.com/" }, "Maven": { diff --git a/build/src/utils/config.js b/build/src/utils/config.js index 3bea536bc1..96b5b2b8ac 100644 --- a/build/src/utils/config.js +++ b/build/src/utils/config.js @@ -214,6 +214,7 @@ function getTagsForVersion(definitionId, version, registry, registryPath, varian } } + const seen = new Set(); return tags.reduce((list, tag) => { // One of the tags that needs to be supported is one where there is no version, but there // are other attributes. For example, python:3 in addition to python:0.35.0-3. So, a version @@ -224,7 +225,11 @@ function getTagsForVersion(definitionId, version, registry, registryPath, varian .replace(/\$\{?VARIANT\}?/, variant || 'NOVARIANT') .replace('-NOVARIANT', ''); if (baseTag.charAt(baseTag.length - 1) !== ':') { - list.push(`${registry}/${registryPath}/${baseTag}`); + const fullTag = `${registry}/${registryPath}/${baseTag}`; + if (!seen.has(fullTag)) { + seen.add(fullTag); + list.push(fullTag); + } } return list; }, []); @@ -305,13 +310,23 @@ function getTagList(definitionId, release, versionPartHandling, registry, regist // If this variant should also be used for the the latest tag, add it. The "latest" value could be // true, false, or a specific variant. "true" assumes the first variant is the latest. const definitionLatestProperty = config.definitionBuildSettings[definitionId].latest; - return tagList.concat((updateLatest + const allTags = tagList.concat((updateLatest && definitionLatestProperty && (!allVariants - || variant === definitionLatestProperty + || variant === definitionLatestProperty || (definitionLatestProperty === true && variant === firstVariant))) ? getLatestTag(definitionId, registry, registryPath) : []); + + // Deduplicate tags while preserving order + const seen = new Set(); + return allTags.filter((tag) => { + if (seen.has(tag)) { + return false; + } + seen.add(tag); + return true; + }); } const getDefinitionObject = (id, variant) => { diff --git a/build/src/utils/image-content-extractor.js b/build/src/utils/image-content-extractor.js index 237d871e12..4f47e2fef0 100644 --- a/build/src/utils/image-content-extractor.js +++ b/build/src/utils/image-content-extractor.js @@ -271,12 +271,34 @@ async function getPipPackageInfo(imageTagOrContainerName, packageList, usePipx, console.log('(*) Gathering information about pip packages...'); const versionLookup = usePipx ? await getPipxVersionLookup(imageTagOrContainerName) : await getPipVersionLookup(imageTagOrContainerName, imageId); - return packageList.map((package) => { - return { - name: package, - version: versionLookup[package] - }; - }); + // Build a fallback lookup keyed by PEP 503 normalized names so packages that + // differ only by casing/normalization (e.g. "pyOpenssl" vs "pyOpenSSL") still resolve. + const normalizedVersionLookup = Object.keys(versionLookup).reduce((prev, name) => { + prev[normalizePipPackageName(name)] = versionLookup[name]; + return prev; + }, {}); + + return packageList.reduce((list, package) => { + const version = versionLookup[package] || normalizedVersionLookup[normalizePipPackageName(package)]; + // Skip packages that aren't actually installed in the inspected environment. + // Emitting an entry without a version produces blank markdown rows and + // conflicting/duplicate cgmanifest.json (SBOM) registrations. + if (version) { + list.push({ + name: package, + version: version + }); + } else { + console.log(`(!) Warning: Could not determine version for pip package "${package}" - skipping.`); + } + return list; + }, []); +} + +// Normalize a Python package name per PEP 503 (lowercase, runs of "-_." collapsed to "-") +// so lookups are tolerant of casing/separator differences between manifests and pip output. +function normalizePipPackageName(name) { + return name.replace(/[-_.]+/g, '-').toLowerCase(); } function getUserName(imageId) { diff --git a/src/dotnet/manifest.json b/src/dotnet/manifest.json index e61223c90d..de6bf64c15 100644 --- a/src/dotnet/manifest.json +++ b/src/dotnet/manifest.json @@ -80,7 +80,7 @@ "languages": { ".NET": { "cgIgnore": true, - "versionCommand": "dotnet --version | grep -oE '[0-9]+\\.[0-9]+\\.[0-9]+' | tr -d '\\n' && echo \\\" (\\$(dotnet --info | grep -ozP 'Host.*:\\s*Version:\\s*\\K[0-9]\\.[0-9]\\.[0-9]' | tr '\\0' '\\n'))\\\"", + "versionCommand": "dotnet --version | grep -oE '[0-9]+\\.[0-9]+\\.[0-9]+' | tr -d '\\n' && echo \\\" (\\$(dotnet --info | grep -ozP 'Host.*:\\s*Version:\\s*\\K[0-9]+\\.[0-9]+\\.[0-9]+' | tr '\\0' '\\n'))\\\"", "path": "/usr", "downloadUrl": "https://dotnet.microsoft.com/" } diff --git a/src/java-8/README.md b/src/java-8/README.md index 65f5eb8dbd..81d622390e 100644 --- a/src/java-8/README.md +++ b/src/java-8/README.md @@ -30,8 +30,8 @@ Refer to [this guide](https://containers.dev/guide/dockerfile) for more details. You can decide how often you want updates by referencing a [semantic version](https://semver.org/) of each image. For example: - `mcr.microsoft.com/devcontainers/java:3-8` (or `3-8-trixie`, `3-8-bookworm` to pin to an OS version) -- `mcr.microsoft.com/devcontainers/java:3.0-8` (or `3.0-8-trixie`, `3.0-8-bookworm` to pin to an OS version) -- `mcr.microsoft.com/devcontainers/java:3.0.11-8` (or `3.0.11-8-trixie`, `3.0.11-8-bookworm` to pin to an OS version) +- `mcr.microsoft.com/devcontainers/java:3.1-8` (or `3.1-8-trixie`, `3.1-8-bookworm` to pin to an OS version) +- `mcr.microsoft.com/devcontainers/java:3.1.0-8` (or `3.1.0-8-trixie`, `3.1.0-8-bookworm` to pin to an OS version) However, we only do security patching on the latest [non-breaking, in support](https://github.com/devcontainers/images/issues/90) versions of images (e.g. `3-8`). You may want to run `apt-get update && apt-get upgrade` in your Dockerfile if you lock to a more specific version to at least pick up OS security updates. diff --git a/src/java-8/manifest.json b/src/java-8/manifest.json index 8a88cf62f6..fa090a3177 100644 --- a/src/java-8/manifest.json +++ b/src/java-8/manifest.json @@ -1,5 +1,5 @@ { - "version": "3.0.11", + "version": "3.1.0", "variants": [ "trixie", "bookworm" @@ -56,7 +56,7 @@ "languages": { "Java": { "cgIgnore": true, - "versionCommand": "/usr/local/sdkman/candidates/java/current/bin/java -version 2>&1 | grep -ozP '^openjdk\\sversion\\s\\\"\\K[^\\\"]+' | tr '\\0' '\\n' && /usr/local/openjdk-*/bin/java --version | grep -ozP 'openjdk\\s+\\K[0-9]+\\.[0-9]+\\.[0-9]+' | tr '\\0' '\\n'", + "versionCommand": "/usr/local/sdkman/candidates/java/current/bin/java -version 2>&1 | grep -ozP '^openjdk\\sversion\\s\\\"\\K[^\\\"]+' | tr '\\0' '\\n'", "path": "/usr/local/sdkman/candidates/java/current
/usr/local" } } diff --git a/src/universal/manifest.json b/src/universal/manifest.json index ce34d88cb2..5339c0b952 100644 --- a/src/universal/manifest.json +++ b/src/universal/manifest.json @@ -119,8 +119,7 @@ "plotly", "jupyterlab_git", "certifi", - "setuptools", - "wheel" + "setuptools" ], "other": { "git": {},