diff --git a/ce/ce/archivers/git.ts b/ce/ce/archivers/git.ts index 41609b72ac..e8f41be30d 100644 --- a/ce/ce/archivers/git.ts +++ b/ce/ce/archivers/git.ts @@ -2,7 +2,6 @@ // Licensed under the MIT License. import { UnpackEvents } from '../interfaces/events'; -import { Session } from '../session'; import { Credentials } from '../util/credentials'; import { execute } from '../util/exec-cmd'; import { isFilePath, Uri } from '../util/uri'; @@ -14,16 +13,12 @@ export interface CloneOptions { /** @internal */ export class Git { - #session: Session; #toolPath: string; #targetFolder: Uri; - #environment: NodeJS.ProcessEnv; - constructor(session: Session, toolPath: string, environment: NodeJS.ProcessEnv, targetFolder: Uri) { - this.#session = session; + constructor(toolPath: string, targetFolder: Uri) { this.#toolPath = toolPath; this.#targetFolder = targetFolder; - this.#environment = environment; } /** @@ -45,7 +40,6 @@ export class Git { options.depth ? `--depth=${options.depth}` : '', '--progress' ], { - env: this.#environment, onStdErrData: chunkToHeartbeat(events), onStdOutData: chunkToHeartbeat(events) }); @@ -70,7 +64,6 @@ export class Git { options.commit ? options.commit : '', options.depth ? `--depth=${options.depth}` : '' ], { - env: this.#environment, cwd: this.#targetFolder.fsPath }); @@ -92,7 +85,6 @@ export class Git { 'checkout', options.commit ? options.commit : '' ], { - env: this.#environment, cwd: this.#targetFolder.fsPath, onStdErrData: chunkToHeartbeat(events), onStdOutData: chunkToHeartbeat(events) @@ -117,7 +109,6 @@ export class Git { options.recurse ? '--recurse-submodules' : '', options.hard ? '--hard' : '' ], { - env: this.#environment, cwd: this.#targetFolder.fsPath, onStdErrData: chunkToHeartbeat(events), onStdOutData: chunkToHeartbeat(events) @@ -140,7 +131,6 @@ export class Git { } const result = await execute(this.#toolPath, ['init'], { - env: this.#environment, cwd: this.#targetFolder.fsPath }); @@ -162,7 +152,6 @@ export class Git { name, location.toString() ], { - env: this.#environment, cwd: this.#targetFolder.fsPath }); @@ -186,7 +175,6 @@ export class Git { options.depth ? `--depth=${options.depth}` : '', options.recursive ? '--recursive' : '', ], { - env: this.#environment, cwd: this.#targetFolder.fsPath, onStdErrData: chunkToHeartbeat(events), onStdOutData: chunkToHeartbeat(events) @@ -210,7 +198,6 @@ export class Git { key, value ], { - env: this.#environment, cwd: this.#targetFolder.fsPath }); return result.code === 0; diff --git a/ce/ce/artifacts/activation.ts b/ce/ce/artifacts/activation.ts index 3c95a4dd6b..5f8a71f6ac 100644 --- a/ce/ce/artifacts/activation.ts +++ b/ce/ce/artifacts/activation.ts @@ -69,7 +69,7 @@ export class Activation { // the folder is relative to the artifact install for (const folder of values) { - this.addPath(pathName, resolve(targetFolder.fsPath, folder)); + this.addPath(pathName, targetFolder.join(folder).fsPath); } } @@ -78,7 +78,7 @@ export class Activation { if (!toolName || !toolPath) { continue; } - this.addTool(toolName, resolve(targetFolder.fsPath, toolPath)); + this.addTool(toolName, targetFolder.join(toolPath).fsPath); } // **** locations **** @@ -87,7 +87,7 @@ export class Activation { continue; } - this.addLocation(name, resolve(targetFolder.fsPath, location)); + this.addLocation(name, targetFolder.join(location).fsPath); } // **** variables **** @@ -547,9 +547,11 @@ export class Activation { return [env, undo]; } - async activate(currentEnvironment: Record, shellScriptFile: Uri | undefined, undoEnvironmentFile: Uri | undefined, msbuildFile: Uri | undefined, json: Uri | undefined) { + async activate(undoEnvironmentFile: Uri | undefined, msbuildFile: Uri | undefined, json: Uri | undefined) { let undoDeactivation = ''; - const scriptKind = extname(shellScriptFile?.fsPath || ''); + const scriptKind = extname(this.#session.postscriptFile?.fsPath || ''); + + const currentEnvironment = {...this.#session.environment}; // load previous activation undo data const previous = currentEnvironment[undoVariableName]; @@ -558,9 +560,10 @@ export class Activation { const deactivationData = await deactivationDataFile.tryReadUTF8(); if (deactivationData) { const deactivationParsed = JSON.parse(deactivationData); - currentEnvironment = undoActivation(currentEnvironment, deactivationParsed.environment || {}); + const previousEnvironmentValues = deactivationParsed.environment || {}; + undoActivation(currentEnvironment, previousEnvironmentValues); delete currentEnvironment[undoVariableName]; - undoDeactivation = generateScriptContent(scriptKind, deactivationParsed.environment || {}, deactivationParsed.aliases || {}); + undoDeactivation = generateScriptContent(scriptKind, previousEnvironmentValues, deactivationParsed.aliases || {}); } } @@ -599,11 +602,11 @@ export class Activation { } // generate shell script if requested - if (shellScriptFile) { + if (this.#session.postscriptFile) { const contents = undoDeactivation + generateScriptContent(scriptKind, variables, aliases); this.#session.channels.verbose(`--------[START SHELL SCRIPT FILE]--------\n${contents}\n--------[END SHELL SCRIPT FILE]---------`); - await shellScriptFile.writeUTF8(contents); + await this.#session.postscriptFile.writeUTF8(contents); } // generate msbuild props file if requested @@ -621,7 +624,7 @@ export class Activation { } - /** produces an environment block that can be passed to child processes to leverage dependent artifacts during installtion/activation. */ + /** produces an environment block that can be passed to child processes to leverage dependent artifacts during installation/activation. */ async getEnvironmentBlock(): Promise { const result = { ... this.#session.environment }; @@ -709,16 +712,14 @@ export async function deactivate(shellScriptFile: Uri, variables: Record, variables: Record) { - const result = { ...currentEnvironment }; - for (const [key, value] of linq.entries(variables)) { +function undoActivation(targetEnvironment: Record, oldVariableValues: Record) { + for (const [key, value] of linq.entries(oldVariableValues)) { if (value) { - result[key] = value; + targetEnvironment[key] = value; } else { - delete result[key]; + delete targetEnvironment[key]; } } - return result; } async function toArrayAsync(iterable: AsyncIterable) { diff --git a/ce/ce/artifacts/artifact.ts b/ce/ce/artifacts/artifact.ts index aa9f1eb167..4b53372798 100644 --- a/ce/ce/artifacts/artifact.ts +++ b/ce/ce/artifacts/artifact.ts @@ -7,7 +7,6 @@ import { resolve } from 'path'; import { MetadataFile } from '../amf/metadata-file'; import { RegistriesDeclaration, RegistryDeclaration } from '../amf/registries'; import { artifactIdentity, prettyRegistryName } from '../cli/format'; -import { FileType } from '../fs/filesystem'; import { i } from '../i18n'; import { activateEspIdf, installEspIdf } from '../installers/espidf'; import { InstallEvents } from '../interfaces/events'; @@ -15,6 +14,7 @@ import { getArtifact, Registry, RegistryResolver } from '../registries/registrie import { Session } from '../session'; import { linq } from '../util/linq'; import { Uri } from '../util/uri'; +import { Activation } from './activation'; import { SetOfDemands } from './SetOfDemands'; export type Selections = Map; // idOrShortName, version @@ -82,6 +82,8 @@ export abstract class ArtifactBase { return Promise.resolve(undefined); } + + abstract loadActivationSettings(activation: Activation): Promise; } export enum InstallStatus { @@ -91,8 +93,6 @@ export enum InstallStatus { } export class Artifact extends ArtifactBase { - allPaths: Array = []; - constructor(session: Session, metadata: MetadataFile, public shortName: string, public targetLocation: Uri) { super(session, metadata); } @@ -129,10 +129,6 @@ export class Artifact extends ArtifactBase { this.session.channels.message(addDisplayPrefix(thisDisplayName, applicableDemands.messages)); if (await this.isInstalled && !options.force) { - if (!await this.loadActivationSettings(events)) { - throw new Error(i`Failed during artifact activation`); - } - events.alreadyInstalledArtifact?.(thisDisplayName); return InstallStatus.AlreadyInstalled; } @@ -160,11 +156,12 @@ export class Artifact extends ArtifactBase { await installer(this.session, this.id, this.version, this.targetLocation, installInfo, events, options); } + if (this.metadata.espidf) { + await installEspIdf(this.session, events, this.targetLocation); + } + // after we unpack it, write out the installed manifest await this.writeManifest(); - if (!await this.loadActivationSettings(events)) { - throw new Error(i`Failed during artifact activation`); - } return InstallStatus.Installed; } catch (err) { try { @@ -186,27 +183,23 @@ export class Artifact extends ArtifactBase { await this.targetLocation.delete({ recursive: true, useTrash: false }); } - async loadActivationSettings(events: Partial) { + + async loadActivationSettings(activation: Activation) : Promise { // construct paths (bin, lib, include, etc.) // construct tools // compose variables // defines - // record all the files in the artifact - this.allPaths = (await this.targetLocation.readDirectory(undefined, { recursive: true })).select(([name, stat]) => stat === FileType.Directory ? name.fsPath + '/' : name.fsPath); for (const exportsBlock of this.applicableDemands.exports) { - this.session.activation.addExports(exportsBlock, this.targetLocation); + activation.addExports(exportsBlock, this.targetLocation); } // if espressif install if (this.metadata.espidf) { - // check for some file that espressif installs to see if it's installed. - if (!await this.targetLocation.exists('.espressif')) { - await installEspIdf(this.session, events, this.targetLocation); - } - // activate - await activateEspIdf(this.session, this.targetLocation); + if (!await activateEspIdf(this.session, activation, this.targetLocation)) { + return false; + } } return true; @@ -256,6 +249,9 @@ export function sanitizeUri(u: string) { } export class ProjectManifest extends ArtifactBase { + loadActivationSettings(activation: Activation) { + return Promise.resolve(true); + } } export class InstalledArtifact extends Artifact { diff --git a/ce/ce/cli/commands/generate-msbuild-props.ts b/ce/ce/cli/commands/generate-msbuild-props.ts new file mode 100644 index 0000000000..f0589ae984 --- /dev/null +++ b/ce/ce/cli/commands/generate-msbuild-props.ts @@ -0,0 +1,66 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import { Activation } from '../../artifacts/activation'; +import { buildRegistryResolver, resolveDependencies } from '../../artifacts/artifact'; +import { i } from '../../i18n'; +import { session } from '../../main'; +import { showArtifacts } from '../artifacts'; +import { Command } from '../command'; +import { error } from '../styling'; +import { Json } from '../switches/json'; +import { MSBuildProps } from '../switches/msbuild-props'; +import { Project } from '../switches/project'; +import { WhatIf } from '../switches/whatIf'; + +export class GenerateMSBuildPropsCommand extends Command { + readonly command = 'generate-msbuild-props'; + readonly aliases = []; + seeAlso = []; + argumentsHelp = []; + whatIf = new WhatIf(this); + project: Project = new Project(this); + msbuildProps: MSBuildProps = new MSBuildProps(this, 'out'); + json : Json = new Json(this); + + get summary() { + return i`Generates MSBuild properties for an activation without downloading anything for a project`; + } + + get description() { return ['']; } + + override async run() { + if (!this.msbuildProps.active) { + error(i`generate-msbuild-props requires --msbuild-props`); + return false; + } + + const projectManifest = await this.project.manifest; + + if (!projectManifest) { + error(i`Unable to find project in folder (or parent folders) for ${session.currentDirectory.fsPath}`); + return false; + } + + const projectResolver = await buildRegistryResolver(session, projectManifest.metadata.registries); + const resolved = await resolveDependencies(session, projectResolver, [projectManifest], 3); + + // print the status of what is going to be activated. + if (!await showArtifacts(resolved, projectResolver, {})) { + error(i`Unable to activate project`); + return false; + } + + const activation = new Activation(session); + for (const artifact of resolved) { + if (!await artifact.artifact.loadActivationSettings(activation)) { + session.channels.error(i`Unable to activate project.`); + return false; + } + } + + const content = activation.generateMSBuild(); + await this.msbuildProps.value?.writeUTF8(content); + return true; + } +} diff --git a/ce/ce/cli/project.ts b/ce/ce/cli/project.ts index fa6460bf8a..8e2ce3e02b 100644 --- a/ce/ce/cli/project.ts +++ b/ce/ce/cli/project.ts @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +import { Activation } from '../artifacts/activation'; import { buildRegistryResolver, ProjectManifest, ResolvedArtifact, resolveDependencies } from '../artifacts/artifact'; import { i } from '../i18n'; import { trackActivation } from '../insights'; @@ -9,9 +10,8 @@ import { Session } from '../session'; import { Uri } from '../util/uri'; import { installArtifacts, showArtifacts } from './artifacts'; import { projectFile } from './format'; -import { error, log } from './styling'; -class ActivationOptions { +export interface ActivationOptions { force?: boolean; allLanguages?: boolean; language?: string; @@ -25,7 +25,15 @@ export async function activate(session: Session, artifacts: Array, targetLocation: Uri) { +export async function installEspIdf(session: Session, events: Partial, targetLocation: Uri) { + // check for some file that espressif installs to see if it's installed. + if (await targetLocation.exists('.espressif')) { return; } + // create the .espressif folder for the espressif installation - await targetLocation.createDirectory('.espressif'); - session.activation.addTool('IDF_TOOLS_PATH', targetLocation.join('.espressif').fsPath); + const dotEspidf = await targetLocation.createDirectory('.espressif'); const vcpkg = new Vcpkg(session); const pythonPath = await vcpkg.fetch('python3_with_venv'); @@ -20,21 +23,25 @@ export async function installEspIdf(session: Session, events: Partial { + session.channels.debug('espidf: ' + chunk); const regex = /\s(100)%/; chunk.toString().split('\n').forEach((line: string) => { const match_array = line.match(regex); @@ -50,7 +57,7 @@ export async function installEspIdf(session: Session, events: Partial { chunk.toString().split('\n').forEach((line: string) => { const splitLine = line.split('='); if (splitLine[0]) { if (splitLine[0] !== 'PATH') { - session.activation.addEnvironmentVariable(splitLine[0].trim(), [splitLine[1].trim()]); + activation.addEnvironmentVariable(splitLine[0].trim(), [splitLine[1].trim()]); } else { const pathValues = splitLine[1].split(delimiter); @@ -90,7 +103,7 @@ export async function activateEspIdf(session: Session, targetLocation: Uri) { // we actually want to use the artifacts we installed, not the ones that are being bundled. // when espressif supports artifacts properly, we shouldn't need this filter. if (! /\.espressif.tools/ig.exec(path)) { - session.activation.addPath(splitLine[0].trim(), session.fileSystem.file(path)); + activation.addPath(splitLine[0].trim(), session.fileSystem.file(path)); } } } @@ -104,5 +117,7 @@ export async function activateEspIdf(session: Session, targetLocation: Uri) { throw new Error(`Failed to activate esp-idf - ${activateIdf.stderr}`); } + activation.addEnvironmentVariable('IDF_PATH', targetDirectory); + activation.addTool('IDF_TOOLS_PATH', dotEspidf.fsPath); return true; } diff --git a/ce/ce/installers/git.ts b/ce/ce/installers/git.ts index 1d332c33f3..ab700ed9b4 100644 --- a/ce/ce/installers/git.ts +++ b/ce/ce/installers/git.ts @@ -21,7 +21,7 @@ export async function installGit(session: Session, name: string, version: string const repo = session.parseLocation(install.location); const targetDirectory = targetLocation.join(options.subdirectory ?? ''); - const gitTool = new Git(session, gitPath, await session.activation.getEnvironmentBlock(), targetDirectory); + const gitTool = new Git(gitPath, targetDirectory); events.unpackArchiveStart?.(repo, targetDirectory); // changing the clone process to do an init/add remote/fetch/checkout because diff --git a/ce/ce/main.ts b/ce/ce/main.ts index 487d0d6f8b..b11c25e2a7 100644 --- a/ce/ce/main.ts +++ b/ce/ce/main.ts @@ -15,6 +15,7 @@ import { CleanCommand } from './cli/commands/clean'; import { DeactivateCommand } from './cli/commands/deactivate'; import { DeleteCommand } from './cli/commands/delete'; import { FindCommand } from './cli/commands/find'; +import { GenerateMSBuildPropsCommand } from './cli/commands/generate-msbuild-props'; import { HelpCommand } from './cli/commands/help'; import { ListCommand } from './cli/commands/list'; import { RegenerateCommand } from './cli/commands/regenerate-index'; @@ -88,6 +89,7 @@ async function main() { const del = new DeleteCommand(commandline); const activate = new ActivateCommand(commandline); + const activate_msbuildprops = new GenerateMSBuildPropsCommand(commandline); const deactivate = new DeactivateCommand(commandline); const regenerate = new RegenerateCommand(commandline); diff --git a/ce/ce/session.ts b/ce/ce/session.ts index 991f78bcd5..e8ed4d9c2c 100644 --- a/ce/ce/session.ts +++ b/ce/ce/session.ts @@ -3,7 +3,7 @@ import { strict } from 'assert'; import { MetadataFile } from './amf/metadata-file'; -import { Activation, deactivate } from './artifacts/activation'; +import { deactivate } from './artifacts/activation'; import { Artifact, InstalledArtifact } from './artifacts/artifact'; import { configurationName, defaultConfig, globalConfigurationFile, postscriptVariable, undo } from './constants'; import { FileSystem } from './fs/filesystem'; @@ -50,7 +50,7 @@ export type Context = { [key: string]: Array | undefined; } & { export type SessionSettings = { readonly vcpkgCommand?: string; - readonly homeFolder?: string; + readonly homeFolder: string; readonly vcpkgArtifactsRoot?: string; readonly vcpkgDownloads?: string; readonly vcpkgRegistriesCache?: string; @@ -73,14 +73,14 @@ export class Session { readonly tmpFolder: Uri; readonly installFolder: Uri; readonly registryFolder: Uri; - readonly activation: Activation = new Activation(this); get vcpkgCommand() { return this.settings.vcpkgCommand; } readonly globalConfig: Uri; readonly downloads: Uri; readonly telemetryEnabled: boolean; currentDirectory: Uri; - configuration!: MetadataFile; + configuration?: MetadataFile; + readonly postscriptFile?: Uri; /** register installer functions here */ private installers = new Map([ @@ -108,7 +108,7 @@ export class Session { this.telemetryEnabled = this.settings['telemetryEnabled']; - this.homeFolder = this.fileSystem.file(settings.homeFolder!); + this.homeFolder = this.fileSystem.file(settings.homeFolder); this.downloads = this.processVcpkgArg(settings.vcpkgDownloads, 'downloads'); this.globalConfig = this.homeFolder.join(globalConfigurationFile); @@ -117,6 +117,9 @@ export class Session { this.registryFolder = this.processVcpkgArg(settings.vcpkgRegistriesCache, 'registries').join('artifact'); this.installFolder = this.processVcpkgArg(settings.vcpkgArtifactsRoot, 'artifacts'); + const postscriptFileName = this.environment[postscriptVariable]; + this.postscriptFile = postscriptFileName ? this.fileSystem.file(postscriptFileName) : undefined; + this.currentDirectory = this.fileSystem.file(currentDirectory); } @@ -131,12 +134,7 @@ export class Session { } async saveConfig() { - await this.configuration.save(this.globalConfig); - } - - #postscriptFile?: Uri; - get postscriptFile() { - return this.#postscriptFile || (this.#postscriptFile = this.environment[postscriptVariable] ? this.fileSystem.file(this.environment[postscriptVariable]!) : undefined); + await this.configuration?.save(this.globalConfig); } async init() { diff --git a/include/vcpkg/commands.generate-msbuild-props.h b/include/vcpkg/commands.generate-msbuild-props.h new file mode 100644 index 0000000000..9708e99341 --- /dev/null +++ b/include/vcpkg/commands.generate-msbuild-props.h @@ -0,0 +1,11 @@ +#pragma once + +#include + +namespace vcpkg::Commands +{ + struct GenerateMSBuildPropsCommand : PathsCommand + { + void perform_and_exit(const VcpkgCmdArguments& args, const VcpkgPaths& paths) const override; + }; +} diff --git a/src/vcpkg/commands.cpp b/src/vcpkg/commands.cpp index e75fa6741d..63ef26abb3 100644 --- a/src/vcpkg/commands.cpp +++ b/src/vcpkg/commands.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -85,6 +86,7 @@ namespace vcpkg::Commands Span> get_available_paths_commands() { static const ActivateCommand activate{}; + static const GenerateMSBuildPropsCommand generate_msbuildprops{}; static const AddCommand add{}; static const AddVersion::AddVersionCommand add_version{}; static const Autocomplete::AutocompleteCommand autocomplete{}; @@ -134,6 +136,7 @@ namespace vcpkg::Commands {"update", &update}, {"x-update-baseline", &update_baseline}, {"use", &use}, + {"x-generate-msbuild-props", &generate_msbuildprops}, {"x-add-version", &add_version}, {"x-ci-clean", &ciclean}, {"x-ci-verify-versions", &ci_verify_versions}, diff --git a/src/vcpkg/commands.generate-msbuild-props.cpp b/src/vcpkg/commands.generate-msbuild-props.cpp new file mode 100644 index 0000000000..ce52e75d04 --- /dev/null +++ b/src/vcpkg/commands.generate-msbuild-props.cpp @@ -0,0 +1,15 @@ +#include + +#include +#include +#include + +namespace vcpkg::Commands +{ + void GenerateMSBuildPropsCommand::perform_and_exit(const VcpkgCmdArguments& args, const VcpkgPaths& paths) const + { + Checks::exit_with_code( + VCPKG_LINE_INFO, + run_configure_environment_command(paths, "generate-msbuild-props", args.get_forwardable_arguments())); + } +}