mirror of https://github.com/svaarala/duktape.git
Sami Vaarala
5 years ago
committed by
GitHub
1 changed files with 248 additions and 0 deletions
@ -0,0 +1,248 @@ |
|||||
|
/* |
||||
|
* Combine a set of a source files into a single C file. |
||||
|
* |
||||
|
* Overview of the process: |
||||
|
* |
||||
|
* * Parse user supplied C files. Add automatic #undefs at the end |
||||
|
* of each C file to avoid defines bleeding from one file to another. |
||||
|
* |
||||
|
* * Combine the C files in specified order. If sources have ordering |
||||
|
* dependencies (depends on application), order may matter. Caller |
||||
|
* provides the intended order. |
||||
|
* |
||||
|
* * Process #include statements in the combined source, categorizing |
||||
|
* them either as "internal" (found in specified include path) or |
||||
|
* "external". Internal includes, unless explicitly excluded, are |
||||
|
* inlined into the result while extenal includes are left as is. |
||||
|
* Duplicate internal #include statements are replaced with a comment. |
||||
|
* |
||||
|
* At every step, source and header lines are represented with explicit |
||||
|
* line objects which keep track of original filename and line. The |
||||
|
* output contains #line directives, if requested, to ensure error |
||||
|
* throwing and other diagnostic info will work in a useful with the |
||||
|
* original sources. It's also possible to generate a combined source |
||||
|
* with no #line directives which may be more appropriate for deployment. |
||||
|
* |
||||
|
* Making the process deterministic is important, so that if users have |
||||
|
* diffs that they apply to the combined source, such diffs would apply |
||||
|
* for as long as possible. |
||||
|
* |
||||
|
* Limitations and notes: |
||||
|
* |
||||
|
* * While there are automatic #undef's for #define's introduced in each |
||||
|
* C file, it's not possible to "undefine" structs, unions, etc. If |
||||
|
* there are structs/unions/typedefs with conflicting names, these |
||||
|
* have to be resolved in the source files first. |
||||
|
* |
||||
|
* * Because duplicate #include statements are suppressed, currently |
||||
|
* assumes #include statements are not conditional. |
||||
|
* |
||||
|
* * A system header might be #include'd in multiple source files with |
||||
|
* different feature defines (like _BSD_SOURCE). Because the #include |
||||
|
* file will only appear once in the resulting source, the first |
||||
|
* occurrence wins. The result may not work correctly if the feature |
||||
|
* defines must actually be different between two or more source files. |
||||
|
*/ |
||||
|
|
||||
|
'use strict'; |
||||
|
|
||||
|
const { readFileUtf8 } = require('../extbindings/fileio.js'); |
||||
|
const { pathJoin, basename, fileExists } = require('../util/fs.js'); |
||||
|
const { createBareObject } = require('../util/bare.js'); |
||||
|
const { stripLastNewline, normalizeNewlines } = require('../util/string_util.js'); |
||||
|
const { cStrEncode } = require('../util/cquote.js'); |
||||
|
|
||||
|
function Line(fileName, lineNo, data) { |
||||
|
this.fileName = basename(fileName); |
||||
|
this.fileNameFull = fileName; |
||||
|
this.lineNo = lineNo; |
||||
|
this.data = data; |
||||
|
} |
||||
|
|
||||
|
function File(fileName, lines) { |
||||
|
this.fileName = basename(fileName); |
||||
|
this.fileNameFull = fileName; |
||||
|
this.lines = lines; |
||||
|
} |
||||
|
File.prototype.pushTextLine = function pushTextLine(text) { |
||||
|
this.lines.push(new Line(this.fileNameFull, this.lines.length + 1, text)); |
||||
|
} |
||||
|
|
||||
|
function readFile(fileName) { |
||||
|
var data = stripLastNewline(normalizeNewlines(readFileUtf8(fileName))); |
||||
|
var lines = data.split('\n').map((line, idx) => new Line(fileName, idx + 1, line)); |
||||
|
return new File(fileName, lines); |
||||
|
} |
||||
|
|
||||
|
function CombineSource(includePaths, includeExcluded) { |
||||
|
// Include path for finding include files which are amalgamated.
|
||||
|
this.includePaths = includePaths; |
||||
|
|
||||
|
// Include files specifically excluded from being inlined.
|
||||
|
this.includeExcluded = includeExcluded; |
||||
|
this.includeExcludedMap = createBareObject({}); |
||||
|
this.includeExcluded.forEach((inc) => { |
||||
|
this.includeExcludedMap[inc] = true; |
||||
|
}); |
||||
|
} |
||||
|
CombineSource.prototype.lookupInclude = function lookupInclude(incFileName) { |
||||
|
var incComponents = incFileName.split(/\/|\\/g); // Split include path, support forward slash and backslash
|
||||
|
|
||||
|
for (let path of this.includePaths) { |
||||
|
let fn = pathJoin.apply(null, [ path ].concat(incComponents)); |
||||
|
if (fileExists(fn)) { |
||||
|
return fn; |
||||
|
} |
||||
|
} |
||||
|
}; |
||||
|
CombineSource.prototype.addAutomaticUndefs = function addAutomaticUndefs(f) { |
||||
|
var defined = createBareObject({}); |
||||
|
|
||||
|
f.lines.forEach((line) => { |
||||
|
let matchDef = /^#define\s+(\w+).*$/.exec(line.data); |
||||
|
if (matchDef) { |
||||
|
//console.debug('defined: ' + matchDef[1]);
|
||||
|
defined[matchDef[1]] = true; |
||||
|
} |
||||
|
let matchUndef = /^#undef\s+(\w+).*$/.exec(line.data); |
||||
|
if (matchUndef) { |
||||
|
// Could just ignore #undef's here: we'd then emit
|
||||
|
// reliable #undef's (though maybe duplicates) at
|
||||
|
// the end.
|
||||
|
//console.debug('undefined: ' + matchUndef[1]);
|
||||
|
delete defined[matchUndef[1]]; |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
// Undefine anything that seems to be left defined. This not a 100%
|
||||
|
// process because some #undef's might be conditional which we don't
|
||||
|
// track at the moment. Note that it's safe to #undef something that's
|
||||
|
// not defined.
|
||||
|
|
||||
|
let keys = Object.getOwnPropertyNames(defined).sort(); |
||||
|
if (keys.length > 0) { |
||||
|
f.pushTextLine(''); |
||||
|
f.pushTextLine('/* automatic undefs */'); |
||||
|
keys.forEach((k) => { |
||||
|
console.debug('automatic #undef for ' + k); |
||||
|
f.pushTextLine('#undef ' + k); |
||||
|
}); |
||||
|
} |
||||
|
}; |
||||
|
CombineSource.prototype.combineFiles = function combineFiles(files, prologueFileName, lineDirectives) { |
||||
|
var _self = this; |
||||
|
var res = []; |
||||
|
var lineMap = []; // indicate combined source lines where uncombined file/line would change
|
||||
|
var metadata = { |
||||
|
line_map: lineMap |
||||
|
} |
||||
|
var currFileName, currLineNo; |
||||
|
var included = createBareObject({}); // headers already included
|
||||
|
|
||||
|
function emit(line) { |
||||
|
if (typeof line === 'string') { |
||||
|
res.push(line); |
||||
|
currLineNo++; |
||||
|
} else { |
||||
|
if (line.fileName !== currFileName || line.lineNo !== currLineNo) { |
||||
|
if (lineDirectives) { |
||||
|
res.push('#line ' + line.lineNo + ' ' + cStrEncode(line.fileName)); |
||||
|
} |
||||
|
lineMap.push({ original_file: line.fileName, |
||||
|
original_line: line.lineNo, |
||||
|
combined_line: res.length + 1 }); |
||||
|
} |
||||
|
res.push(line.data); |
||||
|
currFileName = line.fileName; |
||||
|
currLineNo = line.lineNo + 1; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Process a file, appending it to the result; the input may be a
|
||||
|
// source or an include file. #include directives are handled
|
||||
|
// recursively.
|
||||
|
function processFile(f) { |
||||
|
console.debug('process file: ' + f.fileName); |
||||
|
f.lines.forEach((line) => { |
||||
|
if (!line.data.startsWith('#include')) { |
||||
|
emit(line); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
let matchInc = /^#include\s+(<|")(.*?)(>|").*$/.exec(line.data); |
||||
|
if (!matchInc) { |
||||
|
throw new TypeError('could not match #include line: ' + line.data); |
||||
|
} |
||||
|
let incPath = matchInc[2]; |
||||
|
|
||||
|
if (_self.includeExcludedMap[incPath]) { |
||||
|
// Specific include files excluded from the
|
||||
|
// inlining / duplicate suppression process.
|
||||
|
emit(line); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
if (included[incPath]) { |
||||
|
// We suppress duplicate includes, both internal and
|
||||
|
// external, based on the assumption that includes are
|
||||
|
// not behind #if defined() checks. This is the case for
|
||||
|
// Duktape (except for the include files excluded).
|
||||
|
emit('/* #include ' + incPath + ' -> already included */'); |
||||
|
return; |
||||
|
} |
||||
|
included[incPath] = true; |
||||
|
|
||||
|
// An include file is considered "internal" and is amalgamated
|
||||
|
// if it is found in the include path provided by the user.
|
||||
|
|
||||
|
var incFile = _self.lookupInclude(incPath); |
||||
|
if (incFile) { |
||||
|
console.debug('include considered internal: ' + line.data + ' -> ' + incFile); |
||||
|
emit('/* #include ' + incPath + ' */'); |
||||
|
processFile(readFile(incFile)); |
||||
|
} else { |
||||
|
console.debug('include considered external: ' + line.data); |
||||
|
emit(line); // keep as is
|
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
if (prologueFileName) { |
||||
|
stripLastNewline(normalizeNewlines(readFileUtf8(prologueFileName))).split('\n').forEach((line) => { |
||||
|
emit(line); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
files.forEach((f) => { |
||||
|
processFile(f); |
||||
|
}); |
||||
|
|
||||
|
return { |
||||
|
result: res.join('\n') + '\n', |
||||
|
metadata |
||||
|
}; |
||||
|
}; |
||||
|
|
||||
|
function combineSources(args) { |
||||
|
var sourceFiles = args.sourceFiles; |
||||
|
var includePaths = args.includePaths; |
||||
|
var includeExcluded = args.includeExcluded || []; |
||||
|
var prologueFileName = args.prologueFileName; |
||||
|
var lineDirectives = !!args.lineDirectives; |
||||
|
|
||||
|
var comb = new CombineSource(includePaths, includeExcluded); |
||||
|
includePaths.forEach((inc) => { comb.includePaths.push(inc); }); |
||||
|
|
||||
|
// Read input files, add automatic #undefs.
|
||||
|
var files = sourceFiles.map((fn) => { |
||||
|
let res = readFile(fn); |
||||
|
comb.addAutomaticUndefs(res); |
||||
|
return res; |
||||
|
}); |
||||
|
|
||||
|
// Combine and return.
|
||||
|
var combinedSource, metadata; |
||||
|
({ result: combinedSource, metadata } = comb.combineFiles(files, prologueFileName, lineDirectives)); |
||||
|
return { combinedSource, metadata }; |
||||
|
} |
||||
|
exports.combineSources = combineSources; |
Loading…
Reference in new issue