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

Windows compatibility #864

Merged
merged 11 commits into from
Jan 31, 2023
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ Features, Bug Fixes, API Breaking, Deprecated, Infrastructure, /example Updates
### Bug Fixes

- Fixed `pre-commit` script that did not work properly and skipped the `lint-staged` part.
- Fixed compiling contracts on Windows.

### Features

Expand Down
3 changes: 2 additions & 1 deletion packages/algob/src/lib/compile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import YAML from "yaml";
import { assertDir, ASSETS_DIR, CACHE_DIR } from "../internal/core/project-structure";
import { timestampNow } from "../lib/time";
import type { ASCCache, SCParams } from "../types";
import { NO_FILE_OR_DIRECTORY_ERROR } from "./constants";

export const tealExt = ".teal";
export const pyExt = ".py";
Expand Down Expand Up @@ -111,7 +112,7 @@ export class CompileOp {
const p = path.join(CACHE_DIR, filename + ".yaml");
return YAML.parse(await fs.promises.readFile(p, "utf8")) as ASCCache;
} catch (e) {
if (types.isFileError(e) && e?.errno === -2) {
if ((e as NodeJS.ErrnoException)?.code === NO_FILE_OR_DIRECTORY_ERROR) {
return undefined;
} // handling a not existing file
throw e;
Expand Down
1 change: 1 addition & 0 deletions packages/algob/src/lib/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ export const MIN_UINT64 = 0n;
export const MAX_UINT64 = 0xffffffffffffffffn;
// mock algod credentials
export const mockAlgod = new Algodv2("dummyToken", "https://dummyNetwork", 8080);
export const NO_FILE_OR_DIRECTORY_ERROR = "ENOENT"
5 changes: 3 additions & 2 deletions packages/algob/src/lib/files.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { getPathFromDirRecursive } from "@algo-builder/runtime";
import { BuilderError, ERRORS, types } from "@algo-builder/web";
import { BuilderError, ERRORS } from "@algo-builder/web";
import fs from "fs-extra";
import path from "path";

import { ASSETS_DIR } from "../internal/core/project-structure";
import { NO_FILE_OR_DIRECTORY_ERROR } from "./constants";

function normalizePaths(mainPath: string, paths: string[]): string[] {
return paths.map((n) => path.relative(mainPath, n));
Expand Down Expand Up @@ -45,7 +46,7 @@ export function loadEncodedTxFromFile(fileName: string): Uint8Array | undefined
const buffer = fs.readFileSync(p);
return Uint8Array.from(buffer);
} catch (e) {
if (types.isFileError(e) && e?.errno === -2) {
if ((e as NodeJS.ErrnoException)?.code === NO_FILE_OR_DIRECTORY_ERROR) {
return undefined;
} // handling a not existing file
throw e;
Expand Down
6 changes: 3 additions & 3 deletions packages/algob/src/lib/msig.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { getPathFromDirRecursive } from "@algo-builder/runtime";
import { types } from "@algo-builder/web";
import {
Account,
appendSignMultisigTransaction,
Expand All @@ -17,6 +16,7 @@ import fs from "fs";

import { ASSETS_DIR } from "../internal/core/project-structure";
import { LogicSig } from "../types";
import { NO_FILE_OR_DIRECTORY_ERROR } from "./constants";
import { isSignedTx } from "./tx";

export const blsigExt = ".blsig";
Expand Down Expand Up @@ -56,7 +56,7 @@ export async function readMsigFromFile(filename: string): Promise<EncodedMultisi
const msig = fs.readFileSync(p, "utf8").split("LogicSig: ")[1];
return await decodeMsigObj(msig);
} catch (e) {
if (types.isFileError(e) && e?.errno === -2) {
if ((e as NodeJS.ErrnoException)?.code === NO_FILE_OR_DIRECTORY_ERROR) {
return undefined;
} // handling a not existing file
throw e;
Expand All @@ -77,7 +77,7 @@ export async function readBinaryMultiSig(filename: string): Promise<string | und
const p = getPathFromDirRecursive(ASSETS_DIR, filename) as string;
return fs.readFileSync(p, "base64");
} catch (e) {
if (types.isFileError(e) && e?.errno === -2) {
if ((e as NodeJS.ErrnoException)?.code === NO_FILE_OR_DIRECTORY_ERROR) {
return undefined;
} // handling a not existing file
throw e;
Expand Down
8 changes: 8 additions & 0 deletions packages/runtime/src/lib/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -888,3 +888,11 @@ export enum NumIndex {
export const JS_CONFIG_FILENAME = "algob.config.js";
export const TS_CONFIG_FILENAME = "algob.config.ts";
export const NETWORK_DEFAULT = "default";

export const PythonCommands = ['python3', 'python', 'py'] as const;
export type PythonCommand = typeof PythonCommands[number];

export enum searchStrCommand {
Windows = "Findstr",
UnixLinux = "grep",
}
44 changes: 40 additions & 4 deletions packages/runtime/src/lib/pycompile-op.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { BuilderError, ERRORS } from "@algo-builder/web";
import { exec, spawnSync, SpawnSyncReturns } from "child_process";
import { exec, execSync, spawnSync, SpawnSyncReturns } from "child_process";
import YAML from "yaml";

import type { ReplaceParams, SCParams } from "../types";
import { PythonCommand, PythonCommands, searchStrCommand } from "./constants";
import { getPathFromDirRecursive } from "./files";
import Os from "os";

export const tealExt = ".teal";
export const pyExt = ".py";
Expand Down Expand Up @@ -34,6 +36,20 @@ export class PyCompileOp {
return content;
}

/**
* Description: Check if current OS is Windows
*/
private isWindows(): boolean {
return Os.platform() === "win32";
}

/**
* Description: Returns the command to search strings according to OS
*/
private getSearchStrCommand(): searchStrCommand {
return this.isWindows() ? searchStrCommand.Windows : searchStrCommand.UnixLinux;
}

/**
* Parses scTmplParams and returns ReplaceParams and stringify object
* @param scTmplParams smart contract template parameters
Expand Down Expand Up @@ -77,27 +93,47 @@ export class PyCompileOp {
return program;
}

/**
* Description: Check and returns the executable python command
*/
private getPythonCommand(): PythonCommand {
const pyCommand = PythonCommands.find((command: PythonCommand) => {
try {
execSync(command + " -V", { stdio: "pipe" });
} catch {
return false;
}
return command;
});
if (!pyCommand) {
throw new Error(`executable python command not found.`);
}
return pyCommand;
}

/**
* Description: Runs a subprocess to execute python script
* @param filename : python filename in assets folder
* @param scInitParam : Smart contract initialization parameters.
*/
private runPythonScript(filename: string, scInitParam?: string): SpawnSyncReturns<string> {
const filePath = getPathFromDirRecursive(ASSETS_DIR, filename) as string;
const pythonCommand = this.getPythonCommand();
// used spawnSync instead of spawn, as it is synchronous
if (scInitParam === undefined) {
return spawnSync("python3", [filePath], { encoding: "utf8" });
return spawnSync(pythonCommand, [filePath], { encoding: "utf8" });
}

return spawnSync("python3", [filePath, scInitParam], { encoding: "utf8" });
return spawnSync(pythonCommand, [filePath, scInitParam], { encoding: "utf8" });
}

/**
* Description: This method checks if given module is installed or not. Otherwise throw an exception.
* @param module: Module to be checked if installed or not.
*/
private validatePythonModule(module: string) {
exec(`pip list | grep ${module}`, (err: any) => {
const searchStrCommand = this.getSearchStrCommand();
exec(`pip list | ${searchStrCommand} ${module}`, (err: any) => {
if (err) {
throw new Error(
`"${module}" module not found. Please try running "pip install ${module}"`
Expand Down
9 changes: 0 additions & 9 deletions packages/web/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -334,15 +334,6 @@ export interface RequestError extends Error {
error?: Error;
}

export interface FileError extends Error {
errno: number;
}

// This function is used to check if given objects implements `FileError` interface
export function isFileError(object: unknown): object is FileError {
return Object.prototype.hasOwnProperty.call(object, "errno");
}

// This function is used to check if given objects implements `RequestError` interface
// https://www.technicalfeeder.com/2021/02/how-to-check-if-a-object-implements-an-interface-in-typescript/
export function isRequestError(object: unknown): object is RequestError {
Expand Down