From 5f3097b6f2d668a420154321f437ac2a3093e8ed Mon Sep 17 00:00:00 2001 From: Hunter LaFaille Date: Thu, 10 Oct 2024 22:30:53 -0400 Subject: [PATCH] Build context subsystem (#12) * Refactoring * Refactoring command.go * renaming mod, adding version support * Add copyright notice * Updating text * Working on contexts * reworking * reworking some more * ported to use project context, got away from pointer hell --- .devcontainer/devcontainer.json | 3 +- Makefile | 5 + cli/command.go | 282 ++---------------- cli/util.go | 12 - core/context/project/config.go | 90 ++++++ core/context/project/context.go | 41 +++ core/context/project/fs.go | 100 +++++++ core/context/project/util.go | 12 + core/dependency/cache.go | 38 +++ core/dependency/fs.go | 6 +- core/dependency/implementation.go | 85 ------ core/dependency/resolve.go | 52 ++++ core/project/config.go | 194 ------------ core/registry/implementation.go | 19 +- core/registry/path.go | 14 +- core/service/build.go | 95 ++++++ core/service/clean.go | 49 +++ core/service/dependency.go | 47 +++ core/service/init.go | 72 +++++ core/service/registry.go | 106 +++++++ core/service/version.go | 15 + .../discovery.go} | 10 +- .../source_util.go => source/util.go} | 11 +- core/toolchain/compiler.go | 23 +- core/toolchain/package.go | 14 +- core/toolchain/toolchain_util.go | 12 +- core/util/cli.go | 16 + core/util/context.go | 4 + core/util/fs.go | 4 + core/util/hash.go | 4 + core/util/net.go | 4 + core/util/path.go | 4 + extension/oci.go | 4 + extension/sbom.go | 4 + extension/shade.go | 4 + go.mod | 13 +- go.sum | 1 + main.go | 6 +- 38 files changed, 889 insertions(+), 586 deletions(-) create mode 100644 Makefile delete mode 100644 cli/util.go create mode 100644 core/context/project/config.go create mode 100644 core/context/project/context.go create mode 100644 core/context/project/fs.go create mode 100644 core/context/project/util.go create mode 100644 core/dependency/cache.go delete mode 100644 core/dependency/implementation.go create mode 100644 core/dependency/resolve.go delete mode 100644 core/project/config.go create mode 100644 core/service/build.go create mode 100644 core/service/clean.go create mode 100644 core/service/dependency.go create mode 100644 core/service/init.go create mode 100644 core/service/registry.go create mode 100644 core/service/version.go rename core/{project/source_discovery.go => source/discovery.go} (75%) rename core/{project/source_util.go => source/util.go} (50%) create mode 100644 core/util/cli.go diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 2d4afc3..b4edf42 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -14,7 +14,8 @@ "customizations": { "vscode": { "extensions": [ - "DavidAnson.vscode-markdownlint" + "DavidAnson.vscode-markdownlint", + "redhat.vscode-yaml" ] } } diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..a2a8b6f --- /dev/null +++ b/Makefile @@ -0,0 +1,5 @@ +build: + go build -ldflags="-X kerosenelabs.com/espresso/core/service.CommitSha=$(shell git rev-parse HEAD) -X kerosenelabs.com/espresso/core/service.Version=$(VERSION)" + +install: + sudo mv espresso /usr/local/bin \ No newline at end of file diff --git a/cli/command.go b/cli/command.go index dd49bae..5352846 100644 --- a/cli/command.go +++ b/cli/command.go @@ -1,144 +1,54 @@ +// Copyright (c) 2024 Kerosene Labs +// This file is part of Espresso, which is licensed under the MIT License. +// See the LICENSE file for details. + package cli import ( - "fmt" - "os" - "strings" - "sync" - - "github.com/fatih/color" - "github.com/olekukonko/tablewriter" "github.com/spf13/cobra" - "hlafaille.xyz/espresso/v0/core/dependency" - "hlafaille.xyz/espresso/v0/core/project" - "hlafaille.xyz/espresso/v0/core/registry" - "hlafaille.xyz/espresso/v0/core/toolchain" - "hlafaille.xyz/espresso/v0/core/util" + "kerosenelabs.com/espresso/core/service" ) +// GetVersionCommand gets the prepared "version" command for cobra +func GetVersionCommand() *cobra.Command { + var root = &cobra.Command{ + Use: "version", + Short: "Print the version of Espresso", + Aliases: []string{"v"}, + Run: func(cmd *cobra.Command, args []string) { + service.PrintVersion() + }, + } + return root +} + +// GetCleanCommand gets the prepared "clean" command for cobra func GetCleanCommand() *cobra.Command { var root = &cobra.Command{ Use: "clean", Short: "Clean the build context", Aliases: []string{"c"}, Run: func(cmd *cobra.Command, args []string) { - // get the config - cfg, err := project.GetConfig() - if err != nil { - fmt.Printf("An error occurred while reading the config: %s\n", err) - } - - // get the build dir - buildPath, err := toolchain.GetBuildPath(cfg) - if err != nil { - fmt.Printf("An error occurred while getting the build path: %s\n", err) - return - } - - // get the dist dir - distPath, err := toolchain.GetDistPath(cfg) - if err != nil { - fmt.Printf("An error occurred while getting the build path: %s\n", err) - return - } - - // remove the build dir - err = os.RemoveAll(*buildPath) - if err != nil { - fmt.Printf("An error occurred while deleting the build path: %s\n", err) - return - } - - // remove the dist dir - err = os.RemoveAll(*distPath) - if err != nil { - fmt.Printf("An error occurred while deleting the dist path: %s\n", err) - return - } + service.CleanWorkspace() }, } return root } -// GetProjectCommand returns the pre-built Cobra Command 'project' +// GetBuildCommand gets the prepared "build" command for cobra func GetBuildCommand() *cobra.Command { var root = &cobra.Command{ Use: "build", Short: "Build the project, outputting a distributable.", Aliases: []string{"b"}, Run: func(cmd *cobra.Command, args []string) { - // get the config - cfg, err := project.GetConfig() - if err != nil { - panic(fmt.Sprintf("An error occurred while reading the config: %s\n", err)) - } - color.Cyan("-- Building '%s', please ensure you are compliant with all dependency licenses\n", cfg.Name) - - // discover source files - files, err := project.DiscoverSourceFiles(cfg) - if err != nil { - ErrorQuit(fmt.Sprintf("An error occurred while discovering source files: %s\n", err)) - } - - // run the compiler on each source file - var wg sync.WaitGroup - for _, value := range files { - wg.Add(1) - go func(f *project.SourceFile) { - defer wg.Done() - color.Cyan("-- Compiling: " + f.Path) - toolchain.CompileSourceFile(cfg, &value) - }(&value) - } - wg.Wait() - - // package the project - color.Cyan("-- Packaging distributable") - err = toolchain.PackageClasses(cfg) - if err != nil { - ErrorQuit(fmt.Sprintf("An error occurred while packaging the classes: %s\n", err)) - } - color.Blue("Finished packaging distributable") - - // iterate over each dependency, resolve it and copy it - distPath, err := toolchain.GetDistPath(cfg) - if err != nil { - ErrorQuit(fmt.Sprintf("Unable to get dist path: %s", err)) - } - os.MkdirAll(*distPath+"/libs", 0755) - var depCopyWg sync.WaitGroup - color.Cyan("-- Copying packages to distributable") - for _, dep := range cfg.Dependencies { - depCopyWg.Add(1) - go func() { - defer depCopyWg.Done() - color.Cyan("-- Beginning copy of '%s:%s' to distributable", dep.Group, dep.Name) - resolved, err := dependency.ResolveDependency(&dep, &cfg.Registries) - if err != nil { - ErrorQuit(fmt.Sprintf("Unable to resolve dependency: %s", err)) - } - - // calculate the should-be location of this jar locally - espressoPath, err := util.GetEspressoDirectoryPath() - if err != nil { - ErrorQuit(fmt.Sprintf("Unable to get the espresso home: %s", espressoPath)) - } - signature := registry.CalculatePackageSignature(resolved.Package, resolved.PackageVersion) - cachedPackageHome := espressoPath + "/cachedPackages" + signature + ".jar" - - // copy the file - util.CopyFile(cachedPackageHome, *distPath+"/libs") - - color.Blue("Copied '%s:%s' to distributable", dep.Group, dep.Name) - }() - } - depCopyWg.Wait() - color.Green("Done!") + service.BuildProject() }, } return root } +// GetInitCommand gets the prepared "init" command for cobra func GetInitCommand() *cobra.Command { var root = &cobra.Command{ Use: "init", @@ -148,50 +58,7 @@ func GetInitCommand() *cobra.Command { var name, _ = cmd.Flags().GetString("name") var basePackage, _ = cmd.Flags().GetString("package") - // ensure JAVA_HOME is set - javaHome, err := util.GetJavaHome() - if err != nil { - fmt.Println("JAVA_HOME is not set, do you have Java installed?") - } - - // ensure a proejct doesn't already exist - cfgExists, err := project.ConfigExists() - if err != nil { - fmt.Println("Error occurred while ensuring a config doesn't already exist") - panic(err) - } else if *cfgExists { - fmt.Println("Config already exists") - os.Exit(1) - } - - fmt.Printf("Creating '%s'\n", name) - - // create a base config - cfg := project.ProjectConfig{ - Name: name, - Version: project.Version{ - Major: 0, - Minor: 1, - Patch: 0, - Hotfix: nil, - }, - BasePackage: basePackage, - Toolchain: project.Toolchain{ - Path: *javaHome, - }, - Registries: []project.Registry{{Name: "espresso-registry", Url: "https://github.com/Kerosene-Labs/espresso-registry/archive/refs/heads/main.zip"}}, - Dependencies: []project.Dependency{}, - } - - // write some example code - println("Creating base package, writing example code") - project.WriteExampleCode(&cfg) - - // persist the config - println("Persisting project configuration") - project.PersistConfig(&cfg) - - println("Done.") + service.InitializeProject(name, &basePackage) }, } root.Flags().StringP("name", "n", "", "Name of the project") @@ -200,7 +67,7 @@ func GetInitCommand() *cobra.Command { return root } -// GetRegistryCommand returns the pre build Cobra Command 'dependency' +// GetRegistryCommand gets the prepared "registry" command for cobra func GetRegistryCommand() *cobra.Command { var root = &cobra.Command{ Use: "registry", @@ -213,50 +80,7 @@ func GetRegistryCommand() *cobra.Command { Aliases: []string{"q"}, Run: func(cmd *cobra.Command, args []string) { var term, _ = cmd.Flags().GetString("term") - - // get the config - cfg, err := project.GetConfig() - if err != nil { - panic(fmt.Sprintf("An error occurred while reading the config: %s\n", err)) - } - - // iterate over each registry, get its packages - var filteredPkgs []registry.Package = []registry.Package{} - for _, reg := range cfg.Registries { - color.Blue("Checking '%s'", reg.Name) - regPkgs, err := registry.GetRegistryPackages(reg) - if err != nil { - panic(fmt.Sprintf("An error occurred while fetching packages from the '%s' registry cache: %s", reg.Name, err)) - } - - // filter by name - for _, pkg := range regPkgs { - if term == "*" || - strings.Contains(strings.ToLower(pkg.Name), strings.ToLower(term)) || - strings.Contains(strings.ToLower(pkg.Description), strings.ToLower(term)) { - filteredPkgs = append(filteredPkgs, pkg) - } - } - } - - // print out our packages - color.Cyan("Found %v package(s):", len(filteredPkgs)) - data := [][]string{} - for _, filtered := range filteredPkgs { - data = append(data, []string{ - filtered.Group, - filtered.Name, - filtered.Versions[len(filtered.Versions)-1].Number, - }) - } - - table := tablewriter.NewWriter(os.Stdout) - table.SetHeader([]string{"Group", "Package", "Latest Version"}) - - for _, v := range data { - table.Append(v) - } - table.Render() + service.QueryRegistries(term) }, } query.Flags().StringP("term", "t", "", "Term to query by") @@ -267,35 +91,14 @@ func GetRegistryCommand() *cobra.Command { Use: "invalidate", Short: "Invalidate and recache the declared registries.", Run: func(cmd *cobra.Command, args []string) { - // get the config - cfg, err := project.GetConfig() - if err != nil { - ErrorQuit(fmt.Sprintf("An error occurred while reading the config: %s\n", err)) - } - - // iterate over each registry, invalidate it - for _, reg := range cfg.Registries { - fmt.Printf("Invalidating cache: %s\n", reg.Url) - err = registry.InvalidateRegistryCache(®) - if err != nil { - ErrorQuit(fmt.Sprintf("An error occurred while invalidaing cache(s): %s\n", err)) - } - } - - // iterate over each registry, download the zip - for _, reg := range cfg.Registries { - fmt.Printf("Downloading archive: %s\n", reg.Url) - err = registry.CacheRegistry(®) - if err != nil { - ErrorQuit(fmt.Sprintf("An error occurred while downloading the registry archive: %s\n", err)) - } - } + service.InvalidateRegistryCaches() }, } root.AddCommand(pull) return root } +// GetDependencyCommand gets the prepared "dependency" command for cobra func GetDependencyCommand() *cobra.Command { var root = &cobra.Command{ Use: "dependency", @@ -307,34 +110,7 @@ func GetDependencyCommand() *cobra.Command { Short: "Fetch dependencies from the appropriate registries, storing them within their caches for consumption at compile time.", Aliases: []string{"s"}, Run: func(cmd *cobra.Command, args []string) { - // get the config - cfg, err := project.GetConfig() - if err != nil { - ErrorQuit(fmt.Sprintf("An error occurred while reading the config: %s\n", err)) - } - - // iterate over the dependencies - var wg sync.WaitGroup - for _, dep := range cfg.Dependencies { - wg.Add(1) - go func() { - defer wg.Done() - displayStr := fmt.Sprintf("%s:%s:%s", dep.Group, dep.Name, project.GetVersionAsString(&dep.Version)) - color.Cyan("[%s] Resolving", displayStr) - rdep, err := dependency.ResolveDependency(&dep, &cfg.Registries) - if err != nil { - ErrorQuit(fmt.Sprintf("[%s] An error occurred while resolving dependencies: %s\n", displayStr, err)) - } - - // cache the resolved dependency - err = dependency.CacheResolvedDependency(rdep) - if err != nil { - ErrorQuit(fmt.Sprintf("[%s] An error occurred while caching the resolved dependency: %s\n", displayStr, err)) - } - color.Green("[%s] Cached", displayStr) - }() - } - wg.Wait() + service.SyncDependencies() }, } root.AddCommand(sync) diff --git a/cli/util.go b/cli/util.go deleted file mode 100644 index 8790b33..0000000 --- a/cli/util.go +++ /dev/null @@ -1,12 +0,0 @@ -package cli - -import ( - "os" - - "github.com/fatih/color" -) - -func ErrorQuit(msg string) { - color.Red(msg) - os.Exit(1) -} diff --git a/core/context/project/config.go b/core/context/project/config.go new file mode 100644 index 0000000..f7a3580 --- /dev/null +++ b/core/context/project/config.go @@ -0,0 +1,90 @@ +// Copyright (c) 2024 Kerosene Labs +// This file is part of Espresso, which is licensed under the MIT License. +// See the LICENSE file for details. + +package project + +import ( + "os" + + "gopkg.in/yaml.v3" +) + +// Dependency represents a particular dependency +type Dependency struct { + Group string `yaml:"group"` + Name string `yaml:"name"` + Version Version `yaml:"version"` +} + +// Registry represents a particular repository +type Registry struct { + Name string `yaml:"name"` + Url string `yaml:"url"` +} + +// Toolchain represents the toolchain on the system +type Toolchain struct { + Path string `yaml:"path"` +} + +// ProjectVersion represents a semantic version number +type Version struct { + Major int64 `yaml:"major"` + Minor int64 `yaml:"minor"` + Patch int64 `yaml:"patch"` + Hotfix *string `yaml:"hotfix"` +} + +// ProjectConfig represents an Espresso project +type ProjectConfig struct { + Name string `yaml:"name"` + Version Version `yaml:"version"` + BasePackage string `yaml:"basePackage"` + Toolchain Toolchain `yaml:"toolchain"` + Dependencies []Dependency `yaml:"dependencies"` + Registries []Registry `yaml:"registries"` +} + +// UnmarshalConfig marshals the given ProjectConfig to yml +func UnmarshalConfig(cfgYml string) (ProjectConfig, error) { + var cfg ProjectConfig + err := yaml.Unmarshal([]byte(cfgYml), &cfg) + if err != nil { + return ProjectConfig{}, err + } + return cfg, nil +} + +// MarshalConfig marshals the given ProjectConfig to yml +func MarshalConfig(cfg ProjectConfig) (*string, error) { + data, err := yaml.Marshal(cfg) + if err != nil { + return nil, err + } + resp := string(data) + return &resp, nil +} + +// readConfigFromFileSystem reads and parses the config from the filesystem. +func readConfigFromFileSystem() (ProjectConfig, error) { + // get the config path + configPath, err := GetConfigPath() + if err != nil { + return ProjectConfig{}, err + } + + // read the file + configYml, err := os.ReadFile(configPath) + if err != nil { + return ProjectConfig{}, err + } + + // unmarshal it + projectConfig, err := UnmarshalConfig(string(configYml)) + if err != nil { + return ProjectConfig{}, err + } + + return projectConfig, nil +} diff --git a/core/context/project/context.go b/core/context/project/context.go new file mode 100644 index 0000000..e9e6ddc --- /dev/null +++ b/core/context/project/context.go @@ -0,0 +1,41 @@ +package project + +import "sync" + +// ProjectContext provides context for this instances current project (if any) +type ProjectContext struct { + Config ProjectConfig + ConfigPath string + SourcePath string +} + +var projectContext *ProjectContext = nil +var once sync.Once + +// GetProjectContext lazily loads the ProjectContext singleton +func GetProjectContext() (*ProjectContext, error) { + var err error + once.Do(func() { + // get the config path + configPath, e := GetConfigPath() + if e != nil { + err = e + return + } + + // get our config + config, e := readConfigFromFileSystem() + if e != nil { + err = e + return + } + + // set the project context + projectContext = &ProjectContext{Config: config, ConfigPath: configPath} + }) + if err != nil { + return nil, err + } + + return projectContext, nil +} diff --git a/core/context/project/fs.go b/core/context/project/fs.go new file mode 100644 index 0000000..b2f0d3d --- /dev/null +++ b/core/context/project/fs.go @@ -0,0 +1,100 @@ +package project + +import ( + "os" + "strings" + + "kerosenelabs.com/espresso/core/util" +) + +// getConfigPath gets the absolute path to the config file +func GetConfigPath() (string, error) { + wd, err := os.Getwd() + if err != nil { + return "", err + } + + if util.IsDebugMode() { + path := wd + "/ESPRESSO_DEBUG" + "/espresso.yml" + return path, nil + } else { + path := wd + "/espresso.yml" + return path, nil + } +} + +// getSourcePath gets the path at which there should be source files +func getSourcePath(projectConfig ProjectConfig) (string, error) { + wd, err := os.Getwd() + if err != nil { + return "", err + } + path := wd + + if util.IsDebugMode() { + path += "/ESPRESSO_DEBUG" + } + + path += "/src/java/" + strings.ReplaceAll(projectConfig.BasePackage, ".", "/") + return path, nil +} + +// Persist persists the configuration under the EnvironmentContext to the filesystem +func Persist(projectContext ProjectContext) error { + marshalData, err := MarshalConfig(projectContext.Config) + if err != nil { + return err + } + + // write the config + cfgExists, err := util.DoesPathExist(projectContext.ConfigPath) + if err != nil { + return err + } + + if cfgExists { + file, err := os.Open(projectContext.ConfigPath) + if err != nil { + return err + } + file.WriteString(*marshalData) + defer file.Close() + + } else { + file, err := os.Create(projectContext.ConfigPath) + if err != nil { + return err + } + file.WriteString(*marshalData) + defer file.Close() + } + + return nil +} + +// WriteExampleCode is an internal function that writes example code to a newly created project +func WriteExampleCode(projectContext ProjectContext) error { + os.MkdirAll(projectContext.SourcePath, 0755) + + // create the main file + file, err := os.Create(projectContext.SourcePath + "/Main.java") + if err != nil { + return err + } + defer file.Close() + + // write some code + code := `package ${BASE_PACKAGE}; + +import java.lang.System; + +public class Main { + public static void main(String[] args) { + System.out.println("Hello from Espresso!"); + } +}` + code = strings.ReplaceAll(code, "${BASE_PACKAGE}", projectContext.Config.BasePackage) + file.WriteString(code) + return nil + +} diff --git a/core/context/project/util.go b/core/context/project/util.go new file mode 100644 index 0000000..d2ffb3d --- /dev/null +++ b/core/context/project/util.go @@ -0,0 +1,12 @@ +package project + +import "fmt" + +// GetVersionAsString gets a project version as a string. +func GetVersionAsString(version Version) string { + versionString := fmt.Sprintf("%d.%d.%d", version.Major, version.Minor, version.Patch) + if version.Hotfix != nil { + versionString += *version.Hotfix + } + return versionString +} diff --git a/core/dependency/cache.go b/core/dependency/cache.go new file mode 100644 index 0000000..aee1111 --- /dev/null +++ b/core/dependency/cache.go @@ -0,0 +1,38 @@ +// Copyright (c) 2024 Kerosene Labs +// This file is part of Espresso, which is licensed under the MIT License. +// See the LICENSE file for details. + +package dependency + +import ( + "os" + + "kerosenelabs.com/espresso/core/registry" + "kerosenelabs.com/espresso/core/util" +) + +// CacheResolvedDependency fetches the resolved dependency from the internet +func CacheResolvedDependency(resolvedDependency ResolvedDependency) error { + // get our package signature + packageSignature := registry.CalculatePackageSignature(resolvedDependency.Package, resolvedDependency.PackageVersion) + + // get where we should store this package + espressoPath, err := util.GetEspressoDirectoryPath() + if err != nil { + return err + } + pkgPath := espressoPath + "/cachedPackages/" + packageSignature + ".jar" + + // ensure the package path exists + err = os.MkdirAll(espressoPath+"/cachedPackages", 0755) + if err != nil { + return err + } + + // download the file + err = util.DownloadFile(pkgPath, resolvedDependency.PackageVersion.ArtifactUrl) + if err != nil { + return err + } + return nil +} diff --git a/core/dependency/fs.go b/core/dependency/fs.go index d110ed7..2121e26 100644 --- a/core/dependency/fs.go +++ b/core/dependency/fs.go @@ -1,9 +1,13 @@ +// Copyright (c) 2024 Kerosene Labs +// This file is part of Espresso, which is licensed under the MIT License. +// See the LICENSE file for details. + package dependency import ( "os" - "hlafaille.xyz/espresso/v0/core/util" + "kerosenelabs.com/espresso/core/util" ) // EnsurePackagesDirectory ensures the packages directory exists under the given registry cache. diff --git a/core/dependency/implementation.go b/core/dependency/implementation.go deleted file mode 100644 index 838cf0b..0000000 --- a/core/dependency/implementation.go +++ /dev/null @@ -1,85 +0,0 @@ -package dependency - -import ( - "errors" - "fmt" - "os" - - "hlafaille.xyz/espresso/v0/core/project" - "hlafaille.xyz/espresso/v0/core/registry" - "hlafaille.xyz/espresso/v0/core/util" -) - -// ResolvedDependency represents a match between a project dependency and a registry package. -type ResolvedDependency struct { - Dependency *project.Dependency - Package *registry.Package - PackageVersion *registry.PackageVersionDeclaration -} - -// ResolveDependency resolves the given dependency. This function will iterate over the given registries, -// their packages, and each version of that package. The first match is the once that will be returned. -// Registries follow hierarchical order, so the top-most one is the one that is searched first. -func ResolveDependency(dep *project.Dependency, regs *[]project.Registry) (*ResolvedDependency, error) { - if dep == nil || regs == nil { - return nil, errors.New("required argument was a nil pointer") - } - - // get our version string - depVersionStr := project.GetVersionAsString(&dep.Version) - - // iterate over each registry - for _, reg := range *regs { - // get this registry's packages on the filesystem cache - pkgs, err := registry.GetRegistryPackages(reg) - if err != nil { - return nil, err - } - // iterate over each package - for _, pkg := range pkgs { - // if we have a match via group and name, match a version - if pkg.Group == dep.Group && pkg.Name == dep.Name { - for _, version := range pkg.Versions { - if version.Number == depVersionStr { - return &ResolvedDependency{ - Dependency: dep, - Package: &pkg, - PackageVersion: &version, - }, nil - } - } - } - } - } - return nil, fmt.Errorf("'%s:%s:%s' dependency was unable to be resolved within any given registry", dep.Group, dep.Name, depVersionStr) -} - -// CacheResolvedDependency fetches the resolved dependency from the internet -func CacheResolvedDependency(rdep *ResolvedDependency) error { - if rdep == nil { - return errors.New("rdep was nil") - } - - // get our package signature - packageSignature := registry.CalculatePackageSignature(rdep.Package, rdep.PackageVersion) - - // get where we should store this package - espressoPath, err := util.GetEspressoDirectoryPath() - if err != nil { - return err - } - pkgPath := espressoPath + "/cachedPackages/" + packageSignature + ".jar" - - // ensure the package path exists - err = os.MkdirAll(espressoPath+"/cachedPackages", 0755) - if err != nil { - return err - } - - // download the file - err = util.DownloadFile(pkgPath, rdep.PackageVersion.ArtifactUrl) - if err != nil { - return err - } - return nil -} diff --git a/core/dependency/resolve.go b/core/dependency/resolve.go new file mode 100644 index 0000000..a810a51 --- /dev/null +++ b/core/dependency/resolve.go @@ -0,0 +1,52 @@ +// Copyright (c) 2024 Kerosene Labs +// This file is part of Espresso, which is licensed under the MIT License. +// See the LICENSE file for details. + +package dependency + +import ( + "fmt" + + "kerosenelabs.com/espresso/core/context/project" + "kerosenelabs.com/espresso/core/registry" +) + +// ResolvedDependency represents a match between a project dependency and a registry package. +type ResolvedDependency struct { + Dependency project.Dependency + Package registry.Package + PackageVersion registry.PackageVersionDeclaration +} + +// ResolveDependency resolves the given dependency. This function will iterate over the given registries, +// their packages, and each version of that package. The first match is the once that will be returned. +// Registries follow hierarchical order, so the top-most one is the one that is searched first. +func ResolveDependency(dependency project.Dependency, registries []project.Registry) (ResolvedDependency, error) { + // get our version string + depVersionStr := project.GetVersionAsString(dependency.Version) + + // iterate over each registry + for _, reg := range registries { + // get this registry's packages on the filesystem cache + pkgs, err := registry.GetRegistryPackages(reg) + if err != nil { + return ResolvedDependency{}, err + } + // iterate over each package + for _, pkg := range pkgs { + // if we have a match via group and name, match a version + if pkg.Group == dependency.Group && pkg.Name == dependency.Name { + for _, version := range pkg.Versions { + if version.Number == depVersionStr { + return ResolvedDependency{ + Dependency: dependency, + Package: pkg, + PackageVersion: version, + }, nil + } + } + } + } + } + return ResolvedDependency{}, fmt.Errorf("'%s:%s:%s' dependency was unable to be resolved within any given registry", dependency.Group, dependency.Name, depVersionStr) +} diff --git a/core/project/config.go b/core/project/config.go deleted file mode 100644 index bf65817..0000000 --- a/core/project/config.go +++ /dev/null @@ -1,194 +0,0 @@ -package project - -import ( - "fmt" - "os" - "strings" - - "gopkg.in/yaml.v3" - "hlafaille.xyz/espresso/v0/core/util" -) - -// Dependency represents a particular dependency -type Dependency struct { - Group string `yaml:"group"` - Name string `yaml:"name"` - Version Version `yaml:"version"` -} - -// Registry represents a particular repository -type Registry struct { - Name string `yaml:"name"` - Url string `yaml:"url"` -} - -// Toolchain represents the toolchain on the system -type Toolchain struct { - Path string `yaml:"path"` -} - -// ProjectVersion represents a semantic version number -type Version struct { - Major int64 `yaml:"major"` - Minor int64 `yaml:"minor"` - Patch int64 `yaml:"patch"` - Hotfix *string `yaml:"hotfix"` -} - -// ProjectConfig represents an Espresso project -type ProjectConfig struct { - Name string `yaml:"name"` - Version Version `yaml:"version"` - BasePackage string `yaml:"basePackage"` - Toolchain Toolchain `yaml:"toolchain"` - Dependencies []Dependency `yaml:"dependencies"` - Registries []Registry `yaml:"registries"` -} - -// UnmarshalConfig marshals the given ProjectConfig to yml -func UnmarshalConfig(cfgYml string) (*ProjectConfig, error) { - var cfg ProjectConfig - err := yaml.Unmarshal([]byte(cfgYml), &cfg) - if err != nil { - return nil, err - } - return &cfg, nil -} - -// MarshalConfig marshals the given ProjectConfig to yml -func MarshalConfig(cfg *ProjectConfig) (*string, error) { - data, err := yaml.Marshal(cfg) - if err != nil { - return nil, err - } - resp := string(data) - return &resp, nil -} - -// GetConfig reads the config from the filesystem and returns a pointer to a ProjectConfig, or an error -func GetConfig() (*ProjectConfig, error) { - // get the config path for this context - path, err := GetConfigPath() - if err != nil { - return nil, err - } - - // read the file - file, err := os.ReadFile(*path) - if err != nil { - return nil, err - } - - // unmarshal into a cfg struct - cfg, err := UnmarshalConfig(string(file)) - if err != nil { - return nil, err - } - return cfg, nil -} - -// ConfigExists gets if a config exists at the current directory -func ConfigExists() (*bool, error) { - configPath, err := GetConfigPath() - if err != nil { - return nil, err - } - - _, err = os.Stat(*configPath) - resp := err == nil || !os.IsNotExist(err) - return &resp, nil -} - -// WriteExampleCode is an internal function that writes example code to a newly created project -func WriteExampleCode(cfg *ProjectConfig) error { - path, _ := GetSourcePath(cfg) - os.MkdirAll(*path, 0755) - - // create the main file - file, err := os.Create(*path + "/Main.java") - if err != nil { - return err - } - defer file.Close() - - // write some code - code := `package ${BASE_PACKAGE}; - -import java.lang.System; - -public class Main { - public static void main(String[] args) { - System.out.println("Hello from Espresso!"); - } -}` - code = strings.ReplaceAll(code, "${BASE_PACKAGE}", cfg.BasePackage) - file.WriteString(code) - return nil - -} - -// PersistConfig persists the given ProjectConfig to the filesystem -func PersistConfig(cfg *ProjectConfig) error { - marshalData, err := MarshalConfig(cfg) - if err != nil { - return err - } - - cfgPath, err := GetConfigPath() - if err != nil { - return err - } - - // write the config - cfgExists, err := ConfigExists() - if err != nil { - return err - } - - if *cfgExists { - file, err := os.Open(*cfgPath) - if err != nil { - return err - } - file.WriteString(*marshalData) - defer file.Close() - - } else { - file, err := os.Create(*cfgPath) - if err != nil { - return err - } - file.WriteString(*marshalData) - defer file.Close() - } - - return nil -} - -// GetConfigPath gets the absolute path to the config file -func GetConfigPath() (*string, error) { - wd, err := os.Getwd() - if err != nil { - return nil, err - } - - if util.IsDebugMode() { - path := wd + "/ESPRESSO_DEBUG" + "/espresso.yml" - return &path, nil - } else { - path := wd + "/espresso.yml" - return &path, nil - } -} - -// GetVersionAsString gets a project version as a string. -func GetVersionAsString(version *Version) string { - if version == nil { - panic("version is nil") - } - versionString := fmt.Sprintf("%d.%d.%d", version.Major, version.Minor, version.Patch) - if version.Hotfix != nil { - versionString += *version.Hotfix - } - return versionString -} diff --git a/core/registry/implementation.go b/core/registry/implementation.go index 4e4f024..ad3085b 100644 --- a/core/registry/implementation.go +++ b/core/registry/implementation.go @@ -1,3 +1,7 @@ +// Copyright (c) 2024 Kerosene Labs +// This file is part of Espresso, which is licensed under the MIT License. +// See the LICENSE file for details. + package registry import ( @@ -8,8 +12,8 @@ import ( "strings" "gopkg.in/yaml.v3" - "hlafaille.xyz/espresso/v0/core/project" - "hlafaille.xyz/espresso/v0/core/util" + "kerosenelabs.com/espresso/core/context/project" + "kerosenelabs.com/espresso/core/util" ) // Package is a high level abstraction on top of the raw filesystem based registry caching. Package represents @@ -58,7 +62,7 @@ func MarshalPackageDeclaration(pkgDecl *PackageDeclaration) (string, error) { } // InvalidateRegistry invalidates a particular registry -func InvalidateRegistryCache(reg *project.Registry) error { +func InvalidateRegistryCache(reg project.Registry) error { // get our home dir cachePath, err := GetRegistryCachePath(reg) if err != nil { @@ -71,7 +75,7 @@ func InvalidateRegistryCache(reg *project.Registry) error { } // CacheRegistry downloads a zip archive representing an espresso registry and extracts it to the proper directory -func CacheRegistry(reg *project.Registry) error { +func CacheRegistry(reg project.Registry) error { // get our cache path cachePath, err := GetRegistryCachePath(reg) if err != nil { @@ -100,7 +104,6 @@ func CacheRegistry(reg *project.Registry) error { } // extract the archive - fmt.Println("Extracting") util.Unzip(cachePath+"/archive.zip", cachePath+"/lookup") // check if the registry lookup contains a dependencies folder @@ -112,7 +115,6 @@ func CacheRegistry(reg *project.Registry) error { fmt.Println("An eror occurred: this registry's lookup appears invalid: no packages directory") } - fmt.Println("Done") return nil } @@ -169,9 +171,6 @@ func GetRegistryPackages(reg project.Registry) ([]Package, error) { // GenerateSignature generates a unique signature of a package and version. This can be used to uniquely // reference a local copy of a packages across registries. -func CalculatePackageSignature(dep *Package, version *PackageVersionDeclaration) string { - if dep == nil || version == nil { - panic("dep or version was nil") - } +func CalculatePackageSignature(dep Package, version PackageVersionDeclaration) string { return fmt.Sprintf("%x", sha256.Sum256([]byte(fmt.Sprintf("%s:%s:%s", dep.Group, dep.Name, version.Number)))) } diff --git a/core/registry/path.go b/core/registry/path.go index 5b29ca7..4fb3df8 100644 --- a/core/registry/path.go +++ b/core/registry/path.go @@ -1,15 +1,19 @@ +// Copyright (c) 2024 Kerosene Labs +// This file is part of Espresso, which is licensed under the MIT License. +// See the LICENSE file for details. + package registry import ( "io/fs" "path/filepath" - "hlafaille.xyz/espresso/v0/core/project" - "hlafaille.xyz/espresso/v0/core/util" + "kerosenelabs.com/espresso/core/context/project" + "kerosenelabs.com/espresso/core/util" ) // GetCachePath gets the full cache path from the registry (ex: /home/vscode/.espresso/registries/espresso-registry) -func GetRegistryCachePath(reg *project.Registry) (string, error) { +func GetRegistryCachePath(reg project.Registry) (string, error) { // get our home dir espressoPath, err := util.GetEspressoDirectoryPath() if err != nil { @@ -20,7 +24,7 @@ func GetRegistryCachePath(reg *project.Registry) (string, error) { // GetRegistryCachePackagesLookupPath gets the full cache path of the registry package lookup // (ex: /home/vscode/.espresso/registries/espresso-registry/lookup/espresso-registry-main/packages) -func GetRegistryCachePackagesLookupPath(reg *project.Registry) (string, error) { +func GetRegistryCachePackagesLookupPath(reg project.Registry) (string, error) { regCachePath, err := GetRegistryCachePath(reg) if err != nil { return "", err @@ -32,7 +36,7 @@ func GetRegistryCachePackagesLookupPath(reg *project.Registry) (string, error) { // and looks for group directories (ex: org.projectlombok) func walkRegistryLookup(reg project.Registry) ([]string, error) { // get the cache path - cachePath, err := GetRegistryCachePackagesLookupPath(®) + cachePath, err := GetRegistryCachePackagesLookupPath(reg) if err != nil { return []string{}, err } diff --git a/core/service/build.go b/core/service/build.go new file mode 100644 index 0000000..f4af622 --- /dev/null +++ b/core/service/build.go @@ -0,0 +1,95 @@ +// Copyright (c) 2024 Kerosene Labs +// This file is part of Espresso, which is licensed under the MIT License. +// See the LICENSE file for details. + +package service + +import ( + "fmt" + "os" + "sync" + + "github.com/fatih/color" + "kerosenelabs.com/espresso/core/context/project" + "kerosenelabs.com/espresso/core/dependency" + "kerosenelabs.com/espresso/core/registry" + "kerosenelabs.com/espresso/core/source" + "kerosenelabs.com/espresso/core/toolchain" + "kerosenelabs.com/espresso/core/util" +) + +// BuildProject is a service function for building the current project +func BuildProject() { + // get our project context + projectContext, err := project.GetProjectContext() + if err != nil { + util.ErrorQuit("An error occurred while getting the project context: %s", err) + } + + color.Cyan("-- [%s] Beginning build, please ensure you are compliant with all dependency licenses\n", projectContext.Config.Name) + + // discover source files + files, err := source.DiscoverSourceFiles(projectContext.Config) + if err != nil { + util.ErrorQuit(fmt.Sprintf("An error occurred while discovering source files: %s\n", err)) + } + + // run the compiler on each source file + color.Cyan("-- Compiling") + var wg sync.WaitGroup + for _, value := range files { + wg.Add(1) + go func(f *source.SourceFile) { + defer wg.Done() + err := toolchain.CompileSourceFile(projectContext.Config, value) + if err != nil { + util.ErrorQuit("An error occurred while compiling a source file: %s\n", err) + } + color.Blue("Compiled: " + f.Path) + }(&value) + } + wg.Wait() + + // package the project + color.Cyan("-- Packaging distributable") + err = toolchain.PackageClasses(projectContext.Config) + if err != nil { + util.ErrorQuit(fmt.Sprintf("An error occurred while packaging the classes: %s\n", err)) + } + color.Blue("Finished packaging distributable") + + // iterate over each dependency, resolve it and copy it + distPath, err := toolchain.GetDistPath(projectContext.Config) + if err != nil { + util.ErrorQuit(fmt.Sprintf("Unable to get dist path: %s", err)) + } + os.MkdirAll(*distPath+"/libs", 0755) + var depCopyWg sync.WaitGroup + color.Cyan("-- Copying dependency packages to distributable") + for _, dep := range projectContext.Config.Dependencies { + depCopyWg.Add(1) + go func() { + defer depCopyWg.Done() + color.Cyan("-- Beginning copy of '%s:%s' to distributable", dep.Group, dep.Name) + resolved, err := dependency.ResolveDependency(dep, projectContext.Config.Registries) + if err != nil { + util.ErrorQuit(fmt.Sprintf("Unable to resolve dependency: %s", err)) + } + + // calculate the should-be location of this jar locally + espressoPath, err := util.GetEspressoDirectoryPath() + if err != nil { + util.ErrorQuit(fmt.Sprintf("Unable to get the espresso home: %s", espressoPath)) + } + signature := registry.CalculatePackageSignature(resolved.Package, resolved.PackageVersion) + cachedPackageHome := espressoPath + "/cachedPackages" + signature + ".jar" + + // copy the file + util.CopyFile(cachedPackageHome, *distPath+"/libs") + + color.Blue("Copied '%s:%s' to distributable", dep.Group, dep.Name) + }() + } + depCopyWg.Wait() + color.Green("Done!") +} diff --git a/core/service/clean.go b/core/service/clean.go new file mode 100644 index 0000000..83b2281 --- /dev/null +++ b/core/service/clean.go @@ -0,0 +1,49 @@ +// Copyright (c) 2024 Kerosene Labs +// This file is part of Espresso, which is licensed under the MIT License. +// See the LICENSE file for details. + +package service + +import ( + "os" + + "github.com/fatih/color" + "kerosenelabs.com/espresso/core/context/project" + "kerosenelabs.com/espresso/core/toolchain" + "kerosenelabs.com/espresso/core/util" +) + +// CleanWorkspace is a service function to clean the current workspace. +func CleanWorkspace() { + // get our project context + projectContext, err := project.GetProjectContext() + if err != nil { + util.ErrorQuit("An error occurred while getting the project context: %s", err) + } + + // get the build dir + buildPath, err := toolchain.GetBuildPath(projectContext.Config) + if err != nil { + util.ErrorQuit("An error occurred while getting the build path: %s", err) + } + + // get the dist dir + distPath, err := toolchain.GetDistPath(projectContext.Config) + if err != nil { + util.ErrorQuit("An error occurred while getting the build path: %s", err) + } + + // remove the build dir + err = os.RemoveAll(*buildPath) + if err != nil { + util.ErrorQuit("An error occurred while deleting the build path: %s", err) + } + + // remove the dist dir + err = os.RemoveAll(*distPath) + if err != nil { + util.ErrorQuit("An error occurred while deleting the dist path: %s", err) + } + + color.Green("Cleaned") +} diff --git a/core/service/dependency.go b/core/service/dependency.go new file mode 100644 index 0000000..abbaf32 --- /dev/null +++ b/core/service/dependency.go @@ -0,0 +1,47 @@ +// Copyright (c) 2024 Kerosene Labs +// This file is part of Espresso, which is licensed under the MIT License. +// See the LICENSE file for details. + +package service + +import ( + "fmt" + "sync" + + "github.com/fatih/color" + "kerosenelabs.com/espresso/core/context/project" + "kerosenelabs.com/espresso/core/dependency" + "kerosenelabs.com/espresso/core/util" +) + +// SyncDependencies is a service function to iterate over each dependency and save it within the user's espresso cached packages +func SyncDependencies() { + // get our project context + projectContext, err := project.GetProjectContext() + if err != nil { + util.ErrorQuit("An error occurred while getting the project context: %s", err) + } + + // iterate over the dependencies + var wg sync.WaitGroup + for _, dep := range projectContext.Config.Dependencies { + wg.Add(1) + go func() { + defer wg.Done() + displayStr := fmt.Sprintf("%s:%s:%s", dep.Group, dep.Name, project.GetVersionAsString(dep.Version)) + color.Cyan("[%s] Resolving", displayStr) + rdep, err := dependency.ResolveDependency(dep, projectContext.Config.Registries) + if err != nil { + util.ErrorQuit(fmt.Sprintf("[%s] An error occurred while resolving dependencies: %s\n", displayStr, err)) + } + + // cache the resolved dependency + err = dependency.CacheResolvedDependency(rdep) + if err != nil { + util.ErrorQuit(fmt.Sprintf("[%s] An error occurred while caching the resolved dependency: %s\n", displayStr, err)) + } + color.Green("[%s] Cached", displayStr) + }() + } + wg.Wait() +} diff --git a/core/service/init.go b/core/service/init.go new file mode 100644 index 0000000..8e6f2ef --- /dev/null +++ b/core/service/init.go @@ -0,0 +1,72 @@ +// Copyright (c) 2024 Kerosene Labs +// This file is part of Espresso, which is licensed under the MIT License. +// See the LICENSE file for details. + +package service + +import ( + "fmt" + + "kerosenelabs.com/espresso/core/context/project" + "kerosenelabs.com/espresso/core/util" +) + +// InitializeProject is a service function for initializing a new project +func InitializeProject(name string, basePkg *string) { + // ensure JAVA_HOME is set + javaHome, err := util.GetJavaHome() + if err != nil { + util.ErrorQuit("JAVA_HOME is not set; do you have java installed?") + } + + // ensure a proejct doesn't already exist + // cfgExists, err := project.DoesExist() + // if err != nil { + // fmt.Println("Error occurred while ensuring a config doesn't already exist") + // panic(err) + // } else if *cfgExists { + // fmt.Println("Config already exists") + // os.Exit(1) + // } + + fmt.Printf("Creating '%s'\n", name) + + // create a base config + config := project.ProjectConfig{ + Name: name, + Version: project.Version{ + Major: 0, + Minor: 1, + Patch: 0, + Hotfix: nil, + }, + BasePackage: *basePkg, + Toolchain: project.Toolchain{ + Path: *javaHome, + }, + Registries: []project.Registry{{Name: "espresso-registry", Url: "https://github.com/Kerosene-Labs/espresso-registry/archive/refs/heads/main.zip"}}, + Dependencies: []project.Dependency{}, + } + + // get our config path + configPath, err := project.GetConfigPath() + if err != nil { + util.ErrorQuit("An error occurred while getting the config path: %s", err) + } + + // create our project context + projectContext := project.ProjectContext{ + Config: config, + ConfigPath: configPath, + } + + // write some example code + println("Creating base package, writing example code") + project.WriteExampleCode(projectContext) + + // persist the config + println("Persisting project configuration") + project.Persist(projectContext) + + println("Done.") +} diff --git a/core/service/registry.go b/core/service/registry.go new file mode 100644 index 0000000..50f513a --- /dev/null +++ b/core/service/registry.go @@ -0,0 +1,106 @@ +// Copyright (c) 2024 Kerosene Labs +// This file is part of Espresso, which is licensed under the MIT License. +// See the LICENSE file for details. + +package service + +import ( + "fmt" + "os" + "strings" + "sync" + + "github.com/fatih/color" + "github.com/olekukonko/tablewriter" + "kerosenelabs.com/espresso/core/context/project" + "kerosenelabs.com/espresso/core/registry" + "kerosenelabs.com/espresso/core/util" +) + +// QueryRegistries is a service function for querying all registries declared within a project +func QueryRegistries(term string) { + // get our project context + projectContext, err := project.GetProjectContext() + if err != nil { + util.ErrorQuit("An error occurred while getting the project context: %s", err) + } + + // iterate over each registry, get its packages + var filteredPkgs []registry.Package = []registry.Package{} + for _, reg := range projectContext.Config.Registries { + color.Blue("Checking '%s'", reg.Name) + regPkgs, err := registry.GetRegistryPackages(reg) + if err != nil { + util.ErrorQuit("An error occurred while fetching packages from the '%s' registry cache: %s", reg.Name, err) + } + + // filter by name + for _, pkg := range regPkgs { + if term == "*" || + strings.Contains(strings.ToLower(pkg.Name), strings.ToLower(term)) || + strings.Contains(strings.ToLower(pkg.Description), strings.ToLower(term)) { + filteredPkgs = append(filteredPkgs, pkg) + } + } + } + + // print out our packages + color.Cyan("Found %v package(s):", len(filteredPkgs)) + data := [][]string{} + for _, filtered := range filteredPkgs { + data = append(data, []string{ + filtered.Group, + filtered.Name, + filtered.Versions[len(filtered.Versions)-1].Number, + }) + } + + table := tablewriter.NewWriter(os.Stdout) + table.SetHeader([]string{"Group", "Package", "Latest Version"}) + + for _, v := range data { + table.Append(v) + } + table.Render() +} + +// InvalidateRegistryCaches is a service function that invalidates and recaches all registries declared within the project. +func InvalidateRegistryCaches() { + // get our project context + projectContext, err := project.GetProjectContext() + if err != nil { + util.ErrorQuit("An error occurred while getting the project context: %s", err) + } + + // iterate over each registry, invalidate it + var invalWg sync.WaitGroup + for _, reg := range projectContext.Config.Registries { + invalWg.Add(1) + go func() { + defer invalWg.Done() + color.Cyan("[%s] Invalidating cache", reg.Name) + err = registry.InvalidateRegistryCache(reg) + if err != nil { + util.ErrorQuit("An error occurred while invalidaing cache(s): %s", err) + } + color.Blue("[%s] Invalidated", reg.Name) + }() + } + invalWg.Wait() + + // iterate over each registry, download the zip + var dlWg sync.WaitGroup + for _, reg := range projectContext.Config.Registries { + dlWg.Add(1) + go func() { + defer dlWg.Done() + color.Cyan("[%s] Downloading archive", reg.Name) + err = registry.CacheRegistry(reg) + if err != nil { + util.ErrorQuit(fmt.Sprintf("An error occurred while downloading the registry archive: %s\n", err)) + } + color.Blue("[%s] Finished caching", reg.Name) + }() + } + dlWg.Wait() +} diff --git a/core/service/version.go b/core/service/version.go new file mode 100644 index 0000000..3d3238d --- /dev/null +++ b/core/service/version.go @@ -0,0 +1,15 @@ +// Copyright (c) 2024 Kerosene Labs +// This file is part of Espresso, which is licensed under the MIT License. +// See the LICENSE file for details. + +package service + +import "fmt" + +var CommitSha string +var Version string + +// PrintVersion prints the version and commit sha to the standard output +func PrintVersion() { + fmt.Printf("ver='%s' commit='%s'\n", Version, CommitSha) +} diff --git a/core/project/source_discovery.go b/core/source/discovery.go similarity index 75% rename from core/project/source_discovery.go rename to core/source/discovery.go index 06a1c69..3de5a4a 100644 --- a/core/project/source_discovery.go +++ b/core/source/discovery.go @@ -1,10 +1,16 @@ -package project +// Copyright (c) 2024 Kerosene Labs +// This file is part of Espresso, which is licensed under the MIT License. +// See the LICENSE file for details. + +package source import ( "io/fs" "os" "path/filepath" "strings" + + "kerosenelabs.com/espresso/core/context/project" ) // SourceFIle represents a .java source file on the filesystem @@ -15,7 +21,7 @@ type SourceFile struct { // DiscoverSourceFiles iterates over the project's base package looking for .java files // TODO: make this a goroutine? -func DiscoverSourceFiles(cfg *ProjectConfig) ([]SourceFile, error) { +func DiscoverSourceFiles(cfg project.ProjectConfig) ([]SourceFile, error) { // get the source path srcPath, err := GetSourcePath(cfg) if err != nil { diff --git a/core/project/source_util.go b/core/source/util.go similarity index 50% rename from core/project/source_util.go rename to core/source/util.go index 15db4f4..095a689 100644 --- a/core/project/source_util.go +++ b/core/source/util.go @@ -1,14 +1,19 @@ -package project +// Copyright (c) 2024 Kerosene Labs +// This file is part of Espresso, which is licensed under the MIT License. +// See the LICENSE file for details. + +package source import ( "os" "strings" - "hlafaille.xyz/espresso/v0/core/util" + "kerosenelabs.com/espresso/core/context/project" + "kerosenelabs.com/espresso/core/util" ) // GetProjectPath gets the source path from the given ProjectConfig -func GetSourcePath(cfg *ProjectConfig) (*string, error) { +func GetSourcePath(cfg project.ProjectConfig) (*string, error) { wd, err := os.Getwd() if err != nil { return nil, err diff --git a/core/toolchain/compiler.go b/core/toolchain/compiler.go index 9c04e4a..33cf49a 100644 --- a/core/toolchain/compiler.go +++ b/core/toolchain/compiler.go @@ -1,22 +1,35 @@ +// Copyright (c) 2024 Kerosene Labs +// This file is part of Espresso, which is licensed under the MIT License. +// See the LICENSE file for details. + package toolchain import ( "errors" "os/exec" - "hlafaille.xyz/espresso/v0/core/project" - "hlafaille.xyz/espresso/v0/core/util" + "kerosenelabs.com/espresso/core/context/project" + "kerosenelabs.com/espresso/core/source" + "kerosenelabs.com/espresso/core/util" ) // CompileSourceFile compiles the sourcefile with the given project toolchain -func CompileSourceFile(cfg *project.ProjectConfig, srcFile *project.SourceFile) error { +func CompileSourceFile(cfg project.ProjectConfig, srcFile source.SourceFile) error { + // build our classpath value + cpVal := "" + if util.IsDebugMode() { + cpVal = "ESPRESSO_DEBUG/src/java" + } else { + cpVal = "src/java" + } + // run the compiler command := cfg.Toolchain.Path + "/bin/javac" args := []string{} if util.IsDebugMode() { - args = append(args, "-cp", "ESPRESSO_DEBUG/src/java", "-d", "ESPRESSO_DEBUG/build") + args = append(args, "-cp", cpVal, "-d", "ESPRESSO_DEBUG/build") } else { - args = append(args, "-cp", "src/java", "build") + args = append(args, "-cp", cpVal, "-d", "build") } args = append(args, srcFile.Path) cmd := exec.Command(command, args...) diff --git a/core/toolchain/package.go b/core/toolchain/package.go index 8385ee9..e353b62 100644 --- a/core/toolchain/package.go +++ b/core/toolchain/package.go @@ -1,3 +1,7 @@ +// Copyright (c) 2024 Kerosene Labs +// This file is part of Espresso, which is licensed under the MIT License. +// See the LICENSE file for details. + package toolchain import ( @@ -5,12 +9,12 @@ import ( "os" "os/exec" - "hlafaille.xyz/espresso/v0/core/project" - "hlafaille.xyz/espresso/v0/core/util" + "kerosenelabs.com/espresso/core/context/project" + "kerosenelabs.com/espresso/core/util" ) // GenerateManifest generates a JVM manifest -func GenerateManifest(cfg *project.ProjectConfig) string { +func GenerateManifest(cfg project.ProjectConfig) string { base := "Manifest-Version: 1.0\n" base += "Main-Class: " + cfg.BasePackage + ".Main\n" base += "Created-By: Espresso" @@ -18,7 +22,7 @@ func GenerateManifest(cfg *project.ProjectConfig) string { } // Write the Manifest to the build directory -func WriteManifest(cfg *project.ProjectConfig) error { +func WriteManifest(cfg project.ProjectConfig) error { // get the path where it should live buildPath, err := GetBuildPath(cfg) path := *buildPath + "/MANIFEST.MF" @@ -43,7 +47,7 @@ func WriteManifest(cfg *project.ProjectConfig) error { } // PackageClasses creates a .jar of the given classes -func PackageClasses(cfg *project.ProjectConfig) error { +func PackageClasses(cfg project.ProjectConfig) error { command := cfg.Toolchain.Path + "/bin/jar" args := []string{"cfm"} diff --git a/core/toolchain/toolchain_util.go b/core/toolchain/toolchain_util.go index e174855..2df8fbe 100644 --- a/core/toolchain/toolchain_util.go +++ b/core/toolchain/toolchain_util.go @@ -1,14 +1,18 @@ +// Copyright (c) 2024 Kerosene Labs +// This file is part of Espresso, which is licensed under the MIT License. +// See the LICENSE file for details. + package toolchain import ( "os" - "hlafaille.xyz/espresso/v0/core/project" - "hlafaille.xyz/espresso/v0/core/util" + "kerosenelabs.com/espresso/core/context/project" + "kerosenelabs.com/espresso/core/util" ) // GetBuildPath gets the absolute path to the build directory -func GetBuildPath(cfg *project.ProjectConfig) (*string, error) { +func GetBuildPath(cfg project.ProjectConfig) (*string, error) { wd, err := os.Getwd() if err != nil { return nil, err @@ -24,7 +28,7 @@ func GetBuildPath(cfg *project.ProjectConfig) (*string, error) { } // GetDistPath gets the absolute path to the dist directory -func GetDistPath(cfg *project.ProjectConfig) (*string, error) { +func GetDistPath(cfg project.ProjectConfig) (*string, error) { wd, err := os.Getwd() if err != nil { return nil, err diff --git a/core/util/cli.go b/core/util/cli.go new file mode 100644 index 0000000..37394ea --- /dev/null +++ b/core/util/cli.go @@ -0,0 +1,16 @@ +// Copyright (c) 2024 Kerosene Labs +// This file is part of Espresso, which is licensed under the MIT License. +// See the LICENSE file for details. + +package util + +import ( + "os" + + "github.com/fatih/color" +) + +func ErrorQuit(msg string, args ...any) { + color.Red(msg+"\n", args...) + os.Exit(1) +} diff --git a/core/util/context.go b/core/util/context.go index e3ccdf7..3808be3 100644 --- a/core/util/context.go +++ b/core/util/context.go @@ -1,3 +1,7 @@ +// Copyright (c) 2024 Kerosene Labs +// This file is part of Espresso, which is licensed under the MIT License. +// See the LICENSE file for details. + package util import ( diff --git a/core/util/fs.go b/core/util/fs.go index 6491b08..7517701 100644 --- a/core/util/fs.go +++ b/core/util/fs.go @@ -1,3 +1,7 @@ +// Copyright (c) 2024 Kerosene Labs +// This file is part of Espresso, which is licensed under the MIT License. +// See the LICENSE file for details. + package util import ( diff --git a/core/util/hash.go b/core/util/hash.go index a6f68a6..b0e660b 100644 --- a/core/util/hash.go +++ b/core/util/hash.go @@ -1,3 +1,7 @@ +// Copyright (c) 2024 Kerosene Labs +// This file is part of Espresso, which is licensed under the MIT License. +// See the LICENSE file for details. + package util import ( diff --git a/core/util/net.go b/core/util/net.go index af65561..e69cafd 100644 --- a/core/util/net.go +++ b/core/util/net.go @@ -1,3 +1,7 @@ +// Copyright (c) 2024 Kerosene Labs +// This file is part of Espresso, which is licensed under the MIT License. +// See the LICENSE file for details. + package util import ( diff --git a/core/util/path.go b/core/util/path.go index 0057da5..698263a 100644 --- a/core/util/path.go +++ b/core/util/path.go @@ -1,3 +1,7 @@ +// Copyright (c) 2024 Kerosene Labs +// This file is part of Espresso, which is licensed under the MIT License. +// See the LICENSE file for details. + package util import "os/user" diff --git a/extension/oci.go b/extension/oci.go index dd7f280..fdca355 100644 --- a/extension/oci.go +++ b/extension/oci.go @@ -1,3 +1,7 @@ +// Copyright (c) 2024 Kerosene Labs +// This file is part of Espresso, which is licensed under the MIT License. +// See the LICENSE file for details. + package extension // TODO: write out the OCI extension here diff --git a/extension/sbom.go b/extension/sbom.go index 776a7fc..7630db7 100644 --- a/extension/sbom.go +++ b/extension/sbom.go @@ -1,3 +1,7 @@ +// Copyright (c) 2024 Kerosene Labs +// This file is part of Espresso, which is licensed under the MIT License. +// See the LICENSE file for details. + package extension // TODO: write out the software bill of materials extension here diff --git a/extension/shade.go b/extension/shade.go index 7236169..f6b7f58 100644 --- a/extension/shade.go +++ b/extension/shade.go @@ -1,3 +1,7 @@ +// Copyright (c) 2024 Kerosene Labs +// This file is part of Espresso, which is licensed under the MIT License. +// See the LICENSE file for details. + package extension // TODO: write out the shade extension here too diff --git a/go.mod b/go.mod index 8e4bbb2..7ae6f82 100644 --- a/go.mod +++ b/go.mod @@ -1,16 +1,19 @@ -module hlafaille.xyz/espresso/v0 +module kerosenelabs.com/espresso go 1.22.7 require ( - github.com/fatih/color v1.17.0 // indirect + github.com/fatih/color v1.17.0 + github.com/olekukonko/tablewriter v0.0.5 + github.com/spf13/cobra v1.8.1 + gopkg.in/yaml.v3 v3.0.1 +) + +require ( github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.9 // indirect - github.com/olekukonko/tablewriter v0.0.5 // indirect - github.com/spf13/cobra v1.8.1 // indirect github.com/spf13/pflag v1.0.5 // indirect golang.org/x/sys v0.18.0 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index cb1684d..97a6c35 100644 --- a/go.sum +++ b/go.sum @@ -21,6 +21,7 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/main.go b/main.go index ebb8e8a..8d96632 100644 --- a/main.go +++ b/main.go @@ -2,15 +2,19 @@ package main import ( "github.com/spf13/cobra" - "hlafaille.xyz/espresso/v0/cli" + "kerosenelabs.com/espresso/cli" ) +var CommitSha string +var Version string + func main() { var root = &cobra.Command{ Use: "espresso", Short: "A modern Java build tool.", } + root.AddCommand(cli.GetVersionCommand()) root.AddCommand(cli.GetCleanCommand()) root.AddCommand(cli.GetBuildCommand()) root.AddCommand(cli.GetInitCommand())