Skip to content

Commit 61ad0e4

Browse files
Erotemicfacebook-github-bot
authored andcommitted
cmake: python packages now install to the cannonical directory
Summary: Addresses issue pytorch#1676 Now when `make install` is run, the `caffe2` (and `caffe`) python modules will be installed into the correct site-packages directory (relative to the prefix) instead of directly in the prefix. Closes facebookarchive/caffe2#1677 Reviewed By: pietern Differential Revision: D6710247 Pulled By: bddppq fbshipit-source-id: b49167d48fd94d87f7b7c1ebf0f187ec6a203470
1 parent 7c7e09f commit 61ad0e4

File tree

6 files changed

+166
-39
lines changed

6 files changed

+166
-39
lines changed

.jenkins/test.sh

+28-21
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,21 @@
22

33
set -e
44

5+
# Figure out which Python to use
6+
PYTHON="python"
7+
if [ -n "$BUILD_ENVIRONMENT" ]; then
8+
if [[ "$BUILD_ENVIRONMENT" == py2* ]]; then
9+
PYTHON="python2"
10+
elif [[ "$BUILD_ENVIRONMENT" == py3* ]]; then
11+
PYTHON="python3"
12+
fi
13+
fi
14+
15+
# The prefix must mirror the setting from build.sh
16+
INSTALL_PREFIX="/usr/local/caffe2"
17+
# Add the site-packages in the caffe2 install prefix to the PYTHONPATH
18+
SITE_DIR=$($PYTHON -c "from distutils import sysconfig; print(sysconfig.get_python_lib(prefix=''))")
19+
520
LOCAL_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
621
ROOT_DIR=$(cd "$LOCAL_DIR"/.. && pwd)
722

@@ -11,8 +26,8 @@ if [[ "${BUILD_ENVIRONMENT}" == *-android* ]]; then
1126
exit 0
1227
fi
1328

14-
export PYTHONPATH="${PYTHONPATH}:/usr/local/caffe2"
15-
export LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:/usr/local/caffe2/lib"
29+
export PYTHONPATH="${PYTHONPATH}:${INSTALL_PREFIX}/${SITE_DIR}"
30+
export LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:${INSTALL_PREFIX}/lib"
1631

1732
exit_code=0
1833

@@ -38,8 +53,7 @@ fi
3853
mkdir -p ./test/{cpp,python}
3954
TEST_DIR="$PWD/test"
4055

41-
42-
cd /usr/local/caffe2
56+
cd ${INSTALL_PREFIX}
4357

4458
# Commands below may exit with non-zero status
4559
set +e
@@ -61,36 +75,29 @@ for test in ./test/*; do
6175
fi
6276
done
6377

64-
# Figure out which Python to use
65-
PYTHON="python"
66-
if [ -n "$BUILD_ENVIRONMENT" ]; then
67-
if [[ "$BUILD_ENVIRONMENT" == py2* ]]; then
68-
PYTHON="python2"
69-
elif [[ "$BUILD_ENVIRONMENT" == py3* ]]; then
70-
PYTHON="python3"
71-
fi
72-
fi
78+
# Get the relative path to where the caffe2 python module was installed
79+
CAFFE2_PYPATH="$SITE_DIR/caffe2"
7380

7481
# Collect additional tests to run (outside caffe2/python)
7582
EXTRA_TESTS=()
7683

7784
# CUDA builds always include NCCL support
7885
if [[ "$BUILD_ENVIRONMENT" == *-cuda* ]]; then
79-
EXTRA_TESTS+=(caffe2/contrib/nccl)
86+
EXTRA_TESTS+=("$CAFFE2_PYPATH/contrib/nccl")
8087
fi
8188

8289
# Python tests
8390
echo "Running Python tests.."
8491
"$PYTHON" \
8592
-m pytest \
8693
-v \
87-
--junit-xml="$TEST_DIR"/python/result.xml \
88-
--ignore caffe2/python/test/executor_test.py \
89-
--ignore caffe2/python/operator_test/matmul_op_test.py \
90-
--ignore caffe2/python/operator_test/pack_ops_test.py \
91-
--ignore caffe2/python/mkl/mkl_sbn_speed_test.py \
92-
caffe2/python/ \
93-
${EXTRA_TESTS[@]}
94+
--junit-xml="$TEST_DIR/python/result.xml" \
95+
--ignore "$CAFFE2_PYPATH/python/test/executor_test.py" \
96+
--ignore "$CAFFE2_PYPATH/python/operator_test/matmul_op_test.py" \
97+
--ignore "$CAFFE2_PYPATH/python/operator_test/pack_ops_test.py" \
98+
--ignore "$CAFFE2_PYPATH/python/mkl/mkl_sbn_speed_test.py" \
99+
"$CAFFE2_PYPATH/python" \
100+
"${EXTRA_TESTS[@]}"
94101

95102
tmp_exit_code="$?"
96103
if [ "$exit_code" -eq 0 ]; then

caffe2/CMakeLists.txt

+12-5
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,13 @@ endif()
197197

198198

199199
if (BUILD_PYTHON)
200+
# Python site-packages
201+
# Get canonical directory for python site packages (relative to install
202+
# location). It varys from system to system.
203+
pycmd(python_site_packages "
204+
from distutils import sysconfig
205+
print(sysconfig.get_python_lib(prefix=''))
206+
")
200207
# ---[ Python.
201208
add_library(caffe2_pybind11_state MODULE ${Caffe2_CPU_PYTHON_SRCS})
202209
set_target_properties(caffe2_pybind11_state PROPERTIES COMPILE_FLAGS "-fvisibility=hidden")
@@ -214,7 +221,7 @@ if (BUILD_PYTHON)
214221
target_link_libraries(
215222
caffe2_pybind11_state ${Caffe2_CPU_LINK} ${Caffe2_DEPENDENCY_LIBS}
216223
${Caffe2_PYTHON_DEPENDENCY_LIBS})
217-
install(TARGETS caffe2_pybind11_state DESTINATION caffe2/python)
224+
install(TARGETS caffe2_pybind11_state DESTINATION "${python_site_packages}/caffe2/python")
218225

219226
if(USE_CUDA)
220227
add_library(caffe2_pybind11_state_gpu MODULE ${Caffe2_GPU_PYTHON_SRCS})
@@ -233,7 +240,7 @@ if (BUILD_PYTHON)
233240
target_link_libraries(
234241
caffe2_pybind11_state_gpu ${Caffe2_CPU_LINK} ${Caffe2_GPU_LINK} ${Caffe2_DEPENDENCY_LIBS}
235242
${Caffe2_CUDA_DEPENDENCY_LIBS} ${Caffe2_PYTHON_DEPENDENCY_LIBS})
236-
install(TARGETS caffe2_pybind11_state_gpu DESTINATION caffe2/python)
243+
install(TARGETS caffe2_pybind11_state_gpu DESTINATION "${python_site_packages}/caffe2/python")
237244
endif()
238245

239246
if (MSVC AND CMAKE_GENERATOR MATCHES "Visual Studio")
@@ -292,13 +299,13 @@ if (BUILD_PYTHON)
292299

293300
# Install commands
294301
# Pick up static python files
295-
install(DIRECTORY ${CMAKE_BINARY_DIR}/caffe2 DESTINATION .
302+
install(DIRECTORY ${CMAKE_BINARY_DIR}/caffe2 DESTINATION ${python_site_packages}
296303
FILES_MATCHING PATTERN "*.py")
297304
# Caffe proto files
298-
install(DIRECTORY ${CMAKE_BINARY_DIR}/caffe DESTINATION .
305+
install(DIRECTORY ${CMAKE_BINARY_DIR}/caffe DESTINATION ${python_site_packages}
299306
FILES_MATCHING PATTERN "*.py")
300307
# Caffe2 proto files
301-
install(DIRECTORY ${CMAKE_BINARY_DIR}/caffe2 DESTINATION .
308+
install(DIRECTORY ${CMAKE_BINARY_DIR}/caffe2 DESTINATION ${python_site_packages}
302309
FILES_MATCHING PATTERN "*.py")
303310
endif()
304311

cmake/Utils.cmake

+75
Original file line numberDiff line numberDiff line change
@@ -285,3 +285,78 @@ function(caffe2_include_directories)
285285
endif()
286286
endforeach()
287287
endfunction()
288+
289+
290+
###
291+
# Removes common indentation from a block of text to produce code suitable for
292+
# setting to `python -c`, or using with pycmd. This allows multiline code to be
293+
# nested nicely in the surrounding code structure.
294+
#
295+
# This function respsects PYTHON_EXECUTABLE if it defined, otherwise it uses
296+
# `python` and hopes for the best. An error will be thrown if it is not found.
297+
#
298+
# Args:
299+
# outvar : variable that will hold the stdout of the python command
300+
# text : text to remove indentation from
301+
#
302+
function(dedent outvar text)
303+
# Use PYTHON_EXECUTABLE if it is defined, otherwise default to python
304+
if ("${PYTHON_EXECUTABLE}" STREQUAL "")
305+
set(_python_exe "python")
306+
else()
307+
set(_python_exe "${PYTHON_EXECUTABLE}")
308+
endif()
309+
set(_fixup_cmd "import sys; from textwrap import dedent; print(dedent(sys.stdin.read()))")
310+
# Use echo to pipe the text to python's stdinput. This prevents us from
311+
# needing to worry about any sort of special escaping.
312+
execute_process(
313+
COMMAND echo "${text}"
314+
COMMAND "${_python_exe}" -c "${_fixup_cmd}"
315+
RESULT_VARIABLE _dedent_exitcode
316+
OUTPUT_VARIABLE _dedent_text)
317+
if(NOT ${_dedent_exitcode} EQUAL 0)
318+
message(ERROR " Failed to remove indentation from: \n\"\"\"\n${text}\n\"\"\"
319+
Python dedent failed with error code: ${_dedent_exitcode}")
320+
message(FATAL_ERROR " Python dedent failed with error code: ${_dedent_exitcode}")
321+
endif()
322+
# Remove supurflous newlines (artifacts of print)
323+
string(STRIP "${_dedent_text}" _dedent_text)
324+
set(${outvar} "${_dedent_text}" PARENT_SCOPE)
325+
endfunction()
326+
327+
328+
###
329+
# Helper function to run `python -c "<cmd>"` and capture the results of stdout
330+
#
331+
# Runs a python command and populates an outvar with the result of stdout.
332+
# Common indentation in the text of `cmd` is removed before the command is
333+
# executed, so the caller does not need to worry about indentation issues.
334+
#
335+
# This function respsects PYTHON_EXECUTABLE if it defined, otherwise it uses
336+
# `python` and hopes for the best. An error will be thrown if it is not found.
337+
#
338+
# Args:
339+
# outvar : variable that will hold the stdout of the python command
340+
# cmd : text representing a (possibly multiline) block of python code
341+
#
342+
function(pycmd outvar cmd)
343+
dedent(_dedent_cmd "${cmd}")
344+
# Use PYTHON_EXECUTABLE if it is defined, otherwise default to python
345+
if ("${PYTHON_EXECUTABLE}" STREQUAL "")
346+
set(_python_exe "python")
347+
else()
348+
set(_python_exe "${PYTHON_EXECUTABLE}")
349+
endif()
350+
# run the actual command
351+
execute_process(
352+
COMMAND "${_python_exe}" -c "${_dedent_cmd}"
353+
RESULT_VARIABLE _exitcode
354+
OUTPUT_VARIABLE _output)
355+
if(NOT ${_exitcode} EQUAL 0)
356+
message(ERROR " Failed when running python code: \"\"\"\n${_dedent_cmd}\n\"\"\"")
357+
message(FATAL_ERROR " Python command failed with error code: ${_exitcode}")
358+
endif()
359+
# Remove supurflous newlines (artifacts of print)
360+
string(STRIP "${_output}" _output)
361+
set(${outvar} "${_output}" PARENT_SCOPE)
362+
endfunction()

conda/no_cuda/build.sh

-5
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,3 @@ else
5353
fi
5454

5555
make install/fast
56-
57-
# Python libraries got installed to wrong place, so move them
58-
# to the right place. See https://github.com/caffe2/caffe2/issues/1015
59-
echo "Installing Python to $SP_DIR"
60-
mv $PREFIX/caffe2 $SP_DIR

scripts/build_local.sh

+10-4
Original file line numberDiff line numberDiff line change
@@ -39,14 +39,20 @@ else
3939
cd "$BUILD_ROOT"
4040
echo "Building Caffe2 in: $BUILD_ROOT"
4141

42-
# Now, actually build the target.
4342
cmake "$CAFFE2_ROOT" \
4443
"${CMAKE_ARGS[@]}" \
4544
"$@"
4645

47-
if [ "$(uname)" == 'Darwin' ]; then
48-
cmake --build . -- "-j$(sysctl -n hw.ncpu)"
46+
# Determine the number of CPUs to build with.
47+
# If the `CAFFE_MAKE_NCPUS` variable is not specified, use them all.
48+
if [ -n "${CAFFE_MAKE_NCPUS}" ]; then
49+
CAFFE_MAKE_NCPUS="$CAFFE_MAKE_NCPUS"
50+
elif [ "$(uname)" == 'Darwin' ]; then
51+
CAFFE_MAKE_NCPUS="$(sysctl -n hw.ncpu)"
4952
else
50-
cmake --build . -- "-j$(nproc)"
53+
CAFFE_MAKE_NCPUS="$(nproc)"
5154
fi
55+
56+
# Now, actually build the target.
57+
cmake --build . -- "-j$CAFFE_MAKE_NCPUS"
5258
fi

setup.py

+41-4
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,35 @@ def run(self):
8383

8484

8585
class build_ext(setuptools.command.build_ext.build_ext):
86+
"""
87+
Compiles everything when `python setup.py build` is run using cmake.
88+
89+
Custom args can be passed to cmake by specifying the `CMAKE_ARGS`
90+
environment variable. E.g. to build without cuda support run:
91+
`CMAKE_ARGS=-DUSE_CUDA=Off python setup.py build`
92+
93+
The number of CPUs used by `make` can be specified by passing `-j<ncpus>`
94+
to `setup.py build`. By default all CPUs are used.
95+
"""
96+
user_options = [
97+
('jobs=', 'j', 'Specifies the number of jobs to use with make')
98+
]
99+
100+
def initialize_options(self):
101+
setuptools.command.build_ext.build_ext.initialize_options(self)
102+
self.jobs = None
103+
104+
def finalize_options(self):
105+
setuptools.command.build_ext.build_ext.finalize_options(self)
106+
# Check for the -j argument to make with a specific number of cpus
107+
try:
108+
self.jobs = int(self.jobs)
109+
except Exception:
110+
self.jobs = None
111+
86112
def _build_with_cmake(self):
113+
# build_temp resolves to something like: build/temp.linux-x86_64-3.5
114+
# build_lib resolves to something like: build/lib.linux-x86_64-3.5
87115
build_temp = os.path.realpath(self.build_temp)
88116
build_lib = os.path.realpath(self.build_lib)
89117

@@ -101,6 +129,10 @@ def _build_with_cmake(self):
101129
cmake_args = []
102130
log.info('CMAKE_ARGS: {}'.format(cmake_args))
103131

132+
if self.jobs is not None:
133+
# use envvars to pass information to `build_local.sh`
134+
os.environ['CAFFE_MAKE_NCPUS'] = str(self.jobs)
135+
104136
self.compiler.spawn([
105137
os.path.join(TOP_DIR, 'scripts', 'build_local.sh'),
106138
'-DBUILD_SHARED_LIBS=OFF',
@@ -114,8 +146,7 @@ def _build_with_cmake(self):
114146
'-DBUILD_TEST=OFF',
115147
'-BUILD_BENCHMARK=OFF',
116148
'-DBUILD_BINARY=OFF',
117-
TOP_DIR
118-
] + cmake_args)
149+
] + cmake_args + [TOP_DIR])
119150
# This is assuming build_local.sh will use TOP_DIR/build
120151
# as the cmake build directory
121152
self.compiler.spawn([
@@ -124,11 +155,17 @@ def _build_with_cmake(self):
124155
'install'
125156
])
126157
else:
158+
# if `CMAKE_INSTALL_DIR` is specified in the environment, assume
159+
# cmake has been run and skip the build step.
127160
cmake_install_dir = os.environ['CMAKE_INSTALL_DIR']
128161

162+
# CMake will install the python package to a directory that mirrors the
163+
# standard site-packages name. This will vary slightly depending on the
164+
# OS and python version. (e.g. `lib/python3.5/site-packages`)
165+
python_site_packages = sysconfig.get_python_lib(prefix='')
129166
for d in ['caffe', 'caffe2']:
130-
self.copy_tree(os.path.join(cmake_install_dir, d),
131-
os.path.join(build_lib, d))
167+
src = os.path.join(cmake_install_dir, python_site_packages, d)
168+
self.copy_tree(src, os.path.join(build_lib, d))
132169

133170
def get_outputs(self):
134171
return [os.path.join(self.build_lib, d)

0 commit comments

Comments
 (0)