Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New manifest.json property redirect #1425

Open
wants to merge 6 commits into
base: public
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 86 additions & 0 deletions documentation/tools/manifest.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ A manifest is a JSON file that describes the modules and resources necessary to
* [`platforms`](#platforms)
* [`subplatforms`](#subplatforms)
* [`bundle`](#bundle)
* [`redirect`](#redirect)
* [How manifests are processed](#process)

<a id="example"></a>
Expand Down Expand Up @@ -706,6 +707,91 @@ The `bundle` object is used by the [`mcbundle` command line tool](./tools.md#mcb
"icon": "./store/icon.png"
}
```
***

<a id="redirect"></a>
### `redirect`

The `redirect` array can be used to redirect (or change) manifest properties that are defined in other manifest files. This is useful when including system manifests where properties need to be altered without editing the Moddable core files.

For example, to use a private directory for sub-modules, you can use an `include` redirection like this:

```json
"redirect": {
"include": {
"from": "./targets/$(SUBPLATFORM)/manifest.json",
"to": "$(SRC)/targets/$(PLATFORM)/$(SUBPLATFORM)/manifest.json"
}
}
```

The properties `include`, `strip`, and `preload` (which are string arrays in the manifest) expect `from` and `to` for each property to redirect. The redirection rule can be a single object, or an array of objects:

```json
"redirect": {
"include": [
{
"from": "some-include-path",
"to": "some-redirect-path"
},
{
"from": "another-include-path",
"to": "new-path"
}
]
}
```

The `modules`, `resources`, `data`, `build`, and `config` properties (which are objects in the manifest) expect a similar format, but with an object key qualifier:

```json
"redirect": {
"modules": {
"embedded:provider/builtin": {
"from": "./targets/$(SUBPLATFORM)/manifest.json",
"to": "$(SRC)/targets/$(SUBPLATFORM)/manifest.json"
}
}
}
```

The value of `null` can be used as a wildcard on `from` to match all. For example, to disable all preloads and replace them with a specific list of modules:

```json
"redirect": {
"preload": {
"from": null,
"to": ["engine", "unit-test"]
}
}
```

`to` can also use `null` to indicate the item should be deleted (if `to` is not provided, it is assumed to be a delete). For example, to remove all preloads:

```json
"redirect": {
"preload": {
"from": null
}
}
```

You can qualify `redirect` to apply only for specific platforms by placing it inside the `platforms` property. The following will replace the `config.rotation` value only for the `esp32/m5stick_cplus` platform:

```json
"platforms": {
"esp32/m5stick_cplus": {
"redirect": {
"config": {
"rotation": {
"from": "270",
"to": "90"
}
}
}
}
}
```

***
<a id="process"></a>
Expand Down
83 changes: 83 additions & 0 deletions tools/mcmanifest.js
Original file line number Diff line number Diff line change
Expand Up @@ -1631,6 +1631,7 @@ export class Tool extends TOOL {
this.windows = this.currentPlatform == "win";
this.slash = this.windows ? "\\" : "/";
this.escapedHash = this.windows ? "^#" : "\\#";
this.redirectRules = [];

this.buildPath = this.moddablePath + this.slash + "build";
this.xsPath = this.moddablePath + this.slash + "xs";
Expand Down Expand Up @@ -2186,6 +2187,7 @@ export class Tool extends TOOL {
}
parseManifest(path, manifest) {
let platformInclude;

if (!manifest) {
var buffer = this.readFileString(path);
try {
Expand Down Expand Up @@ -2217,6 +2219,9 @@ export class Tool extends TOOL {
manifest.include = manifest.include.concat(platformInclude);
}
}
if ("redirect" in platform) {
this.redirectRules = this.redirectRules.concat(platform.redirect);
}
if (platform.dependency && ("esp32" == this.platform)) {
manifest.dependencies = [];
for (let i=0; i<platform.dependency.length; i++) {
Expand All @@ -2228,6 +2233,10 @@ export class Tool extends TOOL {
}
}
}
if ("redirect" in manifest) {
this.redirectRules = this.redirectRules.concat(manifest.redirect);
}
this.redirect(manifest);
if ("include" in manifest) {
if (manifest.include instanceof Array)
manifest.include.forEach(include => this.includeManifest(include));
Expand All @@ -2237,6 +2246,79 @@ export class Tool extends TOOL {
this.manifests.push(manifest);
return manifest;
}
redirect(manifest) {
let platform;
if ("platforms" in manifest)
platform = this.matchPlatform(manifest.platforms, this.fullplatform, false);
for (const redirectRule of this.redirectRules) {
for (const manifestProperty of Object.keys(redirectRule)) {
if (!Array.isArray(redirectRule[manifestProperty]))
redirectRule[manifestProperty] = [redirectRule[manifestProperty]];
if (["include", "strip", "preload"].includes(manifestProperty)) {
for (const singleRule of redirectRule[manifestProperty]) {
if (singleRule === null || !("from" in singleRule))
throw new Error(`Missing "from" in redirect rule for property "${manifestProperty}"`);
this.redirectArray(manifest, manifestProperty, singleRule.from , singleRule.to);
if (platform)
this.redirectArray(platform, manifestProperty, singleRule.from, singleRule.to);
}
} else if (["modules", "resources", "data", "build", "config"].includes(manifestProperty)) {
for (const keyedRule of redirectRule[manifestProperty]) {
if (keyedRule === null || "object" !== typeof keyedRule)
throw new Error(`Invalid redirect rule "${manifestProperty}" (must be object, not ${typeof keyedRule})`);
for (const singleRuleKey of Object.keys(keyedRule)) {
const singleRule = keyedRule[singleRuleKey];
if (singleRule === null || "object" !== typeof singleRule)
throw new Error(`Invalid redirect rule "${manifestProperty}.${singleRuleKey}" (must be object, not ${typeof singleRule})`);
if (!("from" in singleRule))
throw new Error(`Missing "from" in redirect rule for property "${manifestProperty}"`);
this.redirectObject(manifest, manifestProperty, singleRuleKey, keyedRule[singleRuleKey].from, keyedRule[singleRuleKey].to);
if (platform)
this.redirectObject(platform, manifestProperty, singleRuleKey, keyedRule[singleRuleKey].from, keyedRule[singleRuleKey].to);
}
}
}
else
throw new Error(`Redirect not supported for property "${manifestProperty}"`);
}
}
}
redirectArray(manifest, property, from, to) {
if (!from) {
if (property in manifest) {
manifest[property] = to ?? [];
}
} else if ("string" == typeof from) {
if (property in manifest) {
let array = Array.isArray(manifest[property]) ? manifest[property] : [manifest[property]];
if (array.includes(from)) {
if (to) {
if (!Array.isArray(manifest[property]))
manifest[property] = [manifest[property]];
manifest[property] = manifest[property].filter(item => item != from).concat(to);
} else {
if (Array.isArray(manifest[property]) && manifest[property].length > 1)
manifest[property] = manifest[property].filter(item => item != from);
else
delete manifest[property];
}
}
}
} else
throw new Error(`Invalid redirect rule "${property}.from" (must be string or null, not ${typeof from})`);
}
redirectObject(manifest, property, key, from, to) {
if (!from) {
if (property in manifest) {
manifest[property] = to ?? {};
}
} else if ("string" === typeof from) {
if (property in manifest) {
this.redirectArray(manifest[property], key, from, to);
}
} else
throw new Error(`Invalid redirect rule "${property}.${key}" (must be object or null, not ${typeof from})`);
}
resolvePrefix(value) {
const colon = value.indexOf(":");
if (colon > 0) {
Expand Down Expand Up @@ -2302,6 +2384,7 @@ export class Tool extends TOOL {
this.manifests.forEach(manifest => this.mergeManifest(this.manifest, manifest));

this.mergeDependencies(this.manifests);
this.redirect(this.manifest);

if (this.manifest.errors.length) {
this.manifest.errors.forEach(error => { this.reportError(null, 0, error); });
Expand Down