Package terminology
Packages are now referred to as extensions. The use of the package name in identifiers implies extension.
A PXT extension lives in its own directory, locally under libs/
in a PXT target. A extension
is described by the pxt.json
file. To show a real example, here is the pxt.json file for the pxt-neopixel extension.
The pxt.json
is described by the interface PackageConfig
in pxtpackage.d.ts:
interface PackageConfig {
name: string; // public:true -> name must match ^[a-z][a-z0-9\-_]+
description?: string; // longer description of extension
license?: string; // name of license (as found on github)
authors?: string[];
files: string[]; // files to be included and compiled in the extension
additionalFilePath?: string; // another directory to find files from
dependencies: Map<string>; // extension dependencies (see below for more)
public?: boolean; // set true to enable the extension to be published (to cloud),
// in support of publishing user scripts
icon?: string; // url to icon -- support for built-in extensions only
card?: CodeCard;
documentation?: string; // doc page to open when loading project
// semver description for support target version
version?: string;
installedVersion?: string;
targetVersions?: TargetVersions; // versions of the target/pxt the extension was compiled against
fileDependencies?: Map<string>; // exclude certain files if dependencies are not fulfilled
testFiles?: string[];
testDependencies?: Map<string>;
simFiles?: string[];
cppDependencies?: Map<string>;
binaryonly?: boolean;
platformio?: PlatformIOConfig;
yotta?: YottaConfig;
gistId?: string;
}
Simple extensions generally just depend on their own target’s core extension:
"dependencies": {
"core": "file:../core"
}
A number of targets use pxt-common-packages and specialize them to fit their target’s needs. These are a base set of extensions for use in a target. For example, the Adafruit Circuit Playground Express extension is the union of a number of extensions.
"dependencies": {
"base": "file:../base",
"core": "file:../core",
"buttons": "file:../buttons",
"accelerometer": "file:../accelerometer",
"lightsensor": "file:../lightsensor",
"thermometer": "file:../thermometer",
"music": "file:../music",
"light": "file:../light",
"switch": "file:../switch",
"infrared": "file:../infrared",
"microphone": "file:../microphone",
"touch": "file:../touch"
}
Each of the above extensions is local to the target but inherits code from pxt-common-packages,
which it can then override or specialize, as the target needs. For example, the button extension
in the target pxt-adafruit is defined in terms of the button extension from
pxt-common-packages using the additionalFilePath
field:
{
"description": "Button A and B drivers",
"additionalFilePath": "../../node_modules/pxt-common-packages/libs/buttons"
}
The additionalFilePath
field refers to the node_modules
directory of the target.
The pxt.json
file need to only specify what’s changed (in the example above description
)
with respect to the pxt.json
in additionalFilePath
.
The additionalFilePath
is recursive or multi-level - the pxt.json
in the referenced directory
might have another additionalFilePath
and it will work as expected.
The files listed under testFiles
are only included when the extension is compiled
as the top-level program and not just imported into some other program.
Typically this happens when you run pxt
from command line in the
extension directory, or when you hit Download when editing extension itself in
the online editor.
They usually contain unit tests for extension.
Similarly, dependencies from testDependencies
are only included when compiled
as top-level. The testDependencies
can be added for multiple targets
and will only be added if they can be resolved.
While not very common,
in some extensions certain functionality should be only enabled when another
extension is already present in the project.
For example, a weather
sensor package may have code for streaming weather
data over radio, but that should be only enabled when there’s already the radio
extension in the project (to avoid problems on boards without radio, or when
Bluetooth disables radio).
Another solution to this problem is to create a new package weather-radio
,
which depends on weather
and radio
.
This is advisable, when the additional functionality is sizable, otherwise
it’s better to keep the number of packages down.
Example configuration:
...
"files": [
"weather-reading.ts",
"weather-radio.ts",
"weather-jacdac.ts",
"jd-helper.ts",
"README.md"
],
"fileDependencies": {
"weather-radio.ts": "radio",
"weather-jacdac.ts": "jacdac",
"jd-helper.ts": "jacdac"
},
...
Here, the file weather-radio.ts
will be only included when radio
is referenced in
the project, and files weather-jacdac.ts
and jd-helper.ts
will be only included when
jacdac
is present.
Typically, you would add radio
and jacdac
as testDependencies
, so you can see
the entire extension in the editor.
There is no point in adding them as regular dependencies
- that would negate the
effects of fileDependencies
and always include both the dependencies and files.
In addition to package names, you can also use target:microbit
or similar
to indicate that the file should be only included when compiling for a specific MakeCode
editor (other options include target:maker
and target:arcade
).
Finally, boolean expressions are allowed, using !
, &&
and ||
operators.
Parentheses are not allowed, and operator precedence is the same as in C or JavaScript
(!
binds tighter than &&
, which binds tighter than ||
).
...
"fileDependencies": {
"weather-radio.ts": "!bluetooth && target:microbit",
"weather-buttons.ts": "target:microbit && arcade-controls || target:arcade"
},
...
In future, we may allow things like "radio >= 1.2.3"
.
Dependencies under cppDependencies
are only considered when generating
code for the C++ compiler.
Usually, one would list all optional packages which contain
C++ code in cppDependencies
of your core package.
Then, when user actually adds any of these optional packages, the
C++ code doesn’t change and re-compilation (and thus cloud round-trip) is not required.
Constants defined to form part of the config.h
file for the the DAL platform used in the build are set in the "yotta"
section. The config
type is either codal
or microbit-dal
:
CODAL example:
"yotta": {
"config": {
"codal": {
"component_count": 64,
"dmesg_buffer_size": 1024
}
}
}
micro:bit DAL example:
"yotta": {
"config": {
"microbit-dal": {
"bluetooth": {
"enabled": 1
}
}
}
}
If not referring to a specific DAL platform, define simple cpp
constants like this:
"yotta": {
"config": {
"DEVICE_USB": 1,
"DEVICE_MOUSE": 1
}
}
See this sample repo for config.json
examples of setting various hardware device constants.