fpp/docs/users-guide/Specifying-Models-as-Files.adoc
Rob Bocchino 24b348cffd Revise ComputeDependencies
Revise User's Guide
2025-11-18 16:51:31 -08:00

1040 lines
32 KiB
Plaintext

== Specifying Models as Files
The previous sections have explained the syntactic and semantic elements
of FPP models.
This section takes a more file-centric view:
it explains how to assemble a collection of elements specified in
several files into a model.
We discuss several tools for specifying and analyzing dependencies between
model files.
We focus on how to use the tools, and we summarize their most important
features.
We do not attempt to cover every feature of every tool.
For more comprehensive coverage, see
https://github.com/nasa/fpp/wiki/Tools[the FPP wiki].
=== Dividing Models into Files
FPP does not require any particular division of model
elements into files.
For example, there is no requirement that each
type definition reside in its own file.
Nor is there any requirement that the names of files correspond
to the names of the definitions they contain.
Of course you should try to adhere to good style when decomposing a large model
into many files.
For example:
* Group related model elements into files, and name the files
according to the purpose of the grouping.
* Choose meaningful module names, and group all files in a single module
in single directory (including its subdirectories).
In the F Prime distribution, the `Fw` and `Svc` directories
follow this pattern, where the {cpp} namespaces `Fw` and `Svc`
correspond to FPP modules.
* Group files into modules and directories logically according to their function.
** You can group files according to their role in the FPP model.
For example, group types separately from ports.
** You can group files according to their role in the FSW.
For example, group framework files separately from application files.
* If the definition of a constant or type is logically part of a component,
then make the definition a member of the component.
There is still the usual requirement that a syntactic unit must begin and end
in the same file.
For example:
* Each type definition is a syntactic unit, so each type definition must begin
and end in the same file.
* A module definition may span several syntactic units of the form
`module { ... }`,
so a module definition may span multiple files (with each unit of the form
`module { ... }` residing in a single file).
These rules are similar to the way that {cpp} requires a class definition
`class C { ... }` or a namespace block `namespace N { ... }` to reside in a
single file, but it allows the definition of a single namespace `N` to span
multiple blocks
`namespace N { ... }` that can be in different files.
=== Include Specifiers
As part of an FPP model, you can write one or more *include specifiers*.
An include specifier is an instruction to include FPP source elements
from one file into another file.
Include specifiers may occur at the top level of a model,
inside a <<Defining-Modules,module definition>>,
inside a <<Defining-Components,component definition>>,
or inside a <<Defining-Topologies,topology definition>>.
The main purpose of include specifiers is to split up large syntactic units
into several files.
For example, a component definition may include a telemetry dictionary
from a separate file.
To write an include specifier, you write the keyword `include`
followed by string denoting a file path.
The path is relative to the file in which the include specifier appears.
By convention, included FPP files end in `.fppi` to distinguish
them from `.fpp` files that are directly analyzed and translated.
For example, suppose that the file `a.fppi` contains the definition
[source,fpp]
----
constant a = 0
----
In a file `b.fppi` in the same directory, you could write this:
[source,fpp]
--------
include "a.fppi"
constant b = a
--------
After resolving the include specifier, the model is equivalent
to the following:
[source,fpp]
----
constant a = 0
constant b = a
----
To see this, do the following:
. Create files `a.fppi` and `b.fpp` as described
above.
. Run `fpp-format -i b.fpp`.
`fpp-format` is a tool for formatting FPP source files.
It also can expand include specifiers.
`fpp-format` is discussed further in the section on
<<Analyzing-and-Translating-Models_Formatting-FPP-Source,
formatting FPP source>>.
As mentioned above, the path is relative to the directory
of the file containing the include specifier.
So if `a.fppi` is located in a subdirectory `A`, you could write this:
[source,fpp]
--------
include "A/a.fppi"
constant b = a
--------
And if `a.fppi` is located in the parent directory, you could write this:
[source,fpp]
--------
include "../a.fppi"
constant b = a
--------
You can write an include specifier inside a module.
In this case, any definitions in the included file are treated as occurring
inside the module.
For example, if `a.fppi` contains the definition `constant a = 0`,
then this source text
[source,fpp]
--------
module M { include "a.fppi" }
--------
defines the constant `M.a`.
As an exercise, try this:
----
% echo "module M { constant a = 0 }" > a.fppi
% fpp-check
include "a.fppi"
constant b = M.a
^D
%
----
The check should pass.
In any case, an included file must contain complete syntactic
units that may legally appear at the point where the include specifier appears.
For example, an included file may contain one or more constant
definitions or type definitions.
It may not contain a bare identifier `a`, as this is not a valid top-level
or module-level syntactic unit.
Nor is it valid to write an include specifier in a place where an identifier
like `a`
is expected.
For example, here is the result of a failed attempt to include
an identifier into a constant definition:
----
% echo a > a.fppi
% fpp-check
module M { constant include "a.fppi" = 0 }
constant b = M.a
^D
fpp-check
stdin: 1.21
module M { constant include "a.fppi" = 0 }
^
error: identifier expected
%
----
=== Dependencies
Whenever a model spans two or more files, one file _F_ may use
one or more definitions appearing in other files.
In order to analyze _F_, the tools must extract
the definitions from these other files, called the *dependencies* of _F_.
For example, suppose the file `a.fpp` contains the following definition:
[source,fpp]
----
constant a = 0
----
And suppose the file `b.fpp` contains the following definition:
[source,fpp]
--------
constant b = a
--------
If you present both files to `fpp-check`, like this:
----
% fpp-check a.fpp b.fpp
----
the check will pass.
However, if you present just `b.fpp`, like this:
----
% fpp-check b.fpp
----
you will get an error stating that the symbol `a` is undefined. (Try it and
see.)
The error occurs because the definition of `a` is located in `a.fpp`,
which was not included in the input to the analysis.
In this case we say that `a.fpp` is a *dependency* of `b.fpp`.
In order to analyze a file _F_ (for example, `b.fpp`), the analyzer
needs to be told where to find all the dependencies of _F_ (for example,
`a.fpp`).
For simple models, we can manage the dependencies by hand, as we
did for the example above.
However, for even moderately complex models, this kind of hand management
becomes difficult.
Therefore FPP has a set of tools and features for automatic dependency
management.
In summary, dependency management in FPP works as follows:
. You run a tool called `fpp-locate-defs` to generate *location specifiers*
for all the definitions _that could be used_ in a set of files _F_.
. You run a tool called `fpp-depend`, passing it the files _F_
and the location specifiers generated in step 1.
It emits a list of files containing definitions _that are actually used_ in _F_
(i.e., the dependencies of _F_).
These steps may occur in separate phases of development.
For example:
* You may run step 1 to locate all the type definitions
available for use in the model.
* You may run step 2 to develop ports that depend on the types.
Typically you would run this step as part of a build process, e.g.,
the CMake build process included in the F Prime distribution.
Below we explain these steps in more detail.
=== Location Specifiers
A location specifier is a unit of syntax in an FPP model.
It specifies the location of a definition used in the model.
Although it is possible to write location specifiers by hand,
you should usually not do so.
Instead, you should write definitions and let the tools discover their
locations, as described
in the section on <<Specifying-Models-as-Files_Locating-Definitions,locating
definitions>>.
==== Syntax
In general, a location specifier consists of the keyword `locate`, a kind of
definition,
the name of a definition, and a string representing a file path.
For type and constant specifiers, there is also an optional `dictionary`
specifier, which we will describe
<<Specifying-Models-as-Files_Location-Specifiers_Dictionary-Definitions,below>>.
For example, to locate the definition
[source,fpp]
----
constant a = 0
----
appearing in the file `a.fpp`, we would write
[source,fpp]
----
# Locating a constant definition
locate constant a at "a.fpp"
----
The kind of definition must be one of the following:
[[location-specifier-kinds]]
.Location Specifier Kinds
|===
|Keyword|Meaning
|`component`
|A <<Defining-Components,component definition>>
|`constant`
|A <<Defining-Constants,constant definition>>
|`instance`
|A <<Defining-Component-Instances,component instance definition>>
|`interface`
|A <<Defining-and-Using-Port-Interfaces_Defining-Port-Interfaces,port interface definition>>
|`port`
|A <<Defining-Ports,port definition>>
|`state` `machine`
|A <<Defining-State-Machines,state machine definition>>
|`topology`
|A <<Defining-Topologies,topology definition>>
|`type`
|A <<Defining-Types,type>> or <<Defining-Enums,enum>> definition
|===
As a further example, to locate a type `T` in a file `T.fpp`, we would write the
following:
[source,fpp]
----
# Locating a type definition
locate type T at "T.fpp"
----
To locate a port `P` in a file `P.fpp`, we write the following:
[source,fpp]
----
# Locating a port definition
locate port P at "P.fpp"
----
To locate an enum, we locate the type; the location of the enumerated
constants are then implied:
[source,fpp]
----
# Locating an enum definition,
# including the enumerated constant definitions
locate type E at "E.fpp"
----
The other kinds operate similarly.
==== Path Names
As with
<<Specifying-Models-as-Files_Include-Specifiers,include specifiers>>,
the path name in a location specifier _L_ is relative to the
location of the file where _L_ appears.
For example, suppose the file `b.fpp` appears in the file system in some
directory _D_.
Suppose also that _D_ has a subdirectory `Constants`, `Constants` contains a
file `a.fpp`,
and `a.fpp` defines the constant `a`.
Then in `b.fpp` we could write this:
[source,fpp]
----
locate constant a at "Constants/a.fpp"
----
If, instead of residing in a subdirectory, `a.fpp` were located one directory above
`b.fpp` in the file system, we could write this:
[source,fpp]
----
locate constant a at "../a.fpp"
----
==== Definition Names
The definition name appearing after the keyword `locate`
may be a qualified name.
For example, suppose the file `M.fpp` contains the following:
[source,fpp]
----
module M { constant a = 0 }
----
Then in file `b.fpp` we could write this:
[source.fpp]
----
locate constant M.a at "M.fpp"
----
Optionally, we may enclose the location specifier in the module `M`, like
this:
[source,fpp]
----
module M { locate constant a at "M.fpp" }
----
A location specifier written inside a module this way has its definition name
implicitly qualified with the module name.
For example, the name `a` appearing in the example above is automatically
resolved to `M.a`.
Note that this rule is different than for other uses of definitions.
For example, when using the constant `M.a` in an expression inside module `M`,
you may spell the constant either `a` or `M.a`;
but when referring to the same constant `M.a` in a location specifier inside
module `M`, you must write `a` and not `M.a`.
(If you wrote `M.a`, it would be incorrectly resolved to `M.M.a`.)
The purpose of this rule is to facilitate dependency analysis,
which occurs before the analyzer has complete information about
definitions and their uses.
==== Included Files
When you write a file that contains definitions and you
<<Specifying-Models-as-Files_Include-Specifiers,include that file in another file>>,
the location of each definition is the file where the definition is
included, not the file where the definition appears.
For example, suppose that file `a.fppi` contains the
definition `constant a = 0`,
and suppose that file `b.fpp` contains the include specifier `include "a.fppi"`.
When analyzing `b.fpp`, the location of the definition of the constant `a`
is `b.fpp`, not `a.fppi`.
==== Dictionary Definitions
For type and constant specifiers only, if the definition being located
is a
<<Dictionary-Definitions,dictionary definition>>, then you must
write the keyword `dictionary` after the keyword `locate`
and before the definition kind.
For example, to locate the dictionary definition
[source,fpp]
----
dictionary constant b = 1
----
appearing in the file `b.fpp`, we would write
[source,fpp]
----
# Locating a dictionary constant definition
locate dictionary constant b at "b.fpp"
----
The `dictionary` keyword tells the analyzer that the definition is a dictionary
definition.
This fact is important when the model includes a topology definition.
In this case, the analyzer includes all dictionary definitions as
dependencies of the model.
That way the definitions are available
when generating the dictionary for the topology.
If a definition is a dictionary definition and the corresponding location
specifier does not specify `dictionary` (or vice versa), then the analyzer
will report an error.
==== Repeated Location Specifiers
An FPP model may contain any number of location specifiers for the same
definition, so long as the following conditions are met:
. All the specifiers must be consistent.
This means that all the specifiers for the same definition
provide the same location, and either they all specify `dictionary`
or none of them does.
. All the specifiers must agree with the definition, if it exists
in the model.
This means that the location specifiers specify the location
of the definition, and the specifiers specify `dictionary`
if and only if the definition is a dictionary definition.
=== Locating Definitions
Given a collection of FPP source files _F_, you can generate location specifiers
for all the definitions in _F_.
The tool for doing this analysis is called `fpp-locate-defs`.
As example, you can run `fpp-locate-defs` to report the locations of all
the definitions in a subdirectory called `Constants` that contains constant
definitions for your model.
When analyzing other files that use the constants, you can use the location
specifiers to discover dependencies on individual files within `Constants`.
==== Running fpp-locate-defs
To locate definitions, do the following:
. Collect all the FPP source files containing the definitions you want to
locate.
For example, run `find Constants -name '*.fpp'`.
. Run `fpp-locate-defs` with the result of step 1 as the command-line
arguments.
The result will be a list of location specifiers.
For example, suppose the file `Constants/a.fpp` defines the constant `a`.
Running
----
% fpp-locate-defs `find Constants -name '*.fpp'`
----
generates the location specifier
[source,fpp]
----
locate constant a at "Constants/a.fpp"
----
==== Location Paths
By default, the location path is relative to the current
directory.
To specify a different base directory, use the option `-d`.
For example, running
----
% fpp-locate-defs -d Constants `find Constants -name '*.fpp'`
----
generates the location specifier
[source,fpp]
----
locate constant a at "a.fpp"
----
==== Included Definitions
Consider the case where you write a definition in one file and
include that file in another file via an
<<Specifying-Models-as-Files_Include-Specifiers,include specifier>>.
For example, suppose file `Constants.fpp` looks like this:
[source.fpp]
----
module Constants {
constant a = 0
include "b.fppi"
}
----
Suppose `b.fppi` contains the definition `constant b = 1`.
If you run `find` on this directory as described above and provide
the output to `fpp-locate-defs`, then you will get the following output:
. The definition of constant `a` is located at `Constants.fpp`.
. The definition of constant `b` is also located at `Constants.fpp`.
For purposes of dependency analysis, this is what you want.
You want uses of `b` to depend on `Constants.fpp` (where the
definition
of `b` is included) rather than `b.fpp` (where the definition of `b` is
stated).
When running a `find` command to find files containing definitions,
you should exclude any files that are included in other files.
If your main FPP files end with `.fpp` and your included FPP files end with
`.fppi`, then running
----
find . -name '*.fpp'
----
will pick up just the main files.
=== Computing Dependencies
Given files _F_ and location specifiers _L_ that locate the definitions used in
_F_, you can
generate the dependencies of _F_.
The tool for doing this is called `fpp-depend`.
==== Running fpp-depend
To run `fpp-depend`, you pass it as input (1) files _F_ that you want to
analyze
and (2) a superset of the location specifiers for the definitions used in that
code.
The tool extracts the location specifiers for the definitions used in _F_, resolves
them to absolute path names (the dependencies of _F_), and writes the
dependencies to standard output.
For example, suppose the file `a.fpp` contains the following
definition:
[source,fpp]
----
constant a = 0
----
Suppose the file `b.fpp` contains the following definition:
[source,fpp]
----
constant b = 1
----
Suppose the file `locations.fpp` contains the following location
specifiers:
[source,fpp]
----
locate constant a at "a.fpp"
locate constant b at "b.fpp"
----
And suppose the file `c.fpp` contains the following definition of `c`,
which uses the definition of `b` but not the definition of `a`:
[source,fpp]
--------
constant c = b + 1
--------
Then running `fpp-depend locations.fpp c.fpp` produces the output
`[path-prefix]/b.fpp`.
The dependency output contains absolute path names, which will vary from system
to system.
Here we represent the system-dependent part of the path as `[path-prefix]`.
----
% fpp-depend locations.fpp c.fpp
[path-prefix]/b.fpp
----
As usual with FPP tools, you can provide input as a set of files
or on standard input.
So the following is equivalent:
----
% cat locations.fpp c.fpp | fpp-depend
[path-prefix]/b.fpp
----
==== Transitive Dependencies
`fpp-depend` computes dependencies transitively.
This means that if _A_ depends on _B_ and _B_
depends on _C_, then _A_ depends on _C_.
For example, suppose again that `locations.fpp`
contains the following location specifiers:
[source,fpp]
----
locate constant a at "a.fpp"
locate constant b at "b.fpp"
----
Suppose the file `a.fpp` contains the following definition:
[source,fpp]
----
constant a = 0
----
Suppose the file `b.fpp` contains the following definition:
[source,fpp]
--------
constant b = a
--------
And suppose that file `c.fpp` contains the following definition:
[source,fpp]
--------
constant c = b
--------
Notice that there is a direct dependency of `c.fpp` on `b.fpp`
and a transitive dependency of `c.fpp` on `a.fpp`.
The transitive dependency occurs because there is a direct dependency
of `c.fpp` on `b.fpp` and a direct dependency of `b.fpp` on `a.fpp`.
Running `fpp-depend` on `locations.fpp` and `c.fpp`
produces both dependencies:
----
% fpp-depend locations.fpp c.fpp
[path-prefix]/a.fpp
[path-prefix]/b.fpp
----
==== Missing Dependencies
Suppose we construct the files `locations.fpp` and `a.fpp`, `b.fpp`, and `c.fpp`
as described in the previous section, but then we temporarily remove `b.fpp`.
Then the following facts are true:
. `fpp-depend` can see the direct dependency of `c.fpp` on `b.fpp`.
. `fpp-depend` can see that `b.fpp` does not exist.
In this case we say that `b.fpp` is a *missing dependency*.
. `fpp-depend` cannot see that `b.fpp` depends on `a.fpp` (that dependency
occurred in the missing file) and therefore it cannot see that
`c.fpp` depends on `a.fpp`.
In this case, by default, `fpp-depend` does the best that it can:
it reports the dependency of `c.fpp` on `b.fpp`.
----
% fpp-depend locations.fpp c.fpp
[path-prefix]/b.fpp
----
The philosophy behind `fpp-depend` is to be as permissive and enabling as
possible.
It doesn't assume that something is wrong because a dependency is missing:
for example, that dependency could be created later, as part of a code-generation
step.
However, you may want to know about missing dependencies, either to issue
a warning or error because something really is wrong, or to identify files to
generate.
To record missing dependencies, use the `-m` option.
It takes as an argument the name of a file, and it writes missing dependencies
(if any)
to that file.
For example, the command
----
fpp-depend -m missing.txt locations.fpp c.fpp
----
writes the missing dependency `[path-prefix]/b.fpp` to `missing.txt` in
addition to writing
the dependency `[path-prefix]/b.fpp` to standard output.
==== Included Files
Suppose file `a.fpp` contains the
<<Specifying-Models-as-Files_Include-Specifiers,include specifier>>
`include "b.fppi"`.
Then there are two options for computing the dependencies of `a.fpp`:
. `a.fpp` does not depend on `b.fppi`.
. `a.fpp` does depend on `b.fppi`.
Option 1 is what you want for assembling the input
to FPP analysis and translation tools such as `fpp-check`.
In this case, when analyzing `a.fpp`, the tool will resolve the include
specifier and include the contents of `b.fppi`. So `b.fppi` should
not be included as a separate input to the analysis.
On the other hand, suppose you are constructing a list of dependencies
for a build system such as the F Prime CMake system.
In this case, the build system doesn't know anything about FPP include specifiers.
However, it needs to know that `a.fpp` does depend on `b.fppi` in the sense that
if `b.fppi` is modified, then `a.fpp` should be analyzed or translated again.
So in this case we want option 2.
By default, `fpp-depend` provides option 1:
----
% echo 'include "b.fppi"' > a.fpp
% rm -f b.fppi
% touch b.fppi
% fpp-depend a.fpp
----
To get option 2, use the `-i` option to `fpp-depend`.
It takes as an argument the name of a file, and it writes the included dependencies
(if any) to that file.
----
% echo 'include "b.fppi"' > a.fpp
% rm -f b.fppi
% touch b.fppi
% fpp-depend -i included.txt a.fpp
% cat included.txt
[path-prefix]/b.fppi
----
In practice, you usually run `fpp-depend` with the `-i` _file_ option
enabled.
Then option 1 corresponds to the output of the tool, and option 2 corresponds
to the output plus the contents of _file_.
==== Dependencies Between Build Modules
As discussed
<<Specifying-Models-as-Files_Computing-Dependencies_Transitive-Dependencies,
above>>, the standard output of `fpp-depend` reports transitive dependencies.
This is ordinarily what you want (a) for computing the input to an FPP
analysis tool and (b) for managing dependencies between files in a build.
For example, suppose that `a.fpp` depends on `b.fpp` and `b.fpp` depends on `c.fpp`.
When running analysis or code generation on `a.fpp`, you will need to import
`b.fpp` and `c.fpp` (see the
<<Specifying-Models-as-Files_Locating-Uses,next section>>
for an example).
Further, if you have a build rule for translating `a.fpp` to {cpp}, then you probably want to
re-run that rule if `c.fpp` changes.
Therefore you need to report a dependency of `a.fpp` on `c.fpp`.
However, suppose that your build system divides the FPP files into groups
of files called *build modules*, and it manages dependencies between
the modules.
This is how the F Prime CMake system works.
In this case, assuming there is no direct dependency from `a.fpp` to `c.fpp`,
you may _not_ want to report a dependency from `a.fpp` to `c.fpp`
to the build system:
. If `a.fpp` and `c.fpp` are in the same build module, then they
are in the same node of the dependency graph.
So there is no dependency to manage.
. Otherwise, it suffices to report the file dependencies (a) from `a.fpp`
to `b.fpp` and (b) from `b.fpp` to `c.fpp`.
We can let the build system infer (a) the direct dependency from the module
containing `a.fpp` to the module containing `b.fpp`; (b) the direct
dependency from the module
containing `b.fpp` to the module containing `c.fpp`; and (c)
the transitive dependency from the module containing `a.fpp`
to the module containing `c.fpp`.
To compute direct dependencies, run `fpp-depend` with the option
`-d` _file_.
The tool will write a list of direct dependencies to _file_.
Because direct dependencies are build dependencies,
any
<<Specifying-Models-as-Files_Computing-Dependencies_Included-Files,
included files>>
will appear in the list.
For this purpose, an included file is (a) any file included by an
input file to `fpp-depend`; or (b) any file included
by such a file, and so forth.
When setting up a build based on build modules, you will typically
use `fpp-depend` as follows, for each module _M_ in the build:
. Let _S_ be the list of source files in _M_.
. Run `fpp-depend -m missing.txt -d direct.txt` _S_ and use the
output as follows:
.. The standard output reports the FPP source files to import
when running FPP analysis tools on the module.
.. `missing.txt` reports missing dependencies.
.. `direct.txt` reports direct dependencies.
Use those to construct module dependencies for the build system.
You can also use the `-g` option to identify generated files;
we discuss this option
<<Analyzing-and-Translating-Models_Identifying-Generated-Files,below>>.
Note that we do not use the `-i` option to `fpp-depend`, because the relevant
included files are already present in `direct.txt`.
==== Framework Dependencies
Certain FPP constructs imply dependencies on parts of the F Prime framework
that may not be available on all platforms.
For example, use of a
<<Defining-Components_Port-Instances_Basic-Port-Instances,guarded input port>>
requires that an operating system provides a mutex lock.
To report framework dependencies, run `fpp-depend` with the option
`-f` _file_, where _file_ is the name of an output file.
The currently recognized framework dependencies are as follows:
* `Fw_Comp` if the FPP model defines a passive component.
* `Fw_CompQueued` if the model defines a queued or active component.
* `Os` if the model defines a queued or active component or
uses a guarded input port specifier.
Each dependency corresponds to a build module (i.e., a
statically compiled library) of the F Prime framework.
`fpp-depend` writes the dependencies in the order that they must
be provided to the linker.
=== Locating Uses
Given a collection of files _F_ and their dependencies _D_, you can generate
the locations of the definitions appearing in _D_ and used in _F_.
This information is not necessary for doing analysis and translation -- for
that it is sufficient to know the file dependencies _D_.
However, by reporting dependencies on individual definitions,
this analysis provides an additional level of detail that may be helpful.
The tool for doing this analysis is called `fpp-locate-uses`.
As an example, you can run `fpp-locate-uses` to report the locations of all the
type definitions used in a port definition.
To locate uses, run `fpp-locate-uses -i` _D_ _F_, where _D_ is a comma-separated
list and _F_ is a space-separated list.
The `-i` option stands for _import_: it says that the files _D_ are to be read
for their
definitions, but not to be included in the results of the analysis.
For example, suppose `a.fpp` defines constant `a`, `b.fpp` defines constant
`b`,
and `c.fpp` uses `a` but not `b`.
Then `fpp-locate-uses -i a.fpp,b.fpp c.fpp` generates the output `locate a at
"a.fpp"`
Note that unlike in the case of
<<Specifying-Models-as-Files_Computing-Dependencies,dependency analysis>>,
the inputs _D_ and _F_ to `fpp-locate-uses` must form a complete model.
There must be no name used in _D_ or in _F_ that is not defined somewhere in
_D_ or in _F_.
If _D_ is the output of running `fpp-depend` on _F_, and there are no
<<Specifying-Models-as-Files_Computing-Dependencies_Missing-Dependencies,
missing dependencies>>,
then this property should hold.
With `fpp-locate-uses`, you can automatically derive the equivalent of the
`include`
declarations that you would normally write by hand when programming in {cpp}.
For example, suppose you have specified a port _P_ that uses a type _T_.
To bring _P_ into a {cpp} translation context, you would write an `include`
directive that
includes _T_ into _P_. In FPP you don't do this.
Instead, you can do the following:
. Run `fpp-locate-defs` to generate location specifiers _L_ for all the type
definitions.
You can do this as needed, or you can do it once and check it in as part of
the module that defines the types.
. Run `fpp-depend` on _L_ and _P_ to generate the dependencies _D_ of _P_.
. Run `fpp-locate-uses -i` _D_ _P_.
The result is a location specifier that gives the location of _T_.
If you wish, you can check the result in as part of the source code that
defines _P_.
Doing this provide as a kind of "`import statement,`" if that is desired
to make the dependencies explicit in the code.
Or you can just use the procedure given above to generate the "import
statement"
whenever desired, and see the dependencies that way.
As with `fpp-locate-defs`, you can use `-d` to specify a base directory
for the location specifiers.
=== Path Name Aliases
Because FPP associates locations with symbols, and the locations
are path names, care is required when using path names that are
aliases of other path names, via symbolic links or hard links.
There are two issues to consider:
relative paths and unique locations.
==== Relative Paths and Symbolic Links
A *relative path* is a path that does not start with a slash and is
relative to the current directory path, which is
set by the environment in which an FPP tool is run.
For example, the command sequence
[source,bash]
----
% cd /home/user/dir
% fpp-check file.fpp
----
sets the current directory path to `/home/user/dir` and then runs
`fpp-check file.fpp`.
In this case, the relative path `file.fpp` is resolved to
`/home/user/dir/file.fpp`.
An *absolute path* is a path that starts with a slash and specifies
a complete path from the root of the file system, e.g.,
`/home/user/dir/file.fpp`.
Because FPP is implemented in Scala, relative paths are resolved by
the Java Virtual Machine (JVM).
When the current directory path contains a symbolic link,
this resolution may not work in the way that you expect.
For example, suppose the following:
* _D_ is an absolute path to a directory.
_D_ is a "`real`" path, i.e., none of the path elements in _D_ is a symbolic
link to a directory.
* _S_ is an absolute path in which one or more of the path elements is a symbolic
link to a directory.
After resolving all symbolic links, _S_ points to _D_.
Suppose that _D_ contains a file `file.fpp`, and that the
current directory path is _D_.
In this case, when you run an FPP tool with `file.fpp` as input,
any symbols defined in `file.fpp` will have location
_D_ `/file.fpp`, as expected.
Now suppose that the current directory path is _S_.
In this case, when you run an FPP tool with `file.fpp` as input,
the symbols defined in `file.fpp` again have location _D_ `/file.fpp`,
when you might expect them to have location _S_ `/file.fpp`.
This is because the JVM resolves all symbolic links before computing
relative path names.
This behavior can cause problems when using the `-p` (path prefix)
option with FPP code generation tools, as described in the section on
<<Analyzing-and-Translating-Models,analyzing and translating models>>.
See that section for details, and for suggested workarounds.
==== Unique Locations
The FPP analyzers assume that each symbol _s_ has
a unique path defining the location of the source file where
_s_ is defined.
If paths contain names that are aliased via symbolic links or hard links, then
this may not be true:
for example, _P~1~_ and _P~2~_ may be syntactically different
absolute paths that represent the same physical location in the file system.
In this case it may be possible for the tools to associate two different locations
with the same FPP symbol definition.
You must ensure that this doesn't happen.
If you present the same file _F_ to the FPP tools several times,
for example to locate definitions and to compute dependencies,
you must ensure that the path describing _F_ is the same each
time, after resolving relative paths as described above.