Skip to content

Commit 9d10df1

Browse files
authored
[build] Improve support for building on GitHub Codespaces (#1241)
Context: https://docs.github.com/en/codespaces/setting-up-your-project-for-codespaces/adding-a-dev-container-configuration/introduction-to-dev-containers Context: https://docs.github.com/en/codespaces/setting-up-your-project-for-codespaces/adding-a-dev-container-configuration/introduction-to-dev-containers#using-the-default-dev-container-configuration Context: https://github.com/orgs/community/discussions/25429 There are two issues when trying to build `Java.Interop.sln` within a [GitHub Codespace][0]: 1. `dotnet build -t:Prepare Java.Interop.sln` fails. 2. `mono` was required on Linux environments. `dotnet build -t:Prepare` fails because `Java.Interop.sln` references `Xamarin.Android.Tools.AndroidSdk.csproj`, which does not exist immediately after checkout: /usr/share/dotnet/sdk/8.0.303/NuGet.targets(414,5): error MSB3202: The project file "…/java-interop/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj" was not found. This apparently happens *before* the `Prepare` target executes, so the `git submodule update --init --recursive` within that target doesn't execute. Fix this by adding a `.devcontainer/devcontainer.json` which contains a `postCreateCommand` section which runs `git submodule update --init --recursive`. Additionally, have `devcontainer.json` use the `mcr.microsoft.com/devcontainers/universal:2-linux` image, which supports C#, Java, and C++ development (among others), which is handy as this repo contains C#, Java, and C++ code to compile. Once the submodule problem is addressed, `dotnet build -t:Prepare` fails again because `mono` was required in Linux environments, and the default environment that GitHub Codespace creates when typing `.` within dotnet/java-interop has `dotnet` installed, but not `mono`. This results the error: …/java-interop/build-tools/Java.Interop.BootstrapTasks/Java.Interop.BootstrapTasks.targets(50,5): error MSB3073: The command "which mono" exited with code 1. Update `Java.Interop.BootstrapTasks.targets` so that `mono` is no longer required on Linux platforms. If `which mono` returns the empty string, then `bin/Build*/MonoInfo.props` is not created. Finally, the `mcr.microsoft.com/devcontainers/universal:2-linux` image defaults to using JDK 17. The `JniPeerMembersTests.DoesTheJmethodNeedToMatchDeclaringType()` test crashes under JDK 17 with: FATAL ERROR in native method: Wrong object class or methodID passed to JNI call Update `DoesTheJmethodNeedToMatchDeclaringType()` to skip the test when running on JDK 17 or later. With these changes, a new GitHub codespace against dotnet/java-interop can now build and run tests, a'la: % dotnet build -t:Prepare Java.Interop.sln % dotnet build Java.Interop.sln % dotnet test bin/TestDebug-net8.0/Java.Interop-Tests.dll … Passed! - Failed: 0, Passed: 629, Skipped: 3, Total: 632, Duration: 4 s - Java.Interop-Tests.dll (net8.0) ~~ Oddities and annoyances with GitHub Codespaces: ~~ 1. On macOS + Safari, "Hamburger menu" > File > Close Editor *says* ⌘W. However, *using* ⌘W results in closing the GitHub Codespaces *Safari tab*. *Oops*. Pressing ⌘Z will reopen the closed Safari tab. I don't know how to close a VSCode tab without using the mouse. Ctrl+W doesn't do it. 2. You make a change, and commit it by using `git commit` within the Terminal (Ctrl+Shift+Backtick ⌃ ⇧ \`). A new tab for `COMMIT_EDITMSG` opens. This is good. If you then *search for text* that appears within that editor window, and that text appears within the editor window -- e.g. search for "the" -- then the "return" key keeps finding the next search match, which makes it difficult to enter blank lines. You must either fully dismiss the search dialog by hitting Escape ⎋ a few times, or by clicking the close `x` button on the search panel, or use Ctrl+return ⌃return to enter a newline. This was "odd", especially as Escape ⎋ doesn't appear to reliably dismiss the search panel for me. [0]: https://github.com/features/codespaces
1 parent 7a058c0 commit 9d10df1

File tree

5 files changed

+81
-16
lines changed

5 files changed

+81
-16
lines changed

.devcontainer/devcontainer.json

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
2+
// README at: https://github.com/devcontainers/templates/tree/main/src/dotnet
3+
{
4+
"name": "Linux Universal Image",
5+
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
6+
"image": "mcr.microsoft.com/devcontainers/universal:2-linux"
7+
8+
// Features to add to the dev container. More info: https://containers.dev/features.
9+
, "features": {
10+
"ghcr.io/devcontainers/features/java:1": {
11+
"version": "17",
12+
"jdkDistro": "ms"
13+
}
14+
}
15+
16+
// Use 'forwardPorts' to make a list of ports inside the container available locally.
17+
// "forwardPorts": [5000, 5001],
18+
// "portsAttributes": {
19+
// "5001": {
20+
// "protocol": "https"
21+
// }
22+
// }
23+
24+
// Use 'postCreateCommand' to run commands after the container is created.
25+
// Have GitHub Codespaces checkout all submodules
26+
// https://github.com/orgs/community/discussions/25429
27+
, "postCreateCommand": "git submodule update --init --recursive"
28+
29+
// Configure tool-specific properties.
30+
// "customizations": {},
31+
32+
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
33+
// "remoteUser": "root"
34+
}

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ and [Architecture][architecture] pages.
2828

2929
`Java.Interop.sln` must first run some "preparatory" tasks before it can be built:
3030

31-
```
31+
```console
3232
dotnet build -t:Prepare
3333
```
3434

build-tools/Java.Interop.BootstrapTasks/Java.Interop.BootstrapTasks.targets

+9-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<Project>
22

33
<Target Name="_CreateMonoInfoProps"
4-
Condition=" $([MSBuild]::IsOSPlatform ('linux')) Or $([MSBuild]::IsOSPlatform ('osx')) "
4+
Condition=" ($([MSBuild]::IsOSPlatform ('linux')) Or $([MSBuild]::IsOSPlatform ('osx'))) And '$(_MonoPath)' != '' "
55
AfterTargets="AfterBuild"
66
DependsOnTargets="_GetLinuxMonoPaths;_GetMacOSMonoPaths"
77
Inputs="$(MSBuildThisFileFullPath);$(MSBuildThisFileDirectory)Java.Interop.BootstrapTasks.csproj"
@@ -45,13 +45,19 @@
4545
<Touch Files="$(_OutputPath)mono.mk" />
4646
</Target>
4747

48-
<Target Name="_GetLinuxMonoPaths"
48+
<Target Name="_GetLinuxMonoPath"
4949
Condition=" $([MSBuild]::IsOSPlatform ('linux')) ">
5050
<Exec
5151
Command="which mono"
52-
ConsoleToMsBuild="True">
52+
ConsoleToMsBuild="true"
53+
IgnoreExitCode="true">
5354
<Output TaskParameter="ConsoleOutput" PropertyName="_MonoPath" />
5455
</Exec>
56+
</Target>
57+
58+
<Target Name="_GetLinuxMonoPaths"
59+
DependsOnTargets="_GetLinuxMonoPath"
60+
Condition=" $([MSBuild]::IsOSPlatform ('linux')) And '$(_MonoPath)' != '' ">
5561
<Exec
5662
Command="pkg-config --variable=libdir mono-2"
5763
ConsoleToMsBuild="True">

tests/Java.Interop-Tests/Java.Interop/JniPeerMembersTests.cs

+5
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,11 @@ public unsafe void DoesTheJmethodNeedToMatchDeclaringType ()
142142
if (Environment.GetEnvironmentVariable ("CPUTYPE") is string cpu && cpu == "arm64") {
143143
Assert.Ignore ("nope!");
144144
}
145+
if (VM.JdkInfo.Version is Version v && v >= new Version (17, 0)) {
146+
// JDK-17 now crashes on this test as well:
147+
// FATAL ERROR in native method: Wrong object class or methodID passed to JNI call
148+
Assert.Ignore ("nope!");
149+
}
145150
var iface = new JniType ("net/dot/jni/test/AndroidInterface");
146151
var desugar = new JniType ("net/dot/jni/test/DesugarAndroidInterface$_CC");
147152
var m = desugar.GetStaticMethod ("getClassName", "()Ljava/lang/String;");

tests/TestJVM/TestJVM.cs

+32-12
Original file line numberDiff line numberDiff line change
@@ -24,20 +24,33 @@ public TestJVMOptions (Assembly? callingAssembly = null)
2424
public ICollection<string> JarFilePaths {get;} = new List<string> ();
2525
public Assembly CallingAssembly {get; set;}
2626
public Dictionary<string, Type>? TypeMappings {get; set;}
27+
28+
internal JdkInfo? JdkInfo {get; set;}
2729
}
2830

2931
public class TestJVM : JreRuntime {
3032

33+
#if !__ANDROID__
34+
public JdkInfo? JdkInfo { get; private set; }
35+
#endif // !__ANDROID__
36+
3137
public TestJVM (TestJVMOptions builder)
3238
: base (OverrideOptions (builder))
3339
{
40+
#if !__ANDROID__
41+
this.JdkInfo = builder.JdkInfo;
42+
#endif // !__ANDROID__
43+
3444
}
3545

3646
static TestJVMOptions OverrideOptions (TestJVMOptions builder)
3747
{
3848
var dir = GetOutputDirectoryName ();
3949

40-
builder.JvmLibraryPath = GetJvmLibraryPath ();
50+
var info = GetJdkInfo ();
51+
52+
builder.JvmLibraryPath = info.JdkJvmPath;
53+
builder.JdkInfo = info.JdkInfo;
4154
builder.JniAddNativeMethodRegistrationAttributePresent = true;
4255
builder.JniGlobalReferenceLogWriter = GetLogOutput ("JAVA_INTEROP_GREF_LOG", "g-", builder.CallingAssembly);
4356
builder.JniLocalReferenceLogWriter = GetLogOutput ("JAVA_INTEROP_LREF_LOG", "l-", builder.CallingAssembly);
@@ -67,35 +80,37 @@ static string GetOutputDirectoryName ()
6780
return new StreamWriter (path, append: false, encoding: new UTF8Encoding (encoderShouldEmitUTF8Identifier: false));
6881
}
6982

70-
public static string? GetJvmLibraryPath ()
83+
public static string? GetJvmLibraryPath () => GetJdkInfo ().JdkJvmPath;
84+
85+
static (JdkInfo? JdkInfo, string? JdkJvmPath) GetJdkInfo ()
7186
{
72-
var jdkDir = ReadJavaSdkDirectoryFromJdkInfoProps ();
73-
if (jdkDir != null) {
74-
return jdkDir;
87+
var info = ReadJavaSdkDirectoryFromJdkInfoProps ();
88+
if (info.JdkJvmPath != null) {
89+
return (JdkInfo: info.JavaSdkDirectory == null ? null : new JdkInfo (info.JavaSdkDirectory), JdkJvmPath: info.JdkJvmPath);
7590
}
7691
var jdk = JdkInfo.GetKnownSystemJdkInfos ()
7792
.FirstOrDefault ();
78-
return jdk?.JdkJvmPath;
93+
return (jdk, jdk?.JdkJvmPath);
7994
}
8095

81-
static string? ReadJavaSdkDirectoryFromJdkInfoProps ()
96+
static (string? JavaSdkDirectory, string? JdkJvmPath) ReadJavaSdkDirectoryFromJdkInfoProps ()
8297
{
8398
var location = typeof (TestJVM).Assembly.Location;
8499
var binDir = Path.GetDirectoryName (Path.GetDirectoryName (location)) ?? Environment.CurrentDirectory;
85100
var testDir = Path.GetFileName (Path.GetDirectoryName (location));
86101
if (testDir == null) {
87-
return null;
102+
return (null, null);
88103
}
89104
if (!testDir.StartsWith ("Test", StringComparison.OrdinalIgnoreCase)) {
90-
return null;
105+
return (null, null);
91106
}
92107
var buildName = testDir.Replace ("Test", "Build");
93108
if (buildName.Contains ('-')) {
94109
buildName = buildName.Substring (0, buildName.IndexOf ('-'));
95110
}
96111
var jdkPropFile = Path.Combine (binDir, buildName, "JdkInfo.props");
97112
if (!File.Exists (jdkPropFile)) {
98-
return null;
113+
return (null, null);
99114
}
100115

101116
var msbuild = XNamespace.Get ("http://schemas.microsoft.com/developer/msbuild/2003");
@@ -108,9 +123,14 @@ static string GetOutputDirectoryName ()
108123
.Elements (msbuild + "JdkJvmPath")
109124
.FirstOrDefault ();
110125
if (jdkJvmPath == null) {
111-
return null;
126+
return (null, null);
112127
}
113-
return jdkJvmPath.Value;
128+
var jdkPath = jdkProps.Elements ()
129+
.Elements (msbuild + "PropertyGroup")
130+
.Elements (msbuild + "JavaSdkDirectory")
131+
.FirstOrDefault ();
132+
133+
return (JavaSdkDirectory: jdkPath?.Value, JdkJvmPath: jdkJvmPath.Value);
114134
}
115135

116136
public TestJVM (string[]? jars = null, Dictionary<string, Type>? typeMappings = null)

0 commit comments

Comments
 (0)