diff --git a/.build/build.spec b/.build/build.spec index 76219d3d..c5d74d08 100644 --- a/.build/build.spec +++ b/.build/build.spec @@ -3,34 +3,35 @@ from fiat.util import generic_folder_check import inspect import os import sys -from pathlib import Path +from pathlib import Path, PurePath #Pre build event setup app_name = "fiat" sys.setrecursionlimit(5000) _file = Path(inspect.getfile(lambda: None)) -cwd = _file.parent -env_path = os.path.dirname(sys.executable) -generic_folder_check(Path(cwd, "../bin")) -mode = "Release" -proj = Path(os.environ["PROJ_LIB"]) +# Get project root directory +project_root = _file.parents[1].absolute() + +build_dir = project_root / ".build" -binaries = [ - (Path(proj, 'proj.db'), './share'), -] +# check if bin folder is in build dir. +generic_folder_check(build_dir / "bin") +mode = "Release" # Build event a = Analysis( - [Path(cwd, "../src/fiat/cli/main.py")], - pathex=[Path(cwd, "../src"), Path(env_path, "lib/site-packages")], - binaries=binaries, + [Path(project_root, "src/fiat/cli/main.py")], + pathex=[Path(project_root, "src")], + binaries=[], datas=[], hiddenimports=[], - hookspath=[], + hookspath=[".build"], hooksconfig={}, - runtime_hooks=[Path(cwd, 'runtime_hooks.py')], + runtime_hooks=[ + Path(build_dir, "pyi_rth_fiat.py"), + ], excludes=[], win_no_prefer_redirects=False, win_private_assemblies=False, diff --git a/.build/hook-fiat.py b/.build/hook-fiat.py new file mode 100644 index 00000000..780c3ed7 --- /dev/null +++ b/.build/hook-fiat.py @@ -0,0 +1,50 @@ +""" +Analysis hook for fiat installation. + +Helps finding different dependencies for PyInstaller. +""" + +import os +import sys + +from PyInstaller.compat import is_conda, is_win +from PyInstaller.utils.hooks.conda import ( + Distribution, + distribution, +) + +datas = [] + +if hasattr(sys, "real_prefix"): # check if in a virtual environment + root_path = sys.real_prefix +else: + root_path = sys.prefix + +if is_conda: + netcdf_dist: Distribution = distribution("libgdal-netcdf") + # append netcdf files to datas + datas += list( + map( + lambda path: (os.path.join(root_path, path), str(path.parent)), + netcdf_dist.files, + ) + ) + # a runtime hook defines the path for `GDAL_DRIVER_PATH` + +# - conda-specific +if is_win: + tgt_proj_data = os.path.join("Library", "share", "proj") + src_proj_data = os.path.join(root_path, "Library", "share", "proj") + +else: # both linux and darwin + tgt_proj_data = os.path.join("share", "proj") + src_proj_data = os.path.join(root_path, "share", "proj") + +if is_conda: + if os.path.exists(src_proj_data): + datas.append((src_proj_data, tgt_proj_data)) + else: + from PyInstaller.utils.hooks import logger + + logger.warning("Datas for proj not found at:\n{}".format(src_proj_data)) + # A runtime hook defines the path for `PROJ_LIB` diff --git a/.build/pyi_rth_fiat.py b/.build/pyi_rth_fiat.py new file mode 100644 index 00000000..b38b566b --- /dev/null +++ b/.build/pyi_rth_fiat.py @@ -0,0 +1,34 @@ +""" +Pyinstaller hook that sets mandatory env_vars. + +Based on https://github.com/conda-forge/gdal-feedstock/blob/main/recipe/scripts/activate.sh +and pyproj hooks. +""" + +import os +import sys + +# Installing `osgeo` Conda packages with plugins requires to set `GDAL_DRIVER_PATH` +is_win = sys.platform.startswith("win") + +if is_win: + gdal_plugins = os.path.join(sys._MEIPASS, "share", "gdalplugins") + if not os.path.exists(gdal_plugins): + gdal_plugins = os.path.join(sys._MEIPASS, "Library", "lib", "gdalplugins") + # last attempt, check if one of the required file is in the generic folder Library/data + if not os.path.exists(os.path.join(gdal_plugins, "gcs.csv")): + gdal_plugins = os.path.join(sys._MEIPASS, "Library", "lib") + +else: + gdal_plugins = os.path.join(sys._MEIPASS, "lib", "gdalplugins") + +if os.path.exists(gdal_plugins): + os.environ["GDAL_DRIVER_PATH"] = gdal_plugins + +if is_win: + proj_data = os.path.join(sys._MEIPASS, "Library", "share", "proj") +else: + proj_data = os.path.join(sys._MEIPASS, "share", "proj") + +if os.path.exists(proj_data): + os.environ["PROJ_LIB"] = proj_data diff --git a/.build/runtime_hooks.py b/.build/runtime_hooks.py deleted file mode 100644 index 7838bb97..00000000 --- a/.build/runtime_hooks.py +++ /dev/null @@ -1,10 +0,0 @@ -"""Runtime hooks for pyinstaller.""" - -import os -import sys -from pathlib import Path - -cwd = Path(sys.executable).parent - -os.environ["PROJ_LIB"] = str(Path(cwd, "bin", "share")) -sys.path.append(str(Path(cwd, "bin", "share"))) diff --git a/pixi.toml b/pixi.toml index 0264ef88..d628aee8 100644 --- a/pixi.toml +++ b/pixi.toml @@ -33,6 +33,9 @@ quarto-quick = { cmd = ["quarto", "render", "docs"] } quarto-render = { cmd = ["quarto", "render", "docs", "--execute"] } serve = { cmd = ["python", "-m", "http.server", "8000", "-d", "docs/_site"] } +# Build +build = { cmd = "pyinstaller .build/build.spec --distpath .build/bin --workpath .build/intermediates", env = { "PROJ_LIB" = "$CONDA_PREFIX/share/proj" } } + # Testing test = { cmd = ["pytest"] } test-lf = { cmd = ["pytest", "--lf", "--tb=short"] } @@ -55,6 +58,7 @@ clean-docs = { cmd = ["rm", "-rf", "docs/_site"] } ## Dependencies [dependencies] gdal = ">=3.5" +libgdal = "*" numpy = "*" regex = "*" tomli = "*"