The question may be closed as "too broad." But... Since you tagged it with Go, I'll answer it in regards to Go.
GoLang has a compiler for each platform. The Go runtime tools (go build
, etc) are built specific to each platform from the same common source code/repo. You can see all the platforms supported with 1.6:
https://github.com/golang/go/tree/release-branch.go1.6/src/runtime
(Take note of the _linux_amd64.go
type suffixes. More on this in a bit.)
Now, how does one compile code to specific platforms? This is where each language has its own specific directives for each platform (if it is a cross-platform language). In previous Go (1.4 and earlier), the runtime code was written in C with platform-specific C directives, similar to what you've already eluded to:
#if os(Linux)
import Glibc
#else
import Darwin.C
#endif
It was ugly... Well, Go actually had some clean cross-compiling C code with tricks like that above more abstracted and manageable. But my old cross-compiled C code was, well, ugly like that.
But since Go 1.5, Go is now written in Go. What this means is: a Go runtime is used to compile Go's source code into a Go runtime. This means the Go language uses its own platform-specific directives to compile its own platform specific runtime.
How Go specifies a platform
So how does Go specify different platform-specific code? There are two different ways:
- build flags
- file suffixes
There are ups and downs to each. As stated by Dave Cheney himself:
In general, when choosing between a build tag or a file suffix, you
should choose a file suffix when there is an exact match between the
platform or architecture and the file you want to include.
mypkg_linux.go // only builds on linux systems
mypkg_windows_amd64.go // only builds on windows 64bit platforms
Conversely if your file is applicable to more than one platform or
architecture, or you need to exclude a specific platform, a build tag
should be used. eg,
% grep '+build' $HOME/go/src/pkg/os/exec/lp_unix.go
// +build darwin dragonfly freebsd linux netbsd openbsd
builds on all unix like platforms.
% grep '+build' $HOME/go/src/pkg/os/types_notwin.go // +build
!windows
builds on all platforms except Windows.
How to write Go code for cross-platform
If you were looking for a pattern on how to write Go code for cross-platform, here's my pattern. Your mileage may vary.
I personally try to stick to file suffixes myself as it makes it easier to denote what files support what platform by just viewing the list of files.
First, start with an interface for your API. Be sure to follow Go's naming convention if for a single purpose.
shell.go
package cmd
// osshell is a global variable that is implement on a per-OS basis.
var osshell shell
// shell is the interface to an OS-agnostic shell to call commands.
type shell interface {
// Exec executes the command with the system's configured osshell.
Exec(command string) (string, error)
}
// Exec executes the command with the system's configured osshell.
func Exec(command string) (string, error) {
return osshell.Exec(command)
}
Since I am not exporting the interface, I didn't suffix with 'er'. Eh, now that I think about it I should have. :)
Now, implement a Unix version that calls sh
:
shell_unix.go
package cmd
import (
"bytes"
"os/exec"
)
func init() {
osshell = &unixshell{}
}
type unixshell struct {
}
func (s *unixshell) Exec(command string) (string, error) {
cmd := exec.Command("sh", "-c", command)
var buf bytes.Buffer
cmd.Stderr = &buf
cmd.Stdout = &buf
// assign return vars
err := cmd.Run()
out := buf.String()
return out, err
}
When compiling this package for a Unix operating system, it will use this file with the suffix _unix.go
. Then, when the executable actually runs, the init()
is called and wires up the unixshell{}
struct implementation.
This all comes together to allow you to write a platform-agnostic program that consumes this package and to access shell commands like this:
package main
import (
"fmt"
"os"
"github.com/eduncan911/go/pkg/cmd"
)
func main() {
output, err := cmd.Exec("echo 'Hello World!'")
if err != nil {
fmt.Println(err)
os.Exit(1)
}
fmt.Println(output)
}
And now run it:
$ go get github.com/eduncan911/go/pkg/cmd
$ go run main.go
Hello World!
(Disclaimer, I only have a unix version implemented for that cmd. I'll throw up some powershell version the next time I am on Windows to test it.)
GoLang's ease of cross-platform development is exactly why I switched to Go for my primary development language as I have several side projects I'd like to be cross-platform.