one-file (1f)
$ ./one-file-1f all
one-file (1f) Informer v1.0.1
Description
===========
one-file (1f) is any single file binary program with its source appended to that binary.
one-file (1f) is a physical manifestation of the KIS(S) principle: Keep It Simple (Stupid).
one-file (1f) is an example of how to cut down over-complicated development processes and packaging schemes which are far too common nowadays.
one-file (1f) binaries have appended to itself (copied onto the end), either a single TEXT source file or a single ZIP file containing multiple source files, possibly also a 'Makefile'.
one-file (1f) source can be extracted simple by piping the output of the applications '--source' switch to a named file (test-1f --src > test-1f.cpp).
one-file (1f) source contain the required commands to succesfully output the source, compile and then reattach the source, in most cases without the use of a 'Makefile' or './configure' script.
History
=======
one-file (1f) was born from the now defunct SpASM32 32-bit Windows Assembler project of the late 1990's. It was a self compiling editor and 32-bit Windows assembler with its own ASCII source file attached to the binary.
Thats right you could send a SpASM32 generated binary to SpASM32 and it would read the source file (not the binary), then allow you to compile the binary, automatically attaching the source.
It was such a simple idea that worked so simply that it demanded implimenting where ever possible. Sadly the rest of the programming community did not seem to think so, and SpASM32 has been lost to history. However the principle lived on.
I first used it to attach and extract the source of a DOS NASM (Netwide Assembler) compiled VESA mode interigator and VESA mode setter for a newly aquired 64M Voodoo 5000 (one of 2 brand new still in the box) in 2005-ish.
Note that idea of attaching source and binary is not a new one. In the 1980's it was common for Spectrum BASIC files to contain inline machine code in REM or PRINT statements. During the 1970's and early 1980's, some CP/M (Z80 forerunner to DOS) programs contain source code mildly compressed (Squashed), some of which were attached in a similar way.
Since 2010, I have noticed that a lot of Linux/SunOS projects have failed to compile, or failed to be updated, mostly because of './configure' or 'Makefile' failures. Often it is the AutoTools (automake, autoconf, etc) processes, which generates those to files, that fails.
I also noticed the increasing convoluted complexity of OS filesystem layouts, their organisation, and default paths when building libraries and applications.
Specifically I wanted a minimalist Debian based distro to boot a Raspberry Pi with Atari ST m68k-cross-tools installed and usable, optionaly without X Windows system install, but rather SDL framebuffer with GLES for graphics, sound, etc.
Because of some of the missing parts for Atari MiNT, updated Ext2, Ext3 & Ext4 filesystems and tools, as well as some other modern filesystem drivers and tools, any cross-tools development requires Kernal 2.36 sources or earlier. Kernal 4.xx sources for Debian now DONT use any type of changable init system (requiring systemd instead).
With these development considerations, and the need to develop and test cross-platform, especially aging hardware, and the need for a smaller foot print, something simpler was needed. An idea, dotOS, combining simplified and cleaned up filesystem layout, NixOS version control package installation ideas, Package.fs management principles, all rooted in /.OS/, hidden on most systems by default.
one-file (1f) programs fit well with dotOS and the need for AtariST statically linked binaries.
Dependancies
============
If you are using Linux or Unix, or OS with a full set of command line tools, then a one-file (1f) depends on what you already have. However if command line tools are an option then the following is a minimum of what you need.
You can use 'one-file (1f) compile' on any OS, for posix or GNU compatible systems you need:
'g++' compiler, usually GNU C++
'ls' where 'ls -l ' outputs size in bytes after the 5th space
'cut' used to cut the above 'ls' output between the 5th & 6th space
one-file (1f) scripts use the following command line tools:
'/bin/sh' shell, or a link to 'bash', etc
'[' or a link to 'test', for shell 'if-then' statements
'echo' optional, this is usually built into the shell
one-file (1f) scripts may use one or more the following:
'which' tells you where the first binary is if you dont provide the path
'cat' concatenates text out to the console, on Windows this is 'type'
'find' finds files in given path (sudo find / -name "*sdl*")
'grep' finds text in files (grep -n -R "SDL_sound.h" /usr/include/*)
'make' if you use a 'Makefile' or './configure' script
(maybe your one-file (1f) program has more than one source file)
On Windows, you can add 'Posix Commandline Tools', but better yet, install 'Cygwin' or 'MingGW'. If you install both of them, you will have cross-platform development for about 99% of platform architectures.
Remember that one-file (1f) is a principle or idea, not a dictatorship, so if you have a simpler set of commands, or use a non-posix system, dont hesitate to change the one-file (1f) tools and source compiler command to suit your needs.
Just remember to Keep It Simple (Stupid), do the changes in a way that allows most users of your system type to also use one-file (1f) compile development.
Find *-1f
=========
We try and keep this simple. 'prog-1f' is defeinitely a one-file (1f) program called 'prog', but does it have source attached. We can check with:
prog-1f --source (this will tell you if there is no source)
prog-1f --src-size (this will output '12345' if there is no source)
prog-1f --version (this should have '(one-file)' or '(1f)' in the output)
prog-1f --1f (confirmation beyond doubt, exits with 31 or 32)
Any one-file (1f) option will return 31 (0x1f) if successful, and 32 (0x20) if an error occured, making it easier than ever to detect a one-file (1f) program.
Lets see what we can find (may output nothing):
'ls *-1f' (programs or folders in the current directory)
one-file-1f
'ls *-1f.cpp' (source in the current directory)
_skel-1f.cpp
'find ~/ -name "*-1f"' (files or folders in the current user directory)
/home/pi/iSource/onefile-1f
/home/pi/iSource/onefile-1f/sdlPlay-1f
/home/pi/iSource/onefile-1f/sdlPlay-1f/sdlPlay-1f
/home/pi/iSource/onefile-1f/sdlRead-1f
/home/pi/iSource/onefile-1f/sdlRead-1f/sdlRead-1f
/home/pi/iSource/onefile-1f/char-1f
/home/pi/iSource/onefile-1f/char-1f/char-1f
/home/pi/iSource/onefile-1f/sdlSlide-1f
/home/pi/iSource/onefile-1f/sdlSlide-1f/sdlSlide-1f
/home/pi/iSource/onefile-1f/one-file-1f
/home/pi/iSource/onefile-1f/one-file-1f/one-file-1f
The decision has been made to include a '-1f' switch argument. This will display text about the switches '--source', '--src', '--src-type', and '--src-size'. How to verify source (NAME=prog; if [ `ls -l $NAME-1f.cpp|cut -d \ -f 5` = `./$NAME-1f --src-size` ]; then echo Verified; else echo Failed; fi), and the basic principles of one-file (1f). Where to find more information about it (like this program), and where to find more programs+sources.
Source
======
There is a source file embeded in this program, so all the following applies to this program as well.
Any one-file (1f) program fully compiled will have a source file appended to it. The source contains code to output that source file to the console, which can be save to a file. Only TEXT files (usually .cpp) and ZIP (.zip) sources are attached, with the '--src-type' switch giving the required file extention.
TEXT & ZIP are available on 99.9% of platform architectures. ZIP is only used if the one-file (1f) program requires more than one source file, or has extra data files. The ZIP may also contain a 'Makefile', possibly even a './configure' script, but these are NOT a requirement.
The primary one-file (1f) source file contains a comment block at the top '/* one-file (1f) compile' that has all the commands used to compile the source and attach it to the binary. It is multi-line shell code, so any line (without the last \) can be pasted into the command prompt to execute that part of the build process.
'source-1f' gives you info about a one-file (1f) source file, including extracting this block to the console. 'make-1f' uses this block to compile, check file sizes, re-compile with sizes, then append source. 'make-1f' is just meant to be a mid-way point, before the need to use a full blown 'Makefile'.
one-file (1f) sources do not have to do things the traditional way. Its intended to keep things simple (build wise) and easy to update or change (eg different compiler).
If you have a large set of fuctions outside your 'main' block, once they have been fully debugged you can place them in another file and use the 'include' to hack them back in place in your main source file. This removes the need for a 'Makefile' or compiling multiple objects.
one-file (1f) source should still be clean and well presented, with 'include's at the top, 'struct's and 'typedef's after that, then 'extern'als if you have a truely multi-file source build. Place 'const'ants in logical places after the 'include's, before the end of 'extern'als.
Make
====
'make-1f' can output a really clean, compact, and simple 'Makefile', when you get to the point that it makes things easier. Otherwise try your best NOT to use 'make', even if you end up creating and using your own 'make.sh' script, make sure you can easily include it into a comment block (eg. /* one-file (1f) 'make.sh').
If you find a 'Makefile' easier, you can include that in into a comment block too (eg. /* one-file (1f) 'Makefile'). Rememeber that the idea of using a one-file (1f) system is to have only one source file that can be attached to your binary after it is compiled.
But this simplicity is only useful if you can get your head around your code, so don't be afraid of making that next step and splitting things up. Try doing it without a 'Makefile' first, thus avoiding seperate object files and linking them.
It is then only a small step to getting the files to function with the use of a 'Makefile', usually just by adding a .h/.hpp header file for each new source file, containing the required 'extern's etc, and adding 'include's in your main source.
The 'Makefile' generated by 'make-1f' is a straight copy of the one used by Parallel Realities in their first C++ cross-platform (Linux/Unix) game, Project: Starfighter. After 10 Years it is still a pleasure to use, easy to adjust, and compatible with current systems.
Example
=======
Skeleton Code
=============
/*
_skel-1f
one-file (1f) skeleton code base
Copyright (C) 2016 MiNTed Games
All Rights Reserved
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
/* one-file (-1f) compile
g++ -Wall -O3 -DVERSION=\"1.0.1\" -o _skel-1f _skel-1f.cpp ; \
g++ -Wall -O3 -DVERSION=\"1.0.1\" -DPRG_SIZE=`ls -l _skel-1f|cut -d \ -f 5` -DSRC_SIZE=`ls -l _skel-1f.cpp|cut -d \ -f 5` -o _skel-1f _skel-1f.cpp ; \
cat _skel-1f.cpp >> _skel-1f
*/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
/*
one-file source appendage
*/
//#define SRC_TYPE ".zip" // if its a ZIP appendage
#define SRC_TYPE ".cpp" // if its a TEXT appendage
#ifndef PRG_SIZE
//#define PRG_SIZE 7370 // last checked binary size was ...
#define PRG_SIZE 12345 // .. this should be close to actual
#endif
#ifndef SRC_SIZE // size of 1 text file or 1 zip file
//#define SRC_SIZE 5893 // last checked source size was ...
#define SRC_SIZE 12345 // .. affects binary size if too small
#endif
const long offset_1f = PRG_SIZE; // this has never been accurate (EXT4)
const long sizeOf_1f = SRC_SIZE; // we use this (-sizeOf_1f) as offset
void version_1f()
{
printf("_skel (one-file) v%s\n", VERSION);
}
void output_1f(char *file_1f)
{
FILE *source_1f;
char out_1f[1];
if (offset_1f == 12345 || sizeOf_1f == 12345)
{
version_1f();
printf("error: no source file or zip\n");
exit(32);
}else{
source_1f = fopen(file_1f, "rb");
if (source_1f == NULL)
{
version_1f();
printf("error: cant open '%s'\n", file_1f);
exit(32);
}
// fseek(source_1f, offset_1f + 223, SEEK_SET); // off by ?
fseek(source_1f, -sizeOf_1f, SEEK_END);
while(true)
{
if (!fread(out_1f, 1, 1, source_1f))
{
fclose(source_1f);
break;
}else
putchar((int) out_1f[0]);
}
}
}
int main(int argc, char *argv[])
{
/* place used variables here */
int j, argDone;
/* this is the begining of commandline argument processing */
if ( (argc == 1) || ( (argc > 1) && (strcmp("--help", argv[1]) == 0) ) )
{
version_1f();
printf("Copyright 2016 MiNTed Games\n\n");
printf("one-file (1f) skeleton code base.\n\n");
printf("usage: _skel-1f [_option_|<char>|<hex>|<unicode>|<ascii>]\n");
if (argc > 1)
{
printf("_option_:");
printf("\t-1f one-file (1f) source information\n");
printf("\t--help This help information\n");
printf("\t-v|--version Version information\n");
}
printf("\n");
exit(0);
}
if ( (argc > 1 ) && ( (strcmp("-1f", argv[1]) == 0) || (strcmp("--1f", argv[1]) == 0) ) )
{
version_1f();
printf("Copyright 2016 MiNTed Games\n");
printf("one-file (1f) source information.\n\n");
if (offset_1f == 12345 || sizeOf_1f == 12345)
{
printf("no source file or zip\n");
exit(32);
}else{
printf("usage: _skel-1f _option_\n");
printf("_option_:\n");
printf("\t--src|--source output attached one-file (1f) source file\n");
printf("\t--src-size size of appended source (in bytes) (%d)\n", sizeOf_1f);
printf("\t--src-type type of source appended (TXT/ZIP) (%s)\n", SRC_TYPE);
printf("\n");
printf("To save one-file (1f) source to file use one of the following:\n");
printf("\t_skel-1f --src > _skel-1f.cpp\n");
printf("\t_skel-1f --src > _skel-1f.zip\n");
printf("\t_skel-1f --src > _skel-1f`_skel-1f --src-type`\n");
printf("To verify one-file (1f) source file size:\n");
printf("\t_skel-1f --src-size (match this to the size in a directory listing)\n");
printf("or use:\n");
printf("NAME=_skel; if [ `ls -l $NAME-1f.cpp|cut -d \\ -f 5` = `./$NAME-1f --src-size` ]; then echo Verified; else echo Failed; fi\n");
printf("one-file (1f) options exit with status:\n");
printf("\t31 no error (0x1f)\n");
printf("\t32 an error occured (0x20)\n");
exit(31);
}
}
if ( (argc > 1) && ( (strcmp("--source", argv[1]) == 0) || (strcmp("--src", argv[1]) == 0) ) )
{
output_1f(argv[0]);
exit(31);
}
if ( (argc > 1) && (strcmp("--src-type", argv[1]) == 0) )
{
printf(SRC_TYPE);
exit(31);
}
if ( (argc > 1) && (strcmp("--src-size", argv[1]) == 0) )
{
printf("%d", SRC_SIZE);
exit(31);
}
if ( (argc > 1) && ( (strcmp("--version", argv[1]) == 0) || (strcmp("-v", argv[1]) == 0) ) )
{
version_1f();
exit(0);
}
/* the rest of your argv[] processing goes here */
/* sdlPlay_init(); j = 0;
for (int i = 1; i < argc; i++)
{
argDone = 0;
if (strcmp(argv[i], "--loop") == 0)
{
i++;
if (i == argc)
{ printf("error: --loop needs a number\n"); exit(1); }
sdlPlay.loop = atoi(argv[i]);
argDone = 1;
}
if (argDone == 0)
{
strcpy(sdlPlay.playList[j], argv[i]);
j++;
}
}
sdlPlay.playlist = j;
*/
/* place your while(true) loop here (if you have one) */
/* section = 0;
while (true)
{
// non-blocking (shows high CPU usage)
// should be event driven, but this is faster
switch(section)
{
case 0:
section = getAudioFile();
break;
case 1:
section = isPlaying();
break;
case 2:
section = playLoop();
break;
case 5:
section = playMusic();
break;
case 6:
section = playMod();
break;
case 7:
section = playSound();
break;
case 9:
sdlPlay.next++;
section = 0;
break;
}
}
*/
/* this is the end, .. my friend, the end */
return(0);
}
<< snip >>
Some commented parts above are present only if you want to get slightly more complex things going straight away. They are optional, and should probably be deleted from your final source immediately if you dont intend to use them.
They are examples of the actual code that is used in 'sdlPlay-1f' to process arguments, and run the main (non-blocking) loop.
You can save the above skeleton directly to a file using:
one-file-1f --src > _skel-1f.cpp
or if you already know what your program is going to be called:
NAME=_name_here_ one-file-1f --source > $NAME-1f.cpp
NOTE: if you use the second style, then 'one-file-1f --src-size' will only match if NAME is 5 letters long (FYI: there are 21 references changed).
Thats it, enjoy programming, one-file (1f) style ...
Future
======
It depends on how useful one-file (1f) programs are, weather or not there will be a need for 'lib-1f' as it were. It does mean releasing your source code into the wilds of the net, and not being able to control its evolution outside what you yourself provide.
But it does enable the possibility of providing OS level extensions that makes using an OS a bit more fun, flexible and friendly, all at the same time.
The current future of one-file (1f) is to provide a bunch of console programs that do things you currently need a full KDE, GNOME or other, now 'over the top' windowing system to do. If it works, 'dotOS' will be able to boot on multiple architectures from the same device, and moving to a new/different architechture is a breeze once you have a compiler for it.
In addition to those console programs are other console 'mini-utilities', like character code output, command line calculator, a font viewer that can also output fonts to PNG, a 'image-font' creator, useful for many (SDL/SDL2) games and graphics libraries (GuiChan) or programs (Grafx2).
Basically lots of console programs that can be easily controled from somewhere else, like a non-(X)Windows based desktop. If a one-file (1f) source .cpp file is correctly assembled, it makes adding that functionality to a greater project that much easier, and changing it is even easier again.
The future is so bright, I gotta wear 'shades-1f' ...
|
©2016 - Paul Wratt - one-file (1f)
|