Merge pull request #14017 from electron/node-gn
build: build node with GN
This commit is contained in:
commit
dda6bb135a
10 changed files with 14 additions and 484 deletions
|
@ -205,7 +205,7 @@ gn-build-steps: &gn-build-steps
|
|||
ELECTRON_DISABLE_SECURITY_WARNINGS: 1
|
||||
command: |
|
||||
cd src
|
||||
ninja -C out/Default electron/build/node:headers
|
||||
ninja -C out/Default third_party/electron_node:headers
|
||||
export npm_config_nodedir="$PWD/out/Default/gen/node_headers"
|
||||
(cd electron/spec && npm install)
|
||||
python electron/script/lib/dbus_mock.py ./out/Default/electron electron/spec --ci --enable-logging
|
||||
|
|
20
BUILD.gn
20
BUILD.gn
|
@ -191,6 +191,7 @@ group("electron") {
|
|||
|
||||
static_library("electron_lib") {
|
||||
configs += [ "//v8:external_startup_data" ]
|
||||
configs += [ "//third_party/electron_node:node_internals" ]
|
||||
|
||||
public_configs = [
|
||||
":branding",
|
||||
|
@ -200,7 +201,7 @@ static_library("electron_lib") {
|
|||
deps = [
|
||||
":atom_js2c",
|
||||
"brightray",
|
||||
"build/node",
|
||||
"//third_party/electron_node:node_lib",
|
||||
"native_mate",
|
||||
"//base",
|
||||
"//base:i18n",
|
||||
|
@ -436,20 +437,6 @@ if (is_mac) {
|
|||
]
|
||||
}
|
||||
|
||||
if (is_component_build) {
|
||||
bundle_data("electron_framework_libraries") {
|
||||
public_deps = [
|
||||
"build/node",
|
||||
]
|
||||
sources = [
|
||||
"$root_out_dir/libnode.dylib",
|
||||
]
|
||||
outputs = [
|
||||
"{{bundle_contents_dir}}/Libraries/{{source_file_part}}",
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
bundle_data("electron_crashpad_helper") {
|
||||
sources = [
|
||||
"$root_out_dir/crashpad_handler",
|
||||
|
@ -478,9 +465,6 @@ if (is_mac) {
|
|||
if (!is_mas_build) {
|
||||
deps += [ ":electron_crashpad_helper" ]
|
||||
}
|
||||
if (is_component_build) {
|
||||
deps += [ ":electron_framework_libraries" ]
|
||||
}
|
||||
info_plist = "atom/common/resources/mac/Info.plist"
|
||||
extra_substitutions = [ "ATOM_BUNDLE_ID=$electron_mac_bundle_id.framework" ]
|
||||
|
||||
|
|
2
DEPS
2
DEPS
|
@ -4,7 +4,7 @@ vars = {
|
|||
'libchromiumcontent_revision':
|
||||
'd6411e421b0b2f0105d274ac4e5c90dc979463ca',
|
||||
'node_version':
|
||||
'v10.2.0-37-gbf0e061ddc',
|
||||
'ed9e26b86435bed3cf5e4dafa59aa90e85f7a4ab',
|
||||
|
||||
'chromium_git':
|
||||
'https://chromium.googlesource.com',
|
||||
|
|
|
@ -16,9 +16,14 @@ template("chdir_action") {
|
|||
])
|
||||
assert(defined(cwd), "Need cwd in $target_name")
|
||||
script = "//electron/build/run-in-dir.py"
|
||||
sources += [ invoker.script ]
|
||||
if (defined(sources)) {
|
||||
sources += [ invoker.script ]
|
||||
} else {
|
||||
assert(defined(inputs))
|
||||
inputs += [ invoker.script ]
|
||||
}
|
||||
args = [
|
||||
cwd,
|
||||
rebase_path(cwd),
|
||||
rebase_path(invoker.script),
|
||||
]
|
||||
args += invoker.args
|
||||
|
|
|
@ -1,318 +0,0 @@
|
|||
if (is_debug) {
|
||||
node_configuration = "Debug"
|
||||
} else {
|
||||
node_configuration = "Release"
|
||||
}
|
||||
|
||||
action("configure_node") {
|
||||
script = "//third_party/electron_node/configure"
|
||||
ssl_libname_suffix = ""
|
||||
if (is_win) {
|
||||
ssl_libname_suffix = ".dll"
|
||||
}
|
||||
args = [
|
||||
"--enable-static",
|
||||
"--release-urlbase=https://atom.io/download/electron",
|
||||
"--shared-openssl",
|
||||
"--shared-openssl-includes=" +
|
||||
rebase_path("//third_party/boringssl/src/include"),
|
||||
"--shared-openssl-libname=boringssl" + ssl_libname_suffix,
|
||||
"--shared-openssl-libpath=" + rebase_path(root_out_dir),
|
||||
"--without-npm",
|
||||
"--without-bundled-v8",
|
||||
"--without-dtrace",
|
||||
"--without-etw",
|
||||
"--without-perfctr",
|
||||
"--without-v8-platform",
|
||||
"--without-intl",
|
||||
"--prefix=",
|
||||
"--config-out-dir=" + rebase_path(target_gen_dir),
|
||||
"--no-run-gyp",
|
||||
"--dest-cpu=$target_cpu",
|
||||
]
|
||||
if (is_component_build) {
|
||||
args += [ "--shared" ]
|
||||
}
|
||||
outputs = [
|
||||
"$target_gen_dir/config.gypi",
|
||||
]
|
||||
}
|
||||
|
||||
action("gyp_node") {
|
||||
script = "//tools/gyp/gyp_main.py"
|
||||
deps = [
|
||||
":configure_node",
|
||||
]
|
||||
inputs = [
|
||||
"//third_party/electron_node/common.gypi",
|
||||
"//third_party/electron_node/node.gyp",
|
||||
"//third_party/electron_node/node.gypi",
|
||||
"$target_gen_dir/config.gypi",
|
||||
"node_override.gypi",
|
||||
]
|
||||
outputs = [
|
||||
"$target_out_dir/$node_configuration/build.ninja",
|
||||
]
|
||||
|
||||
# By default, node will build a dylib called something like
|
||||
# libnode.$node_module_version.dylib, which is inconvenient for our
|
||||
# purposes (since it makes the library's name unpredictable). This forces
|
||||
# it to drop the module_version from the filename and just produce
|
||||
# `libnode.dylib`.
|
||||
if (is_mac) {
|
||||
shlib_suffix = "dylib"
|
||||
} else if (is_win) {
|
||||
shlib_suffix = "dll"
|
||||
} else if (is_linux) {
|
||||
shlib_suffix = "so"
|
||||
}
|
||||
|
||||
args = [
|
||||
"-I",
|
||||
rebase_path("$target_gen_dir/config.gypi", root_build_dir),
|
||||
"-I",
|
||||
rebase_path("node_override.gypi", root_build_dir),
|
||||
"-I",
|
||||
rebase_path("//third_party/electron_node/common.gypi", root_build_dir),
|
||||
"-D",
|
||||
"component=shared_library",
|
||||
"-D",
|
||||
"target_arch=$target_cpu",
|
||||
"-D",
|
||||
"host_arch=$host_cpu",
|
||||
"-D",
|
||||
"config_gypi=" + rebase_path("$target_gen_dir/config.gypi"),
|
||||
|
||||
# This has _gn suffixed otherwise it gets overridden by a shlib_suffix
|
||||
# that's set in node's gyp files. Don't ask me 🤷
|
||||
"-D",
|
||||
"shlib_suffix_gn=" + shlib_suffix,
|
||||
"-D",
|
||||
"llvm_dir=" + rebase_path("//third_party/llvm-build/Release+Asserts"),
|
||||
"-D",
|
||||
"libcxx_dir=" + rebase_path("//buildtools/third_party/libc++"),
|
||||
"-D",
|
||||
"libcxxabi_dir=" + rebase_path("//buildtools/third_party/libc++abi"),
|
||||
"-D",
|
||||
"is_component_build=$is_component_build",
|
||||
|
||||
# bizarrely, gyp generates from the build root instead of from cwd
|
||||
"-Goutput_dir=./$target_out_dir",
|
||||
"-fninja",
|
||||
rebase_path("//third_party/electron_node/node.gyp", root_build_dir),
|
||||
]
|
||||
}
|
||||
|
||||
action("build_node") {
|
||||
deps = [
|
||||
":gyp_node",
|
||||
"//third_party/boringssl",
|
||||
"//third_party/icu",
|
||||
"//v8",
|
||||
"//v8:v8_libbase",
|
||||
"//v8:v8_libplatform",
|
||||
]
|
||||
script = "//electron/build/run-ninja.py"
|
||||
args = [
|
||||
"-C",
|
||||
rebase_path(target_out_dir, root_build_dir) + "/$node_configuration",
|
||||
"node_lib",
|
||||
"libuv",
|
||||
"nghttp2",
|
||||
"cares",
|
||||
"http_parser",
|
||||
"zlib",
|
||||
]
|
||||
if (is_mac) {
|
||||
if (is_component_build) {
|
||||
outputs = [
|
||||
"$target_out_dir/$node_configuration/libnode.dylib",
|
||||
]
|
||||
} else {
|
||||
outputs = [
|
||||
"$target_out_dir/$node_configuration/libnode.a",
|
||||
"$target_out_dir/$node_configuration/libcares.a",
|
||||
"$target_out_dir/$node_configuration/libhttp_parser.a",
|
||||
"$target_out_dir/$node_configuration/libnghttp2.a",
|
||||
"$target_out_dir/$node_configuration/libuv.a",
|
||||
"$target_out_dir/$node_configuration/libzlib.a",
|
||||
]
|
||||
}
|
||||
}
|
||||
if (is_linux) {
|
||||
if (is_component_build) {
|
||||
outputs = [
|
||||
"$target_out_dir/$node_configuration/lib/libnode.so",
|
||||
]
|
||||
} else {
|
||||
outputs = [
|
||||
"$target_out_dir/$node_configuration/obj/third_party/electron_node/libnode.a",
|
||||
"$target_out_dir/$node_configuration/obj/third_party/electron_node/deps/uv/libuv.a",
|
||||
"$target_out_dir/$node_configuration/obj/third_party/electron_node/deps/nghttp2/libnghttp2.a",
|
||||
"$target_out_dir/$node_configuration/obj/third_party/electron_node/deps/cares/libcares.a",
|
||||
"$target_out_dir/$node_configuration/obj/third_party/electron_node/deps/http_parser/libhttp_parser.a",
|
||||
"$target_out_dir/$node_configuration/obj/third_party/electron_node/deps/zlib/libzlib.a",
|
||||
]
|
||||
}
|
||||
}
|
||||
if (is_win) {
|
||||
if (is_component_build) {
|
||||
outputs = [
|
||||
"$target_out_dir/$node_configuration/node.dll.lib",
|
||||
"$target_out_dir/$node_configuration/node.dll",
|
||||
]
|
||||
} else {
|
||||
outputs = [
|
||||
"$target_out_dir/$node_configuration/obj/third_party/electron_node/node.lib",
|
||||
"$target_out_dir/$node_configuration/obj/third_party/electron_node/deps/uv/libuv.lib",
|
||||
"$target_out_dir/$node_configuration/obj/third_party/electron_node/deps/nghttp2/nghttp2.lib",
|
||||
"$target_out_dir/$node_configuration/obj/third_party/electron_node/deps/cares/cares.lib",
|
||||
"$target_out_dir/$node_configuration/obj/third_party/electron_node/deps/http_parser/http_parser.lib",
|
||||
"$target_out_dir/$node_configuration/obj/third_party/electron_node/deps/zlib/zlib.lib",
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
node_libs = get_target_outputs(":build_node")
|
||||
|
||||
copy("copy_node") {
|
||||
deps = [
|
||||
":build_node",
|
||||
]
|
||||
sources = node_libs
|
||||
outputs = [
|
||||
"$root_out_dir/{{source_file_part}}",
|
||||
]
|
||||
}
|
||||
|
||||
config("node_config") {
|
||||
include_dirs = [
|
||||
"//third_party/electron_node/src",
|
||||
"//third_party/electron_node/deps/uv/include",
|
||||
"//third_party/electron_node/deps/cares/include",
|
||||
]
|
||||
if (is_win && is_component_build) {
|
||||
# Windows builds need both the .dll and the .dll.lib copied, but only the
|
||||
# .dll.lib goes in the `libs` list.
|
||||
libs = [ node_libs[0] ]
|
||||
} else {
|
||||
libs = node_libs
|
||||
}
|
||||
cflags_cc = [ "-Wno-deprecated-declarations" ]
|
||||
defines = [
|
||||
# We need to access internal implementations of Node.
|
||||
"NODE_WANT_INTERNALS=1",
|
||||
"HAVE_OPENSSL=1",
|
||||
"HAVE_INSPECTOR=1",
|
||||
]
|
||||
if (is_component_build) {
|
||||
defines += [ "NODE_SHARED_MODE" ]
|
||||
}
|
||||
}
|
||||
|
||||
group("node") {
|
||||
public_configs = [ ":node_config" ]
|
||||
public_deps = [
|
||||
":copy_node",
|
||||
]
|
||||
}
|
||||
|
||||
node_headers_dir = "$root_gen_dir/node_headers/include/node"
|
||||
|
||||
copy("node_headers") {
|
||||
deps = [
|
||||
":configure_node",
|
||||
]
|
||||
sources = [
|
||||
"$target_gen_dir/config.gypi",
|
||||
"//third_party/electron_node/common.gypi",
|
||||
"//third_party/electron_node/src/callback_scope.h",
|
||||
"//third_party/electron_node/src/core.h",
|
||||
"//third_party/electron_node/src/exceptions.h",
|
||||
"//third_party/electron_node/src/node.h",
|
||||
"//third_party/electron_node/src/node_api.h",
|
||||
"//third_party/electron_node/src/node_api_types.h",
|
||||
"//third_party/electron_node/src/node_buffer.h",
|
||||
"//third_party/electron_node/src/node_object_wrap.h",
|
||||
"//third_party/electron_node/src/node_version.h",
|
||||
]
|
||||
outputs = [
|
||||
"$node_headers_dir/{{source_file_part}}",
|
||||
]
|
||||
}
|
||||
|
||||
copy("v8_headers") {
|
||||
sources = [
|
||||
"//v8/include/v8-inspector-protocol.h",
|
||||
"//v8/include/v8-inspector.h",
|
||||
"//v8/include/v8-platform.h",
|
||||
"//v8/include/v8-profiler.h",
|
||||
"//v8/include/v8-testing.h",
|
||||
"//v8/include/v8-util.h",
|
||||
"//v8/include/v8-value-serializer-version.h",
|
||||
"//v8/include/v8-version-string.h",
|
||||
"//v8/include/v8-version.h",
|
||||
"//v8/include/v8.h",
|
||||
"//v8/include/v8config.h",
|
||||
]
|
||||
outputs = [
|
||||
"$node_headers_dir/{{source_file_part}}",
|
||||
]
|
||||
}
|
||||
|
||||
copy("v8_platform_headers") {
|
||||
sources = [
|
||||
"//v8/include/libplatform/libplatform-export.h",
|
||||
"//v8/include/libplatform/libplatform.h",
|
||||
"//v8/include/libplatform/v8-tracing.h",
|
||||
]
|
||||
outputs = [
|
||||
"$node_headers_dir/libplatform/{{source_file_part}}",
|
||||
]
|
||||
}
|
||||
|
||||
copy("uv_headers") {
|
||||
sources = [
|
||||
"//third_party/electron_node/deps/uv/include/android-ifaddrs.h",
|
||||
"//third_party/electron_node/deps/uv/include/pthread-barrier.h",
|
||||
"//third_party/electron_node/deps/uv/include/stdint-msvc2008.h",
|
||||
"//third_party/electron_node/deps/uv/include/tree.h",
|
||||
"//third_party/electron_node/deps/uv/include/uv-aix.h",
|
||||
"//third_party/electron_node/deps/uv/include/uv-bsd.h",
|
||||
"//third_party/electron_node/deps/uv/include/uv-darwin.h",
|
||||
"//third_party/electron_node/deps/uv/include/uv-errno.h",
|
||||
"//third_party/electron_node/deps/uv/include/uv-linux.h",
|
||||
"//third_party/electron_node/deps/uv/include/uv-os390.h",
|
||||
"//third_party/electron_node/deps/uv/include/uv-posix.h",
|
||||
"//third_party/electron_node/deps/uv/include/uv-sunos.h",
|
||||
"//third_party/electron_node/deps/uv/include/uv-threadpool.h",
|
||||
"//third_party/electron_node/deps/uv/include/uv-unix.h",
|
||||
"//third_party/electron_node/deps/uv/include/uv-version.h",
|
||||
"//third_party/electron_node/deps/uv/include/uv-win.h",
|
||||
"//third_party/electron_node/deps/uv/include/uv.h",
|
||||
]
|
||||
outputs = [
|
||||
"$node_headers_dir/{{source_file_part}}",
|
||||
]
|
||||
}
|
||||
|
||||
copy("zlib_headers") {
|
||||
sources = [
|
||||
"//third_party/electron_node/deps/zlib/zconf.h",
|
||||
"//third_party/electron_node/deps/zlib/zlib.h",
|
||||
]
|
||||
outputs = [
|
||||
"$node_headers_dir/{{source_file_part}}",
|
||||
]
|
||||
}
|
||||
|
||||
group("headers") {
|
||||
public_deps = [
|
||||
":node_headers",
|
||||
":uv_headers",
|
||||
":v8_headers",
|
||||
":v8_platform_headers",
|
||||
":zlib_headers",
|
||||
]
|
||||
}
|
|
@ -1,106 +0,0 @@
|
|||
{
|
||||
'variables': {
|
||||
# Node disables the inspector unless icu is enabled. But node doesn't know
|
||||
# that we're building v8 with icu, so force it on.
|
||||
'v8_enable_inspector': 1,
|
||||
|
||||
'shlib_suffix': '<(shlib_suffix_gn)',
|
||||
|
||||
'is_component_build%': 'false',
|
||||
},
|
||||
'conditions': [
|
||||
['OS=="linux"', {
|
||||
'make_global_settings': [
|
||||
['CC', '<(llvm_dir)/bin/clang'],
|
||||
['CXX', '<(llvm_dir)/bin/clang++'],
|
||||
['CC.host', '$(CC)'],
|
||||
['CXX.host', '$(CXX)'],
|
||||
],
|
||||
'target_defaults': {
|
||||
'target_conditions': [
|
||||
['_toolset=="target"', {
|
||||
'cflags_cc': [
|
||||
'-std=gnu++14',
|
||||
'-nostdinc++',
|
||||
'-isystem<(libcxx_dir)/trunk/include',
|
||||
'-isystem<(libcxxabi_dir)/trunk/include',
|
||||
],
|
||||
'ldflags': [
|
||||
'-nostdlib++',
|
||||
],
|
||||
'libraries': [
|
||||
'../../../../../../libc++.so',
|
||||
],
|
||||
}]
|
||||
]
|
||||
}
|
||||
}],
|
||||
['OS=="win"', {
|
||||
'make_global_settings': [
|
||||
['CC', '<(llvm_dir)/bin/clang-cl'],
|
||||
# Also defining CXX doesn't appear to be necessary.
|
||||
]
|
||||
}],
|
||||
],
|
||||
'target_defaults': {
|
||||
'target_conditions': [
|
||||
['_target_name=="node_lib"', {
|
||||
'include_dirs': [
|
||||
'../../../v8',
|
||||
'../../../v8/include',
|
||||
'../../../third_party/icu/source/common',
|
||||
'../../../third_party/icu/source/i18n',
|
||||
],
|
||||
'defines': [
|
||||
'EVP_CTRL_AEAD_SET_IVLEN=EVP_CTRL_GCM_SET_IVLEN',
|
||||
'EVP_CTRL_CCM_SET_TAG=EVP_CTRL_GCM_SET_TAG',
|
||||
'EVP_CTRL_AEAD_GET_TAG=EVP_CTRL_GCM_GET_TAG',
|
||||
'WIN32_LEAN_AND_MEAN',
|
||||
],
|
||||
'conditions': [
|
||||
['OS=="win"', {
|
||||
'conditions': [
|
||||
['is_component_build=="true"', {
|
||||
'libraries': [
|
||||
'-lv8.dll',
|
||||
'-lv8_libbase.dll',
|
||||
'-lv8_libplatform.dll',
|
||||
'-licuuc.dll',
|
||||
'-ldbghelp',
|
||||
],
|
||||
'msvs_settings': {
|
||||
# Change location of some hard-coded paths.
|
||||
'VCLinkerTool': {
|
||||
'AdditionalOptions!': [
|
||||
'/WHOLEARCHIVE:<(PRODUCT_DIR)\\lib\\zlib<(STATIC_LIB_SUFFIX)',
|
||||
'/WHOLEARCHIVE:<(PRODUCT_DIR)\\lib\\libuv<(STATIC_LIB_SUFFIX)',
|
||||
],
|
||||
'AdditionalOptions': [
|
||||
'/WHOLEARCHIVE:<(PRODUCT_DIR)\\obj\\third_party\\electron_node\\deps\\zlib\\zlib<(STATIC_LIB_SUFFIX)',
|
||||
'/WHOLEARCHIVE:<(PRODUCT_DIR)\\obj\\third_party\\electron_node\\deps\\uv\\libuv<(STATIC_LIB_SUFFIX)',
|
||||
],
|
||||
},
|
||||
},
|
||||
}, {
|
||||
'defines': [
|
||||
'U_STATIC_IMPLEMENTATION'
|
||||
]
|
||||
}]
|
||||
],
|
||||
}, {
|
||||
'conditions': [
|
||||
['is_component_build=="true"', {
|
||||
'libraries': [
|
||||
'-lv8',
|
||||
'-lv8_libbase',
|
||||
'-lv8_libplatform',
|
||||
'-licuuc',
|
||||
]
|
||||
}]
|
||||
]
|
||||
}]
|
||||
]
|
||||
}],
|
||||
],
|
||||
},
|
||||
}
|
|
@ -1,35 +0,0 @@
|
|||
{
|
||||
'variables': {
|
||||
'llvm_dir': '<(chromium_src_dir)/third_party/llvm-build/Release+Asserts',
|
||||
},
|
||||
'conditions': [
|
||||
['clang==1', {
|
||||
'make_global_settings': [
|
||||
['CC', '<(llvm_dir)/bin/clang'],
|
||||
['CXX', '<(llvm_dir)/bin/clang++'],
|
||||
['CC.host', '$(CC)'],
|
||||
['CXX.host', '$(CXX)'],
|
||||
],
|
||||
'target_defaults': {
|
||||
'target_conditions': [
|
||||
['OS=="linux" and _toolset=="target"', {
|
||||
'cflags_cc': [
|
||||
'-std=gnu++14',
|
||||
'-nostdinc++',
|
||||
'-isystem<(chromium_src_dir)/buildtools/third_party/libc++/trunk/include',
|
||||
'-isystem<(chromium_src_dir)/buildtools/third_party/libc++abi/trunk/include',
|
||||
],
|
||||
'ldflags': [
|
||||
'-nostdlib++',
|
||||
],
|
||||
}],
|
||||
['OS=="linux" and _toolset=="host"', {
|
||||
'cflags_cc': [
|
||||
'-std=gnu++14',
|
||||
],
|
||||
}],
|
||||
],
|
||||
},
|
||||
}], # clang==1
|
||||
],
|
||||
}
|
|
@ -126,7 +126,7 @@ generate build headers for the modules to compile against, run the following
|
|||
under `src/` directory.
|
||||
|
||||
```sh
|
||||
$ ninja -C out/Default electron/build/node:headers
|
||||
$ ninja -C out/Default third_party/electron_node:headers
|
||||
# Install the test modules with the generated headers
|
||||
$ (cd electron/spec && npm i --nodedir=../../out/Default/gen/node_headers)
|
||||
```
|
||||
|
|
|
@ -6,7 +6,7 @@ config("native_mate_config") {
|
|||
|
||||
source_set("native_mate") {
|
||||
deps = [
|
||||
"../build/node",
|
||||
"//third_party/electron_node:node_lib",
|
||||
"//base",
|
||||
"//net",
|
||||
"//v8:v8_headers",
|
||||
|
|
|
@ -43,7 +43,7 @@ phases:
|
|||
- bash: |
|
||||
set +e
|
||||
cd src
|
||||
ninja -C out/Default electron/build/node:headers
|
||||
ninja -C out/Default third_party/electron_node:headers
|
||||
export npm_config_nodedir="$PWD/out/Default/gen/node_headers"
|
||||
(cd electron/spec && npm install)
|
||||
./out/Default/Electron.app/Contents/MacOS/Electron electron/spec --ci --enable-logging
|
||||
|
|
Loading…
Reference in a new issue