Platform-dependent code: what's the right way?

1,390 views
Skip to first unread message

chai....@gmail.com

unread,
Jun 25, 2014, 5:10:10 AM6/25/14
to golan...@googlegroups.com
Hello,

I'm writing a program, that should set `ulimit -r` on Unix, but does not fail on Windows. I'm using this code:

import (
"runtime"
"syscall"
)

if runtime.GOOS != "windows" {
var rLimit syscall.Rlimit
rLimit.Cur = uint64(20480)
rLimit.Max = rLimit.Cur
syscall.Setrlimit(syscall.RLIMIT_NOFILE, &rLimit)
}

It works on Unix, but fails to compile on Windows.

What is the proper way of wrapping Unix-specific code so that a rlimit-enabled program can compile on Windows as well?

Harmen B

unread,
Jun 25, 2014, 12:32:46 PM6/25/14
to chai....@gmail.com, golan...@googlegroups.com
Maybe use build constraints to have a separate implementation for windows?



--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Dave Cheney

unread,
Jun 25, 2014, 12:40:43 PM6/25/14
to golan...@googlegroups.com, chai....@gmail.com

Carlos Castillo

unread,
Jun 25, 2014, 8:04:48 PM6/25/14
to golan...@googlegroups.com, chai....@gmail.com
That's a very nice article. The only thing it doesn't really mention explicitly is that build tags and filename constraints work for any file type handled by the go tool, such as files compiled by cgo (*.c, *.cc, *.m, etc...), and assembly files (.s, see the math package).

For this situation, the simplest solution is to have two files, each which define the same function, but one is named "ulimit_unix.go" and has a build constraint of "!windows", and a ulimit_windows.go, where the function does nothing (and thus doesn't need to call a syscall function.) If it turns out that the distinction is more nuanced than windows/!windows, then you should probably refactor it into "ulimit_stub.go" with build constraints for the non-functional versions, and ulimit_<something>.go files (possibly using build constraints) for the systems you do support. 

The important thing is to make sure that every platform you do support compiles exactly one version of the function/file. You can use the GOOS/GOARCH environment variables with go list to check other systems:
  • GOOS=windows GOARCH=386 go list -f "{{ .GoFiles }}" - To see the go files compiled on windows/386
  • GOOS=darwin GOARCH=amd64 go list -json - To see all the build information when compiling on 64-bit Mac OS X.
Note: I really like the -json argument to go list, sure it spews extra information, but it's often easier and faster to mentally filter out the unnecessary information, than to remember how to, and construct a proper format string.

Take a look at some of the packages in the stdlib if you want examples. The article mentions os/exec, but the other os packages also use conditional compilation, as well as the math package, and many others. If you're brave, you can look at the syscall and runtime packages, as they are extremely large examples and often have multiple files for most/all OS/ARCH combinations, and use code generation to create them.

Mateusz Czapliński

unread,
Jun 26, 2014, 4:29:26 AM6/26/14
to golan...@googlegroups.com, chai....@gmail.com
On Thursday, June 26, 2014 2:04:48 AM UTC+2, Carlos Castillo wrote:
For this situation, the simplest solution is to have two files, each which define the same function, but one is named "ulimit_unix.go" and has a build constraint of "!windows", and a ulimit_windows.go, where the function does nothing (and thus doesn't need to call a syscall function.) If it turns out that the distinction is more nuanced than windows/!windows, then you should probably refactor it into "ulimit_stub.go" with build constraints for the non-functional versions, and ulimit_<something>.go files (possibly using build constraints) for the systems you do support. 

I always seemed to understand that if you have two files, "foobar_windows.go" and "foobar.go", then "foobar.go" would be used as fallback, i.e. only for non-windows platforms in this case. Not 100% sure if that's actually so, as I never really used this pattern, but should be easily testable by setting GOOS=... and running go list as you suggest.

/Mateusz.

Dave Cheney

unread,
Jun 26, 2014, 4:42:36 AM6/26/14
to Mateusz Czapliński, golang-nuts, chai....@gmail.com
> I always seemed to understand that if you have two files,
> "foobar_windows.go" and "foobar.go", then "foobar.go" would be used as
> fallback, i.e. only for non-windows platforms in this case. Not 100% sure if
> that's actually so, as I never really used this pattern, but should be
> easily testable by setting GOOS=... and running go list as you suggest.

unless foobar.go contains the line " // + build !windows" then it will
always be compiled into the package unconditionally.

Chai Tadada

unread,
Jun 26, 2014, 4:58:37 AM6/26/14
to Carlos Castillo, golan...@googlegroups.com
Dave, Carlos

Thanks a lot for your help. I've actually implemented this just as Carlos advises: a no-op Windows function and a meaningful Unix one in different files with different build tags. Just in case: https://github.com/chillum/httpstress-go

26 июня 2014 г. 3:04:47 EET, Carlos Castillo <cook...@gmail.com> пишет:
>That's a very nice article. The only thing it doesn't really mention
>explicitly is that build tags and filename constraints work for any
>file
>type handled by the go tool, such as files compiled by cgo (*.c, *.cc,
>*.m,
>etc...), and assembly files (.s, see the math package).
>
>For this situation, the simplest solution is to have two files, each
>which
>define the same function, but one is named "ulimit_unix.go" and has a
>build
>constraint of "!windows", and a ulimit_windows.go, where the function
>does
>nothing (and thus doesn't need to call a syscall function.) If it turns
>out
>that the distinction is more nuanced than windows/!windows, then you
>should
>probably refactor it into "ulimit_stub.go" with build constraints for
>the
>non-functional versions, and ulimit_<something>.go files (possibly
>using
>build constraints) for the systems you do support.
>
>The important thing is to make sure that every platform you do support
>compiles exactly one version of the function/file. You can use the
>GOOS/GOARCH environment variables with go list to check other systems:
>
> - GOOS=windows GOARCH=386 go list -f "{{ .GoFiles }}" - To see the go
> files compiled on windows/386
> - GOOS=darwin GOARCH=amd64 go list -json - To see all the build
> information when compiling on 64-bit Mac OS X.
>
>Note: I really like the -json argument to go list, sure it spews extra
>information, but it's often easier and faster to mentally filter out
>the
>unnecessary information, than to remember how to, and construct a
>proper
>format string.
>
>Take a look at some of the packages in the stdlib if you want examples.
>The
>article mentions os/exec, but the other os packages also use
>conditional
>compilation, as well as the math package, and many others. If you're
>brave,
>you can look at the syscall and runtime packages, as they are extremely
>
>large examples and often have multiple files for most/all OS/ARCH
>combinations, and use code generation to create them.
>
>On Wednesday, June 25, 2014 9:40:43 AM UTC-7, Dave Cheney wrote:
>>
>>
>>
>http://dave.cheney.net/2013/10/12/how-to-use-conditional-compilation-with-the-go-build-tool
>
>>
><http://www.google.com/url?q=http%3A%2F%2Fdave.cheney.net%2F2013%2F10%2F12%2Fhow-to-use-conditional-compilation-with-the-go-build-tool&sa=D&sntz=1&usg=AFQjCNHLbz47CKpGn8XlHWMJ3zYcAPVVbA>

Mateusz Czapliński

unread,
Jun 26, 2014, 5:16:02 AM6/26/14
to golan...@googlegroups.com

Looks so indeed, I stand corrected. Thanks!
Reply all
Reply to author
Forward
0 new messages