Repository: shadow/tgen (language: python)

Issue:
Doesn't build against igraph 0.10.3 (default version on fedora 38 and debian 12)
I get:

```
/tgen/src/tgen-graph.c: In function '_tgengraph_hasSelfLoop':
/tgen/src/tgen-graph.c:423:45: warning: passing argument 2 of 'igraph_neighbors' from incompatible pointer type [-Wincompatible-pointer-types]
  423 |         result = igraph_neighbors(g->graph, resultNeighborVertices, vertexIndex, IGRAPH_OUT);
      |                                             ^~~~~~~~~~~~~~~~~~~~~~
      |                                             |
      |                                             igraph_vector_t *
In file included from /usr/include/igraph/igraph.h:57,
                 from /tgen/src/tgen-graph.c:6:
/usr/include/igraph/igraph_interface.h:54:91: note: expected 'igraph_vector_int_t *' but argument is of type 'igraph_vector_t *'
   54 | IGRAPH_EXPORT igraph_error_t igraph_neighbors(const igraph_t *graph, igraph_vector_int_t *neis, igraph_integer_t vid,
      |                                                                      ~~~~~~~~~~~~~~~~~~~~~^~~~
/tgen/src/tgen-graph.c:427:17: warning: 'igraph_vector_e' is deprecated [-Wdeprecated-declarations]
  427 |                 igraph_integer_t dstVertexIndex = igraph_vector_e(resultNeighborVertices, i);
      |                 ^~~~~~~~~~~~~~~~
In file included from /usr/include/igraph/igraph_vector.h:70,
                 from /usr/include/igraph/igraph_random.h:29,
                 from /usr/include/igraph/igraph.h:34:
/usr/include/igraph/igraph_vector_pmt.h:72:47: note: declared here
   72 | IGRAPH_EXPORT IGRAPH_DEPRECATED BASE FUNCTION(igraph_vector, e)(const TYPE(igraph_vector)* v, igraph_integer_t pos);
      |                                               ^~~~~~~~~~~~~
/usr/include/igraph/igraph_pmt.h:24:23: note: in definition of macro 'CONCAT2x'
   24 | #define CONCAT2x(a,b) a ## _ ## b
      |                       ^
/usr/include/igraph/igraph_pmt.h:161:31: note: in expansion of macro 'CONCAT2'
  161 |         #define FUNCTION(a,c) CONCAT2(a,c)
      |                               ^~~~~~~
/usr/include/igraph/igraph_vector_pmt.h:72:38: note: in expansion of macro 'FUNCTION'
   72 | IGRAPH_EXPORT IGRAPH_DEPRECATED BASE FUNCTION(igraph_vector, e)(const TYPE(igraph_vector)* v, igraph_integer_t pos);
      |                                      ^~~~~~~~
/tgen/src/tgen-graph.c: In function '_tgengraph_countIncomingEdges':
/tgen/src/tgen-graph.c:456:41: warning: passing argument 2 of 'igraph_neighbors' from incompatible pointer type [-Wincompatible-pointer-types]
  456 |     result = igraph_neighbors(g->graph, resultNeighborVertices, vertexIndex, IGRAPH_IN);
      |                                         ^~~~~~~~~~~~~~~~~~~~~~
      |                                         |
      |                                         igraph_vector_t *
/usr/include/igraph/igraph_interface.h:54:91: note: expected 'igraph_vector_int_t *' but argument is of type 'igraph_vector_t *'
   54 | IGRAPH_EXPORT igraph_error_t igraph_neighbors(const igraph_t *graph, igraph_vector_int_t *neis, igraph_integer_t vid,
      |                                                                      ~~~~~~~~~~~~~~~~~~~~~^~~~
/tgen/src/tgen-graph.c: In function '_tgengraph_parseGraphProperties':
/tgen/src/tgen-graph.c:1094:5: warning: 'igraph_clusters' is deprecated [-Wdeprecated-declarations]
 1094 |     result = igraph_clusters(g->graph, NULL, NULL, &(g->clusterCount), IGRAPH_WEAK);
      |     ^~~~~~
In file included from /usr/include/igraph/igraph.h:63:
/usr/include/igraph/igraph_components.h:45:48: note: declared here
   45 | IGRAPH_EXPORT IGRAPH_DEPRECATED igraph_error_t igraph_clusters(const igraph_t *graph, igraph_vector_int_t *membership,
      |                                                ^~~~~~~~~~~~~~~
/tgen/src/tgen-graph.c:1120:56: warning: passing argument 3 of 'igraph_cattribute_list' from incompatible pointer type [-Wincompatible-pointer-types]
 1120 |     result = igraph_cattribute_list(g->graph, &gnames, &gtypes, &vnames, &vtypes, &enames, &etypes);
      |                                                        ^~~~~~~
      |                                                        |
      |                                                        igraph_vector_t *
In file included from /usr/include/igraph/igraph_conversion.h:34,
                 from /usr/include/igraph/igraph.h:73:
/usr/include/igraph/igraph_attributes.h:379:91: note: expected 'igraph_vector_int_t *' but argument is of type 'igraph_vector_t *'
  379 |                                          igraph_strvector_t *gnames, igraph_vector_int_t *gtypes,
      |                                                                      ~~~~~~~~~~~~~~~~~~~~~^~~~~~
/tgen/src/tgen-graph.c:1120:74: warning: passing argument 5 of 'igraph_cattribute_list' from incompatible pointer type [-Wincompatible-pointer-types]
 1120 |     result = igraph_cattribute_list(g->graph, &gnames, &gtypes, &vnames, &vtypes, &enames, &etypes);
      |                                                                          ^~~~~~~
      |                                                                          |
      |                                                                          igraph_vector_t *
/usr/include/igraph/igraph_attributes.h:380:91: note: expected 'igraph_vector_int_t *' but argument is of type 'igraph_vector_t *'
  380 |                                          igraph_strvector_t *vnames, igraph_vector_int_t *vtypes,
      |                                                                      ~~~~~~~~~~~~~~~~~~~~~^~~~~~
/tgen/src/tgen-graph.c:1120:92: warning: passing argument 7 of 'igraph_cattribute_list' from incompatible pointer type [-Wincompatible-pointer-types]
 1120 |     result = igraph_cattribute_list(g->graph, &gnames, &gtypes, &vnames, &vtypes, &enames, &etypes);
      |                                                                                            ^~~~~~~
      |                                                                                            |
      |                                                                                            igraph_vector_t *
/usr/include/igraph/igraph_attributes.h:381:91: note: expected 'igraph_vector_int_t *' but argument is of type 'igraph_vector_t *'
  381 |                                          igraph_strvector_t *enames, igraph_vector_int_t *etypes);
      |                                                                      ~~~~~~~~~~~~~~~~~~~~~^~~~~~
/tgen/src/tgen-graph.c:1130:9: error: too many arguments to function 'igraph_strvector_get'
 1130 |         igraph_strvector_get(&gnames, (glong) i, &name);
      |         ^~~~~~~~~~~~~~~~~~~~
In file included from /usr/include/igraph/igraph.h:47:
/usr/include/igraph/igraph_strvector.h:70:27: note: declared here
   70 | IGRAPH_EXPORT const char* igraph_strvector_get(const igraph_strvector_t *sv, igraph_integer_t idx);
      |                           ^~~~~~~~~~~~~~~~~~~~
/tgen/src/tgen-graph.c:1137:9: error: too many arguments to function 'igraph_strvector_get'
 1137 |         igraph_strvector_get(&vnames, (glong) i, &name);
      |         ^~~~~~~~~~~~~~~~~~~~
/usr/include/igraph/igraph_strvector.h:70:27: note: declared here
   70 | IGRAPH_EXPORT const char* igraph_strvector_get(const igraph_strvector_t *sv, igraph_integer_t idx);
      |                           ^~~~~~~~~~~~~~~~~~~~
/tgen/src/tgen-graph.c:1154:9: error: too many arguments to function 'igraph_strvector_get'
 1154 |         igraph_strvector_get(&enames, (glong) i, &name);
      |         ^~~~~~~~~~~~~~~~~~~~
/usr/include/igraph/igraph_strvector.h:70:27: note: declared here
   70 | IGRAPH_EXPORT const char* igraph_strvector_get(const igraph_strvector_t *sv, igraph_integer_t idx);
      |                           ^~~~~~~~~~~~~~~~~~~~
/tgen/src/tgen-graph.c: In function 'tgengraph_getNextActionIDs':
/tgen/src/tgen-graph.c:1346:41: warning: passing argument 2 of 'igraph_neighbors' from incompatible pointer type [-Wincompatible-pointer-types]
 1346 |     result = igraph_neighbors(g->graph, resultNeighborVertices, srcVertexIndex, IGRAPH_OUT);
      |                                         ^~~~~~~~~~~~~~~~~~~~~~
      |                                         |
      |                                         igraph_vector_t *
/usr/include/igraph/igraph_interface.h:54:91: note: expected 'igraph_vector_int_t *' but argument is of type 'igraph_vector_t *'
   54 | IGRAPH_EXPORT igraph_error_t igraph_neighbors(const igraph_t *graph, igraph_vector_int_t *neis, igraph_integer_t vid,
      |                                                                      ~~~~~~~~~~~~~~~~~~~~~^~~~
/tgen/src/tgen-graph.c:1367:9: warning: 'igraph_vector_e' is deprecated [-Wdeprecated-declarations]
 1367 |         igraph_integer_t dstVertexIndex = igraph_vector_e(resultNeighborVertices, i);
      |         ^~~~~~~~~~~~~~~~
/usr/include/igraph/igraph_vector_pmt.h:72:47: note: declared here
   72 | IGRAPH_EXPORT IGRAPH_DEPRECATED BASE FUNCTION(igraph_vector, e)(const TYPE(igraph_vector)* v, igraph_integer_t pos);
      |                                               ^~~~~~~~~~~~~
/usr/include/igraph/igraph_pmt.h:24:23: note: in definition of macro 'CONCAT2x'
   24 | #define CONCAT2x(a,b) a ## _ ## b
      |                       ^
/usr/include/igraph/igraph_pmt.h:161:31: note: in expansion of macro 'CONCAT2'
  161 |         #define FUNCTION(a,c) CONCAT2(a,c)
      |                               ^~~~~~~
/usr/include/igraph/igraph_vector_pmt.h:72:38: note: in expansion of macro 'FUNCTION'
   72 | IGRAPH_EXPORT IGRAPH_DEPRECATED BASE FUNCTION(igraph_vector, e)(const TYPE(igraph_vector)* v, igraph_integer_t pos);
      |                                      ^~~~~~~~
make[2]: *** [src/CMakeFiles/tgen.dir/build.make:118: src/CMakeFiles/tgen.dir/tgen-graph.c.o] Error 1
make[1]: *** [CMakeFiles/Makefile2:116: src/CMakeFiles/tgen.dir/all] Error 2
make: *** [Makefile:136: all] Error 2
```


Discussion / hints:
igraph has undergone a significant refactoring for version 0.10. Now all index-like quantities are represented as integers, while previously they were often floating point numbers, a consequence of igraph's R heritage (R uses floats for everything).

Breaking changes are described here: https://github.com/igraph/igraph/blob/master/CHANGELOG.md#0100---2022-09-05

In my experience, refactoring old code to work with 0.10 is not usually difficult. The main change that's needed is to replace some `igraph_vector_t` types with `igraph_vector_int_t`.

If the maintainers of this library have any questions regarding this, feel free to ask me.
Great, thanks @sporksmith for the bug report and @szhorvat for the details. Should hopefully be a straightforward fix since igraph has some way to find the version:
https://github.com/igraph/igraph/blob/0.10.0/src/version.c
You could use CMake's `find_package()` to require a certain version. igraph installs CMake package files since version 0.9.2.

Trying to support both 0.10.x and earlier versions using `#ifdef`s with the `IGRAPH_VERSION_...` macros is _not_ something I would recommend to try. The changes between 0.9 and 0.10 are likely too big for that.
We [currently support](https://shadow.github.io/docs/guide/supported_platforms.html#officially-supported-platforms) back to Ubuntu 18.04.

If we can't `ifdef` things, then I think our two options would be to force older platforms to update their igraph version, or force newer platforms to downgrade their igraph version. I think we should keep igraph apt/dnf-installable for as many platforms as possible, so my preference would be requiring newer platforms to downgrade their igraph version. For Fedora they might be able to install the old version from the Fedora 37 repository, but as more new platforms are released that use newer igraph versions, this will become more painful.

Maybe instead we can build igraph from source as a cmake external project and statically link it into tgen. igraph is GPL though, so we couldn't ever share tgen binaries I think? Another option could be to bring rust into tgen and write a C wrapper around a petgraph implementation of tgen's graph code.
I briefly checked to see if there's something like an "igraph-0.9" package in fedora 38, but I don't see one. Just "igraph", at version 0.10.3. Maybe the user can enable some alternate package repo  that might have it? I'm not a regular fedora user, so I might be missing an obvious solution here...

```
# dnf search igraph
Fedora rawhide openh264 (From Cisco) - x86_64                                                          4.3 kB/s | 2.5 kB     00:00    
Fedora - Rawhide - Developmental packages for the next Fedora release                                   24 MB/s |  65 MB     00:02    
Fedora - Modular Rawhide - Developmental packages for the next Fedora release                          2.2 MB/s | 2.3 MB     00:01    
==================================================== Name Exactly Matched: igraph =====================================================
igraph.i686 : Library for creating and manipulating graphs
igraph.x86_64 : Library for creating and manipulating graphs
=================================================== Name & Summary Matched: igraph ====================================================
ctan-kerkis-calligraphic-fonts.noarch : Kerkis Calligraphic Type1 fonts
gap-pkg-digraphs.x86_64 : GAP package for digraphs and multidigraphs
gap-pkg-digraphs-doc.noarch : Digraphs documentation
igraph-devel.i686 : Development files for igraph
igraph-devel.x86_64 : Development files for igraph
python3-igraph.x86_64 : Python bindings for igraph
python3-igraph-devel.i686 : Development files for igraph
python3-igraph-devel.x86_64 : Development files for igraph
texlive-epigraph.noarch : A package for typesetting epigraphs
texlive-epigraph-doc.noarch : Documentation for epigraph
texlive-epigraph-keys.noarch : Epigraphs using key values
======================================================== Name Matched: igraph =========================================================
R-igraph.x86_64 : Network Analysis and Visualization
asciigraph.x86_64 : Makes lightweight ASCII line graphs
golang-github-guptarohit-asciigraph-devel.noarch : Makes lightweight ASCII line graphs
golang-x-tools-digraph.x86_64 : Tool for queries over unlabelled directed graphs in text form
mathjax-caligraphic-fonts.noarch : Fonts used by MathJax to display math in the browser
texlive-adigraph.noarch : Augmenting directed graphs

```

```
# dnf info igraph
Last metadata expiration check: 0:00:20 ago on Wed Jan 25 18:25:10 2023.
Available Packages
Name         : igraph
Version      : 0.10.3
...
```
> I briefly checked to see if there's something like an "igraph-0.9" package in fedora 38, but I don't see one. Just "igraph", at version 0.10.3. Maybe the user can enable some alternate package repo that might have it? I'm not a regular fedora user, so I might be missing an obvious solution here...

We used to do this with CentOS 8 back when we used igraph in Shadow, where we'd download from an older EPEL repository:

https://github.com/shadow/shadow/blob/cc8627ef48003fbb8eea8699c0cc7fb76e80d0a8/docs/install_dependencies.md?plain=1#L118-L119
> Maybe instead we can build igraph from source as a cmake external project

This is possible with `add_subdirectory()`, see https://github.com/igraph/igraph-example

> If we can't `ifdef` things

Well, technically you always can, it might just take an unpleasantly large number of `ifdefs` ... 

> I think our two options would be to force older platforms to update their igraph version, or force newer platforms to downgrade their igraph version

Note that all major Linux distros and package managers have 0.10 already:

https://repology.org/project/igraph/versions

Arch/Debian/Fedora have it, and most other distros are based on those.

Debian also has older versions, see `libigraph3` for 0.10, `libigraph2` for 0.9, and `libigraph1`. But I think it only has the development files for the latest version.  So if the aim is for users to be able to _compile_ `tgen` on old Debian (not merely use binaries), that may not be possible.

----

The reason why igraph 0.10 has so many breaking changes is the following: igraph has accumulated a lot of cruft and some non-ideal design decisions during the past 16 years. These were holding it back, and limiting improvements. We are working towards a 1.0 release with a clean and stable API that we can build on top of in the next 10 years. We tried to squeeze the most significant breaking changes into 0.10 to minimize the inconvenience for downstream users, necessitating only a single large refactoring.


> The reason why igraph 0.10 has so many breaking changes is the following: igraph has accumulated a lot of cruft and some non-ideal design decisions during the past 16 years. These were holding it back, and limiting improvements. We are working towards a 1.0 release with a clean and stable API that we can build on top of in the next 10 years. We tried to squeeze the most significant breaking changes into 0.10 to minimize the inconvenience for downstream users, necessitating only a single large refactoring.

We definitely empathize with having to balance the tradeoffs between changing the API and supporting technical debt of old designs that restrict new features :) It's good to see igraph continuing to be maintained, and changing indexes to be integers is definitely a nice change. Our problem here is mostly a package management issue: How can we continue to support our existing supported platforms without adding an extra step requiring users to build igraph from source (and requiring them to deal with the usual building/linking issues that arise)?
> You could use CMake's find_package() to require a certain version. igraph installs CMake package files since version 0.9.2.

Good point - changed our cmake to look for <0.10.0 to at least make the problem and potential resolution clearer for now. https://github.com/shadow/tgen/pull/45

> Note that all major Linux distros and package managers have 0.10 already:
> https://repology.org/project/igraph/versions

It looks like most... don't? e.g. Debian 10 and 11 don't, all Fedoras < 38, all current Ubuntus...

Maybe users could try installing an `rpm` or `deb` from another distro, but that seems fragile at best, since that package won't necessarily be compatible with its dependencies on another distro.

Also I'd think installing a "foreign" package from an *older* distro is more likely to work than from a *newer* one? I think dynamically linked libraries are usually backwards compatible with a binary compiled against an older version, but not always forwards compatible.

> Debian also has older versions, see libigraph3 for 0.10, libigraph2 for 0.9, and libigraph1. But I think it only has the development files for the latest version. So if the aim is for users to be able to compile tgen on old Debian (not merely use binaries), that may not be possible.

Yeah, we currently try to support compiling on all supported platforms, so that may be tricky. Also I didn't see any packages in Fedora 38 with alt versions of igraph, but maybe they'll end up adding one.

So far I think it's probably worth a try to see to use `#ifdef`s to support either version. In any case this isn't very urgent for us, since all of the platforms we currently support have igraph < 0.10.
A small marginally related thing I noticed when looking at the code: `igraph_set_attribute_table()` should be called once on program startup and should be left alone for the program's lifetime. There is no reason to keep setting and restoring it. I amended the docs to point this out: https://igraph.org/c/html/latest/igraph-Attributes.html#igraph_set_attribute_table



> So far I think it's probably worth a try to see to use `#ifdef`s to support either version.

I grepped the sources, and it seems that you are only using simple graph manipulation functionality. You are not using any network analysis functions at the moment. This means that none of the functions that are used have changed their _behaviour_ (other than perhaps bugfixes, such as more stringent argument checks). They only changed API. So going the `#ifdef` route should be at least safe.
I just worked through this issue for myself on Fedora 38, so I'll share what I did for everybody else's benefit (as well as my own memory).

0. Remove the Fedora 38 igraph 0.10 packages if they're already installed through DNF
`sudo dnf remove igraph igraph-devel`

1. Install the dependencies from the Fedora 38 repositories through DNF
`sudo dnf install arpack`

2. Download the igraph and igraph-devel 0.9 RPM files that were packaged for Fedora 37 here:
https://koji.fedoraproject.org/koji/buildinfo?buildID=2013034

3. Install the igraph 0.9 packages through RPM
`sudo rpm -i igraph-0.9.9-2.fc37.x86_64.rpm`
`sudo rpm -i igraph-devel-0.9.9-2.fc37.x86_64.rpm`

I arrived at this solution after first trying to build the igraph 0.9 shared libraries from https://github.com/igraph/igraph/releases/download/0.9.9/igraph-0.9.9.tar.gz, installing to my $HOME/.local. I just couldn't get cmake for TGen to find igraph in that location. If anyone can tell me how to do that with cmake, I'd be grateful, in case I ever run into a similar problem again. I guess I could have installed everything to the default locations with sudo, but I was afraid to try that... but maybe it would be equivalent to what I ended up doing with the RPM packages(?).

I'd also like to report that I'm still using the igraph 0.7 package from the EPEL on a RHEL 8 (formerly CentOS 8) system, as @stevenengler mentioned above, to run TGen with Shadow.

Edited to add: `dnf check-upgrade` shows that it's ready to bring me back up to igraph 0.10 during the next system update, so I'll need to be careful to exclude those.
Debian 12/bookworm is now Debian's stable, and also has igraph 0.10
This can be worked around on bookworm by adding the bullseye sources and installing igraph from that. e.g.:

```shell
$ cat <<EOF > /etc/apt/sources.list.d/bullseye.sources
Types: deb
URIs: http://deb.debian.org/debian
Suites: bullseye bullseye-updates
Components: main
Signed-By: /usr/share/keyrings/debian-archive-keyring.gpg

Types: deb
URIs: http://deb.debian.org/debian-security
Suites: bullseye-security
Components: main
Signed-By: /usr/share/keyrings/debian-archive-keyring.gpg
EOF

$ apt-get install -y libigraph-dev/bullseye
```

Unfortunately unlike the binary package for libigraph, the `-dev` package doesn't have a distinct name or install destination for different versions, so this may not be a viable solution if you need to compile against newer versions of libigraph on the same system.
>>    Maybe instead we can build igraph from source as a cmake external project

> This is possible with add_subdirectory(), see https://github.com/igraph/igraph-example

I poked at this a little bit. I pulled in igraph as a subdirectory using `git subtree`, which makes it relatively transparent to most users (unlike `git submodule`). I got it building without much trouble; I had to make a fake version metadata file in the igraph subdir, since it wasn't able to find version info from git (probably due to using `subtree` instead of `submodule`).

By default it builds as a static library, and then tgen statically links. This is technically nice, but might be tricky since igraph is [GPL v2](https://github.com/igraph/igraph/blob/master/COPYING). I think it might technically be ok if we don't distribute pre-built binaries (which we currently don't)?

I briefly experimented with forcing it to build a dynamic library instead. That also works but we might need to tinker with the build a bit to be get it to create `.so`'s with appropriate names and that don't interfere with other versions of igraph on the system
IANAL, but my understanding is that you are free to distribute binaries with GPL'd components. It's just that this binary distribution, as a whole, will be covered by the GPL with all that it implies (e.g. need to share the sources). In what situation do you expect this to be a problem?

All this has no impact on the license that covers tgen _sources_.

Fix patch:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 2bc5abb..d03a231 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -36,24 +36,6 @@ jobs:
           - 'debian:12-slim'
           - 'fedora:37'
     steps:
-      # See https://github.com/shadow/tgen/issues/44#issuecomment-1693982909
-      - name: Install debian:11 backports
-        if: ${{ matrix.container == 'debian:12-slim' }}
-        run: |
-          cat <<EOF > /etc/apt/sources.list.d/bullseye.sources
-          Types: deb
-          URIs: http://deb.debian.org/debian
-          Suites: bullseye bullseye-updates
-          Components: main
-          Signed-By: /usr/share/keyrings/debian-archive-keyring.gpg
-
-          Types: deb
-          URIs: http://deb.debian.org/debian-security
-          Suites: bullseye-security
-          Components: main
-          Signed-By: /usr/share/keyrings/debian-archive-keyring.gpg
-          EOF
-
       - name: Update packages
         run: |
           case ${{ matrix.container }} in
@@ -75,14 +57,6 @@ jobs:
                 libglib2.0-dev \
                 libigraph-dev
               ;;
-            debian:12*)
-              # See https://github.com/shadow/tgen/issues/44#issuecomment-1693982909
-              apt-get install -y \
-                ${{ matrix.cc }} \
-                cmake \
-                libglib2.0-dev \
-                libigraph-dev/bullseye
-              ;;
             debian*)
               apt-get install -y \
                 ${{ matrix.cc }} \
@@ -175,7 +149,7 @@ jobs:
           python3 -m venv build/toolsenv
           source build/toolsenv/bin/activate
           # Drastically improves time to install requirements on platforms with
-          # older pip in system backages, such as debian-10.
+          # older pip in system packages, such as debian-10.
           pip3 install --upgrade pip
 
           pip3 install -r tools/requirements.txt
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 1d535f1..773b623 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -16,12 +16,7 @@ include(FindPkgConfig)
 find_package(RT REQUIRED)
 find_package(M REQUIRED)
 
-# We don't yet support igraph 0.10.0+, which has some API changes.
-# See https://github.com/shadow/tgen/issues/44
-#
-# Older versions of cmake don't support the `<` operator here; only `<=`.
-pkg_check_modules(IGRAPH REQUIRED igraph<=0.9.9999)
-
+pkg_check_modules(IGRAPH REQUIRED igraph)
 pkg_check_modules(GLIB REQUIRED glib-2.0)
 
 ## Parse out igraph version. Needed to work around breaking API changes in igraph.
diff --git a/src/tgen-graph.c b/src/tgen-graph.c
index c9c16c6..04266df 100644
--- a/src/tgen-graph.c
+++ b/src/tgen-graph.c
@@ -393,7 +393,8 @@ static GError* _tgengraph_parseGraphEdges(TGenGraph* g) {
     if(!error) {
         g->edgeCount = igraph_ecount(g->graph);
         if(g->edgeCount != edgeCount) {
-            tgen_warning("igraph_vcount %d does not match iterator count %d", g->edgeCount, edgeCount);
+            tgen_warning("igraph_vcount %ld does not match iterator count %ld", 
+                (long int) g->edgeCount, (long int) edgeCount);
         }
 
         tgen_info("%u graph edges ok", (guint) g->edgeCount);
@@ -416,15 +417,15 @@ static gboolean _tgengraph_hasSelfLoop(TGenGraph* g, igraph_integer_t vertexInde
     TGEN_ASSERT(g);
     gboolean isLoop = FALSE;
 
-    igraph_vector_t* resultNeighborVertices = g_new0(igraph_vector_t, 1);
-    gint result = igraph_vector_init(resultNeighborVertices, 0);
+    igraph_vector_int_t* resultNeighborVertices = g_new0(igraph_vector_int_t, 1);
+    gint result = igraph_vector_int_init(resultNeighborVertices, 0);
 
     if(result == IGRAPH_SUCCESS) {
         result = igraph_neighbors(g->graph, resultNeighborVertices, vertexIndex, IGRAPH_OUT);
         if(result == IGRAPH_SUCCESS) {
-            glong nVertices = igraph_vector_size(resultNeighborVertices);
+            glong nVertices = igraph_vector_int_size(resultNeighborVertices);
             for (gint i = 0; i < nVertices; i++) {
-                igraph_integer_t dstVertexIndex = igraph_vector_e(resultNeighborVertices, i);
+                igraph_integer_t dstVertexIndex = igraph_vector_int_get(resultNeighborVertices, i);
                 if(vertexIndex == dstVertexIndex) {
                     isLoop = TRUE;
                     break;
@@ -433,7 +434,7 @@ static gboolean _tgengraph_hasSelfLoop(TGenGraph* g, igraph_integer_t vertexInde
         }
     }
 
-    igraph_vector_destroy(resultNeighborVertices);
+    igraph_vector_int_destroy(resultNeighborVertices);
     g_free(resultNeighborVertices);
     return isLoop;
 }
@@ -442,10 +443,10 @@ static glong _tgengraph_countIncomingEdges(TGenGraph* g, igraph_integer_t vertex
     /* Count up the total number of incoming edges */
 
     /* initialize a vector to hold the result neighbor vertices for this action */
-    igraph_vector_t* resultNeighborVertices = g_new0(igraph_vector_t, 1);
+    igraph_vector_int_t* resultNeighborVertices = g_new0(igraph_vector_int_t, 1);
 
     /* initialize with 0 entries, since we dont know how many neighbors we have */
-    gint result = igraph_vector_init(resultNeighborVertices, 0);
+    gint result = igraph_vector_int_init(resultNeighborVertices, 0);
     if(result != IGRAPH_SUCCESS) {
         tgen_critical("igraph_vector_init return non-success code %i", result);
         g_free(resultNeighborVertices);
@@ -456,17 +457,17 @@ static glong _tgengraph_countIncomingEdges(TGenGraph* g, igraph_integer_t vertex
     result = igraph_neighbors(g->graph, resultNeighborVertices, vertexIndex, IGRAPH_IN);
     if(result != IGRAPH_SUCCESS) {
         tgen_critical("igraph_neighbors return non-success code %i", result);
-        igraph_vector_destroy(resultNeighborVertices);
+        igraph_vector_int_destroy(resultNeighborVertices);
         g_free(resultNeighborVertices);
         return -1;
     }
 
     /* handle the results */
-    glong totalIncoming = igraph_vector_size(resultNeighborVertices);
+    glong totalIncoming = igraph_vector_int_size(resultNeighborVertices);
     tgen_debug("found %li incoming 1-hop neighbors to vertex %li", totalIncoming, (glong)vertexIndex);
 
     /* cleanup */
-    igraph_vector_destroy(resultNeighborVertices);
+    igraph_vector_int_destroy(resultNeighborVertices);
     g_free(resultNeighborVertices);
 
     return totalIncoming;
@@ -1068,10 +1069,11 @@ static GError* _tgengraph_parseGraphVertices(TGenGraph* g) {
     if(!error) {
         g->vertexCount = igraph_vcount(g->graph);
         if(g->vertexCount != vertexCount) {
-            tgen_warning("igraph_vcount %d does not match iterator count %d", g->vertexCount, vertexCount);
+            tgen_warning("igraph_vcount %ld does not match iterator count %ld",
+                (long int) g->vertexCount, (long int) vertexCount);
         }
 
-        tgen_info("%u graph vertices ok", (guint) g->vertexCount);
+        tgen_info("%ld graph vertices ok", (long int) g->vertexCount);
     }
 
     return error;
@@ -1091,10 +1093,10 @@ static GError* _tgengraph_parseGraphProperties(TGenGraph* g) {
                 "igraph_is_connected return non-success code %i", result);
     }
 
-    result = igraph_clusters(g->graph, NULL, NULL, &(g->clusterCount), IGRAPH_WEAK);
+    result = igraph_connected_components(g->graph, NULL, NULL, &(g->clusterCount), IGRAPH_WEAK);
     if(result != IGRAPH_SUCCESS) {
         return g_error_new(G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
-                "igraph_clusters return non-success code %i", result);
+                "igraph_connected_components return non-success code %i", result);
     }
 
     /* it must be connected */
@@ -1109,13 +1111,13 @@ static GError* _tgengraph_parseGraphProperties(TGenGraph* g) {
 
     /* now check list of all attributes */
     igraph_strvector_t gnames, vnames, enames;
-    igraph_vector_t gtypes, vtypes, etypes;
+    igraph_vector_int_t gtypes, vtypes, etypes;
     igraph_strvector_init(&gnames, 25);
-    igraph_vector_init(&gtypes, 25);
+    igraph_vector_int_init(&gtypes, 25);
     igraph_strvector_init(&vnames, 25);
-    igraph_vector_init(&vtypes, 25);
+    igraph_vector_int_init(&vtypes, 25);
     igraph_strvector_init(&enames, 25);
-    igraph_vector_init(&etypes, 25);
+    igraph_vector_int_init(&etypes, 25);
 
     result = igraph_cattribute_list(g->graph, &gnames, &gtypes, &vnames, &vtypes, &enames, &etypes);
     if(result != IGRAPH_SUCCESS) {
@@ -1125,16 +1127,16 @@ static GError* _tgengraph_parseGraphProperties(TGenGraph* g) {
 
     GError* error = NULL;
     gint i = 0;
-    for(i = 0; !error && i < igraph_strvector_size(&gnames); i++) {
-        gchar* name = NULL;
-        igraph_strvector_get(&gnames, (glong) i, &name);
 
+    #ifdef DEBUG
+    for(i = 0; !error && i < igraph_strvector_size(&gnames); i++) {
+        const gchar* name = igraph_strvector_get(&gnames, (igraph_integer_t) i);
         tgen_debug("found graph attribute '%s'", name);
     }
+    #endif
 
     for(i = 0; !error && i < igraph_strvector_size(&vnames); i++) {
-        gchar* name = NULL;
-        igraph_strvector_get(&vnames, (glong) i, &name);
+        const gchar* name = igraph_strvector_get(&vnames, (igraph_integer_t) i);
 
         tgen_debug("found vertex attribute '%s'", name);
 
@@ -1150,8 +1152,7 @@ static GError* _tgengraph_parseGraphProperties(TGenGraph* g) {
     }
 
     for(i = 0; !error && i < igraph_strvector_size(&enames); i++) {
-        gchar* name = NULL;
-        igraph_strvector_get(&enames, (glong) i, &name);
+        const gchar* name = igraph_strvector_get(&enames, (igraph_integer_t) i);
 
         tgen_debug("found edge attribute '%s'", name);
 
@@ -1167,11 +1168,11 @@ static GError* _tgengraph_parseGraphProperties(TGenGraph* g) {
     }
 
     igraph_strvector_destroy(&gnames);
-    igraph_vector_destroy(&gtypes);
+    igraph_vector_int_destroy(&gtypes);
     igraph_strvector_destroy(&vnames);
-    igraph_vector_destroy(&vtypes);
+    igraph_vector_int_destroy(&vtypes);
     igraph_strvector_destroy(&enames);
-    igraph_vector_destroy(&etypes);
+    igraph_vector_int_destroy(&etypes);
 
     if(error) {
         tgen_warning("failed to verify graph properties and attributes");
@@ -1271,9 +1272,6 @@ TGenGraph* tgengraph_new(gchar* path) {
          * from multiple threads at the same time. this is not a problem when shadow
          * uses dlmopen to get a private namespace for each plugin. */
 
-        /* use the built-in C attribute handler */
-        igraph_attribute_table_t* oldHandler = igraph_set_attribute_table(&igraph_cattribute_table);
-
         g->graph = _tgengraph_loadNewGraph(g->graphPath);
         if(!g->graph) {
             error = g_error_new(G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
@@ -1290,9 +1288,6 @@ TGenGraph* tgengraph_new(gchar* path) {
         if(!error) {
             error = _tgengraph_parseGraphVertices(g);
         }
-
-        /* replace the old handler */
-        igraph_set_attribute_table(oldHandler);
     }
 
     if(error) {
@@ -1332,10 +1327,10 @@ GQueue* tgengraph_getNextActionIDs(TGenGraph* g, TGenActionID actionID) {
     igraph_integer_t srcVertexIndex = (igraph_integer_t) actionID;
 
     /* initialize a vector to hold the result neighbor vertices for this action */
-    igraph_vector_t* resultNeighborVertices = g_new0(igraph_vector_t, 1);
+    igraph_vector_int_t* resultNeighborVertices = g_new0(igraph_vector_int_t, 1);
 
     /* initialize with 0 entries, since we dont know how many neighbors we have */
-    gint result = igraph_vector_init(resultNeighborVertices, 0);
+    gint result = igraph_vector_int_init(resultNeighborVertices, 0);
     if(result != IGRAPH_SUCCESS) {
         tgen_critical("igraph_vector_init return non-success code %i", result);
         g_free(resultNeighborVertices);
@@ -1346,13 +1341,13 @@ GQueue* tgengraph_getNextActionIDs(TGenGraph* g, TGenActionID actionID) {
     result = igraph_neighbors(g->graph, resultNeighborVertices, srcVertexIndex, IGRAPH_OUT);
     if(result != IGRAPH_SUCCESS) {
         tgen_critical("igraph_neighbors return non-success code %i", result);
-        igraph_vector_destroy(resultNeighborVertices);
+        igraph_vector_int_destroy(resultNeighborVertices);
         g_free(resultNeighborVertices);
         return NULL;
     }
 
     /* handle the results */
-    glong nVertices = igraph_vector_size(resultNeighborVertices);
+    glong nVertices = igraph_vector_int_size(resultNeighborVertices);
     tgen_debug("found %li outgoing neighbors from vertex %li", nVertices, (glong)srcVertexIndex);
 
     /* only follow one edge of all edges with the 'weight' attribute (do a weighted choice)
@@ -1364,7 +1359,7 @@ GQueue* tgengraph_getNextActionIDs(TGenGraph* g, TGenActionID actionID) {
 
     for (gint i = 0; i < nVertices; i++) {
         /* we have source, get destination */
-        igraph_integer_t dstVertexIndex = igraph_vector_e(resultNeighborVertices, i);
+        igraph_integer_t dstVertexIndex = igraph_vector_int_get(resultNeighborVertices, i);
 
         TGenAction* nextAction = _tgengraph_getAction(g, dstVertexIndex);
         if(!nextAction) {
@@ -1378,7 +1373,7 @@ GQueue* tgengraph_getNextActionIDs(TGenGraph* g, TGenActionID actionID) {
         result = igraph_get_eid(g->graph, &edgeIndex, srcVertexIndex, dstVertexIndex, IGRAPH_DIRECTED, TRUE);
         if(result != IGRAPH_SUCCESS) {
             tgen_critical("igraph_get_eid return non-success code %i", result);
-            igraph_vector_destroy(resultNeighborVertices);
+            igraph_vector_int_destroy(resultNeighborVertices);
             g_free(resultNeighborVertices);
             g_queue_free(nextActions);
             g_queue_free(chooseActions);
@@ -1425,7 +1420,7 @@ GQueue* tgengraph_getNextActionIDs(TGenGraph* g, TGenActionID actionID) {
     }
 
     /* cleanup */
-    igraph_vector_destroy(resultNeighborVertices);
+    igraph_vector_int_destroy(resultNeighborVertices);
     g_free(resultNeighborVertices);
     g_queue_free(chooseActions);
     g_queue_free(chooseWeights);
diff --git a/src/tgen-igraph-compat.h b/src/tgen-igraph-compat.h
index 799be0c..d8b41fc 100644
--- a/src/tgen-igraph-compat.h
+++ b/src/tgen-igraph-compat.h
@@ -5,9 +5,30 @@
 #ifndef TGEN_IGRAPH_COMPAT_H_
 #define TGEN_IGRAPH_COMPAT_H_
 
-/* Renamed in in igraph 0.9.0 */
+/* Renamed in igraph 0.9.0 */
 #if IGRAPH_VERSION_MAJOR==0 && IGRAPH_VERSION_MINOR<9
 #define igraph_set_attribute_table igraph_i_set_attribute_table
 #endif
 
+/* Changed in igraph 0.10.0 */
+#if IGRAPH_VERSION_MAJOR == 0 && IGRAPH_VERSION_MINOR < 10
+// Renamed
+#define igraph_connected_components igraph_clusters
+#define IGRAPH_ATTRIBUTE_UNSPECIFIED IGRAPH_ATTRIBUTE_DEFAULT
+// Change to int types
+#define igraph_vector_int_t igraph_vector_t
+#define igraph_vector_int_init igraph_vector_init
+#define igraph_vector_int_size igraph_vector_size
+#define igraph_vector_int_get igraph_vector_e
+#define igraph_vector_int_destroy igraph_vector_destroy
+// Removed the name arg and return it instead
+inline const char *igraph_strvector_get_compat(igraph_strvector_t *sv,
+                                               igraph_integer_t idx) {
+  char *name_ = NULL;
+  (igraph_strvector_get)(sv, idx, &name_);
+  return (const char *)name_;
+}
+#define igraph_strvector_get igraph_strvector_get_compat
+#endif
+
 #endif
\ No newline at end of file
diff --git a/src/tgen-main.c b/src/tgen-main.c
index 592bc06..bf41e33 100644
--- a/src/tgen-main.c
+++ b/src/tgen-main.c
@@ -10,6 +10,7 @@
 #include <igraph.h>
 
 #include "tgen.h"
+#include "tgen-igraph-compat.h"
 
 static void _tgenmain_cleanup(gint status, gpointer arg) {
     if(arg) {
@@ -82,6 +83,9 @@ static gint _tgenmain_run(gint argc, gchar *argv[]) {
         tgen_message("Set SIG_IGN for signal SIGPIPE");
     }
 
+    /* use the built-in C attribute handler. this is set once and then left alone. */
+    igraph_set_attribute_table(&igraph_cattribute_table);
+
     /* parse the config file */
     TGenGraph* graph = tgengraph_new(argv[1]);
     if (!graph) {
diff --git a/src/tgen-markovmodel.c b/src/tgen-markovmodel.c
index 4fc5e79..e0a8ba7 100644
--- a/src/tgen-markovmodel.c
+++ b/src/tgen-markovmodel.c
@@ -109,7 +109,7 @@ static gboolean tgenmarkovmodel_attributeTypeMismatch(const igraph_t *graph, igr
         tgen_warning("Internal error: igraph_cattribute_table.gettype missing; unable to validate attribute types");
         return FALSE;
     }
-    igraph_attribute_type_t type = IGRAPH_ATTRIBUTE_DEFAULT;
+    igraph_attribute_type_t type = IGRAPH_ATTRIBUTE_UNSPECIFIED;
     if (igraph_cattribute_table.gettype(graph, &type, elemtype, name) != IGRAPH_SUCCESS) {
         // The igraph documentation says it'll abort on any error, but in case
         // it doesn't, error out here.
@@ -880,10 +880,6 @@ static igraph_t* _tgenmarkovmodel_loadGraph(FILE* graphFileStream, const gchar*
     rewind(graphFileStream);
 
     igraph_t* graph = g_new0(igraph_t, 1);
-
-    /* make sure we use the correct attribute handler */
-    igraph_set_attribute_table(&igraph_cattribute_table);
-
     result = igraph_read_graph_graphml(graph, graphFileStream, 0);
 
     if (result != IGRAPH_SUCCESS) {


Test patch:
diff --git a/test/test-markovmodel.c b/test/test-markovmodel.c
index 819f88a..63b3961 100644
--- a/test/test-markovmodel.c
+++ b/test/test-markovmodel.c
@@ -2,7 +2,9 @@
 #include <stdint.h>
 
 #include <glib.h>
+#include <igraph.h>
 
+#include "tgen-igraph-compat.h"
 #include "tgen-log.h"
 #include "tgen-markovmodel.h"
 
@@ -87,6 +89,9 @@ gint main(gint argc, gchar *argv[]) {
         return EXIT_FAILURE;
     }
 
+    /* use the built-in C attribute handler. this is set once and then left alone. */
+    igraph_set_attribute_table(&igraph_cattribute_table);
+
     guint32 seed = (guint32)atoi(argv[1]);
     gchar* path = g_strdup(argv[2]);
     gchar* name = g_path_get_basename(path);