feat(broccoli): add diffing MergeTrees plugin
Closes #1815 Closes #2064
This commit is contained in:
parent
41ae8e76f0
commit
4ee3fdaf7f
47
tools/broccoli/broccoli-merge-trees.spec.ts
Normal file
47
tools/broccoli/broccoli-merge-trees.spec.ts
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
/// <reference path="../typings/node/node.d.ts" />
|
||||||
|
/// <reference path="../typings/jasmine/jasmine.d.ts" />
|
||||||
|
|
||||||
|
let mockfs = require('mock-fs');
|
||||||
|
import fs = require('fs');
|
||||||
|
import {TreeDiffer} from './tree-differ';
|
||||||
|
import {MergeTrees} from './broccoli-merge-trees';
|
||||||
|
|
||||||
|
describe('MergeTrees', () => {
|
||||||
|
afterEach(() => mockfs.restore());
|
||||||
|
|
||||||
|
function mergeTrees(inputPaths, cachePath, options) {
|
||||||
|
return new MergeTrees(inputPaths, cachePath, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
function MakeTreeDiffers(rootDirs) {
|
||||||
|
let treeDiffers = rootDirs.map((rootDir) => new TreeDiffer('MergeTrees', rootDir));
|
||||||
|
treeDiffers.diffTrees = () => { return treeDiffers.map(tree => tree.diffTree()); };
|
||||||
|
return treeDiffers;
|
||||||
|
}
|
||||||
|
|
||||||
|
function read(path) { return fs.readFileSync(path, "utf-8"); }
|
||||||
|
|
||||||
|
it('should copy the file from the right-most inputTree', () => {
|
||||||
|
let testDir: any = {
|
||||||
|
'tree1': {'foo.js': mockfs.file({content: 'tree1/foo.js content', mtime: new Date(1000)})},
|
||||||
|
'tree2': {'foo.js': mockfs.file({content: 'tree2/foo.js content', mtime: new Date(1000)})},
|
||||||
|
'tree3': {'foo.js': mockfs.file({content: 'tree3/foo.js content', mtime: new Date(1000)})}
|
||||||
|
};
|
||||||
|
mockfs(testDir);
|
||||||
|
let treeDiffer = MakeTreeDiffers(['tree1', 'tree2', 'tree3']);
|
||||||
|
let treeMerger = mergeTrees(['tree1', 'tree2', 'tree3'], 'dest', {});
|
||||||
|
treeMerger.rebuild(treeDiffer.diffTrees());
|
||||||
|
expect(read('dest/foo.js')).toBe('tree3/foo.js content');
|
||||||
|
|
||||||
|
delete testDir.tree2['foo.js'];
|
||||||
|
delete testDir.tree3['foo.js'];
|
||||||
|
mockfs(testDir);
|
||||||
|
treeMerger.rebuild(treeDiffer.diffTrees());
|
||||||
|
expect(read('dest/foo.js')).toBe('tree1/foo.js content');
|
||||||
|
|
||||||
|
testDir.tree2['foo.js'] = mockfs.file({content: 'tree2/foo.js content', mtime: new Date(1000)});
|
||||||
|
mockfs(testDir);
|
||||||
|
treeMerger.rebuild(treeDiffer.diffTrees());
|
||||||
|
expect(read('dest/foo.js')).toBe('tree2/foo.js content');
|
||||||
|
});
|
||||||
|
});
|
86
tools/broccoli/broccoli-merge-trees.ts
Normal file
86
tools/broccoli/broccoli-merge-trees.ts
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
import fs = require('fs');
|
||||||
|
import fse = require('fs-extra');
|
||||||
|
import path = require('path');
|
||||||
|
var symlinkOrCopySync = require('symlink-or-copy').sync;
|
||||||
|
import {wrapDiffingPlugin, DiffingBroccoliPlugin, DiffResult} from './diffing-broccoli-plugin';
|
||||||
|
|
||||||
|
function pathExists(filePath) {
|
||||||
|
try {
|
||||||
|
if (fs.statSync(filePath)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
if (e.code !== "ENOENT") {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function outputFileSync(sourcePath, destPath) {
|
||||||
|
let dirname = path.dirname(destPath);
|
||||||
|
fse.mkdirsSync(dirname, {fs: fs});
|
||||||
|
fse.removeSync(destPath);
|
||||||
|
symlinkOrCopySync(sourcePath, destPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
export class MergeTrees implements DiffingBroccoliPlugin {
|
||||||
|
private mergedPaths: {[key: string]: number} = Object.create(null);
|
||||||
|
|
||||||
|
constructor(public inputPaths: string[], public cachePath: string, public options) {}
|
||||||
|
|
||||||
|
rebuild(treeDiffs: DiffResult[]) {
|
||||||
|
treeDiffs.forEach((treeDiff: DiffResult, index) => {
|
||||||
|
let inputPath = this.inputPaths[index];
|
||||||
|
let existsLater = (relativePath) => {
|
||||||
|
for (let i = treeDiffs.length - 1; i > index; --i) {
|
||||||
|
if (pathExists(path.join(this.inputPaths[i], relativePath))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
let existsSooner = (relativePath) => {
|
||||||
|
for (let i = index - 1; i >= 0; --i) {
|
||||||
|
if (pathExists(path.join(this.inputPaths[i], relativePath))) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
};
|
||||||
|
treeDiff.changedPaths.forEach((changedPath) => {
|
||||||
|
let inputTreeIndex = this.mergedPaths[changedPath];
|
||||||
|
if (inputTreeIndex !== index && !existsLater(changedPath)) {
|
||||||
|
inputTreeIndex = this.mergedPaths[changedPath] = index;
|
||||||
|
let sourcePath = path.join(inputPath, changedPath);
|
||||||
|
let destPath = path.join(this.cachePath, changedPath);
|
||||||
|
outputFileSync(sourcePath, destPath);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
treeDiff.removedPaths.forEach((removedPath) => {
|
||||||
|
let inputTreeIndex = this.mergedPaths[removedPath];
|
||||||
|
|
||||||
|
// if inputTreeIndex !== index, this same file was handled during
|
||||||
|
// changedPaths handling
|
||||||
|
if (inputTreeIndex !== index) return;
|
||||||
|
|
||||||
|
let destPath = path.join(this.cachePath, removedPath);
|
||||||
|
fse.removeSync(destPath);
|
||||||
|
let newInputTreeIndex = existsSooner(removedPath);
|
||||||
|
|
||||||
|
// Update cached value (to either newInputTreeIndex value or undefined)
|
||||||
|
this.mergedPaths[removedPath] = newInputTreeIndex;
|
||||||
|
|
||||||
|
if (newInputTreeIndex >= 0) {
|
||||||
|
// Copy the file from the newInputTreeIndex inputPath if necessary.
|
||||||
|
let newInputPath = this.inputPaths[newInputTreeIndex];
|
||||||
|
let sourcePath = path.join(newInputPath, removedPath);
|
||||||
|
outputFileSync(sourcePath, destPath);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default wrapDiffingPlugin(MergeTrees);
|
@ -3,7 +3,7 @@
|
|||||||
var Funnel = require('broccoli-funnel');
|
var Funnel = require('broccoli-funnel');
|
||||||
var flatten = require('broccoli-flatten');
|
var flatten = require('broccoli-flatten');
|
||||||
var htmlReplace = require('../html-replace');
|
var htmlReplace = require('../html-replace');
|
||||||
var mergeTrees = require('broccoli-merge-trees');
|
import mergeTrees from '../broccoli-merge-trees';
|
||||||
var path = require('path');
|
var path = require('path');
|
||||||
var replace = require('broccoli-replace');
|
var replace = require('broccoli-replace');
|
||||||
var stew = require('broccoli-stew');
|
var stew = require('broccoli-stew');
|
||||||
|
@ -6,7 +6,7 @@ import {MultiCopy} from './../multi_copy';
|
|||||||
import destCopy from '../broccoli-dest-copy';
|
import destCopy from '../broccoli-dest-copy';
|
||||||
var Funnel = require('broccoli-funnel');
|
var Funnel = require('broccoli-funnel');
|
||||||
var glob = require('glob');
|
var glob = require('glob');
|
||||||
var mergeTrees = require('broccoli-merge-trees');
|
import mergeTrees from '../broccoli-merge-trees';
|
||||||
var path = require('path');
|
var path = require('path');
|
||||||
var renderLodashTemplate = require('broccoli-lodash');
|
var renderLodashTemplate = require('broccoli-lodash');
|
||||||
var replace = require('broccoli-replace');
|
var replace = require('broccoli-replace');
|
||||||
|
@ -4,7 +4,7 @@ import destCopy from '../broccoli-dest-copy';
|
|||||||
import compileWithTypescript from '../broccoli-typescript';
|
import compileWithTypescript from '../broccoli-typescript';
|
||||||
import transpileWithTraceur from '../traceur/index';
|
import transpileWithTraceur from '../traceur/index';
|
||||||
var Funnel = require('broccoli-funnel');
|
var Funnel = require('broccoli-funnel');
|
||||||
var mergeTrees = require('broccoli-merge-trees');
|
import mergeTrees from '../broccoli-merge-trees';
|
||||||
var path = require('path');
|
var path = require('path');
|
||||||
var renderLodashTemplate = require('broccoli-lodash');
|
var renderLodashTemplate = require('broccoli-lodash');
|
||||||
var replace = require('broccoli-replace');
|
var replace = require('broccoli-replace');
|
||||||
|
Loading…
x
Reference in New Issue
Block a user