Index: ChangeLog.md ================================================================== --- ChangeLog.md +++ ChangeLog.md @@ -1,6 +1,55 @@ # PiDP-8/I Changes + + +## Version 2020.04.xx + +* Raspberry Pi 4 support. + +* [Configurable screen manager][rmsm], allowing either tmux or + "none" as an alternative to GNU screen. Initial work on this + feature done by Ryan Finnie. + +* Added udev rules to allow mounting media from disks on USB + floppy drives. (Thanks to Ryan Finnie for this feature.) + +* Updated SIMH. The primary user-visible changes from the perspective + of a PiDP-8/I user are: + + * Better IPS rate calibration for the PDP-8 simulator when + throttling. The simulator now does a precalibration pass to + achieve a good initial guess at the host's IPS rate rather + than drop sharply into a calibrated level some seconds past + the simulator startup time, as in the prior release. + + * Improvements to SCP, the command shell / script interpreter: + + * Add the `RENAME/MOVE/MV`, `MKDIR`, and `RMDIR` commands. + + * The `SAVE` command can now overwrite existing files. + + * Several improvements to power-of-2 unit handling in command + output and parameter input. + + * Regular expressions in SIMH `EXPECT` commands now use + PCRE syntax if available instead of the POSIX regex + library. + + * Many improvements to tape device handling. (Nothing PDP-8 + specific, just generic SIMH improvements.) + + * Portability and documentation improvements. + +* The build system now detects the availability of Python 3 and + prefers it if available. + +* Updated Autosetup to v0.6.9+. Allows it to work under Tcl 8.7. + +* Portability and documentation improvements. + +[rmsm]: https://tangentsoft.com/pidp8i/doc/trunk/README.md#rc-screen-manager + ## Version 2019.04.25 — The "OS/8 V3F and os8-run" release * The banner feature in this release is that Bill Cattey transformed Index: HACKERS.md ================================================================== --- HACKERS.md +++ HACKERS.md @@ -12,11 +12,11 @@ The PiDP-8/I software project is hosted using the [Fossil][fossil] [distributed version control system][dvcs], which provides most of the features of GitHub without [the complexities of Git][fvg]. Those new to Fossil should at least read its [Quick Start Guide][fqsg]. -If you want to go deeper, the [the Schimpf book][fbook] is somewhat +If you want to go deeper, [the Schimpf book][fbook] is somewhat outdated, but it is still the best single coherent tutorial on Fossil. [The official Fossil docs][fdoc] are much more up to date, but they take a piecemeal approach to topics, rather than the linear tutorial approach of a book, so it is not my first recommendation for learning Fossil. Those docs are better for polishing your skills and for reference after @@ -68,12 +68,12 @@ $ cd ~/src/pidp8i/trunk $ fossil open ~/museum/pidp8i.fossil The `clone` command gets you a file called `pidp8i.fossil` containing the full history of the PiDP-8/I software project from the upstream -2015.12.15 release onward. You can call that clone file anything you -like and put it in any directory you like. Even the `.fossil` extension +2015.12.15 release onward. You can call that clone file anything you +like and put it in any directory you like. Even the `.fossil` extension is largely a convention, not a requirement. You only need to clone the repository once per machine. Thereafter, you will just be working with that same clone. @@ -85,27 +85,27 @@ If you have a developer account on the `tangentsoft.com/pidp8i` Fossil instance, just add your username to the URL like so: $ fossil clone https://USERNAME@tangentsoft.com/pidp8i pidp8i.fossil -If you've already cloned anonymously, simply tell Fossil about the new +If you’ve already cloned anonymously, simply tell Fossil about the new sync URL instead: $ cd ~/src/pidp8i/trunk $ fossil sync https://USERNAME@tangentsoft.com/pidp8i Either way, Fossil will ask you for the password for `USERNAME` on the -remote Fossil instance, and it will offer to remember it for you. If +remote Fossil instance, and it will offer to remember it for you. If you let it remember the password, operation from then on is scarcely different from working with an anonymous clone, except that on checkin, your changes will be sync’d back to the repository on tangentsoft.com if -you're online at the time, and you'll get credit under your developer +you’re online at the time, and you’ll get credit under your developer account name for the checkin. -If you're working offline, Fossil will still do the checkin locally, and +If you’re working offline, Fossil will still do the checkin locally, and it will sync up with the central repository after you get back online. -It is best to work on a branch when unable to use Fossil's autosync +It is best to work on a branch when unable to use Fossil’s autosync feature, as you are less likely to have a sync conflict when attempting to send a new branch to the central server than in attempting to merge your changes to the tip of trunk into the current upstream trunk, which may well have changed since you went offline. @@ -112,14 +112,14 @@ You can purposely work offline by disabling autosync mode: $ fossil set autosync 0 Until you re-enable it (`autosync 1`) Fossil will stop trying to sync -your local changes back to the central repo. In this mode, Fossil works -more like Git's default mode, buying you many of the same problems that -go along with that working style. I recommend disabling autosync mode -only when you are truly going to be offline and don't want Fossil +your local changes back to the central repo. In this mode, Fossil works +more like Git’s default mode, buying you many of the same problems that +go along with that working style. I recommend disabling autosync mode +only when you are truly going to be offline and don’t want Fossil attempting to sync when you know it will fail. Getting Developer Access @@ -137,15 +137,15 @@ The directory structure shown in the commands above is more complicated than strictly necessary, but it has a number of nice properties. First, it collects other software projects under a common top-level -directory, which I'm calling `~/src`, but you are free to use any scheme +directory, which I’m calling `~/src`, but you are free to use any scheme you like. Second, the project directory (`~/src/pidp8i`) stores multiple separate -checkouts, one for each version I'm actively working with at the moment. +checkouts, one for each version I’m actively working with at the moment. So, to add a few other checkouts, you could say: $ cd ~/src/pidp8i $ mkdir -p release # another branch $ mkdir -p v20151215 # a tag this time, not a branch @@ -172,12 +172,12 @@ When you say `fossil update` in a check-out directory, you get the “tip” state of that version’s branch. This means that if you created your “`release`” check-out while version 2017.01.23 was current and you say “`fossil update`” today, you’ll get the release version 2019.04.25 or -later. But, since the `v20151215` tag was made on trunk, saying “`fossil -update`” in that check-out directory will fast-forward you to the tip of +later. But, since the `v20151215` tag was made on trunk, saying +“`fossil update`” in that check-out directory will fast-forward you to the tip of trunk; you won’t remain pinned to that old version. The PiDP-8/I project uses tags for [each released version][tags], and it has [many working branches][brlist]. You can use any of those names in “`fossil open`” and “`fossil update`” commands, and you can also use any @@ -193,12 +193,12 @@ Creating Branches ---- Creating a branch in Fossil is scary-simple, to the point that those -coming from other version control systems may ask, "Is that really all -there is to it?" Yes, really, this is it: +coming from other version control systems may ask, “Is that really all +there is to it?” Yes, really, this is it: $ fossil ci --branch new-branch-name That is to say, you make your changes as you normally would; then when you go to check them in, you give the `--branch` option to the @@ -205,58 +205,57 @@ `ci/checkin` command to put the changes on a new branch, rather than add them to the same branch the changes were made against. While developers with login rights to the PiDP-8/I Fossil instance are allowed to check in on the trunk at any time, we recommend using -branches whenever you're working on something experimental, or where you -can't make the necessary changes in a single coherent checkin. +branches whenever you’re working on something experimental, or where you +can’t make the necessary changes in a single coherent checkin. -One of this project's core principles is that `trunk` should always -build without error, and it should always function correctly. That's an +One of this project’s core principles is that `trunk` should always +build without error, and it should always function correctly. That’s an ideal we have not always achieved, but we do always *try* to achieve it. Contrast branches, which PiDP-8/I developers may use to isolate work -until it is ready to merge into the trunk. It is okay to check work in -on a branch that doesn't work, or doesn't even *build*, so long as the +until it is ready to merge into the trunk. It is okay to check work in +on a branch that doesn’t work, or doesn’t even *build*, so long as the goal is to get it to a point that it does build and work properly before merging it into trunk. Here again we have a difference with Git: because Fossil normally syncs your work back to the central repository, this means we get to see the -branches you are still working on. This is a *good thing*. Do not fear -committing broken or otherwise bad code to a branch. [You are not your -code.][daff] We are software developers, too: we understand that +branches you are still working on. This is a *good thing*. Do not fear +committing broken or otherwise bad code to a branch. [You are not your +code.][daff] We are software developers, too: we understand that software development is an iterative process, and that not all ideas spring forth perfect and production-ready from the fingers of its -developers. These public branches let your collaborators see what -you're up to, and maybe lend advice or a hand in the work; mostly, -public branches let your collaborators see what you're up to, so they're -not surprised when the change finally lands in trunk. +developers. These public branches let your collaborators see what +you’re up to; they may be able to lend advice, to help with the work, or +to at least be unsurprised when your change finally lands in trunk. This is part of what I mean about Fossil fostering close cooperation rather than fostering wild tangents. Jim McCarthy (author of [Dynamics of Software Development][dosd]) has a presentation on YouTube that touches on this topic at a couple of points: -* [Don't go dark](https://www.youtube.com/watch?v=9OJ9hplU8XA) -* [Beware of a guy in a room](https://www.youtube.com/watch?v=oY6BCHqEbyc) +* [Don’t go dark](https://www.youtube.com/watch?v=9OJ9hplU8XA) +* [Beware of a guy in a room](https://www.youtube.com/watch?v=oY6BCHqEbyc) -Fossil's sync-by-default behavior fights these negative tendencies. +Fossil’s sync-by-default behavior fights these negative tendencies. PiDP-8/I project developers are welcome to create branches at will. The main rule is to follow the branch naming scheme: all lowercase with hyphens separating words. See the [available branch list][brlist] for examples to emulate. If you have checkin rights on the repository, it is generally fine to -check things in on someone else's feature branch, as long as you do so -in a way that cooperates with the purpose of that branch. The same is +check things in on someone else’s feature branch, as long as you do so +in a way that cooperates with the purpose of that branch. The same is true of `trunk`: you should not check something in directly on the trunk that changes the nature of the software in a major way without -discussing the idea first. This is yet another use for branches: to +discussing the idea first. This is yet another use for branches: to make a possibly-controversial change so that it can be discussed before being merged into the trunk. [daff]: http://www.hanselman.com/blog/YouAreNotYourCode.aspx [dosd]: http://amzn.to/2iEVoBL @@ -266,47 +265,47 @@ Special Branches ---- Most of the branches in the PiDP-8/I project are feature branches of the sort described in the previous section: an isolated line of development -by one or more of the project's developers to work towards some new +by one or more of the project’s developers to work towards some new feature, with the goal of merging that feature into the `trunk` branch. There are a few branches in the project that are special, which are subject to different rules than other branches: -* **release** - One of the steps in the +* **release** — One of the steps in the [release process][relpr] is to merge the stabilized `trunk` into the `release` branch, from which the release tarballs and binary OS - images are created. Only the project's release manager — currently + images are created. Only the project’s release manager — currently Warren Young — should make changes to this branch. * **bogus** or **BOGUS** — Because a branch is basically just a label for a specific checkin, Fossil allows the tip - of one branch to be "moved" to another branch by applying a branch - label to that checkin. We use this label when someone makes a - checkin on the tip of a branch that should be "forgotten." Fossil + of one branch to be “moved” to another branch by applying a branch + label to that checkin. We use this label when someone makes a + checkin on the tip of a branch that should be “forgotten.” Fossil makes destroying project history very difficult, on purpose, so - things moved to the "bogus" branch are not actually destroyed; + things moved to the “bogus” branch are not actually destroyed; instead, they are merely moved out of the way so that they do not - interfere with that branch's normal purpose. + interfere with that branch’s normal purpose. If you find yourself needing to prune the tip of a branch this way, the simplest way is to do it via the web UI, using the checkin - description page's "edit" link. You can instead do it from the + description page’s “edit” link. You can instead do it from the command line with the `fossil amend` command. -[relpr]: https://tangentsoft.com/pidp8i/doc/trunk/doc/RELEASE-PROCESS.md +[relpr]: https://tangentsoft.com/pidp8i/doc/trunk/doc/RELEASE-PROCESS.md Developer Discussion Forum ---- -The "[Forum][pfor]" link at the top of the Fossil web interface is for +The “[Forum][pfor]” link at the top of the Fossil web interface is for discussing the development of the PiDP-8/I software only. All other -traffic should go to [the mailing list][ggml] instead. We're not trying +traffic should go to [the mailing list][ggml] instead. We’re not trying to split the community by providing a second discussion forum; we just think many development-related discussions are too low-level to be of any interest to most of the people on the mailing list. You can sign up for the forums without having a developer login, and you @@ -313,17 +312,17 @@ can even post anonymously. If you have a login, you can [sign up for email alerts][alert] if you like. Keep in mind that posts to the Fossil forum are treated much the same way as ticket submissions and wiki articles. They are permanently -archived with the project. The "edit" feature of Fossil forums just +archived with the project. The “edit” feature of Fossil forums just creates a replacement record for a post, but the old post is still -available in the repository. Don't post anything you wouldn't want made +available in the repository. Don’t post anything you wouldn’t want made part of the permanent record of the project! -[ggml]: https://groups.google.com/forum/#!forum/pidp-8 -[pfor]: https://tangentsoft.com/pidp8i/forum +[ggml]: https://groups.google.com/forum/#!forum/pidp-8 +[pfor]: https://tangentsoft.com/pidp8i/forum [alert]: https://tangentsoft.com/pidp8i/alerts Debug Builds @@ -331,68 +330,68 @@ By default, the build system creates a release build, but you can force it to produce a binary without as much optimization and with debug symbols included: - $ ./configure --debug-mode - $ make clean - $ tools/mmake + $ ./configure --debug-mode + $ make clean + $ tools/mmake Manipulating the Build System Source Files ---- The [autosetup build system][asbs] is composed of these files and directories: - auto.def - autosetup/* - configure - Makefile.in + auto.def + autosetup/* + configure + Makefile.in Unlike with GNU Autoconf, which you may be familiar with, the -`configure` script is not output from some other tool. It is just a +`configure` script is not output from some other tool. It is just a driver for the generic Tcl and C code under the `autosetup` directory, which in turn runs the project-specific `auto.def` Tcl script to -configure the software. Some knowledge of [Tcl syntax][tcldoc] will +configure the software. Some knowledge of [Tcl syntax][tcldoc] will therefore be helpful in modifying `auto.def`. If you have to modify any of the files in `autosetup/` to get some needed effect, you should try to get that change into the upstream [Autosetup][asbs] project, then merge that change down into the local copy when it lands upstream. If you do not have Tcl installed on your system, `configure` builds a minimal Tcl interpreter called `jimsh0`, based on the [Jim Tcl][jim] -project. Developers working on the build system are encouraged to use -this stripped-down version of Tcl rather than "real" Tcl because Jim Tcl +project. Developers working on the build system are encouraged to use +this stripped-down version of Tcl rather than “real” Tcl because Jim Tcl is mostly-pure subset of Tcl, and `jimsh0` is a subset of the complete -Jim Tcl distribution, so any changes you make that work with the -`jimsh0` interpreter should also work with "real" Tcl, but not vice -versa. If you have Tcl installed and don't really need it, consider +Jim Tcl distribution, so any changes you make that work with the +`jimsh0` interpreter should also work with “real” Tcl, but not vice +versa. If you have Tcl installed and don’t really need it, consider uninstalling it to force Autosetup to build and use `jimsh0`. The `Makefile.in` file is largely a standard [GNU `make`][gmake] file excepting only that it has variables substituted into it by Autosetup -using its `@VARIABLE@` syntax. At this time, we do not attempt to +using its `@VARIABLE@` syntax. At this time, we do not attempt to achieve compatibility with other `make` programs, though in the future we may need it to work with [BSD `make`][bmake] as well, so if you are adding features, you might want to stick to the common subset of -features implemented by both the GNU and BSD flavors of `make`. We do +features implemented by both the GNU and BSD flavors of `make`. We do not anticipate any need to support any other `make` flavors. -This, by the way, is why we're not using some heavy-weight build system -such as the GNU Autotools, CMake, etc. The primary advantage of GNU +This, by the way, is why we’re not using some heavy-weight build system +such as the GNU Autotools, CMake, etc. The primary advantage of GNU Autotools is that you can generate source packages that will configure -and build on weird and ancient flavors of Unix; we don't need that. +and build on weird and ancient flavors of Unix; we don’t need that. Cross-platform build systems such as CMake ease building the same software on multiple disparate platforms straightforward, but the PiDP-8/I software is built primarily on and for a single operating -system, Raspbian Linux. It also happens to build and run on [several +system, Raspbian Linux. It also happens to build and run on [several other OSes][oscomp], for which we also do not need the full power of -something like CMake. Autosetup and GNU `make` suffice for our purposes +something like CMake. Autosetup and GNU `make` suffice for our purposes here. [asbs]: http://msteveb.github.io/autosetup/ [bmake]: https://www.freebsd.org/doc/en/books/developers-handbook/tools-make.html [gmake]: https://www.gnu.org/software/make/ @@ -405,180 +404,180 @@ Directory Structure ---- The directory structure of the PiDP-8/I project is as follows: -* `.` - Top level, occupied only by the few files the end user +* `.` — Top level, occupied only by the few files the end user of the source code needs immediately at hand on first unpacking the project: the top level build system files, key documentation, and licensing information. If a given file *can* be buried deeper, it *should* be buried to reduce clutter at this most precious level of the hierarchy. -* `.fossil-settings` - Versioned settings for the Fossil build +* `.fossil-settings` — Versioned settings for the Fossil build system which Fossil applies as defaults everywhere you check out a - Fossil version. Settings made here are intended to be correct for + Fossil version. Settings made here are intended to be correct for all users of the system; think of these not as expressing defaults - but as expressing *policy*. It is possible to override these + but as expressing *policy*. It is possible to override these settings, but we do not make settings here if we expect that some users may quibble with our choices here. Any setting whose value may vary between users of the Fossil repository should be done locally with a `fossil set` command. Say `fossil help set` at the command line for more on this. -* `autosetup` - The bulk of the [Autosetup build system][asbs]. +* `autosetup` — The bulk of the [Autosetup build system][asbs]. These are generic files, not modified by the project itself. We occasionally run `tools/autosetup-update` to merge in upstream changes. -* `bin` - Programs installed to `$prefix/bin`, which may also +* `bin` — Programs installed to `$prefix/bin`, which may also be run during development, if only to test changes to those - programs. Some scripts stored here are written in place by the - project's developers, while other files in this directory are + programs. Some scripts stored here are written in place by the + project’s developers, while other files in this directory are outputs of the build system. The content of this directory is copied to `$prefix/bin` at - installation time, which is added to the user's `PATH` by the + installation time, which is added to the user’s `PATH` by the `make install` script. -* `boot` - SIMH initialization scripts. The `*.script.in` +* `boot` — SIMH initialization scripts. The `*.script.in` files are written by the project developers but have local configuration values substituted in by the `configure` script to - produce a `*.script` version. Scripts which need no config-time - values substituted in are checked in directly as `*.script`. The + produce a `*.script` version. Scripts which need no config-time + values substituted in are checked in directly as `*.script`. The `*.script` files in this directory which do not fall into either of those categories are outputs of `tools/mkbootscript`, which produces them from `palbart` assembly listings. All of these `*.script` files are copied to `$prefix/share/boot` by `make mediainstall` which runs automatically from `make install` when we detect that the binary media and SIMH boot scripts have - never been installed at this site before. On subsequent installs, + never been installed at this site before. On subsequent installs, the user chooses whether to run `make mediainstall` by hand to overwrite all of this. -* `doc` - Documentation files sufficiently unimportant to a new +* `doc` — Documentation files sufficiently unimportant to a new user of the software that they need not be at the top level of the - project tree. Such files can wait for new users to discover them. + project tree. Such files can wait for new users to discover them. - Fossil's [embedded documentation][edoc] feature allows us to present + Fossil’s [embedded documentation][edoc] feature allows us to present the contents of `doc` to web site users all but indistinguishably - from a wiki page. Why are there two different ways to achieve the + from a wiki page. Why are there two different ways to achieve the same end, and how do we decide which mechanism to use? - The rule is simple: if a given document's change history is tied to + The rule is simple: if a given document’s change history is tied to the history of the PiDP-8/I project itself, it goes in `doc`, else - it goes in the wiki. When checking out older versions of the + it goes in the wiki. When checking out older versions of the PiDP-8/I software, you expect to roll back to contemporaneous versions of the project documentation, which is what happens to all files stored in the repository, including those in `doc`, but this - does not happen to the wiki documents. The wiki always presents the + does not happen to the wiki documents. The wiki always presents the most current version, no matter what version you have locally checked out. - (Fossil's wiki feature behaves much like Wikipedia: it keeps change + (Fossil’s wiki feature behaves much like Wikipedia: it keeps change history for wiki documents, but it always presents the most recent version unless you manually go poking around in the history to pull - up old versions. If you check out a historical version of the + up old versions. If you check out a historical version of the software and then say `fossil ui` within that checkout directory, the resulting web view still shows the most recent locally-available version of each wiki document, not versions of the wiki documents contemporaneous with the historical version of the Fossil tree you have checked out.) The `doc/graphics` subdirectory holds JPEGs and SVGs displayed inline within wiki articles. -* `etc` - Files which get copied to `/etc` or one of its +* `etc` — Files which get copied to `/etc` or one of its subdirectories at installation time. There is an exception: `pidp8i.service.in` does not get installed to - `/etc` at install time, but only because systemd's [unit file load + `/etc` at install time, but only because systemd’s [unit file load path scheme][uflp] is screwy: *some* unit files go in `/etc`, while - others do not. The systemd docs claim we can put user units in + others do not. The systemd docs claim we can put user units in `/etc/systemd/user` but this does not appear to work on a Raspberry - Pi running Raspbian Stretch at least. We've fallen back to another + Pi running Raspbian Stretch at least. We’ve fallen back to another directory that *does* work, which feels more right to us anyway, but - which happens not to be in `/etc`. If systemd were designed sanely, - we'd install such files to `$HOME/etc/systemd` but noooo.... + which happens not to be in `/etc`. If systemd were designed sanely, + we’d install such files to `$HOME/etc/systemd` but noooo… Since none of the above actually argues for creating another - top-level repository directory to hold this one file, we've chosen + top-level repository directory to hold this one file, we’ve chosen to store it in `etc`. -* `examples` - Example programs for the end user's edification. +* `examples` — Example programs for the end user’s edification. Many of these are referenced by documentation files and therefore should not be renamed or moved, since there may be public web links referring to these examples. -* `hardware` - Schematics and such for the PiDP-8/I board or +* `hardware` — Schematics and such for the PiDP-8/I board or associated hardware. -* `labels` - Graphics intended to be printed out and used as +* `labels` — Graphics intended to be printed out and used as labels for removable media. -* `lib` - Library routines used by other programs, installed to +* `lib` — Library routines used by other programs, installed to `$prefix/lib`. -* `libexec` - A logical extension of `lib`, these are +* `libexec` — A logical extension of `lib`, these are standalone programs that nevertheless are intended to be run - primarily by other programs. Whereas a file in `lib` might have its - interface described by a programmer's reference manual, the + primarily by other programs. Whereas a file in `lib` might have its + interface described by a programmer’s reference manual, the interface of a program in `libexec` is described by its usage - message. Example: + message. Example: - * `scanswitch` - Run by `etc/pidp8i`. - + * `scanswitch` — Run by `etc/pidp8i`. +

It is run by hand only by developers modifying its behavior.

Programs in `libexec` are installed to `$prefix/libexec`, which is - *not* put into the user's `PATH`, on purpose. If a program should - end up in the user's `PATH`, it belongs in `bin`. Alternately, a + *not* put into the user’s `PATH`, on purpose. If a program should + end up in the user’s `PATH`, it belongs in `bin`. Alternately, a wrapper may be put in `bin` which calls a `libexec` program as a helper. -* `media` - Binary media images used either by SIMH directly or +* `media` — Binary media images used either by SIMH directly or by tools like `os8-run` to produce media used by SIMH. The contents of this tree are installed to `$prefix/share/media`. -* `obj` - Intermediate output directory used by the build - system. It is safe to remove this directory at any time, as its - contents may be recreated by `make`. No file checked into Fossil +* `obj` — Intermediate output directory used by the build + system. It is safe to remove this directory at any time, as its + contents may be recreated by `make`. No file checked into Fossil should be placed here. (Contrast `bin` which does have some files checked into Fossil; all of the *other* files that end up in `bin` can be recreated by `make`, but not these few hand-written programs.) -* `src` - Source code for the project's programs, especially - those that cannot be used until they are built. The great majority - of these programs are written in C. The build system's output +* `src` — Source code for the project’s programs, especially + those that cannot be used until they are built. The great majority + of these programs are written in C. The build system’s output directories are `bin`, `boot`, `libexec`, and `obj`. - Programs that can be used without being "built," example programs, + Programs that can be used without being “built,” example programs, and single-file scripts are placed elsewhere: `bin`, `examples`, - `libexec`, `tools`, etc. Basically, we place such files where the + `libexec`, `tools`, etc. Basically, we place such files where the build system *would* place them if they were built from something under `src`. - There are no program sources in the top level of `src`. The file + There are no program sources in the top level of `src`. The file `src/config.h` may appear to be an exception to that restriction, - but it is *generated output* of the `configure` script, not "source - code" *per se*. + but it is *generated output* of the `configure` script, not “source + code” *per se*. Multi-module programs each have their own subdirectory of `src`, each named after the program contained within. Single module programs live in `src/misc` or `src/asm`, depending on whether they are host-side C programs or PAL8 assembly programs. -* `test` - Output directory used by `tools/test-*`. +* `test` — Output directory used by `tools/test-*`. -* `tools` - Programs run only during development and not +* `tools` — Programs run only during development and not installed. If a program is initially created here but we later decide that it should be installed for use by end users of the PiDP-8/I system, we move it to either `bin` or `libexec`, depending on whether it is run @@ -602,20 +601,20 @@ $ fossil diff > my-changes.patch Then either upload that file somewhere (e.g. Pastebin) and point to it from a [forum post][pfor] or attach the patch to a new [PiDP-8/I mailing list][ggml] message. Either way, include a declaration of the license -you wish to contribute your changes under. We suggest using the [SIMH +you wish to contribute your changes under. We suggest using the [SIMH license][simhl], but any [non-viral][viral] [OSI-approved license][osil] should suffice. We’re willing to tolerate viral licenses for standalone products; for example, CC8 is under the GPL, but it’s fine because it isn’t hard-linked into any other part of the PiDP-8/I software system. If your change is more than a small patch, `fossil diff` might not -incorporate all of the changes you have made. The old unified `diff` -format can't encode branch names, file renamings, file deletions, tags, -checkin comments, and other Fossil-specific information. For such +incorporate all of the changes you have made. The old unified `diff` +format can’t encode branch names, file renamings, file deletions, tags, +checkin comments, and other Fossil-specific information. For such changes, it is better to send a Fossil bundle: $ fossil set autosync 0 # disable autosync $ fossil checkin --branch my-changes ...followed by more checkins on that branch... @@ -622,12 +621,12 @@ $ fossil bundle export --branch my-changes my-changes.bundle After that first `fossil checkin --branch ...` command, any subsequent `fossil ci` commands will check your changes in on that branch without needing a `--branch` option until you explicitly switch that checkout -directory to some other branch. This lets you build up a larger change -on a private branch until you're ready to submit the whole thing as a +directory to some other branch. This lets you build up a larger change +on a private branch until you’re ready to submit the whole thing as a bundle. Because you are working on a branch on your private copy of the PiDP-8/I Fossil repository, you are free to make as many checkins as you like on the new branch before giving the `bundle export` command. @@ -634,15 +633,15 @@ Once you are done with the bundle, send it to the developers the same way you should a patch file. If you provide a quality patch, we are likely to offer you a developer -login on [the repository][repo] so you don't have to continue with the +login on [the repository][repo] so you don’t have to continue with the patch or bundle methods. Please make your patches or experimental branch bundles against the tip -of the current trunk. PiDP-8/I often drifts enough during development +of the current trunk. PiDP-8/I often drifts enough during development that a patch against a stable release may not apply to the trunk cleanly otherwise. [osil]: https://opensource.org/licenses [repo]: https://tangentsoft.com/pidp8i/ @@ -652,37 +651,90 @@ The PiDP-8/I Software Project Code Style Rules ---- -Every code base should have a common code style. Love it or -hate it, here are PiDP-8/I's current code style rules: +Every code base should have a common code style. Love it or +hate it, here are PiDP-8/I’s current code style rules: **C Source Code** -File types: `c`, `h`, `c.in` +File types: `*.c`, `*.h`, `*.c.in` -We follow the SIMH project's pre-existing code style when modifying one +We follow the SIMH project’s pre-existing code style when modifying one of its source files: * Spaces for indents, size 4; tabs are rendered size 8 in HTML output, - since that's how a PDP-8 terminal would likely interpret it! + since that’s how a PDP-8 terminal would likely interpret it! -* DOS line endings. (Yes, even though this is a Linux-based project! +* DOS line endings. (Yes, even though this is a Linux-based project! All decent Linux text editors can cope with this.) * Function, structure, type, and variable names are all lowercase, with underscores separating words * Macro names are in `ALL_UPPERCASE_WITH_UNDERSCORES` * Whitespace in the SIMH C files is of a style I have never seen - anywhere else in my decades of software development. This example + anywhere else in my decades of software development. This example shows the important features: - int some_function (char some_parameter) - { + ```C + int some_function (char some_parameter) + { + int some_variable = 0; + + if (some_parameter != '\0') { + int nbytes = sizeof (some_parameter); + char *buffer = malloc (4 * nbytes); + + switch (some_parameter) { + case 'a': + do_something_with_buffer ((char *)buffer); + default: + do_something_else (); + } + } + else { + some_other_function (with_a_large, "number of parameters", + wraps_with_a_single, "indent level"); + printf (stderr, "Failed to allocate buffer.\n"); + } + } + ``` + + It is vaguely like K&R C style except that: + + * The top level of statements in a function are not indented + + * The closing curly brace is indented to the same level as the + statement(s) it contains + + * There is a space before all opening parentheses, not just those + used in `if`, `while`, and similar flow control statements. + + Nested open parentheses do not have extra spaces, however. Only + the outer opening parenthesis has a space separating it from + what went before. + + * Multiple variables declared together don’t have their types and + variable names aligned in columns. + + I find that this style is mostly sensible, but with two serious problems: + I find the indented closing curly braces confusing, and I find that the + loss of the first indent level for the statements inside a function makes + functions all visually run together in a screenful of code. Therefore, + when we have the luxury to be working on a file separate from SIMH, + we use a variant of its style with these two changes, which you can + produce with the `tools/restyle` command. See its internal comments for + details. + + That gives the following, when applied to the above example: + + ```C + int some_function (char some_parameter) + { int some_variable = 0; if (some_parameter != '\0') { int nbytes = sizeof (some_parameter); char *buffer = malloc (4 * nbytes); @@ -690,96 +742,47 @@ switch (some_parameter) { case 'a': do_something_with_buffer ((char *)buffer); default: do_something_else (); - } - } - else { - some_other_function (with_a_large, "number of parameters", - wraps_with_a_single, "indent level"); - printf (stderr, "Failed to allocate buffer.\n"); - } - } - - It is vaguely like K&R C style except that: - - - The top level of statements in a function are not indented - - - The closing curly brace is indented to the same level as the - statement(s) it contains - - - There is a space before all opening parentheses, not just those - used in `if`, `while`, and similar flow control statements. - - Nested open parentheses do not have extra spaces, however. Only - the outer opening parenthesis has a space separating it from - what went before. - - - Multiple variables declared together don't have their types and - variable names aligned in columns. - -I find that this style is mostly sensible, but with two serious problems: -I find the indented closing curly braces confusing, and I find that the -loss of the first indent level for the statements inside a function makes -functions all visually run together in a screenful of code. Therefore, -when we have the luxury to be working on a file separate from SIMH, -we use a variant of its style with these two changes, which you can -produce with the `tools/restyle` command. See its internal comments for -details. - -That gives the following, when applied to the above example: - - int some_function (char some_parameter) - { - int some_variable = 0; - - if (some_parameter != '\0') { - int nbytes = sizeof (some_parameter); - char *buffer = malloc (4 * nbytes); - - switch (some_parameter) { - case 'a': - do_something_with_buffer ((char *)buffer); - default: - do_something_else (); - } - } - else { - some_other_function (with_a_large, "number of parameters", - wraps_with_a_single, "indent level"); - printf (stderr, "Failed to allocate buffer.\n"); - } - } - -If that looks greatly different, realize that it is just two indenting -level differences: add one indent at function level, except for the -closing braces, which we leave at their previous position. - -SIMH occasionally exceeds 100-column lines. I recommend breaking -long lines at 72 columns. Call me an 80-column traditionalist. - -When in doubt, mimic what you see in the current code. When still in -doubt, ask on the [project forum][pfor]. + } + } + else { + some_other_function (with_a_large, "number of parameters", + wraps_with_a_single, "indent level"); + printf (stderr, "Failed to allocate buffer.\n"); + } + } + ``` + + If that looks greatly different, realize that it is just two indenting + level differences: add one indent at function level, except for the + closing braces, which we leave at their previous position. + + SIMH occasionally exceeds 100-column lines. I recommend breaking + long lines at 72 columns. Call me an 80-column traditionalist. + + When in doubt, mimic what you see in the current code. When still in + doubt, ask on the [project forum][pfor]. [indent]: http://linux.die.net/man/1/indent **Plain Text Files** -File types: `md`, `txt` +File types: `*.md`, `*.txt` * Spaces for indents, size 4. -* Unix line endings. The only common text editor I'm aware of that +* Unix line endings. The only common text editor I’m aware of that has a problem with this is Notepad, and people looking at these files anywhere other than unpacked on a Raspberry Pi box are probably looking at them through the Fossil web interface on tangentsoft.com. * Markdown files must follow the syntax flavor understood by - [Fossil's Markdown interpreter][fmd]. + [Fossil’s Markdown interpreter][fmd]. [fmd]: https://tangentsoft.com/pidp8i/md_rules @@ -815,23 +818,23 @@ before you resolve a ticket. There is a process interlock between the Resolution and Status settings for a ticket: while Status is not Closed, Resolution should be Open. When Resolution changes from Open, Status should change to either Review -or, preferentially, Closed. A resolution is an end state, not an +or, preferentially, Closed. A resolution is an end state, not an expression of changeable intent: no more ceremony than setting a ticket’s Resolution state *away* from Open and its Status *to* Closed is required. If you do not intend to close a ticket but wish to advocate for a particular resolution, just add a comment to the ticket and let someone else choose whether to close the ticket or not. Don’t change the Resolution value until the issue has been *resolved* for good. -For example, the resolution "Works as Designed" does not merely mean, -“Yes, we know it works that way,” it also implies “...and we have no -intention to change that behavior, ever.” If there is a chance that the +For example, the resolution “Works as Designed” does not merely mean, +“Yes, we know it works that way,” it also implies “…and we have no +intention to change that behavior, ever.” If there is a chance that the behavior described in the ticket could change, you should not assign any resolution. Just leave it open until someone decides to do something final with the ticket. This is not to say that a ticket can never be re-opened once it’s had a @@ -850,9 +853,9 @@ > marked Implemented, but still Closed. ## License -Copyright © 2016-2019 by Warren Young. This document is licensed under +Copyright © 2016-2020 by Warren Young. This document is licensed under the terms of [the SIMH license][sl]. -[sl]: https://tangentsoft.com/pidp8i/doc/trunk/SIMH-LICENSE.md +[sl]: https://tangentsoft.com/pidp8i/doc/trunk/SIMH-LICENSE.md Index: Makefile.in ================================================================== --- Makefile.in +++ Makefile.in @@ -5,11 +5,11 @@ # If you are seeing this at the top of a file called Makefile and you # intend to make edits, do that in Makefile.in. Saying "make" will then # re-build Makefile from that modified Makefile.in before proceeding to # do the "make" operation. # -# Copyright © 2015 by Oscar Vermeulen, © 2016-2019 by Warren Young +# Copyright © 2015 by Oscar Vermeulen, © 2016-2020 by Warren Young # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, @@ -34,22 +34,32 @@ # authorization from those authors. ######################################################################## # Git commit ID and time of the latest version of the SIMH 4 project on # GitHub that has been merged into this source base. -SGCID=4e0450cff96830c5aced36928a4427adfc5314f8 -SGCTM=2019-04-18T20:03:17-07:00 +SGCID=cabd3784bc7e33d63906602e33a5d01b60c1dee1 +SGCTM=2020-04-17T18:20:27-07:00 # C build flags for the PDP-8 simulator and its PiDP-8/I extensions. SIM_CFLAGS := @CFLAGS@ @PI_CFLAGS@ @BUILDMODE@ \ -Wno-unused-result -Wno-parentheses \ -DUSE_READER_THREAD -DHAVE_DLOPEN=$(subst .,,@SH_SOEXT@) -DPIDP8I \ - -DSIM_ASYNCH_IO -DHAVE_REGEX_H -DHAVE_GLOB \ + -DSIM_ASYNCH_IO -DHAVE_GLOB \ -DSIM_GIT_COMMIT_ID=$(SGCID) -DSIM_GIT_COMMIT_TIME=$(SGCTM) \ + -DSIM_BUILD_TOOL='autosetup+gmake' \ -D_GNU_SOURCE \ -U__STRICT_ANSI__ \ -I @srcdir@/src/SIMH -I @srcdir@/src/pidp8i -I src -I src/SIMH -I src/pidp8i +ifneq "@HAVE_PCRE_H@" "" +SIM_CFLAGS += -DHAVE_PCRE_H +SIM_LFLAGS := -lpcre +else +SIM_CFLAGS += -DHAVE_REGEX_H +endif +ifneq "@HAVE_PCREPOSIX_H@" "" +SIM_CFLAGS += -DHAVE_PCREPOSIX_H +endif PIDP8I_CFLAGS = $(SIM_CFLAGS) SIMH_CFLAGS = $(SIM_CFLAGS) SIMH_PDP8_CFLAGS = $(SIM_CFLAGS) # Greatly stripped-down build options for the cc8 cross-compiler @@ -249,10 +259,13 @@ @srcdir@/src/cc8/Makefile.in \ @srcdir@/src/SIMH/Makefile.in \ @srcdir@/src/SIMH/PDP8/Makefile.in INFILES = \ @srcdir@/bin/os8-cp.in \ + @srcdir@/bin/os8-run.in \ + @srcdir@/bin/teco-pi-demo.in \ + @srcdir@/bin/txt2os8.in \ @srcdir@/bin/pidp8i.in \ @srcdir@/boot/0.script.in \ @srcdir@/boot/2.script.in \ @srcdir@/boot/3.script.in \ @srcdir@/boot/4.script.in \ @@ -262,12 +275,15 @@ @srcdir@/boot/run-v3f.script.in \ @srcdir@/boot/tss8.script.in \ @srcdir@/etc/pidp8i.service.in \ @srcdir@/etc/sudoers.in \ @srcdir@/etc/usb-mount@.service.in \ + @srcdir@/lib/os8script.py.in \ + @srcdir@/lib/simh.py.in \ @srcdir@/src/pidp8i/gpio-common.c.in \ @srcdir@/tools/simh-update.in \ + @srcdir@/tools/test-os8-send-file.in \ $(OS8RUN_INFILES) OS8RUN_OUTFILES := $(subst @srcdir@/,,$(OS8RUN_INFILES)) OS8RUN_OUTFILES := $(subst .in,,$(OS8RUN_OUTFILES)) PRECIOUS_OUTFILES := $(subst @srcdir@/,,$(PRECIOUS_INFILES)) PRECIOUS_OUTFILES := $(subst .in,,$(PRECIOUS_OUTFILES)) @@ -303,10 +319,12 @@ V3F_TC08_TU56 = bin/v3f-tc08.tu56 V3F_TD12K_TU56 = bin/v3f-td12k.tu56 V3F_BOOT_TAPE= bin/v3f-@OS8_TAPE_DEVICE@.tu56 CC8_TU56_SCRIPT = $(OS8_SCRIPTS_DIR)/cc8-tu56.os8 + +RCFILE := @ABSPREFIX@/etc/pidp8i.rc CLTXT = /boot/cmdline.txt ADF := adrules.mk @@ -425,10 +443,13 @@ @( test -d /etc/sudoers.d -a -w /etc/sudoers.d -a -x /bin/systemctl && \ @INSTALL@ -m 440 -o root -g root @srcdir@/etc/sudoers \ /etc/sudoers.d/099_pidp8i \ ) || true + @# Install runtime config file if there isn't one there already. + @test -f $(RCFILE) || @INSTALL@ -m 644 -o @INSTUSR@ @srcdir@/etc/pidp8i.rc $(RCFILE) + @# Add installation bin dir to the non-root user's PATH unless it's @# already in there or we aren't running under sudo. @(for p in .profile .bash_profile ; do \ test -n "$$SUDO_USER" -a -w "/home/$$SUDO_USER/$$p" && \ ! grep -qF "@ABSPREFIX@/bin" "/home/$$SUDO_USER/$$p" && \ @@ -476,10 +497,13 @@ @sed -e 's#^build =.*#build = "@ABSPREFIX@"#' \ -e 's#^media =.*#media = os.path.join (build, "share/media/")#' \ -e 's#^os8mo =.*#os8mo = os8mi#' \ < $(PIDP8I_DIRS) > @prefix@/$(PIDP8I_DIRS) @chgrp @INSTGRP@ @prefix@/$(PIDP8I_DIRS) + + @# We need a directory for PIDs and such in some cases + @@INSTALL@ -d -m 755 @prefix@/run instdirs: @echo "Creating installation directory tree..." @for d in $(INSTDIRS) ; do @INSTALL@ -m 755 -o @INSTUSR@ -g @INSTGRP@ -d @prefix@/$$d ; done @@ -688,11 +712,11 @@ $(BUILDDIRS): mkdir -p $@ $(PIDP8I_SIM): $(SIM_OBJS) obj/pidp8i/gpio-@LED_DRIVER_MODULE@ls.o - $(CC) -o $@ @PI_LFLAGS@ $^ $(LIBS) @PI_LIBS@ + $(CC) -o $@ @PI_LFLAGS@ $^ $(LIBS) $(SIM_LFLAGS) @PI_LIBS@ ln -f bin/pidp8i-sim bin/pdp8 bin/cc8: $(CC8_OBJS) $(CC) -o $@ $^ $(LIBS) Index: README.md ================================================================== --- README.md +++ README.md @@ -734,32 +734,61 @@ ## Runtime Configuration -The `pidp8i` command may be configured by the optional `pidp8i.rc` file, +The `pidp8i` command may be configured by the `pidp8i.rc` file, located by default in `/opt/pidp8i/etc/`. This is a Bourne shell script -which is sourced by `pidp8i` if it exists, and recognizes the following -variables: +which is sourced by `pidp8i` if it exists which may set the following +variables for the `pidp8i` script to affect how it works: -### SCREEN_MANAGER=screen - -By default, pidp8i installs and uses [GNU screen(1)][gnuscreen] to -manage screen sessions. However, if you prefer to use [tmux(1)][tmux] -as a screen manager for the pidp8i session, you may set -`SCREEN_MANAGER=tmux`. Note that if you make this change, you are -responsible for installing tmux; on Raspbian, this is done by: - - $ sudo apt-get install tmux - -Switching between configured screen managers must be done while pidp8i -is stopped. - -[gnuscreen]: https://www.gnu.org/software/screen/ -[tmux]: https://tmux.github.io/ +### `SCREEN_MANAGER=screen` + +By default, the PiDP-8/I software distribution installs and uses [GNU +`screen(1)`][gscr] to allow the simulator to run in the background yet be +reattached from a later terminal session, then possibly later to be +backgrounded once again. Without the intermediation of something like +`screen`, the simulator would either forever be in the background and +we’d have to export the console [another way][scons] or you’d have to +fire it up interactively any time you wanted to use it. This scheme lets +us have it both ways. + +The `SCREEN_MANAGER` setting is for use by those that need something +other than GNU `screen`. There are several alternatives: + +* **screen**: The default, per above. + +* [**tmux**][tmux]: A popular alternative to `screen`, especially on + on BSD platforms. Note that the "attention" character for `tmux` + is Ctrl-B by default, not Ctrl-A as with + `screen`. + +* **none**: This mode is for interactive use, allowing you to + run the installed simulator with the installed media without any + screen manager at all. + + In this mode, the `pidp8i` and `pidp8i start` commands do the + same thing: run the simulator directly attached to your current + interactive terminal. The `pidp8i stop` command becomes a no-op, + since stopping the simulator is then done in the standard SIMH way: + Ctrl-E, quit. + +Note that the alternative screen managers are not installed by default. +If you set `SCREEN_MANAGER=tmux`, you must then ensure that `tmux` is in +fact installed before the `pidp8i` script goes to try and use it. On +Raspbian, this is done by: + + $ sudo apt install tmux + +Switching between configured screen managers must be done while the +simulator is stopped. + +[gscr]: https://www.gnu.org/software/screen/ +[scons]: /wiki?name=Serial+or+Telnet+PDP-8+Console +[tmux]: https://tmux.github.io/ ## The OS/8 RK05 Disk Image For the first several years of the PiDP-8/I project, the OS/8 RK05 disk @@ -989,11 +1018,11 @@ `sudo` permissions. ## License -Copyright © 2016-2019 by Warren Young. This document is licensed under +Copyright © 2016-2020 by Warren Young. This document is licensed under the terms of [the SIMH license][sl]. [cprj]: https://tangentsoft.com/pidp8i/ [sm1]: http://obsolescence.wixsite.com/obsolescence/2016-pidp-8-building-instructions Index: auto.def ================================================================== --- auto.def +++ auto.def @@ -339,10 +339,11 @@ } # Check for headers, functions, etc. whose absence we can work around cc-check-decls __progname cc-check-includes time.h +cc-check-includes pcre.h pcreposix.h cc-check-function-in-lib clock_gettime rt cc-check-functions clock_nanosleep nanosleep usleep cc-check-functions sched_yield cc-with {-includes signal.h} { cc-check-types sighandler_t sig_t @@ -467,36 +468,56 @@ catch {exec hostname} host set user $::env(USER) define BUILDUSER "$user@$host" define BUILDTS [clock format [clock seconds] -format "%Y.%m.%d at %T %Z"] -# The os8-run script requires Python and some non-core modules. -set status [catch {exec python -c exit} result] -if {$status != 0} { - user-error "Python 2 does not appear to be installed here. It is required." +# The os8-run script requires Python 2 or 3 and some non-core modules. +set status [catch {exec python3 -c exit} result] +if {$status == 0} { + set pyver 3 + set pycmd "python3" +} else { + set status [catch {exec python2 -c exit} result] + if {$status == 0} { + set pyver 2 + set pycmd "python2" + } else { + set status [catch {exec python -c exit} result] + if {$status == 0} { + set status [catch {exec python --version | grep -q 'Version 2'} result] + set pyver [expr $status == 0 ? 2 : 3] + set pycmd "python" + } + } +} +if {$pyver == ""} { + user-error "Python does not appear to be installed here. It is required." } -msg-result "Python 2 is installed here." -set status [catch {exec python -c "import pexpect" 2> /dev/null} result] +define PYCMD $pycmd +define PYVER $pyver +msg-result "Python $pyver is installed here as '$pycmd'." +set status [catch {exec $pycmd -c "import pexpect" 2> /dev/null} result] if {$status != 0} { set msg "The Python pexpect module is not installed here. Fix with\n\n" - append msg " sudo apt install python-pip\n" - append msg " sudo pip install pexpect\n" + append msg " sudo apt install $pycmd-pip\n" + append msg "\nTHEN:\n" + append msg " sudo pip$pyver install pexpect\n" append msg "\nOR:\n" append msg "\n sudo easy_install pexpect\n" append msg "\nOR:\n" - append msg "\n sudo apt install python-pexpect\n" + append msg "\n sudo apt install $pycmd-pexpect\n" user-error $msg } msg-result "Python module pexpect is installed here." -set status [catch {exec python -c "import pkg_resources" 2> /dev/null} result] +set status [catch {exec $pycmd -c "import pkg_resources" 2> /dev/null} result] if {$status != 0} { set msg "The Python pkg_resources module is not installed here. Fix with\n" - append msg "\n sudo pip install pkg_resources\n" + append msg "\n sudo pip$pyver install pkg_resources\n" append msg "\nOR:\n" append msg "\n sudo easy_install pkg_resources\n" append msg "\nOR:\n" - append msg "\n sudo apt install python-pkg-resources\n" + append msg "\n sudo apt install $pycmd-pkg-resources\n" user-error $msg } msg-result "Python module pkg_resources is installed here." # Check for Perl and that it can run the test corpus builder. Not fatal @@ -565,10 +586,13 @@ make-config-header src/config.h \ -auto {ENABLE_* HAVE_* PACKAGE_* SIZEOF_*} \ -bare {ILS_MODE PCB_*} make-template bin/pidp8i.in make-template bin/os8-cp.in +make-template bin/os8-run.in +make-template bin/teco-pi-demo.in +make-template bin/txt2os8.in make-template boot/common.script.in make-template boot/0.script.in make-template boot/2.script.in make-template boot/3.script.in make-template boot/4.script.in @@ -579,13 +603,15 @@ make-template boot/tss8.script.in make-template etc/pidp8i.service.in make-template etc/sudoers.in make-template etc/usb-mount@.service.in make-template examples/Makefile.in +make-template lib/os8script.py.in make-template lib/pidp8i/__init__.py.in make-template lib/pidp8i/dirs.py.in make-template lib/pidp8i/ips.py.in +make-template lib/simh.py.in make-template media/os8/init.tx.in make-template media/os8/3finit.tx.in make-template src/Makefile.in make-template src/cc8/Makefile.in make-template src/cc8/os8/Makefile.in @@ -592,7 +618,10 @@ make-template src/pidp8i/gpio-common.c.in make-template src/pidp8i/main.c.in make-template src/SIMH/Makefile.in make-template src/SIMH/PDP8/Makefile.in make-template tools/simh-update.in +make-template tools/test-os8-send-file.in make-template Makefile.in -exec chmod +x "$builddir/bin/pidp8i" "$builddir/tools/simh-update" "$builddir/bin/os8-cp" +foreach f [concat [glob "$builddir/bin/*"] [glob "$builddir/tools/*"]] { + file attributes $f -permissions +x +} Index: autosetup/README.autosetup ================================================================== --- autosetup/README.autosetup +++ autosetup/README.autosetup @@ -1,6 +1,6 @@ -README.autosetup created by autosetup v0.6.9 +README.autosetup created by autosetup v0.6.9+ This is the autosetup directory for a local install of autosetup. It contains autosetup, support files and loadable modules. *.tcl files in this directory are optional modules which Index: autosetup/autosetup ================================================================== --- autosetup/autosetup +++ autosetup/autosetup @@ -4,11 +4,11 @@ # vim:se syntax=tcl: # \ dir=`dirname "$0"`; exec "`$dir/autosetup-find-tclsh`" "$0" "$@" # Note that the version has a trailing + on unreleased versions -set autosetup(version) 0.6.9 +set autosetup(version) 0.6.9+ # Can be set to 1 to debug early-init problems set autosetup(debug) [expr {"--debug" in $argv}] ################################################################## @@ -91,17 +91,17 @@ # We simply parse anything that looks like an option set autosetup(getopt) [getopt argv] #"=Core Options:" options-add { - help:=local => "display help and options. Optionally specify a module name, such as --help=system" + help:=all => "display help and options. Optional: module name, such as --help=system" licence license => "display the autosetup license" - version => "display the version of autosetup" + version => "display the version of autosetup" ref:=text manual:=text reference:=text => "display the autosetup command reference. 'text', 'wiki', 'asciidoc' or 'markdown'" - debug => "display debugging output as autosetup runs" - install:=. => "install autosetup to the current or given directory" + debug => "display debugging output as autosetup runs" + install:=. => "install autosetup to the current or given directory" } if {$autosetup(installed)} { # hidden options so we can produce a nice error options-add { sysinstall:path @@ -202,24 +202,30 @@ } } autosetup_add_dep $autosetup(autodef) - define CONFIGURE_OPTS "" + # Add $argv to CONFIGURE_OPTS, but ignore duplicates and quote if needed + set configure_opts {} foreach arg $autosetup(argv) { - define-append CONFIGURE_OPTS [quote-if-needed $arg] + set quoted [quote-if-needed $arg] + # O(n^2), but n will be small + if {$quoted ni $configure_opts} { + lappend configure_opts $quoted + } } + define CONFIGURE_OPTS [join $configure_opts] define AUTOREMAKE [file-normalize $autosetup(exe)] define-append AUTOREMAKE [get-define CONFIGURE_OPTS] # Log how we were invoked configlog "Invoked as: [getenv WRAPPER $::argv0] [quote-argv $autosetup(argv)]" configlog "Tclsh: [info nameofexecutable]" - # Note that auto.def is *not* loaded in the global scope - source $autosetup(autodef) + # Load auto.def as module "auto.def" + autosetup_load_module auto.def source $autosetup(autodef) # Could warn here if options {} was not specified show-notices @@ -340,12 +346,12 @@ } if {![info exists result]} { # No user-specified value. Has options-defaults been set? foreach opt $names { - if {[dict exists $::autosetup(options-defaults) $opt]} { - set result [dict get $autosetup(options-defaults) $opt] + if {[dict exists $::autosetup(optdefault) $opt]} { + set result [dict get $autosetup(optdefault) $opt] } } } if {[info exists result]} { @@ -373,11 +379,11 @@ } # Parse the option definition in $opts and update # ::autosetup(setoptions) and ::autosetup(optionhelp) appropriately # -proc options-add {opts {header ""}} { +proc options-add {opts} { global autosetup # First weed out comment lines set realopts {} foreach line [split $opts \n] { @@ -389,12 +395,11 @@ for {set i 0} {$i < [llength $opts]} {incr i} { set opt [lindex $opts $i] if {[string match =* $opt]} { # This is a special heading - lappend autosetup(optionhelp) $opt "" - set header {} + lappend autosetup(optionhelp) [list $opt $autosetup(module)] continue } unset -nocomplain defaultvalue equal value #puts "i=$i, opt=$opt" @@ -451,12 +456,12 @@ } } else { # String option. lappend autosetup(options) $name - if {$colon eq ":"} { - # Was ":name=default" given? + if {$equal ne "="} { + # Was the option given as "name:value=default"? # If so, set $value to the display name and $defaultvalue to the default # (This is the preferred way to set a default value for a string option) if {[regexp {^([^=]+)=(.*)$} $value -> value defaultvalue]} { dict set autosetup(optdefault) $name $defaultvalue } @@ -466,13 +471,13 @@ if {[dict exists $autosetup(options-defaults) $name]} { # A default was specified with options-defaults, so use it set defaultvalue [dict get $autosetup(options-defaults) $name] dict set autosetup(optdefault) $name $defaultvalue } elseif {![info exists defaultvalue]} { - # For backward compatibility, if ":name" was given, use name as both - # the display text and the default value, but only if the user - # specified the option without the value + # No default value was given by value=default or options-defaults + # so use the value as the default when the plain option with no + # value is given (.e.g. just --opt instead of --opt=value) set defaultvalue $value } if {$equal eq "="} { # String option with optional value @@ -507,39 +512,22 @@ if {[lindex $opts $i+1] eq "=>"} { set desc [lindex $opts $i+2] if {[info exists defaultvalue]} { set desc [string map [list @default@ $defaultvalue] $desc] } - #string match \n* $desc - if {$header ne ""} { - lappend autosetup(optionhelp) $header "" - set header "" - } # A multi-line description - lappend autosetup(optionhelp) $opthelp $desc + lappend autosetup(optionhelp) [list $opthelp $autosetup(module) $desc] incr i 2 } } } # @module-options optionlist # -# Like 'options', but used within a module. +# Deprecated. Simply use 'options' from within a module. proc module-options {opts} { - set header "" - if {$::autosetup(showhelp) > 1 && [llength $opts]} { - set header "Module Options:" - } - options-add $opts $header - - if {$::autosetup(showhelp)} { - # Ensure that the module isn't executed on --help - # We are running under eval or source, so use break - # to prevent further execution - #return -code break -level 2 - return -code break - } + options $opts } proc max {a b} { expr {$a > $b ? $a : $b} } @@ -564,14 +552,21 @@ if {$len} { puts "" } } -proc options-show {} { +# Display options (from $autosetup(optionhelp)) for modules that match +# glob pattern $what +proc options-show {what} { + set local 0 # Determine the max option width set max 0 - foreach {opt desc} $::autosetup(optionhelp) { + foreach help $::autosetup(optionhelp) { + lassign $help opt module desc + if {![string match $what $module]} { + continue + } if {[string match =* $opt] || [string match \n* $desc]} { continue } set max [max $max [string length $opt]] } @@ -580,17 +575,27 @@ catch { lassign [exec stty size] rows cols } incr cols -1 # Now output - foreach {opt desc} $::autosetup(optionhelp) { + foreach help $::autosetup(optionhelp) { + lassign $help opt module desc + if {![string match $what $module]} { + continue + } + if {$local == 0 && $module eq "auto.def"} { + puts "Local Options:" + incr local + } if {[string match =* $opt]} { + # Output a special heading line" puts [string range $opt 1 end] continue } puts -nonewline " [format %-${max}s $opt]" if {[string match \n* $desc]} { + # Output a pre-formatted help description as-is puts $desc } else { options-wrap-desc [string trim $desc] $cols " " $indent [expr $max + 2] } } @@ -608,16 +613,20 @@ # # The default is 'name=0', meaning that the option is disabled by default. # If 'name=1' is used to make the option enabled by default, the description should reflect # that with text like "Disable support for ...". # -# An argument option (one which takes a parameter) is of the form: +# An argument option (one which takes a parameter) is of one of the following forms: # -## name:[=]value => "Description of this option" +## name:value => "Description of this option" +## name:value=default => "Description of this option with a default value" +## name:=value => "Description of this option with an optional value" # # If the 'name:value' form is used, the value must be provided with the option (as '--name=myvalue'). -# If the 'name:=value' form is used, the value is optional and the given value is used as the default +# If the 'name:value=default' form is used, the option has the given default value even if not +# specified by the user. +# If the 'name:=value' form is used, the value is optional and the given value is used # if it is not provided. # # The description may contain '@default@', in which case it will be replaced with the default # value for the option (taking into account defaults specified with 'options-defaults'. # @@ -627,23 +636,26 @@ # For example, '--disable-lfs' is an alias for '--disable=largefile': # ## lfs=1 largefile=1 => "Disable large file support" # proc options {optlist} { - # Allow options as a list or args - options-add $optlist "Local Options:" + global autosetup - if {$::autosetup(showhelp)} { - options-show - exit 0 + options-add $optlist + + if {$autosetup(showhelp)} { + # If --help, stop now to show help + return -code break } - # Check for invalid options - if {[opt-bool option-checking]} { - foreach o [dict keys $::autosetup(getopt)] { - if {$o ni $::autosetup(options)} { - user-error "Unknown option --$o" + if {$autosetup(module) eq "auto.def"} { + # Check for invalid options + if {[opt-bool option-checking]} { + foreach o [dict keys $::autosetup(getopt)] { + if {$o ni $::autosetup(options)} { + user-error "Unknown option --$o" + } } } } } @@ -1171,12 +1183,13 @@ foreach m $args { if {[info exists libmodule($m)]} { continue } set libmodule($m) 1 + if {[info exists modsource(${m}.tcl)]} { - automf_load eval $modsource(${m}.tcl) + autosetup_load_module $m eval $modsource(${m}.tcl) } else { set locs [list ${m}.tcl ${m}/init.tcl] set found 0 foreach dir $dirs { foreach loc $locs { @@ -1192,11 +1205,11 @@ } if {$found} { # For the convenience of the "use" source, point to the directory # it is being loaded from set ::usedir [file dirname $source] - automf_load source $source + autosetup_load_module $m source $source autosetup_add_dep $source } else { autosetup-error "use: No such module: $m" } } @@ -1205,23 +1218,28 @@ proc autosetup_load_auto_modules {} { global autosetup modsource # First load any embedded auto modules foreach mod [array names modsource *.auto] { - automf_load eval $modsource($mod) + autosetup_load_module $mod eval $modsource($mod) } # Now any external auto modules foreach file [glob -nocomplain $autosetup(libdir)/*.auto $autosetup(libdir)/*/*.auto] { - automf_load source $file + autosetup_load_module [file tail $file] source $file } } # Load module source in the global scope by executing the given command -proc automf_load {args} { +proc autosetup_load_module {module args} { + global autosetup + set prev $autosetup(module) + set autosetup(module) $module + if {[catch [list uplevel #0 $args] msg opts] ni {0 2 3}} { autosetup-full-error [error-dump $msg $opts $::autosetup(debug)] } + set autosetup(module) $prev } # Initial settings set autosetup(exe) $::argv0 set autosetup(istcl) 1 @@ -1229,10 +1247,11 @@ set autosetup(installed) 0 set autosetup(sysinstall) 0 set autosetup(msg-checking) 0 set autosetup(msg-quiet) 0 set autosetup(inittypes) {} +set autosetup(module) autosetup # Embedded modules are inserted below here set autosetup(installed) 1 set autosetup(sysinstall) 0 # ----- @module asciidoc-formatting.tcl ----- @@ -1434,26 +1453,26 @@ puts "Usage: [file tail $::autosetup(exe)] \[options\] \[settings\]\n" puts "This is [autosetup_version], a build environment \"autoconfigurator\"" puts "See the documentation online at http://msteveb.github.com/autosetup/\n" - if {$what eq "local"} { - if {[file exists $::autosetup(autodef)]} { - # This relies on auto.def having a call to 'options' - # which will display options and quit - source $::autosetup(autodef) - } else { - options-show - } - } else { - incr ::autosetup(showhelp) - if {[catch {use $what}]} { - user-error "Unknown module: $what" - } else { - options-show - } - } + if {$what in {all local}} { + # Need to load auto.def now + if {[file exists $::autosetup(autodef)]} { + # Load auto.def as module "auto.def" + autosetup_load_module auto.def source $::autosetup(autodef) + } + if {$what eq "all"} { + set what * + } else { + set what auto.def + } + } else { + use $what + puts "Options for module $what:" + } + options-show $what exit 0 } proc autosetup_show_license {} { global modsource autosetup Index: autosetup/autosetup-find-tclsh ================================================================== --- autosetup/autosetup-find-tclsh +++ autosetup/autosetup-find-tclsh @@ -3,11 +3,11 @@ # If not found, builds a bootstrap jimsh from source # Prefer $autosetup_tclsh if is set in the environment d=`dirname "$0"` { "$d/jimsh0" "$d/autosetup-test-tclsh"; } 2>/dev/null && exit 0 PATH="$PATH:$d"; export PATH -for tclsh in $autosetup_tclsh jimsh tclsh tclsh8.5 tclsh8.6; do +for tclsh in $autosetup_tclsh jimsh tclsh tclsh8.5 tclsh8.6 tclsh8.7; do { $tclsh "$d/autosetup-test-tclsh"; } 2>/dev/null && exit 0 done echo 1>&2 "No installed jimsh or tclsh, building local bootstrap jimsh0" for cc in ${CC_FOR_BUILD:-cc} gcc; do { $cc -o "$d/jimsh0" "$d/jimsh0.c"; } 2>/dev/null || continue Index: autosetup/cc-db.tcl ================================================================== --- autosetup/cc-db.tcl +++ autosetup/cc-db.tcl @@ -6,10 +6,10 @@ # The 'cc-db' module provides a knowledge-base of system idiosyncrasies. # In general, this module can always be included. use cc -module-options {} +options {} # openbsd needs sys/types.h to detect some system headers cc-include-needs sys/socket.h sys/types.h cc-include-needs netinet/in.h sys/types.h Index: autosetup/cc-lib.tcl ================================================================== --- autosetup/cc-lib.tcl +++ autosetup/cc-lib.tcl @@ -5,12 +5,10 @@ # # Provides a library of common tests on top of the 'cc' module. use cc -module-options {} - # @cc-check-lfs # # The equivalent of the 'AC_SYS_LARGEFILE' macro. # # defines 'HAVE_LFS' if LFS is available, Index: autosetup/cc-shared.tcl ================================================================== --- autosetup/cc-shared.tcl +++ autosetup/cc-shared.tcl @@ -18,11 +18,11 @@ ## SH_LINKRPATH Format for setting the rpath when linking an executable, %s = path ## SH_LINKFLAGS Flags to use linking an executable which will load shared objects ## LD_LIBRARY_PATH Environment variable which specifies path to shared libraries ## STRIPLIBFLAGS Arguments to strip a dynamic library -module-options {} +options {} # Defaults: gcc on unix define SHOBJ_CFLAGS -fPIC define SHOBJ_LDFLAGS -shared define SH_CFLAGS -fPIC Index: autosetup/cc.tcl ================================================================== --- autosetup/cc.tcl +++ autosetup/cc.tcl @@ -27,11 +27,11 @@ ## CC_FOR_BUILD ## LD use system -module-options {} +options {} # Checks for the existence of the given function by linking # proc cctest_function {function} { cctest -link 1 -declare "extern void $function\(void);" -code "$function\();" @@ -678,15 +678,15 @@ } define CPP [get-env CPP "[get-define CC] -E"] # XXX: Could avoid looking for a C++ compiler until requested -# Note that if CXX isn't found, we just set it to "false". It might not be needed. +# If CXX isn't found, it is set to the empty string. if {[env-is-set CXX]} { define CXX [find-an-executable -required [get-env CXX ""]] } else { - define CXX [find-an-executable [get-define cross]c++ [get-define cross]g++ false] + define CXX [find-an-executable [get-define cross]c++ [get-define cross]g++] } # CXXFLAGS default to CFLAGS if not specified define CXXFLAGS [get-env CXXFLAGS [get-define CFLAGS]] Index: autosetup/pkg-config.tcl ================================================================== --- autosetup/pkg-config.tcl +++ autosetup/pkg-config.tcl @@ -13,11 +13,11 @@ # # 'PKG_CONFIG' may be set to use an alternative to 'pkg-config'. use cc -module-options { +options { sysroot:dir => "Override compiler sysroot for pkg-config search path" } # @pkg-config-init ?required? # @@ -71,10 +71,13 @@ set sysroot [get-define SYSROOT] # XXX: It's possible that these should be set only when invoking pkg-config global env set env(PKG_CONFIG_DIR) "" + # Supposedly setting PKG_CONFIG_LIBDIR means that PKG_CONFIG_PATH is ignored, + # but it doesn't seem to work that way in practice + set env(PKG_CONFIG_PATH) "" # Do we need to try /usr/local as well or instead? set env(PKG_CONFIG_LIBDIR) $sysroot/usr/lib/pkgconfig:$sysroot/usr/share/pkgconfig set env(PKG_CONFIG_SYSROOT_DIR) $sysroot } } @@ -106,22 +109,34 @@ if {!$ok} { msg-result "no pkg-config" return 0 } - if {[catch {exec [get-define PKG_CONFIG] --modversion "$module $args"} version]} { + set pkgconfig [get-define PKG_CONFIG] + + set ret [catch {exec $pkgconfig --modversion "$module $args"} version] + configlog "$pkgconfig --modversion $module $args: $version" + if {$ret} { msg-result "not found" - configlog "pkg-config --modversion $module $args: $version" + return 0 + } + # Sometimes --modversion succeeds but because of dependencies it isn't usable + # This seems to show up with --cflags + set ret [catch {exec $pkgconfig --cflags $module} cflags] + if {$ret} { + msg-result "unusable ($version - see config.log)" + configlog "$pkgconfig --cflags $module" + configlog $cflags return 0 } msg-result $version set prefix [feature-define-name $module PKG_] define HAVE_${prefix} define ${prefix}_VERSION $version - define ${prefix}_LIBS [exec pkg-config --libs-only-l $module] - define ${prefix}_LDFLAGS [exec pkg-config --libs-only-L $module] - define ${prefix}_CFLAGS [exec pkg-config --cflags $module] + define ${prefix}_CFLAGS $cflags + define ${prefix}_LIBS [exec $pkgconfig --libs-only-l $module] + define ${prefix}_LDFLAGS [exec $pkgconfig --libs-only-L $module] return 1 } # @pkg-config-get module setting # @@ -131,5 +146,22 @@ # the value of 'PKG_PANGO_CFLAGS', or '""' if not defined. proc pkg-config-get {module name} { set prefix [feature-define-name $module PKG_] get-define ${prefix}_${name} "" } + +# @pkg-config-get-var module variable +# +# Return the value of the given variable from the given pkg-config module. +# The module must already have been successfully detected with pkg-config. +# e.g. +# +## if {[pkg-config harfbuzz >= 2.5]} { +## define harfbuzz_libdir [pkg-config-get-var harfbuzz libdir] +## } +# +# Returns the empty string if the variable isn't defined. +proc pkg-config-get-var {module variable} { + set pkgconfig [get-define PKG_CONFIG] + set prefix [feature-define-name $module HAVE_PKG_] + exec $pkgconfig $module --variable $variable +} Index: autosetup/system.tcl ================================================================== --- autosetup/system.tcl +++ autosetup/system.tcl @@ -25,11 +25,11 @@ if {[is-defined defaultprefix]} { user-notice "Note: defaultprefix is deprecated. Use options-defaults to set default options" options-defaults [list prefix [get-define defaultprefix]] } -module-options [subst -noc -nob { +options { host:host-alias => {a complete or partial cpu-vendor-opsys for the system where the application will run (defaults to the same value as --build)} build:build-alias => {a complete or partial cpu-vendor-opsys for the system where the application will be built (defaults to the result of running config.guess)} @@ -50,11 +50,11 @@ localstatedir: runstatedir: maintainer-mode=0 dependency-tracking=0 silent-rules=0 -}] +} # @check-feature name { script } # # defines feature '$name' to the return value of '$script', # which should be 1 if found or 0 if not found. Index: autosetup/tmake.tcl ================================================================== --- autosetup/tmake.tcl +++ autosetup/tmake.tcl @@ -9,11 +9,11 @@ # ## CONFIGURED - to indicate that the project is configured use system -module-options {} +options {} define CONFIGURED # @make-tmake-settings outfile patterns ... # Index: bin/os8-cp.in ================================================================== --- bin/os8-cp.in +++ bin/os8-cp.in @@ -1,14 +1,14 @@ -#!/usr/bin/env python +#!/usr/bin/env @PYCMD@ # -*- coding: utf-8 -*- ######################################################################## # Generalized facility to manipulate os8 device images from the POSIX # (host) side using OS/8 system programs under SIMH. # # See USAGE message below for details. # -# Copyright © 2018 by Bill Cattey and Warren Young +# Copyright © 2018-2019 by Bill Cattey and Warren Young # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, @@ -110,11 +110,11 @@ #### abort_prog ######################################################## # Print err_string and exit with -1 return status. def abort_prog (err_str): - print "Abort: " + err_str + print("Abort: " + err_str) sys.exit(-1) #### parse_attach ###################################################### # Parser for OS/8 attach spec. @@ -124,11 +124,11 @@ abort_prog ("Need unit number for: " + match.group(1) + ".") image_spec = [match.group(1), match.group(2), imagename] if match.group(3) == 's': if action_plan ["sys"] != None: - print ("Already specified system device. Ignoring sys mount of: " + imagename) + print("Already specified system device. Ignoring sys mount of: " + imagename) else: action_plan["sys"] = image_spec else: action_plan["mount"].append(image_spec) @@ -194,11 +194,11 @@ def parse_action_file(fname): try: manifest = open(fname, "r") except IOError: - print fname + " not found. Skipping." + print(fname + " not found. Skipping.") return None ioline_re = re.compile("(\S+)\s+(\S+)\s+(\S+)") action_plan = {} @@ -206,37 +206,38 @@ action_plan["mount"] = [] action_plan["copy"] = [] for line in manifest: ioline = line.strip() - if DEBUG: print "parse_action_file: ioline: " + ioline + if DEBUG: print("parse_action_file: ioline: " + ioline) if ioline == "": continue if ioline[0] == '#': continue # Allow comments m=re.match(ioline_re, ioline) if m== None: - print "Ignoring line: " + ioline + print("Ignoring line: " + ioline) continue option = m.group(1) source = m.group(2) destination = m.group(3) if option == "att": m = re.match(_dev_actfile_re, source) - if m== None: + if m == None: abort_prog ("Could not parse attach spec: " + source) parse_attach (action_plan, m, path_expand(destination)) else: if len(option) != 1: - print "Format options are only 1 letter in size. Ignoring line: " + ioline + print("Format options are only 1 letter in size. " + \ + "Ignoring line: " + ioline) elif option[0] not in _valid_pip_options: - print "Unrecognize option in line: " + ioline + print("Unrecognize option in line: " + ioline) elif source == None: - print "Null value of source. Ignoring line: " + ioline + print("Null value of source. Ignoring line: " + ioline) elif destination == None: - print "Null value of destination. Ignoring line: " + ioline + print("Null value of destination. Ignoring line: " + ioline) else: append_copy(action_plan, option, source, destination) return action_plan @@ -254,16 +255,16 @@ def is_directory(path): if DEBUG: "is_directory (" + path + ")" m = re.match(_os8_file_re, path) if m != None: - if DEBUG: print "OS/8 Match: DEV: " + m.group(1) + ", File: " + str(m.group(2)) + if DEBUG: print("OS/8 Match: DEV: " + m.group(1) + ", File: " + str(m.group(2))) if m.group(2) == None or m.group(2) == "": return True # Just a device so yes it's a directory. else: return False if has_os8_wildcards(path): - if DEBUG: print "Has wildcards." + if DEBUG: print("Has wildcards.") return False return os.path.isdir(path) #### has_os8_wildcards ################################################# @@ -286,11 +287,11 @@ for line in lines[1:]: # First line is our command. Skip it. line = line.strip() if line == "": continue m = re.match("(\S+)\s*\.(\S+)", line) if m == None: continue - # if DEBUG: print "file_list_from_expect: group 1: " + m.group(1) + ", group 2: " + m.group(2) + # if DEBUG: print("file_list_from_expect: group 1: " + m.group(1) + ", group 2: " + m.group(2)) fname = m.group(1) + "." + m.group(2) file_list.append(fname) return file_list @@ -310,11 +311,12 @@ if copy_type == "from": copy_type = "within" else: copy_type = "into" if "/" in destination: - print "append_copy, into: Illegal OS/8 file spec containing a slash:" + destination + print("append_copy, into: Illegal OS/8 file spec containing " + \ + "a slash:" + destination) sys.exit(-1) destination = destination.upper() if copy_type == "": abort_prog ("append_copy: No OS/8 file spec found with source: " + source + ", destination: " + destination) @@ -513,13 +515,13 @@ # First the simple bit set options if arg == "-d": DEBUG = True elif arg == "-h": if VERBOSE: - print VERBOSE_USAGE + print(VERBOSE_USAGE) else: - print USAGE + print(USAGE) sys.exit(0) elif arg == "-q": QUIET = True elif arg == "-v": VERBOSE = 1 @@ -526,12 +528,12 @@ # look for option args. elif arg in _arg_to_option: new_opt = _arg_to_option[arg] if mode_opt == new_opt: - print "Warning redundant reset of mode option to " + \ - _pip_option_info[new_opt] + print("Warning redundant reset of mode option to " + \ + _pip_option_info[new_opt]) mode_opt = new_opt # Not a simple bit set option. elif arg == "--action-file": @@ -553,29 +555,34 @@ parse_attach (action_plan, m, sys.argv[idx]) # Do file parser if we didn't get an OS/8 attach spec. else: - if DEBUG: print "File parsing of: " + arg + if DEBUG: print("File parsing of: " + arg) # Need to know if arg is Linux. If so, we need to do globbing. # If you want OS/8 globbing, specify a device to prevent globbing # from being run. m = re.match(_os8_file_re, arg) if m == None: # Yup, it's POSIX. Glob it. - if DEBUG: print arg + " is POSIX." + if DEBUG: print(arg + " is POSIX.") more_files = glob.glob(arg) if more_files == []: - if DEBUG: print "No more files in POSIX Glob. Our file is: " + arg + if DEBUG: + print("No more files in POSIX Glob. Our file is: " + arg) more_files.append(arg) # If file not found may be an OS/8 internal xfer. for new_file in more_files: if filespec_seen == 0: source = new_file first_mode = mode_opt - if DEBUG: print "Globber: Setting initial source: " + source + " and mode: " + first_mode + if DEBUG: + print("Globber: Setting initial source: " + source + \ + " and mode: " + first_mode) elif filespec_seen == 1: - if DEBUG: print "Globber: Setting initial destination: " + destination + if DEBUG: + print("Globber: Setting initial destination: " + \ + destination) destination = new_file else: if DEBUG: "Globber: Appending destination to list. New file is: " + new_file file_and_mode_list.append([mode_opt,destination]) destination = new_file @@ -582,14 +589,16 @@ filespec_seen += 1 else: if filespec_seen == 0: source = arg first_mode = mode_opt - if DEBUG: print "Setting initial source: " + source + " and mode: " + first_mode + if DEBUG: + print("Setting initial source: " + source + \ + " and mode: " + first_mode) elif filespec_seen == 1: destination = arg - if DEBUG: print "Setting initial destination: " + destination + if DEBUG: print("Setting initial destination: " + destination) else: file_and_mode_list.append([mode_opt, destination]) destination = arg if DEBUG: "Appending destination to list. New file is: " + new_file filespec_seen += 1 @@ -603,11 +612,11 @@ # Now it gets a little complicated... # If neither source nor destination is OS/8, pretend they both were OS/8 "DSK:" # If source is OS/8, and has OS/8 wild cards, the destination must be a directory. else: # If more than 2 files, the destination must be either an OS/8 device or a Linux directory. - if DEBUG: print "Destination: " + destination + if DEBUG: print("Destination: " + destination) if filespec_seen > 2 and is_directory(destination) == False: abort_prog ("Destination must be a Linux directory or OS/8 device for multiple source files.") m1 = re.match(_os8_file_re, source) m2 = re.match(_os8_file_re, destination) @@ -638,20 +647,20 @@ def main (): action_plan = parse_args() if action_plan == None: abort_prog ("No action plan was parsed.") - if DEBUG: print str(action_plan) + if DEBUG: print(str(action_plan)) # Create the SIMH child instance and tell it where to send log output try: s = simh (dirs.build, True) except (RuntimeError) as e: - print "Could not start simulator: " + e.message + '!' + print("Could not start simulator: " + e.message + '!') exit (1) - # s.set_logfile (os.fdopen (sys.stdout.fileno (), 'w', 0)) - s.set_logfile (open ("logfile.txt", 'w')) + # s.set_logfile (os.fdopen (sys.stdout.fileno (), 'wb', 0)) + s.set_logfile (open ("logfile.txt", 'wb')) if VERBOSE: s.verbose = True # Perform sys attach att_spec = action_plan["sys"] if att_spec == None: att_spec = _default_att_spec @@ -658,42 +667,48 @@ simh_boot_dev = att_spec[0] + att_spec[1] # Compose simh dev from name and unit. imagename = att_spec[2] if not os.path.exists (imagename): abort_prog ("Requested boot image file: " + imagename + " not found.") if VERBOSE or DEBUG: - print "Attaching " + simh_boot_dev + " to " + imagename + print("Attaching " + simh_boot_dev + " to " + imagename) s.send_cmd ("att " + simh_boot_dev + " " + imagename) images_to_zero = [] # Attach other mounts for att_spec in action_plan["mount"]: simh_dev = att_spec[0] + att_spec[1] # Compose simh dev from name and unit. imagename = att_spec[2] if os.path.exists (imagename): - if VERBOSE or DEBUG: print "Modifying existing " + simh_dev + " image " + imagename + if VERBOSE or DEBUG: + print("Modifying existing " + simh_dev + " image " + imagename) else: - if VERBOSE or DEBUG: print "Will create a new image file named: " + imagename + if VERBOSE or DEBUG: + print("Will create a new image file named: " + imagename) # Save this att_spec so we can zero it later. images_to_zero.append (att_spec) if VERBOSE or DEBUG: - print "Attaching " + simh_dev + " to " + imagename + print("Attaching " + simh_dev + " to " + imagename) s.send_cmd ("att " + simh_dev + " " + imagename) - if VERBOSE or DEBUG: print "Booting " + simh_boot_dev + "..." + if VERBOSE or DEBUG: print("Booting " + simh_boot_dev + "...") s.send_cmd ("boot " + simh_boot_dev) for att_spec in images_to_zero: os8dev = _os8_from_simh_dev[att_spec[0]] if os8dev in _os8_partitions: for partition in _os8_partitions[os8dev]: os8name = os8dev + partition + att_spec[1] + ":" - if VERBOSE or DEBUG: print "Initializing directory of " + os8name + " in " + imagename + if VERBOSE or DEBUG: + print("Initializing directory of " + os8name + " in " + \ + imagename) s.os8_send_cmd ('\\.', "ZERO " + os8name) else: os8name = os8dev + att_spec[1] + ":" - if VERBOSE or DEBUG: print "Initializing directory of " + os8name + " in " + imagename + if VERBOSE or DEBUG: + print("Initializing directory of " + os8name + " in " + \ + imagename) s.os8_send_cmd ('\\.', "ZERO " + os8name) # Perform copy operations for do_copy in action_plan["copy"]: mode_opt = do_copy[0] @@ -703,11 +718,13 @@ if mode_opt in _option_to_pip: pip_option = _option_to_pip[mode_opt] else: abort_prog ("Unrecognized mode option: " + mode_opt) - if DEBUG: print "Source: " + source + ", Destination: " + destination + ", Mode: " + mode_opt + "." + if DEBUG: + print("Source: " + source + ", Destination: " + destination + \ + ", Mode: " + mode_opt + ".") # Is this "from" OS/8 to POSIX, "into" OS/8 from POSIX or "within" OS/8? # "into" -- Attach source to simh ptr # If we are operating "from" and source has wild cards, # Use DIRECT to create list of files. # "from" -- Attach destination to ptp. We've already done POSIX globing. @@ -717,48 +734,50 @@ s.os8_pip_to(source, destination, pip_option) elif copy_type == "from": if has_os8_wildcards(source): # Split off device from source: os8dev = source[0:source.index(":")+1] - if DEBUG: print "Wild card dev: " + os8dev + if DEBUG: print("Wild card dev: " + os8dev) # Use OS/8 Direct to enumerate our input files. - if DEBUG: print "Calling OS/8 DIRECT on wild card filespec: " + source + if DEBUG: + print("Calling OS/8 DIRECT on wild card filespec: " + source) s.os8_send_cmd ('\\.', "DIR " + source + "/F=1") # Now harvest direct output. One file per line. Ignore blank lines. # Maybe parse the FREE BLOCKS Output. # Done when we see a dot. s._child.expect("\d+\s+FREE BLOCKS") files = file_list_from_expect(s._child.before) for filename in files: if VERBOSE or DEBUG: - print "Wildcard call os8_pip_from: copy from: {" + os8dev + "}{" + filename + "}" + \ - " to: " + destination + ", mode: " + pip_option + print("Wildcard call os8_pip_from: copy from: " + \ + "{" + os8dev + "}{" + filename + "}" + \ + " to: " + destination + ", mode: " + pip_option) s.os8_pip_from(os8dev + filename, destination, pip_option) else: if VERBOSE or DEBUG: - print "Call os8_pip_from: copy from: " + source + " to " + destination + \ - ", mode: " + pip_option + print("Call os8_pip_from: copy from: " + source + " to " + \ + destination + ", mode: " + pip_option) s.os8_pip_from(source, destination, pip_option) elif copy_type == "within": if VERBOSE or DEBUG: - print "Call COPY of: " + source + " to " + destination + print("Call COPY of: " + source + " to " + destination) s.os8_send_cmd ('\\.', "COPY " + destination + "< " + source) else: abort_prog ("Unrecognized copy type: " + copy_type) # Should never happen. # Detach all mounts and then sys. s.back_to_cmd ('\\.') for att_spec in action_plan["mount"]: simh_dev = att_spec[0] + att_spec[1] # Compose simh dev from name and unit. if VERBOSE or DEBUG: - print "Detaching " + simh_dev + print("Detaching " + simh_dev) s.send_cmd ("det " + simh_dev) if VERBOSE or DEBUG: - print "Detaching " + simh_boot_dev + print("Detaching " + simh_boot_dev) s.send_cmd ("det " + simh_boot_dev) # And shut down the simulator. if VERBOSE or DEBUG: - print "Quitting simh." + print("Quitting simh.") s.send_cmd ('quit') if __name__ == "__main__": main() DELETED bin/os8-run Index: bin/os8-run ================================================================== --- bin/os8-run +++ /dev/null @@ -1,228 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -######################################################################## -# Script runner for OS/8 under SIMH. -# The library module os8script.py does the heavy lifting. -# -# See USAGE message below for details. -# -# Copyright © 2018 by Bill Cattey and Warren Young -# -# Permission is hereby granted, free of charge, to any person obtaining -# a copy of this software and associated documentation files (the -# "Software"), to deal in the Software without restriction, including -# without limitation the rights to use, copy, modify, merge, publish, -# distribute, sublicense, and/or sell copies of the Software, and to -# permit persons to whom the Software is furnished to do so, subject to -# the following conditions: -# -# The above copyright notice and this permission notice shall be -# included in all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -# IN NO EVENT SHALL THE AUTHORS LISTED ABOVE BE LIABLE FOR ANY CLAIM, -# DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT -# OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE -# OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -# -# Except as contained in this notice, the names of the authors above -# shall not be used in advertising or otherwise to promote the sale, -# use or other dealings in this Software without prior written -# authorization from those authors. -######################################################################## - -# Bring in just the basics so we can bring in our local modules -import os -import sys - -sys.path.insert (0, os.path.dirname (__file__) + '/../lib') -sys.path.insert (0, os.getcwd () + '/lib') - -# Remaining Python core modules -import subprocess -import string -import re -import shutil -import argparse -from itertools import chain - -# Our local modules -from pidp8i import * -from simh import * -from os8script import * - - -#### GLOBALS AND CONSTANTS ############################################# - -DEBUG = False -VERBOSE = False -VERY_VERBOSE = False -QUIET = False -SCRIPT_FILE = "" - -USAGE = "usage: " + os.path.basename (__file__) + \ - " [-h] [-d] [-v] [-vv] [--enable enable_option] ... \n\t[--disable disable_option] ... " + \ - "\n \tscript [script] ... " - -_en_dis_arg_re = re.compile("^(enable|disable)_(\S+)$") - - -def opt_set(en_dis, option, options_enabled, options_disabled): - if option == None or en_dis == None: return - # print en_dis + " " + option - if en_dis == "enable": - if option not in options_enabled: - options_enabled.append(option) - if option in options_disabled: - options_disabled.remove(option) - elif en_dis == "disable": - if option not in options_disabled: - options_disabled.append(option) - if option in options_enabled: - options_enabled.remove(option) - else: return - - -def add_bool (self, *args, **kwargs): - kwargs['action'] = 'store_true' - kwargs['default'] = False - self.add_argument (*args, **kwargs) - - -#### parse_args ######################################################## - -def parse_args (script_files, options_enabled, options_disabled): - global DEBUG - global VERBOSE - global VERY_VERBOSE - global USAGE - - enable_usage = "" - disable_usage = "" - - idx = 1 - numargs = len(sys.argv) - - if numargs < 2: - print USAGE - sys.exit(-1) - - # Add arguments corresponding to --*-os8-* configure script options - max_obn_len = 0 - for obn, vals in os8opts.opts.iteritems(): - max_obn_len = max(max_obn_len, len(obn)) - for obn, vals in os8opts.opts.iteritems(): - if vals[0]: - # Enable option - pad_str = (max_obn_len - len (obn)) * " " - new_usage = " " + obn + ": " + pad_str + vals[1] + "\n" - disable_usage += new_usage - else: - # Disable option - pad_str = (max_obn_len - len (obn)) * " " - new_usage = " " + obn + ": " + pad_str + vals[1] + "\n" - enable_usage += new_usage - - if enable_usage != "": - USAGE += "\n Known enable options: \n" - USAGE += enable_usage - - if disable_usage != "": - USAGE += "\n Known disable options: \n" - USAGE += disable_usage - - while idx < numargs: - arg = sys.argv[idx] - # print "idx: " + str(idx) + ", arg: " + arg - # print "Files: " + str(script_files) - # print "Options: " + str(options_enabled) - if arg == "-d" or arg == "--debug": - DEBUG = True - elif arg == "-h" or arg == "--help": - print USAGE - sys.exit(0) - elif arg == "-v" or arg == "--verbose": - VERBOSE = 1 - elif arg == "-vv" or arg == "--very-verbose": - VERY_VERBOSE = 1 - elif arg == "--enable": - idx +=1 - if idx == numargs: - print "expecting an option but got none." - else: - option = sys.argv[idx] - # Only add the option once - if option not in options_enabled: options_enabled.append(option) - elif arg == "--disable": - idx +=1 - if idx == numargs: - print "expecting an option but got none." - else: - option = sys.argv[idx] - # Only add the option once - if option not in options_disabled: options_disabled.append(option) - else: - script_files.append(arg) - idx += 1 - - - -#### main ############################################################## -# Program entry point. Parses the command line and drives the above. - -def main (): - script_files = [] - options_enabled = [] - options_disabled = [] - - parse_args (script_files, options_enabled, options_disabled) - if len(script_files) == 0: - print "Need a script file to run." - sys.exit(-1) - - if VERBOSE: - print "script_files: " + str(script_files) - if DEBUG: - print "options_enabled" + str(options_enabled) - print "options_disabled" + str(options_disabled) - - # Append SIMH and OS/8 output to a file by default. - # - # We append because we're run twice in each test directory via the - # os8-sys Makefile target called by test-os8-run, once for the "dist" - # media and once for the actual RK05 bootable media. So, the second - # run must append its logs to the first run's log file. - # - # Send the log info to the console instead of the progress messages if - # -v was given. See https://stackoverflow.com/questions/21239338 - s = simh (dirs.build, True) - if VERBOSE: s.verbose = True - s.set_logfile (open (dirs.log + 'os8-run' + '.log', 'a') \ - if not VERY_VERBOSE else os.fdopen (sys.stdout.fileno (), 'w', 0)) - - os8 = os8script (s, options_enabled, options_disabled, verbose=VERBOSE, debug=DEBUG) - - for script_file in script_files: - if VERBOSE: - print os.path.basename (__file__) + " -- Language Version: " + os8.lang_version - - print "Running script file: " + script_file - os8.run_script_file (script_file) - - # After all scripts are done, we remove any scratch files, - # detach any mounted devices, and shut down simh gracefully. - - for filename in os8.scratch_list: - if os8.verbose: print "Deleting scratch_copy: " + filename - os.remove(filename) - - s.send_cmd ("detach all") - - s.quit () - if VERBOSE: print "Done!" - - -if __name__ == "__main__": - main() ADDED bin/os8-run.in Index: bin/os8-run.in ================================================================== --- /dev/null +++ bin/os8-run.in @@ -0,0 +1,228 @@ +#!/usr/bin/env @PYCMD@ +# -*- coding: utf-8 -*- +######################################################################## +# Script runner for OS/8 under SIMH. +# The library module os8script.py does the heavy lifting. +# +# See USAGE message below for details. +# +# Copyright © 2018-2019 by Bill Cattey and Warren Young +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +# IN NO EVENT SHALL THE AUTHORS LISTED ABOVE BE LIABLE FOR ANY CLAIM, +# DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT +# OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE +# OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# +# Except as contained in this notice, the names of the authors above +# shall not be used in advertising or otherwise to promote the sale, +# use or other dealings in this Software without prior written +# authorization from those authors. +######################################################################## + +# Bring in just the basics so we can bring in our local modules +import os +import sys + +sys.path.insert (0, os.path.dirname (__file__) + '/../lib') +sys.path.insert (0, os.getcwd () + '/lib') + +# Remaining Python core modules +import subprocess +import string +import re +import shutil +import argparse +from itertools import chain + +# Our local modules +from pidp8i import * +from simh import * +from os8script import * + + +#### GLOBALS AND CONSTANTS ############################################# + +DEBUG = False +VERBOSE = False +VERY_VERBOSE = False +QUIET = False +SCRIPT_FILE = "" + +USAGE = "usage: " + os.path.basename (__file__) + \ + " [-h] [-d] [-v] [-vv] [--enable enable_option] ... \n\t[--disable disable_option] ... " + \ + "\n \tscript [script] ... " + +_en_dis_arg_re = re.compile("^(enable|disable)_(\S+)$") + + +def opt_set(en_dis, option, options_enabled, options_disabled): + if option == None or en_dis == None: return + # print(en_dis + " " + option) + if en_dis == "enable": + if option not in options_enabled: + options_enabled.append(option) + if option in options_disabled: + options_disabled.remove(option) + elif en_dis == "disable": + if option not in options_disabled: + options_disabled.append(option) + if option in options_enabled: + options_enabled.remove(option) + else: return + + +def add_bool (self, *args, **kwargs): + kwargs['action'] = 'store_true' + kwargs['default'] = False + self.add_argument (*args, **kwargs) + + +#### parse_args ######################################################## + +def parse_args (script_files, options_enabled, options_disabled): + global DEBUG + global VERBOSE + global VERY_VERBOSE + global USAGE + + enable_usage = "" + disable_usage = "" + + idx = 1 + numargs = len(sys.argv) + + if numargs < 2: + print(USAGE) + sys.exit(-1) + + # Add arguments corresponding to --*-os8-* configure script options + max_obn_len = 0 + for obn, vals in os8opts.opts.items(): + max_obn_len = max(max_obn_len, len(obn)) + for obn, vals in os8opts.opts.items(): + if vals[0]: + # Enable option + pad_str = (max_obn_len - len (obn)) * " " + new_usage = " " + obn + ": " + pad_str + vals[1] + "\n" + disable_usage += new_usage + else: + # Disable option + pad_str = (max_obn_len - len (obn)) * " " + new_usage = " " + obn + ": " + pad_str + vals[1] + "\n" + enable_usage += new_usage + + if enable_usage != "": + USAGE += "\n Known enable options: \n" + USAGE += enable_usage + + if disable_usage != "": + USAGE += "\n Known disable options: \n" + USAGE += disable_usage + + while idx < numargs: + arg = sys.argv[idx] + # print("idx: " + str(idx) + ", arg: " + arg) + # print("Files: " + str(script_files)) + # print("Options: " + str(options_enabled)) + if arg == "-d" or arg == "--debug": + DEBUG = True + elif arg == "-h" or arg == "--help": + print(USAGE) + sys.exit(0) + elif arg == "-v" or arg == "--verbose": + VERBOSE = 1 + elif arg == "-vv" or arg == "--very-verbose": + VERY_VERBOSE = 1 + elif arg == "--enable": + idx +=1 + if idx == numargs: + print("expecting an option but got none.") + else: + option = sys.argv[idx] + # Only add the option once + if option not in options_enabled: options_enabled.append(option) + elif arg == "--disable": + idx +=1 + if idx == numargs: + print("expecting an option but got none.") + else: + option = sys.argv[idx] + # Only add the option once + if option not in options_disabled: options_disabled.append(option) + else: + script_files.append(arg) + idx += 1 + + + +#### main ############################################################## +# Program entry point. Parses the command line and drives the above. + +def main (): + script_files = [] + options_enabled = [] + options_disabled = [] + + parse_args (script_files, options_enabled, options_disabled) + if len(script_files) == 0: + print("Need a script file to run.") + sys.exit(-1) + + if VERBOSE: + print("script_files: " + str(script_files)) + if DEBUG: + print("options_enabled" + str(options_enabled)) + print("options_disabled" + str(options_disabled)) + + # Append SIMH and OS/8 output to a file by default. + # + # We append because we're run twice in each test directory via the + # os8-sys Makefile target called by test-os8-run, once for the "dist" + # media and once for the actual RK05 bootable media. So, the second + # run must append its logs to the first run's log file. + # + # Send the log info to the console instead of the progress messages if + # -v was given. See https://stackoverflow.com/questions/21239338 + s = simh (dirs.build, True) + if VERBOSE: s.verbose = True + s.set_logfile (open (dirs.log + 'os8-run' + '.log', 'ab') \ + if not VERY_VERBOSE else os.fdopen (sys.stdout.fileno (), 'wb', 0)) + + os8 = os8script (s, options_enabled, options_disabled, verbose=VERBOSE, debug=DEBUG) + + for script_file in script_files: + if VERBOSE: + print(os.path.basename (__file__) + " -- Language Version: " + os8.lang_version) + + print("Running script file: " + script_file) + os8.run_script_file (script_file) + + # After all scripts are done, we remove any scratch files, + # detach any mounted devices, and shut down simh gracefully. + + for filename in os8.scratch_list: + if os8.verbose: print("Deleting scratch_copy: " + filename) + os.remove(filename) + + s.send_cmd ("detach all") + + s.quit () + if VERBOSE: print("Done!") + + +if __name__ == "__main__": + main() Index: bin/pidp8i.in ================================================================== --- bin/pidp8i.in +++ bin/pidp8i.in @@ -1,11 +1,11 @@ #!/bin/bash ######################################################################## # pidp8i.in - Collection of scriptlets for dealing with the pidp8i # systemd user service and its associated screen manager session. # -# Copyright © 2015-2019 Oscar Vermeulen and Warren Young +# Copyright © 2015-2020 Oscar Vermeulen and Warren Young # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, @@ -40,23 +40,34 @@ if [ -e "$prefix/etc/pidp8i.rc" ] then . "$prefix/etc/pidp8i.rc" fi [ -n "$SCREEN_MANAGER" ] || SCREEN_MANAGER=screen +if [ ! -t 1 ] && [ "$SCREEN_MANAGER" = "none" ] +then + echo "The 'none' screen manager mode is meant for interactive use only!" + exit 1 +fi is_running() { - if [ "$SCREEN_MANAGER" = "tmux" ] + if [ "$SCREEN_MANAGER" = "none" ] + then + return 1 + elif [ "$SCREEN_MANAGER" = "tmux" ] then tmux has-session -t pidp8i 2>/dev/null else procs=`screen -list pidp8i | $ggrep -Pc '\d\.pidp8i'` test -n "$procs" && test $procs -gt 0 && return 0 || return 1 fi } sim_child_pid() { - if [ "$SCREEN_MANAGER" = "tmux" ] + if [ "$SCREEN_MANAGER" = "none" ] + then + echo "" + elif [ "$SCREEN_MANAGER" = "tmux" ] then tmux list-sessions -F '#{session_name} #{pid}' | awk '/^pidp8i / {print $2}' else screen -ls pidp8i | grep -Eo '[[:digit:]]+\.pidp8i' | grep -Eo '^[[:digit:]]+' fi @@ -63,12 +74,15 @@ } # Were we given a command line argument? if [ -z "$1" ] then - # No, so just try to attach to the running simulator - if is_running + # No, so try to run or attach to the running simulator + if [ "$SCREEN_MANAGER" = "none" ] + then + exec $0 start + elif is_running then echo Joining simulator session already in progress... if [ "$SCREEN_MANAGER" = "tmux" ] then exec tmux attach-session -d -t pidp8i @@ -145,20 +159,31 @@ # commands involving file paths. This default is chosen because it # satisfies both criteria. # If you change the default here, change that script as well. set -e cd "$prefix/share/media" - if [ "$SCREEN_MANAGER" = "tmux" ] + + # Start the simulator + if [ "$SCREEN_MANAGER" = "none" ] + then + exec "$sim" "$bscript" + elif [ "$SCREEN_MANAGER" = "tmux" ] then tmux new-session -s pidp8i -d "$sim" "$bscript" else screen -dm -S pidp8i "$sim" "$bscript" fi - if [ -x $systemctl ] + + # Tell systemd where to find the backgrounded simulator so it can + # stop it via systemctl. Skipped on non-systemd systems and where + # SCREEN_MANAGER=none. + scpid=$(sim_child_pid) + if [ -x $systemctl ] && [ -n "$scpid" ] && [ $scpid -gt 0 ] then - systemd-notify --ready --pid=$(sim_child_pid) + systemd-notify --ready --pid=$scpid fi + exit 0 elif [ "$verb" = "stop" ] then # Someone (maybe the user via "pidp8i stop", maybe systemd via # "systemctl pidp8i stop") is telling us to stop the background @@ -177,21 +202,21 @@ echo -n "Stopping $sim simulator..." for sig in TERM KILL ; do pkill -$sig $sim ; sleep 1 ; done # The screen manager might still be running despite its # only child dying. Nuke it hard if so. - if is_running - then - if [ "$SCREEN_MANAGER" = "tmux" ] - then - tmux kill-session -t pidp8i - else - screen -S pidp8i -X quit - fi + if ! is_running || [ "$SCREEN_MANAGER" = "none" ] + then + echo "The PiDP-8/I simulator is stopped." + elif [ "$SCREEN_MANAGER" = "tmux" ] + then + tmux kill-session -t pidp8i + else + screen -S pidp8i -X quit fi exit 0 fi done exit 1 fi DELETED bin/teco-pi-demo Index: bin/teco-pi-demo ================================================================== --- bin/teco-pi-demo +++ /dev/null @@ -1,185 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -######################################################################## -# teco-pi-demo - Starts the simulator with the OS/8, sends one of the -# famous TECO "calculate pi" program to it, and starts it running at -# a very slow rate of speed to act as a blinkenlights demo. -# -# Copyright © 2017-2019 by Warren Young. -# -# Permission is hereby granted, free of charge, to any person obtaining -# a copy of this software and associated documentation files (the -# "Software"), to deal in the Software without restriction, including -# without limitation the rights to use, copy, modify, merge, publish, -# distribute, sublicense, and/or sell copies of the Software, and to -# permit persons to whom the Software is furnished to do so, subject to -# the following conditions: -# -# The above copyright notice and this permission notice shall be -# included in all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -# IN NO EVENT SHALL THE AUTHORS LISTED ABOVE BE LIABLE FOR ANY CLAIM, -# DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT -# OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE -# OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -# -# Except as contained in this notice, the names of the authors above -# shall not be used in advertising or otherwise to promote the sale, -# use or other dealings in this Software without prior written -# authorization from those authors. -######################################################################## - -# Bring in just the basics so we can bring in our local modules -import os -import sys -sys.path.insert (0, os.path.dirname (__file__) + '/../lib') -sys.path.insert (0, os.getcwd () + '/lib') - -# Other core modules we need -from datetime import datetime -import time - -# Our local modules -from pidp8i import * -from simh import * - - -#### main ############################################################## - -def main (): - # Check for command line flags - benchmark = len (sys.argv) > 1 and sys.argv[1] == '-b' - - # Create the SIMH child instance and tell it where to send log output - try: - s = simh (dirs.build) - except (RuntimeError) as e: - print "Could not start simulator: " + e.message + '!' - exit (1) - s.set_logfile (os.fdopen (sys.stdout.fileno (), 'w', 0)) - - # Find and boot the built OS/8 bin disk - rk = os.path.join (dirs.os8mo, 'v3d.rk05') - if not os.path.isfile (rk): - print "Could not find " + rk + "; OS/8 media not yet built?" - exit (1) - print "Booting " + rk + "..." - s.send_cmd ("att rk0 " + rk) - s.send_cmd ("boot rk0") - - # Start TECO8 in the simulator under OS/8 - s.os8_send_cmd ('\\.', "R TECO") - - # The macro comes from http://www.iwriteiam.nl/HaPi_TECO_macro.html - # and it was created by Stanley Rabinowitz. - # - # The 248 preceding "UN" in the first line of the macro is the number - # of digits of pi to calculate. That value was reached by experiment - # as the largest value that runs without crashing TECO with a - # - # ?MEM STORAGE CAPACITY EXCEEDED - # - # error. You can see that by increasing the value below, commenting - # out the throttle setting below, and running the demo. On a Pi 3, it - # should take a bit over an hour to complete, if it doesn't error out. - # - # With the simulator throttled, generating 248 digits takes 17 years! - # - # That is based on generating 1 digit every ~16 seconds on a Pi 3 when - # running unthrottled, roughly 8 MIPS. When throttled to 59 IPS — or - # 17ms per instruction, as below — you multiply the seconds by the - # factor 8 MIPS / 59 IPS = ~136000, giving about 2.2 million seconds - # per digit. Multiplying that by 248 gives ~17 years. - macro = [ - 'GZ0J\UNQN"E 248UN \' BUH BUV HK', - 'QN< J BUQ QN*10/3UI', - 'QI< \+2*10+(QQ*QI)UA B L K QI*2-1UJ QA/QJUQ', - 'QA-(QQ*QJ)-2\ 10@I// -1%I >', - 'QQ/10UT QH+QT+48UW QW-58"E 48UW %V \' QV"N QV^T \' QWUV QQ-(QT*10)UH >', - 'QV^T @^A/', - '/HKEX', - ] - - # First and last lines are handled specially, so slice them off. - first = macro.pop (0) - last = macro.pop () - - # Send the first line of the macro; implicitly awaits 1st TECO prompt - s.os8_send_cmd ('\\*', first) - - # Blindly send core lines of the macro; TECO gives no prompts for 'em. - for line in macro: - s.os8_send_line (line) - - # Send last line of macro sans CR, followed by two Esc characters to - # start it running. - s.os8_send_str (last) # not os8_send_line! - s.os8_send_ctrl ('[') - s.os8_send_ctrl ('[') - - if benchmark: - # Run demo long enough to get a good sense of the simulator's - # execution rate while unthrottled on this host hardware. If - # you don't run it long enough, the IPS value is untrustworthy. - try: - s.spin (10) - except pexpect.TIMEOUT: - # Explicitly shift back from OS/8 context to SIMH command context. - # We cannot rely on class simh to do this automatically because it - # expects to see a . prompt from the prior command, but we're - # still in TECO here, so we must be explicit. - s.os8_send_ctrl ('e') - - # Ask the simulator what IPS rate we ran that benchmark at. - s.send_cmd ('show clocks') - line = s.read_tail ('Execution Rate:') - curr_ips = int (line.strip().replace(',', '').split(' ')[0]) - pf = open ('lib/pidp8i/ips.py', 'a') - pf.write ('current = ' + str (curr_ips) + ' # ' + \ - str (datetime.today ()) + '\n') - pf.close () - s.send_cmd ('quit') - pdp_ratio = float (curr_ips) / ips.pdp8i - rpi_ratio = float (curr_ips) / ips.raspberry_pi_b_plus - print "\nYour system is " + format (rpi_ratio, '.1f') + \ - " times faster than a Raspberry Pi Model B+" - print "or " + format (pdp_ratio, '.1f') + \ - " times faster than a PDP-8/I.\n" - else: - # Normal mode. Tell SIMH and throttle down to a rate suitable for a - # blinkenlights demo. 1/17 means SIMH runs one instruction then - # waits for 17ms, yielding ~59 IPS. - time.sleep (0.02) # FIXME: simulator chokes on 'cont' without this - s.os8_send_ctrl ('e') # same justification as above - s.send_cmd ('set throttle 1/17') - - # You can't hit Ctrl-E while running this script in the foreground - # since pexpect takes over stdio. Therefore, if you want to be able - # to send commands to the simulator while the demo is running, - # uncomment the line below, which will let you send commands to the - # simulator via telnet. From another terminal or SSH session: - # - # $ telnet localhost 3141 - # - # or from a remote machine: - # - # $ telnet 192.168.1.2 3141 - # - # It's disabled by default because SIMH can't be made to listen only - # on localhost, so doing this may be a security risk. SIMH disables - # obviously-unsafe commands like ! on the remote console, but it is - # possible some mischief may be possible via this path anyway. It - # could be used to exfiltrate a sensitive file via ATTACH, for one - # thing. For another, it's a potential DoS vector. - #s.send_cmd ('set remote telnet=3141') - - # Let it run. Never exits. - s.send_cmd ('cont') - s.spin () - - -if __name__ == "__main__": - main() ADDED bin/teco-pi-demo.in Index: bin/teco-pi-demo.in ================================================================== --- /dev/null +++ bin/teco-pi-demo.in @@ -0,0 +1,185 @@ +#!/usr/bin/env @PYCMD@ +# -*- coding: utf-8 -*- +######################################################################## +# teco-pi-demo - Starts the simulator with the OS/8, sends one of the +# famous TECO "calculate pi" program to it, and starts it running at +# a very slow rate of speed to act as a blinkenlights demo. +# +# Copyright © 2017-2019 by Warren Young. +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +# IN NO EVENT SHALL THE AUTHORS LISTED ABOVE BE LIABLE FOR ANY CLAIM, +# DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT +# OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE +# OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# +# Except as contained in this notice, the names of the authors above +# shall not be used in advertising or otherwise to promote the sale, +# use or other dealings in this Software without prior written +# authorization from those authors. +######################################################################## + +# Bring in just the basics so we can bring in our local modules +import os +import sys +sys.path.insert (0, os.path.dirname (__file__) + '/../lib') +sys.path.insert (0, os.getcwd () + '/lib') + +# Other core modules we need +from datetime import datetime +import time + +# Our local modules +from pidp8i import * +from simh import * + + +#### main ############################################################## + +def main (): + # Check for command line flags + benchmark = len (sys.argv) > 1 and sys.argv[1] == '-b' + + # Create the SIMH child instance and tell it where to send log output + try: + s = simh (dirs.build) + except (RuntimeError) as e: + print "Could not start simulator: " + e.message + '!' + exit (1) + s.set_logfile (os.fdopen (sys.stdout.fileno (), 'wb', 0)) + + # Find and boot the built OS/8 bin disk + rk = os.path.join (dirs.os8mo, 'v3d.rk05') + if not os.path.isfile (rk): + print "Could not find " + rk + "; OS/8 media not yet built?" + exit (1) + print "Booting " + rk + "..." + s.send_cmd ("att rk0 " + rk) + s.send_cmd ("boot rk0") + + # Start TECO8 in the simulator under OS/8 + s.os8_send_cmd ('\\.', "R TECO") + + # The macro comes from http://www.iwriteiam.nl/HaPi_TECO_macro.html + # and it was created by Stanley Rabinowitz. + # + # The 248 preceding "UN" in the first line of the macro is the number + # of digits of pi to calculate. That value was reached by experiment + # as the largest value that runs without crashing TECO with a + # + # ?MEM STORAGE CAPACITY EXCEEDED + # + # error. You can see that by increasing the value below, commenting + # out the throttle setting below, and running the demo. On a Pi 3, it + # should take a bit over an hour to complete, if it doesn't error out. + # + # With the simulator throttled, generating 248 digits takes 17 years! + # + # That is based on generating 1 digit every ~16 seconds on a Pi 3 when + # running unthrottled, roughly 8 MIPS. When throttled to 59 IPS — or + # 17ms per instruction, as below — you multiply the seconds by the + # factor 8 MIPS / 59 IPS = ~136000, giving about 2.2 million seconds + # per digit. Multiplying that by 248 gives ~17 years. + macro = [ + 'GZ0J\UNQN"E 248UN \' BUH BUV HK', + 'QN< J BUQ QN*10/3UI', + 'QI< \+2*10+(QQ*QI)UA B L K QI*2-1UJ QA/QJUQ', + 'QA-(QQ*QJ)-2\ 10@I// -1%I >', + 'QQ/10UT QH+QT+48UW QW-58"E 48UW %V \' QV"N QV^T \' QWUV QQ-(QT*10)UH >', + 'QV^T @^A/', + '/HKEX', + ] + + # First and last lines are handled specially, so slice them off. + first = macro.pop (0) + last = macro.pop () + + # Send the first line of the macro; implicitly awaits 1st TECO prompt + s.os8_send_cmd ('\\*', first) + + # Blindly send core lines of the macro; TECO gives no prompts for 'em. + for line in macro: + s.os8_send_line (line) + + # Send last line of macro sans CR, followed by two Esc characters to + # start it running. + s.os8_send_str (last) # not os8_send_line! + s.os8_send_ctrl ('[') + s.os8_send_ctrl ('[') + + if benchmark: + # Run demo long enough to get a good sense of the simulator's + # execution rate while unthrottled on this host hardware. If + # you don't run it long enough, the IPS value is untrustworthy. + try: + s.spin (10) + except pexpect.TIMEOUT: + # Explicitly shift back from OS/8 context to SIMH command context. + # We cannot rely on class simh to do this automatically because it + # expects to see a . prompt from the prior command, but we're + # still in TECO here, so we must be explicit. + s.os8_send_ctrl ('e') + + # Ask the simulator what IPS rate we ran that benchmark at. + s.send_cmd ('show clocks') + line = s.read_tail ('Execution Rate:') + curr_ips = int (line.strip().replace(',', '').split(' ')[0]) + pf = open ('lib/pidp8i/ips.py', 'a') + pf.write ('current = ' + str (curr_ips) + ' # ' + \ + str (datetime.today ()) + '\n') + pf.close () + s.send_cmd ('quit') + pdp_ratio = float (curr_ips) / ips.pdp8i + rpi_ratio = float (curr_ips) / ips.raspberry_pi_b_plus + print "\nYour system is " + format (rpi_ratio, '.1f') + \ + " times faster than a Raspberry Pi Model B+" + print "or " + format (pdp_ratio, '.1f') + \ + " times faster than a PDP-8/I.\n" + else: + # Normal mode. Tell SIMH and throttle down to a rate suitable for a + # blinkenlights demo. 1/17 means SIMH runs one instruction then + # waits for 17ms, yielding ~59 IPS. + time.sleep (0.02) # FIXME: simulator chokes on 'cont' without this + s.os8_send_ctrl ('e') # same justification as above + s.send_cmd ('set throttle 1/17') + + # You can't hit Ctrl-E while running this script in the foreground + # since pexpect takes over stdio. Therefore, if you want to be able + # to send commands to the simulator while the demo is running, + # uncomment the line below, which will let you send commands to the + # simulator via telnet. From another terminal or SSH session: + # + # $ telnet localhost 3141 + # + # or from a remote machine: + # + # $ telnet 192.168.1.2 3141 + # + # It's disabled by default because SIMH can't be made to listen only + # on localhost, so doing this may be a security risk. SIMH disables + # obviously-unsafe commands like ! on the remote console, but it is + # possible some mischief may be possible via this path anyway. It + # could be used to exfiltrate a sensitive file via ATTACH, for one + # thing. For another, it's a potential DoS vector. + #s.send_cmd ('set remote telnet=3141') + + # Let it run. Never exits. + s.send_cmd ('cont') + s.spin () + + +if __name__ == "__main__": + main() DELETED bin/txt2os8 Index: bin/txt2os8 ================================================================== --- bin/txt2os8 +++ /dev/null @@ -1,151 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -######################################################################## -# Create a tu56 or rk05 image and fill it with ASCII files, i.e. source code. -# -# It is intended to be be called manually when we have a POSIX -# directory full of ASCII files we want to bulk-copy into SIMH. -# -# The argument is taken both as the name of the image to create -# and the list of files to copy in. -# -# For now, it takes all input and produces all output in the -# current working directory. -# -# IMPORTANT: Currently all input files are mindlessly passed through -# txt2ptp which transforms POSIX ASCII files to OS/8 ASCII files. -# It WILL mutilate non-ASCII files. -# -# This program is based on the old cc8-tu56-update program, last -# shipped by this project in release v20171222. -# -# Copyright © 2017 by Warren Young and Bill Cattey -# -# Permission is hereby granted, free of charge, to any person obtaining -# a copy of this software and associated documentation files (the -# "Software"), to deal in the Software without restriction, including -# without limitation the rights to use, copy, modify, merge, publish, -# distribute, sublicense, and/or sell copies of the Software, and to -# permit persons to whom the Software is furnished to do so, subject to -# the following conditions: -# -# The above copyright notice and this permission notice shall be -# included in all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -# IN NO EVENT SHALL THE AUTHORS LISTED ABOVE BE LIABLE FOR ANY CLAIM, -# DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT -# OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE -# OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -# -# Except as contained in this notice, the names of the authors above -# shall not be used in advertising or otherwise to promote the sale, -# use or other dealings in this Software without prior written -# authorization from those authors. -######################################################################## - -# Bring in just the basics so we can bring in our local modules -import os -import sys -import argparse - -sys.path.insert (0, os.path.dirname (__file__) + '/../lib') -sys.path.insert (0, os.getcwd () + '/lib') - -# Our local modules -from pidp8i import * -from simh import * - -# Other global Python modules -import glob -import subprocess - - -#### GLOBALS AND CONSTANTS ############################################# - -progmsg = True - - -#### main ############################################################## - -def main (): - global progmsg - - # Set up the arg parser and use it to parse the command line. - parser = argparse.ArgumentParser() - parser.add_argument("name", - help="Create an OS/8 image from a list of ASCII files.") - group = parser.add_mutually_exclusive_group(required=True) - group.add_argument("--tu56", "-t", action="store_true") - group.add_argument("--rk05a", "-ra", action="store_true") - group.add_argument("--rk05b", "-rb", action="store_true") - - args = parser.parse_args() - - print "Filename: " + args.name - if args.tu56: - sdev = "dt0" - os8dev = "DTA0:" - imagename = args.name + ".tu56" - stat_str = "DECtape" - - if args.rk05a: - sdev = "rk1" - os8dev = "RKA1:" - imagename = args.name + ".rk05" - stat_str = "partition A of" - - if args.rk05b: - sdev = "rk1" - os8dev = "RKB1:" - imagename = args.name + ".rk05" - stat_str = "partition B of" - - listname = args.name + ".list" - - # Create the SIMH child instance and tell it where to send log output - try: - s = simh (dirs.build, True) - except (RuntimeError) as e: - print "Could not start simulator: " + e.message + '!' - exit (1) - s.set_logfile (os.fdopen (sys.stdout.fileno (), 'w', 0)) - - # Attach a clean version of the image to the simulator - if os.path.exists (imagename): - print "Overwriting old " + stat_str + " image " + imagename - - s.send_cmd ("att " + sdev + " " + imagename) - - # Find and boot the bootable OS/8 disk. Use the "patched" version - # because that is what "make run" uses; we use that command to - # inspect this script's work. - rk = os.path.join (dirs.os8mo, 'os8v3d-patched.rk05') - if not os.path.isfile (rk): - print "Could not find " + rk + "; OS/8 media not yet built?" - exit (1) - print "Booting " + rk + "..." - s.send_cmd ("att rk0 " + rk) - s.send_cmd ("boot rk0") - - s.os8_send_cmd ('\\.', "ZERO " + os8dev) - - manifest = open (listname, "r") - - for line in manifest: - src = line.strip() - if src == "": continue - if src[0] == '#': continue # Allow commenting out files - - dest = src.upper () - s.os8_send_file (src, os8dev + dest) - - # Exit simulator nicely so that image detaches cleanly - s.back_to_cmd ('\\.') - s.send_cmd ("det " + sdev) - s.send_cmd ('quit') - - -if __name__ == "__main__": main() ADDED bin/txt2os8.in Index: bin/txt2os8.in ================================================================== --- /dev/null +++ bin/txt2os8.in @@ -0,0 +1,151 @@ +#!/usr/bin/env @PYCMD@ +# -*- coding: utf-8 -*- +######################################################################## +# Create a tu56 or rk05 image and fill it with ASCII files, i.e. source code. +# +# It is intended to be be called manually when we have a POSIX +# directory full of ASCII files we want to bulk-copy into SIMH. +# +# The argument is taken both as the name of the image to create +# and the list of files to copy in. +# +# For now, it takes all input and produces all output in the +# current working directory. +# +# IMPORTANT: Currently all input files are mindlessly passed through +# txt2ptp which transforms POSIX ASCII files to OS/8 ASCII files. +# It WILL mutilate non-ASCII files. +# +# This program is based on the old cc8-tu56-update program, last +# shipped by this project in release v20171222. +# +# Copyright © 2017-2019 by Warren Young and Bill Cattey +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +# IN NO EVENT SHALL THE AUTHORS LISTED ABOVE BE LIABLE FOR ANY CLAIM, +# DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT +# OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE +# OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# +# Except as contained in this notice, the names of the authors above +# shall not be used in advertising or otherwise to promote the sale, +# use or other dealings in this Software without prior written +# authorization from those authors. +######################################################################## + +# Bring in just the basics so we can bring in our local modules +import os +import sys +import argparse + +sys.path.insert (0, os.path.dirname (__file__) + '/../lib') +sys.path.insert (0, os.getcwd () + '/lib') + +# Our local modules +from pidp8i import * +from simh import * + +# Other global Python modules +import glob +import subprocess + + +#### GLOBALS AND CONSTANTS ############################################# + +progmsg = True + + +#### main ############################################################## + +def main (): + global progmsg + + # Set up the arg parser and use it to parse the command line. + parser = argparse.ArgumentParser() + parser.add_argument("name", + help="Create an OS/8 image from a list of ASCII files.") + group = parser.add_mutually_exclusive_group(required=True) + group.add_argument("--tu56", "-t", action="store_true") + group.add_argument("--rk05a", "-ra", action="store_true") + group.add_argument("--rk05b", "-rb", action="store_true") + + args = parser.parse_args() + + print "Filename: " + args.name + if args.tu56: + sdev = "dt0" + os8dev = "DTA0:" + imagename = args.name + ".tu56" + stat_str = "DECtape" + + if args.rk05a: + sdev = "rk1" + os8dev = "RKA1:" + imagename = args.name + ".rk05" + stat_str = "partition A of" + + if args.rk05b: + sdev = "rk1" + os8dev = "RKB1:" + imagename = args.name + ".rk05" + stat_str = "partition B of" + + listname = args.name + ".list" + + # Create the SIMH child instance and tell it where to send log output + try: + s = simh (dirs.build, True) + except (RuntimeError) as e: + print "Could not start simulator: " + e.message + '!' + exit (1) + s.set_logfile (os.fdopen (sys.stdout.fileno (), 'wb', 0)) + + # Attach a clean version of the image to the simulator + if os.path.exists (imagename): + print "Overwriting old " + stat_str + " image " + imagename + + s.send_cmd ("att " + sdev + " " + imagename) + + # Find and boot the bootable OS/8 disk. Use the "patched" version + # because that is what "make run" uses; we use that command to + # inspect this script's work. + rk = os.path.join (dirs.os8mo, 'os8v3d-patched.rk05') + if not os.path.isfile (rk): + print "Could not find " + rk + "; OS/8 media not yet built?" + exit (1) + print "Booting " + rk + "..." + s.send_cmd ("att rk0 " + rk) + s.send_cmd ("boot rk0") + + s.os8_send_cmd ('\\.', "ZERO " + os8dev) + + manifest = open (listname, "r") + + for line in manifest: + src = line.strip() + if src == "": continue + if src[0] == '#': continue # Allow commenting out files + + dest = src.upper () + s.os8_send_file (src, os8dev + dest) + + # Exit simulator nicely so that image detaches cleanly + s.back_to_cmd ('\\.') + s.send_cmd ("det " + sdev) + s.send_cmd ('quit') + + +if __name__ == "__main__": main() Index: doc/class-simh.md ================================================================== --- doc/class-simh.md +++ doc/class-simh.md @@ -84,17 +84,17 @@ ## Logging The next step is to tell the `s` object where to send its logging output: - s.set_logfile (os.fdopen (sys.stdout.fileno (), 'w', 0)) + s.set_logfile (os.fdopen (sys.stdout.fileno (), 'wb', 0)) Contrast the corresponding line in `os8-run` which chooses whether to send logging output to the console or to a log file: - s.set_logfile (open (dirs.log + 'os8-run' + '.log', 'a') \ - if not VERY_VERBOSE else os.fdopen (sys.stdout.fileno (), 'w', 0)) + s.set_logfile (open (dirs.log + 'os8-run' + '.log', 'ab') \ + if not VERY_VERBOSE else os.fdopen (sys.stdout.fileno (), 'wb', 0)) Note that this more complicated scheme appends to the log file instead of overwriting it because there are cases where `os8-run` gets run more than once with different script inputs, so we want to preserve the prior script outputs, not keep only the latest. ADDED etc/pidp8i.rc Index: etc/pidp8i.rc ================================================================== --- /dev/null +++ etc/pidp8i.rc @@ -0,0 +1,5 @@ +# PiDP-8/I runtime configuration file. See it README.md file or +# https://tangentsoft.com/pidp8i/doc/trunk/README.md#runtime + +# Alternatives: none, tmux +SCREEN_MANAGER=screen DELETED lib/os8script.py Index: lib/os8script.py ================================================================== --- lib/os8script.py +++ /dev/null @@ -1,1887 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -######################################################################## -# simh-os8-script.py Library for scripting OS/8 under SIMH -# Contains validators and callers for os8 and simh commands to make -# it easier to create scripts. -# -# Copyright © 2017 by Jonathan Trites, William Cattey, and Warren Young. -# -# Permission is hereby granted, free of charge, to any person obtaining -# a copy of this software and associated documentation files (the -# "Software"), to deal in the Software without restriction, including -# without limitation the rights to use, copy, modify, merge, publish, -# distribute, sublicense, and/or sell copies of the Software, and to -# permit persons to whom the Software is furnished to do so, subject to -# the following conditions: -# -# The above copyright notice and this permission notice shall be -# included in all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -# IN NO EVENT SHALL THE AUTHORS LISTED ABOVE BE LIABLE FOR ANY CLAIM, -# DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT -# OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE -# OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -# -# Except as contained in this notice, the names of the authors above -# shall not be used in advertising or otherwise to promote the sale, -# use or other dealings in this Software without prior written -# authorization from those authors. -######################################################################## - -# Bring in just the basics so we can bring in our local modules -import os -import sys -import tempfile -sys.path.insert (0, os.path.dirname (__file__) + '/../lib') -sys.path.insert (0, os.getcwd () + '/lib') - -# Python core modules we use -import re -from string import Template -import shutil -import subprocess - -# Our local modules -from pidp8i import * -from simh import * - -# Script Language Version -# Update this version number as the language evolves. -# Version 1.0 is the first public version. -LANG_VERSION = "1.0" - -# Error Class Definitions ############################################## -# Enables us to use exceptions from within this module. - -class Error(Exception): - """Base Class for exceptions in this module.""" - pass - -class InputError(Error): - """Exception raised for errors in the input. - - Attributes: - expr -- input expression in which the error occurred - msg -- explanation of the error - """ - - def __init__(self, msg): - self.msg = msg - - def __str__(self): - return self.msg - - -# Private globals ###################################################### -# Visible within this file, but not to the outside. - -# Identify a begin enabled/not_disabled command. group(1) contains either the enabled or -# disabled flag. Put the rest of the line in group(2) -_begin_en_dis_comm_re = re.compile ("^begin\s+(enabled|default|version)\s+(.+)$") - -# Identify an end enabled/not_disabled command. group(1) contains either the enabled or -# disabled flag. Put the rest of the line in group(2) -_end_en_dis_comm_re = re.compile ("^end\s+(enabled|default|version)\s+(.+)$") - -# Identify an end comm and put the rest of the line in group(1) -_end_comm_re = re.compile ("^end\s+(.+)?$") - -# Identify an end option command and put the rest of the line in group(1) -_end_option_comm_re = re.compile ("^end\s+option\s+(.+)$") - -# A valid version spec -_version_parse_re = re.compile ("^((\d+\.)*)?(\d+)?$") - -# Name of the DECtape image file we create -_new_sys_tape_prefix = "system" - -# Parser regexps used in patcher -_com_os8_parse_str = "^\.([a-zA-Z]+)\s*(.*)$" -_com_os8_parse = re.compile(_com_os8_parse_str) -_com_split_str = "^([a-zA-Z]+)\s*(.*)$" -_com_split_parse = re.compile(_com_split_str) -_odt_parse_str = "^([0-7]+)\s*/\s*(\S+)\s+([0-7;]+)" -_odt_parse = re.compile(_odt_parse_str) - -# Put command keyword in group(1) and the rest is in group(3) -_comm_re_str = "^(\S+)(\s+(.+))?$" -_comm_re = re.compile(_comm_re_str) - -# Identify an end comm and put the rest of the line in group(1) -_end_comm_re = re.compile ("^end\s+(.+)?$") - -# Identify an end option command and put the rest of the line in group(1) -_end_option_comm_re = re.compile ("^end\s+option\s+(.+)$") - -# Identify a begin command and put the rest of the line in group(1) -_begin_option_comm_re = re.compile ("^begin\s+option\s+(.+)$") - -# Parse an argument string into a sys device with -# device name in group(1), unit number in group(2) -# We put all bootable devices into this string so that when -# we add more devices, for example rl for RL01, we change one -# string not many. -_simh_boot_dev_str = "(rk|td|dt|rx)(\d*)" -_simh_boot_re = re.compile("^" + _simh_boot_dev_str + "$") - -# Parse an argument string for mount into SIMH device -# device name in group(1), unit number in group(2) -# And the rest in group (3) -_mount_regex_str = "^" + _simh_boot_dev_str + "\s+(.+)$" -_mount_re = re.compile(_mount_regex_str) - -# Map of SIMH device names to OS/8 device name prefixes. -_os8_from_simh_dev = {"rk" : "RK", "td" : "DTA", "dt" : "DTA", "rx" : "RX"} - -_os8_partitions = {"RK": ["A", "B"]} - -# OS/8 file name matching regex -_os8_file_re = re.compile("(\S+):(\S+)?") - -# Regular expression for syntax checking inside FOTP -# Destination is in group(1), Source is in group(3) -_fotp_re = re.compile ("^((\S+:)?\S+)<((\S+:)?\S+)$") - -# Regular expression for detecting the 2 arg and 3 arg forms -# of the "pal8" script command. - -# OS/8 name regex template: -# Optional device spec, i.e. DTA0: -# File spec with a specific extension or no extension. - -_os8_fspec = Template ("((\S+:)?([A-Z0-9]{1,6}|[A-Z0-9]{1,6}\.$ext))") -_os8_BN_fspec = _os8_fspec.substitute(ext="BN") -_os8_PA_fspec = _os8_fspec.substitute(ext="PA") -_os8_LS_fspec = _os8_fspec.substitute(ext="LS") - -# For the two arg form: -# The full destination spec is in group(1), The full source spec is in group(4). -# The device components, if any, are in group(2) for destination, and -# group(5) for source. -# The file components are in group(3) for destination, and group (6) for source. -# The destination file must either end in ".BN" or have no extension. -# The source must file either end in ".PA" or have no extension. -_two_arg_pal_re = re.compile ("^" + _os8_BN_fspec + "\s*<\s*" + _os8_PA_fspec + "$") - -# For the 3 arg form: -# The full destination spec is in group(1), The full source spec is in group(7). -# The full listing spec is in group(4) -# The device components, if any, are in group(2) for destination, group(5) -# for listing, and group(8) for source. -# The file components are in group(3) for destination, and group(9) for source, -# and group(6) for listing. -# The destination file must either end in ".BN" or have no extension. -# The source must file either end in ".PA" or have no extension. -# The listing must either end in "LS" or have no extension. - -_three_arg_pal_re = re.compile ("^" + _os8_BN_fspec + "\s*,\s*" + _os8_LS_fspec + "\s*<\s*" + _os8_PA_fspec + "$") - -# Regular expression for syntax checking inside ABSLDR -# One or more OS/8 binary files and optional args beginning with a slash. - -_absldr_re = re.compile ("^" + _os8_BN_fspec + "(," + _os8_BN_fspec + ")*(/\S)*$") - -# Regular expressions for syntax checking for cpto and cpfrom. -# May be where destination and default option /A is implied. -# Or