From 9201baa65bb6974220bff99d4d764b462d9cfa07 Mon Sep 17 00:00:00 2001 From: Darcy Wong Date: Wed, 5 Nov 2025 09:49:21 +0700 Subject: [PATCH 01/33] fix(android): Use Alert DIalog if keyboard pkg missing js files Fixes: #15097 Fixes: KEYMAN-ANDROID-251 --- .../src/main/java/com/tavultesoft/kmapro/PackageActivity.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/PackageActivity.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/PackageActivity.java index 28517ef04a..c065eec5af 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/PackageActivity.java +++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/PackageActivity.java @@ -85,7 +85,8 @@ public class PackageActivity extends BaseActivity implements if (pkgTarget.equals(PackageProcessor.PP_TARGET_LEXICAL_MODELS)) { kmpProcessor = new LexicalModelPackageProcessor(resourceRoot); } else if (!pkgTarget.equals(PackageProcessor.PP_TARGET_KEYBOARDS)) { - showErrorToast(getString(R.string.no_targets_to_install)); + // Not using showErrorToast to avoid cluttering Sentry with noise + showErrorDialog(context, pkgId, getString(R.string.no_targets_to_install)); return; } tempPackagePath = kmpProcessor.unzipKMP(kmpFile); From 28d3378589482347fe57e9ba016bd8e8bbd0ca11 Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Fri, 7 Nov 2025 09:24:28 +0100 Subject: [PATCH 02/33] fix(web): add unit tests for DeadkeyTracker and fix a few bugs - initialize `Deadkey.matched` in c'tor - fix `Deadkey.equal` method - verify deadkey exists before removing it in `DeadkeyTracker.remove` - add unit tests for DeadkeyTracker Test-bot: skip --- web/src/engine/js-processor/build.sh | 7 +- web/src/engine/js-processor/src/deadkeys.ts | 20 +- web/src/engine/js-processor/src/index.ts | 9 + .../engine/js-processor/src/kbdInterface.ts | 2 + .../js-processor/deadkeyTracker.tests.ts | 283 ++++++++++++++++++ 5 files changed, 311 insertions(+), 10 deletions(-) create mode 100644 web/src/test/auto/headless/engine/js-processor/deadkeyTracker.tests.ts diff --git a/web/src/engine/js-processor/build.sh b/web/src/engine/js-processor/build.sh index 0fc0ef7e7a..6d5989b515 100755 --- a/web/src/engine/js-processor/build.sh +++ b/web/src/engine/js-processor/build.sh @@ -36,7 +36,12 @@ do_build () { --format esm } +do_test() { + test-headless "${SUBPROJECT_NAME}" "" + test-headless-typescript "${SUBPROJECT_NAME}" +} + builder_run_action configure node_select_version_and_npm_ci builder_run_action clean rm -rf "${KEYMAN_ROOT}/web/build/${SUBPROJECT_NAME}" builder_run_action build do_build -builder_run_action test test-headless "${SUBPROJECT_NAME}" "" +builder_run_action test do_test diff --git a/web/src/engine/js-processor/src/deadkeys.ts b/web/src/engine/js-processor/src/deadkeys.ts index 53cf0d5995..8c3effc7cb 100644 --- a/web/src/engine/js-processor/src/deadkeys.ts +++ b/web/src/engine/js-processor/src/deadkeys.ts @@ -11,10 +11,11 @@ export class Deadkey { this.p = pos; this.d = id; this.o = Deadkey.ordinalSeed++; + this.matched = 0; } - match(p: number, d: number): boolean { - const result:boolean = (this.p == p && this.d == d); + match(pos: number, deadkey: number): boolean { + const result:boolean = (this.p == pos && this.d == deadkey); return result; } @@ -39,7 +40,7 @@ export class Deadkey { } equal(other: Deadkey) { - return this.d == other.d && this.p == other.d && this.o == other.o; + return this.d == other.d && this.p == other.p && this.o == other.o; } /** @@ -82,11 +83,11 @@ export class DeadkeyTracker { * Scope Public * @param {number} caretPos current cursor position * @param {number} n expected offset of deadkey from cursor - * @param {number} d deadkey + * @param {number} deadkey deadkey * @return {boolean} True if deadkey found selected context matches val * Description Match deadkey at current cursor position */ - isMatch(caretPos: number, n: number, d: number): boolean { + isMatch(caretPos: number, n: number, deadkey: number): boolean { if(this.dks.length == 0) { return false; // I3318 } @@ -96,7 +97,7 @@ export class DeadkeyTracker { for(let i = 0; i < this.dks.length; i++) { // Don't re-match an already-matched deadkey. It's possible to have two identical // entries, and they should be kept separately. - if(this.dks[i].match(n, d) && !this.dks[i].matched) { + if(this.dks[i].match(n, deadkey) && !this.dks[i].matched) { this.dks[i].set(); // Assumption: since we match the first possible entry in the array, we // match the entry with the lower ordinal - the 'first' deadkey in the position. @@ -115,7 +116,9 @@ export class DeadkeyTracker { remove(dk: Deadkey) { const index = this.dks.indexOf(dk); - this.dks.splice(index, 1); + if (index > -1) { + this.dks.splice(index, 1); + } } clear() { @@ -161,7 +164,6 @@ export class DeadkeyTracker { } const otherDks = other.dks; - const matchedDks: Deadkey[] = []; for(const dk of this.dks) { const match = otherDks.find((otherDk) => dk.equal(otherDk)); @@ -170,7 +172,7 @@ export class DeadkeyTracker { } } - return matchedDks.length == otherDks.length; + return true; } count(): number { diff --git a/web/src/engine/js-processor/src/index.ts b/web/src/engine/js-processor/src/index.ts index 1755499119..42ad8d94d1 100644 --- a/web/src/engine/js-processor/src/index.ts +++ b/web/src/engine/js-processor/src/index.ts @@ -9,3 +9,12 @@ export { default as OutputTarget } from "./outputTarget.js"; export * from "./outputTarget.js"; export { Mock } from "./mock.js"; export * from "./stringDivergence.js"; + +import { DeadkeyTracker } from './deadkeys.js'; + +/** + * these are exported only for unit tests, do not use + */ +export const unitTestEndpoints = { + DeadkeyTracker, +}; diff --git a/web/src/engine/js-processor/src/kbdInterface.ts b/web/src/engine/js-processor/src/kbdInterface.ts index ff4fffb467..1b4d7747c1 100644 --- a/web/src/engine/js-processor/src/kbdInterface.ts +++ b/web/src/engine/js-processor/src/kbdInterface.ts @@ -613,6 +613,8 @@ export default class KeyboardInterface extends KeyboardHarness { * @param {number} d deadkey * @return {boolean} True if deadkey found selected context matches val * Description Match deadkey at current cursor position + * + * Deprecated: use `fullContextMatch` instead. */ deadkeyMatch(n: number, outputTarget: OutputTarget, d: number): boolean { return outputTarget.hasDeadkeyMatch(n, d); diff --git a/web/src/test/auto/headless/engine/js-processor/deadkeyTracker.tests.ts b/web/src/test/auto/headless/engine/js-processor/deadkeyTracker.tests.ts new file mode 100644 index 0000000000..ba4f0d403e --- /dev/null +++ b/web/src/test/auto/headless/engine/js-processor/deadkeyTracker.tests.ts @@ -0,0 +1,283 @@ +/* + * Keyman is copyright (C) SIL Global. MIT License. + */ + +import { assert } from 'chai'; +import { Deadkey, unitTestEndpoints } from 'keyman/engine/js-processor'; + +const DeadkeyTracker = unitTestEndpoints.DeadkeyTracker; + +describe('DeadkeyTracker', function() { + it('can add deadkeys and return count', function() { + const tracker = new DeadkeyTracker(); + tracker.add(new Deadkey(3, 1)); + tracker.add(new Deadkey(5, 2)); + tracker.add(new Deadkey(10, 3)); + + assert.equal(tracker.dks.length, 3); + assert.equal(tracker.count(), 3); + }); + + it('sorts deadkeys by position, latest first', function() { + const tracker = new DeadkeyTracker(); + tracker.add(new Deadkey(5, 2)); + tracker.add(new Deadkey(3, 1)); + tracker.add(new Deadkey(10, 3)); + + const sortedDeadkeys = tracker.toSortedArray(); + + assert.equal(sortedDeadkeys[0].p, 10); + assert.equal(sortedDeadkeys[1].p, 5); + assert.equal(sortedDeadkeys[2].p, 3); + }); + + it('can adjust positions', function () { + const tracker = new DeadkeyTracker(); + tracker.add(new Deadkey(3, 1)); + tracker.add(new Deadkey(5, 2)); + tracker.add(new Deadkey(10, 3)); + + tracker.adjustPositions(4, 2); // adjust deadkeys after position 4 by +2 + + const sortedDeadkeys = tracker.toSortedArray(); + assert.equal(sortedDeadkeys[0].p, 12); // 10 + 2 + assert.equal(sortedDeadkeys[1].p, 7); // 5 + 2 + assert.equal(sortedDeadkeys[2].p, 3); // unchanged + }); + + describe('equal', function () { + it('treats cloned tracker as equal', function () { + const tracker1 = new DeadkeyTracker(); + tracker1.add(new Deadkey(3, 1)); + tracker1.add(new Deadkey(5, 2)); + tracker1.add(new Deadkey(10, 3)); + + const tracker2 = tracker1.clone(); + + assert.isTrue(tracker1.equal(tracker2)); + }); + + it('treats two trackers with same deadkeys create separately not as equal', function () { + const tracker1 = new DeadkeyTracker(); + tracker1.add(new Deadkey(3, 1)); + tracker1.add(new Deadkey(5, 2)); + tracker1.add(new Deadkey(10, 3)); + + const tracker2 = new DeadkeyTracker(); + tracker2.add(new Deadkey(3, 1)); + tracker2.add(new Deadkey(5, 2)); + tracker2.add(new Deadkey(10, 3)); + + assert.isFalse(tracker1.equal(tracker2)); + }); + + it('treats two trackers with same deadkeys in different order not as equal', function () { + const tracker1 = new DeadkeyTracker(); + tracker1.add(new Deadkey(5, 2)); + tracker1.add(new Deadkey(3, 1)); + tracker1.add(new Deadkey(10, 3)); + + const tracker2 = new DeadkeyTracker(); + tracker2.add(new Deadkey(10, 3)); + tracker2.add(new Deadkey(5, 2)); + tracker2.add(new Deadkey(3, 1)); + + assert.isFalse(tracker1.equal(tracker2)); + }); + + it('treats two trackers with same deadkeys in different places not as equal', function () { + const tracker1 = new DeadkeyTracker(); + tracker1.add(new Deadkey(7, 2)); + tracker1.add(new Deadkey(3, 1)); + tracker1.add(new Deadkey(12, 3)); + + const tracker2 = new DeadkeyTracker(); + tracker2.add(new Deadkey(10, 3)); + tracker2.add(new Deadkey(5, 2)); + tracker2.add(new Deadkey(3, 1)); + + assert.isFalse(tracker1.equal(tracker2)); + }); + + it('treats a tracker with additional deadkeys not as equal', function () { + const tracker1 = new DeadkeyTracker(); + tracker1.add(new Deadkey(3, 1)); + tracker1.add(new Deadkey(5, 2)); + tracker1.add(new Deadkey(10, 3)); + + const tracker2 = tracker1.clone(); + tracker2.add(new Deadkey(15, 4)); + + assert.isFalse(tracker1.equal(tracker2)); + }); + }); + + it('can clear deadkeys', function() { + const tracker = new DeadkeyTracker(); + tracker.add(new Deadkey(3, 1)); + tracker.add(new Deadkey(5, 2)); + tracker.add(new Deadkey(10, 3)); + + tracker.clear(); + + assert.equal(tracker.count(), 0); + }); + + describe('remove', function () { + it('can remove existing deadkey', function () { + const tracker = new DeadkeyTracker(); + tracker.add(new Deadkey(3, 1)); + const dk = new Deadkey(5, 2); + tracker.add(dk); + tracker.add(new Deadkey(10, 3)); + + tracker.remove(dk); + + assert.equal(tracker.count(), 2); + assert.equal(tracker.dks[0].p, 3); + assert.equal(tracker.dks[1].p, 10); + }); + + it('does not remove similar deadkey', function () { + const tracker = new DeadkeyTracker(); + tracker.add(new Deadkey(3, 1)); + tracker.add(new Deadkey(5, 2)); + tracker.add(new Deadkey(10, 3)); + + tracker.remove(new Deadkey(5, 2)); + + assert.equal(tracker.count(), 3); + assert.equal(tracker.dks[0].p, 3); + assert.equal(tracker.dks[1].p, 5); + assert.equal(tracker.dks[2].p, 10); + }); + }); + + it('sorts deadkeys in cloned tracker', function () { + const tracker1 = new DeadkeyTracker(); + tracker1.add(new Deadkey(5, 1)); + tracker1.add(new Deadkey(3, 2)); + tracker1.add(new Deadkey(10, 3)); + + const tracker2 = tracker1.clone(); + + assert.equal(tracker2.count(), 3); + assert.equal(tracker2.dks[0].p, 10); + assert.equal(tracker2.dks[1].p, 5); + assert.equal(tracker2.dks[2].p, 3); + }); + + it('can delete matched deadkeys', function () { + const dk1 = new Deadkey(5, 1); + const dk2 = new Deadkey(3, 2); + const dk3 = new Deadkey(10, 3); + const tracker = new DeadkeyTracker(); + tracker.add(dk1); + tracker.add(dk2); + tracker.add(dk3); + dk2.set(); + dk3.set(); + + tracker.deleteMatched(); + + assert.equal(tracker.count(), 1); + assert.equal(tracker.dks[0].p, 5); + }); + + it('can reset matched deadkeys', function () { + const dk1 = new Deadkey(5, 1); + const dk2 = new Deadkey(3, 2); + const dk3 = new Deadkey(10, 3); + const tracker = new DeadkeyTracker(); + tracker.add(dk1); + tracker.add(dk2); + tracker.add(dk3); + dk2.set(); + dk3.set(); + + tracker.resetMatched(); + + assert.equal(tracker.count(), 3); + assert.equal(tracker.dks[0].matched, 0); + assert.equal(tracker.dks[1].matched, 0); + assert.equal(tracker.dks[2].matched, 0); + }); + + describe('isMatch', function () { + it('returns false without having any deadkeys', function () { + const tracker = new DeadkeyTracker(); + + assert.isFalse(tracker.isMatch(0, 1, 2)); + }); + + it('returns false for non-matching deadkey', function () { + const tracker = new DeadkeyTracker(); + tracker.add(new Deadkey(3, 1)); + tracker.add(new Deadkey(5, 2)); + tracker.add(new Deadkey(10, 3)); + + assert.isFalse(tracker.isMatch(0, 88, 99)); + + assert.equal(tracker.dks[0].matched, 0); + assert.equal(tracker.dks[1].matched, 0); + assert.equal(tracker.dks[2].matched, 0); + }); + + it('returns false if offset is wrong', function () { + const tracker = new DeadkeyTracker(); + tracker.add(new Deadkey(3, 1)); + tracker.add(new Deadkey(5, 2)); + tracker.add(new Deadkey(10, 3)); + + // REVIEW: this behavior seems odd + assert.isFalse(tracker.isMatch(0, 3, 1)); + + assert.equal(tracker.dks[0].matched, 0); + assert.equal(tracker.dks[1].matched, 0); + assert.equal(tracker.dks[2].matched, 0); + }); + + it('returns true for matching deadkey and sets matched flag on deadkey', function () { + const tracker = new DeadkeyTracker(); + tracker.add(new Deadkey(3, 1)); + tracker.add(new Deadkey(5, 2)); + tracker.add(new Deadkey(10, 3)); + + // REVIEW: this behavior seems odd + assert.isTrue(tracker.isMatch(0, -3, 1)); + + assert.equal(tracker.dks[0].matched, 1); + assert.equal(tracker.dks[1].matched, 0); + assert.equal(tracker.dks[2].matched, 0); + }); + + it('returns true for matching deadkey if caret on dk', function () { + const tracker = new DeadkeyTracker(); + tracker.add(new Deadkey(3, 1)); + tracker.add(new Deadkey(5, 2)); + tracker.add(new Deadkey(10, 3)); + + assert.isTrue(tracker.isMatch(3, 0, 1)); + + assert.equal(tracker.dks[0].matched, 1); + assert.equal(tracker.dks[1].matched, 0); + assert.equal(tracker.dks[2].matched, 0); + }); + + it('returns false for matching deadkey if already matched and resets matched flag', function () { + const tracker = new DeadkeyTracker(); + tracker.add(new Deadkey(3, 1)); + tracker.add(new Deadkey(5, 2)); + tracker.add(new Deadkey(10, 3)); + tracker.dks[0].matched = 1; + tracker.dks[1].matched = 1; + tracker.dks[2].matched = 1; + + assert.isFalse(tracker.isMatch(0, -3, 1)); + + assert.equal(tracker.dks[0].matched, 0); + assert.equal(tracker.dks[1].matched, 0); + assert.equal(tracker.dks[2].matched, 0); + }); + }); +}); From 98ed960c8f9ad84de26cf16c99a7a26e338fd7bb Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Fri, 7 Nov 2025 13:22:45 +0100 Subject: [PATCH 03/33] fix(web): address code review comments --- web/src/engine/js-processor/src/deadkeys.ts | 11 ++-- .../js-processor/deadkeyTracker.tests.ts | 53 +++++++++++++++++++ 2 files changed, 59 insertions(+), 5 deletions(-) diff --git a/web/src/engine/js-processor/src/deadkeys.ts b/web/src/engine/js-processor/src/deadkeys.ts index 8c3effc7cb..e2f17ab3ad 100644 --- a/web/src/engine/js-processor/src/deadkeys.ts +++ b/web/src/engine/js-processor/src/deadkeys.ts @@ -118,6 +118,8 @@ export class DeadkeyTracker { const index = this.dks.indexOf(dk); if (index > -1) { this.dks.splice(index, 1); + } else { + console.warn('Deadkey to remove not found in DeadkeyTracker.'); } } @@ -163,11 +165,10 @@ export class DeadkeyTracker { return false; } - const otherDks = other.dks; - - for(const dk of this.dks) { - const match = otherDks.find((otherDk) => dk.equal(otherDk)); - if(!match) { + const sortedDeadkeys = this.toSortedArray(); + const otherDks = other.toSortedArray(); + for(let i = 0; i < sortedDeadkeys.length; i++) { + if(!sortedDeadkeys[i].equal(otherDks[i])) { return false; } } diff --git a/web/src/test/auto/headless/engine/js-processor/deadkeyTracker.tests.ts b/web/src/test/auto/headless/engine/js-processor/deadkeyTracker.tests.ts index ba4f0d403e..36cf439346 100644 --- a/web/src/test/auto/headless/engine/js-processor/deadkeyTracker.tests.ts +++ b/web/src/test/auto/headless/engine/js-processor/deadkeyTracker.tests.ts @@ -57,6 +57,23 @@ describe('DeadkeyTracker', function() { assert.isTrue(tracker1.equal(tracker2)); }); + it('treats two trackers with identical deadkeys as equal', function () { + const dk1 = new Deadkey(3, 1); + const dk2 = new Deadkey(5, 2); + const dk3 = new Deadkey(10, 3); + const tracker1 = new DeadkeyTracker(); + tracker1.add(dk1); + tracker1.add(dk2); + tracker1.add(dk3); + + const tracker2 = new DeadkeyTracker(); + tracker2.add(dk1); + tracker2.add(dk2); + tracker2.add(dk3); + + assert.isTrue(tracker1.equal(tracker2)); + }); + it('treats two trackers with same deadkeys create separately not as equal', function () { const tracker1 = new DeadkeyTracker(); tracker1.add(new Deadkey(3, 1)); @@ -110,6 +127,42 @@ describe('DeadkeyTracker', function() { assert.isFalse(tracker1.equal(tracker2)); }); + + it('treats non-indentical trackers with duplicate deadkeys not as equal', function () { + const dk1 = new Deadkey(3, 1); + const dk2 = new Deadkey(5, 2); + const dk3 = new Deadkey(10, 3); + const tracker1 = new DeadkeyTracker(); + tracker1.add(dk1); + tracker1.add(dk1); // add same again + tracker1.add(dk2); + + const tracker2 = new DeadkeyTracker(); + tracker2.add(dk1); + tracker2.add(dk2); + tracker2.add(dk3); + + assert.isFalse(tracker1.equal(tracker2)); + }); + + it('treats indentical trackers with duplicate deadkeys as equal', function () { + const dk1 = new Deadkey(3, 1); + const dk2 = new Deadkey(5, 2); + const dk3 = new Deadkey(10, 3); + const tracker1 = new DeadkeyTracker(); + tracker1.add(dk1); + tracker1.add(dk1); // add same again + tracker1.add(dk2); + tracker1.add(dk3); + + const tracker2 = new DeadkeyTracker(); + tracker2.add(dk1); + tracker2.add(dk1); // add same again + tracker2.add(dk2); + tracker2.add(dk3); + + assert.isTrue(tracker1.equal(tracker2)); + }); }); it('can clear deadkeys', function() { From 03831e4f252426b9978e97091f8b3ac3e97ad913 Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Fri, 7 Nov 2025 13:47:23 +0100 Subject: [PATCH 04/33] fix(web): clarify meaning of parameters for `isMatch` --- web/src/engine/js-processor/src/deadkeys.ts | 17 ++++---- .../js-processor/deadkeyTracker.tests.ts | 41 ++++++++++++++++++- 2 files changed, 49 insertions(+), 9 deletions(-) diff --git a/web/src/engine/js-processor/src/deadkeys.ts b/web/src/engine/js-processor/src/deadkeys.ts index e2f17ab3ad..16c97b1a30 100644 --- a/web/src/engine/js-processor/src/deadkeys.ts +++ b/web/src/engine/js-processor/src/deadkeys.ts @@ -82,22 +82,25 @@ export class DeadkeyTracker { * Function isMatch * Scope Public * @param {number} caretPos current cursor position - * @param {number} n expected offset of deadkey from cursor - * @param {number} deadkey deadkey - * @return {boolean} True if deadkey found selected context matches val + * @param {number} offset expected offset to add to deadkey position to get to caretPos + * @param {number} deadkey deadkey to match + * @return {boolean} True if deadkey found at offset position * Description Match deadkey at current cursor position */ - isMatch(caretPos: number, n: number, deadkey: number): boolean { + isMatch(caretPos: number, offset: number, deadkey: number): boolean { if(this.dks.length == 0) { return false; // I3318 } - const sp=caretPos; - n = sp - n; + // Calculate position of deadkey from beginning of context. + // Caret position is always after the deadkey position (although this + // doesn't really matter for the calculation). + const dkPosition = caretPos - offset; + for(let i = 0; i < this.dks.length; i++) { // Don't re-match an already-matched deadkey. It's possible to have two identical // entries, and they should be kept separately. - if(this.dks[i].match(n, deadkey) && !this.dks[i].matched) { + if (this.dks[i].match(dkPosition, deadkey) && !this.dks[i].matched) { this.dks[i].set(); // Assumption: since we match the first possible entry in the array, we // match the entry with the lower ordinal - the 'first' deadkey in the position. diff --git a/web/src/test/auto/headless/engine/js-processor/deadkeyTracker.tests.ts b/web/src/test/auto/headless/engine/js-processor/deadkeyTracker.tests.ts index 36cf439346..ecd6ed9523 100644 --- a/web/src/test/auto/headless/engine/js-processor/deadkeyTracker.tests.ts +++ b/web/src/test/auto/headless/engine/js-processor/deadkeyTracker.tests.ts @@ -282,7 +282,6 @@ describe('DeadkeyTracker', function() { tracker.add(new Deadkey(5, 2)); tracker.add(new Deadkey(10, 3)); - // REVIEW: this behavior seems odd assert.isFalse(tracker.isMatch(0, 3, 1)); assert.equal(tracker.dks[0].matched, 0); @@ -290,13 +289,51 @@ describe('DeadkeyTracker', function() { assert.equal(tracker.dks[2].matched, 0); }); + it('returns true if offset is correct and we have multiple deadkeys with same id', function () { + const tracker = new DeadkeyTracker(); + tracker.add(new Deadkey(3, 1)); + tracker.add(new Deadkey(5, 1)); + tracker.add(new Deadkey(10, 3)); + + assert.isTrue(tracker.isMatch(7, 2, 1)); + + assert.equal(tracker.dks[0].matched, 0); + assert.equal(tracker.dks[1].matched, 1); + assert.equal(tracker.dks[2].matched, 0); + }); + + it('matches entry with lowest ordinal value', function () { + const tracker = new DeadkeyTracker(); + tracker.add(new Deadkey(3, 1)); + tracker.add(new Deadkey(3, 1)); + tracker.add(new Deadkey(10, 3)); + + assert.isTrue(tracker.isMatch(0, -3, 1)); + + assert.equal(tracker.dks[0].matched, 1); + assert.equal(tracker.dks[1].matched, 0); + assert.equal(tracker.dks[2].matched, 0); + }); + + it('matches entry with lowest ordinal value if caret is after deadkey', function () { + const tracker = new DeadkeyTracker(); + tracker.add(new Deadkey(3, 1)); + tracker.add(new Deadkey(3, 1)); + tracker.add(new Deadkey(10, 3)); + + assert.isTrue(tracker.isMatch(5, 2, 1)); + + assert.equal(tracker.dks[0].matched, 1); + assert.equal(tracker.dks[1].matched, 0); + assert.equal(tracker.dks[2].matched, 0); + }); + it('returns true for matching deadkey and sets matched flag on deadkey', function () { const tracker = new DeadkeyTracker(); tracker.add(new Deadkey(3, 1)); tracker.add(new Deadkey(5, 2)); tracker.add(new Deadkey(10, 3)); - // REVIEW: this behavior seems odd assert.isTrue(tracker.isMatch(0, -3, 1)); assert.equal(tracker.dks[0].matched, 1); From 269d619ed9c12ffc0453adfbdf6d0d8333f95cd2 Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Fri, 7 Nov 2025 17:32:33 +0100 Subject: [PATCH 05/33] docs(web): add another clarifying comment --- web/src/engine/js-processor/src/deadkeys.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/src/engine/js-processor/src/deadkeys.ts b/web/src/engine/js-processor/src/deadkeys.ts index 16c97b1a30..c8a3792771 100644 --- a/web/src/engine/js-processor/src/deadkeys.ts +++ b/web/src/engine/js-processor/src/deadkeys.ts @@ -1,6 +1,6 @@ // Defines the base Deadkey-tracking object. export class Deadkey { - p: number; // Position of deadkey + p: number; // Position of deadkey in the textstore (deadkey comes before the character at this position) d: number; // Numerical id of the deadkey o: number; // Ordinal value of the deadkey (resolves same-place conflicts) matched: number; From c573709ee663282b03e9efefe1484bf3edadda16 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Fri, 7 Nov 2025 13:51:30 +0100 Subject: [PATCH 06/33] maint(resources): add build timing report to builder This is enabled automatically for CI builds, and can be enabled with `--timing` parameter otherwise. Also, adds `builder_launch`, documentation to come. Test-bot: skip --- .github/workflows/deb-packaging.yml | 1 + linux/scripts/deb-packaging.sh | 3 + resources/build/test/test.sh | 7 +- resources/builder.inc.sh | 210 ++++++++++++++++++++++++++-- 4 files changed, 211 insertions(+), 10 deletions(-) diff --git a/.github/workflows/deb-packaging.yml b/.github/workflows/deb-packaging.yml index 28440c4211..86b07e89c0 100644 --- a/.github/workflows/deb-packaging.yml +++ b/.github/workflows/deb-packaging.yml @@ -31,6 +31,7 @@ env: STATUS_CONTEXT: 'Ubuntu Packaging' DEBFULLNAME: 'Keyman GHA packager' DEBEMAIL: 'support@keyman.com' + _builder_timings: false jobs: sourcepackage: diff --git a/linux/scripts/deb-packaging.sh b/linux/scripts/deb-packaging.sh index 97f069377b..f6f44be2d1 100755 --- a/linux/scripts/deb-packaging.sh +++ b/linux/scripts/deb-packaging.sh @@ -6,6 +6,9 @@ set -eu shopt -s inherit_errexit +# Avoid timing reports as we don't have node in this script +export _builder_timings=false + ## START STANDARD BUILD SCRIPT INCLUDE # adjust relative paths as necessary THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" diff --git a/resources/build/test/test.sh b/resources/build/test/test.sh index 12530335af..8cb7ecfc18 100755 --- a/resources/build/test/test.sh +++ b/resources/build/test/test.sh @@ -2,6 +2,9 @@ set -eu +# Avoid timing reports in unit tests +export _builder_timings=false + ## START STANDARD BUILD SCRIPT INCLUDE # adjust relative paths as necessary THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" @@ -243,7 +246,7 @@ if [[ $KEYMAN_VERSION_ENVIRONMENT == "local" ]]; then expected="$(builder_echo grey "Local build environment detected: setting --debug")"$'\n'"$(builder_echo setmark "test.sh parameters: <--feature xyzzy --bar abc --baz def test --debug>")" fi if [[ "${parse_output[*]}" != "${expected}" ]]; then - builder_die "FAIL: Wrong output for '--feature xyzzy --bar abc --baz def test':\n Actual : ${parse_output[*]}\n Expected: ${expected}" + builder_die "FAIL: Wrong output for '--feature xyzzy --bar abc --baz def test':\n Actual : '${parse_output[*]}'\n Expected: '${expected}'" fi #---------------------------------------------------------------------- @@ -436,7 +439,7 @@ echo # Finally, run with --help so we can see what it looks like; note: # builder_parse calls `exit 0` on a --help run, so running in a subshell echo -e "${COLOR_BLUE}## Testing --help${COLOR_RESET}" - builder_parse --no-color --help + builder_parse --no-color --help --no-timings ) || builder_die "FAIL: builder-parse unexpectedly returned failure code $?" echo -e "${COLOR_GREEN}======================================================${COLOR_RESET}" diff --git a/resources/builder.inc.sh b/resources/builder.inc.sh index c899383290..7d9c5a0256 100755 --- a/resources/builder.inc.sh +++ b/resources/builder.inc.sh @@ -392,6 +392,7 @@ _builder_failure_trap() { local action target _builder_cleanup_deps + _builder_timing_cleanup_and_report $trappedExitCode # Since 'exit' is also trapped, we can also handle end-of-script incomplete actions. if [[ $trappedExitCode == 0 ]]; then @@ -440,13 +441,32 @@ _builder_cleanup_deps() { # Child scripts #------------------------------------------------------------------------------------------ +builder_launch() { + local script="$1" + local action="$2" + local target + if [[ $# -lt 3 ]]; then + target=":*" + else + target="$3" + fi + _builder_execute_child_script "${action}" "${target}" "${KEYMAN_ROOT}${script}" +} + _builder_execute_child() { local action=$1 local target=$2 - local script="$THIS_SCRIPT_PATH/${_builder_target_paths[$target]}/build.sh" + _builder_execute_child_script "$action" "$target" "$script" +} + +_builder_execute_child_script() { + local action="$1" + local target="$2" + local script="$3" builder_echo start "$action$target" "## $action$target starting..." + _builder_timing_start "$action$target" # Build array of specified inheritable options local child_options=() @@ -475,6 +495,7 @@ _builder_execute_child() { $builder_debug \ $_builder_offline \ && ( + _builder_timing_stop "$action$target" builder_echo end "$action$target" success "## $action$target completed successfully" ) || ( result=$? @@ -679,6 +700,9 @@ builder_start_action() { fi _builder_current_action="${_builder_matched_action}" + # Note: we include the action's dependencies in timing + _builder_timing_start "${_builder_matched_action}" + # Build dependencies as required _builder_do_build_deps "${_builder_matched_action}" return 0 @@ -687,6 +711,143 @@ builder_start_action() { fi } +################################################################################################### +# Timing +################################################################################################### + +_builder_timing_setup() { + if $_builder_timings; then + export _builder_timing_report_file=`mktemp` + _builder_timing_start "build.sh:top" + fi +} + +_builder_timing_start() { + local context="$1" + if [[ ! -z "${_builder_timing_report_file:+x}" ]]; then + echo "START $THIS_SCRIPT_IDENTIFIER:$context $(date +%s.%N)" >> "$_builder_timing_report_file" + fi +} + +_builder_timing_stop() { + local context="$1" + if [[ ! -z "${_builder_timing_report_file:+x}" ]]; then + echo "STOP $THIS_SCRIPT_IDENTIFIER:$context $(date +%s.%N)" >> "$_builder_timing_report_file" + fi +} + +# +# Removes temporary `_builder_timing_report_file` file when top-level build +# script finishes and reports on timings if they were requested. +# +_builder_timing_cleanup_and_report() { + local build_exit_code="$1" + if builder_is_dep_build || builder_is_child_build || [[ -z ${_builder_timing_report_file:+x} ]]; then + return 0 + fi + + if [[ -z "${_builder_timing_report_file:+x}" ]]; then + return 0 + fi + + if [[ "$build_exit_code" != 0 ]]; then + # Hide the timings report if build failed, but still cleanup + _builder_timings=false + fi + + if $_builder_timings; then + _builder_timing_stop "build.sh:top" + + if ! command -v node > /dev/null; then + builder_warn "WARNING: node.js not found, skipping timings report" + else + builder_heading "## Build timings" + + # for simplicity of distribution, we embed the node.js script here + + # shellcheck disable=SC2016 + node -e ' + const fs = require("node:fs"); + const process = require("node:process"); + + let + COLOR_RED="", COLOR_GREEN="", COLOR_YELLOW="", COLOR_BLUE="", COLOR_PURPLE="", COLOR_TEAL="", + COLOR_WHITE="", COLOR_BRIGHT_WHITE="", COLOR_GREY="", COLOR_RESET="", BUILDER_BOLD=""; + + if(process.stdout.isTTY) { + COLOR_RED="\x1b[31m"; COLOR_GREEN="\x1b[32m"; COLOR_YELLOW="\x1b[33m"; COLOR_BLUE="\x1b[34m"; + COLOR_PURPLE="\x1b[35m"; COLOR_TEAL="\x1b[36m"; COLOR_WHITE="\x1b[38;5;252m"; + COLOR_BRIGHT_WHITE="\x1b[38;5;255m"; COLOR_GREY="\x1b[90m"; COLOR_RESET="\x1b(B\x1b[m"; + BUILDER_BOLD="\x1b[1m"; + } + + const log = fs.readFileSync(process.argv[process.argv.length-1], "utf8").split("\n"); + + let top = null; + const modules = {}; + const tree = []; + + for(const line of log) { + if(line.trim() == "") continue; + const [event, area, time] = line.split(" "); + const [module, action, target, sub] = area.split(":"); + if(event == "START") { + const mod = {area, module, action, target: target ? ":"+target : "", sub: sub ?? "", start:parseFloat(time), stop:null, modules:[]}; + if(!top) top = mod; + modules[area] = mod; + if(tree.length) { + tree[tree.length-1].modules.push(mod); + } + tree.push(mod); + } else if(event == "STOP") { + if(tree.length) { + const mod = tree.pop(); + if(mod.area != area) { + console.error(`Expected ${area} to equal ${mod.area}`); + } + } + const mod = modules[area]; + if(!mod) { + console.error(`START not found for ${area}`); + } else { + mod.stop = parseFloat(time); + } + } else { + console.error(`Unrecognized line: "${line}"`); + } + } + + if(tree.length) { + console.error(`Expected tree to be empty`); + console.dir(tree); + } + + if(!Object.keys(modules).length || !top) { + console.error(`Expected report to be non-empty`); + process.exit(0); + } + + // Write out the tree with Pretty Printing + + printModule("", top); + + function printModule(indent, mod) { + const duration = Math.round((mod.stop - mod.start) * 10) / 10; + console.log(`${indent}${BUILDER_BOLD}[${mod.module}]${COLOR_RESET} ${COLOR_GREEN}${mod.action}${mod.target}${COLOR_RESET} ${mod.sub} ${duration}`); + for(const submod of mod.modules) { + printModule(indent+" ", submod); + } + } + ' "${_builder_timing_report_file}" + fi + fi + + rm -f "$_builder_timing_report_file" + _builder_timing_report_file= +} + +################################################################################################### + # # Returns 0 if the user has --option on the command line # @@ -1096,6 +1257,7 @@ _builder_get_default_description() { :engine) description="engine module" ;; :module) description="this module" ;; :tools) description="build tools for this project" ;; + --timings) description="report on timings" ;; --debug) description="debug build" ;; --release) description="release build (prevents --debug in local-env builds)" ;; esac @@ -1318,6 +1480,16 @@ builder_parse() { _builder_parse_expanded_parameters() { _builder_build_deps=--deps builder_verbose= + + if [[ -z "${_builder_timings+x}" ]]; then + # Only set _builder_timings if not already set + if builder_is_ci_build; then + _builder_timings=true + else + _builder_timings=false + fi + export _builder_timings + fi builder_debug= local is_release=false local _params=($@) @@ -1438,6 +1610,12 @@ _builder_parse_expanded_parameters() { _builder_chosen_options+=(--verbose) builder_verbose=--verbose ;; + --timings) + _builder_timings=true + ;; + --no-timings) + _builder_timings=false + ;; --debug) _builder_chosen_options+=(--debug) builder_debug=--debug @@ -1529,18 +1707,17 @@ _builder_parse_expanded_parameters() { fi if builder_is_dep_build; then - if [[ -z ${_builder_deps_built+x} ]]; then - builder_die "FATAL ERROR: Expected '_builder_deps_built' variable to be set" - fi + _builder_verify_expected_sub_process_variables elif builder_is_child_build; then - if [[ -z ${_builder_deps_built+x} ]]; then - builder_die "FATAL ERROR: Expected '_builder_deps_built' variable to be set" - fi + _builder_verify_expected_sub_process_variables else # This is a top-level invocation, so we want to track which dependencies # have been built, so they don't get built multiple times. export _builder_deps_built=`mktemp` + # We will also track timings + _builder_timing_setup + # Per #11106, local builds use --debug by default. # Second condition prevents the block (and message) from executing when --debug is already specified explicitly. if [[ ${KEYMAN_VERSION_ENVIRONMENT:-} == "local" ]] && [[ $builder_debug != --debug ]] && ! $is_release; then @@ -1575,6 +1752,17 @@ _builder_parse_expanded_parameters() { fi } +_builder_verify_expected_sub_process_variables() { + if [[ -z ${_builder_deps_built+x} ]]; then + builder_die "FATAL ERROR: Expected '_builder_deps_built' variable to be set" + fi + if $_builder_timings; then + if [[ -z ${_builder_timing_report_file+x} ]]; then + builder_die "FATAL ERROR: Expected '_builder_timing_report_file' variable to be set" + fi + fi +} + # # Returns 0 (true) if --builder-ignore-unknown-options flag has been specified # in the command line @@ -1755,6 +1943,8 @@ builder_finish_action() { builder_echo end "$action_name" error "## $action_name failed with message: $result" fi + _builder_timing_stop "${_builder_matched_action}" + # Remove $action$target from the array; it is no longer a current action _builder_current_action= else @@ -1833,6 +2023,8 @@ _builder_do_build_deps() { return 0 fi + _builder_timing_start "$1:dependencies" + for dep in "${_builder_deps[@]}"; do # Don't attempt to build dependencies that don't match the current # action:target (wildcards supported for matches here) @@ -1861,7 +2053,7 @@ _builder_do_build_deps() { $_builder_build_deps \ --builder-dep-parent "$THIS_SCRIPT_IDENTIFIER" && ( if $_builder_debug_internal; then - builder_echo success "## Dependency $dep$dep_target for $_builder_matched_action_name successfully" + builder_echo success "## Dependency $dep$dep_target for $_builder_matched_action_name completed successfully" fi ) || ( result=$? @@ -1869,6 +2061,8 @@ _builder_do_build_deps() { exit $result ) || exit $? # Required due to above subshell masking exit done + + _builder_timing_stop "$1:dependencies" } # From 5dbc438f53d3e11c29710d458636a3f60f667531 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Mon, 10 Nov 2025 10:37:52 +0100 Subject: [PATCH 07/33] maint(resources): add `builder_launch` function Use `builder_launch` to launch other builder scripts rather than calling directly, so that inheritable state and options are passed through. Test-bot: skip --- developer/src/kmc/build.sh | 2 +- docs/builder.md | 20 ++++++++++ resources/builder.inc.sh | 74 ++++++++++++++++++++++++------------ web/src/app/webview/build.sh | 2 +- 4 files changed, 72 insertions(+), 26 deletions(-) diff --git a/developer/src/kmc/build.sh b/developer/src/kmc/build.sh index 81cc94dcca..56a2dd84fa 100755 --- a/developer/src/kmc/build.sh +++ b/developer/src/kmc/build.sh @@ -53,7 +53,7 @@ function do_build() { function do_test() { typescript_run_eslint_mocha_tests 50 - ./test/command-line-tests.sh test + builder_launch /developer/src/kmc/test/command-line-tests.sh test } #------------------------------------------------------------------------------------------------------------------- diff --git a/docs/builder.md b/docs/builder.md index 538bf783bd..0d6a662006 100644 --- a/docs/builder.md +++ b/docs/builder.md @@ -1038,6 +1038,26 @@ fi -------------------------------------------------------------------------------- +## `builder_launch` function + +Starts a child script build, passing current build dependency, inheritable +options, and timing status through as parameters. This should be used rather +than calling the build script directly, to avoid multiple builds of +dependencies. + +### Parameters + +* 1: `script` path to script, relative to root of repo +* 2: `action` action(s)+target(s) for the child script to run, comma separated, at least one + +### Example + +```bash + builder_launch /core/build.sh configure,build:wasm +``` + +-------------------------------------------------------------------------------- + ## `builder_parse` function Initializes a build.sh script, parses command line. Will abort the script if diff --git a/resources/builder.inc.sh b/resources/builder.inc.sh index 7d9c5a0256..b74c83cb65 100755 --- a/resources/builder.inc.sh +++ b/resources/builder.inc.sh @@ -377,7 +377,7 @@ _builder_item_is_target() { function _builder_warn_if_incomplete() { if [ -n "${_builder_current_action}" ]; then - builder_echo warning "$_builder_current_action never reported success or failure" + builder_echo warning "WARNING: $_builder_current_action never reported success or failure" # exit 1 # If we wanted this scenario to result in a forced build-script fail. fi @@ -441,31 +441,47 @@ _builder_cleanup_deps() { # Child scripts #------------------------------------------------------------------------------------------ +# +# Starts a child script build, passing current build dependency, inheritable +# options, and timing status through as parameters. This should be used rather +# than calling the build script directly, to avoid multiple builds of +# dependencies. +# +# ### Parameters +# +# * 1: `script` path to script, relative to root of repo +# * 2: `action` action(s)+target(s) for the child script to run, comma separated, at least one +# +# ### Example +# +# ```bash +# builder_launch /core/build.sh configure,build:wasm +# ``` +# builder_launch() { local script="$1" local action="$2" - local target - if [[ $# -lt 3 ]]; then - target=":*" - else - target="$3" - fi - _builder_execute_child_script "${action}" "${target}" "${KEYMAN_ROOT}${script}" + _builder_execute_child_script "${KEYMAN_ROOT}${script}" "${action}" "" } _builder_execute_child() { - local action=$1 - local target=$2 - local script="$THIS_SCRIPT_PATH/${_builder_target_paths[$target]}/build.sh" - _builder_execute_child_script "$action" "$target" "$script" -} - -_builder_execute_child_script() { local action="$1" local target="$2" - local script="$3" + local script="$THIS_SCRIPT_PATH/${_builder_target_paths[$target]}/build.sh" + _builder_execute_child_script "$script" "$action" +} - builder_echo start "$action$target" "## $action$target starting..." +# ### Parameters +# +# * 1: `script` path to script +# * 2: `action` action(s)+target(s) for the child script to run, comma separated, at least one +# * 3: `target` reporting target, used when executing a child script through builder_run_child_actions, optional +_builder_execute_child_script() { + local script="$1" + local action="$2" + local target="${3:-}" + + builder_echo start "child#$action$target" "## child $action$target starting..." _builder_timing_start "$action$target" # Build array of specified inheritable options @@ -485,21 +501,25 @@ _builder_execute_child_script() { dep_module="$builder_dep_parent" fi + # Note: we do not pass '$target' to the child script, because it is used here + # only for reporting; the target was used to select the correct script in + # _builder_execute_child() + "$script" \ --builder-child \ $_builder_build_deps \ $dep_flag "$dep_module" \ - $action \ + "$action" \ ${child_options[@]} \ $builder_verbose \ $builder_debug \ $_builder_offline \ && ( _builder_timing_stop "$action$target" - builder_echo end "$action$target" success "## $action$target completed successfully" + builder_echo end "child#$action$target" success "## child $action$target completed successfully" ) || ( result=$? - builder_echo end "$action$target" error "## $action$target failed with exit code $result" + builder_echo end "child#$action$target" error "## child $action$target failed with exit code $result" exit $result ) || exit $? # Required due to above subshell masking exit } @@ -1526,7 +1546,7 @@ _builder_parse_expanded_parameters() { action=$new_action ;; *) - builder_warn "Parameter $action has $? matches, could mean any of {$new_action}" + builder_warn "ERROR: Parameter $action has $? matches, could mean any of {$new_action}" exit 1 ;; esac @@ -1537,7 +1557,7 @@ _builder_parse_expanded_parameters() { target=$new_target ;; *) - builder_warn "Parameter $target has $? matches, could mean any of {$new_target}" + builder_warn "ERROR: Parameter $target has $? matches, could mean any of {$new_target}" exit 1 ;; esac @@ -1711,6 +1731,12 @@ _builder_parse_expanded_parameters() { elif builder_is_child_build; then _builder_verify_expected_sub_process_variables else + + # For now, we'll leave this warning disabled, but may re-enable in #15130 + # if [[ ! -z "${_builder_deps_built+x}" ]]; then + # builder_warn "WARNING: Child build '${THIS_SCRIPT_IDENTIFIER}' instantiated without using builder_launch" + # fi + # This is a top-level invocation, so we want to track which dependencies # have been built, so they don't get built multiple times. export _builder_deps_built=`mktemp` @@ -2309,13 +2335,13 @@ builder_describe_platform() { # For testing, we can override the current platform if [[ ! -z "${BUILDER_PLATFORM_OVERRIDE+x}" ]]; then - builder_warn "BUILDER_PLATFORM_OVERRIDE variable found. Overriding detected platform '$builder_platform' with '$BUILDER_PLATFORM_OVERRIDE'" + builder_warn "WARNING: BUILDER_PLATFORM_OVERRIDE variable found. Overriding detected platform '$builder_platform' with '$BUILDER_PLATFORM_OVERRIDE'" builder_platform="$BUILDER_PLATFORM_OVERRIDE" fi # For testing, we can override the current tools if [[ ! -z "${BUILDER_TOOLS_OVERRIDE+x}" ]]; then - builder_warn "BUILDER_TOOLS_OVERRIDE variable found. Overriding detected tools (${builder_installed_tools[@]}) with (${BUILDER_TOOLS_OVERRIDE[@]})" + builder_warn "WARNING: BUILDER_TOOLS_OVERRIDE variable found. Overriding detected tools (${builder_installed_tools[@]}) with (${BUILDER_TOOLS_OVERRIDE[@]})" builder_installed_tools=("${BUILDER_TOOLS_OVERRIDE[@]}") fi diff --git a/web/src/app/webview/build.sh b/web/src/app/webview/build.sh index 403aaec4e4..32534e6950 100755 --- a/web/src/app/webview/build.sh +++ b/web/src/app/webview/build.sh @@ -78,7 +78,7 @@ compile_and_copy() { node map-polyfill-bundler.js # For dependent test pages. - "$KEYMAN_ROOT/web/src/test/manual/embed/android-harness/build.sh" + builder_launch /web/src/test/manual/embed/android-harness/build.sh configure,build } builder_run_action configure node_select_version_and_npm_ci From 408514daf30200f40f31bf56cc8fc31ea5464616 Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Mon, 10 Nov 2025 12:00:28 +0100 Subject: [PATCH 08/33] Update web/src/engine/js-processor/src/deadkeys.ts Co-authored-by: Marc Durdin --- web/src/engine/js-processor/src/deadkeys.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/src/engine/js-processor/src/deadkeys.ts b/web/src/engine/js-processor/src/deadkeys.ts index c8a3792771..fed03b39ed 100644 --- a/web/src/engine/js-processor/src/deadkeys.ts +++ b/web/src/engine/js-processor/src/deadkeys.ts @@ -122,7 +122,7 @@ export class DeadkeyTracker { if (index > -1) { this.dks.splice(index, 1); } else { - console.warn('Deadkey to remove not found in DeadkeyTracker.'); + console.warn(`Deadkey ${JSON.stringify(dk)} to remove not found in DeadkeyTracker.`); } } From 2d8a4aeebc89c0559a7298d89e9f257c77fc19b9 Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Mon, 10 Nov 2025 15:24:46 +0100 Subject: [PATCH 09/33] maint(core): fix option defaults for Meson >= 1.1 Meson 1.1 changed the name of the options file from `meson_options.txt` to `meson.options`. Currently we're stuck with Meson 1.0 as minimum Meson version because we still support Ubuntu 22.04 Jammy, but on some machines a newer version of meson gets installed which causes the options to be ignored. The first attempt to simply symlink or copy `meson_options.txt` to `meson.options` failed because git on Windows doesn't support symlinks by default, and Meson complains if it finds two options files (#15127). Fortunately not having an options file is only a problem for Core where we have one option in the options file that defaults to true. The solution implemented in this change is to always pass the option to Meson. Test-bot: skip --- core/build.bat | 2 +- core/build.sh | 2 +- resources/build/minimum-versions.inc.sh | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/core/build.bat b/core/build.bat index 5dd219e9f2..fc4f400bfb 100644 --- a/core/build.bat +++ b/core/build.bat @@ -83,7 +83,7 @@ if "!COMMAND!" == "configure" ( if "%1" == "--no-tests" ( meson setup build\!ARCH!\!BUILDTYPE! !STATIC_LIBRARY! --buildtype !BUILDTYPE! -Dkeyman_core_tests=false --werror %2 %3 %4 %5 %6 %7 %8 %9 || exit !errorlevel! ) else ( - meson setup build\!ARCH!\!BUILDTYPE! !STATIC_LIBRARY! --buildtype !BUILDTYPE! --werror %1 %2 %3 %4 %5 %6 %7 %8 %9 || exit !errorlevel! + meson setup build\!ARCH!\!BUILDTYPE! !STATIC_LIBRARY! --buildtype !BUILDTYPE! -Dkeyman_core_tests=true --werror %1 %2 %3 %4 %5 %6 %7 %8 %9 || exit !errorlevel! ) shift ) diff --git a/core/build.sh b/core/build.sh index 1c1d337b1c..1defefe13b 100755 --- a/core/build.sh +++ b/core/build.sh @@ -79,7 +79,7 @@ builder_parse "$@" # if we don't plan to run them, for example when doing a dependency build # in CI # -MESON_OPTION_keyman_core_tests= +MESON_OPTION_keyman_core_tests="-Dkeyman_core_tests=true" BUILD_BAT_keyman_core_tests= if builder_is_dep_build || builder_has_option --no-tests; then diff --git a/resources/build/minimum-versions.inc.sh b/resources/build/minimum-versions.inc.sh index d768697022..0ceaef15b0 100644 --- a/resources/build/minimum-versions.inc.sh +++ b/resources/build/minimum-versions.inc.sh @@ -30,7 +30,7 @@ KEYMAN_MIN_VERSION_NODE_MAJOR=20 # node version source of truth is KEYMAN_MIN_VERSION_NPM=10.5.1 # 10.5.0 has bug, discussed in #10350 KEYMAN_MIN_VERSION_EMSCRIPTEN=3.1.58 # Use KEYMAN_USE_EMSDK to automatically update to this version KEYMAN_MIN_VERSION_VISUAL_STUDIO=2022 # Visual Studio 2022, see /docs/build/windows.md for workloads and components -KEYMAN_MIN_VERSION_MESON=1.0.0 +KEYMAN_MIN_VERSION_MESON=1.0.0 # TODO: rename meson_options.txt to meson.options when updating to >= 1.1, see #15127. KEYMAN_VERSION_GRADLE=8.12 # See /android/KMEA/gradle/wrapper/gradle-wrapper.properties From 3a942a92f0c2bdc22f3a5c749c12d97fed636adb Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Mon, 10 Nov 2025 17:06:42 +0100 Subject: [PATCH 10/33] chore(linux): Update debian changelog (cherry picked from commit 9eeb6be6953cdca52242adb9999e60d18a623bf8) --- linux/debian/changelog | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/linux/debian/changelog b/linux/debian/changelog index dd0f96f05a..5f9833317e 100644 --- a/linux/debian/changelog +++ b/linux/debian/changelog @@ -1,3 +1,9 @@ +keyman (18.0.244-1) unstable; urgency=medium + + * New upstream release. + + -- Eberhard Beilharz Mon, 10 Nov 2025 17:06:33 +0100 + keyman (18.0.243-1) unstable; urgency=medium * replace dbus-x11 dependency with default-dbus-session-bus | dbus-session-bus From 43d022ba0116343de7f0f68db8f7b3f2d72e421f Mon Sep 17 00:00:00 2001 From: Keyman Build Agent Date: Mon, 10 Nov 2025 12:01:09 -0600 Subject: [PATCH 11/33] auto: increment master version to 19.0.159 Test-bot: skip Build-bot: skip --- HISTORY.md | 4 ++++ VERSION.md | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/HISTORY.md b/HISTORY.md index 9340e79af5..5ff1fa4f9d 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,9 @@ # Keyman Version History +## 19.0.158 alpha 2025-11-10 + +* fix(web): add unit tests for DeadkeyTracker and fix a few bugs (#15113) + ## 19.0.157 alpha 2025-11-06 * chore(common): Consolidate platformtest.js keyboard to `/common/test/keyboards/` (#15034) diff --git a/VERSION.md b/VERSION.md index 6823fd6611..a2a3e8d506 100644 --- a/VERSION.md +++ b/VERSION.md @@ -1 +1 @@ -19.0.158 \ No newline at end of file +19.0.159 \ No newline at end of file From 61eb2d90901fdc14b4f43d60a6d43c790cd41c47 Mon Sep 17 00:00:00 2001 From: Darcy Wong Date: Tue, 11 Nov 2025 14:37:05 +0700 Subject: [PATCH 12/33] docs(android): Document disclaimer of Keyman containing adware/spyware --- android/docs/help/about/adware.md | 13 +++++++++++++ android/docs/help/about/index.md | 1 + android/docs/help/android_images/attention.png | Bin 0 -> 98491 bytes android/docs/help/index.md | 1 + 4 files changed, 15 insertions(+) create mode 100644 android/docs/help/about/adware.md create mode 100644 android/docs/help/android_images/attention.png diff --git a/android/docs/help/about/adware.md b/android/docs/help/about/adware.md new file mode 100644 index 0000000000..cc38280a33 --- /dev/null +++ b/android/docs/help/about/adware.md @@ -0,0 +1,13 @@ +--- +title: Does Keyman include adware or spyware? +--- + +No! + +We never have and never will include adware or spyware components. +Keyman does contact the keyman.com website periodically to check for updates for both Keyman and the keyboards you have installed. +In this process, the only information communicated is the current version of Keyman and the version of the keyboards. + +When [enabling Keyman as a system keyboard](../start/enabling-system-keyboard), Android displays the following warning, which is standard for any system keyboard. + +![](../android_images/attention.png) \ No newline at end of file diff --git a/android/docs/help/about/index.md b/android/docs/help/about/index.md index 4428a4bb75..728cdc6ec9 100644 --- a/android/docs/help/about/index.md +++ b/android/docs/help/about/index.md @@ -5,5 +5,6 @@ title: About Keyman for Android * [Welcome to Keyman](welcome) * [What's New](whatsnew) * [System Requirements](system-requirements) +* [Does Keyman include adware or spyware?](adware) * [Data Privacy Policy](privacy) * [Version History](history) \ No newline at end of file diff --git a/android/docs/help/android_images/attention.png b/android/docs/help/android_images/attention.png new file mode 100644 index 0000000000000000000000000000000000000000..5a55450cbdc89fc824007c7aca801910329a538a GIT binary patch literal 98491 zcmeFZWmJ@F_%}L$gaU$zf`EXE(vs4pqJo4d-H3F<(2R(Hje&F;gmexaA|NF(^pMgW z!vI6gdyV`5p7ZUj^X;s&*2`LZ?Y&v>%=6sOeP7qFE?#P?tI!`~K88Rb=?coLGVPXmOXvu`{?xh)MQabMTgU87)!++E+(72x|saJMB(U*6xs(jZ(ifn{Sfs< zbp9yWNrO8agR-matugT#6)c>O@q0drGIx<3@sjST31jgwA_gXj8c$#R`?~tZN5G5h zzyE$Aa5mVG^WWDoKRTzE|NX$y_0r4#zV{`sX*}}3pZv-8e}3@)U(56T%PpHj_W#}^ zqtO5LMs=6mn4bPtI2BcKc9WGX&zq3f8yU)h%6QQzwe#iIyZds-}kfz z_6(1pV^b9|qD{pAS&GU7{%3|9NBrfICQuyvUpHDV_VW*Xw!Q40?0+4@O4Kzfx2wGiJ*3s#CO`v9u+bC!W0J%dd=;9^d1hzG&NoYHeJXD7{=#_+rGr zZRcUmI=maqG8STXbw}OCJ^7M+q3Z0qH($`hbWG+LCClkh7JUZFqf))E83zYr^ondw z9KUemd1g-eF-L64t>%xH=?QWFbE&Hi&kQS)*V2*#f5wVsk|C6)zw_#mpF(7Es7r@?B_r z$NKu)H_22FX7%_RL0+Ri+}|oGWJT=z-C5ckJCNwk!`@(F9Iov_vXoxA$F;zn3F`Wg zH=c`q=JVa(z2{QG)CjxdN&C5m;s2KUbY&i^CXb}#`MY=TBHq+Rq}oQy1xN`C3)9Vy zJiCkYQli-(BOb7i25cL9%y&1q&=xR1mZo%F*i4Z2I&2+%wR96B>Mg@|;f4!BJ5Bvo zYb5`N*jT%}mdKqobL`U_l9Gc$7tj5BlOOa4Hiu*R^$Tti4I7mY5f8?Hy|mof!a8@A zI2y{n9eL3()A5c?_Em@69-XQGI%SE&AUVeoW(g;Um=%ju2DAxQlRBcMxmnS|g7f3Y zk9igi$FMR9so7dzL%T5vL_%@DiT(K?g0n~Vef|;7&gwd7HiW7`ezA{^JFUka_h};^9zqB=EL#A7! zDpJv{xwl@QwHd3fGCwG???29OP?Xosdto8`-*ypr%gH`GL>Y}mJ2&m(?eW<6y|V&V zRqp3 zp_5_ctXYbjF3qb%el@6YJay>CAR=&8Wnmj1YC-IN*cs2rTC zdS=Fu!Adc>4U`kVbuux8@v2VNxmYp#RO*}sFLkS87D;^?1su)6mKClde57?c*ms0G zn$1GYG)ZGFm(Put>)$>&lgK&dZXX#J_xilZ6NC^IYi`Y9`DM*`G5bg{iWJ3)yn#@& z2ZdD0fsKhO#oP}|-?*N3@on1tGygcZAU4TADkn~%swOs7-xry1TRgHZ6SL?odS6d~ zabR1=FWL6QJKK2QQhvMzhKqiO0=vJ7lx$1nD)qUOmEm^nG3DCrQ`&E*fQso{yj#= zEJ`Emx22?}_NINg?9j)X^J$4A)&g7Ni16Pn2p}F9h&m0Q@F{F~8Zd5B(1OUu`1nOqYU zaFE1NQ*7VAw`f1zLW?i%ePW`3UpRVB((RIz$6PXMb=`aC9v5kgev!ZXp@v_d9szjj z>)1!WTeogaB>Jt8A!OfcQhbK9Q@e=g)FX7<-=!m(?reQYm7w-Nae}S&sftN16Reg( zeJK5Pzb%LQZ6unBUCM(7Hf&SC_J~)C3bxw1b5`5=lQ@;fsVi!=2is%LG!E_42T)o% zGqkzYVnv&gK30Tys&pQj3M+A9QdCr!d41?a3D&9p86_>bdX04ln+(23+XjqQnc>DkrHSC~_G&m( zK2`#kKr3OOX5&uPK^auUfeNqOc3~;%SWIfbt00QKdfSJV?qx}+L;d~TMZwUH<<_%g z9B=BZgvfG7#tqepepMzi+eY%0_&%0Amfpb|+3S0~nYt-ftut^X2|BJzt=2&yuC><* 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 K6$|e@3;aLP<>$`; literal 0 HcmV?d00001 diff --git a/android/docs/help/index.md b/android/docs/help/index.md index 360af91e6d..758e44d661 100644 --- a/android/docs/help/index.md +++ b/android/docs/help/index.md @@ -6,6 +6,7 @@ title: Keyman for Android 19.0 Help * [Welcome to Keyman](about/welcome) * [What's New](about/whatsnew) * [System Requirements](about/system-requirements) +* [Does Keyman include adware or spyware?](about/adware) * [Data Privacy Policy](about/privacy) * [Version History](about/history) From 1c5fc9d6fc670c2f1aebe694b7259216a9fe1b07 Mon Sep 17 00:00:00 2001 From: Darcy Wong Date: Tue, 11 Nov 2025 15:03:41 +0700 Subject: [PATCH 13/33] Apply suggestions from code review Co-authored-by: Marc Durdin --- android/docs/help/about/adware.md | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/android/docs/help/about/adware.md b/android/docs/help/about/adware.md index cc38280a33..05c71b74c4 100644 --- a/android/docs/help/about/adware.md +++ b/android/docs/help/about/adware.md @@ -5,9 +5,18 @@ title: Does Keyman include adware or spyware? No! We never have and never will include adware or spyware components. -Keyman does contact the keyman.com website periodically to check for updates for both Keyman and the keyboards you have installed. -In this process, the only information communicated is the current version of Keyman and the version of the keyboards. -When [enabling Keyman as a system keyboard](../start/enabling-system-keyboard), Android displays the following warning, which is standard for any system keyboard. +Keyman does contact the keyman.com website periodically to check for updates for the keyboards and predictive text dictionaries you have installed. +In this process, the only information communicated is the current version of Keyman and the version of the keyboards and dictionaries. -![](../android_images/attention.png) \ No newline at end of file +When [enabling Keyman as a system keyboard](../start/enabling-system-keyboard), Android displays the following warning, which is the same message that is displayed for any keyboard app that is installed onto an Android device. + +![](../android_images/attention.png) + +> Attention: This input method may be able to collect all the text you +> type, including personal data like passwords and credit card numbers. +> It comes from the app Keyman. Use this input method? Cancel / OK + +The Keyman app does not record the keystrokes you type and does not send keystrokes off-device, unlike many other keyboard apps. All predictive text suggestions are entirely on your device. + +Keyman is an open source application, and you are most welcome to inspect the source code at https://github.com/keymanapp/keyman. \ No newline at end of file From 32de05a385ff7cc2148c02900ce39d8d01f43207 Mon Sep 17 00:00:00 2001 From: Darcy Wong Date: Tue, 11 Nov 2025 15:42:58 +0700 Subject: [PATCH 14/33] docs(android): Rearrange section and add Sentry blurb --- android/docs/help/about/adware.md | 14 ++++++++++---- .../docs/help/android_images/attention.png | Bin 98491 -> 17207 bytes 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/android/docs/help/about/adware.md b/android/docs/help/about/adware.md index 05c71b74c4..ecc7acf422 100644 --- a/android/docs/help/about/adware.md +++ b/android/docs/help/about/adware.md @@ -6,9 +6,6 @@ No! We never have and never will include adware or spyware components. -Keyman does contact the keyman.com website periodically to check for updates for the keyboards and predictive text dictionaries you have installed. -In this process, the only information communicated is the current version of Keyman and the version of the keyboards and dictionaries. - When [enabling Keyman as a system keyboard](../start/enabling-system-keyboard), Android displays the following warning, which is the same message that is displayed for any keyboard app that is installed onto an Android device. ![](../android_images/attention.png) @@ -19,4 +16,13 @@ When [enabling Keyman as a system keyboard](../start/enabling-system-keyboard), The Keyman app does not record the keystrokes you type and does not send keystrokes off-device, unlike many other keyboard apps. All predictive text suggestions are entirely on your device. -Keyman is an open source application, and you are most welcome to inspect the source code at https://github.com/keymanapp/keyman. \ No newline at end of file +Keyman is an open source application, and you are most welcome to inspect the source code at https://github.com/keymanapp/keyman. + +## Checking for Updates + +Keyman does contact the keyman.com website periodically to check for updates for the keyboards and predictive text dictionaries you have installed. +In this process, the only information communicated is the current version of Keyman and the version of the keyboards and dictionaries. + +## Crash Report Information + +When enabled, the Keyman app will send crash reporting information to [Sentry](https://keyman.sentry.io). No personally identifiable information or keyboard strokes are recorded. This can be disabled in the Keyman [settings menu](../basic/config/). diff --git a/android/docs/help/android_images/attention.png b/android/docs/help/android_images/attention.png index 5a55450cbdc89fc824007c7aca801910329a538a..febe155e34256c927ce30cc5ef7e56814dca772f 100644 GIT binary patch literal 17207 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 literal 98491 zcmeFZWmJ@F_%}L$gaU$zf`EXE(vs4pqJo4d-H3F<(2R(Hje&F;gmexaA|NF(^pMgW z!vI6gdyV`5p7ZUj^X;s&*2`LZ?Y&v>%=6sOeP7qFE?#P?tI!`~K88Rb=?coLGVPXmOXvu`{?xh)MQabMTgU87)!++E+(72x|saJMB(U*6xs(jZ(ifn{Sfs< zbp9yWNrO8agR-matugT#6)c>O@q0drGIx<3@sjST31jgwA_gXj8c$#R`?~tZN5G5h zzyE$Aa5mVG^WWDoKRTzE|NX$y_0r4#zV{`sX*}}3pZv-8e}3@)U(56T%PpHj_W#}^ zqtO5LMs=6mn4bPtI2BcKc9WGX&zq3f8yU)h%6QQzwe#iIyZds-}kfz z_6(1pV^b9|qD{pAS&GU7{%3|9NBrfICQuyvUpHDV_VW*Xw!Q40?0+4@O4Kzfx2wGiJ*3s#CO`v9u+bC!W0J%dd=;9^d1hzG&NoYHeJXD7{=#_+rGr zZRcUmI=maqG8STXbw}OCJ^7M+q3Z0qH($`hbWG+LCClkh7JUZFqf))E83zYr^ondw z9KUemd1g-eF-L64t>%xH=?QWFbE&Hi&kQS)*V2*#f5wVsk|C6)zw_#mpF(7Es7r@?B_r z$NKu)H_22FX7%_RL0+Ri+}|oGWJT=z-C5ckJCNwk!`@(F9Iov_vXoxA$F;zn3F`Wg zH=c`q=JVa(z2{QG)CjxdN&C5m;s2KUbY&i^CXb}#`MY=TBHq+Rq}oQy1xN`C3)9Vy zJiCkYQli-(BOb7i25cL9%y&1q&=xR1mZo%F*i4Z2I&2+%wR96B>Mg@|;f4!BJ5Bvo zYb5`N*jT%}mdKqobL`U_l9Gc$7tj5BlOOa4Hiu*R^$Tti4I7mY5f8?Hy|mof!a8@A zI2y{n9eL3()A5c?_Em@69-XQGI%SE&AUVeoW(g;Um=%ju2DAxQlRBcMxmnS|g7f3Y zk9igi$FMR9so7dzL%T5vL_%@DiT(K?g0n~Vef|;7&gwd7HiW7`ezA{^JFUka_h};^9zqB=EL#A7! zDpJv{xwl@QwHd3fGCwG???29OP?Xosdto8`-*ypr%gH`GL>Y}mJ2&m(?eW<6y|V&V zRqp3 zp_5_ctXYbjF3qb%el@6YJay>CAR=&8Wnmj1YC-IN*cs2rTC zdS=Fu!Adc>4U`kVbuux8@v2VNxmYp#RO*}sFLkS87D;^?1su)6mKClde57?c*ms0G zn$1GYG)ZGFm(Put>)$>&lgK&dZXX#J_xilZ6NC^IYi`Y9`DM*`G5bg{iWJ3)yn#@& z2ZdD0fsKhO#oP}|-?*N3@on1tGygcZAU4TADkn~%swOs7-xry1TRgHZ6SL?odS6d~ zabR1=FWL6QJKK2QQhvMzhKqiO0=vJ7lx$1nD)qUOmEm^nG3DCrQ`&E*fQso{yj#= zEJ`Emx22?}_NINg?9j)X^J$4A)&g7Ni16Pn2p}F9h&m0Q@F{F~8Zd5B(1OUu`1nOqYU zaFE1NQ*7VAw`f1zLW?i%ePW`3UpRVB((RIz$6PXMb=`aC9v5kgev!ZXp@v_d9szjj z>)1!WTeogaB>Jt8A!OfcQhbK9Q@e=g)FX7<-=!m(?reQYm7w-Nae}S&sftN16Reg( zeJK5Pzb%LQZ6unBUCM(7Hf&SC_J~)C3bxw1b5`5=lQ@;fsVi!=2is%LG!E_42T)o% zGqkzYVnv&gK30Tys&pQj3M+A9QdCr!d41?a3D&9p86_>bdX04ln+(23+XjqQnc>DkrHSC~_G&m( zK2`#kKr3OOX5&uPK^auUfeNqOc3~;%SWIfbt00QKdfSJV?qx}+L;d~TMZwUH<<_%g z9B=BZgvfG7#tqepepMzi+eY%0_&%0Amfpb|+3S0~nYt-ftut^X2|BJzt=2&yuC><* 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 K6$|e@3;aLP<>$`; From e56619911d812b15c7012aee80fea970028525e7 Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Tue, 11 Nov 2025 11:28:20 +0100 Subject: [PATCH 15/33] chore(linux): add support for Ubuntu 26.04 LTS Resolute Build-bot: skip Test-bot: skip --- .github/workflows/deb-packaging.yml | 4 ++-- linux/scripts/launchpad.sh | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/deb-packaging.yml b/.github/workflows/deb-packaging.yml index 28440c4211..46560223fd 100644 --- a/.github/workflows/deb-packaging.yml +++ b/.github/workflows/deb-packaging.yml @@ -135,7 +135,7 @@ jobs: strategy: fail-fast: true matrix: - dist: [jammy, noble, plucky] + dist: [jammy, noble, plucky, questing] steps: - name: Checkout @@ -161,7 +161,7 @@ jobs: strategy: fail-fast: true matrix: - dist: [questing] + dist: [resolute] steps: - name: Checkout diff --git a/linux/scripts/launchpad.sh b/linux/scripts/launchpad.sh index 5fd9220784..881fa5b42b 100755 --- a/linux/scripts/launchpad.sh +++ b/linux/scripts/launchpad.sh @@ -33,7 +33,7 @@ else fi echo "ppa: ${ppa}" -distributions="${DIST:-jammy noble plucky questing}" +distributions="${DIST:-jammy noble plucky questing resolute}" packageversion="${PACKAGEVERSION:-1~sil1}" BASEDIR=$(pwd) From 602b28fe6cf2b2c2d566be7441c2ab576325f46e Mon Sep 17 00:00:00 2001 From: Keyman Build Agent Date: Tue, 11 Nov 2025 12:01:26 -0600 Subject: [PATCH 16/33] auto: increment master version to 19.0.160 Test-bot: skip Build-bot: skip --- HISTORY.md | 4 ++++ VERSION.md | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/HISTORY.md b/HISTORY.md index 5ff1fa4f9d..f70cc84347 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,9 @@ # Keyman Version History +## 19.0.159 alpha 2025-11-11 + +* maint(core): fix option defaults for Meson >= 1.1 (#15131) + ## 19.0.158 alpha 2025-11-10 * fix(web): add unit tests for DeadkeyTracker and fix a few bugs (#15113) diff --git a/VERSION.md b/VERSION.md index a2a3e8d506..3576a02ff4 100644 --- a/VERSION.md +++ b/VERSION.md @@ -1 +1 @@ -19.0.159 \ No newline at end of file +19.0.160 \ No newline at end of file From a49ee18378078859ea6cff94fe77152053b1fd46 Mon Sep 17 00:00:00 2001 From: Keyman Build Agent Date: Wed, 12 Nov 2025 12:01:30 -0600 Subject: [PATCH 17/33] auto: increment master version to 19.0.161 Test-bot: skip Build-bot: skip --- HISTORY.md | 5 +++++ VERSION.md | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/HISTORY.md b/HISTORY.md index f70cc84347..b4a84c127b 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,10 @@ # Keyman Version History +## 19.0.160 alpha 2025-11-12 + +* fix(android): Change Alert Dialog instead of Toast if package missing keyboard and lexical-model files (#15098) +* chore(linux): add support for Ubuntu 26.04 LTS Resolute (#15137) + ## 19.0.159 alpha 2025-11-11 * maint(core): fix option defaults for Meson >= 1.1 (#15131) diff --git a/VERSION.md b/VERSION.md index 3576a02ff4..79cc7b29e5 100644 --- a/VERSION.md +++ b/VERSION.md @@ -1 +1 @@ -19.0.160 \ No newline at end of file +19.0.161 \ No newline at end of file From 5879f087c7f656396955f211d9dacb8bc575c6ae Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Thu, 13 Nov 2025 14:45:23 +0100 Subject: [PATCH 18/33] fix(windows): handle forward slash for KEYMAN_ROOT env var Test-bot: skip --- common/windows/delphi/general/KeymanPaths.pas | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/windows/delphi/general/KeymanPaths.pas b/common/windows/delphi/general/KeymanPaths.pas index 4086be32ce..a0cc07d5b4 100644 --- a/common/windows/delphi/general/KeymanPaths.pas +++ b/common/windows/delphi/general/KeymanPaths.pas @@ -395,7 +395,7 @@ var begin // On developer machines, if we are running within the source repo, then use // those paths - keyman_root := GetEnvironmentVariable('KEYMAN_ROOT'); + keyman_root := GetEnvironmentVariable('KEYMAN_ROOT').Replace('/', '\'); if (keyman_root <> '') and SameText(keyman_root, ParamStr(0).Substring(0, keyman_root.Length)) then begin // Source repo, bin folder From 7c9c21d260c6495be3b4a9020dd7589044544b3e Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Thu, 13 Nov 2025 10:10:39 +0100 Subject: [PATCH 19/33] fix(windows): handle exceptions when walking stack in exception handler This change wraps the stack walk with an exception handler, which will enable some crash handlers to continue. However, in some circumstances, such as the stack captured below, it appears to be an unrecoverable crash. So, to mitigate this scenario, we also now only capture stack traces for crashes that we know are serious -- the set of exception types listed in the patch. I experienced this issue on my machine after a Windows update; it is not clear to me if this is related to the Windows update or if it is due to my debugging environment for Keyman. The symptoms were that Keyman Configuration would start up, but show just a blank white dialog, and a few seconds later would abort. Observing process history showed that kmshell.exe and kmbrowserhost.exe would both crash, with related call stacks -- see below for a sample. These call stacks are coming out of keyman32 calling OutputDebugString from DllMain, which may not be a safe thing to do, because of the exception side-effect. A separate patch will be submitted to address that - probably just removing any OutputDebugString calls from DllMain paths. In any case, this change adds robustness and should mitigate other unexpected exceptions. ``` 0:026> kb # ChildEBP RetAddr Args to Child 00 08a1d4c8 750d6e3d 750d6e20 750bd307 04447396 ucrtbase!abort+0x31 01 08a1d4d0 750bd307 04447396 051caaa0 629f52f0 ucrtbase!_purecall+0x1d 02 08a1d508 62ab2914 0495e1c8 00000000 08a1d5fc ucrtbase!__crt_state_management::wrapped_invoke+0x2a 03 08a1d528 62aba00e 0495e1c8 00000000 08a1d5fc dbghelp!SymbolDataSimpleImpl<1,3>::getData+0x24 04 08a1d580 62ac649d 0495e1c8 08a1d5fc 08a1da34 dbghelp!GetData::getSymData+0x6e 05 08a1d5dc 62ac3272 08a1d5fc 05497580 08a1da5c dbghelp!CPubByAddrTrav::get+0xad 06 08a1d948 62ac7340 08a1da00 08a1da34 08a1da5c dbghelp!findBetterSymbol+0x62 07 08a1da40 62ac87eb 08a1da5c 08a1da64 62a05ea0 dbghelp!CAllSymsByAddrTrav::getEnclosingSymbol+0x80 08 08a1da6c 62abfb10 00000001 00030ed1 05497580 dbghelp!CAllSymsByAddrTrav::init+0xeb 09 08a1da88 62a876e9 00000001 00030ed1 043e9008 dbghelp!CAllSymsByAddrTrav::FInit+0xa0 0a 08a1daac 62a87846 043e9008 00000001 00030ed1 dbghelp!CDiaSession::findSymbolByAddr+0x2a9 0b 08a1dadc 62a87899 043e9008 00031ed1 00000000 dbghelp!CDiaSession::findSymbolByRVA+0x76 0c 08a1db10 62aed8f0 043e9008 00031ed1 00000000 dbghelp!CDiaSession::findSymbolByRVAEx+0x39 0d 08a1dbac 62b1a980 533b1ed1 00000000 00000000 dbghelp!FindSymbolByVA+0x117 0e 08a1dc00 629ed8da 533b1ed1 00000000 93b94d79 dbghelp!diaGetFpoFromAddr+0x6c 0f 08a1dc4c 629ed647 533b1ed1 00000000 93b94dad dbghelp!LookupFunctionEntryX86+0x23d 10 08a1dc98 629ed4c7 ffffffff 533b1ed1 00000000 dbghelp!SymFunctionTableAccess64AccessRoutines+0x167 11 08a1dcb4 629ec7a1 ffffffff 533b1ed1 00000000 dbghelp!FunctionTableAccessRoutineLocal+0x17 12 08a1dcd8 629eba25 533b1ed1 00000000 08a1dd10 dbghelp!DbhStackServices::GetFunctionEntry+0x31 13 08a1dd58 629eb126 00000001 08a1de00 08a1eda0 dbghelp!DbsStackUnwinder::StaticAdjustForNoReturn+0x4a 14 08a1ddbc 629e8a76 08a1ddf8 08a1dea8 08a1dec8 dbghelp!DbsX86StackUnwinder::UnwindAndUpdateInternalContext+0x136 15 08a1deec 629e88b4 08a1eda0 00000005 05482908 dbghelp!DbsStackUnwinder::DoDbhUnwind+0x177 16 08a1df1c 629e87b8 08a1eda0 00000005 05482908 dbghelp!DbsStackUnwinder::DbhUnwind+0x58 17 08a1e038 629e867e 08a1eda0 08a1f000 00725a60 dbghelp!PickX86Walk+0x109 18 08a1ecf4 62af77c9 08a1eda0 08a1f000 00000000 dbghelp!DoUnwindStackFrameUsingServices+0xbb 19 08a1ed4c 62b3ad4b 0000014c ffffffff fffffffe dbghelp!StackWalk2+0x1c9 1a 08a1eebc 2c7667c4 0000014c ffffffff fffffffe dbghelp!StackWalk64+0x8b WARNING: Stack unwind information not available. Following frames may be wrong. 1b 08a1f2cc 2c765ad4 00000000 04e57318 08a1f32c kmshell+0x3667c4 1c 08a1f2dc 7726369f 08a1f2f8 08a1f428 08a1f3d8 kmshell+0x365ad4 1d 08a1f32c 7725eb04 00000000 5338cc66 08a1fbac ntdll!RtlpCallVectoredHandlers+0xf8 1e 08a1f3c0 7726b66f 08a1f3d8 08a1f428 08a1f3d8 ntdll!RtlDispatchException+0x67 1f 08a1f3c0 753e5004 08a1f3d8 08a1f428 08a1f3d8 ntdll!KiUserExceptionDispatcher+0xf 20 08a1f8ec 754c7ab0 40010006 00000000 00000002 KERNELBASE!RaiseException+0x64 21 08a1fb54 533ab6a2 08a1fbac 5338cc66 08a1fe38 KERNELBASE!OutputDebugStringA+0x50 22 08a1fcb0 533b1ba3 53673110 5338cc66 08a1fe38 keyman32!_OutputThreadDebugString+0x42 [D:\Projects\keyman\app\windows\src\engine\keyman32\K32_DBG.CPP @ 291] 23 08a1fd18 533b1ed2 00000000 5338cc66 08a1fe38 keyman32!ReleaseKeyboards+0x13 [D:\Projects\keyman\app\windows\src\engine\keyman32\keyman32.cpp @ 841] 24 08a1fd70 533b1fad 00000000 5338cc66 08a1fe38 keyman32!UninitialiseProcess+0x22 [D:\Projects\keyman\app\windows\src\engine\keyman32\keyman32.cpp @ 160] 25 08a1fdcc 53431b12 53380000 00000003 00000000 keyman32!DllMain+0x5d [D:\Projects\keyman\app\windows\src\engine\keyman32\keyman32.cpp @ 143] 26 08a1fe10 53431cf0 53380000 00000003 00000000 keyman32!dllmain_dispatch+0xb2 [D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\dll_dllmain.cpp @ 281] 27 08a1fe24 77268ff6 53380000 00000003 00000000 keyman32!_DllMainCRTStartup+0x20 [D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\dll_dllmain.cpp @ 334] 28 08a1fe44 772a0df3 5338cc66 53380000 00000003 ntdll!LdrxCallInitRoutine+0x16 29 08a1fe68 7722b11c 00000003 00000000 fb42ae4c ntdll!LdrpCallInitRoutineInternal+0x22 2a 08a1feb0 77229812 00000003 00000000 fb42af9c ntdll!LdrpCallInitRoutine+0xae 2b 08a1ff60 77261670 1138e500 00000000 052cbe98 ntdll!LdrShutdownThread+0x1b2 2c 08a1ff74 757c5d50 00000000 757c5d30 08a1ffdc ntdll!RtlExitUserThread+0x30 2d 08a1ff84 7725d6db 052cbe98 fb42af20 00000000 kernel32!BaseThreadInitThunk+0x20 2e 08a1ffdc 7725d661 ffffffff 772a4680 00000000 ntdll!__RtlUserThreadStart+0x2b 2f 08a1ffec 00000000 1138e500 052cbe98 00000000 ntdll!_RtlUserThreadStart+0x1b ``` --- .../delphi/ext/sentry/Sentry.Client.pas | 45 ++++++++++++++----- 1 file changed, 34 insertions(+), 11 deletions(-) diff --git a/common/windows/delphi/ext/sentry/Sentry.Client.pas b/common/windows/delphi/ext/sentry/Sentry.Client.pas index 94faf74f85..2471a7900b 100644 --- a/common/windows/delphi/ext/sentry/Sentry.Client.pas +++ b/common/windows/delphi/ext/sentry/Sentry.Client.pas @@ -10,6 +10,8 @@ uses Winapi.Windows, jwaimagehlp, + jwantstatus, + jwawinbase, sentry; @@ -229,20 +231,41 @@ var Skip: Integer; LastMask: WORD; begin - // Floating point state may be broken here, so let's mask it out and continue - // We'll restore state afterwards - LastMask := Get8087CW; - Set8087CW_Threadsafe($1332); + try + if (ExceptionInfo.ExceptionRecord.ExceptionCode <> EXCEPTION_ACCESS_VIOLATION) and + (ExceptionInfo.ExceptionRecord.ExceptionCode <> EXCEPTION_ILLEGAL_INSTRUCTION) and + (ExceptionInfo.ExceptionRecord.ExceptionCode <> EXCEPTION_PRIV_INSTRUCTION) and + (ExceptionInfo.ExceptionRecord.ExceptionCode <> EXCEPTION_POSSIBLE_DEADLOCK) and + (ExceptionInfo.ExceptionRecord.ExceptionCode <> EXCEPTION_IN_PAGE_ERROR) and + (ExceptionInfo.ExceptionRecord.ExceptionCode <> EXCEPTION_STACK_OVERFLOW) and + (ExceptionInfo.ExceptionRecord.ExceptionCode <> STATUS_INVALID_PARAMETER) and + (ExceptionInfo.ExceptionRecord.ExceptionCode <> cDelphiException) then + begin + // These are the most common exceptions we wish to catch; we won't get + // call stacks for any other exception types, but we will still log + // important exceptions + Exit(0); + end; - if ExceptionInfo.ExceptionRecord.ExceptionCode = cDelphiException - then Skip := DELPHI_FRAMES_TO_SKIP - else Skip := 0; - CaptureStackTraceForException(ExceptionInfo.ExceptionRecord.ExceptionAddress, ExceptionInfo, Skip); + // Floating point state may be broken here, so let's mask it out and continue + // We'll restore state afterwards + LastMask := Get8087CW; + Set8087CW_Threadsafe($1332); - // Restore FP state - Set8087CW_Threadsafe(LastMask); + if ExceptionInfo.ExceptionRecord.ExceptionCode = cDelphiException + then Skip := DELPHI_FRAMES_TO_SKIP + else Skip := 0; + CaptureStackTraceForException(ExceptionInfo.ExceptionRecord.ExceptionAddress, ExceptionInfo, Skip); - Result := 0; //EXCEPTION_CONTINUE_SEARCH; + // Restore FP state + Set8087CW_Threadsafe(LastMask); + + Result := 0; //EXCEPTION_CONTINUE_SEARCH; + except + // Silently handle errors here because we can't do anything with them; + // we'll just get no stack + Result := 0; + end; end; constructor TSentryClient.Create(AOptions: TSentryClientOptions; const ALogger: string; AFlags: TSentryClientFlags); From 54642dc86df86652eee5b56001a649deb29f6cdf Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Thu, 13 Nov 2025 10:34:11 +0100 Subject: [PATCH 20/33] fix(windows): disable OutputDebugString from DllMain Following investigation, it seems that OutputDebugString can trigger a loader lock as it throws an exception which can be handled by a global exception handler, which may try and load/unload DLLs or perform other unsafe tasks (e.g. StackWalk). The safest solution is to remove the debug string logging altogether from code paths that run from DllMain. It would be nice to further simplify DllMain but that would be a significant amount of additional work. --- windows/src/engine/keyman32/k32_globals.cpp | 8 ++------ windows/src/engine/keyman32/keyman32.cpp | 9 +-------- .../engine/keyman32/serialkeyeventclient.cpp | 20 +++++++++---------- 3 files changed, 13 insertions(+), 24 deletions(-) diff --git a/windows/src/engine/keyman32/k32_globals.cpp b/windows/src/engine/keyman32/k32_globals.cpp index 2fa5319d68..807b246466 100644 --- a/windows/src/engine/keyman32/k32_globals.cpp +++ b/windows/src/engine/keyman32/k32_globals.cpp @@ -133,18 +133,14 @@ PKEYMAN64THREADDATA Globals_InitThread() void Globals_UninitThread() { - OutputThreadDebugString("Globals_UninitThread"); + // OutputThreadDebugString("Globals_UninitThread"); if (!Globals_ProcessInitialised()) { - OutputThreadDebugString("Globals_UninitThread aborted without cleanup"); + // OutputThreadDebugString("Globals_UninitThread aborted without cleanup"); return; } ISerialKeyEventClient::Shutdown(); - PKEYMAN64THREADDATA _td = ThreadGlobals(); - if (_td) { - delete _td->pSerialKeyEventClient; - } CloseThreadSharedBufferManager(); EnterCriticalSection(&csGlobals); diff --git a/windows/src/engine/keyman32/keyman32.cpp b/windows/src/engine/keyman32/keyman32.cpp index aadd695906..505de20175 100644 --- a/windows/src/engine/keyman32/keyman32.cpp +++ b/windows/src/engine/keyman32/keyman32.cpp @@ -119,27 +119,20 @@ BOOL __stdcall DllMain(HINSTANCE hinstDll, DWORD fdwReason, LPVOID reserved) switch(fdwReason) { case DLL_PROCESS_ATTACH: - //OutputThreadDebugString("DLL_PROCESS_ATTACH"); if(!Globals_InitProcess()) return FALSE; break; case DLL_PROCESS_DETACH: if (reserved == NULL) { // If reserved == NULL, that means the library is being unloaded, but // the process is not terminating. - //OutputThreadDebugString("DLL_PROCESS_DETACH not terminating"); UninitialiseProcess(FALSE); Globals_UninitProcess(); - } - else { - //OutputThreadDebugString("DLL_PROCESS_DETACH terminating"); } break; case DLL_THREAD_ATTACH: - //OutputThreadDebugString("DLL_THREAD_ATTACH"); Globals_InitThread(); break; case DLL_THREAD_DETACH: - //OutputThreadDebugString("DLL_THREAD_DETACH"); UninitialiseProcess(FALSE); Globals_UninitThread(); break; @@ -838,7 +831,7 @@ void RefreshKeyboards(BOOL Initialising) void ReleaseKeyboards(BOOL Lock) { - OutputThreadDebugString("ReleaseKeyboards"); + // OutputThreadDebugString("ReleaseKeyboards"); PKEYMAN64THREADDATA _td = ThreadGlobals(); if(!_td || !_td->lpKeyboards) return; diff --git a/windows/src/engine/keyman32/serialkeyeventclient.cpp b/windows/src/engine/keyman32/serialkeyeventclient.cpp index 5cf31b8189..676d44dd9b 100644 --- a/windows/src/engine/keyman32/serialkeyeventclient.cpp +++ b/windows/src/engine/keyman32/serialkeyeventclient.cpp @@ -22,25 +22,25 @@ public: m_hKeyEvent = OpenEvent(EVENT_MODIFY_STATE, FALSE, GLOBAL_KEY_EVENT_NAME); if (m_hKeyEvent == 0) { - DebugLastError("OpenEvent"); + // DebugLastError("OpenEvent"); return; } m_hKeyMutex = OpenMutex(MUTEX_ALL_ACCESS, FALSE, GLOBAL_KEY_MUTEX_NAME); if (m_hKeyMutex == 0) { - DebugLastError("OpenMutex"); + // DebugLastError("OpenMutex"); return; } m_hMMF = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, GLOBAL_FILE_MAPPING_NAME); if (m_hMMF == 0) { - DebugLastError("OpenFileMapping"); + // DebugLastError("OpenFileMapping"); return; } m_pSharedData = (SerialKeyEventSharedData *)MapViewOfFile(m_hMMF, FILE_MAP_ALL_ACCESS, 0, 0, sizeof(SerialKeyEventSharedData)); if (!m_pSharedData) { - DebugLastError("MapViewOfFile"); + // DebugLastError("MapViewOfFile"); return; } } @@ -48,25 +48,25 @@ public: SerialKeyEventClient::~SerialKeyEventClient() { if (m_pSharedData != NULL) { if (!UnmapViewOfFile(m_pSharedData)) { - DebugLastError("UnmapViewOfFile"); + // DebugLastError("UnmapViewOfFile"); } } if (m_hMMF != NULL) { if (!CloseHandle(m_hMMF)) { - DebugLastError("CloseHandle(m_hMMF)"); + // DebugLastError("CloseHandle(m_hMMF)"); } } if (m_hKeyMutex != NULL) { if (!CloseHandle(m_hKeyMutex)) { - DebugLastError("CloseHandle(m_hKeyMutex)"); + // DebugLastError("CloseHandle(m_hKeyMutex)"); } } if (m_hKeyEvent != NULL) { if (!CloseHandle(m_hKeyEvent)) { - DebugLastError("CloseHandle(m_hKeyEvent)"); + // DebugLastError("CloseHandle(m_hKeyEvent)"); } } } @@ -149,7 +149,7 @@ public: }; void ISerialKeyEventClient::Startup() { - OutputThreadDebugString("ISerialKeyEventClient::Startup"); + // OutputThreadDebugString("ISerialKeyEventClient::Startup"); PKEYMAN64THREADDATA _td = ThreadGlobals(); if (_td) { _td->pSerialKeyEventClient = new SerialKeyEventClient(); @@ -157,7 +157,7 @@ void ISerialKeyEventClient::Startup() { } void ISerialKeyEventClient::Shutdown() { - OutputThreadDebugString("ISerialKeyEventClient::Shutdown"); + // OutputThreadDebugString("ISerialKeyEventClient::Shutdown"); PKEYMAN64THREADDATA _td = ThreadGlobals(); if (_td && _td->pSerialKeyEventClient) { delete _td->pSerialKeyEventClient; From edca16f508787b06be9e0950602083a221a47b2d Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Fri, 19 Sep 2025 16:50:36 +0200 Subject: [PATCH 21/33] change(windows): include map2pdb generation for relevant Delphi projects Adds a call to map2pdb for Delphi projects, if map2pdb is an executable on the path. This way, we get .pdb files we can use for debugging and for performance profiling. This also replaces tds2dbg. If map2dbg is available, then the build and install actions will copy the PDB to be alongside the corresponding executable, making most debug actions relatively seamless for finding symbols. Also fixes up setup.exe in Windows and Developer to extend setup.exe to a 512-byte boundary after map2pdb patches the PE. Relates-to: #14787 Build-bot: skip release:developer,windows Test-bot: skip --- common/windows/delphi/ext/build.sh | 2 +- common/windows/delphi/ext/sentry/build.sh | 6 +- common/windows/delphi/ext/tds2dbg.txt | 49 - common/windows/delphi/ext/tds2dbg/.gitignore | 1 - common/windows/delphi/ext/tds2dbg/Build.txt | 8 - common/windows/delphi/ext/tds2dbg/ChangeLog | 12 - common/windows/delphi/ext/tds2dbg/LICENSE | 15 - common/windows/delphi/ext/tds2dbg/Makefile | 22 - common/windows/delphi/ext/tds2dbg/ReadMe.txt | 64 - common/windows/delphi/ext/tds2dbg/build.sh | 34 - .../windows/delphi/ext/tds2dbg/fileutils.cpp | 158 - common/windows/delphi/ext/tds2dbg/fileutils.h | 564 ---- common/windows/delphi/ext/tds2dbg/log.cpp | 44 - common/windows/delphi/ext/tds2dbg/log.h | 82 - common/windows/delphi/ext/tds2dbg/main.cpp | 115 - common/windows/delphi/ext/tds2dbg/tds2dbg.bin | Bin 259584 -> 0 bytes common/windows/delphi/ext/tds2dbg/tds2dbg.cpp | 2568 ----------------- common/windows/delphi/ext/tds2dbg/tds2dbg.dcf | 1515 ---------- common/windows/delphi/ext/tds2dbg/tds2dbg.h | 38 - common/windows/delphi/ext/tds2dbg/tds2dbg.sln | 31 - .../delphi/ext/tds2dbg/tds2dbg.vcxproj | 119 - .../ext/tds2dbg/tds2dbg.vcxproj.filters | 50 - .../windows/delphi/ext/tds2dbg/tdscvstructs.h | 1278 -------- common/windows/delphi/ext/tds2dbg/testa.cpp | 27 - developer/src/kmconvert/build.sh | 14 +- developer/src/kmdbrowserhost/build.sh | 14 +- developer/src/setup/build.sh | 31 +- developer/src/tike/build.sh | 10 +- resources/build/win/delphi_flags.inc.sh | 1 - resources/build/win/environment.inc.sh | 30 +- windows/src/desktop/inst/build.sh | 4 - windows/src/desktop/insthelp/build.sh | 7 +- windows/src/desktop/kmbrowserhost/build.sh | 14 +- windows/src/desktop/kmconfig/build.sh | 14 +- windows/src/desktop/kmshell/build.sh | 9 +- windows/src/desktop/setup/build.sh | 27 +- windows/src/engine/insthelper/build.sh | 7 +- windows/src/engine/keyman/build.sh | 14 +- windows/src/engine/keymanmc/build.sh | 8 +- windows/src/engine/kmcomapi/build.sh | 11 +- windows/src/engine/tsysinfo/build.sh | 8 +- windows/src/engine/tsysinfox64/build.sh | 9 +- 42 files changed, 146 insertions(+), 6888 deletions(-) delete mode 100644 common/windows/delphi/ext/tds2dbg.txt delete mode 100644 common/windows/delphi/ext/tds2dbg/.gitignore delete mode 100644 common/windows/delphi/ext/tds2dbg/Build.txt delete mode 100644 common/windows/delphi/ext/tds2dbg/ChangeLog delete mode 100644 common/windows/delphi/ext/tds2dbg/LICENSE delete mode 100644 common/windows/delphi/ext/tds2dbg/Makefile delete mode 100644 common/windows/delphi/ext/tds2dbg/ReadMe.txt delete mode 100755 common/windows/delphi/ext/tds2dbg/build.sh delete mode 100644 common/windows/delphi/ext/tds2dbg/fileutils.cpp delete mode 100644 common/windows/delphi/ext/tds2dbg/fileutils.h delete mode 100644 common/windows/delphi/ext/tds2dbg/log.cpp delete mode 100644 common/windows/delphi/ext/tds2dbg/log.h delete mode 100644 common/windows/delphi/ext/tds2dbg/main.cpp delete mode 100644 common/windows/delphi/ext/tds2dbg/tds2dbg.bin delete mode 100644 common/windows/delphi/ext/tds2dbg/tds2dbg.cpp delete mode 100644 common/windows/delphi/ext/tds2dbg/tds2dbg.dcf delete mode 100644 common/windows/delphi/ext/tds2dbg/tds2dbg.h delete mode 100644 common/windows/delphi/ext/tds2dbg/tds2dbg.sln delete mode 100644 common/windows/delphi/ext/tds2dbg/tds2dbg.vcxproj delete mode 100644 common/windows/delphi/ext/tds2dbg/tds2dbg.vcxproj.filters delete mode 100644 common/windows/delphi/ext/tds2dbg/tdscvstructs.h delete mode 100644 common/windows/delphi/ext/tds2dbg/testa.cpp diff --git a/common/windows/delphi/ext/build.sh b/common/windows/delphi/ext/build.sh index 05dffca69d..8097e5d1bb 100755 --- a/common/windows/delphi/ext/build.sh +++ b/common/windows/delphi/ext/build.sh @@ -8,7 +8,7 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" builder_describe \ "Keyman common third party Delphi components" \ clean configure build test reset \ - :cef4delphi :dcpcrypt :jwa :sentry :tds2dbg + :cef4delphi :dcpcrypt :jwa :sentry builder_parse "$@" builder_run_child_actions clean configure build test diff --git a/common/windows/delphi/ext/sentry/build.sh b/common/windows/delphi/ext/sentry/build.sh index c0b0cc9147..fcbd94b70e 100755 --- a/common/windows/delphi/ext/sentry/build.sh +++ b/common/windows/delphi/ext/sentry/build.sh @@ -24,10 +24,8 @@ function do_test() { delphi_msbuild SentryClientTest.dproj "//p:Platform=Win32" delphi_msbuild SentryClientVclTest.dproj "//p:Platform=Win32" - sentrytool_delphiprep "$WIN32_TARGET_PATH/SentryClientTest.exe" -dpr SentryClientTest.dpr - sentrytool_delphiprep "$WIN32_TARGET_PATH/SentryClientVclTest.exe" -dpr SentryClientVclTest.dpr - tds2dbg "$WIN32_TARGET_PATH/SentryClientTest.exe" - tds2dbg "$WIN32_TARGET_PATH/SentryClientVclTest.exe" + do_map2pdb "$WIN32_TARGET_PATH/SentryClientTest.map" "$WIN32_TARGET" + do_map2pdb "$WIN32_TARGET_PATH/SentryClientVclTest.map" "$WIN32_TARGET" sentry-cli upload-dif -p keyman-windows --wait --include-sources . diff --git a/common/windows/delphi/ext/tds2dbg.txt b/common/windows/delphi/ext/tds2dbg.txt deleted file mode 100644 index fd23f6f915..0000000000 --- a/common/windows/delphi/ext/tds2dbg.txt +++ /dev/null @@ -1,49 +0,0 @@ -Keyman Third Party Package Readme: Tds2Dbg -=========================================== - -Name: Tds2Dbg -Purpose: Tds2Dbg converts .tds to .dbg files -Source: https://sourceforge.net/projects/tds2dbg/ -Version: 2014-12-22 -License: ISC (MIT compatible) -Updated for: N/A -Documentation: https://sourceforge.net/projects/tds2dbg/ -Download location: https://sourceforge.net/projects/tds2dbg/ -Licensed For: Unlimited developers -Include Paths: N/A - - -Installation notes ------------------- - -Download from site above. - -Build notes ------------ - -tds2dbg is used for building .dbg files from .tds files so that Delphi -projects can be debugged in WinDbg/Visual Studio and with dump files. - -This project builds with Borland C++ 5.5 and not other compilers; -while it may build successfully with other compilers, there appear to -be alignment issues. Hence, the executable is included in the repository -to avoid requiring another build tool. - -License - ISC -------------- - -Copyright (c) 2009-2010, Bjarne Juul Pasgaard - -Permission to use, copy, modify, and/or distribute this software for any -purpose with or without fee is hereby granted, provided that the above -copyright notice, this permission notice and the following disclaimer -appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - diff --git a/common/windows/delphi/ext/tds2dbg/.gitignore b/common/windows/delphi/ext/tds2dbg/.gitignore deleted file mode 100644 index 65776c32fc..0000000000 --- a/common/windows/delphi/ext/tds2dbg/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/bin/ \ No newline at end of file diff --git a/common/windows/delphi/ext/tds2dbg/Build.txt b/common/windows/delphi/ext/tds2dbg/Build.txt deleted file mode 100644 index c8bb64961f..0000000000 --- a/common/windows/delphi/ext/tds2dbg/Build.txt +++ /dev/null @@ -1,8 +0,0 @@ -Get the free Borland C++ Builder 5.5 from - https://downloads.embarcadero.com/free/c_builder - -Ensure that the location of bcc32.exe is in your path -and type "make.exe" in a command prompt with the -current working directory set to the directory -holder the sources for tds2dbg. - diff --git a/common/windows/delphi/ext/tds2dbg/ChangeLog b/common/windows/delphi/ext/tds2dbg/ChangeLog deleted file mode 100644 index 6f5f67398e..0000000000 --- a/common/windows/delphi/ext/tds2dbg/ChangeLog +++ /dev/null @@ -1,12 +0,0 @@ -tds2dbg 0.2.0 - - * Added support for conversion of public and static symbols. - * Corrected padding and alignment. - * Preliminary support for conversion of types. Still incomplete. - * Can now convert a non-PE CodeView NB09 file to a PE CodeView file. - * Can now generate bare CodeView files (i.e. no PE header). - -tds2dbg 0.1.0 - - * Initial version - diff --git a/common/windows/delphi/ext/tds2dbg/LICENSE b/common/windows/delphi/ext/tds2dbg/LICENSE deleted file mode 100644 index 0d7784e0f5..0000000000 --- a/common/windows/delphi/ext/tds2dbg/LICENSE +++ /dev/null @@ -1,15 +0,0 @@ -Copyright (c) 2009-2010, Bjarne Juul Pasgaard - -Permission to use, copy, modify, and/or distribute this software for any -purpose with or without fee is hereby granted, provided that the above -copyright notice, this permission notice and the following disclaimer -appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - diff --git a/common/windows/delphi/ext/tds2dbg/Makefile b/common/windows/delphi/ext/tds2dbg/Makefile deleted file mode 100644 index e01296837e..0000000000 --- a/common/windows/delphi/ext/tds2dbg/Makefile +++ /dev/null @@ -1,22 +0,0 @@ -info: - echo The following builds require Borland C++ compiler to be installed and in the path; see Readme.txt for details. - echo So, the tds2dbg.bin is kept in the repo in order to make builds simpler - -BCC55DIR=c:\borland\bcc55 - -tds2dbg.exe: main.cpp tds2dbg.cpp fileutils.cpp log.cpp -# $(CL) /Fe$< /EHsc $** - path $(BCC55DIR)\bin;$(PATH) - $(BCC55DIR)\bin\bcc32 -y -v -L$(BCC55DIR)\Lib -I$(BCC55DIR)\Include -e$< $** - -test.exe: testa.cpp -# $(CL) /Fe$< /EHsc $** - $(BCC55DIR)\bin\bcc32 -y -v -M -L$(BCC55DIR)\Lib -I$(BCC55DIR)\Include -e$< $** - -clean: - -del /Q tds2dbg.obj fileutils.obj log.obj main.obj tds2dbg.tds - -del /Q testa.obj test.exe test.tds test.dbg - -rmdir /S /Q html - -doc: - doxygen tds2dbg.dcf diff --git a/common/windows/delphi/ext/tds2dbg/ReadMe.txt b/common/windows/delphi/ext/tds2dbg/ReadMe.txt deleted file mode 100644 index 6184609438..0000000000 --- a/common/windows/delphi/ext/tds2dbg/ReadMe.txt +++ /dev/null @@ -1,64 +0,0 @@ -NAME - - tds2dbg - Borland TDS debug symbol file to Microsft DBG debug symbol file - converter. - -SYNOPSIS - - tds2dbg [ [-h|--help] | [-t|--types] [-s|--statics] [--] program.exe ] - -DESCRIPTION - - tds2dbg will read the Borland debug symbol file associated with the - Microsoft Windows executable program.exe (program.tds) and create a - corresponding Microsoft DBG debug symbol file (program.dbg). Additionally, - it will modify a flag in program.exe to signal that debug symbols are - available externally to the executable. - - The dbg files can be used with, say, windbg available from the - Microsoft Debugging Tools at the Microsoft Windows Hardware Developer - Central (http://www.microsoft.com/whdc). - -OPTIONS - - -h, --help Prints a help text. Running tds2dbg without arguments - will do the same. - -v, --verbose Each occurence of this option increases verbosity. - -q, --quiet Operate silently. - -t, --types Convert types (incomplete feature). - -s, --statics Treat statics as publics. - -b, --barecv Write bare code view data. No PE header is added. - -KNOWN ISSUES - - tds2dbg is currently only able to insert source code references, public - symbols, and static symbols into the generated DBG file. Debug information - on local variables is partially generated, but no debugger seems to be - able to use this information. Additionally, conversion of types is not - fully supported and is only done on request (the -t option). - -VERSION - - This is the documentation for tds2dbg version 0.2.0 - -AUTHOR - - Copyright (C) 2009-2010, Bjarne Juul Pasgaard - -LICENSE - - Copyright (C) 2009-2010, Bjarne Juul Pasgaard - - Permission to use, copy, modify, and/or distribute this software for any - purpose with or without fee is hereby granted, provided that the above - copyright notice, this permission notice and the following disclaimer - appear in all copies. - - THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - diff --git a/common/windows/delphi/ext/tds2dbg/build.sh b/common/windows/delphi/ext/tds2dbg/build.sh deleted file mode 100755 index 805f957ea1..0000000000 --- a/common/windows/delphi/ext/tds2dbg/build.sh +++ /dev/null @@ -1,34 +0,0 @@ -#!/usr/bin/env bash -## START STANDARD BUILD SCRIPT INCLUDE -# adjust relative paths as necessary -THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" -. "${THIS_SCRIPT%/*}/../../../../../resources/build/builder-full.inc.sh" -## END STANDARD BUILD SCRIPT INCLUDE - -# TODO: move this out of ext and into tools - -builder_describe "tds2dbg component" clean configure build test -builder_parse "$@" - -#------------------------------------------------------------------------------------------------------------------- - -source "$KEYMAN_ROOT/resources/build/win/environment.inc.sh" - -builder_describe_outputs \ - configure:project /resources/build/win/delphi_environment_generated.inc.sh \ - build:project "$WIN32_TARGET_PATH/tds2dbg.exe" - -#------------------------------------------------------------------------------------------------------------------- - -function do_build() { - mkdir -p "$WIN32_TARGET_PATH" - cp tds2dbg.bin "$WIN32_TARGET_PATH/tds2dbg.exe" - - mkdir -p "$KEYMAN_ROOT/common/windows/bin/tools" - cp "$WIN32_TARGET_PATH/tds2dbg.exe" "$KEYMAN_ROOT/common/windows/bin/tools/" -} - -builder_run_action clean:project clean_windows_project_files -builder_run_action configure:project configure_windows_build_environment -builder_run_action build:project do_build -# builder_run_action test:project do_test diff --git a/common/windows/delphi/ext/tds2dbg/fileutils.cpp b/common/windows/delphi/ext/tds2dbg/fileutils.cpp deleted file mode 100644 index f25013fcb7..0000000000 --- a/common/windows/delphi/ext/tds2dbg/fileutils.cpp +++ /dev/null @@ -1,158 +0,0 @@ -// Copyright (c) 2009-2010, Bjarne Juul Pasgaard -// -// Permission to use, copy, modify, and/or distribute this software for any -// purpose with or without fee is hereby granted, provided that the above -// copyright notice, this permission notice and the following disclaimer -// appear in all copies. -// -// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - -/// @file fileutils.cpp File utilities - -#include "fileutils.h" - -FileWrapper::FileWrapper(FILE* f) : m_f(f) -{ -} - -unsigned int -FileWrapper::tell() const { - return ftell(m_f); -} - -void -FileWrapper::seek(unsigned int pos) throw(std::runtime_error) { - if(fseek(m_f,pos,SEEK_SET)) { - throw std::runtime_error("Failed to set read/write position"); - } -} - -void -FileWrapper::skip(unsigned int amount) throw(std::runtime_error) { - if(fseek(m_f,amount,SEEK_CUR)) { - throw std::runtime_error("Failed to advance read/write position"); - } -} - -void -FileWrapper::write1bLpfName(const std::string& name) throw(std::runtime_error) -{ - unsigned char len = (unsigned char) (name.length()); - - if((sizeof len) != fwrite(&len,1,(sizeof len),m_f)) { - throw std::runtime_error("Failed to write length field of string"); - } - if((unsigned int)(len+1) != fwrite(name.c_str(),1,len+1,m_f)) { - throw std::runtime_error("Failed to write string contents"); - } -} - -RecordReader::RecordReader(FileWrapper& tdsFile, unsigned int recSz) : - m_tdsFile(tdsFile) -{ - m_recPos = tdsFile.tell(); - m_endPos = m_recPos + recSz; -} - -RecordWriter::~RecordWriter() -{ - WriteQCont::iterator it = m_writeQ.begin(); - while(m_writeQ.end() != it) { - delete *it++; - } -} - -void -RecordWriter::commit(FileWrapper& f) const throw(std::runtime_error) -{ - WriteQCont::const_iterator it = m_writeQ.begin(); - while(m_writeQ.end() != it) { - (*it++)->commit(f); - } -} - -unsigned int -RecordWriter::length() const -{ - unsigned int len = 0; - WriteQCont::const_iterator it = m_writeQ.begin(); - while(m_writeQ.end() != it) { - len += (*it++)->length(); - } - return len; -} - -void -RecordWriter::write1bLpfName(const std::string& name) -{ - unsigned char len = (unsigned char) (name.length()); - - this->write(len); - this->write(*(name.c_str()),len+1); -} - -RecordWriter::WriteQueueElement::~WriteQueueElement() -{ -} - -unsigned int -RecordWriter::CopiedData::length() const -{ - return m_data.size(); -} - -void -RecordWriter::CopiedData::commit(FileWrapper& f) const throw(std::runtime_error) -{ - f.write(m_data.front(),m_data.size()); -} - -RecordWriter::WriteQueueElement* -RecordWriter::CopiedData::append(const unsigned char* src, unsigned int len) -{ - m_data.reserve(m_data.size()+len); - while(len--) { - m_data.push_back(*src++); - } - return 0; -} - -RecordWriter::ReferencedData:: -ReferencedData(const unsigned char* src, unsigned int len) - : m_src(src), m_len(len) -{ -} - -unsigned int -RecordWriter::ReferencedData::length() const -{ - return m_len; -} - -void -RecordWriter::ReferencedData::commit(FileWrapper& f) const - throw(std::runtime_error) -{ - f.write(*m_src,m_len); -} - -RecordWriter::WriteQueueElement* -RecordWriter::ReferencedData::append(const unsigned char* src, unsigned int len) -{ - CopiedData* c = new CopiedData(); - c->append(src,len); - return c; -} - - -// Local Variables: -// mode:c++ -// c-basic-offset: 3 -// indent-tabs-mode: nil -// End: diff --git a/common/windows/delphi/ext/tds2dbg/fileutils.h b/common/windows/delphi/ext/tds2dbg/fileutils.h deleted file mode 100644 index 0d7d09af95..0000000000 --- a/common/windows/delphi/ext/tds2dbg/fileutils.h +++ /dev/null @@ -1,564 +0,0 @@ -// Copyright (c) 2009-2010, Bjarne Juul Pasgaard -// -// Permission to use, copy, modify, and/or distribute this software for any -// purpose with or without fee is hereby granted, provided that the above -// copyright notice, this permission notice and the following disclaimer -// appear in all copies. -// -// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - -/// @file fileutils.h File utilities - -#ifndef FILEUTILS_H -#define FILEUTILS_H - -#include -#include - -/// @brief Base class for file access wrappers. -class FileWrapper { -public: - - /// @brief Constructor. - /// - /// @param f The file stream being wrapped. Ownership not transferred. - FileWrapper(FILE* f); - - /// @brief Returns the current read/write position. - /// @return The current write position. - unsigned int tell() const; - - /// @brief Sets the current read/write position. - /// - /// @param pos The new (absolute) write position. - /// - /// @throw runtime_error If an error occured. - void seek(unsigned int pos) throw(std::runtime_error); - - /// @brief Skips a number of bytes from the input stream. - /// - /// @param amount The number of bytes to skip. - /// - /// @throw runtime_error If an error occured. - void skip(unsigned int amount) throw(std::runtime_error); - - /// @brief Reads a sequence of structures from the current read - /// position and throws an exception if end of file is - /// reached prematurely. - /// - /// @param dst The 1st structure to hold the data read in. - /// @param num The number of structures to read. - /// - /// @tparam The class of the structures to read. - /// - /// @throw runtime_error If not all bytes could be read. - template - void read(T& dst, unsigned int num = 1) throw(std::runtime_error) { - unsigned int n = fread(&dst,1,num*sizeof(T),m_f); - if(num*sizeof(T) != n) { - throw std::runtime_error("Unexpected end of file"); - } - } - - /// @brief Attempts to reads a sequence of structures from the - /// current read position. - /// - /// @param dst The 1st structure to hold the data read in. - /// @param num The number of structures to read. - /// - /// @return The number of bytes actually read. - /// - /// @tparam The class of the structures to read. - template - unsigned int tryRead(T& dst, unsigned int num = 1) { - return fread(&dst,1,num*sizeof(T),m_f); - } - - /// @brief Writes a sequence of bytes to the current write position. - /// - /// @param src Reference to the 1st structure to be written. - /// @param num The number of instances to write. - /// - /// @return The number of bytes actually written. - /// - /// @tparam T Class of the structure to write. - /// - /// @throw runtime_error If an error occured. - template - unsigned int - write(const T& src, unsigned int num = 1) throw(std::runtime_error) { - unsigned int n = fwrite(&src,1,num*sizeof(T),m_f); - if(n != num*sizeof(T)) { - throw std::runtime_error("Write error in write()"); - } - return n; - } - - /// @brief Writes multiple copies of the same structure starting from the - /// current write position. - /// - /// @param src Reference to the structure to be written. - /// @param num The number of copies to write. - /// - /// @return The number of bytes actually written. - /// - /// @tparam T Class of the structure to write. - /// - /// @throw runtime_error If an error occured. - template - unsigned int - writeRep(const T& src, unsigned int num = 1) throw(std::runtime_error); - - /// @brief Writes a (1-byte) length-prefixed name to the current - /// write position. - /// - /// @param name The name to write. - /// - /// @throw runtime_error If an error occured. - void write1bLpfName(const std::string& name) throw(std::runtime_error); - -protected: - - FILE* m_f; ///< The file stream. - -}; - - -template -unsigned int -FileWrapper::writeRep(const T& src, unsigned int num) - throw(std::runtime_error) -{ - unsigned int n = 0; - while(num--) { - unsigned int m = fwrite(&src,1,sizeof(T),m_f); - if(sizeof(T) != m) { - throw std::runtime_error("Write error in writeRep()"); - } - n += m; - } - return n; -} - -/// @brief Reads a record sequentially from a file while keeping track -/// of the number of bytes available. -class RecordReader { -public: - - /// @brief Constructor. - /// - /// @param tdsFile The file to read from. - /// @param recSz The number of bytes in the record. - RecordReader(FileWrapper& tdsFile, unsigned int recSz); - - /// @brief Returns the current read position. - /// - /// @return The current (absolute) read position. - unsigned int tell() { - return m_tdsFile.tell(); - } - - /// @brief Determines whether an end-of-record condition is met. - /// - /// @retval true if all bytes have been read. - /// @retval false otherwise. - bool eor() const { - return (m_tdsFile.tell() >= m_endPos); - } - - /// @brief Sets the current absolute read position. - /// - /// @param pos The new (absolute) read position. - /// - /// @throw runtime_error In case of errors. - void seek(unsigned int pos) throw(std::runtime_error) { - m_tdsFile.seek(pos); - } - - /// @brief Skips a number of bytes from the input stream. - /// - /// @param amount The number of bytes to skip. - /// - /// @throw runtime_error In case of errors. - void skip(unsigned int amount) throw(std::runtime_error) { - m_tdsFile.skip(amount); - } - - /// @brief Reads a sequence of structures from the current read - /// position in the TDS file. - /// - /// @param dst The 1st structure to hold the data read in. - /// @param num The number of structures to read. - /// - /// @throw runtime_error In case of errors. - template - void read(T& dst, unsigned int num = 1) throw(std::runtime_error) { - m_tdsFile.read(dst,num); - } - -protected: - - FileWrapper& m_tdsFile; ///< The file to read from. - - unsigned int m_recPos; ///< The position of the current record. - - unsigned int m_endPos; ///< The position after the end of the record list. - -}; - -/// @brief Registers data for later writing to a file while counting -/// the number of bytes to be written. -class RecordWriter { -public: - - /// @brief Destructor. - ~RecordWriter(); - - /// @brief Commits the data registered to a file at the current write - /// position of the file. - /// - /// @param file The file to write to. - /// - /// @throw runtime_error In case of errors. - void commit(FileWrapper& f) const throw(std::runtime_error); - - /// @brief Returns the number of bytes registered for later writing. - /// - /// @return The number of bytes registered. - unsigned int length() const; - - /// @brief Appends a sequence of structures to the tail of the - /// write queue. - /// - /// The data provided is copied into the internal write queue. - /// - /// @param dst The 1st structure that holds the data to be written. - /// @param num The number of structures to write. - /// - /// @return The number of bytes written. - template - unsigned int write(const T& src, unsigned int num = 1) { - unsigned int n = sizeof(T)*num; - WriteQueueElement* e; - - if(0 == m_writeQ.size()) { - e = new CopiedData(); - m_writeQ.push_back(e); - } - - e = m_writeQ.back()->append(reinterpret_cast(&src),n); - if(e) { - // New element created, append it to the queue. - m_writeQ.push_back(e); - } - return n; - } - - /// @brief Writes multiple copies of the same structure starting from the - /// current write position. - /// - /// @param src Reference to the structure to be written. - /// @param num The number of copies to write. - /// - /// @return The number of bytes actually written. - /// - /// @tparam T Class of the structure to write. - template - unsigned int writeRep(const T& src, unsigned int num = 1) { - unsigned int n = 0; - while(num--) { - n+= this->write(src); - } - return n; - } - - /// @brief Appends a sequence of structures to the tail of the - /// write queue. - /// - /// This menthod appends a reference to the data to the write - /// queue. The data provided is not copied. - /// - /// @param dst The 1st structure that holds the data to be written. - /// @param num The number of structures to write. - /// - /// @return The number of bytes written. - template - unsigned int delayedWrite(const T& src, unsigned int num = 1) { - unsigned int n = sizeof(T)*num; - m_writeQ. - push_back(new - ReferencedData(reinterpret_cast(&src),n)); - return n; - } - - /// @brief Appends a (1-byte) length-prefixed name to the write queue. - /// - /// @param name The name to write. - void write1bLpfName(const std::string& name); - -protected: - - /// @brief Base class for write queue data. - class WriteQueueElement - { - public: - - /// @brief Destructor. - virtual ~WriteQueueElement(); - - /// @brief Returns the number of bytes registered within this element. - /// - /// @return The number of bytes registered within this element. - virtual unsigned int length() const = 0; - - /// @brief Commits the data in this element to the current write - /// position of a file. - /// - /// @param f The file to write to. - /// - /// @throw runtime_error In case of errors. - virtual void commit(FileWrapper& f) const throw(std::runtime_error) = 0; - - /// @brief Appends a copy of a sequence of bytes to the current - /// element. - /// - /// @param src The first byte to copy. - /// @param len The number of bytes to copy. - /// - /// @return 0 if the bytes were appended to the current element, or - /// a pointer to a new element that received the copied bytes. - virtual - WriteQueueElement* append(const unsigned char* src, unsigned int len) = 0; - - }; - - /// @brief Data copied into the write queue. - class CopiedData : public WriteQueueElement - { - public: - - // Overridden base class method. - unsigned int length() const; - - // Overridden base class method. - void commit(FileWrapper& f) const throw(std::runtime_error); - - // Overridden base class method. - WriteQueueElement* append(const unsigned char* src, unsigned int len); - - protected: - - std::vector m_data; - - }; - - /// @brief Data referenced from the write queue. - class ReferencedData : public WriteQueueElement - { - public: - - // Overridden base class method. - unsigned int length() const; - - // Overridden base class method. - void commit(FileWrapper& f) const throw(std::runtime_error); - - // Overridden base class method. - WriteQueueElement* append(const unsigned char* src, unsigned int len); - - /// @brief Constructor. - /// - /// @param src The first byte to reference. - /// @param len The number of subsequent bytes to reference. - ReferencedData(const unsigned char* src, unsigned int len); - - protected: - - const unsigned char* m_src; ///< The first byte referenced. - unsigned int m_len; ///< The number of bytes referenced. - - }; - -protected: - - /// @brief Container underlying the internal write queue. - typedef std::vector WriteQCont; - - /// @brief The internal write queue. - WriteQCont m_writeQ; - -}; - -/// @brief Postpones writing of a record until committed. -/// -/// @note It is important to create an instance of this class before writing -/// any records that should follow it (in file order). -/// -/// @tparam R The record structure. -template -class PpRecordWriter { -public: - - /// @brief Constructor. - /// - /// @param dbgFile The DBG file to write to. - /// @param rec The record to be written when committed to. - PpRecordWriter(FileWrapper& dbgFile, R& rec) : - m_dbgFile(dbgFile), m_rec(rec), m_len(0) - { - m_recPos = dbgFile.tell(); - dbgFile.skip(sizeof(R)); - } - - /// @brief Commits to the record. - /// - /// This method will write out the record. - /// - /// @throw runtime_error In case of errors. - void commit() throw(std::runtime_error) { - unsigned int oldPos = m_dbgFile.tell(); - m_dbgFile.seek(m_recPos); - m_len = m_dbgFile.write(m_rec); - m_dbgFile.seek(oldPos); - } - - /// @brief Returns the number of bytes written. - /// @return The number of bytes written. - unsigned int length() const { - return m_len; - } - -protected: - - /// @brief The DBG file to write to. - FileWrapper& m_dbgFile; - - /// @breif The record to write. - R& m_rec; - - /// @brief File position of the record. - unsigned int m_recPos; - - /// @brief Number of bytes written. - unsigned int m_len; - -}; - -/// @brief Postpones writing of a header with a length field to be -/// computed until committed. -/// -/// @tparam H The header structure. -/// @tparam L The type of the length field. -template -class HeaderWithLen { -public: - - /// @brief Constructor. - /// - /// @param hdr The header to be written when committed to. - /// Must remain available during the entire lifetime of the - /// object created. - /// @param lenFld The length field to update. Must remaining available - /// during the entire lifetime of the object created. - HeaderWithLen(H& hdr, L& lenFld) : m_hdr(hdr), m_lenFld(lenFld) - { - } - - /// @brief Writes the header and the data registered by a RecordWriter. - /// - /// This method will write out the header with an updated length field - /// as well as the data registered by the RecordWriter. - /// The length field will be computed as the size of the header, plus the - /// size of the data written by the RecordWriter, minus the size of the - /// length field itself. - /// - /// @param f The file to write to. - /// @param w The record writer that was used to write the data committed to. - /// - /// @throw runtime_error In case of errors. - void - commit(FileWrapper& f, const RecordWriter& w) throw(std::runtime_error){ - m_lenFld = sizeof(H) + w.length()-sizeof(L); - f.write(m_hdr); - w.commit(f); - } - - /// @brief Returns the number of bytes (to be) written by the record writer - /// and the header writer itself. - /// - /// @return The number of bytes (to be) written. - unsigned int length(const RecordWriter& w) const { - return sizeof(H) + w.length(); - } - -protected: - - /// @brief The header to write. - H& m_hdr; - - /// @brief The length field to update. - L& m_lenFld; - -}; - -/// @brief Writes a sequence of bytes from one file object to another. -/// -/// @param dst Destination file object. -/// @param src Source file object. -/// @param amount Number of bytes to copy. -/// -/// @tparam D Class of the destination object. -/// @tparam S Class of the source object. -/// -/// @throw runtime_error In case of errors. -template -void -copyFileContent(D& dst, S& src, unsigned int amount = 1) - throw(std::runtime_error) -{ - unsigned char buf[512]; - unsigned int toRead = (sizeof buf); - while(amount) { - if(toRead>amount) { - toRead = amount; - } - src.read(buf[0],toRead); - amount -= toRead; - dst.write(buf[0],toRead); - } -} - -/// @brief Appends the contents of one file to another. -/// -/// @param dst Destination file object. -/// @param src Source file object. -/// -/// @tparam D Class of the destination object. -/// @tparam S Class of the source object. -/// -/// @throw runtime_error In case of errors. -template -void -copyFile(D& dst, S& src) throw(std::runtime_error) -{ - unsigned char buf[512]; - unsigned int bytesRead; - do { - bytesRead = src.tryRead(buf[0],(sizeof buf)); - dst.write(buf[0],bytesRead); - } while((sizeof buf) == bytesRead); -} - -#endif - -// Local Variables: -// mode:c++ -// c-basic-offset: 3 -// indent-tabs-mode: nil -// End: diff --git a/common/windows/delphi/ext/tds2dbg/log.cpp b/common/windows/delphi/ext/tds2dbg/log.cpp deleted file mode 100644 index b6dc31d4c7..0000000000 --- a/common/windows/delphi/ext/tds2dbg/log.cpp +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (c) 2009-2010, Bjarne Juul Pasgaard -// -// Permission to use, copy, modify, and/or distribute this software for any -// purpose with or without fee is hereby granted, provided that the above -// copyright notice, this permission notice and the following disclaimer -// appear in all copies. -// -// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - -/// @file log.cpp Log utilitites - -#include "log.h" - -std::ostream& Log::operator()(unsigned int verbosity) -{ - if(verbosity - -/// @brief Logger class. -class Log -{ -public: - - /// @brief Constructor. - Log() : m_verbosity(0), m_nullStream(&m_nullStreamBuf) {} - - /// @brief Sets the level of verbosity - /// - /// @param verbosity New level of verbosity. 0 means silence. - void setVerbosity(unsigned int verbosity) { m_verbosity = verbosity; } - - /// @brief Returns a reference to a stream handling the requested level - /// of verbosity. - /// - /// @param verbosity The requested level of verbosity. - std::ostream& operator()(unsigned int verbosity = 0); - -protected: - - /// @brief Helper class implementing a dummy stream buffer that - /// discards everything sent to it. - /// - /// @ingroup HMILoggingFeatures - class NullStreamBuf : public std::streambuf - { - public: - - /// @brief Constructor - NullStreamBuf(); - - }; - -protected: - - /// @brief The current level of verbosity. - unsigned int m_verbosity; - - /// @brief Supporting stream buffer for m_nullStream. - NullStreamBuf m_nullStreamBuf; - - /// @brief Internal stream used as a null-stream that discards - /// everything streamed to it. - std::ostream m_nullStream; - -}; - -/// @brief Accesser for the global Log instance. -/// -/// @return Reference to the global log. -Log& mylog(); - -#endif LOG_H - -// Local Variables: -// mode:c++ -// c-basic-offset: 3 -// indent-tabs-mode: nil -// End: diff --git a/common/windows/delphi/ext/tds2dbg/main.cpp b/common/windows/delphi/ext/tds2dbg/main.cpp deleted file mode 100644 index c0cbd579a7..0000000000 --- a/common/windows/delphi/ext/tds2dbg/main.cpp +++ /dev/null @@ -1,115 +0,0 @@ -// Copyright (c) 2009-2010, Bjarne Juul Pasgaard -// -// Permission to use, copy, modify, and/or distribute this software for any -// purpose with or without fee is hereby granted, provided that the above -// copyright notice, this permission notice and the following disclaimer -// appear in all copies. -// -// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - -#include "tds2dbg.h" -#include -#include -#include - -void printUsage(const char* argv0) -{ - std::cerr - << "Usage: " << argv0 - << " [-h|--help] [-t|--types] [-s|--statics] [--] exefilenamewithextension\n" - "Options:\n" - " -h, --help Prints this message\n" - " -v, --verbose Increase verbosity\n" - " -q, --quiet Operate silently\n" - " -t, --types Convert types (incomplete feature)\n" - " -s, --statics Treat statics as publics\n" - " -b, --barecv Write bare code view data\n" - << std::endl; -} - -int main(int argc, char* argv[]) -{ - int argNo = 1; - unsigned int verbosity = 1; - bool convertTypes = false; - bool staticsAsPublics = false; - bool bareCodeView = false; - - while(argc > 1) { - char* a = argv[argNo]; - if('-' != *a || 0==strcmp("--",a)) { - break; - } else if(0==strcmp("-h",a) || 0==strcmp("--help",a)) { - printUsage(argv[0]); - return 0; - } else if(0==strcmp("--verbose",a) || 0==strcmp("-v",a)) { - if(verbosity) ++verbosity; - } else if(0==strcmp("--quiet",a) || 0==strcmp("-q",a)) { - verbosity = 0; - } else if(0==strcmp("--types",a) || 0==strcmp("-t",a)) { - convertTypes = true; - } else if(0==strcmp("--statics",a) || 0==strcmp("-s",a)) { - staticsAsPublics = true; - } else if(0==strcmp("--barecv",a) || 0==strcmp("-b",a)) { - bareCodeView = true; - } else { - std::cerr << "Unknown option " << a << std::endl; - printUsage(argv[0]); - return 1; - } - --argc; - ++argNo; - } - - if(2 != argc) { - printUsage(argv[0]); - return 1; - } - - std::string exeFile(argv[argNo]); - std::string::size_type dotP = exeFile.rfind('.'); - if(std::string::npos == dotP) { - std::cerr - << "Argument exe file name must be specified with extension" - << std::endl; - return 2; - } - - std::string tdsFile(exeFile.substr(0,dotP+1)); - std::string dbgFile(tdsFile); - - const std::ctype& ctf = - std::use_facet >(std::locale()); - std::string::size_type ep = dotP; - while(exeFile.length() != ep) { - exeFile[ep] = ctf.tolower(exeFile[ep]); - ++ep; - } - - std::string ext(exeFile.substr(dotP+1)); - - if(!ext.compare("tds") || !ext.compare("dbg")) { - std::cerr - << "Executable file cannot have extension .tds or .dbg" - << std::endl; - return 1; - } - - tdsFile.append("tds"); - dbgFile.append("dbg"); - - return tds2dbg(exeFile.c_str(),tdsFile.c_str(),dbgFile.c_str(), - verbosity,convertTypes,staticsAsPublics,bareCodeView); -} - -// Local Variables: -// mode:c++ -// c-basic-offset: 3 -// indent-tabs-mode: nil -// End: diff --git a/common/windows/delphi/ext/tds2dbg/tds2dbg.bin b/common/windows/delphi/ext/tds2dbg/tds2dbg.bin deleted file mode 100644 index 999d60f19d1e0e1b4919b135cbbe20422ab16a1f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 259584 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 K#qqzi1^yqi6X;|B diff --git a/common/windows/delphi/ext/tds2dbg/tds2dbg.cpp b/common/windows/delphi/ext/tds2dbg/tds2dbg.cpp deleted file mode 100644 index 39f20967f7..0000000000 --- a/common/windows/delphi/ext/tds2dbg/tds2dbg.cpp +++ /dev/null @@ -1,2568 +0,0 @@ -// Copyright (c) 2009-2010, Bjarne Juul Pasgaard -// -// Permission to use, copy, modify, and/or distribute this software for any -// purpose with or without fee is hereby granted, provided that the above -// copyright notice, this permission notice and the following disclaimer -// appear in all copies. -// -// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - -#include "tds2dbg.h" -#include "tdscvstructs.h" -#include "log.h" -#include "fileutils.h" -#include -#include -#include -#include - - -/// @brief Ensures that the next write operation starts on an aligned -/// address. -/// -/// @param dst The target to write to. -/// @param p The current write position of the target. -/// @param m Alignment mask. Must be of the form 2^n-1. -/// -/// @tparam T The target (e.g. file wrapper) class to write to. It must -/// provide a
void write(unsigned char*, unsigned int);
-/// method. -/// -/// @throw runtime_error In case of errors. -template -void zeroPad(T& dst, unsigned int p, unsigned int m) throw(std::runtime_error) -{ - unsigned int algnmnt = p & m; - if(algnmnt) { - unsigned char filler = 0; - dst.writeRep(filler,1+m-algnmnt); - } -} - -/// @brief Ensure that a symbol record to be written does not cross a -/// page boundary in a file. -/// -/// S_ALIGN symbols are inserted to ensure proper page alignment. -/// -/// @throw runtime_error In case of errors. -void -pageAlignSymbol(FileWrapper& f, unsigned int len, unsigned int mask) - throw(std::runtime_error) -{ - unsigned int headPg = (f.tell()) & (~mask); - unsigned int tailPg = (f.tell()+len) & (~mask); - if(headPg != tailPg) { - // Compute the number of bytes to skip - tailPg -= f.tell(); - - // Enough bytes for a S_ALIGN record with 4 bytes padding? - if(tailPg<8) { - // Otherwise, skip a complete page. - tailPg += 1 + mask; - - } - // Report on alignment to be applied - mylog()(4) << " Inserted S_ALIGN at " << f.tell() - << " with " << tailPg-4 - << " padding bytes to make room for " - << len << " bytes." << std::endl; - - CVsymRecHdr symHdr(tailPg-2,CVskAlign); - - f.write(symHdr); - - unsigned char pad = 0; - - f.writeRep(pad,tailPg-4); - - } -} - -/// @brief Ensure that a block of bytes to be written does not cross a -/// page boundary in a file. -/// -/// 0's are inserted to ensure proper page alignment. -/// -/// @throw runtime_error In case of errors. -void -pageAlign(FileWrapper& f, unsigned int len, unsigned int mask) - throw(std::runtime_error) -{ - unsigned int headPg = (f.tell()) & mask; - unsigned int tailPg = (f.tell()+len) & mask; - if(headPg != tailPg) { - // Compute the number of bytes to skip - tailPg -= f.tell(); - - unsigned char pad = 0; - - f.write(pad,tailPg); - } -} - -/// @brief Handles conversion (from TDS to Code View) of and settings related -/// to various elements. -class Converter -{ -public: - - /// @brief Constructor. - /// - /// @param doConvTypes True if types are to be converted. - /// @param staticsAsPublics True if statics are to be treated as publics. - Converter(bool doConvTypes, bool staticsAsPublics) : - m_doConvTypes(doConvTypes), m_staticsAsPublics(staticsAsPublics) {} - - /// @brief Converts from a 4 byte TDS type index to a 2-byte DBG type index. - /// - /// @param typeIdx The 4-byte TDS type index. - /// - /// @return The 2-byte DBG type index. - WORD typIdx(DWORD typeIdx) const; - - /// @brief Loads all names from the sstNames subsection in a - /// TDS file into an internal cache. - /// - /// @param tdsFile The TDS file to read from. - /// @param offs Location of the sstNames subsection in the tds file. - /// @param sz Number of bytes in the sstNames subsection. - /// - /// @throw runtime_error If reading of sstNames failed. - void loadNameCache(FileWrapper& tdsFile, unsigned int offs, unsigned int sz) - throw(std::runtime_error); - - /// @brief Returns a name with a specified 1-based index. - /// - /// @param nameIdx The 1-based index of the name. - /// - /// @return The name retrieved. Empty if not found. - std::string getName(unsigned int nameIdx) const; - - /// @brief Returns true if sstGlobalTypes is to be converted. - /// - /// @retval true If sstGlobalTypes is to be converted. - /// @retval false Otherwise. - bool doConvertTypes() const { return m_doConvTypes; } - - /// @brief Returns true if statics are to be treated as publics. - /// - /// @retval true If statics are to be treated as publics. - /// @retval false Otherwise. - bool staticAsPub() const { return m_staticsAsPublics; } - -protected: - - /// @brief True if types are to be converted. - bool m_doConvTypes; - - /// @brief True if statics are to be treated as publics - bool m_staticsAsPublics; - - /// @brief Name cache container type. - typedef std::map NameCache; - - /// @brief Cache of names. - /// - /// The key is the name index - NameCache m_cache; - -}; - -WORD -Converter::typIdx(DWORD typeIdx) const { - // TypeIdx lower than 0x1000 (predefined types) do not need conversion. - if(!m_doConvTypes && typeIdx>=0x1000) { - return 0; - } - - static bool warned = false; - - if(typeIdx>0xFFFF) { - if(!warned) { - mylog()(1) - << "Warning: TDS type index too large for DBG file" << std::endl; - warned = true; - } - return 0; - } - - return (WORD) typeIdx; -} - -void -Converter:: -loadNameCache(FileWrapper& tdsFile, unsigned int offs, unsigned int sz) - throw(std::runtime_error) -{ - // Remember the current read position - unsigned int oldPos = tdsFile.tell(); - - - // Read the number of names - unsigned int numNames; - tdsFile.seek(offs); - tdsFile.read(numNames); - - // Loop to read in all names - unsigned int nameIdx = 1; - - while(sz>4 && numNames) { - // Read length of the name - unsigned char len; - tdsFile.read(len); - - // Prepare a slot in the name cache - std::pair insertPoint = - m_cache.insert(NameCache::value_type(nameIdx++,std::string())); - insertPoint.first->second.clear(); - insertPoint.first->second.reserve(len); - - // Loop to read the contents of the name - while(sz && len) { - unsigned char c; - tdsFile.read(c); - insertPoint.first->second.push_back(c); - --sz; - --len; - } - - // An additional 0 is included between each name. Skip it! - if(sz) { - tdsFile.skip(1); - --sz; - } - - // Next name - --numNames; - } - - // Return to the old read position - tdsFile.seek(oldPos); -} - -std::string -Converter::getName(unsigned int nameIdx) const -{ - static bool warningReported = false; - - NameCache::const_iterator it = m_cache.find(nameIdx); - - if(m_cache.end() != it) { - return it->second; - } else if(!warningReported) { - warningReported = true; - mylog()(1) << "Failed to get name with index " - << std::hex << nameIdx << ". Further reports suppressed." - << std::endl; - } - return std::string(); -} - -/// @brief The number of hash buckets in the name hash table. -#define NUM_HASH_BUCKETS 0x82 - -/// @brief Internal symbol name hash table -class NameHshTbl -{ -public: - - /// @brief Constructor. - /// - /// @param numBuckets The number of buckets to use in the hash table. - NameHshTbl(unsigned short numBuckets); - - /// @brief Appends a symbol to the name hash table. - /// - /// @param n The name of the symbol to append. - /// @param offset The offset of the symbol reference from the beginning of - /// the symbols. - void append(const std::string& n, DWORD offset); - - /// @brief Writes the symbol name hash table to a symbol subsection of a - /// DBG file. - /// - /// @param dbgFile The DBG file to write to. - /// @param[out] hshTableSz The size of the generated address table. - /// - /// @throw runtime_error In case of errors. - void write(FileWrapper& dbgFile, DWORD& hshTableSz) const - throw(std::runtime_error); - -protected: - - /// @brief Element in a hash bucket. - struct HshBcktElem - { - /// @brief Default constructor. - HshBcktElem() {} - - /// @brief Constructor. - /// - /// @param offs The offset of the symbol reference from the beginning of - /// the symbols. - /// @param hsh The name hash of the symbol. - HshBcktElem(DWORD offs, DWORD hsh) : symRefOffs(offs), nameHash(hsh) {} - - /// @brief The offset of the symbol reference from the beginning of the - /// symbols. - DWORD symRefOffs; - - /// @brief The name hash of the symbol. - DWORD nameHash; - }; - - /// @brief Container type for the hash buckets that make up the table. - /// - /// The index is the hash bucket number and the value is a vector of - /// symbol offsets in that bucket. - typedef std::vector > HashBuckets; - -protected: - - /// @brief The internal name hash table. - HashBuckets hshTable; - -}; - -NameHshTbl::NameHshTbl(unsigned short numBuckets) : hshTable(numBuckets) -{ -} - -void -NameHshTbl::write(FileWrapper& dbgFile, DWORD& hshTableSz) const - throw(std::runtime_error) -{ - // Remember where we start writing to - unsigned int strtPos = dbgFile.tell(); - - // Write out the number of hash buckets. - unsigned short numBuckets = hshTable.size(); - dbgFile.write(numBuckets); - - // Filler bytes - numBuckets = 0; - dbgFile.write(numBuckets); - - // Write out offsets of each buckets offset table - unsigned int dw = 0; - HashBuckets::const_iterator bcktIt = hshTable.begin(); - while(hshTable.end() != bcktIt) { - // Write out current offset - dbgFile.write(dw); - // Compute offset of next buckets offset table - dw += bcktIt->size() * 8; - // Next bucket - ++bcktIt; - } - - // Write out the number of elements in each buckets offset table - bcktIt = hshTable.begin(); - while(hshTable.end() != bcktIt) { - // Write out the number of elements in this buckets offset table - dw = bcktIt->size(); - dbgFile.write(dw); - // Next bucket - ++bcktIt; - } - - // Write out the offset tables for all buckets - bcktIt = hshTable.begin(); - while(hshTable.end() != bcktIt) { - // Loop over all symbols within this bucket - std::vector::const_iterator symIt = bcktIt->begin(); - while(bcktIt->end() != symIt) { - // Write out offset and name hash of current symbol - dbgFile.write(*symIt); - // Next symbol - ++symIt; - } - // Next segment - ++bcktIt; - } - - // Compute the total size of the address hash table - hshTableSz = dbgFile.tell() - strtPos; - - // Log the results - mylog()(2) << " Appended name hash table at " << std::hex - << strtPos << " with size " << hshTableSz << std::endl; -} - -void NameHshTbl::append(const std::string& n, DWORD offset) -{ - unsigned int l = n.size(); - unsigned long hshTail = 0; - while(l & 3) { - --l; - hshTail |= (n[l] & 0xDF); - hshTail <<= 8; - } - - l >>= 2; - - unsigned long hsh = 0; - const unsigned long* ln = - reinterpret_cast(n.c_str()); - for(unsigned int i=0; i < l; ++i) { - hsh ^= 0xDFDFDFDF & (ln[i]); - _lrotl(hsh,4); - } - - hsh ^= hshTail; - - hshTable[hsh % hshTable.size()]. - push_back(HshBcktElem(offset,hsh)); -} - -/// @brief Record used internally for temporary storage of symbols. -struct SymbolRecord { - DWORD typeIdx; ///< Type index of the symbol. - DWORD nameIdx; ///< Name index of the symbol. - WORD symKind; ///< Symbol kind. - WORD moduleIdx; ///< Index of module that contains this symbol record. - DWORD offsSymRec; /**< Offset of symbol record in the DBG symbol table - where it is located. */ - DWORD symRefOffs; ///< Symbol reference offset to insert in hash table. */ -}; - -/// @brief Container type of the temporary storage of symbols within a segment. -/// -/// The key is the offset relative to the segment. This ensures that -/// symbols are ordered according to the offset. -typedef std::map SegmSymMap; - -/// @brief Container type of the temporary storage of public symbols. -/// -/// The key is the segment. -typedef std::map SymRecMap; - -/// @brief Appends an address hash table to a symbol subsection. -/// -/// @param dbgFile The DBG file to write to. -/// @param[out] hshTableSz The size of the generated address table. -/// @param syms The symbols to generate the hash table for. -/// The symRefOffs member for each symbol must -/// have been set correctly. -/// -/// @throw runtime_error In case of errors. -void createAddrHashTable(FileWrapper& dbgFile, - DWORD& hshTableSz, - const SymRecMap& syms) throw(std::runtime_error) -{ - // Remember where we start writing to - unsigned int strtPos = dbgFile.tell(); - - // Write out the number of segments. - unsigned short numSegs = syms.size(); - dbgFile.write(numSegs); - - // Filler bytes - numSegs = 0; - dbgFile.write(numSegs); - - // Write out offsets of each segments offset table - unsigned int dw = 0; - SymRecMap::const_iterator segIt = syms.begin(); - while(syms.end() != segIt) { - // Write out current offset - dbgFile.write(dw); - // Compute offset of next segments offset table - dw += segIt->second.size() * 8; - // Next segment - ++segIt; - } - - // Write out the number of elements in each segments offset table - segIt = syms.begin(); - while(syms.end() != segIt) { - // Write out the number of elements in this segments offset table - dw = segIt->second.size(); - dbgFile.write(dw); - // Next segment - ++segIt; - } - - // Write out the offset tables for all segments - segIt = syms.begin(); - while(syms.end() != segIt) { - // Loop over all symbols within this segment - SegmSymMap::const_iterator symIt = segIt->second.begin(); - while(segIt->second.end() != symIt) { - // Write out offset of symbol record within the subsection - dbgFile.write(symIt->second.symRefOffs); - // Write out offset of symbol relative to segment - dbgFile.write(symIt->first); - // Next symbol - ++symIt; - } - // Next segment - ++segIt; - } - - // Compute the total size of the address hash table - hshTableSz = dbgFile.tell() - strtPos; - - // Log the results - mylog()(2) << " Appended address hash table at " << std::hex - << strtPos << " with size " << hshTableSz << std::endl; -} - -/// @brief Appends a closing S_ALIGN symbol. -/// -/// @param dbgFile The file to append to. -/// @param algnmtn The alignment mask to apply. -/// -/// @throw runtime_error In case of errors. -void appendClosingAlign(FileWrapper& dbgFile) throw(std::runtime_error) -{ - // The symbol record header - CVsymRecHdr recHdr(6,CVskAlign); - - // Reserve space for a DBG symbol record header to be - // written out later. - HeaderWithLen dbgRecHdr(recHdr,recHdr.len); - - // Create a record writer to keep track of the number of - // payload bytes written. - RecordWriter wrtr; - - DWORD pad = 0xFFFFFFFF; - - wrtr.write(pad); - - // Ensure that the record does not cross a page boundary. - unsigned int bytesToWrite = dbgRecHdr.length(wrtr); - pageAlignSymbol(dbgFile,bytesToWrite,0xFFF); - - dbgRecHdr.commit(dbgFile,wrtr); -} - -/// @brief Converts a TDS symbol table to a DBG symbol table. -/// -/// See "Symbols" in Tools Interface Standard Formats Specification -/// for Windows version 1.0 for the definition of a DBG type record -/// and type string. -/// -/// @param conv Converter used for various conversion tasks. -/// @param moduleIdx Index of the current module. 0 if none. -/// @param tdsFile The input tds file. -/// @param tdsTableSz The size of the symbol table in the input. -/// @param dbgFile The output dbg file. -/// @param[out] symTableSz The size of the symbol table in the output. -/// @param[out] syms Addressable symbols already and inserted in -/// the generated symbol table. -/// @param[out] pubSyms Contents for the sstGlobalPub subsection. -/// @param[out] staticSyms Contents for the sstStaticSym subsection. -/// -/// @throw runtime_error In case of errors. -void convertSymbolSubsection(const Converter& conv, - unsigned short moduleIdx, - FileWrapper& tdsFile, - DWORD tdsTableSz, - FileWrapper& dbgFile, - DWORD& symTableSz, - SymRecMap& syms, - NameHshTbl& nameHshTable, - SymRecMap& pubSyms, - SymRecMap& staticSyms) - throw(std::runtime_error) -{ - // Remember where we start writing to - unsigned int strtPos = dbgFile.tell(); - - // Loop over all symbol records in the TDS file - RecordReader rdr(tdsFile,tdsTableSz); - - while(!rdr.eor()) { - - // Remember the file pos that we will start reading the record from - unsigned int tdsHdrPos = rdr.tell(); - - // Read the symbol record header - CVsymRecHdr recHdr; - rdr.read(recHdr); - - // Remember the record length - unsigned int recLen = recHdr.len; - - switch(recHdr.symKind) { - case CVskCompile: { - // Handle compile flags (copy almost as is --- seems like the - // tds files have a wrong length field, which we must correct). - mylog()(3) << " Converting S_COMPILE" << std::endl; - - // Read the payload header from the tds file. - CVsrCompileFlagPayloadHdr tdsPlHdr; - rdr.read(tdsPlHdr); - - BYTE versionLen; - rdr.read(versionLen); - - // Reserve space for a DBG symbol record header to be - // written out later. - HeaderWithLen dbgRecHdr(recHdr,recHdr.len); - - // Create a record writer to keep track of the number of - // payload bytes written. - RecordWriter wrtr; - - // Write the payload header from the TDS file as is. - wrtr.write(tdsPlHdr); - - // Write a length-prefixed name (copy from TDS file). - wrtr.write(versionLen); - copyFileContent(wrtr,tdsFile,versionLen); - - // Ensure that the next record is properly aligned on a - // dword boundary - zeroPad(wrtr,dbgRecHdr.length(wrtr),sizeof(int)-1); - - // Ensure that the record does not cross a page boundary. - pageAlignSymbol(dbgFile,dbgRecHdr.length(wrtr),0xFFF); - - // Write the symbol record header with the correct length field - dbgRecHdr.commit(dbgFile,wrtr); - break; - } - case CVskRegister: { - // Handle Register symbol record - mylog()(3) << " Converting S_REGISTER" << std::endl; - - // Read the symbol record payload from the TDS file - TDSsrRegisterPayload tdsPl; - rdr.read(tdsPl); - - // Reserve space for a DBG symbol record header to be - // written out later. - HeaderWithLen dbgRecHdr(recHdr,recHdr.len); - - // Create a record writer to keep track of the number of payload - // bytes written. - RecordWriter wrtr; - - // Create the payload header for the DBG file - CVsrRegisterPayloadHdr dbgPlHdr = { - conv.typIdx(tdsPl.typeIdx), - tdsPl.regId - }; - - // Write the payload header - wrtr.write(dbgPlHdr); - - // Append the length-prefixed name of the symbol to the payload - wrtr.write1bLpfName(conv.getName(tdsPl.nameIdx)); - - // Ensure that the next record is properly aligned on a - // dword boundary - zeroPad(wrtr,dbgRecHdr.length(wrtr),sizeof(int)-1); - - // Ensure that the record does not cross a page boundary. - pageAlignSymbol(dbgFile,dbgRecHdr.length(wrtr),0xFFF); - - // Write the symbol record header with the correct length field - dbgRecHdr.commit(dbgFile,wrtr); - break; - } - case CVskUDT: { - // Handle a User defined type - - if(conv.doConvertTypes()) { - - mylog()(3) << " Converting S_UDT" << std::endl; - - // Read the entire payload from the TDS file - TDSsrUserDefinedTypePayload tdsPl; - rdr.read(tdsPl); - - // If sstGlobalTypes is not converted, we can only handle - // predefined types (typeIdx<0x1000). - - // Reserve space for a DBG symbol record header to be - // written out later. - HeaderWithLen dbgRecHdr(recHdr,recHdr.len); - - // Create a record writer to keep track of the number of - // payload bytes written. - RecordWriter wrtr; - - // Create the payload header for the DBG file - CVsrUserDefinedTypePayloadHdr dbgPlHdr = - {conv.typIdx(tdsPl.typeIdx)}; - - // Write the payload header - wrtr.write(dbgPlHdr); - - // Get the type name - const std::string& tpName = conv.getName(tdsPl.nameIdx); - - // Append the length-prefixed type name to the payload - wrtr.write1bLpfName(tpName); - - // Ensure that the next record is properly aligned on a - // dword boundary - zeroPad(wrtr,dbgRecHdr.length(wrtr),sizeof(int)-1); - - // Ensure that the record does not cross a page boundary. - pageAlignSymbol(dbgFile,dbgRecHdr.length(wrtr),0xFFF); - - // The type name should potentially be referenced from a - // name hash table, so register it's location in the name hash. - nameHshTable.append(tpName,dbgFile.tell()-strtPos); - - // Write the symbol record header with the correct - // length field - dbgRecHdr.commit(dbgFile,wrtr); - - } else { - mylog()(3) << " Skipping S_UDT" << std::endl; - } - break; - } - case CVskSSearch: { - // Handle a Start Search Symbol - mylog()(3) << " Converting S_SSEARCH" << std::endl; - - // Read the payload from the TDS file - TDSsrStartSearchPayload tdsPl; - rdr.read(tdsPl); - - // Reserve space for a DBG symbol record header to be - // written out later. - HeaderWithLen dbgRecHdr(recHdr,recHdr.len); - - // Create a record writer to keep track of the number of - // payload bytes written. - RecordWriter wrtr; - - // Create the corresponding DBG payload - CVsrStartSearchPayload dbgPl = {tdsPl.offs, tdsPl.segment}; - - // Write the payload to the DBG file - wrtr.write(dbgPl); - - // Ensure that the next record is properly aligned on a - // dword boundary - zeroPad(wrtr,dbgRecHdr.length(wrtr),sizeof(int)-1); - - // Ensure that the record does not cross a page boundary. - pageAlignSymbol(dbgFile,dbgRecHdr.length(wrtr),0xFFF); - - // Write the symbol record header with the correct length field - dbgRecHdr.commit(dbgFile,wrtr); - break; - } - case CVskEnd: { - // Handle an End of Block symbol - // No payload, no conversion, just write as is. - mylog()(3) << " Converting S_END" << std::endl; - - // Reserve space for a DBG symbol record header to be - // written out later. - HeaderWithLen dbgRecHdr(recHdr,recHdr.len); - - // Create a record writer to keep track of the number of - // payload bytes written (none). - RecordWriter wrtr; - - // Ensure that the next record is properly aligned on a - // dword boundary - zeroPad(wrtr,dbgRecHdr.length(wrtr),sizeof(int)-1); - - // Ensure that the record does not cross a page boundary. - pageAlignSymbol(dbgFile,dbgRecHdr.length(wrtr),0xFFF); - - // Write the symbol record header with the correct length field - dbgRecHdr.commit(dbgFile,wrtr); - break; - } - case CVskBPRel: { - // Handle a BP Relative symbol - mylog()(3) << " Converting S_BPREL" << std::endl; - - // Read the payload from the TDS file - TDSsrBPRelativePayload tdsPl; - rdr.read(tdsPl); - - // Reserve space for a DBG symbol record header to be - // written out later. - HeaderWithLen dbgRecHdr(recHdr,recHdr.len); - - // Create a record writer to keep track of the number of - // payload bytes written. - RecordWriter wrtr; - - // Create the corresponding payload header for the DBG file - CVsrBPRelativePayloadHdr dbgPlHdr = { - tdsPl.offset,conv.typIdx(tdsPl.typeIdx) - }; - - // Write the payload header to the DBG file - wrtr.write(dbgPlHdr); - - // Append the length-prefixed symbol name to the payload header - wrtr.write1bLpfName(conv.getName(tdsPl.nameIdx)); - - // Ensure that the next record is properly aligned on a - // dword boundary - zeroPad(wrtr,dbgRecHdr.length(wrtr),sizeof(int)-1); - - // Ensure that the record does not cross a page boundary. - pageAlignSymbol(dbgFile,dbgRecHdr.length(wrtr),0xFFF); - - // Write the symbol record header with the correct length field - dbgRecHdr.commit(dbgFile,wrtr); - break; - } - case CVskLocalData: // Intentional fall-through - case CVskGlobalData: { - - if(CVskLocalData == recHdr.symKind) { - mylog()(3) << " Converting S_LDATA" << std::endl; - } else { - mylog()(3) << " Converting S_GDATA" << std::endl; - } - - // Read the payload from the TDS file - TDSsrGlobalDataPayload tdsPl; - rdr.read(tdsPl); - - // Reserve space for a DBG symbol record header to be - // written out later. - HeaderWithLen dbgRecHdr(recHdr,recHdr.len); - - // Create a record writer to keep track of the number of - // payload bytes written. - RecordWriter wrtr; - - // Create the corresponding payload header for the DBG file - CVsrGlobalDataPayloadHdr dbgPlHdr = - {tdsPl.offset,tdsPl.segment,conv.typIdx(tdsPl.typeIdx)}; - - // Write the payload header to the DBG file - wrtr.write(dbgPlHdr); - - // Get the symbol name - const std::string& symName = conv.getName(tdsPl.nameIdx); - - // Append the length-prefixed symbol name to the payload header - wrtr.write1bLpfName(symName); - - // Ensure that the next record is properly aligned on a - // dword boundary - zeroPad(wrtr,dbgRecHdr.length(wrtr),sizeof(int)-1); - - // Ensure that the record does not cross a page boundary. - pageAlignSymbol(dbgFile,dbgRecHdr.length(wrtr),0xFFF); - - // Remember where the record is written to - unsigned int dbgHdrPos = dbgFile.tell() - strtPos; - - // Write the symbol record header with the correct length field - dbgRecHdr.commit(dbgFile,wrtr); - - // Global and Local Data symbol records must have a - // corresponding PUB32 record in the sstGlobalPub - // section or a corresponding DATAREF record in the - // sstStaticSym, respectively, of the DBG file as - // well. We must therefore store the record for later - // use. - SymbolRecord sr; - - sr.typeIdx = tdsPl.typeIdx; - sr.nameIdx = tdsPl.nameIdx; - sr.moduleIdx = moduleIdx; - sr.offsSymRec = dbgHdrPos; - sr.symRefOffs = dbgHdrPos; - - if(CVskGlobalData == recHdr.symKind || conv.staticAsPub()) { - sr.symKind = CVskPublic; - pubSyms[tdsPl.segment][tdsPl.offset] = sr; - } else { - sr.symKind = CVskDataRef; - staticSyms[tdsPl.segment][tdsPl.offset] = sr; - } - - // The address of the symbol should potentially be referenced - // from an address hash table, so we need to add to the syms. - syms[tdsPl.segment][tdsPl.offset] = sr; - - // The symbol name should potentially be referenced from a - // name hash table, so we need to register it in the name hash. - nameHshTable.append(symName,dbgHdrPos); - - break; - } - case CVskPublic: // Intentional fall-through - case CVskLocalProc: - case CVskGlobalProc: { - // Handle Public, LocalProc, or GlobalProc symbol - - if(CVskPublic == recHdr.symKind) { - mylog()(3) << " Converting S_PUB" << std::endl; - } else if(CVskLocalProc == recHdr.symKind) { - mylog()(3) << " Converting S_LPROC" << std::endl; - } else { - mylog()(3) << " Converting S_GPROC" << std::endl; - } - - // Read the payload header from the TDS file (ignore the - // length-prefixed linker name of the symbol). - TDSsrLocalProcPayloadHdr tdsPlHdr; - rdr.read(tdsPlHdr); - - // Reserve space for a DBG symbol record header to be - // written out later. - HeaderWithLen dbgRecHdr(recHdr,recHdr.len); - - // Create a record writer to keep track of the number of - // payload bytes written. - RecordWriter wrtr; - - // Prepare the payload header for the DBG file. - CVsrLocalProcPayloadHdr dbgSrHdr = - {tdsPlHdr.parent,tdsPlHdr.end,tdsPlHdr.next, - tdsPlHdr.procLen,tdsPlHdr.dbgStart,tdsPlHdr.dbgEnd, - tdsPlHdr.offset,tdsPlHdr.segment, - conv.typIdx(tdsPlHdr.typeIdx), (BYTE) (tdsPlHdr.flags)}; - - mylog()(3) << " Writing header" << std::endl; - - // Write the payload header to the DBG file - wrtr.write(dbgSrHdr); - - // Get the symbol name - const std::string& symName = conv.getName(tdsPlHdr.nameIdx); - - // Append the length-prefixed symbol name to the payload header - wrtr.write1bLpfName(symName); - - // Ensure that the next record is properly aligned on a - // dword boundary - zeroPad(wrtr,dbgRecHdr.length(wrtr),sizeof(int)-1); - - // Ensure that the record does not cross a page boundary. - pageAlignSymbol(dbgFile,dbgRecHdr.length(wrtr),0xFFF); - - // Remember where the record is written to - unsigned int dbgHdrPos = dbgFile.tell() - strtPos; - - // Write the symbol record header with the correct length field - dbgRecHdr.commit(dbgFile,wrtr); - - // Global and Local Procedure symbol records must have - // a corresponding PUB32 record in the sstGlobalPub - // section or a corresponding PROCREF record in the - // sstStaticSym, respectively, of the DBG file as - // well. We must therefore store the record for later - // use. - SymbolRecord sr; - - sr.typeIdx = tdsPlHdr.typeIdx; - sr.nameIdx = tdsPlHdr.nameIdx; - sr.moduleIdx = moduleIdx; - sr.offsSymRec = dbgHdrPos; - sr.symRefOffs = dbgHdrPos; - - if(CVskLocalProc != recHdr.symKind || conv.staticAsPub()) { - sr.symKind = CVskPublic; - pubSyms[tdsPlHdr.segment][tdsPlHdr.offset] = sr; - } else { - sr.symKind = CVskProcRef; - staticSyms[tdsPlHdr.segment][tdsPlHdr.offset] = sr; - } - - // The address of the symbol should potentially be referenced - // from an address hash table, so we need to add to the syms. - syms[tdsPlHdr.segment][tdsPlHdr.offset] = sr; - - // The symbol name should potentially be referenced from a - // name hash table, so we need to register it in the name hash. - nameHshTable.append(symName,dbgHdrPos); - - break; - } - case CVskBlock32: { - mylog()(3) << " Converting S_BLOCK" << std::endl; - - // Read the payload from the TDS file - TDSsrBlock32Payload tdsPl; - rdr.read(tdsPl); - - // Reserve space for a DBG symbol record header to be - // written out later. - HeaderWithLen dbgRecHdr(recHdr,recHdr.len); - - // Create a record writer to keep track of the number of - // payload bytes written. - RecordWriter wrtr; - - // Create the corresponding payload header for the DBG file - CVsrBlock32PayloadHdr dbgPlHdr = - {tdsPl.parent,tdsPl.end,tdsPl.next,tdsPl.len,tdsPl.offset, - tdsPl.segment}; - - // Write the payload header to the DBG file - wrtr.write(dbgPlHdr); - - // Append the length-prefixed symbol name to the payload header - wrtr.write1bLpfName(conv.getName(tdsPl.nameIdx)); - - // Ensure that the next record is properly aligned on a - // dword boundary - zeroPad(wrtr,dbgRecHdr.length(wrtr),sizeof(int)-1); - - // Ensure that the record does not cross a page boundary. - pageAlignSymbol(dbgFile,dbgRecHdr.length(wrtr),0xFFF); - - // Write the symbol record header with the correct length field - dbgRecHdr.commit(dbgFile,wrtr); - break; - } - case CVskSkip: { - mylog()(3) << " Skipping S_SKIP" << std::endl; - // Skip this record - break; - } - case CVskGProcRef: { - // The GPROCREF's located in the sstGlobalSym of TDS files - // doesn't contain enough info (reference to the originating - // module is 0) for us to create a Code View PROCREF record - // out of it. We therefore create a PUB32 in the sstGlobalPub - // instead. - - mylog()(3) << " Converting S_GPROCREF" << std::endl; - - /// @todo Use the segment and offset to look up the data needed - /// to create a Code View PROCREF as well. - - // Read the type string payload from the TDS file - TDSsrGlobalProcRefPayload tdsPl; - rdr.read(tdsPl); - - SymbolRecord sr; - - sr.typeIdx = tdsPl.typeIdx; - sr.nameIdx = tdsPl.nameIdx; - sr.moduleIdx = 0xFFFF; - sr.offsSymRec = sr.symRefOffs = dbgFile.tell()-strtPos; - sr.symKind = CVskPublic; - pubSyms[tdsPl.segment][tdsPl.offset] = sr; - break; - } - case CVskGDataRef: { - // The GDATAREF's located in the sstGlobalSym of TDS files - // doesn't contain enough info (reference to the originating - // module is 0) for us to create a Code View PROCREF record - // out of it. We therefore create a PUB32 in the sstGlobalPub - // instead. - - mylog()(3) << " Converting S_GDATAREF" << std::endl; - - /// @todo Use the segment and offset to look up the data needed - /// to create a Code View DATAREF as well. - - // Read the type string payload from the TDS file - TDSsrGlobalDataRefPayload tdsPl; - rdr.read(tdsPl); - - SymbolRecord sr; - - sr.typeIdx = tdsPl.typeIdx; - sr.nameIdx = tdsPl.nameIdx; - sr.moduleIdx = 0xFFFF; - sr.offsSymRec = sr.symRefOffs = dbgFile.tell()-strtPos; - sr.symKind = CVskPublic; - pubSyms[tdsPl.segment][tdsPl.offset] = sr; - break; - } - case CVskNamespace: // Intentional fall-through - case CVskUsing: - // We currently don't know how to convert this. There does - // not seem to be a corresponding symbol kind in CodeView. - case CVskEntry32: - // We currently don't know how to convert this. Borlands - // documentation states that this symbol record is optional, - // so we leave it out. - break; - case CVskOptVar32: - // Variable life range support. We don't know how to - // convert this, currently. - break; - case CVskProcRet32: - // Procedure return epilogue. - // Don't know how to convert this. - break; - case CVskSaveRegs32: - // Don't know how to convert this. - break; - default: - mylog()(1) - << "Warning: Unhandled symbol record with symbol kind " - << std::hex << recHdr.symKind << " at " - << tdsHdrPos << " with length " << recLen << std::endl; - break; - } - - // Jump to the next symbol record - rdr.seek(tdsHdrPos + sizeof(recHdr.len)+ recLen); - } - - // Symbol table must end with a closing S_ALIGN - appendClosingAlign(dbgFile); - - // Compute the size of the contructed symbol table. - symTableSz = dbgFile.tell()-strtPos; -} - -/// @brief Converts a numeric leaf from a TDS type record to a numeric leaf -/// in a DBG type record. -/// -/// @param leafId The id of the numeric leaf. -/// @param tsPos The position within the TDS file from which the leaf Id -/// was read. -/// @param tsRdr Type string reader. -/// @param tsWrtr Type string writer. -/// -/// @throw runtime_error In case of errors. -void convertNumericLeaf(WORD leafId, - unsigned int tsPos, - RecordReader& tsRdr, - RecordWriter& tsWrtr) throw(std::runtime_error) -{ - switch(leafId) { - case LF_CHAR: - tsWrtr.write(leafId); - copyFileContent(tsWrtr,tsRdr,1); - break; - case LF_SHORT: // Intentional fall-through - case LF_USHORT: - tsWrtr.write(leafId); - copyFileContent(tsWrtr,tsRdr,2); - break; - case LF_LONG: // Intentional fall-through - case LF_ULONG: - case LF_REAL32: - case LF_COMPLEX32: - tsWrtr.write(leafId); - copyFileContent(tsWrtr,tsRdr,4); - break; - case LF_REAL48: - tsWrtr.write(leafId); - copyFileContent(tsWrtr,tsRdr,6); - break; - case LF_REAL64: // Intentional fall-through - case LF_QUADWORD: - case LF_UQUADWORD: - case LF_COMPLEX64: - tsWrtr.write(leafId); - copyFileContent(tsWrtr,tsRdr,8); - break; - case LF_REAL80: - case LF_COMPLEX80: - tsWrtr.write(leafId); - copyFileContent(tsWrtr,tsRdr,10); - break; - case LF_REAL128: - case LF_COMPLEX128: - tsWrtr.write(leafId); - copyFileContent(tsWrtr,tsRdr,16); - break; - case LF_VARSTRING: - unsigned short len; - tsRdr.read(len); - tsWrtr.write(leafId); - tsWrtr.write(len); - copyFileContent(tsWrtr,tsRdr,len); - break; - default: - if(leafId recWrtr(dbgRecHdr,dbgRecHdr.len); - - // Establish a reader for the TDS type strings - RecordReader tsRdr(tdsFile,tdsRecHdr.len); - - // Establish a writer for the DBG type strings - RecordWriter tsWrtr; - - // Loop over all type strings in this type record - bool nextTypeString = false; - while(!nextTypeString && !tsRdr.eor()) { - - // Remember the position of the type string in the TDS file - unsigned int tsPos = tsRdr.tell(); - - // Read the leaf identifier of the TDS type string - TDSTypeString tdsTs; - tsRdr.read(tdsTs.leaf); - - // Prepare the DBG type string - CVTypeString dbgTs; - dbgTs.leaf = tdsTs.leaf; // Set identical for a start - - switch(tdsTs.leaf) { - case LF_MODIFIER: { - tsRdr.read(tdsTs.modifier); - dbgTs.modifier.attribute = tdsTs.modifier.attribute; - dbgTs.modifier.typeIdx = conv.typIdx(tdsTs.modifier.typeIdx); - tsWrtr.write(dbgTs.leaf); - tsWrtr.write(dbgTs.modifier); - break; - } - case LF_POINTER: { - tsRdr.read(tdsTs.pointer); - - unsigned char ptrType = (tdsTs.pointer.attribute & 0x1f); - unsigned char ptrMode = (tdsTs.pointer.attribute & 0xE0) >> 5; - - if(6 == ptrType) { - mylog()(1) - << "Unhandled pointer type " << std::hex << ((int) ptrType) - << " at " << tsPos << std::endl; - nextTypeString = true; - } else { - // Convert the fixed size part - dbgTs.pointer.attribute = tdsTs.pointer.attribute; - dbgTs.pointer.typeIdx = conv.typIdx(tdsTs.pointer.typeIdx); - tsWrtr.write(dbgTs.leaf); - tsWrtr.write(dbgTs.pointer); - - // Convert the variant part - if(1 < ptrMode) { - /// @todo for pointer to data/method member, TDS - /// and DBG seem to interchange the position of - /// the class and format fields. Verify this! - WORD format; - DWORD classIdx; - - tsRdr.read(format); - tsRdr.read(classIdx); - - WORD dbgClassIdx = conv.typIdx(classIdx); - tsWrtr.write(dbgClassIdx); - tsWrtr.write(format); - - // Number of bytes to copy depends on format. - unsigned bytesToCopy = 0; - - switch(format) { - case 0: - case 1: - case 5: - bytesToCopy = 2; - break; - case 3: - case 6: - case 8: - case 11: - bytesToCopy = 4; - break; - case 2: - case 4: - case 9: - bytesToCopy = 6; - break; - case 7: - case 12: - bytesToCopy = 8; - break; - case 10: - bytesToCopy = 10; - break; - case 13: - bytesToCopy = 16; - break; - default: - mylog()(1) - << "Unhandled variant format in LF_POINTER at " - << std::hex << tsPos << std::endl; - break; - } - copyFileContent(tsWrtr,tsRdr,bytesToCopy); - } else if(3 == ptrType || 5 == ptrType) { - /// @todo Test this. Not sure it's correct. - WORD seg; - tsRdr.read(seg); - tsWrtr.write(seg); - } else if(8 == ptrType) { - /// @todo Test this. Not sure it's correct. - mylog()(1) - << "Warning at " << std::hex << tsPos - << ": Current conversion of LF_POINTER variant part is" - " dubious when pointer is based on type" << std::endl; - - // Copy type index - DWORD typeIdx; - tsRdr.read(typeIdx); - unsigned short dbgTypeIdx = conv.typIdx(typeIdx); - tsWrtr.write(dbgTypeIdx); - - // Copy length prefixed name - BYTE l; - tsRdr.read(l); - tsWrtr.write(l); - copyFileContent(tsWrtr,tsRdr,l); - } - } - break; - } - case LF_ARRAY: { - tsRdr.read(tdsTs.array); - dbgTs.array.elemTypeIdx = conv.typIdx(tdsTs.array.elemTypeIdx); - dbgTs.array.idxTypeIdx = conv.typIdx(tdsTs.array.idxTypeIdx); - tsWrtr.write(dbgTs.leaf); - tsWrtr.write(dbgTs.array); - - // Convert the numerical leaf representing the length of the array - unsigned int numLeafPos = tsRdr.tell(); - WORD numLeafId; - - tsRdr.read(numLeafId); - - convertNumericLeaf(numLeafId,numLeafPos,tsRdr,tsWrtr); - - tsWrtr.write1bLpfName(conv.getName(tdsTs.array.nameIdx)); - break; - } - case LF_CLASS: // Intentional fall-through - case LF_STRUCTURE: { - tsRdr.read(tdsTs.cls); - dbgTs.cls.count = tdsTs.cls.count; - dbgTs.cls.fieldTypeIdx = conv.typIdx(tdsTs.cls.fieldTypeIdx); - dbgTs.cls.property = tdsTs.cls.property; - // Set bit 8 of the property field according to whether or - // not the declaration of this class or structure is - // contained within another. - if(tdsTs.cls.cClass) { - dbgTs.cls.property |= 0x80; - } else { - dbgTs.cls.property &= 0xff7f; - } - dbgTs.cls.dList = conv.typIdx(tdsTs.cls.dList); - dbgTs.cls.vShape = conv.typIdx(tdsTs.cls.vShape); - tsWrtr.write(dbgTs.leaf); - tsWrtr.write(dbgTs.procedure); - copyNumericLeaf(tsRdr,tsWrtr); - tsWrtr.write1bLpfName(conv.getName(tdsTs.cls.nameIdx)); - break; - } - case LF_PROCEDURE: { - tsRdr.read(tdsTs.procedure); - dbgTs.procedure.rvTypeIdx = - conv.typIdx(tdsTs.procedure.rvTypeIdx); - dbgTs.procedure.callConv = tdsTs.procedure.callConv; - dbgTs.procedure.reserved = 0; - dbgTs.procedure.parms = tdsTs.procedure.parms; - dbgTs.procedure.argList = tdsTs.procedure.argList; - tsWrtr.write(dbgTs.leaf); - tsWrtr.write(dbgTs.procedure); - break; - } - case LF_MFUNCTION: { - tsRdr.read(tdsTs.mFunction); - dbgTs.mFunction.rvTypeIdx = - conv.typIdx(tdsTs.mFunction.rvTypeIdx); - dbgTs.mFunction.classIdx = - conv.typIdx(tdsTs.mFunction.classIdx); - dbgTs.mFunction.thisIdx = - conv.typIdx(tdsTs.mFunction.thisIdx); - dbgTs.mFunction.callConv = tdsTs.mFunction.callConv; - dbgTs.mFunction.reserved = 0; - dbgTs.mFunction.parms = tdsTs.mFunction.parms; - dbgTs.mFunction.argList = - conv.typIdx(tdsTs.mFunction.argList); - dbgTs.mFunction.thisAdjust = - tdsTs.mFunction.thisAdjust; - break; - } - case LF_NOTTRANS: { - tsWrtr.write(dbgTs.leaf); - break; - } - case LF_PAD0: // Intentional fall-throughs - case LF_PAD1: - case LF_PAD2: - case LF_PAD3: - case LF_PAD4: - case LF_PAD5: - case LF_PAD6: - case LF_PAD7: - case LF_PAD8: - case LF_PAD9: - case LF_PAD10: - case LF_PAD11: - case LF_PAD12: - case LF_PAD13: - case LF_PAD14: - case LF_PAD15: - // Skip all these --- we'll do our own alignment. - break; - case LF_ARGLIST: { - // Copy the number of arguments in the list. - WORD argCount; - tsRdr.read(argCount); - tsWrtr.write(argCount); - - // In TDS files: argCount 4-byte type indices. - // In DBG files: argCount 2-byte type indices. - while(!tsRdr.eor() && argCount) { - DWORD typeIdx; - tsRdr.read(typeIdx); - WORD dbgTypeIdx = conv.typIdx(typeIdx); - tsWrtr.write(dbgTypeIdx); - --argCount; - } - break; - } - case LF_MLIST: { - tsWrtr.write(dbgTs.leaf); - TDStsMListElem tdsElem; - CVtsMListElem dbgElem; - while(!tsRdr.eor()) { - tsRdr.read(tdsElem); - dbgElem.attribute = tdsElem.attribute; - dbgElem.typeIdx = conv.typIdx(tdsElem.typeIdx); - tsWrtr.write(dbgElem); - unsigned int mProp = dbgElem.attribute & MEMBATTR_PROP_MASK; - // The 4-byte vtaboffset is only present when the method - // property is introducing virtual. - if(MEMBATTR_PROP_INTVIRT == mProp || - MEMBATTR_PROP_INTPURE == mProp) { - unsigned int vTabOffs; - tsRdr.read(vTabOffs); - tsWrtr.write(vTabOffs); - } - } - break; - } - case LF_CHAR: - case LF_SHORT: // Intentional fall-through - case LF_USHORT: - case LF_LONG: // Intentional fall-through - case LF_ULONG: - case LF_REAL32: - case LF_COMPLEX32: - case LF_REAL48: - case LF_REAL64: // Intentional fall-through - case LF_QUADWORD: - case LF_UQUADWORD: - case LF_COMPLEX64: - case LF_REAL80: - case LF_COMPLEX80: - case LF_REAL128: - case LF_COMPLEX128: - case LF_VARSTRING: - convertNumericLeaf(tdsTs.leaf,tsPos,tsRdr,tsWrtr); - break; - default: - // Set all unhandled types as NOTTRANS - dbgTs.leaf = LF_NOTTRANS; - tsWrtr.write(dbgTs.leaf); - - // Skip to the next type record as we cannot safely - // continue with this type record. - nextTypeString = true; - - mylog()(1) - << "Error: Unhandled type record with leaf id " - << std::hex << tdsTs.leaf << " at " << tsPos << std::endl; - } - - // Ensure that the next type string is properly aligned on a - // dword boundary - unsigned int bytesToWrite = recWrtr.length(tsWrtr); - unsigned int malgnmnt = bytesToWrite & 0x3; - if(malgnmnt) { - unsigned char padBytes = 3 - (unsigned char) malgnmnt; - // Determine which LF_PADxx identifier to write out. - unsigned char alignLeaf = 0xEF + padBytes; - tsWrtr.write(alignLeaf); - // Append padding bytes. - alignLeaf = 0; - tsWrtr.write(alignLeaf,padBytes); - } - } - - // Move to where the type record is to be written - dbgFile.seek(nextDbgTrPos); - - // Ensure that the record does not cross a page boundary. - pageAlign(dbgFile,recWrtr.length(tsWrtr),0xBFFF); - - // Remember the write position after alignment relative to the - // first type record. - unsigned int dbgTrPos = (dbgFile.tell() - frstDbgTrPos); - - // Update the record header length field and output the header and - // the data registered with the record writer. - recWrtr.commit(dbgFile,tsWrtr); - ++numDbgTypes; - - // Remember where to write the next type record to. - nextDbgTrPos = dbgFile.tell(); - - // Move to the position of the type record offset in the DBG file. - dbgFile.seek(dbgTrOffsPos); - - // Write the offset of the type record to the DBG file. - dbgFile.write(dbgTrPos); - - // Remember where to write the next type recor offset to the DBG file. - dbgTrOffsPos = dbgFile.tell(); - - // Move to read the next type record offset - recRdr.seek(nextTdsTrOffsPos); - --numTdsTypes; - } -} - -/// @brief Creates the sstGlobalPub subsection. -/// -/// @param conv Converter to use for various conversion tasks. -/// @param dbgFile The DBG file to write to. -/// @param[out] symTablSz Symbol table size. -/// @param[out] symHshTableSz Symbol hash table size. -/// @param[out] addrHshTableSz Address hash table size. -/// @param syms The public symbols. -/// -/// @throw runtime_error In case of errors. -void createGlobalPub(const Converter& conv, - FileWrapper& dbgFile, - DWORD& symTableSz, - DWORD& symHshTableSz, - DWORD& addrHshTableSz, - SymRecMap& syms) throw(std::runtime_error) -{ - // Create the internal name hash table - NameHshTbl nameHshTbl(NUM_HASH_BUCKETS); - - // Remember the DBG file offset of the first symbol - unsigned int frstSymOffs = dbgFile.tell(); - - // All symbol records in sstGlobalPub have kind PUB32. - CVsymRecHdr recHdr; - recHdr.symKind = CVskPublic; - - SymRecMap::iterator segIt = syms.begin(); - - // Loop over all segments - while(syms.end() != segIt) { - - SegmSymMap::iterator symIt = segIt->second.begin(); - - // Loop over all symbols within that segment - while(segIt->second.end() != symIt) { - - // Reserve space for a DBG symbol record header to be - // written out later. - HeaderWithLen dbgRecHdr(recHdr,recHdr.len); - - // Create a record writer to keep track of the number of - // payload bytes written. - RecordWriter wrtr; - - // Create the corresponding payload header for the DBG file - CVsrGlobalDataPayloadHdr dbgPlHdr = - {symIt->first, - segIt->first, - conv.typIdx(symIt->second.typeIdx)}; - - // Write the payload header to the DBG file - wrtr.write(dbgPlHdr); - - // Look up the symbol name - const std::string& symName = conv.getName(symIt->second.nameIdx); - - // Append the length-prefixed symbol name to the payload header - wrtr.write1bLpfName(symName); - - // Ensure that the next record is properly aligned on a - // dword boundary - zeroPad(wrtr,dbgRecHdr.length(wrtr),sizeof(int)-1); - - // Ensure that the record does not cross a page boundary. - pageAlignSymbol(dbgFile,dbgRecHdr.length(wrtr),0xFFF); - - // Store the relative offset of the current symbol record. - // We will need this later on for the address hash table. - // Note that this field may have been set elsewhere (e.g. - // in convertSymbolSubsection) to point to the position - // where the original symbol was found. We don't need that - // here, however. - symIt->second.symRefOffs = dbgFile.tell() - frstSymOffs; - - // Write the symbol record header with the correct length field - dbgRecHdr.commit(dbgFile,wrtr); - - // Update the internal name hash table - nameHshTbl.append(symName,symIt->second.symRefOffs); - - // Next symbol - ++symIt; - } - - // Next segment - ++segIt; - } - - // Symbol table must end with a closing S_ALIGN - appendClosingAlign(dbgFile); - - // Compute the size of the symbol table - symTableSz = dbgFile.tell()-frstSymOffs; - - // Proceed to write the symbol hash table - nameHshTbl.write(dbgFile,symHshTableSz); - - // Proceed to write the address hash table - createAddrHashTable(dbgFile,addrHshTableSz,syms); -} - -/// @brief Creates the sstStaticSym or similar subsection. -/// -/// @param conv Converter to use for various conversion tasks. -/// @param dbgFile The DBG file to write to. -/// @param[out] symTableSz Symbol table size. -/// @param[out] symHshTableSz Symbol hash table size. -/// @param[out] addrHshTableSz Address hash table size. -/// @param syms The symbols to write to the subsection. -/// @param algnmnt The size of pages. No symbol must cross a page. -/// 0 if none. -/// -/// @throw runtime_error In case of errors. -void createSymbolsSubsection(const Converter& conv, - FileWrapper& dbgFile, - DWORD& symTableSz, - DWORD& symHshTableSz, - DWORD& addrHshTableSz, - SymRecMap& syms, - unsigned int algnmnt) throw(std::runtime_error) -{ - // Create the internal name hash table - NameHshTbl nameHshTbl(NUM_HASH_BUCKETS); - - // Remember where we start writing to - unsigned int strtPos = dbgFile.tell(); - - CVsymRecHdr recHdr; - - SymRecMap::iterator segIt = syms.begin(); - - // Loop over all segments - while(syms.end() != segIt) { - - // Loop over all symbols within this segment - SegmSymMap::iterator symIt = segIt->second.begin(); - - while(segIt->second.end() != symIt) { - - // Set the symbol kind in the record header - recHdr.symKind = symIt->second.symKind; - - // Reserve space for a DBG symbol record header to be - // written out later. - HeaderWithLen dbgRecHdr(recHdr,recHdr.len); - - // Create a record writer to keep track of the number of - // payload bytes written. - RecordWriter wrtr; - - // Create the corresponding payload header for the DBG file - CVsrDataRefPayload dbgPl = - {0,symIt->second.offsSymRec,symIt->second.moduleIdx}; - - // Write the payload header to the DBG file - wrtr.write(dbgPl); - - // Ensure that the next record is properly aligned on a - // dword boundary - zeroPad(wrtr,dbgRecHdr.length(wrtr),sizeof(int)-1); - - // Ensure that the record does not cross a page boundary. - pageAlignSymbol(dbgFile,dbgRecHdr.length(wrtr),algnmnt); - - // Store the relative offset of the current symbol record. - // We will need this later on for the address hash table. - // Note that this field may have been set elsewhere (e.g. - // in convertSymbolSubsection) to point to the position - // where the original symbol was found. We don't need that - // here, however. - symIt->second.symRefOffs = dbgFile.tell() - strtPos; - - // Write the symbol record header with the correct length field - dbgRecHdr.commit(dbgFile,wrtr); - - // Look up the symbol name - const std::string& symName = conv.getName(symIt->second.nameIdx); - - // Update the internal name hash table - nameHshTbl.append(symName,symIt->second.symRefOffs); - - // Next symbol - ++symIt; - } - - // Next segment - ++segIt; - } - - // Some symbol tables must end with a closing S_ALIGN - just do it for all. - appendClosingAlign(dbgFile); - - // Compute the size of the generated symbol table - symTableSz = dbgFile.tell() - strtPos; - - // Proceed to write the symbol hash table - nameHshTbl.write(dbgFile,symHshTableSz); - - // Proceed to write the address hash table - createAddrHashTable(dbgFile,addrHshTableSz,syms); -} - -/// @brief TDS to Code View conversion algorithm -/// -/// @param conv The converter context to use. -/// @param tdsCvHdr The header already read from the TDS file. -/// @param tdsFile The TDS file to read from. The current read position -/// is assumed to be right after the CVHdr already read in. -/// @param segMap Input for the sstSegMap subsection. -/// @param dbgFile The DBG file to read from. -/// @param lfaBase Location of the Code View header in the output file. -/// -/// @throw runtime_error In case of errors. -void convertTdsToCodeView(Converter& conv, - CVHdr& tdsCvHdr, - FileWrapper& tdsFile, - const std::vector& segMap, - FileWrapper& dbgFile, - unsigned int lfaBase) - throw(std::runtime_error) -{ - // Move to the location of the Subsection directory in the tds file. - tdsFile.seek(tdsCvHdr.lfoDir); - - // Read the Subsection directory header from the TDS file. - CVDirHdr tdsCvDirHdr; - tdsFile.read(tdsCvDirHdr); - - // Read select subsection directory entries from the TDS files - typedef std::vector CVDirEntryList; - CVDirEntryList tdsSubSecDirEntries; - - mylog()(2) << "TDS subsection directory header has " - << tdsCvDirHdr.cDir << " entries." << std::endl; - - for(DWORD i=0 ; i tdsBaseSrcFile(fixedHdr.cFile); - tdsFile.read(tdsBaseSrcFile[0],tdsBaseSrcFile.size()); - - // Skip writing CVssSrcModule::hdr.baseSrcFile to the DBG - // file for now. We will compute and write it later on. - long int fileOffsOfbaseSrcFile = dbgFile.tell(); - dbgFile.skip(sizeof(DWORD)*fixedHdr.cFile); - - // Prepare the vector that will contain the data for - // CVssSrcModule::hdr.baseSrcFile - std::vector dbgBaseSrcFile; - - // Copy the TDSssSrcModule::hdr.startEnd to - // CVssSrcModule::hdr.startEnd - copyFileContent(dbgFile,tdsFile,2*sizeof(DWORD)*fixedHdr.cSeg); - - // Copy the TDSssSrcModule::hdr.seg to CVssSrcModule::hdr.seg - copyFileContent(dbgFile,tdsFile,sizeof(WORD)*fixedHdr.cSeg); - - // Copy the TDSssSrcModule::files to CVssSrcModule::files - std::vector::const_iterator srcFileIt = tdsBaseSrcFile.begin(); - while(tdsBaseSrcFile.end() != srcFileIt) { - dbgBaseSrcFile.push_back(dbgFile.tell()-dbgSubSecBase); - - TDSssSrcModuleFileHdr tdsFileHdr; - tdsFile.seek(subSecIt->lfo+*srcFileIt); - tdsFile.read(tdsFileHdr); - - CVssSrcModuleFileHdr dbgFileHdr = { tdsFileHdr.cSeg, 0 }; - dbgFile.write(dbgFileHdr); - - // Read in TDSssSrcModuleFile::baseSrcLn from the TDS file. - std::vector tdsBaseSrcLn(dbgFileHdr.cSeg); - tdsFile.read(tdsBaseSrcLn[0],tdsBaseSrcLn.size()); - - // Skip writing CVssSrcModuleFile::baseSrcLn to the DBG - // file as we will write it later on when the offsets are known. - long int fileOffsOfbaseSrcLen = dbgFile.tell(); - dbgFile.skip(sizeof(DWORD)*tdsFileHdr.cSeg); - - // Prepare a vector for the contents of baseSrcLn - std::vector dbgBaseSrcLen; - - // Copy TDSssSrcModuleFile::startEnd to CVssSrcModuleFile::startEnd - copyFileContent(dbgFile,tdsFile,2*sizeof(DWORD)*tdsFileHdr.cSeg); - - // Write out CVssSrcModuleFile::nameLen and CVsrcModuleFile::name - dbgFile.write1bLpfName(conv.getName(tdsFileHdr.nameIdx)); - - // Copy the CVssSrcModuleSeg's - std::vector::const_iterator segIt = tdsBaseSrcLn.begin(); - while(tdsBaseSrcLn.end() != segIt) { - dbgBaseSrcLen.push_back(dbgFile.tell()-dbgSubSecBase); - - // Copy CVssSrcModuleSeg::hdr - CVssSrcModuleSegHdr segHdr; - tdsFile.seek(subSecIt->lfo+*segIt); - tdsFile.read(segHdr); - dbgFile.write(segHdr); - - // Copy CVssSrcModuleSeg::offset and - // CVssSrcModuleSeg::linenumber - copyFileContent(dbgFile,tdsFile,(sizeof(WORD)+sizeof(DWORD))*segHdr.cPair); - - ++segIt; - } - - // Remember where we are - long int moduleRecEnd = dbgFile.tell(); - - // Write the correct offsets into CVssSrcModuleFile::baseSrcLen - dbgFile.seek(fileOffsOfbaseSrcLen); - dbgFile.write(dbgBaseSrcLen[0],dbgBaseSrcLen.size()); - - // Move back to where we were - dbgFile.seek(moduleRecEnd); - - ++srcFileIt; - } - - // Remember where we are - long int moduleEnd = dbgFile.tell(); - - // Write the correct offsets into baseSourceFile[cFile] - dbgFile.seek(fileOffsOfbaseSrcFile); - dbgFile.write(dbgBaseSrcFile[0],dbgBaseSrcFile.size()); - - // Move back to where we were - dbgFile.seek(moduleEnd); - - dirEntry.cb = dbgFile.tell() - dbgSubSecBase; - dbgSubSecDirEntries.push_back(dirEntry); - break; - } - - case CVsstIdxGlobalSym: { - mylog()(2) << "Converting sstGlobalSym found at " - << std::hex << tdsFile.tell() - << ". Writing at " << dbgFile.tell() - << " (" << (dbgFile.tell()-lfaBase) << ")" << std::endl; - - // Read in the header of the subsection - TDSssGlobalSymHdr tdsSymHdr; - tdsFile.read(tdsSymHdr); - - // Prepare the corresponding DBG header - CVssGlobalSymHdr dbgSymHdr; - dbgSymHdr.symhash = 0x0A; // Symbol hash table provided - dbgSymHdr.addrhash = 0x0C; // Address hash table provided - PpRecordWriter hdrWrtr(dbgFile,dbgSymHdr); - - unsigned int tdsTableSz = dirEntry.cb - (sizeof tdsSymHdr); - - // Set up output argument to contain converted symbols - SymRecMap syms; - - // Set up the internal name hash table - NameHshTbl nameHshTbl(NUM_HASH_BUCKETS); - - // Convert the symbols - convertSymbolSubsection(conv,0,tdsFile,tdsTableSz, - dbgFile,dbgSymHdr.cbSymbol,syms,nameHshTbl, - sstGlobalPubInput,sstStaticSymInput); - - // Proceed to write the symbol hash table - nameHshTbl.write(dbgFile,dbgSymHdr.cbSymHash); - - // Proceed to write the address hash table - createAddrHashTable(dbgFile,dbgSymHdr.cbAddrHash,syms); - - // Write the header with the update fields - hdrWrtr.commit(); - dirEntry.cb = dbgSymHdr.cbSymbol + dbgSymHdr.cbSymHash - + dbgSymHdr.cbAddrHash + hdrWrtr.length(); - - // Postpone appending the directory entry as we need to - // ensure that the directory entry for the sstGlobalPub - // preceeds the directory entry for the sstGlobalSym. - - // Align next subsection on dword boundary - zeroPad(dbgFile,dbgFile.tell(),sizeof(int)-1); - - // Proceed to generate the sstGlobalPub - mylog()(2) << "Generating sstGlobalPub at " - << std::hex << dbgFile.tell() - << " (" << (dbgFile.tell()-lfaBase) << ")" << std::endl; - - CVDirEntry glPubDirEntry; - glPubDirEntry.subsection = CVsstIdxGlobalPub; - glPubDirEntry.iMod = 0xFFFF; - glPubDirEntry.lfo = dbgFile.tell()-lfaBase; - - // Prepare the DBG header for this subsection - CVssGlobalSymHdr glPubHdr; - glPubHdr.symhash = 0x0A; // Symbol hash table provided - glPubHdr.addrhash = 0x0C; // Address hash table provided - - PpRecordWriter hdrWrtr2(dbgFile,glPubHdr); - - createGlobalPub(conv,dbgFile,glPubHdr.cbSymbol, - glPubHdr.cbSymHash,glPubHdr.cbAddrHash, - sstGlobalPubInput); - - // Write the header with the update fields - hdrWrtr2.commit(); - glPubDirEntry.cb = hdrWrtr2.length() + - glPubHdr.cbSymbol + glPubHdr.cbSymHash + glPubHdr.cbAddrHash; - - // Update the directory - dbgSubSecDirEntries.push_back(glPubDirEntry); - dbgSubSecDirEntries.push_back(dirEntry); - break; - } - - case CVsstIdxGlobalTypes: { - - // Skip generation of this section if not requested. - if(!conv.doConvertTypes()) { - mylog()(2) << "Skipping contents of"; - } else { - mylog()(2) << "Converting"; - } - mylog()(2) << " sstGlobalTypes found at " - << std::hex << tdsFile.tell() - << ". Writing at " << dbgFile.tell() - << " (" << (dbgFile.tell() - lfaBase) << ")" - << std::endl; - - // Remember the location of the subsection - unsigned int ssPos = tdsFile.tell(); (void) ssPos; - - CVssGlobalTypesHdr tdsTypHdr; - tdsFile.read(tdsTypHdr); - - // Prepare the corresponding DBG header - CVssGlobalTypesHdr dbgTypHdr = { 0x1000000, 0 }; - PpRecordWriter hdrWrtr(dbgFile,dbgTypHdr); - - unsigned int tdsTableSz = dirEntry.cb - (sizeof tdsTypHdr); - (void) tdsTableSz; - - if(conv.doConvertTypes()) { - // Convert type table and increment dirEntry.cb correspondingly - convertTypes(conv,tdsFile,tdsTypHdr.cTypes,tdsTableSz,ssPos, - dbgFile,dbgTypHdr.cTypes); - } - - // Write the header with the updated fields - hdrWrtr.commit(); - - // Compute the size of the subsection - dirEntry.cb = dbgFile.tell() - dbgSubSecBase; - - // Update the directory - dbgSubSecDirEntries.push_back(dirEntry); - break; - } - - case CVsstIdxAlignSym: { - mylog()(2) << "Converting sstAlignSym found at " - << std::hex << tdsFile.tell() - << ". Writing at " << dbgFile.tell() - << " (" << (dbgFile.tell()-lfaBase) << ")" << std::endl; - - CVssAlignSymHdr subSecHdr; - - tdsFile.read(subSecHdr); - - if(subSecHdr.signature != 1) { - mylog()(1) - << "Warning: Unidentified sstAlignSym signature!" - << std::endl; - } - - // Write the corresponding DBG header - dbgFile.write(subSecHdr); - - // Convert the symbols - DWORD symTableSz = 0; - unsigned int tdsTableSz = dirEntry.cb - (sizeof subSecHdr); - - // Set up a dummy internal name hash table - NameHshTbl nameHshTbl(NUM_HASH_BUCKETS); - - // Set up output argument to contain converted symbols - SymRecMap syms; - - // Convert the symbols - convertSymbolSubsection(conv,++moduleIdx, - tdsFile,tdsTableSz, - dbgFile, - symTableSz, - syms, - nameHshTbl, - sstGlobalPubInput, - sstStaticSymInput); - - // Compute the size of the subsection - dirEntry.cb = dbgFile.tell() - dbgSubSecBase; - - // Update the directory - dbgSubSecDirEntries.push_back(dirEntry); - break; - } - - default: - mylog()(1) << "Unidentified subsection found at " - << std::hex << tdsFile.tell() << std::endl; - break; - } - ++subSecIt; - } - - { - // Proceed to generate the sstStaticSym - - // Align subsection on dword boundary - zeroPad(dbgFile,dbgFile.tell(),sizeof(int)-1); - - mylog()(2) << "Generating sstStaticSym at " - << std::hex << dbgFile.tell() - << " (" << (dbgFile.tell()-lfaBase) << ")" << std::endl; - - CVDirEntry dirEntry; - dirEntry.subsection = CVsstIdxStaticSym; - dirEntry.iMod = 0xFFFF; - dirEntry.lfo = dbgFile.tell()-lfaBase; - - // Prepare the DBG header for this subsection - CVssGlobalSymHdr dbgHdr; - dbgHdr.symhash = 0x0A; // Symbol hash table provided - dbgHdr.addrhash = 0x0C; // Address hash table provided - PpRecordWriter hdrWrtr(dbgFile,dbgHdr); - - createSymbolsSubsection(conv,dbgFile,dbgHdr.cbSymbol,dbgHdr.cbSymHash, - dbgHdr.cbAddrHash,sstStaticSymInput,0); - - // Write the header with the update fields - hdrWrtr.commit(); - - // Compute the size of the subsection - dirEntry.cb = dbgHdr.cbSymbol + dbgHdr.cbSymHash + dbgHdr.cbAddrHash - + hdrWrtr.length(); - - // Push a matching entry to the tail of the directory - dbgSubSecDirEntries.push_back(dirEntry); - } - - // TDS files do not contain a sstSegMap. Make sure one is created in the - // DBG file. - { - // Align subsection on dword boundary - zeroPad(dbgFile,dbgFile.tell(),sizeof(int)-1); - - mylog()(2) << "Generating sstSegMap at " - << std::hex << dbgFile.tell() - << " (" << (dbgFile.tell()-lfaBase) << ")" << std::endl; - - CVDirEntry dirEntry; - dirEntry.subsection = CVsstIdxSegMap; - dirEntry.iMod = 0xFFFF; - dirEntry.lfo = dbgFile.tell()-lfaBase; - - RecordWriter wrtr; - - CVssSegMapHdr segMapHdr = {(WORD) segMap.size(), (WORD) segMap.size()}; - wrtr.write(segMapHdr); - - std::vector::const_iterator segIt = segMap.begin(); - while(segMap.end() != segIt) { - wrtr.write(*segIt); - ++segIt; - } - - // Write the contents - wrtr.commit(dbgFile); - - // Compute the size of the subsection - dirEntry.cb = wrtr.length(); - - // Push a matching entry to the tail of the directory - dbgSubSecDirEntries.push_back(dirEntry); - } - - // Prepare the CodeView header. - CVHdr dbgCvHdr = {{'N','B','0','9'},sizeof(CVHdr)}; // Due to bounds checking with zero terminator this must be assigned as array of 4 chars rather than string - - // Store the offset (relative to lfaBase) of the subsection - // directory header in the CodeView header. - dbgCvHdr.lfoDir = dbgFile.tell() - lfaBase; - - // Create the data for the subsection directory header in the DBG file. - CVDirHdr dbgCvDirHdr = tdsCvDirHdr; - dbgCvDirHdr.cDir = dbgSubSecDirEntries.size(); - - // Write the subsection directory header to the DBG file. - dbgFile.write(dbgCvDirHdr); - - // Write out all subsection directory headers for the DBG file. - for(DWORD i = 0; i segMap; - for(DWORD i=0 ; i<(DWORD)ntHdrs.FileHeader.NumberOfSections ; ++i) { - IMAGE_SECTION_HEADER secHdr; - if(1 != fread(&secHdr,(sizeof secHdr),1,ef)) { - throw std::runtime_error("Could not read SECTION_HEADER" - " from exe file"); - } - if(!bareCodeView) { - dbgFile.write(secHdr); - } - - CVssSegMapSegDesc segDesc = - {0,0,0,((WORD) i),0xFFFF,0xFFFF,0,((WORD) secHdr.Misc.VirtualSize)}; - segMap.push_back(segDesc); - } - - // Note: We do currently not attempt to create the - // IMAGE_DEBUG_TYPE_OMAP_FROM_SRC and IMAGE_DEBUG_TYPE_OMAP_TO_SRC - // subsections. - - // Write the IMAGE_DEBUG_DIRECTORY to the DBG PE file. - IMAGE_DEBUG_DIRECTORY dbgDir; - memset(&dbgDir,0,(sizeof dbgDir)); - - // Skips this if bare Code View output is requested (i.e. not an DBG PE file) - if(!bareCodeView) { - dbgDir.Characteristics = 0; - dbgDir.TimeDateStamp = ntHdrs.FileHeader.TimeDateStamp; - dbgDir.MajorVersion = 0; - dbgDir.MinorVersion = 0; - dbgDir.Type = IMAGE_DEBUG_TYPE_CODEVIEW; - dbgDir.SizeOfData = 0; // Will be filled in later - dbgDir.AddressOfRawData = 0; - dbgDir.PointerToRawData = sizeof(IMAGE_SEPARATE_DEBUG_HEADER)+ - (ntHdrs.FileHeader.NumberOfSections * sizeof(IMAGE_SECTION_HEADER))+ - sizeof(IMAGE_DEBUG_DIRECTORY); - dbgFile.write(dbgDir); - } - - // Remember where to write the Code View header - this is lfaBase - unsigned int lfaBase = dbgFile.tell(); - - // Reserve space for the Code View header - dbgFile.skip(sizeof(CVHdr)); - - // Read the CodeView header of the TDS file - the first record in the file. - CVHdr tdsCvHdr; - tdsFile.seek(0); - tdsFile.read(tdsCvHdr); - - mylog()(2) << "TDS header with signature " - << tdsCvHdr.signature[0] - << tdsCvHdr.signature[1] - << tdsCvHdr.signature[2] - << tdsCvHdr.signature[3] << std::endl; - - if('N' == tdsCvHdr.signature[0] && - 'B' == tdsCvHdr.signature[1] && - '0' == tdsCvHdr.signature[2] && - '9' == tdsCvHdr.signature[3]) { - - // TDS file already contains Code View NB09 - no need for conversion, - // just do a plain copy and update the PE header afterwards. - dbgFile.write(tdsCvHdr); - copyFile(dbgFile,tdsFile); - - } else if('F' == tdsCvHdr.signature[0] && - 'B' == tdsCvHdr.signature[1] && - '0' == tdsCvHdr.signature[2] && - ('9' == tdsCvHdr.signature[3] || - 'A' == tdsCvHdr.signature[3])) { - - // Do the conversion and update the PE header afterwards. - convertTdsToCodeView(conv,tdsCvHdr,tdsFile,segMap,dbgFile,lfaBase); - - } else { - throw std::runtime_error("Unidentified TDS signature"); - } - - // Are we generating a PE based DBG file or just a bare Code View file? - if(!bareCodeView) { - // Compute the total amount of Code View data - dbgDir.SizeOfData = dbgFile.tell() - lfaBase; - - // Write out the updated dbgDir.SizeOfData - dbgFile.seek(sizeof(IMAGE_SEPARATE_DEBUG_HEADER)+ - ntHdrs.FileHeader.NumberOfSections* - sizeof(IMAGE_SECTION_HEADER)); - dbgFile.write(dbgDir); - } -} - -int tds2dbg(const char* exeFileName, - const char* tdsFileName, - const char* dbgFileName, - unsigned int verbosity, - bool doConvTypes, - bool staticsAsPublics, - bool bareCodeView) -{ - // Set the verbosity level. - mylog().setVerbosity(verbosity); - - int ret = 1; - - FILE *ef = fopen(exeFileName,"rb+"); - if(ef) { - FILE *tf = fopen(tdsFileName,"rb"); - if(tf) { - FILE* df = fopen(dbgFileName,"wb"); - if(df) { - try { - genDbgFromExeNTds(ef,tf,df,doConvTypes,staticsAsPublics, - bareCodeView); - } catch(std::exception& e) { - mylog()(0) << e.what() << std::endl; - } - fclose(df); - ret = 0; - } else { - mylog()(0) - << "Unable to open " << dbgFileName << " for writing" << std::endl; - } - fclose(tf); - } else { - mylog()(0) - << "Unable to open " << tdsFileName << " for reading" << std::endl; - } - fclose(ef); - } - return ret; -} - - -// Local Variables: -// mode:c++ -// c-basic-offset: 3 -// indent-tabs-mode: nil -// End: diff --git a/common/windows/delphi/ext/tds2dbg/tds2dbg.dcf b/common/windows/delphi/ext/tds2dbg/tds2dbg.dcf deleted file mode 100644 index 25add6f39a..0000000000 --- a/common/windows/delphi/ext/tds2dbg/tds2dbg.dcf +++ /dev/null @@ -1,1515 +0,0 @@ -# Doxyfile 1.6.1 - -# This file describes the settings to be used by the documentation system -# doxygen (www.doxygen.org) for a project -# -# All text after a hash (#) is considered a comment and will be ignored -# The format is: -# TAG = value [value, ...] -# For lists items can also be appended using: -# TAG += value [value, ...] -# Values that contain spaces should be placed between quotes (" ") - -#--------------------------------------------------------------------------- -# Project related configuration options -#--------------------------------------------------------------------------- - -# This tag specifies the encoding used for all characters in the config file -# that follow. The default is UTF-8 which is also the encoding used for all -# text before the first occurrence of this tag. Doxygen uses libiconv (or the -# iconv built into libc) for the transcoding. See -# http://www.gnu.org/software/libiconv for the list of possible encodings. - -DOXYFILE_ENCODING = UTF-8 - -# The PROJECT_NAME tag is a single word (or a sequence of words surrounded -# by quotes) that should identify the project. - -PROJECT_NAME = tds2dbg - -# The PROJECT_NUMBER tag can be used to enter a project or revision number. -# This could be handy for archiving the generated documentation or -# if some version control system is used. - -PROJECT_NUMBER = 0.2 - -# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) -# base path where the generated documentation will be put. -# If a relative path is entered, it will be relative to the location -# where doxygen was started. If left blank the current directory will be used. - -OUTPUT_DIRECTORY = html - -# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create -# 4096 sub-directories (in 2 levels) under the output directory of each output -# format and will distribute the generated files over these directories. -# Enabling this option can be useful when feeding doxygen a huge amount of -# source files, where putting all generated files in the same directory would -# otherwise cause performance problems for the file system. - -CREATE_SUBDIRS = NO - -# The OUTPUT_LANGUAGE tag is used to specify the language in which all -# documentation generated by doxygen is written. Doxygen will use this -# information to generate all constant output in the proper language. -# The default language is English, other supported languages are: -# Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional, -# Croatian, Czech, Danish, Dutch, Esperanto, Farsi, Finnish, French, German, -# Greek, Hungarian, Italian, Japanese, Japanese-en (Japanese with English -# messages), Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian, -# Polish, Portuguese, Romanian, Russian, Serbian, Serbian-Cyrilic, Slovak, -# Slovene, Spanish, Swedish, Ukrainian, and Vietnamese. - -OUTPUT_LANGUAGE = English - -# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will -# include brief member descriptions after the members that are listed in -# the file and class documentation (similar to JavaDoc). -# Set to NO to disable this. - -BRIEF_MEMBER_DESC = YES - -# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend -# the brief description of a member or function before the detailed description. -# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the -# brief descriptions will be completely suppressed. - -REPEAT_BRIEF = YES - -# This tag implements a quasi-intelligent brief description abbreviator -# that is used to form the text in various listings. Each string -# in this list, if found as the leading text of the brief description, will be -# stripped from the text and the result after processing the whole list, is -# used as the annotated text. Otherwise, the brief description is used as-is. -# If left blank, the following values are used ("$name" is automatically -# replaced with the name of the entity): "The $name class" "The $name widget" -# "The $name file" "is" "provides" "specifies" "contains" -# "represents" "a" "an" "the" - -ABBREVIATE_BRIEF = - -# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then -# Doxygen will generate a detailed section even if there is only a brief -# description. - -ALWAYS_DETAILED_SEC = NO - -# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all -# inherited members of a class in the documentation of that class as if those -# members were ordinary class members. Constructors, destructors and assignment -# operators of the base classes will not be shown. - -INLINE_INHERITED_MEMB = NO - -# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full -# path before files name in the file list and in the header files. If set -# to NO the shortest path that makes the file name unique will be used. - -FULL_PATH_NAMES = NO - -# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag -# can be used to strip a user-defined part of the path. Stripping is -# only done if one of the specified strings matches the left-hand part of -# the path. The tag can be used to show relative paths in the file list. -# If left blank the directory from which doxygen is run is used as the -# path to strip. - -STRIP_FROM_PATH = - -# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of -# the path mentioned in the documentation of a class, which tells -# the reader which header file to include in order to use a class. -# If left blank only the name of the header file containing the class -# definition is used. Otherwise one should specify the include paths that -# are normally passed to the compiler using the -I flag. - -STRIP_FROM_INC_PATH = - -# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter -# (but less readable) file names. This can be useful is your file systems -# doesn't support long names like on DOS, Mac, or CD-ROM. - -SHORT_NAMES = NO - -# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen -# will interpret the first line (until the first dot) of a JavaDoc-style -# comment as the brief description. If set to NO, the JavaDoc -# comments will behave just like regular Qt-style comments -# (thus requiring an explicit @brief command for a brief description.) - -JAVADOC_AUTOBRIEF = NO - -# If the QT_AUTOBRIEF tag is set to YES then Doxygen will -# interpret the first line (until the first dot) of a Qt-style -# comment as the brief description. If set to NO, the comments -# will behave just like regular Qt-style comments (thus requiring -# an explicit \brief command for a brief description.) - -QT_AUTOBRIEF = NO - -# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen -# treat a multi-line C++ special comment block (i.e. a block of //! or /// -# comments) as a brief description. This used to be the default behaviour. -# The new default is to treat a multi-line C++ comment block as a detailed -# description. Set this tag to YES if you prefer the old behaviour instead. - -MULTILINE_CPP_IS_BRIEF = NO - -# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented -# member inherits the documentation from any documented member that it -# re-implements. - -INHERIT_DOCS = YES - -# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce -# a new page for each member. If set to NO, the documentation of a member will -# be part of the file/class/namespace that contains it. - -SEPARATE_MEMBER_PAGES = NO - -# The TAB_SIZE tag can be used to set the number of spaces in a tab. -# Doxygen uses this value to replace tabs by spaces in code fragments. - -TAB_SIZE = 8 - -# This tag can be used to specify a number of aliases that acts -# as commands in the documentation. An alias has the form "name=value". -# For example adding "sideeffect=\par Side Effects:\n" will allow you to -# put the command \sideeffect (or @sideeffect) in the documentation, which -# will result in a user-defined paragraph with heading "Side Effects:". -# You can put \n's in the value part of an alias to insert newlines. - -ALIASES = - -# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C -# sources only. Doxygen will then generate output that is more tailored for C. -# For instance, some of the names that are used will be different. The list -# of all members will be omitted, etc. - -OPTIMIZE_OUTPUT_FOR_C = NO - -# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java -# sources only. Doxygen will then generate output that is more tailored for -# Java. For instance, namespaces will be presented as packages, qualified -# scopes will look different, etc. - -OPTIMIZE_OUTPUT_JAVA = NO - -# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran -# sources only. Doxygen will then generate output that is more tailored for -# Fortran. - -OPTIMIZE_FOR_FORTRAN = NO - -# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL -# sources. Doxygen will then generate output that is tailored for -# VHDL. - -OPTIMIZE_OUTPUT_VHDL = NO - -# Doxygen selects the parser to use depending on the extension of the files it parses. -# With this tag you can assign which parser to use for a given extension. -# Doxygen has a built-in mapping, but you can override or extend it using this tag. -# The format is ext=language, where ext is a file extension, and language is one of -# the parsers supported by doxygen: IDL, Java, Javascript, C#, C, C++, D, PHP, -# Objective-C, Python, Fortran, VHDL, C, C++. For instance to make doxygen treat -# .inc files as Fortran files (default is PHP), and .f files as C (default is Fortran), -# use: inc=Fortran f=C. Note that for custom extensions you also need to set FILE_PATTERNS otherwise the files are not read by doxygen. - -EXTENSION_MAPPING = - -# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want -# to include (a tag file for) the STL sources as input, then you should -# set this tag to YES in order to let doxygen match functions declarations and -# definitions whose arguments contain STL classes (e.g. func(std::string); v.s. -# func(std::string) {}). This also make the inheritance and collaboration -# diagrams that involve STL classes more complete and accurate. - -BUILTIN_STL_SUPPORT = YES - -# If you use Microsoft's C++/CLI language, you should set this option to YES to -# enable parsing support. - -CPP_CLI_SUPPORT = NO - -# Set the SIP_SUPPORT tag to YES if your project consists of sip sources only. -# Doxygen will parse them like normal C++ but will assume all classes use public -# instead of private inheritance when no explicit protection keyword is present. - -SIP_SUPPORT = NO - -# For Microsoft's IDL there are propget and propput attributes to indicate getter -# and setter methods for a property. Setting this option to YES (the default) -# will make doxygen to replace the get and set methods by a property in the -# documentation. This will only work if the methods are indeed getting or -# setting a simple type. If this is not the case, or you want to show the -# methods anyway, you should set this option to NO. - -IDL_PROPERTY_SUPPORT = NO - -# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC -# tag is set to YES, then doxygen will reuse the documentation of the first -# member in the group (if any) for the other members of the group. By default -# all members of a group must be documented explicitly. - -DISTRIBUTE_GROUP_DOC = NO - -# Set the SUBGROUPING tag to YES (the default) to allow class member groups of -# the same type (for instance a group of public functions) to be put as a -# subgroup of that type (e.g. under the Public Functions section). Set it to -# NO to prevent subgrouping. Alternatively, this can be done per class using -# the \nosubgrouping command. - -SUBGROUPING = YES - -# When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum -# is documented as struct, union, or enum with the name of the typedef. So -# typedef struct TypeS {} TypeT, will appear in the documentation as a struct -# with name TypeT. When disabled the typedef will appear as a member of a file, -# namespace, or class. And the struct will be named TypeS. This can typically -# be useful for C code in case the coding convention dictates that all compound -# types are typedef'ed and only the typedef is referenced, never the tag name. - -TYPEDEF_HIDES_STRUCT = NO - -# The SYMBOL_CACHE_SIZE determines the size of the internal cache use to -# determine which symbols to keep in memory and which to flush to disk. -# When the cache is full, less often used symbols will be written to disk. -# For small to medium size projects (<1000 input files) the default value is -# probably good enough. For larger projects a too small cache size can cause -# doxygen to be busy swapping symbols to and from disk most of the time -# causing a significant performance penality. -# If the system has enough physical memory increasing the cache will improve the -# performance by keeping more symbols in memory. Note that the value works on -# a logarithmic scale so increasing the size by one will rougly double the -# memory usage. The cache size is given by this formula: -# 2^(16+SYMBOL_CACHE_SIZE). The valid range is 0..9, the default is 0, -# corresponding to a cache size of 2^16 = 65536 symbols - -SYMBOL_CACHE_SIZE = 0 - -#--------------------------------------------------------------------------- -# Build related configuration options -#--------------------------------------------------------------------------- - -# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in -# documentation are documented, even if no documentation was available. -# Private class members and static file members will be hidden unless -# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES - -EXTRACT_ALL = YES - -# If the EXTRACT_PRIVATE tag is set to YES all private members of a class -# will be included in the documentation. - -EXTRACT_PRIVATE = YES - -# If the EXTRACT_STATIC tag is set to YES all static members of a file -# will be included in the documentation. - -EXTRACT_STATIC = YES - -# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) -# defined locally in source files will be included in the documentation. -# If set to NO only classes defined in header files are included. - -EXTRACT_LOCAL_CLASSES = YES - -# This flag is only useful for Objective-C code. When set to YES local -# methods, which are defined in the implementation section but not in -# the interface are included in the documentation. -# If set to NO (the default) only methods in the interface are included. - -EXTRACT_LOCAL_METHODS = NO - -# If this flag is set to YES, the members of anonymous namespaces will be -# extracted and appear in the documentation as a namespace called -# 'anonymous_namespace{file}', where file will be replaced with the base -# name of the file that contains the anonymous namespace. By default -# anonymous namespace are hidden. - -EXTRACT_ANON_NSPACES = YES - -# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all -# undocumented members of documented classes, files or namespaces. -# If set to NO (the default) these members will be included in the -# various overviews, but no documentation section is generated. -# This option has no effect if EXTRACT_ALL is enabled. - -HIDE_UNDOC_MEMBERS = YES - -# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all -# undocumented classes that are normally visible in the class hierarchy. -# If set to NO (the default) these classes will be included in the various -# overviews. This option has no effect if EXTRACT_ALL is enabled. - -HIDE_UNDOC_CLASSES = YES - -# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all -# friend (class|struct|union) declarations. -# If set to NO (the default) these declarations will be included in the -# documentation. - -HIDE_FRIEND_COMPOUNDS = NO - -# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any -# documentation blocks found inside the body of a function. -# If set to NO (the default) these blocks will be appended to the -# function's detailed documentation block. - -HIDE_IN_BODY_DOCS = NO - -# The INTERNAL_DOCS tag determines if documentation -# that is typed after a \internal command is included. If the tag is set -# to NO (the default) then the documentation will be excluded. -# Set it to YES to include the internal documentation. - -INTERNAL_DOCS = YES - -# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate -# file names in lower-case letters. If set to YES upper-case letters are also -# allowed. This is useful if you have classes or files whose names only differ -# in case and if your file system supports case sensitive file names. Windows -# and Mac users are advised to set this option to NO. - -CASE_SENSE_NAMES = YES - -# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen -# will show members with their full class and namespace scopes in the -# documentation. If set to YES the scope will be hidden. - -HIDE_SCOPE_NAMES = NO - -# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen -# will put a list of the files that are included by a file in the documentation -# of that file. - -SHOW_INCLUDE_FILES = YES - -# If the INLINE_INFO tag is set to YES (the default) then a tag [inline] -# is inserted in the documentation for inline members. - -INLINE_INFO = YES - -# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen -# will sort the (detailed) documentation of file and class members -# alphabetically by member name. If set to NO the members will appear in -# declaration order. - -SORT_MEMBER_DOCS = YES - -# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the -# brief documentation of file, namespace and class members alphabetically -# by member name. If set to NO (the default) the members will appear in -# declaration order. - -SORT_BRIEF_DOCS = NO - -# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the (brief and detailed) documentation of class members so that constructors and destructors are listed first. If set to NO (the default) the constructors will appear in the respective orders defined by SORT_MEMBER_DOCS and SORT_BRIEF_DOCS. This tag will be ignored for brief docs if SORT_BRIEF_DOCS is set to NO and ignored for detailed docs if SORT_MEMBER_DOCS is set to NO. - -SORT_MEMBERS_CTORS_1ST = NO - -# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the -# hierarchy of group names into alphabetical order. If set to NO (the default) -# the group names will appear in their defined order. - -SORT_GROUP_NAMES = NO - -# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be -# sorted by fully-qualified names, including namespaces. If set to -# NO (the default), the class list will be sorted only by class name, -# not including the namespace part. -# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. -# Note: This option applies only to the class list, not to the -# alphabetical list. - -SORT_BY_SCOPE_NAME = NO - -# The GENERATE_TODOLIST tag can be used to enable (YES) or -# disable (NO) the todo list. This list is created by putting \todo -# commands in the documentation. - -GENERATE_TODOLIST = YES - -# The GENERATE_TESTLIST tag can be used to enable (YES) or -# disable (NO) the test list. This list is created by putting \test -# commands in the documentation. - -GENERATE_TESTLIST = YES - -# The GENERATE_BUGLIST tag can be used to enable (YES) or -# disable (NO) the bug list. This list is created by putting \bug -# commands in the documentation. - -GENERATE_BUGLIST = YES - -# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or -# disable (NO) the deprecated list. This list is created by putting -# \deprecated commands in the documentation. - -GENERATE_DEPRECATEDLIST= YES - -# The ENABLED_SECTIONS tag can be used to enable conditional -# documentation sections, marked by \if sectionname ... \endif. - -ENABLED_SECTIONS = - -# The MAX_INITIALIZER_LINES tag determines the maximum number of lines -# the initial value of a variable or define consists of for it to appear in -# the documentation. If the initializer consists of more lines than specified -# here it will be hidden. Use a value of 0 to hide initializers completely. -# The appearance of the initializer of individual variables and defines in the -# documentation can be controlled using \showinitializer or \hideinitializer -# command in the documentation regardless of this setting. - -MAX_INITIALIZER_LINES = 30 - -# Set the SHOW_USED_FILES tag to NO to disable the list of files generated -# at the bottom of the documentation of classes and structs. If set to YES the -# list will mention the files that were used to generate the documentation. - -SHOW_USED_FILES = YES - -# If the sources in your project are distributed over multiple directories -# then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy -# in the documentation. The default is NO. - -SHOW_DIRECTORIES = NO - -# Set the SHOW_FILES tag to NO to disable the generation of the Files page. -# This will remove the Files entry from the Quick Index and from the -# Folder Tree View (if specified). The default is YES. - -SHOW_FILES = YES - -# Set the SHOW_NAMESPACES tag to NO to disable the generation of the -# Namespaces page. -# This will remove the Namespaces entry from the Quick Index -# and from the Folder Tree View (if specified). The default is YES. - -SHOW_NAMESPACES = YES - -# The FILE_VERSION_FILTER tag can be used to specify a program or script that -# doxygen should invoke to get the current version for each file (typically from -# the version control system). Doxygen will invoke the program by executing (via -# popen()) the command , where is the value of -# the FILE_VERSION_FILTER tag, and is the name of an input file -# provided by doxygen. Whatever the program writes to standard output -# is used as the file version. See the manual for examples. - -FILE_VERSION_FILTER = - -# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed by -# doxygen. The layout file controls the global structure of the generated output files -# in an output format independent way. The create the layout file that represents -# doxygen's defaults, run doxygen with the -l option. You can optionally specify a -# file name after the option, if omitted DoxygenLayout.xml will be used as the name -# of the layout file. - -LAYOUT_FILE = - -#--------------------------------------------------------------------------- -# configuration options related to warning and progress messages -#--------------------------------------------------------------------------- - -# The QUIET tag can be used to turn on/off the messages that are generated -# by doxygen. Possible values are YES and NO. If left blank NO is used. - -QUIET = NO - -# The WARNINGS tag can be used to turn on/off the warning messages that are -# generated by doxygen. Possible values are YES and NO. If left blank -# NO is used. - -WARNINGS = YES - -# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings -# for undocumented members. If EXTRACT_ALL is set to YES then this flag will -# automatically be disabled. - -WARN_IF_UNDOCUMENTED = YES - -# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for -# potential errors in the documentation, such as not documenting some -# parameters in a documented function, or documenting parameters that -# don't exist or using markup commands wrongly. - -WARN_IF_DOC_ERROR = YES - -# This WARN_NO_PARAMDOC option can be abled to get warnings for -# functions that are documented, but have no documentation for their parameters -# or return value. If set to NO (the default) doxygen will only warn about -# wrong or incomplete parameter documentation, but not about the absence of -# documentation. - -WARN_NO_PARAMDOC = NO - -# The WARN_FORMAT tag determines the format of the warning messages that -# doxygen can produce. The string should contain the $file, $line, and $text -# tags, which will be replaced by the file and line number from which the -# warning originated and the warning text. Optionally the format may contain -# $version, which will be replaced by the version of the file (if it could -# be obtained via FILE_VERSION_FILTER) - -WARN_FORMAT = "$file:$line: $text" - -# The WARN_LOGFILE tag can be used to specify a file to which warning -# and error messages should be written. If left blank the output is written -# to stderr. - -WARN_LOGFILE = - -#--------------------------------------------------------------------------- -# configuration options related to the input files -#--------------------------------------------------------------------------- - -# The INPUT tag can be used to specify the files and/or directories that contain -# documented source files. You may enter file names like "myfile.cpp" or -# directories like "/usr/src/myproject". Separate the files or directories -# with spaces. - -INPUT = main.cpp tdscvstructs.h tds2dbg.h tds2dbg.cpp log.h \ - log.cpp fileutils.h fileutils.cpp - -# This tag can be used to specify the character encoding of the source files -# that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is -# also the default input encoding. Doxygen uses libiconv (or the iconv built -# into libc) for the transcoding. See http://www.gnu.org/software/libiconv for -# the list of possible encodings. - -INPUT_ENCODING = UTF-8 - -# If the value of the INPUT tag contains directories, you can use the -# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp -# and *.h) to filter out the source-files in the directories. If left -# blank the following patterns are tested: -# *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx -# *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.py *.f90 - -FILE_PATTERNS = - -# The RECURSIVE tag can be used to turn specify whether or not subdirectories -# should be searched for input files as well. Possible values are YES and NO. -# If left blank NO is used. - -RECURSIVE = NO - -# The EXCLUDE tag can be used to specify files and/or directories that should -# excluded from the INPUT source files. This way you can easily exclude a -# subdirectory from a directory tree whose root is specified with the INPUT tag. - -EXCLUDE = - -# The EXCLUDE_SYMLINKS tag can be used select whether or not files or -# directories that are symbolic links (a Unix filesystem feature) are excluded -# from the input. - -EXCLUDE_SYMLINKS = NO - -# If the value of the INPUT tag contains directories, you can use the -# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude -# certain files from those directories. Note that the wildcards are matched -# against the file with absolute path, so to exclude all test directories -# for example use the pattern */test/* - -EXCLUDE_PATTERNS = - -# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names -# (namespaces, classes, functions, etc.) that should be excluded from the -# output. The symbol name can be a fully qualified name, a word, or if the -# wildcard * is used, a substring. Examples: ANamespace, AClass, -# AClass::ANamespace, ANamespace::*Test - -EXCLUDE_SYMBOLS = - -# The EXAMPLE_PATH tag can be used to specify one or more files or -# directories that contain example code fragments that are included (see -# the \include command). - -EXAMPLE_PATH = - -# If the value of the EXAMPLE_PATH tag contains directories, you can use the -# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp -# and *.h) to filter out the source-files in the directories. If left -# blank all files are included. - -EXAMPLE_PATTERNS = - -# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be -# searched for input files to be used with the \include or \dontinclude -# commands irrespective of the value of the RECURSIVE tag. -# Possible values are YES and NO. If left blank NO is used. - -EXAMPLE_RECURSIVE = NO - -# The IMAGE_PATH tag can be used to specify one or more files or -# directories that contain image that are included in the documentation (see -# the \image command). - -IMAGE_PATH = - -# The INPUT_FILTER tag can be used to specify a program that doxygen should -# invoke to filter for each input file. Doxygen will invoke the filter program -# by executing (via popen()) the command , where -# is the value of the INPUT_FILTER tag, and is the name of an -# input file. Doxygen will then use the output that the filter program writes -# to standard output. -# If FILTER_PATTERNS is specified, this tag will be -# ignored. - -INPUT_FILTER = - -# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern -# basis. -# Doxygen will compare the file name with each pattern and apply the -# filter if there is a match. -# The filters are a list of the form: -# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further -# info on how filters are used. If FILTER_PATTERNS is empty, INPUT_FILTER -# is applied to all files. - -FILTER_PATTERNS = - -# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using -# INPUT_FILTER) will be used to filter the input files when producing source -# files to browse (i.e. when SOURCE_BROWSER is set to YES). - -FILTER_SOURCE_FILES = NO - -#--------------------------------------------------------------------------- -# configuration options related to source browsing -#--------------------------------------------------------------------------- - -# If the SOURCE_BROWSER tag is set to YES then a list of source files will -# be generated. Documented entities will be cross-referenced with these sources. -# Note: To get rid of all source code in the generated output, make sure also -# VERBATIM_HEADERS is set to NO. - -SOURCE_BROWSER = NO - -# Setting the INLINE_SOURCES tag to YES will include the body -# of functions and classes directly in the documentation. - -INLINE_SOURCES = NO - -# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct -# doxygen to hide any special comment blocks from generated source code -# fragments. Normal C and C++ comments will always remain visible. - -STRIP_CODE_COMMENTS = YES - -# If the REFERENCED_BY_RELATION tag is set to YES -# then for each documented function all documented -# functions referencing it will be listed. - -REFERENCED_BY_RELATION = NO - -# If the REFERENCES_RELATION tag is set to YES -# then for each documented function all documented entities -# called/used by that function will be listed. - -REFERENCES_RELATION = NO - -# If the REFERENCES_LINK_SOURCE tag is set to YES (the default) -# and SOURCE_BROWSER tag is set to YES, then the hyperlinks from -# functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will -# link to the source code. -# Otherwise they will link to the documentation. - -REFERENCES_LINK_SOURCE = YES - -# If the USE_HTAGS tag is set to YES then the references to source code -# will point to the HTML generated by the htags(1) tool instead of doxygen -# built-in source browser. The htags tool is part of GNU's global source -# tagging system (see http://www.gnu.org/software/global/global.html). You -# will need version 4.8.6 or higher. - -USE_HTAGS = NO - -# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen -# will generate a verbatim copy of the header file for each class for -# which an include is specified. Set to NO to disable this. - -VERBATIM_HEADERS = YES - -#--------------------------------------------------------------------------- -# configuration options related to the alphabetical class index -#--------------------------------------------------------------------------- - -# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index -# of all compounds will be generated. Enable this if the project -# contains a lot of classes, structs, unions or interfaces. - -ALPHABETICAL_INDEX = NO - -# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then -# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns -# in which this list will be split (can be a number in the range [1..20]) - -COLS_IN_ALPHA_INDEX = 5 - -# In case all classes in a project start with a common prefix, all -# classes will be put under the same header in the alphabetical index. -# The IGNORE_PREFIX tag can be used to specify one or more prefixes that -# should be ignored while generating the index headers. - -IGNORE_PREFIX = - -#--------------------------------------------------------------------------- -# configuration options related to the HTML output -#--------------------------------------------------------------------------- - -# If the GENERATE_HTML tag is set to YES (the default) Doxygen will -# generate HTML output. - -GENERATE_HTML = YES - -# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. -# If a relative path is entered the value of OUTPUT_DIRECTORY will be -# put in front of it. If left blank `html' will be used as the default path. - -HTML_OUTPUT = html - -# The HTML_FILE_EXTENSION tag can be used to specify the file extension for -# each generated HTML page (for example: .htm,.php,.asp). If it is left blank -# doxygen will generate files with .html extension. - -HTML_FILE_EXTENSION = .html - -# The HTML_HEADER tag can be used to specify a personal HTML header for -# each generated HTML page. If it is left blank doxygen will generate a -# standard header. - -HTML_HEADER = - -# The HTML_FOOTER tag can be used to specify a personal HTML footer for -# each generated HTML page. If it is left blank doxygen will generate a -# standard footer. - -HTML_FOOTER = - -# The HTML_STYLESHEET tag can be used to specify a user-defined cascading -# style sheet that is used by each HTML page. It can be used to -# fine-tune the look of the HTML output. If the tag is left blank doxygen -# will generate a default style sheet. Note that doxygen will try to copy -# the style sheet file to the HTML output directory, so don't put your own -# stylesheet in the HTML output directory as well, or it will be erased! - -HTML_STYLESHEET = - -# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes, -# files or namespaces will be aligned in HTML using tables. If set to -# NO a bullet list will be used. - -HTML_ALIGN_MEMBERS = YES - -# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML -# documentation will contain sections that can be hidden and shown after the -# page has loaded. For this to work a browser that supports -# JavaScript and DHTML is required (for instance Mozilla 1.0+, Firefox -# Netscape 6.0+, Internet explorer 5.0+, Konqueror, or Safari). - -HTML_DYNAMIC_SECTIONS = NO - -# If the GENERATE_DOCSET tag is set to YES, additional index files -# will be generated that can be used as input for Apple's Xcode 3 -# integrated development environment, introduced with OSX 10.5 (Leopard). -# To create a documentation set, doxygen will generate a Makefile in the -# HTML output directory. Running make will produce the docset in that -# directory and running "make install" will install the docset in -# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find -# it at startup. -# See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html for more information. - -GENERATE_DOCSET = NO - -# When GENERATE_DOCSET tag is set to YES, this tag determines the name of the -# feed. A documentation feed provides an umbrella under which multiple -# documentation sets from a single provider (such as a company or product suite) -# can be grouped. - -DOCSET_FEEDNAME = "Doxygen generated docs" - -# When GENERATE_DOCSET tag is set to YES, this tag specifies a string that -# should uniquely identify the documentation set bundle. This should be a -# reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen -# will append .docset to the name. - -DOCSET_BUNDLE_ID = org.doxygen.Project - -# If the GENERATE_HTMLHELP tag is set to YES, additional index files -# will be generated that can be used as input for tools like the -# Microsoft HTML help workshop to generate a compiled HTML help file (.chm) -# of the generated HTML documentation. - -GENERATE_HTMLHELP = NO - -# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can -# be used to specify the file name of the resulting .chm file. You -# can add a path in front of the file if the result should not be -# written to the html output directory. - -CHM_FILE = - -# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can -# be used to specify the location (absolute path including file name) of -# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run -# the HTML help compiler on the generated index.hhp. - -HHC_LOCATION = - -# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag -# controls if a separate .chi index file is generated (YES) or that -# it should be included in the master .chm file (NO). - -GENERATE_CHI = NO - -# If the GENERATE_HTMLHELP tag is set to YES, the CHM_INDEX_ENCODING -# is used to encode HtmlHelp index (hhk), content (hhc) and project file -# content. - -CHM_INDEX_ENCODING = - -# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag -# controls whether a binary table of contents is generated (YES) or a -# normal table of contents (NO) in the .chm file. - -BINARY_TOC = NO - -# The TOC_EXPAND flag can be set to YES to add extra items for group members -# to the contents of the HTML help documentation and to the tree view. - -TOC_EXPAND = NO - -# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and QHP_VIRTUAL_FOLDER -# are set, an additional index file will be generated that can be used as input for -# Qt's qhelpgenerator to generate a Qt Compressed Help (.qch) of the generated -# HTML documentation. - -GENERATE_QHP = NO - -# If the QHG_LOCATION tag is specified, the QCH_FILE tag can -# be used to specify the file name of the resulting .qch file. -# The path specified is relative to the HTML output folder. - -QCH_FILE = - -# The QHP_NAMESPACE tag specifies the namespace to use when generating -# Qt Help Project output. For more information please see -# http://doc.trolltech.com/qthelpproject.html#namespace - -QHP_NAMESPACE = - -# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating -# Qt Help Project output. For more information please see -# http://doc.trolltech.com/qthelpproject.html#virtual-folders - -QHP_VIRTUAL_FOLDER = doc - -# If QHP_CUST_FILTER_NAME is set, it specifies the name of a custom filter to add. -# For more information please see -# http://doc.trolltech.com/qthelpproject.html#custom-filters - -QHP_CUST_FILTER_NAME = - -# The QHP_CUST_FILT_ATTRS tag specifies the list of the attributes of the custom filter to add.For more information please see -#
Qt Help Project / Custom Filters. - -QHP_CUST_FILTER_ATTRS = - -# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this project's -# filter section matches. -# Qt Help Project / Filter Attributes. - -QHP_SECT_FILTER_ATTRS = - -# If the GENERATE_QHP tag is set to YES, the QHG_LOCATION tag can -# be used to specify the location of Qt's qhelpgenerator. -# If non-empty doxygen will try to run qhelpgenerator on the generated -# .qhp file. - -QHG_LOCATION = - -# The DISABLE_INDEX tag can be used to turn on/off the condensed index at -# top of each HTML page. The value NO (the default) enables the index and -# the value YES disables it. - -DISABLE_INDEX = NO - -# This tag can be used to set the number of enum values (range [1..20]) -# that doxygen will group on one line in the generated HTML documentation. - -ENUM_VALUES_PER_LINE = 4 - -# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index -# structure should be generated to display hierarchical information. -# If the tag value is set to YES, a side panel will be generated -# containing a tree-like index structure (just like the one that -# is generated for HTML Help). For this to work a browser that supports -# JavaScript, DHTML, CSS and frames is required (i.e. any modern browser). -# Windows users are probably better off using the HTML help feature. - -GENERATE_TREEVIEW = NO - -# By enabling USE_INLINE_TREES, doxygen will generate the Groups, Directories, -# and Class Hierarchy pages using a tree view instead of an ordered list. - -USE_INLINE_TREES = NO - -# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be -# used to set the initial width (in pixels) of the frame in which the tree -# is shown. - -TREEVIEW_WIDTH = 250 - -# Use this tag to change the font size of Latex formulas included -# as images in the HTML documentation. The default is 10. Note that -# when you change the font size after a successful doxygen run you need -# to manually remove any form_*.png images from the HTML output directory -# to force them to be regenerated. - -FORMULA_FONTSIZE = 10 - -# When the SEARCHENGINE tag is enable doxygen will generate a search box for the HTML output. The underlying search engine uses javascript -# and DHTML and should work on any modern browser. Note that when using HTML help (GENERATE_HTMLHELP) or Qt help (GENERATE_QHP) -# there is already a search function so this one should typically -# be disabled. - -SEARCHENGINE = YES - -#--------------------------------------------------------------------------- -# configuration options related to the LaTeX output -#--------------------------------------------------------------------------- - -# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will -# generate Latex output. - -GENERATE_LATEX = NO - -# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. -# If a relative path is entered the value of OUTPUT_DIRECTORY will be -# put in front of it. If left blank `latex' will be used as the default path. - -LATEX_OUTPUT = latex - -# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be -# invoked. If left blank `latex' will be used as the default command name. - -LATEX_CMD_NAME = latex - -# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to -# generate index for LaTeX. If left blank `makeindex' will be used as the -# default command name. - -MAKEINDEX_CMD_NAME = makeindex - -# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact -# LaTeX documents. This may be useful for small projects and may help to -# save some trees in general. - -COMPACT_LATEX = NO - -# The PAPER_TYPE tag can be used to set the paper type that is used -# by the printer. Possible values are: a4, a4wide, letter, legal and -# executive. If left blank a4wide will be used. - -PAPER_TYPE = a4wide - -# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX -# packages that should be included in the LaTeX output. - -EXTRA_PACKAGES = - -# The LATEX_HEADER tag can be used to specify a personal LaTeX header for -# the generated latex document. The header should contain everything until -# the first chapter. If it is left blank doxygen will generate a -# standard header. Notice: only use this tag if you know what you are doing! - -LATEX_HEADER = - -# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated -# is prepared for conversion to pdf (using ps2pdf). The pdf file will -# contain links (just like the HTML output) instead of page references -# This makes the output suitable for online browsing using a pdf viewer. - -PDF_HYPERLINKS = YES - -# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of -# plain latex in the generated Makefile. Set this option to YES to get a -# higher quality PDF documentation. - -USE_PDFLATEX = YES - -# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. -# command to the generated LaTeX files. This will instruct LaTeX to keep -# running if errors occur, instead of asking the user for help. -# This option is also used when generating formulas in HTML. - -LATEX_BATCHMODE = NO - -# If LATEX_HIDE_INDICES is set to YES then doxygen will not -# include the index chapters (such as File Index, Compound Index, etc.) -# in the output. - -LATEX_HIDE_INDICES = NO - -# If LATEX_SOURCE_CODE is set to YES then doxygen will include source code with syntax highlighting in the LaTeX output. Note that which sources are shown also depends on other settings such as SOURCE_BROWSER. - -LATEX_SOURCE_CODE = NO - -#--------------------------------------------------------------------------- -# configuration options related to the RTF output -#--------------------------------------------------------------------------- - -# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output -# The RTF output is optimized for Word 97 and may not look very pretty with -# other RTF readers or editors. - -GENERATE_RTF = NO - -# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. -# If a relative path is entered the value of OUTPUT_DIRECTORY will be -# put in front of it. If left blank `rtf' will be used as the default path. - -RTF_OUTPUT = rtf - -# If the COMPACT_RTF tag is set to YES Doxygen generates more compact -# RTF documents. This may be useful for small projects and may help to -# save some trees in general. - -COMPACT_RTF = NO - -# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated -# will contain hyperlink fields. The RTF file will -# contain links (just like the HTML output) instead of page references. -# This makes the output suitable for online browsing using WORD or other -# programs which support those fields. -# Note: wordpad (write) and others do not support links. - -RTF_HYPERLINKS = NO - -# Load stylesheet definitions from file. Syntax is similar to doxygen's -# config file, i.e. a series of assignments. You only have to provide -# replacements, missing definitions are set to their default value. - -RTF_STYLESHEET_FILE = - -# Set optional variables used in the generation of an rtf document. -# Syntax is similar to doxygen's config file. - -RTF_EXTENSIONS_FILE = - -#--------------------------------------------------------------------------- -# configuration options related to the man page output -#--------------------------------------------------------------------------- - -# If the GENERATE_MAN tag is set to YES (the default) Doxygen will -# generate man pages - -GENERATE_MAN = NO - -# The MAN_OUTPUT tag is used to specify where the man pages will be put. -# If a relative path is entered the value of OUTPUT_DIRECTORY will be -# put in front of it. If left blank `man' will be used as the default path. - -MAN_OUTPUT = man - -# The MAN_EXTENSION tag determines the extension that is added to -# the generated man pages (default is the subroutine's section .3) - -MAN_EXTENSION = .3 - -# If the MAN_LINKS tag is set to YES and Doxygen generates man output, -# then it will generate one additional man file for each entity -# documented in the real man page(s). These additional files -# only source the real man page, but without them the man command -# would be unable to find the correct page. The default is NO. - -MAN_LINKS = NO - -#--------------------------------------------------------------------------- -# configuration options related to the XML output -#--------------------------------------------------------------------------- - -# If the GENERATE_XML tag is set to YES Doxygen will -# generate an XML file that captures the structure of -# the code including all documentation. - -GENERATE_XML = NO - -# The XML_OUTPUT tag is used to specify where the XML pages will be put. -# If a relative path is entered the value of OUTPUT_DIRECTORY will be -# put in front of it. If left blank `xml' will be used as the default path. - -XML_OUTPUT = xml - -# The XML_SCHEMA tag can be used to specify an XML schema, -# which can be used by a validating XML parser to check the -# syntax of the XML files. - -XML_SCHEMA = - -# The XML_DTD tag can be used to specify an XML DTD, -# which can be used by a validating XML parser to check the -# syntax of the XML files. - -XML_DTD = - -# If the XML_PROGRAMLISTING tag is set to YES Doxygen will -# dump the program listings (including syntax highlighting -# and cross-referencing information) to the XML output. Note that -# enabling this will significantly increase the size of the XML output. - -XML_PROGRAMLISTING = YES - -#--------------------------------------------------------------------------- -# configuration options for the AutoGen Definitions output -#--------------------------------------------------------------------------- - -# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will -# generate an AutoGen Definitions (see autogen.sf.net) file -# that captures the structure of the code including all -# documentation. Note that this feature is still experimental -# and incomplete at the moment. - -GENERATE_AUTOGEN_DEF = NO - -#--------------------------------------------------------------------------- -# configuration options related to the Perl module output -#--------------------------------------------------------------------------- - -# If the GENERATE_PERLMOD tag is set to YES Doxygen will -# generate a Perl module file that captures the structure of -# the code including all documentation. Note that this -# feature is still experimental and incomplete at the -# moment. - -GENERATE_PERLMOD = NO - -# If the PERLMOD_LATEX tag is set to YES Doxygen will generate -# the necessary Makefile rules, Perl scripts and LaTeX code to be able -# to generate PDF and DVI output from the Perl module output. - -PERLMOD_LATEX = NO - -# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be -# nicely formatted so it can be parsed by a human reader. -# This is useful -# if you want to understand what is going on. -# On the other hand, if this -# tag is set to NO the size of the Perl module output will be much smaller -# and Perl will parse it just the same. - -PERLMOD_PRETTY = YES - -# The names of the make variables in the generated doxyrules.make file -# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. -# This is useful so different doxyrules.make files included by the same -# Makefile don't overwrite each other's variables. - -PERLMOD_MAKEVAR_PREFIX = - -#--------------------------------------------------------------------------- -# Configuration options related to the preprocessor -#--------------------------------------------------------------------------- - -# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will -# evaluate all C-preprocessor directives found in the sources and include -# files. - -ENABLE_PREPROCESSING = YES - -# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro -# names in the source code. If set to NO (the default) only conditional -# compilation will be performed. Macro expansion can be done in a controlled -# way by setting EXPAND_ONLY_PREDEF to YES. - -MACRO_EXPANSION = NO - -# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES -# then the macro expansion is limited to the macros specified with the -# PREDEFINED and EXPAND_AS_DEFINED tags. - -EXPAND_ONLY_PREDEF = NO - -# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files -# in the INCLUDE_PATH (see below) will be search if a #include is found. - -SEARCH_INCLUDES = YES - -# The INCLUDE_PATH tag can be used to specify one or more directories that -# contain include files that are not input files but should be processed by -# the preprocessor. - -INCLUDE_PATH = - -# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard -# patterns (like *.h and *.hpp) to filter out the header-files in the -# directories. If left blank, the patterns specified with FILE_PATTERNS will -# be used. - -INCLUDE_FILE_PATTERNS = - -# The PREDEFINED tag can be used to specify one or more macro names that -# are defined before the preprocessor is started (similar to the -D option of -# gcc). The argument of the tag is a list of macros of the form: name -# or name=definition (no spaces). If the definition and the = are -# omitted =1 is assumed. To prevent a macro definition from being -# undefined via #undef or recursively expanded use the := operator -# instead of the = operator. - -PREDEFINED = - -# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then -# this tag can be used to specify a list of macro names that should be expanded. -# The macro definition that is found in the sources will be used. -# Use the PREDEFINED tag if you want to use a different macro definition. - -EXPAND_AS_DEFINED = - -# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then -# doxygen's preprocessor will remove all function-like macros that are alone -# on a line, have an all uppercase name, and do not end with a semicolon. Such -# function macros are typically used for boiler-plate code, and will confuse -# the parser if not removed. - -SKIP_FUNCTION_MACROS = YES - -#--------------------------------------------------------------------------- -# Configuration::additions related to external references -#--------------------------------------------------------------------------- - -# The TAGFILES option can be used to specify one or more tagfiles. -# Optionally an initial location of the external documentation -# can be added for each tagfile. The format of a tag file without -# this location is as follows: -# -# TAGFILES = file1 file2 ... -# Adding location for the tag files is done as follows: -# -# TAGFILES = file1=loc1 "file2 = loc2" ... -# where "loc1" and "loc2" can be relative or absolute paths or -# URLs. If a location is present for each tag, the installdox tool -# does not have to be run to correct the links. -# Note that each tag file must have a unique name -# (where the name does NOT include the path) -# If a tag file is not located in the directory in which doxygen -# is run, you must also specify the path to the tagfile here. - -TAGFILES = - -# When a file name is specified after GENERATE_TAGFILE, doxygen will create -# a tag file that is based on the input files it reads. - -GENERATE_TAGFILE = - -# If the ALLEXTERNALS tag is set to YES all external classes will be listed -# in the class index. If set to NO only the inherited external classes -# will be listed. - -ALLEXTERNALS = NO - -# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed -# in the modules index. If set to NO, only the current project's groups will -# be listed. - -EXTERNAL_GROUPS = YES - -# The PERL_PATH should be the absolute path and name of the perl script -# interpreter (i.e. the result of `which perl'). - -PERL_PATH = /usr/bin/perl - -#--------------------------------------------------------------------------- -# Configuration options related to the dot tool -#--------------------------------------------------------------------------- - -# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will -# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base -# or super classes. Setting the tag to NO turns the diagrams off. Note that -# this option is superseded by the HAVE_DOT option below. This is only a -# fallback. It is recommended to install and use dot, since it yields more -# powerful graphs. - -CLASS_DIAGRAMS = YES - -# You can define message sequence charts within doxygen comments using the \msc -# command. Doxygen will then run the mscgen tool (see -# http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the -# documentation. The MSCGEN_PATH tag allows you to specify the directory where -# the mscgen tool resides. If left empty the tool is assumed to be found in the -# default search path. - -MSCGEN_PATH = - -# If set to YES, the inheritance and collaboration graphs will hide -# inheritance and usage relations if the target is undocumented -# or is not a class. - -HIDE_UNDOC_RELATIONS = YES - -# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is -# available from the path. This tool is part of Graphviz, a graph visualization -# toolkit from AT&T and Lucent Bell Labs. The other options in this section -# have no effect if this option is set to NO (the default) - -HAVE_DOT = NO - -# By default doxygen will write a font called FreeSans.ttf to the output -# directory and reference it in all dot files that doxygen generates. This -# font does not include all possible unicode characters however, so when you need -# these (or just want a differently looking font) you can specify the font name -# using DOT_FONTNAME. You need need to make sure dot is able to find the font, -# which can be done by putting it in a standard location or by setting the -# DOTFONTPATH environment variable or by setting DOT_FONTPATH to the directory -# containing the font. - -DOT_FONTNAME = FreeSans - -# The DOT_FONTSIZE tag can be used to set the size of the font of dot graphs. -# The default size is 10pt. - -DOT_FONTSIZE = 10 - -# By default doxygen will tell dot to use the output directory to look for the -# FreeSans.ttf font (which doxygen will put there itself). If you specify a -# different font using DOT_FONTNAME you can set the path where dot -# can find it using this tag. - -DOT_FONTPATH = - -# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen -# will generate a graph for each documented class showing the direct and -# indirect inheritance relations. Setting this tag to YES will force the -# the CLASS_DIAGRAMS tag to NO. - -CLASS_GRAPH = YES - -# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen -# will generate a graph for each documented class showing the direct and -# indirect implementation dependencies (inheritance, containment, and -# class references variables) of the class with other documented classes. - -COLLABORATION_GRAPH = YES - -# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen -# will generate a graph for groups, showing the direct groups dependencies - -GROUP_GRAPHS = YES - -# If the UML_LOOK tag is set to YES doxygen will generate inheritance and -# collaboration diagrams in a style similar to the OMG's Unified Modeling -# Language. - -UML_LOOK = NO - -# If set to YES, the inheritance and collaboration graphs will show the -# relations between templates and their instances. - -TEMPLATE_RELATIONS = NO - -# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT -# tags are set to YES then doxygen will generate a graph for each documented -# file showing the direct and indirect include dependencies of the file with -# other documented files. - -INCLUDE_GRAPH = YES - -# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and -# HAVE_DOT tags are set to YES then doxygen will generate a graph for each -# documented header file showing the documented files that directly or -# indirectly include this file. - -INCLUDED_BY_GRAPH = YES - -# If the CALL_GRAPH and HAVE_DOT options are set to YES then -# doxygen will generate a call dependency graph for every global function -# or class method. Note that enabling this option will significantly increase -# the time of a run. So in most cases it will be better to enable call graphs -# for selected functions only using the \callgraph command. - -CALL_GRAPH = NO - -# If the CALLER_GRAPH and HAVE_DOT tags are set to YES then -# doxygen will generate a caller dependency graph for every global function -# or class method. Note that enabling this option will significantly increase -# the time of a run. So in most cases it will be better to enable caller -# graphs for selected functions only using the \callergraph command. - -CALLER_GRAPH = NO - -# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen -# will graphical hierarchy of all classes instead of a textual one. - -GRAPHICAL_HIERARCHY = YES - -# If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES -# then doxygen will show the dependencies a directory has on other directories -# in a graphical way. The dependency relations are determined by the #include -# relations between the files in the directories. - -DIRECTORY_GRAPH = YES - -# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images -# generated by dot. Possible values are png, jpg, or gif -# If left blank png will be used. - -DOT_IMAGE_FORMAT = png - -# The tag DOT_PATH can be used to specify the path where the dot tool can be -# found. If left blank, it is assumed the dot tool can be found in the path. - -DOT_PATH = - -# The DOTFILE_DIRS tag can be used to specify one or more directories that -# contain dot files that are included in the documentation (see the -# \dotfile command). - -DOTFILE_DIRS = - -# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of -# nodes that will be shown in the graph. If the number of nodes in a graph -# becomes larger than this value, doxygen will truncate the graph, which is -# visualized by representing a node as a red box. Note that doxygen if the -# number of direct children of the root node in a graph is already larger than -# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note -# that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH. - -DOT_GRAPH_MAX_NODES = 50 - -# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the -# graphs generated by dot. A depth value of 3 means that only nodes reachable -# from the root by following a path via at most 3 edges will be shown. Nodes -# that lay further from the root node will be omitted. Note that setting this -# option to 1 or 2 may greatly reduce the computation time needed for large -# code bases. Also note that the size of a graph can be further restricted by -# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction. - -MAX_DOT_GRAPH_DEPTH = 0 - -# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent -# background. This is disabled by default, because dot on Windows does not -# seem to support this out of the box. Warning: Depending on the platform used, -# enabling this option may lead to badly anti-aliased labels on the edges of -# a graph (i.e. they become hard to read). - -DOT_TRANSPARENT = NO - -# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output -# files in one run (i.e. multiple -o and -T options on the command line). This -# makes dot run faster, but since only newer versions of dot (>1.8.10) -# support this, this feature is disabled by default. - -DOT_MULTI_TARGETS = YES - -# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will -# generate a legend page explaining the meaning of the various boxes and -# arrows in the dot generated graphs. - -GENERATE_LEGEND = YES - -# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will -# remove the intermediate dot files that are used to generate -# the various graphs. - -DOT_CLEANUP = YES diff --git a/common/windows/delphi/ext/tds2dbg/tds2dbg.h b/common/windows/delphi/ext/tds2dbg/tds2dbg.h deleted file mode 100644 index d77c49a5db..0000000000 --- a/common/windows/delphi/ext/tds2dbg/tds2dbg.h +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright (c) 2009-2010, Bjarne Juul Pasgaard -// -// Permission to use, copy, modify, and/or distribute this software for any -// purpose with or without fee is hereby granted, provided that the above -// copyright notice, this permission notice and the following disclaimer -// appear in all copies. -// -// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - -#ifndef TDS2DBGH -#define TDS2DBGH - -/// @brief Converts an exe and a tds file to a dbg file. -/// -/// @param exeFileName Name of EXE file. -/// @param tdsFileName Name of TDS file. -/// @param dbgFileName Name of DBG file. -/// @param verbosity Verbosity level. 0 means silence. -/// @param doConvTypes Should types be converted? -/// @param staticsAsPublics Treat statics as publics? -/// @param bareCodeView Write bare code view (no header). -/// -/// @todo doConvTypes == true is incomplete. -int tds2dbg(const char* exeFileName, - const char* tdsFileName, - const char* dbgFileName, - unsigned int verbosity, - bool doConvTypes, - bool staticsAsPublics, - bool bareCodeView); - -#endif diff --git a/common/windows/delphi/ext/tds2dbg/tds2dbg.sln b/common/windows/delphi/ext/tds2dbg/tds2dbg.sln deleted file mode 100644 index eed6a4bb0a..0000000000 --- a/common/windows/delphi/ext/tds2dbg/tds2dbg.sln +++ /dev/null @@ -1,31 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.28307.1000 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "tds2dbg", "tds2dbg.vcxproj", "{40BFECE3-7FC0-4649-A3DC-E017FF84C75B}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|x64 = Debug|x64 - Debug|x86 = Debug|x86 - Release|x64 = Release|x64 - Release|x86 = Release|x86 - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {40BFECE3-7FC0-4649-A3DC-E017FF84C75B}.Debug|x64.ActiveCfg = Debug|x64 - {40BFECE3-7FC0-4649-A3DC-E017FF84C75B}.Debug|x64.Build.0 = Debug|x64 - {40BFECE3-7FC0-4649-A3DC-E017FF84C75B}.Debug|x86.ActiveCfg = Debug|Win32 - {40BFECE3-7FC0-4649-A3DC-E017FF84C75B}.Debug|x86.Build.0 = Debug|Win32 - {40BFECE3-7FC0-4649-A3DC-E017FF84C75B}.Release|x64.ActiveCfg = Release|x64 - {40BFECE3-7FC0-4649-A3DC-E017FF84C75B}.Release|x64.Build.0 = Release|x64 - {40BFECE3-7FC0-4649-A3DC-E017FF84C75B}.Release|x86.ActiveCfg = Release|Win32 - {40BFECE3-7FC0-4649-A3DC-E017FF84C75B}.Release|x86.Build.0 = Release|Win32 - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {50FB6E2C-4FEF-4828-A315-1017276BF47A} - EndGlobalSection -EndGlobal diff --git a/common/windows/delphi/ext/tds2dbg/tds2dbg.vcxproj b/common/windows/delphi/ext/tds2dbg/tds2dbg.vcxproj deleted file mode 100644 index 5183081136..0000000000 --- a/common/windows/delphi/ext/tds2dbg/tds2dbg.vcxproj +++ /dev/null @@ -1,119 +0,0 @@ - - - - - Debug - Win32 - - - Release - Win32 - - - Debug - x64 - - - Release - x64 - - - - 15.0 - {40BFECE3-7FC0-4649-A3DC-E017FF84C75B} - Win32Proj - 10.0 - - - - Application - true - v143 - - - Application - false - v143 - - - Application - true - v143 - - - Application - false - v143 - - - - - - - - - - - - - - - - - - - - - true - - - true - - - - WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) - MultiThreadedDebugDLL - Level3 - ProgramDatabase - Disabled - - - MachineX86 - true - Console - - - - - WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) - MultiThreadedDLL - Level3 - ProgramDatabase - - - MachineX86 - true - Console - true - true - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/common/windows/delphi/ext/tds2dbg/tds2dbg.vcxproj.filters b/common/windows/delphi/ext/tds2dbg/tds2dbg.vcxproj.filters deleted file mode 100644 index a74a66814b..0000000000 --- a/common/windows/delphi/ext/tds2dbg/tds2dbg.vcxproj.filters +++ /dev/null @@ -1,50 +0,0 @@ - - - - - {4FC737F1-C7A5-4376-A066-2A32D752A2FF} - cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx - - - {93995380-89BD-4b04-88EB-625FBE52EBFB} - h;hh;hpp;hxx;hm;inl;inc;xsd - - - {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} - rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav - - - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - - - Resource Files - - - \ No newline at end of file diff --git a/common/windows/delphi/ext/tds2dbg/tdscvstructs.h b/common/windows/delphi/ext/tds2dbg/tdscvstructs.h deleted file mode 100644 index ab0a488f09..0000000000 --- a/common/windows/delphi/ext/tds2dbg/tdscvstructs.h +++ /dev/null @@ -1,1278 +0,0 @@ -// Copyright (c) 2009-2010, Bjarne Juul Pasgaard -// -// Permission to use, copy, modify, and/or distribute this software for any -// purpose with or without fee is hereby granted, provided that the above -// copyright notice, this permission notice and the following disclaimer -// appear in all copies. -// -// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - -/// @file tdscvstruct.sh TDS and Code View structures. - -#ifndef TDSCVSTRUCTS_H -#define TDSCVSTRUCTS_H - -#include - -// Use word-alignment -#ifdef __BORLANDC__ -#pragma option push -#pragma option -a2 -w-8059 -#endif - -/// @page DebugSymFileStructures Debug Symbol File Structure -/// -/// @section DBGFileStructure DBG File Structure. -/// -/// Resources -///
    -///
  • Under The Hood, "The Scoop on .DBG Files", Matt Pietrek, -/// Microsoft Systems Journal, March 1999
  • -///
  • Undocumented Windows 2000 Secrets, A Programers Cookbook, -/// Sven B. Schreiber, Addison-Wesley, -/// (http://undocumented.rawol.com/)
  • -///
  • Tool Interface Standard (TIS) - -/// Formats Specification for Windows Version 1.0 -/// (http://www.x86.org/ftp/manuals/tools/winf10.pdf)
  • -///
-/// -/// See the documentation for IMAGE_SEPARATE_DEBUG_HEADER in winnt.h: -/// @code -/// IMAGE_SEPARATE_DEBUG_HEADER -/// Signature = IMAGE_SEPARATE_DEBUG_SIGNATURE -/// IMAGE_SECTION_HEADER* (Copied from the executable) -/// IMAGE_DEBUG_DIRECTORY* (COFF, MISC, FPO Data, CodeView) -/// COFF Information - not generated -/// Misc Information - not generated -/// FPO Data - not generated -/// CodeView Information - The only information handled by MS Debuggers -/// lfaBase: NB09 (ascii) -/// DWORD offsetOfSubSectionDir; -/// SubSectionTable -/// SubSection* -/// SubSectionDir -/// SubSectionDirEntry* -/// @endcode -/// -/// -/// @section PDBFileStructure PDB File Structure -/// -/// NOTE: The current code is not able to generate PDB files. -/// -/// A PDB file is a PE file (see -/// "http://msdn.microsoft.com/en-us/magazine/ms809762.aspx" -/// MSDN Magazine -/// Debugging and Error Handling Technical Articles -/// Peering Inside the PE: A Tour of the Win32 Portable Executable File Format, -/// By Matt Pietrek, March 1994) -/// -/// PE file structure (see winnt.h). -/// @code -/// IMAGE_DOS_HEADER -/// e_lfanew = Releative location of IMAGE_NT_HEADERS -/// IMAGE_NT_HEADERS -/// DWORD Signature = IMAGE_NT_SIGNATURE -/// IMAGE_FILE_HEADER -/// Machine -/// NumberOfSections -/// IMAGE_OPTIONAL_HEADER -/// IMAGE_DATA_DIRECTORY[16] -/// DWORD VirtualAddress -/// DWORD Size -/// Section Table -/// IMAGE_SECTION_HEADER -/// -/// IMAGE_DATA_DIRECTORY[IMAGE_DIRECTORY_ENTRY_DEBUG] points to: -/// IMAGE_DEBUG_DIRECTORY+ (number of dirs computed from DataDirectory.Size) -/// Type=IMAGE_DEBUG_TYPE_CODEVIEW -/// @endcode - -/// @page TdsFileStructure Structure of TDS file -/// -/// CodeView header signature is "FB09" or "FB0A". Tail of tds file is -/// "FB0A" or "FB09" followed by length of file. -/// -/// The layout of a TDS file seems to be (derived from a dump made by -/// Borland's tdump.exe): -/// -/// @code -/// Header -/// sstModule* -/// sstAlignSym |* (sstAlignSym and sstSrcModule occur in pairs) -/// sstSrcModule| -/// sstGlobalSym -/// sstGlobalTypes -/// sstNames -/// @endcode -/// -/// -/// @section TDSsstNamesSubSec Borlands sstNames subsection -/// -/// A Borland specific susbection with index 0x130. -/// -/// @code -/// struct TDSsstNames { -/// DWORD numberOfNames -/// Name []; -/// }; -/// -/// struct Name { -/// unsigned char len; // Length of name excluding zero-terminator -/// char name; // Zero-terminated name -/// }; -/// @endcode -/// -/// -/// @section TDSsstSrcModuleSubSec Borlands sstSrcModule subsection -/// -/// The only deviation is the use of name index in the file info record -/// instead of an inlined name: -/// -/// @code -/// struct TDSssSrcModule { -/// CVssSrcModuleHdr hdr; -/// TDSssSrcModuleRec files[hdr.cFile]; -/// }; -/// -/// struct CVssSrcModule { -/// CVssSrcModuleHdr hdr; -/// CVssSrcModuleRec files[hdr.cFile]; -/// }; -/// -/// struct CVssSrcModuleHdr : public CVssSrcModuleFixedHdr { -/// DWORD baseSrcFile[cFile]; -/// DWORD startEnd[2*cSeg]; -/// WORD seg[cSeg]; -/// }; -/// -/// struct CVssSrcModuleFixedHdr { -/// WORD cFile; -/// WORD cSeg; -/// }; -/// -/// struct TDSssSrcModuleRec { -/// TDSsrcModuleFile file; -/// CVsrcModuleSeg segs[file.cSeg]; -/// }; -/// -/// struct CVssSrcModuleRec { -/// CVsrcModuleFile file; -/// CVsrcModuleSeg segs[file.cSeg]; -/// }; -/// -/// struct TDSssSrcModuleFile : public TDSssSrcModuleFileHdr { -/// DWORD baseSrcLn[cSeg]; -/// DWORD startEnd[2*cSeg]; -/// }; -/// -/// struct TDSssSrcModuleFileHdr { -/// WORD cSeg; -/// DWORD NameId; // This is "WORD pad" in MS STI -/// }; -/// -/// struct CVssSrcModuleFile : public CVssSrcModuleFileHdr { -/// DWORD baseSrcLen[cSeg]; -/// DWORD startEnd[2*cSeg]; -/// WORD nameLen; -/// char name[nameLen]; -/// }; -/// -/// struct CVssSrcModuleFileHdr { -/// WORD cSeg; -/// WORD pad; -/// }; -/// -/// struct CVssSrcModuleSeg { -/// CVssSrcModuleSegHdr hdr; -/// DWORD offset[cPair]; -/// WORD linenumber[cPair]; -/// }; -/// -/// struct CVssSrcModuleSegHdr { -/// WORD seg; -/// WORD cPair; -/// }; -/// @endcode -/// -/// -/// @section TDSsstSegMapSubSec Code View sstSegMap subsection -/// -/// Not present in TDS files. But apparently required (and ignored?) by -/// Microsofts debuggers. -/// -// -/// @section TDSsstGlobalSymSubSec TDS sstGlobalSym subsection -/// -/// The header (TDSssGlobalSymHdr) is an extension of the corresponding -/// Code View header (CVssGlobalSymHdr). -/// -/// -/// @section TDSSymbolRecords TDS Symbol Records -/// -/// -/// @subsection TDSSymbolRecordEntryPoint The Entry Point Symbol Record -/// -/// Identified by means of kind index S_ENTRY32=0x210. There is no -/// corresponding record in Code View. Payload: -/// -/// @code -/// struct TDSsrEntry32Payload { -/// DWORD offset; // Offset -/// WORD seg; // Segment -/// }; -/// @endcode -/// -/// -/// @subsection TDSSymbolRecordNameSpace The Namespace Symbol Record -/// -/// Identified by means of kind index S_NAMESPACE=0x25. There is no -/// corresponding record in Code View. Payload: -/// -/// @code -/// struct TDSsrNameSpacePayload { -/// DWORD nameIdx; // Index of namespace name in sstNames. -/// DWORD pad; // Padding? -/// WORD cUsing; // Number of name indices for using namespaces. -/// DWORD [cUsing]; // Indices of using namespace names in sstNames. -/// }; -/// @endcode -/// -/// -/// @subsection TDSSymbolRecordUDF The User Defined Type Symbol Record -/// -/// Identified by means of kind index s_UDT=0x4. The TDS form of the -/// record payload (TDSsrUserDefinedTypePayload) deviates from the Code View -/// form of the record payload (CVsrUserDefinedTypePayload): -/// -/// @code -/// struct CVsrUserDefinedTypePayloadHdr { -/// WORD typeIdx; // Type index -/// }; -/// -/// struct CVsrUserDefinedTypePayload { -/// CVsrUserDefinedTypePayloadHdr hdr; -/// unsigned char nameLen; -/// char name[nameLen]; -/// }; -/// -/// struct TDSsrUserDefinedTypePayload { -/// DWORD typeIdx; // Type index -/// WORD flags; // 0: on=tag,off=typedef 1: on=nested -/// DWORD nameIdx; // Index of the types name in sstNames. -/// DWORD brwsInfoOffs; // Browser info offset -/// }; -/// @endcode -/// -/// -/// @subsection TDSSymbolRecordGlobalProcRef The Global Procedure Reference Symbol Record -/// -/// Identified by means of kind index S_GPROCREF=0x20. There is no -/// corresponding record in Code View --- except, perhaps, for -/// S_PROCREF. The S_GPROCREF symbol can be used to identify public -/// functions/procedures that should be registered in -/// sstGlobalPub of the DBG file. Payload: -/// -/// @code -/// struct TDSsrGlobalProcRefPayload { -/// DWORD offSymRef; // Seems always to be 0. -/// DWORD typeIdx; // Type index. 0 if none. -/// DWORD nameIdx; // Index of name in sstNames -/// DWORD brwsInfoOffset; // Browser info offset? -/// DWORD offset; // Address offset of proc or name index of -/// // DLL if segment==0xFFFF. -/// WORD segment; // Segment. -/// DWORD nameIdx; // Index of linker name, if segment=0xFFFF? -/// }; -/// @endcode -/// -/// -/// @subsection TDSSymbolRecordGlobalDataRef The Global Data Reference Symbol Record -/// -/// Identified by means of kind index S_GDATAREF=0x21. There is no -/// corresponding record in Code View --- except, perhaps, for -/// S_DATAREF. The S_GDATAREF symbol can be used to identify public -/// data that should be registered in sstGlobalPub of the DBG -/// file. Payload: -/// -/// @code -/// struct TDSsrGlobalDataRefPayload { -/// DWORD offSymRef; // Seems always to be 0. -/// DWORD typeIdx; // Type index. 0 if none. -/// DWORD nameIdx; // Index of name in sstNames -/// DWORD brwsInfoOffset; // Browser info offset? -/// DWORD offset; // Address offset of proc or name index of -/// // DLL if segment==0xFFFF. -/// WORD segment; // Segment. -/// }; -/// @endcode -/// -/// -/// @subsection TDSSymbolRecordGlobalData The Global Data Symbol Record -/// -/// Identified by means of kind index S_GLOBALDATA=0x202. The TDS form -/// of the payload (TDSsrGlobalDataPayload) deviates from the Code -/// View form of the payload (CVsrGlobalDataPayload): -/// -/// @code -/// struct CVsrGlobalDataPayloadHdr { -/// DWORD offset; -/// WORD segment; -/// WORD typeIdx; -/// }; -/// -/// struct CVsrGlobalDataPayload { -/// CVsrGlobalDataPayloadHdr hdr; -/// unsigned char nameLen; -/// char name[nameLen]; -/// }; -/// -/// struct TDSsrGlobalDataPayload { -/// DWORD offset; // Address offset -/// WORD segment; // Segment -/// WORD flags; // 1=TLS var, 0=Not a TLS var.? -/// DWORD typeIdx; // Index of type -/// DWORD nameIdx; // Index of name in sstNames. -/// DWORD brwsInfoOffs; // Browser info offset? -/// }; -/// @endcode -/// -/// -/// @subsection TDSSymbolRecordLocalData The Local Data Symbol Record -/// -/// Identified by means of kind index S_LOCALDATA=0x201. The TDS form -/// of the payload is identical to TDSsrGlobalDataPayload. The Code View -/// form of the payload is identical to CVsrGlobalDataPayload. -/// -/// -/// @section TDSsstGlobalTypesSubSec TDS sstGlobalTypes subsection -/// -/// TDS relies on a signature of 0x1 in the first 4 bytes, whereas -/// Code View uses 0x1000000. In TDS files, offsets in type directory -/// are computed relative to the start of the subsection, whereas for -/// Code View (NB09), offsets are relative to the first type record. -/// -/// -/// @section TDSTypeRecords The TDS Type Records -/// -/// TDS and Code View rely on the same type record header (CVtrHeader). -/// The header is followed by a sequence of one or more type strings as -/// defined in the following subsections. -/// -/// -/// @subsection TDSTypeStringModifier The Modifier Type String -/// -/// Identified by LF_MODIFIER=0x1. Code View uses the format specified -/// by CVtsModifier, whereas TDS files use the format specified by -/// TDStsModifier. -/// -/// -/// @subsection TDSTypeStringPointer The Pointer Type String -/// -/// Identified by LF_POINTER=0x2. Code view relies on the format -/// specified by CVtsPointer, whereas TDS files rely on the format -/// specified by TDStsPointer. In both cases, there is a variant part -/// that follows immediately after CVtsPointer and TDStsPointer, respectively. -/// -/// @todo Investigate the differences in the variant part. -/// -/// -/// @subsection TDSTypeStringArray The Array Type String -/// -/// Identified by LF_ARRAY=0x3. Code view use the format specified by -/// CVtsArray followed by a length-prefixed name of the array. TDS -/// files rely on a format specified by TDStsArray. -/// -/// -/// @subsection TDSTypeStringClass The Class Type String -/// -/// Identified by LF_CLASS=0x4. The same comments as for the structure -/// type string (LF_STRUCTURE) applies here as well. -/// -/// -/// @subsection TDSTypeStringStructure The Structure Type String -/// -/// Identified by LF_STRUCTURE=0x5. It deviates from the corresponding Code -/// View structure: -/// -/// @code -/// struct CVtsClasses { -/// WORD count; // Number of elements in the class or structure. -/// WORD fieldIdx; // Type index of the field list for this class. -/// WORD property; -/// WORD dList; // Type index of the derivation list. -/// WORD vshape; // Type index of the virtual function table shape desc. -/// unsigned char len[]; // Numeric leaf describing the size of the structure. -/// unsigned char nameLen; -/// char name[nameLen]; -/// }; -/// -/// struct TDStsClasses { -/// WORD count; // Number of elements in the class or structure -/// DWORD fieldIdx; // Type index of the field list for this class. -/// WORD property; -/// DWORD contCls; // Type index of containing class. -/// DWORD dList; // Type index of the derivation list. -/// DWORD vshape; // Type index of the virtual function table shape desc. -/// DWORD nameIdx; // Index of name in sstNames -/// WORD len; // Length of the structure in bytes -/// }; -/// @endcode -/// -/// Apart from this, bit 8 of TDStsClasses::property is on if the class has a -/// destructor, whereas bit 8 of CVtsClasses::property indicates whether the -/// definition is scoped. -/// -/// -/// @subsection TDSTypeStringEnum The Enum Type String -/// -/// Identified by LF_ENUM=0x7. It deviates from the corresponding Code View -/// structure as follows: -/// -/// @code -/// struct CVtsEnum { -/// WORD count; // Number of enumerations. -/// WORD typeIdx; // Type index of the container underlying the enum. -/// WORD fieldIdx; // Type index of the fields of the enum. -/// WORD property; // Property bit field -/// unsigned char nameLen; -/// char name[nameLen]; -/// }; -/// -/// struct TDStsEnum { -/// WORD count; // Number of enumeration. -/// DWORD typeIdx; // Type index of the container underlying the enum. -/// DWORD fieldIdx; // Type index of the fields of the enum -/// DWORD contCls; // Type index of the containing class -/// DWORD nameIdx; // Index of name in sstNames -/// }; -/// @endcode -/// -/// -/// @subsection TDSTypeStringProcedure The Procedure Type String -/// -/// Identified by LF_PROCEDURE=0x8. The Code View and TDS structures -/// (CVtsProcedure and TDStsProcedure) dffer in the size of the -/// argList member. -/// -/// -/// @subsection TDSTypeStringMembFunc The Member Function Type String -/// -/// Identified by LF_MFUNCTION=0x9. It deviates from the corresponding -/// Code View structure as follows: -/// -/// @code -/// struct CVtsMFunction { -/// WORD rvTypeIdx; // Index of the return value type. -/// WORD classIdx; // Type index of the containg class. -/// WORD thisIdx; // Type index of the this pointer. -/// BYTE callConv; // Calling convention. -/// BYTE reserved; -/// WORD parms; // Number of params. -/// WORD argList; // Type index of the argument list. -/// DWORD thisAdjust; // This pointer adjustment. -/// }; -/// -/// struct TDStsMFunction { -/// DWORD rvTypeIdx; // Index of the return value type. -/// DWORD classIdx; // Type index of the containing class. -/// DWORD thisIdx; // Type index of the this pointer. -/// BYTE callConv; // Calling convention. -/// BYTE reserved; -/// WORD params; // Number of params. -/// DWORD argList; // Type index of the argument list. -/// DWORD thisAdjust; // This pointer adjustment. -/// }; -/// @endcode -/// -/// -/// @subsection TDSTypeStringVTShape The Virtual Function Table Shape Type String -/// -/// Identified by LF_VTSHAPE=0xA. It is identical to the corresponding Code -/// View structure. -/// -/// -/// @subsection TDSTypeStringLabel The Label Type String -/// -/// Identified by LF_LABEL=0xE. It is identical to the corresponding Code -/// View structure. -/// -/// -/// @subsection TDSTypeStringPArray The PArray Type String -/// -/// Identified by LF_PARRAY=0x32. There is no corresponding Code View structure. -/// -/// @code -/// struct TDStsPArray { -/// DWORD elemTypeIdx; // Type index of type of array elements -/// DWORD idxTypeIdx; // Type index of index variable -/// DWORD nameIdx; // Name index in sstNames -/// WORD size; -/// WORD numElements; -/// }; -/// @endcode -/// -/// -/// @subsection TDSTypeStringArgList The ArgList Type String -/// -/// Identified by LF_ARGLIST=0x0201. It is identical to the -/// corresponding Code View structure. -/// -/// -/// @subsection TDSTypeStringFieldList The Field List Type String -/// -/// Identified by LF_FIELDLIST=0x204. It is identical to the corresponding -/// Code View structure. The padding used between the individual fields are -/// as defined for Code View, too. -/// -/// -/// @subsection TDSTypeStringMethodList The Method List Type String -/// -/// Identified by LF_MLIST=0x207. It is deviates from the corresponding -/// Code View structure. -/// -/// @code -/// struct CVtsMList { -/// CVtsMListElem records[]; -/// }; -/// -/// struct CVtsMListElem { -/// WORD attribute; -/// WORD typeIdx; // Type index of the procedure record -/// DWORD vtabOffset[]; // 0 or 1 element. -/// }; -/// -/// struct TDStsMList { -/// TDStsMListElem records[]; -/// }; -/// -/// struct TDStsMListElem { -/// WORD attribute; -/// DWORD typeIdx; -/// DWORD brwsInfoOffst; // Browser info offset -/// DWORD vtabOffset[];; // 0 or 1 element. -/// }; -/// @endcode -/// -/// -/// @subsection TDSTypeStringEnumerate The Enumerate Type String -/// -/// Identified by LF_ENUMERATE=0x403. It deviates from the corresponding -/// Code View structure: -/// -/// @code -/// struct CVtsEnumerate { -/// WORD attribute; -/// unsigned char value[]; // Numeric leaf specifying the value of the enum -/// unsigned char nameLen; -/// char name[nameLen]; -/// }; -/// -/// struct TDStsEnumerator { -/// WORD attribute; -/// DWORD nameIdx; // Index of the enum name in sstNames -/// DWORD brwsInfoOffs; // Browser info offset? -/// unsigned char value[]; // Numeric leaf specifying the value of the enum -/// }; -/// -/// -/// @subsection TDSTypeStringMethod The Method Type String -/// -/// Identified by LF_METHOD=0x408. It deviates from the corresponding Code View -/// structure: -/// -/// @code -/// struct CVtsMethod { -/// WORD count; // Number of methods in method list -/// WORD listIdx; // Type index of the method list (size>1 when overloaded). -/// BYTE nameLen; -/// char name[nameLen]; -/// }; -/// -/// struct TDStsMethod { -/// WORD count; // Number of methods in method list -/// DWORD listIdx; // Type index of the method list (size_1 when overloaded). -/// DWORD nameIdx; // Index of name in sstNames -/// }; -/// @endcode - - -/// @brief Code View header -struct CVHdr { - char signature[4]; // File offset of this field is lfaBase - DWORD lfoDir; -}; - -/// @brief Code View directory header -struct CVDirHdr { - WORD cbDirHdr; // Length of directory header - WORD cbDirEntry; // Length of each directory entry - DWORD cDir; // Number of directory entries - LONG lfoNextDir; // Offset of lfaBase of next dir. Unused in TDS files. - DWORD flags; // Unused. -}; - -/// @brief Code View directory entry -struct CVDirEntry { - WORD subsection; // Subdirectory index. - WORD iMod; // Module index. - DWORD lfo; // Offset from the base address lfaBase - DWORD cb; // Number of bytes in subsection -}; - -/// @brief Extended Code View subsection types for TDS files -enum CVSst -{ - CVsstIdxModule = 0x120, - CVsstIdxAlignSym = 0x125, - CVsstIdxSrcModule = 0x127, - CVsstIdxGlobalSym = 0x129, - CVsstIdxGlobalPub = 0x12a, - CVsstIdxGlobalTypes = 0x12b, - CVsstIdxSegMap = 0x12d, - CVsstIdxNames = 0x130, - CVsstIdxStaticSym = 0x134 -}; - -/// @brief Header of Code View sstModule subsection -/// -/// The header is followed by a length-prefixed name of the module -/// and cSeg CVssModuleSegInfo records (in that order). -struct CVssModuleHdr { - WORD ovlNumber; - WORD iLib; - WORD cSeg; - WORD style; -}; - -/// @brief Header of TDS sstModule subsection -/// -/// The header is followed by cSeg CVssModuleSegInfo records. -struct TDSssModuleHdr : public CVssModuleHdr { - DWORD nameIdx; ///< 1-based name index in sstNames - char pad[16]; ///< Unused? -}; - -/// @brief Segment info record from Code View sstModule subsection -struct CVssModuleSegInfo { - WORD seg; - WORD flags; - DWORD offs; - DWORD cbSeg; -}; - -/// @brief Fixed part of the header of the Code View and TDS -/// sstSrcModule subsection. -/// -/// The header is followed by a structure of the form -/// @code -/// DWORD baseSrcFile[cFile]; -/// DWORD startEnd[2*cSeg]; -/// WORD seg[cSeg]; -/// @endcode -/// For DBG Files, the above is followed by cFile instances of the -/// following structure -/// @code -/// WORD cSeg2; // See CVssSrcModuleFileHdr -/// WORD pad; -/// DWORD baseSrcLen[cSeg2]; -/// DWORD startEnd[2*cSeg2]; -/// WORD nameLen; -/// char name[nameLen]; -/// { -/// WORD seg; // See CVssSrcModuleSegHdr -/// WORD cPair; -/// DWORD offset[cPair]; -/// WORD linenumber[cPair]; -/// } [cSeg2]; -/// @endcode -/// whereas for TDS files, it is followed by cFile instances of the -/// following structure -/// @code -/// WORD cSeg2; // See TDSssSrcModuleFileHdr -/// DWORD nameIdx; -/// DWORD baseSrcLen[cSeg2]; -/// DWORD startEnd[2*cSeg2]; -/// { -/// WORD seg; // Se CVssSrcModuleSegHdr -/// WORD cPair; -/// DWORD offset[cPair]; -/// WORD linenumber[cPair]; -/// } [cSeg2]; -/// @endcode -struct CVssSrcModuleFixedHdr { - WORD cFile; - WORD cSeg; -}; - -/// @brief Header of the Segment info record from Code View sstSrcModule subsec -struct CVssSrcModuleSegHdr { - WORD seg; - WORD cPair; -}; - -/// @brief Header of the File info record from TDS sstSrcModule subsec -struct TDSssSrcModuleFileHdr { - WORD cSeg; - DWORD nameIdx; -}; - -/// @brief Header of the File info record from Code View sstSrcModule subsec -struct CVssSrcModuleFileHdr { - WORD cSeg; - WORD pad; -}; - -/// @brief Header of the Code View sstSegMap subsection -struct CVssSegMapHdr { - WORD cSeg; ///< Number of segment descriptors - WORD cSegLog; ///< Number of logical segment descriptors -}; - -/// @brief A segment descriptor in the Code View sstSegMap subection -struct CVssSegMapSegDesc { - WORD flags; ///< Descriptor flags. - WORD ovl; ///< Logical overlay number. - WORD group; ///< Group index. - WORD frame; ///< Frame. - WORD iSegName; ///< Segment name. 0xFFFF means no name. - WORD iClassName; ///< Class name. 0xFFFF means no name. - WORD offset; /** Offset of logical segment within physical segment. - Always 0. */ - WORD cbSeg; ///< Size of segment. -}; - -/// @brief Header of the Code View sstGlobalTypes subsection -struct CVssGlobalTypesHdr { - DWORD flags; ///< Flags and signature. - DWORD cTypes; ///< Number of types in the subsection -}; - -/// @brief Header of the Code View sstGlobalSym subsection -struct CVssGlobalSymHdr { - WORD symhash; - WORD addrhash; - DWORD cbSymbol; - DWORD cbSymHash; - DWORD cbAddrHash; -}; - -/// @brief Header for the TDS sstGlobalSym subsection -struct TDSssGlobalSymHdr : public CVssGlobalSymHdr { - DWORD numUserDefTypes; ///< Number of user defined types. - DWORD numOtherSyms; ///< Number of other symbols. - DWORD numSyms; ///< Number of symbols. - DWORD numNameSpcs; ///< Number of namespaces. -}; - -/// @brief Header for the CodeView and TDS sstAlignSym subsection -struct CVssAlignSymHdr { - DWORD signature; ///< Always 1. -}; - -/// @brief Code View leaf identifiers -enum CVLeafId { - LF_MODIFIER = 0x0001, - LF_POINTER = 0x0002, - LF_ARRAY = 0x0003, - LF_CLASS = 0x0004, - LF_STRUCTURE = 0x0005, - LF_PROCEDURE = 0x0008, - LF_MFUNCTION = 0x0009, - LF_NOTTRANS = 0x0010, - LF_PAD0 = 0x00f0, - LF_PAD1 = 0x00f1, - LF_PAD2 = 0x00f2, - LF_PAD3 = 0x00f3, - LF_PAD4 = 0x00f4, - LF_PAD5 = 0x00f5, - LF_PAD6 = 0x00f6, - LF_PAD7 = 0x00f7, - LF_PAD8 = 0x00f8, - LF_PAD9 = 0x00f9, - LF_PAD10 = 0x00fa, - LF_PAD11 = 0x00fb, - LF_PAD12 = 0x00fc, - LF_PAD13 = 0x00fd, - LF_PAD14 = 0x00fe, - LF_PAD15 = 0x00ff, - LF_ARGLIST = 0x0201, - LF_MLIST = 0x0207, - LF_NUMERIC = 0x8000, - LF_CHAR = 0x8000, - LF_SHORT = 0x8001, - LF_USHORT = 0x8002, - LF_LONG = 0x8003, - LF_ULONG = 0x8004, - LF_REAL32 = 0x8005, - LF_REAL64 = 0x8006, - LF_REAL80 = 0x8007, - LF_REAL128 = 0x8008, - LF_QUADWORD = 0x8009, - LF_UQUADWORD = 0x800a, - LF_REAL48 = 0x800b, - LF_COMPLEX32 = 0x800c, - LF_COMPLEX64 = 0x800d, - LF_COMPLEX80 = 0x800e, - LF_COMPLEX128 = 0x800f, - LF_VARSTRING = 0x8010 -}; - -/// @brief Header of the Code View type record. -struct CVtrHeader { - WORD len; ///< Length excluding the length field itself. -}; - -/// @brief Type string for Code View LF_MODIFIER -struct CVtsModifier { - WORD attribute; ///< Modifier attributes - WORD typeIdx; ///< Index of modified type -}; - -/// @brief Type string for TDS LF_MODIFIER -struct TDStsModifier { - WORD attribute; ///< Modifier attributes - DWORD typeIdx; ///< Index of modified type -}; - -/// @brief Type string for Code View LF_POINTER -struct CVtsPointer { - WORD attribute; ///< Pointer attributes. - WORD typeIdx; ///< Type index of the object pointed to. -}; - -/// @brief Type string for TDS LF_POINTER -struct TDStsPointer { - WORD attribute; ///< Pointer attributes. - DWORD typeIdx; ///< Type index of the object pointed to. -}; - -/// @brief Type string for Code View LF_ARRAY. -/// -/// The complete type string consists of the CVtsArray structure followed -/// by a length-prefixed name. -struct CVtsArray { - WORD elemTypeIdx; ///< Type index of the array elements - WORD idxTypeIdx; ///< Type index of the index variable -}; - -/// @brief Type string for TDS LF_ARRAY -struct TDStsArray { - DWORD elemTypeIdx; ///< Type index of the array elements - DWORD idxTypeIdx; ///< Type index of the index variable - DWORD nameIdx; - WORD size; - WORD numElements; -}; - -/// @brief Type string for Code View LF_CLASS (and LF_STRUCTURE) -/// -/// The complete type string consists of the CVtsClass structure followed -/// by a numeric leaf specifying the size of the class in bytes and the -/// length-prefixed name of the type (in that order). -struct CVtsClass { - WORD count; ///< Number of elements in the class or structure. - WORD fieldTypeIdx; ///< Type index of the field list of this class. - WORD property; ///< Property bit field. - WORD dList; ///< Type index of derivation list. - WORD vShape; ///< Type index of the virtual function table shape desc. -}; - -/// @brief Type string for TDS LF_CLASS (and LF_STRUCTURE) -/// -/// The complete type string consists of the CVtsClass structure followed -/// by a numeric leaf specifying the size of the class in bytes. -struct TDStsClass { - WORD count; ///< Number of elements in the class or structure. - DWORD fieldTypeIdx;///< Type index of the field list of this class. - WORD property; ///< Property bit field. - DWORD cClass; ///< Index of containing class. - DWORD dList; ///< Type index of derivation list. - DWORD vShape; ///< Type index of the virtual function table shape desc. - DWORD nameIdx; ///< Index of class name. -}; - -/// @brief Type string for Code View LF_PROCEDURE -struct CVtsProcedure { - WORD rvTypeIdx; ///< Type index of the return value. - BYTE callConv; ///< Calling convention. - BYTE reserved; ///< Reserved. - WORD parms; ///< Number of parameters. - WORD argList; ///< Type index of argument list. -}; - -/// @brief Type string for TDS LF_PROCEDURE -struct TDStsProcedure { - DWORD rvTypeIdx; ///< Type index of the return value. - BYTE callConv; ///< Calling convention. - BYTE reserved; ///< Reserved. - WORD parms; ///< Number of parameters. - DWORD argList; ///< Type index of argument list. -}; - -/// @brief Type string for Code View LF_MFUNCTION -struct CVtsMFunction { - WORD rvTypeIdx; ///< Type index of the return value. - WORD classIdx; ///< Type index of the containing class. - WORD thisIdx; ///< Type index of the this parameter. - BYTE callConv; ///< Calling convention. - BYTE reserved; ///< Reserved. - WORD parms; ///< Number of parameters. - WORD argList; ///< Type index of argument list. - DWORD thisAdjust; ///< Logical this adjuster. -}; - -/// @brief Type string for TDS LF_MFUNCTION -struct TDStsMFunction { - DWORD rvTypeIdx; ///< Type index of the return value. - DWORD classIdx; ///< Type index of the containing class. - DWORD thisIdx; ///< Type index of the this parameter. - BYTE callConv; ///< Calling convention. - BYTE reserved; ///< Reserved. - WORD parms; ///< Number of parameters. - DWORD argList; ///< Type index of argument list. - DWORD thisAdjust; ///< Logical this adjuster. -}; - -/// @defgroup MemberAttrBitField Member Attribute Bit Field -/// @{ - -/// @brief Mask for the access attribute -#define MEMBATTR_ACCESS_MASK 0x0003 - -/// @brief Value of the access attribute if the method has no access protection -#define MEMBATTR_NOACCPROT 0x0000 - -/// @brief Value of the access attribute if the method offers private access -#define MEMBATTR_PRIVATE 0x0001 - -/// @brief Value of the access attribute if the method offers protected access -#define MEMBATTR_PROTECTED 0x0002 - -/// @brief Value of the access attribute if the method offers public access -#define MEMBATTR_PUBLIC 0x0003 - -/// @brief Mask for the method properties -#define MEMBATTR_PROP_MASK 0x001C - -/// @brief Value of the method properties for a vanilla method -#define MEMBATTR_PROP_VANILLA 0x0000 - -/// @brief Value of the method properties for a virtual method -#define MEMBATTR_PROP_VIRTUAL 0x0004 - -/// @brief Value of the method properties for a static method -#define MEMBATTR_PROP_STATIC 0x0008 - -/// @brief Value of the method properties for a friend method -#define MEMBATTR_PROP_FRIEND 0x000C - -/// @brief Value of the method properties for an "introducing" virtual method -#define MEMBATTR_PROP_INTVIRT 0x0010 - -/// @brief Value of the method properties for a pure virtual method -#define MEMBATTR_PROP_PUREV 0x0014 - -/// @brief Value of the method properties for an "introducing" pure virtual -/// method. -#define MEMBATTR_PROP_INTPURE 0x0018 - -/// @brief Set if method is never instantiated by the compiler. -#define MEMBATTR_PSEUDO 0x0020 - -/// @brief Set if class cannot be inherited -#define MEMBATTR_NONINHERIT 0x0040 - -/// @brief Set if class cannot be constructed -#define MEMBATTR_NOCONSTRUCT 0x0080 - -/// @brief Set if this is an overloaded operator -#define MEMBATTR_OVLOP 0x0100 - -/// @brief Set if this is a type conversion operator -#define MEMBATTR_CNVOP 0x0200 - -/// @brief Set if this is a constructor -#define MEMBATTR_ISCTOR 0x0400 - -/// @brief Set if this is a destructor -#define MEMBATTR_ISDTOR 0x0800 - -/// @brief Set if this is a compiler-created method -#define MEMBATTR_ISBLTIN 0x1000 - -/// @brief Set if this is a bitfield member -#define MEMBATTR_ISBITFIELD 0x2000 - -/// @} - -/// @brief Type string element for Code View LF_MLIST -/// -/// The full type string is a sequence of CVtsMListElem structures, -/// each optionally followed by a 4-byte vtaboffset. -struct CVtsMListElem { - WORD attribute; ///< Member function attributes. - WORD typeIdx; ///< Type index of member function signature. -}; - -/// @brief Type string element for TDS LF_MLIST -/// -/// The full type string is a sequence of TDStsMListElem structures, -/// each optionally followed by a 4-byte vtaboffset. -struct TDStsMListElem { - WORD attribute; ///< Member function attributes. - DWORD typeIdx; ///< Type index of member function signature. - DWORD brwsOffs; ///< Browser offset. -}; - -/// @brief Type string for TDS files. -struct TDSTypeString { - WORD leaf; ///< Leaf identifier (see CVLeafId). - union { - TDStsModifier modifier; ///< Type string for a modifier - TDStsPointer pointer; ///< Type string for a pointer. - TDStsArray array; ///< Type string for an array. - TDStsClass cls; ///< Type string for a class. - TDStsProcedure procedure; ///< Type string for a procedure. - TDStsMFunction mFunction; ///< Type string for a member function. - }; -}; - -/// @brief Type string for Code View files -struct CVTypeString { - WORD leaf; ///< Leaf indentifier (see CVLeafId). - union { - CVtsModifier modifier; ///< Type string for a modifier. - CVtsPointer pointer; ///< Type string for a pointer. - CVtsArray array; ///< Type string for an array. - CVtsClass cls; ///< Type string for a class. - CVtsProcedure procedure; ///< Type string for a procedure. - CVtsMFunction mFunction; ///< Type string for a member function. - }; -}; - -/// @brief The kind of symbol described in the payload of a symbol record. -enum CVSymKind { - CVskCompile = 0x0001, - CVskRegister = 0x0002, - CVskUDT = 0x0004, - CVskSSearch = 0x0005, - CVskEnd = 0x0006, - CVskSkip = 0x0007, - CVskGProcRef = 0x0020, - CVskGDataRef = 0x0021, - CVskNamespace = 0x0025, - CVskUsing = 0x0026, - CVskBPRel = 0x0200, - CVskLocalData = 0x0201, - CVskGlobalData = 0x0202, - CVskPublic = 0x0203, - CVskLocalProc = 0x0204, - CVskGlobalProc = 0x0205, - CVskBlock32 = 0x0207, - CVskEntry32 = 0x0210, - CVskOptVar32 = 0x0211, // Variable life range support. - CVskProcRet32 = 0x0212, // Procedure return epilogue. - CVskSaveRegs32 = 0x0213, // ??? - CVskProcRef = 0x0400, - CVskDataRef = 0x0401, - CVskAlign = 0x0402 -}; - -/// @brief Header of the Code View symbol record -struct CVsymRecHdr { - - /// @brief Default constructor - CVsymRecHdr() : len(1), symKind(CVskSkip) {} - - /// @brief Constructor - CVsymRecHdr(WORD len_, WORD symKind_) : len(len_), symKind(symKind_) {} - - WORD len; ///< Length excluding length field itself - WORD symKind; ///< Kind of symbol (see CVSymKind) -}; - -/// @brief Header of the payload of the Code View and TDS Compile Flag -/// symbol record. -struct CVsrCompileFlagPayloadHdr { - BYTE machine; ///< Target processor enum. - BYTE language; ///< Language enum. - WORD flags; ///< Misc flags. -}; - -/// @brief Header of the payload of the Code View Register symbol record -/// -/// @note Register tracking information is assumed to be absent. -struct CVsrRegisterPayloadHdr { - WORD typeIdx; ///< Type index. - WORD regId; ///< Register identifier. -}; - -/// @brief Payload of the TDS Register symbol record { -struct TDSsrRegisterPayload { - DWORD typeIdx; ///< Type index. - WORD regId; ///< Register indentifier. - DWORD nameIdx; ///< Index of symbol name. - WORD brwsOffs; ///< Browser info offset. -}; - -/// @brief Header of the payload of the Code View User Defined Type -/// symbol record -struct CVsrUserDefinedTypePayloadHdr { - WORD typeIdx; ///< Type index -}; - -/// @brief The payload of the TDS User Defined Type symbol record -struct TDSsrUserDefinedTypePayload { - DWORD typeIdx; ///< Type index - WORD flags; ///< 0: on=tag,off=typedef 1: on=nested - DWORD nameIdx; ///< Index of the types name in sstNames - DWORD brwsInfoOffs; ///< Browser info offset -}; - -/// @brief The payload of the Code View Start Search Symbol Record -struct CVsrStartSearchPayload { - DWORD offs; ///< Offset of first code within segment - WORD segment; ///< Segment (PE section) -}; - -/// @brief The payload of the TDS Start Search Symbol Record -struct TDSsrStartSearchPayload : public CVsrStartSearchPayload { - WORD codeSyms; ///< Number of code symbols. - WORD dataSyms; ///< Number of data symbols. - DWORD firstData; ///< Offset of first data within segment - WORD dummy; ///< Currently not understood what this is used for. -}; - -/// @brief The payload of the Code View Procedure Reference Record -struct CVsrProcRefPayload { - DWORD checksum; ///< Checksum. - DWORD offSymRef; /**< Offset of symbol record refered to relative to the - symbol table for the module (sstAlignSym). */ - WORD module; /**< Index of module that contains the symbol record for - the procedure. */ -}; - -/// @brief The payload of the TDS Global Procedure Reference Record -struct TDSsrGlobalProcRefPayload { - DWORD offSymRef; /**< Offset of symbol record referred to relative to - the FB0A signature. Most often 0. */ - DWORD typeIdx; ///< Type index. 0 if none. - DWORD nameIdx; ///< Index of name in sstNames - DWORD brwsInfoOffset; ///< Browser info offset? - DWORD offset; /**< Address offset of proc or name index of - DLL if segment==0xFFFF. */ - WORD segment; ///< Segment. - DWORD linkNameIdx; ///< Index of linker name, if segment=0xFFFF? -}; - -/// @brief The payload of the Code View Data Reference Record -typedef CVsrProcRefPayload CVsrDataRefPayload; - -struct TDSsrGlobalDataRefPayload { - DWORD offSymRef; /**< Always 0. */ - DWORD typeIdx; ///< Type index. 0 if none. - DWORD nameIdx; ///< Index of name in sstNames - DWORD brwsInfoOffset; ///< Browser info offset? - DWORD offset; /**< Address offset of proc or name index of - DLL if segment==0xFFFF. */ - WORD segment; ///< Segment. -}; - -/// @brief The header of the payload of the Code View Global Data Symbol Record -struct CVsrGlobalDataPayloadHdr { - DWORD offset; ///< Address offset - WORD segment; ///< Segment - WORD typeIdx; ///< Index of type -}; - -/// @brief The payload of the TDS Global Data symbol record -struct TDSsrGlobalDataPayload { - DWORD offset; ///< Address offset - WORD segment; ///< Segment - WORD flags; ///< 1=TLS var, 0=Not a TLS var.? - DWORD typeIdx; ///< Index of type - DWORD nameIdx; ///< Index of name in sstNames. - DWORD brwsInfoOffs; ///< Browser info offset? -}; - -/// @brief The header of the payload of the Code View BP Relative symbol record -struct CVsrBPRelativePayloadHdr { - DWORD offset; ///< Signed offset relative to BP. - WORD typeIdx; ///< Index of the type. -}; - -/// @brief The payload of the TDS BP Relative symbol record -struct TDSsrBPRelativePayload { - DWORD offset; ///< Signed offset relative to BP. - DWORD typeIdx; ///< Index of the type. - DWORD nameIdx; ///< Index of the symbol name. - DWORD brwsOffs; ///< Browser info offset. -}; - -/// @brief The header of the payload of the Code View Local Proc symbol record -struct CVsrLocalProcPayloadHdr { - DWORD parent; ///< Parent of procedure relative to subsection. - DWORD end; ///< End of procedure relative to subsection. - DWORD next; ///< Next procedure relative to subsection. - DWORD procLen; ///< Length of procedure in bytes. - DWORD dbgStart; ///< Offset of code where stack frame is ok. - DWORD dbgEnd; ///< Offset of code where return value is ok. - DWORD offset; ///< Offset of 1st procedure code. - WORD segment; ///< Segment of procedure code. - WORD typeIdx; ///< Index of type (procedure signature) - BYTE flags; ///< Procedure flags -}; - -/// @brief The payload of the TDS Local Proc symbol record. -/// -/// The structure is appended with a length-prefixed linker name of the -/// procedure. -struct TDSsrLocalProcPayloadHdr { - DWORD parent; ///< Parent of procedure relative to subsection. - DWORD end; ///< End of procedure relative to subsection. - DWORD next; ///< Next procedure relative to subsection. - DWORD procLen; ///< Length of procedure in bytes. - DWORD dbgStart; ///< Offset of code where stack frame is ok. - DWORD dbgEnd; ///< Offset of code where return value is ok. - DWORD offset; ///< Offset of 1st procedure code. - WORD segment; ///< Segment of procedure code. ??? - WORD flags; ///< Not sure whether this is correct! - DWORD typeIdx; ///< Index of type (procedure signature) ??? - DWORD nameIdx; ///< Index of procedure name. - DWORD brwsOffs; ///< Browser info offset (not sure). -}; - -/// @brief The payload header of the DBG Block Start symbol record. -/// -/// The structure is appended with a length-prefixed expression string. -struct CVsrBlock32PayloadHdr { - DWORD parent; ///< Parent of block relative to subsection. - DWORD end; ///< End of block relative to subsection. - DWORD next; ///< Next block relative to subsection. - DWORD len; ///< Length of block in bytes. - DWORD offset; ///< Offset of block. - WORD segment; ///< Segment of block. -}; - -/// @brief The payload of the TDS Block Start symbol record. -struct TDSsrBlock32Payload : public CVsrBlock32PayloadHdr { - DWORD nameIdx; ///< Name index of expression string. -}; - -#ifdef __BORLANDC__ -#pragma option pop -#endif - -#endif - -// Local Variables: -// mode:c++ -// c-basic-offset: 3 -// indent-tabs-mode: nil -// End: diff --git a/common/windows/delphi/ext/tds2dbg/testa.cpp b/common/windows/delphi/ext/tds2dbg/testa.cpp deleted file mode 100644 index d3f55dadcc..0000000000 --- a/common/windows/delphi/ext/tds2dbg/testa.cpp +++ /dev/null @@ -1,27 +0,0 @@ -#include -#include - -static int a = 1; - -int main(int argc, char* argv[]) -{ - (void) argc; (void) argv; - - int b = a+1; - - printf("%i %i\n",a,b); - - while(a<0xf0000000) { - ++a; - printf("%i\n",a++); - Sleep(1000); - } - - return 0; -} - -// Local Variables: -// mode:c++ -// c-basic-offset: 3 -// indent-tabs-mode: nil -// End: diff --git a/developer/src/kmconvert/build.sh b/developer/src/kmconvert/build.sh index e18977f05b..75c9210546 100755 --- a/developer/src/kmconvert/build.sh +++ b/developer/src/kmconvert/build.sh @@ -29,11 +29,10 @@ function do_build() { build_manifest.res run_in_vs_env rc icons.rc delphi_msbuild kmconvert.dproj "//p:Platform=Win32" - sentrytool_delphiprep "$WIN32_TARGET" kmconvert.dpr - tds2dbg "$WIN32_TARGET" + do_map2pdb "$WIN32_TARGET_PATH/kmconvert.map" "$WIN32_TARGET" cp "$WIN32_TARGET" "$DEVELOPER_PROGRAM" - builder_if_release_build_level cp "$WIN32_TARGET_PATH/kmconvert.dbg" "$DEVELOPER_DEBUGPATH" + cp_if_exists "$WIN32_TARGET_PATH/kmconvert.pdb" "$DEVELOPER_DEBUGPATH" rm -rf "$DEVELOPER_PROGRAM/projects/templates" mkdir -p "$DEVELOPER_PROGRAM/projects/templates" @@ -53,7 +52,12 @@ function do_publish() { wrap-signcode //d "Keyman Developer" "$DEVELOPER_PROGRAM/kmconvert.exe" wrap-symstore "$DEVELOPER_PROGRAM/kmconvert.exe" //t keyman-developer - wrap-symstore "$DEVELOPER_DEBUGPATH/kmconvert.dbg" //t keyman-developer + wrap-symstore "$DEVELOPER_DEBUGPATH/kmconvert.pdb" //t keyman-developer +} + +function do_install() { + cp "$DEVELOPER_PROGRAM/kmconvert.exe" "$INSTALLPATH_KEYMANDEVELOPER/kmconvert.exe" + cp_if_exists "$DEVELOPER_DEBUGPATH/kmconvert.pdb" "$INSTALLPATH_KEYMANDEVELOPER/kmconvert.pdb" } builder_run_action clean:project clean_windows_project_files @@ -61,5 +65,5 @@ builder_run_action configure:project configure_windows_build_environment builder_run_action build:project do_build builder_run_action test:project do_test builder_run_action publish:project do_publish -builder_run_action install:project cp "$DEVELOPER_PROGRAM/kmconvert.exe" "$INSTALLPATH_KEYMANDEVELOPER/kmconvert.exe" +builder_run_action install:project do_install builder_run_action edit:project start kmconvert.dproj diff --git a/developer/src/kmdbrowserhost/build.sh b/developer/src/kmdbrowserhost/build.sh index 8996b17bc4..a1a6ecc58d 100755 --- a/developer/src/kmdbrowserhost/build.sh +++ b/developer/src/kmdbrowserhost/build.sh @@ -28,11 +28,10 @@ function do_build() { build_version.res build_manifest.res delphi_msbuild kmdbrowserhost.dproj "//p:Platform=Win32" - sentrytool_delphiprep "$WIN32_TARGET" kmdbrowserhost.dpr - tds2dbg "$WIN32_TARGET" + do_map2pdb "$WIN32_TARGET_PATH/kmdbrowserhost.map" "$WIN32_TARGET" cp "$WIN32_TARGET" "$DEVELOPER_PROGRAM" - builder_if_release_build_level cp "$WIN32_TARGET_PATH/kmdbrowserhost.dbg" "$DEVELOPER_DEBUGPATH" + cp_if_exists "$WIN32_TARGET_PATH/kmdbrowserhost.pdb" "$DEVELOPER_DEBUGPATH" } function do_publish() { @@ -41,7 +40,12 @@ function do_publish() { wrap-signcode //d "Keyman Developer" "$DEVELOPER_PROGRAM/kmdbrowserhost.exe" wrap-symstore "$DEVELOPER_PROGRAM/kmdbrowserhost.exe" //t keyman-developer - wrap-symstore "$DEVELOPER_DEBUGPATH/kmdbrowserhost.dbg" //t keyman-developer + wrap-symstore "$DEVELOPER_DEBUGPATH/kmdbrowserhost.pdb" //t keyman-developer +} + +function do_install() { + cp "$DEVELOPER_PROGRAM/kmdbrowserhost.exe" "$INSTALLPATH_KEYMANDEVELOPER/kmdbrowserhost.exe" + cp_if_exists "$DEVELOPER_DEBUGPATH/kmdbrowserhost.pdb" "$INSTALLPATH_KEYMANDEVELOPER/kmdbrowserhost.pdb" } builder_run_action clean:project clean_windows_project_files @@ -49,6 +53,6 @@ builder_run_action configure:project configure_windows_build_environment builder_run_action build:project do_build # builder_run_action test:project do_test builder_run_action publish:project do_publish -builder_run_action install:project cp "$DEVELOPER_PROGRAM/kmdbrowserhost.exe" "$INSTALLPATH_KEYMANDEVELOPER/kmdbrowserhost.exe" +builder_run_action install:project do_install builder_run_action edit:project start kmdbrowserhost.dproj diff --git a/developer/src/setup/build.sh b/developer/src/setup/build.sh index 1e4ab94a68..da16c4e574 100755 --- a/developer/src/setup/build.sh +++ b/developer/src/setup/build.sh @@ -30,11 +30,32 @@ function do_build() { build_version.res build_manifest.res delphi_msbuild setup.dproj "//p:Platform=Win32" - sentrytool_delphiprep "$WIN32_TARGET" setup.dpr - tds2dbg "$WIN32_TARGET" + do_map2pdb "$WIN32_TARGET_PATH/setup.map" "$WIN32_TARGET" + + # + # The installer needs to be a multiple of 512 bytes in order for the SFX code + # to find the ZIP header. After map2pdb appends its header, the file may not + # be the right size. So take the file size of setup.exe and generate a nul + # file that would expand it to nearest 512 bytes, and append it to the + # executable. This will make the file 512 bytes larger if its size is already + # on a 512 byte boundary. + # + + local file_size expansion_size expansion_file + + file_size=$(stat -c %s "$WIN32_TARGET") + expansion_size=$(( 512 - ( $file_size % 512 ) )) + expansion_file="$(mktemp)" + dd if=/dev/zero of="${expansion_file}" bs=1 count="${expansion_size}" + cat "$WIN32_TARGET" "$expansion_file" > "$WIN32_TARGET.out" + mv -f "$WIN32_TARGET.out" "$WIN32_TARGET" + rm -f "${expansion_file}" + + cp "$WIN32_TARGET" "$DEVELOPER_PROGRAM" - builder_if_release_build_level cp "$WIN32_TARGET_PATH/setup.dbg" "$DEVELOPER_DEBUGPATH/devsetup.dbg" - builder_if_release_build_level mv "$WIN32_TARGET_PATH/setup.dbg" "$WIN32_TARGET_PATH/devsetup.dbg" + cp_if_exists "$WIN32_TARGET_PATH/setup.pdb" "$DEVELOPER_DEBUGPATH/devsetup.pdb" + # builder_if_release_build_level cp "$WIN32_TARGET_PATH/setup.pdb" "$DEVELOPER_DEBUGPATH/devsetup.pdb" + # builder_if_release_build_level mv "$WIN32_TARGET_PATH/setup.pdb" "$WIN32_TARGET_PATH/devsetup.pdb" } function do_publish() { @@ -42,7 +63,7 @@ function do_publish() { # Not signing here because we need to sign the bundled installer instead # wrap-signcode /d "Keyman Developer Setup" $(DEVELOPER_PROGRAM)\setup.exe wrap-symstore "$DEVELOPER_PROGRAM/setup.exe" //t keyman-developer - wrap-symstore "$DEVELOPER_DEBUGPATH/setup.dbg" //t keyman-developer + wrap-symstore "$DEVELOPER_DEBUGPATH/setup.pdb" //t keyman-developer } builder_run_action clean:project clean_windows_project_files diff --git a/developer/src/tike/build.sh b/developer/src/tike/build.sh index 15e2add3c4..ceec4b4428 100755 --- a/developer/src/tike/build.sh +++ b/developer/src/tike/build.sh @@ -68,16 +68,15 @@ function do_build() { cp -R "$DEVELOPER_ROOT/src/tike/xml/"* "$DEVELOPER_PROGRAM/xml/" delphi_msbuild tike.dproj "//p:Platform=Win32" - sentrytool_delphiprep "$WIN32_TARGET" tike.dpr - tds2dbg "$WIN32_TARGET" + do_map2pdb "$WIN32_TARGET_PATH/tike.map" "$WIN32_TARGET" cp "$WIN32_TARGET" "$DEVELOPER_PROGRAM" + cp_if_exists "$WIN32_TARGET_PATH/tike.pdb" "$DEVELOPER_DEBUGPATH" cp kmc.cmd "$DEVELOPER_PROGRAM" cp "$KEYMAN_ROOT/core/build/x86/$TARGET_PATH/src/$KEYMANCORE_DLL" "$DEVELOPER_PROGRAM" - builder_if_release_build_level cp "$WIN32_TARGET_PATH/tike.dbg" "$DEVELOPER_DEBUGPATH" cp "$KEYMAN_ROOT/core/build/x86/$TARGET_PATH/src/$KEYMANCORE_DLL" "$WIN32_TARGET_PATH" - builder_if_release_build_level cp "$KEYMAN_ROOT/core/build/x86/$TARGET_PATH/src/$KEYMANCORE_PDB" "$WIN32_TARGET_PATH" + builder_if_release_build_level cp "$KEYMAN_ROOT/core/build/x86/$TARGET_PATH/src/$KEYMANCORE_PDB" "$DEVELOPER_DEBUGPATH" cp "$KEYMAN_ROOT/common/windows/delphi/ext/sentry/sentry.dll" "$DEVELOPER_PROGRAM/" cp "$KEYMAN_ROOT/common/windows/delphi/ext/sentry/sentry.x64.dll" "$DEVELOPER_PROGRAM/" @@ -96,11 +95,12 @@ function do_publish() { wrap-signcode //d "Keyman Developer" "$DEVELOPER_PROGRAM/sentry.x64.dll" wrap-signcode //d "Keyman Developer" "$DEVELOPER_PROGRAM/crashpad_handler.exe" wrap-symstore "$DEVELOPER_PROGRAM/tike.exe" //t keyman-developer - wrap-symstore "$DEVELOPER_DEBUGPATH/tike.dbg" //t keyman-developer + wrap-symstore "$DEVELOPER_DEBUGPATH/tike.pdb" //t keyman-developer } function do_install() { cp "$DEVELOPER_PROGRAM/tike.exe" "$INSTALLPATH_KEYMANDEVELOPER/tike.exe" + cp_if_exists "$DEVELOPER_DEBUGPATH/tike.pdb" "$INSTALLPATH_KEYMANDEVELOPER/tike.pdb" cp "$DEVELOPER_PROGRAM/$KEYMANCORE_DLL" "$INSTALLPATH_KEYMANDEVELOPER/$KEYMANCORE_DLL" } diff --git a/resources/build/win/delphi_flags.inc.sh b/resources/build/win/delphi_flags.inc.sh index 087e88c60f..441aa0f964 100644 --- a/resources/build/win/delphi_flags.inc.sh +++ b/resources/build/win/delphi_flags.inc.sh @@ -30,5 +30,4 @@ DEVELOPER_DEBUGPATH="$DEVELOPER_ROOT/debug" # Delphi build tool macros SENTRYTOOL="$COMMON_ROOT/tools/sentrytool/$WIN32_TARGET_PATH/sentrytool.exe" -TDS2DBG="$KEYMAN_ROOT/common/windows/bin/tools/tds2dbg" DEVTOOLS="$COMMON_ROOT/tools/devtools/$WIN32_TARGET_PATH/devtools.exe" diff --git a/resources/build/win/environment.inc.sh b/resources/build/win/environment.inc.sh index 40c4de6db5..bb7575e188 100644 --- a/resources/build/win/environment.inc.sh +++ b/resources/build/win/environment.inc.sh @@ -72,23 +72,25 @@ run_in_delphi_env() { ) } -sentrytool_delphiprep() { - if builder_is_ci_build && builder_is_ci_build_level_build; then - builder_echo "Skipping sentrytool_delphiprep - buildLevel=build: $@" - return 0 +# +# If map2pdb is on the path, then run it. Note: we do not include it in the repo +# because it can have false positives in malware scans. map2pdb is used to +# generate .pdb files for Delphi-generated executables, which can then be used +# in debuggers such as WinDbg, or profilers such as VTune. +# +do_map2pdb() { + if hash map2pdb 2>/dev/null; then + map2pdb "$1" -bind:"$2" fi - - local EXE_PATH="$1" - local DPR_PATH="$2" - ( - builder_echo heading "### delphi: sentrytool_delphiprep $1 $2" - source "$KEYMAN_ROOT/resources/build/win/delphi_environment.inc.sh" - "$SENTRYTOOL" delphiprep -r "$KEYMAN_ROOT" -i "$DELPHIINCLUDES" "$(cygpath -w "$EXE_PATH")" -dpr "$DPR_PATH" - ) } -tds2dbg() { - builder_if_release_build_level "$TDS2DBG" "$@" +# +# If the first parameter is a file that exists, do the copy, otherwise, no-op. +# +cp_if_exists() { + if [[ -f "$1" ]]; then + cp "$@" + fi } delphi_msbuild() { diff --git a/windows/src/desktop/inst/build.sh b/windows/src/desktop/inst/build.sh index 8e64342a22..14da0b7427 100755 --- a/windows/src/desktop/inst/build.sh +++ b/windows/src/desktop/inst/build.sh @@ -90,10 +90,6 @@ function do_publish() { rm -f setup.inf cat "$WINDOWS_PROGRAM_APP/setup-redist.exe" keymandesktop.zip > keymandesktop.exe rm -f keymandesktop.zip - - # - # Sign the installer - # wrap-signcode //d "Keyman for Windows" keymandesktop.exe builder_if_release_build_level copy-installer diff --git a/windows/src/desktop/insthelp/build.sh b/windows/src/desktop/insthelp/build.sh index d57a4b935f..0a84eeefe5 100755 --- a/windows/src/desktop/insthelp/build.sh +++ b/windows/src/desktop/insthelp/build.sh @@ -29,11 +29,10 @@ function do_build() { # TODO: why no manifest? # build_manifest.res delphi_msbuild insthelp.dproj "//p:Platform=Win32" - sentrytool_delphiprep "$WIN32_TARGET" insthelp.dpr - tds2dbg "$WIN32_TARGET" + do_map2pdb "$WIN32_TARGET_PATH/insthelp.map" "$WIN32_TARGET" cp "$WIN32_TARGET" "$WINDOWS_PROGRAM_APP" - builder_if_release_build_level cp "$WIN32_TARGET_PATH/insthelp.dbg" "$WINDOWS_DEBUGPATH_APP/insthelp.dbg" + cp_if_exists "$WIN32_TARGET_PATH/insthelp.pdb" "$WINDOWS_DEBUGPATH_APP" } function do_publish() { @@ -43,7 +42,7 @@ function do_publish() { wrap-signcode //d "Keyman for Windows Install Helper" "$WINDOWS_PROGRAM_APP/insthelp.exe" wrap-symstore "$WINDOWS_PROGRAM_APP/insthelp.exe" //t keyman-windows - wrap-symstore "$WINDOWS_DEBUGPATH_APP/insthelp.dbg" //t keyman-windows + wrap-symstore "$WINDOWS_DEBUGPATH_APP/insthelp.pdb" //t keyman-windows } builder_run_action clean:project clean_windows_project_files diff --git a/windows/src/desktop/kmbrowserhost/build.sh b/windows/src/desktop/kmbrowserhost/build.sh index ab309a09b0..4c405f1c31 100755 --- a/windows/src/desktop/kmbrowserhost/build.sh +++ b/windows/src/desktop/kmbrowserhost/build.sh @@ -28,11 +28,10 @@ function do_build() { build_version.res build_manifest.res delphi_msbuild kmbrowserhost.dproj "//p:Platform=Win32" - sentrytool_delphiprep "$WIN32_TARGET" kmbrowserhost.dpr - tds2dbg "$WIN32_TARGET" + do_map2pdb "$WIN32_TARGET_PATH/kmbrowserhost.map" "$WIN32_TARGET" cp "$WIN32_TARGET" "$WINDOWS_PROGRAM_APP" - builder_if_release_build_level cp "$WIN32_TARGET_PATH/kmbrowserhost.dbg" "$WINDOWS_DEBUGPATH_APP/kmbrowserhost.dbg" + cp_if_exists "$WIN32_TARGET_PATH/kmbrowserhost.pdb" "$WINDOWS_DEBUGPATH_APP" } function do_publish() { @@ -41,7 +40,12 @@ function do_publish() { wrap-signcode //d "Keyman for Windows" "$WINDOWS_PROGRAM_APP/kmbrowserhost.exe" wrap-symstore "$WINDOWS_PROGRAM_APP/kmbrowserhost.exe" //t keyman-windows - wrap-symstore "$WINDOWS_DEBUGPATH_APP/kmbrowserhost.dbg" //t keyman-windows + wrap-symstore "$WINDOWS_DEBUGPATH_APP/kmbrowserhost.pdb" //t keyman-windows +} + +function do_install() { + cp "$WINDOWS_PROGRAM_APP/kmbrowserhost.exe" "$INSTALLPATH_KEYMANAPP/kmbrowserhost.exe" + cp_if_exists "$WINDOWS_DEBUGPATH_APP/kmbrowserhost.pdb" "$INSTALLPATH_KEYMANAPP/kmbrowserhost.pdb" } builder_run_action clean:project clean_windows_project_files @@ -49,5 +53,5 @@ builder_run_action configure:project configure_windows_build_environment builder_run_action build:project do_build # builder_run_action test:project do_test builder_run_action publish:project do_publish -builder_run_action install:project cp "$WINDOWS_PROGRAM_APP/kmbrowserhost.exe" "$INSTALLPATH_KEYMANAPP/kmbrowserhost.exe" +builder_run_action install:project do_install builder_run_action edit:project start kmbrowserhost.dproj diff --git a/windows/src/desktop/kmconfig/build.sh b/windows/src/desktop/kmconfig/build.sh index bfe07e5868..2ba87c1082 100755 --- a/windows/src/desktop/kmconfig/build.sh +++ b/windows/src/desktop/kmconfig/build.sh @@ -28,11 +28,10 @@ function do_build() { build_version.res build_manifest.res delphi_msbuild kmconfig.dproj "//p:Platform=Win32" - sentrytool_delphiprep "$WIN32_TARGET" kmconfig.dpr - tds2dbg "$WIN32_TARGET" + do_map2pdb "$WIN32_TARGET_PATH/kmconfig.map" "$WIN32_TARGET" cp "$WIN32_TARGET" "$WINDOWS_PROGRAM_APP" - builder_if_release_build_level cp "$WIN32_TARGET_PATH/kmconfig.dbg" "$WINDOWS_DEBUGPATH_APP/kmconfig.dbg" + cp_if_exists "$WIN32_TARGET_PATH/kmconfig.pdb" "$WINDOWS_DEBUGPATH_APP" } function do_publish() { @@ -41,7 +40,12 @@ function do_publish() { wrap-signcode //d "Keyman for Windows" "$WINDOWS_PROGRAM_APP/kmconfig.exe" wrap-symstore "$WINDOWS_PROGRAM_APP/kmconfig.exe" //t keyman-windows - wrap-symstore "$WINDOWS_DEBUGPATH_APP/kmconfig.dbg" //t keyman-windows + wrap-symstore "$WINDOWS_DEBUGPATH_APP/kmconfig.pdb" //t keyman-windows +} + +function do_install() { + cp "$WINDOWS_PROGRAM_APP/kmconfig.exe" "$INSTALLPATH_KEYMANAPP/kmconfig.exe" + cp_if_exists "$WINDOWS_DEBUGPATH_APP/kmconfig.pdb" "$INSTALLPATH_KEYMANAPP/kmconfig.pdb" } builder_run_action clean:project clean_windows_project_files @@ -49,5 +53,5 @@ builder_run_action configure:project configure_windows_build_environment builder_run_action build:project do_build # builder_run_action test:project do_test builder_run_action publish:project do_publish -builder_run_action install:project cp "$WINDOWS_PROGRAM_APP/kmconfig.exe" "$INSTALLPATH_KEYMANAPP/kmconfig.exe" +builder_run_action install:project do_install builder_run_action edit:project start kmconfig.dproj diff --git a/windows/src/desktop/kmshell/build.sh b/windows/src/desktop/kmshell/build.sh index 03d4b786fa..b2147962eb 100755 --- a/windows/src/desktop/kmshell/build.sh +++ b/windows/src/desktop/kmshell/build.sh @@ -35,11 +35,10 @@ function do_build() { build_manifest.res run_in_vs_env rc icons.rc delphi_msbuild kmshell.dproj "//p:Platform=Win32" - sentrytool_delphiprep "$WIN32_TARGET" kmshell.dpr - tds2dbg "$WIN32_TARGET" + do_map2pdb "$WIN32_TARGET_PATH/kmshell.map" "$WIN32_TARGET" cp "$WIN32_TARGET" "$WINDOWS_PROGRAM_APP" - builder_if_release_build_level cp "$WIN32_TARGET_PATH/kmshell.dbg" "$WINDOWS_DEBUGPATH_APP/kmshell.dbg" + cp_if_exists "$WIN32_TARGET_PATH/kmshell.pdb" "$WINDOWS_DEBUGPATH_APP" do_build_data } @@ -73,11 +72,13 @@ function do_publish() { wrap-signcode //d "Keyman for Windows" "$WINDOWS_PROGRAM_APP/kmshell.exe" wrap-symstore "$WINDOWS_PROGRAM_APP/kmshell.exe" //t keyman-windows - wrap-symstore "$WINDOWS_DEBUGPATH_APP/kmshell.dbg" //t keyman-windows + wrap-symstore "$WINDOWS_DEBUGPATH_APP/kmshell.pdb" //t keyman-windows } function do_install() { cp "$WINDOWS_PROGRAM_APP/kmshell.exe" "$INSTALLPATH_KEYMANAPP/kmshell.exe" + cp_if_exists "$WINDOWS_DEBUGPATH_APP/kmshell.pdb" "$INSTALLPATH_KEYMANAPP/kmshell.pdb" + rm -rf "$INSTALLPATH_KEYMANAPP/xml" mkdir -p "$INSTALLPATH_KEYMANAPP/xml" cp -r "$WINDOWS_PROGRAM_APP/xml"/* "$INSTALLPATH_KEYMANAPP/xml/" diff --git a/windows/src/desktop/setup/build.sh b/windows/src/desktop/setup/build.sh index 5b97dda8ba..57e03de7ac 100755 --- a/windows/src/desktop/setup/build.sh +++ b/windows/src/desktop/setup/build.sh @@ -37,13 +37,32 @@ function do_build() { "$DEVTOOLS" -buildsetupstrings locale\\ .\\ delphi_msbuild setup.dproj "//p:Platform=Win32" - sentrytool_delphiprep "$WIN32_TARGET" setup.dpr - tds2dbg "$WIN32_TARGET" + do_map2pdb "$WIN32_TARGET_PATH/setup.map" "$WIN32_TARGET" + + # + # The installer needs to be a multiple of 512 bytes in order for the SFX code + # to find the ZIP header. After map2pdb appends its header, the file may not + # be the right size. So take the file size of setup.exe and generate a nul + # file that would expand it to nearest 512 bytes, and append it to the + # executable. This will make the file 512 bytes larger if its size is already + # on a 512 byte boundary. + # + + local file_size expansion_size expansion_file + + file_size=$(stat -c %s "$WIN32_TARGET") + expansion_size=$(( 512 - ( $file_size % 512 ) )) + expansion_file="$(mktemp)" + dd if=/dev/zero of="${expansion_file}" bs=1 count="${expansion_size}" + cat "$WIN32_TARGET" "$expansion_file" > "$WIN32_TARGET.out" + mv -f "$WIN32_TARGET.out" "$WIN32_TARGET" + rm -f "${expansion_file}" cp "$WIN32_TARGET" "$WINDOWS_PROGRAM_APP" + cp_if_exists "$WIN32_TARGET_PATH/setup.pdb" "$WINDOWS_DEBUGPATH_APP" + # setup-redist.exe does not get signed as it is intended for a bundled installer cp "$WIN32_TARGET" "$WINDOWS_PROGRAM_APP/setup-redist.exe" - builder_if_release_build_level cp "$WIN32_TARGET_PATH/setup.dbg" "$WINDOWS_DEBUGPATH_APP/setup.dbg" } function do_build_debug_manifest() { @@ -61,7 +80,7 @@ function do_publish() { wrap-signcode //d "Keyman for Windows Setup" "$WINDOWS_PROGRAM_APP/setup.exe" wrap-symstore "$WINDOWS_PROGRAM_APP/setup.exe" //t keyman-windows - wrap-symstore "$WINDOWS_DEBUGPATH_APP/setup.dbg" //t keyman-windows + wrap-symstore "$WINDOWS_DEBUGPATH_APP/setup.pdb" //t keyman-windows } builder_run_action clean:project do_clean diff --git a/windows/src/engine/insthelper/build.sh b/windows/src/engine/insthelper/build.sh index 62aec62586..5f8a4b2eb8 100755 --- a/windows/src/engine/insthelper/build.sh +++ b/windows/src/engine/insthelper/build.sh @@ -27,18 +27,17 @@ function do_build() { create-windows-output-folders build_version.res delphi_msbuild insthelper.dproj "//p:Platform=Win32" - sentrytool_delphiprep "$WIN32_TARGET" insthelper.dpr - tds2dbg "$WIN32_TARGET" + do_map2pdb "$WIN32_TARGET_PATH/insthelper.map" "$WIN32_TARGET" cp "$WIN32_TARGET" "$WINDOWS_PROGRAM_ENGINE" - builder_if_release_build_level cp "$WIN32_TARGET_PATH/insthelper.dbg" "$WINDOWS_DEBUGPATH_ENGINE/insthelper.dbg" + cp_if_exists "$WIN32_TARGET_PATH/insthelper.pdb" "$WINDOWS_DEBUGPATH_ENGINE" } function do_publish() { wrap-signcode //d "Keyman Engine for Windows" "$WINDOWS_PROGRAM_ENGINE/insthelper.dll" wrap-symstore "$WINDOWS_PROGRAM_ENGINE/insthelper.dll" //t keyman-engine-windows - wrap-symstore "$WINDOWS_DEBUGPATH_ENGINE/insthelper.dbg" //t keyman-engine-windows + wrap-symstore "$WINDOWS_DEBUGPATH_ENGINE/insthelper.pdb" //t keyman-engine-windows } builder_run_action clean:project clean_windows_project_files diff --git a/windows/src/engine/keyman/build.sh b/windows/src/engine/keyman/build.sh index efdaf0698c..c13effa338 100755 --- a/windows/src/engine/keyman/build.sh +++ b/windows/src/engine/keyman/build.sh @@ -38,11 +38,10 @@ function do_build() { run_in_vs_env rc osktoolbar.rc run_in_vs_env rc langswitch/langswitchmanager.rc delphi_msbuild keyman.dproj "//p:Platform=Win32" - sentrytool_delphiprep "$WIN32_TARGET" keyman.dpr - tds2dbg "$WIN32_TARGET" + do_map2pdb "$WIN32_TARGET_PATH/keyman.map" "$WIN32_TARGET" cp "$WIN32_TARGET" "$WINDOWS_PROGRAM_ENGINE" - builder_if_release_build_level cp "$WIN32_TARGET_PATH/keyman.dbg" "$WINDOWS_DEBUGPATH_ENGINE/keyman.dbg" + cp_if_exists "$WIN32_TARGET_PATH/keyman.pdb" "$WINDOWS_DEBUGPATH_ENGINE" # Also copy sentry files here cp "$KEYMAN_ROOT/common/windows/delphi/ext/sentry/sentry.dll" \ @@ -65,7 +64,7 @@ function do_publish() { wrap-signcode //d "Keyman Engine for Windows" "$WINDOWS_PROGRAM_ENGINE/crashpad_handler.exe" wrap-symstore "$WINDOWS_PROGRAM_ENGINE/keyman.exe" //t keyman-engine-windows - wrap-symstore "$WINDOWS_DEBUGPATH_ENGINE/keyman.dbg" //t keyman-engine-windows + wrap-symstore "$WINDOWS_DEBUGPATH_ENGINE/keyman.pdb" //t keyman-engine-windows } function do_build_debug_manifest() { @@ -77,12 +76,17 @@ function do_build_debug_manifest() { mv -f std-manifest.tmp manifest.in } +function do_install() { + cp "$WINDOWS_PROGRAM_ENGINE/keyman.exe" "$INSTALLPATH_KEYMANENGINE/keyman.exe" + cp_if_exists "$WINDOWS_DEBUGPATH_ENGINE/keyman.pdb" "$INSTALLPATH_KEYMANENGINE/keyman.pdb" +} + builder_run_action clean:project do_clean builder_run_action configure:project configure_windows_build_environment builder_run_action build:project do_build # builder_run_action test:project do_test builder_run_action publish:project do_publish -builder_run_action install:project cp "$WINDOWS_PROGRAM_ENGINE/keyman.exe" "$INSTALLPATH_KEYMANENGINE/keyman.exe" +builder_run_action install:project do_install builder_run_action debug-manifest:project do_build_debug_manifest builder_run_action edit:project start keyman.dproj diff --git a/windows/src/engine/keymanmc/build.sh b/windows/src/engine/keymanmc/build.sh index 8ad933abb7..389cc9ecb1 100755 --- a/windows/src/engine/keymanmc/build.sh +++ b/windows/src/engine/keymanmc/build.sh @@ -30,10 +30,10 @@ function do_build() { function do_install() { cp "$WINDOWS_PROGRAM_ENGINE/keymanmc.dll" "$INSTALLPATH_KEYMANENGINE/keymanmc.dll" - reg add 'HKLM\\SYSTEM\\CurrentControlSet\\services\\eventlog\\Application\\Keyman' //v CategoryCount //t REG_DWORD //d 1 //f - reg add 'HKLM\\SYSTEM\\CurrentControlSet\\services\\eventlog\\Application\\Keyman' //v CategoryMessageFile //t REG_SZ //d "$INSTALLPATH_KEYMANENGINE\\keymanmc.dll" //f - reg add 'HKLM\\SYSTEM\\CurrentControlSet\\services\\eventlog\\Application\\Keyman' //v EventMessageFile //t REG_SZ //d "$INSTALLPATH_KEYMANENGINE\\keymanmc.dll" //f - reg add 'HKLM\\SYSTEM\\CurrentControlSet\\services\\eventlog\\Application\\Keyman' //v TypesSupported //t REG_DWORD //d 7 //f + reg add 'HKLM\SYSTEM\CurrentControlSet\services\eventlog\Application\Keyman' //v CategoryCount //t REG_DWORD //d 1 //f + reg add 'HKLM\SYSTEM\CurrentControlSet\services\eventlog\Application\Keyman' //v CategoryMessageFile //t REG_SZ //d "$INSTALLPATH_KEYMANENGINE\\keymanmc.dll" //f + reg add 'HKLM\SYSTEM\CurrentControlSet\services\eventlog\Application\Keyman' //v EventMessageFile //t REG_SZ //d "$INSTALLPATH_KEYMANENGINE\\keymanmc.dll" //f + reg add 'HKLM\SYSTEM\CurrentControlSet\services\eventlog\Application\Keyman' //v TypesSupported //t REG_DWORD //d 7 //f } builder_run_action clean:project rm -f keymanmc.dll keymanmc.h keymanmc.rc keymanmc.res MSG00409.bin diff --git a/windows/src/engine/kmcomapi/build.sh b/windows/src/engine/kmcomapi/build.sh index 78b57e792b..9776de77af 100755 --- a/windows/src/engine/kmcomapi/build.sh +++ b/windows/src/engine/kmcomapi/build.sh @@ -36,15 +36,16 @@ function do_build() { run_in_vs_env gentlb -Tkmcomapi.tlb kmcomapi.ridl delphi_msbuild kmcomapi.dproj "//p:Platform=Win32" - sentrytool_delphiprep "$WIN32_TARGET" kmcomapi.dpr - tds2dbg "$WIN32_TARGET" + do_map2pdb "$WIN32_TARGET_PATH/kmcomapi.map" "$WIN32_TARGET" + # TODO: map2pdb for .x64 delphi_msbuild kmcomapi.dproj "//p:Platform=Win64" # Delphi does not allow us to build to a different target filename so we rename after build mv -f "$WIN64_TARGET_PATH/kmcomapi.dll" "$WIN64_TARGET_PATH/kmcomapi.x64.dll" cp "$WIN32_TARGET" "$WINDOWS_PROGRAM_ENGINE" - builder_if_release_build_level cp "$WIN32_TARGET_PATH/kmcomapi.dbg" "$WINDOWS_DEBUGPATH_ENGINE/kmcomapi.dbg" + cp_if_exists "$WIN32_TARGET_PATH/kmcomapi.pdb" "$WINDOWS_DEBUGPATH_ENGINE" + cp "$WIN64_TARGET_PATH/kmcomapi.x64.dll" "$WINDOWS_PROGRAM_ENGINE/kmcomapi.x64.dll" # x64 Delphi symbols not supported: cp "$WIN64_TARGET_PATH/kmcomapi.dbg" "$WINDOWS_PROGRAM_ENGINE/kmcomapi.x64.dbg" @@ -56,7 +57,7 @@ function do_publish() { wrap-symstore "$WINDOWS_PROGRAM_ENGINE/kmcomapi.dll" //t keyman-engine-windows wrap-symstore "$WINDOWS_PROGRAM_ENGINE/kmcomapi.x64.dll" //t keyman-engine-windows - wrap-symstore "$WINDOWS_DEBUGPATH_ENGINE/kmcomapi.dbg" //t keyman-engine-windows + wrap-symstore "$WINDOWS_DEBUGPATH_ENGINE/kmcomapi.pdb" //t keyman-engine-windows # No support for 64-bit delphi symbols } @@ -65,6 +66,8 @@ function do_install() { cp "$WINDOWS_PROGRAM_ENGINE/kmcomapi.dll" "$INSTALLPATH_KEYMANENGINE/kmcomapi.dll" regsvr32 //s "$INSTALLPATH_KEYMANENGINE/kmcomapi.dll" + cp_if_exists "$WINDOWS_DEBUGPATH_ENGINE/kmcomapi.pdb" "$INSTALLPATH_KEYMANENGINE/kmcomapi.pdb" + regsvr32 //s //u "$INSTALLPATH_KEYMANENGINE/kmcomapi.x64.dll" cp "$WINDOWS_PROGRAM_ENGINE/kmcomapi.x64.dll" "$INSTALLPATH_KEYMANENGINE/kmcomapi.x64.dll" regsvr32 //s "$INSTALLPATH_KEYMANENGINE/kmcomapi.x64.dll" diff --git a/windows/src/engine/tsysinfo/build.sh b/windows/src/engine/tsysinfo/build.sh index e3390b899b..3e94f381ba 100755 --- a/windows/src/engine/tsysinfo/build.sh +++ b/windows/src/engine/tsysinfo/build.sh @@ -42,12 +42,11 @@ function do_build() { rm -f tsysinfox64.bin delphi_msbuild tsysinfo.dproj "//p:Platform=Win32" - sentrytool_delphiprep "$WIN32_TARGET" tsysinfo.dpr - tds2dbg "$WIN32_TARGET" + do_map2pdb "$WIN32_TARGET_PATH/tsysinfo.map" "$WIN32_TARGET" cp "$WIN32_TARGET" "$WINDOWS_PROGRAM_ENGINE" cp *.xslt "$WINDOWS_PROGRAM_ENGINE/" - builder_if_release_build_level cp "$WIN32_TARGET_PATH/tsysinfo.dbg" "$WINDOWS_DEBUGPATH_ENGINE/tsysinfo.dbg" + cp_if_exists "$WIN32_TARGET_PATH/tsysinfo.pdb" "$WINDOWS_DEBUGPATH_ENGINE" } function do_publish() { @@ -57,11 +56,12 @@ function do_publish() { wrap-signcode //d "Keyman Engine for Windows" "$WINDOWS_PROGRAM_ENGINE/tsysinfo.exe" wrap-symstore "$WINDOWS_PROGRAM_ENGINE/tsysinfo.exe" //t keyman-engine-windows - wrap-symstore "$WINDOWS_DEBUGPATH_ENGINE/tsysinfo.dbg" //t keyman-engine-windows + wrap-symstore "$WINDOWS_DEBUGPATH_ENGINE/tsysinfo.pdb" //t keyman-engine-windows } function do_install() { cp "$WINDOWS_PROGRAM_ENGINE/tsysinfo.exe" "$INSTALLPATH_KEYMANENGINE/tsysinfo.exe" + cp_if_exists "$WINDOWS_DEBUGPATH_ENGINE/tsysinfo.pdb" "$INSTALLPATH_KEYMANENGINE/tsysinfo.pdb" cp *.xslt "$INSTALLPATH_KEYMANENGINE/" } diff --git a/windows/src/engine/tsysinfox64/build.sh b/windows/src/engine/tsysinfox64/build.sh index 0a97e01c22..d7b3e9a355 100755 --- a/windows/src/engine/tsysinfox64/build.sh +++ b/windows/src/engine/tsysinfox64/build.sh @@ -39,15 +39,10 @@ function do_publish() { wrap-signcode //d "Keyman Engine for Windows" "$WINDOWS_PROGRAM_ENGINE/tsysinfox64.exe" } -function do_install() { - cp "$WINDOWS_PROGRAM_ENGINE/tsysinfox64.exe" "$INSTALLPATH_KEYMANENGINE/tsysinfox64.exe" - cp *.xslt "$INSTALLPATH_KEYMANENGINE/" -} - builder_run_action clean:project clean_windows_project_files builder_run_action configure:project configure_windows_build_environment builder_run_action build:project do_build -# builder_run_action test:project do_test +# builder_run_action test:project (no current tests) builder_run_action publish:project do_publish -builder_run_action install:project do_install +# builder_run_action install:project (installed as part of tsysinfo) builder_run_action edit:project start tsysinfox64.dproj From 72da1be22ab66625947aca50c68e94275bcb8618 Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Thu, 13 Nov 2025 16:43:54 +0100 Subject: [PATCH 22/33] chore(linux): use `devel` for next Ubuntu release This change moves to use [`devel`[1]](https://hub.docker.com/_/ubuntu#whats-in-this-image) instead of a specific release name for the next Ubuntu release. That way it will always be able to find an image and will automatically start using the next one as soon as it becomes available. Build-bot: skip Test-bot: skip --- .github/workflows/deb-packaging.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deb-packaging.yml b/.github/workflows/deb-packaging.yml index 46560223fd..1ecb306cc2 100644 --- a/.github/workflows/deb-packaging.yml +++ b/.github/workflows/deb-packaging.yml @@ -161,7 +161,7 @@ jobs: strategy: fail-fast: true matrix: - dist: [resolute] + dist: [devel] steps: - name: Checkout From cb1a308418d2911c391c077242c7afdf5b85c507 Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Thu, 13 Nov 2025 18:38:26 +0100 Subject: [PATCH 23/33] fix(web): improve determination of default path Browser extensions can dynamically insert scripts into a web page, so it's possible that the last script is not what we expected, which can cause the path to the script to be wrong. This change fixes the determination of the default path by using the `document.currentScript` property. It also replaces the use of the deprecated `substr` function in those places. Fixes: #15158 Test-bot: skip --- web/src/app/browser/src/debug-main.ts | 16 ++++++---------- web/src/app/browser/src/release-main.ts | 16 ++++++---------- web/src/app/webview/src/debug-main.ts | 16 ++++++---------- web/src/app/webview/src/release-main.ts | 16 ++++++---------- 4 files changed, 24 insertions(+), 40 deletions(-) diff --git a/web/src/app/browser/src/debug-main.ts b/web/src/app/browser/src/debug-main.ts index 973e0d7efc..aa37c3bdd3 100644 --- a/web/src/app/browser/src/debug-main.ts +++ b/web/src/app/browser/src/debug-main.ts @@ -1,16 +1,12 @@ import { KeymanEngine } from './keymanEngine.js' import { SourcemappedWorker } from '@keymanapp/lexical-model-layer/web' - /** - * Determine path and protocol of executing script, setting them as - * construction defaults. - * - * This can only be done during load when the active script will be the - * last script loaded. Otherwise the script must be identified by name. - */ - const scripts = document.getElementsByTagName('script'); - const ss = scripts[scripts.length-1].src; - const sPath = ss.substr(0,ss.lastIndexOf('/')+1); +/** +* Determine path and protocol of executing script, setting them as +* construction defaults. +*/ +const ss = (document.currentScript as HTMLScriptElement).src; +const sPath = ss.substring(0, ss.lastIndexOf('/') + 1); // @ts-ignore window['keyman'] = new KeymanEngine(SourcemappedWorker, sPath); \ No newline at end of file diff --git a/web/src/app/browser/src/release-main.ts b/web/src/app/browser/src/release-main.ts index faf96e9097..45395748a6 100644 --- a/web/src/app/browser/src/release-main.ts +++ b/web/src/app/browser/src/release-main.ts @@ -1,16 +1,12 @@ import { KeymanEngine } from './keymanEngine.js' import { Worker } from '@keymanapp/lexical-model-layer/web' - /** - * Determine path and protocol of executing script, setting them as - * construction defaults. - * - * This can only be done during load when the active script will be the - * last script loaded. Otherwise the script must be identified by name. - */ - const scripts = document.getElementsByTagName('script'); - const ss = scripts[scripts.length-1].src; - const sPath = ss.substr(0,ss.lastIndexOf('/')+1); +/** +* Determine path and protocol of executing script, setting them as +* construction defaults. +*/ +const ss = (document.currentScript as HTMLScriptElement).src; +const sPath = ss.substring(0, ss.lastIndexOf('/') + 1); // @ts-ignore window['keyman'] = new KeymanEngine(Worker, sPath); \ No newline at end of file diff --git a/web/src/app/webview/src/debug-main.ts b/web/src/app/webview/src/debug-main.ts index 646c8f1687..aa37c3bdd3 100644 --- a/web/src/app/webview/src/debug-main.ts +++ b/web/src/app/webview/src/debug-main.ts @@ -1,16 +1,12 @@ import { KeymanEngine } from './keymanEngine.js' import { SourcemappedWorker } from '@keymanapp/lexical-model-layer/web' - /** - * Determine path and protocol of executing script, setting them as - * construction defaults. - * - * This can only be done during load when the active script will be the - * last script loaded. Otherwise the script must be identified by name. - */ -const scripts = document.getElementsByTagName('script'); -const ss = scripts[scripts.length-1].src; -const sPath = ss.substring(0,ss.lastIndexOf('/')+1); +/** +* Determine path and protocol of executing script, setting them as +* construction defaults. +*/ +const ss = (document.currentScript as HTMLScriptElement).src; +const sPath = ss.substring(0, ss.lastIndexOf('/') + 1); // @ts-ignore window['keyman'] = new KeymanEngine(SourcemappedWorker, sPath); \ No newline at end of file diff --git a/web/src/app/webview/src/release-main.ts b/web/src/app/webview/src/release-main.ts index ba70bfef9b..45395748a6 100644 --- a/web/src/app/webview/src/release-main.ts +++ b/web/src/app/webview/src/release-main.ts @@ -1,16 +1,12 @@ import { KeymanEngine } from './keymanEngine.js' import { Worker } from '@keymanapp/lexical-model-layer/web' - /** - * Determine path and protocol of executing script, setting them as - * construction defaults. - * - * This can only be done during load when the active script will be the - * last script loaded. Otherwise the script must be identified by name. - */ -const scripts = document.getElementsByTagName('script'); -const ss = scripts[scripts.length-1].src; -const sPath = ss.substring(0,ss.lastIndexOf('/')+1); +/** +* Determine path and protocol of executing script, setting them as +* construction defaults. +*/ +const ss = (document.currentScript as HTMLScriptElement).src; +const sPath = ss.substring(0, ss.lastIndexOf('/') + 1); // @ts-ignore window['keyman'] = new KeymanEngine(Worker, sPath); \ No newline at end of file From cd4e7c02b96ff6d6ae98890cd84fa59c57cdd286 Mon Sep 17 00:00:00 2001 From: Keyman Build Agent Date: Thu, 13 Nov 2025 12:01:24 -0600 Subject: [PATCH 24/33] auto: increment master version to 19.0.162 Test-bot: skip Build-bot: skip --- HISTORY.md | 4 ++++ VERSION.md | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/HISTORY.md b/HISTORY.md index b4a84c127b..1fc5a99336 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,9 @@ # Keyman Version History +## 19.0.161 alpha 2025-11-13 + +* docs(android): Document disclaimer of Keyman NOT containing adware/spyware (#15136) + ## 19.0.160 alpha 2025-11-12 * fix(android): Change Alert Dialog instead of Toast if package missing keyboard and lexical-model files (#15098) diff --git a/VERSION.md b/VERSION.md index 79cc7b29e5..02f69884fc 100644 --- a/VERSION.md +++ b/VERSION.md @@ -1 +1 @@ -19.0.161 \ No newline at end of file +19.0.162 \ No newline at end of file From bf4d707662989d5013135b56283c26e99eca2c6c Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Thu, 13 Nov 2025 14:46:12 +0100 Subject: [PATCH 25/33] change(windows): use circular buffer for logging Test-bot: skip --- .../src/global/delphi/debug/Keyman.System.DebugLogManager.pas | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/windows/src/global/delphi/debug/Keyman.System.DebugLogManager.pas b/windows/src/global/delphi/debug/Keyman.System.DebugLogManager.pas index 9937d476c3..52ad48dd52 100644 --- a/windows/src/global/delphi/debug/Keyman.System.DebugLogManager.pas +++ b/windows/src/global/delphi/debug/Keyman.System.DebugLogManager.pas @@ -149,7 +149,7 @@ begin pSessionProperties.Wnode.Flags := WNODE_FLAG_TRACED_GUID; pSessionProperties.Wnode.ClientContext := 1; //QPC clock resolution pSessionProperties.Wnode.Guid := SessionGuid; - pSessionProperties.LogFileMode := EVENT_TRACE_FILE_MODE_SEQUENTIAL; + pSessionProperties.LogFileMode := EVENT_TRACE_FILE_MODE_CIRCULAR; pSessionProperties.MaximumFileSize := 256; // 256 MB pSessionProperties.LoggerNameOffset := sizeof(EVENT_TRACE_PROPERTIES); pSessionProperties.LogFileNameOffset := sizeof(EVENT_TRACE_PROPERTIES) + sizeof(LOGSESSION_NAME); From 0988a86fa4181d4905c6efb87ada3591999e0039 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Fri, 14 Nov 2025 10:18:38 +0100 Subject: [PATCH 26/33] maint(resources): improve builder_launch to allow for arbitrary parameters Also cleans up reporting of child scripts and links the list of reserved parameters across a few locations. Address review comments. --- docs/builder.md | 6 +- resources/build/test/test.sh | 4 +- resources/builder.inc.sh | 125 ++++++++++++++++++----------------- 3 files changed, 69 insertions(+), 66 deletions(-) diff --git a/docs/builder.md b/docs/builder.md index 0d6a662006..534d8731ea 100644 --- a/docs/builder.md +++ b/docs/builder.md @@ -1045,15 +1045,17 @@ options, and timing status through as parameters. This should be used rather than calling the build script directly, to avoid multiple builds of dependencies. +Do not use builder standard options such as `--deps` or `--debug`. + ### Parameters * 1: `script` path to script, relative to root of repo -* 2: `action` action(s)+target(s) for the child script to run, comma separated, at least one +* 2+: `parameters` action(s), target(s), parameters for the child script to run ### Example ```bash - builder_launch /core/build.sh configure,build:wasm + builder_launch core/build.sh configure,build:wasm --no-tests ``` -------------------------------------------------------------------------------- diff --git a/resources/build/test/test.sh b/resources/build/test/test.sh index 8cb7ecfc18..f13a68c727 100755 --- a/resources/build/test/test.sh +++ b/resources/build/test/test.sh @@ -303,12 +303,12 @@ function builder_echo_start_end_tests() { TEAMCITY_GIT_PATH="" _builder_debug_internal=false _builder_is_child=0 - expected="" + expected="$(builder_echo heading "description")" result=$(builder_echo start "foo" "description") if [[ "${result[*]}" != "${expected}" ]]; then builder_die "FAIL: Wrong output for 'builder_echo start foo description' (no debug, is child):\n Actual : ${result[*]}\n Expected: ${expected}" fi - expected="" + expected="$(builder_echo success "description")" result=$(builder_echo end "foo" success "description") if [[ "${result[*]}" != "${expected}" ]]; then builder_die "FAIL: Wrong output for 'builder_echo end foo success description' (no debug, is child):\n Actual : ${result[*]}\n Expected: ${expected}" diff --git a/resources/builder.inc.sh b/resources/builder.inc.sh index b74c83cb65..fe657fd2b0 100755 --- a/resources/builder.inc.sh +++ b/resources/builder.inc.sh @@ -30,6 +30,11 @@ set -eu # SHLVL=0 +# A regex of reserved builder parameters, must be kept in sync with +# the matching of builder parameters in _builder_parse_expanded_parameters +_builder_reserved_parameters='^(--help|-h|--color|--no-color|--verbose|-v|--timings|--no-timings|--debug|--release|--deps|--no-deps|--force-deps|--builder-dep-parent|--builder-child|--builder-report-dependencies|--builder-completion-describe|--offline|--builder-ignore-unknown-options)$' + + # _builder_init is called internally at the bottom of this file after we have # all function declarations in place. function _builder_init() { @@ -180,7 +185,7 @@ function builder_heading() { builder_echo() { - local color=white message= mark= block= action= do_output=true test= + local color=white message= mark= block= action= test= local echo_target=echo if [[ $# -gt 1 ]]; then @@ -194,9 +199,6 @@ builder_echo() { shift 2 action="start" color="heading" - if ! builder_is_running_on_teamcity && builder_is_child_build; then - do_output=${_builder_debug_internal:-false} - fi elif [[ $1 == "end" ]] || [[ $1 == "endTest" ]]; then # builder_echo end block status message test="$1" @@ -204,9 +206,6 @@ builder_echo() { color="$3" shift 3 action="end" - if [[ "${color}" != "error" ]] && ! builder_is_running_on_teamcity && builder_is_child_build; then - do_output=${_builder_debug_internal:-false} - fi fi fi message="$*" @@ -219,30 +218,28 @@ builder_echo() { fi fi - if ${do_output}; then - if [[ ! -z ${COLOR_RED+x} ]]; then - case $color in - white) color="$COLOR_WHITE" ;; - grey) color="$COLOR_GREY" ;; - green|success) color="$COLOR_GREEN" ;; - blue|heading) color="$COLOR_BLUE" ;; - yellow|warning) color="$COLOR_YELLOW" ;; - red|error) color="$COLOR_RED"; echo_target=_builder_error_echo ;; - purple) color="$COLOR_PURPLE" ;; - brightwhite) color="$COLOR_BRIGHTWHITE" ;; - teal|debug) color="$COLOR_TEAL" ;; - setmark) mark="$HEADING_SETMARK" color="$COLOR_PURPLE" ;; - esac + if [[ ! -z ${COLOR_RED+x} ]]; then + case $color in + white) color="$COLOR_WHITE" ;; + grey) color="$COLOR_GREY" ;; + green|success) color="$COLOR_GREEN" ;; + blue|heading) color="$COLOR_BLUE" ;; + yellow|warning) color="$COLOR_YELLOW" ;; + red|error) color="$COLOR_RED"; echo_target=_builder_error_echo ;; + purple) color="$COLOR_PURPLE" ;; + brightwhite) color="$COLOR_BRIGHTWHITE" ;; + teal|debug) color="$COLOR_TEAL" ;; + setmark) mark="$HEADING_SETMARK" color="$COLOR_PURPLE" ;; + esac - if builder_is_dep_build; then - $echo_target -e "$mark$COLOR_GREY[$THIS_SCRIPT_IDENTIFIER]$COLOR_RESET $color$message$COLOR_RESET" - else - $echo_target -e "$mark$BUILDER_BOLD$COLOR_BRIGHT_WHITE[$THIS_SCRIPT_IDENTIFIER]$COLOR_RESET $color$message$COLOR_RESET" - fi + if builder_is_dep_build; then + $echo_target -e "$mark$COLOR_GREY[$THIS_SCRIPT_IDENTIFIER]$COLOR_RESET $color$message$COLOR_RESET" else - # Cope with the case of pre-init message and just emit plain text - $echo_target -e "$message" + $echo_target -e "$mark$BUILDER_BOLD$COLOR_BRIGHT_WHITE[$THIS_SCRIPT_IDENTIFIER]$COLOR_RESET $color$message$COLOR_RESET" fi + else + # Cope with the case of pre-init message and just emit plain text + $echo_target -e "$message" fi if [[ "${action}" == "end" ]] && builder_is_running_on_teamcity; then @@ -447,28 +444,47 @@ _builder_cleanup_deps() { # than calling the build script directly, to avoid multiple builds of # dependencies. # +# Do not use builder standard options such as `--deps` or `--debug`. +# # ### Parameters # # * 1: `script` path to script, relative to root of repo -# * 2: `action` action(s)+target(s) for the child script to run, comma separated, at least one +# * 2+: `parameters` action(s), target(s), parameters for the child script to run # # ### Example # # ```bash -# builder_launch /core/build.sh configure,build:wasm +# builder_launch core/build.sh configure,build:wasm --no-tests # ``` # builder_launch() { local script="$1" - local action="$2" - _builder_execute_child_script "${KEYMAN_ROOT}${script}" "${action}" "" + local param + shift + + if [[ ! "$script" =~ ^/(.+) ]]; then + builder_die "Error: builder_launch: script path must start with /, and is relative to repo root" + fi + + for param in "$@"; do + if [[ "${param}" =~ ${_builder_reserved_parameters} ]]; then + builder_die "Error: builder_launch: reserved parameter '${param}' used" + fi + done + + builder_echo grey "## script '$script $*' launched..." + _builder_execute_child_script "${KEYMAN_ROOT}${script}" "$@" + builder_echo grey "## script '$script $*' completed" } _builder_execute_child() { local action="$1" local target="$2" local script="$THIS_SCRIPT_PATH/${_builder_target_paths[$target]}/build.sh" + + builder_echo grey "## child $action$target starting..." _builder_execute_child_script "$script" "$action" + builder_echo grey "## child $action$target completed successfully" } # ### Parameters @@ -478,18 +494,15 @@ _builder_execute_child() { # * 3: `target` reporting target, used when executing a child script through builder_run_child_actions, optional _builder_execute_child_script() { local script="$1" - local action="$2" - local target="${3:-}" - - builder_echo start "child#$action$target" "## child $action$target starting..." - _builder_timing_start "$action$target" + # subsequent parameters passed as $@/$* below + shift # Build array of specified inheritable options local child_options=() local opt for opt in "${_builder_options_inheritable[@]}"; do - if builder_has_option $opt; then - child_options+=($opt) + if builder_has_option "${opt}"; then + child_options+=("${opt}") fi done @@ -509,17 +522,14 @@ _builder_execute_child_script() { --builder-child \ $_builder_build_deps \ $dep_flag "$dep_module" \ - "$action" \ - ${child_options[@]} \ + "$@" \ + "${child_options[@]}" \ $builder_verbose \ $builder_debug \ $_builder_offline \ - && ( - _builder_timing_stop "$action$target" - builder_echo end "child#$action$target" success "## child $action$target completed successfully" - ) || ( + || ( result=$? - builder_echo end "child#$action$target" error "## child $action$target failed with exit code $result" + builder_echo error "## child script '$script $*' failed with exit code $result" exit $result ) || exit $? # Required due to above subshell masking exit } @@ -766,10 +776,6 @@ _builder_timing_cleanup_and_report() { return 0 fi - if [[ -z "${_builder_timing_report_file:+x}" ]]; then - return 0 - fi - if [[ "$build_exit_code" != 0 ]]; then # Hide the timings report if build failed, but still cleanup _builder_timings=false @@ -1615,6 +1621,7 @@ _builder_parse_expanded_parameters() { fi else + # See also _builder_reserved_parameters case "$key" in --help|-h) builder_display_usage @@ -1810,21 +1817,14 @@ _builder_completion_describe() { echo -n "; " printf '%s ' "${_builder_targets[@]}" echo -n "; " - # Remove all '+' suffixes from options; they're a config on the option, not part - # of the actual option text itself. - local _builder_opts=() - for e in "${!_builder_params[@]}"; do - if [[ $e =~ ^-- ]]; then - _builder_opts+=(${e%+*}) - fi - done - # Add default options - _builder_opts+=( --verbose --debug --color --no-color --offline --help ) + local opts=("${_builder_options[@]}") + # Add default options; see also _builder_reserved_parameters + opts+=( --verbose --debug --release --color --no-color --offline --help --timings --no-timings ) if builder_has_dependencies; then - _builder_opts+=( --deps --no-deps --force-deps ) + opts+=( --deps --no-deps --force-deps ) fi - printf '%s ' "${_builder_opts[@]}" + printf '%s ' "${opts[@]}" } builder_display_usage() { @@ -1900,6 +1900,7 @@ builder_display_usage() { fi done + # See also _builder_reserved_parameters _builder_pad $width " --verbose, -v" "Verbose logging" _builder_pad $width " --debug, -d" "Debug build" _builder_pad $width " --color" "Force colorized output" From fc04e2445b1ec12aca48234fa64525488ade9c98 Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Fri, 14 Nov 2025 12:35:48 +0100 Subject: [PATCH 27/33] fix(web): address code review comments Deal with `currentScript` being null/undefined. --- web/src/app/browser/src/debug-main.ts | 4 ++-- web/src/app/browser/src/release-main.ts | 4 ++-- web/src/app/webview/src/debug-main.ts | 4 ++-- web/src/app/webview/src/release-main.ts | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/web/src/app/browser/src/debug-main.ts b/web/src/app/browser/src/debug-main.ts index aa37c3bdd3..aa6b23a2a4 100644 --- a/web/src/app/browser/src/debug-main.ts +++ b/web/src/app/browser/src/debug-main.ts @@ -5,8 +5,8 @@ import { SourcemappedWorker } from '@keymanapp/lexical-model-layer/web' * Determine path and protocol of executing script, setting them as * construction defaults. */ -const ss = (document.currentScript as HTMLScriptElement).src; -const sPath = ss.substring(0, ss.lastIndexOf('/') + 1); +const ss = (document.currentScript as HTMLScriptElement)?.src; +const sPath = ss ? ss.substring(0, ss.lastIndexOf('/') + 1) : './'; // @ts-ignore window['keyman'] = new KeymanEngine(SourcemappedWorker, sPath); \ No newline at end of file diff --git a/web/src/app/browser/src/release-main.ts b/web/src/app/browser/src/release-main.ts index 45395748a6..da6b5be109 100644 --- a/web/src/app/browser/src/release-main.ts +++ b/web/src/app/browser/src/release-main.ts @@ -5,8 +5,8 @@ import { Worker } from '@keymanapp/lexical-model-layer/web' * Determine path and protocol of executing script, setting them as * construction defaults. */ -const ss = (document.currentScript as HTMLScriptElement).src; -const sPath = ss.substring(0, ss.lastIndexOf('/') + 1); +const ss = (document.currentScript as HTMLScriptElement)?.src; +const sPath = ss ? ss.substring(0, ss.lastIndexOf('/') + 1) : './'; // @ts-ignore window['keyman'] = new KeymanEngine(Worker, sPath); \ No newline at end of file diff --git a/web/src/app/webview/src/debug-main.ts b/web/src/app/webview/src/debug-main.ts index aa37c3bdd3..aa6b23a2a4 100644 --- a/web/src/app/webview/src/debug-main.ts +++ b/web/src/app/webview/src/debug-main.ts @@ -5,8 +5,8 @@ import { SourcemappedWorker } from '@keymanapp/lexical-model-layer/web' * Determine path and protocol of executing script, setting them as * construction defaults. */ -const ss = (document.currentScript as HTMLScriptElement).src; -const sPath = ss.substring(0, ss.lastIndexOf('/') + 1); +const ss = (document.currentScript as HTMLScriptElement)?.src; +const sPath = ss ? ss.substring(0, ss.lastIndexOf('/') + 1) : './'; // @ts-ignore window['keyman'] = new KeymanEngine(SourcemappedWorker, sPath); \ No newline at end of file diff --git a/web/src/app/webview/src/release-main.ts b/web/src/app/webview/src/release-main.ts index 45395748a6..da6b5be109 100644 --- a/web/src/app/webview/src/release-main.ts +++ b/web/src/app/webview/src/release-main.ts @@ -5,8 +5,8 @@ import { Worker } from '@keymanapp/lexical-model-layer/web' * Determine path and protocol of executing script, setting them as * construction defaults. */ -const ss = (document.currentScript as HTMLScriptElement).src; -const sPath = ss.substring(0, ss.lastIndexOf('/') + 1); +const ss = (document.currentScript as HTMLScriptElement)?.src; +const sPath = ss ? ss.substring(0, ss.lastIndexOf('/') + 1) : './'; // @ts-ignore window['keyman'] = new KeymanEngine(Worker, sPath); \ No newline at end of file From 7d6e04f9acb8bf125a7d3e0bb74a636395b4fe39 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Fri, 14 Nov 2025 13:58:16 +0100 Subject: [PATCH 28/33] Apply suggestions from code review Build-bot: skip --- resources/builder.inc.sh | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/resources/builder.inc.sh b/resources/builder.inc.sh index fe657fd2b0..0ce80a5c2d 100755 --- a/resources/builder.inc.sh +++ b/resources/builder.inc.sh @@ -490,8 +490,7 @@ _builder_execute_child() { # ### Parameters # # * 1: `script` path to script -# * 2: `action` action(s)+target(s) for the child script to run, comma separated, at least one -# * 3: `target` reporting target, used when executing a child script through builder_run_child_actions, optional +# * 2+: `parameters` action(s), target(s), parameters for the child script to run _builder_execute_child_script() { local script="$1" # subsequent parameters passed as $@/$* below @@ -514,10 +513,6 @@ _builder_execute_child_script() { dep_module="$builder_dep_parent" fi - # Note: we do not pass '$target' to the child script, because it is used here - # only for reporting; the target was used to select the correct script in - # _builder_execute_child() - "$script" \ --builder-child \ $_builder_build_deps \ From 6fe3e3e8ba7fe818505a8b1de7093a40c7b7c688 Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Fri, 14 Nov 2025 18:23:49 +0100 Subject: [PATCH 29/33] maint(linux): free disk space before running autopkgtests Running the autopkgtests during a Ubuntu packaging build GHA recently started to fail with an out-of-disk-space error. This change removes some large directories with tools that we don't need for the autopkgtests. Fixes: #15171 Build-bot: skip Test-bot: skip --- .github/workflows/deb-packaging.yml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/.github/workflows/deb-packaging.yml b/.github/workflows/deb-packaging.yml index 3e6b36e349..34fd04509e 100644 --- a/.github/workflows/deb-packaging.yml +++ b/.github/workflows/deb-packaging.yml @@ -186,6 +186,23 @@ jobs: runs-on: ubuntu-latest steps: + - name: Free disk space + run: | + # Free disk space by removing some large directories that we don't use (#15171) + # see https://dev.to/mathio/squeezing-disk-space-from-github-actions-runners-an-engineers-guide-3pjg + + # Remove Android SDKs (frees 12 GB on Ubuntu 24.04) + sudo rm -rf /usr/local/lib/android + + # Remove Haskell (GHC) (frees 6.4 GB) + sudo rm -rf /usr/local/.ghcup + + # Remove CodeQL and other toolcaches (frees 5.8 GB) + sudo rm -rf /opt/hostedtoolcache + + # Remove .NET SDKs (frees 4.0 GB) + sudo rm -rf /usr/share/dotnet + - name: Download Source Artifacts uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 with: From 9eb41504c93d2099db3dadc425ee01e364124dce Mon Sep 17 00:00:00 2001 From: Keyman Build Agent Date: Fri, 14 Nov 2025 12:03:41 -0600 Subject: [PATCH 30/33] auto: increment master version to 19.0.163 Test-bot: skip Build-bot: skip --- HISTORY.md | 8 ++++++++ VERSION.md | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/HISTORY.md b/HISTORY.md index 1fc5a99336..b9bfda0b16 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,13 @@ # Keyman Version History +## 19.0.162 alpha 2025-11-14 + +* chore(linux): use `devel` for next Ubuntu release (#15157) +* maint(resources): add build timing report to builder (#15114) +* maint(resources): add `builder_launch` function (#15129) +* fix(web): improve determination of default path (#15159) +* maint(linux): free disk space before running autopkgtests (#15172) + ## 19.0.161 alpha 2025-11-13 * docs(android): Document disclaimer of Keyman NOT containing adware/spyware (#15136) diff --git a/VERSION.md b/VERSION.md index 02f69884fc..df4030b054 100644 --- a/VERSION.md +++ b/VERSION.md @@ -1 +1 @@ -19.0.162 \ No newline at end of file +19.0.163 \ No newline at end of file From 9940de591c4d4f89eba90ebd029914ab67221c0d Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Sat, 15 Nov 2025 06:57:22 +0100 Subject: [PATCH 31/33] maint(mac): handle hdiutil detach failure correctly in make-km-dmg.sh Fixes: #10201 Test-bot: skip Build-bot: skip release:mac --- mac/Keyman4MacIM/make-km-dmg.sh | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/mac/Keyman4MacIM/make-km-dmg.sh b/mac/Keyman4MacIM/make-km-dmg.sh index d949a96af3..5a3176b298 100755 --- a/mac/Keyman4MacIM/make-km-dmg.sh +++ b/mac/Keyman4MacIM/make-km-dmg.sh @@ -192,12 +192,17 @@ builder_echo info "Detaching \"$WORKING_COPY_OF_IMAGE\"" DETACH_SUCCESS=0 DETACH_FORCE= while (( DETACH_SUCCESS < 10 )); do - DETACH_RESULT=0 - hdiutil detach $STAGING_DIR $VERBOSITY $DETACH_FORCE || DETACH_RESULT=$? - if [[ $DETACH_RESULT != 0 || -d "$STAGING_DIR" ]] ; then + if hdiutil detach $STAGING_DIR $VERBOSITY $DETACH_FORCE; then + DETACH_RESULT=0 + echo "hdiutil detach reported success" + else + DETACH_RESULT=$? + echo "hdiutil detach reported failure code ${DETACH_RESULT}" + fi + if [[ $DETACH_RESULT != 0 ]] || [[ -d "$STAGING_DIR" ]] ; then (( DETACH_SUCCESS++ )) echo "Failed to unmount: \"$STAGING_DIR\" on attempt #$DETACH_SUCCESS. Waiting 5 seconds to try again." - if (( DETACH_SUCCESS > 5 )); then + if (( DETACH_SUCCESS == 6 )); then echo " Note: Detach failed first five times, so now attempting with '-force'" DETACH_FORCE=-force fi From 6a2a59510e7f41efbb45b0afe1c2fb37616e13a9 Mon Sep 17 00:00:00 2001 From: Keyman Build Agent Date: Mon, 17 Nov 2025 12:02:17 -0600 Subject: [PATCH 32/33] auto: increment master version to 19.0.164 Test-bot: skip Build-bot: skip --- HISTORY.md | 10 ++++++++++ VERSION.md | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/HISTORY.md b/HISTORY.md index b9bfda0b16..3dccd07f59 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,15 @@ # Keyman Version History +## 19.0.163 alpha 2025-11-17 + +* chore(linux): Update debian changelog (#15133) +* fix(windows): handle exceptions when walking stack in exception handler (#15150) +* fix(windows): disable OutputDebugString from DllMain (#15151) +* change(windows): include map2pdb generation for relevant Delphi projects (#14799) +* change(windows): use circular buffer for logging (#15163) +* fix(windows): handle forward slash for KEYMAN_ROOT env var (#15156) +* maint(mac): handle hdiutil detach failure correctly in make-km-dmg.sh (#15176) + ## 19.0.162 alpha 2025-11-14 * chore(linux): use `devel` for next Ubuntu release (#15157) diff --git a/VERSION.md b/VERSION.md index df4030b054..79c54165ad 100644 --- a/VERSION.md +++ b/VERSION.md @@ -1 +1 @@ -19.0.163 \ No newline at end of file +19.0.164 \ No newline at end of file From 85dcdcf066a30887ebc8b0b30ffb5a632fb2bb84 Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Thu, 20 Nov 2025 15:35:46 +1000 Subject: [PATCH 33/33] feat(windows): update wix to 3.14.1 Update the WiX toolset build version to 3.14.1 for Arm64. Fixes: #15192 --- docs/build/windows.md | 2 +- resources/build/win/wix.inc.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/build/windows.md b/docs/build/windows.md index f485605f58..edc756340f 100644 --- a/docs/build/windows.md +++ b/docs/build/windows.md @@ -345,7 +345,7 @@ SETX KEYMAN_CEF4DELPHI_ROOT "c:\Projects\keyman\CEF4Delphi_Binary" ```ps1 # Elevated PowerShell choco install 7zip html-help-workshop -choco install wixtoolset --version=3.11.1 +choco install wixtoolset --version=3.14.1 git clone https://github.com/keymanapp/CEF4Delphi_Binary C:\Projects\keyman\CEF4Delphi_Binary ``` diff --git a/resources/build/win/wix.inc.sh b/resources/build/win/wix.inc.sh index a9e084b10a..0ad63e85b7 100644 --- a/resources/build/win/wix.inc.sh +++ b/resources/build/win/wix.inc.sh @@ -1,5 +1,5 @@ -WIXPATH="$ProgramFilesx86/WiX Toolset v3.11/bin" +WIXPATH="$ProgramFilesx86/WiX Toolset v3.14/bin" # !IFDEF LINT WIXLIGHTLINT=()