3 Portable [software](software.md) is software that is easy to [port](port.md) to (make run on) other [platforms](platform.md). Platforms here mean anything that serves as an environment enabling software to run, i.e. [hardware](hardware.md) platforms ([CPUs](cpu.md), [ISAs](isa.md), game consoles, ...), different [operating systems](os.md) vs [bare metal](bare_metal.md), [fantasy consoles](fantasy_console.md) etc. **Portability is an extremely important attribute of [good software](lrs.md)** as it allows us to write the program once and then run it on many different computers with little effort -- without portability we'd be constantly busy rewriting old programs to run on new computers, portability allows us to free our programs from being tied to specific computers and exist abstractly and independently and so become [future proof](future_proof.md). Examples of highly portable programs include [Anarch](anarch.md), [Simon Tatham's Portable Puzzle Collection](stppc.md), [sbase](sbase.md) (suckless) implementation of Unix tools such as [cat](cat.md) and [cmp](cmp.md) etc. (one wisdom coming from [Unix](unix.md) development actually states that portability should be favored even before performance).
5 **Portability is different from mere [multiplatformness](multiplatform.md)**: multiplatform software simply runs on more than one platform without necessarily being designed with high portability in mind; portable software on the other hand possesses the inherent attribute of being designed so that very little effort is required to make it run on wide range of general platforms. Multiplatformness can be achieved cheaply by using a [bloated](bloat.md) framework such as the Godot engine or [QT](qt.md) framework, however that will not achieve portability; on the contrary it will hurt portability. Portability is achieved through good and careful design, efficient code and avoiding [dependencies](dependency.md) and [bloat](bloat.md).
7 In connection to software the word *portable* also has one other meaning used mainly in context of [Windows](windows.md) programs: it is sometimes used for a binary executable program that can be run without installing (i.e. it can be carried around and ran from a USB drive etc.). However we'll stick to the previously defined meaning.
9 ## How To Make Portable Programs
11 In short: use **[abstraction](abstraction.md)** (only necessarily small amount) to not get tied to any specific platform (separate [frontend](frontend.md) and [backend](backend.md)), **[keep it simple](kiss.md)**, **minimize [dependencies](dependency.md)** (minimize use of [libraries](library.md) and requiring hardware such as [floating point](float.md) unit or a [GPU](gpu.md), have [fallbacks](fallback.md)), write efficient, [simple](kiss.md) code (lower hardware demands will support more platforms), avoid platform-specific features (don't write in [assembly](assembly.md) as that's specific to each CPU, don't directly use [Linux](linux.md) [syscalls](syscall.md) as these are specific to Linux etc.). Also use **[self hosting](self_hosting.md)** (i.e. write your programming language in itself etc.) to make your program **[self contained](self_contained.md)** and minimize dependencies on anything external.
13 Remember, portability is about **making it easy for a programmer to take your program and make it run elsewhere**, so portability is kind of a mindset, it is about constantly putting oneself in the shoes of someone else with a very different computer and asking questions such as "how hard will it be to make this work if this library isn't available?" and "how hard would it be make this work in a desert?"; see also **[bootstrapping](boot.md)**. Even things that are supposed or commonly expected to be present on all platforms, such as a file system or a raster screen, may not be present on some computers -- always remember this.
15 **Do NOT use big [frameworks](framework.md)/engines** -- it is one of the greatest misconceptions among many inexperienced programmers to think portable software is created with big frameworks, such as the [Godot](godot.md) engine or the [QT](qt.md) framework, which can "single click" export/deploy software to different platforms. This will merely achieve creating a badly [bloated](bloat.md) multiplatform program that's completely dependent on the framework itself which drags along hundreds of [dependencies](dependency.md) and wastes computing resources (RAM, CPU, storage, ...) which are all factors directly contradicting portability. If you for example create a snake game in Godot, you won't be able to port it to [embedded](embedded.md) devices or devices without an operating system even though the snake game itself is simple enough to run on such devices -- the game drags along the whole Godot engine which is so huge, complex and hardware demanding that it prevents the simple game from running on simple hardware.
17 **The same goes for languages and [libraries](library.md)**: do NOT use big/bloated languages such as [Python](python.md), [Java](java.md) or [JavaScript](javascript.md) -- your program would immediately become dependent on a hugely complex ecosystem of such language. For portability you should basically **only write in [C](c.md)** (the best established, time tested, relatively simple language supported basically by every platform) or in [C++](cpp.md) at worst, and even with these languages do NOT use the newer standards as these hugely limit the number of compliant compilers that will be able to compile your program. The best is to write in C89 or C99 standard of C. **Minimize the number of libraries you use**, even if it is the standard library of your language -- not all compilers fully adhere to standards and some don't have the standard library even if the should. For [shell](shell.md) [scripts](script.md) only use **[posix shell](posix_shell.md)**, i.e. only use constructs, utilities and flags/features defined by the posix standard, even if you have more "powerful" shell and utilities like [Bash](bash.md) and [GNU](gnu.md) utils.
19 { A great example of how avoiding C features can help your programs be more portable can be seen with [Dusk OS](duskos.md), a very small operating system that will likely be THE system we use if (or rather when) the [collapse](collapse.md) strikes. The system is implementing what they call "Almost C" (https://git.sr.ht/~vdupras/duskos/tree/master/fs/doc/cc/index.txt) -- a language trying to be close to C but avoiding standard compliance to keep simplicity. They want to port C programs but HAVE TO keep it simple so they just can't implement full C and when the judgement day comes, the programs that don't rely on much will simply be the ones that survive. If you just hide behind the excuse "the feature is in the standard so IT HAS TO BE IMEPLEMENTED", your program will end up more unlikely to be ported, an old piece of paper saying your program should run simply won't matter. In Dusk OS you can actually see this porting effort happening right now. ~drummyfish }
21 In your compiled programs **always make your own thin [I/O](io.md) abstraction, [decouple](coupling.md) your I/O libraries, separate [frontend](frontend.md) and [backend](backend.md)**. This is one of the most basic and most important things to do. Why? Well unless you're writing a library, you will need to use I/O (write out messages, draw to screen, create [GUI](gui.md), read keyboard commands, read from files, read from network, ...) so you will NEED to use some library for this (C [stdlib](stdlib.md), [SDL](sdl.md), OS [syscalls](syscall.md), [Xlib](xlib.md), ...) but you absolutely DON'T WANT this library to become a hard [dependency](dependency.md) of your program because if your program depends let's say on SDL, you won't be able to make your program run on platforms that don't have SDL. So the situation is that you HAVE TO use some I/O library but you don't want to become dependent on it.
23 The way to solve this is to create your own small I/O abstraction in your project, i.e. your own functions (such as `drawPixel`, `writeMessage`, `keyPressed`, `playSound`, `readFrile` etc.) for performing I/O, which you will use inside your main program. These functions will be defined in a small file which will basically be your own small I/O library just for your program. The functions you define there will then internally use functions of whatever underlying I/O system you choose to use at the time as your [frontend](frontend.md) (SDL, Xlib, SFML, ...); the important thing is that your main program code won't itself depend on the underlying system, it will only depend on your I/O abstraction, your own functions. Your custom I/O functions will depend on the underlying I/O system but in a way that's very easy to change -- let's say that your `keyPressed` function internally uses SDL's `SDL_GetKeyboardState` to read keyboard state. If you want to switch from using SDL to using a different frontend, you will only have to change the code in one place: in your I/O abstraction code, i.e. inside your `keyPressed` function. E.g. if you switch from SDL to SFML, you will just delete the code inside your `keyPressed` function and put in another code that uses SFML functions to read keyboard (e.g. the `isKeyPressed` attribute), and your whole code will instantly just work on SFML. In fact you can have multiple implementations of your functions and allow switching of different backends freely -- just as it is possible to compile a [C](c.md) program with any C compiler, you can make it possible to compile your program with any I/O frontend. If you used SDL's specific functions in your main code, you would have to completely rewrite your whole codebase if you wanted to switch away from SDL -- for this reason your main code must never directly touch the underlying I/O system, it must only do so through your I/O abstraction. Of course these principles may apply to any other thing that requires use of external libraries, not just I/O. Here is a small picture:
27 _______________ _______________
29 | program | | program | backend
30 |_______________| |_______________|
31 _______________ _______________
33 | librar(y|ies) | | abstraction | API
34 |_______________| |_______________|
37 | librar(y|ies) | frontend
41 This is all demonstrated by [LRS](lrs.md) programs such as [Anarch](anarch.md) or [SAF](saf.md), you can take a look at their code to see how it all works.
43 Anyway the following is a simple [C](c.md) code to demonstrate the abstraction from an I/O system -- it draws a dithered rectangle to the screen and waits until the user pressed the `q` key, then ends. The main code is written independently of any I/O system and can use either C [stdlib](stdlib.md) (*stdio*, draws the rectangle to terminal with ASCII characters) or SDL2 (draws the rectangle to actual window) as its frontend -- of course more frontends (e.g. one using Xlib or SFML) can be added easily, this is left as an exercise :) Also here we have a single piece of code, in practice you'll probably want to put your backend in one file and then have each frontend in its own file; switching frontends will then be achieved by only changing one `#include`.
49 // our I/O abstraction:
50 void ioInit(void); // init our I/O
51 void ioEnd(void); // destroy our I/O
52 void drawPixel(int x, int y, int white);
54 int isKeyPressed(char key);
56 // our main program code:
61 for (int y = 3; y < 20; ++y) // draw dithered rectangle
62 for (int x = 30; x < 60; ++x)
63 drawPixel(x,y,x % 2 == y % 2);
67 while (!isKeyPressed('q')); // wait for pressing 'q'
74 /*---------------------------------------------------
75 implementation of our I/O abstraction for different
78 #ifdef FRONTEND_STDLIB // C stdio terminal frontend
80 char screen[SCREEN_W * SCREEN_H];
85 for (int i = 0; i < SCREEN_W * SCREEN_H; ++i)
89 void ioEnd(void) { } // nothing needed here
91 void drawPixel(int x, int y, int white)
93 screen[y * SCREEN_W + x] = white != 0;
98 for (int i = 0; i < SCREEN_W * SCREEN_H; ++i)
100 if (i % SCREEN_W == 0)
103 putchar(screen[i] ? '#' : '.');
109 int isKeyPressed(char key)
111 return getchar() == key;
113 #elif defined(FRONTEND_SDL) // SDL2 frontend
114 #include <SDL2/SDL.h>
115 unsigned char screen[SCREEN_W * SCREEN_H];
117 SDL_Renderer *renderer;
118 SDL_Texture *texture;
122 for (int i = 0; i < SCREEN_W * SCREEN_H; ++i)
127 window = SDL_CreateWindow("sdl",SDL_WINDOWPOS_UNDEFINED,
128 SDL_WINDOWPOS_UNDEFINED,SCREEN_W,SCREEN_H,SDL_WINDOW_SHOWN);
130 renderer = SDL_CreateRenderer(window,-1,0);
132 texture = SDL_CreateTexture(renderer,SDL_PIXELFORMAT_RGB332,
133 SDL_TEXTUREACCESS_STATIC,SCREEN_W,SCREEN_H);
138 SDL_DestroyTexture(texture);
139 SDL_DestroyRenderer(renderer);
140 SDL_DestroyWindow(window);
143 void drawPixel(int x, int y, int white)
145 screen[y * SCREEN_W + x] = (white != 0) * 255;
150 SDL_UpdateTexture(texture,NULL,screen,SCREEN_W);
152 SDL_RenderClear(renderer);
153 SDL_RenderCopy(renderer,texture,NULL,NULL);
154 SDL_RenderPresent(renderer);
157 int isKeyPressed(char key)
160 const unsigned char *keyboard = SDL_GetKeyboardState(NULL);
162 return keyboard[SDL_SCANCODE_A + (key - 'a')];
167 If you compile this code as
170 gcc -DFRONTEND_STDLIB main.c
173 You'll get the stdlib version. If you compile it as
176 gcc -DFRONTEND_SDL -lSDL2 main.c
179 You'll get the SDL version.
181 A great example of this kind of portable design can be seen e.g. in well written **[compilers](compiler.md)** that separate their architecture into an frontend and backend -- imagine we are writing for example a [C](c.md) compiler. The parser of C syntax can be easily written in a portable way, we simply write functions that work with text, however we find difficulty in asking what [instruction set](isa.md) we will compile to. If we choose one, such as [x86](x86.md), then we will not only write an x86 specific code generator, but also e.g. an x86 specific [optimizer](optimization.md); the part of the compiler that may get so complex that it ends up being bigger than the rest of the code. What if then we also want to support another ISA such as [Arm](arm.md) or [RISC-V](risc_v.md), will we have to rewrite our painstakingly written optimizer for those architectures from scratch? The solution is the same as explained above in regards to I/O: we make an abstraction above the instruction set, here called an [intermediate representation](intermediate_representation.md), usually some [bytecode](bytecode.md), i.e. the compiler first translates C to the abstract bytecode, then we may perform all the complex optimizations on this bytecode, and only then, in the last moment, we relatively simply translate this bytecode to whatever specific instruction set.
183 Programming languages, operating systems and other "platforms" also usually employ [self hosting](self_hosting.md) to greatly increase portability -- you will most often see a serious programming language written in itself and if not, then at very least e.g. its standard library will be written as such. See also [bootstrapping](bootstrapping.md).