diff --git a/src/Program.spec.ts b/src/Program.spec.ts index 5367495cf..04b405980 100644 --- a/src/Program.spec.ts +++ b/src/Program.spec.ts @@ -2967,7 +2967,7 @@ describe('Program', () => { }); }); - describe('getManifest', () => { + describe('manifest', () => { beforeEach(() => { fsExtra.emptyDirSync(tempDir); fsExtra.writeFileSync(`${tempDir}/manifest`, trim` @@ -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'); diff --git a/src/Program.ts b/src/Program.ts index 066b17285..27022543f 100644 --- a/src/Program.ts +++ b/src/Program.ts @@ -1349,51 +1349,65 @@ export class Program { return files; } + private _manifest: Map; + /** - * 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) { + // 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; public dispose() { this.plugins.emit('beforeProgramDispose', { program: this }); diff --git a/src/ProgramBuilder.spec.ts b/src/ProgramBuilder.spec.ts index f6a72c712..9f2403411 100644 --- a/src/ProgramBuilder.spec.ts +++ b/src/ProgramBuilder.spec.ts @@ -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) => { diff --git a/src/ProgramBuilder.ts b/src/ProgramBuilder.ts index 434cb5ad9..946f9e9da 100644 --- a/src/ProgramBuilder.ts +++ b/src/ProgramBuilder.ts @@ -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 }); }