project('gst-plugins-rs',
        'rust',
        'c',
        version: '0.10.11',
        meson_version : '>= 0.60')

# dependencies.py needs a toml parsing module
python = import('python').find_installation(modules: ['tomllib'], required: false)
if not python.found()
  python = import('python').find_installation(modules: ['tomli'])
endif
fs = import('fs')
host_system = host_machine.system()

if get_option('debug')
  target = 'debug'
else
  target = 'release'
endif

cargo = find_program('cargo', version:'>=1.40')
cargo_wrapper = find_program('cargo_wrapper.py')
cargo_c = find_program('cargo-cbuild', version:'>=0.9.3', required: false)

rustc = meson.get_compiler('rust')

if not cargo_c.found()
  error('cargo-c missing, install it with: \'cargo install cargo-c\'')
endif

system = host_machine.system()
exe_suffix = ''
if system == 'windows'
  exe_suffix = '.exe'
  ext_dynamic = 'dll'
  ext_static = 'lib'
elif system == 'darwin'
  ext_dynamic = 'dylib'
  ext_static = 'a'
else
  ext_dynamic = 'so'
  ext_static = 'a'
endif

# Extra env to pass to cargo
extra_env = {}

# Used to not lookup the same dependency multiple times which clutters logs
deps_cache = {}

# Need to depends on all gstreamer-rs deps to ensure they are built
# before gstreamer-rs when building with gst-build.
# Custom targets can't depend on dependency() objects so we have to depend
# on the library variable from the subproject instead.
glib_req = '>=2.62'
gst_req = '>=1.20.0'
depends = []

deps = [
  # name, subproject name, subproject dep, library object
  ['gstreamer-1.0', 'gstreamer', 'gst_dep', 'libgst'],
  ['gstreamer-app-1.0', 'gst-plugins-base', 'app_dep', 'gstapp'],
  ['gstreamer-audio-1.0', 'gst-plugins-base', 'audio_dep', 'gstaudio'],
  ['gstreamer-base-1.0', 'gstreamer', 'gst_base_dep', 'gst_base'],
  ['gstreamer-video-1.0', 'gst-plugins-base', 'video_dep', 'gstvideo']  
]

if get_option('threadshare').allowed() \
  or get_option('onvif').allowed() \
  or get_option('raptorq').allowed() \
  or get_option('rtp').allowed() \
  or get_option('webrtc').allowed()
  deps += [['gstreamer-rtp-1.0', 'gst-plugins-base', 'rtp_dep', 'gst_rtp']]
endif
if get_option('webrtc').allowed() \
  or get_option('webrtchttp').allowed()
  deps += [['gstreamer-webrtc-1.0', 'gst-plugins-bad', 'gstwebrtc_dep', 'gstwebrtc']]
  deps += [['gstreamer-sdp-1.0', 'gst-plugins-base', 'sdp_dep', 'gstsdp']]
endif
if get_option('tests').allowed()
  deps += [['gstreamer-check-1.0', 'gstreamer', 'gst_check_dep', 'gst_check']]
endif
if get_option('gtk4').allowed()
  deps += [['gstreamer-gl-1.0', 'gst-plugins-base', 'gst_gl_dep', 'gstgl']]
endif
if get_option('threadshare').allowed()
  deps += [['gstreamer-net-1.0', 'gstreamer', 'gst_net_dep', 'gst_net']]
endif

glib_dep = dependency('glib-2.0', version: glib_req)
deps_cache += {'glib-2.0': glib_dep}

foreach d: deps
  dep = dependency(d[0], version: gst_req,
    fallback : [d[1], d[2]])
  set_variable(d[2], dep)
  deps_cache += {d[0]: dep}
  if dep.type_name() == 'internal'
    lib = subproject(d[1]).get_variable(d[3])
    depends += lib
  endif
endforeach

# kept in the same order as the `members` list in Cargo.toml
plugins = {
  'audiofx': {
    'library': 'libgstrsaudiofx',
    'examples': ['hrtfrender'],
  },
  'claxon': {'library': 'libgstclaxon'},
  # csound has a non-trivial external dependency, see below
  'lewton': {'library': 'libgstlewton'},
  'spotify': {'library': 'libgstspotify'},

  'file': {'library': 'libgstrsfile'},
  # sodium can have an external dependency, see below
  'threadshare': {
    'library': 'libgstthreadshare',
    'examples': [
      'ts-benchmark',
      'udpsrc-benchmark-sender',
      'tcpclientsrc-benchmark-sender',
      'ts-standalone',
    ],
  },

  'mp4': {'library': 'libgstmp4'},
  'fmp4': {
    'library': 'libgstfmp4',
    'examples': [
      'dash_vod',
      'hls_live',
      'hls_vod',
    ],
  },

  'aws': {
    'library': 'libgstaws',
    'extra-deps': {'openssl': '>=1.1'},
  },
  'hlssink3': {'library': 'libgsthlssink3'},
  'ndi': {'library': 'libgstndi'},
  'onvif': {
    'library': 'libgstrsonvif',
    'extra-deps': {'pangocairo': ''},
  },
  'raptorq': {'library': 'libgstraptorq'},
  'reqwest': {'library': 'libgstreqwest'},
  'rtp': {'library': 'libgstrsrtp'},
  'webrtchttp': {'library': 'libgstwebrtchttp'},
  'webrtc': {
    'library': 'libgstrswebrtc',
    'examples': ['webrtcsink-stats-server'],
  },

  'textahead': {'library': 'libgsttextahead'},
  'json': {'library': 'libgstjson'},
  'regex': {'library': 'libgstregex'},
  'textwrap': {'library': 'libgsttextwrap'},

  'tracers': {'library': 'libgstrstracers'},
  'uriplaylistbin': {
    'library': 'libgsturiplaylistbin',
    'examples': ['playlist'],
    'features': ['clap'],
  },

  'cdg': {'library': 'libgstcdg'},
  'closedcaption': {
    'library': 'libgstrsclosedcaption',
    'extra-deps': {
      'pango': '',
      'pangocairo': '',
      'cairo-gobject': '',
    }
  },
  'dav1d': {
    'library': 'libgstdav1d',
    'extra-deps': {'dav1d': '>=1.0'},
  },
  'ffv1': {'library': 'libgstffv1'},
  'flavors': {'library': 'libgstrsflv'},
  'gif': {
    'library': 'libgstgif',
    'examples': ['testvideosrc2gif'],
  },
  # gtk4 is added below
  'hsv': {'library': 'libgsthsv'},
  'png': {
    'library': 'libgstrspng',
    'examples': ['pngenc'],
  },
  'rav1e': {'library': 'libgstrav1e'},
  'videofx': {
    'library': 'libgstrsvideofx',
    'extra-deps': {'cairo-gobject': ''},
  },
}

# Won't build on platforms where it bundles the sources because of:
# https://github.com/qnighy/libwebp-sys2-rs/issues/12
# the fix is:
# https://github.com/qnighy/libwebp-sys2-rs/pull/13
if host_system not in ['windows', 'darwin']
  # FIXME: libwebp-sys2 will build its bundled version on msvc and apple platforms
  # https://github.com/qnighy/libwebp-sys2-rs/issues/4
  plugins += {'webp': {
    'library': 'libgstrswebp',
    'extra-deps': {'libwebpdemux': ''},
  }}
endif

sodium_opt = get_option('sodium')
if sodium_opt.allowed()
  sodium_plugin = {'sodium': {
    'library': 'libgstsodium',
    'examples': ['generate-keys', 'encrypt-example', 'decrypt-example'],
    'features': ['serde', 'serde_json', 'clap'],
  }}
  if get_option('sodium-source') == 'system'
    sodium_dep = dependency('libsodium', required: sodium_opt.enabled())
    extra_env += {'SODIUM_USE_PKG_CONFIG': '1'}
    if sodium_dep.found()
      plugins += sodium_plugin
    endif
  else
    plugins += sodium_plugin
  endif
endif

cc = meson.get_compiler('c')
csound_option = get_option('csound')
if csound_option.allowed()
  # if csound isn't distributed with pkg-config then user needs to define CSOUND_LIB_DIR with its location

  res = run_command(python, '-c', 'import os; print(os.environ["CSOUND_LIB_DIR"])', check: false)
  if res.returncode() == 0
    csound_libdir = res.stdout().strip()
    csound_dep = cc.find_library('csound64', dirs: csound_libdir, required: false)
    if csound_dep.found()
      plugins += {'csound': {
        'library': 'libgstcsound',
        'examples': ['csound-effect'],
      }}
      extra_env += {'CSOUND_LIB_DIR': csound_libdir}
    elif csound_option.enabled()
      error('csound option is enabled, but csound64 library could not be found and CSOUND_LIB_DIR was not set')
    endif
  endif
endif

if get_option('gtk4').allowed()
  gtk4_features = []
  gl_winsys = gst_gl_dep.get_variable('gl_winsys').split()
  gl_platforms = gst_gl_dep.get_variable('gl_platforms').split()
  if 'wayland' in gl_winsys
    gtk4_features += 'wayland'
  endif
  if 'x11' in gl_winsys
    if 'egl' in gl_platforms
      gtk4_features += 'x11egl'
    endif
    if 'glx' in gl_platforms
      gtk4_features += 'x11glx'
    endif
  elif host_system == 'windows'
    if 'egl' in gl_platforms
      gtk4_features += 'winegl'
    endif
  endif
  plugins += {
    'gtk4': {
      'library': 'libgstgtk4',
      'examples': ['gtksink'],
      'extra-deps': {'gtk4': '>=4.6'},
      'features': gtk4_features,
    },
    'fallbackswitch': {
      'library': 'libgstfallbackswitch',
      'examples': ['gtk-fallbackswitch'],
      'features': ['gtk', 'gio', 'gst-plugin-gtk4'],
    },
    'livesync': {
      'library': 'libgstlivesync',
      'examples': ['gtk-livesync'],
      'features': ['gtk', 'gio', 'gst-plugin-gtk4'],
    },
    'togglerecord': {
      'library': 'libgsttogglerecord',
      'examples': ['gtk-recording'],
      'features': ['gtk', 'gio', 'gst-plugin-gtk4'],
    },
  }
endif

# Process plugins list

default_library = get_option('default_library')

library_suffixes = []
if default_library in ['shared', 'both']
  library_suffixes += [ext_dynamic]
endif
if default_library in ['static', 'both']
  library_suffixes += [ext_static]
endif

# cargo packages (plugins) to build
packages = []
# cargo features
features = []
# examples to build
examples = []
# Add the plugin library files as output
output = []

if get_option('gtk4').allowed()
  if glib_dep.version().version_compare('>=2.74')
    features += ['glib/v2_74', 'gio/v2_74']
  elif glib_dep.version().version_compare('>=2.72')
    features += ['glib/v2_72', 'gio/v2_72']
  elif glib_dep.version().version_compare('>=2.70')
    features += ['glib/v2_70', 'gio/v2_70']
  elif glib_dep.version().version_compare('>=2.68')
    features += ['glib/v2_68', 'gio/v2_68']
  elif glib_dep.version().version_compare('>=2.66')
    features += ['glib/v2_66', 'gio/v2_66']
  elif glib_dep.version().version_compare('>=2.64')
    features += ['glib/v2_64', 'gio/v2_64']
  elif glib_dep.version().version_compare('>=2.62')
    features += ['glib/v2_62', 'gio/v2_62']
  elif glib_dep.version().version_compare('>=2.60')
    features += ['glib/v2_60', 'gio/v2_60']
  elif glib_dep.version().version_compare('>=2.58')
    features += ['glib/v2_58', 'gio/v2_58']
  endif
endif

if get_option('rav1e').allowed() and find_program('nasm', required: false).found()
  features += 'gst-plugin-rav1e/asm'
endif

if get_option('default_library') == 'static'
  extra_env += {
    # Tell the pkg-config crate to think of all libraries as static
    'PKG_CONFIG_ALL_STATIC': '1',
    # Tell the system-deps crate to process linker flag for static deps
    'SYSTEM_DEPS_LINK': 'static'
  }
endif

foreach plugin_name, details: plugins
  plugin_opt = get_option(plugin_name)
  if plugin_opt.allowed()
    plugin_deps_found = true
    foreach dep_name, dep_ver: details.get('extra-deps', {})
      if dep_ver != ''
        dep = dependency(dep_name, version: dep_ver, required: plugin_opt)
      else
        dep = dependency(dep_name, required: plugin_opt)
      endif
      deps_cache += {dep_name: dep}
      if not dep.found()
        plugin_deps_found = false
        break
      endif
    endforeach
    plugin_features = details.get('features', [])
    if plugin_deps_found
      # Validate gst-plugin features
      foreach feature: plugin_features
        if feature.startswith('gst-plugin') and not packages.contains(feature)
          plugin_deps_found = false
          break
        endif
      endforeach
    endif
    if plugin_deps_found
      packages += f'gst-plugin-@plugin_name@'
      features += plugin_features
      extra_features = run_command('dependencies.py', meson.current_source_dir(), plugin_name,
        '--feature', '--gst-version', gst_dep.version(), capture: true, check: true).stdout().strip()
      if extra_features != ''
        features += extra_features.split(',')
      endif

      examples += details.get('examples', [])
      lib = details.get('library')
      if default_library in ['shared', 'both']
        output += [lib + '.' + ext_dynamic]
      endif
      if default_library in ['static', 'both']
        output += [lib + '.' + ext_static]
      endif
    endif
  endif
endforeach

feature_args = []
if features.length() > 0
  feature_args += ['--features', features]
endif

plugins_install_dir = get_option('libdir') / 'gstreamer-1.0'
pkgconfig_install_dir = get_option('libdir') / 'pkgconfig'

extra_args = []
if get_option('doc').disabled()
  extra_args += ['--disable-doc']
endif

# 'pkgconfig' is the entry in the machine file, if specified
pkg_config = find_program('pkgconfig', 'pkg-config', required: false)
if pkg_config.found()
  extra_env += {'PKG_CONFIG': pkg_config.full_path()}
endif

pkg_config_path = get_option('pkg_config_path')
if pkg_config_path.length() > 0
  pathsep = ':'
  if host_system == 'windows'
    pathsep = ';'
  endif
  extra_env += {'PKG_CONFIG_PATH': pathsep.join(pkg_config_path)}
endif

# get cmdline for rust
extra_env += {'RUSTC': ' '.join(rustc.cmd_array())}

plugins = []
if output.length() > 0
  rs_plugins = custom_target('gst-plugins-rs',
    build_by_default: true,
    output: output,
    console: true,
    install: true,
    install_dir: plugins_install_dir,
    depends: depends,
    depfile: 'gst-plugins-rs.dep',
    env: extra_env,
    command: [cargo_wrapper,
      'build',
      meson.current_build_dir(),
      meson.current_source_dir(),
      meson.global_build_root(),
      target,
      get_option('prefix'),
      get_option('libdir'),
      '--packages', packages,
      '--depfile', '@DEPFILE@',
      '--lib-suffixes', library_suffixes,
    ] + feature_args + extra_args)
  plugins = rs_plugins.to_list()
endif

# This is used by GStreamer to static link Rust plugins into gst-full
gst_plugins = []
pc_files = []
plugin_names = []
foreach plugin : plugins
  # skip the 'lib' prefix and extension from plugin path
  plugin_name = fs.stem(plugin.full_path()).substring(3)

  option_name = plugin_name.substring(3)
  if option_name.startswith('rs')
    option_name = option_name.substring(2)
  endif
  if option_name == 'flv'
    option_name = 'flavors'
  endif
  if not get_option(option_name).allowed()
    continue
  endif

  # Extract plugin dependencies from their Cargo.toml file
  plugin_deps = []
  p = run_command('dependencies.py', meson.current_source_dir(), plugin_name,
    capture: true,
    check: true)
  foreach dep_name : p.stdout().strip().split(',')
    dep_name_version = dep_name.split('|')
    dep_name = dep_name_version.get(0).strip()
    if dep_name_version.length() > 1
      extras = {'version': dep_name_version.get(1).strip()}
    else
      extras = {}
    endif
    if deps_cache.has_key(dep_name)
      plugin_deps += deps_cache[dep_name]
    else
      dep = dependency(dep_name, required: false, kwargs: extras)
      plugin_deps += dep
      deps_cache += {dep_name: dep}
    endif
  endforeach

  dep = declare_dependency(
    link_with: plugin,
    dependencies: plugin_deps,
    variables: {'full_path': plugin.full_path()},
  )
  meson.override_dependency(plugin_name, dep)

  if default_library == 'static' and plugin_name in ['gstcsound', 'gstthreadshare', 'gstgtk4']
    warning('Static plugin @0@ is known to fail. It will not be included in libgstreamer-full.'.format(plugin_name))
  else
    gst_plugins += dep

    pc_files += [plugin_name + '.pc']
    if plugin_name.startswith('gst')
      plugin_names += [plugin_name.substring(3)]
    else
      plugin_names += [plugin_name]
    endif
  endif
endforeach

subdir('docs')

# We don't need to pass a command as we depends on the target above
# but it is currently mandatory ( https://github.com/mesonbuild/meson/issues/8059 )
# so use python as it's guaranteed to be present on any setup
if pc_files.length() > 0
  custom_target('gst-plugins-rs-pc-files',
    build_by_default: true,
    output: pc_files,
    console: true,
    install: true,
    install_dir: pkgconfig_install_dir,
    depends: rs_plugins,
    command: [python, '-c', '""'])
endif

if get_option('webrtc').allowed()
  custom_target('gst-webrtc-signalling-server',
    build_by_default: true,
    output: 'gst-webrtc-signalling-server',
    console: true,
    install: true,
    install_dir: get_option('bindir'),
    depfile: 'gst-webrtc-signalling-server.dep',
    env: extra_env,
    command: [cargo_wrapper,
      'build',
      meson.current_build_dir(),
      meson.current_source_dir(),
      meson.global_build_root(),
      target,
      get_option('prefix'),
      get_option('libdir'),
      '--depfile', '@DEPFILE@',
      '--bin', 'gst-webrtc-signalling-server',
      '--exe-suffix', exe_suffix,
    ])
endif

if get_option('examples').allowed() and examples.length() > 0
  custom_target('gst-plugins-rs-examples',
    build_by_default: true,
    output: examples,
    console: true,
    install: false,
    depfile: 'gst-plugins-rs-examples.dep',
    env: extra_env,
    command: [cargo_wrapper,
      'build',
      meson.current_build_dir(),
      meson.current_source_dir(),
      meson.global_build_root(),
      target,
      get_option('prefix'),
      get_option('libdir'),
      '--depfile', '@DEPFILE@',
      '--packages', packages,
      '--examples', examples,
      '--exe-suffix', exe_suffix,
    ] + feature_args)
endif

if get_option('tests').allowed()
  test('tests',
    cargo_wrapper,
    env: extra_env,
    args: ['test',
      meson.current_build_dir(),
      meson.current_source_dir(),
      meson.global_build_root(),
      target,
      get_option('prefix'),
      get_option('libdir'),
      '--packages', packages],
    timeout: 600)
endif

summary({
    'Plugins': plugin_names,
}, list_sep: ', ')
