Added help for users to get acme installed on their system if it's missing.
This commit is contained in:
parent
163d6ff9c5
commit
027eda5069
2 changed files with 245 additions and 18 deletions
141
README.md
141
README.md
|
|
@ -16,10 +16,23 @@ c65gm compiles high-level source code into ACME assembler syntax for the 6502 pr
|
||||||
- **Optimizations**: Constant folding, self-assignment detection
|
- **Optimizations**: Constant folding, self-assignment detection
|
||||||
- **Safety features**: Compile-time detection of overlapping absolute addresses in function call chains
|
- **Safety features**: Compile-time detection of overlapping absolute addresses in function call chains
|
||||||
|
|
||||||
## Requirements
|
## Prerequisites
|
||||||
|
|
||||||
|
### ACME Assembler (Required for creating .prg executables)
|
||||||
|
c65gm requires the ACME Cross-Assembler to create executable .prg files. If you only need to generate assembly (.asm) files, you can use compile mode without ACME.
|
||||||
|
|
||||||
|
**Installation options by platform:**
|
||||||
|
|
||||||
|
- **Ubuntu/Debian**: `sudo apt install acme`
|
||||||
|
- **Fedora/RHEL**: `sudo dnf install acme`
|
||||||
|
- **Arch Linux**: `sudo pacman -S acme`
|
||||||
|
- **macOS**: `brew install acme`
|
||||||
|
- **Windows**: Download binary from [ACME releases](https://github.com/meonwax/acme/releases)
|
||||||
|
|
||||||
|
If ACME is not found when running the `build` command, c65gm will display platform-specific installation instructions.
|
||||||
|
|
||||||
|
### Go (Required for building c65gm from source)
|
||||||
- **Go**: Version 1.25.1 or higher (tested with 1.25.5)
|
- **Go**: Version 1.25.1 or higher (tested with 1.25.5)
|
||||||
- **Acme**: Tested with release 0.97 ("Zem")
|
|
||||||
|
|
||||||
|
|
||||||
The project uses Go modules with these dependencies:
|
The project uses Go modules with these dependencies:
|
||||||
|
|
@ -75,13 +88,116 @@ Compile and assemble directly to a .prg file:
|
||||||
```
|
```
|
||||||
|
|
||||||
### Key Features
|
### Key Features
|
||||||
- **Self-contained**: No external build scripts needed
|
- **Self-contained**: Embedded standard library and Starlark interpreter (ACME assembler required for .prg creation)
|
||||||
- **Flexible syntax**: `-i`/`-in` and `-o`/`-out` are equivalent
|
- **Flexible syntax**: `-i`/`-in` and `-o`/`-out` are equivalent
|
||||||
- **Smart defaults**: Output extension determines mode (.prg = build, .asm = compile)
|
- **Smart defaults**: Output extension determines mode (.prg = build, .asm = compile)
|
||||||
- **ACME integration**: Automatically finds and runs ACME assembler with `-f cbm` by default
|
- **ACME integration**: Automatically finds and runs ACME assembler with `-f cbm` by default (provides platform-specific installation instructions if missing)
|
||||||
- **Backward compatible**: Legacy `-in`/`-out` flags still work
|
- **Backward compatible**: Legacy `-in`/`-out` flags still work
|
||||||
- **Customizable**: Use `--no-cbm` to disable CBM format, `--keep-asm` to keep intermediate files
|
- **Customizable**: Use `--no-cbm` to disable CBM format, `--keep-asm` to keep intermediate files
|
||||||
|
|
||||||
|
## Compiler Options Reference
|
||||||
|
|
||||||
|
### Default Behavior (Simplest Case)
|
||||||
|
The simplest way to use c65gm is to provide just the input file:
|
||||||
|
```bash
|
||||||
|
c65gm myprogram.c65
|
||||||
|
```
|
||||||
|
This will:
|
||||||
|
1. Compile `myprogram.c65` to assembly
|
||||||
|
2. Run ACME assembler with `-f cbm` format
|
||||||
|
3. Produce `myprogram.prg` (C64 executable)
|
||||||
|
|
||||||
|
### Input and Output Files
|
||||||
|
- **`-i`, `--input`** (`-in`): Specify input `.c65` file (required)
|
||||||
|
- **`-o`, `--output`** (`-out`): Specify output file (optional)
|
||||||
|
|
||||||
|
**Examples:**
|
||||||
|
```bash
|
||||||
|
# Explicit input/output
|
||||||
|
c65gm -i program.c65 -o game.prg
|
||||||
|
|
||||||
|
# Input only (default output: program.prg)
|
||||||
|
c65gm program.c65
|
||||||
|
|
||||||
|
# Legacy syntax (still works)
|
||||||
|
c65gm -in program.c65 -out output.asm
|
||||||
|
```
|
||||||
|
|
||||||
|
### Operation Modes
|
||||||
|
c65gm automatically determines the operation mode based on the output file extension:
|
||||||
|
|
||||||
|
#### Build Mode (`.prg` extension)
|
||||||
|
Compiles **and** assembles to create a C64 executable:
|
||||||
|
```bash
|
||||||
|
c65gm program.c65 # → program.prg (default)
|
||||||
|
c65gm program.c65 -o game.prg # → game.prg
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Compile Mode (`.asm` extension)
|
||||||
|
Compiles only, producing ACME assembly for manual assembly:
|
||||||
|
```bash
|
||||||
|
c65gm program.c65 -o output.asm # → output.asm
|
||||||
|
c65gm compile -i program.c65 # → program.asm
|
||||||
|
```
|
||||||
|
|
||||||
|
### Subcommands
|
||||||
|
For explicit control, use subcommands:
|
||||||
|
|
||||||
|
#### `build` - Compile + Assemble
|
||||||
|
```bash
|
||||||
|
c65gm build -i program.c65 [-o output.prg] [--keep-asm] [--no-cbm]
|
||||||
|
```
|
||||||
|
- `--keep-asm`: Keep intermediate `.asm` file
|
||||||
|
- `--no-cbm`: Don't add `-f cbm` flag to ACME (for non-CBM targets)
|
||||||
|
|
||||||
|
#### `compile` - Compile Only
|
||||||
|
```bash
|
||||||
|
c65gm compile -i program.c65 [-o output.asm]
|
||||||
|
```
|
||||||
|
Produces assembly file only, doesn't run ACME.
|
||||||
|
|
||||||
|
#### `help` - Show Usage
|
||||||
|
```bash
|
||||||
|
c65gm help
|
||||||
|
c65gm -h
|
||||||
|
c65gm --help
|
||||||
|
```
|
||||||
|
|
||||||
|
### ACME Assembler Integration
|
||||||
|
c65gm automatically:
|
||||||
|
1. Searches `PATH` for `acme` executable
|
||||||
|
2. Runs: `acme -o output.prg -f cbm temp.asm`
|
||||||
|
3. Shows ACME output and errors
|
||||||
|
4. Cleans up temporary files (unless `--keep-asm`)
|
||||||
|
|
||||||
|
**Error Handling:** If ACME is not found, c65gm shows installation instructions and suggests using `compile` mode instead.
|
||||||
|
|
||||||
|
### Environment Variables
|
||||||
|
- **`C65LIBPATH`**: Search path for `#INCLUDE <file>` directives
|
||||||
|
```bash
|
||||||
|
export C65LIBPATH=/path/to/c65gm/lib
|
||||||
|
c65gm program.c65
|
||||||
|
```
|
||||||
|
|
||||||
|
### Examples Summary
|
||||||
|
```bash
|
||||||
|
# Quick build (recommended)
|
||||||
|
c65gm program.c65
|
||||||
|
|
||||||
|
# Custom output name
|
||||||
|
c65gm -i program.c65 -o game.prg
|
||||||
|
|
||||||
|
# Keep intermediate assembly
|
||||||
|
c65gm build -i program.c65 --keep-asm
|
||||||
|
|
||||||
|
# Compile only (no ACME)
|
||||||
|
c65gm compile -i program.c65
|
||||||
|
|
||||||
|
# Legacy two-step process (still works)
|
||||||
|
c65gm -in program.c65 -out program.asm
|
||||||
|
acme -f cbm -o program.prg program.asm
|
||||||
|
```
|
||||||
|
|
||||||
## Running Tests
|
## Running Tests
|
||||||
|
|
||||||
Run all tests:
|
Run all tests:
|
||||||
|
|
@ -135,4 +251,19 @@ The example directories also contain `cm.sh` scripts showing the old build metho
|
||||||
## License
|
## License
|
||||||
|
|
||||||
Copyright (C) 1999, 2025 Mattias Hansson
|
Copyright (C) 1999, 2025 Mattias Hansson
|
||||||
Distributed under GPL.
|
|
||||||
|
This program is free software; you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation; either version 2 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program; if not, write to the Free Software
|
||||||
|
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
|
||||||
|
See [LICENSE](LICENSE) file for the full GPL v2 license text.
|
||||||
|
|
|
||||||
122
main.go
122
main.go
|
|
@ -1,12 +1,15 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"c65gm/internal/commands"
|
"c65gm/internal/commands"
|
||||||
"c65gm/internal/compiler"
|
"c65gm/internal/compiler"
|
||||||
|
|
@ -17,6 +20,13 @@ import (
|
||||||
// Copyright (C) 1999, 2025 Mattias Hansson
|
// Copyright (C) 1999, 2025 Mattias Hansson
|
||||||
// Distributed under GPL.
|
// Distributed under GPL.
|
||||||
|
|
||||||
|
// ANSI color codes for error messages
|
||||||
|
const (
|
||||||
|
colorRed = "\033[31m"
|
||||||
|
colorYellow = "\033[33m"
|
||||||
|
colorReset = "\033[0m"
|
||||||
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
fmt.Println("c65gm - A 6502 Cross-Compiler for the ACME Cross-Assembler.")
|
fmt.Println("c65gm - A 6502 Cross-Compiler for the ACME Cross-Assembler.")
|
||||||
fmt.Println("Copyright (C) 1999, 2025 Mattias Hansson. v1.0.0")
|
fmt.Println("Copyright (C) 1999, 2025 Mattias Hansson. v1.0.0")
|
||||||
|
|
@ -61,11 +71,19 @@ func main() {
|
||||||
if i+1 < len(args) {
|
if i+1 < len(args) {
|
||||||
inputFile = args[i+1]
|
inputFile = args[i+1]
|
||||||
i++ // Skip next arg
|
i++ // Skip next arg
|
||||||
|
} else {
|
||||||
|
fmt.Fprintln(os.Stderr, "Error: -i flag requires a filename")
|
||||||
|
printUsage()
|
||||||
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
} else if arg == "-o" || arg == "-out" {
|
} else if arg == "-o" || arg == "-out" {
|
||||||
if i+1 < len(args) {
|
if i+1 < len(args) {
|
||||||
outputFile = args[i+1]
|
outputFile = args[i+1]
|
||||||
i++ // Skip next arg
|
i++ // Skip next arg
|
||||||
|
} else {
|
||||||
|
fmt.Fprintln(os.Stderr, "Error: -o flag requires a filename")
|
||||||
|
printUsage()
|
||||||
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
} else if !strings.HasPrefix(arg, "-") && inputFile == "" {
|
} else if !strings.HasPrefix(arg, "-") && inputFile == "" {
|
||||||
// First non-flag argument is the input file
|
// First non-flag argument is the input file
|
||||||
|
|
@ -308,7 +326,7 @@ func runACME(asmFile, outputFile string, noCBM bool) error {
|
||||||
// Check if ACME is available
|
// Check if ACME is available
|
||||||
acmePath, err := exec.LookPath("acme")
|
acmePath, err := exec.LookPath("acme")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("acme assembler not found in PATH. Please install ACME: https://github.com/meonwax/acme")
|
return fmt.Errorf("acme assembler not found in PATH")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build ACME command arguments
|
// Build ACME command arguments
|
||||||
|
|
@ -332,22 +350,100 @@ func runACME(asmFile, outputFile string, noCBM bool) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// detectPackageManager detects available package managers and returns platform-specific installation instructions
|
||||||
|
func detectPackageManager() (string, string) {
|
||||||
|
// Platform detection
|
||||||
|
os := runtime.GOOS
|
||||||
|
|
||||||
|
// Helper function to check if a command exists
|
||||||
|
commandExists := func(cmd string) bool {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
// Try "command -v" first (POSIX standard)
|
||||||
|
checkCmd := exec.CommandContext(ctx, "command", "-v", cmd)
|
||||||
|
if err := checkCmd.Run(); err == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to "which" (common but not POSIX)
|
||||||
|
checkCmd = exec.CommandContext(ctx, "which", cmd)
|
||||||
|
if err := checkCmd.Run(); err == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
switch os {
|
||||||
|
case "linux":
|
||||||
|
// Linux package manager detection (priority order)
|
||||||
|
if commandExists("apt") {
|
||||||
|
return "Ubuntu/Debian (apt)", "sudo apt install acme"
|
||||||
|
}
|
||||||
|
if commandExists("dnf") {
|
||||||
|
return "Fedora/RHEL (dnf)", "sudo dnf install acme"
|
||||||
|
}
|
||||||
|
if commandExists("yum") {
|
||||||
|
return "RHEL/CentOS (yum)", "sudo yum install acme"
|
||||||
|
}
|
||||||
|
if commandExists("pacman") {
|
||||||
|
return "Arch (pacman)", "sudo pacman -S acme"
|
||||||
|
}
|
||||||
|
if commandExists("zypper") {
|
||||||
|
return "openSUSE (zypper)", "sudo zypper install acme"
|
||||||
|
}
|
||||||
|
// Generic Linux fallback
|
||||||
|
return "Linux", "Download from releases or use your package manager"
|
||||||
|
|
||||||
|
case "darwin": // macOS
|
||||||
|
if commandExists("brew") {
|
||||||
|
return "macOS (Homebrew)", "brew install acme"
|
||||||
|
}
|
||||||
|
return "macOS", "brew install acme # Install Homebrew first, or download binary from releases"
|
||||||
|
|
||||||
|
case "windows":
|
||||||
|
// Windows package manager detection
|
||||||
|
if commandExists("choco") {
|
||||||
|
return "Windows (Chocolatey)", "choco install acme"
|
||||||
|
}
|
||||||
|
if commandExists("scoop") {
|
||||||
|
return "Windows (Scoop)", "scoop install acme"
|
||||||
|
}
|
||||||
|
// Check for winget (Windows Package Manager)
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
|
||||||
|
defer cancel()
|
||||||
|
checkCmd := exec.CommandContext(ctx, "cmd", "/c", "where", "winget")
|
||||||
|
if err := checkCmd.Run(); err == nil {
|
||||||
|
return "Windows (winget)", "winget install acme"
|
||||||
|
}
|
||||||
|
return "Windows", "Download binary from: https://github.com/meonwax/acme/releases"
|
||||||
|
|
||||||
|
default:
|
||||||
|
return "Unknown OS", "Download from: https://github.com/meonwax/acme"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// checkACMEAvailable checks if ACME is installed and provides helpful instructions if not
|
// checkACMEAvailable checks if ACME is installed and provides helpful instructions if not
|
||||||
func checkACMEAvailable() error {
|
func checkACMEAvailable() error {
|
||||||
_, err := exec.LookPath("acme")
|
_, err := exec.LookPath("acme")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf(`ACME assembler not found in PATH.
|
platform, installCmd := detectPackageManager()
|
||||||
|
|
||||||
c65gm requires ACME (Cross-Assembler) to create executable files.
|
var message strings.Builder
|
||||||
Please install ACME from: https://github.com/meonwax/acme
|
message.WriteString(fmt.Sprintf("%sACME assembler not found in PATH%s\n\n", colorRed, colorReset))
|
||||||
|
message.WriteString("c65gm requires ACME (Cross-Assembler) to create executable files.\n\n")
|
||||||
Installation options:
|
|
||||||
- Linux: Download from releases or use package manager
|
message.WriteString(fmt.Sprintf("%sInstallation options for %s:%s\n", colorYellow, platform, colorReset))
|
||||||
- macOS: brew install acme
|
message.WriteString(fmt.Sprintf(" %s\n\n", installCmd))
|
||||||
- Windows: Download binary from releases
|
|
||||||
|
message.WriteString("Or use compile mode to generate assembly only:\n")
|
||||||
Or use compile mode to generate assembly only:
|
message.WriteString(" c65gm compile -i program.c65\n\n")
|
||||||
c65gm compile -i program.c65`)
|
|
||||||
|
message.WriteString("For manual download or more information:\n")
|
||||||
|
message.WriteString(" https://github.com/meonwax/acme")
|
||||||
|
|
||||||
|
return fmt.Errorf("%s", message.String())
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue