Simplest way to develop on macOS and build on Linux

I like to work on macOS but I write code that will run on Ubuntu. For this reason (as far as I know) I have to compile on Ubuntu to get the build errors that are specific to that operating system e.g. use FoundationNetworking for URLRequests on Linux.

So right now I develop my command line tools in Xcode on macOS and use Multipass to emulate a shell instance of Ubuntu and build on that. This works great but I wonder if there is better way to do this.

Is there a way to build for Ubuntu without emulation or to make Xcode use the Multipass emulator to build the app?

I imagine that I could create a build system maybe use CI to do all this but I'm looking for simple ways that I can set up for any new project in seconds and I want to keep the feedback loop as small as possible.

2 Likes

Seems you're doing it the right way. Xcode doesn't cross-compile for non-Apple platforms, nor does it run within a Linux environment. Most everyone uses a Linux virtual machine or Docker image to compile and run code on Linux using a MacOS host computer. Multipass is one such virtual machine environment. Not really an emulation since the basic instruction set is the same.

Since Multipass is a command line tool under Darwin, conceivably you could build an Xcode run script that runs a series of commands to Multipass, or add an external Makefile target to build the Ubuntu image using Multipass-based commands from within Xcode, but, you just using Xcode as a way to run the Makefile. But, it could work, build and debug on Darwin, build the Ubuntu Makefile target, and, if you want, add another Makefile target to run a test script using the Ubuntu image

So I can improve my system further by utilising Make and I can automate the process with Xcode build phases.

Makes sense, thanks for the guidance! I'll document my findings in a blog post in the future.

You could also build in a docker container, no?

Another Xcode trick you want to incorporate into your workflow are Xcode behaviors. Look under Xcode>Behaviors menu item. I've defined two behaviors to open a Terminal and a Finder window in my project's directory structure when I want to get to the file system or a terminal based on the project root from within Xcode. You could add a behavior that runs a Multipass shell and you should be able to inherit the Xcode environment variables so you can get to the DerivedData, Build folders, etc., where your Ubuntu images may be located so you run a debugger under Ubuntu, etc..

1 Like

Multipass is a form of docker container. It's actually a light weight system on top of a virtual machine hypervisor that gives you access to your environment using Darwin command line tools. It actually looks kind of neat! And, because it can be used as a Darwin command line tool, you could potentially do a lot of things within Xcode run scripts and external Makefile targets to keep within an Xcode build environment. If you using Xcode as your primary work environment, this could really be interesting to support building your code within a Linux environment.

1 Like

It should be possible to cross-compile from macOS to linux, just as others have from macOS to Android. You will need a C cross-compilation toolchain for linux and the Swift stdlib and core libraries for linux, which you can just get from the official Swift toolchain for linux.

Once you have those, you simply pass the right flags with those cross-compilation paths and some flags to the Swift compiler, as every Swift compiler is a cross-compiler just like clang, and you should be able to build for linux on macOS.

Take a look at the flags used to cross-compile for Android:

  1. -tools-directory should point to the cross-compilation toolchain, ie the C compiler and linker.
  2. -sdk to the C headers and libraries for linux, ie the directory containing /usr/include and /usr/lib/ with those files.
  3. You can specify the path to the Swift stdlib and core libraries separately with the -resource-dir flag not used in that documentation, ie /path/to/linux-swiftc/usr/lib/swift.

It may take some time to get this all setup, but you should have the quick iteration you're looking for after that.

2 Likes

The OP wants to do this from within Xcode. He wants to write and debug the code using Xcode, then compile and try out on Ubuntu. Canonical has this Multipass product that provides Darwin command line tools to interact with an Ubuntu instance running on a hypervisor. Given that the OP sets up his Ubuntu instance with right native Swift tools (which I assume he has since he has been developing for awhile), he should be able to tell the instance to compile this, link that, put the executable here, using Darwin command line invocations. Well, if you can do that from a Terminal, iTerm, xterm, what have you, then you can build Xcode run scripts, external Makefile targets in Xcode to build within Xcode.

Now, I understand the OP could use your cross-compiling tools, and also put something together using a cross-compiler within Xcode, but, he also has an Ubuntu environment that can be manipulated from the command line to run his code for testing, which can also potentially be incorporated into his Xcode workflow..

He seems to have a VM-bsed environment already to go, was just looking for potential of integrating his Darwin/Ubuntu workflows.

If your priority is speed in setting up new projects, I would go the cross-compiling route. SwiftPM supports “destination” JSON files which can contain all of your important compiler flags and sysroot paths, so it’s very simple to build a new project using those settings.

Of course, since you’re cross-compiling, you can’t run any executables you build. That means you’ll still need a more complex setup for running tests, so that’s something to consider.

2 Likes

So if I understand correctly I can include the necessary flags and argument for cross-compiling in a destination.json file? Can I have multiple destination.json files one for macOS and one for Linux?

It seems that if I want to test and debug, I'll have to use Multipass but for building and seeing the compiler errors I don't need Multipass, I can just change the compiler flags.

Of course it seems simpler to just use Multipass for everything from building to debugging to make the development stack simpler.

So if I understand correctly I can include the necessary flags and argument for cross-compiling in a destination.json file?

If you're using the Swift package manager, yes, though I've never used that json cross-compilation config myself. I thought you wanted to use Xcode, so I just gave you the cross-compilation flags that would be needed either way.

Can I have multiple destination.json files one for macOS and one for Linux?

You pass it in with a --destination flag, so that should work.

2 Likes