Skip to content

Commit

Permalink
Merge pull request #151 from kpwelsh/main
Browse files Browse the repository at this point in the history
Create Fierro-cpu Anaconda Builds + Github Actions
  • Loading branch information
kpwelsh authored Dec 19, 2023
2 parents 97e8bc4 + 015a66f commit e2a2f1c
Show file tree
Hide file tree
Showing 7 changed files with 269 additions and 28 deletions.
47 changes: 46 additions & 1 deletion .conda/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,4 +165,49 @@ conda activate fierro-dev
```
And then you have all of the dependencies necessary to successfully build Fierro from source.

These packages are `noarch: generic` packages, because they don't contain any architecture/OS specific build artifacts themselves. Whether or not you can install them on your system depends solely on the dependencies being available.
These packages are `noarch: generic` packages, because they don't contain any architecture/OS specific build artifacts themselves. Whether or not you can install them on your system depends solely on the dependencies being available.


## Debugging Tips
Anaconda packages are great when they work, but debugging them can be a pain, since it takes so long (relatively) to initailize the build environment. If you find yourself having to debug a conda package, don't waste time and follow these tips:


### Inspect the build/host environments.
The intermediate build environments created can be found under `~/anaconda3/conda-bld/`. These are cleaned up if the build is successful, but are left for inspecting if it fails. Your intermediate environemnts might look something like `~/mambaforge/conda-bld/fierro-cpu_1702931506301`, and ones that are still around can be uncovered by running `conda env list`:
```
# conda environments:
#
~/anaconda3
base * ~/mambaforge
~/mambaforge/conda-bld/cross-apple-test_1699913171912/_build_env
~/mambaforge/conda-bld/fierro-cpu_1702931083445/_build_env
~/mambaforge/conda-bld/fierro-cpu_1702931506301/_build_env
~/mambaforge/conda-bld/fierro-evpfft_1700005728459/_build_env
~/mambaforge/conda-bld/fierro-heffte-cuda_1702567877996/_build_env
~/mambaforge/conda-bld/fierro-heffte-cuda_1702568248715/_build_env
~/mambaforge/conda-bld/fierro-heffte-cuda_1702569485778/_build_env
~/mambaforge/conda-bld/fierro-heffte-cuda_1702571726755/_build_env
~/mambaforge/conda-bld/fierro-heffte-cuda_1702572653009/_build_env
~/mambaforge/conda-bld/fierro-heffte-cuda_1702573356532/_build_env
~/mambaforge/conda-bld/fierro-heffte-cuda_1702581207071/_build_env
~/mambaforge/conda-bld/fierro-heffte-cuda_1702582399999/_build_env
~/mambaforge/conda-bld/fierro-voxelizer-py_1700001705130/_build_env
~/mambaforge/conda-bld/fierro-voxelizer-py_1700003960166/_build_env
~/mambaforge/conda-bld/fierro-voxelizer-py_1700006765747/_build_env
EVPFFT_MATAR ~/mambaforge/envs/EVPFFT_MATAR
... ...
```

Under that build folder you will find 3 relevant things:
1. `_build_env` -- The environment where your "build" dependencies get installed (aka `$BUILD_PREFIX`)
2. `_h_env_placehold_placehold_placehold_placehold_placehold...` -- The environment where your "host" dependencies get installed, and where you install your package (aka `$PREFIX`).
3. `work` -- Where your source files are placed + where `conda_build.sh` (the exact script that is run) can be found.

You can activate each of these environments or just inspect the installed packages with `conda list -p <path>`.

### Modify and rebuild under `work`
If you think you identified the issue, don't go back and try the package build again. First modify the intermediate build files and try the rebuild from there. If you think you are missing a host dependency, for example, run `conda install <package> -p <path-to-host-env>` and rerun `work/conda_build.sh` to see if that fixes it. If you think you need to modify the build script, just change `conda_build.sh` and rerun. Once the package builds successfully, make sure to make you changes to the original `meta.yaml` and/or `build.sh` files and then try the package build again.

### Targetting Cross Compiling
If you are having issues with the cross-compiling build targets, you can speicfically target just those by adding `--variants "{ target_platform: linux-aarch64 }"`. If you are using the `.conda/build_variants.yaml` file, you will need to comment out the variants you don't want to test.
24 changes: 10 additions & 14 deletions .conda/fierro/cpu/build.sh
Original file line number Diff line number Diff line change
@@ -1,26 +1,22 @@
export SRC_DIR=$(pwd)
mkdir -p build
cd build

export MPI_FLAGS="--allow-run-as-root"

if [ $(uname) == Linux ]; then
export MPI_FLAGS="$MPI_FLAGS;-mca;plm;isolated"
fi

# These flag variables are set by anaconda.
source "$RECIPE_DIR/../../cross-compile-setup.sh"
mkdir build
cd build

# -D _LIBCPP_DISABLE_AVAILABILITY
# is added to the CXXFLAGS for MacOS builds.
# see https://conda-forge.org/docs/maintainer/knowledge_base.html#newer-c-features-with-old-sdk

cmake -D CMAKE_BUILD_TYPE:STRING=RELEASE \
cmake .. \
-D CMAKE_BUILD_TYPE:STRING=RELEASE \
-D CMAKE_INSTALL_PREFIX:PATH=$PREFIX \
-D CMAKE_CXX_STANDARD:STRING=17 \
-D BUILD_PARALLEL_EXPLICIT_SOLVER=ON \
-D BUILD_IMPLICIT_SOLVER=ON \
-D BUILD_ELEMENTS=OFF \
-D DISTRIBUTION=True \
-D DISTRIBUTION=On \
$CMAKE_ARGS \
$SRC_DIR \
-D CMAKE_CXX_FLAGS="$PATCHED_CXXFLAGS -fopenmp" \
-D CMAKE_CXX_FLAGS="$PATCHED_CXXFLAGS -fopenmp -D_LIBCPP_DISABLE_AVAILABILITY" \
-D MPI_C_COMPILER="$BUILD_PREFIX/bin/mpicc" \
-D MPI_CXX_COMPILER="$BUILD_PREFIX/bin/mpicxx" \
-D VECTOR_ARCH_FLAGS="$VECTOR_ARCH_FLAGS" \
Expand Down
23 changes: 14 additions & 9 deletions .conda/fierro/cpu/meta.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
{% set version = "1.0.0" %}
{% set compiler_version = "10.4.0" %}
{% set linux_compiler_version = "10.4.0" %}
{% set macos_compiler_version = "12" %}
# We need the same MPI version in build + host.
# So we have to specify it, unfortunately
{% set mpi_version = "4.1" %}

package:
name: fierro-cpu
Expand All @@ -10,27 +14,28 @@ source:
git_depth: 1

build:
number: 1
number: 2
script_env:
- PLATFORM={{ target_platform }}

requirements:
build:
- cmake >=3.17.0
- {{ compiler('c') }} ={{ compiler_version }}
- {{ compiler('cxx') }} ={{ compiler_version }}
- openmpi
- {{ compiler('c') }}={{ linux_compiler_version }} # [linux]
- {{ compiler('c') }}={{ macos_compiler_version }} # [osx]
- {{ compiler('cxx') }}={{ linux_compiler_version }} # [linux]
- {{ compiler('cxx') }}={{ macos_compiler_version }} # [osx]
- openmpi={{ mpi_version }}
host:
- _openmp_mutex
- openmpi
- _openmp_mutex # [linux]
- llvm-openmp # [osx]
- openmpi={{ mpi_version }}
- fierro-trilinos-cpu
- elements
run:
- fierro-trilinos-cpu
- mpi

#TODO: Add tests

about:
home: https://github.com/lanl/Fierro
license: BSD-3-Clause
Expand Down
12 changes: 12 additions & 0 deletions .github/workflows/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,15 @@ The recipe_dir is the folder containing the conda build `meta.yaml` file as well

The second is the package specific action that triggers off of package specific source code changes and invokes `build-conda-package` with the correct package recipe location and additional build variants. In the case of our source-in-repo packages, these trigger of off relevant source code changes. For other dependencies, where the source is not contained in the repo, we configure the actions for manual dispatch.


# Debugging
Complex github actions can be very annoying to debug for two reasons:
1. The feedback loop is long/slow. Pushing changes to github and waiting for a runner to initialize can easily add ~5 mins to your feedback loop.
2. You don't have easy access to the runtime environment. Github automatically will unmount the docker image and be on its way if an action fails.

We have some tools to deal with this, though. If you can run docker (not WSL1), you can use [act](https://github.com/nektos/act) to execute github actions locally. This is **much** faster for debugging things like action syntax. We also have a tool to deal with #2 that I have built into the `build-conda-package` action. We have a `debug` option that enables a special workflow step after the package building step. If the debug flag is set, we make use of the [tmate](https://github.com/mxschmitt/action-tmate) github action to create a temporary ssh host and wait for 30 minutes. On the github Actions page you can inspect the log and find something like:
```
SSH: ssh [email protected]
```
If you are the user that triggered the action, you can ssh into the docker container that was runing your build action. From there you can continue debugging.
15 changes: 15 additions & 0 deletions .github/workflows/publish-fierro-cpu.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
name: 'Publish Fierro-CPU'

on:
push:
paths:
- .conda/fierro/cpu/**
- .github/workflows/publish-fierro-cpu.yaml
workflow_dispatch:

jobs:
publish:
uses: ./.github/workflows/build-conda-package.yaml
with:
recipe_dir: .conda/fierro/cpu
secrets: inherit
168 changes: 168 additions & 0 deletions src/CLI/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
# Fiero Command Line Interface
This CLI package is a Fierro command line interface that is intended to act as a consolidated front-end for our Fierro backend tools. It provides a consistent interface and framework for exposing tools to the user.

## Front-End
The front end here is built off of [argparse](https://github.com/p-ranav/argparse), which implements a language agnostic standard for simple command line interface design. It defines a syntax for programs from the command line with suitable helptext/defaults/post-processing.

The Fierro-CLI implements two things:
1. Output directory -- A directory to run the program in. Outputs of the program will by default be put here if some other backend specific option is not specified.
2. Subcommands -- a list of argparse "subcommands" that each represent an available backend tool.

Example:
```
$ fierro -h
Usage: fierro [--help] [--version] [--output VAR] {mesh-builder,parallel-explicit,parallel-implicit,voxelizer}
Optional arguments:
-h, --help shows help message and exits
-v, --version prints version information and exits
-o, --output output directory for the program [default: "."]
Subcommands:
mesh-builder Build rectangular or cylindrical mesh in 2 or 3 dimensions. Useful for setting up test problems.
parallel-explicit Use an explicit solution scheme to step the system.
parallel-implicit Use an implicit solution scheme to step the system.
voxelizer Take a mesh defined by an STL and turn it into a dense voxel grid represented by a VTK structured mesh. The extent of the grid is selected by taking the minimum and maximum extent of the mesh along each dimenison. Note: Only supports binary STL mesh inputs
```

## Backends
This Fierro-CLI package is designed to operate decoupled from backends. Each registered backend is an executable that may or may not be present in the system.

### Registering Backends
To add a new backend, you only need to do a couple of steps.
#### 1. Implementing NewBackend.hpp
You should start by copying one of the existing backend.hpp files and implementing your new backend. We will use `VoxelizerBackend.hpp` as an example.

You should derive from `FierroBackend` and tell the base class which executable name to look for
```c++
#include "backend.hpp"
struct VoxelizerBackend: public FierroBackend {
VoxelizerBackend() : FierroBackend("fierro-voxelizer") {
...
}
...
};
```
Then, in the constructor, setup your argparse command
```c++
struct VoxelizerBackend: public FierroBackend {
VoxelizerBackend() : FierroBackend("fierro-voxelizer") {
this->command = std::shared_ptr<argparse::ArgumentParser>(
new argparse::ArgumentParser("voxelizer") // <--- This is the name that shows up under "Subcommands:" in the CLI
);
// Here you can add your arguments with their helptext.
this->command->add_argument("input-file")
.help("The binary STL file to voxelize.")
.action(absolute_fp); // <-- You can also add a post-processing command. In this case we convert the input filepath to an absolute one.
this->command->add_argument("output-name")
.help("The name of the VTK structured mesh output file.");
this->command->add_argument("x")
.help("The number of voxels along the X direction (int).");
this->command->add_argument("y")
.help("The number of voxels along the Y direction (int).");
this->command->add_argument("z")
.help("The number of voxels along the Z direction (int).");
this->command->add_argument("use-index-space")
.help("Wether or not to output the VTK in index-space coordinates.");
// Add a description for your subcommand
this->command->add_description(
"Take a mesh defined by an STL and turn it into a dense voxel grid represented by a VTK structured mesh. "
"The extent of the grid is selected by taking the minimum and maximum extent of the mesh along each dimenison. "
"Note: Only supports binary STL mesh inputs."
);
}
...
};
```

Lastly you tell the CLI framework how to actually invoke the backend command. This is mostly just building a system command string and invoking it, but I suppose you could do whatever you want.
```c++

struct VoxelizerBackend: public FierroBackend {
...

/**
* Invoke the backend executable.
* Will throw an ArgumentException if the arguments aren't valid.
*
* @return Return code of the executable.
*/
int invoke() {
if (!exec_path.has_value())
throw std::runtime_error("Cannot invoke executable without absolute path.");

auto err = this->validate_arguments();
if (err.has_value()) throw ArgumentException(err.value());

std::string sys_command;
sys_command = this->exec_path.value().string()
+ " " + this->command->get("input-file")
+ " " + this->command->get("output-name")
+ " " + this->command->get("x")
+ " " + this->command->get("y")
+ " " + this->command->get("z")
+ " " + this->command->get("use-index-space");

return system(sys_command.c_str());
}
};
```

You probably also want to add some validation to your input arguments. The voxelizer does this with a serparate function:

```c++
struct VoxelizerBackend: public FierroBackend {
...

/**
* Check that the arguments of the CLI are valid and make sense.
*
* Checks for explicit/implicit logical consistency.
* Checks that the mesh file is real and readable.
* Does not check that it is correctly formatted.
*
* @return An optional error. If the empty, the arguments are valid.
*
*/
std::optional<std::string> validate_arguments() {
std::string msg = "";
auto parser = this->command;

// Here we just check if the input file is an actual file.
if (!file_exists(parser->get<std::string>("input-file"))) {
msg = "Unable to find input file: " + parser->get<std::string>("input-file");
}

if (msg.length() > 0) {
return std::optional(msg);
}
return {};
}
...
};
```

Now you have a well defined backend.


#### 2. Plug it into the CLI framework
Head over to main.cpp and add your backend to the list of backends:
```c++
std::vector<std::shared_ptr<FierroBackend>> BACKENDS {
std::shared_ptr<ParallelExplicit>(),
std::shared_ptr<ParallelImplicit>(),
std::shared_ptr<MeshBuilderBackend>(),
std::shared_ptr<VoxelizerBackend>(),
//std::shared_ptr<YourBackend>() <-- Add yours here
};

```

Now you have everything set up, and it will automatically be picked up if its compiled and in the system.
8 changes: 4 additions & 4 deletions src/CLI/src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@
#include <string>

std::vector<std::shared_ptr<FierroBackend>> BACKENDS {
std::shared_ptr<FierroBackend>(new ParallelExplicit()),
std::shared_ptr<FierroBackend>(new ParallelImplicit()),
std::shared_ptr<FierroBackend>(new MeshBuilderBackend()),
std::shared_ptr<FierroBackend>(new VoxelizerBackend()),
std::shared_ptr<ParallelExplicit>(),
std::shared_ptr<ParallelImplicit>(),
std::shared_ptr<MeshBuilderBackend>(),
std::shared_ptr<VoxelizerBackend>(),
};

std::vector<std::shared_ptr<FierroBackend>> find_fierro_backends() {
Expand Down

0 comments on commit e2a2f1c

Please sign in to comment.