diff --git a/.github/workflows/ci-conan.yml b/.github/workflows/ci-conan.yml index 93ffe1d..7b47a5e 100644 --- a/.github/workflows/ci-conan.yml +++ b/.github/workflows/ci-conan.yml @@ -1,75 +1,59 @@ -name: cosim CI Conan +name: CI # This workflow is triggered on pushes to the repository. on: [push, workflow_dispatch] jobs: - conan-on-linux: - name: Conan + linux: + name: Linux runs-on: ubuntu-latest strategy: fail-fast: false matrix: build_type: [Debug, Release] compiler_version: [9] - compiler_libcxx: [libstdc++11] option_proxyfmu: ['proxyfmu=True', 'proxyfmu=False'] option_shared: ['shared=True', 'shared=False'] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Generate Dockerfile run: | mkdir /tmp/osp-builder-docker cat <<'EOF' >/tmp/osp-builder-docker/Dockerfile - FROM conanio/gcc${{ matrix.compiler_version }} - USER root - RUN apt-get update && apt-get install -y --force-yes patchelf - ENV CONAN_LOGIN_USERNAME_OSP=${{ secrets.osp_artifactory_usr }} - ENV CONAN_PASSWORD_OSP=${{ secrets.osp_artifactory_pwd }} - ENV CONAN_REVISIONS_ENABLED=1 - ENV CONAN_NON_INTERACTIVE=1 - ENV CONAN_USE_ALWAYS_SHORT_PATHS=1 + FROM conanio/gcc${{ matrix.compiler_version }}-ubuntu16.04 COPY entrypoint.sh / ENTRYPOINT /entrypoint.sh EOF - name: Generate entrypoint.sh run: | - mkdir build - chmod 777 build cat <<'EOF' >/tmp/osp-builder-docker/entrypoint.sh #!/bin/bash -v set -eu - cd /mnt/source/build conan remote add osp https://osp.jfrog.io/artifactory/api/conan/conan-local --force - conan install -s build_type=${{ matrix.build_type }} -s compiler.version=${{ matrix.compiler_version }} -s compiler.libcxx=${{ matrix.compiler_libcxx }} -o libcosim:${{ matrix.option_proxyfmu }} -o libcosim:${{ matrix.option_shared }} -b missing .. - for f in dist/lib/*; do patchelf --set-rpath \$ORIGIN $f; done - cmake -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} .. - cmake --build . - cmake --build . --target install + cd /mnt/source + conan install . -s build_type=${{ matrix.build_type }} -o "libcosim/*:${{ matrix.option_proxyfmu }}" -o "libcosim/*:${{ matrix.option_shared }}" --build=missing + cmake -S . -B build/${{ matrix.build_type }} -DCMAKE_TOOLCHAIN_FILE=generators/conan_toolchain.cmake -DCMAKE_POLICY_DEFAULT_CMP0091=NEW -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} + cmake --build build/${{ matrix.build_type }} + cmake --build build/${{ matrix.build_type }} --target install EOF chmod 0777 /tmp/osp-builder-docker/entrypoint.sh - name: Build Docker image - run: | - docker build -t osp-builder /tmp/osp-builder-docker/ + run: docker build -t osp-builder /tmp/osp-builder-docker/ - name: Build cosim run: | - docker run --rm --env GITHUB_REF="$GITHUB_REF" -v $(pwd):/mnt/source osp-builder + chmod 0777 $(pwd) # because commands in conanio containers run as an unprivileged user + mkdir -m 0777 build + docker run --rm -v $(pwd):/mnt/source osp-builder - name: Upload artifact - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 with: name: cosim-${{ runner.os }}-${{ matrix.build_type }}-${{ matrix.compiler_version }}-${{ matrix.option_proxyfmu }}-${{ matrix.option_shared }} - path: build/dist + path: build/${{ matrix.build_type }}/dist - conan-on-windows: - name: Conan + windows: + name: Windows runs-on: ${{ matrix.os }} - env: - CONAN_LOGIN_USERNAME_OSP: ${{ secrets.osp_artifactory_usr }} - CONAN_PASSWORD_OSP: ${{ secrets.osp_artifactory_pwd }} - CONAN_REVISIONS_ENABLED: 1 - CONAN_NON_INTERACTIVE: 1 - CONAN_USE_ALWAYS_SHORT_PATHS: 1 strategy: fail-fast: false matrix: @@ -79,24 +63,23 @@ jobs: option_shared: ['shared=True', 'shared=False'] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Install prerequisites run: | pip3 install --upgrade setuptools pip - pip3 install conan --force-reinstall -v "conan==1.59" + pip3 install conan - name: Configure Conan - run: conan remote add osp https://osp.jfrog.io/artifactory/api/conan/conan-local --force + run: | + conan profile detect + conan remote add osp https://osp.jfrog.io/artifactory/api/conan/conan-local --force - name: Build - shell: bash run: | - mkdir build - cd build - conan install -s build_type=${{ matrix.build_type }} -o libcosim:${{ matrix.option_proxyfmu }} -o libcosim:${{ matrix.option_shared }} -b missing ../ - cmake -A x64 ../ - cmake --build . --config ${{ matrix.build_type }} - cmake --build . --config ${{ matrix.build_type }} --target install + conan install . -s build_type=${{ matrix.build_type }} -o "libcosim/*:${{ matrix.option_proxyfmu }}" -o "libcosim/*:${{ matrix.option_shared }}" --build=missing + cmake -S . -B build "-DCMAKE_TOOLCHAIN_FILE=generators/conan_toolchain.cmake" -DCMAKE_POLICY_DEFAULT_CMP0091=NEW + cmake --build build --config ${{ matrix.build_type }} + cmake --build build --config ${{ matrix.build_type }} --target install - name: Upload artifact - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 with: name: cosim-${{ runner.os }}-${{ matrix.build_type }}-${{ matrix.option_proxyfmu }}-${{ matrix.option_shared }} path: build/dist diff --git a/.gitignore b/.gitignore index b59ca6f..2311650 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ *.swp build/ +CMakeUserPresets.json # JetBrains CLion: .idea/ diff --git a/CMakeLists.txt b/CMakeLists.txt index ec0fb64..73f603a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,9 @@ -cmake_minimum_required(VERSION 3.9) +cmake_minimum_required(VERSION 3.15) project("cosim" VERSION "0.8.0") -set(CMAKE_INSTALL_PREFIX "${CMAKE_BINARY_DIR}/dist") + +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${CMAKE_BINARY_DIR}/dist" CACHE PATH "Install prefix" FORCE) +endif() # To enable verbose when needed set(CMAKE_VERBOSE_MAKEFILE OFF) @@ -33,15 +36,8 @@ endif() # Dependencies # ============================================================================== -if(EXISTS ${CMAKE_BINARY_DIR}/conanbuildinfo.cmake) - include("${CMAKE_BINARY_DIR}/conanbuildinfo.cmake") - conan_basic_setup(NO_OUTPUT_DIRS) -else() - message(FATAL_ERROR "The file conanbuildinfo.cmake doesn't exist, you have to run conan install first!") -endif() - find_package(libcosim REQUIRED) -find_package(Boost REQUIRED COMPONENTS program_options) +find_package(Boost REQUIRED COMPONENTS log program_options) # ============================================================================== # Targets @@ -77,7 +73,7 @@ add_executable(cosim "src/version_option.cpp" ) target_include_directories(cosim PRIVATE "${generatedFilesDir}") -target_link_libraries(cosim PRIVATE libcosim::cosim Boost::program_options) +target_link_libraries(cosim PRIVATE libcosim::cosim Boost::log Boost::program_options) if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") # This makes the linker set RPATH rather than RUNPATH for the resulting diff --git a/README.md b/README.md index de5794b..2739e9a 100644 --- a/README.md +++ b/README.md @@ -4,9 +4,9 @@ cosim `cosim` is a command-line co-simulation tool based on [libcosim]. It has three primary use cases: - * Running simulations from other programs or scripts + * Running co-simulations from a command-line environment + * Running co-simulations from other programs or scripts * FMU testing and debugging - * Users who simply prefer to work from the command line Specifically, `cosim` can be used to perform the following tasks: @@ -15,7 +15,7 @@ Specifically, `cosim` can be used to perform the following tasks: * Show information about an FMU The output from the simulations is in the form of CSV files that can be easily -parsed by other programs, for example Microsoft Excel. +parsed by other programs. Usage ----- @@ -30,8 +30,8 @@ extra information (e.g. progress) to the user's terminal: cosim run path/to/my_system --output-dir=path/to/my_results -v -The documentation is built into the program itself, by means of the `help` subcommand. - +The documentation is built into the program itself and can be accessed by +means of the `help` subcommand. How to build ------------ @@ -40,30 +40,43 @@ The tools and steps required to build `cosim` are more or less the same as those required for libcosim, so we refer to the [libcosim README] for this information. There are some noteworthy differences, though: - * Conan is a *mandatory* requirement for the time being. + * Conan 2.x is a *mandatory* requirement for the time being. * Doxygen is not needed, as there is no API documentation to generate. To summarise, a typical configure–build–run session might look like the following. -On Linux: - - mkdir build - cd build - conan install .. - cmake .. -DCMAKE_BUILD_TYPE=Debug - cmake --build . - ./cosim help - +On Linux, starting from the root source directory (the one that contains this +README): +```sh +conan install -s build_type=Release --build=missing . # Install dependencies +cmake --preset=conan-release # Configure build system +cmake --build --preset=conan-release # Build +cmake --build --preset=conan-release --target=install # Install to dist/ +build/Release/dist/bin/cosim help # Run +``` And on Windows: - - mkdir build - cd build - conan install .. -s build_type=Debug - cmake .. -A x64 - cmake --build . - activate_run.bat - Debug\cosim help - deactivate_run.bat - - +```bat +conan install -s build_type=Release --build=missing . &:: Install dependencies +cmake --preset=conan-default &:: Configure build system +cmake --build --preset=conan-release &:: Build +cmake --build --preset=conan-release --target=install &:: Install to dist/ +build/dist/bin/cosim help &:: Run +``` +In both cases, `Release` and `conan-release` can be replaced with `Debug` and +`conan-debug`, respectively, if you're building for development purposes. +See the [Conan CMakeToolchain documentation] for more information about CMake +presets (and what to do if your CMake version doesn't allow you to use them). + +The `cmake --target=install` command will copy the resulting `cosim` +executable to the `build[/Release]/dist/bin` directory. The shared libraries +that `cosim` depends on will be copied to the same directory or to +`build/Release/dist/lib`, depending on platform, by the `conan install` +command. Thus, the `dist` directory contains the entire release bundle. You +may also choose to install to a different directory by setting the +[`CMAKE_INSTALL_PREFIX`] variable, but note that dependencies won't be +included in the installation then. + + +[`CMAKE_INSTALL_PREFIX`]: https://cmake.org/cmake/help/latest/variable/CMAKE_INSTALL_PREFIX.html +[Conan CMakeToolchain documentation]: https://docs.conan.io/2/examples/tools/cmake/cmake_toolchain/build_project_cmake_presets.html [libcosim]: https://github.com/open-simulation-platform/libcosim [libcosim README]: https://github.com/open-simulation-platform/libcosim#readme diff --git a/conanfile.py b/conanfile.py new file mode 100644 index 0000000..f040da2 --- /dev/null +++ b/conanfile.py @@ -0,0 +1,80 @@ +import os + +from conan import ConanFile +from conan.tools.cmake import CMakeDeps, CMakeToolchain, cmake_layout +from conan.tools.env import VirtualBuildEnv +from conan.tools.files import copy + +class CosimCLIConan(ConanFile): + settings = "os", "compiler", "build_type", "arch" + default_options = { "*:shared": True } + + def requirements(self): + self.tool_requires("cmake/[>=3.19]") + if self.settings.os == "Linux": + self.tool_requires("patchelf/[<0.18]") + self.requires("libcosim/0.10.3@osp/stable") + self.requires("boost/[>=1.71]") + + def layout(self): + cmake_layout(self) + + def generate(self): + # Import shared libraries and executables from dependency packages + # to the 'dist/' folder. + bindir = os.path.join(self.build_folder, "dist", "bin") + dldir = (bindir if self.settings.os == "Windows" else + os.path.join(self.build_folder, "dist", "lib")) + dependency_libs = { + # For some dependencies, we only want a subset of the libraries + "boost" : [ + "boost_atomic*", + "boost_chrono*", + "boost_container*", + "boost_context*", + "boost_date_time*", + "boost_filesystem*", + "boost_locale*", + "boost_log*", + "boost_log_setup*", + "boost_program_options*", + "boost_random*", + "boost_regex*", + "boost_serialization*", + "boost_system*", + "boost_thread*"], + "thrift": ["thrift", "thriftd"], + } + for req, dep in self.dependencies.items(): + self._import_dynamic_libs(dep, dldir, dependency_libs.get(req.ref.name, ["*"])) + if self.dependencies["libcosim"].options.proxyfmu: + self._import_executables(self.dependencies["proxyfmu"], bindir, ["*"]) + + # Generate build system + tc = CMakeToolchain(self) + tc.generate() + CMakeDeps(self).generate() + + def _import_dynamic_libs(self, dependency, target_dir, patterns): + if dependency.options.get_safe("shared", False): + if self.settings.os == "Windows": + depdirs = dependency.cpp_info.bindirs + else: + depdirs = dependency.cpp_info.libdirs + for depdir in depdirs: + for pattern in patterns: + patternx = pattern+".dll" if self.settings.os == "Windows" else "lib"+pattern+".so*" + files = copy(self, patternx, depdir, target_dir, keep_path=False) + self._update_rpath(files, "$ORIGIN") + + def _import_executables(self, dependency, target_dir, patterns=["*"]): + for bindir in dependency.cpp_info.bindirs: + for pattern in patterns: + patternx = pattern+".exe" if self.settings.os == "Windows" else pattern + files = copy(self, patternx, bindir, target_dir, keep_path=False) + self._update_rpath(files, "$ORIGIN/../lib") + + def _update_rpath(self, files, new_rpath): + if files and self.settings.os == "Linux": + with VirtualBuildEnv(self).environment().vars(self).apply(): + self.run("patchelf --set-rpath '" + new_rpath + "' '" + ("' '".join(files)) + "'") diff --git a/conanfile.txt b/conanfile.txt deleted file mode 100644 index ee2d2fd..0000000 --- a/conanfile.txt +++ /dev/null @@ -1,54 +0,0 @@ -[requires] -libcosim/0.10.2@osp/stable - -[generators] -cmake -virtualrunenv - -[imports] -bin, boost_atomic*.dll -> ./dist/bin -bin, boost_chrono*.dll -> ./dist/bin -bin, boost_context*.dll -> ./dist/bin -bin, boost_date_time*.dll -> ./dist/bin -bin, boost_filesystem*.dll -> ./dist/bin -bin, boost_log*.dll -> ./dist/bin -bin, boost_log_setup*.dll -> ./dist/bin -bin, boost_program_options*.dll -> ./dist/bin -bin, boost_regex*.dll -> ./dist/bin -bin, boost_system*.dll -> ./dist/bin -bin, boost_thread*.dll -> ./dist/bin -bin, cosim.dll -> ./dist/bin -bin, fmilib_shared.dll -> ./dist/bin -bin, xerces-c*.dll -> ./dist/bin -bin, yaml-cpp*.dll -> ./dist/bin -bin, zip.dll -> ./dist/bin -bin, proxyfmu* -> ./dist/bin -bin, fmilibwrapper*.dll -> ./dist/bin - -lib, libboost_atomic.so.* -> ./dist/lib -lib, libboost_chrono.so.* -> ./dist/lib -lib, libboost_context.so.* -> ./dist/lib -lib, libboost_date_time.so.* -> ./dist/lib -lib, libboost_filesystem.so.* -> ./dist/lib -lib, libboost_log.so.* -> ./dist/lib -lib, libboost_log_setup.so.* -> ./dist/lib -lib, libboost_program_options.so.* -> ./dist/lib -lib, libboost_regex.so.* -> ./dist/lib -lib, libboost_system.so.* -> ./dist/lib -lib, libboost_thread.so.* -> ./dist/lib -lib, libcosim.so -> ./dist/lib -lib, libfmilib_shared.so -> ./dist/lib -lib, libxerces-c*.so -> ./dist/lib -lib, libyaml-cpp*.so.* -> ./dist/lib -lib, libzip.so.* -> ./dist/lib -lib, libfmilibwrapper*.so -> ./dist/lib -lib, libproxyfmu-client*.so -> ./dist/lib - -., license* -> ./dist/doc/licenses @ folder=True, ignore_case=True, keep_path=False -., */license* -> ./dist/doc/licenses @ folder=True, ignore_case=True, keep_path=False -., copying* -> ./dist/doc/licenses @ folder=True, ignore_case=True, keep_path=False -., */copying* -> ./dist/doc/licenses @ folder=True, ignore_case=True, keep_path=False -., notice* -> ./dist/doc/licenses @ folder=True, ignore_case=True, keep_path=False -., */notice* -> ./dist/doc/licenses @ folder=True, ignore_case=True, keep_path=False -., authors* -> ./dist/doc/licenses @ folder=True, ignore_case=True, keep_path=False -., */authors* -> ./dist/doc/licenses @ folder=True, ignore_case=True, keep_path=False