Add a Bazel-built slicot via f2c

Previously, we were using the host binaries along with slycot, which
didn't support using it at runtime.

We built slicot itself using f2c, and then the normal Bazel C/C++
infrastructure on the resulting C code.

We're also using clapack, which is lapack and BLAS pre-run through f2c
along with various fixups.

The build is horribly noisy and full of warnings and information
messages, but oh well.

Change-Id: If13056ff56d334b7207cd9f0f7c0ce22b999b78e
diff --git a/WORKSPACE b/WORKSPACE
index f220d3c..1a5bff3 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -13,11 +13,6 @@
   commit = '5af5f283cb23cbe23c4dfea4d5e56071bdbd6e70',
 )
 
-bind(
-  name = 'slycot',
-  actual = '@slycot_repo//:slycot',
-)
-
 new_http_archive(
   name = 'arm_frc_linux_gnueabi_repo',
   build_file = 'tools/cpp/arm-frc-linux-gnueabi/arm-frc-linux-gnueabi.BUILD',
@@ -121,3 +116,20 @@
   url = 'http://frc971.org/Build-Dependencies/libusb-1.0.21-windows.tar.xz',
   sha256 = 'fc2ba03992f343aabbaf9eb90559c6e00cdc6a2bd914d7cebea85857d5244015',
 )
+
+# The data tarball of the same-named Debian package.
+new_http_archive(
+    name = "f2c",
+    sha256 = "2c677437f8217a2e2b23e41b33995d0571644fc1bea46de858f8913a5053e3f4",
+    url = "http://frc971.org/Build-Dependencies/f2c_20100827-1_amd64.xz.tar.xz",
+    build_file = "debian/f2c.BUILD",
+)
+
+# Downloaded from http://www.netlib.org/clapack/.
+new_http_archive(
+  name = 'clapack',
+  url = "http://frc971.org/Build-Dependencies/clapack-3.2.1.tgz",
+  build_file = 'debian/clapack.BUILD',
+  sha256 = '6dc4c382164beec8aaed8fd2acc36ad24232c406eda6db462bd4c41d5e455fac',
+  strip_prefix = 'CLAPACK-3.2.1/',
+)
diff --git a/debian/clapack.BUILD b/debian/clapack.BUILD
new file mode 100644
index 0000000..528b6a7
--- /dev/null
+++ b/debian/clapack.BUILD
@@ -0,0 +1,293 @@
+licenses(['notice'])
+
+load('@//tools/build_rules:fortran.bzl', 'f2c_copts')
+
+genrule(
+  name = 'create_sysdep1',
+  outs = ['extra_includes/sysdep1.h'],
+  srcs = ['F2CLIBS/libf2c/sysdep1.h0'],
+  cmd = 'cp $< $@',
+)
+
+_xerbla_patch = '''
+--- xerbla.c   2018-02-19 19:58:03.685420156 -0500
++++ xerbla.c   2018-02-19 19:59:02.993259128 -0500
+@@ -55,7 +55,7 @@ static integer c__1 = 1;
+ /*     .. */
+ /*     .. Executable Statements .. */
+ 
+-    printf("** On entry to %6s, parameter number %2i had an illegal value\\n",
++    printf("** On entry to %6s, parameter number %2li had an illegal value\\n",
+\t\tsrname, *info);
+ 
+ 
+'''
+genrule(
+  name = 'patch_xerbla',
+  srcs = ['SRC/xerbla.c'],
+  outs = ['patched_xerbla.c'],
+  cmd = '\n'.join([
+    'cp $< $@',
+    'patch $@ - <<END',
+    _xerbla_patch,
+    'END',
+  ]),
+)
+
+_err_patch = '''
+--- err.c  2018-02-19 20:06:40.532033141 -0500
++++ err.c  2018-02-19 20:10:25.907439219 -0500
+@@ -164,8 +164,10 @@ f__fatal(int n, const char *s)
+ \tif (f__curunit) {
+ \t\tfprintf(stderr,"apparent state: unit %d ",
+ \t\t\t(int)(f__curunit-f__units));
+-\t\tfprintf(stderr, f__curunit->ufnm ? "named %s\\n" : "(unnamed)\\n",
+-\t\t\tf__curunit->ufnm);
++\t\tif (f__curunit->ufnm)
++\t\t\tfprintf(stderr, "named %s\\n", f__curunit->ufnm);
++\t\telse
++\t\t\tfprintf(stderr, "(unnamed)\\n");
+ \t\t}
+ \telse
+ \t\tfprintf(stderr,"apparent state: internal I/O\\n");
+'''
+genrule(
+  name = 'patch_err',
+  srcs = ['F2CLIBS/libf2c/err.c'],
+  outs = ['patched_err.c'],
+  cmd = '\n'.join([
+    'cp $< $@',
+    'patch $@ - <<END',
+    _err_patch,
+    'END',
+  ]),
+)
+
+cc_library(
+  name = 'clapack',
+  visibility = ['//visibility:public'],
+  srcs = glob(include = [
+    'SRC/*.c',
+    'BLAS/SRC/*.c',
+  ], exclude = [
+    # These are duplicated in SRC (with the ones in SRC a bit more cleaned up).
+    'BLAS/SRC/xerbla.c',
+    'BLAS/SRC/xerbla_array.c',
+
+    # We need to use a patched version of this.
+    'SRC/xerbla.c',
+
+    # Files requiring XBLAS (extended precision), which we don't have.
+    'SRC/sgesvxx.c',
+    'SRC/sgerfsx.c',
+    'SRC/sla_gerfsx_extended.c',
+    'SRC/sla_geamv.c',
+    'SRC/sla_gercond.c',
+    'SRC/sla_gerpvgrw.c',
+    'SRC/ssysvxx.c',
+    'SRC/ssyrfsx.c',
+    'SRC/sla_syrfsx_extended.c',
+    'SRC/sla_syamv.c',
+    'SRC/sla_syrcond.c',
+    'SRC/sla_syrpvgrw.c',
+    'SRC/sposvxx.c',
+    'SRC/sporfsx.c',
+    'SRC/sla_porfsx_extended.c',
+    'SRC/sla_porcond.c',
+    'SRC/sla_porpvgrw.c',
+    'SRC/sgbsvxx.c',
+    'SRC/sgbrfsx.c',
+    'SRC/sla_gbrfsx_extended.c',
+    'SRC/sla_gbamv.c',
+    'SRC/sla_gbrcond.c',
+    'SRC/sla_gbrpvgrw.c',
+    'SRC/sla_lin_berr.c',
+    'SRC/slarscl2.c',
+    'SRC/slascl2.c',
+    'SRC/sla_wwaddw.c',
+    'SRC/cgesvxx.c',
+    'SRC/cgerfsx.c',
+    'SRC/cla_gerfsx_extended.c',
+    'SRC/cla_geamv.c',
+    'SRC/cla_gercond_c.c',
+    'SRC/cla_gercond_x.c',
+    'SRC/cla_gerpvgrw.c',
+    'SRC/csysvxx.c',
+    'SRC/csyrfsx.c',
+    'SRC/cla_syrfsx_extended.c',
+    'SRC/cla_syamv.c',
+    'SRC/cla_syrcond_c.c',
+    'SRC/cla_syrcond_x.c',
+    'SRC/cla_syrpvgrw.c',
+    'SRC/cposvxx.c',
+    'SRC/cporfsx.c',
+    'SRC/cla_porfsx_extended.c',
+    'SRC/cla_porcond_c.c',
+    'SRC/cla_porcond_x.c',
+    'SRC/cla_porpvgrw.c',
+    'SRC/cgbsvxx.c',
+    'SRC/cgbrfsx.c',
+    'SRC/cla_gbrfsx_extended.c',
+    'SRC/cla_gbamv.c',
+    'SRC/cla_gbrcond_c.c',
+    'SRC/cla_gbrcond_x.c',
+    'SRC/cla_gbrpvgrw.c',
+    'SRC/chesvxx.c',
+    'SRC/cherfsx.c',
+    'SRC/cla_herfsx_extended.c',
+    'SRC/cla_heamv.c',
+    'SRC/cla_hercond_c.c',
+    'SRC/cla_hercond_x.c',
+    'SRC/cla_herpvgrw.c',
+    'SRC/cla_lin_berr.c',
+    'SRC/clarscl2.c',
+    'SRC/clascl2.c',
+    'SRC/cla_wwaddw.c',
+    'SRC/dgesvxx.c',
+    'SRC/dgerfsx.c',
+    'SRC/dla_gerfsx_extended.c',
+    'SRC/dla_geamv.c',
+    'SRC/dla_gercond.c',
+    'SRC/dla_gerpvgrw.c',
+    'SRC/dsysvxx.c',
+    'SRC/dsyrfsx.c',
+    'SRC/dla_syrfsx_extended.c',
+    'SRC/dla_syamv.c',
+    'SRC/dla_syrcond.c',
+    'SRC/dla_syrpvgrw.c',
+    'SRC/dposvxx.c',
+    'SRC/dporfsx.c',
+    'SRC/dla_porfsx_extended.c',
+    'SRC/dla_porcond.c',
+    'SRC/dla_porpvgrw.c',
+    'SRC/dgbsvxx.c',
+    'SRC/dgbrfsx.c',
+    'SRC/dla_gbrfsx_extended.c',
+    'SRC/dla_gbamv.c',
+    'SRC/dla_gbrcond.c',
+    'SRC/dla_gbrpvgrw.c',
+    'SRC/dla_lin_berr.c',
+    'SRC/dlarscl2.c',
+    'SRC/dlascl2.c',
+    'SRC/dla_wwaddw.c',
+    'SRC/zgesvxx.c',
+    'SRC/zgerfsx.c',
+    'SRC/zla_gerfsx_extended.c',
+    'SRC/zla_geamv.c',
+    'SRC/zla_gercond_c.c',
+    'SRC/zla_gercond_x.c',
+    'SRC/zla_gerpvgrw.c',
+    'SRC/zsysvxx.c',
+    'SRC/zsyrfsx.c',
+    'SRC/zla_syrfsx_extended.c',
+    'SRC/zla_syamv.c',
+    'SRC/zla_syrcond_c.c',
+    'SRC/zla_syrcond_x.c',
+    'SRC/zla_syrpvgrw.c',
+    'SRC/zposvxx.c',
+    'SRC/zporfsx.c',
+    'SRC/zla_porfsx_extended.c',
+    'SRC/zla_porcond_c.c',
+    'SRC/zla_porcond_x.c',
+    'SRC/zla_porpvgrw.c',
+    'SRC/zgbsvxx.c',
+    'SRC/zgbrfsx.c',
+    'SRC/zla_gbrfsx_extended.c',
+    'SRC/zla_gbamv.c',
+    'SRC/zla_gbrcond_c.c',
+    'SRC/zla_gbrcond_x.c',
+    'SRC/zla_gbrpvgrw.c',
+    'SRC/zhesvxx.c',
+    'SRC/zherfsx.c',
+    'SRC/zla_herfsx_extended.c',
+    'SRC/zla_heamv.c',
+    'SRC/zla_hercond_c.c',
+    'SRC/zla_hercond_x.c',
+    'SRC/zla_herpvgrw.c',
+    'SRC/zla_lin_berr.c',
+    'SRC/zlarscl2.c',
+    'SRC/zlascl2.c',
+    'SRC/zla_wwaddw.c',
+  ]) + [
+    'INSTALL/dlamch.c',
+    'INSTALL/slamch.c',
+
+    'patched_xerbla.c',
+    'patched_err.c',
+
+    'F2CLIBS/libf2c/s_cat.c',
+    'F2CLIBS/libf2c/d_lg10.c',
+    'F2CLIBS/libf2c/d_sign.c',
+    'F2CLIBS/libf2c/i_dnnt.c',
+    'F2CLIBS/libf2c/pow_di.c',
+    'F2CLIBS/libf2c/s_copy.c',
+    'F2CLIBS/libf2c/s_cmp.c',
+    'F2CLIBS/libf2c/i_nint.c',
+    'F2CLIBS/libf2c/f77_aloc.c',
+    'F2CLIBS/libf2c/exit_.c',
+    'F2CLIBS/libf2c/r_cnjg.c',
+    'F2CLIBS/libf2c/c_abs.c',
+    'F2CLIBS/libf2c/r_imag.c',
+    'F2CLIBS/libf2c/c_div.c',
+    'F2CLIBS/libf2c/c_exp.c',
+    'F2CLIBS/libf2c/d_imag.c',
+    'F2CLIBS/libf2c/r_sign.c',
+    'F2CLIBS/libf2c/d_cnjg.c',
+    'F2CLIBS/libf2c/z_abs.c',
+    'F2CLIBS/libf2c/z_div.c',
+    'F2CLIBS/libf2c/z_exp.c',
+    'F2CLIBS/libf2c/z_sqrt.c',
+    'F2CLIBS/libf2c/pow_dd.c',
+    'F2CLIBS/libf2c/pow_ri.c',
+    'F2CLIBS/libf2c/pow_ci.c',
+    'F2CLIBS/libf2c/pow_ii.c',
+    'F2CLIBS/libf2c/pow_zi.c',
+    'F2CLIBS/libf2c/c_sqrt.c',
+    'F2CLIBS/libf2c/r_lg10.c',
+    'F2CLIBS/libf2c/i_len.c',
+    'F2CLIBS/libf2c/cabs.c',
+    'F2CLIBS/libf2c/sig_die.c',
+    'F2CLIBS/libf2c/close.c',
+    'F2CLIBS/libf2c/open.c',
+    'F2CLIBS/libf2c/endfile.c',
+    'F2CLIBS/libf2c/util.c',
+    'F2CLIBS/libf2c/iio.c',
+    'F2CLIBS/libf2c/fmt.c',
+    'F2CLIBS/libf2c/rdfmt.c',
+    'F2CLIBS/libf2c/wrtfmt.c',
+    'F2CLIBS/libf2c/ctype.c',
+    'F2CLIBS/libf2c/wref.c',
+    'F2CLIBS/libf2c/fmtlib.c',
+    'F2CLIBS/libf2c/lread.c',
+    'F2CLIBS/libf2c/rsfe.c',
+    'F2CLIBS/libf2c/sfe.c',
+    'F2CLIBS/libf2c/dolio.c',
+    'F2CLIBS/libf2c/wsfe.c',
+
+    'extra_includes/sysdep1.h',
+  ],
+  hdrs = glob([
+    'INCLUDE/*.h',
+    'F2CLIBS/libf2c/*.h',
+  ]),
+  includes = [
+    'INCLUDE',
+    'F2CLIBS/libf2c',
+    'extra_includes',
+  ],
+  copts = f2c_copts + [
+    '-Wno-sign-compare',
+    '-Wno-cast-qual',
+    '-Wno-cast-align',
+    '-Wno-self-assign',
+
+    # Some files don't #include system headers when they should. sysdep1.h
+    # messes with feature test macros, so it always has to come first.
+    '-include', 'sysdep1.h',
+    '-include', 'stdio.h',
+
+    # Don't mangle the names of all the BLAS symbols, because slicot needs to
+    # call them directly.
+    '-DNO_BLAS_WRAP',
+  ],
+)
diff --git a/debian/f2c.BUILD b/debian/f2c.BUILD
new file mode 100644
index 0000000..00adb76
--- /dev/null
+++ b/debian/f2c.BUILD
@@ -0,0 +1,5 @@
+filegroup(
+  name = 'f2c',
+  visibility = ['//visibility:public'],
+  srcs = ['usr/bin/f2c'],
+)
diff --git a/debian/slycot.BUILD b/debian/slycot.BUILD
index 69ae570..0122f5d 100644
--- a/debian/slycot.BUILD
+++ b/debian/slycot.BUILD
@@ -1,7 +1,7 @@
 # TODO(austin): I bet this is wrong.
 licenses(['restricted'])
 
-load('@//tools/build_rules:fortran.bzl', 'fortran_library')
+load('@//tools/build_rules:fortran.bzl', 'f2c_library')
 
 # We can't create _wrapper.so in the slycot folder, and can't move it.
 # The best way I found to do this is to modify _wrapper.pyf to instead generate
@@ -16,6 +16,21 @@
   restricted_to = ['@//tools:k8'],
 )
 
+# The contents of the file telling f2py how to translate various types. The
+# format doesn't seem to be very well-documented, but this seems to make all the
+# argument types match up.
+_f2py_f2cmap_contents = '''{
+"integer": {
+  "check m>=0": "long",
+  "check n>=0": "long",
+  "check p>=0": "long",
+  "": "long",
+},
+"logical": {
+  "": "long",
+},
+}'''
+
 # Now generate the module wrapper.
 genrule(
   name = '_fortranwrappermodule',
@@ -27,7 +42,24 @@
     'slycot/src/transform.pyf',
   ],
   outs = ['_fortranwrappermodule.c'],
-  cmd = '/usr/bin/python /usr/bin/f2py $(location :slycot/src/_fortranwrapper.pyf) --include-paths external/slycot_repo/slycot/src/ --coutput $(OUTS) && sed "s/Generation date.*/Generation date: redacted/" -i $(OUTS)',
+  cmd = '\n'.join([
+    'cat > .f2py_f2cmap <<END',
+    _f2py_f2cmap_contents,
+    'END',
+    'readlink -f .f2py_f2cmap',
+    ' '.join([
+      '/usr/bin/python',
+      '/usr/bin/f2py',
+      '$(location :slycot/src/_fortranwrapper.pyf)',
+      '--include-paths external/slycot_repo/slycot/src/',
+      '--coutput $(OUTS)',
+    ]),
+    ' '.join([
+      'sed',
+      '"s/Generation date.*/Generation date: redacted/"',
+      '-i $(OUTS)',
+    ]),
+  ]),
   restricted_to = ['@//tools:k8'],
 )
 
@@ -38,7 +70,7 @@
     ':_fortranwrappermodule',
   ],
   deps = [
-    ':fortran_files',
+    ':slicot',
     '@usr_repo//:python2.7_lib',
     '@usr_repo//:python2.7_f2py',
   ],
@@ -53,23 +85,16 @@
   restricted_to = ['@//tools:k8'],
 )
 
-# Now actually build the fortran files.
-fortran_library(
-  name = 'fortran_files',
-  srcs = glob(['slycot/src/*.f']),
-)
-
-# Link it all together.  Make sure it is dynamically linked since I don't know
-# how to build the fortran files in statically to a single .so yet, and I'm not
-# sure bazel does either.
+# Link it all together.  Make sure it is dynamically linked so it can be
+# loaded by the Python interpreter.
 cc_binary(
   name = '_fortranwrapper.so',
   deps = [
-    ':fortran_files',
+    ':slicot',
     ':slycot_c',
   ],
-  linkopts = ['-shared', '-lblas', '-llapack'],
   linkstatic = False,
+  linkshared = True,
   restricted_to = ['@//tools:k8'],
 )
 
@@ -79,7 +104,6 @@
   outs = ['slycot/_wrapper.py'],
   cmd = 'echo "from external.slycot_repo._fortranwrapper import *" > $(OUTS)',
   output_to_bindir = True,
-  restricted_to = ['@//tools:k8'],
 )
 
 # Now present a python library for slycot
@@ -100,3 +124,17 @@
   visibility = ['//visibility:public'],
   restricted_to = ['@//tools:k8'],
 )
+
+f2c_library(
+  name = 'slicot',
+  visibility = ['//visibility:public'],
+  srcs = glob(['slycot/src/*.f']),
+  copts = [
+    # This gets triggered because it doesn't realize xerbla doesn't return.
+    # TODO(Brian): Try and get __attribute__((noreturn)) on xerbla somehow.
+    '-Wno-uninitialized',
+  ],
+  deps = [
+    '@clapack',
+  ],
+)
diff --git a/frc971/control_loops/python/BUILD b/frc971/control_loops/python/BUILD
index 6ece1f4..64201c4 100644
--- a/frc971/control_loops/python/BUILD
+++ b/frc971/control_loops/python/BUILD
@@ -24,7 +24,7 @@
   ],
   deps = [
     '//external:python-glog',
-    '//external:slycot',
+    '@slycot_repo//:slycot',
   ],
   data = [
     '//third_party/cddlib:_cddlib.so',
diff --git a/tools/build_rules/fortran.bzl b/tools/build_rules/fortran.bzl
index e1f2abf..e8b25c1 100644
--- a/tools/build_rules/fortran.bzl
+++ b/tools/build_rules/fortran.bzl
@@ -1,3 +1,5 @@
+load('@//tools/build_rules:select.bzl', 'compiler_select')
+
 def _single_fortran_object_impl(ctx):
   toolchain_cflags = (ctx.fragments.cpp.compiler_options([]) +
       ctx.fragments.cpp.c_options +
@@ -91,3 +93,60 @@
     visibility = visibility,
     restricted_to = ['@//tools:k8'],
   )
+
+f2c_copts = compiler_select({
+  'clang': [
+    '-Wno-incompatible-pointer-types-discards-qualifiers',
+    # Clang appears to be a bit over-eager about this and the comma operator.
+    '-Wno-sometimes-uninitialized',
+  ],
+  'gcc': [
+    # TODO(Brian): Remove this once we can actually disable all the warnings.
+    # https://gcc.gnu.org/bugzilla/show_bug.cgi?id=43245 isn't fixed in our
+    # roborio toolchain yet, so we can't for now.
+    '-Wno-error',
+  ],
+}) + [
+  # f2c appears to know what it's doing without adding extra ().
+  '-Wno-parentheses',
+
+  '-Wno-unused-parameter',
+  '-Wno-missing-field-initializers',
+  '-Wno-unused-variable',
+]
+'''Copts to use when compiling f2c-generated files.
+
+This is useful when building externally-f2ced files.'''
+
+def f2c_library(name, srcs, copts = [], **kwargs):
+  '''Converts Fortran code to C and then compiles it.
+
+  Attrs:
+    srcs: .f source files
+    **kwargs: passed to native.cc_library
+  '''
+  c_srcs = [f[:-2] + '.c' for f in srcs]
+
+  out_dir = c_srcs[0].split('/')[:-1]
+  for c_src in c_srcs:
+    if c_src.split('/')[:-1] != out_dir:
+      # Need to figure out how to make multiple f2c calls or something to
+      # support this, and we haven't had a use case yet.
+      fail('Multiple output directories not supported', 'srcs')
+
+  native.genrule(
+    name = '_%s_f2c' % name,
+    visibility = ['//visibility:private'],
+    srcs = srcs,
+    outs = c_srcs,
+    tools = [
+      '@f2c',
+    ],
+    cmd = '$(location @f2c) -d$(@D)/%s $(SRCS)' % ('/'.join(out_dir),),
+  )
+  native.cc_library(
+    name = name,
+    srcs = c_srcs,
+    copts = f2c_copts + copts,
+    **kwargs
+  )
diff --git a/tools/build_rules/select.bzl b/tools/build_rules/select.bzl
index 151283f..994e775 100644
--- a/tools/build_rules/select.bzl
+++ b/tools/build_rules/select.bzl
@@ -68,6 +68,6 @@
   if 'clang' not in values:
     fail('Need to handle clang!', 'values')
   return select({
-    '//tools:compiler_gcc': values['gcc'],
-    '//tools:compiler_clang': values['clang'],
+    '@//tools:compiler_gcc': values['gcc'],
+    '@//tools:compiler_clang': values['clang'],
   })