Merge pull request #8627 from keymanapp/feat/developer/8150-kmc-package-messages

feat(developer): add CompilerMessages support to kmc-package
This commit is contained in:
Marc Durdin 2023-04-20 13:45:18 +10:00 committed by GitHub
commit c129dfe9a2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 131 additions and 16 deletions

View file

@ -6,6 +6,8 @@ import KEYMAN_VERSION from "@keymanapp/keyman-version";
import type { KpsFile, KpsFileContentFile, KpsFileInfo, KpsFileKeyboard, KpsFileLanguage, KpsFileLexicalModel, KpsFileOptions, KpsPackage } from './kps-file.js';
import type { KmpJsonFile, KmpJsonFileInfo, KmpJsonFileLanguage, KmpJsonFileOptions } from './kmp-json-file.js';
import { CompilerCallbacks } from 'common/web/types/build/src/main.js';
import { CompilerMessages } from './messages.js';
export { type KmpJsonFile } from './kmp-json-file.js';
@ -13,6 +15,9 @@ const FILEVERSION_KMP_JSON = '12.0';
export default class KmpCompiler {
constructor(private callbacks: CompilerCallbacks) {
}
public transformKpsToKmpObject(kpsString: string, kpsPath: string): KmpJsonFile {
// Load the KPS data from XML as JS structured data.
@ -234,7 +239,8 @@ export default class KmpCompiler {
data.files = [];
}
data.files.forEach(function(value) {
let failed = false;
data.files.forEach((value) => {
// Get the path of the file
let filename = value.name;
@ -246,7 +252,7 @@ export default class KmpCompiler {
if(path.isAbsolute(value.name)) {
// absolute paths are not very cross-platform compatible -- we are going to have trouble
// with path separators and roots
// TODO: emit a warning
this.callbacks.reportMessage(CompilerMessages.Warn_AbsolutePath({filename: value.name}));
} else {
// Transform separators to platform separators -- kps files may use
// either / or \, although older kps files were always \.
@ -258,13 +264,31 @@ export default class KmpCompiler {
filename = path.resolve(basePath, filename);
}
const basename = path.basename(filename);
let data = fs.readFileSync(filename);
if(!fs.existsSync(filename)) {
this.callbacks.reportMessage(CompilerMessages.Error_FileDoesNotExist({filename: filename}));
failed = true;
return;
}
let data;
try {
data = fs.readFileSync(filename);
} catch(e) {
this.callbacks.reportMessage(CompilerMessages.Error_FileCouldNotBeRead({filename: filename, e: e}));
failed = true;
return;
}
zip.file(basename, data);
// Remove path data from files before JSON save
value.name = basename;
});
if(failed) {
return null;
}
zip.file(kmpJsonFileName, JSON.stringify(data, null, 2));
// Generate kmp file

View file

@ -0,0 +1,23 @@
import { CompilerErrorNamespace, CompilerErrorSeverity, CompilerMessageSpec as m } from "@keymanapp/common-types";
const Namespace = CompilerErrorNamespace.PackageCompiler;
// const SevInfo = CompilerErrorSeverity.Info | Namespace;
// const SevHint = CompilerErrorSeverity.Hint | Namespace;
const SevWarn = CompilerErrorSeverity.Warn | Namespace;
const SevError = CompilerErrorSeverity.Error | Namespace;
const SevFatal = CompilerErrorSeverity.Fatal | Namespace;
export class CompilerMessages {
static Fatal_UnexpectedException = (o:{e: any}) => m(this.FATAL_UnexpectedException, `Unexpected exception: ${(o.e ?? 'unknown error').toString()}\n\nCall stack:\n${(o.e instanceof Error ? o.e.stack : (new Error()).stack)}`);
static FATAL_UnexpectedException = SevFatal | 0x0001;
static Warn_AbsolutePath = (o:{filename: string}) => m(this.WARN_AbsolutePath, `File ${o.filename} has an absolute path, which is not portable`);
static WARN_AbsolutePath = SevWarn | 0x0002;
static Error_FileDoesNotExist = (o:{filename: string}) => m(this.ERROR_FileDoesNotExist, `File ${o.filename} does not exist.`);
static ERROR_FileDoesNotExist = SevError | 0x0003;
static Error_FileCouldNotBeRead = (o:{filename: string; e: any}) => m(this.ERROR_FileCouldNotBeRead, `File ${o.filename} could not be read: ${(o.e ?? 'unknown error').toString()}.`);
static ERROR_FileCouldNotBeRead = SevError | 0x0004;
}

View file

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<Package>
<System>
<KeymanDeveloperVersion>15.0.266.0</KeymanDeveloperVersion>
<FileVersion>7.0</FileVersion>
</System>
<Options>
<ExecuteProgram></ExecuteProgram>
<ReadMeFile></ReadMeFile>
<GraphicFile></GraphicFile>
<MSIFileName></MSIFileName>
<MSIOptions></MSIOptions>
<FollowKeyboardVersion/>
</Options>
<StartMenu>
<Folder></Folder>
<Items/>
</StartMenu>
<Info>
<Name URL="">Absolute Path</Name>
</Info>
<Files>
<File>
<Name>\build\absolute_path.kmx</Name>
<Description>File absolute_path.kmx</Description>
<CopyLocation>0</CopyLocation>
<FileType>.kmx</FileType>
</File>
</Files>
</Package>

View file

@ -7,6 +7,9 @@ import {makePathToFixture} from './helpers/index.js';
import JSZip from 'jszip';
import KEYMAN_VERSION from "@keymanapp/keyman-version";
import { type KmpJsonFile } from '../src/kmp-json-file.js';
import { TestCompilerCallbacks } from '@keymanapp/developer-test-helpers';
import { CompilerMessages } from '../src/messages.js';
describe('KmpCompiler', function () {
const MODELS : string[] = [
@ -14,7 +17,8 @@ describe('KmpCompiler', function () {
'withfolders.qaa.sencoten',
];
let kmpCompiler = new KmpCompiler();
const callbacks = new TestCompilerCallbacks();
let kmpCompiler = new KmpCompiler(callbacks);
for (let modelID of MODELS) {
const kpsPath = modelID.includes('withfolders') ?
@ -78,11 +82,13 @@ describe('KmpCompiler', function () {
}
it('should generates a valid .kmp (zip) file', async function() {
this.timeout(10000); // building a zip file can sometimes be slow
// const kmpPath = makePathToFixture('khmer_angkor', 'build', 'khmer_angkor.kmp');
const kpsPath = makePathToFixture('khmer_angkor', 'source', 'khmer_angkor.kps');
const kmpJsonRefPath = makePathToFixture('khmer_angkor', 'ref', 'kmp.json');
const kmpCompiler = new KmpCompiler();
const kmpCompiler = new KmpCompiler(callbacks);
const source = fs.readFileSync(kpsPath, 'utf-8');
const kmpJsonFixture: KmpJsonFile = JSON.parse(fs.readFileSync(kmpJsonRefPath, 'utf-8'));
@ -117,4 +123,27 @@ describe('KmpCompiler', function () {
assert.deepEqual(kmpJsonData, kmpJsonFixture);
});
it('should warn on absolute paths', async function() {
this.timeout(10000); // building a zip file can sometimes be slow
callbacks.clear();
// const kmpPath = makePathToFixture('khmer_angkor', 'build', 'khmer_angkor.kmp');
const kpsPath = makePathToFixture('absolute_path', 'source', 'absolute_path.kps');
const kmpCompiler = new KmpCompiler(callbacks);
const source = fs.readFileSync(kpsPath, 'utf-8');
let kmpJson: KmpJsonFile = null;
assert.doesNotThrow(() => {
kmpJson = kmpCompiler.transformKpsToKmpObject(source, kpsPath);
});
await assert.isNull(kmpCompiler.buildKmpFile(kpsPath, kmpJson));
assert.lengthOf(callbacks.messages, 2);
assert.deepEqual(callbacks.messages[0].code, CompilerMessages.WARN_AbsolutePath);
assert.deepEqual(callbacks.messages[1].code, CompilerMessages.ERROR_FileDoesNotExist); //TODO: this should be a file-missing-error
});
});

View file

@ -1,6 +1,8 @@
import * as fs from 'fs';
import { BuildActivity, BuildActivityOptions } from './BuildActivity.js';
import KmpCompiler from '@keymanapp/kmc-package';
import { CompilerCallbacks } from '@keymanapp/common-types';
import { NodeCompilerCallbacks } from 'src/util/NodeCompilerCallbacks.js';
export class BuildPackage extends BuildActivity {
public get name(): string { return 'Package'; }
@ -8,28 +10,31 @@ export class BuildPackage extends BuildActivity {
public get compiledExtension(): string { return '.kmp'; }
public get description(): string { return 'Build a Keyman package'; }
public async build(infile: string, options: BuildActivityOptions): Promise<boolean> {
let outfile = this.getOutputFilename(infile, options);
const c: CompilerCallbacks = new NodeCompilerCallbacks();
const outfile = this.getOutputFilename(infile, options);
//
// Load .kps source data
//
let kpsString: string = fs.readFileSync(infile, 'utf8');
let kmpCompiler = new KmpCompiler();
let kmpJsonData = kmpCompiler.transformKpsToKmpObject(kpsString, infile);
const kpsString: string = fs.readFileSync(infile, 'utf8');
const kmpCompiler = new KmpCompiler(c);
const kmpJsonData = kmpCompiler.transformKpsToKmpObject(kpsString, infile);
if(!kmpJsonData) {
return false;
}
//
// Build the .kmp package file
//
let data = await kmpCompiler.buildKmpFile(infile, kmpJsonData);
if(data) {
fs.writeFileSync(outfile, data, 'binary');
} else {
// TODO error logging
const data = await kmpCompiler.buildKmpFile(infile, kmpJsonData);
if(!data) {
return false;
}
fs.writeFileSync(outfile, data, 'binary');
return true;
}
}

View file

@ -10,6 +10,7 @@ import KmpCompiler from '@keymanapp/kmc-package';
import { ModelInfoOptions as ModelInfoOptions, writeMergedModelMetadataFile } from '@keymanapp/kmc-model-info';
import { SysExits } from './util/sysexits.js';
import KEYMAN_VERSION from "@keymanapp/keyman-version";
import { NodeCompilerCallbacks } from './util/NodeCompilerCallbacks.js';
let inputFilename: string;
const program = new Command();
@ -45,8 +46,9 @@ let jsFilename = program.opts().jsFilename ? program.opts().jsFilename : path.jo
// Load .kps source data
//
const callbacks = new NodeCompilerCallbacks();
let kpsString: string = fs.readFileSync(kpsFilename, 'utf8');
let kmpCompiler = new KmpCompiler();
let kmpCompiler = new KmpCompiler(callbacks);
let kmpJsonData = kmpCompiler.transformKpsToKmpObject(kpsString, kpsFilename);
//

View file

@ -8,6 +8,7 @@ import { Command } from 'commander';
import KmpCompiler from '@keymanapp/kmc-package';
import { SysExits } from './util/sysexits.js';
import KEYMAN_VERSION from "@keymanapp/keyman-version";
import { NodeCompilerCallbacks } from './util/NodeCompilerCallbacks.js';
let inputFilename: string;
const program = new Command();
@ -34,8 +35,9 @@ let outputFilename: string = program.opts().outFile ? program.opts().outFile : i
// Load .kps source data
//
const callbacks = new NodeCompilerCallbacks();
let kpsString: string = fs.readFileSync(inputFilename, 'utf8');
let kmpCompiler = new KmpCompiler();
let kmpCompiler = new KmpCompiler(callbacks);
let kmpJsonData = kmpCompiler.transformKpsToKmpObject(kpsString, inputFilename);
//