Skip to content

Commit

Permalink
Upgrade to Conan 2 (#109)
Browse files Browse the repository at this point in the history
I had to switch from a simple `conanfile.txt` to a more involved `conanfile.py` because the "import" functionality has been removed in Conan 2. Initially this seemed like a hassle, but in the end, it turned out to be beneficial:

* We no longer have to maintain the list of imports by hand. Instead, we dynamically iterate over the dependencies list in the `generate()` method.
* It allowed me to adjust the RPATH of the imported files from within the conanfile, rather than doing it in the GitHub Actions workflow file as before. Thus, people who build locally also get libaries and executables with an appropriate RPATH.
  • Loading branch information
kyllingstad authored Mar 4, 2024
1 parent 697ccf9 commit 71b7bf3
Show file tree
Hide file tree
Showing 6 changed files with 155 additions and 136 deletions.
73 changes: 28 additions & 45 deletions .github/workflows/ci-conan.yml
Original file line number Diff line number Diff line change
@@ -1,74 +1,58 @@
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']

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 }} -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 }}" --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 }}
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:
Expand All @@ -77,24 +61,23 @@ jobs:
option_proxyfmu: ['proxyfmu=True', 'proxyfmu=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 }} -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 }}" --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 }}
path: build/dist
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
*.swp
build/
CMakeUserPresets.json

# JetBrains CLion:
.idea/
Expand Down
18 changes: 7 additions & 11 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
65 changes: 39 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand All @@ -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
-----
Expand All @@ -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
------------
Expand All @@ -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
80 changes: 80 additions & 0 deletions conanfile.py
Original file line number Diff line number Diff line change
@@ -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)) + "'")
Loading

0 comments on commit 71b7bf3

Please sign in to comment.