diff --git a/.gitignore b/.gitignore index 69d7cde35e..b1c3544c55 100755 --- a/.gitignore +++ b/.gitignore @@ -41,6 +41,7 @@ externalTools/fswAuto/autowrapper/wraps/* **/outputFiles/* src/moduleTemplates/autoCModule/* src/moduleTemplates/autoCppModule/* +src/CMakeUserPresets.json supportData/EphemerisData/*.bsp diff --git a/conanfile.py b/conanfile.py index ea7e2b9bc4..a5f2581146 100644 --- a/conanfile.py +++ b/conanfile.py @@ -8,39 +8,29 @@ import pkg_resources +from conan import ConanFile +from conan.tools.cmake import CMakeToolchain, CMake, cmake_layout, CMakeDeps +from conan.tools.microsoft import is_msvc + sys.path.insert(1, './src/utilities/') import makeDraftModule +# XXX: this statement is needed to enable Windows to print ANSI codes in the Terminal +# see https://stackoverflow.com/questions/287871/how-to-print-colored-text-in-terminal-in-python/3332860#3332860 +os.system("") + # define the print color codes statusColor = '\033[92m' failColor = '\033[91m' warningColor = '\033[93m' endColor = '\033[0m' -# TODO: Remove this check (deprecated, same as managePipEnvironment flag below). -try: - from conans import __version__ as conan_version - if int(conan_version[0]) >= 2: - print(failColor + "conan version " + conan_version + " is not compatible with Basilisk.") - print("use version 1.40.1 to 1.xx.0 to work with the conan repo changes." + endColor) - exit(0) - from conans.tools import Version - # check conan version 1.xx - if conan_version < Version("1.40.1"): - print(failColor + "conan version " + conan_version + " is not compatible with Basilisk.") - print("use version 1.40.1+ to work with the conan repo changes from 2021." + endColor) - exit(0) - from conans import ConanFile, CMake, tools -except ModuleNotFoundError: - print("Please make sure you install python conan (version 1.xx, not 2.xx) package\nRun command `pip install \"conan<2\"` " - "for Windows\nRun command `pip3 install \"conan<2\"` for Linux/MacOS") - sys.exit(1) - -# define BSK module option list (option name and default value) + +# # define BSK module option list (option name and default value) bskModuleOptionsBool = { - "opNav": False, - "vizInterface": True, - "buildProject": True, + "opNav": [[True, False], False], + "vizInterface": [[True, False], True], + "buildProject": [[True, False], True], # XXX: Set managePipEnvironment to True to keep the old behaviour of # managing the `pip` environment directly (upgrading, installing Python @@ -48,16 +38,16 @@ # installation, and should only be required for users who are still calling # this file with `python conanfile.py ...`. # TODO: Remove all managePipEnvironment behaviour! - "managePipEnvironment": True + "managePipEnvironment": [[True, False], True], } bskModuleOptionsString = { - "autoKey": "", # TODO: Remove, used only for managePipEnvironment. - "pathToExternalModules": "", - "pyLimitedAPI": "", + "autoKey": [["", "u", "s","c"], ""], # TODO: Remove, used only for managePipEnvironment. + "pathToExternalModules": [["ANY"], ""], + "pyLimitedAPI": [["ANY"], ""], } bskModuleOptionsFlag = { - "clean": False, - "allOptPkg": False # TODO: Remove, used only for managePipEnvironment. + "clean": [[True, False], False], + "allOptPkg": [[True, False], False] # TODO: Remove, used only for managePipEnvironment. } # this statement is needed to enable Windows to print ANSI codes in the Terminal @@ -67,43 +57,51 @@ def is_running_virtual_env(): return sys.prefix != sys.base_prefix +required_conan_version = ">=2.0.5" + class BasiliskConan(ConanFile): name = "Basilisk" homepage = "https://avslab.github.io/basilisk/" f = open('docs/source/bskVersion.txt', 'r') version = f.read() f.close() - generators = "cmake_find_package_multi" - requires = "eigen/3.4.0" + # generators = "CMakeDeps" settings = "os", "compiler", "build_type", "arch" build_policy = "missing" license = "ISC" - options = {"generator": "ANY"} - default_options = {"generator": ""} + # Requirements + requires = [ + "eigen/3.4.0", + ] + package_type = "shared-library" # TODO: Confirm this is correct. + options = { + # define BSK module option list + "sourceFolder": ["ANY"], + "buildFolder": ["ANY"], + "generator": ["ANY"], + } + default_options = { + "sourceFolder": "src", + "buildFolder": "dist3", + "generator": "", + } + + # Sources are located in the same place as this recipe, copy them to the recipe + exports_sources = "setup.py", "CMakeLists.txt", "src/*", "include/*" for opt, value in bskModuleOptionsBool.items(): - options.update({opt: [True, False]}) - default_options.update({opt: value}) + options.update({opt: value[0]}) + default_options.update({opt: value[1]}) for opt, value in bskModuleOptionsString.items(): - options.update({opt: "ANY"}) - default_options.update({opt: value}) + options.update({opt: value[0]}) + default_options.update({opt: value[1]}) for opt, value in bskModuleOptionsFlag.items(): - options.update({opt: [True, False]}) - default_options.update({opt: value}) - - # set cmake generator default - generator = None - - - try: - # enable this flag for access revised conan modules. - subprocess.check_output(["conan", "config", "set", "general.revisions_enabled=1"]) - except: - pass + options.update({opt: value[0]}) + default_options.update({opt: value[1]}) def system_requirements(self): - if not self.options.managePipEnvironment: + if not self.options.get_safe("managePipEnvironment"): return # Don't need to manage any pip requirements # TODO: Remove everything here, which only runs if we have set @@ -126,7 +124,7 @@ def system_requirements(self): # Also install build system requirements. # TODO: Read these from the `pyproject.toml` file directly? # NOTE: These are *NOT* runtime requirements and should *NOT* be in `requirements.txt`! - "conan>=1.40.1, <2.00.0", + "conan>=2.0.5", "setuptools>=70.1.0", "setuptools-scm>=8.0", "packaging>=22", @@ -134,7 +132,7 @@ def system_requirements(self): ] checkStr = "Required" - if self.options.allOptPkg: + if self.options.get_safe("allOptPkg"): optFile = open('requirements_optional.txt', 'r') optionalPkgs = optFile.read().replace("`", "").split('\n') optFile.close() @@ -160,8 +158,8 @@ def system_requirements(self): installCmd = [sys.executable, "-m", "pip", "install"] if not is_running_virtual_env(): - if self.options.autoKey: - choice = self.options.autoKey + if self.options.get_safe("autoKey"): + choice = self.options.get_safe("autoKey") else: choice = input(warningColor + f"Required python package " + elem + " is missing" + endColor + "\nInstall for user (u), system (s) or cancel(c)? ") @@ -177,59 +175,56 @@ def system_requirements(self): except subprocess.CalledProcessError: print(failColor + f"Was not able to install " + elem + endColor) + def build_requirements(self): + # Protobuf is also required as a tool (in order for CMake to find the + # Conan-installed `protoc` compiler). + # See https://github.com/conan-io/conan-center-index/issues/21737 + # and https://github.com/conan-io/conan-center-index/pull/22244#issuecomment-1910770387 + self.tool_requires("protobuf/") + def requirements(self): - if self.options.opNav: - self.requires.add("pcre/8.45") - self.requires.add("opencv/4.5.5") - self.options['opencv'].with_ffmpeg = False # video frame encoding lib - self.options['opencv'].with_ade = False # graph manipulations framework - self.options['opencv'].with_tiff = False # encode/decode image in TIFF format - self.options['opencv'].with_openexr = False # encode/decode image in EXR format - self.options['opencv'].with_webp = False # encode/decode image in WEBP format - self.options['opencv'].with_quirc = False # QR code lib - self.requires.add("zlib/1.2.13") - self.requires.add("xz_utils/5.4.0") + if self.options.get_safe("opNav"): + self.requires("opencv/4.5.5") - if self.options.vizInterface or self.options.opNav: - self.requires.add("protobuf/3.20.0") - self.options['zeromq'].encryption = False # Basilisk does not use data streaming encryption. - self.requires.add("cppzmq/4.5.0") + if self.options.get_safe("vizInterface") or self.options.get_safe("opNav"): + self.requires("protobuf/3.21.12") + self.requires("cppzmq/4.5.0") def configure(self): - if self.options.clean: + if self.options.get_safe("clean"): # clean the distribution folder to start fresh - self.options.clean = False root = os.path.abspath(os.path.curdir) distPath = os.path.join(root, "dist3") if os.path.exists(distPath): shutil.rmtree(distPath, ignore_errors=True) - if self.settings.build_type == "Debug": + if self.settings.get_safe("build_type") == "Debug": print(warningColor + "Build type is set to Debug. Performance will be significantly lower." + endColor) + self.options['zeromq'].encryption = False # Basilisk does not use data streaming encryption. + # Install additional opencv methods - if self.options.opNav: + if self.options.get_safe("opNav"): self.options['opencv'].contrib = True + self.options['opencv'].with_ffmpeg = False # video frame encoding lib + self.options['opencv'].gapi = False # graph manipulations framework + self.options['opencv'].with_tiff = False # generate image in TIFF format + self.options['opencv'].with_openexr = False # generate image in EXR format + self.options['opencv'].with_quirc = False # QR code lib + self.options['opencv'].with_webp = False # raster graphics file format for web + # Raise an issue to conan-center to fix this bug. Using workaround to disable freetype for windows # Issue link: https://github.com/conan-community/community/issues/341 #TODO Remove this once they fix this issue. - if self.settings.os == "Windows": - self.options['opencv'].contrib_freetype = False + # TODO: Confirm if still needed. + if is_msvc(self): + self.options['opencv'].freetype = False + + if is_msvc(self): + self.options["*"].shared = True + + # Other dependency options + self.options['zeromq'].encryption = False # Basilisk does not use data streaming encryption. - if self.options.generator == "": - # Select default generator supplied to cmake based on os - if self.settings.os == "Macos": - self.generator = "Xcode" - elif self.settings.os == "Windows": - self.generator = "Visual Studio 16 2019" - self.options["*"].shared = True - else: - print("Creating a make file for project. ") - print("Specify your own using the -o generator='Name' flag during conan install") - else: - self.generator = str(self.options.generator) - if self.settings.os == "Windows": - self.options["*"].shared = True - print("cmake generator set to: " + statusColor + str(self.generator) + endColor) def package_id(self): if self.settings.compiler == "Visual Studio": @@ -243,55 +238,98 @@ def imports(self): self.keep_imports = True self.copy("*.dll", "../Basilisk", "bin") - def build(self): - if self.options.pathToExternalModules: - print(statusColor + "Including External Folder: " + endColor + str(self.options.pathToExternalModules)) - - root = os.path.abspath(os.path.curdir) + def layout(self): + cmake_layout(self, + src_folder=str(self.options.get_safe("sourceFolder")), + build_folder=str(self.options.get_safe("buildFolder")) + ) - self.folders.source = os.path.join(root, "src") - self.folders.build = os.path.join(root, "dist3") + # XXX: Override the build folder again to keep it consistent between + # multi- (e.g. Visual Studio) and single-config (e.g. Make) generators. + # Otherwise, it's too difficult to extract the value of this into the + # setup.py file programmatically. + self.folders.build = str(self.options.get_safe("buildFolder")) - cmake = CMake(self, set_cmake_flags=True, generator=self.generator) - if self.settings.compiler == "Visual Studio": - cmake.definitions["CONAN_LINK_RUNTIME_MULTI"] = cmake.definitions["CONAN_LINK_RUNTIME"] - cmake.definitions["CONAN_LINK_RUNTIME"] = False - cmake.definitions["BUILD_OPNAV"] = self.options.opNav - cmake.definitions["BUILD_VIZINTERFACE"] = self.options.vizInterface - cmake.definitions["EXTERNAL_MODULES_PATH"] = self.options.pathToExternalModules - cmake.definitions["PYTHON_VERSION"] = f"{sys.version_info.major}.{sys.version_info.minor}" + def generate(self): + if self.options.get_safe("pathToExternalModules"): + print(statusColor + "Including External Folder: " + endColor + str(self.options.pathToExternalModules)) - if self.options.pyLimitedAPI != "": - cmake.definitions["PY_LIMITED_API"] = self.options.pyLimitedAPI + if self.settings.build_type == "Debug": + self.output.warning("Build type is set to Debug. Performance will be significantly slower.") + + # with chdir(self.generators_folder): + # self.output.info("Auto-Generating Draft Modules...") + # genMod = makeDraftModule.moduleGenerator() + # genMod.cleanBuild = True + # genMod.verbose = False + # makeDraftModule.fillCppInfo(genMod) + # genMod.createCppModule() + # makeDraftModule.fillCInfo(genMod) + # genMod.createCModule() + + # ------------------------------------------------------------- + # Run the CMake configuration generators. + # ------------------------------------------------------------- + deps = CMakeDeps(self) + deps.set_property("eigen", "cmake_target_name", "Eigen3::Eigen3") # XXX: Override, original is "Eigen3::Eigen" + deps.set_property("cppzmq", "cmake_target_name", "cppzmq::cppzmq") # XXX: Override, original is "cppzmq" + deps.generate() + + tc = CMakeToolchain(self) + generatorString = str(self.options.get_safe("generator")) + if generatorString == "": + # Select default generator supplied to cmake based on os + if self.settings.os == "Macos": + generatorString = "Xcode" + tc.generator = generatorString + elif self.settings.os == "Windows": + generatorString = "Visual Studio 16 2019" + tc.generator = generatorString + self.options["*"].shared = True + else: + print("Creating a make file for project. ") + print("Specify your own using the -o generator='Name' flag during conan install") + else: + tc.generator = generatorString + if self.settings.os == "Windows": + self.options["*"].shared = True + print("cmake generator set to: " + statusColor + generatorString + endColor) + tc.cache_variables["BUILD_OPNAV"] = bool(self.options.get_safe("opNav")) + tc.cache_variables["BUILD_VIZINTERFACE"] = bool(self.options.get_safe("vizInterface")) + if self.options.get_safe("pathToExternalModules"): + tc.cache_variables["EXTERNAL_MODULES_PATH"] = self.externalModulesPath.resolve().as_posix() + tc.cache_variables["PYTHON_VERSION"] = f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}" # Set the build rpath, since we don't install the targets, so that the # shared libraries can find each other using relative paths. - cmake.definitions["CMAKE_BUILD_RPATH_USE_ORIGIN"] = True + tc.cache_variables["CMAKE_BUILD_RPATH_USE_ORIGIN"] = True + # Set the minimum buildable MacOS version. + # tc.cache_variables["CMAKE_OSX_DEPLOYMENT_TARGET"] = "10.13" + tc.parallel = True - # TODO: Set the minimum buildable MacOS version. - # (This might be necessary but I'm not sure yet, leaving it here for future reference.) - # cmake.definitions["CMAKE_OSX_DEPLOYMENT_TARGET"] = "10.13" + # Generate! + tc.generate() - cmake.parallel = True + def build(self): + + cmake = CMake(self) print(statusColor + "Configuring cmake..." + endColor) cmake.configure() - if self.options.managePipEnvironment: + + if self.options.get_safe("managePipEnvironment"): # TODO: it's only needed when conanfile.py is handling pip installations. self.add_basilisk_to_sys_path() - if self.options.buildProject: + + if self.options.get_safe("buildProject"): print(statusColor + "\nCompiling Basilisk..." + endColor) start = datetime.now() - if self.generator == "Xcode": - # Xcode multi-threaded needs specialized arguments - cmake.build(['--', '-jobs', str(tools.cpu_count()), '-parallelizeTargets']) - else: - cmake.build() + cmake.build() print("Total Build Time: " + str(datetime.now() - start)) print(f"{statusColor}The Basilisk build is successful and the scripts are ready to run{endColor}") else: print(f"{statusColor}Finished configuring the Basilisk project.{endColor}") if self.settings.os != "Linux": - print(f"{statusColor}Please open project file inside dist3 with {self.generator} IDE " + print(f"{statusColor}Please open project file inside dist3 with IDE " f"and build the project for {self.settings.build_type}{endColor}") else: print(f"{statusColor}Please go to dist3 folder and run command " @@ -299,17 +337,17 @@ def build(self): return def add_basilisk_to_sys_path(self): - print("Adding Basilisk module to python\n") + print(f"{statusColor}Adding Basilisk module to python{endColor}\n") # NOTE: "--no-build-isolation" is used here only to force pip to use the # packages installed directly by this Conanfile (using the # "managePipEnvironment" option). Otherwise, it is not necessary. - add_basilisk_module_command = [sys.executable, "-m", "pip", "install", "--no-build-isolation", "-e", "."] + add_basilisk_module_command = [sys.executable, "-m", "pip", "install", "--no-build-isolation", "-e", "../"] - if self.options.allOptPkg: + if self.options.get_safe("allOptPkg"): # Install the optional requirements as well add_basilisk_module_command[-1] = ".[optional]" - if not is_running_virtual_env() and self.options.autoKey != 's': + if not is_running_virtual_env() and self.options.get_safe("autoKey") != 's': add_basilisk_module_command.append("--user") process = subprocess.Popen(add_basilisk_module_command, stdout=subprocess.PIPE, stderr=subprocess.PIPE) @@ -348,23 +386,23 @@ def add_basilisk_to_sys_path(self): parser.add_argument("--buildType", help="build type", default="Release", choices=["Release", "Debug"]) # parser.add_argument("--clean", help="make a clean distribution folder", action="store_true") for opt, value in bskModuleOptionsBool.items(): - parser.add_argument("--" + opt, help="build modules for " + opt + " behavior", default=value, + parser.add_argument("--" + opt, help="build modules for " + opt + " behavior", default=value[1], type=lambda x: (str(x).lower() == 'true')) for opt, value in bskModuleOptionsString.items(): - parser.add_argument("--" + opt, help="using string option for " + opt, default=value) + parser.add_argument("--" + opt, help="using string option for " + opt, default=value[1]) for opt, value in bskModuleOptionsFlag.items(): if sys.version_info < (3, 9, 0): - parser.add_argument("--" + opt, help="using flag option for " + opt, default=value, action='store_true') + parser.add_argument("--" + opt, help="using flag option for " + opt, default=value[1], action='store_true') else: - parser.add_argument("--" + opt, help="using flag option for " + opt, default=value, + parser.add_argument("--" + opt, help="using flag option for " + opt, default=value[1], action=argparse.BooleanOptionalAction) args = parser.parse_args() # set the build destination folder - buildFolderName = 'dist3/conan' + # buildFolderName = 'dist3/conan' # run the auto-module generation script - # this ensures that this script is up to date with the latest BSK code base + # this ensures that this script is up-to-date with the latest BSK code base # and that the associated unit test draft file runs print(statusColor + "Auto-Generating Draft Modules... " + endColor, end=" ") genMod = makeDraftModule.moduleGenerator() @@ -380,32 +418,35 @@ def add_basilisk_to_sys_path(self): conanCmdString = list() conanCmdString.append(f'{sys.executable} -m conans.conan install . --build=missing') conanCmdString.append(' -s build_type=' + str(args.buildType)) - conanCmdString.append(' -if ' + buildFolderName) + # conanCmdString.append(' -if ' + buildFolderName) + optionsString = list() if args.generator: - conanCmdString.append(' -o generator="' + str(args.generator) + '"') + optionsString.append(' -o "&:generator=' + str(args.generator) + '"') for opt, value in bskModuleOptionsBool.items(): - conanCmdString.append(' -o ' + opt + '=' + str(vars(args)[opt])) + optionsString.append(' -o "&:' + opt + '=' + str(vars(args)[opt]) + '"') + conanCmdString.append(''.join(optionsString)) + for opt, value in bskModuleOptionsString.items(): if str(vars(args)[opt]): if opt == "pathToExternalModules": externalPath = os.path.abspath(str(vars(args)[opt]).rstrip(os.path.sep)) if os.path.exists(externalPath): - conanCmdString.append(' -o ' + opt + '=' + externalPath) + conanCmdString.append(' -o ' + opt + "= '&:" + externalPath + "'") else: print(f"{failColor}Error: path {str(vars(args)[opt])} does not exist{endColor}") sys.exit(1) else: - conanCmdString.append(' -o ' + opt + '=' + str(vars(args)[opt])) + conanCmdString.append(' -o "&:' + opt + '=' + str(vars(args)[opt]) + '"') for opt, value in bskModuleOptionsFlag.items(): if vars(args)[opt]: - conanCmdString.append(' -o ' + opt + '=True') + conanCmdString.append(' -o "&:' + opt + '=True"') conanCmdString = ''.join(conanCmdString) - print(statusColor + "Running this conan command:" + endColor) + print(statusColor + "Running:" + endColor) print(conanCmdString) completedProcess = subprocess.run(conanCmdString, shell=True, check=True) # run conan build - cmakeCmdString = f'{sys.executable} -m conans.conan build . -if ' + buildFolderName - print(statusColor + "Running cmake:" + endColor) - print(cmakeCmdString) - completedProcess = subprocess.run(cmakeCmdString, shell=True, check=True) + buildCmdString = f'{sys.executable} -m conans.conan build . ' + ''.join(optionsString) + print(statusColor + "Running:" + endColor) + print(buildCmdString) + completedProcess = subprocess.run(buildCmdString, shell=True, check=True) diff --git a/pyproject.toml b/pyproject.toml index 247a3da563..83c0db87ab 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ requires = [ "packaging>=22", # Due to incompatibility: https://github.com/pypa/setuptools/issues/4483 # Requirements for building Basilisk through conanfile - "conan>=1.40.1, <2.00.0", + "conan>=2.0.5", "cmake>=3.26", "swig==4.2.1" # Known to work with https://github.com/nightlark/swig-pypi/pull/120 ]