Skip to content

Commit 5af95da

Browse files
authored
Improve SCons support (#18945)
Made it so that `emscons` also provides the `EMSCRIPTEN_ROOT` environment variable and made the SCons tool provide the environment variables that `emmake` (and others) does. I was in the spirit of #18279 and started making the SCons tool search for the root directory by scanning the `PATH` instead of relying on that variable, but then I realised that it would be much simpler for `emscons` to provide it. I also noted that, unlike `emmake` and `emconfigure`, the `emscons` command would not set some environment variables — notably to me, the `pkg-config`-related ones. Because the default SCons behaviour is to not propagate environment variables to child processes, it made little sense to add them in `emscons.py`. Instead I made the SCons tool directly import the relevant Python package from the root directory and get only the relevant environment variables from there, so that it would not touch any other. For that I had to create a different function, because `get_building_env()` returns the inherited environment variables as well. For the tests I made the SCons script aware of what environment variables to expect by passing to it an argument containing them in JSON form. Is that too hacky?
1 parent cb5af84 commit 5af95da

File tree

8 files changed

+153
-37
lines changed

8 files changed

+153
-37
lines changed

ChangeLog.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,12 @@ See docs/process.md for more on how version tagging works.
2020

2121
3.1.64 (in development)
2222
-----------------------
23+
- Updated the SCons tool to not require the `EMSCRIPTEN_ROOT` environment
24+
variable, in which case it will assume that SCons will find the binaries in
25+
(its) `PATH`.
26+
- Updated `emscons` to apply the `EMSCRIPTEN_ROOT`, `EMSCONS_PKG_CONFIG_LIBDIR`
27+
and `EMSCONS_PKG_CONFIG_PATH` environment variables. The SCons tool will use
28+
last two to set up `PKG_CONFIG_LIBDIR` and `PKG_CONFIG_PATH` respectively.
2329

2430
3.1.63 - 07/12/24
2531
-----------------

emscons.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,15 @@
1111
import os
1212
import subprocess
1313
import sys
14-
from tools import utils
14+
from tools import building, utils
1515

1616
tool_path = utils.path_from_root('tools/scons/site_scons/site_tools/emscripten')
17+
building_env = building.get_building_env()
1718

1819
env = os.environ.copy()
1920
env['EMSCRIPTEN_TOOL_PATH'] = tool_path
21+
env['EMSCRIPTEN_ROOT'] = utils.path_from_root()
22+
env['EMSCONS_PKG_CONFIG_LIBDIR'] = building_env['PKG_CONFIG_LIBDIR']
23+
env['EMSCONS_PKG_CONFIG_PATH'] = building_env['PKG_CONFIG_PATH']
2024

2125
sys.exit(subprocess.call(sys.argv[1:], env=env))

test/scons/env/SConstruct

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import json
2+
import os
3+
4+
5+
def assert_(value, expected):
6+
if value != expected:
7+
print("assert failed:", value, expected)
8+
Exit(1)
9+
10+
11+
env = Environment(toolpath=[os.environ.get('EMSCRIPTEN_TOOL_PATH')])
12+
env['ENV'] = dict()
13+
14+
env.Tool('emscripten')
15+
16+
AddOption(
17+
'--expected-env',
18+
dest='expected',
19+
type='string',
20+
nargs=1,
21+
action='store',
22+
help='JSON with the expected environment parameters',
23+
)
24+
25+
expected_env = json.loads(GetOption('expected'))
26+
expected_env_env = expected_env.pop('ENV')
27+
28+
for key in expected_env_env:
29+
assert_(env['ENV'].get(key), expected_env_env[key])
30+
31+
for key in expected_env:
32+
assert_(env.get(key), expected_env[key])
33+
34+
Exit(0)
File renamed without changes.
File renamed without changes.
File renamed without changes.

test/test_other.py

Lines changed: 66 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
if __name__ == '__main__':
2828
raise Exception('do not run this file directly; do something like: test/runner other')
2929

30+
from tools.building import get_building_env
3031
from tools.shared import config
3132
from tools.shared import EMCC, EMXX, EMAR, EMRANLIB, FILE_PACKAGER, LLVM_NM
3233
from tools.shared import CLANG_CC, CLANG_CXX, LLVM_AR, LLVM_DWARFDUMP, LLVM_DWP, EMCMAKE, EMCONFIGURE, WASM_LD
@@ -3038,25 +3039,84 @@ def test_dwarf_sourcemap_names(self):
30383039
@with_env_modify({'EMSCRIPTEN_ROOT': path_from_root()})
30393040
def test_scons(self):
30403041
# this test copies the site_scons directory alongside the test
3041-
shutil.copytree(test_file('scons'), 'test')
3042+
shutil.copytree(test_file('scons/simple'), 'test')
30423043
shutil.copytree(path_from_root('tools/scons/site_scons'), Path('test/site_scons'))
30433044
with utils.chdir('test'):
30443045
self.run_process(['scons'])
30453046
output = self.run_js('scons_integration.js', assert_returncode=5)
30463047
self.assertContained('If you see this - the world is all right!', output)
30473048

30483049
@requires_scons
3049-
@with_env_modify({'EMSCRIPTEN_TOOLPATH': path_from_root('tools/scons/site_scons'),
3050-
'EMSCRIPTEN_ROOT': path_from_root()})
3050+
@with_env_modify({
3051+
'EMSCRIPTEN_ROOT': path_from_root(),
3052+
'EMSCONS_PKG_CONFIG_LIBDIR': '/pkg/config/libdir',
3053+
'EMSCONS_PKG_CONFIG_PATH': '/pkg/config/path',
3054+
})
3055+
def test_scons_env(self):
3056+
# this test copies the site_scons directory alongside the test
3057+
shutil.copytree(test_file('scons/env'), 'test')
3058+
shutil.copytree(path_from_root('tools/scons/site_scons'), Path('test/site_scons'))
3059+
3060+
expected_to_propagate = json.dumps({
3061+
'CC': path_from_root('emcc'),
3062+
'CXX': path_from_root('em++'),
3063+
'AR': path_from_root('emar'),
3064+
'RANLIB': path_from_root('emranlib'),
3065+
'ENV': {
3066+
'PKG_CONFIG_LIBDIR': '/pkg/config/libdir',
3067+
'PKG_CONFIG_PATH': '/pkg/config/path',
3068+
}
3069+
})
3070+
3071+
with utils.chdir('test'):
3072+
self.run_process(['scons', '--expected-env', expected_to_propagate])
3073+
3074+
@requires_scons
3075+
def test_scons_env_no_emscons(self):
3076+
shutil.copytree(test_file('scons/env'), 'test')
3077+
shutil.copytree(path_from_root('tools/scons/site_scons'), Path('test/site_scons'))
3078+
3079+
expected_to_propagate = json.dumps({
3080+
'CC': 'emcc',
3081+
'CXX': 'em++',
3082+
'AR': 'emar',
3083+
'RANLIB': 'emranlib',
3084+
'ENV': {
3085+
'PKG_CONFIG_LIBDIR': None,
3086+
'PKG_CONFIG_PATH': None,
3087+
}
3088+
})
3089+
3090+
with utils.chdir('test'):
3091+
self.run_process(['scons', '--expected-env', expected_to_propagate])
3092+
3093+
@requires_scons
30513094
def test_emscons(self):
3052-
# uses the emscons wrapper which requires EMSCRIPTEN_TOOLPATH to find
3053-
# site_scons
3054-
shutil.copytree(test_file('scons'), 'test')
3095+
shutil.copytree(test_file('scons/simple'), 'test')
30553096
with utils.chdir('test'):
30563097
self.run_process([path_from_root('emscons'), 'scons'])
30573098
output = self.run_js('scons_integration.js', assert_returncode=5)
30583099
self.assertContained('If you see this - the world is all right!', output)
30593100

3101+
@requires_scons
3102+
def test_emscons_env(self):
3103+
shutil.copytree(test_file('scons/env'), 'test')
3104+
3105+
building_env = get_building_env()
3106+
expected_to_propagate = json.dumps({
3107+
'CC': path_from_root('emcc'),
3108+
'CXX': path_from_root('em++'),
3109+
'AR': path_from_root('emar'),
3110+
'RANLIB': path_from_root('emranlib'),
3111+
'ENV': {
3112+
'PKG_CONFIG_LIBDIR': building_env['PKG_CONFIG_LIBDIR'],
3113+
'PKG_CONFIG_PATH': building_env['PKG_CONFIG_PATH'],
3114+
}
3115+
})
3116+
3117+
with utils.chdir('test'):
3118+
self.run_process([path_from_root('emscons'), 'scons', '--expected-env', expected_to_propagate])
3119+
30603120
def test_embind_fail(self):
30613121
out = self.expect_fail([EMXX, test_file('embind/test_unsigned.cpp')])
30623122
self.assertContained("undefined symbol: _embind_register_function", out)

tools/scons/site_scons/site_tools/emscripten/emscripten.py

Lines changed: 42 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -8,48 +8,60 @@
88
import os
99

1010

11-
def generate(env, emscripten_path=None, **kw):
12-
""" SCons tool entry point """
11+
def path_to_bin(emscripten_root, bin_name):
12+
if emscripten_root is None:
13+
return bin_name
14+
else:
15+
return os.path.join(emscripten_root, bin_name)
16+
1317

14-
if emscripten_path is None:
15-
emscripten_path = os.environ.get('EMSCRIPTEN_ROOT')
16-
if not emscripten_path:
17-
raise 'Unable to find emscripten. Please set EMSCRIPTEN_ROOT'
18+
def generate(env, **kw):
19+
""" SCons tool entry point """
1820

1921
# SCons does not by default invoke the compiler with the
20-
# environment variabls from the parent calling process,
22+
# environment variables from the parent calling process,
2123
# so manually route all environment variables referenced
2224
# by Emscripten to the child.
23-
for var in ['EM_CACHE', 'EMCC_DEBUG', 'EM_CONFIG',
24-
'EMMAKEN_JUST_CONFIGURE', 'EMCC_CFLAGS', 'EMCC_TEMP_DIR',
25-
'EMCC_AUTODEBUG', 'EM_COMPILER_WRAPPER',
26-
'MOZ_DISABLE_AUTO_SAFE_MODE', 'EMCC_STDERR_FILE',
27-
'EMSCRIPTEN_SUPPRESS_USAGE_WARNING', 'NODE_PATH', 'EMCC_JSOPT_MIN_CHUNK_SIZE',
28-
'EMCC_JSOPT_MAX_CHUNK_SIZE', 'EMCC_CORES', 'EMCC_NO_OPT_SORT',
29-
'EMCC_BUILD_DIR', 'EMCC_DEBUG_SAVE', 'EMCC_SKIP_SANITY_CHECK',
30-
'EM_PKG_CONFIG_PATH', 'EMCC_CLOSURE_ARGS',
31-
'EMCC_FORCE_STDLIBS', 'EMCC_ONLY_FORCED_STDLIBS', 'EM_PORTS', 'IDL_CHECKS', 'IDL_VERBOSE']:
32-
if os.environ.get(var):
33-
env['ENV'][var] = os.environ.get(var)
34-
try:
35-
emscPath = emscripten_path.abspath
36-
except:
37-
emscPath = emscripten_path
38-
39-
env.Replace(CC=os.path.join(emscPath, "emcc"))
40-
env.Replace(CXX=os.path.join(emscPath, "em++"))
25+
emscripten_env_vars = ['EM_CACHE', 'EMCC_DEBUG', 'EM_CONFIG',
26+
'EMMAKEN_JUST_CONFIGURE', 'EMCC_CFLAGS', 'EMCC_TEMP_DIR',
27+
'EMCC_AUTODEBUG', 'EM_COMPILER_WRAPPER', 'MOZ_DISABLE_AUTO_SAFE_MODE',
28+
'EMCC_STDERR_FILE', 'EMSCRIPTEN_SUPPRESS_USAGE_WARNING', 'NODE_PATH',
29+
'EMCC_JSOPT_MIN_CHUNK_SIZE', 'EMCC_JSOPT_MAX_CHUNK_SIZE',
30+
'EMCC_CORES', 'EMCC_NO_OPT_SORT', 'EMCC_BUILD_DIR',
31+
'EMCC_DEBUG_SAVE', 'EMCC_SKIP_SANITY_CHECK', 'EM_PKG_CONFIG_PATH',
32+
'EMCC_CLOSURE_ARGS', 'EMCC_FORCE_STDLIBS',
33+
'EMCC_ONLY_FORCED_STDLIBS', 'EM_PORTS', 'IDL_CHECKS', 'IDL_VERBOSE']
34+
35+
pkg_config_vars = {
36+
'EMSCONS_PKG_CONFIG_LIBDIR': 'PKG_CONFIG_LIBDIR',
37+
'EMSCONS_PKG_CONFIG_PATH': 'PKG_CONFIG_PATH',
38+
}
39+
40+
for var in emscripten_env_vars:
41+
if var in os.environ:
42+
env['ENV'][var] = os.environ[var]
43+
44+
for var in pkg_config_vars:
45+
if var in os.environ:
46+
real_key = pkg_config_vars[var]
47+
env['ENV'][real_key] = os.environ[var]
48+
49+
# Binary paths will be constructed from here if available.
50+
# Otherwise they are assumed to be in the PATH.
51+
emscripten_root = os.environ.get('EMSCRIPTEN_ROOT')
52+
53+
env.Replace(CC=path_to_bin(emscripten_root, 'emcc'))
54+
env.Replace(CXX=path_to_bin(emscripten_root, 'em++'))
4155
# LINK uses smark_link by default which will choose
4256
# either emcc or em++ depending on if there are any C++ sources
4357
# in the program, so no need to change that.
4458
# SHLINK and LDMODULE should use LINK so no
4559
# need to change them here
4660

47-
env.Replace(AR=os.path.join(emscPath, "emar"))
48-
env.Replace(RANLIB=os.path.join(emscPath, "emranlib"))
61+
env.Replace(AR=path_to_bin(emscripten_root, 'emar'))
62+
env.Replace(RANLIB=path_to_bin(emscripten_root, 'emranlib'))
4963

50-
env.Replace(OBJSUFFIX=[".js", ".bc", ".o"][2])
51-
env.Replace(LIBSUFFIX=[".js", ".bc", ".o"][2])
52-
env.Replace(PROGSUFFIX=[".html", ".js"][1])
64+
env.Replace(PROGSUFFIX='.js')
5365

5466

5567
def exists(env):

0 commit comments

Comments
 (0)