From 55f86fcf1ee0f745ae87dd759d16bfea5ba130fe Mon Sep 17 00:00:00 2001 From: Sam Clegg Date: Fri, 17 Jan 2025 11:22:08 -0800 Subject: [PATCH] Improve #include handling for JS library files Files references via #include can now either relative to the including file or come from the system library. Previously only relative paths were allowed. Also we now give a nicer error message when the file is not found. --- src/parseTools.mjs | 29 +++++++++++++++++++++++++---- src/utility.mjs | 4 +++- test/test_other.py | 16 ++++++++++++++++ 3 files changed, 44 insertions(+), 5 deletions(-) diff --git a/src/parseTools.mjs b/src/parseTools.mjs index 02f3b43aca1fc..e993b1a1a5782 100644 --- a/src/parseTools.mjs +++ b/src/parseTools.mjs @@ -9,6 +9,7 @@ */ import * as path from 'node:path'; +import {existsSync} from 'node:fs'; import { addToCompileTimeContext, @@ -19,6 +20,7 @@ import { runInMacroContext, setCurrentFile, warn, + srcDir, } from './utility.mjs'; const FOUR_GB = 4 * 1024 * 1024 * 1024; @@ -40,6 +42,24 @@ export function processMacros(text, filename) { }); } +function findIncludeFile(filename, currentDir) { + if (path.isAbsolute(filename)) { + return existsSync(filename) ? filename : null; + } + + // Search for include files either relative to the including file, + // or in the src root directory. + const includePath = [currentDir, srcDir]; + for (const p of includePath) { + const f = path.join(p, filename); + if (existsSync(f)) { + return f; + } + } + + return null; +} + // Simple #if/else/endif preprocessing for a file. Checks if the // ident checked is true in our global. // Also handles #include x.js (similar to C #include ) @@ -125,11 +145,12 @@ export function preprocess(filename) { if (includeFile.startsWith('"')) { includeFile = includeFile.substr(1, includeFile.length - 2); } - // Include files are always relative to the current file being processed - if (!path.isAbsolute(includeFile)) { - includeFile = path.join(path.dirname(filename), includeFile); + const absPath = findIncludeFile(includeFile, path.dirname(filename)); + if (!absPath) { + error(`${filename}:${i + 1}: file not found: ${includeFile}`); + continue; } - const result = preprocess(includeFile); + const result = preprocess(absPath); if (result) { ret += `// include: ${includeFile}\n`; ret += result; diff --git a/src/utility.mjs b/src/utility.mjs index c31d7fc986684..d31bbb2ace06d 100644 --- a/src/utility.mjs +++ b/src/utility.mjs @@ -233,11 +233,13 @@ export function read(filename) { // Use import.meta.dirname here once we drop support for node v18. const __dirname = url.fileURLToPath(new URL('.', import.meta.url)); +export const srcDir = __dirname; + // Returns an absolute path for a file, resolving it relative to this script // (i.e. relative to the src/ directory). export function localFile(filename) { assert(!path.isAbsolute(filename)); - return path.join(__dirname, filename); + return path.join(srcDir, filename); } // Anything needed by the script that we load below must be added to the diff --git a/test/test_other.py b/test/test_other.py index 197af3ee369f3..138ba9d2dcca1 100644 --- a/test/test_other.py +++ b/test/test_other.py @@ -4922,6 +4922,22 @@ def test_jslib_errors(self): self.assertContained('error_in_js_libraries.js:5: #error This is an error string!', err) self.assertContained('error_in_js_libraries.js:7: #error This is a second error string!', err) + def test_jslib_include(self): + create_file('inc.js', ''' + let MY_VAR = 10; + ''') + create_file('foo.js', ''' + // Include a file from system directory + #include "arrayUtils.js" + // Include a local file. + #include "inc.js" + ''') + self.run_process([EMCC, test_file('hello_world.c'), '--js-library', 'foo.js']) + + delete_file('inc.js') + err = self.expect_fail([EMCC, test_file('hello_world.c'), '--js-library', 'foo.js']) + self.assertContained('foo.js:5: file not found: inc.js', err) + def test_postjs_errors(self): create_file('post.js', '#preprocess\n#error This is an error') err = self.expect_fail([EMCC, test_file('hello_world.c'), '--post-js', 'post.js'])