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

Enhance error handling to support third-party tools #509

Merged
merged 23 commits into from
Jul 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
c7cfa37
Enhance error handling to support third-party tools
rahulyadav-57 Jun 28, 2024
c46cc8a
Merge branch 'main' into enhance-error-handling
rahulyadav-57 Jun 28, 2024
17ce414
fix: remove unused consoleLogger and jest mocks
rahulyadav-57 Jun 28, 2024
f74838a
Merge branch 'main' into enhance-error-handling
rahulyadav-57 Jul 3, 2024
a457c81
Add TactErrorCollection type
rahulyadav-57 Jul 3, 2024
3029053
Address review comments
rahulyadav-57 Jul 5, 2024
f51875b
Merge branch 'main' into enhance-error-handling
rahulyadav-57 Jul 5, 2024
6d7e69f
feat: add `-q`/`--quiet` flags to suppress compiler log output
novusnota Jul 5, 2024
4e96bbd
chore: CHANGELOG
novusnota Jul 5, 2024
d8d1d2b
chore: formatting
novusnota Jul 5, 2024
0bb4e92
Update src/pipeline/build.ts
anton-trunov Jul 5, 2024
d10df50
Update src/pipeline/build.ts
anton-trunov Jul 5, 2024
9f7f119
Update scripts/prepare.ts
anton-trunov Jul 5, 2024
17999a7
Update src/pipeline/build.ts
anton-trunov Jul 5, 2024
701a340
Update src/pipeline/build.ts
anton-trunov Jul 5, 2024
824ac30
Update src/node.ts
anton-trunov Jul 5, 2024
f621b22
Update src/pipeline/build.ts
anton-trunov Jul 5, 2024
f1de083
Update src/pipeline/build.ts
anton-trunov Jul 5, 2024
94d8b94
Update scripts/prepare.ts
anton-trunov Jul 5, 2024
3d9970d
Merge branch 'main' into enhance-error-handling
anton-trunov Jul 5, 2024
576b354
fix prepare.ts
anton-trunov Jul 5, 2024
8d8b772
fix build.ts
anton-trunov Jul 5, 2024
b6dcbae
chore: simplified a bit more
novusnota Jul 5, 2024
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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added

- `-e` / `--eval` CLI flags to evaluate constant Tact expressions: PR [#462](https://github.com/tact-lang/tact/pull/462)
- `-q` / `--quiet` CLI flags to suppress compiler log output: PR [#509](https://github.com/tact-lang/tact/pull/509)

### Changed

Expand All @@ -35,6 +36,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Error message for duplicate receiver definitions inherited from traits: PR [#519](https://github.com/tact-lang/tact/issues/519)
- Usage of `initOf` inside of `init()` does not cause error `135` anymore: PR [#521](https://github.com/tact-lang/tact/issues/521)
- Usage of `newAddress` with hash parts shorter than 64 hexadecimal digits does not cause constant evaluation error `Invalid address hash length` anymore: PR [#525](https://github.com/tact-lang/tact/pull/525)
- Introduced a streamlined error logger for compilation pipeline to support third-party tools: PR [#509](https://github.com/tact-lang/tact/pull/509)

## [1.4.0] - 2024-06-21

Expand Down
12 changes: 6 additions & 6 deletions bin/tact
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ meowModule.then(
Flags
-c, --config CONFIG Specify path to config file (tact.config.json)
-p, --project ...names Build only the specified project name(s) from the config file
-q, --quiet Suppress compiler log output
--with-decompilation Full compilation followed by decompilation of produced binary code
--func Output intermediate FunC code and exit
--check Perform syntax and type checking, then exit
Expand Down Expand Up @@ -51,14 +52,12 @@ meowModule.then(
);
},
},
eval: {
shortFlag: "e",
type: "string",
},
projects: { shortFlag: "p", type: "string", isMultiple: true },
quiet: { shortFlag: "q", type: "boolean", default: false },
withDecompilation: { type: "boolean", default: false },
func: { type: "boolean", default: false },
check: { type: "boolean", default: false },
eval: { shortFlag: "e", type: "string" },
version: { shortFlag: "v", type: "boolean" },
help: { shortFlag: "h", type: "boolean" },
},
Expand Down Expand Up @@ -164,10 +163,11 @@ meowModule.then(
configPath: cli.flags.config,
projectNames: cli.flags.projects ?? [],
additionalCliOptions: { mode },
suppressLog: cli.flags.quiet,
})
.then((success) => {
.then((response) => {
novusnota marked this conversation as resolved.
Show resolved Hide resolved
// https://nodejs.org/docs/v20.12.1/api/process.html#exit-codes
process.exit(success ? 0 : 30);
process.exit(response.ok ? 0 : 30);
});
},
);
137 changes: 75 additions & 62 deletions scripts/prepare.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,82 +5,95 @@ import { FuncCompilationResult, funcCompile } from "../src/func/funcCompile";
import path from "path";
import { glob } from "glob";
import { verify } from "../src/verify";
import { consoleLogger } from "../src/logger";
import { Logger } from "../src/logger";
import { __DANGER__disableVersionNumber } from "../src/pipeline/version";

// Read cases
void (async () => {
// Disable version number in packages
__DANGER__disableVersionNumber();

// Compile projects
if (!(await run({ configPath: __dirname + "/../tact.config.json" }))) {
console.error("Tact projects compilation failed");
process.exit(1);
}
const logger = new Logger();

// Verify projects
for (const pkgPath of glob.sync(
path.normalize(
path.resolve(__dirname, "..", "examples", "output", "*.pkg"),
),
)) {
const res = await verify({ pkg: fs.readFileSync(pkgPath, "utf-8") });
if (!res.ok) {
console.error("Failed to verify " + pkgPath + ": " + res.error);
process.exit(1);
try {
// Compile projects
const compileResult = await run({
configPath: path.join(__dirname, "..", "tact.config.json"),
});
if (!compileResult.ok) {
throw new Error("Tact projects compilation failed");
}
}

// Compile func files
for (const p of [{ path: __dirname + "/../func/" }]) {
const recs = fs.readdirSync(p.path);
for (const r of recs) {
if (!r.endsWith(".fc")) {
continue;
// Verify projects
for (const pkgPath of glob.sync(
path.normalize(
path.resolve(__dirname, "..", "examples", "output", "*.pkg"),
),
)) {
const res = await verify({
pkg: fs.readFileSync(pkgPath, "utf-8"),
});
if (!res.ok) {
throw new Error(`Failed to verify ${pkgPath}: ${res.error}`);
}
}

// Precompile
console.log("Processing " + p.path + r);
let c: FuncCompilationResult;
try {
const stdlibPath = path.resolve(
__dirname,
"..",
"stdlib",
"stdlib.fc",
);
const stdlib = fs.readFileSync(stdlibPath, "utf-8");
const code = fs.readFileSync(p.path + r, "utf-8");
c = await funcCompile({
entries: [stdlibPath, p.path + r],
sources: [
{
path: stdlibPath,
content: stdlib,
},
{
path: p.path + r,
content: code,
},
],
logger: consoleLogger,
});
if (!c.ok) {
console.error(c.log);
process.exit(1);
// Compile func files
for (const p of [{ path: path.join(__dirname, "..", "func") }]) {
const files = fs.readdirSync(p.path);
for (const file of files) {
if (!file.endsWith(".fc")) {
continue;
}
} catch (e) {
console.error(e);
console.error("Failed");
process.exit(1);
}
fs.writeFileSync(p.path + r + ".fift", c.fift!);
fs.writeFileSync(p.path + r + ".cell", c.output!);

// Cell -> Fift decompiler
const source = decompileAll({ src: c.output! });
fs.writeFileSync(p.path + r + ".rev.fift", source);
// Precompile
const funcFileFullPath = path.join(p.path, file);
logger.info(`Processing ${funcFileFullPath}`);
let c: FuncCompilationResult;
try {
const stdlibPath = path.resolve(
__dirname,
"..",
"stdlib",
"stdlib.fc",
);
const stdlib = fs.readFileSync(stdlibPath, "utf-8");
const code = fs.readFileSync(funcFileFullPath, "utf-8");
c = await funcCompile({
entries: [stdlibPath, funcFileFullPath],
sources: [
{
path: stdlibPath,
content: stdlib,
},
{
path: funcFileFullPath,
content: code,
},
],
logger: logger,
});
if (!c.ok) {
logger.error(c.log);
throw new Error(
`FunC compilation failed for ${funcFileFullPath}`,
);
}
} catch (e) {
logger.error(e as Error);
logger.error(`Failed for ${funcFileFullPath}`);
throw e;
}
fs.writeFileSync(funcFileFullPath + ".fift", c.fift!);
fs.writeFileSync(funcFileFullPath + ".cell", c.output!);

// Cell -> Fift decompiler
const source = decompileAll({ src: c.output! });
fs.writeFileSync(funcFileFullPath + ".rev.fift", source);
}
}
} catch (error) {
logger.error(error as Error);
process.exit(1);
}
})();
12 changes: 8 additions & 4 deletions src/browser.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { Config, verifyConfig } from "./config/parseConfig";
import { TactLogger } from "./logger";
import { Logger } from "./logger";
import { build } from "./pipeline/build";
import { createVirtualFileSystem } from "./vfs/createVirtualFileSystem";

export async function run(args: {
config: Config;
files: Record<string, string>;
logger?: TactLogger | null | undefined;
logger?: Logger;
}) {
// Verify config
const config = verifyConfig(args.config);
Expand All @@ -19,14 +19,18 @@ export async function run(args: {

// Compile
let success = true;
let errorCollection: Error[] = [];
for (const p of config.projects) {
const built = await build({
config: p,
project,
stdlib,
logger: args.logger,
});
success = success && built;
success = success && built.ok;
if (!built.ok) {
errorCollection = { ...errorCollection, ...built.error };
}
}
return success;
return { ok: success, error: errorCollection };
}
7 changes: 7 additions & 0 deletions src/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,3 +104,10 @@ export function idTextErr(
}
return `"${ident.text}"`;
}

export type TactErrorCollection =
| Error
| TactParseError
| TactCompilationError
| TactInternalCompilerError
| TactConstEvalError;
4 changes: 2 additions & 2 deletions src/func/funcCompile.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import fs from "fs";
import path from "path";
import { consoleLogger } from "../logger";
import { Logger } from "../logger";
import { funcCompile } from "./funcCompile";
import files from "../imports/stdlib";

Expand All @@ -22,7 +22,7 @@ describe("funcCompile", () => {
},
{ path: "/small.fc", content: source },
],
logger: consoleLogger,
logger: new Logger(),
});
expect(res.ok).toBe(true);
});
Expand Down
7 changes: 3 additions & 4 deletions src/func/funcCompile.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { TactLogger } from "../logger";
import { errorToString } from "../utils/errorToString";
import { Logger } from "../logger";

// Wasm Imports
// eslint-disable-next-line @typescript-eslint/no-var-requires
Expand Down Expand Up @@ -60,7 +59,7 @@ type CompileResult =
export async function funcCompile(args: {
entries: string[];
sources: { path: string; content: string }[];
logger: TactLogger;
logger: Logger;
}): Promise<FuncCompilationResult> {
// Parameters
const files: string[] = args.entries;
Expand Down Expand Up @@ -181,7 +180,7 @@ export async function funcCompile(args: {
}
}
} catch (e) {
args.logger.error(errorToString(e));
args.logger.error(e as Error);
throw Error("Unexpected compiler response");
} finally {
for (const i of allocatedFunctions) {
Expand Down
92 changes: 85 additions & 7 deletions src/logger.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,87 @@
export type TactLogger = {
log: (message: string) => void;
error: (message: string) => void;
};
export enum LogLevel {
/** Logging is turned off */
NONE,
/** Logs only error messages */
ERROR,
/** Logs warning and error messages */
WARN,
/** Logs informational, warning, and error messages */
INFO,
/** Logs debugging, informational, warning, and error messages */
DEBUG,
}

type messageType = string | Error;

export const consoleLogger: TactLogger = {
log: console.log,
error: console.error,
export interface LogMethods {
debug: (message: messageType) => void;
info: (message: messageType) => void;
warn: (message: messageType) => void;
error: (message: messageType) => void;
}

const logLevelToMethodName: { [key in LogLevel]: keyof LogMethods | null } = {
[LogLevel.NONE]: null,
[LogLevel.ERROR]: "error",
[LogLevel.WARN]: "warn",
[LogLevel.INFO]: "info",
[LogLevel.DEBUG]: "debug",
};

function getLoggingMethod(level: LogLevel): keyof LogMethods | null {
return logLevelToMethodName[level];
}

export class Logger {
private level: LogLevel;
private logMethods: LogMethods;

constructor(level: LogLevel = LogLevel.INFO) {
this.level = level;
this.logMethods = {
debug: console.log,
info: console.log,
warn: console.warn,
error: console.error,
};
}

protected log(level: LogLevel, message: messageType) {
if (this.level === LogLevel.NONE) {
return;
}

if (message instanceof Error) {
message = message.stack ?? message.message;
} else {
message = message.toString();
}

if (level > this.level) return;

const loggingMethod = getLoggingMethod(level);
if (!loggingMethod) return;

this.logMethods[loggingMethod](message);
}

debug(message: messageType) {
this.log(LogLevel.DEBUG, message);
}

info(message: messageType) {
this.log(LogLevel.INFO, message);
}

warn(message: messageType) {
this.log(LogLevel.WARN, message);
}

error(message: messageType) {
this.log(LogLevel.ERROR, message);
}

setLevel(level: LogLevel) {
this.level = level;
}
}
Loading
Loading