Skip to content

Commit

Permalink
Merge branch 'develop-equalize' into develop. Close #248.
Browse files Browse the repository at this point in the history
**Description**

Currently, the cFS, ROS, F' and standalone backends perform similar
functionality and are built around similar ideas, but are built slightly
differently. This causes an unnecessary amount of code replication, and makes
it hard to factorize code out, to incorporate features from a backend into
others, and to connect all 4 backends to the GUI.

To make the code easier to maintain and help introduce new features in Ogma, we
should modify the implementation of all four of those backends to have the same
structure, use similar types, and ideally use the same auxiliary functions.
This will help us meet existing requirements in terms of features that need to
be implemented in Ogma, and in terms of code cleaning.

**Type**

- Management: Code cleaning and re-architecturing.

**Additional context**

None.

**Requester**

- Ivan Perez.

**Method to check presence of bug**

Not applicable (not a bug).

**Expected result**

The cFS, ROS, FPrime and standalone backend use the same types to manage
failure, and rely on the same auxiliary functions for the common features they
implement.

The following Dockerfile checks that generating a FPrime, cFS, ROS, and
standalone applications, with different data souces and with and without the
feature proposed renders the same result for each backend, after which it
prints the message "Success":

```
FROM ubuntu:focal

ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update

RUN apt-get install --yes \
      curl g++ gcc git libgmp3-dev libz-dev make pkg-config

RUN mkdir -p $HOME/.local/bin
ENV PATH=$PATH:/root/.local/bin/

RUN curl https://downloads.haskell.org/~ghcup/0.1.17.7/x86_64-linux-ghcup-0.1.17.7 -o $HOME/.local/bin/ghcup
RUN chmod a+x $HOME/.local/bin/ghcup

ENV PATH=$PATH:/root/.ghcup/bin/
RUN ghcup install ghc 9.10
RUN ghcup install cabal 3.12
RUN ghcup set ghc 9.10.1
RUN cabal update

ADD document.json      /tmp/document.json
ADD json-format.cfg    /tmp/json-format.cfg
ADD fprime-variable-db /tmp/fprime-variable-db
ADD fprime-handlers    /tmp/fprime-handlers

SHELL ["/bin/bash", "-c"]
CMD git clone $REPO \
    && cd $NAME \
    && git checkout $BASE_COMMIT \
    && cabal install ogma-cli:ogma \
    && ogma fprime     --app-target-dir fprime_original        --handlers-file /tmp/fprime-handlers                   --input-file /tmp/document.json -f /tmp/json-format.cfg -p literal --variable-db /tmp/fprime-variable-db \
    && ogma ros        --app-target-dir ros_spec_original      --handlers-file ogma-cli/examples/ros-copilot/handlers --input-file /tmp/document.json -f /tmp/json-format.cfg -p literal --variable-db ogma-cli/examples/ros-copilot/vars-db \
    && ogma ros        --app-target-dir ros_variables_original --handlers-file ogma-cli/examples/ros-copilot/handlers --variable-db ogma-cli/examples/ros-copilot/vars-db     --variable-file ogma-cli/examples/ros-copilot/variables \
    && ogma cfs        --app-target-dir cfs_original           --handlers-file ogma-cli/examples/cfs-handlers         --variable-db ogma-cli/examples/cfs-variable-db         --variable-file ogma-cli/examples/cfs-variables \
    && ogma standalone --target-dir     standalone_original    --file-name /tmp/document.json -f /tmp/json-format.cfg -p literal --target-file-name copilot \
    && git checkout $COMMIT \
    && cabal install --overwrite-policy=always ogma-cli:ogma \
    && ogma fprime     --app-target-dir fprime_updated         --handlers-file /tmp/fprime-handlers                   --input-file /tmp/document.json -f /tmp/json-format.cfg -p literal --variable-db /tmp/fprime-variable-db \
    && ogma ros        --app-target-dir ros_spec_updated       --handlers-file ogma-cli/examples/ros-copilot/handlers --input-file /tmp/document.json -f /tmp/json-format.cfg -p literal --variable-db ogma-cli/examples/ros-copilot/vars-db \
    && ogma ros        --app-target-dir ros_variables_updated  --handlers-file ogma-cli/examples/ros-copilot/handlers --variable-db ogma-cli/examples/ros-copilot/vars-db  --variable-file ogma-cli/examples/ros-copilot/variables \
    && ogma cfs        --app-target-dir cfs_updated            --handlers-file ogma-cli/examples/cfs-handlers         --variable-db ogma-cli/examples/cfs-variable-db      --variable-file ogma-cli/examples/cfs-variables \
    && ogma standalone --target-dir     standalone_updated     --file-name /tmp/document.json -f /tmp/json-format.cfg -p literal --target-file-name copilot \
    && diff -rq fprime_original        fprime_updated \
    && diff -rq ros_variables_original ros_variables_updated \
    && diff -rq ros_spec_original      ros_spec_updated \
    && diff -rq cfs_original           cfs_updated \
    && diff -rq standalone_original    standalone_updated \
    && echo "Success"
```

Command (substitute variables based on new path after merge):
```sh
$ docker run -e "REPO=https://github.com/NASA/ogma" -e "NAME=ogma" -e "BASE_COMMIT=f1da8cabc6c9437396db20ac26f3356d9a68a465" -e "COMMIT=<HASH>" -it ogma-verify-248
```

**Solution implemented**

Make all the aforementioned backends be built around the `ExceptT` monad, using
just `ErrorTriplet` to capture errors.

Make all the aforementioned backends define a top-level command and provide a
record to pass all options, named accordingly.

Re-define `ExprPair` to be a wrapper over a non-existential type, and define
the spec parsing function so that it is polymorphic.

Externalize the spec parsing feature into its own function in the standalone
backend.

Pack results in a data structure prior to turning it into JSON in the
standalone backend.

List auxiliary functions in each file using the same names across backends, and
list them in the same order.

List exports and imports in the same way across backends.

Rename options to have the same names.

Make the variables file optional in the cFS backend.

Adjust the tests and CLI to match the new interface.

**Further notes**

None.
  • Loading branch information
ivanperez-keera committed Feb 8, 2025
2 parents f1da8ca + 6497e49 commit 5b6f5ac
Show file tree
Hide file tree
Showing 11 changed files with 1,044 additions and 950 deletions.
3 changes: 2 additions & 1 deletion ogma-cli/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Revision history for ogma-cli

## [1.X.Y] - 2025-02-07
## [1.X.Y] - 2025-02-08

* Add all auxiliary test files to distributable Cabal package (#216).
* Remove extraneous EOL character (#224).
Expand All @@ -12,6 +12,7 @@
* Fix formatting of template variables in README (#222).
* Update README with new ROS template variables (#244).
* Update README with new FPrime template variables (#246).
* Adjust CLI to match new backend API (#248).

## [1.6.0] - 2025-01-21

Expand Down
37 changes: 21 additions & 16 deletions ogma-cli/src/CLI/CommandCFSApp.hs
Original file line number Diff line number Diff line change
Expand Up @@ -50,15 +50,16 @@ import Options.Applicative ( Parser, help, long, metavar, optional, showDefault,
import Command.Result ( Result )

-- External imports: actions or commands supported
import Command.CFSApp ( ErrorCode, cFSApp )
import Command.CFSApp ( ErrorCode )
import qualified Command.CFSApp

-- * Command

-- | Options needed to generate the cFS application.
data CommandOpts = CommandOpts
{ cFSAppTarget :: String
, cFSAppTemplateDir :: Maybe String
, cFSAppVarNames :: String
, cFSAppVarNames :: Maybe String
, cFSAppVarDB :: Maybe String
, cFSAppHandlers :: Maybe String
, cFSAppTemplateVars :: Maybe String
Expand All @@ -70,14 +71,16 @@ data CommandOpts = CommandOpts
--
-- This is just an uncurried version of "Command.CFSApp".
command :: CommandOpts -> IO (Result ErrorCode)
command c =
cFSApp
(cFSAppTarget c)
(cFSAppTemplateDir c)
(cFSAppVarNames c)
(cFSAppVarDB c)
(cFSAppHandlers c)
(cFSAppTemplateVars c)
command c = Command.CFSApp.command options
where
options = Command.CFSApp.CommandOptions
{ Command.CFSApp.commandTargetDir = cFSAppTarget c
, Command.CFSApp.commandTemplateDir = cFSAppTemplateDir c
, Command.CFSApp.commandVariables = cFSAppVarNames c
, Command.CFSApp.commandVariableDB = cFSAppVarDB c
, Command.CFSApp.commandHandlers = cFSAppHandlers c
, Command.CFSApp.commandExtraVars = cFSAppTemplateVars c
}

-- * CLI

Expand All @@ -103,12 +106,14 @@ commandOptsParser = CommandOpts
<> help strCFSAppTemplateDirArgDesc
)
)
<*> strOption
( long "variable-file"
<> metavar "FILENAME"
<> showDefault
<> value "variables"
<> help strCFSAppVarListArgDesc
<*> optional
( strOption
( long "variable-file"
<> metavar "FILENAME"
<> showDefault
<> value "variables"
<> help strCFSAppVarListArgDesc
)
)
<*> optional
( strOption
Expand Down
25 changes: 13 additions & 12 deletions ogma-cli/src/CLI/CommandFPrimeApp.hs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ import Options.Applicative ( Parser, help, long, metavar, optional, short,
import Command.Result ( Result )

-- External imports: actions or commands supported
import Command.FPrimeApp (ErrorCode, fprimeApp)
import Command.FPrimeApp (ErrorCode)
import qualified Command.FPrimeApp

-- * Command
Expand All @@ -60,7 +60,7 @@ data CommandOpts = CommandOpts
{ fprimeAppInputFile :: Maybe String
, fprimeAppTarget :: String
, fprimeAppTemplateDir :: Maybe String
, fprimeAppVarNames :: Maybe String
, fprimeAppVariables :: Maybe String
, fprimeAppVarDB :: Maybe String
, fprimeAppHandlers :: Maybe String
, fprimeAppFormat :: String
Expand All @@ -74,18 +74,19 @@ data CommandOpts = CommandOpts
--
-- This is just a wrapper around "Command.fprimeApp".
command :: CommandOpts -> IO (Result ErrorCode)
command c = fprimeApp (fprimeAppInputFile c) options
command c = Command.FPrimeApp.command options
where
options =
Command.FPrimeApp.FPrimeAppOptions
{ Command.FPrimeApp.fprimeAppTargetDir = fprimeAppTarget c
, Command.FPrimeApp.fprimeAppTemplateDir = fprimeAppTemplateDir c
, Command.FPrimeApp.fprimeAppVarNames = fprimeAppVarNames c
, Command.FPrimeApp.fprimeAppVariableDB = fprimeAppVarDB c
, Command.FPrimeApp.fprimeAppHandlers = fprimeAppHandlers c
, Command.FPrimeApp.fprimeAppFormat = fprimeAppFormat c
, Command.FPrimeApp.fprimeAppPropFormat = fprimeAppPropFormat c
, Command.FPrimeApp.fprimeAppPropVia = fprimeAppPropVia c
Command.FPrimeApp.CommandOptions
{ Command.FPrimeApp.commandInputFile = fprimeAppInputFile c
, Command.FPrimeApp.commandTargetDir = fprimeAppTarget c
, Command.FPrimeApp.commandTemplateDir = fprimeAppTemplateDir c
, Command.FPrimeApp.commandVariables = fprimeAppVariables c
, Command.FPrimeApp.commandVariableDB = fprimeAppVarDB c
, Command.FPrimeApp.commandHandlers = fprimeAppHandlers c
, Command.FPrimeApp.commandFormat = fprimeAppFormat c
, Command.FPrimeApp.commandPropFormat = fprimeAppPropFormat c
, Command.FPrimeApp.commandPropVia = fprimeAppPropVia c
}

-- * CLI
Expand Down
23 changes: 12 additions & 11 deletions ogma-cli/src/CLI/CommandROSApp.hs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ import Options.Applicative ( Parser, help, long, metavar, optional, short,
import Command.Result ( Result )

-- External imports: actions or commands supported
import Command.ROSApp (ErrorCode, rosApp)
import Command.ROSApp (ErrorCode)
import qualified Command.ROSApp

-- * Command
Expand All @@ -74,17 +74,18 @@ data CommandOpts = CommandOpts
--
-- This is just a wrapper around "Command.ROSApp".
command :: CommandOpts -> IO (Result ErrorCode)
command c = rosApp (rosAppInputFile c) options
command c = Command.ROSApp.command options
where
options = Command.ROSApp.ROSAppOptions
{ Command.ROSApp.rosAppTargetDir = rosAppTarget c
, Command.ROSApp.rosAppTemplateDir = rosAppTemplateDir c
, Command.ROSApp.rosAppVariables = rosAppVarNames c
, Command.ROSApp.rosAppVariableDB = rosAppVarDB c
, Command.ROSApp.rosAppHandlers = rosAppHandlers c
, Command.ROSApp.rosAppFormat = rosAppFormat c
, Command.ROSApp.rosAppPropFormat = rosAppPropFormat c
, Command.ROSApp.rosAppPropVia = rosAppPropVia c
options = Command.ROSApp.CommandOptions
{ Command.ROSApp.commandInputFile = rosAppInputFile c
, Command.ROSApp.commandTargetDir = rosAppTarget c
, Command.ROSApp.commandTemplateDir = rosAppTemplateDir c
, Command.ROSApp.commandVariables = rosAppVarNames c
, Command.ROSApp.commandVariableDB = rosAppVarDB c
, Command.ROSApp.commandHandlers = rosAppHandlers c
, Command.ROSApp.commandFormat = rosAppFormat c
, Command.ROSApp.commandPropFormat = rosAppPropFormat c
, Command.ROSApp.commandPropVia = rosAppPropVia c
}

-- * CLI
Expand Down
36 changes: 13 additions & 23 deletions ogma-cli/src/CLI/CommandStandalone.hs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ import Command.Result ( Result(..) )
import Data.Location ( Location(..) )

-- External imports: actions or commands supported
import Command.Standalone (standalone)
import Command.Standalone (ErrorCode)
import qualified Command.Standalone

-- * Command
Expand All @@ -70,17 +70,19 @@ data CommandOpts = CommandOpts

-- | Transform an input specification into a Copilot specification.
command :: CommandOpts -> IO (Result ErrorCode)
command c = standalone (standaloneFileName c) internalCommandOpts
command c =
Command.Standalone.command internalCommandOpts
where
internalCommandOpts :: Command.Standalone.StandaloneOptions
internalCommandOpts = Command.Standalone.StandaloneOptions
{ Command.Standalone.standaloneTargetDir = standaloneTargetDir c
, Command.Standalone.standaloneTemplateDir = standaloneTemplateDir c
, Command.Standalone.standaloneFormat = standaloneFormat c
, Command.Standalone.standalonePropFormat = standalonePropFormat c
, Command.Standalone.standaloneTypeMapping = types
, Command.Standalone.standaloneFilename = standaloneTarget c
, Command.Standalone.standalonePropVia = standalonePropVia c
internalCommandOpts :: Command.Standalone.CommandOptions
internalCommandOpts = Command.Standalone.CommandOptions
{ Command.Standalone.commandInputFile = standaloneFileName c
, Command.Standalone.commandTargetDir = standaloneTargetDir c
, Command.Standalone.commandTemplateDir = standaloneTemplateDir c
, Command.Standalone.commandFormat = standaloneFormat c
, Command.Standalone.commandPropFormat = standalonePropFormat c
, Command.Standalone.commandTypeMapping = types
, Command.Standalone.commandFilename = standaloneTarget c
, Command.Standalone.commandPropVia = standalonePropVia c
}

types :: [(String, String)]
Expand Down Expand Up @@ -193,15 +195,3 @@ strStandaloneTargetDesc =
strStandalonePropViaDesc :: String
strStandalonePropViaDesc =
"Command to pre-process individual properties"

-- * Error codes

-- | Encoding of reasons why the command can fail.
--
-- The error code used is 1 for user error.
type ErrorCode = Int

-- | Error: the specification file cannot be read due to the format being
-- unknown.
ecSpecError :: ErrorCode
ecSpecError = 2
3 changes: 2 additions & 1 deletion ogma-core/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Revision history for ogma-core

## [1.X.Y] - 2025-02-07
## [1.X.Y] - 2025-02-08

* Import liftIO from Control.Monad.IO.Class (#215).
* Remove references to old design of Ogma from hlint files (#220).
Expand All @@ -16,6 +16,7 @@
* Re-structure cFS backend to avoid nested conditions (#242).
* Make structured data available to ROS template (#244).
* Make structured data available to FPrime template (#246).
* Equalize backends (#248).

## [1.6.0] - 2025-01-21

Expand Down
Loading

0 comments on commit 5b6f5ac

Please sign in to comment.