From 95e56850721d371c358443993f1fb6bc8299d73f Mon Sep 17 00:00:00 2001 From: Dagen Brock Date: Mon, 26 Apr 2021 07:50:38 -0500 Subject: [PATCH] commit initial version --- README.md | 55 ++++++++++++++++++++++++++ appy.yaml | 13 +++++++ cmd/asm.go | 22 +++++++++++ cmd/brun.go | 27 +++++++++++++ cmd/build.go | 22 +++++++++++ cmd/disk.go | 22 +++++++++++ cmd/root.go | 85 +++++++++++++++++++++++++++++++++++++++++ cmd/run.go | 25 ++++++++++++ core/assembler.go | 32 ++++++++++++++++ core/diskbuilder.go | 46 ++++++++++++++++++++++ core/project/project.go | 39 +++++++++++++++++++ core/runner.go | 22 +++++++++++ go.mod | 9 +++++ main.go | 22 +++++++++++ testsrc/pc.s | 2 + testsrc/sp.s | 2 + 16 files changed, 445 insertions(+) create mode 100644 README.md create mode 100644 appy.yaml create mode 100644 cmd/asm.go create mode 100644 cmd/brun.go create mode 100644 cmd/build.go create mode 100644 cmd/disk.go create mode 100644 cmd/root.go create mode 100644 cmd/run.go create mode 100644 core/assembler.go create mode 100644 core/diskbuilder.go create mode 100644 core/project/project.go create mode 100644 core/runner.go create mode 100644 go.mod create mode 100644 main.go create mode 100644 testsrc/pc.s create mode 100644 testsrc/sp.s diff --git a/README.md b/README.md new file mode 100644 index 0000000..21c0883 --- /dev/null +++ b/README.md @@ -0,0 +1,55 @@ +# Appy + +Local project builder for emulator disk images + +## Getting Started + +Install Appy then create an `appy.yaml` project definition file. + +Run Appy to assemble, build, and run your source. Or do all 3 with: +``` +appy brun +``` + +### What this solves + +We often need to assemble projects locally, but that is only part of the equation for people writing project to run under emulation. + +The second step is taking our assembled binaries, and combining them with any other assets, data files, operating system files, et cetera, into a single disk image for use with an emulator. + +Often this is handled by a set of scripts the author has locally in the projects. However this leads to problems when collaborating with other people or building pipelines. + +Problem 1: You need to not only provide the scripts, but also the build tools they use, at the correct version. + +Problem 2: The scripts often contain bespoke logic for things like handling filetypes or where to place files on the disk image. + +Appy abstracts the tools away from a script mindset, and into a project mindset. Currently the tool paths are hardcoded but next versions will include dynamic tool providers, including vendoring from internet sources! + +## The project file + +Currently it uses an `appy.yaml` file in the current project directory. + +### Running Appy + +Appy has a verb command structure (sorta like git) for executing the various functions. + +Invoke like: `appy command` + +Commands: +``` +$ appy asm # assemble all files + +$ appy disk # create all disk images and add all files + +$ appy run # launch an emulator + +$ appy build # assemble files and make disk, aka 'asm'+'disk' + +$ appy brun # assemble files, make disk, and launch emulator + # aka 'asm'+'disk'+'run' +``` + +### Version Notes +This is an early experimental version not intended for public use. + +Versioning from external sources and custom tool paths not yet implemented but all core functionality exists. diff --git a/appy.yaml b/appy.yaml new file mode 100644 index 0000000..040a617 --- /dev/null +++ b/appy.yaml @@ -0,0 +1,13 @@ +assemble: [testsrc/sp.s, testsrc/pc.s] +disks: +- name: mydiskimage + file: mydiskimage800.2mg + size: 800KB + files: + - input: ../PRODOS.2.4.2/PRODOS + output: /mydiskimage + - input: testsrc/sp.s + output: /mydiskimage + - input: ../modsearch/potential/eclipse.ntp + output: /mydiskimage + ftaux: #B30000 \ No newline at end of file diff --git a/cmd/asm.go b/cmd/asm.go new file mode 100644 index 0000000..552c56e --- /dev/null +++ b/cmd/asm.go @@ -0,0 +1,22 @@ +package cmd + +import ( + "github.com/digarok/appy/core" + + "github.com/spf13/cobra" +) + +// 'appy asm' command +var asmCmd = &cobra.Command{ + Use: "asm", + Short: "Assemble the source files in your project", + Long: `This will run your 6502/65816 assembler, such as Merlin32, +against all of the files specified for assembly in your project.`, + Run: func(cmd *cobra.Command, args []string) { + core.Assemble() + }, +} + +func init() { + rootCmd.AddCommand(asmCmd) +} diff --git a/cmd/brun.go b/cmd/brun.go new file mode 100644 index 0000000..a8eb9ac --- /dev/null +++ b/cmd/brun.go @@ -0,0 +1,27 @@ +package cmd + +import ( + "github.com/digarok/appy/core" + "github.com/spf13/cobra" +) + +// brunCmd represents the brun command +var brunCmd = &cobra.Command{ + Use: "brun", + Short: "A brief description of your command", + Long: `A longer description that spans multiple lines and likely contains examples +and usage of using your command. For example: + +Cobra is a CLI library for Go that empowers applications. +This application is a tool to generate the needed files +to quickly create a Cobra application.`, + Run: func(cmd *cobra.Command, args []string) { + core.Assemble() + core.BuildDisk() + core.Run() + }, +} + +func init() { + rootCmd.AddCommand(brunCmd) +} diff --git a/cmd/build.go b/cmd/build.go new file mode 100644 index 0000000..928c2f4 --- /dev/null +++ b/cmd/build.go @@ -0,0 +1,22 @@ +package cmd + +import ( + "github.com/digarok/appy/core" + "github.com/spf13/cobra" +) + +// buildCmd represents the build command +var buildCmd = &cobra.Command{ + Use: "build", + Short: "This is the equivalent of 'asm' and 'disk' in a single command.", + Long: `This will first assemble your files and then build the disk +image(s) specified in your project file.`, + Run: func(cmd *cobra.Command, args []string) { + core.Assemble() + core.BuildDisk() + }, +} + +func init() { + rootCmd.AddCommand(buildCmd) +} diff --git a/cmd/disk.go b/cmd/disk.go new file mode 100644 index 0000000..d7f55db --- /dev/null +++ b/cmd/disk.go @@ -0,0 +1,22 @@ +package cmd + +import ( + "github.com/digarok/appy/core" + "github.com/spf13/cobra" +) + +// 'appy disk' command +var diskCmd = &cobra.Command{ + Use: "disk", + Short: "Create disk images from your project", + Long: `This will launch a disk creation utility (CADIUS), and +create the disk images with the files as specified by +your project configuration file.`, + Run: func(cmd *cobra.Command, args []string) { + core.BuildDisk() + }, +} + +func init() { + rootCmd.AddCommand(diskCmd) +} diff --git a/cmd/root.go b/cmd/root.go new file mode 100644 index 0000000..13e23b9 --- /dev/null +++ b/cmd/root.go @@ -0,0 +1,85 @@ +package cmd + +import ( + "fmt" + "os" + + homedir "github.com/mitchellh/go-homedir" + "github.com/spf13/cobra" + "github.com/spf13/viper" + + "github.com/digarok/appy/core/project" +) + +var cfgFile string + +// rootCmd represents the base command when called without any subcommands +var rootCmd = &cobra.Command{ + Use: "appy", + Short: "A happy little Apple II project application.", + Long: `This will assemble your source files, and build your disk images, +and let you run them in an Apple II emulator in a single command. For example: + + + ./appy run # this will do everything! + + +`, + // Uncomment the following line if your bare application + // has an action associated with it: + // Run: func(cmd *cobra.Command, args []string) { }, +} + +// Execute adds all child commands to the root command and sets flags appropriately. +// This is called by main.main(). It only needs to happen once to the rootCmd. +func Execute() { + // fmt.Println("root.Execute()") + cobra.CheckErr(rootCmd.Execute()) +} + +func init() { + cobra.OnInitialize(initConfig) + // fmt.Println("root.init()") + // Here you will define your flags and configuration settings. + // Cobra supports persistent flags, which, if defined here, + // will be global for your application. + + rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.appy.yaml)") + + // Cobra also supports local flags, which will only run + // when this action is called directly. + rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") +} + +// initConfig reads in config file and ENV variables if set. +func initConfig() { + // fmt.Println("root.initConfig()") + + if cfgFile != "" { + // Use config file from the flag. + viper.SetConfigFile(cfgFile) + } else { + viper.SetConfigName("appy") + + // Typically look in the current path for the project config. + viper.AddConfigPath(".") + // Find home directory. + home, err := homedir.Dir() + cobra.CheckErr(err) + + // Search config in home directory with name ".appy" (without extension). + viper.AddConfigPath(home) + + } + + viper.AutomaticEnv() // read in environment variables that match + + // If a config file is found, read it in. + if err := viper.ReadInConfig(); err == nil { + fmt.Fprintln(os.Stderr, "Using config file:", viper.ConfigFileUsed()) + } else { + fmt.Fprintln(os.Stderr, "Error loading config:", err) + } + project.SelfConfigure() + // project.Id() +} diff --git a/cmd/run.go b/cmd/run.go new file mode 100644 index 0000000..3de01f6 --- /dev/null +++ b/cmd/run.go @@ -0,0 +1,25 @@ +package cmd + +import ( + "github.com/digarok/appy/core" + "github.com/spf13/cobra" +) + +// runCmd represents the run command +var runCmd = &cobra.Command{ + Use: "run", + Short: "A brief description of your command", + Long: `A longer description that spans multiple lines and likely contains examples +and usage of using your command. For example: + +Cobra is a CLI library for Go that empowers applications. +This application is a tool to generate the needed files +to quickly create a Cobra application.`, + Run: func(cmd *cobra.Command, args []string) { + core.Run() + }, +} + +func init() { + rootCmd.AddCommand(runCmd) +} diff --git a/core/assembler.go b/core/assembler.go new file mode 100644 index 0000000..1e09d94 --- /dev/null +++ b/core/assembler.go @@ -0,0 +1,32 @@ +package core + +import ( + "fmt" + "log" + "os/exec" + + "github.com/spf13/viper" +) + +var filesToAssemble []string + +const Merlin32Path = "/usr/local/bin/merlin32" + +func Assemble() { + //fmt.Println("asm called") + filesToAssemble = viper.GetViper().GetStringSlice("assemble") + + //fmt.Fprintln(os.Stderr, "HEY", filesToAssemble) + + for _, filename := range filesToAssemble { + fmt.Printf("Assembling %v\n", filename) + + cmd := exec.Command(Merlin32Path, "-V", filename) + + err := cmd.Run() + + if err != nil { + log.Fatal(err) + } + } +} diff --git a/core/diskbuilder.go b/core/diskbuilder.go new file mode 100644 index 0000000..5ae68d8 --- /dev/null +++ b/core/diskbuilder.go @@ -0,0 +1,46 @@ +package core + +import ( + "fmt" + "log" + "os/exec" + + "github.com/digarok/appy/core/project" +) + +const CadiusPath = "/usr/local/bin/cadius" + +func CreateDisk(name string, file string, size string) { + fmt.Printf("Creating Disk: \"%s\" -> %s \tSize: %s\n", name, file, size) + + cmd := exec.Command(CadiusPath, "CREATEVOLUME", file, name, size) + + err := cmd.Run() + + if err != nil { + log.Fatal(err) + } +} + +func AddFiles(disk project.Disk) { + fmt.Printf("Add files to: \"%s\"\n", disk.Name) + for _, file := range disk.Files { + // fmt.Printf("%s ADDFILE %s %s %s\n", CadiusPath, disk.File, file.Output, file.Input) + fmt.Printf(" Adding file: -----> %s\n", file.Input) + cmd := exec.Command(CadiusPath, "ADDFILE", disk.File, file.Output, file.Input) + err := cmd.Run() + + if err != nil { + log.Fatal(err) + } + } + +} + +func BuildDisk() { + var p = project.AppyProj + for _, disk := range p.Disks { + CreateDisk(disk.Name, disk.File, disk.Size) + AddFiles(disk) + } +} diff --git a/core/project/project.go b/core/project/project.go new file mode 100644 index 0000000..c28b0e5 --- /dev/null +++ b/core/project/project.go @@ -0,0 +1,39 @@ +package project + +import ( + "fmt" + "log" + + "github.com/spf13/viper" +) + +type Project struct { + name string + Disks []Disk +} + +type Disk struct { + Name string + File string + Size string + Files []File +} + +type File struct { + Input string + Output string +} + +var AppyProj Project + +func SelfConfigure() { + AppyProj.name = "Selfie" + err := viper.Unmarshal(&AppyProj) + if err != nil { + log.Fatalf("unable to decode into struct, %v", err) + } +} + +func Id() { + fmt.Println("HI FROM:", AppyProj.name) +} diff --git a/core/runner.go b/core/runner.go new file mode 100644 index 0000000..ef037e0 --- /dev/null +++ b/core/runner.go @@ -0,0 +1,22 @@ +package core + +import ( + "fmt" + "log" + "os/exec" +) + +const emulatorPath = "gsplus" + +func Run() { + fmt.Println("Running an emulator") + + cmd := exec.Command(emulatorPath) + + err := cmd.Run() + + if err != nil { + log.Fatal(err) + } + +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..6333164 --- /dev/null +++ b/go.mod @@ -0,0 +1,9 @@ +module github.com/digarok/appy + +go 1.16 + +require ( + github.com/mitchellh/go-homedir v1.1.0 // indirect + github.com/spf13/cobra v1.1.3 // indirect + github.com/spf13/viper v1.7.1 // indirect +) diff --git a/main.go b/main.go new file mode 100644 index 0000000..f645a4d --- /dev/null +++ b/main.go @@ -0,0 +1,22 @@ +/* +Copyright © 2021 NAME HERE + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +package main + +import "github.com/digarok/appy/cmd" + +func main() { + cmd.Execute() +} diff --git a/testsrc/pc.s b/testsrc/pc.s new file mode 100644 index 0000000..e0907df --- /dev/null +++ b/testsrc/pc.s @@ -0,0 +1,2 @@ + org $300 + rts \ No newline at end of file diff --git a/testsrc/sp.s b/testsrc/sp.s new file mode 100644 index 0000000..1b2c8c4 --- /dev/null +++ b/testsrc/sp.s @@ -0,0 +1,2 @@ + org $2000 + rts \ No newline at end of file