github.com/sapplications/sb@v0.0.0-20240116135441-1a13cafe3497/smartbuilder.go (about)

     1  // Copyright 2022 Vitalii Noha vitalii.noga@gmail.com. All rights reserved.
     2  
     3  package sb
     4  
     5  import (
     6  	"errors"
     7  	"fmt"
     8  	"os"
     9  	"os/exec"
    10  	"path/filepath"
    11  	"runtime"
    12  
    13  	"github.com/hashicorp/go-hclog"
    14  	"github.com/hashicorp/go-plugin"
    15  )
    16  
    17  // Generate generates smart builder unit (.sb) using smart application unit.
    18  func (b *SmartBuilder) Generate(application string) error {
    19  	defer handleError()
    20  	b.logInfo(fmt.Sprintf("generating \"%s\" application", application))
    21  	// load and check application
    22  	b.ModManager.SetLogger(b.Logger)
    23  	mod, err := b.ModManager.ReadAll()
    24  	if err != nil {
    25  		return err
    26  	}
    27  	b.logTrace(fmt.Sprintf("checking \"%s\" application", application))
    28  	application, err = b.checkApplication(application, mod)
    29  	if err != nil {
    30  		return err
    31  	}
    32  	sources := mod.Items()
    33  	info, err := getApp(application, sources)
    34  	if err != nil {
    35  		return err
    36  	}
    37  	coder := ""
    38  	found := false
    39  	for _, row := range info {
    40  		if row[0] == coderAttrName {
    41  			found = true
    42  			if len(row) > 1 {
    43  				coder = row[1]
    44  			}
    45  			break
    46  		}
    47  	}
    48  	if !found {
    49  		return fmt.Errorf(AttrIsMissingF, coderAttrName, application)
    50  	}
    51  	// process application
    52  	client, raw, err := b.newPlugin(coder)
    53  	if err != nil {
    54  		return err
    55  	}
    56  	defer client.Kill()
    57  	builder := raw.(builder)
    58  	b.logTrace(fmt.Sprintf("generating \"%s\" application using sgo plugin", application))
    59  	if err := builder.Generate(application, &sources); err != nil {
    60  		return err
    61  	}
    62  	return nil
    63  }
    64  
    65  // Build builds an application using the generated items.
    66  func (b *SmartBuilder) Build(application string) error {
    67  	defer handleError()
    68  	b.logInfo(fmt.Sprintf("building \"%s\" application", application))
    69  	// load and check application
    70  	b.ModManager.SetLogger(b.Logger)
    71  	mod, err := b.ModManager.ReadAll()
    72  	if err != nil {
    73  		return err
    74  	}
    75  	application, err = b.checkApplication(application, mod)
    76  	if err != nil {
    77  		return err
    78  	}
    79  	info, err := getApp(application, mod.Items())
    80  	if err != nil {
    81  		return err
    82  	}
    83  	coder := ""
    84  	found := false
    85  	for _, row := range info {
    86  		if row[0] == coderAttrName {
    87  			found = true
    88  			if len(row) > 1 {
    89  				coder = row[1]
    90  			}
    91  			break
    92  		}
    93  	}
    94  	if !found {
    95  		return fmt.Errorf(AttrIsMissingF, coderAttrName, application)
    96  	}
    97  	// process application
    98  	client, raw, err := b.newPlugin(coder)
    99  	if err != nil {
   100  		return err
   101  	}
   102  	defer client.Kill()
   103  	builder := raw.(builder)
   104  	b.logTrace(fmt.Sprintf("generating \"%s\" application using sgo plugin", application))
   105  	if err := builder.Build(application); err != nil {
   106  		return err
   107  	}
   108  	return nil
   109  }
   110  
   111  // Clean removes generated/compiled files.
   112  func (b *SmartBuilder) Clean(application string) error {
   113  	defer handleError()
   114  	b.logInfo(fmt.Sprintf("cleaning \"%s\" application", application))
   115  	// load and check application
   116  	b.ModManager.SetLogger(b.Logger)
   117  	mod, err := b.ModManager.ReadAll()
   118  	if err != nil {
   119  		return err
   120  	}
   121  	application, err = b.checkApplication(application, mod)
   122  	if err != nil {
   123  		return err
   124  	}
   125  	sources := mod.Items()
   126  	info, err := getApp(application, sources)
   127  	if err != nil {
   128  		return err
   129  	}
   130  	coder := ""
   131  	found := false
   132  	for _, row := range info {
   133  		if row[0] == coderAttrName {
   134  			found = true
   135  			if len(row) > 1 {
   136  				coder = row[1]
   137  			}
   138  			break
   139  		}
   140  	}
   141  	if !found {
   142  		return fmt.Errorf(AttrIsMissingF, coderAttrName, application)
   143  	}
   144  	// process application
   145  	client, raw, err := b.newPlugin(coder)
   146  	if err != nil {
   147  		return err
   148  	}
   149  	defer client.Kill()
   150  	builder := raw.(builder)
   151  	if err := builder.Clean(application, &sources); err != nil {
   152  		return err
   153  	}
   154  	return nil
   155  }
   156  
   157  // Run runs the application.
   158  func (b *SmartBuilder) Run(application string) error {
   159  	defer handleError()
   160  	b.logInfo(fmt.Sprintf("running \"%s\" application", application))
   161  	// load and check application
   162  	b.ModManager.SetLogger(b.Logger)
   163  	mod, err := b.ModManager.ReadAll()
   164  	if err != nil {
   165  		return err
   166  	}
   167  	application, err = b.checkApplication(application, mod)
   168  	if err != nil {
   169  		return err
   170  	}
   171  	// run an application
   172  	folder, _ := filepath.Abs(filepath.Dir(os.Args[0]))
   173  	folder = filepath.Join(folder, application)
   174  	if runtime.GOOS == "windows" {
   175  		application += ".exe"
   176  	}
   177  	if _, err = os.Stat(filepath.Join(folder, application)); err != nil {
   178  		if errors.Is(err, os.ErrNotExist) {
   179  			return fmt.Errorf(AppIsMissingInSystemF, application)
   180  		} else {
   181  			return err
   182  		}
   183  	}
   184  	wd, _ := os.Getwd()
   185  	if err = os.Chdir(folder); err != nil {
   186  		return err
   187  	}
   188  	cmd := exec.Command(application)
   189  	output, err := cmd.Output()
   190  	if err == nil {
   191  		fmt.Print(string(output))
   192  	}
   193  	os.Chdir(wd)
   194  	return err
   195  }
   196  
   197  // Version displays a version of the application.
   198  func (b *SmartBuilder) Version() string {
   199  	return AppVersionString
   200  }
   201  
   202  // Init creates a apps.sb module and initialize it with the apps item.
   203  // If the apps item is exist then do nothing.
   204  func (b *SmartBuilder) Init() error {
   205  	b.logInfo("initializing module")
   206  	b.ModManager.SetLogger(b.Logger)
   207  	return b.ModManager.AddItem(DefaultModuleName, AppsItemName)
   208  }
   209  
   210  // ReadAll loads modules.
   211  func (b *SmartBuilder) ReadAll(kind string) (ModReader, error) {
   212  	defer handleError()
   213  	b.logInfo(fmt.Sprint("reading modules"))
   214  	b.ModManager.SetLogger(b.Logger)
   215  	mod, err := b.ModManager.ReadAll()
   216  	if err != nil {
   217  		return nil, err
   218  	}
   219  	return mod, nil
   220  }
   221  
   222  // AddItem adds an item to the module.
   223  func (b *SmartBuilder) AddItem(module, item string) error {
   224  	defer handleError()
   225  	b.logInfo(fmt.Sprintf("adding \"%s\" item to \"%s\" module", item, module))
   226  	b.ModManager.SetLogger(b.Logger)
   227  	return b.ModManager.AddItem(module, item)
   228  }
   229  
   230  // AddDependency adds a dependency to the item.
   231  func (b *SmartBuilder) AddDependency(item, dependency, resolver string, update bool) error {
   232  	defer handleError()
   233  	b.logInfo(fmt.Sprintf("adding \"%s\" dependency to \"%s\" item", dependency, item))
   234  	b.ModManager.SetLogger(b.Logger)
   235  	return b.ModManager.AddDependency(item, dependency, resolver, update)
   236  }
   237  
   238  // DeleteItem deletes the item from the module.
   239  func (b *SmartBuilder) DeleteItem(item string) error {
   240  	defer handleError()
   241  	b.logInfo(fmt.Sprintf("deleting \"%s\" item", item))
   242  	b.ModManager.SetLogger(b.Logger)
   243  	return b.ModManager.DeleteItem(item)
   244  }
   245  
   246  // DeleteDependency deletes the dependency from the item.
   247  func (b *SmartBuilder) DeleteDependency(item, dependency string) error {
   248  	defer handleError()
   249  	b.logInfo(fmt.Sprintf("deleting \"%s\" dependency from \"%s\" item", dependency, item))
   250  	b.ModManager.SetLogger(b.Logger)
   251  	return b.ModManager.DeleteDependency(item, dependency)
   252  }
   253  
   254  func (b *SmartBuilder) newPlugin(name string) (client *plugin.Client, raw interface{}, err error) {
   255  	defer func() {
   256  		if r := recover(); r != nil {
   257  			fmt.Printf(ErrorMessageF, r)
   258  			if client != nil {
   259  				client.Kill()
   260  			}
   261  		}
   262  	}()
   263  	logger := hclog.New(&hclog.LoggerOptions{
   264  		Name:   name,
   265  		Output: os.Stdout,
   266  		Level:  hclog.Error,
   267  	})
   268  	pluginMap := map[string]plugin.Plugin{
   269  		name: b.Builder.(plugin.Plugin),
   270  	}
   271  	cmd := name
   272  	if runtime.GOOS == "windows" {
   273  		cmd += ".exe"
   274  	}
   275  	client = plugin.NewClient(&plugin.ClientConfig{
   276  		HandshakeConfig: b.PluginHandshake,
   277  		Plugins:         pluginMap,
   278  		Cmd:             exec.Command(cmd),
   279  		Logger:          logger,
   280  	})
   281  	rpcClient, err := client.Client()
   282  	if err != nil {
   283  		return nil, nil, err
   284  	}
   285  	raw, err = rpcClient.Dispense(name)
   286  	if err != nil {
   287  		return nil, nil, err
   288  	}
   289  	return
   290  }
   291  
   292  func (b *SmartBuilder) checkApplication(application string, reader ModReader) (string, error) {
   293  	// read the current application if it is not specified and only one is exist
   294  	if application == "" {
   295  		apps, err := getApps(reader.Items())
   296  		if err != nil {
   297  			return "", err
   298  		}
   299  		// check the number of existing applications
   300  		if len(apps) == 0 {
   301  			return "", errors.New(AppIsMissing)
   302  		}
   303  		if len(apps) != 1 {
   304  			return "", fmt.Errorf(AppIsNotSpecified)
   305  		}
   306  		// select the existing application
   307  		application = apps[0][0]
   308  	}
   309  	return application, nil
   310  }
   311  
   312  //lint:ignore U1000 Ignore unused function
   313  func (b *SmartBuilder) logTrace(message string) {
   314  	if b.Logger != nil {
   315  		b.Logger.Trace(message)
   316  	}
   317  }
   318  
   319  //lint:ignore U1000 Ignore unused function
   320  func (b *SmartBuilder) logDebug(message string) {
   321  	if b.Logger != nil {
   322  		b.Logger.Debug(message)
   323  	}
   324  }
   325  
   326  //lint:ignore U1000 Ignore unused function
   327  func (b *SmartBuilder) logInfo(message string) {
   328  	if b.Logger != nil {
   329  		b.Logger.Info(message)
   330  	}
   331  }
   332  
   333  //lint:ignore U1000 Ignore unused function
   334  func (b *SmartBuilder) logWarn(message string) {
   335  	if b.Logger != nil {
   336  		b.Logger.Warn(message)
   337  	}
   338  }
   339  
   340  //lint:ignore U1000 Ignore unused function
   341  func (b *SmartBuilder) logError(message string) {
   342  	if b.Logger != nil {
   343  		b.Logger.Error(message)
   344  	}
   345  }