From e17f34a9de9ef0622e671058fd54853720c9d7f6 Mon Sep 17 00:00:00 2001 From: Fabien Chouteau Date: Wed, 3 Mar 2021 07:18:34 -0800 Subject: [PATCH] Start support for crate configuration (#673) This is the possibility for crates to define configuration variables that will then generate Ada, C, or GPR files based on the values set by depending crates. For embedded projects where compile optimization and static memory usage are important, it is possible to define sizes of cache buffers or maximum amount of some resources. It allows to enable or disable features at compile time, such as debug logging. --- doc/catalog-format-spec.md | 224 +++++ src/alire/alire-crate_configuration.adb | 401 +++++++++ src/alire/alire-crate_configuration.ads | 71 ++ src/alire/alire-properties-configurations.adb | 786 ++++++++++++++++++ src/alire/alire-properties-configurations.ads | 109 +++ src/alire/alire-properties-from_toml.ads | 59 +- src/alire/alire-releases.adb | 4 +- src/alire/alire-releases.ads | 11 + src/alire/alire-roots.adb | 74 +- src/alire/alire-roots.ads | 18 +- src/alire/alire-toml_keys.ads | 2 + src/alire/alire-utils-yaml.adb | 2 +- src/alire/alire-workspace.adb | 7 + src/alr/alr-commands-withing.adb | 2 +- testsuite/drivers/asserts.py | 17 +- .../basic_index/he/hello/hello-1.0.1.toml | 13 + .../li/libhello/libhello-1.0.0.toml | 3 + .../basic/my_index/hello_src/hello_world.gpr | 6 + .../basic/my_index/hello_src/src/main.adb | 6 + .../he/hello_world/hello_world-0.1.0.toml | 13 + .../basic/my_index/index/index.toml | 1 + .../libcrate_config-1.0.0.toml | 16 + .../libcrate_config_src/libcrate_config.gpr | 43 + .../my_index/libcrate_config_src/src/plop.adb | 27 + .../my_index/libcrate_config_src/src/plop.ads | 3 + .../my_index/libcrate_config_src/src/test.c | 10 + testsuite/tests/crate_config/basic/test.py | 30 + testsuite/tests/crate_config/basic/test.yaml | 4 + .../get/external-tool-dependency/test.py | 4 + testsuite/tests/get/git-local/test.py | 6 +- testsuite/tests/get/unpack-in-place/test.py | 4 + .../tests/index/bad-config-vars/manifest.toml | 14 + .../my_index/index/he/hello_world/.gitignore | 1 + .../bad-config-vars/my_index/index/index.toml | 1 + testsuite/tests/index/bad-config-vars/test.py | 169 ++++ .../tests/index/bad-config-vars/test.yaml | 4 + testsuite/tests/show/jekyll/test.py | 19 +- 37 files changed, 2119 insertions(+), 65 deletions(-) create mode 100644 src/alire/alire-crate_configuration.adb create mode 100644 src/alire/alire-crate_configuration.ads create mode 100644 src/alire/alire-properties-configurations.adb create mode 100644 src/alire/alire-properties-configurations.ads create mode 100644 testsuite/tests/crate_config/basic/my_index/hello_src/hello_world.gpr create mode 100644 testsuite/tests/crate_config/basic/my_index/hello_src/src/main.adb create mode 100644 testsuite/tests/crate_config/basic/my_index/index/he/hello_world/hello_world-0.1.0.toml create mode 100644 testsuite/tests/crate_config/basic/my_index/index/index.toml create mode 100644 testsuite/tests/crate_config/basic/my_index/index/li/libcrate_config/libcrate_config-1.0.0.toml create mode 100644 testsuite/tests/crate_config/basic/my_index/libcrate_config_src/libcrate_config.gpr create mode 100644 testsuite/tests/crate_config/basic/my_index/libcrate_config_src/src/plop.adb create mode 100644 testsuite/tests/crate_config/basic/my_index/libcrate_config_src/src/plop.ads create mode 100644 testsuite/tests/crate_config/basic/my_index/libcrate_config_src/src/test.c create mode 100644 testsuite/tests/crate_config/basic/test.py create mode 100644 testsuite/tests/crate_config/basic/test.yaml create mode 100644 testsuite/tests/index/bad-config-vars/manifest.toml create mode 100644 testsuite/tests/index/bad-config-vars/my_index/index/he/hello_world/.gitignore create mode 100644 testsuite/tests/index/bad-config-vars/my_index/index/index.toml create mode 100644 testsuite/tests/index/bad-config-vars/test.py create mode 100644 testsuite/tests/index/bad-config-vars/test.yaml diff --git a/doc/catalog-format-spec.md b/doc/catalog-format-spec.md index e5ba71a7..6ac583ac 100644 --- a/doc/catalog-format-spec.md +++ b/doc/catalog-format-spec.md @@ -441,6 +441,65 @@ static, i.e. they cannot depend on the context. notes = "Experimental version" ``` + - `config_variables` optional table of crate configuration variable + definitions. + + For more information on crate configuration, see [Using crate + configuration](#using-crate-configuration). + + The keys of the table are names of the variables. Variable definitions + themselves are tables with the following entries: + + - `type`: mandatory string which defines the type of the variable, it can + be: + + - `String`: any string + + - `Boolean`: either `True` or `False` + + - `Enum`: enumeration type + + - `Integer`: an integer value that can be encoded in 64-bit + + - `Real`: a real value that can be encoded in IEEE 754 binary64 + + - `default`: optional default value for the variable. Will be used if no + crates explicitly set a value for this variable. Must be a valid value + for the type. + + - `first`: (optional) for `Real` and `Integer` types only. Defines the + lower bound of valid values for the type (inclusive). + + - `last`: (optional) for `Real` and `Integer` types only. Defines the + upper bound of valid values for the type (inclusive). + + - `values`: mandatory for `Enum` types. An array of strings containing + all the possible values for the enumeration. + + + Example: + ```toml + [config_variables] + Device_Name = {type = "String", default = "no device name"} + Print_Debug = {type = "Boolean", default = false} + Debug_Level = {type = "Enum", values = ["Info", "Debug", "Warn", "Error"], default = "Warn"} + Buffer_Size = {type = "Integer", first = 0, last = 1024, default = 256} + Max_Power = {type = "Real", first = 0.0, last = 100.0, default = 50.0} + ``` + - `config_settings` optional table of variables assignment: + + The keys of the table are crate names, and entries are sub-tables of + `variable_name` and `value`. The type of the value has to match the + definition of the variable type. + + Example: + ```toml + [config_settings] + crate_1.var1 = 42 + crate_1.var2 = true + crate_2.var1 = "Debug" + ``` + ## External releases The above information applies to regular releases distributed from sources @@ -547,6 +606,171 @@ available.'case(toolchain)'.user = false - `word-size`: architecture word size. Currently supported values are: `bits-32`, `bits-64`, `bits-unknown` +## Using crate configuration + +`Alire` provides a mechanism for crates to expose a list of variables that can +be set by other crates depending on them. The configuration variables will then +be converted to Ada, C and GPR source files that can be used to change the +behavior or feature set of the code. + +Let's start with a simple example. A crate named `test` can print debug log on +the console. However printing on the console has a performance impact, for an +embedded project it can even have a significant code size impact. Therefore it +would be best if this logging can be disabled/enabled at compile time. + +To achieve this, a crate maintainer can define a configuration variable in the +crate manifest `alire.toml`. The definition will be like so: +```toml +[config_variables] +Enable_Logs = {type = "Boolean", default = false} +``` +A single variable of type `Boolean` with a default value of `false`. + +From this definition, `Alire` will generate various source files, including an +Ada package specification: + +```ada +package Test_Config is + Enable_Logs : constant Boolean := False; +end Test_Config; +``` + +In the crate source code, this configuration package can be used like so: +```ada + if Test_Config.Enable_Logs then + Print_Log ("This is a log message."); + end if; +``` + +If one of the crates depending on `test` sets the configuration variable to +`true`, e.g.: + +```toml +[config_settings] +test.Enable_Logs = true +``` + +The constant value will change in the generated configuration package: +```ada +package Test_Config is + Enable_Logs : constant Boolean := True; +end Test_Config; +``` +Which will enable logging in the `test` crate. + +It is possible for multiple depending crates to set `test.Enable_Logs` to the +same value, however if two depending crates set the variable to a different +value then the configuration is invalid and `Alire` will print an error. If no +depending crates set the `test.Enable_Logs` variable, then its default value is +used. + +### When to use crate configuration? + +Usually when something has to be static or known at compiler-time, either for +performance or memory usage. + +### When _not_ to use crate configuration? + +When the Ada languages provides a better alternative. There are many ways to +provide an Ada API that will result in compile time optimization or static +memory usage. + +For instance, discriminants are an effective way to let the user define the +size of a buffer: + +```ada + type Buffered_Thing (Size : Positive) is private; +private + type Buffer_Array is array (Positive range <>) of Unsigned_8; + type Buffered_Thing (Size : Positive) is record + Buf : Buffer_Array (1 .. Size); + end record; +``` + +With this definition, users are then able to allocate either statically, on the +stack or on the heap depending on their project. + +```ada + Thing : Buffered_Thing (Size => 256); +``` + +### Use cases + +#### Log levels + +Enumerations variables in crate configuration can be used to set a level of log +verbosity: +```toml +[config_variables] +Log_Level = {type = "Enum", values = ["Info", "Debug", "Warn", "Error"], default = "Warn"} +``` + +#### Buffer size + +Integer variables can be used the define the size of a static buffer: + +```toml +[config_variables] +Buffer_Size = {type = "Integer", first = 0, last = 1024, default = 256} +``` +This is useful in particular for embedded projects where compile time memory +usage is preferred over dynamic allocation. + +#### Server URL + +String variables can be used to define the URL of a website or service: + +```toml +[config_variables] +URL_Name = {type = "String", default = "example.com"} +``` + +#### PID coefficients + +Real variables can be used for PID coefficients: +```toml +[config_variables] +Proportional = {type = "Real"} +Integral = {type = "Real"} +Derivative = {type = "Real"} +``` +#### Worst case allocation + +Integer variable can be used to define The maximum length of file names in a +file-system: +```toml +[config_variables] +Max_Filename_Length = {type = "Integer", first = 5, last = 128} +``` + +#### Select algorithm in GPR project file + +Crate configuration also generates a GPR project file, therefore it can be used +to control which units are compiled in the project. +```toml +[config_variables] +Sort_Algorithm = {type = "Enum", values = ["bubble", "quick", "merge"]} +``` + +The generated GPR will look something like this: +```ada +project Test_Config is + type Sort_Algorith_Kind is ("bubble", "quick", "merge"); + Sort_Algorith : Debug_Level_Kind := "quick"; +end Test_Config; +``` + +It can be used in the main GPR file like so: + +```ada + package Naming is + for Body ("Test.Sort") use "test-sort__" & Test_Config.Sort_Algorith; + end Naming; +``` +With the files `test-sort__bubble.adb`, `test-sort__quick.adb` and +`test-sort__merge.adb` each implementing a different algorithm. + + ## Further reading ## You can inspect [index files](https://github.com/alire-project/alire-index) to diff --git a/src/alire/alire-crate_configuration.adb b/src/alire/alire-crate_configuration.adb new file mode 100644 index 00000000..63f3f669 --- /dev/null +++ b/src/alire/alire-crate_configuration.adb @@ -0,0 +1,401 @@ +with Ada.Strings.Unbounded; use Ada.Strings.Unbounded; +with Ada.Directories; +with Ada.Text_IO; +with Ada.Characters.Handling; + +with Alire_Early_Elaboration; +with Alire.Solutions; +with Alire.Roots; +with Alire.Utils.TTY; +with Alire.Origins; + +with GNAT.IO; + +with Alire.Directories; + +with TOML; use TOML; + +package body Alire.Crate_Configuration is + + package TTY renames Utils.TTY; + package TIO renames Ada.Text_IO; + + ---------- + -- Load -- + ---------- + + procedure Load (This : in out Global_Config; + Root : Alire.Roots.Root) + is + Solution : constant Solutions.Solution := Root.Solution; + begin + + -- Warnings when setting up an incomplete environment + + if not Solution.Is_Complete then + Trace.Debug ("Generating incomplete environment" + & " because of missing dependencies"); + + -- Normally we would generate a warning, but since that will pollute + -- the output making it unusable, for once we write directly to + -- stderr (unless quiet is in effect): + + if not Alire_Early_Elaboration.Switch_Q then + GNAT.IO.Put_Line + (GNAT.IO.Standard_Error, + TTY.Warn ("warn:") & " Generating incomplete environment" + & " because of missing dependencies"); + end if; + end if; + + for Rel of Solution.Releases.Including (Root.Release) loop + This.Load_Definitions (Root => Root, + Crate => Rel.Name); + end loop; + + for Rel of Solution.Releases.Including (Root.Release) loop + This.Load_Settings (Root => Root, + Crate => Rel.Name); + end loop; + + Use_Default_Values (This); + end Load; + + --------------------------- + -- Generate_Config_Files -- + --------------------------- + + procedure Generate_Config_Files (This : Global_Config; + Root : Alire.Roots.Root) + is + use Alire.Directories; + use Alire.Origins; + + Solution : constant Solutions.Solution := Root.Solution; + begin + + -- Warnings when setting up an incomplete environment + + if not Solution.Is_Complete then + Trace.Debug ("Generating incomplete environment" + & " because of missing dependencies"); + + -- Normally we would generate a warning, but since that will pollute + -- the output making it unusable, for once we write directly to + -- stderr (unless quiet is in effect): + + if not Alire_Early_Elaboration.Switch_Q then + GNAT.IO.Put_Line + (GNAT.IO.Standard_Error, + TTY.Warn ("warn:") & " Generating incomplete environment" + & " because of missing dependencies"); + end if; + end if; + + for Rel of Solution.Releases.Including (Root.Release) loop + + -- We don't create config files for external releases, since they are + -- not sources built by Alire. + if Rel.Origin.Kind /= Alire.Origins.External then + + declare + Conf_Dir : constant Absolute_Path := + Root.Release_Base (Rel.Name) / "config"; + begin + Ada.Directories.Create_Path (Conf_Dir); + + This.Generate_Ada_Config + (Rel.Name, Conf_Dir / (+Rel.Name & "_config.ads")); + + This.Generate_GPR_Config + (Rel.Name, + Conf_Dir / (+Rel.Name & "_config.gpr"), + Root.Direct_Withs (Rel)); + + This.Generate_C_Config + (Rel.Name, Conf_Dir / (+Rel.Name & "_config.h")); + end; + end if; + end loop; + end Generate_Config_Files; + + ------------------------- + -- Generate_Ada_Config -- + ------------------------- + + procedure Generate_Ada_Config (This : Global_Config; + Crate : Crate_Name; + Filepath : Absolute_Path) + is + File : TIO.File_Type; + + begin + + TIO.Create (File, TIO.Out_File, Filepath); + + TIO.Put_Line + (File, "-- Configuration for " & (+Crate) & " generated by Alire"); + + TIO.Put_Line (File, "package " & (+Crate) & "_Config is"); + for C in This.Map.Iterate loop + declare + Elt : constant Config_Maps.Constant_Reference_Type := + This.Map.Constant_Reference (C); + + Type_Def : Config_Type_Definition renames Elt.Type_Def.Element; + + Key : constant String := To_String (Config_Maps.Key (C)); + begin + if Elt.Value = TOML.No_TOML_Value then + Raise_Checked_Error + ("Configuration variable should be set at this point." & + " Missing call to Use_Default_Values()?"); + end if; + + if Alire.Utils.Starts_With (Key, +Crate & ".") then + TIO.New_Line (File); + TIO.Put_Line (File, Type_Def.To_Ada_Declaration (Elt.Value)); + end if; + end; + end loop; + TIO.New_Line (File); + TIO.Put_Line (File, "end " & (+Crate) & "_Config;"); + TIO.Close (File); + end Generate_Ada_Config; + + ------------------------- + -- Generate_GPR_Config -- + ------------------------- + + procedure Generate_GPR_Config (This : Global_Config; + Crate : Crate_Name; + Filepath : Absolute_Path; + Withs : Alire.Utils.String_Set) + is + File : TIO.File_Type; + + begin + + TIO.Create (File, TIO.Out_File, Filepath); + + TIO.Put_Line + (File, "-- Configuration for " & (+Crate) & " generated by Alire"); + + for W of Withs loop + TIO.Put_Line (File, "with """ & W & """;"); + end loop; + + TIO.Put_Line (File, "abstract project " & (+Crate) & "_Config is"); + for C in This.Map.Iterate loop + declare + Elt : constant Config_Maps.Constant_Reference_Type := + This.Map.Constant_Reference (C); + + Type_Def : Config_Type_Definition renames Elt.Type_Def.Element; + + Key : constant String := To_String (Config_Maps.Key (C)); + begin + if Elt.Value = TOML.No_TOML_Value then + Raise_Checked_Error + ("Configuration variable should be set at this point." & + " Missing call to Use_Default_Values()?"); + end if; + + if Alire.Utils.Starts_With (Key, +Crate & ".") then + TIO.New_Line (File); + TIO.Put_Line (File, Type_Def.To_GPR_Declaration (Elt.Value)); + end if; + end; + end loop; + + TIO.New_Line (File); + TIO.Put_Line (File, "end " & (+Crate) & "_Config;"); + TIO.Close (File); + end Generate_GPR_Config; + + ----------------------- + -- Generate_C_Config -- + ----------------------- + + procedure Generate_C_Config (This : Global_Config; + Crate : Crate_Name; + Filepath : Absolute_Path) + is + File : TIO.File_Type; + + Crate_Upper : constant String := + Ada.Characters.Handling.To_Upper (+Crate); + + begin + + TIO.Create (File, TIO.Out_File, Filepath); + + TIO.Put_Line + (File, "/* Configuration for " & (+Crate) & " generated by Alire */"); + TIO.Put_Line (File, "#ifndef " & Crate_Upper & "_CONFIG_H"); + TIO.Put_Line (File, "#define " & Crate_Upper & "_CONFIG_H"); + for C in This.Map.Iterate loop + declare + Elt : constant Config_Maps.Constant_Reference_Type := + This.Map.Constant_Reference (C); + + Type_Def : Config_Type_Definition renames Elt.Type_Def.Element; + + Key : constant String := To_String (Config_Maps.Key (C)); + begin + if Elt.Value = TOML.No_TOML_Value then + Raise_Checked_Error + ("Configuration variable should be set at this point." & + " Missing call to Use_Default_Values()?"); + end if; + + if Alire.Utils.Starts_With (Key, +Crate & ".") then + TIO.New_Line (File); + TIO.Put_Line (File, Type_Def.To_C_Declaration (Elt.Value)); + end if; + end; + end loop; + TIO.New_Line (File); + TIO.Put_Line (File, "#endif"); + TIO.Close (File); + end Generate_C_Config; + + ---------------------- + -- Load_Definitions -- + ---------------------- + + procedure Load_Definitions (This : in out Global_Config; + Root : Roots.Root; + Crate : Crate_Name) + is + + -------------------- + -- Add_Definition -- + -------------------- + + procedure Add_Definition (Type_Def : Config_Type_Definition) is + Type_Name_Lower : constant String := + Ada.Characters.Handling.To_Lower (Type_Def.Name); + + Name : constant Unbounded_String := +(+Crate & "." & Type_Name_Lower); + begin + if This.Map.Contains (Name) then + Raise_Checked_Error + ("Configuration variable '" & (+Name) & "' already defined"); + end if; + + declare + Setting : Config_Setting; + begin + Setting.Type_Def.Replace_Element (Type_Def); + Setting.Value := TOML.No_TOML_Value; + This.Map.Insert (Name, Setting); + end; + end Add_Definition; + + Rel : constant Releases.Release := Root.Release (Crate); + + begin + for Prop of Rel.On_Platform_Properties (Root.Environment, + Config_Type_Definition'Tag) + loop + Add_Definition (Config_Type_Definition (Prop)); + end loop; + end Load_Definitions; + + ------------------- + -- Load_Settings -- + ------------------- + + procedure Load_Settings (This : in out Global_Config; + Root : Roots.Root; + Crate : Crate_Name) + is + + Rel : constant Releases.Release := Root.Release (Crate); + + --------------- + -- Set_Value -- + --------------- + + procedure Set_Value (Crate : Unbounded_String; Val : Assignment) is + Val_Name_Lower : constant String := + Ada.Characters.Handling.To_Lower (+Val.Name); + Name : constant Unbounded_String := Crate & "." & Val_Name_Lower; + begin + + -- TODO check if setting configuration of a dependency + + if not This.Map.Contains (Name) then + Raise_Checked_Error + ("Unknown configuration variable '" & (+Name) & "'"); + end if; + + declare + Ref : constant Config_Maps.Reference_Type := + This.Map.Reference (Name); + begin + + if not Valid (Ref.Type_Def.Element, Val.Value) then + Raise_Checked_Error + ("Invalid value from '" & (+Crate) & + "'" & " for type " & Image (Ref.Type_Def.Element)); + end if; + + if Ref.Value /= No_TOML_Value and then Ref.Value /= Val.Value then + Raise_Checked_Error + ("Conflicting value for configuration variable '" & + (+Name) & "' from '" & (+Ref.Set_By) & "' and '" + & (+Crate) & "'."); + else + Ref.Value := Val.Value; + Ref.Set_By := +(+Crate); + end if; + end; + end Set_Value; + + begin + + for Prop of Rel.On_Platform_Properties (Root.Environment, + Config_Value_Assignment'Tag) + loop + declare + List : Config_Value_Assignment renames + Config_Value_Assignment (Prop); + begin + for Elt of List.List loop + Set_Value (List.Crate, Elt); + end loop; + end; + end loop; + + end Load_Settings; + + ------------------------ + -- Use_Default_Values -- + ------------------------ + + procedure Use_Default_Values (Conf : in out Global_Config) is + begin + for C in Conf.Map.Iterate loop + declare + Elt : constant Config_Maps.Reference_Type := + Conf.Map.Reference (C); + + Key : constant String := To_String (Config_Maps.Key (C)); + begin + if Elt.Value = TOML.No_TOML_Value then + if Elt.Type_Def.Element.Default /= No_TOML_Value then + Elt.Value := Elt.Type_Def.Element.Default; + Elt.Set_By := +"default value"; + else + Raise_Checked_Error + ("Configuration variable '" & Key & + " not set and has no default value."); + end if; + end if; + end; + end loop; + end Use_Default_Values; + +end Alire.Crate_Configuration; diff --git a/src/alire/alire-crate_configuration.ads b/src/alire/alire-crate_configuration.ads new file mode 100644 index 00000000..58255101 --- /dev/null +++ b/src/alire/alire-crate_configuration.ads @@ -0,0 +1,71 @@ +with TOML; + +with Alire.Releases; +with Alire.Properties.Configurations; +limited with Alire.Roots; + +private with Ada.Strings.Unbounded; +private with Ada.Containers.Hashed_Maps; +private with Ada.Strings.Unbounded.Hash; +private with Ada.Containers.Indefinite_Holders; +private with Alire.Utils; + +package Alire.Crate_Configuration is + + type Global_Config is tagged limited private; + + procedure Load (This : in out Global_Config; + Root : Alire.Roots.Root); + + procedure Generate_Config_Files (This : Global_Config; + Root : Alire.Roots.Root); + +private + + use Alire.Properties.Configurations; + + package Config_Type_Definition_Holder + is new Ada.Containers.Indefinite_Holders (Config_Type_Definition); + + type Config_Setting is record + Type_Def : Config_Type_Definition_Holder.Holder; + Value : TOML.TOML_Value; + Set_By : Ada.Strings.Unbounded.Unbounded_String; + end record; + + package Config_Maps is new Ada.Containers.Hashed_Maps + (Key_Type => Ada.Strings.Unbounded.Unbounded_String, + Element_Type => Config_Setting, + Hash => Ada.Strings.Unbounded.Hash, + Equivalent_Keys => Ada.Strings.Unbounded."="); + + type Global_Config is tagged limited record + Map : Config_Maps.Map; + end record; + + procedure Use_Default_Values (Conf : in out Global_Config); + -- Use default value for unset variable, raise Checked_Error if a variable + -- has no default value. + + procedure Load_Definitions (This : in out Global_Config; + Root : Roots.Root; + Crate : Crate_Name); + + procedure Load_Settings (This : in out Global_Config; + Root : Roots.Root; + Crate : Crate_Name); + + procedure Generate_Ada_Config (This : Global_Config; + Crate : Crate_Name; + Filepath : Absolute_Path); + + procedure Generate_GPR_Config (This : Global_Config; + Crate : Crate_Name; + Filepath : Absolute_Path; + Withs : Alire.Utils.String_Set); + + procedure Generate_C_Config (This : Global_Config; + Crate : Crate_Name; + Filepath : Absolute_Path); + +end Alire.Crate_Configuration; diff --git a/src/alire/alire-properties-configurations.adb b/src/alire/alire-properties-configurations.adb new file mode 100644 index 00000000..4777310d --- /dev/null +++ b/src/alire/alire-properties-configurations.adb @@ -0,0 +1,786 @@ +with TOML; use TOML; + +with Alire.Utils.YAML; + +with Ada.Characters.Handling; + +with Ada.Strings.Unbounded; use Ada.Strings.Unbounded; + +package body Alire.Properties.Configurations is + + ------------- + -- To_Type -- + ------------- + + function To_Type (Str : String) return Config_Type_Kind is + Lower : constant String := Ada.Characters.Handling.To_Lower (Str); + begin + if Lower = "real" then + return Real; + elsif Lower = "integer" then + return Int; + elsif Lower = "string" then + return Configurations.Str; + elsif Lower = "enum" then + return Enum; + elsif Lower = "boolean" then + return Bool; + end if; + + Raise_Checked_Error + ("Invalid configuration type '" & Str & + "', must be (real, integer, string, enum or boolean)"); + end To_Type; + + --------------- + -- To_String -- + --------------- + + function To_String (Str_Array : TOML_Value; + Wrap_With_Quotes : Boolean := False) + return String + is + Res : Unbounded_String; + First : Boolean := True; + begin + if Str_Array.Kind /= TOML_Array + and then + Str_Array.Item_Kind /= TOML_String + then + raise Program_Error with "Invalid TOML kind for enum values"; + end if; + + for Index in 1 .. Str_Array.Length loop + declare + Val : constant String := Str_Array.Item (Index).As_String; + Val_Str : constant Unbounded_String := +(if Wrap_With_Quotes + then """" & Val & """" + else Val); + begin + if First then + Res := Res & Val_Str; + First := False; + else + Res := Res & ", " & Val_Str; + end if; + end; + end loop; + + return +Res; + end To_String; + + ----------- + -- Image -- + ----------- + + function Image (This : Config_Integer) return String is + Ret : constant String := This'Img; + begin + if Ret (Ret'First) = ' ' then + return Ret (Ret'First + 1 .. Ret'Last); + else + return Ret; + end if; + end Image; + + ----------- + -- Image -- + ----------- + + function Image (This : Config_Real) return String is + begin + case This.Kind is + when Regular => + declare + Ret : constant String := This.Value'Img; + begin + if Ret (Ret'First) = ' ' then + return Ret (Ret'First + 1 .. Ret'Last); + else + return Ret; + end if; + end; + when NaN => + if This.Positive then + return "+nan"; + else + return "-nan"; + end if; + when Infinity => + if This.Positive then + return "+inf"; + else + return "-inf"; + end if; + end case; + end Image; + + ----------- + -- Image -- + ----------- + + function Image (Val : TOML_Value) return String is + begin + if Val = No_TOML_Value then + Raise_Checked_Error + ("Unexpected No_TOML_Value in conversion to String"); + end if; + + case Val.Kind is + when TOML_String => + return Val.As_String; + when TOML_Boolean => + return Val.As_Boolean'Img; + when TOML_Float => + return Image (Val.As_Float); + when TOML_Integer => + return Image (Val.As_Integer); + + when TOML_Array => + if Val.Item_Kind /= TOML_String then + Raise_Checked_Error ("Unexpected kind '" & Val.Item_Kind'Img & + "' in array conversion to String"); + else + return To_String (Val); + end if; + when others => + Raise_Checked_Error ("Unexpected kind '" & Val.Kind'Img & + "' in conversion to String"); + end case; + end Image; + + -------------------- + -- Type_To_String -- + -------------------- + + function Type_To_String (This : Config_Type_Definition) return String + is ((case This.Kind is + when Str => "String", + when Bool => "Boolean", + when Enum => "Enum (" & To_String (This.Values) & ")", + when Real => "Real range " & Image (This.Real_First) & " .. " & + Image (This.Real_Last), + when Int => "Integer range " & Image (This.Int_First) & " .. " & + Image (This.Int_Last)) + ); + + ----------- + -- Image -- + ----------- + + overriding + function Image (This : Config_Type_Definition) return String is + Ret : Unbounded_String; + begin + Ret := "Config type: " & This.Name & " : " & Type_To_String (This) & ""; + + if This.Default /= No_TOML_Value then + Append (Ret, String'(" default: '" & Image (This.Default) & "'")); + end if; + + return +Ret; + end Image; + + ------------- + -- To_TOML -- + ------------- + + overriding + function To_TOML (This : Config_Type_Definition) return TOML.TOML_Value is + Table1 : constant TOML.TOML_Value := TOML.Create_Table; + Table2 : constant TOML.TOML_Value := TOML.Create_Table; + + begin + Table2.Set ("type", Create_String (case This.Kind is + when Str => "String", + when Enum => "Enum", + when Int => "Integer", + when Bool => "Boolean", + when Real => "Real")); + + if This.Default /= TOML.No_TOML_Value then + Table2.Set ("default", This.Default); + end if; + + case This.Kind is + when Enum => + Table2.Set ("values", This.Values); + + when Int => + Table2.Set ("first", Create_Integer (This.Int_First)); + Table2.Set ("last", Create_Integer (This.Int_Last)); + + when Real => + Table2.Set ("first", Create_Float (This.Real_First)); + Table2.Set ("last", Create_Float (This.Real_Last)); + + when Bool | Str => null; + end case; + + Table1.Set (+This.Name, Table2); + return Table1; + end To_TOML; + + ------------- + -- To_YAML -- + ------------- + + overriding + function To_YAML (This : Config_Type_Definition) return String is + Ret : Unbounded_String; + begin + Ret := "{name: '" & This.Name & "', type: '" & + Type_To_String (This) & "'"; + + if This.Default /= No_TOML_Value then + Append (Ret, ", default: " & + Alire.Utils.YAML.YAML_Stringify (Image (This.Default))); + end if; + + return +Ret & "}"; + end To_YAML; + + ----------- + -- Image -- + ----------- + + overriding + function Image (This : Config_Value_Assignment) return String is + Ret : Unbounded_String; + First : Boolean := True; + begin + Ret := "Config set: " & This.Crate & " {"; + + for Elt of This.List loop + if not First then + Append (Ret, ", "); + else + First := False; + end if; + + Append (Ret, +Elt.Name & " := " & Image (Elt.Value)); + end loop; + + Append (Ret, "}"); + return +Ret; + end Image; + + ------------- + -- To_TOML -- + ------------- + + overriding + function To_TOML (This : Config_Value_Assignment) return TOML.TOML_Value is + Root : constant TOML.TOML_Value := TOML.Create_Table; + Assign : constant TOML.TOML_Value := TOML.Create_Table; + + begin + + for Elt of This.List loop + Assign.Set (Elt.Name, Elt.Value); + end loop; + Root.Set (+This.Crate, Assign); + + return Root; + end To_TOML; + + ------------- + -- To_YAML -- + ------------- + + overriding + function To_YAML (This : Config_Value_Assignment) return String is + Ret : Unbounded_String; + First : Boolean := True; + begin + Ret := "{crate: '" & This.Crate & "', settings: ["; + + for Elt of This.List loop + if not First then + Append (Ret, ", " & ASCII.LF); + else + First := False; + end if; + + Append (Ret, "{name: '" & Elt.Name & "', value: " & + Alire.Utils.YAML.YAML_Stringify (Image (Elt.Value)) & + "}"); + end loop; + + Append (Ret, "]}"); + return +Ret; + end To_YAML; + + ----------- + -- Valid -- + ----------- + + function Valid (This : Config_Type_Definition; + Val : TOML.TOML_Value) + return Boolean + is + begin + + if Val = No_TOML_Value then + return False; + end if; + + -- Validate the TOML type + case This.Kind is + when Str | Enum => + if Val.Kind /= TOML_String then + return False; + end if; + when Bool => + if Val.Kind /= TOML_Boolean then + return False; + end if; + when Real => + if Val.Kind /= TOML_Float then + return False; + end if; + when Int => + if Val.Kind /= TOML_Integer then + return False; + end if; + end case; + + case This.Kind is + when Str | Bool => + -- Nothing else to check + return True; + when Enum => + declare + Str : constant String := Val.As_String; + begin + for Index in 1 .. This.Values.Length loop + if This.Values.Item (Index).As_String = Str then + return True; + end if; + end loop; + return False; + end; + + when Real => + + declare + F : constant Config_Real := Val.As_Float; + First : Config_Real renames This.Real_First; + Last : Config_Real renames This.Real_Last; + begin + + -- We don't accept infinity or Nan here + if F.Kind /= Regular then + return False; + end if; + + return not ( + -- Lower bound + (First.Kind = Infinity and then First.Positive) + or else + (First.Kind /= Infinity and then F.Value < First.Value) + or else + -- Upper bound + (Last.Kind = Infinity and then not Last.Positive) + or else + (Last.Kind /= Infinity and then F.Value > Last.Value) + ); + end; + when Int => + declare + I : constant Config_Integer := Val.As_Integer; + begin + return I >= This.Int_First and then I <= This.Int_Last; + end; + end case; + exception + when others => + return False; + end Valid; + + ---------- + -- Name -- + ---------- + + function Name (This : Config_Type_Definition) return String + is (+This.Name); + + ------------------------ + -- To_Ada_Declaration -- + ------------------------ + + function To_Ada_Declaration (This : Config_Type_Definition; + Value : TOML.TOML_Value) + return String + is + use ASCII; + Name : constant String := +This.Name; + Indent : constant String := " "; + begin + case This.Kind is + + when Str => + return Indent & Name & " : constant String := """ & + Value.As_String & """;"; + + when Bool => + return Indent & Name & " : constant Boolean := " & + (if Value.As_Boolean then "True" else "False") & ";"; + + when Enum => + return Indent & "type " & Name & "_Kind is (" & + To_String (This.Values) & ");" & LF & + Indent & Name & " : constant " & Name & "_Kind := " & + Value.As_String & ";"; + + when Real => + + -- For Real and Int we do not declare a ranged type such as: + -- + -- type Test_Type is range 0 .. 100; + -- Test : constant Test_Type := 50; + -- + -- because this would limit the possibilities of users for things + -- such as alignment size, etc. Instead we define named numbers + -- (constant) for the First, Last and actual value of the + -- variable: + -- + -- Test_First : constant := 0; + -- Test_Last : constant := 100 + -- Test : constant := 50; + -- + -- User can then define their own types: + -- + -- type Test_Type is range Config.Test_First .. Config.Test_Last + -- with Size => 32; + + return Indent & Name & "_First : constant := " & + Image (This.Real_First) & ";" & LF & + Indent & Name & "_Last : constant := " & + Image (This.Real_Last) & ";" & LF & + Indent & Name & " : constant := " & + Image (Value.As_Float) & ";"; + + when Int => + + return Indent & Name & "_First : constant := " & + This.Int_First'Img & ";" & LF & + Indent & Name & "_Last : constant := " & + This.Int_Last'Img & ";" & LF & + Indent & Name & " : constant := " & + Value.As_Integer'Img & ";"; + + end case; + end To_Ada_Declaration; + + ------------------------ + -- To_GPR_Declaration -- + ------------------------ + + function To_GPR_Declaration (This : Config_Type_Definition; + Value : TOML.TOML_Value) + return String + is + use ASCII; + Name : constant String := +This.Name; + Indent : constant String := " "; + begin + case This.Kind is + + when Str => + return Indent & Name & " := """ & Value.As_String & """;"; + + when Bool => + return Indent & Name & " := """ & + (if Value.As_Boolean then "True" else "False") & """;"; + + when Enum => + return Indent & "type " & Name & "_Kind is (" & + To_String (This.Values, Wrap_With_Quotes => True) & ");" & LF & + Indent & Name & " : " & Name & "_Kind := """ & Value.As_String + & """;"; + + when Real => + + return Indent & Name & "_First := """ & + Image (This.Real_First) & """;" & LF & + Indent & Name & "_Last := """ & + Image (This.Real_Last) & """;" & LF & + Indent & Name & " := """ & + Image (Value.As_Float) & """;"; + + when Int => + + return Indent & Name & "_First := """ & + Image (This.Int_First) & """;" & LF & + Indent & Name & "_Last := """ & + Image (This.Int_Last) & """;" & LF & + Indent & Name & " := """ & Image (Value.As_Integer) & """;"; + + end case; + end To_GPR_Declaration; + + ---------------------- + -- To_C_Declaration -- + ---------------------- + + function To_C_Declaration (This : Config_Type_Definition; + Value : TOML.TOML_Value) + return String + is + use ASCII; + Name : constant String := +This.Name; + + begin + case This.Kind is + + when Str => + return "#define " & Name & " """ & Value.As_String & """"; + + when Bool => + return "#define " & Name & "_True 1" & LF & + "#define " & Name & "_False 0" & LF & + "#define " & Name & " " & + (if Value.As_Boolean then "1" else "0"); + + when Enum => + declare + Ret : Unbounded_String; + Count : Positive := 1; + + Value_Id : Positive := 1; + begin + for Index in 1 .. This.Values.Length loop + declare + Elt : constant String := + This.Values.Item (Index).As_String; + begin + Ret := Ret & "#define " & Name & "_" & Elt & + Count'Img & LF; + + if Elt = Value.As_String then + Value_Id := Count; + end if; + + Count := Count + 1; + end; + end loop; + + return +Ret & LF & + "#define " & Name & " " & Value_Id'Img; + end; + + when Real => + return + "#define " & Name & "_First " & Image (This.Real_First) & LF & + "#define " & Name & "_Last " & Image (This.Real_Last) & LF & + "#define " & Name & " " & Image (Value.As_Float); + + when Int => + return + "#define " & Name & "_First " & Image (This.Int_First) & LF & + "#define " & Name & "_Last " & Image (This.Int_Last) & LF & + "#define " & Name & " " & Image (Value.As_Integer); + + end case; + end To_C_Declaration; + + --------------------------- + -- Definitions_From_TOML -- + --------------------------- + + function Definitions_From_TOML (From : TOML_Adapters.Key_Queue) + return Conditional.Properties + is + use Conditional.For_Properties; + + ---------------- + -- Create_One -- + ---------------- + + function Create_One (Name : String; + Raw : TOML.TOML_Value) + return Conditional.Properties + is + From : constant TOML_Adapters.Key_Queue := + TOML_Adapters.From (Raw, TOML_Keys.Config_Vars & "." & Name); + Typ : TOML_Value; + First : TOML_Value; + Last : TOML_Value; + + Unused : Boolean; + + begin + if Raw.Kind /= TOML_Table then + From.Checked_Error ("variable definition must be a table"); + end if; + + if not From.Pop ("type", Typ) then + From.Checked_Error ("'type' missing"); + end if; + + if Typ.Kind /= TOML_String then + From.Checked_Error ("'type' must be string"); + end if; + + declare + Type_Def : Config_Type_Definition (To_Type (Typ.As_String)); + begin + Type_Def.Name := +Name; + + case Type_Def.Kind is + + when Int => + if From.Pop ("first", First) then + if First.Kind /= TOML_Integer then + From.Checked_Error ("first must be interger"); + end if; + + Type_Def.Int_First := First.As_Integer; + else + Type_Def.Int_First := Config_Integer'First; + end if; + + if From.Pop ("last", Last) then + if Last.Kind /= TOML_Integer then + From.Checked_Error ("'last' must be integer"); + end if; + + Type_Def.Int_Last := Last.As_Integer; + else + Type_Def.Int_Last := Config_Integer'Last; + end if; + + when Real => + if From.Pop ("first", First) then + if First.Kind /= TOML_Float then + From.Checked_Error ("'first' must be float/real"); + end if; + + Type_Def.Real_First := First.As_Float; + + if Type_Def.Real_First.Kind = TOML.NaN then + From.Checked_Error ("'first' cannot be NaN"); + end if; + else + Type_Def.Real_First := (TOML.Infinity, Positive => False); + end if; + + if From.Pop ("last", Last) then + if Last.Kind /= TOML_Float then + From.Checked_Error ("'last' must be float/real"); + end if; + + Type_Def.Real_Last := Last.As_Float; + + if Type_Def.Real_Last.Kind = TOML.NaN then + From.Checked_Error ("'last' cannot be NaN"); + end if; + else + Type_Def.Real_Last := (TOML.Infinity, Positive => True); + end if; + + when Enum => + if From.Pop ("values", Type_Def.Values) then + if Type_Def.Values.Kind /= TOML_Array + or else + not Type_Def.Values.Item_Kind_Set + or else + Type_Def.Values.Item_Kind /= TOML_String + then + From.Checked_Error + ("'values' must be a not empty array of strings"); + end if; + else + From.Checked_Error + ("missing 'values' for enumeration type"); + end if; + + when others => + null; + end case; + + if From.Pop ("default", Type_Def.Default) then + if not Valid (Type_Def, Type_Def.Default) then + From.Checked_Error ("invalid default value for " & + Type_To_String (Type_Def)); + end if; + else + Type_Def.Default := No_TOML_Value; + end if; + + From.Report_Extra_Keys; + + return New_Value (Type_Def); + end; + end Create_One; + + Raw : constant TOML_Value := From.Pop; + begin + if Raw.Kind /= TOML_Table then + Raise_Checked_Error (TOML_Keys.Config_Vars & "must be a table"); + end if; + + return Props : Conditional.Properties do + for Item of Iterate_On_Table (Raw) loop + Props := Props and Create_One (To_String (Item.Key), + Item.Value); + Raw.Unset (Item.Key); + end loop; + end return; + end Definitions_From_TOML; + + ---------------------------- + -- Assignements_From_TOML -- + ---------------------------- + + function Assignements_From_TOML (From : TOML_Adapters.Key_Queue) + return Conditional.Properties + is + use Conditional.For_Properties; + + ---------------- + -- Create_One -- + ---------------- + + function Create_One (Crate : String; + Raw : TOML.TOML_Value) + return Conditional.Properties + is + Val : Config_Value_Assignment; + begin + if Raw.Kind /= TOML_Table then + Raise_Checked_Error + (TOML_Keys.Config_Sets & " assignements must be a table"); + end if; + + Val.Crate := +Crate; + + for Item of Iterate_On_Table (Raw) loop + declare + Assign : Assignment; + begin + Assign.Name := Item.Key; + Assign.Value := Item.Value; + + Val.List.Append (Assign); + end; + end loop; + return New_Value (Val); + end Create_One; + + Raw : constant TOML_Value := From.Pop; + begin + if Raw.Kind /= TOML_Table then + raise Checked_Error with TOML_Keys.Config_Sets & " must be a table"; + end if; + + return Props : Conditional.Properties do + for Item of Iterate_On_Table (Raw) loop + Props := Props and Create_One (To_String (Item.Key), Item.Value); + Raw.Unset (Item.Key); + end loop; + end return; + end Assignements_From_TOML; + +end Alire.Properties.Configurations; diff --git a/src/alire/alire-properties-configurations.ads b/src/alire/alire-properties-configurations.ads new file mode 100644 index 00000000..792b4298 --- /dev/null +++ b/src/alire/alire-properties-configurations.ads @@ -0,0 +1,109 @@ +with Alire.Conditional; +with Alire.TOML_Adapters; +with Alire.TOML_Keys; + +with Ada.Strings.Unbounded; +with Ada.Containers.Doubly_Linked_Lists; + +with TOML; + +package Alire.Properties.Configurations with Preelaborate is + + type Config_Type_Definition (<>) is new Properties.Property with private; + + function Valid (This : Config_Type_Definition; + Val : TOML.TOML_Value) + return Boolean; + + function Default (This : Config_Type_Definition) return TOML.TOML_Value; + + function Name (This : Config_Type_Definition) return String; + + function To_Ada_Declaration (This : Config_Type_Definition; + Value : TOML.TOML_Value) + return String + with Pre => This.Valid (Value); + + function To_GPR_Declaration (This : Config_Type_Definition; + Value : TOML.TOML_Value) + return String + with Pre => This.Valid (Value); + + function To_C_Declaration (This : Config_Type_Definition; + Value : TOML.TOML_Value) + return String + with Pre => This.Valid (Value); + + type Assignment is record + Name : Ada.Strings.Unbounded.Unbounded_String; + Value : TOML.TOML_Value; + end record; + + package Assignement_List_Pck + is new Ada.Containers.Doubly_Linked_Lists (Assignment); + + type Config_Value_Assignment is new Properties.Property with record + Crate : Ada.Strings.Unbounded.Unbounded_String; + List : Assignement_List_Pck.List; + end record; + + function Definitions_From_TOML (From : TOML_Adapters.Key_Queue) + return Conditional.Properties; + + function Assignements_From_TOML (From : TOML_Adapters.Key_Queue) + return Conditional.Properties; + +private + + type Config_Type_Kind is (Real, Int, Enum, Str, Bool); + + subtype Config_Integer is TOML.Any_Integer; + subtype Config_Real is TOML.Any_Float; + + type Config_Type_Definition (Kind : Config_Type_Kind) + is new Properties.Property + with record + Name : Ada.Strings.Unbounded.Unbounded_String; + Default : TOML.TOML_Value; + case Kind is + when Real => + Real_First, Real_Last : Config_Real; + when Int => + Int_First, Int_Last : Config_Integer; + when Enum => + Values : TOML.TOML_Value; + when Str | Bool => + null; + end case; + end record; + + overriding + function Key (This : Config_Type_Definition) return String + is (Alire.TOML_Keys.Config_Vars); + + overriding + function Image (This : Config_Type_Definition) return String; + + overriding + function To_TOML (This : Config_Type_Definition) return TOML.TOML_Value; + + overriding + function To_YAML (This : Config_Type_Definition) return String; + + function Default (This : Config_Type_Definition) return TOML.TOML_Value + is (This.Default); + + overriding + function Key (This : Config_Value_Assignment) return String + is (Alire.TOML_Keys.Config_Sets); + + overriding + function To_TOML (This : Config_Value_Assignment) return TOML.TOML_Value; + + overriding + function Image (This : Config_Value_Assignment) return String; + + overriding + function To_YAML (This : Config_Value_Assignment) return String; + +end Alire.Properties.Configurations; diff --git a/src/alire/alire-properties-from_toml.ads b/src/alire/alire-properties-from_toml.ads index c95ff657..bab0988c 100644 --- a/src/alire/alire-properties-from_toml.ads +++ b/src/alire/alire-properties-from_toml.ads @@ -1,6 +1,7 @@ with Alire.Conditional; with Alire.Crates; with Alire.Properties.Actions; +with Alire.Properties.Configurations; with Alire.Properties.Environment; with Alire.Properties.Labeled; with Alire.Properties.Licenses; @@ -15,6 +16,8 @@ package Alire.Properties.From_TOML is type Property_Keys is (Actions, Authors, Auto_GPR_With, + Config_Variables, + Config_Settings, Description, Environment, Executables, @@ -93,33 +96,39 @@ package Alire.Properties.From_TOML is Maintainers_Logins | Name | Tags | - Website => Labeled.From_TOML'Access, + Website => Labeled.From_TOML'Access, others => null); -- This loader is used for properties a manifest containing externals may -- provide, shared by all external definitions found therein Release_Loaders : constant Loader_Array (Property_Keys) := - (Actions => Properties.Actions.From_TOML'Access, - Authors => Labeled.From_TOML'Access, - Auto_GPR_With => Bool.From_TOML'Access, - Description => Labeled.From_TOML'Access, - Environment => - Properties.Environment.From_TOML'Access, - Executables => Labeled.From_TOML'Access, - GPR_Externals | - GPR_Set_Externals - => Scenarios.From_TOML'Access, - Hint => null, - Licenses => Properties.Licenses.From_TOML'Access, - Long_Description | - Maintainers | - Maintainers_Logins | - Name | - Notes | - Project_Files | - Tags | - Version | - Website => Labeled.From_TOML'Access); + (Actions => Properties.Actions.From_TOML'Access, + Authors => Labeled.From_TOML'Access, + Auto_GPR_With => Bool.From_TOML'Access, + Description => Labeled.From_TOML'Access, + + Config_Variables => + Properties.Configurations.Definitions_From_TOML'Access, + Config_Settings => + Properties.Configurations.Assignements_From_TOML'Access, + + Environment => + Properties.Environment.From_TOML'Access, + Executables => Labeled.From_TOML'Access, + GPR_Externals | + GPR_Set_Externals + => Scenarios.From_TOML'Access, + Hint => null, + Licenses => Properties.Licenses.From_TOML'Access, + Long_Description | + Maintainers | + Maintainers_Logins | + Name | + Notes | + Project_Files | + Tags | + Version | + Website => Labeled.From_TOML'Access); -- This loader applies to a normal release manifest -- The following array determines which properties accept dynamic @@ -127,6 +136,12 @@ package Alire.Properties.From_TOML is Loaders_During_Case : constant array (Property_Keys) of Property_Loader := (Actions => Properties.Actions.From_TOML'Access, + + Config_Variables => + Properties.Configurations.Definitions_From_TOML'Access, + Config_Settings => + Properties.Configurations.Assignements_From_TOML'Access, + Environment => Properties.Environment.From_TOML'Access, Executables => Labeled.From_TOML_Executable_Cases'Access, GPR_Set_Externals => Scenarios.From_TOML_Cases'Access, diff --git a/src/alire/alire-releases.adb b/src/alire/alire-releases.adb index 8adc520d..a8d0e438 100644 --- a/src/alire/alire-releases.adb +++ b/src/alire/alire-releases.adb @@ -883,7 +883,9 @@ package body Alire.Releases is "version: " & Utils.YAML.YAML_Stringify (R.Version_Image) & ASCII.LF & "short_description: " & Utils.YAML.YAML_Stringify (R.Description) & ASCII.LF & - "dependencies: " & R.Dependencies.To_YAML; + "dependencies: " & R.Dependencies.To_YAML & ASCII.LF & + "config_variables: " & Props_To_YAML (R.Config_Variables) & ASCII.LF & + "config_settings: " & Props_To_YAML (R.Config_Settings) & ASCII.LF; end To_YAML; ------------- diff --git a/src/alire/alire-releases.ads b/src/alire/alire-releases.ads index 50ef30be..6c35f2d9 100644 --- a/src/alire/alire-releases.ads +++ b/src/alire/alire-releases.ads @@ -260,6 +260,9 @@ package Alire.Releases is function Tag (R : Release) return Alire.Properties.Vector; + function Config_Variables (R : Release) return Alire.Properties.Vector; + function Config_Settings (R : Release) return Alire.Properties.Vector; + function Auto_GPR_With (R : Release) return Boolean; procedure Print (R : Release); @@ -442,6 +445,14 @@ private is (Conditional.Enumerate (R.Properties).Filter (Alire.TOML_Keys.Tag)); + function Config_Variables (R : Release) return Alire.Properties.Vector + is (Conditional.Enumerate (R.Properties).Filter + (Alire.TOML_Keys.Config_Vars)); + + function Config_Settings (R : Release) return Alire.Properties.Vector + is (Conditional.Enumerate (R.Properties).Filter + (Alire.TOML_Keys.Config_Sets)); + use all type Origins.Kinds; function Unique_Folder (R : Release) return Folder_String is (Utils.Head (+R.Name, Extension_Separator) & "_" & diff --git a/src/alire/alire-roots.adb b/src/alire/alire-roots.adb index d34afe36..cf151647 100644 --- a/src/alire/alire-roots.adb +++ b/src/alire/alire-roots.adb @@ -10,6 +10,7 @@ with Alire.Solutions.Diffs; with Alire.Utils.TTY; with Alire.Utils.User_Input; with Alire.Workspace; +with Alire.Crate_Configuration; with GNAT.OS_Lib; @@ -28,6 +29,50 @@ package body Alire.Roots is end return; end Build_Context; + ------------------ + -- Direct_Withs -- + ------------------ + + function Direct_Withs (This : Root; + Dependent : Releases.Release) + return Utils.String_Set + is + Sol : Solutions.Solution renames This.Solution; + begin + return Files : Utils.String_Set do + + -- Traverse direct dependencies of the given release + + for Dep of Dependent.Flat_Dependencies (This.Environment) loop + + -- For dependencies that appear in the solution as releases, get + -- their project files in the current environment. + + if Sol.Releases.Contains (Dep.Crate) + and then + Sol.Releases.Element (Dep.Crate).Auto_GPR_With + then + for File of Sol.Releases.Element (Dep.Crate).Project_Files + (This.Environment, With_Path => False) + loop + Files.Include (File); + end loop; + end if; + end loop; + end return; + end Direct_Withs; + + ---------------------------- + -- Generate_Configuration -- + ---------------------------- + + procedure Generate_Configuration (This : Root) is + Conf : Alire.Crate_Configuration.Global_Config; + begin + Conf.Load (This); + Conf.Generate_Config_Files (This); + end Generate_Configuration; + ------------------ -- Check_Stored -- ------------------ @@ -93,35 +138,6 @@ package body Alire.Roots is Context.Export; end Export_Build_Environment; - ----------------------- - -- GPR_Project_Files -- - ----------------------- - - function GPR_Project_Files (This : Root; - Exclude_Root : Boolean) - return Utils.String_Set - is - Files : Utils.String_Set; - begin - - -- Add files from every release in the solution - - for Rel of This.Solution.Releases.Including (Release (This)) loop - - if (not Exclude_Root or else Rel.Name /= Release (This).Name) - and then - Rel.Auto_GPR_With - then - for File of Rel.Project_Files - (This.Environment, With_Path => False) - loop - Files.Include (File); - end loop; - end if; - end loop; - return Files; - end GPR_Project_Files; - ------------------- -- Project_Paths -- ------------------- diff --git a/src/alire/alire-roots.ads b/src/alire/alire-roots.ads index c23b367b..05ef944e 100644 --- a/src/alire/alire-roots.ads +++ b/src/alire/alire-roots.ads @@ -53,9 +53,16 @@ package Alire.Roots is function Build_Context (This : Root) return Alire.Environment.Context; + function Direct_Withs (This : Root; + Dependent : Releases.Release) + return Utils.String_Set; + -- Returns project file names of direct dependencies of the given dependent + procedure Export_Build_Environment (This : Root); -- Export the build environment (PATH, GPR_PROJECT_PATH) of the given root + procedure Generate_Configuration (This : Root); + procedure Extend (This : in out Root; Dependencies : Conditional.Dependencies := Conditional.No_Dependencies; @@ -71,12 +78,11 @@ package Alire.Roots is -- solution in this root. This includes al releases' paths and any linked -- directories. - function GPR_Project_Files (This : Root; - Exclude_Root : Boolean) - return Utils.String_Set; - -- Return all the gprbuild project files defined for the solution in this - -- root. If Exclude_Root is True, the project files of the root crate are - -- excluded from the result. + type Solution_Components is (Root_Release, + Direct_Dependencies, + Indirect_Dependencies); + + type Components_Array is array (Solution_Components) of Boolean; function Release (This : Root) return Releases.Release; diff --git a/src/alire/alire-toml_keys.ads b/src/alire/alire-toml_keys.ads index c5bfe1ed..00366865 100644 --- a/src/alire/alire-toml_keys.ads +++ b/src/alire/alire-toml_keys.ads @@ -10,6 +10,8 @@ package Alire.TOML_Keys with Preelaborate is Auto_GPR_With : constant String := "auto-gpr-with"; Available : constant String := "available"; Compiler : constant String := "compiler"; + Config_Vars : constant String := "config_variables"; + Config_Sets : constant String := "config_settings"; Depends_On : constant String := "depends-on"; Description : constant String := "description"; Distribution : constant String := "distribution"; diff --git a/src/alire/alire-utils-yaml.adb b/src/alire/alire-utils-yaml.adb index 82e9caa5..3296ce73 100644 --- a/src/alire/alire-utils-yaml.adb +++ b/src/alire/alire-utils-yaml.adb @@ -12,7 +12,7 @@ package body Alire.Utils.YAML is (T'Class (V.Element (Pos)).To_YAML & (if Pos = V.Last_Index then "" - else ", " & Image (V, Pos + 1))); + else "," & ASCII.LF & Image (V, Pos + 1))); begin if V.Is_Empty then diff --git a/src/alire/alire-workspace.adb b/src/alire/alire-workspace.adb index a8fdbf4a..70fe982d 100644 --- a/src/alire/alire-workspace.adb +++ b/src/alire/alire-workspace.adb @@ -124,6 +124,9 @@ package body Alire.Workspace is Solution.Print_Hints (Root.Environment); + -- Update/Create configuration files + Root.Generate_Configuration; + -- Check that the solution does not contain suspicious dependencies, -- taking advantage that this procedure is called whenever a change -- to dependencies is happening. @@ -351,6 +354,10 @@ package body Alire.Workspace is Deps_Dir => Root.Dependencies_Dir); end if; end if; + + -- Update/Create configuration files + Root.Generate_Configuration; + end Update_And_Deploy_Dependencies; end Alire.Workspace; diff --git a/src/alr/alr-commands-withing.adb b/src/alr/alr-commands-withing.adb index bf8318d9..8b6f8162 100644 --- a/src/alr/alr-commands-withing.adb +++ b/src/alr/alr-commands-withing.adb @@ -46,7 +46,7 @@ package body Alr.Commands.Withing is loop Utils.Auto_GPR_With.Update (Alire.OS_Lib."/" (Root.Current.Path, File), - Root.Current.GPR_Project_Files (Exclude_Root => True)); + Root.Current.Direct_Withs (Root.Current.Release)); end loop; end Auto_GPR_With; diff --git a/testsuite/drivers/asserts.py b/testsuite/drivers/asserts.py index 7db3ebea..549eb36b 100644 --- a/testsuite/drivers/asserts.py +++ b/testsuite/drivers/asserts.py @@ -4,6 +4,7 @@ testcases based on Python scripts. """ import re +import difflib def indent(text, prefix=' '): @@ -13,14 +14,28 @@ def indent(text, prefix=' '): return '\n'.join(prefix + repr(line) for line in text.splitlines()) +def pretty_diff(expected, actual): + diff = difflib.unified_diff(expected.splitlines(), + actual.splitlines(), + fromfile='expected', + tofile='actual') + + return "\n".join(diff) + + def assert_eq(expected, actual, label=None): if expected != actual: + if isinstance(actual, str) and isinstance(expected, str): + diff = '\nDiff:\n' + pretty_diff(expected, actual) + else: + diff = '' + text = ['Unexpected {}:'.format(label or 'output'), 'Expecting:', indent(str(expected)), 'But got:', indent(str(actual))] - assert False, '\n'.join(text) + assert False, '\n'.join(text) + diff def assert_match(expected_re, actual, label=None, flags=re.S): diff --git a/testsuite/fixtures/basic_index/he/hello/hello-1.0.1.toml b/testsuite/fixtures/basic_index/he/hello/hello-1.0.1.toml index a0a71ad5..382ce61f 100644 --- a/testsuite/fixtures/basic_index/he/hello/hello-1.0.1.toml +++ b/testsuite/fixtures/basic_index/he/hello/hello-1.0.1.toml @@ -12,5 +12,18 @@ tags = ["tag1", "other-tag"] [[depends-on]] libhello = "^1.0" +[config_variables] +Var1={type="Boolean"} +Var2={type="String", default="str"} +Var3={type="Enum", values=["A", "B"], default="A"} +Var4={type="Integer", default=0} +Var5={type="Integer", first=-1, last=1, default=0} +Var7={type="Real", default=0.0} +Var6={type="Real", first=-1.0, last=1.0, default=0.0} + +[config_settings] +hello.Var1=true # So far it is possible for a crate to set its own var +libhello.Var1=false + [origin] url = "file:../../../crates/hello_1.0.1" diff --git a/testsuite/fixtures/basic_index/li/libhello/libhello-1.0.0.toml b/testsuite/fixtures/basic_index/li/libhello/libhello-1.0.0.toml index 8f5e85d9..9258c911 100644 --- a/testsuite/fixtures/basic_index/li/libhello/libhello-1.0.0.toml +++ b/testsuite/fixtures/basic_index/li/libhello/libhello-1.0.0.toml @@ -4,6 +4,9 @@ version = "1.0.0" maintainers = ["alejandro@mosteo.com"] maintainers-logins = ["mylogin"] +[config_variables] +Var1={type="Boolean", default=true} + [gpr-externals] TEST_GPR_EXTERNAL = ["gpr_ext_A", "gpr_ext_B", "gpr_ext_C"] diff --git a/testsuite/tests/crate_config/basic/my_index/hello_src/hello_world.gpr b/testsuite/tests/crate_config/basic/my_index/hello_src/hello_world.gpr new file mode 100644 index 00000000..f65bd595 --- /dev/null +++ b/testsuite/tests/crate_config/basic/my_index/hello_src/hello_world.gpr @@ -0,0 +1,6 @@ +with "config/hello_world_config"; + +project Hello_World is + for Source_Dirs use ("src"); + for Main use ("main.adb"); +end Hello_World; diff --git a/testsuite/tests/crate_config/basic/my_index/hello_src/src/main.adb b/testsuite/tests/crate_config/basic/my_index/hello_src/src/main.adb new file mode 100644 index 00000000..12cfabfb --- /dev/null +++ b/testsuite/tests/crate_config/basic/my_index/hello_src/src/main.adb @@ -0,0 +1,6 @@ +with Plop; + +procedure Main is +begin + Plop.Print; +end Main; diff --git a/testsuite/tests/crate_config/basic/my_index/index/he/hello_world/hello_world-0.1.0.toml b/testsuite/tests/crate_config/basic/my_index/index/he/hello_world/hello_world-0.1.0.toml new file mode 100644 index 00000000..115863a5 --- /dev/null +++ b/testsuite/tests/crate_config/basic/my_index/index/he/hello_world/hello_world-0.1.0.toml @@ -0,0 +1,13 @@ +description = "\"Hello, world!\" demonstration project" +name = "hello_world" +version = "0.1.0" +licenses = "GPL-3.0-only" +maintainers = ["example@example.com"] +maintainers-logins = ["mylogin"] +executables=['main'] + +[origin] +url = "file:../../../hello_src" + +[[depends-on]] +libcrate_config = "*" diff --git a/testsuite/tests/crate_config/basic/my_index/index/index.toml b/testsuite/tests/crate_config/basic/my_index/index/index.toml new file mode 100644 index 00000000..346c93fc --- /dev/null +++ b/testsuite/tests/crate_config/basic/my_index/index/index.toml @@ -0,0 +1 @@ +version = "1.0" diff --git a/testsuite/tests/crate_config/basic/my_index/index/li/libcrate_config/libcrate_config-1.0.0.toml b/testsuite/tests/crate_config/basic/my_index/index/li/libcrate_config/libcrate_config-1.0.0.toml new file mode 100644 index 00000000..eb70107d --- /dev/null +++ b/testsuite/tests/crate_config/basic/my_index/index/li/libcrate_config/libcrate_config-1.0.0.toml @@ -0,0 +1,16 @@ +description = "crate config demonstration project" +name = "libcrate_config" +version = "1.0.0" +licenses = "GPL-3.0-only" +maintainers = ["example@example.com"] +maintainers-logins = ["mylogin"] + +[origin] +url = "file:../../../libcrate_config_src" + +[config_variables] +Var_Bool = {type="Boolean", default=true} +Var_String = {type="String", default="Test string."} +Var_Int = {type="Integer", first=-42, last=42, default=-1} +Var_Real = {type="Real", first=-42.0, last=42.0, default=-1.0} +Var_Enum = {type="Enum", values=["A", "B", "C"], default="B"} diff --git a/testsuite/tests/crate_config/basic/my_index/libcrate_config_src/libcrate_config.gpr b/testsuite/tests/crate_config/basic/my_index/libcrate_config_src/libcrate_config.gpr new file mode 100644 index 00000000..8de2db71 --- /dev/null +++ b/testsuite/tests/crate_config/basic/my_index/libcrate_config_src/libcrate_config.gpr @@ -0,0 +1,43 @@ +with "config/libcrate_config_config"; + +project Libcrate_Config is + for Source_Dirs use ("src", "config"); + for Languages use ("Ada", "C"); + for Library_Name use "crate_config"; + for Library_Kind use "static"; + + Src_Dirs := ("src", "config"); + + case Libcrate_Config_Config.Var_Bool is + when "True" => null; + when others => + Src_Dirs := Src_Dirs & ("invalid value for Var_Bool"); + end case; + + case Libcrate_Config_Config.Var_String is + when "Test string." => null; + when others => + Src_Dirs := Src_Dirs & ("invalid value for Var_String"); + end case; + + case Libcrate_Config_Config.Var_Enum is + when "B" => null; + when others => + Src_Dirs := Src_Dirs & ("invalid value for Var_Enum"); + end case; + + case Libcrate_Config_Config.Var_Int is + when "-1" => null; + when others => + Src_Dirs := Src_Dirs & ("invalid value for Var_Int"); + end case; + + case Libcrate_Config_Config.Var_Int_Last is + when "42" => null; + when others => + Src_Dirs := Src_Dirs & ("invalid value for Var_Int_Last"); + end case; + + for Source_Dirs use Src_Dirs; + +end Libcrate_Config; diff --git a/testsuite/tests/crate_config/basic/my_index/libcrate_config_src/src/plop.adb b/testsuite/tests/crate_config/basic/my_index/libcrate_config_src/src/plop.adb new file mode 100644 index 00000000..4bd20e9c --- /dev/null +++ b/testsuite/tests/crate_config/basic/my_index/libcrate_config_src/src/plop.adb @@ -0,0 +1,27 @@ +with Ada.Text_IO; use Ada.Text_IO; + +with Libcrate_Config_Config; use Libcrate_Config_Config; + +package body Plop is + + type My_Int_Type is range Var_Int_First .. Var_Int_Last; + My_Int : constant My_Int_Type := Var_Int; + + type My_Real_Type is digits 10 range Var_Real_First .. Var_Real_Last; + My_Real : constant My_Real_Type := Var_Real; + + procedure Print is + procedure Test_C_Print; + pragma Import(C, Test_C_Print, "test_c_print"); + + begin + Put_Line ("Ada -> Var_Bool: " & Libcrate_Config_Config.Var_Bool'Img); + Put_Line ("Ada -> Var_String: '" & Libcrate_Config_Config.Var_String & "'"); + Put_Line ("Ada -> Var_Int: " & My_Int'Img); + Put_Line ("Ada -> Var_Real: " & My_Real'Img); + Put_Line ("Ada -> Var_Enum: " & Libcrate_Config_Config.Var_Enum'Img); + + Test_C_Print; + end Print; + +end Plop; diff --git a/testsuite/tests/crate_config/basic/my_index/libcrate_config_src/src/plop.ads b/testsuite/tests/crate_config/basic/my_index/libcrate_config_src/src/plop.ads new file mode 100644 index 00000000..17a1e218 --- /dev/null +++ b/testsuite/tests/crate_config/basic/my_index/libcrate_config_src/src/plop.ads @@ -0,0 +1,3 @@ +package Plop is + procedure Print; +end Plop; diff --git a/testsuite/tests/crate_config/basic/my_index/libcrate_config_src/src/test.c b/testsuite/tests/crate_config/basic/my_index/libcrate_config_src/src/test.c new file mode 100644 index 00000000..fc656f93 --- /dev/null +++ b/testsuite/tests/crate_config/basic/my_index/libcrate_config_src/src/test.c @@ -0,0 +1,10 @@ +#include +#include "libcrate_config_config.h" + +void test_c_print(void) { + printf("C -> Var_Bool: %d\n", Var_Bool); + printf("C -> Var_String: '%s'\n", Var_String); + printf("C -> Var_Int: %d\n", Var_Int); + printf("C -> Var_Real: %f\n", Var_Real); + printf("C -> Var_Enum: %d\n", Var_Enum); +} diff --git a/testsuite/tests/crate_config/basic/test.py b/testsuite/tests/crate_config/basic/test.py new file mode 100644 index 00000000..e2e0a6db --- /dev/null +++ b/testsuite/tests/crate_config/basic/test.py @@ -0,0 +1,30 @@ +""" +Test basic crate configuration +""" + +from drivers.alr import run_alr +from drivers.asserts import assert_eq + +import os + +# Get and check post fetch action +run_alr('get', 'hello_world') +os.chdir("hello_world_0.1.0_filesystem/") + +run_alr('build') +p = run_alr('run') +assert_eq("Ada -> Var_Bool: TRUE\n" + "Ada -> Var_String: 'Test string.'\n" + "Ada -> Var_Int: -1\n" + "Ada -> Var_Real: -1.000000000E+00\n" + "Ada -> Var_Enum: B\n" + "C -> Var_Bool: 1\n" + "C -> Var_String: 'Test string.'\n" + "C -> Var_Int: -1\n" + "C -> Var_Real: -1.000000\n" + "C -> Var_Enum: 2\n", + p.out) + +print(p.out) + +print('SUCCESS') diff --git a/testsuite/tests/crate_config/basic/test.yaml b/testsuite/tests/crate_config/basic/test.yaml new file mode 100644 index 00000000..0a859639 --- /dev/null +++ b/testsuite/tests/crate_config/basic/test.yaml @@ -0,0 +1,4 @@ +driver: python-script +indexes: + my_index: + in_fixtures: false diff --git a/testsuite/tests/get/external-tool-dependency/test.py b/testsuite/tests/get/external-tool-dependency/test.py index f669751d..242b7ace 100644 --- a/testsuite/tests/get/external-tool-dependency/test.py +++ b/testsuite/tests/get/external-tool-dependency/test.py @@ -33,6 +33,10 @@ compare(dir_content, 'main_1.0.0_filesystem/alire/cache', 'main_1.0.0_filesystem/alire/cache/dependencies', make_dep_dir, + 'main_1.0.0_filesystem/config', + 'main_1.0.0_filesystem/config/main_config.ads', + 'main_1.0.0_filesystem/config/main_config.gpr', + 'main_1.0.0_filesystem/config/main_config.h', 'main_1.0.0_filesystem/noop.gpr', 'main_1.0.0_filesystem/src', 'main_1.0.0_filesystem/src/noop.adb' diff --git a/testsuite/tests/get/git-local/test.py b/testsuite/tests/get/git-local/test.py index ed33e70b..3c9bced3 100644 --- a/testsuite/tests/get/git-local/test.py +++ b/testsuite/tests/get/git-local/test.py @@ -25,7 +25,11 @@ compare(list(filter 'libfoo_1.0.0_9ddda32b/b/y/p', 'libfoo_1.0.0_9ddda32b/b/y/q', 'libfoo_1.0.0_9ddda32b/b/z', - 'libfoo_1.0.0_9ddda32b/c' + 'libfoo_1.0.0_9ddda32b/c', + 'libfoo_1.0.0_9ddda32b/config', + 'libfoo_1.0.0_9ddda32b/config/libfoo_config.ads', + 'libfoo_1.0.0_9ddda32b/config/libfoo_config.gpr', + 'libfoo_1.0.0_9ddda32b/config/libfoo_config.h' ]) diff --git a/testsuite/tests/get/unpack-in-place/test.py b/testsuite/tests/get/unpack-in-place/test.py index 905b560c..c274f595 100644 --- a/testsuite/tests/get/unpack-in-place/test.py +++ b/testsuite/tests/get/unpack-in-place/test.py @@ -12,6 +12,10 @@ compare(contents('libhello_1.0.0_filesystem'), ['libhello_1.0.0_filesystem/alire', 'libhello_1.0.0_filesystem/alire.lock', 'libhello_1.0.0_filesystem/alire.toml', + 'libhello_1.0.0_filesystem/config', + 'libhello_1.0.0_filesystem/config/libhello_config.ads', + 'libhello_1.0.0_filesystem/config/libhello_config.gpr', + 'libhello_1.0.0_filesystem/config/libhello_config.h', 'libhello_1.0.0_filesystem/libhello.gpr', 'libhello_1.0.0_filesystem/src', 'libhello_1.0.0_filesystem/src/libhello.adb', diff --git a/testsuite/tests/index/bad-config-vars/manifest.toml b/testsuite/tests/index/bad-config-vars/manifest.toml new file mode 100644 index 00000000..831bf38b --- /dev/null +++ b/testsuite/tests/index/bad-config-vars/manifest.toml @@ -0,0 +1,14 @@ +# This manifest is a base modified and copied by the test to introduce +# different invalid variable definitions. + +description = "\"Hello, world!\" demonstration project" +name = "hello_world" +version = "0.1.0" +licenses = "GPL-3.0-only" +maintainers = ["example@example.com"] +maintainers-logins = ["mylogin"] + +[origin] +url = "file:." + +[config_variables] diff --git a/testsuite/tests/index/bad-config-vars/my_index/index/he/hello_world/.gitignore b/testsuite/tests/index/bad-config-vars/my_index/index/he/hello_world/.gitignore new file mode 100644 index 00000000..f935021a --- /dev/null +++ b/testsuite/tests/index/bad-config-vars/my_index/index/he/hello_world/.gitignore @@ -0,0 +1 @@ +!.gitignore diff --git a/testsuite/tests/index/bad-config-vars/my_index/index/index.toml b/testsuite/tests/index/bad-config-vars/my_index/index/index.toml new file mode 100644 index 00000000..346c93fc --- /dev/null +++ b/testsuite/tests/index/bad-config-vars/my_index/index/index.toml @@ -0,0 +1 @@ +version = "1.0" diff --git a/testsuite/tests/index/bad-config-vars/test.py b/testsuite/tests/index/bad-config-vars/test.py new file mode 100644 index 00000000..1439944f --- /dev/null +++ b/testsuite/tests/index/bad-config-vars/test.py @@ -0,0 +1,169 @@ +""" +Test invalid crate configuration variable definitions +""" + +import os + +from drivers.alr import run_alr +from drivers.asserts import assert_match +from drivers.helpers import content_of + + +def make_manifest(var_def): + """ Make an hello_world manifest that include the variable definitions + passed as argument. + """ + dst = os.path.join("my_index", "index", "he", "hello_world", + "hello_world-0.1.0.toml") + + src = "manifest.toml" + + # Remove existing manifest, if any + if os.path.exists(dst): + os.remove(dst) + + with open(dst, 'w') as f: + f.write(content_of(src)) + f.write(var_def) + + +def check_error(var_def, expected): + + make_manifest(var_def) + + p = run_alr('show', 'hello_world', + complain_on_error=False, debug=False, quiet=True) + assert_match('ERROR:.*' + expected + '\n', p.out) + +def check_ok(var_def): + + make_manifest(var_def) + + p = run_alr('show', 'hello_world', + complain_on_error=True, debug=False, quiet=True) + + +os.remove(os.path.join("my_index", "index", "he", "hello_world", + ".gitignore")) + +check_error('var1=["plop"]', 'variable definition must be a table') +check_error('var1={}', "config_variables.var1:") +check_error('var1={}', "'type' missing") +check_error('var1={type=42}', "'type' must be string") +check_error('var1={type=""}', "Invalid configuration type ''," + " must be \(real, integer, string," + " enum or boolean\)") +check_error('var1={type="test"}', "Invalid configuration type 'test'.*") +check_error('var1={type="String", plop="test"}', "forbidden extra entries: plop") + +# String +check_ok('var1={type="String"}') +check_ok('var1={type="String", default="test"}') + +check_error('var1={type="String", first="test"}', "forbidden extra entries: first") +check_error('var1={type="String", last="test"}', "forbidden extra entries: last") +check_error('var1={type="String", values="test"}', "forbidden extra entries: values") + +expected = "invalid default value for String" +check_error('var1={type="String", default=42}', expected) +check_error('var1={type="String", default=42.0}', expected) +check_error('var1={type="String", default=false}', expected) +check_error('var1={type="String", default=["test"]}', expected) +check_error('var1={type="String", default={plop="test"}}', expected) + +# Boolean +check_ok('var1={type="Boolean"}') +check_ok('var1={type="Boolean", default=true}') +check_ok('var1={type="Boolean", default=false}') + +check_error('var1={type="Boolean", first=true}', "forbidden extra entries: first") +check_error('var1={type="Boolean", last=true}', "forbidden extra entries: last") +check_error('var1={type="Boolean", values=true}', "forbidden extra entries: values") + +expected = "invalid default value for Boolean" +check_error('var1={type="Boolean", default=42}', expected) +check_error('var1={type="Boolean", default=42.0}', expected) +check_error('var1={type="Boolean", default="false"}', expected) +check_error('var1={type="Boolean", default=["test"]}', expected) +check_error('var1={type="Boolean", default={plop="test"}}', expected) + +# Integer +check_ok('var1={type="Integer"}') +check_ok('var1={type="Integer", default=42}') +check_ok('var1={type="Integer", default=9223372036854775807}') +check_ok('var1={type="Integer", default=-9223372036854775808}') +check_ok('var1={type="Integer", first=0}') +check_ok('var1={type="Integer", last=10}') +check_ok('var1={type="Integer", first=0, last=10}') +check_ok('var1={type="Integer", first=0, last=10, default=5}') + +check_error('var1={type="Integer", values=0}', "forbidden extra entries: values") + +expected = "invalid default value for Integer range .* \.\. .*" +check_error('var1={type="Integer", first=0, default=-1}', expected) +check_error('var1={type="Integer", last=0, default=1}', expected) +check_error('var1={type="Integer", first=0, last=10, default=20}', expected) +check_error('var1={type="Integer", default="42"}', expected) +check_error('var1={type="Integer", default=42.0}', expected) +check_error('var1={type="Integer", default=false}', expected) +check_error('var1={type="Integer", default=["test"]}', expected) +check_error('var1={type="Integer", default={plop="test"}}', expected) + +# Real +check_ok('var1={type="Real"}') +check_ok('var1={type="Real", default=42.0}') +check_ok('var1={type="Real", default=4.9406564584124654e-324}') +check_ok('var1={type="Real", default=1.7976931348623157e+308}') + +check_ok('var1={type="Real", first=0.0}') +check_ok('var1={type="Real", last=10.0}') +check_ok('var1={type="Real", first=0.0, last=10.0}') +check_ok('var1={type="Real", first=0.0, last=10.0, default=5.0}') + +expected = "'first' cannot be NaN" +check_error('var1={type="Real", first=nan}', expected) +check_error('var1={type="Real", first=+nan}', expected) +check_error('var1={type="Real", first=-nan}', expected) +expected = "'last' cannot be NaN" +check_error('var1={type="Real", last=nan}', expected) +check_error('var1={type="Real", last=+nan}', expected) +check_error('var1={type="Real", last=-nan}', expected) + +check_error('var1={type="Real", values=0}', "forbidden extra entries: values") + +expected = "invalid default value for Real range .* \.\. .*" +check_error('var1={type="Real", first=0.0, default=-1.0}', expected) +check_error('var1={type="Real", last=0.0, default=1.0}', expected) +check_error('var1={type="Real", first=0.0, last=10.0, default=20.0}', expected) +check_error('var1={type="Real", default=nan}', expected) +check_error('var1={type="Real", default=+nan}', expected) +check_error('var1={type="Real", default=-nan}', expected) +check_error('var1={type="Real", default=inf}', expected) +check_error('var1={type="Real", default=+inf}', expected) +check_error('var1={type="Real", default=-inf}', expected) +check_error('var1={type="Real", default=42}', expected) +check_error('var1={type="Real", default="42.0"}', expected) +check_error('var1={type="Real", default=false}', expected) +check_error('var1={type="Real", default=["test"]}', expected) +check_error('var1={type="Real", default={plop="test"}}', expected) + +# Enum +check_ok('var1={type="Enum", values=["A"]}') +check_ok('var1={type="Enum", values=["A"], default="A"}') +check_ok('var1={type="Enum", values=["A", "B"]}') +check_ok('var1={type="Enum", values=["A", "B"], default="B"}') + +check_error('var1={type="Enum", values=["A"], first="test"}', "forbidden extra entries: first") +check_error('var1={type="Enum", values=["A"], last="test"}', "forbidden extra entries: last") +check_error('var1={type="Enum", values=[]}', "'values' must be a not empty array of strings") +check_error('var1={type="Enum"}', "missing 'values' for enumeration type") + +expected = "invalid default value for Enum \(A, B\)" +check_error('var1={type="Enum", values=["A", "B"], default="C"}', expected) +check_error('var1={type="Enum", values=["A", "B"], default=42}', expected) +check_error('var1={type="Enum", values=["A", "B"], default=42.0}', expected) +check_error('var1={type="Enum", values=["A", "B"], default=false}', expected) +check_error('var1={type="Enum", values=["A", "B"], default=["test"]}', expected) +check_error('var1={type="Enum", values=["A", "B"], default={plop="test"}}', expected) + +print('SUCCESS') diff --git a/testsuite/tests/index/bad-config-vars/test.yaml b/testsuite/tests/index/bad-config-vars/test.yaml new file mode 100644 index 00000000..0a859639 --- /dev/null +++ b/testsuite/tests/index/bad-config-vars/test.yaml @@ -0,0 +1,4 @@ +driver: python-script +indexes: + my_index: + in_fixtures: false diff --git a/testsuite/tests/show/jekyll/test.py b/testsuite/tests/show/jekyll/test.py index c71fcbfb..37101cfe 100644 --- a/testsuite/tests/show/jekyll/test.py +++ b/testsuite/tests/show/jekyll/test.py @@ -11,14 +11,27 @@ assert_eq( '---\n' 'layout: crate\n' 'crate: "hello"\n' - 'authors: ["Bob", "Alice"]\n' - 'maintainers: ["alejandro@mosteo.com", "bob@example.com"]\n' + 'authors: ["Bob",\n' + '"Alice"]\n' + 'maintainers: ["alejandro@mosteo.com",\n' + '"bob@example.com"]\n' 'licenses: ["GPL-3.0-only OR MIT"]\n' 'websites: ["example.com"]\n' - 'tags: ["tag1", "other-tag"]\n' + 'tags: ["tag1",\n' + '"other-tag"]\n' 'version: "1.0.1"\n' 'short_description: "\\"Hello, world!\\" demonstration project"\n' 'dependencies: [{crate: "libhello", version: "^1.0"}]\n' + 'config_variables: [{name: \'Var1\', type: \'Boolean\'},\n' + '{name: \'Var2\', type: \'String\', default: "str"},\n' + '{name: \'Var3\', type: \'Enum (A, B)\', default: "A"},\n' + '{name: \'Var4\', type: \'Integer range -9223372036854775808 .. 9223372036854775807\', default: "0"},\n' + '{name: \'Var5\', type: \'Integer range -1 .. 1\', default: "0"},\n' + '{name: \'Var6\', type: \'Real range -1.00000000000000E+00 .. 1.00000000000000E+00\', default: "0.00000000000000E+00"},\n' + '{name: \'Var7\', type: \'Real range -inf .. +inf\', default: "0.00000000000000E+00"}]\n' + 'config_settings: [{crate: \'hello\', settings: [{name: \'Var1\', value: "TRUE"}]},\n' + '{crate: \'libhello\', settings: [{name: \'Var1\', value: "FALSE"}]}]\n' + '\n' '---\n' 'This is an example of long description in a multi-line string.\n' '\n' -- 2.39.5