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

Find Windows executable #288

Merged
merged 1 commit into from
Apr 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions common/src/dev-container-cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import path from 'path';
import {env} from 'process';
import {promisify} from 'util';
import {ExecFunction} from './exec';
import {findWindowsExecutable} from './windows';

const cliVersion = "0"; // Use 'latest' to get latest CLI version, or pin to specific version e.g. '0.14.1' if required

Expand All @@ -27,7 +28,8 @@ function getSpecCliInfo() {

async function isCliInstalled(exec: ExecFunction): Promise<boolean> {
try {
const {exitCode} = await exec(getSpecCliInfo().command, ['--help'], {
const command = await findWindowsExecutable(getSpecCliInfo().command);
const {exitCode} = await exec(command, ['--help'], {
silent: true,
});
return exitCode === 0;
Expand Down Expand Up @@ -121,7 +123,7 @@ async function runSpecCliJsonCommand<T>(options: {
err: data => options.log(data),
env: options.env ? {...process.env, ...options.env} : process.env,
};
const command = getSpecCliInfo().command;
const command = await findWindowsExecutable(getSpecCliInfo().command);
console.log(`About to run ${command} ${options.args.join(' ')}`); // TODO - take an output arg to allow GH to use core.info
await spawn(command, options.args, spawnOptions);

Expand All @@ -138,7 +140,7 @@ async function runSpecCliNonJsonCommand(options: {
err: data => options.log(data),
env: options.env ? {...process.env, ...options.env} : process.env,
};
const command = getSpecCliInfo().command;
const command = await findWindowsExecutable(getSpecCliInfo().command);
console.log(`About to run ${command} ${options.args.join(' ')}`); // TODO - take an output arg to allow GH to use core.info
const result = await spawn(command, options.args, spawnOptions);
return result.code
Expand Down
86 changes: 86 additions & 0 deletions common/src/windows.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import * as path from 'path';
import * as fs from 'fs';

// From Dev Containers CLI
export async function findWindowsExecutable(command: string): Promise<string> {
if (process.platform !== 'win32') {
return command;
}

// If we have an absolute path then we take it.
if (path.isAbsolute(command)) {
return await findWindowsExecutableWithExtension(command) || command;
}
const cwd = process.cwd();
if (/[/\\]/.test(command)) {
// We have a directory and the directory is relative (see above). Make the path absolute
// to the current working directory.
const fullPath = path.join(cwd, command);
return await findWindowsExecutableWithExtension(fullPath) || fullPath;
}
let pathValue: string | undefined = undefined;
let paths: string[] | undefined = undefined;
const env = process.env;
// Path can be named in many different ways and for the execution it doesn't matter
for (let key of Object.keys(env)) {
if (key.toLowerCase() === 'path') {
const value = env[key];
if (typeof value === 'string') {
pathValue = value;
paths = value.split(path.delimiter)
.filter(Boolean);
}
break;
}
}
// No PATH environment. Bail out.
if (paths === void 0 || paths.length === 0) {
const err = new Error(`No PATH to look up executable '${command}'.`);
(err as any).code = 'ENOENT';
throw err;
}
// We have a simple file name. We get the path variable from the env
// and try to find the executable on the path.
for (let pathEntry of paths) {
// The path entry is absolute.
let fullPath: string;
if (path.isAbsolute(pathEntry)) {
fullPath = path.join(pathEntry, command);
} else {
fullPath = path.join(cwd, pathEntry, command);
}
const withExtension = await findWindowsExecutableWithExtension(fullPath);
if (withExtension) {
return withExtension;
}
}
// Not found in PATH. Bail out.
const err = new Error(`Exectuable '${command}' not found on PATH '${pathValue}'.`);
(err as any).code = 'ENOENT';
throw err;
}

const pathext = process.env.PATHEXT;
const executableExtensions = pathext ? pathext.toLowerCase().split(';') : ['.com', '.exe', '.bat', '.cmd'];

async function findWindowsExecutableWithExtension(fullPath: string) {
if (executableExtensions.indexOf(path.extname(fullPath)) !== -1) {
return await isFile(fullPath) ? fullPath : undefined;
}
for (const ext of executableExtensions) {
const withExtension = fullPath + ext;
if (await isFile(withExtension)) {
return withExtension;
}
}
return undefined;
}

function isFile(filepath: string): Promise<boolean> {
return new Promise(r => fs.stat(filepath, (err, stat) => r(!err && stat.isFile())));
}
Loading