📜 ⬆️ ⬇️

How to write Go code that is easy to port

(Translation of an article with tips on writing a truly cross-platform code in Go)
Go perfectly adapted to work with different platforms. My main development environment is on Windows, but I always work with Linux systems. So I naturally try to avoid things that can create problems.



My attitude to cross-platform development is such that if you consider yourself a serious developer, your code should at least be built on other platforms , because even if not all functions can be used everywhere, some users still want at least some of the functionality your library on other platforms.
')
Recently, I helped to make the Windows version of a very nice backup program , because I wanted to explore alternatives to zpaq , a very good archiver with journaling and focus on compression. During porting, I noted a few things that might be useful to others.

Minimize use of syscall (or sys)


Let's start with the obvious. The syscall package is different for each platform, and, although there are common points, you are almost guaranteed to get into trouble. Of course, there may be very good reasons for using it, but before using it, make sure that there are no other ways to do the same. If you use syscall / sys, immediately get ready for porting you have to create separate files with the necessary assembly tags, for example // + build darwin dragonfly freebsd linux netbsd openbsd solaris, and have a dummy implementation that will fill in the missing functionality when building other platforms.

There is also a github.com/golang/sys package that carries system calls into separate packages for each platform. This, however, does not solve the problem, so the same considerations apply.

Try not to depend on signals.



“No signal” by Πάνος Τσαλιγόπουλο

Signals are a very useful thing. There are lots of servers that use SIGHUP to reload the configuration, SIGUSR2 to restart the service and so on. Please keep in mind that these signals are not available on other platforms, so do not make the main functionality dependent on them. A web server without examples above will work normally on Windows, even though there is a lack of some functionality. Of course, it would be better to have a similar solution for Windows, but as long as the server compiles and works normally, I don’t think that anyone would object.

So if, say, you are writing some kind of working service, you should not make the signal the only way to give a command to your service.

File System Differences


Do not forget that file systems are different.

The last point, by the way, is the most common mistake I have encountered.
func example() (err error) { var f *os.File f, err = os.Create("myfile.txt") if err != nil { return } defer f.Close() err = f.write([]byte{"somedata"}) if err != nil { return } // Do more work... err = os.Remove("myfile.txt") } 

This is not a very obvious mistake, but since this is a simple example, it is easy to see that we are trying to delete the file before we close it.

The problem is that it works well on most systems, but on Windows it will fall. Here is a more correct example to do this:
 func example() (err error) { var f *os.File f, err = os.Create("myfile.txt") if err != nil { return } defer func() { f.Close() err2 := os.Remove("myfile.txt") if err == nil && err2 != nil { err = err2 } }() err = f.write([]byte{"somedata"}) // Do more work } 

As you can see, maintaining the order of closing a file is not a trivial task. If you choose an approach with two defers, remember that os.Remove must be defined before Close, since defer calls are executed in the reverse order.

Here is a more detailed article describing the differences between Windows and Linux file systems.

Use translator if using ANSI



Using console commands to format output can be a good solution. This can help to make the conclusion easier for visual perception, using colors or displaying progress without having to scroll through the text.

If you use ANSI codes, you should always use the translator library. Use it immediately and save yourself from a headache in the aftermath. Here are some that I found in random order.

If you know any other good libraries, please write in the comments.

Avoid dependence on symbolic links.


Symbolic links are a nice thing. It allows you to do cool things, like creating a new version of a file, and just have a link that automatically points to the latest version of the file. But in Windows, symbolic links can only be created if the program has Administrator rights. So, even though this is a nice thing, try to ensure that the functionality of the program does not depend on it.

If possible, avoid CGO and external programs.


You should strive to avoid CGO as soon as possible, since it is quite difficult to set up a working environment for building in Windows. If you use cgo, you refuse not only Windows, but also AppEngine users. If your code is a program, not a library, be ready to upload binary files under Windows.

The same applies to external programs. Try to minimize their use for tasks that can be solved by libraries, and use external programs only for complex tasks.

At compile time or at run time?


Often, when working with some OS, you wonder how to write OS-specific code. This may be a platform-specific code that is needed to satisfy some point. Take for example. next function; it does a lot of things, but one of the requirements is that on non-Windows platforms, the file must be read-only. Let's look at two implementations:
 func example() { filename := "myfile.txt" fi, _ := os.Stat(f) // set file to readonly, except on Windows if runtime.GOOS != "windows" { os.Chmod(f, fi.Mode()&os.FileMode(^uint32(0222))) } } 

There is a check at runtime, whether the program is running in Windows or not.

Compare this to:
 func example() { filename := "myfile.txt" fi, _ := os.Stat(f) setNewFileMode(f, fi) } // example_unix.go //+build !windows // set file to readonly func setNewFileMode(f string, fi os.FileInfo) error { return os.Chmod(f, fi.Mode()&os.FileMode(^uint32(0222))) } // example_windows.go: // we don't set file to readonly on Windows func setNewFileMode(f string, fi os.FileInfo) error { return nil } 

The latest version is how many would say it should be done. I deliberately complicated the example to show that this may not always be the best solution. As for me, there may be cases where the first option is more preferable, especially if there is only one place for such code - and in short, you don’t need to look into several files to see what the code does.

I made a small tablet with the minuses and plus points of each of the approaches:
Pros "at compile time"Pros "during execution"
Minimum or absent overheadYou can keep all the code in one place
Code for each platform in a separate fileSome errors can be detected without cross-compiling.
Can use imports that are not compiled on all platforms.


Cons "at compile time"Cons "during execution"
Code duplication may be needed.There is no easy way to see where the platform-specific code is located.
May result in many small files and one large file with a bunch of scattered functionality.Slight overhead to check
To view the code, you need to open several files.Structures / functions that are not platform independent cannot be used.
You need to use cross-compilation to make sure that the code is going

In general, I would recommend checking at compile time, with different files, except, perhaps, cases when you write a test. But in certain cases you may prefer to check for execution time.

Configure CI for cross-platform tests


As a conclusion, set up cross-platform tests. This is one of the useful things I learned from restic , from which cross- hairs have already been tuned. When Go 1.5 is released, the cross-platform compilation will be even easier, as it will require even less gestures to configure.

At the same time, and for older versions of Go, you can look at gox , which helps automate cross-compilation. If you need even more advanced functionality, pay attention to goxc .

Happy coding!

From the author:

Source: https://habr.com/ru/post/269943/


All Articles