How to programming languages implement cross platform features?

huangapple go评论105阅读模式
英文:

How to programming languages implement cross platform features?

问题

这是一个更一般的问题,自从Swift成为开源项目并且Linux版本缺少一些功能以来,我就一直困扰着我。

有许多跨平台的编程语言。以Go语言为例,它的标准库非常强大,包含了许多有用的结构和基于原始数据类型的函数。但是它还实现了I/O、网络、操作系统和同步等功能。

那么Swift和LLVM编译器基础设施与之相比如何呢?

对于clang来说,我认为它存在跨平台的并发性,我们可以进行交叉编译。但是对于Swift来说,不同平台之间存在差异,Mac版本依赖于"Darwin.C",而Linux版本依赖于"Glibc"。这导致了一些尴尬的代码片段:

#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

Swift/LLVM是如何处理这些特定于平台的特性的?它们总是在编译器的前端处理,依赖于C库吗?还是它们会作为编译器的一部分来实现?

我读到过Go/Rust编译器本身是用Go/Rust编写的。这让我相信编译器在每个操作系统上的实现方式是不同的,以实现并发性、网络等功能,与C库无关。

是这样吗?还是有些编程语言更擅长隐藏它们的依赖关系?

英文:

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?

答案1

得分: 9

这个问题可能会被关闭,因为它被认为是“过于宽泛”。但是...由于你标记了Go,我将根据Go来回答。

Go语言为每个平台都有一个编译器。Go运行时工具(go build等)是从相同的源代码/仓库针对每个平台构建的。你可以在1.6版本中看到支持的所有平台:

https://github.com/golang/go/tree/release-branch.go1.6/src/runtime

(注意_linux_amd64.go类型后缀。稍后会详细介绍。)

那么,如何将代码编译为特定的平台?这就是每种语言针对每个平台(如果它是跨平台语言)都有自己特定指令的地方(如果它是跨平台语言)。在以前的Go版本(1.4及更早版本)中,运行时代码是用C语言编写的,具有特定于平台的C语言指令,类似于你已经提到的:

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

这很丑陋...实际上,Go有一些干净的交叉编译C代码,像上面那样的抽象和可管理的技巧。但是我的旧的交叉编译C代码,嗯,就像那样丑陋。

但是自从Go 1.5以来,Go现在是用Go语言编写的。这意味着:***Go运行时用于将Go的源代码编译成Go运行时。***这意味着Go语言使用自己的平台特定指令来编译自己的平台特定运行时。

Go如何指定平台

那么Go如何指定不同的平台特定代码?有两种不同的方式:

  • 构建标记(build flags)
  • 文件后缀(file suffixes)

每种方式都有优缺点。正如Dave Cheney本人所说

> 一般来说,在构建标记和文件后缀之间选择时,当平台或架构与要包含的文件完全匹配时,应选择文件后缀。

mypkg_linux.go         // 仅在Linux系统上构建
mypkg_windows_amd64.go // 仅在Windows 64位平台上构建

> 相反,如果您的文件适用于多个平台或架构,或者您需要排除特定平台,则应使用构建标记。例如,

% grep '+build' $HOME/go/src/pkg/os/exec/lp_unix.go 
// +build darwin dragonfly freebsd linux netbsd openbsd

> 在所有类Unix平台上构建。

% grep '+build' $HOME/go/src/pkg/os/types_notwin.go  // +build
!windows

> 在除Windows外的所有平台上构建。

如何编写跨平台的Go代码

如果你想要一个关于如何编写跨平台的Go代码的模式,这是我的模式。你的情况可能有所不同。

我个人喜欢使用文件后缀,因为通过查看文件列表,可以更容易地确定哪些文件支持哪些平台。

首先,为你的API定义一个接口。如果只有一个目的,请遵循Go的命名约定。

shell.go

package cmd

// osshell是一个在每个操作系统上实现的全局变量。
var osshell shell

// shell是与操作系统无关的shell调用命令的接口。
type shell interface {
	// Exec使用系统配置的osshell执行命令。
	Exec(command string) (string, error)
}

// Exec使用系统配置的osshell执行命令。
func Exec(command string) (string, error) {
	return osshell.Exec(command)
}

由于我没有导出接口,所以我没有使用er作为后缀。嗯,现在我想起来了,我应该加上。

现在,实现一个调用sh的Unix版本:

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

	// 分配返回变量
	err := cmd.Run()
	out := buf.String()
	return out, err
}

当将此包编译为Unix操作系统时,它将使用带有后缀_unix.go的文件。然后,当可执行文件实际运行时,将调用init()并连接unixshell{}结构实现。

这样,你就可以编写一个与平台无关的程序,使用这个包来访问shell命令,像这样:

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)
}

现在运行它:

$ go get github.com/eduncan911/go/pkg/cmd
$ go run main.go
Hello World!

(免责声明,我只实现了一个Unix版本的cmd。下次我在Windows上测试时,我会提供一些PowerShell版本。)

正是因为Go语言方便的跨平台开发,我才将其作为我的主要开发语言,因为我有几个希望实现跨平台的项目。

英文:

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. How to programming languages implement cross platform features?

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.

huangapple
  • 本文由 发表于 2016年3月28日 21:45:56
  • 转载请务必保留本文链接:https://go.coder-hub.com/36263667.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定