Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow EnvironmentVariableCollection API to apply changes via shell integration #179476

Closed
Tracked by #20822
Tyriar opened this issue Apr 7, 2023 · 19 comments · Fixed by #191082
Closed
Tracked by #20822

Allow EnvironmentVariableCollection API to apply changes via shell integration #179476

Tyriar opened this issue Apr 7, 2023 · 19 comments · Fixed by #191082
Assignees
Labels
api api-finalization feature-request Request for new features or functionality insiders-released Patch has been released in VS Code Insiders on-testplan terminal Integrated terminal issues
Milestone

Comments

@Tyriar
Copy link
Member

Tyriar commented Apr 7, 2023

A problem the Python extension has is that it sets environment variables but they get overridden during shell startup scripts. This issue would allow adding an option to the variable mutator for the timing a particular environment variable is applied.

@karrtikr
Copy link
Contributor

@Tyriar FYI none of the integration scripts work if VSCODE_ENV_* variable is set, each shell fails with its own type of error.

@sandy081
Copy link
Member

Moving this to next milestone as this is a feature request and we are late in the release cycle to make this happen.

@Tyriar
Copy link
Member Author

Tyriar commented May 11, 2023

Proposal:

export enum EnvironmentVariableMutatorTiming {
	/**
	 * Apply to the environment just before the process is created.
	 */
	ProcessCreation = 1,

	/**
	 * Apply to the environment in the shell integration script. Note that this _will not_ apply
	 * the mutator if shell integration is disabled or not working for some reason.
	 */
	ShellIntegration = 2,

	/**
	 * Apply to the environment just before the process is created and then again in the shell
	 * integration script. Note that this _will not_ apply the mutator if shell integration is
	 * disabled or not working for some reason.
	 */
	Both = 3
}

export interface EnvironmentVariableMutatorOptions {
	/**
	 * When the mutator will be applied to the environment.
	 */
	timing?: EnvironmentVariableMutatorTiming;
}

/**
 * A type of mutation and its value to be applied to an environment variable.
 */
export interface EnvironmentVariableMutator {
	/**
	 * Options applied to the mutator.
	 */
	readonly options?: EnvironmentVariableMutatorOptions;
}

export interface EnvironmentVariableCollection extends Iterable<[variable: string, mutator: EnvironmentVariableMutator]> {
	/**
	 * @param options Options applied to the mutator.
	 */
	replace(variable: string, value: string, options?: EnvironmentVariableMutatorOptions): void;

	/**
	 * @param options Options applied to the mutator.
	 */
	append(variable: string, value: string, options?: EnvironmentVariableMutatorOptions): void;

	/**
	 * @param options Options applied to the mutator.
	 */
	prepend(variable: string, value: string, options?: EnvironmentVariableMutatorOptions): void;
}

The timing property will be implemented by the specially handled VSCODE_ENV_* variables.

@Tyriar
Copy link
Member Author

Tyriar commented May 11, 2023

Feedback from API sync:

  • "Timing" makes it seems ms/performance-related. Some other ideas: phase, stage, sequence. Think about it and look at existing APIs for something similar
  • timing should be readonly
  • Make values on EnvironmentVariableMutator non-falsy and initialize with defaults instead of undefined

@Tyriar
Copy link
Member Author

Tyriar commented May 11, 2023

Make values on EnvironmentVariableMutator non-falsy and initialize with defaults instead of undefined

I couldn't find many examples of similar things, and when I did it's optional everywhere like in ProcessExecutionOptions:

export class ProcessExecution {
/**
* Creates a process execution.
*
* @param process The process to start.
* @param options Optional options for the started process.
*/
constructor(process: string, options?: ProcessExecutionOptions);
/**
* Creates a process execution.
*
* @param process The process to start.
* @param args Arguments to be passed to the process.
* @param options Optional options for the started process.
*/
constructor(process: string, args: string[], options?: ProcessExecutionOptions);
/**
* The process to be executed.
*/
process: string;
/**
* The arguments passed to the process. Defaults to an empty array.
*/
args: string[];
/**
* The process options used when the process is executed.
* Defaults to undefined.
*/
options?: ProcessExecutionOptions;
}

I'll keep it as is for now as we can make it non-undefined later in a non-breaking change.

@Tyriar
Copy link
Member Author

Tyriar commented May 11, 2023

Updated proposal after feedback:

export interface EnvironmentVariableMutatorOptions {
	/**
	 * Apply to the environment just before the process is created.
	 * 
	 * Defaults to true.
	 */
	applyAtProcessCreation?: boolean;
	
	/**
	 * Apply to the environment in the shell integration script. Note that this _will not_ apply
	 * the mutator if shell integration is disabled or not working for some reason.
	 * 
	 * Defaults to false.
	 */
	applyAtShellIntegration?: boolean;
}

/**
 * A type of mutation and its value to be applied to an environment variable.
 */
export interface EnvironmentVariableMutator {
	/**
	 * Options applied to the mutator.
	 */
	readonly options: EnvironmentVariableMutatorOptions;
}

export interface EnvironmentVariableCollection extends Iterable<[variable: string, mutator: EnvironmentVariableMutator]> {
	/**
	 * @param options Options applied to the mutator.
	 */
	replace(variable: string, value: string, options?: EnvironmentVariableMutatorOptions): void;

	/**
	 * @param options Options applied to the mutator.
	 */
	append(variable: string, value: string, options?: EnvironmentVariableMutatorOptions): void;

	/**
	 * @param options Options applied to the mutator.
	 */
	prepend(variable: string, value: string, options?: EnvironmentVariableMutatorOptions): void;
}

Comments:

  • I split out the timing enum into 2 booleans as that better future proofs us (what would Both mean if we had a 3rd timing?) and is simpler in a way.
  • For EnvironmentVariableMutator.options we can have it return a Required<EnvironmentVariableMutatorOptions>, I don't see us using Required<> anywhere in the API though so I'll leave is as the less strict EnvironmentVariableMutatorOptions.

@Tyriar
Copy link
Member Author

Tyriar commented May 11, 2023

Currently blocked on the changes to workspace scope moving to a EnvironmentVariableCollection function as replace, append, prepend prototypes conflict.

@Tyriar
Copy link
Member Author

Tyriar commented May 19, 2023

TPI #182970

@Tyriar
Copy link
Member Author

Tyriar commented May 23, 2023

Question for API sync: #182883 (review)

@Tyriar
Copy link
Member Author

Tyriar commented May 30, 2023

Feedback to bring to sync:

  • Should the default of applyAtProcessCreation be false?

@karrtikr
Copy link
Contributor

Another option: Rename it to doNotApplyAtProcessCreation and keep default as false.

@Tyriar
Copy link
Member Author

Tyriar commented Jul 26, 2023

Bug report for zsh: #188875
Bug report for fish: #184009

@Tyriar
Copy link
Member Author

Tyriar commented Aug 7, 2023

The bugs don't block this as they shouldn't change the API shape.

@Tyriar
Copy link
Member Author

Tyriar commented Aug 7, 2023

We do need to finalize how the defaults work for applyAtProcessCreation though.

@Tyriar
Copy link
Member Author

Tyriar commented Aug 8, 2023

Let's treat undefined as false and update the jsdoc to explain this. Good to finalize

@karrtikr
Copy link
Contributor

Particularly for Python extension, it's easy to miss to set or not set it when replacing or prepending:

envVarCollection.replace(
    'PATH',
    `${path.dirname(interpreter.path)}${path.delimiter}${process.env[pathVarName]}`,
    { applyAtShellIntegration: true, applyAtProcessCreation: true },
);

or

envVarCollection.prepend('PS1', value, {
    applyAtShellIntegration: true,
    applyAtProcessCreation: false,
});

Maybe we should consider making it a required parameter as it is always something which we look at.

@Tyriar
Copy link
Member Author

Tyriar commented Aug 11, 2023

@karrtikr the options arg can't be required due to backwards compatibility, the individual option properties should be required as then it would be inconsistent if we added a new property (which must be optional).

I think the undefined is treated as false thing fixes most of the API ux problems.

@karrtikr
Copy link
Contributor

To be clear, I'm suggesting just to make applyAtProcessCreation required when using options. As that is the confusing property which changes value when options is used.

Tyriar added a commit that referenced this issue Aug 22, 2023
Tyriar added a commit that referenced this issue Aug 23, 2023
@VSCodeTriageBot VSCodeTriageBot added unreleased Patch has not yet been released in VS Code Insiders insiders-released Patch has been released in VS Code Insiders and removed unreleased Patch has not yet been released in VS Code Insiders labels Aug 24, 2023
@github-actions github-actions bot locked and limited conversation to collaborators Oct 9, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
api api-finalization feature-request Request for new features or functionality insiders-released Patch has been released in VS Code Insiders on-testplan terminal Integrated terminal issues
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants