Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add manifest loading from files #942

25 changes: 23 additions & 2 deletions src/Program.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2967,7 +2967,7 @@ describe('Program', () => {
});
});

describe('getManifest', () => {
describe('manifest', () => {
beforeEach(() => {
fsExtra.emptyDirSync(tempDir);
fsExtra.writeFileSync(`${tempDir}/manifest`, trim`
Expand All @@ -2989,7 +2989,28 @@ describe('Program', () => {
program.dispose();
});

it('loads the manifest', () => {
it('loads the manifest from project root', () => {
let manifest = program.getManifest();
testCommonManifestValues(manifest);
expect(manifest.get('bs_const')).to.equal('DEBUG=false');
});

it('loads the manifest from a FileObj', () => {
fsExtra.emptyDirSync(tempDir);
fsExtra.ensureDirSync(`${tempDir}/someDeepDir`);
fsExtra.writeFileSync(`${tempDir}/someDeepDir/manifest`, trim`
# Channel Details
title=sample manifest
major_version=2
minor_version=0
build_version=0
supports_input_launch=1
bs_const=DEBUG=false
`);
program.loadManifest({
src: `${tempDir}/someDeepDir/manifest`,
dest: 'manifest'
});
let manifest = program.getManifest();
testCommonManifestValues(manifest);
expect(manifest.get('bs_const')).to.equal('DEBUG=false');
Expand Down
88 changes: 51 additions & 37 deletions src/Program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1349,51 +1349,65 @@ export class Program {
return files;
}

private _manifest: Map<string, string>;

/**
* Get a map of the manifest information
* Modify a parsed manifest map by reading `bs_const` and injecting values from `options.manifest.bs_const`
* @param parsedManifest The manifest map to read from and modify
*/
public getManifest() {
if (!this._manifest) {
//load the manifest file.
//TODO update this to get the manifest from the files array or require it in the options...we shouldn't assume the location of the manifest
let manifestPath = path.join(this.options.rootDir, 'manifest');

let contents: string;
try {
//we only load this manifest once, so do it sync to improve speed downstream
contents = fsExtra.readFileSync(manifestPath, 'utf-8');
let parsedManifest = parseManifest(contents);

// Lift the bs_consts defined in the manifest
let bsConsts = getBsConst(parsedManifest, false);

// Override or delete any bs_consts defined in the bs config
for (const key in this.options?.manifest?.bs_const) {
const value = this.options.manifest.bs_const[key];
if (value === null) {
bsConsts.delete(key);
} else {
bsConsts.set(key, value);
}
}
private buildBsConstsIntoParsedManifest(parsedManifest: Map<string, string>) {
// Lift the bs_consts defined in the manifest
let bsConsts = getBsConst(parsedManifest, false);

// Override or delete any bs_consts defined in the bs config
for (const key in this.options?.manifest?.bs_const) {
const value = this.options.manifest.bs_const[key];
if (value === null) {
bsConsts.delete(key);
} else {
bsConsts.set(key, value);
}
}

// convert the new list of bs consts back into a string for the rest of the down stream systems to use
let constString = '';
for (const [key, value] of bsConsts) {
constString += `${constString !== '' ? ';' : ''}${key}=${value.toString()}`;
}
// convert the new list of bs consts back into a string for the rest of the down stream systems to use
let constString = '';
for (const [key, value] of bsConsts) {
constString += `${constString !== '' ? ';' : ''}${key}=${value.toString()}`;
}

// Set the updated bs_const value
parsedManifest.set('bs_const', constString);
// Set the updated bs_const value
parsedManifest.set('bs_const', constString);
}

this._manifest = parsedManifest;
} catch (err) {
this._manifest = new Map();
}
/**
* Try to find and load the manifest into memory
* @param manifestFileObj A pointer to a potential manifest file object found during loading
*/
public loadManifest(manifestFileObj?: FileObj) {
let manifestPath = manifestFileObj
? manifestFileObj.src
: path.join(this.options.rootDir, 'manifest');

try {
// we only load this manifest once, so do it sync to improve speed downstream
const contents = fsExtra.readFileSync(manifestPath, 'utf-8');
const parsedManifest = parseManifest(contents);
this.buildBsConstsIntoParsedManifest(parsedManifest);
this._manifest = parsedManifest;
} catch (e) {
this._manifest = new Map();
}
}

/**
* Get a map of the manifest information
*/
public getManifest() {
if (!this._manifest) {
this.loadManifest();
}
return this._manifest;
}
private _manifest: Map<string, string>;

public dispose() {
this.plugins.emit('beforeProgramDispose', { program: this });
Expand Down
22 changes: 22 additions & 0 deletions src/ProgramBuilder.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,28 @@ describe('ProgramBuilder', () => {
expect(stub.getCalls()).to.be.lengthOf(3);
});

it('finds and loads a manifest before all other files', async () => {
sinon.stub(util, 'getFilePaths').returns(Promise.resolve([{
src: 'file1.brs',
dest: 'file1.brs'
}, {
src: 'file2.bs',
dest: 'file2.bs'
}, {
src: 'file3.xml',
dest: 'file4.xml'
}, {
src: 'manifest',
dest: 'manifest'
}]));

let stubLoadManifest = sinon.stub(builder.program, 'loadManifest');
let stubSetFile = sinon.stub(builder.program, 'setFile');
sinon.stub(builder, 'getFileContents').returns(Promise.resolve(''));
await builder['loadAllFilesAST']();
expect(stubLoadManifest.calledBefore(stubSetFile)).to.be.true;
});

it('loads all type definitions first', async () => {
const requestedFiles = [] as string[];
builder['fileResolvers'].push((filePath) => {
Expand Down
61 changes: 22 additions & 39 deletions src/ProgramBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -466,59 +466,42 @@ export class ProgramBuilder {
*/
private async loadAllFilesAST() {
await this.logger.time(LogLevel.log, ['Parsing files'], async () => {
let errorCount = 0;
let files = await this.logger.time(LogLevel.debug, ['getFilePaths'], async () => {
return util.getFilePaths(this.options);
});
this.logger.trace('ProgramBuilder.loadAllFilesAST() files:', files);

const acceptableSourceExtensions = ['.bs', '.brs', '.xml'];
const typedefFiles = [] as FileObj[];
const nonTypedefFiles = [] as FileObj[];
const sourceFiles = [] as FileObj[];
let manifestFile: FileObj | null = null;

for (const file of files) {
const srcLower = file.src.toLowerCase();
if (srcLower.endsWith('.d.bs')) {
typedefFiles.push(file);
} else if (acceptableSourceExtensions.includes(path.extname(srcLower))) {
sourceFiles.push(file);
} else {
nonTypedefFiles.push(file);
if (file.dest.toLowerCase() === 'manifest') {
manifestFile = file;
}
}
}

//preload every type definition file first, which eliminates duplicate file loading
await Promise.all(
typedefFiles.map(async (fileObj) => {
try {
this.program.setFile(
fileObj,
await this.getFileContents(fileObj.src)
);
} catch (e) {
//log the error, but don't fail this process because the file might be fixable later
this.logger.log(e);
}
})
);

const acceptableExtensions = ['.bs', '.brs', '.xml'];
//parse every file other than the type definitions
await Promise.all(
nonTypedefFiles.map(async (fileObj) => {
try {
let fileExtension = path.extname(fileObj.src).toLowerCase();

//only process certain file types
if (acceptableExtensions.includes(fileExtension)) {
this.program.setFile(
fileObj,
await this.getFileContents(fileObj.src)
);
}
} catch (e) {
//log the error, but don't fail this process because the file might be fixable later
this.logger.log(e);
}
})
);
return errorCount;
if (manifestFile) {
this.program.loadManifest(manifestFile);
}

const loadFile = async (fileObj) => {
try {
this.program.setFile(fileObj, await this.getFileContents(fileObj.src));
} catch (e) {
this.logger.log(e); // log the error, but don't fail this process because the file might be fixable later
}
};
await Promise.all(typedefFiles.map(loadFile)); // preload every type definition file, which eliminates duplicate file loading
await Promise.all(sourceFiles.map(loadFile)); // parse source files
});
}

Expand Down