diff options
| author | Jake Petroules <jake.petroules@petroules.com> | 2015-06-26 23:51:07 -0700 |
|---|---|---|
| committer | Jake Petroules <jake.petroules@petroules.com> | 2015-07-25 01:01:23 +0000 |
| commit | c27a4d0980f57e31a7e0773b921e015bf4736119 (patch) | |
| tree | d1019930694d9c9ef6f2381b06ae3c1da8ae0b36 | |
| parent | b24c37c89f7db5363f4860ba269dba482b19a5f1 (diff) | |
| download | qbs-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.qbs | 2 | ||||
| -rw-r--r-- | share/qbs/modules/typescript/TypeScriptModule.qbs | 48 | ||||
| -rw-r--r-- | share/qbs/modules/typescript/qbs-tsc-scan/.gitignore | 5 | ||||
| -rw-r--r-- | share/qbs/modules/typescript/qbs-tsc-scan/qbs-tsc-scan.ts | 68 | ||||
| -rw-r--r-- | share/qbs/modules/typescript/typescript.js | 119 |
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; +} |
