mirror of
https://github.com/mkxp-z/mkxp-z.git
synced 2025-08-26 16:53:45 +02:00
Simplify the libretro Emscripten build process
This commit is contained in:
parent
f4659619b0
commit
80324f0e94
4 changed files with 56 additions and 60 deletions
66
.github/workflows/autobuild.yml
vendored
66
.github/workflows/autobuild.yml
vendored
|
@ -384,13 +384,13 @@ jobs:
|
||||||
mkdir "$runner_temp"/retro-phase2
|
mkdir "$runner_temp"/retro-phase2
|
||||||
cd build
|
cd build
|
||||||
CLICOLOR_FORCE=1 ninja -v
|
CLICOLOR_FORCE=1 ninja -v
|
||||||
strip libretro-mkxp-z.dll
|
strip mkxp-z_libretro.dll
|
||||||
mv libretro-mkxp-z.dll "$runner_temp"/retro-phase2/mkxp-z_libretro.dll
|
mv mkxp-z_libretro.dll "$runner_temp"/retro-phase2
|
||||||
|
|
||||||
- name: Upload artifact
|
- name: Upload artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: libretro-mkxp-z.windows.${{ github.event_name == 'pull_request' && format('PR{0}', github.event.number) || github.ref_name }}-${{ steps.short-sha.outputs.sha }}
|
name: mkxp-z_libretro.windows.${{ github.event_name == 'pull_request' && format('PR{0}', github.event.number) || github.ref_name }}-${{ steps.short-sha.outputs.sha }}
|
||||||
path: ${{ runner.temp }}/retro-phase2
|
path: ${{ runner.temp }}/retro-phase2
|
||||||
|
|
||||||
build-retro-linux-gnu:
|
build-retro-linux-gnu:
|
||||||
|
@ -481,16 +481,16 @@ jobs:
|
||||||
CLICOLOR_FORCE=1 ninja -v
|
CLICOLOR_FORCE=1 ninja -v
|
||||||
if [ '${{ matrix.arch_mkxpz }}' != 'x86_64' ]
|
if [ '${{ matrix.arch_mkxpz }}' != 'x86_64' ]
|
||||||
then
|
then
|
||||||
${{ matrix.arch_gcc }}-strip libretro-mkxp-z.so
|
${{ matrix.arch_gcc }}-strip mkxp-z_libretro.so
|
||||||
else
|
else
|
||||||
strip libretro-mkxp-z.so
|
strip mkxp-z_libretro.so
|
||||||
fi
|
fi
|
||||||
mv libretro-mkxp-z.so ${{ runner.temp }}/retro-phase2/mkxp-z_libretro.so
|
mv mkxp-z_libretro.so ${{ runner.temp }}/retro-phase2
|
||||||
|
|
||||||
- name: Upload artifact
|
- name: Upload artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: libretro-mkxp-z.linux-gnu.${{ matrix.arch_mkxpz }}.${{ github.event_name == 'pull_request' && format('PR{0}', github.event.number) || github.ref_name }}-${{ steps.short-sha.outputs.sha }}
|
name: mkxp-z_libretro.linux-gnu.${{ matrix.arch_mkxpz }}.${{ github.event_name == 'pull_request' && format('PR{0}', github.event.number) || github.ref_name }}-${{ steps.short-sha.outputs.sha }}
|
||||||
path: ${{ runner.temp }}/retro-phase2
|
path: ${{ runner.temp }}/retro-phase2
|
||||||
|
|
||||||
build-retro-linux-musl:
|
build-retro-linux-musl:
|
||||||
|
@ -619,13 +619,13 @@ jobs:
|
||||||
mkdir ${{ runner.temp }}/retro-phase2
|
mkdir ${{ runner.temp }}/retro-phase2
|
||||||
cd build
|
cd build
|
||||||
CLICOLOR_FORCE=1 ninja -v
|
CLICOLOR_FORCE=1 ninja -v
|
||||||
llvm-strip libretro-mkxp-z.so
|
llvm-strip mkxp-z_libretro.so
|
||||||
mv libretro-mkxp-z.so ${{ runner.temp }}/retro-phase2/mkxp-z_libretro.so
|
mv mkxp-z_libretro.so ${{ runner.temp }}/retro-phase2
|
||||||
|
|
||||||
- name: Upload artifact
|
- name: Upload artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: libretro-mkxp-z.linux-musl.${{ matrix.arch_mkxpz }}.${{ github.event_name == 'pull_request' && format('PR{0}', github.event.number) || github.ref_name }}-${{ steps.short-sha.outputs.sha }}
|
name: mkxp-z_libretro.linux-musl.${{ matrix.arch_mkxpz }}.${{ github.event_name == 'pull_request' && format('PR{0}', github.event.number) || github.ref_name }}-${{ steps.short-sha.outputs.sha }}
|
||||||
path: ${{ runner.temp }}/retro-phase2
|
path: ${{ runner.temp }}/retro-phase2
|
||||||
|
|
||||||
build-retro-android:
|
build-retro-android:
|
||||||
|
@ -710,13 +710,13 @@ jobs:
|
||||||
mkdir ${{ runner.temp }}/retro-phase2
|
mkdir ${{ runner.temp }}/retro-phase2
|
||||||
cd build
|
cd build
|
||||||
CLICOLOR_FORCE=1 ninja -v
|
CLICOLOR_FORCE=1 ninja -v
|
||||||
llvm-strip libretro-mkxp-z.so
|
llvm-strip mkxp-z_libretro.so
|
||||||
mv libretro-mkxp-z.so ${{ runner.temp }}/retro-phase2/mkxp-z_libretro.so
|
mv mkxp-z_libretro.so ${{ runner.temp }}/retro-phase2
|
||||||
|
|
||||||
- name: Upload artifact
|
- name: Upload artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: libretro-mkxp-z.android.${{ matrix.arch_mkxpz }}.${{ github.event_name == 'pull_request' && format('PR{0}', github.event.number) || github.ref_name }}-${{ steps.short-sha.outputs.sha }}
|
name: mkxp-z_libretro.android.${{ matrix.arch_mkxpz }}.${{ github.event_name == 'pull_request' && format('PR{0}', github.event.number) || github.ref_name }}-${{ steps.short-sha.outputs.sha }}
|
||||||
path: ${{ runner.temp }}/retro-phase2
|
path: ${{ runner.temp }}/retro-phase2
|
||||||
|
|
||||||
build-retro-darwin:
|
build-retro-darwin:
|
||||||
|
@ -777,13 +777,13 @@ jobs:
|
||||||
mkdir ${{ runner.temp }}/retro-phase2
|
mkdir ${{ runner.temp }}/retro-phase2
|
||||||
cd build
|
cd build
|
||||||
CLICOLOR_FORCE=1 ninja -v
|
CLICOLOR_FORCE=1 ninja -v
|
||||||
strip -x libretro-mkxp-z.dylib
|
strip -x mkxp-z_libretro.dylib
|
||||||
mv libretro-mkxp-z.dylib ${{ runner.temp }}/retro-phase2/mkxp-z_libretro.dylib
|
mv mkxp-z_libretro.dylib ${{ runner.temp }}/retro-phase2
|
||||||
|
|
||||||
- name: Upload artifact
|
- name: Upload artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: libretro-mkxp-z.darwin.${{ matrix.arch_mkxpz }}.${{ github.event_name == 'pull_request' && format('PR{0}', github.event.number) || github.ref_name }}-${{ steps.short-sha.outputs.sha }}
|
name: mkxp-z_libretro.darwin.${{ matrix.arch_mkxpz }}.${{ github.event_name == 'pull_request' && format('PR{0}', github.event.number) || github.ref_name }}-${{ steps.short-sha.outputs.sha }}
|
||||||
path: ${{ runner.temp }}/retro-phase2
|
path: ${{ runner.temp }}/retro-phase2
|
||||||
|
|
||||||
build-retro-emscripten:
|
build-retro-emscripten:
|
||||||
|
@ -836,36 +836,22 @@ jobs:
|
||||||
mkdir ${{ runner.temp }}/retro-phase2
|
mkdir ${{ runner.temp }}/retro-phase2
|
||||||
cd build
|
cd build
|
||||||
CLICOLOR_FORCE=1 ninja -v
|
CLICOLOR_FORCE=1 ninja -v
|
||||||
|
mv mkxp-z_libretro.bc ${{ runner.temp }}
|
||||||
- name: Link core
|
|
||||||
run: |
|
|
||||||
cd build
|
|
||||||
mkdir mkxp-link
|
|
||||||
cd mkxp-link
|
|
||||||
set -o xtrace
|
|
||||||
find ../ -type f -name '*.a' -print0 | while IFS= read -r -d '' path; do
|
|
||||||
filename="$(basename "$path")"
|
|
||||||
mkdir "$filename.p"
|
|
||||||
cd "$filename.p"
|
|
||||||
emar x "../$path"
|
|
||||||
cd ..
|
|
||||||
done
|
|
||||||
find ./ -type f -name '*.o' -print0 | xargs -0 emcc -r -o ${{ runner.temp }}/libretro-mkxp-z.bc
|
|
||||||
|
|
||||||
- name: Build RetroArch
|
- name: Build RetroArch
|
||||||
run: |
|
run: |
|
||||||
cd ${{ runner.temp }}
|
cd ${{ runner.temp }}
|
||||||
git clone https://github.com/libretro/RetroArch retroarch --depth 1 -b $(curl -s https://api.github.com/repos/libretro/RetroArch/releases/latest | jq -r '.tag_name')
|
git clone https://github.com/libretro/RetroArch retroarch --depth 1 -b $(curl -s https://api.github.com/repos/libretro/RetroArch/releases/latest | jq -r '.tag_name')
|
||||||
mv libretro-mkxp-z.bc retroarch/libretro_emscripten.bc
|
mv mkxp-z_libretro.bc retroarch/libretro_emscripten.bc
|
||||||
cd retroarch
|
cd retroarch
|
||||||
emmake make -f Makefile.emscripten LIBRETRO=mkxp-z
|
CLICOLOR_FORCE=1 emmake make -f Makefile.emscripten LIBRETRO=mkxp-z
|
||||||
mv mkxp-z_libretro.wasm ${{ runner.temp }}/retro-phase2
|
mv mkxp-z_libretro.wasm ${{ runner.temp }}/retro-phase2
|
||||||
mv mkxp-z_libretro.js ${{ runner.temp }}/retro-phase2
|
mv mkxp-z_libretro.js ${{ runner.temp }}/retro-phase2
|
||||||
|
|
||||||
- name: Upload artifact
|
- name: Upload artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: libretro-mkxp-z.emscripten.${{ github.event_name == 'pull_request' && format('PR{0}', github.event.number) || github.ref_name }}-${{ steps.short-sha.outputs.sha }}
|
name: mkxp-z_libretro.emscripten.${{ github.event_name == 'pull_request' && format('PR{0}', github.event.number) || github.ref_name }}-${{ steps.short-sha.outputs.sha }}
|
||||||
path: ${{ runner.temp }}/retro-phase2
|
path: ${{ runner.temp }}/retro-phase2
|
||||||
|
|
||||||
build-retro-vita:
|
build-retro-vita:
|
||||||
|
@ -914,12 +900,12 @@ jobs:
|
||||||
mkdir ~/retro-phase2
|
mkdir ~/retro-phase2
|
||||||
cd build
|
cd build
|
||||||
CLICOLOR_FORCE=1 ninja -v
|
CLICOLOR_FORCE=1 ninja -v
|
||||||
mv libretro-mkxp-z.a ~/retro-phase2/mkxp-z_libretro.a
|
mv mkxp-z_libretro.a ~/retro-phase2
|
||||||
|
|
||||||
- name: Upload artifact
|
- name: Upload artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: libretro-mkxp-z.vita.${{ github.event_name == 'pull_request' && format('PR{0}', github.event.number) || github.ref_name }}-${{ steps.short-sha.outputs.sha }}
|
name: mkxp-z_libretro.vita.${{ github.event_name == 'pull_request' && format('PR{0}', github.event.number) || github.ref_name }}-${{ steps.short-sha.outputs.sha }}
|
||||||
path: ~/retro-phase2
|
path: ~/retro-phase2
|
||||||
|
|
||||||
build-retro-wiiu:
|
build-retro-wiiu:
|
||||||
|
@ -970,12 +956,12 @@ jobs:
|
||||||
mkdir ~/retro-phase2
|
mkdir ~/retro-phase2
|
||||||
cd build
|
cd build
|
||||||
CLICOLOR_FORCE=1 ninja -v
|
CLICOLOR_FORCE=1 ninja -v
|
||||||
mv libretro-mkxp-z.a ~/retro-phase2/mkxp-z_libretro.a
|
mv mkxp-z_libretro.a ~/retro-phase2
|
||||||
|
|
||||||
- name: Upload artifact
|
- name: Upload artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: libretro-mkxp-z.wiiu.${{ github.event_name == 'pull_request' && format('PR{0}', github.event.number) || github.ref_name }}-${{ steps.short-sha.outputs.sha }}
|
name: mkxp-z_libretro.wiiu.${{ github.event_name == 'pull_request' && format('PR{0}', github.event.number) || github.ref_name }}-${{ steps.short-sha.outputs.sha }}
|
||||||
path: ~/retro-phase2
|
path: ~/retro-phase2
|
||||||
|
|
||||||
build-retro-switch:
|
build-retro-switch:
|
||||||
|
@ -1027,10 +1013,10 @@ jobs:
|
||||||
mkdir ~/retro-phase2
|
mkdir ~/retro-phase2
|
||||||
cd build
|
cd build
|
||||||
CLICOLOR_FORCE=1 ninja -v
|
CLICOLOR_FORCE=1 ninja -v
|
||||||
mv libretro-mkxp-z.a ~/retro-phase2/mkxp-z_libretro.a
|
mv mkxp-z_libretro.a ~/retro-phase2
|
||||||
|
|
||||||
- name: Upload artifact
|
- name: Upload artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: libretro-mkxp-z.switch.${{ github.event_name == 'pull_request' && format('PR{0}', github.event.number) || github.ref_name }}-${{ steps.short-sha.outputs.sha }}
|
name: mkxp-z_libretro.switch.${{ github.event_name == 'pull_request' && format('PR{0}', github.event.number) || github.ref_name }}-${{ steps.short-sha.outputs.sha }}
|
||||||
path: ~/retro-phase2
|
path: ~/retro-phase2
|
||||||
|
|
19
meson.build
19
meson.build
|
@ -85,6 +85,7 @@ if get_option('retro') == true
|
||||||
'ALSOFT_EXAMPLES': false,
|
'ALSOFT_EXAMPLES': false,
|
||||||
'ALSOFT_UPDATE_BUILD_VERSION': false,
|
'ALSOFT_UPDATE_BUILD_VERSION': false,
|
||||||
'ALSOFT_EMBED_HRTF_DATA': false,
|
'ALSOFT_EMBED_HRTF_DATA': false,
|
||||||
|
'ALSOFT_RTKIT': false,
|
||||||
'ALSOFT_BACKEND_PIPEWIRE': false,
|
'ALSOFT_BACKEND_PIPEWIRE': false,
|
||||||
'ALSOFT_BACKEND_PULSEAUDIO': false,
|
'ALSOFT_BACKEND_PULSEAUDIO': false,
|
||||||
'ALSOFT_BACKEND_ALSA': false,
|
'ALSOFT_BACKEND_ALSA': false,
|
||||||
|
@ -174,6 +175,12 @@ if get_option('retro') == true
|
||||||
retro_link_args += '-static-libstdc++'
|
retro_link_args += '-static-libstdc++'
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
# When targeting Emscripten, we need to build a relocatable object
|
||||||
|
if host_system == 'emscripten'
|
||||||
|
compilers['cpp'].has_link_argument('-r', required: true)
|
||||||
|
retro_link_args += '-r'
|
||||||
|
endif
|
||||||
|
|
||||||
# If possible, stop the linker from reexporting the symbols from the static libraries we use (e.g. zlib)
|
# If possible, stop the linker from reexporting the symbols from the static libraries we use (e.g. zlib)
|
||||||
if host_system != 'emscripten' and compilers['cpp'].has_link_argument('-Wl,--version-script,' + join_paths(meson.current_source_dir(), 'retro/link.T')) # Only works with GNU linker and LLVM linker
|
if host_system != 'emscripten' and compilers['cpp'].has_link_argument('-Wl,--version-script,' + join_paths(meson.current_source_dir(), 'retro/link.T')) # Only works with GNU linker and LLVM linker
|
||||||
retro_link_args += '-Wl,--version-script,' + join_paths(meson.current_source_dir(), 'retro/link.T')
|
retro_link_args += '-Wl,--version-script,' + join_paths(meson.current_source_dir(), 'retro/link.T')
|
||||||
|
@ -206,8 +213,11 @@ if get_option('retro') == true
|
||||||
retro_deps += compilers['cpp'].find_library('iconv')
|
retro_deps += compilers['cpp'].find_library('iconv')
|
||||||
endif
|
endif
|
||||||
|
|
||||||
library(
|
build_target(
|
||||||
'retro-' + meson.project_name(),
|
meson.project_name() + '_libretro',
|
||||||
|
name_prefix: '',
|
||||||
|
name_suffix: host_system == 'emscripten' ? 'bc' : [],
|
||||||
|
target_type: host_system == 'emscripten' ? 'executable' : 'library',
|
||||||
dependencies: retro_deps,
|
dependencies: retro_deps,
|
||||||
c_args: [
|
c_args: [
|
||||||
'-fno-optimize-sibling-calls',
|
'-fno-optimize-sibling-calls',
|
||||||
|
@ -223,7 +233,6 @@ if get_option('retro') == true
|
||||||
cpp_args: ['-Wno-unused-command-line-argument'] + retro_cppflags + retro_defines,
|
cpp_args: ['-Wno-unused-command-line-argument'] + retro_cppflags + retro_defines,
|
||||||
link_args: retro_link_args,
|
link_args: retro_link_args,
|
||||||
gnu_symbol_visibility: 'hidden',
|
gnu_symbol_visibility: 'hidden',
|
||||||
install: true, # Prevents Meson from creating thin archives when building with `--default-library static`; see https://github.com/mesonbuild/meson/issues/9479
|
|
||||||
include_directories: [
|
include_directories: [
|
||||||
include_directories('.'),
|
include_directories('.'),
|
||||||
include_directories('src'),
|
include_directories('src'),
|
||||||
|
@ -248,7 +257,7 @@ if get_option('retro') == true
|
||||||
include_directories(join_paths(retro_phase1, 'sdl/include')),
|
include_directories(join_paths(retro_phase1, 'sdl/include')),
|
||||||
],
|
],
|
||||||
sources: [
|
sources: [
|
||||||
vcs_tag(command: ['git', 'rev-parse', '--short', 'HEAD'], fallback: 'unknown', input: 'src/git-hash.h.in', output: 'git-hash.h', replace_string: '@MKXPZ_GIT_HASH@'),
|
vcs_tag(command: ['git', 'rev-parse', '--short', 'HEAD'], fallback: 'unknown', input: 'src/git-hash.h.in', output: 'git-hash.h'),
|
||||||
'src/core.cpp',
|
'src/core.cpp',
|
||||||
'src/sharedstate.cpp',
|
'src/sharedstate.cpp',
|
||||||
'src/audio/alstream.cpp',
|
'src/audio/alstream.cpp',
|
||||||
|
@ -294,7 +303,7 @@ if get_option('retro') == true
|
||||||
)
|
)
|
||||||
else
|
else
|
||||||
|
|
||||||
global_sources += vcs_tag(command: ['git', 'rev-parse', '--short', 'HEAD'], fallback: 'unknown', input: 'src/git-hash.h.in', output: 'git-hash.h', replace_string: '@MKXPZ_GIT_HASH@')
|
global_sources += vcs_tag(command: ['git', 'rev-parse', '--short', 'HEAD'], fallback: 'unknown', input: 'src/git-hash.h.in', output: 'git-hash.h')
|
||||||
if host_endian == 'big'
|
if host_endian == 'big'
|
||||||
global_args += '-DMKXPZ_BIG_ENDIAN'
|
global_args += '-DMKXPZ_BIG_ENDIAN'
|
||||||
global_args += '-DWABT_BIG_ENDIAN=1'
|
global_args += '-DWABT_BIG_ENDIAN=1'
|
||||||
|
|
29
src/core.cpp
29
src/core.cpp
|
@ -317,6 +317,7 @@ extern "C" RETRO_API void retro_init() {
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" RETRO_API void retro_deinit() {
|
extern "C" RETRO_API void retro_deinit() {
|
||||||
|
std::free(sound_buf);
|
||||||
std::free(frame_buf);
|
std::free(frame_buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -359,27 +360,27 @@ extern "C" RETRO_API void retro_reset() {
|
||||||
extern "C" RETRO_API void retro_run() {
|
extern "C" RETRO_API void retro_run() {
|
||||||
input_poll();
|
input_poll();
|
||||||
|
|
||||||
if (!mkxp_retro::sandbox.has_value()) {
|
if (mkxp_retro::sandbox.has_value()) {
|
||||||
return;
|
try {
|
||||||
}
|
if (sb().run<struct main>()) {
|
||||||
|
log_printf(RETRO_LOG_INFO, "[Sandbox] Ruby terminated normally\n");
|
||||||
try {
|
deinit_sandbox();
|
||||||
if (sb().run<struct main>()) {
|
return;
|
||||||
log_printf(RETRO_LOG_INFO, "[Sandbox] Ruby terminated normally\n");
|
}
|
||||||
|
} catch (SandboxException) {
|
||||||
|
log_printf(RETRO_LOG_ERROR, "[Sandbox] Ruby threw an exception\n");
|
||||||
deinit_sandbox();
|
deinit_sandbox();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} catch (SandboxException) {
|
|
||||||
log_printf(RETRO_LOG_ERROR, "[Sandbox] Ruby threw an exception\n");
|
|
||||||
deinit_sandbox();
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
video_refresh(frame_buf, 640, 480, 640 * 4);
|
video_refresh(frame_buf, 640, 480, 640 * 4);
|
||||||
|
|
||||||
audio->render();
|
if (mkxp_retro::sandbox.has_value()) {
|
||||||
alcRenderSamplesSOFT(al_device, sound_buf, 735);
|
audio->render();
|
||||||
audio_sample_batch(sound_buf, 735);
|
alcRenderSamplesSOFT(al_device, sound_buf, 735);
|
||||||
|
audio_sample_batch(sound_buf, 735);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" RETRO_API size_t retro_serialize_size() {
|
extern "C" RETRO_API size_t retro_serialize_size() {
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
#ifndef MKXPZ_GIT_HASH
|
#ifndef MKXPZ_GIT_HASH
|
||||||
#define MKXPZ_GIT_HASH "@MKXPZ_GIT_HASH@"
|
# define MKXPZ_GIT_HASH "@VCS_TAG@"
|
||||||
#endif /* MKXPZ_GIT_HASH */
|
#endif /* MKXPZ_GIT_HASH */
|
||||||
|
|
Loading…
Add table
Reference in a new issue