coordinator/setup.py (352 lines of code) (raw):
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# Copyright 2020 Alibaba Group Holding Limited. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
import contextlib
import glob
import itertools
import os
import platform
import shutil
import subprocess
import sys
import tempfile
from distutils.cmd import Command
from setuptools import find_packages
from setuptools import setup
from setuptools.command.build_py import build_py
from setuptools.command.develop import develop
from setuptools.command.sdist import sdist
def get_version(file):
"""Get the version of the package from the given file."""
__version__ = ""
if os.path.isfile(file):
with open(file, "r", encoding="utf-8") as fp:
__version__ = fp.read().strip()
return __version__
repo_root = os.path.dirname(os.path.abspath(__file__))
version = get_version(os.path.join(repo_root, "..", "VERSION"))
GRAPHSCOPE_HOME = os.environ.get("GRAPHSCOPE_HOME", "/usr/local")
INSTALL_PREFIX = os.environ.get("INSTALL_PREFIX", "/opt/graphscope")
# copy any files contains in ${INSTALL_PREFIX} into site-packages/graphscope.runtime
def _get_extra_data(): # noqa: C901
# Copy
# 1) ${INSTALL_PREFIX}
# 2) headers of arrow/glog/gflags/google/openmpi/vineyard
# 3) openmpi
# 4) builtin app libraries
# into site-packages/graphscope.runtime
#
# For shrink the package size less than "100M", we split graphscope into
# 1) graphscope: libs include *.so, runtime such as 'bin', and full-openmpi
# 2) gs-coordinator: include python related code of gscoordinator
# 3) gs-include: header files
# 4) gs-engine: other runtime info such as 'conf', and *.jar
# 5) gs-apps: precompiled builtin applications
def __unknown_platform(action, platform):
raise RuntimeError(f"Unknown platform '{platform}' to {action}")
def __get_homebrew_prefix(package=None):
cmd = [shutil.which("brew"), "--prefix"]
if package is not None:
cmd.append(package)
return subprocess.check_output(cmd).decode("utf-8", errors="ignore").strip("\n")
def __get_openmpi_prefix():
if platform.system() == "Linux":
# install "/opt/openmpi" in gsruntime image
return "/opt/openmpi"
elif platform.system() == "Darwin":
return __get_homebrew_prefix("openmpi")
else:
__unknown_platform("find openmpi", platform.system())
def __get_vineyard_prefix():
if platform.system() == "Linux":
return GRAPHSCOPE_HOME
elif platform.system() == "Darwin":
return __get_homebrew_prefix("vineyard")
else:
__unknown_platform("find vineyard", platform.system())
def __get_lib_suffix():
if platform.system() == "Linux":
return "so"
elif platform.system() == "Darwin":
return "dylib"
else:
__unknown_platform("resolve lib suffix", platform.system())
name = os.environ.get("package_name", "gs-coordinator")
RUNTIME_ROOT = "graphscope.runtime"
data = {}
# data format:
# {"source_dir": "package_dir"} or
# {"source_dir": (package_dir, [exclude_list])}
if name == "graphscope":
# engine and lib
data = {
f"{INSTALL_PREFIX}/bin/": os.path.join(RUNTIME_ROOT, "bin"),
f"{INSTALL_PREFIX}/lib/": os.path.join(RUNTIME_ROOT, "lib"),
f"{INSTALL_PREFIX}/lib64/": os.path.join(RUNTIME_ROOT, "lib64"),
f"{__get_vineyard_prefix()}/lib/libvineyard_internal_registry.{__get_lib_suffix()}": os.path.join(
RUNTIME_ROOT, "lib"
),
}
# openmpi
data.update(
{
__get_openmpi_prefix(): os.path.join(RUNTIME_ROOT),
}
)
elif name == "gs-engine":
data = {
f"{INSTALL_PREFIX}/conf/": os.path.join(RUNTIME_ROOT, "conf"),
f"{INSTALL_PREFIX}/*.jar": os.path.join(RUNTIME_ROOT),
}
elif name == "gs-include":
data = {
f"{INSTALL_PREFIX}/include/": os.path.join(RUNTIME_ROOT, "include"),
f"{GRAPHSCOPE_HOME}/include/grape": os.path.join(RUNTIME_ROOT, "include"),
f"{GRAPHSCOPE_HOME}/include/string_view": os.path.join(
RUNTIME_ROOT, "include"
),
f"{__get_vineyard_prefix()}/include/vineyard": os.path.join(
RUNTIME_ROOT, "include"
),
f"{GRAPHSCOPE_HOME}/include/arrow": os.path.join(RUNTIME_ROOT, "include"),
f"{GRAPHSCOPE_HOME}/include/boost": os.path.join(RUNTIME_ROOT, "include"),
f"{GRAPHSCOPE_HOME}/include/glog": os.path.join(RUNTIME_ROOT, "include"),
f"{GRAPHSCOPE_HOME}/include/gflags": os.path.join(RUNTIME_ROOT, "include"),
f"{GRAPHSCOPE_HOME}/include/google": os.path.join(RUNTIME_ROOT, "include"),
}
if platform.system() == "Linux":
data["/usr/include/rapidjson"] = os.path.join(RUNTIME_ROOT, "include")
data["/usr/include/msgpack"] = os.path.join(RUNTIME_ROOT, "include")
data["/usr/include/msgpack.hpp"] = os.path.join(RUNTIME_ROOT, "include")
# recent protobuf requires include absl directly
if os.path.isdir(f"{GRAPHSCOPE_HOME}/include/absl"):
data[f"{GRAPHSCOPE_HOME}/include/absl"] = os.path.join(
RUNTIME_ROOT, "include"
)
elif platform.system() == "Darwin":
homebrew_prefix = __get_homebrew_prefix()
data[f"{homebrew_prefix}/include/rapidjson"] = os.path.join(
RUNTIME_ROOT, "include"
)
data[f"{homebrew_prefix}/include/msgpack"] = os.path.join(
RUNTIME_ROOT, "include"
)
data[f"{homebrew_prefix}/include/msgpack.hpp"] = os.path.join(
RUNTIME_ROOT, "include"
)
# recent protobuf requires include absl directly
if os.path.isdir(f"{homebrew_prefix}/include/absl"):
data[f"{homebrew_prefix}/include/absl"] = os.path.join(
RUNTIME_ROOT, "include"
)
elif name == "gs-apps":
# precompiled applications
data = {
os.path.join("/", tempfile.gettempprefix(), "gs", "builtin"): os.path.join(
RUNTIME_ROOT, "precompiled"
),
}
return data
class BuildBuiltin(Command):
description = "Build builtin app gar file"
user_options = []
def initialize_options(self):
pass
def finalize_options(self):
pass
def run(self):
app_home_path = os.path.join(repo_root, "gscoordinator", "builtin", "app")
gar_file = os.path.join(app_home_path, "builtin_app.gar")
zip_file = os.path.join(app_home_path, "builtin_app.zip")
# Remove previous files if exist.
for f in [gar_file, zip_file]:
with contextlib.suppress(FileNotFoundError):
os.remove(f)
shutil.make_archive(
os.path.join(app_home_path, "builtin_app"),
format="zip",
root_dir=app_home_path,
base_dir=None,
)
shutil.move(
src=zip_file,
dst=gar_file,
)
class FormatAndLint(Command):
description = "Format and lint code"
user_options = []
user_options = [("inplace=", "i", "Run code formatter and linter inplace")]
def initialize_options(self):
self.inplace = False
def finalize_options(self):
if self.inplace or self.inplace == "True" or self.inplace == "true":
self.inplace = True
else:
self.inplace = False
def run(self):
if self.inplace:
subprocess.check_call([sys.executable, "-m", "isort", "."], cwd=repo_root)
subprocess.check_call([sys.executable, "-m", "black", "."], cwd=repo_root)
subprocess.check_call([sys.executable, "-m", "flake8", "."], cwd=repo_root)
else:
subprocess.check_call(
[sys.executable, "-m", "isort", "--check", "--diff", "."], cwd=repo_root
)
subprocess.check_call(
[sys.executable, "-m", "black", "--check", "--diff", "."], cwd=repo_root
)
subprocess.check_call([sys.executable, "-m", "flake8", "."], cwd=repo_root)
class CustomBuildPy(build_py):
def _get_data_files(self):
"""Add custom out-of-tree package data files."""
rs = super()._get_data_files()
if os.environ.get("WITH_EXTRA_DATA") != "ON":
return rs
for sources, package in _get_extra_data().items():
excludes = []
if isinstance(package, tuple):
excludes = package[1]
package = package[0]
src_dir = os.path.dirname(sources)
build_dir = os.path.join(*([self.build_lib] + package.split(os.sep)))
filenames = []
for file in itertools.chain(
glob.glob(sources + "/**/*", recursive=True),
glob.glob(sources, recursive=False),
):
if os.path.isfile(file) or (
os.path.islink(file) and not os.path.isdir(file)
):
if not any([f in file for f in excludes]):
filenames.append(os.path.relpath(file, src_dir))
rs.append((package, src_dir, build_dir, filenames))
return rs
def run(self):
self.run_command("build_builtin")
build_py.run(self)
class CustomDevelop(develop):
def run(self):
self.run_command("build_builtin")
develop.run(self)
class CustomSDist(sdist):
def run(self):
self.run_command("build_builtin")
sdist.run(self)
with open(
os.path.join(os.path.abspath(os.path.dirname(__file__)), "..", "README.md"),
encoding="utf-8",
mode="r",
) as fp:
long_description = fp.read()
def parsed_package_dir():
name = os.environ.get("package_name", "gs-coordinator")
if name == "gs-coordinator":
return {".": "."}
return {}
def parsed_packages():
name = os.environ.get("package_name", "gs-coordinator")
if name == "gs-coordinator":
return find_packages(".")
return ["graphscope_runtime"]
def parsed_package_data():
name = os.environ.get("package_name", "gs-coordinator")
if name == "gs-coordinator":
return {
"gscoordinator": [
"builtin/app/builtin_app.gar",
"builtin/app/*.yaml",
"template/*.template",
"VERSION",
],
}
return {}
def parsed_reqs():
name = os.environ.get("package_name", "gs-coordinator")
if name == "gs-coordinator":
with open(
os.path.join(repo_root, "requirements.txt"), "r", encoding="utf-8"
) as fp:
pkgs = fp.read().splitlines()
pkgs.append(f"graphscope_client == {version}")
return pkgs
elif name == "graphscope":
return [
f"gs-coordinator == {version}",
f"gs-engine == {version}",
f"gs-include == {version}",
f"gs-apps == {version}",
]
else:
return []
def parsed_dev_reqs():
name = os.environ.get("package_name", "gs-coordinator")
if name == "gs-coordinator":
with open(
os.path.join(repo_root, "requirements-dev.txt"), "r", encoding="utf-8"
) as fp:
return {"dev": fp.read().splitlines()}
return {}
def parse_version(root, **kwargs):
"""
Parse function for setuptools_scm that first tries to read '../VERSION' file
to get a version number.
"""
from setuptools_scm.git import parse
from setuptools_scm.version import meta
version_file = os.path.join(repo_root, "..", "VERSION")
if os.path.isfile(version_file):
with open(version_file, "r", encoding="utf-8") as fp:
return meta(fp.read().strip())
return parse(root, **kwargs)
class GenerateFlexServer(Command):
description = "generate flex server from OpenApi specification file"
user_options = []
def initialize_options(self):
pass
def finalize_options(self):
pass
def run(self):
# generate
specification = os.path.join(
repo_root, "..", "flex", "openapi", "openapi_coordinator.yaml"
)
cmd = [
"openapi-generator",
"generate",
"-g",
"python-flask",
"-i",
str(specification),
"-o",
repo_root,
"--package-name",
"gscoordinator.flex",
]
env = os.environ.copy()
env["OPENAPI_GENERATOR_VERSION"] = "7.3.0"
subprocess.check_call(
cmd,
env=env,
)
setup(
name=os.environ.get("package_name", "gs-coordinator"),
description="",
include_package_data=True,
long_description=long_description,
long_description_content_type="text/markdown",
author="GraphScope Team, Damo Academy",
author_email="graphscope@alibaba-inc.com",
url="https://github.com/alibaba/GraphScope",
license="Apache License 2.0",
classifiers=[
"Development Status :: 5 - Production/Stable",
"Intended Audience :: Developers",
"License :: OSI Approved :: Apache Software License",
"Topic :: Software Development :: Libraries",
"Topic :: System :: Distributed Computing",
"Operating System :: MacOS :: MacOS X",
"Operating System :: POSIX",
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
],
keywords="GraphScope, Graph Computations",
use_scm_version={
"root": repo_root,
"parse": parse_version,
},
setup_requires=[
"setuptools_scm>=5.0.0,<8",
],
package_dir=parsed_package_dir(),
packages=parsed_packages(),
package_data=parsed_package_data(),
cmdclass={
"build_builtin": BuildBuiltin,
"build_py": CustomBuildPy,
"sdist": CustomSDist,
"develop": CustomDevelop,
"lint": FormatAndLint,
"generate_flex_server": GenerateFlexServer,
},
install_requires=parsed_reqs(),
extras_require=parsed_dev_reqs(),
)
if os.name == "nt":
class _ReprableString(str):
def __repr__(self) -> str: # pylint: disable=invalid-repr-returned
return self
raise RuntimeError(
_ReprableString(
"""
====================================================================
GraphScope doesn't support Windows natively, please try to install graphscope in WSL
https://docs.microsoft.com/en-us/windows/wsl/install
with pip.
===================================================================="""
)
)