如何通过编程语言实现跨平台功能? [关闭]

This is more of a general question that bugs me ever since Swift became open source and the linux port lacked feeatures.

There are many cross platform programming languages. Lets take Go for example. The (awesome) Go standard library has many packages. Some are helpful structs and functions based on primitive data types. But others implement I/O, networking, os, and sync.

How does this compare to Swift and the LLVM compiler infastructure?

For clang I think there exists e.g. cross platform concurrency, and we can cross compile. But for Swift there are platform differences where the Mac version depends on "Darwin.C" and the Linux one on "Glibc". This results in some awkward code snippets:

#if os(Linux)
import Glibc
#else
import Darwin.C
#endif

...

#if os(Linux)
            let j = Int(random() % (count - i)))) + i
#else
            let j = Int(arc4random_uniform(UInt32(count - i))) + i
#endif

Does Swift/LLVM handle these platform specific features always on the front end of the compiler such that they depend on c libraries? Or do/will they implement them as part of the compiler?

I read that the Go/Rust compiler is itself written in Go/Rust. This leads me to believe that the compiler is implemented differently for each OS to feature concurrenty, networking - is independent of c libraries.

Is that so? Or are some programming language just better at hiding their dependencies?

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.