infinipaint/conan/icu/all/conanfile.py
copilot 1c1d63f376
Some checks are pending
Windows Release / windows-x86_64 (push) Waiting to run
Ship 0.8 fog/freistellen and fix Windows CI for friends
Publish the portable zip to public Forgejo from the private GitHub
builder, and force Ninja so MSVC 195 deps build without CMake 4.2.
2026-10-25 18:07:47 +01:00

388 lines
18 KiB
Python

import glob
import hashlib
import os
import shutil
from conan import ConanFile
from conan.errors import ConanInvalidConfiguration
from conan.tools.apple import is_apple_os
from conan.tools.build import cross_building, stdcpp_library, check_min_cppstd
from conan.tools.env import Environment, VirtualBuildEnv
from conan.tools.files import apply_conandata_patches, copy, export_conandata_patches, get, mkdir, rename, replace_in_file, rm, rmdir, save
from conan.tools.gnu import Autotools, AutotoolsToolchain
from conan.tools.layout import basic_layout
from conan.tools.microsoft import check_min_vs, is_msvc, unix_path
from conan.tools.scm import Version
required_conan_version = ">=1.57.0"
class ICUConan(ConanFile):
name = "icu-infinipaint"
homepage = "http://site.icu-project.org"
license = "ICU"
description = "ICU is a mature, widely used set of C/C++ and Java libraries " \
"providing Unicode and Globalization support for software applications."
url = "https://github.com/conan-io/conan-center-index"
topics = ("icu4c", "i see you", "unicode")
package_type = "library"
settings = "os", "arch", "compiler", "build_type"
options = {
"shared": [True, False],
"fPIC": [True, False],
"data_packaging": ["files", "archive", "library", "static"],
"with_dyload": [True, False],
"dat_package_file": [None, "ANY"],
"with_icuio": [True, False],
"with_extras": [True, False],
}
default_options = {
"shared": False,
"fPIC": True,
"data_packaging": "archive",
"with_dyload": True,
"dat_package_file": None,
"with_icuio": True,
"with_extras": False,
}
@property
def _min_cppstd(self):
return 17
@property
def _compilers_minimum_version(self):
return {
"gcc": "8",
"clang": "7",
"apple-clang": "12",
"Visual Studio": "16",
"msvc": "192",
}
@property
def _settings_build(self):
return getattr(self, "settings_build", self.settings)
@property
def _enable_icu_tools(self):
return self.settings.os not in ["iOS", "tvOS", "watchOS", "Emscripten"]
def export_sources(self):
export_conandata_patches(self)
def config_options(self):
if self.settings.os == "Windows":
del self.options.fPIC
def configure(self):
if self.options.shared:
self.options.rm_safe("fPIC")
if Version(self.version) >= "74.1":
self.license = "Unicode-3.0"
def validate(self):
if self.options.dat_package_file:
if not os.path.exists(str(self.options.dat_package_file)):
raise ConanInvalidConfiguration("Non-existent dat_package_file specified")
if Version(self.version) >= "75.1":
if self.settings.compiler.cppstd:
check_min_cppstd(self, self._min_cppstd)
minimum_version = self._compilers_minimum_version.get(str(self.settings.compiler), False)
if minimum_version and Version(self.settings.compiler.version) < minimum_version:
raise ConanInvalidConfiguration(
f"{self.ref} requires C++{self._min_cppstd}, which your compiler does not support."
)
def layout(self):
basic_layout(self, src_folder="src")
@staticmethod
def _sha256sum(file_path):
m = hashlib.sha256()
with open(file_path, "rb") as fh:
for data in iter(lambda: fh.read(8192), b""):
m.update(data)
return m.hexdigest()
def package_id(self):
if self.info.options.dat_package_file:
self.info.options.dat_package_file = self._sha256sum(str(self.info.options.dat_package_file))
def build_requirements(self):
if self._settings_build.os == "Windows":
self.win_bash = True
if not self.conf.get("tools.microsoft.bash:path", check_type=str):
self.tool_requires("msys2/cci.latest")
if cross_building(self) and hasattr(self, "settings_build"):
self.tool_requires(str(self.ref))
def source(self):
get(self, **self.conan_data["sources"][self.version], strip_root=True)
def generate(self):
env = VirtualBuildEnv(self)
env.generate()
tc = AutotoolsToolchain(self)
if check_min_vs(self, "180", raise_invalid=False):
tc.extra_cflags.append("-FS")
tc.extra_cxxflags.append("-FS")
if Version(self.version) >= "75.1" and not self.settings.compiler.cppstd and is_msvc(self):
tc.extra_cxxflags.append(f"-std:c++{self._min_cppstd}")
if not self.options.shared:
tc.extra_defines.append("U_STATIC_IMPLEMENTATION")
if is_apple_os(self):
tc.extra_defines.append("_DARWIN_C_SOURCE")
yes_no = lambda v: "yes" if v else "no"
tc.configure_args.extend([
"--datarootdir=${prefix}/lib", # do not use share
f"--enable-release={yes_no(self.settings.build_type != 'Debug')}",
f"--enable-debug={yes_no(self.settings.build_type == 'Debug')}",
f"--enable-dyload={yes_no(self.options.with_dyload)}",
f"--enable-extras={yes_no(self.options.with_extras)}",
f"--enable-icuio={yes_no(self.options.with_icuio)}",
"--disable-layoutex",
"--disable-layout",
f"--enable-tools={yes_no(self._enable_icu_tools)}",
"--disable-tests",
"--disable-samples",
])
if cross_building(self):
base_path = unix_path(self, self.dependencies.build["icu-infinipaint"].package_folder)
tc.configure_args.append(f"--with-cross-build={base_path}")
if self.settings.os in ["iOS", "tvOS", "watchOS"]:
# ICU build scripts interpret all Apple platforms as 'darwin'.
# Since this can coincide with the `build` triple, we need to tweak
# the build triple to avoid the collision and ensure the scripts
# know we are cross-building.
host_triplet = f"{str(self.settings.arch)}-apple-darwin"
build_triplet = f"{str(self._settings_build.arch)}-apple"
tc.update_configure_args({"--host": host_triplet,
"--build": build_triplet})
else:
arch64 = ["x86_64", "sparcv9", "ppc64", "ppc64le", "armv8", "armv8.3", "mips64"]
bits = "64" if self.settings.arch in arch64 else "32"
tc.configure_args.append(f"--with-library-bits={bits}")
tc.configure_args.append(f"--with-data-packaging={self.options.data_packaging}")
tc.generate()
if is_msvc(self):
env = Environment()
env.define("CC", "cl -nologo")
env.define("CXX", "cl -nologo")
if cross_building(self):
env.define("icu_cv_host_frag", "mh-msys-msvc")
env.vars(self).save_script("conanbuild_icu_msvc")
def _patch_sources(self):
apply_conandata_patches(self)
replace_in_file(
self,
os.path.join(self.source_folder, "source", "configure"),
"if test -z \"$PYTHON\"",
"if true",
)
if self._settings_build.os == "Windows":
# https://unicode-org.atlassian.net/projects/ICU/issues/ICU-20545
makeconv_cpp = os.path.join(self.source_folder, "source", "tools", "makeconv", "makeconv.cpp")
replace_in_file(self, makeconv_cpp,
"pathBuf.appendPathPart(arg, localError);",
"pathBuf.append(\"/\", localError); pathBuf.append(arg, localError);")
# relocatable shared libs on macOS
mh_darwin = os.path.join(self.source_folder, "source", "config", "mh-darwin")
replace_in_file(self, mh_darwin, "-install_name $(libdir)/$(notdir", "-install_name @rpath/$(notdir")
replace_in_file(self,
mh_darwin,
"-install_name $(notdir $(MIDDLE_SO_TARGET)) $(PKGDATA_TRAILING_SPACE)",
"-install_name @rpath/$(notdir $(MIDDLE_SO_TARGET))",
)
# workaround for https://unicode-org.atlassian.net/browse/ICU-20531
mkdir(self, os.path.join(self.build_folder, "data", "out", "tmp"))
# workaround for "No rule to make target 'out/tmp/dirs.timestamp'"
save(self, os.path.join(self.build_folder, "data", "out", "tmp", "dirs.timestamp"), "")
def build(self):
self._patch_sources()
if self.options.dat_package_file:
dat_package_file = glob.glob(os.path.join(self.source_folder, "source", "data", "in", "*.dat"))
if dat_package_file:
shutil.copy(str(self.options.dat_package_file), dat_package_file[0])
# GCC 16 ICEs compiling ICU 77 headers under -std=gnu++23 (enum in uchar.h).
# Keep the package usable from a C++23 consumer, but build ICU itself as C++17.
if self.settings.os == "Linux":
import os as _os
for var in ("CXXFLAGS", "CFLAGS"):
cur = _os.environ.get(var, "")
parts = [p for p in cur.split() if not p.startswith("-std=") and not p.startswith("-O")]
if var == "CXXFLAGS":
parts.extend(["-std=gnu++17", "-O1"])
else:
parts.append("-O1")
_os.environ[var] = " ".join(parts)
autotools = Autotools(self)
autotools.configure(build_script_folder=os.path.join(self.source_folder, "source"))
if self.settings.os == "Linux":
for root, _, files in os.walk(self.build_folder):
for name in files:
if name in ("Makefile", "mh-linux") or name.endswith(".mk"):
path = os.path.join(root, name)
try:
replace_in_file(self, path, "-O3", "-O1", strict=False)
replace_in_file(self, path, "-std=gnu++23", "-std=gnu++17", strict=False)
replace_in_file(self, path, "-std=c++23", "-std=c++17", strict=False)
except Exception:
pass
autotools.make()
@property
def _data_filename(self):
vtag = Version(self.version).major
return f"icudt{vtag}l.dat"
@property
def _data_path(self):
data_dir_name = "icu"
if self.settings.os == "Windows" and self.settings.build_type == "Debug":
data_dir_name += "d"
data_dir = os.path.join(self.package_folder, "lib", data_dir_name, str(self.version))
return os.path.join(data_dir, self._data_filename)
def package(self):
copy(self, "LICENSE", src=self.source_folder, dst=os.path.join(self.package_folder, "licenses"))
autotools = Autotools(self)
autotools.install()
dll_files = glob.glob(os.path.join(self.package_folder, "lib", "*.dll"))
if dll_files:
bin_dir = os.path.join(self.package_folder, "bin")
mkdir(self, bin_dir)
for dll in dll_files:
dll_name = os.path.basename(dll)
rm(self, dll_name, bin_dir)
rename(self, src=dll, dst=os.path.join(bin_dir, dll_name))
if self.settings.os != "Windows" and self.options.data_packaging in ["files", "archive"]:
mkdir(self, os.path.join(self.package_folder, "res"))
rename(self, src=self._data_path, dst=os.path.join(self.package_folder, "res", self._data_filename))
# Copy some files required for cross-compiling
config_dir = os.path.join(self.package_folder, "config")
copy(self, "icucross.mk", src=os.path.join(self.build_folder, "config"), dst=config_dir)
copy(self, "icucross.inc", src=os.path.join(self.build_folder, "config"), dst=config_dir)
rmdir(self, os.path.join(self.package_folder, "lib", "icu"))
rmdir(self, os.path.join(self.package_folder, "lib", "man"))
rmdir(self, os.path.join(self.package_folder, "lib", "pkgconfig"))
rmdir(self, os.path.join(self.package_folder, "share"))
def package_info(self):
self.cpp_info.set_property("cmake_find_mode", "both")
self.cpp_info.set_property("cmake_file_name", "ICU")
prefix = "s" if self.settings.os == "Windows" and not self.options.shared else ""
suffix = "d" if self.settings.os == "Windows" and self.settings.build_type == "Debug" else ""
# icudata
self.cpp_info.components["icu-data"].set_property("cmake_target_name", "ICU::data")
icudata_libname = "icudt" if self.settings.os == "Windows" else "icudata"
self.cpp_info.components["icu-data"].libs = [f"{prefix}{icudata_libname}{suffix}"]
if not self.options.shared:
self.cpp_info.components["icu-data"].defines.append("U_STATIC_IMPLEMENTATION")
# icu uses c++, so add the c++ runtime
libcxx = stdcpp_library(self)
if libcxx:
self.cpp_info.components["icu-data"].system_libs.append(libcxx)
# Alias of data CMake component
self.cpp_info.components["icu-data-alias"].set_property("cmake_target_name", "ICU::dt")
self.cpp_info.components["icu-data-alias"].requires = ["icu-data"]
# icuuc
self.cpp_info.components["icu-uc"].set_property("cmake_target_name", "ICU::uc")
self.cpp_info.components["icu-uc"].set_property("pkg_config_name", "icu-uc")
self.cpp_info.components["icu-uc"].libs = [f"{prefix}icuuc{suffix}"]
self.cpp_info.components["icu-uc"].requires = ["icu-data"]
if self.settings.os in ["Linux", "FreeBSD"]:
self.cpp_info.components["icu-uc"].system_libs = ["m", "pthread"]
if self.options.with_dyload:
self.cpp_info.components["icu-uc"].system_libs.append("dl")
elif self.settings.os == "Windows":
self.cpp_info.components["icu-uc"].system_libs = ["advapi32"]
# icui18n
self.cpp_info.components["icu-i18n"].set_property("cmake_target_name", "ICU::i18n")
self.cpp_info.components["icu-i18n"].set_property("pkg_config_name", "icu-i18n")
icui18n_libname = "icuin" if self.settings.os == "Windows" else "icui18n"
self.cpp_info.components["icu-i18n"].libs = [f"{prefix}{icui18n_libname}{suffix}"]
self.cpp_info.components["icu-i18n"].requires = ["icu-uc"]
if self.settings.os in ["Linux", "FreeBSD"]:
self.cpp_info.components["icu-i18n"].system_libs = ["m"]
# Alias of i18n CMake component
self.cpp_info.components["icu-i18n-alias"].set_property("cmake_target_name", "ICU::in")
self.cpp_info.components["icu-i18n-alias"].requires = ["icu-i18n"]
# icuio
if self.options.with_icuio:
self.cpp_info.components["icu-io"].set_property("cmake_target_name", "ICU::io")
self.cpp_info.components["icu-io"].set_property("pkg_config_name", "icu-io")
self.cpp_info.components["icu-io"].libs = [f"{prefix}icuio{suffix}"]
self.cpp_info.components["icu-io"].requires = ["icu-i18n", "icu-uc"]
if self.settings.os != "Windows" and self.options.data_packaging in ["files", "archive"]:
self.cpp_info.components["icu-data"].resdirs = ["res"]
data_path = os.path.join(self.package_folder, "res", self._data_filename).replace("\\", "/")
self.runenv_info.prepend_path("ICU_DATA", data_path)
if self._enable_icu_tools or self.options.with_extras:
self.buildenv_info.prepend_path("ICU_DATA", data_path)
if self._enable_icu_tools:
# icutu
self.cpp_info.components["icu-tu"].set_property("cmake_target_name", "ICU::tu")
self.cpp_info.components["icu-tu"].libs = [f"{prefix}icutu{suffix}"]
self.cpp_info.components["icu-tu"].requires = ["icu-i18n", "icu-uc"]
if self.settings.os in ["Linux", "FreeBSD"]:
self.cpp_info.components["icu-tu"].system_libs = ["pthread"]
# icutest
self.cpp_info.components["icu-test"].set_property("cmake_target_name", "ICU::test")
self.cpp_info.components["icu-test"].libs = [f"{prefix}icutest{suffix}"]
self.cpp_info.components["icu-test"].requires = ["icu-tu", "icu-uc"]
# TODO: to remove after conan v2
self.cpp_info.names["cmake_find_package"] = "ICU"
self.cpp_info.names["cmake_find_package_multi"] = "ICU"
self.cpp_info.components["icu-data"].names["cmake_find_package"] = "data"
self.cpp_info.components["icu-data"].names["cmake_find_package_multi"] = "data"
self.cpp_info.components["icu-data-alias"].names["cmake_find_package"] = "dt"
self.cpp_info.components["icu-data-alias"].names["cmake_find_package_multi"] = "dt"
self.cpp_info.components["icu-uc"].names["cmake_find_package"] = "uc"
self.cpp_info.components["icu-uc"].names["cmake_find_package_multi"] = "uc"
self.cpp_info.components["icu-i18n"].names["cmake_find_package"] = "i18n"
self.cpp_info.components["icu-i18n"].names["cmake_find_package_multi"] = "i18n"
self.cpp_info.components["icu-i18n-alias"].names["cmake_find_package"] = "in"
self.cpp_info.components["icu-i18n-alias"].names["cmake_find_package_multi"] = "in"
if self.options.with_icuio:
self.cpp_info.components["icu-io"].names["cmake_find_package"] = "io"
self.cpp_info.components["icu-io"].names["cmake_find_package_multi"] = "io"
if self.settings.os != "Windows" and self.options.data_packaging in ["files", "archive"]:
self.env_info.ICU_DATA.append(data_path)
if self._enable_icu_tools:
self.cpp_info.components["icu-tu"].names["cmake_find_package"] = "tu"
self.cpp_info.components["icu-tu"].names["cmake_find_package_multi"] = "tu"
self.cpp_info.components["icu-test"].names["cmake_find_package"] = "test"
self.cpp_info.components["icu-test"].names["cmake_find_package_multi"] = "test"
if self._enable_icu_tools or self.options.with_extras:
self.env_info.PATH.append(os.path.join(self.package_folder, "bin"))