Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix broken compilation with Ruby interpreter compiled with XCode 14 #114

Merged
merged 1 commit into from
Jun 8, 2023

Conversation

stanhu
Copy link
Contributor

@stanhu stanhu commented Sep 16, 2022

XCode 14 warns if the -undefined dynamic_lookup linker option is used. As a result, the configure script for the Ruby interpreter disables these flags, causing building the native gem to fail with undefined symbols:

compiling encoder.c
encoder.c:307:14: warning: unused function 'rb_cBignum_ffi_yajl' [-Wunused-function]
static VALUE rb_cBignum_ffi_yajl(VALUE self, VALUE rb_yajl_gen, VALUE state) {
             ^
1 warning generated.
linking shared-object ffi_yajl/ext/encoder.bundle
Undefined symbols for architecture arm64:
  "_yajl_gen_alloc", referenced from:
      _mEncoder_do_yajl_encode in encoder.o
  "_yajl_gen_array_close", referenced from:
      _gen_array_close in encoder.o
  "_yajl_gen_array_open", referenced from:
      _gen_array_open in encoder.o
  "_yajl_gen_bool", referenced from:
      _gen_true in encoder.o
      _gen_false in encoder.o
  "_yajl_gen_config", referenced from:
      _mEncoder_do_yajl_encode in encoder.o
  "_yajl_gen_free", referenced from:
      _mEncoder_do_yajl_encode in encoder.o
  "_yajl_gen_get_buf", referenced from:
      _mEncoder_do_yajl_encode in encoder.o
  "_yajl_gen_map_close", referenced from:
      _gen_map_close in encoder.o
  "_yajl_gen_map_open", referenced from:
      _gen_map_open in encoder.o
  "_yajl_gen_null", referenced from:
      _gen_null in encoder.o
  "_yajl_gen_number", referenced from:
      _gen_number in encoder.o
  "_yajl_gen_string", referenced from:
      _gen_cstring in encoder.o
ld: symbol(s) not found for architecture arm64
clang: error: linker command failed with exit code 1 (use -v to see invocation)
make: *** [encoder.bundle] Error 1

To fix this, we can explicitly list the symbols that will be dynamically looked up and use that in the -U linker option to specify that it is okay for that symbol to have no definition.

Relates to ruby/ruby#6193

Description

[Please describe what this change achieves]

Issues Resolved

[List any existing issues this PR resolves, or any Discourse or
StackOverflow discussions that are relevant]

Check List

@stanhu stanhu requested review from a team as code owners September 16, 2022 05:14
@stanhu stanhu force-pushed the sh-xcode-14-fix branch 2 times, most recently from 13dd567 to 18b57e0 Compare September 16, 2022 05:20
@stanhu
Copy link
Contributor Author

stanhu commented Sep 16, 2022

Another option would be to use -bundle_loader: https://blog.tim-smith.us/2015/09/python-extension-modules-os-x/

 % gcc -dynamic -bundle -o encoder.bundle encoder.o -L. -L/private/tmp/ruby/lib -L/private/tmp/ruby/lib/ruby/gems/2.7.0/gems/libyajl2-2.1.0/lib/libyajl2/vendored-libyajl2/lib -L. -fstack-protector-strong -Wl,-multiply_defined,suppress -lruby.2.7 -m64  -lruby.2.7
Undefined symbols for architecture arm64:
  "_yajl_gen_alloc", referenced from:
      _mEncoder_do_yajl_encode in encoder.o
  "_yajl_gen_array_close", referenced from:
      _gen_array_close in encoder.o
  "_yajl_gen_array_open", referenced from:
      _gen_array_open in encoder.o
  "_yajl_gen_bool", referenced from:
      _gen_true in encoder.o
      _gen_false in encoder.o
  "_yajl_gen_config", referenced from:
      _mEncoder_do_yajl_encode in encoder.o
  "_yajl_gen_free", referenced from:
      _mEncoder_do_yajl_encode in encoder.o
  "_yajl_gen_get_buf", referenced from:
      _mEncoder_do_yajl_encode in encoder.o
  "_yajl_gen_map_close", referenced from:
      _gen_map_close in encoder.o
  "_yajl_gen_map_open", referenced from:
      _gen_map_open in encoder.o
  "_yajl_gen_null", referenced from:
      _gen_null in encoder.o
  "_yajl_gen_number", referenced from:
      _gen_number in encoder.o
  "_yajl_gen_string", referenced from:
      _gen_cstring in encoder.o
ld: symbol(s) not found for architecture arm64
clang: error: linker command failed with exit code 1 (use -v to see invocation)
% gcc -dynamic -bundle -o encoder.bundle encoder.o -L. -L/private/tmp/ruby/lib -L/private/tmp/ruby/lib/ruby/gems/2.7.0/gems/libyajl2-2.1.0/lib/libyajl2/vendored-libyajl2/lib -L. -fstack-protector-strong -Wl,-multiply_defined,suppress -lruby.2.7 -m64  -lruby.2.7 -bundle_loader /tmp/ruby/lib/ruby/gems/2.7.0/gems/libyajl2-1.2.0/lib/libyajl2/vendored-libyajl2/lib/libyajl.bundle
%

@kateinoigakukun
Copy link

@stanhu Yes, adding -bundle_loader option is one of the potential solutions. But CRuby already uses it when --disable-shared (which means libruby.dylib is not used, and each extension bundle resolves CRuby symbols from ruby executable file), and ld64 doesn't support multiple bundle loaders, so extension library cannot specify bundle_loader.

# a result Ruby interpreters compiled under XCode 14 no longer specify
# this flag by default in DLDFLAGS. Let's specify the list of dynamic symbols
# here to avoid compilation failures.
if macos?
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can probably just look at whether LDFLAGS has -Wl,-undefined dynamic_lookup, or just do this all the time.

@stanhu stanhu force-pushed the sh-xcode-14-fix branch 2 times, most recently from 72d64b8 to bf55e49 Compare November 14, 2022 07:39
@softbrada
Copy link

softbrada commented Dec 13, 2022

can this be merged, please, and released to RubyGems? 🥺 it works for me on MacOS using Apple M1 with ARM64 architecture 🥳

XCode 14 warns if the `-undefined dynamic_lookup` linker option is
used. As a result, the `configure` script for the Ruby interpreter
disables these flags, causing building the native gem to fail with
undefined symbols:

```
compiling encoder.c
encoder.c:307:14: warning: unused function 'rb_cBignum_ffi_yajl' [-Wunused-function]
static VALUE rb_cBignum_ffi_yajl(VALUE self, VALUE rb_yajl_gen, VALUE state) {
             ^
1 warning generated.
linking shared-object ffi_yajl/ext/encoder.bundle
Undefined symbols for architecture arm64:
  "_yajl_gen_alloc", referenced from:
      _mEncoder_do_yajl_encode in encoder.o
  "_yajl_gen_array_close", referenced from:
      _gen_array_close in encoder.o
  "_yajl_gen_array_open", referenced from:
      _gen_array_open in encoder.o
  "_yajl_gen_bool", referenced from:
      _gen_true in encoder.o
      _gen_false in encoder.o
  "_yajl_gen_config", referenced from:
      _mEncoder_do_yajl_encode in encoder.o
  "_yajl_gen_free", referenced from:
      _mEncoder_do_yajl_encode in encoder.o
  "_yajl_gen_get_buf", referenced from:
      _mEncoder_do_yajl_encode in encoder.o
  "_yajl_gen_map_close", referenced from:
      _gen_map_close in encoder.o
  "_yajl_gen_map_open", referenced from:
      _gen_map_open in encoder.o
  "_yajl_gen_null", referenced from:
      _gen_null in encoder.o
  "_yajl_gen_number", referenced from:
      _gen_number in encoder.o
  "_yajl_gen_string", referenced from:
      _gen_cstring in encoder.o
ld: symbol(s) not found for architecture arm64
clang: error: linker command failed with exit code 1 (use -v to see invocation)
make: *** [encoder.bundle] Error 1
```

To fix this, we can explicitly list the symbols that will be
dynamically looked up and use that in the `-U` linker option to
specify that it is okay for that symbol to have no definition.

Relates to ruby/ruby#6193

Signed-off-by: Stan Hu <stanhu@gmail.com>
@sonarqubecloud
Copy link

Kudos, SonarCloud Quality Gate passed!    Quality Gate passed

Bug A 0 Bugs
Vulnerability A 0 Vulnerabilities
Security Hotspot A 0 Security Hotspots
Code Smell A 0 Code Smells

No Coverage information No Coverage information
100.0% 100.0% Duplication

@stanhu
Copy link
Contributor Author

stanhu commented Dec 13, 2022

Note that Ruby 2.7.7 and 3.0.5 have shipped a fix for https://bugs.ruby-lang.org/issues/19005, so this pull request isn't needed for those versions.

@thr3a
Copy link

thr3a commented Jan 6, 2023

@stanhu

Thank you for sharing.

Note that Ruby 2.7.7 and 3.0.5 have shipped a fix for https://bugs.ruby-lang.org/issues/19005

I tried installing on Ruby 3.1.3 and got an error.

However, I was able to install successfully with the following settings:

bundle config build.ffi-yajl --local --with-ldflags="-Wl,-undefined,dynamic_lookup"

Hasn't the fix for this issue been backported to Ruby 3.1.3?

❯ ruby -v
ruby 3.1.3p185 (2022-11-24 revision 1a6b16756e) [arm64-darwin22]

@stanhu
Copy link
Contributor Author

stanhu commented Jan 6, 2023

Yes, I've confirmed that Ruby 3.1.3 appears to have the fix (ruby/ruby@bf92aac), but for some reason I'm not seeing the ADDITIONAL_DLDFLAGS set with -Wl,-undefined,dynamic_lookup:

irb(main):001:0> RUBY_VERSION
=> "3.1.3"
irb(main):002:0> RbConfig::CONFIG['ADDITIONAL_DLDFLAGS']
=> ""

Whereas 3.0.5 has this:

irb(main):001:0> RUBY_VERSION
=> "3.0.5"
irb(main):004:0> RbConfig::CONFIG['ADDITIONAL_DLDFLAGS']
=> "-Wl,-undefined,dynamic_lookup"

@stanhu
Copy link
Contributor Author

stanhu commented Jan 7, 2023

Actually, I'm seeing the problem show up in Ruby 3.2.0 as well.

@stanhu
Copy link
Contributor Author

stanhu commented Jan 7, 2023

Confirmed there is a problem with Ruby 3.1.3 and up: https://bugs.ruby-lang.org/issues/19005#note-25

UPDATE: This bug has been reported in https://bugs.ruby-lang.org/issues/19082.

@tpowell-progress tpowell-progress self-assigned this Feb 2, 2023
@stanhu
Copy link
Contributor Author

stanhu commented May 23, 2023

@tpowell-progress Is there anything I can do to get this merged? Our team is hitting this failure now that we've updated to Ruby 3.1.4.

@tpowell-progress
Copy link
Contributor

@tpowell-progress Is there anything I can do to get this merged? Our team is hitting this failure now that we've updated to Ruby 3.1.4.

Sorry, the supporting projects fly below my radar most of the time. However, I just ran into this problem as well on ARM64 as well. I need to see what the release process is for this, but adding it to the to do list.

@tpowell-progress tpowell-progress added the Expeditor: Bump Version Minor Used by github.minor_bump_labels to bump the Minor version number. label Jun 8, 2023
@tpowell-progress
Copy link
Contributor

Ok, I'm merging but also bumping to 2.5.0. Have an ARM64 build for chef that will need to include and also need to bump chef-powershell so as to not cause a version conflict for the next Chef 18 release.

@tpowell-progress
Copy link
Contributor

FYI, this is breaking in the Windows pipelines publicly accessible example, so I won't be able to merge this in until I get to the bottom of that issue. Difficulty: It works on my VMs (but fails on multiple CI setups)

@stanhu
Copy link
Contributor Author

stanhu commented Jun 9, 2023

@tpowell-progress To be clear, the Windows error seems unrelated to this pull request?

I'm not quite sure where yajldll is supposed to come from here:

encoder.c:307:14: warning: 'rb_cBignum_ffi_yajl' defined but not used
[-Wunused-function]
307 | static VALUE rb_cBignum_ffi_yajl(VALUE self, VALUE rb_yajl_gen, VALUE
state) {
      |              ^~~~~~~~~~~~~~~~~~~
linking shared-object ffi_yajl/ext/encoder.so
C:/opscode/chef/embedded/msys64/ucrt64/bin/../lib/gcc/x86_64-w64-mingw32/11.2.0/../../../../x86_64-w64-mingw32/bin/ld.exe:
cannot find -lyajldll: No such file or directory
collect2.exe: error: ld returned 1 exit status

@tpowell-progress
Copy link
Contributor

@stanhu Yes, I've even compared the .gem files without finding anything (such as vendored artifacts created by an intermediate step) that would contribute to the issue. I'm going to create a clean branch off main to double check, but I think this issue (much like the macOS issue in that branch) is related to how we're trying to orchestrate a test environment with gem installs that are looking for public versions + how we bridge that gap in Windows and macOS.

@tpowell-progress
Copy link
Contributor

tpowell-progress commented Jun 12, 2023

I've had to yank 2.5 due to a few pins being on '~> 2.x', and unfortunately, multiple builds started failing. Need to go back and fix those pins, but I'm a few build issues deep at the moment.

@stanhu
Copy link
Contributor Author

stanhu commented Jun 12, 2023

@tpowell-progress Unfortunately we had already updated our project to use v2.5.0, so the yank caused our builds to fail.

@stanhu
Copy link
Contributor Author

stanhu commented Jun 12, 2023

I should note on Windows Server 2022, this gem installed and built fine on Ruby 3.1.3:

PS C:\Users\stan\downloads\ffi-yajl> gem install .\ffi-yajl-2.5.0.gem
Fetching libyajl2-2.1.0.gem
Temporarily enhancing PATH for MSYS/MINGW...
Building native extensions. This could take a while...
Successfully installed libyajl2-2.1.0
Building native extensions. This could take a while...
Successfully installed ffi-yajl-2.5.0
Parsing documentation for libyajl2-2.1.0
Installing ri documentation for libyajl2-2.1.0
Parsing documentation for ffi-yajl-2.5.0
Installing ri documentation for ffi-yajl-2.5.0
Done installing documentation for libyajl2, ffi-yajl after 4 seconds
2 gems installed

Unfortunately, yanking the gem also makes it harder to replicate the issue.

@tpowell-progress
Copy link
Contributor

Working on a 3.0.0 build due to it being a "breaking" change in certain cases.

@tpowell-progress
Copy link
Contributor

tpowell-progress commented Jun 12, 2023

@stanhu Pushed a 3.0.0 version of the gem so that all the ~> 2.x versions wouldn't automatically pick it up. I'll be fixing those pins in the next updates and will likely have 3.0.x or 3.1.x version soon for this, but at least there will be an updated gem. I apologize for the mess.

@stanhu
Copy link
Contributor Author

stanhu commented Jun 12, 2023

Unfortunately the only reason we depend on ffi-yajl is for Ohai, and we're using v17.9.0 at the moment, so the v3.0.0 tag doesn't really help us in the short term.

I've reproduced the problem by installing Chef and running Chef's version of gem install:

PS C:\opscode\chef\embedded\bin> ./gem install C:\opscode\ffi-yajl-2.5.0.gem
Temporarily enhancing PATH for MSYS/MINGW...
Building native extensions. This could take a while...
ERROR:  Error installing C:\opscode\ffi-yajl-2.5.0.gem:
        ERROR: Failed to build gem native extension.

    current directory: C:/opscode/chef/embedded/lib/ruby/gems/3.1.0/gems/ffi-yajl-2.5.0/ext/ffi_yajl/ext/encoder
C:/opscode/chef/embedded/bin/ruby.exe -I C:/opscode/chef/embedded/lib/ruby/3.1.0 -r ./siteconf20230612-3076-xisy9f.rb extconf.rb
 -IC:/opscode/chef/embedded/lib/ruby/gems/3.1.0/gems/libyajl2-2.1.0/lib/libyajl2/vendored-libyajl2/include -IC:/opscode/chef/embedded/include -DFD_SETSIZE=2048 -march=x86-64 -O3
 -LC:/opscode/chef/embedded/lib/ruby/gems/3.1.0/gems/libyajl2-2.1.0/lib/libyajl2/vendored-libyajl2/lib -L. -LC:/opscode/chef/embedded/lib -fno-lto -Wl,--no-as-needed
creating Makefile

current directory: C:/opscode/chef/embedded/lib/ruby/gems/3.1.0/gems/ffi-yajl-2.5.0/ext/ffi_yajl/ext/encoder
make DESTDIR\= clean

current directory: C:/opscode/chef/embedded/lib/ruby/gems/3.1.0/gems/ffi-yajl-2.5.0/ext/ffi_yajl/ext/encoder
make DESTDIR\=
generating encoder-x64-mingw-ucrt.def
compiling encoder.c
encoder.c:307:14: warning: 'rb_cBignum_ffi_yajl' defined but not used [-Wunused-function]
  307 | static VALUE rb_cBignum_ffi_yajl(VALUE self, VALUE rb_yajl_gen, VALUE state) {
      |              ^~~~~~~~~~~~~~~~~~~
linking shared-object ffi_yajl/ext/encoder.so
C:/opscode/chef/embedded/msys64/ucrt64/bin/../lib/gcc/x86_64-w64-mingw32/11.2.0/../../../../x86_64-w64-mingw32/bin/ld.exe: cannot find -lyajldll: No such file or directory
collect2.exe: error: ld returned 1 exit status
make: *** [Makefile:263: encoder.so] Error 1

make failed, exit code 2

Gem files will remain installed in C:/opscode/chef/embedded/lib/ruby/gems/3.1.0/gems/ffi-yajl-2.5.0 for inspection.
Results logged to C:/opscode/chef/embedded/lib/ruby/gems/3.1.0/extensions/x64-mingw-ucrt/3.1.0/ffi-yajl-2.5.0/gem_make.out

I think libyajl2 might have shipped with the Chef Omnibus version, but this version does not include libyajldll.a:

C:\opscode\chef\embedded\lib\ruby\gems\3.1.0\gems\libyajl2-2.1.0\lib\libyajl2\vendored-libyajl2\lib>dir
 Volume in drive C has no label.
 Volume Serial Number is 3E45-2818

 Directory of C:\opscode\chef\embedded\lib\ruby\gems\3.1.0\gems\libyajl2-2.1.0\lib\libyajl2\vendored-libyajl2\lib

06/02/2023  03:14 PM    <DIR>          .
06/02/2023  03:08 PM    <DIR>          ..
06/02/2023  03:08 PM             1,345 libyajl.def
06/02/2023  03:08 PM           147,441 libyajl.so
               2 File(s)        148,786 bytes
               2 Dir(s)  21,475,872,768 bytes free

For reference, my local Ruby install contains this in C:\Ruby31-x64\lib\ruby\gems\3.1.0\gems\libyajl2-2.1.0\lib\libyajl2\vendored-libyajl2\lib:

C:\>dir C:\Ruby31-x64\lib\ruby\gems\3.1.0\gems\libyajl2-2.1.0\lib\libyajl2\vendored-libyajl2\lib
 Volume in drive C has no label.
 Volume Serial Number is 3E45-2818

 Directory of C:\Ruby31-x64\lib\ruby\gems\3.1.0\gems\libyajl2-2.1.0\lib\libyajl2\vendored-libyajl2\lib

06/12/2023  07:21 PM    <DIR>          .
06/12/2023  07:21 PM    <DIR>          ..
06/12/2023  07:21 PM             1,345 libyajl.def
06/12/2023  07:21 PM            81,408 libyajl.so
06/12/2023  07:21 PM            34,398 libyajldll.a
               3 File(s)        117,151 bytes
               2 Dir(s)  21,474,656,256 bytes free

Since ffi-yajl v2.4.0 is also shipped with Chef Omnibus, the bundle install normally works fine if the required version of ffi-yajl is already installed. However, if any version of ffi-yajl needs to be installed again (including v2.4.0), the build will fail since libyajldll.a is not present.

It seems to me there are two ways to solve this:

  1. Ship libyajldll.a as part of Chef Omnibus Windows. This seems preferable to me.
  2. Pin the version of ffi-yajl used by Chef and make it clear that reinstalling this gem won't work with Omnibus.

@stanhu
Copy link
Contributor Author

stanhu commented Jun 12, 2023

@tpowell-progress Given that CI appears to be trying to update from an existing Chef Omnibus install, it does seem to me that shipping libyajldll.a makes sense since anytime ffi-yajl is updated you will run into the same problem.

I did a quick scan of the build rules in https://github.com/chef/chef/tree/main/omnibus. I didn't see where libyajldll.a might have been pruned:

I don't suppose you know why that file might have been dropped? I'm launching a local build to see if I can replicate this.

@stanhu
Copy link
Contributor Author

stanhu commented Jun 12, 2023

Ok, I think https://github.com/chef/omnibus-software/blob/e43f50af2e03dc073174f8de819d08432b9b22b7/config/software/ruby-cleanup.rb#L40-L53 is removing all *.a files.

Perhaps there should be an exception for Windows? UPDATE: I see this is needed to clean up some cruft, like libffi.a.

stanhu added a commit to stanhu/libyajl2-gem that referenced this pull request Jun 12, 2023
By default, omnibus-software will clean up all *.a files in the Ruby
gem path
(https://github.com/chef/omnibus-software/blob/e43f50af2e03dc073174f8de819d08432b9b22b7/config/software/ruby-cleanup.rb#L40-L53).

By convention, Windows implib files should have a .lib extension
(https://learn.microsoft.com/en-us/cpp/build/reference/implib-name-import-library?view=msvc-170),
so this change makes it possible to preserve the required library.

Relates to chef/ffi-yajl#114
@stanhu
Copy link
Contributor Author

stanhu commented Jun 12, 2023

Ok, it seems that the Windows implib should have a .lib extension instead of .a, and that should make it possible to ship this library with Chef Omnibus. I've validated the .lib continues to work:

C:\opscode\chef\embedded\lib\ruby\gems\3.1.0\gems\libyajl2-2.1.0\lib\libyajl2\vendored-libyajl2\lib>copy c:\opscode\libyajldll.a libyajldll.lib
        1 file(s) copied.

C:\opscode\chef\embedded\lib\ruby\gems\3.1.0\gems\libyajl2-2.1.0\lib\libyajl2\vendored-libyajl2\lib>c:\opscode\chef\embedded\bin\gem install c:\opscode\ffi-yajl-2.5.0.gem
Temporarily enhancing PATH for MSYS/MINGW...
Building native extensions. This could take a while...
Successfully installed ffi-yajl-2.5.0
Parsing documentation for ffi-yajl-2.5.0
Installing ri documentation for ffi-yajl-2.5.0
Done installing documentation for ffi-yajl after 1 seconds
1 gem installed

chef/libyajl2-gem#28 would fix the extension.

stanhu added a commit to stanhu/libyajl2-gem that referenced this pull request Jun 12, 2023
By default, omnibus-software will clean up all *.a files in the Ruby
gem path
(https://github.com/chef/omnibus-software/blob/e43f50af2e03dc073174f8de819d08432b9b22b7/config/software/ruby-cleanup.rb#L40-L53).

By convention, Windows implib files should have a .lib extension
(https://learn.microsoft.com/en-us/cpp/build/reference/implib-name-import-library?view=msvc-170),
so this change makes it possible to preserve the required library.

Relates to chef/ffi-yajl#114

Signed-off-by: Stan Hu <stanhu@gmail.com>
stanhu added a commit to stanhu/libyajl2-gem that referenced this pull request Jun 12, 2023
By default, omnibus-software will clean up all *.a files in the Ruby
gem path
(https://github.com/chef/omnibus-software/blob/e43f50af2e03dc073174f8de819d08432b9b22b7/config/software/ruby-cleanup.rb#L40-L53).

By convention, Windows implib files should have a .lib extension
(https://learn.microsoft.com/en-us/cpp/build/reference/implib-name-import-library?view=msvc-170),
so this change makes it possible to preserve the required library.

Relates to chef/ffi-yajl#114

Signed-off-by: Stan Hu <stanhu@gmail.com>
@tpowell-progress
Copy link
Contributor

I've added an internal issue (CHEF-4109) for this as will break internal development as well for ARM Macs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Expeditor: Bump Version Minor Used by github.minor_bump_labels to bump the Minor version number.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants