| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| import os |
| import sys |
| import glob |
| import shutil |
| import fileinput |
| import platform |
| import argparse |
| import zipfile |
| import hashlib |
| import ssl |
| import requests |
| import subprocess |
| import multiprocessing |
|
|
|
|
| PLATFORM_IS_WINDOWS = platform.system() == "Windows" |
| PLATFORM_IS_LINUX = platform.system() == "Linux" |
| PLATFORM_IS_MAC = platform.system() == "Darwin" |
|
|
|
|
| def parse_args(): |
| parser = argparse.ArgumentParser( |
| description="Build COLMAP and its dependencies locally under Windows, " |
| "Mac, and Linux. Note that under Mac and Linux, it is " |
| "usually easier and faster to use the available package " |
| "managers for the dependencies (see documentation). " |
| "However, if you are on a (cluster) system without root " |
| "access, this script might be useful. This script " |
| "downloads the necessary dependencies automatically from " |
| "the Internet. It assumes that CMake, Boost, Qt5, and CUDA " |
| "(optional) are already installed on the system. Under " |
| "Windows you must specify the location of these libraries.") |
| parser.add_argument("--build_path", required=True) |
| parser.add_argument("--colmap_path", required=True, |
| help="The path to the top COLMAP source folder, which " |
| "contains src/, scripts/, CMakeLists.txt, etc." ) |
| parser.add_argument("--qt_path", default="", |
| required=PLATFORM_IS_WINDOWS or PLATFORM_IS_MAC, |
| help="The path to the folder containing Qt, " |
| "e.g., under Windows: C:/Qt/5.9.1/msvc2013_64/ " |
| "or under Mac: /usr/local/opt/qt/") |
| parser.add_argument("--boost_path", default="", |
| required=PLATFORM_IS_WINDOWS, |
| help="The path to the folder containing Boost, " |
| "e.g., under Windows: " |
| "C:/local/boost_1_64_0/lib64-msvc-12.0") |
| parser.add_argument("--cgal_path", default="", |
| help="The path to the folder containing CGAL, " |
| "e.g., under Windows: C:/dev/CGAL-4.11.2/build") |
| parser.add_argument("--cuda_path", default="", |
| help="The path to the folder containing CUDA, " |
| "e.g., under Windows: C:/Program Files/NVIDIA GPU " |
| "Computing Toolkit/CUDA/v8.0") |
| parser.add_argument("--cuda_archs", default="Auto", |
| help="List of CUDA architectures for which to generate " |
| "code, e.g., Auto, All, Maxwell, Pascal, ...") |
| parser.add_argument("--with_suite_sparse", |
| dest="with_suite_sparse", action="store_true") |
| parser.add_argument("--without_suite_sparse", |
| dest="with_suite_sparse", action="store_false", |
| help="Whether to use SuiteSparse as a sparse solver " |
| "(default with SuiteSparse)") |
| parser.add_argument("--with_cuda", |
| dest="with_cuda", action="store_true") |
| parser.add_argument("--without_cuda", |
| dest="with_cuda", action="store_false", |
| help="Whether to enable CUDA functionality") |
| parser.add_argument("--with_opengl", |
| dest="with_opengl", action="store_true") |
| parser.add_argument("--without_opengl", |
| dest="with_opengl", action="store_false", |
| help="Whether to enable OpenGL functionality") |
| parser.add_argument("--with_tests", |
| dest="with_tests", action="store_true") |
| parser.add_argument("--without_tests", |
| dest="with_tests", action="store_false", |
| help="Whether to build unit tests") |
| parser.add_argument("--build_type", default="Release", |
| help="Build type, e.g., Debug, Release, RelWithDebInfo") |
| parser.add_argument("--cmake_generator", default="", |
| help="CMake generator, e.g., Visual Studio 14") |
| parser.add_argument("--no_ssl_verification", |
| dest="ssl_verification", action="store_false", |
| help="Whether to disable SSL certificate verification " |
| "while downloading the source code") |
|
|
| parser.set_defaults(with_suite_sparse=True) |
| parser.set_defaults(with_cuda=True) |
| parser.set_defaults(with_opengl=True) |
| parser.set_defaults(with_tests=True) |
| parser.set_defaults(ssl_verification=True) |
|
|
| args = parser.parse_args() |
|
|
| args.build_path = os.path.abspath(args.build_path) |
| args.download_path = os.path.join(args.build_path, "__download__") |
| args.install_path = os.path.join(args.build_path, "__install__") |
|
|
| args.cmake_config_args = [] |
| args.cmake_config_args.append( |
| "-DCMAKE_BUILD_TYPE={}".format(args.build_type)) |
| args.cmake_config_args.append( |
| "-DCMAKE_PREFIX_PATH={}".format(args.install_path)) |
| args.cmake_config_args.append( |
| "-DCMAKE_INSTALL_PREFIX={}".format(args.install_path)) |
| if args.cmake_generator: |
| args.cmake_config_args.extend(["-G", args.cmake_generator]) |
| if PLATFORM_IS_WINDOWS: |
| args.cmake_config_args.append( |
| "-DCMAKE_GENERATOR_TOOLSET='host=x64'") |
| if "Win64" not in args.cmake_generator: |
| args.cmake_config_args.append( |
| "-DCMAKE_GENERATOR_PLATFORM=x64") |
|
|
| args.cmake_build_args = ["--"] |
| if PLATFORM_IS_WINDOWS: |
| |
| args.cmake_build_args.append( |
| "/maxcpucount:{}".format(multiprocessing.cpu_count())) |
| else: |
| |
| args.cmake_build_args.append( |
| "-j{}".format(multiprocessing.cpu_count())) |
|
|
| if not args.ssl_verification: |
| ssl._create_default_https_context = ssl._create_unverified_context |
|
|
| return args |
|
|
|
|
| def mkdir_if_not_exists(path): |
| assert os.path.exists(os.path.dirname(os.path.abspath(path))) |
| if not os.path.exists(path): |
| os.makedirs(path) |
|
|
|
|
| def copy_file_if_not_exists(source, destination): |
| if os.path.exists(destination): |
| return |
| shutil.copyfile(source, destination) |
|
|
|
|
| def check_md5_hash(path, md5_hash): |
| computed_md5_hash = hashlib.md5() |
| with open(path, "rb") as f: |
| for chunk in iter(lambda: f.read(4096), b""): |
| computed_md5_hash.update(chunk) |
| computed_md5_hash = computed_md5_hash.hexdigest() |
| if md5_hash != computed_md5_hash: |
| print("MD5 mismatch for {}: {} == {}".format( |
| path, md5_hash, computed_md5_hash)) |
| sys.exit(1) |
|
|
|
|
| def download_zipfile(url, archive_path, unzip_path, md5_hash): |
| if not os.path.exists(archive_path): |
| r = requests.get(url) |
| with open(archive_path, 'wb') as outfile: |
| outfile.write(r.content) |
| check_md5_hash(archive_path, md5_hash) |
| with zipfile.ZipFile(archive_path, "r") as fid: |
| fid.extractall(unzip_path) |
|
|
|
|
| def build_cmake_project(args, path, extra_config_args=[], |
| extra_build_args=[], cmakelists_path=".."): |
| mkdir_if_not_exists(path) |
|
|
| cmake_command = ["cmake"] \ |
| + args.cmake_config_args \ |
| + extra_config_args \ |
| + [cmakelists_path] |
| return_code = subprocess.call(cmake_command, cwd=path) |
| if return_code != 0: |
| print("Command failed:", " ".join(cmake_command)) |
| sys.exit(1) |
|
|
| cmake_command = ["cmake", |
| "--build", ".", |
| "--target", "install", |
| "--config", args.build_type] \ |
| + args.cmake_build_args \ |
| + extra_build_args |
| return_code = subprocess.call(cmake_command, cwd=path) |
| if return_code != 0: |
| print("Command failed:", " ".join(cmake_command)) |
| sys.exit(1) |
|
|
|
|
| def build_eigen(args): |
| path = os.path.join(args.build_path, "eigen") |
| if os.path.exists(path): |
| return |
|
|
| url = "http://gitlab.com/libeigen/eigen/-/archive/3.3.7/eigen-3.3.7.zip" |
| archive_path = os.path.join(args.download_path, "eigen-3.3.7.zip") |
| download_zipfile(url, archive_path, args.build_path, |
| "888aab45512cc0c734b3e8f60280daba") |
| shutil.move(glob.glob(os.path.join(args.build_path, "eigen-*"))[0], path) |
|
|
| build_cmake_project(args, os.path.join(path, "__build__")) |
|
|
|
|
| def build_freeimage(args): |
| path = os.path.join(args.build_path, "freeimage") |
| if os.path.exists(path): |
| return |
|
|
| if PLATFORM_IS_WINDOWS: |
| url = "http://downloads.sourceforge.net/freeimage/FreeImage3180Win32Win64.zip" |
| archive_path = os.path.join(args.download_path, "FreeImage3180Win32Win64.zip") |
| download_zipfile(url, archive_path, args.build_path, |
| "393d3df75b14cbcb4887da1c395596e2") |
| shutil.move(os.path.join(args.build_path, "FreeImage"), path) |
| copy_file_if_not_exists( |
| os.path.join(path, "Dist/x64/FreeImage.h"), |
| os.path.join(args.install_path, "include/FreeImage.h")) |
| copy_file_if_not_exists( |
| os.path.join(path, "Dist/x64/FreeImage.lib"), |
| os.path.join(args.install_path, "lib/FreeImage.lib")) |
| copy_file_if_not_exists( |
| os.path.join(path, "Dist/x64/FreeImage.dll"), |
| os.path.join(args.install_path, "lib/FreeImage.dll")) |
| else: |
| url = "http://downloads.sourceforge.net/freeimage/FreeImage3180.zip" |
| archive_path = os.path.join(args.download_path, "FreeImage3180.zip") |
| download_zipfile(url, archive_path, args.build_path, |
| "f8ba138a3be233a3eed9c456e42e2578") |
| shutil.move(os.path.join(args.build_path, "FreeImage"), path) |
|
|
| if PLATFORM_IS_MAC: |
| with fileinput.FileInput(os.path.join(path, "Makefile.gnu"), |
| inplace=True, backup=".bak") as fid: |
| for line in fid: |
| if "cp *.so Dist/" in line: |
| continue |
| if "FreeImage: $(STATICLIB) $(SHAREDLIB)" in line: |
| line = "FreeImage: $(STATICLIB)" |
| print(line, end="") |
| elif PLATFORM_IS_LINUX: |
| with fileinput.FileInput( |
| os.path.join(path, "Source/LibWebP/src/dsp/" |
| "upsampling_mips_dsp_r2.c"), |
| inplace=True, backup=".bak") as fid: |
| for i, line in enumerate(fid): |
| if i >= 36 and i <= 44: |
| line = line.replace("%[\"", "%[\" ") |
| line = line.replace("\"],", " \"],") |
| print(line, end="") |
| with fileinput.FileInput( |
| os.path.join(path, "Source/LibWebP/src/dsp/" |
| "yuv_mips_dsp_r2.c"), |
| inplace=True, backup=".bak") as fid: |
| for i, line in enumerate(fid): |
| if i >= 56 and i <= 58: |
| line = line.replace("\"#", "\"# ") |
| line = line.replace("\"(%", " \"(%") |
| print(line, end="") |
|
|
| subprocess.call(["make", "-f", "Makefile.gnu", |
| "-j{}".format(multiprocessing.cpu_count())], cwd=path) |
|
|
| copy_file_if_not_exists( |
| os.path.join(path, "Source/FreeImage.h"), |
| os.path.join(args.install_path, "include/FreeImage.h")) |
| copy_file_if_not_exists( |
| os.path.join(path, "libfreeimage.a"), |
| os.path.join(args.install_path, "lib/libfreeimage.a")) |
|
|
|
|
| def build_glew(args): |
| path = os.path.join(args.build_path, "glew") |
| if os.path.exists(path): |
| return |
|
|
| url = "https://github.com/nigels-com/glew/releases/download/glew-2.1.0/glew-2.1.0.zip" |
| archive_path = os.path.join(args.download_path, "glew-2.1.0.zip") |
| download_zipfile(url, archive_path, args.build_path, |
| "dff2939fd404d054c1036cc0409d19f1") |
| shutil.move(os.path.join(args.build_path, "glew-2.1.0"), path) |
|
|
| build_cmake_project(args, os.path.join(path, "build/cmake/__build__")) |
|
|
| if PLATFORM_IS_WINDOWS: |
| shutil.move(os.path.join(args.install_path, "bin/glew32.dll"), |
| os.path.join(args.install_path, "lib/glew32.dll")) |
| os.remove(os.path.join(args.install_path, "bin/glewinfo.exe")) |
| os.remove(os.path.join(args.install_path, "bin/visualinfo.exe")) |
|
|
|
|
| def build_gflags(args): |
| path = os.path.join(args.build_path, "gflags") |
| if os.path.exists(path): |
| return |
|
|
| url = "https://github.com/gflags/gflags/archive/v2.2.2.zip" |
| archive_path = os.path.join(args.download_path, "gflags-2.2.2.zip") |
| download_zipfile(url, archive_path, args.build_path, |
| "ff856ff64757f1381f7da260f79ba79b") |
| shutil.move(os.path.join(args.build_path, "gflags-2.2.2"), path) |
| os.remove(os.path.join(path, "BUILD")) |
|
|
| build_cmake_project(args, os.path.join(path, "__build__")) |
|
|
|
|
| def build_glog(args): |
| path = os.path.join(args.build_path, "glog") |
| if os.path.exists(path): |
| return |
|
|
| url = "https://github.com/google/glog/archive/v0.3.5.zip" |
| archive_path = os.path.join(args.download_path, "glog-0.3.5.zip") |
| download_zipfile(url, archive_path, args.build_path, |
| "454766d0124951091c95bad33dafeacd") |
| shutil.move(os.path.join(args.build_path, "glog-0.3.5"), path) |
|
|
| build_cmake_project(args, os.path.join(path, "__build__")) |
|
|
|
|
| def build_suite_sparse(args): |
| if not args.with_suite_sparse: |
| return |
|
|
| path = os.path.join(args.build_path, "suite-sparse") |
| if os.path.exists(path): |
| return |
|
|
| url = "https://codeload.github.com/jlblancoc/" \ |
| "suitesparse-metis-for-windows/zip/" \ |
| "7bc503bfa2c4f1be9176147d36daf9e18340780a" |
| archive_path = os.path.join(args.download_path, "suite-sparse.zip") |
| download_zipfile(url, archive_path, args.build_path, |
| "e7c27075e8e0afc9d2cf188630090946") |
| shutil.move(os.path.join(args.build_path, |
| "suitesparse-metis-for-windows-" |
| "7bc503bfa2c4f1be9176147d36daf9e18340780a"), path) |
|
|
| build_cmake_project(args, os.path.join(path, "__build__")) |
|
|
| if PLATFORM_IS_WINDOWS: |
| lapack_blas_path = os.path.join(path, "lapack_windows/x64/*") |
| mkdir_if_not_exists(os.path.join(args.install_path, "lib64")) |
| mkdir_if_not_exists(os.path.join(args.install_path, |
| "lib64/lapack_blas_windows")) |
| for library_path in glob.glob(lapack_blas_path): |
| copy_file_if_not_exists( |
| library_path, os.path.join(args.install_path, |
| "lib64/lapack_blas_windows", |
| os.path.basename(library_path))) |
|
|
|
|
| def build_ceres_solver(args): |
| path = os.path.join(args.build_path, "ceres-solver") |
| if os.path.exists(path): |
| return |
|
|
| url = "https://github.com/ceres-solver/ceres-solver/archive/2.1.0.zip" |
| archive_path = os.path.join(args.download_path, "ceres-solver-2.1.0.zip") |
| download_zipfile(url, archive_path, args.build_path, |
| "0d4fbfd9d381b85a362365c8d5f468c8") |
| shutil.move(os.path.join(args.build_path, "ceres-solver-2.1.0"), path) |
|
|
| extra_config_args = [ |
| "-DBUILD_TESTING=OFF", |
| "-DBUILD_EXAMPLES=OFF", |
| ] |
|
|
| if args.with_suite_sparse: |
| extra_config_args.extend([ |
| "-DLAPACK=ON", |
| "-DSUITESPARSE=ON", |
| ]) |
| if PLATFORM_IS_WINDOWS: |
| extra_config_args.extend([ |
| "-DLAPACK_LIBRARIES={}".format( |
| os.path.join(args.install_path, |
| "lib64/lapack_blas_windows/liblapack.lib")), |
| "-DBLAS_LIBRARIES={}".format( |
| os.path.join(args.install_path, |
| "lib64/lapack_blas_windows/libblas.lib")), |
| ]) |
|
|
| if PLATFORM_IS_WINDOWS: |
| extra_config_args.append("-DCMAKE_CXX_FLAGS=/DGOOGLE_GLOG_DLL_DECL=") |
|
|
| build_cmake_project(args, os.path.join(path, "__build__"), |
| extra_config_args=extra_config_args) |
|
|
|
|
| def build_colmap(args): |
| extra_config_args = [] |
| if args.qt_path != "": |
| extra_config_args.append("-DQt5_DIR={}".format( |
| os.path.join(args.qt_path, "lib/cmake/Qt5"))) |
|
|
| if args.boost_path != "": |
| extra_config_args.append( |
| "-DBOOST_ROOT={}".format(args.boost_path)) |
| extra_config_args.append( |
| "-DBOOST_LIBRARYDIR={}".format(args.boost_path)) |
|
|
| if args.cuda_path != "": |
| extra_config_args.append( |
| "-DCUDA_TOOLKIT_ROOT_DIR={}".format(args.cuda_path)) |
|
|
| if args.with_cuda: |
| extra_config_args.append("-DCUDA_ENABLED=ON") |
| else: |
| extra_config_args.append("-DCUDA_ENABLED=OFF") |
|
|
| if args.cuda_archs: |
| extra_config_args.append("-DCUDA_ARCHS={}".format(args.cuda_archs)) |
|
|
| if args.with_opengl: |
| extra_config_args.append("-DOPENGL_ENABLED=ON") |
| else: |
| extra_config_args.append("-DOPENGL_ENABLED=OFF") |
|
|
| if args.with_tests: |
| extra_config_args.append("-DTESTS_ENABLED=ON") |
| else: |
| extra_config_args.append("-DTESTS_ENABLED=OFF") |
|
|
| if args.cgal_path: |
| extra_config_args.append("-DCGAL_DIR={}".format(args.cgal_path)) |
|
|
| if PLATFORM_IS_WINDOWS: |
| extra_config_args.append("-DCMAKE_CXX_FLAGS=/DGOOGLE_GLOG_DLL_DECL=") |
|
|
| mkdir_if_not_exists(os.path.join(args.build_path, "colmap")) |
|
|
| build_cmake_project(args, os.path.join(args.build_path, "colmap/__build__"), |
| extra_config_args=extra_config_args, |
| cmakelists_path=os.path.abspath(args.colmap_path)) |
|
|
|
|
| def build_post_process(args): |
| if PLATFORM_IS_WINDOWS: |
| lapack_paths = glob.glob( |
| os.path.join(args.install_path, "lib64/lapack_blas_windows/*.dll")) |
| if lapack_paths: |
| for lapack_path in lapack_paths: |
| copy_file_if_not_exists( |
| lapack_path, |
| os.path.join( |
| args.install_path, "lib", |
| os.path.basename(lapack_path))) |
|
|
| if args.qt_path: |
| copy_file_if_not_exists( |
| os.path.join(args.qt_path, "bin/Qt5Core.dll"), |
| os.path.join(args.install_path, "lib/Qt5Core.dll")) |
| copy_file_if_not_exists( |
| os.path.join(args.qt_path, "bin/Qt5Gui.dll"), |
| os.path.join(args.install_path, "lib/Qt5Gui.dll")) |
| copy_file_if_not_exists( |
| os.path.join(args.qt_path, "bin/Qt5Widgets.dll"), |
| os.path.join(args.install_path, "lib/Qt5Widgets.dll")) |
| mkdir_if_not_exists( |
| os.path.join(args.install_path, "lib/platforms")) |
| copy_file_if_not_exists( |
| os.path.join(args.qt_path, "plugins/platforms/qwindows.dll"), |
| os.path.join(args.install_path, "lib/platforms/qwindows.dll")) |
|
|
| if args.with_cuda and args.cuda_path: |
| cudart_lib_path = glob.glob(os.path.join(args.cuda_path, |
| "bin/cudart64_*.dll"))[0] |
| copy_file_if_not_exists( |
| cudart_lib_path, |
| os.path.join(args.install_path, "lib", |
| os.path.basename(cudart_lib_path))) |
|
|
| if args.cgal_path: |
| gmp_lib_path = os.path.join( |
| args.cgal_path, "auxiliary/gmp/lib/libgmp-10.dll") |
| if os.path.exists(gmp_lib_path): |
| copy_file_if_not_exists( |
| gmp_lib_path, |
| os.path.join(args.install_path, "lib/libgmp-10.dll")) |
| cgal_lib_path = glob.glob(os.path.join( |
| args.cgal_path, "bin/CGAL-vc*-mt-*.dll")) |
| copy_file_if_not_exists( |
| cgal_lib_path[0], |
| os.path.join(args.install_path, "lib", |
| os.path.basename(cgal_lib_path[0]))) |
|
|
|
|
| def main(): |
| args = parse_args() |
|
|
| mkdir_if_not_exists(args.build_path) |
| mkdir_if_not_exists(args.download_path) |
| mkdir_if_not_exists(args.install_path) |
| mkdir_if_not_exists(os.path.join(args.install_path, "include")) |
| mkdir_if_not_exists(os.path.join(args.install_path, "bin")) |
| mkdir_if_not_exists(os.path.join(args.install_path, "lib")) |
| mkdir_if_not_exists(os.path.join(args.install_path, "share")) |
|
|
| build_eigen(args) |
| build_freeimage(args) |
| build_glew(args) |
| build_gflags(args) |
| build_glog(args) |
| build_suite_sparse(args) |
| build_ceres_solver(args) |
| build_colmap(args) |
| build_post_process(args) |
|
|
| print() |
| print() |
| print("Successfully installed COLMAP in: {}".format(args.install_path)) |
| if PLATFORM_IS_WINDOWS: |
| print(" To run COLMAP, navigate to {} and run COLMAP.bat".format( |
| args.install_path)) |
| else: |
| print(" To run COLMAP, execute LD_LIBRARY_PATH={} {}".format( |
| os.path.join(args.install_path, "lib"), |
| os.path.join(args.install_path, "colmap"))) |
|
|
|
|
| if __name__ == "__main__": |
| main() |
|
|