Skip to content

Commit 1380704

Browse files
committed
Transform maven, gradle and jupyterlab usages to their features v2 counterparts (#461)
1 parent be6b3a3 commit 1380704

3 files changed

Lines changed: 107 additions & 4 deletions

File tree

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
"definitions": "npm-run-all definitions-clean definitions-copy",
3636
"lint": "eslint -c .eslintrc.js --rulesdir ./build/eslint --max-warnings 0 --ext .ts ./src",
3737
"definitions-clean": "rimraf dist/node_modules/vscode-dev-containers",
38-
"definitions-copy": "copyfiles \"node_modules/vscode-dev-containers/container-features/{devcontainer-features.json,feature-scripts.env,fish-debian.sh,gradle-debian.sh,homebrew-debian.sh,install.sh,jupyterlab-debian.sh,maven-debian.sh}\" dist",
38+
"definitions-copy": "copyfiles \"node_modules/vscode-dev-containers/container-features/{devcontainer-features.json,feature-scripts.env,fish-debian.sh,homebrew-debian.sh,install.sh}\" dist",
3939
"npm-pack": "npm pack",
4040
"clean": "npm-run-all clean-dist clean-built",
4141
"clean-dist": "rimraf dist",

src/spec-configuration/containerFeaturesConfiguration.ts

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -634,7 +634,8 @@ async function processUserFeatures(params: ContainerFeatureInternalParams, confi
634634
let configPath = config.configFilePath && uriToFsPath(config.configFilePath, platform);
635635
output.write(`configPath: ${configPath}`, LogLevel.Trace);
636636

637-
for (const userFeature of userFeatures) {
637+
const updatedUserFeatures = updateDeprecatedFeaturesIntoOptions(userFeatures, output);
638+
for (const userFeature of updatedUserFeatures) {
638639
const newFeatureSet = await processFeatureIdentifier(params, configPath, workspaceRoot, userFeature);
639640

640641
if (!newFeatureSet) {
@@ -645,6 +646,56 @@ async function processUserFeatures(params: ContainerFeatureInternalParams, confi
645646
return featuresConfig;
646647
}
647648

649+
const deprecatedFeaturesIntoOptions: Record<string, { mapTo: string; withOptions: any }> = {
650+
gradle: {
651+
mapTo: 'java',
652+
withOptions: {
653+
installGradle: true
654+
}
655+
},
656+
maven: {
657+
mapTo: 'java',
658+
withOptions: {
659+
installMaven: true
660+
}
661+
},
662+
jupyterlab: {
663+
mapTo: 'python',
664+
withOptions: {
665+
installJupyterlab: true
666+
}
667+
},
668+
};
669+
670+
export function updateDeprecatedFeaturesIntoOptions(userFeatures: DevContainerFeature[], output: Log) {
671+
const newFeaturePath = 'ghcr.io/devcontainers/features';
672+
const versionBackwardComp = '1';
673+
for (const update of userFeatures.filter(feature => deprecatedFeaturesIntoOptions[feature.id])) {
674+
const { mapTo, withOptions } = deprecatedFeaturesIntoOptions[update.id];
675+
output.write(`(!) WARNING: Using the deprecated '${update.id}' Feature. It is now part of the '${mapTo}' Feature. See https://github.com/devcontainers/features/tree/main/src/${mapTo}#options for the updated Feature.`, LogLevel.Warning);
676+
const qualifiedMapToId = `${newFeaturePath}/${mapTo}`;
677+
let userFeature = userFeatures.find(feature => feature.id === mapTo || feature.id === qualifiedMapToId || feature.id.startsWith(`${qualifiedMapToId}:`));
678+
if (userFeature) {
679+
userFeature.options = {
680+
...(
681+
typeof userFeature.options === 'object' ? userFeature.options :
682+
typeof userFeature.options === 'string' ? { version: userFeature.options } :
683+
{}
684+
),
685+
...withOptions,
686+
};
687+
} else {
688+
userFeature = {
689+
id: `${qualifiedMapToId}:${versionBackwardComp}`,
690+
options: withOptions
691+
};
692+
userFeatures.push(userFeature);
693+
}
694+
}
695+
const updatedUserFeatures = userFeatures.filter(feature => !deprecatedFeaturesIntoOptions[feature.id]);
696+
return updatedUserFeatures;
697+
}
698+
648699
export async function getFeatureIdType(params: CommonParams, userFeatureId: string) {
649700
const { output } = params;
650701
// See the specification for valid feature identifiers:

src/test/container-features/featureHelpers.test.ts

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@ import { assert } from 'chai';
22
import * as path from 'path';
33
import { DevContainerConfig, DevContainerFeature } from '../../spec-configuration/configuration';
44
import { OCIRef } from '../../spec-configuration/containerCollectionsOCI';
5-
import { Feature, FeatureSet, getBackwardCompatibleFeatureId, getFeatureInstallWrapperScript, processFeatureIdentifier } from '../../spec-configuration/containerFeaturesConfiguration';
5+
import { Feature, FeatureSet, getBackwardCompatibleFeatureId, getFeatureInstallWrapperScript, processFeatureIdentifier, updateDeprecatedFeaturesIntoOptions } from '../../spec-configuration/containerFeaturesConfiguration';
66
import { getSafeId, findContainerUsers } from '../../spec-node/containerFeatures';
77
import { ImageMetadataEntry } from '../../spec-node/imageMetadata';
88
import { SubstitutedConfig } from '../../spec-node/utils';
9-
import { createPlainLog, LogLevel, makeLog } from '../../spec-utils/log';
9+
import { createPlainLog, LogLevel, makeLog, nullLog } from '../../spec-utils/log';
1010

1111
export const output = makeLog(createPlainLog(text => process.stdout.write(text), () => LogLevel.Trace));
1212

@@ -447,6 +447,58 @@ describe('validate function getBackwardCompatibleFeatureId', () => {
447447
});
448448
});
449449

450+
describe('validate function updateDeprecatedFeaturesIntoOptions', () => {
451+
it('should add feature with option', () => {
452+
const updated = updateDeprecatedFeaturesIntoOptions([
453+
{
454+
id: 'jupyterlab',
455+
options: {}
456+
}
457+
], nullLog);
458+
assert.strictEqual(updated.length, 1);
459+
assert.strictEqual(updated[0].id, 'ghcr.io/devcontainers/features/python:1');
460+
assert.ok(updated[0].options);
461+
assert.strictEqual(typeof updated[0].options, 'object');
462+
assert.strictEqual((updated[0].options as Record<string, string | boolean | undefined>)['installJupyterlab'], true);
463+
});
464+
465+
it('should update feature with option', () => {
466+
const updated = updateDeprecatedFeaturesIntoOptions([
467+
{
468+
id: 'ghcr.io/devcontainers/features/python:1',
469+
options: {}
470+
},
471+
{
472+
id: 'jupyterlab',
473+
options: {}
474+
}
475+
], nullLog);
476+
assert.strictEqual(updated.length, 1);
477+
assert.strictEqual(updated[0].id, 'ghcr.io/devcontainers/features/python:1');
478+
assert.ok(updated[0].options);
479+
assert.strictEqual(typeof updated[0].options, 'object');
480+
assert.strictEqual((updated[0].options as Record<string, string | boolean | undefined>)['installJupyterlab'], true);
481+
});
482+
483+
it('should update legacy feature with option', () => {
484+
const updated = updateDeprecatedFeaturesIntoOptions([
485+
{
486+
id: 'python',
487+
options: {}
488+
},
489+
{
490+
id: 'jupyterlab',
491+
options: {}
492+
}
493+
], nullLog);
494+
assert.strictEqual(updated.length, 1);
495+
assert.strictEqual(updated[0].id, 'python');
496+
assert.ok(updated[0].options);
497+
assert.strictEqual(typeof updated[0].options, 'object');
498+
assert.strictEqual((updated[0].options as Record<string, string | boolean | undefined>)['installJupyterlab'], true);
499+
});
500+
});
501+
450502
describe('validate function getFeatureInstallWrapperScript', () => {
451503
it('returns a valid script when optional feature values do not exist', () => {
452504
const feature: Feature = {

0 commit comments

Comments
 (0)