# Copyright 2014 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

import("//build/config/features.gni")
import("//build/config/host_byteorder.gni")
import("config.gni")
import("sources.gni")

if (is_android) {
  import("//build/config/android/rules.gni")
}

if (is_mac && !icu_is_in_fuchsia) {
  import("//build/config/sanitizers/sanitizers.gni")
}

assert(!icu_disable_thin_archive || !is_component_build,
       "icu_disable_thin_archive only works in static library builds")

# Meta target that includes both icuuc and icui18n. Most targets want both.
# You can depend on the individually if you need to.
group("icu") {
  public_deps = [
    ":icui18n",
    ":icuuc",
  ]
}

# Allow access to normalization test data from unit/integration tests.
copy("normalization_test") {
  testonly = true
  sources = [ "source/data/unidata/NormalizationTest.txt" ]
  outputs = [ "$root_out_dir/testdata/{{source_file_part}}" ]
}

# Shared config used by ICU and all dependents.
config("icu_config") {
  defines = [
    # Tell ICU to not insert |using namespace icu;| into its headers,
    # so that chrome's source explicitly has to use |icu::|.
    "U_USING_ICU_NAMESPACE=0",

    # We don't use ICU plugins and dyload is only necessary for them.
    # NaCl-related builds also fail looking for dlfcn.h when it's enabled.
    "U_ENABLE_DYLOAD=0",

    # v8/Blink need to know whether Chromium's copy of ICU is used or not.
    "USE_CHROMIUM_ICU=1",

    # Enable tracing to connect to UMA but disable tracing of resource
    # to avoid performance issues.
    "U_ENABLE_TRACING=1",
    "U_ENABLE_RESOURCE_TRACING=0",
  ]

  if (!is_component_build) {
    defines += [ "U_STATIC_IMPLEMENTATION" ]
  }

  include_dirs = [
    "source/common",
    "source/i18n",
  ]

  if (icu_use_data_file) {
    defines += [ "ICU_UTIL_DATA_IMPL=ICU_UTIL_DATA_FILE" ]
  } else {
    defines += [ "ICU_UTIL_DATA_IMPL=ICU_UTIL_DATA_STATIC" ]
  }
}

# Config used only by ICU code.
config("icu_code") {
  cflags = []
  defines = [
    "HAVE_DLOPEN=0",

    # Only build encoding coverters and detectors necessary for HTML5.
    "UCONFIG_ONLY_HTML_CONVERSION=1",

    "UCONFIG_USE_ML_PHRASE_BREAKING=1",

    # TODO(jshin): do we still need this?
    "UCONFIG_USE_WINDOWS_LCID_MAPPING_API=0",

    # No dependency on the default platform encoding.
    # Will cut down the code size.
    "U_CHARSET_IS_UTF8=1",
  ]

  if (is_win) {
    # Disable some compiler warnings.
    cflags += [
      "/wd4005",  # Macro redefinition.
      "/wd4068",  # Unknown pragmas.
      "/wd4267",  # Conversion from size_t on 64-bits.
      "/utf-8",  # ICU source files are in UTF-8.
    ]
    if (!is_clang) {
      cflags += [
        # Ignore some msvc warnings here because V8 still supports msvc.
        "/wd4244",  # Conversion: possible loss of data.
      ]
      defines += [
        # https://crbug.com/1274247
        # <ctgmath> is deprecated in C++17, but ICU still uses it, so we should
        # silence the warning for now.
        "_SILENCE_CXX17_C_HEADER_DEPRECATION_WARNING",
      ]
    }
  } else if (is_linux || is_chromeos || is_android || icu_is_in_fuchsia) {
    cflags += [ "-Wno-unused-function" ]
  }
  if (is_clang) {
    cflags += [
      # ICU has some code with the pattern:
      #   if (found = uprv_getWindowsTimeZoneInfo(...))
      "-Wno-parentheses",

      # ucnv2022.cpp contains three functions that are only used when
      # certain preprocessor defines are set.
      # unistr.cpp also has an unused function for non-component builds.
      "-Wno-unused-function",

      # putil.cpp contains unused variables when building for iOS simulators.
      "-Wno-unused-variable",

      # Required for uversion.h putting a using namespace directive in the header.
      "-Wno-header-hygiene",

      # ICU has decided not to fix this warning as doing so would break its
      # stable API.
      "-Wno-ambiguous-reversed-operator",
    ]
  }
  if (is_clang || is_linux || is_chromeos || is_android || icu_is_in_fuchsia) {
    cflags += [
      # ICU uses its own deprecated functions.
      "-Wno-deprecated-declarations",
    ]
  }
  if (icu_is_in_fuchsia) {
    cflags +=
        [
          # Disable spurious thread safety errors in umutex.cpp
          "-Wno-thread-safety",

          # Can probably remove the below after
          # https://unicode-org.atlassian.net/projects/ICU/issues/ICU-20869
          # is fixed.
          "-Wno-implicit-int-float-conversion",
          "-Wno-conversion",

          # Needed for C++20
          "-Wno-ambiguous-reversed-operator",
          "-Wno-rewrite-not-bool",
          "-Wno-deprecated-anon-enum-enum-conversion",
          "-Wno-deprecated-array-compare",
          "-Wno-deprecated-pragma",

          # Used for conditional changes to the compilation process that
          # are only needed for the Fuchsia toolchain.
          "-DICU_IS_IN_FUCHSIA",

          # Fuchsia's default compilation config makes
          # no-newline-after-eof an error. While in ICU it
          # probably does not matter. So we instruct Fuchsia
          # to lower its standards when compiling ICU.
          "-Wno-newline-eof",
        ] + icu_fuchsia_extra_compile_flags
  }
  if (current_cpu == "wasm") {
    # Tell ICU that we are a 32 bit platform, otherwise,
    # double-conversion-utils.h doesn't know how to operate.
    defines += [ "__i386__" ]
  }
}

# Config used to set default visibility to hidden.
config("visibility_hidden") {
  cflags = []
  if (is_mac || is_linux || is_chromeos || is_android || is_fuchsia) {
    cflags += [ "-fvisibility=hidden" ]
  }
}

template("generate_icu_component") {
  if (icu_is_in_fuchsia) {
    target(default_library_type, target_name) {
      forward_variables_from(invoker,
                             "*",
                             [
                               "testonly",
                               "visibility",
                             ])
      assert(fuchsia_output_name_postfix == "")

      # If icu_use_target_out_dir is defined and set, then the component
      # will be output in the regular target_out_dir, rather than the default
      # root_build_dir.
      # See README.fuchsia for details.
      if (defined(icu_use_target_out_dir) && icu_use_target_out_dir) {
        output_dir = target_out_dir
      }

      # ICU uses RTTI, replace the default "no rtti" config (if applied).
      configs += [
        "//build/config:no_rtti",
        "//build/config:symbol_visibility_hidden",
      ]
      configs -= [
        "//build/config:no_rtti",
        "//build/config:symbol_visibility_hidden",
      ]

      configs += [ "//build/config:rtti" ]

      # These need to be applied after the main configs so the "-Wno-*" options
      # take effect.
      configs += [ ":icu_code" ]
      configs += extra_configs

      # Add Fuchsia-specific configs
      configs += icu_fuchsia_extra_configs

      # Remove Fuchsia-specific configs, the "add-then-remove" ensures that
      # removing a config that is not there does not fail.
      configs += icu_fuchsia_remove_configs
      configs -= icu_fuchsia_remove_configs
      public_configs = [ ":icu_config" ]
    }
  } else {
    component(target_name) {
      forward_variables_from(invoker,
                             "*",
                             [
                               "testonly",
                               "visibility",
                             ])

      # If icu_use_target_output_dir is defined and set, then the component
      # will be output in the regular target_out_dir, rather than the default
      # root_build_dir.
      # See README.fuchsia for details.
      if (defined(icu_use_target_output_dir) && icu_use_target_output_dir) {
        output_dir = target_out_dir
      }

      if (is_fuchsia) {
        base_output_name = target_name
        if (defined(invoker.output_name)) {
          base_output_name = invoker.output_name
        }

        # Fuchsia puts its own libicu*.so in /system/lib where we need to put our
        # .so when doing component builds, so we need to give this a different name.
        output_name = "${base_output_name}_cr${fuchsia_output_name_postfix}"
      } else {
        assert(fuchsia_output_name_postfix == "")
      }

      # ICU uses RTTI, replace the default "no rtti" config (if applied).
      configs += [
        "//build/config/compiler:no_rtti",
        "//build/config/compiler:chromium_code",
      ]
      configs -= [
        "//build/config/compiler:no_rtti",
        "//build/config/compiler:chromium_code",
      ]
      configs += [
        "//build/config/compiler:rtti",
        "//build/config/compiler:no_chromium_code",
      ]

      # These need to be applied after the main configs so the "-Wno-*" options
      # take effect.
      configs += [ ":icu_code" ]
      configs += extra_configs

      # See a similar stanza above for details.
      configs += icu_fuchsia_extra_configs
      configs += icu_fuchsia_remove_configs
      configs -= icu_fuchsia_remove_configs
      public_configs = [ ":icu_config" ]

      # Make icu into a standalone static library. Currently This is only useful
      # on Chrome OS.
      if (icu_disable_thin_archive) {
        configs -= [ "//build/config/compiler:thin_archive" ]
        complete_static_lib = true
      }
    }
  }
}

template("generate_icui18n") {
  generate_icu_component(target_name) {
    assert(defined(invoker.icuuc_deps), "Need the 'icuuc_deps' parameter.")
    icuuc_deps = invoker.icuuc_deps

    fuchsia_output_name_postfix = ""
    if (defined(invoker.fuchsia_output_name_postfix)) {
      fuchsia_output_name_postfix = invoker.fuchsia_output_name_postfix
    }

    forward_variables_from(invoker,
                           "*",
                           [
                             "testonly",
                             "visibility",
                           ])

    sources = icu18n_sources
    public = icu18n_public

    defines = [ "U_I18N_IMPLEMENTATION" ]
    deps = icuuc_deps

    # TODO(fxbug.dev/42180987): workaround for toolchain issues, see bug
    if (icu_is_in_fuchsia && is_fuchsia) {
      deps += [ "//build/config/fuchsia:uses-outline-atomics-fxbug98632" ]
    }
  }
}

generate_icui18n("icui18n") {
  extra_configs = []
  if (icu_is_in_fuchsia) {
    # See similar stanza above for details.
    extra_configs += icu_fuchsia_extra_configs
    extra_configs += icu_fuchsia_remove_configs
    extra_configs -= icu_fuchsia_remove_configs
  }
  icuuc_deps = [ ":icuuc_private" ]
}

generate_icui18n("icui18n_hidden_visibility") {
  extra_configs = [ ":visibility_hidden" ]
  if (icu_is_in_fuchsia) {
    # See similar stanza above for details.
    extra_configs += icu_fuchsia_extra_configs
    extra_configs += icu_fuchsia_remove_configs
    extra_configs -= icu_fuchsia_remove_configs
  }
  icuuc_deps = [ ":icuuc_private_hidden_visibility" ]
  if (is_fuchsia && !icu_is_in_fuchsia) {
    fuchsia_output_name_postfix = "_hidden_visibility"
  }
}

template("generate_icuuc") {
  generate_icu_component(target_name) {
    fuchsia_output_name_postfix = ""
    if (defined(invoker.fuchsia_output_name_postfix)) {
      fuchsia_output_name_postfix = invoker.fuchsia_output_name_postfix
    }

    forward_variables_from(invoker,
                           "*",
                           [
                             "testonly",
                             "visibility",
                           ])

    sources = icuuc_sources
    public_deps = [ ":icuuc_public" ]

    defines = [ "U_COMMON_IMPLEMENTATION" ]
    deps = [ ":icudata" ]

    if (icu_use_data_file && icu_use_stub_data) {
      sources += [ "source/stubdata/stubdata.cpp" ]
    }

    defines += [ "U_ICUDATAENTRY_IN_COMMON" ]

    # TODO(fxbug.dev/42180987): workaround for toolchain issues, see bug
    if (icu_is_in_fuchsia && is_fuchsia) {
      deps += [ "//build/config/fuchsia:uses-outline-atomics-fxbug98632" ]
    }
  }
}

group("icuuc") {
  public_deps = [ ":icuuc_private" ]
}

group("icuuc_hidden_visibility") {
  public_deps = [ ":icuuc_private_hidden_visibility" ]
}

source_set("icuuc_public") {
  sources = icuuc_public
}

generate_icuuc("icuuc_private") {
  extra_configs = []
  if (icu_is_in_fuchsia) {
    # See similar stanza above for details.
    extra_configs += icu_fuchsia_extra_configs
    extra_configs += icu_fuchsia_remove_configs
    extra_configs -= icu_fuchsia_remove_configs
  }
  output_name = "icuuc"
  visibility = [
    ":icui18n",
    ":icuuc",
  ]
}

generate_icuuc("icuuc_private_hidden_visibility") {
  extra_configs = [ ":visibility_hidden" ]
  if (icu_is_in_fuchsia) {
    # See similar stanza above for details.
    extra_configs += icu_fuchsia_extra_configs
    extra_configs += icu_fuchsia_remove_configs
    extra_configs -= icu_fuchsia_remove_configs
  }
  output_name = "icuuc_hidden_visibility"
  visibility = [
    ":icui18n_hidden_visibility",
    ":icuuc_hidden_visibility",
  ]
  if (is_fuchsia && !icu_is_in_fuchsia) {
    fuchsia_output_name_postfix = "_hidden_visibility"
  }
}

if (is_android && enable_java_templates) {
  android_assets("icu_assets") {
    if (icu_use_data_file) {
      sources = [ "$root_out_dir/icudtl.dat" ]
      deps = [ ":icudata" ]
      disable_compression = true
    }
  }
}

if (is_android) {
  data_dir = "android"
} else if (is_ios && !use_blink) {
  data_dir = "ios"
} else if (is_chromeos) {
  data_dir = "chromeos"
} else if (current_cpu == "wasm") {
  data_dir = "flutter"
} else if (icu_is_in_fuchsia && icu_fuchsia_override_data_dir != "") {
  # See //config.gni for details.
  data_dir = icu_fuchsia_override_data_dir
} else {
  data_dir = "common"
}

if (current_cpu == "mips" || current_cpu == "mips64" ||
    host_byteorder == "big") {
  data_bundle_prefix = "icudtb"
} else {
  data_bundle_prefix = "icudtl"
}
data_bundle = "${data_bundle_prefix}.dat"

# Some code paths end up not using these, marking them to avoid build
# breaks.
# See README.fuchsia for details.
not_needed([
             "data_bundle",
             "data_bundle_prefix",
             "data_dir",
           ])

if (icu_copy_icudata_to_root_build_dir) {
  copy("copy_icudata") {
    sources = [ "$data_dir/$data_bundle" ]
    outputs = [ "$root_out_dir/$data_bundle" ]
    data = [ "$root_out_dir/$data_bundle" ]
  }
}

if (icu_use_data_file) {
  if (is_ios) {
    bundle_data("icudata") {
      sources = [ "$data_dir/$data_bundle" ]
      outputs = [ "{{bundle_resources_dir}}/{{source_file_part}}" ]
    }
  } else {
    group("icudata") {
      if (icu_copy_icudata_to_root_build_dir) {
        # Guarded by a flag, to avoid name clashes if other build processes
        # also happen to generate the output file by the same name.
        # See README.fuchsia for details.
        public_deps = [ ":copy_icudata" ]
      }
    }
  }
} else {
  if (current_cpu == "wasm") {
    data_assembly = "$target_gen_dir/${data_bundle_prefix}_dat.cpp"
  } else {
    data_assembly = "$target_gen_dir/${data_bundle_prefix}_dat.S"
  }
  inline_data_assembly = "$target_gen_dir/${data_bundle_prefix}_dat.cc"
  action("make_data_assembly") {
    if (current_cpu == "wasm") {
      # See scripts/make_data_cpp.py for details on building ICU for wasm.
      script = "scripts/make_data_cpp.py"
      inputs = [ "$data_dir/$data_bundle" ]
      outputs = [ data_assembly ]
      args = [
        rebase_path(inputs[0], root_build_dir),
        rebase_path(data_assembly, root_build_dir),
      ]
    } else {
      script = "scripts/make_data_assembly.py"
      inputs = [ "$data_dir/$data_bundle" ]
      outputs = [ data_assembly ]
      args = [
        rebase_path(inputs[0], root_build_dir),
        rebase_path(data_assembly, root_build_dir),
      ]
      if (is_mac || is_ios) {
        args += [ "--mac" ]
      } else if (is_win) {
        if (target_cpu == "arm64" || target_cpu == "x64") {
          args += [ "--win64" ]
        } else if (target_cpu == "arm" || target_cpu == "x86") {
          args += [ "--win32" ]
        } else {
          assert(false)
        }
      }
    }
  }

  if (is_win) {
    action("make_inline_data_assembly") {
      deps = [ ":make_data_assembly" ]
      script = "scripts/asm_to_inline_asm.py"
      inputs = [ data_assembly ]
      outputs = [ inline_data_assembly ]
      args = rebase_path([
                           data_assembly,
                           inline_data_assembly,
                         ],
                         root_build_dir)
    }
  } else {
    not_needed([ "inline_data_assembly" ])
  }

  source_set("icudata") {
    defines = [ "U_HIDE_DATA_SYMBOL" ]
    if (is_win) {
      sources = [ inline_data_assembly ]
      deps = [ ":make_inline_data_assembly" ]
    } else {
      sources = [ data_assembly ]
      deps = [ ":make_data_assembly" ]
      asmflags = []
      if (current_cpu == "arm64") {
        import("//build/config/arm.gni")
        if (arm_control_flow_integrity == "standard" ||
            arm_control_flow_integrity == "bti+pac") {
          asmflags += [ "-mmark-bti-property" ]
        }
      }
    }
  }
}
