Skip to content

Commit

Permalink
!414 New build script for developers + minor fixes for more recent Py…
Browse files Browse the repository at this point in the history
…thon versions

* Fix installation issue
* Make shellcheck happier
* Make shellcheck happy
* Improve help message for build_locally.sh script
* Fix linter warnings
* Automatically create a pth-file with path to source directory
* Add script to build MindQuantum locally for developpers
* Remove some the uses of soon to be deprecated distutils package
  • Loading branch information
Takishima authored and donghufeng committed Mar 17, 2022
1 parent eef6183 commit d6f2298
Show file tree
Hide file tree
Showing 5 changed files with 283 additions and 39 deletions.
1 change: 1 addition & 0 deletions _build/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
# Copyright 2022 <Huawei Technologies Co., Ltd>
#
# Licensed under the Apache License, Version 2.0 (the "License");
Expand Down
21 changes: 11 additions & 10 deletions _build/backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,13 @@

"""Custom build backend."""

import distutils.log
import hashlib
import logging
import os
import platform
import sys

import setuptools.build_meta

from utils import get_cmake_command

build_sdist = setuptools.build_meta.build_sdist
Expand All @@ -48,18 +47,20 @@ def get_requires_for_build_wheel(config_settings=None):


def generate_digest_file(fname):
"""Generate a SHA256 digest file for the wheels."""
name = os.path.basename(fname)
sha256_hash = hashlib.sha256()
with open(fname, 'rb') as f:
with open(fname, 'rb') as wheel_file:
# Read and update hash string value in blocks of 1M
for byte_block in iter(lambda: f.read(1 << 20), b""):
for byte_block in iter(lambda: wheel_file.read(1 << 20), b""):
sha256_hash.update(byte_block)

with open(f'{fname}.sha256', 'w') as f:
f.write(f'{sha256_hash.hexdigest()} {name}\n')
with open(f'{fname}.sha256', 'w') as digest_file:
digest_file.write(f'{sha256_hash.hexdigest()} {name}\n')


def build_sdist(sdist_directory, config_settings=None):
"""Build a source distribution."""
name = setuptools.build_meta.build_sdist(sdist_directory=sdist_directory, config_settings=config_settings)
generate_digest_file(os.path.join(sdist_directory, name))
return name
Expand Down Expand Up @@ -107,7 +108,7 @@ def build_wheel(wheel_directory, config_settings=None, metadata_directory=None):
name_full,
]

distutils.log.info('Calling ' + ' '.join(sys.argv))
logging.info('Calling %s', ' '.join(sys.argv))

auditwheel.main.main()

Expand All @@ -116,8 +117,8 @@ def build_wheel(wheel_directory, config_settings=None, metadata_directory=None):
raise RuntimeError('Cannot delocate wheel on Linux without the `auditwheel` package installed!') from err
elif delocate_wheel and platform.system() == 'Darwin':
try:
from delocate.cmd import (
delocate_wheel, # pylint: disable=import-outside-toplevel
from delocate.cmd import ( # pylint: disable=import-outside-toplevel
delocate_wheel,
)

argv = sys.argv
Expand All @@ -128,7 +129,7 @@ def build_wheel(wheel_directory, config_settings=None, metadata_directory=None):
name_full,
]

distutils.log.info('Calling ' + ' '.join(sys.argv))
logging.info('Calling %s', ' '.join(sys.argv))
delocate_wheel.main()

sys.argv = argv
Expand Down
17 changes: 9 additions & 8 deletions _build/utils.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
# Copyright 2022 <Huawei Technologies Co., Ltd>
#
# Licensed under the Apache License, Version 2.0 (the "License");
Expand All @@ -15,8 +16,8 @@
"""Helper functions for building MindQuantum."""

import contextlib
import distutils.log
import errno
import logging
import os
import shutil
import stat
Expand Down Expand Up @@ -68,7 +69,7 @@ def remove_read_only(func, path, exc_info):
raise exc_info[0].with_traceback(exc_info[1], exc_info[2])

if os.path.exists(directory):
distutils.log.info(f'Removing {directory} (and everything under it)')
logging.info('Removing %s (and everything under it)', directory)
shutil.rmtree(directory, ignore_errors=False, onerror=remove_read_only)


Expand All @@ -85,7 +86,7 @@ def get_executable(exec_name):

exec_name = os.path.basename(exec_name)

distutils.log.info(f'trying to locate {exec_name} in {root_path}')
logging.info('trying to locate %s in %s', exec_name, root_path)

search_paths = [root_path, os.path.join(root_path, 'bin'), os.path.join(root_path, 'Scripts')]

Expand All @@ -96,9 +97,9 @@ def get_executable(exec_name):
with fdopen(os.devnull, 'w') as devnull:
subprocess.check_call([cmd, '--version'], stdout=devnull, stderr=devnull)
except (OSError, subprocess.CalledProcessError):
distutils.log.info(f' failed in {base_path}')
logging.info(' failed in %s', base_path)
else:
distutils.log.info(f' command found: {cmd}')
logging.info(' command found:%s', cmd)
return cmd

# That did not work: try calling it through Python
Expand All @@ -108,12 +109,12 @@ def get_executable(exec_name):
with fdopen(os.devnull, 'w') as devnull:
subprocess.check_call(cmd + ['--version'], stdout=devnull, stderr=devnull)
except (OSError, subprocess.CalledProcessError):
distutils.log.info(f' failed in {base_path}')
logging.info(' failed in %s', base_path)
else:
distutils.log.info(f' command found: {cmd}')
logging.info(' command found: %s', cmd)
return cmd

distutils.log.info(' command *not* found!')
logging.info(' command *not* found!')
return None


Expand Down
235 changes: 235 additions & 0 deletions build_locally.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
#!/bin/bash
# Copyright 2021 Huawei Technologies Co., Ltd
#
# 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.

BASEPATH=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )

# ==============================================================================
# Default values

do_clean=0
do_clean_build_dir=0
do_clean_cache=0
do_clean_venv=0
enable_gpu=0
n_jobs=$(nproc)

source_dir=$(realpath "$BASEPATH")
build_dir=$(realpath "$source_dir/build")

# ==============================================================================

call_cmake() {
echo "**********"
echo "Calling CMake with: cmake " "$@"
echo "**********"
cmake "$@"
}

# ==============================================================================

die() { echo "$*" >&2; exit 2; } # complain to STDERR and exit with error
no_arg() {
if [ -n "$OPTARG" ]; then die "No arg allowed for --$OPT option"; fi; }
needs_arg() { if [ -z "$OPTARG" ]; then die "No arg for --$OPT option"; fi; }

# ------------------------------------------------------------------------------

help_message() {
echo 'Build MindQunantum locally (in-source build)'
echo ''
echo 'This is mainly relevant for developers that do not want to always '
echo 'have to reinstall the Python package'
echo ''
echo 'This script will create a Python virtualenv in the MindQuantum root'
echo 'directory and then build all the C++ Python modules and place the'
echo 'generated libraries in their right locations within the MindQuantum'
echo 'folder hierarchy so Python knows how to find them.'
echo ''
echo 'A pth-file will be created in the virtualenv site-packages directory'
echo 'so that the MindQuantum root folder will be added to the Python PATH'
echo 'without the need to modify PYTHONPATH.'
echo -e '\nUsage:'
echo " $(basename "$0") [options] [-- cmake_options]"
echo -e '\nOptions:'
echo ' -h,--help Show this help message and exit'
echo ' -B [dir] Specify build directory'
echo " Defaults to: $build_dir"
echo ' -c,--clean Run make clean before building'
echo ' --clean-all Clean everything before building.'
echo ' Equivalent to --clean-venv --clean-builddir'
echo ' --clean-builddir Delete build directory before building'
echo ' --clean-cache Re-run CMake to generate the CMake cache'
echo ' --clean-venv Clean Python virtualenv before building'
echo ' --gpu Enable GPU support'
echo ' -j,--jobs [N] Number of parallel jobs for building'
echo " Defaults to: $n_jobs"
echo ''
echo 'Any options after "--" will be passed onto CMake when configuring'
echo -e '\nExample calls:'
echo "$0 -B build"
echo "$0 -B build --gpu"
echo "$0 -B build -- -DIN_PLACE_BUILD=ON"
}

# ==============================================================================

while getopts hcB:j:-: OPT; do
if [ "$OPT" = "-" ]; then # long option: reformulate OPT and OPTARG
OPT="${OPTARG%%=*}" # extract long option name
OPTARG="${OPTARG#$OPT}" # extract long option argument (may be empty)
OPTARG="${OPTARG#=}" # if long option argument, remove assigning `=`
fi
# shellcheck disable=SC2214
case "$OPT" in
h | help ) no_arg;
help_message >&2
exit 1 ;;
B ) needs_arg;
build_dir="$OPTARG"
;;
c | clean ) no_arg;
do_clean=1
;;
clean-all ) no_arg;
do_clean_venv=1
do_clean_build_dir=1
;;
clean-builddir ) no_arg;
do_clean_build_dir=1
;;
clean-cache ) no_arg;
do_clean_cache=1
;;
clean-venv ) no_arg;
do_clean_venv=1
;;
gpu ) no_arg;
enable_gpu=1
;;
j | jobs ) needs_arg;
n_jobs="$OPTARG"
;;
??* ) die "Illegal option --OPT: $OPT" ;;
\? ) exit 2 ;; # bad short option (error reported via getopts)
esac
done
shift $((OPTIND-1)) # remove parsed options and args from $@ list

# ==============================================================================

if command -v python3 >/dev/null 2>&1; then
PYTHON=python3
elif command -v python >/dev/null 2>&1; then
PYTHON=python3
else
echo 'Unable to locate python or python3!' 1>&2
exit 1
fi

# ==============================================================================

mk_new_dir() {
local create_dir="$1" # the target to make

if [[ -d "${create_dir}" ]];then
rm -rf "${create_dir}"
fi

mkdir -pv "${create_dir}"
}


# ==============================================================================

set -e

cd "${BASEPATH}"

# ------------------------------------------------------------------------------
# Create a virtual environment for building the wheel

if [ $do_clean_venv -eq 1 ]; then
echo "Deleting virtualenv folder: $BASEPATH/venv"
rm -rf venv
fi

if [ $do_clean_build_dir -eq 1 ]; then
echo "Deleting build folder: $build_dir"
rm -rf "$build_dir"
fi

created_venv=0
if [ ! -d "$BASEPATH/venv" ]; then
created_venv=1
echo "Creating Python virtualenv: $PYTHON -m venv venv"
$PYTHON -m venv venv
fi

source venv/bin/activate

if [ $created_venv -eq 1 ]; then
pkgs=(pip setuptools wheel build pybind11)

if [[ "$OSTYPE" == "linux-gnu"* ]]; then
pkgs+=(auditwheel)
elif [[ "$OSTYPE" == "darwin"* ]]; then
pkgs+=(delocate)
fi

echo "Updating Python packages: $PYTHON -m pip install -U ${pkgs[*]}"
$PYTHON -m pip install -U "${pkgs[@]}"
fi

# Make sure the root directory is in the virtualenv PATH
site_pkg_dir=$($PYTHON -c 'import site; print(site.getsitepackages()[0])')
pth_file="$site_pkg_dir/mindquantum_local.pth"

if [ ! -e "$pth_file" ]; then
echo "Creating pth-file in $pth_file"
echo "$BASEPATH" > "$pth_file"
fi

# ------------------------------------------------------------------------------
# Setup arguments for build

cmake_args=(-DENABLE_PROJECTQ:BOOL=ON -DENABLE_QUEST:BOOL=OFF -DIN_PLACE_BUILD:BOOL=ON)

if [[ $enable_gpu -eq 1 ]]; then
cmake_args+=(-DENABLE_CUDA:BOOL=ON)
fi

# ------------------------------------------------------------------------------
# Build

do_configure=0
if [ ! -d "$build_dir" ]; then
do_configure=1
elif [ $do_clean_cache -eq 1 ]; then
do_configure=1
echo "Removing CMake cache at: $build_dir/CMakeCache.txt"
rm -f "$build_dir/CMakeCache.txt"
fi

if [ $do_configure -eq 1 ]; then
call_cmake -S "$source_dir" -B "$build_dir" "${cmake_args[@]}" "$@"
fi

if [ $do_clean -eq 1 ]; then
call_cmake --build "$build_dir" --target clean
fi

call_cmake --build "$build_dir" --target all -j "$n_jobs"

# ==============================================================================
Loading

0 comments on commit d6f2298

Please sign in to comment.