summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJake Petroules <jake.petroules@petroules.com>2015-06-26 23:51:07 -0700
committerJake Petroules <jake.petroules@petroules.com>2015-07-25 01:01:23 +0000
commitc27a4d0980f57e31a7e0773b921e015bf4736119 (patch)
treed1019930694d9c9ef6f2381b06ae3c1da8ae0b36
parentb24c37c89f7db5363f4860ba269dba482b19a5f1 (diff)
downloadqbs-c27a4d0980f57e31a7e0773b921e015bf4736119.tar.gz
Implement perfect output artifact tracking for TypeScript compiler.
This uses the TypeScript compiler API do to the work, similarly to the solution implemented for Java. Change-Id: I2a9c06ca9bec79af3341cb4f6effe9008e9ed276 Reviewed-by: Christian Kandeler <christian.kandeler@theqtcompany.com>
-rw-r--r--share/qbs/imports/qbs/Probes/TypeScriptProbe.qbs2
-rw-r--r--share/qbs/modules/typescript/TypeScriptModule.qbs48
-rw-r--r--share/qbs/modules/typescript/qbs-tsc-scan/.gitignore5
-rw-r--r--share/qbs/modules/typescript/qbs-tsc-scan/qbs-tsc-scan.ts68
-rw-r--r--share/qbs/modules/typescript/typescript.js119
5 files changed, 219 insertions, 23 deletions
diff --git a/share/qbs/imports/qbs/Probes/TypeScriptProbe.qbs b/share/qbs/imports/qbs/Probes/TypeScriptProbe.qbs
index a676edc63..f567089aa 100644
--- a/share/qbs/imports/qbs/Probes/TypeScriptProbe.qbs
+++ b/share/qbs/imports/qbs/Probes/TypeScriptProbe.qbs
@@ -45,6 +45,8 @@ BinaryProbe {
property var version
configure: {
+ if (!nodejsToolchainInstallPath)
+ throw '"nodejsToolchainInstallPath" must be specified';
// HACK: Duplicated from PathProbe.qbs
if (!names)
throw '"names" must be specified';
diff --git a/share/qbs/modules/typescript/TypeScriptModule.qbs b/share/qbs/modules/typescript/TypeScriptModule.qbs
index 654b13392..e92ce4b15 100644
--- a/share/qbs/modules/typescript/TypeScriptModule.qbs
+++ b/share/qbs/modules/typescript/TypeScriptModule.qbs
@@ -41,6 +41,12 @@ Module {
additionalProductTypes: ["compiled_typescript"]
+ // QBS-833 workaround
+ Probes.NodeJsProbe { id: nodejsProbe; pathPrefixes: [nodejsProbe.toolchainInstallPath] }
+ nodejs.toolchainInstallPath: nodejsProbe.path
+ nodejs.interpreterFileName: nodejsProbe.fileName
+ nodejs.interpreterFilePath: nodejsProbe.filePath
+
Probes.TypeScriptProbe {
id: tsc
pathPrefixes: [toolchainInstallPath]
@@ -146,11 +152,49 @@ Module {
fileTags: ["typescript"]
}
+ Group {
+ name: "io.qt.qbs.internal.typescript-helper"
+ files: [
+ FileInfo.joinPaths(path, "qbs-tsc-scan", "qbs-tsc-scan.ts"),
+ FileInfo.joinPaths(product.typescript.toolchainInstallPath, "typescript.d.ts")
+ ]
+ fileTags: ["typescript.typescript-internal"]
+ }
+
+ Rule {
+ multiplex: true
+ inputs: ["typescript.typescript-internal"]
+
+ outputFileTags: ["typescript.compiled_typescript-internal"]
+ outputArtifacts: {
+ if (!TypeScript.supportsModernFeatures(product))
+ return [];
+ return [{
+ filePath: FileInfo.joinPaths(product.buildDirectory,
+ ".io.qt.qbs.internal.typescript", "qbs-tsc-scan.js"),
+ fileTags: ["typescript.compiled_typescript-internal"]
+ }];
+ }
+
+ prepare: {
+ var inputPaths = inputs["typescript.typescript-internal"].map(function (input) {
+ return input.filePath;
+ });
+
+ var args = ["--module", "commonjs",
+ "--outDir", FileInfo.path(output.filePath)].concat(inputPaths);
+ var cmd = new Command(ModUtils.moduleProperty(product, "compilerPath"), args);
+ cmd.silent = true;
+ return [cmd];
+ }
+ }
+
Rule {
id: typescriptCompiler
multiplex: true
inputs: ["typescript"]
inputsFromDependencies: ["typescript_declaration"]
+ explicitlyDependsOn: ["typescript.compiled_typescript-internal"]
outputArtifacts: TypeScript.outputArtifacts(product, inputs)
@@ -160,9 +204,9 @@ Module {
var cmd, cmds = [];
cmd = new Command(ModUtils.moduleProperty(product, "compilerPath"),
- TypeScript.tscArguments(product, inputs, outputs));
+ TypeScript.tscArguments(product, inputs));
cmd.description = "compiling " + (ModUtils.moduleProperty(product, "singleFile")
- ? primaryOutput.fileName
+ ? outputs.compiled_typescript[0].fileName
: inputs.typescript.map(function(obj) {
return obj.fileName; }).join(", "));
cmd.highlight = "compiler";
diff --git a/share/qbs/modules/typescript/qbs-tsc-scan/.gitignore b/share/qbs/modules/typescript/qbs-tsc-scan/.gitignore
new file mode 100644
index 000000000..2ac7185a8
--- /dev/null
+++ b/share/qbs/modules/typescript/qbs-tsc-scan/.gitignore
@@ -0,0 +1,5 @@
+# Visual Studio Code IDE
+tsconfig.json
+qbs-tsc-scan.js
+typings/node/node.d.ts
+typings/typescript/typescript.d.ts
diff --git a/share/qbs/modules/typescript/qbs-tsc-scan/qbs-tsc-scan.ts b/share/qbs/modules/typescript/qbs-tsc-scan/qbs-tsc-scan.ts
new file mode 100644
index 000000000..ca5eb420c
--- /dev/null
+++ b/share/qbs/modules/typescript/qbs-tsc-scan/qbs-tsc-scan.ts
@@ -0,0 +1,68 @@
+import ts = require("typescript");
+
+declare var process: any;
+
+export namespace io.qt.qbs {
+ export class Artifact {
+ filePath: string;
+ fileTags: string[];
+ }
+
+ export namespace tools {
+ export namespace utils {
+ function stringEndsWith(s: string, e: string) {
+ return s.slice(-e.length) === e;
+ }
+
+ export function artifactFromFilePath(filePath: string): Artifact {
+ var fileTags: string[] = [];
+ if (stringEndsWith(filePath, ".js.map")) {
+ fileTags.push("source_map");
+ } else if (stringEndsWith(filePath, ".js")) {
+ fileTags.push("js", "compiled_typescript");
+ } else if (stringEndsWith(filePath, ".d.ts")) {
+ fileTags.push("typescript_declaration");
+ }
+
+ return { filePath: filePath, fileTags: fileTags };
+ }
+ }
+
+ function compileInternal(fileNames: string[], options: ts.CompilerOptions): qbs.Artifact[] {
+ var outputArtifacts: qbs.Artifact[] = [];
+ var program = ts.createProgram(fileNames, options);
+ var emitResult = program.emit(undefined, filePath => {
+ outputArtifacts.push(utils.artifactFromFilePath(filePath));
+ });
+
+ var allDiagnostics = ts.getPreEmitDiagnostics(program).concat(emitResult.diagnostics);
+ allDiagnostics.forEach(diagnostic => {
+ var message = ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n');
+ if (diagnostic.file) {
+ var { line, character } = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start);
+ console.error(`${diagnostic.file.fileName} (${line + 1},${character + 1}): ${message}`);
+ } else {
+ console.error(message);
+ }
+ });
+
+ return emitResult.emitSkipped ? undefined : outputArtifacts;
+ }
+
+ export function compile(commandLineArguments: string[]): qbs.Artifact[] {
+ var parsedCommandLine = ts.parseCommandLine(commandLineArguments);
+ return compileInternal(parsedCommandLine.fileNames, parsedCommandLine.options);
+ }
+
+ export function TypeScriptCompilerScannerToolMain(): void {
+ var outputArtifacts = compile(process.argv.slice(2));
+ if (outputArtifacts !== undefined) {
+ console.log(JSON.stringify(outputArtifacts));
+ } else {
+ process.exit(1);
+ }
+ }
+ }
+}
+
+io.qt.qbs.tools.TypeScriptCompilerScannerToolMain();
diff --git a/share/qbs/modules/typescript/typescript.js b/share/qbs/modules/typescript/typescript.js
index 52676f6ea..68a0b0a58 100644
--- a/share/qbs/modules/typescript/typescript.js
+++ b/share/qbs/modules/typescript/typescript.js
@@ -46,12 +46,10 @@ function findTscVersion(compilerFilePath, nodejsPath) {
}
}
-function tscArguments(product, inputs, outputs) {
+function tscArguments(product, inputs) {
var i;
var args = [];
- var primaryOutput = outputs.compiled_typescript[0];
-
if (ModUtils.moduleProperty(product, "warningLevel") === "pedantic") {
args.push("--noImplicitAny");
}
@@ -64,10 +62,6 @@ function tscArguments(product, inputs, outputs) {
var moduleLoader = ModUtils.moduleProperty(product, "moduleLoader");
if (moduleLoader) {
- if (ModUtils.moduleProperty(product, "singleFile")) {
- throw("typescript.singleFile cannot be true when typescript.moduleLoader is set");
- }
-
args.push("--module");
args.push(moduleLoader);
}
@@ -90,12 +84,15 @@ function tscArguments(product, inputs, outputs) {
args.push(flags[i]);
}
- args.push("--outDir");
- args.push(product.buildDirectory);
+ if (supportsModernFeatures(product)) {
+ args.push("--rootDir", product.sourceDirectory);
+ }
+
+ args.push("--outDir", product.buildDirectory);
if (ModUtils.moduleProperty(product, "singleFile")) {
- args.push("--out");
- args.push(primaryOutput.filePath);
+ args.push("--out",
+ FileInfo.joinPaths(product.destinationDirectory, product.targetName) + ".js");
}
if (inputs.typescript_declaration) {
@@ -104,30 +101,99 @@ function tscArguments(product, inputs, outputs) {
}
}
- for (i = 0; i < inputs.typescript.length; ++i) {
- args.push(inputs.typescript[i].filePath);
+ if (inputs.typescript) {
+ for (i = 0; i < inputs.typescript.length; ++i) {
+ args.push(inputs.typescript[i].filePath);
+ }
+ }
+
+ if (inputs["typescript.typescript-internal"]) {
+ for (i = 0; i < inputs["typescript.typescript-internal"].length; ++i) {
+ args.push(inputs["typescript.typescript-internal"][i].filePath);
+ }
}
return args;
}
function outputArtifacts(product, inputs) {
+ if (!supportsModernFeatures(product)) {
+ print("WARNING: Qbs does not properly support TypeScript versions prior to 1.5 due to " +
+ "severe limitations in dependency tracking. This is TypeScript version " +
+ ModUtils.moduleProperty(product, "version") + ". It is strongly recommended that " +
+ "you upgrade TypeScript, or continue at your own risk.");
+ return legacyOutputArtifacts(product, inputs);
+ }
+
+ var process;
+ try {
+ process = new Process();
+ process.setEnv("NODE_PATH", ModUtils.moduleProperty(product, "toolchainInstallPath"));
+ process.exec(product.moduleProperty("nodejs", "interpreterFilePath"),
+ [FileInfo.joinPaths(product.buildDirectory,
+ ".io.qt.qbs.internal.typescript",
+ "qbs-tsc-scan.js")]
+ .concat(tscArguments(product, inputs)), true);
+ var artifacts = JSON.parse(process.readStdOut());
+
+ // Find and tag the "main" output file
+ var applicationFile = product.moduleProperty("nodejs", "applicationFile");
+ if (applicationFile) {
+ var i, appIndex = -1;
+ if (product.moduleProperty("typescript", "singleFile")) {
+ for (i = 0; i < artifacts.length; ++i) {
+ if (artifacts[i].fileTags.contains("compiled_typescript")) {
+ appIndex = i;
+ break;
+ }
+ }
+ } else {
+ var expected = FileInfo.relativePath(product.sourceDirectory, applicationFile);
+ if (!expected.endsWith(".ts"))
+ // tsc doesn't allow this anyways, so it's a perfectly reasonable restriction
+ throw "TypeScript source file name '" + applicationFile +
+ "' does not end with .ts";
+
+ expected = expected.slice(0, -2) + "js";
+
+ for (i = 0; i < artifacts.length; ++i) {
+ if (expected === FileInfo.relativePath(product.buildDirectory,
+ artifacts[i].filePath)) {
+ appIndex = i;
+ break;
+ }
+ }
+ }
+
+ if (appIndex === -1 || !artifacts[appIndex].fileTags.contains("compiled_typescript"))
+ throw "nodejs.applicationFile was set, but Qbs couldn't find the compiled " +
+ "JavaScript file corresponding to '" + applicationFile + "'";
+
+ artifacts[appIndex].fileTags = artifacts[appIndex].fileTags.concat(["application_js"]);
+ }
+
+ return artifacts;
+ } finally {
+ if (process)
+ process.close();
+ }
+}
+
+function legacyOutputArtifacts(product, inputs) {
var artifacts = [];
if (!inputs.typescript) {
return artifacts;
}
+ var jsTags = ["js", "compiled_typescript"];
+ var filePath = FileInfo.joinPaths(product.destinationDirectory, product.targetName);
if (product.moduleProperty("typescript", "singleFile")) {
- var jsTags = ["js", "compiled_typescript"];
-
// We could check
// if (product.moduleProperty("nodejs", "applicationFile") === inputs.typescript[i].filePath)
// but since we're compiling to a single file there's no need to state it explicitly
jsTags.push("application_js");
- var filePath = FileInfo.joinPaths(product.destinationDirectory, product.targetName);
-
artifacts.push({fileTags: jsTags,
filePath: FileInfo.joinPaths(
product.moduleProperty("nodejs",
@@ -145,7 +211,7 @@ function outputArtifacts(product, inputs) {
}
} else {
for (var i = 0; i < inputs.typescript.length; ++i) {
- var jsTags = ["js", "compiled_typescript"];
+ jsTags = ["js", "compiled_typescript"];
if (product.moduleProperty("nodejs", "applicationFile") === inputs.typescript[i].filePath)
jsTags.push("application_js");
@@ -154,9 +220,9 @@ function outputArtifacts(product, inputs) {
inputs.typescript[i].filePath));
var baseName = FileInfo.baseName(inputs.typescript[i].fileName);
- var filePath = FileInfo.joinPaths(product.destinationDirectory,
- intermediatePath,
- baseName);
+ filePath = FileInfo.joinPaths(product.destinationDirectory,
+ intermediatePath,
+ baseName);
artifacts.push({fileTags: jsTags,
filePath: FileInfo.joinPaths(
@@ -179,3 +245,14 @@ function outputArtifacts(product, inputs) {
return artifacts;
}
+
+function supportsModernFeatures(product) {
+ var compilerVersionMajor = ModUtils.moduleProperty(product, "versionMajor");
+ if (compilerVersionMajor === 1) {
+ if (ModUtils.moduleProperty(product, "versionMinor") >= 5) {
+ return true;
+ }
+ }
+
+ return compilerVersionMajor > 1;
+}