Skip to content

Commit 07f5964

Browse files
authored
feature(breaking): Add Linux support (#28)
feature: Add app discovery support for Linux * polish: Add `target_os` field to the ErrorKind::UnsupportedPlatform { .. } error * bugfix: Error in installation_directory() shouldn't say from_app_directory() * refactor(breaking): Make WolframApp.build_code into Option<u32> (was u32) Not all Wolfram apps make the build code easily discoverable without launching a WL session, which isn't fast or reliable enough for first-pass discovery of apps. This is particulary hard on Linux. * Add new linux.rs, with discover_all() and from_app_directory() methods with implementations specific to Linux. * Fill in OperatingSystem::Linux cases in file lookup methods on WolframApp * Add conversion of 'aarch64-unknown-linux-gnu' target triple to 'Linux-ARM64' System ID App discovery on Linux is more difficult and finicky than on macOS and Windows because there is no standard file/format for application metadata (.plist on macOS, the registry on Windows), so any information about an app has to be parsed out of whatever files are in the app directory. Undoubtedly, this initial implementation of Linux app discovery can be improved. However, it suffices to discover and run all of the various file lookup methods for three app/versions I tested it against: - Mathematica 13.1 - Wolfram Engine v13.0 - Wolfram Engine v13.3 (internal build) * bugfix: Fix todo!() in ErrorKind::Other case in Display impl * docs: Mention cross-compilation sanity check tests in Development.md
1 parent e38e320 commit 07f5964

File tree

7 files changed

+324
-62
lines changed

7 files changed

+324
-62
lines changed

README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,4 +100,7 @@ Unless you explicitly state otherwise, any contribution intentionally submitted
100100
for inclusion in the work by you, as defined in the Apache-2.0 license, shall be
101101
dual licensed as above, without any additional terms or conditions.
102102

103-
See [CONTRIBUTING.md](./CONTRIBUTING.md) for more information.
103+
See [CONTRIBUTING.md](./CONTRIBUTING.md) for more information.
104+
105+
See [**Development.md**](./docs/Development.md) for instructions on how to
106+
perform common development tasks when contributing to this project.

docs/Development.md

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,37 @@ enabled:
99
```shell
1010
$ cargo build --features cli
1111
$ ./target/debug/wolfram-app-discovery
12-
```
12+
```
13+
14+
### Check building on other platforms
15+
16+
Doing a full test of `wolfram-app-discovery` requires actually running it
17+
on each platform. However, it is often useful to test that type checking and
18+
building complete successfully when targeting each of the three operating
19+
systems (macOS, Windows, and Linux) that `wolfram-app-discovery` supports.
20+
21+
Note that when doing these quick "does it build?" tests, testing both x86_64 and
22+
ARM variants of an operating system doesn't provide much additional coverage
23+
beyond checking only one or the other.
24+
25+
**Build for macOS:**
26+
27+
```shell
28+
$ cargo build --target x86_64-apple-darwin
29+
$ cargo build --target -apple-darwin
30+
```
31+
32+
**Build for Windows:**
33+
34+
```shell
35+
$ cargo build --target x86_64-pc-windows-msvc
36+
```
37+
38+
**Build for Linux:**
39+
40+
x86-64:
41+
42+
```shell
43+
$ cargo build --target x86_64-unknown-linux-gnu
44+
$ cargo build --target aarch64-unknown-linux-gnu
45+
```

src/lib.rs

Lines changed: 105 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ use std::{
6767
fmt::{self, Display},
6868
path::PathBuf,
6969
process,
70+
str::FromStr,
7071
};
7172

7273

@@ -138,7 +139,7 @@ pub struct AppVersion {
138139
revision: u32,
139140
minor_revision: Option<u32>,
140141

141-
build_code: u32,
142+
build_code: Option<u32>,
142143
}
143144

144145
/// Wolfram Language version number.
@@ -183,6 +184,7 @@ pub(crate) enum ErrorKind {
183184
},
184185
UnsupportedPlatform {
185186
operation: String,
187+
target_os: OperatingSystem,
186188
},
187189
Other(String),
188190
}
@@ -292,9 +294,11 @@ pub fn system_id_from_target(rust_target: &str) -> Result<&'static str, Error> {
292294
"x86_64-apple-darwin" => "MacOSX-x86-64",
293295
"x86_64-unknown-linux-gnu" => "Linux-x86-64",
294296
"x86_64-pc-windows-msvc" | "x86_64-pc-windows-gnu" => "Windows-x86-64",
297+
295298
// 64-bit ARM
296299
"aarch64-apple-darwin" => "MacOSX-ARM64",
297300
"aarch64-apple-ios" | "aarch64-apple-ios-sim" => "iOS-ARM64", // iOS
301+
"aarch64-unknown-linux-gnu" => "Linux-ARM64",
298302
"aarch64-linux-android" => "Android",
299303

300304
// 32-bit ARM (e.g. Raspberry Pi)
@@ -366,6 +370,24 @@ impl WolframAppType {
366370
// TODO?
367371
}
368372
}
373+
374+
// TODO(cleanup): Make this method unnecessary. This is a synthesized thing,
375+
// not necessarily meaningful. Remove WolframApp.app_name?
376+
#[allow(dead_code)]
377+
fn app_name(&self) -> &'static str {
378+
match self {
379+
WolframAppType::Mathematica => "Mathematica",
380+
WolframAppType::Engine => "Wolfram Engine",
381+
WolframAppType::Desktop => "Wolfram Desktop",
382+
WolframAppType::Player => "Wolfram Player",
383+
WolframAppType::PlayerPro => "Wolfram Player Pro",
384+
WolframAppType::FinancePlatform => "Wolfram Finance Platform",
385+
WolframAppType::ProgrammingLab => "Wolfram Programming Lab",
386+
WolframAppType::WolframAlphaNotebookEdition => {
387+
"Wolfram|Alpha Notebook Edition"
388+
},
389+
}
390+
}
369391
}
370392

371393
impl WolframVersion {
@@ -413,9 +435,60 @@ impl AppVersion {
413435
}
414436

415437
#[allow(missing_docs)]
416-
pub fn build_code(&self) -> u32 {
438+
pub fn build_code(&self) -> Option<u32> {
417439
self.build_code
418440
}
441+
442+
fn parse(version: &str) -> Result<Self, Error> {
443+
fn parse(s: &str) -> Result<u32, Error> {
444+
u32::from_str(s).map_err(|err| {
445+
Error::other(format!(
446+
"invalid application version number component: '{}': {}",
447+
s, err
448+
))
449+
})
450+
}
451+
452+
let components: Vec<&str> = version.split(".").collect();
453+
454+
let app_version = match components.as_slice() {
455+
// 5 components: major.minor.revision.minor_revision.build_code
456+
[major, minor, revision, minor_revision, build_code] => AppVersion {
457+
major: parse(major)?,
458+
minor: parse(minor)?,
459+
revision: parse(revision)?,
460+
461+
minor_revision: Some(parse(minor_revision)?),
462+
build_code: Some(parse(build_code)?),
463+
},
464+
// 4 components: major.minor.revision.build_code
465+
[major, minor, revision, build_code] => AppVersion {
466+
major: parse(major)?,
467+
minor: parse(minor)?,
468+
revision: parse(revision)?,
469+
470+
minor_revision: None,
471+
build_code: Some(parse(build_code)?),
472+
},
473+
// 3 components: [major.minor.revision]
474+
[major, minor, revision] => AppVersion {
475+
major: parse(major)?,
476+
minor: parse(minor)?,
477+
revision: parse(revision)?,
478+
479+
minor_revision: None,
480+
build_code: None,
481+
},
482+
_ => {
483+
return Err(Error::other(format!(
484+
"unexpected application version number format: {}",
485+
version
486+
)))
487+
},
488+
};
489+
490+
Ok(app_version)
491+
}
419492
}
420493

421494
impl Filter {
@@ -730,10 +803,11 @@ impl WolframApp {
730803
OperatingSystem::MacOS => self.app_directory.join("Contents"),
731804
OperatingSystem::Windows => self.app_directory.clone(),
732805
// FIXME: Fill this in for Linux
733-
OperatingSystem::Linux | OperatingSystem::Other => {
806+
OperatingSystem::Linux => self.app_directory().clone(),
807+
OperatingSystem::Other => {
734808
panic!(
735809
"{}",
736-
platform_unsupported_error("WolframApp::from_app_directory()",)
810+
platform_unsupported_error("WolframApp::installation_directory()",)
737811
)
738812
},
739813
}
@@ -758,7 +832,16 @@ impl WolframApp {
758832
OperatingSystem::Windows => {
759833
self.installation_directory().join("WolframKernel.exe")
760834
},
761-
OperatingSystem::Linux | OperatingSystem::Other => {
835+
OperatingSystem::Linux => {
836+
// NOTE: This empirically is valid for:
837+
// - Mathematica (tested: 13.1)
838+
// - Wolfram Engine (tested: 13.0, 13.3 prerelease)
839+
// TODO: Is this correct for Wolfram Desktop?
840+
self.installation_directory()
841+
.join("Executables")
842+
.join("WolframKernel")
843+
},
844+
OperatingSystem::Other => {
762845
return Err(platform_unsupported_error("kernel_executable_path()"));
763846
},
764847
};
@@ -781,7 +864,17 @@ impl WolframApp {
781864
let path = match OperatingSystem::target_os() {
782865
OperatingSystem::MacOS => PathBuf::from("MacOS").join("wolframscript"),
783866
OperatingSystem::Windows => PathBuf::from("wolframscript.exe"),
784-
OperatingSystem::Linux | OperatingSystem::Other => {
867+
OperatingSystem::Linux => {
868+
// NOTE: This empirically is valid for:
869+
// - Mathematica (tested: 13.1)
870+
// - Wolfram Engine (tested: 13.0, 13.3 prerelease)
871+
PathBuf::from("SystemFiles")
872+
.join("Kernel")
873+
.join("Binaries")
874+
.join(target_system_id())
875+
.join("wolframscript")
876+
},
877+
OperatingSystem::Other => {
785878
return Err(platform_unsupported_error(
786879
"wolframscript_executable_path()",
787880
));
@@ -827,7 +920,8 @@ impl WolframApp {
827920
// version.
828921
OperatingSystem::MacOS => "libWSTPi4.a",
829922
OperatingSystem::Windows => "wstp64i4s.lib",
830-
OperatingSystem::Linux | OperatingSystem::Other => {
923+
OperatingSystem::Linux => "libWSTP64i4.a",
924+
OperatingSystem::Other => {
831925
return Err(platform_unsupported_error("wstp_static_library_path()"));
832926
},
833927
};
@@ -981,6 +1075,7 @@ impl WolframApp {
9811075
fn platform_unsupported_error(name: &str) -> Error {
9821076
Error(ErrorKind::UnsupportedPlatform {
9831077
operation: name.to_owned(),
1078+
target_os: OperatingSystem::target_os(),
9841079
})
9851080
}
9861081

@@ -1158,11 +1253,11 @@ impl Display for ErrorKind {
11581253
f,
11591254
"app specified by environment variable '{env_var}' does not match filter: {filter_err}",
11601255
),
1161-
ErrorKind::UnsupportedPlatform { operation } => write!(
1256+
ErrorKind::UnsupportedPlatform { operation, target_os } => write!(
11621257
f,
1163-
"operation '{operation}' is not yet implemented for this platform",
1258+
"operation '{operation}' is not yet implemented for this platform: {target_os:?}",
11641259
),
1165-
ErrorKind::Other(_) => todo!(),
1260+
ErrorKind::Other(message) => write!(f, "{message}"),
11661261
}
11671262
}
11681263
}

0 commit comments

Comments
 (0)