# Copyright 2020 - 2025, project-repo and the cagebreak contributors
# SPDX-License-Identifier: MIT
project(
  'nedm',
  'c',
version : '3.0.1',
license : 'MIT',
default_options : ['c_std=c23,c11', 'warning_level=3']
)

add_project_arguments(
  [
    '-DWLR_USE_UNSTABLE',
    '-Werror=implicit-function-declaration',
    '-Wundef',
  ],
  language: 'c',
)

if get_option('buildtype').startswith('debug')
  add_project_arguments(
	[
	  '-DDEBUG',
	],
	language : 'c',
)
endif

cc = meson.get_compiler('c')

fuzz_compile_args = []
fuzz_link_args = []
if get_option('fuzz')
  fuzzing_engine = meson.get_compiler('c').find_library('Fuzzer', required : false)
  if fuzzing_engine.found()
	fuzz_compile_args += '-fsanitize-coverage=trace-pc-guard,trace-cmp'
  elif cc.has_argument('-fsanitize=fuzzer-no-link')
	fuzz_compile_args += '-fsanitize=fuzzer-no-link'
	fuzz_link_args += '-fsanitize=fuzzer-no-link'
  else
    error('Looks like neither libFuzzer nor -fsanitize=fuzzer-no-link is supported. Disable fuzzing using -Dfuzz=false to build without fuzzers.')
  endif
endif

is_freebsd = host_machine.system().startswith('freebsd')
if is_freebsd
  add_project_arguments(
    [
      '-Wno-format-extra-args',
      '-Wno-gnu-zero-variadic-macro-arguments',
    ],
    language: 'c'
  )
endif

wlroots        = dependency('wlroots-0.19')
wayland_protos = dependency('wayland-protocols', version: '>=1.14')
wayland_server = dependency('wayland-server')
wayland_cursor = dependency('wayland-cursor')
wayland_client = dependency('wayland-client')
xkbcommon      = dependency('xkbcommon')
cairo          = dependency('cairo')
pango          = dependency('pango')
pangocairo     = dependency('pangocairo')
fontconfig     = dependency('fontconfig')
libinput       = dependency('libinput')
libevdev       = dependency('libevdev')
libudev       = dependency('libudev')
math           = cc.find_library('m')

wl_protocol_dir = wayland_protos.get_variable(pkgconfig : 'pkgdatadir')
wayland_scanner = find_program('wayland-scanner')
wayland_scanner_server = generator(
  wayland_scanner,
  output: '@BASENAME@-protocol.h',
  arguments: ['server-header', '@INPUT@', '@OUTPUT@'],
)

server_protocols = [
  [wl_protocol_dir, 'stable/xdg-shell/xdg-shell.xml'],
  [wl_protocol_dir, 'unstable/pointer-constraints/pointer-constraints-unstable-v1.xml'],
  [wl_protocol_dir, 'unstable/relative-pointer/relative-pointer-unstable-v1.xml'],
  ['protocols', 'wlr-layer-shell-unstable-v1.xml'],
]

server_protos_headers = []

foreach p : server_protocols
  xml = join_paths(p)
  server_protos_headers += wayland_scanner_server.process(xml)
endforeach

server_protos = declare_dependency(
  sources: server_protos_headers,
)

if get_option('xwayland')
  wlroots_has_xwayland = cc.get_define('WLR_HAS_XWAYLAND', prefix: '#include <wlr/config.h>', dependencies: wlroots) == '1'
  if not wlroots_has_xwayland
    error('Cannot build Cagebreak with XWayland support: wlroots has been built without it')
  else
    have_xwayland = true
  endif
else
  have_xwayland = false
endif

fanalyzer_compile_flag=[]
if cc.has_argument('-fanalyzer')
  have_fanalyze=true
  fanalyzer_compile_flag=[ '-fanalyzer' ]
else
  have_fanalyze=false
endif

if get_option('version_override') != ''
  version = '@0@'.format(get_option('version_override'))
else
  version = '@0@'.format(meson.project_version())
endif

conf_data = configuration_data()
conf_data.set10('NEDM_HAS_XWAYLAND', have_xwayland)
conf_data.set10('NEDM_HAS_FANALYZE', have_fanalyze)
conf_data.set_quoted('NEDM_VERSION', version)


nedm_main_file = [ 'nedm.c', ]
nedm_source_strings = [
  'idle_inhibit_v1.c',
  'input_manager.c',
  'ipc_server.c',
  'keybinding.c',
  'layer_shell.c',
  'workspace.c',
  'output.c',
  'parse.c',
  'seat.c',
  'status_bar.c',
  'util.c',
  'view.c',
  'wallpaper.c',
  'xdg_shell.c',
  'libinput.c',
  'server.c',
  'message.c',
  'pango.c',
]

nedm_header_strings = [
  'idle_inhibit_v1.h',
  'ipc_server.h',
  'keybinding.h',
  'layer_shell.h',
  'workspace.h',
  'output.h',
  'parse.h',
  'seat.h',
  'server.h',
  'status_bar.h',
  'util.h',
  'view.h',
  'wallpaper.h',
  'xdg_shell.h',
  'pango.h',
  'message.h',
]

if conf_data.get('NEDM_HAS_XWAYLAND', 0) == 1
  nedm_source_strings += 'xwayland.c'
  nedm_header_strings += 'xwayland.h'
endif

nedm_sources = [
  configure_file(input: 'config.h.in',
		 output: 'config.h',
		 configuration: conf_data),
  ]

nedm_headers = []

foreach source : nedm_source_strings
  nedm_sources += files(source)
endforeach

foreach header : nedm_header_strings
  nedm_headers += files(header)
endforeach
# Dependencies marked with "true" are required to have
# the version specified below in order for the build
# to be reproducible.
nedm_dependencies_dict = {
  'server_protos': [server_protos,true],
  'wayland_server': [wayland_server,false],
  'wayland_client': [wayland_client,true],
  'wayland_cursor': [wayland_cursor,true],
  'wlroots': [wlroots,true],
  'xkbcommon': [xkbcommon,true],
  'fontconfig': [fontconfig,true],
  'libinput': [libinput,true],
  'libevdev': [libevdev,true],
  'libudev': [libudev,true],
  'pango': [pango,true],
  'cairo': [cairo,true],
  'pangocairo': [pangocairo,true],
  'math': [math,true],
}

reproducible_build_versions = { 
  'server_protos': '-1',
  'wayland_server': '1.19.0',
  'wayland_client': '1.23.1',
  'wayland_cursor': '1.23.1',
  'wlroots': '0.19.0',
  'xkbcommon': '1.10.0',
  'fontconfig': '2.17.1',
  'libinput': '1.28.1',
  'libevdev': '1.13.4',
  'libudev': '257',
  'pango': '1.56.4',
  'cairo': '1.18.4',
  'pangocairo': '1.56.4',
  'math': '-1'
}

nedm_dependencies = []

foreach name, dep : nedm_dependencies_dict
  nedm_dependencies += dep[0]
endforeach

foreach name, dep : nedm_dependencies_dict
  if reproducible_build_versions[name] != '-1'
    if dep[1] == false
      if dep[0].version() < reproducible_build_versions[name]
        warning('The installed version of "' + name + '" on your machine (' + dep[0].version() + ') is older than the one used to generate the binary specified in Hashes.md (' + reproducible_build_versions[name] + '). It is recommended to use an up-to-date version for compiling cagebreak.'
        )
      endif
    elif reproducible_build_versions[name] != dep[0].version()
      warning('The installed version of "' + name + '" on your machine (' + dep[0].version() + ') differs from the one used to generate the binary specified in Hashes.md (' + reproducible_build_versions[name] + '). Cagebreak does not guarantee a reproducible build for this configuration.'
      )
      break
    endif
  endif
endforeach

reproducible_build_compiler = 'gcc'
reproducible_build_compiler_version = '15.1.1'

if cc.get_id() != reproducible_build_compiler
  warning('The compiler "' + cc.get_id() + '" differs from the one used to generate to binary specified in Hashes.md (' + reproducible_build_compiler + ').')
elif cc.version() != reproducible_build_compiler_version
  warning('The version of ' + cc.get_id() + ' (' + cc.version() + ') differs from the one used to generate the binary specified in Hashes.md ' + reproducible_build_compiler_version + '.')
endif

executable(
  meson.project_name(),
  nedm_main_file + nedm_sources + nedm_headers,
  dependencies: nedm_dependencies,
  install: true,
  link_args: fuzz_link_args+fanalyzer_compile_flag,
  c_args: fuzz_compile_args,
)

install_data('examples/config', install_dir : '/etc/xdg/cagebreak')
install_data('LICENSE', install_dir : '/usr/share/licenses/' + meson.project_name() + '/')

if get_option('man-pages')
  scdoc = find_program('scdoc')
  secssinceepoch = 1751745126
  shcommand = 'export SOURCE_DATE_EPOCH=' + secssinceepoch.to_string() + ' ; @0@ < @INPUT@'.format(scdoc.full_path())
  sh = find_program('sh')
  mandir1 = join_paths(get_option('mandir'), 'man1')
  mandir5 = join_paths(get_option('mandir'), 'man5')
  mandir7 = join_paths(get_option('mandir'), 'man7')

  cagebreak_man = custom_target('cagebreak_man',
    output : 'cagebreak.1',
    input : 'man/cagebreak.1.md',
    capture : true,
    command : [sh, '-c', shcommand],
    install: true,
    install_dir: mandir1
    )

  cagebreak_man = custom_target('cagebreak_config_man',
    output : 'cagebreak-config.5',
    input : 'man/cagebreak-config.5.md',
    capture : true,
    command : [sh, '-c', shcommand],
    install: true,
    install_dir: mandir5
    )

  cagebreak_man = custom_target('cagebreak_socket_man',
    output : 'cagebreak-socket.7',
    input : 'man/cagebreak-socket.7.md',
    capture : true,
    command : [sh, '-c', shcommand],
    install: true,
    install_dir: mandir7
    )
endif

fuzz_sources = [
  'fuzz/fuzz-parse.c', 
  'fuzz/fuzz-lib.c',
  ]

fuzz_headers = [
  'parse.h',
  'fuzz/fuzz-lib.h',
  ]

fuzz_override_lib = [ 'fuzz/execl_override.c' ]

if get_option('fuzz')
  inc = include_directories(['.','build/'])

  fuzz_dependencies = nedm_dependencies
  if fuzzing_engine.found()
    fuzz_dependencies += fuzzing_engine
  else
    link_args = [ '-fsanitize=fuzzer', ]
  endif

override_lib = shared_library('execl_override',
  [ fuzz_override_lib ],
  dependencies: [ cairo,pango,pangocairo ],
  install: false
  )

  executable(
    'fuzz-parse',
    fuzz_sources + fuzz_headers + nedm_headers + nedm_sources,
    dependencies: fuzz_dependencies,
    install: false,
    include_directories: inc,
    link_args: link_args + fuzz_link_args,
    c_args: fuzz_compile_args,
    link_with: override_lib,
    )
endif

summary = [
	'',
	'NEDM @0@'.format(version),
	'',
	'    xwayland: @0@'.format(have_xwayland),
	''
]
message('\n'.join(summary))

run_target('devel-install',
  command : ['scripts/install-development-environment'])

run_target('fuzz',
  command : ['scripts/fuzz', get_option('corpus')])

run_target('adjust-epoch',
  command : ['scripts/adjust-epoch'])

run_target('git-tag',
  command : ['scripts/git-tag', get_option('gpg_id'), meson.project_version()])

run_target('output-hashes',
  command : ['scripts/output-hashes', meson.project_version()])

run_target('create-sigs',
  command : ['scripts/create-signatures', get_option('gpg_id')])

run_target('set-ver',
  command : ['scripts/set-version', meson.project_version()])

run_target('create-artefacts',
  command : ['scripts/create-release-artefacts', get_option('gpg_id'), meson.project_version()])

# Test Suite

test('Build without warnings', find_program('test/build-w-o-warnings'), env : [ ''.join('MESONCURRENTCONFIGDIR=', meson.current_source_dir()) ], suite: 'devel')
test('Example script header is consistent', find_program('test/script-header'), env : [ ''.join('MESONCURRENTCONFIGDIR=', meson.current_source_dir()) ], suite: 'devel')
test('Script executability is consistent', find_program('test/script-header'), env : [ ''.join('MESONCURRENTCONFIGDIR=', meson.current_source_dir()) ], suite: 'devel')
test('Man page consistency', find_program('test/man-pages'), env : [ ''.join('MESONCURRENTCONFIGDIR=', meson.current_source_dir()) ], suite: 'devel')
test('Build without xwayland', find_program('test/build-w-o-xwayland'), env : [ ''.join('MESONCURRENTCONFIGDIR=', meson.current_source_dir()) ], suite: 'devel')
test('Copyright and LICENSE', find_program('test/copyright-license'), args : [ nedm_main_file + nedm_source_strings + nedm_header_strings + fuzz_sources + fuzz_headers + fuzz_override_lib ], env : [ ''.join('MESONCURRENTCONFIGDIR=', meson.current_source_dir()), ''.join('MESONLICENSE=', meson.project_license())], suite: 'devel' )
test('Formatting check (clang-format)', find_program('test/clang-format'), args : [ nedm_main_file + nedm_source_strings + nedm_header_strings + fuzz_sources + fuzz_headers + fuzz_override_lib ], env : [ ''.join('MESONCURRENTCONFIGDIR=', meson.current_source_dir()) ], suite: 'devel')
test('Script linting (shellcheck)', find_program('test/shellcheck'), env : [ ''.join('MESONCURRENTCONFIGDIR=', meson.current_source_dir()) ], suite: 'devel')
test('GPG key validity', find_program('test/gpg-validity'), args : [ meson.project_version() ], env : [ ''.join('MESONCURRENTCONFIGDIR=', meson.current_source_dir()) ], suite: 'devel')
test('Illegal Strings', find_program('test/illegal-strings'), env : [ ''.join('MESONCURRENTCONFIGDIR=', meson.current_source_dir()) ], suite: 'devel')
test('Static analysis (scan-build)', find_program('test/scan-build'), env : [ ''.join('MESONCURRENTCONFIGDIR=', meson.current_source_dir()) ], suite: 'devel-long')
test('Arguments', find_program('test/arguments'), args : [ meson.project_version() ], env : [ ''.join('MESONCURRENTCONFIGDIR=', meson.current_source_dir()) ], suite: 'basic')
test('Environment Variables', find_program('test/environment-variables'), env : [ ''.join('MESONCURRENTCONFIGDIR=', meson.current_source_dir()) ], suite: 'basic')
test('Semantic versioning', find_program('test/versions'), args : [ meson.project_version() ], env : [ ''.join('MESONCURRENTCONFIGDIR=', meson.current_source_dir()) ], suite: 'release')
test('Signature validity', find_program('test/gpg-signatures'), suite: 'release')
test('Hashes.md', find_program('test/hashes-md'), args : [ meson.project_version() ], env : [ ''.join('MESONCURRENTCONFIGDIR=', meson.current_source_dir()) ], suite: 'release')
test('Non-auto tests', find_program('test/non-auto-tests'), args : [ meson.project_version() ], env : [ ''.join('MESONCURRENTCONFIGDIR=', meson.current_source_dir()) ], suite: 'release')
test('Git tag', find_program('test/git-tag'), args : [ meson.project_version() ], suite: 'release')
test('Release-artefacts', find_program('test/check-artefacts'), args : [ meson.project_version() ], env : [ ''.join('MESONCURRENTCONFIGDIR=', meson.current_source_dir()) ], suite: 'release')
